mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-04 18:06:23 +02:00
Compare commits
118 Commits
Author | SHA1 | Date | |
---|---|---|---|
3378247bee | |||
7507412a1d | |||
6b9a193aa8 | |||
c2979760b5 | |||
1fcf1d4a49 | |||
4012606910 | |||
e975124630 | |||
6862aa6ee7 | |||
8a22558f25 | |||
b5b9f9656e | |||
2fffc55d34 | |||
7d39f1582e | |||
75ed053a35 | |||
cfedf5fff4 | |||
be73a36339 | |||
1ad8ba2604 | |||
6848a12095 | |||
4797298706 | |||
5eaf4cbefd | |||
7f1844e541 | |||
6c7ac7fc22 | |||
42f9e1034b | |||
e3cd0fdd13 | |||
40ff550460 | |||
7119340d44 | |||
e140bc3906 | |||
fdc08d365d | |||
8f1b88e39e | |||
6a40a0a466 | |||
89fc6b6ac9 | |||
afa67b838a | |||
f7b7d282bf | |||
09780ea9f3 | |||
a7dab950f3 | |||
ec0737e392 | |||
d664993d56 | |||
a9c6c57a67 | |||
08e7374eee | |||
42da1448fb | |||
9f9e0b98ad | |||
556646aaec | |||
174fb0b5f4 | |||
836706653b | |||
17f1a2b92a | |||
3b08b16c11 | |||
68eb2f26c9 | |||
72757887c9 | |||
eb5dbd43ae | |||
1f1dfac3f9 | |||
2479b14aba | |||
ac24703512 | |||
db0091ba6f | |||
2159158c30 | |||
76d790425f | |||
90968d4333 | |||
577a509875 | |||
a9728abfc8 | |||
14d1b8e2b6 | |||
285c73e95e | |||
483db7028c | |||
082ff46041 | |||
f32e8f22c8 | |||
766dde7c42 | |||
b68687044e | |||
c9e84e9dd3 | |||
0126ac46fc | |||
9b7ea836d0 | |||
537b420d35 | |||
16906c5951 | |||
45e9aaa462 | |||
564cc15c04 | |||
8fd18d56a5 | |||
a5692d4ecf | |||
2d83f79433 | |||
f3ece74406 | |||
8de1f60347 | |||
b4252f8fd1 | |||
fe2b50a9ef | |||
d8ae8c3821 | |||
c9a026fabb | |||
64eca1546e | |||
b19fe98ff4 | |||
b393ddf879 | |||
7bb7d85c1d | |||
6e976153e7 | |||
03e758cee4 | |||
0d36b8f826 | |||
f82fa08d72 | |||
d6787e6c56 | |||
b9d870645f | |||
ef89430f9b | |||
953a0d4e4a | |||
5ea2d68438 | |||
d65a03f6ac | |||
b588b2bf5c | |||
d455e2cd13 | |||
9306631d6e | |||
f735da504b | |||
487a713ca0 | |||
095ad328ee | |||
a38afa0cec | |||
9619698543 | |||
4b1a471b35 | |||
b6039b0bff | |||
d8fa43034f | |||
92f993e054 | |||
c172deb0f3 | |||
dee6aed010 | |||
76f021a6e3 | |||
2f244ea028 | |||
5f5ddc8f01 | |||
8b473745cb | |||
18575ee1ee | |||
e58b38fd13 | |||
b043c34632 | |||
b748bf3b0d | |||
be12d5e6fc | |||
7c4941f868 |
@ -31,13 +31,13 @@ environment:
|
||||
CHANNEL: beta
|
||||
# Nightly channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: nightly-2017-12-21
|
||||
CHANNEL: nightly
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: nightly-2017-12-21
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: nightly-2017-12-21
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: nightly-2017-12-21
|
||||
CHANNEL: nightly
|
||||
|
||||
# Install Rust and Cargo
|
||||
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
||||
|
@ -31,14 +31,14 @@ before_script:
|
||||
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then
|
||||
if [[ "$TRAVIS_RUST_VERSION" != "1.24.0" ]]; then
|
||||
cargo clean
|
||||
cargo test --features="alpn,tls" -- --nocapture
|
||||
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)
|
||||
USE_SKEPTIC=1 cargo tarpaulin --out Xml
|
||||
USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
echo "Uploaded code coverage"
|
||||
fi
|
||||
|
131
CHANGES.md
131
CHANGES.md
@ -1,5 +1,136 @@
|
||||
# Changes
|
||||
|
||||
## [0.6.15] - 2018-07-11
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix h2 compatibility #352
|
||||
|
||||
* Fix duplicate tail of StaticFiles with index_file. #344
|
||||
|
||||
|
||||
## [0.6.14] - 2018-06-21
|
||||
|
||||
### Added
|
||||
|
||||
* Allow to disable masking for websockets client
|
||||
|
||||
### Fixed
|
||||
|
||||
* SendRequest execution fails with the "internal error: entered unreachable code" #329
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
* Add `Router::with_async()` method for async handler registration.
|
||||
|
||||
* Added error response functions for 501,502,503,504
|
||||
|
||||
* Fix client request timeout handling
|
||||
|
||||
|
||||
## 0.6.2 (2018-05-09)
|
||||
|
||||
* WsWriter trait is optional.
|
||||
|
||||
|
||||
## 0.6.1 (2018-05-08)
|
||||
|
||||
* Fix http/2 payload streaming #215
|
||||
|
@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.6.1"
|
||||
version = "0.6.15"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
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"
|
||||
documentation = "https://docs.rs/actix-web/"
|
||||
categories = ["network-programming", "asynchronous",
|
||||
@ -46,8 +46,11 @@ flate2-c = ["flate2/miniz-sys"]
|
||||
# rust backend for flate2 crate
|
||||
flate2-rust = ["flate2/rust_backend"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["tls", "alpn", "session", "brotli", "flate2-c"]
|
||||
|
||||
[dependencies]
|
||||
actix = "^0.5.5"
|
||||
actix = "^0.5.8"
|
||||
|
||||
base64 = "0.9"
|
||||
bitflags = "1.0"
|
||||
|
@ -1,5 +1,7 @@
|
||||
## Migration from 0.5 to 0.6
|
||||
|
||||
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
|
||||
|
||||
* `ws::Message::Close` now includes optional close reason.
|
||||
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
|
||||
|
||||
@ -30,7 +32,7 @@
|
||||
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
|
||||
is generic over `S`
|
||||
|
||||
* `HttpRequest::query()` is deprecated. Use `Query` extractor.
|
||||
* Use `Query` extractor instead of HttpRequest::query()`.
|
||||
|
||||
```rust
|
||||
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
|
||||
|
15
README.md
15
README.md
@ -2,12 +2,12 @@
|
||||
|
||||
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||
|
||||
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/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
|
||||
* 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)
|
||||
* 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
|
||||
* Multipart streams
|
||||
* 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),
|
||||
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/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)
|
||||
|
||||
## 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 (Releases)](https://docs.rs/actix-web/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
@ -33,10 +34,10 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||
|
||||
```rust
|
||||
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 {
|
||||
format!("Hello {}! id:{}", info.0, info.1)
|
||||
fn index(info: Path<(u32, String)>) -> impl Responder {
|
||||
format!("Hello {}! id:{}", info.1, info.0)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
9
build.rs
9
build.rs
@ -1,8 +1,15 @@
|
||||
extern crate version_check;
|
||||
|
||||
fn main() {
|
||||
match version_check::is_min_version("1.26.0") {
|
||||
Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"),
|
||||
_ => (),
|
||||
};
|
||||
match version_check::is_nightly() {
|
||||
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
|
||||
Some(true) => {
|
||||
println!("cargo:rustc-cfg=actix_nightly");
|
||||
println!("cargo:rustc-cfg=actix_impl_trait");
|
||||
}
|
||||
Some(false) => (),
|
||||
None => (),
|
||||
};
|
||||
|
@ -22,6 +22,7 @@ pub struct HttpApplication<S = ()> {
|
||||
prefix_len: usize,
|
||||
router: Router,
|
||||
inner: Rc<UnsafeCell<Inner<S>>>,
|
||||
filters: Option<Vec<Box<Predicate<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 =
|
||||
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
||||
|
||||
req.set_prefix_len(prefix_len as u16);
|
||||
if path.is_empty() {
|
||||
req.match_info_mut().set("tail", "/");
|
||||
} else {
|
||||
req.match_info_mut().set("tail", path);
|
||||
}
|
||||
req.match_info_mut().set("tail", path);
|
||||
return HandlerType::Handler(idx);
|
||||
}
|
||||
}
|
||||
@ -147,11 +144,21 @@ impl<S: 'static> HttpHandler for HttpApplication<S> {
|
||||
|| path.split_at(self.prefix_len).1.starts_with('/'))
|
||||
};
|
||||
if m {
|
||||
let mut req = req.with_state(Rc::clone(&self.state), self.router.clone());
|
||||
let tp = self.get_handler(&mut req);
|
||||
let mut req2 =
|
||||
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);
|
||||
Ok(Box::new(Pipeline::new(
|
||||
req,
|
||||
req2,
|
||||
Rc::clone(&self.middlewares),
|
||||
inner,
|
||||
tp,
|
||||
@ -172,6 +179,7 @@ struct ApplicationParts<S> {
|
||||
external: HashMap<String, Resource>,
|
||||
encoding: ContentEncoding,
|
||||
middlewares: Vec<Box<Middleware<S>>>,
|
||||
filters: Vec<Box<Predicate<S>>>,
|
||||
}
|
||||
|
||||
/// Structure that follows the builder pattern for building application
|
||||
@ -194,6 +202,7 @@ impl App<()> {
|
||||
handlers: Vec::new(),
|
||||
external: HashMap::new(),
|
||||
encoding: ContentEncoding::Auto,
|
||||
filters: Vec::new(),
|
||||
middlewares: Vec::new(),
|
||||
}),
|
||||
}
|
||||
@ -233,6 +242,7 @@ where
|
||||
handlers: Vec::new(),
|
||||
external: HashMap::new(),
|
||||
middlewares: Vec::new(),
|
||||
filters: Vec::new(),
|
||||
encoding: ContentEncoding::Auto,
|
||||
}),
|
||||
}
|
||||
@ -271,8 +281,8 @@ where
|
||||
/// let app = App::new()
|
||||
/// .prefix("/app")
|
||||
/// .resource("/test", |r| {
|
||||
/// r.get().f(|_| HttpResponse::Ok());
|
||||
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// r.get().f(|_| HttpResponse::Ok());
|
||||
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// })
|
||||
/// .finish();
|
||||
/// }
|
||||
@ -289,6 +299,26 @@ where
|
||||
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.
|
||||
///
|
||||
/// This is a simplified version of the `App::resource()` method.
|
||||
@ -304,10 +334,12 @@ where
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .route("/test", http::Method::GET,
|
||||
/// |_: HttpRequest| HttpResponse::Ok())
|
||||
/// .route("/test", http::Method::POST,
|
||||
/// |_: HttpRequest| HttpResponse::MethodNotAllowed());
|
||||
/// .route("/test", http::Method::GET, |_: HttpRequest| {
|
||||
/// HttpResponse::Ok()
|
||||
/// })
|
||||
/// .route("/test", http::Method::POST, |_: HttpRequest| {
|
||||
/// HttpResponse::MethodNotAllowed()
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
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};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .scope("/{project_id}", |scope| {
|
||||
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||
/// });
|
||||
/// let app = App::new().scope("/{project_id}", |scope| {
|
||||
/// scope
|
||||
/// .resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
@ -369,14 +401,6 @@ where
|
||||
{
|
||||
{
|
||||
let mut scope = Box::new(f(Scope::new()));
|
||||
|
||||
let mut path = path.trim().trim_right_matches('/').to_owned();
|
||||
if !path.is_empty() && !path.starts_with('/') {
|
||||
path.insert(0, '/')
|
||||
}
|
||||
if !path.ends_with('/') {
|
||||
path.push('/');
|
||||
}
|
||||
let parts = self.parts.as_mut().expect("Use after finish");
|
||||
|
||||
let filters = scope.take_filters();
|
||||
@ -414,11 +438,10 @@ where
|
||||
/// use actix_web::{http, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .resource("/users/{userid}/{friend}", |r| {
|
||||
/// r.get().f(|_| HttpResponse::Ok());
|
||||
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// });
|
||||
/// let app = App::new().resource("/users/{userid}/{friend}", |r| {
|
||||
/// r.get().f(|_| HttpResponse::Ok());
|
||||
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
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};
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
||||
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
|
||||
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
|
||||
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
@ -526,13 +549,11 @@ where
|
||||
/// use actix_web::{http, App, HttpRequest, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .handler("/app", |req: HttpRequest| {
|
||||
/// match *req.method() {
|
||||
/// http::Method::GET => HttpResponse::Ok(),
|
||||
/// http::Method::POST => HttpResponse::MethodNotAllowed(),
|
||||
/// _ => HttpResponse::NotFound(),
|
||||
/// }});
|
||||
/// let app = App::new().handler("/app", |req: HttpRequest| match *req.method() {
|
||||
/// http::Method::GET => HttpResponse::Ok(),
|
||||
/// http::Method::POST => HttpResponse::MethodNotAllowed(),
|
||||
/// _ => HttpResponse::NotFound(),
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> App<S> {
|
||||
@ -573,15 +594,14 @@ where
|
||||
///
|
||||
/// ```rust
|
||||
/// # 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
|
||||
/// fn config(app: App) -> App {
|
||||
/// app
|
||||
/// .resource("/test", |r| {
|
||||
/// r.get().f(|_| HttpResponse::Ok());
|
||||
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// })
|
||||
/// app.resource("/test", |r| {
|
||||
/// r.get().f(|_| HttpResponse::Ok());
|
||||
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
@ -622,6 +642,11 @@ where
|
||||
handlers: parts.handlers,
|
||||
resources,
|
||||
}));
|
||||
let filters = if parts.filters.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(parts.filters)
|
||||
};
|
||||
|
||||
HttpApplication {
|
||||
state: Rc::new(parts.state),
|
||||
@ -630,6 +655,7 @@ where
|
||||
prefix,
|
||||
prefix_len,
|
||||
inner,
|
||||
filters,
|
||||
}
|
||||
}
|
||||
|
||||
@ -648,19 +674,22 @@ where
|
||||
/// struct State2;
|
||||
///
|
||||
/// fn main() {
|
||||
/// # thread::spawn(|| {
|
||||
/// server::new(|| { vec![
|
||||
/// App::with_state(State1)
|
||||
/// .prefix("/app1")
|
||||
/// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
|
||||
/// .boxed(),
|
||||
/// App::with_state(State2)
|
||||
/// .prefix("/app2")
|
||||
/// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
|
||||
/// .boxed() ]})
|
||||
/// .bind("127.0.0.1:8080").unwrap()
|
||||
/// # thread::spawn(|| {
|
||||
/// server::new(|| {
|
||||
/// vec![
|
||||
/// App::with_state(State1)
|
||||
/// .prefix("/app1")
|
||||
/// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
|
||||
/// .boxed(),
|
||||
/// App::with_state(State2)
|
||||
/// .prefix("/app2")
|
||||
/// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
|
||||
/// .boxed(),
|
||||
/// ]
|
||||
/// }).bind("127.0.0.1:8080")
|
||||
/// .unwrap()
|
||||
/// .run()
|
||||
/// # });
|
||||
/// # });
|
||||
/// }
|
||||
/// ```
|
||||
pub fn boxed(mut self) -> Box<HttpHandler> {
|
||||
@ -711,7 +740,8 @@ mod tests {
|
||||
use http::StatusCode;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use test::TestRequest;
|
||||
use pred;
|
||||
use test::{TestRequest, TestServer};
|
||||
|
||||
#[test]
|
||||
fn test_default_resource() {
|
||||
@ -780,9 +810,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_handler() {
|
||||
let mut app = App::new()
|
||||
.handler("/test", |_| HttpResponse::Ok())
|
||||
.finish();
|
||||
let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish();
|
||||
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
let resp = app.run(req);
|
||||
@ -807,9 +835,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_handler2() {
|
||||
let mut app = App::new()
|
||||
.handler("test", |_| HttpResponse::Ok())
|
||||
.finish();
|
||||
let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish();
|
||||
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
let resp = app.run(req);
|
||||
@ -863,29 +889,21 @@ mod tests {
|
||||
#[test]
|
||||
fn test_route() {
|
||||
let mut app = App::new()
|
||||
.route("/test", Method::GET, |_: HttpRequest| {
|
||||
HttpResponse::Ok()
|
||||
})
|
||||
.route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok())
|
||||
.route("/test", Method::POST, |_: HttpRequest| {
|
||||
HttpResponse::Created()
|
||||
})
|
||||
.finish();
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::GET)
|
||||
.finish();
|
||||
let req = TestRequest::with_uri("/test").method(Method::GET).finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::POST)
|
||||
.finish();
|
||||
let req = TestRequest::with_uri("/test").method(Method::POST).finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||
|
||||
let req = TestRequest::with_uri("/test")
|
||||
.method(Method::HEAD)
|
||||
.finish();
|
||||
let req = TestRequest::with_uri("/test").method(Method::HEAD).finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
@ -922,4 +940,21 @@ mod tests {
|
||||
let resp = app.run(req);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
18
src/body.rs
18
src/body.rs
@ -62,10 +62,28 @@ impl Body {
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this binary empy.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match *self {
|
||||
Body::Empty => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create body from slice (copy)
|
||||
pub fn from_slice(s: &[u8]) -> Body {
|
||||
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 {
|
||||
|
@ -8,8 +8,10 @@ use std::{fmt, io, mem, time};
|
||||
use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError};
|
||||
use actix::fut::WrapFuture;
|
||||
use actix::registry::ArbiterService;
|
||||
use actix::{fut, Actor, ActorFuture, ActorResponse, Arbiter, AsyncContext, Context,
|
||||
ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn};
|
||||
use actix::{
|
||||
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::unsync::oneshot;
|
||||
@ -179,6 +181,7 @@ pub struct ClientConnector {
|
||||
pool: Rc<Pool>,
|
||||
pool_modified: Rc<Cell<bool>>,
|
||||
|
||||
resolver: Addr<Unsync, Connector>,
|
||||
conn_lifetime: Duration,
|
||||
conn_keep_alive: Duration,
|
||||
limit: usize,
|
||||
@ -223,6 +226,7 @@ impl Default for ClientConnector {
|
||||
pool: Rc::new(Pool::new(Rc::clone(&_modified))),
|
||||
pool_modified: _modified,
|
||||
connector: builder.build().unwrap(),
|
||||
resolver: Connector::from_registry(),
|
||||
conn_lifetime: Duration::from_secs(75),
|
||||
conn_keep_alive: Duration::from_secs(15),
|
||||
limit: 100,
|
||||
@ -243,6 +247,7 @@ impl Default for ClientConnector {
|
||||
subscriber: None,
|
||||
pool: Rc::new(Pool::new(Rc::clone(&_modified))),
|
||||
pool_modified: _modified,
|
||||
resolver: Connector::from_registry(),
|
||||
conn_lifetime: Duration::from_secs(75),
|
||||
conn_keep_alive: Duration::from_secs(15),
|
||||
limit: 100,
|
||||
@ -275,9 +280,9 @@ impl ClientConnector {
|
||||
/// # use std::io::Write;
|
||||
/// extern crate openssl;
|
||||
/// 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() {
|
||||
/// let sys = System::new("test");
|
||||
@ -310,6 +315,7 @@ impl ClientConnector {
|
||||
subscriber: None,
|
||||
pool: Rc::new(Pool::new(Rc::clone(&modified))),
|
||||
pool_modified: modified,
|
||||
resolver: Connector::from_registry(),
|
||||
conn_lifetime: Duration::from_secs(75),
|
||||
conn_keep_alive: Duration::from_secs(15),
|
||||
limit: 100,
|
||||
@ -369,6 +375,12 @@ impl ClientConnector {
|
||||
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 {
|
||||
// check limits
|
||||
if self.limit > 0 {
|
||||
@ -429,8 +441,7 @@ impl ClientConnector {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.acquired_per_host
|
||||
.insert(key.clone(), per_host + 1);
|
||||
self.acquired_per_host.insert(key.clone(), per_host + 1);
|
||||
}
|
||||
|
||||
fn release_key(&mut self, key: &Key) {
|
||||
@ -441,8 +452,7 @@ impl ClientConnector {
|
||||
return;
|
||||
};
|
||||
if per_host > 1 {
|
||||
self.acquired_per_host
|
||||
.insert(key.clone(), per_host - 1);
|
||||
self.acquired_per_host.insert(key.clone(), per_host - 1);
|
||||
} else {
|
||||
self.acquired_per_host.remove(key);
|
||||
}
|
||||
@ -518,9 +528,7 @@ impl ClientConnector {
|
||||
fn collect_periodic(&mut self, ctx: &mut Context<Self>) {
|
||||
self.collect(true);
|
||||
// re-schedule next collect period
|
||||
ctx.run_later(Duration::from_secs(1), |act, ctx| {
|
||||
act.collect_periodic(ctx)
|
||||
});
|
||||
ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx));
|
||||
|
||||
// send stats
|
||||
let stats = mem::replace(&mut self.stats, ClientConnectorStats::default());
|
||||
@ -707,7 +715,7 @@ impl Handler<Connect> for ClientConnector {
|
||||
|
||||
{
|
||||
ActorResponse::async(
|
||||
Connector::from_registry()
|
||||
self.resolver
|
||||
.send(
|
||||
ResolveConnect::host_and_port(&conn.0.host, port)
|
||||
.timeout(conn_timeout),
|
||||
@ -857,7 +865,7 @@ impl fut::ActorFuture for Maintenance {
|
||||
let conn = AcquiredConn(key.clone(), Some(Rc::clone(&act.pool)));
|
||||
|
||||
fut::WrapFuture::<ClientConnector>::actfuture(
|
||||
Connector::from_registry().send(
|
||||
act.resolver.send(
|
||||
ResolveConnect::host_and_port(&conn.0.host, conn.0.port)
|
||||
.timeout(waiter.conn_timeout),
|
||||
),
|
||||
@ -1107,10 +1115,7 @@ impl Pool {
|
||||
if self.to_close.borrow().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(mem::replace(
|
||||
&mut *self.to_close.borrow_mut(),
|
||||
Vec::new(),
|
||||
))
|
||||
Some(mem::replace(&mut *self.to_close.borrow_mut(), Vec::new()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1118,10 +1123,7 @@ impl Pool {
|
||||
if self.to_release.borrow().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(mem::replace(
|
||||
&mut *self.to_release.borrow_mut(),
|
||||
Vec::new(),
|
||||
))
|
||||
Some(mem::replace(&mut *self.to_release.borrow_mut(), Vec::new()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,10 @@ mod request;
|
||||
mod response;
|
||||
mod writer;
|
||||
|
||||
pub use self::connector::{ClientConnector, ClientConnectorError, ClientConnectorStats,
|
||||
Connect, Connection, Pause, Resume};
|
||||
pub use self::connector::{
|
||||
ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection,
|
||||
Pause, Resume,
|
||||
};
|
||||
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
|
||||
pub use self::pipeline::{SendRequest, SendRequestError};
|
||||
pub use self::request::{ClientRequest, ClientRequestBuilder};
|
||||
@ -49,6 +51,7 @@ use httpresponse::HttpResponse;
|
||||
impl ResponseError for SendRequestError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match *self {
|
||||
SendRequestError::Timeout => HttpResponse::GatewayTimeout(),
|
||||
SendRequestError::Connector(_) => HttpResponse::BadGateway(),
|
||||
_ => HttpResponse::InternalServerError(),
|
||||
}.into()
|
||||
|
@ -194,6 +194,7 @@ impl Future for SendRequest {
|
||||
self.state = State::Send(pl);
|
||||
}
|
||||
State::Send(mut pl) => {
|
||||
pl.poll_timeout()?;
|
||||
pl.poll_write().map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str())
|
||||
})?;
|
||||
@ -269,7 +270,8 @@ impl Pipeline {
|
||||
#[inline]
|
||||
fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
|
||||
if let Some(ref mut conn) = self.conn {
|
||||
match self.parser
|
||||
match self
|
||||
.parser
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.parse(conn, &mut self.parser_buf)
|
||||
@ -310,12 +312,13 @@ impl Pipeline {
|
||||
let mut need_run = false;
|
||||
|
||||
// need write?
|
||||
match self.poll_write()
|
||||
match self
|
||||
.poll_write()
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?
|
||||
{
|
||||
Async::NotReady => need_run = true,
|
||||
Async::Ready(_) => {
|
||||
let _ = self.poll_timeout().map_err(|e| {
|
||||
self.poll_timeout().map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::Other, format!("{}", e))
|
||||
})?;
|
||||
}
|
||||
@ -324,7 +327,8 @@ impl Pipeline {
|
||||
// need read?
|
||||
if self.parser.is_some() {
|
||||
loop {
|
||||
match self.parser
|
||||
match self
|
||||
.parser
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.parse_payload(conn, &mut self.parser_buf)?
|
||||
@ -371,16 +375,15 @@ impl Pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_timeout(&mut self) -> Poll<(), SendRequestError> {
|
||||
fn poll_timeout(&mut self) -> Result<(), SendRequestError> {
|
||||
if self.timeout.is_some() {
|
||||
match self.timeout.as_mut().unwrap().poll() {
|
||||
Ok(Async::Ready(())) => Err(SendRequestError::Timeout),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(_) => unreachable!(),
|
||||
Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
|
||||
Ok(Async::NotReady) => (),
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -469,7 +472,8 @@ impl Pipeline {
|
||||
}
|
||||
|
||||
// flush io but only if we need to
|
||||
match self.writer
|
||||
match self
|
||||
.writer
|
||||
.poll_completed(self.conn.as_mut().unwrap(), false)
|
||||
{
|
||||
Ok(Async::Ready(_)) => {
|
||||
|
@ -499,10 +499,7 @@ impl ClientRequestBuilder {
|
||||
jar.add(cookie.into_owned());
|
||||
self.cookies = Some(jar)
|
||||
} else {
|
||||
self.cookies
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.add(cookie.into_owned());
|
||||
self.cookies.as_mut().unwrap().add(cookie.into_owned());
|
||||
}
|
||||
self
|
||||
}
|
||||
@ -610,9 +607,7 @@ impl ClientRequestBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
let mut request = self.request
|
||||
.take()
|
||||
.expect("cannot reuse request builder");
|
||||
let mut request = self.request.take().expect("cannot reuse request builder");
|
||||
|
||||
// set cookies
|
||||
if let Some(ref mut jar) = self.cookies {
|
||||
@ -657,9 +652,7 @@ impl ClientRequestBuilder {
|
||||
S: Stream<Item = Bytes, Error = E> + 'static,
|
||||
E: Into<Error>,
|
||||
{
|
||||
self.body(Body::Streaming(Box::new(
|
||||
stream.map_err(|e| e.into()),
|
||||
)))
|
||||
self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
|
||||
}
|
||||
|
||||
/// Set an empty body and generate `ClientRequest`
|
||||
|
@ -103,12 +103,7 @@ impl ClientResponse {
|
||||
|
||||
impl fmt::Debug for ClientResponse {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let res = writeln!(
|
||||
f,
|
||||
"\nClientResponse {:?} {}",
|
||||
self.version(),
|
||||
self.status()
|
||||
);
|
||||
let res = writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status());
|
||||
let _ = writeln!(f, " headers:");
|
||||
for (key, val) in self.headers().iter() {
|
||||
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
||||
@ -138,14 +133,12 @@ mod tests {
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let resp = ClientResponse::new(ClientMessage::default());
|
||||
resp.as_mut().headers.insert(
|
||||
header::COOKIE,
|
||||
HeaderValue::from_static("cookie1=value1"),
|
||||
);
|
||||
resp.as_mut().headers.insert(
|
||||
header::COOKIE,
|
||||
HeaderValue::from_static("cookie2=value2"),
|
||||
);
|
||||
resp.as_mut()
|
||||
.headers
|
||||
.insert(header::COOKIE, HeaderValue::from_static("cookie1=value1"));
|
||||
resp.as_mut()
|
||||
.headers
|
||||
.insert(header::COOKIE, HeaderValue::from_static("cookie2=value2"));
|
||||
|
||||
let dbg = format!("{:?}", resp);
|
||||
assert!(dbg.contains("ClientResponse"));
|
||||
|
@ -12,8 +12,9 @@ use flate2::write::{DeflateEncoder, GzEncoder};
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::Compression;
|
||||
use futures::{Async, Poll};
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE,
|
||||
TRANSFER_ENCODING};
|
||||
use http::header::{
|
||||
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
|
||||
};
|
||||
use http::{HttpTryFrom, Version};
|
||||
use time::{self, Duration};
|
||||
use tokio_io::AsyncWrite;
|
||||
@ -253,10 +254,8 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
||||
}
|
||||
let mut b = BytesMut::new();
|
||||
let _ = write!(b, "{}", bytes.len());
|
||||
req.headers_mut().insert(
|
||||
CONTENT_LENGTH,
|
||||
HeaderValue::try_from(b.freeze()).unwrap(),
|
||||
);
|
||||
req.headers_mut()
|
||||
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
Body::Streaming(_) | Body::Actor(_) => {
|
||||
|
@ -6,8 +6,10 @@ use std::marker::PhantomData;
|
||||
|
||||
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
|
||||
use actix::fut::ActorFuture;
|
||||
use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message,
|
||||
SpawnHandle, Syn, Unsync};
|
||||
use actix::{
|
||||
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
|
||||
Syn, Unsync,
|
||||
};
|
||||
|
||||
use body::{Binary, Body};
|
||||
use error::{Error, ErrorInternalServerError};
|
||||
@ -80,7 +82,8 @@ where
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
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
|
||||
}
|
||||
#[inline]
|
||||
|
16
src/de.rs
16
src/de.rs
@ -202,7 +202,8 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
|
||||
where
|
||||
K: de::DeserializeSeed<'de>,
|
||||
{
|
||||
self.current = self.params
|
||||
self.current = self
|
||||
.params
|
||||
.next()
|
||||
.map(|&(ref k, ref v)| (k.as_ref(), v.as_ref()));
|
||||
match self.current {
|
||||
@ -336,9 +337,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_enum(ValueEnum {
|
||||
value: self.value,
|
||||
})
|
||||
visitor.visit_enum(ValueEnum { value: self.value })
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(
|
||||
@ -372,9 +371,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
Err(de::value::Error::custom(
|
||||
"unsupported type: tuple struct",
|
||||
))
|
||||
Err(de::value::Error::custom("unsupported type: tuple struct"))
|
||||
}
|
||||
|
||||
unsupported_type!(deserialize_any, "any");
|
||||
@ -415,10 +412,7 @@ impl<'de> de::EnumAccess<'de> for ValueEnum<'de> {
|
||||
where
|
||||
V: de::DeserializeSeed<'de>,
|
||||
{
|
||||
Ok((
|
||||
seed.deserialize(Key { key: self.value })?,
|
||||
UnitVariant,
|
||||
))
|
||||
Ok((seed.deserialize(Key { key: self.value })?, UnitVariant))
|
||||
}
|
||||
}
|
||||
|
||||
|
253
src/error.rs
253
src/error.rs
@ -1,9 +1,9 @@
|
||||
//! Error and Result module
|
||||
use std::cell::RefCell;
|
||||
use std::io::Error as IoError;
|
||||
use std::str::Utf8Error;
|
||||
use std::string::FromUtf8Error;
|
||||
use std::{fmt, io, result};
|
||||
use std::sync::Mutex;
|
||||
use std::{fmt, io, mem, result};
|
||||
|
||||
use actix::MailboxError;
|
||||
use cookie;
|
||||
@ -21,9 +21,10 @@ pub use url::ParseError as UrlParseError;
|
||||
// re-exports
|
||||
pub use cookie::ParseError as CookieParseError;
|
||||
|
||||
use body::Body;
|
||||
use handler::Responder;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use httpresponse::{HttpResponse, InnerHttpResponse};
|
||||
|
||||
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
|
||||
/// for actix web operations
|
||||
@ -33,21 +34,44 @@ use httpresponse::HttpResponse;
|
||||
/// `Result`.
|
||||
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 {
|
||||
cause: Box<ResponseError>,
|
||||
backtrace: Option<Backtrace>,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Returns a reference to the underlying cause of this Error.
|
||||
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665
|
||||
/// Deprecated way to reference the underlying response error.
|
||||
#[deprecated(
|
||||
since = "0.6.0", note = "please use `Error::as_response_error()` instead"
|
||||
)]
|
||||
pub fn cause(&self) -> &ResponseError {
|
||||
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
|
||||
/// carries one.
|
||||
///
|
||||
/// This uses the same `Backtrace` type that `failure` uses.
|
||||
pub fn backtrace(&self) -> &Backtrace {
|
||||
if let Some(bt) = self.cause.backtrace() {
|
||||
bt
|
||||
@ -55,10 +79,65 @@ impl Error {
|
||||
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`
|
||||
pub trait ResponseError: Fail {
|
||||
pub trait ResponseError: Fail + InternalResponseErrorAsFail {
|
||||
/// Create response for error
|
||||
///
|
||||
/// 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 {
|
||||
fn from(err: Error) -> Self {
|
||||
HttpResponse::from_error(err)
|
||||
@ -111,11 +190,9 @@ impl<T: ResponseError> From<T> for Error {
|
||||
}
|
||||
|
||||
/// Compatibility for `failure::Error`
|
||||
impl<T> ResponseError for failure::Compat<T>
|
||||
where
|
||||
T: fmt::Display + fmt::Debug + Sync + Send + 'static,
|
||||
{
|
||||
}
|
||||
impl<T> ResponseError for failure::Compat<T> where
|
||||
T: fmt::Display + fmt::Debug + Sync + Send + 'static
|
||||
{}
|
||||
|
||||
impl From<failure::Error> for Error {
|
||||
fn from(err: failure::Error) -> Error {
|
||||
@ -317,10 +394,7 @@ pub enum HttpRangeError {
|
||||
/// Return `BadRequest` for `HttpRangeError`
|
||||
impl ResponseError for HttpRangeError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::with_body(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Invalid Range header provided",
|
||||
)
|
||||
HttpResponse::with_body(StatusCode::BAD_REQUEST, "Invalid Range header provided")
|
||||
}
|
||||
}
|
||||
|
||||
@ -551,8 +625,8 @@ impl From<UrlParseError> for UrlGenerationError {
|
||||
/// use actix_web::fs::NamedFile;
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<fs::NamedFile> {
|
||||
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
|
||||
/// Ok(f)
|
||||
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
|
||||
/// Ok(f)
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
@ -562,12 +636,9 @@ pub struct InternalError<T> {
|
||||
backtrace: Backtrace,
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for InternalError<T> {}
|
||||
unsafe impl<T> Send for InternalError<T> {}
|
||||
|
||||
enum InternalErrorType {
|
||||
Status(StatusCode),
|
||||
Response(RefCell<Option<HttpResponse>>),
|
||||
Response(Mutex<Option<Box<InnerHttpResponse>>>),
|
||||
}
|
||||
|
||||
impl<T> InternalError<T> {
|
||||
@ -582,9 +653,21 @@ impl<T> InternalError<T> {
|
||||
|
||||
/// Create `InternalError` with predefined `HttpResponse`.
|
||||
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 {
|
||||
cause,
|
||||
status: InternalErrorType::Response(RefCell::new(Some(response))),
|
||||
status: InternalErrorType::Response(Mutex::new(Some(resp))),
|
||||
backtrace: Backtrace::new(),
|
||||
}
|
||||
}
|
||||
@ -625,8 +708,8 @@ where
|
||||
match self.status {
|
||||
InternalErrorType::Status(st) => HttpResponse::new(st),
|
||||
InternalErrorType::Response(ref resp) => {
|
||||
if let Some(resp) = resp.borrow_mut().take() {
|
||||
resp
|
||||
if let Some(resp) = resp.lock().unwrap().take() {
|
||||
HttpResponse::from_inner(resp)
|
||||
} else {
|
||||
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
@ -757,6 +840,46 @@ where
|
||||
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *NOT IMPLEMENTED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorNotImplemented<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *BAD GATEWAY* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorBadGateway<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::BAD_GATEWAY).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *SERVICE UNAVAILABLE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorServiceUnavailable<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *GATEWAY TIMEOUT* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorGatewayTimeout<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -796,7 +919,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cause() {
|
||||
fn test_as_fail() {
|
||||
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
||||
let desc = orig.description().to_owned();
|
||||
let e = ParseError::Io(orig);
|
||||
@ -814,7 +937,7 @@ mod tests {
|
||||
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
||||
let desc = orig.description().to_owned();
|
||||
let e = Error::from(orig);
|
||||
assert_eq!(format!("{}", e.cause()), desc);
|
||||
assert_eq!(format!("{}", e.as_fail()), desc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -912,4 +1035,78 @@ mod tests {
|
||||
let resp: HttpResponse = err.error_response();
|
||||
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]
|
||||
fn test_error_helpers() {
|
||||
let r: HttpResponse = ErrorBadRequest("err").into();
|
||||
assert_eq!(r.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let r: HttpResponse = ErrorUnauthorized("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
|
||||
|
||||
let r: HttpResponse = ErrorForbidden("err").into();
|
||||
assert_eq!(r.status(), StatusCode::FORBIDDEN);
|
||||
|
||||
let r: HttpResponse = ErrorNotFound("err").into();
|
||||
assert_eq!(r.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
|
||||
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
|
||||
let r: HttpResponse = ErrorRequestTimeout("err").into();
|
||||
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
|
||||
|
||||
let r: HttpResponse = ErrorConflict("err").into();
|
||||
assert_eq!(r.status(), StatusCode::CONFLICT);
|
||||
|
||||
let r: HttpResponse = ErrorGone("err").into();
|
||||
assert_eq!(r.status(), StatusCode::GONE);
|
||||
|
||||
let r: HttpResponse = ErrorPreconditionFailed("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
|
||||
|
||||
let r: HttpResponse = ErrorExpectationFailed("err").into();
|
||||
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
|
||||
|
||||
let r: HttpResponse = ErrorInternalServerError("err").into();
|
||||
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
|
||||
let r: HttpResponse = ErrorNotImplemented("err").into();
|
||||
assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED);
|
||||
|
||||
let r: HttpResponse = ErrorBadGateway("err").into();
|
||||
assert_eq!(r.status(), StatusCode::BAD_GATEWAY);
|
||||
|
||||
let r: HttpResponse = ErrorServiceUnavailable("err").into();
|
||||
assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE);
|
||||
|
||||
let r: HttpResponse = ErrorGatewayTimeout("err").into();
|
||||
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use serde::de::{self, DeserializeOwned};
|
||||
use serde_urlencoded;
|
||||
|
||||
use de::PathDeserializer;
|
||||
use error::{Error, ErrorNotFound, ErrorBadRequest};
|
||||
use error::{Error, ErrorBadRequest, ErrorNotFound};
|
||||
use handler::{AsyncResult, FromRequest};
|
||||
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
|
||||
use httprequest::HttpRequest;
|
||||
@ -330,9 +330,7 @@ impl<S: 'static> FromRequest<S> for Bytes {
|
||||
cfg.check_mimetype(req)?;
|
||||
|
||||
Ok(Box::new(
|
||||
MessageBody::new(req.clone())
|
||||
.limit(cfg.limit)
|
||||
.from_err(),
|
||||
MessageBody::new(req.clone()).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!(TupleFromRequest3, (0, A), (1, B), (2, C));
|
||||
tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D));
|
||||
tuple_from_req!(
|
||||
TupleFromRequest5,
|
||||
(0, A),
|
||||
(1, B),
|
||||
(2, C),
|
||||
(3, D),
|
||||
(4, E)
|
||||
);
|
||||
tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E));
|
||||
tuple_from_req!(
|
||||
TupleFromRequest6,
|
||||
(0, A),
|
||||
@ -587,11 +578,7 @@ mod tests {
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"hello=world"));
|
||||
|
||||
match Bytes::from_request(&req, &cfg)
|
||||
.unwrap()
|
||||
.poll()
|
||||
.unwrap()
|
||||
{
|
||||
match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s, Bytes::from_static(b"hello=world"));
|
||||
}
|
||||
@ -606,11 +593,7 @@ mod tests {
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"hello=world"));
|
||||
|
||||
match String::from_request(&req, &cfg)
|
||||
.unwrap()
|
||||
.poll()
|
||||
.unwrap()
|
||||
{
|
||||
match String::from_request(&req, &cfg).unwrap().poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s, "hello=world");
|
||||
}
|
||||
@ -680,10 +663,7 @@ mod tests {
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((
|
||||
Resource::new("index", "/{key}/{value}/"),
|
||||
Some(resource),
|
||||
));
|
||||
routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
@ -735,10 +715,7 @@ mod tests {
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((
|
||||
Resource::new("index", "/{key}/{value}/"),
|
||||
Some(resource),
|
||||
));
|
||||
routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
|
344
src/fs.rs
344
src/fs.rs
@ -20,7 +20,7 @@ use mime_guess::{get_mime_type, guess_mime_type};
|
||||
use error::Error;
|
||||
use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler};
|
||||
use header;
|
||||
use http::{Method, StatusCode};
|
||||
use http::{ContentEncoding, HttpRange, Method, StatusCode};
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
@ -38,6 +38,7 @@ pub struct NamedFile {
|
||||
md: Metadata,
|
||||
modified: Option<SystemTime>,
|
||||
cpu_pool: Option<CpuPool>,
|
||||
encoding: Option<ContentEncoding>,
|
||||
only_get: bool,
|
||||
status_code: StatusCode,
|
||||
}
|
||||
@ -58,12 +59,14 @@ impl NamedFile {
|
||||
let path = path.as_ref().to_path_buf();
|
||||
let modified = md.modified().ok();
|
||||
let cpu_pool = None;
|
||||
let encoding = None;
|
||||
Ok(NamedFile {
|
||||
path,
|
||||
file,
|
||||
md,
|
||||
modified,
|
||||
cpu_pool,
|
||||
encoding,
|
||||
only_get: false,
|
||||
status_code: StatusCode::OK,
|
||||
})
|
||||
@ -114,6 +117,13 @@ impl NamedFile {
|
||||
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> {
|
||||
// This etag format is similar to Apache's.
|
||||
self.modified.as_ref().map(|mtime| {
|
||||
@ -203,31 +213,32 @@ impl Responder for NamedFile {
|
||||
if self.status_code != StatusCode::OK {
|
||||
let mut resp = HttpResponse::build(self.status_code);
|
||||
resp.if_some(self.path().extension(), |ext, resp| {
|
||||
resp.set(header::ContentType(get_mime_type(
|
||||
&ext.to_string_lossy(),
|
||||
)));
|
||||
resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
|
||||
}).if_some(self.path().file_name(), |file_name, resp| {
|
||||
let mime_type = guess_mime_type(self.path());
|
||||
let inline_or_attachment = match mime_type.type_() {
|
||||
mime::IMAGE | mime::TEXT => "inline",
|
||||
_ => "attachment",
|
||||
};
|
||||
resp.header(
|
||||
"Content-Disposition",
|
||||
format!(
|
||||
"{inline_or_attachment}; filename={filename}",
|
||||
inline_or_attachment = inline_or_attachment,
|
||||
filename = file_name.to_string_lossy()
|
||||
),
|
||||
);
|
||||
});
|
||||
let mime_type = guess_mime_type(self.path());
|
||||
let inline_or_attachment = match mime_type.type_() {
|
||||
mime::IMAGE | mime::TEXT | mime::VIDEO => "inline",
|
||||
_ => "attachment",
|
||||
};
|
||||
resp.header(
|
||||
"Content-Disposition",
|
||||
format!(
|
||||
"{inline_or_attachment}; filename={filename}",
|
||||
inline_or_attachment = inline_or_attachment,
|
||||
filename = file_name.to_string_lossy()
|
||||
),
|
||||
);
|
||||
});
|
||||
if let Some(current_encoding) = self.encoding {
|
||||
resp.content_encoding(current_encoding);
|
||||
}
|
||||
let reader = ChunkedReadFile {
|
||||
size: self.md.len(),
|
||||
offset: 0,
|
||||
cpu_pool: self.cpu_pool
|
||||
.unwrap_or_else(|| req.cpu_pool().clone()),
|
||||
cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()),
|
||||
file: Some(self.file),
|
||||
fut: None,
|
||||
counter: 0,
|
||||
};
|
||||
return Ok(resp.streaming(reader));
|
||||
}
|
||||
@ -266,15 +277,16 @@ impl Responder for NamedFile {
|
||||
};
|
||||
|
||||
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.set(header::ContentType(get_mime_type(
|
||||
&ext.to_string_lossy(),
|
||||
)));
|
||||
resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
|
||||
}).if_some(self.path().file_name(), |file_name, resp| {
|
||||
let mime_type = guess_mime_type(self.path());
|
||||
let inline_or_attachment = match mime_type.type_() {
|
||||
mime::IMAGE | mime::TEXT => "inline",
|
||||
mime::IMAGE | mime::TEXT | mime::VIDEO => "inline",
|
||||
_ => "attachment",
|
||||
};
|
||||
resp.header(
|
||||
@ -293,6 +305,38 @@ impl Responder for NamedFile {
|
||||
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 {
|
||||
return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish());
|
||||
} else if not_modified {
|
||||
@ -303,12 +347,15 @@ impl Responder for NamedFile {
|
||||
Ok(resp.finish())
|
||||
} else {
|
||||
let reader = ChunkedReadFile {
|
||||
size: self.md.len(),
|
||||
offset: 0,
|
||||
cpu_pool: self.cpu_pool
|
||||
.unwrap_or_else(|| req.cpu_pool().clone()),
|
||||
offset,
|
||||
size: length,
|
||||
cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()),
|
||||
file: Some(self.file),
|
||||
fut: None,
|
||||
counter: 0,
|
||||
};
|
||||
if offset != 0 || length != self.md.len() {
|
||||
return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader));
|
||||
};
|
||||
Ok(resp.streaming(reader))
|
||||
}
|
||||
@ -323,6 +370,7 @@ pub struct ChunkedReadFile {
|
||||
cpu_pool: CpuPool,
|
||||
file: Option<File>,
|
||||
fut: Option<CpuFuture<(File, Bytes), io::Error>>,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl Stream for ChunkedReadFile {
|
||||
@ -336,6 +384,7 @@ impl Stream for ChunkedReadFile {
|
||||
self.fut.take();
|
||||
self.file = Some(file);
|
||||
self.offset += bytes.len() as u64;
|
||||
self.counter += bytes.len() as u64;
|
||||
Ok(Async::Ready(Some(bytes)))
|
||||
}
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
@ -344,14 +393,16 @@ impl Stream for ChunkedReadFile {
|
||||
|
||||
let size = self.size;
|
||||
let offset = self.offset;
|
||||
let counter = self.counter;
|
||||
|
||||
if size == offset {
|
||||
if size == counter {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
let mut file = self.file.take().expect("Use after completion");
|
||||
self.fut = Some(self.cpu_pool.spawn_fn(move || {
|
||||
let max_bytes = cmp::min(size.saturating_sub(offset), 65_536) as usize;
|
||||
let mut buf = BytesMut::with_capacity(max_bytes);
|
||||
let max_bytes: usize;
|
||||
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))?;
|
||||
let nbytes = file.read(unsafe { buf.bytes_mut() })?;
|
||||
if nbytes == 0 {
|
||||
@ -584,7 +635,8 @@ impl<S: 'static> Handler<S> for StaticFiles<S> {
|
||||
if !self.accessible {
|
||||
Ok(self.default.handle(req))
|
||||
} else {
|
||||
let relpath = match req.match_info()
|
||||
let relpath = match req
|
||||
.match_info()
|
||||
.get("tail")
|
||||
.map(|tail| PathBuf::from_param(tail.trim_left_matches('/')))
|
||||
{
|
||||
@ -601,10 +653,6 @@ impl<S: 'static> Handler<S> for StaticFiles<S> {
|
||||
// TODO: It'd be nice if there were a good usable URL manipulation
|
||||
// library
|
||||
let mut new_path: String = req.path().to_owned();
|
||||
for el in relpath.iter() {
|
||||
new_path.push_str(&el.to_string_lossy());
|
||||
new_path.push('/');
|
||||
}
|
||||
if !new_path.ends_with('/') {
|
||||
new_path.push('/');
|
||||
}
|
||||
@ -656,9 +704,7 @@ mod tests {
|
||||
"text/x-toml"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers()
|
||||
.get(header::CONTENT_DISPOSITION)
|
||||
.unwrap(),
|
||||
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||
"inline; filename=Cargo.toml"
|
||||
);
|
||||
}
|
||||
@ -682,9 +728,7 @@ mod tests {
|
||||
"image/png"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers()
|
||||
.get(header::CONTENT_DISPOSITION)
|
||||
.unwrap(),
|
||||
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||
"inline; filename=test.png"
|
||||
);
|
||||
}
|
||||
@ -708,9 +752,7 @@ mod tests {
|
||||
"application/octet-stream"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers()
|
||||
.get(header::CONTENT_DISPOSITION)
|
||||
.unwrap(),
|
||||
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||
"attachment; filename=test.binary"
|
||||
);
|
||||
}
|
||||
@ -735,14 +777,173 @@ mod tests {
|
||||
"text/x-toml"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers()
|
||||
.get(header::CONTENT_DISPOSITION)
|
||||
.unwrap(),
|
||||
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||
"inline; filename=Cargo.toml"
|
||||
);
|
||||
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]
|
||||
fn test_named_file_not_allowed() {
|
||||
let req = TestRequest::default().method(Method::POST).finish();
|
||||
@ -752,6 +953,21 @@ mod tests {
|
||||
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]
|
||||
fn test_named_file_any_method() {
|
||||
let req = TestRequest::default().method(Method::POST).finish();
|
||||
@ -764,7 +980,8 @@ mod tests {
|
||||
fn test_static_files() {
|
||||
let mut st = StaticFiles::new(".").show_files_listing();
|
||||
st.accessible = false;
|
||||
let resp = st.handle(HttpRequest::default())
|
||||
let resp = st
|
||||
.handle(HttpRequest::default())
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = resp.as_msg();
|
||||
@ -772,7 +989,8 @@ mod tests {
|
||||
|
||||
st.accessible = true;
|
||||
st.show_index = false;
|
||||
let resp = st.handle(HttpRequest::default())
|
||||
let resp = st
|
||||
.handle(HttpRequest::default())
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = resp.as_msg();
|
||||
@ -782,9 +1000,7 @@ mod tests {
|
||||
req.match_info_mut().add("tail", "");
|
||||
|
||||
st.show_index = true;
|
||||
let resp = st.handle(req)
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
@ -797,12 +1013,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_redirect_to_index() {
|
||||
let mut st = StaticFiles::new(".").index_file("index.html");
|
||||
let mut req = HttpRequest::default();
|
||||
let mut req = TestRequest::default().uri("/tests").finish();
|
||||
|
||||
req.match_info_mut().add("tail", "tests");
|
||||
|
||||
let resp = st.handle(req)
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(
|
||||
@ -810,12 +1025,10 @@ mod tests {
|
||||
"/tests/index.html"
|
||||
);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
let mut req = TestRequest::default().uri("/tests/").finish();
|
||||
req.match_info_mut().add("tail", "tests/");
|
||||
|
||||
let resp = st.handle(req)
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(
|
||||
@ -827,12 +1040,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_redirect_to_index_nested() {
|
||||
let mut st = StaticFiles::new(".").index_file("Cargo.toml");
|
||||
let mut req = HttpRequest::default();
|
||||
let mut req = TestRequest::default().uri("/tools/wsload").finish();
|
||||
req.match_info_mut().add("tail", "tools/wsload");
|
||||
|
||||
let resp = st.handle(req)
|
||||
.respond_to(&HttpRequest::default())
|
||||
.unwrap();
|
||||
let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(
|
||||
@ -907,7 +1118,8 @@ mod tests {
|
||||
App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml"))
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/test/%43argo.toml"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
|
@ -362,7 +362,8 @@ where
|
||||
self, req: &HttpRequest<S>,
|
||||
) -> Result<AsyncResult<HttpResponse>, Error> {
|
||||
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) {
|
||||
Ok(reply) => match reply.into().into() {
|
||||
AsyncResultItem::Ok(resp) => ok(resp),
|
||||
@ -397,10 +398,7 @@ where
|
||||
S: 'static,
|
||||
{
|
||||
pub fn new(h: H) -> Self {
|
||||
WrapHandler {
|
||||
h,
|
||||
s: PhantomData,
|
||||
}
|
||||
WrapHandler { h, s: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,16 +454,16 @@ where
|
||||
S: 'static,
|
||||
{
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
let fut = (self.h)(req.clone())
|
||||
.map_err(|e| e.into())
|
||||
.then(move |r| match r.respond_to(&req) {
|
||||
let fut = (self.h)(req.clone()).map_err(|e| e.into()).then(move |r| {
|
||||
match r.respond_to(&req) {
|
||||
Ok(reply) => match reply.into().into() {
|
||||
AsyncResultItem::Ok(resp) => Either::A(ok(resp)),
|
||||
AsyncResultItem::Err(e) => Either::A(err(e)),
|
||||
AsyncResultItem::Future(fut) => Either::B(fut),
|
||||
},
|
||||
Err(e) => Either::A(err(e)),
|
||||
});
|
||||
}
|
||||
});
|
||||
AsyncResult::async(Box::new(fut))
|
||||
}
|
||||
}
|
||||
@ -492,14 +490,15 @@ where
|
||||
/// }
|
||||
///
|
||||
/// /// extract path info using serde
|
||||
/// fn index(state: State<MyApp>, info: Path<Info>) -> String {
|
||||
/// format!("{} {}!", state.msg, info.username)
|
||||
/// fn index(data: (State<MyApp>, Path<Info>)) -> String {
|
||||
/// let (state, path) = data;
|
||||
/// format!("{} {}!", state.msg, path.username)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::with_state(MyApp{msg: "Welcome"}).resource(
|
||||
/// "/{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>);
|
||||
|
@ -1,8 +1,9 @@
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
pub use self::Encoding::{Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip,
|
||||
Identity, Trailers};
|
||||
pub use self::Encoding::{
|
||||
Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers,
|
||||
};
|
||||
|
||||
/// A value to represent an encoding used in `Transfer-Encoding`
|
||||
/// or `Accept-Encoding` header.
|
||||
|
@ -132,7 +132,8 @@ impl FromStr for EntityTag {
|
||||
return Err(::error::ParseError::Header);
|
||||
}
|
||||
// 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])
|
||||
{
|
||||
// No need to check if the last char is a DQUOTE,
|
||||
@ -141,7 +142,8 @@ impl FromStr for EntityTag {
|
||||
weak: false,
|
||||
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])
|
||||
{
|
||||
return Ok(EntityTag {
|
||||
@ -213,10 +215,7 @@ mod tests {
|
||||
format!("{}", EntityTag::strong("foobar".to_owned())),
|
||||
"\"foobar\""
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", EntityTag::strong("".to_owned())),
|
||||
"\"\""
|
||||
);
|
||||
assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\"");
|
||||
assert_eq!(
|
||||
format!("{}", EntityTag::weak("weak-etag".to_owned())),
|
||||
"W/\"weak-etag\""
|
||||
@ -225,10 +224,7 @@ mod tests {
|
||||
format!("{}", EntityTag::weak("\u{0065}".to_owned())),
|
||||
"W/\"\x65\""
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", EntityTag::weak("".to_owned())),
|
||||
"W/\"\""
|
||||
);
|
||||
assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -105,9 +105,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_date() {
|
||||
assert_eq!(
|
||||
"Sun, 07 Nov 1994 08:48:37 GMT"
|
||||
.parse::<HttpDate>()
|
||||
.unwrap(),
|
||||
"Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(),
|
||||
NOV_07
|
||||
);
|
||||
assert_eq!(
|
||||
@ -117,9 +115,7 @@ mod tests {
|
||||
NOV_07
|
||||
);
|
||||
assert_eq!(
|
||||
"Sun Nov 7 08:48:37 1994"
|
||||
.parse::<HttpDate>()
|
||||
.unwrap(),
|
||||
"Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(),
|
||||
NOV_07
|
||||
);
|
||||
assert!("this-is-no-date".parse::<HttpDate>().is_err());
|
||||
|
@ -63,11 +63,7 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
|
||||
match self.quality.0 {
|
||||
1000 => Ok(()),
|
||||
0 => f.write_str("; q=0"),
|
||||
x => write!(
|
||||
f,
|
||||
"; q=0.{}",
|
||||
format!("{:03}", x).trim_right_matches('0')
|
||||
),
|
||||
x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -295,10 +291,6 @@ mod tests {
|
||||
#[test]
|
||||
fn test_fuzzing_bugs() {
|
||||
assert!("99999;".parse::<QualityItem<String>>().is_err());
|
||||
assert!(
|
||||
"\x0d;;;=\u{d6aa}=="
|
||||
.parse::<QualityItem<String>>()
|
||||
.is_err()
|
||||
)
|
||||
assert!("\x0d;;;=\u{d6aa}==".parse::<QualityItem<String>>().is_err())
|
||||
}
|
||||
}
|
||||
|
@ -190,16 +190,8 @@ mod tests {
|
||||
// trailing slashes
|
||||
let params = vec![
|
||||
("/resource1", "", StatusCode::OK),
|
||||
(
|
||||
"/resource1/",
|
||||
"/resource1",
|
||||
StatusCode::MOVED_PERMANENTLY,
|
||||
),
|
||||
(
|
||||
"/resource2",
|
||||
"/resource2/",
|
||||
StatusCode::MOVED_PERMANENTLY,
|
||||
),
|
||||
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
|
||||
("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
|
||||
("/resource2/", "", StatusCode::OK),
|
||||
("/resource1?p1=1&p2=2", "", StatusCode::OK),
|
||||
(
|
||||
@ -222,11 +214,7 @@ mod tests {
|
||||
if !target.is_empty() {
|
||||
assert_eq!(
|
||||
target,
|
||||
r.headers()
|
||||
.get(header::LOCATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -276,16 +264,8 @@ mod tests {
|
||||
// trailing slashes
|
||||
let params = vec![
|
||||
("/resource1/a/b", "", StatusCode::OK),
|
||||
(
|
||||
"/resource1/",
|
||||
"/resource1",
|
||||
StatusCode::MOVED_PERMANENTLY,
|
||||
),
|
||||
(
|
||||
"/resource1//",
|
||||
"/resource1",
|
||||
StatusCode::MOVED_PERMANENTLY,
|
||||
),
|
||||
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
|
||||
("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
|
||||
(
|
||||
"//resource1//a//b",
|
||||
"/resource1/a/b",
|
||||
@ -356,11 +336,7 @@ mod tests {
|
||||
if !target.is_empty() {
|
||||
assert_eq!(
|
||||
target,
|
||||
r.headers()
|
||||
.get(header::LOCATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -540,11 +516,7 @@ mod tests {
|
||||
if !target.is_empty() {
|
||||
assert_eq!(
|
||||
target,
|
||||
r.headers()
|
||||
.get(header::LOCATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -55,10 +55,7 @@ impl HttpResponse {
|
||||
STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
|
||||
STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
|
||||
STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG);
|
||||
STATIC_RESP!(
|
||||
UnsupportedMediaType,
|
||||
StatusCode::UNSUPPORTED_MEDIA_TYPE
|
||||
);
|
||||
STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
|
||||
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
|
||||
|
||||
@ -67,14 +64,8 @@ impl HttpResponse {
|
||||
STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY);
|
||||
STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
|
||||
STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
|
||||
STATIC_RESP!(
|
||||
VersionNotSupported,
|
||||
StatusCode::HTTP_VERSION_NOT_SUPPORTED
|
||||
);
|
||||
STATIC_RESP!(
|
||||
VariantAlsoNegotiates,
|
||||
StatusCode::VARIANT_ALSO_NEGOTIATES
|
||||
);
|
||||
STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
|
||||
STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
|
||||
STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
|
||||
STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED);
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ use serde::de::DeserializeOwned;
|
||||
use serde_urlencoded;
|
||||
use std::str;
|
||||
|
||||
use error::{ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError};
|
||||
use error::{
|
||||
ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError,
|
||||
};
|
||||
use header::Header;
|
||||
use json::JsonBody;
|
||||
use multipart::Multipart;
|
||||
@ -96,10 +98,8 @@ pub trait HttpMessage {
|
||||
/// `size` is full size of response (file).
|
||||
fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> {
|
||||
if let Some(range) = self.headers().get(header::RANGE) {
|
||||
HttpRange::parse(
|
||||
unsafe { str::from_utf8_unchecked(range.as_bytes()) },
|
||||
size,
|
||||
).map_err(|e| e.into())
|
||||
HttpRange::parse(unsafe { str::from_utf8_unchecked(range.as_bytes()) }, size)
|
||||
.map_err(|e| e.into())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
@ -148,7 +148,6 @@ pub trait HttpMessage {
|
||||
/// Returns error:
|
||||
///
|
||||
/// * content type is not `application/x-www-form-urlencoded`
|
||||
/// * transfer encoding is `chunked`.
|
||||
/// * content-length is greater than 256k
|
||||
///
|
||||
/// ## Server example
|
||||
@ -365,9 +364,7 @@ where
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(req) = self.req.take() {
|
||||
if req.chunked().unwrap_or(false) {
|
||||
return Err(UrlencodedError::Chunked);
|
||||
} else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
|
||||
if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
if len > 262_144 {
|
||||
@ -385,12 +382,12 @@ where
|
||||
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
|
||||
return Err(UrlencodedError::ContentType);
|
||||
}
|
||||
let encoding = req.encoding()
|
||||
.map_err(|_| UrlencodedError::ContentType)?;
|
||||
let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?;
|
||||
|
||||
// future
|
||||
let limit = self.limit;
|
||||
let fut = req.from_err()
|
||||
let fut = req
|
||||
.from_err()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(UrlencodedError::Overflow)
|
||||
@ -488,10 +485,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_encoding_error() {
|
||||
let req = TestRequest::with_header("content-type", "applicatjson").finish();
|
||||
assert_eq!(
|
||||
Some(ContentTypeError::ParseError),
|
||||
req.encoding().err()
|
||||
);
|
||||
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
"content-type",
|
||||
@ -578,13 +572,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
@ -664,8 +651,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.payload_mut()
|
||||
.unread_data(Bytes::from_static(b"test"));
|
||||
req.payload_mut().unread_data(Bytes::from_static(b"test"));
|
||||
match req.body().poll().ok().unwrap() {
|
||||
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
|
||||
_ => unreachable!("error"),
|
||||
|
@ -27,7 +27,6 @@ use uri::Url as InnerUrl;
|
||||
|
||||
bitflags! {
|
||||
pub(crate) struct MessageFlags: u8 {
|
||||
const QUERY = 0b0000_0001;
|
||||
const KEEPALIVE = 0b0000_0010;
|
||||
}
|
||||
}
|
||||
@ -40,8 +39,6 @@ pub struct HttpInnerMessage {
|
||||
pub headers: HeaderMap,
|
||||
pub extensions: Extensions,
|
||||
pub params: Params<'static>,
|
||||
pub cookies: Option<Vec<Cookie<'static>>>,
|
||||
pub query: Params<'static>,
|
||||
pub addr: Option<SocketAddr>,
|
||||
pub payload: Option<Payload>,
|
||||
pub info: Option<ConnectionInfo<'static>>,
|
||||
@ -49,6 +46,9 @@ pub struct HttpInnerMessage {
|
||||
resource: RouterResource,
|
||||
}
|
||||
|
||||
struct Query(Params<'static>);
|
||||
struct Cookies(Vec<Cookie<'static>>);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
enum RouterResource {
|
||||
Notset,
|
||||
@ -64,9 +64,7 @@ impl Default for HttpInnerMessage {
|
||||
headers: HeaderMap::with_capacity(16),
|
||||
flags: MessageFlags::empty(),
|
||||
params: Params::new(),
|
||||
query: Params::new(),
|
||||
addr: None,
|
||||
cookies: None,
|
||||
payload: None,
|
||||
extensions: Extensions::new(),
|
||||
info: None,
|
||||
@ -91,7 +89,6 @@ impl HttpInnerMessage {
|
||||
self.addr = None;
|
||||
self.info = None;
|
||||
self.flags = MessageFlags::empty();
|
||||
self.cookies = None;
|
||||
self.payload = None;
|
||||
self.prefix = 0;
|
||||
self.resource = RouterResource::Notset;
|
||||
@ -121,9 +118,7 @@ impl HttpRequest<()> {
|
||||
headers,
|
||||
payload,
|
||||
params: Params::new(),
|
||||
query: Params::new(),
|
||||
extensions: Extensions::new(),
|
||||
cookies: None,
|
||||
addr: None,
|
||||
info: None,
|
||||
prefix: 0,
|
||||
@ -146,6 +141,12 @@ impl HttpRequest<()> {
|
||||
pub fn with_state<S>(self, state: Rc<S>, router: Router) -> HttpRequest<S> {
|
||||
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> {
|
||||
@ -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.
|
||||
#[inline]
|
||||
pub fn router(&self) -> Option<&Router> {
|
||||
@ -369,20 +379,20 @@ impl<S> HttpRequest<S> {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.6.0", note = "please use `Query<T>` extractor")]
|
||||
/// Get a reference to the Params object.
|
||||
/// Params is a container for url query parameters.
|
||||
pub fn query(&self) -> &Params {
|
||||
if !self.as_ref().flags.contains(MessageFlags::QUERY) {
|
||||
self.as_mut().flags.insert(MessageFlags::QUERY);
|
||||
let params: &mut Params =
|
||||
unsafe { mem::transmute(&mut self.as_mut().query) };
|
||||
params.clear();
|
||||
pub fn query<'a>(&'a self) -> &'a Params {
|
||||
if self.extensions().get::<Query>().is_none() {
|
||||
let mut params: Params<'a> = Params::new();
|
||||
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
|
||||
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.
|
||||
@ -399,7 +409,7 @@ impl<S> HttpRequest<S> {
|
||||
|
||||
/// Load request cookies.
|
||||
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 mut cookies = Vec::new();
|
||||
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.
|
||||
@ -427,6 +437,12 @@ impl<S> HttpRequest<S> {
|
||||
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.
|
||||
///
|
||||
/// Params is a container for url parameters.
|
||||
@ -664,10 +680,8 @@ mod tests {
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let routes = vec![(
|
||||
Resource::new("index", "/user/{name}.{ext}"),
|
||||
Some(resource),
|
||||
)];
|
||||
let routes =
|
||||
vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
|
||||
let (router, _) = Router::new("/", ServerSettings::default(), routes);
|
||||
assert!(router.has_route("/user/test.html"));
|
||||
assert!(!router.has_route("/test/unknown"));
|
||||
@ -696,22 +710,38 @@ mod tests {
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let routes = vec![(
|
||||
Resource::new("index", "/user/{name}.{ext}"),
|
||||
Some(resource),
|
||||
)];
|
||||
let routes = vec![(Resource::new("index", "/user/{name}.html"), Some(resource))];
|
||||
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
|
||||
assert!(router.has_route("/user/test.html"));
|
||||
assert!(!router.has_route("/prefix/user/test.html"));
|
||||
|
||||
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!(
|
||||
url.ok().unwrap().as_str(),
|
||||
"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]
|
||||
fn test_url_for_external() {
|
||||
let req = HttpRequest::default();
|
||||
|
@ -89,7 +89,7 @@ impl HttpResponse {
|
||||
/// Constructs a error response
|
||||
#[inline]
|
||||
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
|
||||
}
|
||||
@ -241,6 +241,14 @@ impl HttpResponse {
|
||||
pub fn set_write_buffer_capacity(&mut self, cap: usize) {
|
||||
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 {
|
||||
@ -297,11 +305,13 @@ impl HttpResponseBuilder {
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{HttpRequest, HttpResponse, Result, http};
|
||||
/// use actix_web::{http, HttpRequest, HttpResponse, Result};
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<HttpResponse> {
|
||||
/// 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())
|
||||
/// }
|
||||
/// fn main() {}
|
||||
@ -455,7 +465,8 @@ impl HttpResponseBuilder {
|
||||
/// .path("/")
|
||||
/// .secure(true)
|
||||
/// .http_only(true)
|
||||
/// .finish())
|
||||
/// .finish(),
|
||||
/// )
|
||||
/// .finish()
|
||||
/// }
|
||||
/// fn main() {}
|
||||
@ -466,10 +477,7 @@ impl HttpResponseBuilder {
|
||||
jar.add(cookie.into_owned());
|
||||
self.cookies = Some(jar)
|
||||
} else {
|
||||
self.cookies
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.add(cookie.into_owned());
|
||||
self.cookies.as_mut().unwrap().add(cookie.into_owned());
|
||||
}
|
||||
self
|
||||
}
|
||||
@ -534,9 +542,7 @@ impl HttpResponseBuilder {
|
||||
if let Some(e) = self.err.take() {
|
||||
return Error::from(e).into();
|
||||
}
|
||||
let mut response = self.response
|
||||
.take()
|
||||
.expect("cannot reuse response builder");
|
||||
let mut response = self.response.take().expect("cannot reuse response builder");
|
||||
if let Some(ref jar) = self.cookies {
|
||||
for cookie in jar.delta() {
|
||||
match HeaderValue::from_str(&cookie.to_string()) {
|
||||
@ -558,9 +564,7 @@ impl HttpResponseBuilder {
|
||||
S: Stream<Item = Bytes, Error = E> + 'static,
|
||||
E: Into<Error>,
|
||||
{
|
||||
self.body(Body::Streaming(Box::new(
|
||||
stream.map_err(|e| e.into()),
|
||||
)))
|
||||
self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
|
||||
}
|
||||
|
||||
/// Set a json body and generate `HttpResponse`
|
||||
@ -654,7 +658,8 @@ impl Responder for &'static str {
|
||||
type Error = 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")
|
||||
.body(self))
|
||||
}
|
||||
@ -673,7 +678,8 @@ impl Responder for &'static [u8] {
|
||||
type Error = 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")
|
||||
.body(self))
|
||||
}
|
||||
@ -692,7 +698,8 @@ impl Responder for String {
|
||||
type Error = 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")
|
||||
.body(self))
|
||||
}
|
||||
@ -711,7 +718,8 @@ impl<'a> Responder for &'a String {
|
||||
type Error = 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")
|
||||
.body(self))
|
||||
}
|
||||
@ -730,7 +738,8 @@ impl Responder for Bytes {
|
||||
type Error = 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")
|
||||
.body(self))
|
||||
}
|
||||
@ -749,7 +758,8 @@ impl Responder for BytesMut {
|
||||
type Error = 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")
|
||||
.body(self))
|
||||
}
|
||||
@ -782,12 +792,12 @@ impl<'a, S> From<&'a HttpRequest<S>> for HttpResponseBuilder {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InnerHttpResponse {
|
||||
pub(crate) struct InnerHttpResponse {
|
||||
version: Option<Version>,
|
||||
headers: HeaderMap,
|
||||
status: StatusCode,
|
||||
reason: Option<&'static str>,
|
||||
body: Body,
|
||||
pub(crate) body: Body,
|
||||
chunked: Option<bool>,
|
||||
encoding: Option<ContentEncoding>,
|
||||
connection_type: Option<ConnectionType>,
|
||||
@ -796,6 +806,9 @@ struct InnerHttpResponse {
|
||||
error: Option<Error>,
|
||||
}
|
||||
|
||||
unsafe impl Sync for InnerHttpResponse {}
|
||||
unsafe impl Send for InnerHttpResponse {}
|
||||
|
||||
impl InnerHttpResponse {
|
||||
#[inline]
|
||||
fn new(status: StatusCode, body: Body) -> InnerHttpResponse {
|
||||
@ -822,9 +835,9 @@ thread_local!(static POOL: Rc<UnsafeCell<HttpResponsePool>> = HttpResponsePool::
|
||||
|
||||
impl HttpResponsePool {
|
||||
pub fn pool() -> Rc<UnsafeCell<HttpResponsePool>> {
|
||||
Rc::new(UnsafeCell::new(HttpResponsePool(
|
||||
VecDeque::with_capacity(128),
|
||||
)))
|
||||
Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity(
|
||||
128,
|
||||
))))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -944,7 +957,8 @@ mod tests {
|
||||
.del_cookie(&cookies[0])
|
||||
.finish();
|
||||
|
||||
let mut val: Vec<_> = resp.headers()
|
||||
let mut val: Vec<_> = resp
|
||||
.headers()
|
||||
.get_all("Set-Cookie")
|
||||
.iter()
|
||||
.map(|v| v.to_str().unwrap().to_owned())
|
||||
@ -975,9 +989,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_force_close() {
|
||||
let resp = HttpResponse::build(StatusCode::OK)
|
||||
.force_close()
|
||||
.finish();
|
||||
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
|
||||
assert!(!resp.keep_alive().unwrap())
|
||||
}
|
||||
|
||||
@ -986,10 +998,7 @@ mod tests {
|
||||
let resp = HttpResponse::build(StatusCode::OK)
|
||||
.content_type("text/plain")
|
||||
.body(Body::Empty);
|
||||
assert_eq!(
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
"text/plain"
|
||||
)
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1036,10 +1045,10 @@ mod tests {
|
||||
}
|
||||
|
||||
impl Body {
|
||||
pub(crate) fn binary(&self) -> Option<&Binary> {
|
||||
pub(crate) fn bin_ref(&self) -> &Binary {
|
||||
match *self {
|
||||
Body::Binary(ref bin) => Some(bin),
|
||||
_ => None,
|
||||
Body::Binary(ref bin) => bin,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1055,7 +1064,7 @@ mod tests {
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
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();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
@ -1064,7 +1073,7 @@ mod tests {
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
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();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
@ -1073,10 +1082,7 @@ mod tests {
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.body().binary().unwrap(),
|
||||
&Binary::from(b"test".as_ref())
|
||||
);
|
||||
assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref()));
|
||||
|
||||
let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
@ -1085,10 +1091,7 @@ mod tests {
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.body().binary().unwrap(),
|
||||
&Binary::from(b"test".as_ref())
|
||||
);
|
||||
assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref()));
|
||||
|
||||
let resp: HttpResponse = "test".to_owned().into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
@ -1097,10 +1100,7 @@ mod tests {
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.body().binary().unwrap(),
|
||||
&Binary::from("test".to_owned())
|
||||
);
|
||||
assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned()));
|
||||
|
||||
let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
@ -1109,10 +1109,7 @@ mod tests {
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.body().binary().unwrap(),
|
||||
&Binary::from("test".to_owned())
|
||||
);
|
||||
assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned()));
|
||||
|
||||
let resp: HttpResponse = (&"test".to_owned()).into();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
@ -1121,10 +1118,7 @@ mod tests {
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.body().binary().unwrap(),
|
||||
&Binary::from(&"test".to_owned())
|
||||
);
|
||||
assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned()));
|
||||
|
||||
let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
@ -1133,10 +1127,7 @@ mod tests {
|
||||
HeaderValue::from_static("text/plain; charset=utf-8")
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.body().binary().unwrap(),
|
||||
&Binary::from(&"test".to_owned())
|
||||
);
|
||||
assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned()));
|
||||
|
||||
let b = Bytes::from_static(b"test");
|
||||
let resp: HttpResponse = b.into();
|
||||
@ -1147,7 +1138,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.body().binary().unwrap(),
|
||||
resp.body().bin_ref(),
|
||||
&Binary::from(Bytes::from_static(b"test"))
|
||||
);
|
||||
|
||||
@ -1160,7 +1151,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.body().binary().unwrap(),
|
||||
resp.body().bin_ref(),
|
||||
&Binary::from(Bytes::from_static(b"test"))
|
||||
);
|
||||
|
||||
@ -1172,10 +1163,7 @@ mod tests {
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.body().binary().unwrap(),
|
||||
&Binary::from(BytesMut::from("test"))
|
||||
);
|
||||
assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test")));
|
||||
|
||||
let b = BytesMut::from("test");
|
||||
let resp: HttpResponse = b.respond_to(&req).ok().unwrap();
|
||||
@ -1185,10 +1173,7 @@ mod tests {
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.body().binary().unwrap(),
|
||||
&Binary::from(BytesMut::from("test"))
|
||||
);
|
||||
assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
15
src/info.rs
15
src/info.rs
@ -53,7 +53,8 @@ impl<'a> ConnectionInfo<'a> {
|
||||
|
||||
// scheme
|
||||
if scheme.is_none() {
|
||||
if let Some(h) = req.headers()
|
||||
if let Some(h) = req
|
||||
.headers()
|
||||
.get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
@ -74,7 +75,8 @@ impl<'a> ConnectionInfo<'a> {
|
||||
|
||||
// host
|
||||
if host.is_none() {
|
||||
if let Some(h) = req.headers()
|
||||
if let Some(h) = req
|
||||
.headers()
|
||||
.get(HeaderName::from_str(X_FORWARDED_HOST).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
@ -98,7 +100,8 @@ impl<'a> ConnectionInfo<'a> {
|
||||
|
||||
// remote addr
|
||||
if remote.is_none() {
|
||||
if let Some(h) = req.headers()
|
||||
if let Some(h) = req
|
||||
.headers()
|
||||
.get(HeaderName::from_str(X_FORWARDED_FOR).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
@ -189,10 +192,8 @@ mod tests {
|
||||
assert_eq!(info.remote(), Some("192.0.2.60"));
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
header::HOST,
|
||||
HeaderValue::from_static("rust-lang.org"),
|
||||
);
|
||||
req.headers_mut()
|
||||
.insert(header::HOST, HeaderValue::from_static("rust-lang.org"));
|
||||
|
||||
let info = ConnectionInfo::new(&req);
|
||||
assert_eq!(info.scheme(), "http");
|
||||
|
16
src/json.rs
16
src/json.rs
@ -121,7 +121,8 @@ impl<T: Serialize> Responder for Json<T> {
|
||||
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
|
||||
let body = serde_json::to_string(&self.0)?;
|
||||
|
||||
Ok(req.build_response(StatusCode::OK)
|
||||
Ok(req
|
||||
.build_response(StatusCode::OK)
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}
|
||||
@ -295,7 +296,8 @@ where
|
||||
}
|
||||
|
||||
let limit = self.limit;
|
||||
let fut = req.from_err()
|
||||
let fut = req
|
||||
.from_err()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(JsonPayloadError::Overflow)
|
||||
@ -362,10 +364,7 @@ mod tests {
|
||||
fn test_json_body() {
|
||||
let req = HttpRequest::default();
|
||||
let mut json = req.json::<MyObject>();
|
||||
assert_eq!(
|
||||
json.poll().err().unwrap(),
|
||||
JsonPayloadError::ContentType
|
||||
);
|
||||
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
@ -373,10 +372,7 @@ mod tests {
|
||||
header::HeaderValue::from_static("application/text"),
|
||||
);
|
||||
let mut json = req.json::<MyObject>();
|
||||
assert_eq!(
|
||||
json.poll().err().unwrap(),
|
||||
JsonPayloadError::ContentType
|
||||
);
|
||||
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(
|
||||
|
37
src/lib.rs
37
src/lib.rs
@ -6,15 +6,15 @@
|
||||
//! # use std::thread;
|
||||
//!
|
||||
//! fn index(info: Path<(String, u32)>) -> String {
|
||||
//! format!("Hello {}! id:{}", info.0, info.1)
|
||||
//! format!("Hello {}! id:{}", info.0, info.1)
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! # thread::spawn(|| {
|
||||
//! server::new(
|
||||
//! || App::new()
|
||||
//! .resource("/{name}/{id}/index.html", |r| r.with(index)))
|
||||
//! .bind("127.0.0.1:8080").unwrap()
|
||||
//! server::new(|| {
|
||||
//! App::new().resource("/{name}/{id}/index.html", |r| r.with(index))
|
||||
//! }).bind("127.0.0.1:8080")
|
||||
//! .unwrap()
|
||||
//! .run();
|
||||
//! # });
|
||||
//! }
|
||||
@ -25,7 +25,7 @@
|
||||
//! Besides the API documentation (which you are currently looking
|
||||
//! 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)
|
||||
//! * [GitHub repository](https://github.com/actix/actix-web)
|
||||
//! * [Cargo package](https://crates.io/crates/actix-web)
|
||||
@ -60,9 +60,24 @@
|
||||
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
|
||||
//! * Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||
//! * 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(
|
||||
specialization, // for impl ErrorResponse for std::error::Error
|
||||
extern_prelude,
|
||||
))]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
@ -169,12 +184,17 @@ pub use body::{Binary, Body};
|
||||
pub use context::HttpContext;
|
||||
pub use error::{Error, ResponseError, Result};
|
||||
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 httprequest::HttpRequest;
|
||||
pub use httpresponse::HttpResponse;
|
||||
pub use json::Json;
|
||||
pub use scope::Scope;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")]
|
||||
pub use ws::WsWriter;
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
@ -210,6 +230,7 @@ pub mod dev {
|
||||
pub use resource::ResourceHandler;
|
||||
pub use route::Route;
|
||||
pub use router::{Resource, ResourceType, Router};
|
||||
pub use with::ExtractorConfig;
|
||||
}
|
||||
|
||||
pub mod http {
|
||||
|
@ -19,16 +19,16 @@
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate actix_web;
|
||||
//! use actix_web::{http, App, HttpRequest, HttpResponse};
|
||||
//! use actix_web::middleware::cors::Cors;
|
||||
//! use actix_web::{http, App, HttpRequest, HttpResponse};
|
||||
//!
|
||||
//! fn index(mut req: HttpRequest) -> &'static str {
|
||||
//! "Hello world"
|
||||
//! "Hello world"
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let app = App::new()
|
||||
//! .configure(|app| Cors::for_app(app) // <- Construct CORS middleware builder
|
||||
//! let app = App::new().configure(|app| {
|
||||
//! Cors::for_app(app) // <- Construct CORS middleware builder
|
||||
//! .allowed_origin("https://www.rust-lang.org/")
|
||||
//! .allowed_methods(vec!["GET", "POST"])
|
||||
//! .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::HEAD).f(|_| HttpResponse::MethodNotAllowed());
|
||||
//! })
|
||||
//! .register());
|
||||
//! .register()
|
||||
//! });
|
||||
//! }
|
||||
//! ```
|
||||
//! In this example custom *CORS* middleware get registered for "/index.html"
|
||||
@ -232,18 +233,20 @@ impl Cors {
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{http, App, HttpResponse};
|
||||
/// use actix_web::middleware::cors::Cors;
|
||||
/// use actix_web::{http, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder
|
||||
/// let app = App::new().configure(
|
||||
/// |app| {
|
||||
/// Cors::for_app(app) // <- Construct CORS builder
|
||||
/// .allowed_origin("https://www.rust-lang.org/")
|
||||
/// .resource("/resource", |r| { // register resource
|
||||
/// 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> {
|
||||
@ -275,9 +278,7 @@ impl Cors {
|
||||
/// `ResourceHandler::middleware()` method, but in that case *Cors*
|
||||
/// middleware wont be able to handle *OPTIONS* requests.
|
||||
pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) {
|
||||
resource
|
||||
.method(Method::OPTIONS)
|
||||
.h(|_| HttpResponse::Ok());
|
||||
resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok());
|
||||
resource.middleware(self);
|
||||
}
|
||||
|
||||
@ -304,12 +305,11 @@ impl Cors {
|
||||
fn validate_allowed_method<S>(
|
||||
&self, req: &mut HttpRequest<S>,
|
||||
) -> Result<(), CorsError> {
|
||||
if let Some(hdr) = req.headers()
|
||||
.get(header::ACCESS_CONTROL_REQUEST_METHOD)
|
||||
{
|
||||
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
|
||||
if let Ok(meth) = hdr.to_str() {
|
||||
if let Ok(method) = Method::try_from(meth) {
|
||||
return self.inner
|
||||
return self
|
||||
.inner
|
||||
.methods
|
||||
.get(&method)
|
||||
.and_then(|_| Some(()))
|
||||
@ -328,8 +328,8 @@ impl Cors {
|
||||
match self.inner.headers {
|
||||
AllOrSome::All => Ok(()),
|
||||
AllOrSome::Some(ref allowed_headers) => {
|
||||
if let Some(hdr) = req.headers()
|
||||
.get(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
||||
if let Some(hdr) =
|
||||
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
||||
{
|
||||
if let Ok(headers) = hdr.to_str() {
|
||||
let mut hdrs = HashSet::new();
|
||||
@ -371,8 +371,8 @@ impl<S> Middleware<S> for Cors {
|
||||
.as_str()[1..],
|
||||
).unwrap(),
|
||||
)
|
||||
} else if let Some(hdr) = req.headers()
|
||||
.get(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
||||
} else if let Some(hdr) =
|
||||
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
||||
{
|
||||
Some(hdr.clone())
|
||||
} else {
|
||||
@ -413,7 +413,8 @@ impl<S> Middleware<S> for Cors {
|
||||
})
|
||||
.header(
|
||||
header::ACCESS_CONTROL_ALLOW_METHODS,
|
||||
&self.inner
|
||||
&self
|
||||
.inner
|
||||
.methods
|
||||
.iter()
|
||||
.fold(String::new(), |s, v| s + "," + v.as_str())
|
||||
@ -422,7 +423,10 @@ impl<S> Middleware<S> for Cors {
|
||||
.finish(),
|
||||
))
|
||||
} 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)
|
||||
}
|
||||
@ -493,8 +497,8 @@ impl<S> Middleware<S> for Cors {
|
||||
/// ```rust
|
||||
/// # extern crate http;
|
||||
/// # extern crate actix_web;
|
||||
/// use http::header;
|
||||
/// use actix_web::middleware::cors;
|
||||
/// use http::header;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let cors = cors::Cors::build()
|
||||
@ -766,12 +770,13 @@ impl<S: 'static> CorsBuilder<S> {
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{http, App, HttpResponse};
|
||||
/// use actix_web::middleware::cors::Cors;
|
||||
/// use actix_web::{http, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder
|
||||
/// let app = App::new().configure(
|
||||
/// |app| {
|
||||
/// Cors::for_app(app) // <- Construct CORS builder
|
||||
/// .allowed_origin("https://www.rust-lang.org/")
|
||||
/// .allowed_methods(vec!["GET", "POST"])
|
||||
/// .allowed_header(http::header::CONTENT_TYPE)
|
||||
@ -783,8 +788,9 @@ impl<S: 'static> CorsBuilder<S> {
|
||||
/// r.method(http::Method::HEAD)
|
||||
/// .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>
|
||||
@ -866,7 +872,8 @@ impl<S: 'static> CorsBuilder<S> {
|
||||
}
|
||||
|
||||
let cors = self.construct();
|
||||
let mut app = self.app
|
||||
let mut app = self
|
||||
.app
|
||||
.take()
|
||||
.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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "MissingOrigin")]
|
||||
fn test_validate_missing_origin() {
|
||||
let cors = Cors::build()
|
||||
.allowed_origin("https://www.example.com")
|
||||
.finish();
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
cors.start(&mut req).unwrap();
|
||||
}
|
||||
// #[test]
|
||||
// #[should_panic(expected = "MissingOrigin")]
|
||||
// fn test_validate_missing_origin() {
|
||||
// let mut cors = Cors::build()
|
||||
// .allowed_origin("https://www.example.com")
|
||||
// .finish();
|
||||
// let mut req = HttpRequest::default();
|
||||
// cors.start(&mut req).unwrap();
|
||||
// }
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "OriginNotAllowed")]
|
||||
@ -1094,9 +1100,8 @@ mod tests {
|
||||
resp.headers().get(header::VARY).unwrap().as_bytes()
|
||||
);
|
||||
|
||||
let resp: HttpResponse = HttpResponse::Ok()
|
||||
.header(header::VARY, "Accept")
|
||||
.finish();
|
||||
let resp: HttpResponse =
|
||||
HttpResponse::Ok().header(header::VARY, "Accept").finish();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
assert_eq!(
|
||||
&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();
|
||||
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"))
|
||||
.header("ORIGIN", "https://www.example.com")
|
||||
.finish()
|
||||
|
@ -112,9 +112,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
|
||||
let resp = HttpResponse::Ok()
|
||||
.header(CONTENT_TYPE, "0002")
|
||||
.finish();
|
||||
let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish();
|
||||
let resp = match mw.response(&mut req, resp) {
|
||||
Ok(Response::Done(resp)) => resp,
|
||||
_ => panic!(),
|
||||
|
@ -179,7 +179,8 @@ impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
|
||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||
let mut req = req.clone();
|
||||
|
||||
let fut = self.backend
|
||||
let fut = self
|
||||
.backend
|
||||
.from_request(&mut req)
|
||||
.then(move |res| match res {
|
||||
Ok(id) => {
|
||||
|
@ -376,9 +376,7 @@ mod tests {
|
||||
headers,
|
||||
None,
|
||||
);
|
||||
let resp = HttpResponse::build(StatusCode::OK)
|
||||
.force_close()
|
||||
.finish();
|
||||
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
|
||||
let entry_time = time::now();
|
||||
|
||||
let render = |fmt: &mut Formatter| {
|
||||
@ -399,9 +397,7 @@ mod tests {
|
||||
HeaderMap::new(),
|
||||
None,
|
||||
);
|
||||
let resp = HttpResponse::build(StatusCode::OK)
|
||||
.force_close()
|
||||
.finish();
|
||||
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
|
||||
let entry_time = time::now();
|
||||
|
||||
let render = |fmt: &mut Formatter| {
|
||||
|
@ -80,6 +80,7 @@ use serde_json::error::Error as JsonError;
|
||||
use time::Duration;
|
||||
|
||||
use error::{Error, ResponseError, Result};
|
||||
use handler::FromRequest;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
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>>);
|
||||
|
||||
#[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> {
|
||||
let mut req = req.clone();
|
||||
|
||||
let fut = self.0
|
||||
.from_request(&mut req)
|
||||
.then(move |res| match res {
|
||||
Ok(sess) => {
|
||||
req.extensions_mut().insert(Arc::new(SessionImplCell(
|
||||
RefCell::new(Box::new(sess)),
|
||||
)));
|
||||
FutOk(None)
|
||||
}
|
||||
Err(err) => FutErr(err),
|
||||
});
|
||||
let fut = self.0.from_request(&mut req).then(move |res| match res {
|
||||
Ok(sess) => {
|
||||
req.extensions_mut()
|
||||
.insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess)))));
|
||||
FutOk(None)
|
||||
}
|
||||
Err(err) => FutErr(err),
|
||||
});
|
||||
Ok(Started::Future(Box::new(fut)))
|
||||
}
|
||||
|
||||
@ -551,4 +577,24 @@ mod tests {
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -122,11 +122,7 @@ where
|
||||
if let Some(err) = self.error.take() {
|
||||
Err(err)
|
||||
} else if self.safety.current() {
|
||||
self.inner
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.poll(&self.safety)
|
||||
self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety)
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
@ -175,11 +171,13 @@ where
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
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()
|
||||
{
|
||||
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[boundary.len() + 2..boundary.len() + 4] == b"--"
|
||||
{
|
||||
@ -513,14 +511,18 @@ where
|
||||
match payload.read_exact(boundary.len() + 4)? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
Async::Ready(Some(chunk)) => {
|
||||
if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--"
|
||||
Async::Ready(Some(mut chunk)) => {
|
||||
if &chunk[..2] == b"\r\n"
|
||||
&& &chunk[2..4] == b"--"
|
||||
&& &chunk[4..] == boundary.as_bytes()
|
||||
{
|
||||
payload.unread_data(chunk);
|
||||
Ok(Async::Ready(None))
|
||||
} 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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,15 @@ impl<'a> Params<'a> {
|
||||
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
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
|
@ -671,10 +671,7 @@ mod tests {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadHelper::new(payload);
|
||||
|
||||
assert_eq!(
|
||||
Async::NotReady,
|
||||
payload.read_until(b"ne").ok().unwrap()
|
||||
);
|
||||
assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap());
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
|
@ -148,6 +148,7 @@ impl Pipeline<(), Inner<()>> {
|
||||
}
|
||||
|
||||
impl<S: 'static, H> Pipeline<S, H> {
|
||||
#[inline]
|
||||
fn is_done(&self) -> bool {
|
||||
match self.1 {
|
||||
PipelineState::None
|
||||
@ -192,7 +193,9 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
|
||||
match self.1 {
|
||||
PipelineState::None => return Ok(Async::Ready(true)),
|
||||
PipelineState::Error => {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into())
|
||||
return Err(
|
||||
io::Error::new(io::ErrorKind::Other, "Internal error").into()
|
||||
)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@ -259,7 +262,7 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
||||
_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 {
|
||||
return Some(RunMiddlewares::init(info, resp));
|
||||
}
|
||||
if info.count == len {
|
||||
let reply = unsafe { &mut *self.hnd.get() }
|
||||
.handle(info.req().clone(), self.htype);
|
||||
return Some(WaitingResponse::init(info, reply));
|
||||
} else {
|
||||
loop {
|
||||
loop {
|
||||
if info.count == len {
|
||||
let reply = unsafe { &mut *self.hnd.get() }
|
||||
.handle(info.req().clone(), self.htype);
|
||||
return Some(WaitingResponse::init(info, reply));
|
||||
} else {
|
||||
match info.mws[info.count as usize].start(info.req_mut()) {
|
||||
Ok(Started::Done) => info.count += 1,
|
||||
Ok(Started::Response(resp)) => {
|
||||
@ -291,13 +294,13 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
||||
continue 'outer;
|
||||
}
|
||||
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> {
|
||||
#[inline]
|
||||
fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H> {
|
||||
if info.count == 0 {
|
||||
return ProcessResponse::init(resp);
|
||||
@ -676,6 +680,7 @@ struct FinishingMiddlewares<S, H> {
|
||||
}
|
||||
|
||||
impl<S: 'static, H> FinishingMiddlewares<S, H> {
|
||||
#[inline]
|
||||
fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> {
|
||||
if info.count == 0 {
|
||||
Completed::init(info)
|
||||
|
67
src/pred.rs
67
src/pred.rs
@ -181,11 +181,7 @@ pub fn Header<S: 'static>(
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct HeaderPredicate<S>(
|
||||
header::HeaderName,
|
||||
header::HeaderValue,
|
||||
PhantomData<S>,
|
||||
);
|
||||
pub struct HeaderPredicate<S>(header::HeaderName, header::HeaderValue, PhantomData<S>);
|
||||
|
||||
impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -228,6 +263,28 @@ mod tests {
|
||||
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]
|
||||
fn test_methods() {
|
||||
let mut req = HttpRequest::new(
|
||||
|
@ -1,9 +1,11 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::Future;
|
||||
use http::{Method, StatusCode};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use error::Error;
|
||||
use handler::{AsyncResult, FromRequest, Handler, Responder};
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
@ -132,10 +134,7 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
/// ```
|
||||
pub fn method(&mut self, method: Method) -> &mut Route<S> {
|
||||
self.routes.push(Route::default());
|
||||
self.routes
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.filter(pred::Method(method))
|
||||
self.routes.last_mut().unwrap().filter(pred::Method(method))
|
||||
}
|
||||
|
||||
/// Register a new route and add handler object.
|
||||
@ -183,6 +182,25 @@ impl<S: 'static> ResourceHandler<S> {
|
||||
self.routes.last_mut().unwrap().with(handler);
|
||||
}
|
||||
|
||||
/// Register a new route and add async handler.
|
||||
///
|
||||
/// This is shortcut for:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// Application::resource("/", |r| r.route().with_async(index)
|
||||
/// ```
|
||||
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
|
||||
where
|
||||
F: Fn(T) -> R + 'static,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
self.routes.push(Route::default());
|
||||
self.routes.last_mut().unwrap().with_async(handler);
|
||||
}
|
||||
|
||||
/// Register a resource middleware
|
||||
///
|
||||
/// This is similar to `App's` middlewares, but
|
||||
|
143
src/route.rs
143
src/route.rs
@ -5,15 +5,19 @@ use std::rc::Rc;
|
||||
use futures::{Async, Future, Poll};
|
||||
|
||||
use error::Error;
|
||||
use handler::{AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler,
|
||||
Responder, RouteHandler, WrapHandler};
|
||||
use handler::{
|
||||
AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder,
|
||||
RouteHandler, WrapHandler,
|
||||
};
|
||||
use http::StatusCode;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::{Finished as MiddlewareFinished, Middleware,
|
||||
Response as MiddlewareResponse, Started as MiddlewareStarted};
|
||||
use middleware::{
|
||||
Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse,
|
||||
Started as MiddlewareStarted,
|
||||
};
|
||||
use pred::Predicate;
|
||||
use with::{ExtractorConfig, With, With2, With3};
|
||||
use with::{ExtractorConfig, With, With2, With3, WithAsync};
|
||||
|
||||
/// Resource route definition
|
||||
///
|
||||
@ -62,13 +66,12 @@ impl<S: 'static> Route<S> {
|
||||
/// # extern crate actix_web;
|
||||
/// # use actix_web::*;
|
||||
/// # fn main() {
|
||||
/// App::new()
|
||||
/// .resource("/path", |r|
|
||||
/// r.route()
|
||||
/// .filter(pred::Get())
|
||||
/// .filter(pred::Header("content-type", "text/plain"))
|
||||
/// .f(|req| HttpResponse::Ok())
|
||||
/// )
|
||||
/// App::new().resource("/path", |r| {
|
||||
/// r.route()
|
||||
/// .filter(pred::Get())
|
||||
/// .filter(pred::Header("content-type", "text/plain"))
|
||||
/// .f(|req| HttpResponse::Ok())
|
||||
/// })
|
||||
/// # .finish();
|
||||
/// # }
|
||||
/// ```
|
||||
@ -111,7 +114,7 @@ impl<S: 'static> Route<S> {
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Path, Result, http};
|
||||
/// use actix_web::{http, App, Path, Result};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
@ -125,8 +128,40 @@ impl<S: 'static> Route<S> {
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with(index),
|
||||
/// ); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It is possible to use tuples for specifing multiple extractors for one
|
||||
/// handler function.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// # use std::collections::HashMap;
|
||||
/// use actix_web::{http, App, Json, Path, Query, Result};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// extract path info using serde
|
||||
/// fn index(
|
||||
/// info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>),
|
||||
/// ) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", info.0.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with(index),
|
||||
/// ); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
|
||||
@ -140,6 +175,49 @@ impl<S: 'static> Route<S> {
|
||||
cfg
|
||||
}
|
||||
|
||||
/// Set async handler function, use request extractor for parameters.
|
||||
/// Also this method needs to be used if your handler function returns
|
||||
/// `impl Future<>`
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{http, App, Error, Path};
|
||||
/// use futures::Future;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// extract path info using serde
|
||||
/// fn index(info: Path<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
|
||||
/// unimplemented!()
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with_async(index),
|
||||
/// ); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with_async<T, F, R, I, E>(&mut self, handler: F) -> ExtractorConfig<S, T>
|
||||
where
|
||||
F: Fn(T) -> R + 'static,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
{
|
||||
let cfg = ExtractorConfig::default();
|
||||
self.h(WithAsync::new(handler, Clone::clone(&cfg)));
|
||||
cfg
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Set handler function, use request extractor for both parameters.
|
||||
///
|
||||
/// ```rust
|
||||
@ -147,7 +225,7 @@ impl<S: 'static> Route<S> {
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Query, Path, Result, http};
|
||||
/// use actix_web::{http, App, Path, Query, Result};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct PParam {
|
||||
@ -166,8 +244,9 @@ impl<S: 'static> Route<S> {
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with2(index),
|
||||
/// ); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with2<T1, T2, F, R>(
|
||||
@ -189,6 +268,7 @@ impl<S: 'static> Route<S> {
|
||||
(cfg1, cfg2)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Set handler function, use request extractor for all parameters.
|
||||
pub fn with3<T1, T2, T3, F, R>(
|
||||
&mut self, handler: F,
|
||||
@ -224,9 +304,7 @@ struct InnerHandler<S>(Rc<UnsafeCell<Box<RouteHandler<S>>>>);
|
||||
impl<S: 'static> InnerHandler<S> {
|
||||
#[inline]
|
||||
fn new<H: Handler<S>>(h: H) -> Self {
|
||||
InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(
|
||||
h,
|
||||
)))))
|
||||
InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(h)))))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -237,9 +315,7 @@ impl<S: 'static> InnerHandler<S> {
|
||||
R: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
{
|
||||
InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(
|
||||
h,
|
||||
)))))
|
||||
InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(h)))))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -352,7 +428,7 @@ impl<S: 'static> StartMiddlewares<S> {
|
||||
_s: PhantomData,
|
||||
})
|
||||
}
|
||||
Err(err) => return FinishingMiddlewares::init(info, err.into()),
|
||||
Err(err) => return RunMiddlewares::init(info, err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -368,11 +444,11 @@ impl<S: 'static> StartMiddlewares<S> {
|
||||
if let Some(resp) = resp {
|
||||
return Some(RunMiddlewares::init(info, resp));
|
||||
}
|
||||
if info.count == len {
|
||||
let reply = info.handler.handle(info.req.clone());
|
||||
return Some(WaitingResponse::init(info, reply));
|
||||
} else {
|
||||
loop {
|
||||
loop {
|
||||
if info.count == len {
|
||||
let reply = info.handler.handle(info.req.clone());
|
||||
return Some(WaitingResponse::init(info, reply));
|
||||
} else {
|
||||
match info.mws[info.count].start(&mut info.req) {
|
||||
Ok(MiddlewareStarted::Done) => info.count += 1,
|
||||
Ok(MiddlewareStarted::Response(resp)) => {
|
||||
@ -383,16 +459,13 @@ impl<S: 'static> StartMiddlewares<S> {
|
||||
continue 'outer;
|
||||
}
|
||||
Err(err) => {
|
||||
return Some(FinishingMiddlewares::init(
|
||||
info,
|
||||
err.into(),
|
||||
))
|
||||
return Some(RunMiddlewares::init(info, err.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => return Some(FinishingMiddlewares::init(info, err.into())),
|
||||
Err(err) => return Some(RunMiddlewares::init(info, err.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +216,8 @@ impl Resource {
|
||||
Ok(re) => re,
|
||||
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()))
|
||||
.collect();
|
||||
PatternType::Dynamic(re, names, len)
|
||||
@ -310,8 +311,16 @@ impl Resource {
|
||||
None
|
||||
}
|
||||
}
|
||||
PatternType::Prefix(ref s) => if path.starts_with(s) {
|
||||
PatternType::Prefix(ref s) => if path == s {
|
||||
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 {
|
||||
None
|
||||
},
|
||||
@ -326,22 +335,41 @@ impl Resource {
|
||||
U: IntoIterator<Item = I>,
|
||||
I: AsRef<str>,
|
||||
{
|
||||
let mut iter = elements.into_iter();
|
||||
let mut path = if self.rtp != ResourceType::External {
|
||||
format!("{}/", router.prefix())
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
for el in &self.elements {
|
||||
match *el {
|
||||
PatternElement::Str(ref s) => path.push_str(s),
|
||||
PatternElement::Var(_) => {
|
||||
if let Some(val) = iter.next() {
|
||||
path.push_str(val.as_ref())
|
||||
} else {
|
||||
return Err(UrlGenerationError::NotEnoughElements);
|
||||
let mut path = match self.tp {
|
||||
PatternType::Prefix(ref p) => p.to_owned(),
|
||||
PatternType::Static(ref p) => p.to_owned(),
|
||||
PatternType::Dynamic(..) => {
|
||||
let mut path = String::new();
|
||||
let mut iter = elements.into_iter();
|
||||
for el in &self.elements {
|
||||
match *el {
|
||||
PatternElement::Str(ref s) => path.push_str(s),
|
||||
PatternElement::Var(_) => {
|
||||
if let Some(val) = iter.next() {
|
||||
path.push_str(val.as_ref())
|
||||
} else {
|
||||
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)
|
||||
@ -406,6 +434,10 @@ impl Resource {
|
||||
}
|
||||
}
|
||||
|
||||
if !el.is_empty() {
|
||||
elems.push(PatternElement::Str(el.clone()));
|
||||
}
|
||||
|
||||
let re = if is_dynamic {
|
||||
if !for_prefix {
|
||||
re1.push('$');
|
||||
@ -438,12 +470,9 @@ mod tests {
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_recognizer() {
|
||||
fn test_recognizer10() {
|
||||
let routes = vec![
|
||||
(
|
||||
Resource::new("", "/name"),
|
||||
Some(ResourceHandler::default()),
|
||||
),
|
||||
(Resource::new("", "/name"), Some(ResourceHandler::default())),
|
||||
(
|
||||
Resource::new("", "/name/{val}"),
|
||||
Some(ResourceHandler::default()),
|
||||
@ -464,6 +493,10 @@ mod tests {
|
||||
Resource::new("", "/v/{tail:.*}"),
|
||||
Some(ResourceHandler::default()),
|
||||
),
|
||||
(
|
||||
Resource::new("", "/test2/{test}.html"),
|
||||
Some(ResourceHandler::default()),
|
||||
),
|
||||
(
|
||||
Resource::new("", "{test}/index.html"),
|
||||
Some(ResourceHandler::default()),
|
||||
@ -501,8 +534,12 @@ mod tests {
|
||||
"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!(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");
|
||||
}
|
||||
|
||||
@ -530,10 +567,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_recognizer_with_prefix() {
|
||||
let routes = vec![
|
||||
(
|
||||
Resource::new("", "/name"),
|
||||
Some(ResourceHandler::default()),
|
||||
),
|
||||
(Resource::new("", "/name"), Some(ResourceHandler::default())),
|
||||
(
|
||||
Resource::new("", "/name/{val}"),
|
||||
Some(ResourceHandler::default()),
|
||||
@ -554,10 +588,7 @@ mod tests {
|
||||
|
||||
// same patterns
|
||||
let routes = vec![
|
||||
(
|
||||
Resource::new("", "/name"),
|
||||
Some(ResourceHandler::default()),
|
||||
),
|
||||
(Resource::new("", "/name"), Some(ResourceHandler::default())),
|
||||
(
|
||||
Resource::new("", "/name/{val}"),
|
||||
Some(ResourceHandler::default()),
|
||||
|
297
src/scope.rs
297
src/scope.rs
@ -10,8 +10,10 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler
|
||||
use http::Method;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::{Finished as MiddlewareFinished, Middleware,
|
||||
Response as MiddlewareResponse, Started as MiddlewareStarted};
|
||||
use middleware::{
|
||||
Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse,
|
||||
Started as MiddlewareStarted,
|
||||
};
|
||||
use pred::Predicate;
|
||||
use resource::ResourceHandler;
|
||||
use router::Resource;
|
||||
@ -36,12 +38,12 @@ type NestedInfo<S> = (Resource, Route<S>, Vec<Box<Predicate<S>>>);
|
||||
/// use actix_web::{http, App, HttpRequest, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .scope("/{project_id}/", |scope| {
|
||||
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||
/// });
|
||||
/// let app = App::new().scope("/{project_id}/", |scope| {
|
||||
/// scope
|
||||
/// .resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
@ -87,13 +89,14 @@ impl<S: 'static> Scope<S> {
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .scope("/app", |scope| {
|
||||
/// scope.filter(pred::Header("content-type", "text/plain"))
|
||||
/// .route("/test1", http::Method::GET, index)
|
||||
/// .route("/test2", http::Method::POST,
|
||||
/// |_: HttpRequest| HttpResponse::MethodNotAllowed())
|
||||
/// });
|
||||
/// let app = App::new().scope("/app", |scope| {
|
||||
/// scope
|
||||
/// .filter(pred::Header("content-type", "text/plain"))
|
||||
/// .route("/test1", http::Method::GET, index)
|
||||
/// .route("/test2", http::Method::POST, |_: HttpRequest| {
|
||||
/// HttpResponse::MethodNotAllowed()
|
||||
/// })
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> Self {
|
||||
@ -114,12 +117,11 @@ impl<S: 'static> Scope<S> {
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .scope("/app", |scope| {
|
||||
/// scope.with_state("/state2", AppState, |scope| {
|
||||
/// scope.resource("/test1", |r| r.f(index))
|
||||
/// })
|
||||
/// });
|
||||
/// let app = App::new().scope("/app", |scope| {
|
||||
/// scope.with_state("/state2", AppState, |scope| {
|
||||
/// scope.resource("/test1", |r| r.f(index))
|
||||
/// })
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
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 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 filters: Vec<Box<Predicate<S>>> = vec![Box::new(FiltersWrapper {
|
||||
state: Rc::clone(&state),
|
||||
@ -168,12 +162,9 @@ impl<S: 'static> Scope<S> {
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::with_state(AppState)
|
||||
/// .scope("/app", |scope| {
|
||||
/// scope.nested("/v1", |scope| {
|
||||
/// scope.resource("/test1", |r| r.f(index))
|
||||
/// })
|
||||
/// });
|
||||
/// let app = App::with_state(AppState).scope("/app", |scope| {
|
||||
/// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.f(index)))
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
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 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();
|
||||
self.nested.push((
|
||||
Resource::prefix("", &path),
|
||||
@ -225,12 +208,13 @@ impl<S: 'static> Scope<S> {
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .scope("/app", |scope| {
|
||||
/// scope.route("/test1", http::Method::GET, index)
|
||||
/// .route("/test2", http::Method::POST,
|
||||
/// |_: HttpRequest| HttpResponse::MethodNotAllowed())
|
||||
/// });
|
||||
/// let app = App::new().scope("/app", |scope| {
|
||||
/// scope.route("/test1", http::Method::GET, index).route(
|
||||
/// "/test2",
|
||||
/// http::Method::POST,
|
||||
/// |_: HttpRequest| HttpResponse::MethodNotAllowed(),
|
||||
/// )
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
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();
|
||||
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)
|
||||
.expect("Can not use after configuration")
|
||||
.push((pattern, Rc::new(UnsafeCell::new(handler))));
|
||||
@ -270,17 +259,16 @@ impl<S: 'static> Scope<S> {
|
||||
/// use actix_web::*;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .scope("/api", |scope| {
|
||||
/// scope.resource("/users/{userid}/{friend}", |r| {
|
||||
/// r.get().f(|_| HttpResponse::Ok());
|
||||
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// r.route()
|
||||
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
|
||||
/// .filter(pred::Header("Content-Type", "text/plain"))
|
||||
/// .f(|_| HttpResponse::Ok())
|
||||
/// })
|
||||
/// });
|
||||
/// let app = App::new().scope("/api", |scope| {
|
||||
/// scope.resource("/users/{userid}/{friend}", |r| {
|
||||
/// r.get().f(|_| HttpResponse::Ok());
|
||||
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// r.route()
|
||||
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
|
||||
/// .filter(pred::Header("Content-Type", "text/plain"))
|
||||
/// .f(|_| HttpResponse::Ok())
|
||||
/// })
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
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();
|
||||
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)
|
||||
.expect("Can not use after configuration")
|
||||
.push((pattern, Rc::new(UnsafeCell::new(handler))));
|
||||
@ -327,13 +320,13 @@ impl<S: 'static> Scope<S> {
|
||||
impl<S: 'static> RouteHandler<S> for Scope<S> {
|
||||
fn handle(&mut self, mut req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
||||
let path = unsafe { &*(&req.match_info()["tail"] as *const _) };
|
||||
let path = if path == "" { "/" } else { path };
|
||||
|
||||
// recognize resources
|
||||
for &(ref pattern, ref resource) in self.resources.iter() {
|
||||
if pattern.match_with_params(path, req.match_info_mut()) {
|
||||
let default = unsafe { &mut *self.default.as_ref().get() };
|
||||
|
||||
req.match_info_mut().remove("tail");
|
||||
if self.middlewares.is_empty() {
|
||||
let resource = unsafe { &mut *resource.get() };
|
||||
return resource.handle(req, Some(default));
|
||||
@ -361,16 +354,12 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
let prefix_len = len + prefix_len - 1;
|
||||
let prefix_len = len + prefix_len;
|
||||
let path: &'static str =
|
||||
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
||||
|
||||
req.set_prefix_len(prefix_len as u16);
|
||||
if path.is_empty() {
|
||||
req.match_info_mut().set("tail", "/");
|
||||
} else {
|
||||
req.match_info_mut().set("tail", path);
|
||||
}
|
||||
req.match_info_mut().set("tail", path);
|
||||
|
||||
let hnd: &mut RouteHandler<_> =
|
||||
unsafe { (&mut *(handler.get())).as_mut() };
|
||||
@ -400,8 +389,7 @@ struct Wrapper<S: 'static> {
|
||||
|
||||
impl<S: 'static, S2: 'static> RouteHandler<S2> for Wrapper<S> {
|
||||
fn handle(&mut self, req: HttpRequest<S2>) -> AsyncResult<HttpResponse> {
|
||||
self.scope
|
||||
.handle(req.change_state(Rc::clone(&self.state)))
|
||||
self.scope.handle(req.change_state(Rc::clone(&self.state)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -527,7 +515,7 @@ impl<S: 'static> StartMiddlewares<S> {
|
||||
_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 {
|
||||
return Some(RunMiddlewares::init(info, resp));
|
||||
}
|
||||
if info.count == len {
|
||||
let resource = unsafe { &mut *info.resource.get() };
|
||||
let reply = if let Some(ref default) = info.default {
|
||||
let d = unsafe { &mut *default.as_ref().get() };
|
||||
resource.handle(info.req.clone(), Some(d))
|
||||
loop {
|
||||
if info.count == len {
|
||||
let resource = unsafe { &mut *info.resource.get() };
|
||||
let reply = if let Some(ref default) = info.default {
|
||||
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 {
|
||||
resource.handle(info.req.clone(), None)
|
||||
};
|
||||
return Some(WaitingResponse::init(info, reply));
|
||||
} else {
|
||||
loop {
|
||||
match info.mws[info.count].start(&mut info.req) {
|
||||
Ok(MiddlewareStarted::Done) => info.count += 1,
|
||||
Ok(MiddlewareStarted::Response(resp)) => {
|
||||
@ -563,12 +551,14 @@ impl<S: 'static> StartMiddlewares<S> {
|
||||
self.fut = Some(fut);
|
||||
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);
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn test_scope_route() {
|
||||
let mut app = App::new()
|
||||
@ -883,6 +926,71 @@ mod tests {
|
||||
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]
|
||||
fn test_scope_with_state_filter() {
|
||||
struct State;
|
||||
@ -925,6 +1033,27 @@ mod tests {
|
||||
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]
|
||||
fn test_nested_scope_filter() {
|
||||
let mut app = App::new()
|
||||
|
@ -61,7 +61,7 @@ where
|
||||
settings,
|
||||
peer,
|
||||
io,
|
||||
BytesMut::with_capacity(4096),
|
||||
BytesMut::with_capacity(8192),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@ -93,12 +93,12 @@ where
|
||||
let el = self as *mut _;
|
||||
self.node = Some(Node::new(el));
|
||||
let _ = match self.proto {
|
||||
Some(HttpProtocol::H1(ref mut h1)) => self.node
|
||||
.as_ref()
|
||||
.map(|n| h1.settings().head().insert(n)),
|
||||
Some(HttpProtocol::H2(ref mut h2)) => self.node
|
||||
.as_ref()
|
||||
.map(|n| h2.settings().head().insert(n)),
|
||||
Some(HttpProtocol::H1(ref mut h1)) => {
|
||||
self.node.as_ref().map(|n| h1.settings().head().insert(n))
|
||||
}
|
||||
Some(HttpProtocol::H2(ref mut h2)) => {
|
||||
self.node.as_ref().map(|n| h2.settings().head().insert(n))
|
||||
}
|
||||
Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => {
|
||||
self.node.as_ref().map(|n| settings.head().insert(n))
|
||||
}
|
||||
@ -168,9 +168,8 @@ where
|
||||
if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() {
|
||||
match kind {
|
||||
ProtocolKind::Http1 => {
|
||||
self.proto = Some(HttpProtocol::H1(h1::Http1::new(
|
||||
settings, io, addr, buf,
|
||||
)));
|
||||
self.proto =
|
||||
Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf)));
|
||||
return self.poll();
|
||||
}
|
||||
ProtocolKind::Http2 => {
|
||||
|
@ -12,8 +12,10 @@ use flate2::read::GzDecoder;
|
||||
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
|
||||
#[cfg(feature = "flate2")]
|
||||
use flate2::Compression;
|
||||
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING,
|
||||
CONTENT_LENGTH, TRANSFER_ENCODING};
|
||||
use http::header::{
|
||||
HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
|
||||
TRANSFER_ENCODING,
|
||||
};
|
||||
use http::{HttpTryFrom, Method, Version};
|
||||
|
||||
use body::{Binary, Body};
|
||||
@ -31,6 +33,7 @@ pub(crate) enum PayloadType {
|
||||
}
|
||||
|
||||
impl PayloadType {
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
|
||||
// check 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))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
|
||||
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
|
||||
PayloadType::Sender(sender)
|
||||
}
|
||||
}
|
||||
|
||||
impl PayloadWriter for PayloadType {
|
||||
@ -383,17 +391,21 @@ impl ContentEncoder {
|
||||
) -> ContentEncoder {
|
||||
let version = resp.version().unwrap_or_else(|| req.version);
|
||||
let is_head = req.method == Method::HEAD;
|
||||
let mut body = resp.replace_body(Body::Empty);
|
||||
let has_body = match body {
|
||||
Body::Empty => false,
|
||||
Body::Binary(ref bin) => {
|
||||
!(response_encoding == ContentEncoding::Auto && bin.len() < 96)
|
||||
let mut len = 0;
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
|
||||
let has_body = match resp.body() {
|
||||
&Body::Empty => false,
|
||||
&Body::Binary(ref bin) => {
|
||||
len = bin.len();
|
||||
!(response_encoding == ContentEncoding::Auto && len < 96)
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// Enable content encoding only if response does not contain Content-Encoding
|
||||
// header
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
let mut encoding = if has_body {
|
||||
let encoding = match response_encoding {
|
||||
ContentEncoding::Auto => {
|
||||
@ -420,47 +432,59 @@ impl ContentEncoder {
|
||||
} else {
|
||||
ContentEncoding::Identity
|
||||
};
|
||||
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
|
||||
let mut encoding = ContentEncoding::Identity;
|
||||
|
||||
let mut transfer = match body {
|
||||
Body::Empty => {
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
|
||||
let mut transfer = match resp.body() {
|
||||
&Body::Empty => {
|
||||
if req.method != Method::HEAD {
|
||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||
}
|
||||
TransferEncoding::length(0, buf)
|
||||
}
|
||||
Body::Binary(ref mut bytes) => {
|
||||
if !(encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
&Body::Binary(_) => {
|
||||
#[cfg(any(feature = "brotli", feature = "flate2"))]
|
||||
{
|
||||
let tmp = SharedBytes::default();
|
||||
let transfer = TransferEncoding::eof(tmp.clone());
|
||||
let mut enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||
DeflateEncoder::new(transfer, Compression::fast()),
|
||||
),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
|
||||
transfer,
|
||||
Compression::fast(),
|
||||
)),
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
|
||||
}
|
||||
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
|
||||
ContentEncoding::Auto => unreachable!(),
|
||||
};
|
||||
// TODO return error!
|
||||
let _ = enc.write(bytes.clone());
|
||||
let _ = enc.write_eof();
|
||||
if !(encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
let tmp = SharedBytes::default();
|
||||
let transfer = TransferEncoding::eof(tmp.clone());
|
||||
let mut enc = match encoding {
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||
DeflateEncoder::new(transfer, Compression::fast()),
|
||||
),
|
||||
#[cfg(feature = "flate2")]
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
||||
GzEncoder::new(transfer, Compression::fast()),
|
||||
),
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoding::Br => {
|
||||
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
|
||||
}
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
*bytes = Binary::from(tmp.take());
|
||||
encoding = ContentEncoding::Identity;
|
||||
let bin = resp.replace_body(Body::Empty).binary();
|
||||
|
||||
// TODO return error!
|
||||
let _ = enc.write(bin);
|
||||
let _ = enc.write_eof();
|
||||
let body = tmp.take();
|
||||
len = body.len();
|
||||
|
||||
encoding = ContentEncoding::Identity;
|
||||
resp.replace_body(Binary::from(body));
|
||||
}
|
||||
}
|
||||
|
||||
if is_head {
|
||||
let mut b = BytesMut::new();
|
||||
let _ = write!(b, "{}", bytes.len());
|
||||
let _ = write!(b, "{}", len);
|
||||
resp.headers_mut().insert(
|
||||
CONTENT_LENGTH,
|
||||
HeaderValue::try_from(b.freeze()).unwrap(),
|
||||
@ -470,7 +494,7 @@ impl ContentEncoder {
|
||||
}
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
Body::Streaming(_) | Body::Actor(_) => {
|
||||
&Body::Streaming(_) | &Body::Actor(_) => {
|
||||
if resp.upgrade() {
|
||||
if version == Version::HTTP_2 {
|
||||
error!("Connection upgrade is forbidden for HTTP/2");
|
||||
@ -481,15 +505,19 @@ impl ContentEncoder {
|
||||
}
|
||||
TransferEncoding::eof(buf)
|
||||
} else {
|
||||
if !(encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||
}
|
||||
ContentEncoder::streaming_encoding(buf, version, resp)
|
||||
}
|
||||
}
|
||||
};
|
||||
//
|
||||
// check for head response
|
||||
if is_head {
|
||||
resp.set_body(Body::Empty);
|
||||
transfer.kind = TransferEncodingKind::Length(0);
|
||||
} else {
|
||||
resp.replace_body(body);
|
||||
}
|
||||
|
||||
match encoding {
|
||||
@ -590,7 +618,7 @@ impl ContentEncoder {
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(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(
|
||||
self,
|
||||
ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())),
|
||||
@ -602,7 +630,7 @@ impl ContentEncoder {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(())
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
@ -611,7 +639,7 @@ impl ContentEncoder {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(())
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
@ -620,14 +648,14 @@ impl ContentEncoder {
|
||||
Ok(mut writer) => {
|
||||
writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(())
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
ContentEncoder::Identity(mut writer) => {
|
||||
writer.encode_eof();
|
||||
let res = writer.encode_eof();
|
||||
*self = ContentEncoder::Identity(writer);
|
||||
Ok(())
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -763,8 +791,7 @@ impl TransferEncoding {
|
||||
return Ok(*remaining == 0);
|
||||
}
|
||||
let len = cmp::min(*remaining, msg.len() as u64);
|
||||
self.buffer
|
||||
.extend(msg.take().split_to(len as usize).into());
|
||||
self.buffer.extend(msg.take().split_to(len as usize).into());
|
||||
|
||||
*remaining -= len as u64;
|
||||
Ok(*remaining == 0)
|
||||
@ -777,14 +804,16 @@ impl TransferEncoding {
|
||||
|
||||
/// Encode eof. Return `EOF` state of encoder
|
||||
#[inline]
|
||||
pub fn encode_eof(&mut self) {
|
||||
pub fn encode_eof(&mut self) -> bool {
|
||||
match self.kind {
|
||||
TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (),
|
||||
TransferEncodingKind::Eof => true,
|
||||
TransferEncodingKind::Length(rem) => rem == 0,
|
||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||
if !*eof {
|
||||
*eof = true;
|
||||
self.buffer.extend_from_slice(b"0\r\n\r\n");
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -848,15 +877,13 @@ impl AcceptEncoding {
|
||||
Err(_) => 0.0,
|
||||
},
|
||||
};
|
||||
Some(AcceptEncoding {
|
||||
encoding,
|
||||
quality,
|
||||
})
|
||||
Some(AcceptEncoding { encoding, quality })
|
||||
}
|
||||
|
||||
/// Parse a raw Accept-Encoding header value into an ordered list.
|
||||
pub fn parse(raw: &str) -> ContentEncoding {
|
||||
let mut encodings: Vec<_> = raw.replace(' ', "")
|
||||
let mut encodings: Vec<_> = raw
|
||||
.replace(' ', "")
|
||||
.split(',')
|
||||
.map(|l| AcceptEncoding::new(l))
|
||||
.collect();
|
||||
@ -879,9 +906,7 @@ mod tests {
|
||||
fn test_chunked_te() {
|
||||
let bytes = SharedBytes::default();
|
||||
let mut enc = TransferEncoding::chunked(bytes.clone());
|
||||
assert!(!enc.encode(Binary::from(b"test".as_ref()))
|
||||
.ok()
|
||||
.unwrap());
|
||||
assert!(!enc.encode(Binary::from(b"test".as_ref())).ok().unwrap());
|
||||
assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap());
|
||||
assert_eq!(
|
||||
bytes.get_mut().take().freeze(),
|
||||
|
188
src/server/h1.rs
188
src/server/h1.rs
@ -146,10 +146,12 @@ where
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// read data from stream
|
||||
pub fn poll_io(&mut self) {
|
||||
// read io from socket
|
||||
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() {
|
||||
// notify all tasks
|
||||
@ -158,7 +160,6 @@ where
|
||||
entry.pipe.disconnected()
|
||||
}
|
||||
// kill keepalive
|
||||
self.flags.remove(Flags::KEEPALIVE);
|
||||
self.keepalive_timer.take();
|
||||
|
||||
// on parse error, stop reading stream but tasks need to be
|
||||
@ -205,10 +206,9 @@ where
|
||||
self.stream.reset();
|
||||
|
||||
if ready {
|
||||
item.flags
|
||||
.insert(EntryFlags::EOF | EntryFlags::FINISHED);
|
||||
item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED);
|
||||
} else {
|
||||
item.flags.insert(EntryFlags::FINISHED);
|
||||
item.flags.insert(EntryFlags::EOF);
|
||||
}
|
||||
}
|
||||
// no more IO for this iteration
|
||||
@ -270,7 +270,12 @@ where
|
||||
debug!("Error sending data: {}", 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
|
||||
for h in self.settings.handlers().iter_mut() {
|
||||
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 {
|
||||
pipe,
|
||||
flags: EntryFlags::empty(),
|
||||
@ -347,6 +381,7 @@ where
|
||||
} else {
|
||||
error!("Internal server error: unexpected payload chunk");
|
||||
self.flags.insert(Flags::ERROR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Some(Message::Eof)) => {
|
||||
@ -355,6 +390,7 @@ where
|
||||
} else {
|
||||
error!("Internal server error: unexpected eof");
|
||||
self.flags.insert(Flags::ERROR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(None) => break,
|
||||
@ -367,6 +403,7 @@ where
|
||||
};
|
||||
payload.set_error(e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -398,8 +435,12 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use std::net::Shutdown;
|
||||
use std::{cmp, time};
|
||||
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use http::{Method, Version};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::*;
|
||||
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]
|
||||
fn test_parse() {
|
||||
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.method(), Method::GET);
|
||||
assert_eq!(req.path(), "/test");
|
||||
assert_eq!(
|
||||
req.headers().get("test").unwrap().as_bytes(),
|
||||
b"value"
|
||||
);
|
||||
assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value");
|
||||
}
|
||||
Ok(_) | Err(_) => unreachable!("Error during parsing http request"),
|
||||
}
|
||||
@ -635,7 +768,8 @@ mod tests {
|
||||
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
|
||||
let req = HttpRequest::from_message(msg.message());
|
||||
|
||||
let val: Vec<_> = req.headers()
|
||||
let val: Vec<_> = req
|
||||
.headers()
|
||||
.get_all("Set-Cookie")
|
||||
.iter()
|
||||
.map(|v| v.to_str().unwrap().to_owned())
|
||||
@ -918,13 +1052,7 @@ mod tests {
|
||||
.as_ref(),
|
||||
b"line"
|
||||
);
|
||||
assert!(
|
||||
reader
|
||||
.decode(&mut buf, &settings)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.eof()
|
||||
);
|
||||
assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1005,13 +1133,7 @@ mod tests {
|
||||
assert!(reader.decode(&mut buf, &settings).unwrap().is_none());
|
||||
|
||||
buf.extend(b"\r\n");
|
||||
assert!(
|
||||
reader
|
||||
.decode(&mut buf, &settings)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.eof()
|
||||
);
|
||||
assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1029,17 +1151,9 @@ mod tests {
|
||||
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")
|
||||
let chunk = reader
|
||||
.decode(&mut buf, &settings)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.chunk();
|
||||
let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk();
|
||||
assert_eq!(chunk, Bytes::from_static(b"data"));
|
||||
let chunk = reader
|
||||
.decode(&mut buf, &settings)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.chunk();
|
||||
let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk();
|
||||
assert_eq!(chunk, Bytes::from_static(b"line"));
|
||||
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
|
||||
assert!(msg.eof());
|
||||
|
@ -60,22 +60,16 @@ impl H1Decoder {
|
||||
}
|
||||
}
|
||||
|
||||
match self.parse_message(src, settings)
|
||||
match self
|
||||
.parse_message(src, settings)
|
||||
.map_err(DecoderError::Error)?
|
||||
{
|
||||
Async::Ready((msg, decoder)) => {
|
||||
if let Some(decoder) = decoder {
|
||||
self.decoder = Some(decoder);
|
||||
Ok(Some(Message::Message {
|
||||
msg,
|
||||
payload: true,
|
||||
}))
|
||||
} else {
|
||||
Ok(Some(Message::Message {
|
||||
msg,
|
||||
payload: false,
|
||||
}))
|
||||
}
|
||||
self.decoder = decoder;
|
||||
Ok(Some(Message::Message {
|
||||
msg,
|
||||
payload: self.decoder.is_some(),
|
||||
}))
|
||||
}
|
||||
Async::NotReady => {
|
||||
if src.len() >= MAX_BUFFER_SIZE {
|
||||
@ -148,7 +142,7 @@ impl H1Decoder {
|
||||
header::CONTENT_LENGTH => {
|
||||
if let Ok(s) = value.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
content_length = Some(len)
|
||||
content_length = Some(len);
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", len);
|
||||
return Err(ParseError::Header);
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
|
||||
|
||||
use bytes::BufMut;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use std::io;
|
||||
use std::rc::Rc;
|
||||
@ -45,7 +45,7 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
|
||||
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
|
||||
) -> H1Writer<T, H> {
|
||||
H1Writer {
|
||||
flags: Flags::empty(),
|
||||
flags: Flags::KEEPALIVE,
|
||||
encoder: ContentEncoder::empty(buf.clone()),
|
||||
written: 0,
|
||||
headers_size: 0,
|
||||
@ -62,7 +62,7 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.written = 0;
|
||||
self.flags = Flags::empty();
|
||||
self.flags = Flags::KEEPALIVE;
|
||||
}
|
||||
|
||||
pub fn disconnected(&mut self) {
|
||||
@ -100,6 +100,16 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
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(
|
||||
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
|
||||
encoding: ContentEncoding,
|
||||
@ -108,9 +118,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
self.encoder =
|
||||
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
|
||||
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
|
||||
self.flags.insert(Flags::STARTED | Flags::KEEPALIVE);
|
||||
self.flags = Flags::STARTED | Flags::KEEPALIVE;
|
||||
} else {
|
||||
self.flags.insert(Flags::STARTED);
|
||||
self.flags = Flags::STARTED;
|
||||
}
|
||||
|
||||
// Connection upgrade
|
||||
@ -138,7 +148,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
let reason = msg.reason().as_bytes();
|
||||
let mut is_bin = if let Body::Binary(ref bytes) = body {
|
||||
buffer.reserve(
|
||||
256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()
|
||||
256
|
||||
+ msg.headers().len() * AVERAGE_HEADER_SIZE
|
||||
+ bytes.len()
|
||||
+ reason.len(),
|
||||
);
|
||||
true
|
||||
@ -255,9 +267,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
}
|
||||
|
||||
fn write_eof(&mut self) -> io::Result<WriterState> {
|
||||
self.encoder.write_eof()?;
|
||||
|
||||
if !self.encoder.is_eof() {
|
||||
if !self.encoder.write_eof()? {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"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 _) };
|
||||
let written = self.write_data(buf)?;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ where
|
||||
flags: Flags::empty(),
|
||||
tasks: VecDeque::new(),
|
||||
state: State::Handshake(server::handshake(IoWrapper {
|
||||
unread: Some(buf),
|
||||
unread: if buf.is_empty() { None } else { Some(buf) },
|
||||
inner: io,
|
||||
})),
|
||||
keepalive_timer: None,
|
||||
@ -133,7 +133,8 @@ where
|
||||
Err(err) => {
|
||||
error!("Unhandled error: {}", err);
|
||||
item.flags.insert(
|
||||
EntryFlags::EOF | EntryFlags::ERROR
|
||||
EntryFlags::EOF
|
||||
| EntryFlags::ERROR
|
||||
| EntryFlags::WRITE_DONE,
|
||||
);
|
||||
item.stream.reset(Reason::INTERNAL_ERROR);
|
||||
@ -150,7 +151,8 @@ where
|
||||
}
|
||||
Err(err) => {
|
||||
item.flags.insert(
|
||||
EntryFlags::ERROR | EntryFlags::WRITE_DONE
|
||||
EntryFlags::ERROR
|
||||
| EntryFlags::WRITE_DONE
|
||||
| EntryFlags::FINISHED,
|
||||
);
|
||||
error!("Unhandled error: {}", err);
|
||||
@ -248,7 +250,8 @@ where
|
||||
if not_ready {
|
||||
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));
|
||||
} else {
|
||||
return Ok(Async::NotReady);
|
||||
|
@ -71,6 +71,16 @@ impl<H: 'static> Writer for H2Writer<H> {
|
||||
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(
|
||||
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
|
||||
encoding: ContentEncoding,
|
||||
@ -79,9 +89,6 @@ impl<H: 'static> Writer for H2Writer<H> {
|
||||
self.flags.insert(Flags::STARTED);
|
||||
self.encoder =
|
||||
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
|
||||
if let Body::Empty = *msg.body() {
|
||||
self.flags.insert(Flags::EOF);
|
||||
}
|
||||
|
||||
// http2 specific
|
||||
msg.headers_mut().remove(CONNECTION);
|
||||
@ -98,15 +105,22 @@ impl<H: 'static> Writer for H2Writer<H> {
|
||||
let body = msg.replace_body(Body::Empty);
|
||||
match body {
|
||||
Body::Binary(ref bytes) => {
|
||||
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(),
|
||||
);
|
||||
if bytes.is_empty() {
|
||||
msg.headers_mut()
|
||||
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
|
||||
self.flags.insert(Flags::EOF);
|
||||
} else {
|
||||
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 => {
|
||||
self.flags.insert(Flags::EOF);
|
||||
msg.headers_mut()
|
||||
.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());
|
||||
}
|
||||
|
||||
match self.respond
|
||||
match self
|
||||
.respond
|
||||
.send_response(resp, self.flags.contains(Flags::EOF))
|
||||
{
|
||||
Ok(stream) => self.stream = Some(stream),
|
||||
@ -130,14 +145,18 @@ impl<H: 'static> Writer for H2Writer<H> {
|
||||
trace!("Response: {:?}", msg);
|
||||
|
||||
if let Body::Binary(bytes) = body {
|
||||
self.flags.insert(Flags::EOF);
|
||||
self.written = bytes.len() as u64;
|
||||
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));
|
||||
if bytes.is_empty() {
|
||||
Ok(WriterState::Done)
|
||||
} else {
|
||||
self.flags.insert(Flags::EOF);
|
||||
self.written = bytes.len() as u64;
|
||||
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 {
|
||||
msg.replace_body(body);
|
||||
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> {
|
||||
self.encoder.write_eof()?;
|
||||
|
||||
self.flags.insert(Flags::EOF);
|
||||
if !self.encoder.is_eof() {
|
||||
if !self.encoder.write_eof()? {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Last payload item, but eof is not reached",
|
||||
|
@ -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
|
||||
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 {
|
||||
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',
|
||||
@ -251,63 +251,33 @@ mod tests {
|
||||
let mut bytes = BytesMut::new();
|
||||
bytes.reserve(50);
|
||||
write_content_length(0, &mut bytes);
|
||||
assert_eq!(
|
||||
bytes.take().freeze(),
|
||||
b"\r\ncontent-length: 0\r\n"[..]
|
||||
);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(9, &mut bytes);
|
||||
assert_eq!(
|
||||
bytes.take().freeze(),
|
||||
b"\r\ncontent-length: 9\r\n"[..]
|
||||
);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(10, &mut bytes);
|
||||
assert_eq!(
|
||||
bytes.take().freeze(),
|
||||
b"\r\ncontent-length: 10\r\n"[..]
|
||||
);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(99, &mut bytes);
|
||||
assert_eq!(
|
||||
bytes.take().freeze(),
|
||||
b"\r\ncontent-length: 99\r\n"[..]
|
||||
);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(100, &mut bytes);
|
||||
assert_eq!(
|
||||
bytes.take().freeze(),
|
||||
b"\r\ncontent-length: 100\r\n"[..]
|
||||
);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(101, &mut bytes);
|
||||
assert_eq!(
|
||||
bytes.take().freeze(),
|
||||
b"\r\ncontent-length: 101\r\n"[..]
|
||||
);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(998, &mut bytes);
|
||||
assert_eq!(
|
||||
bytes.take().freeze(),
|
||||
b"\r\ncontent-length: 998\r\n"[..]
|
||||
);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(1000, &mut bytes);
|
||||
assert_eq!(
|
||||
bytes.take().freeze(),
|
||||
b"\r\ncontent-length: 1000\r\n"[..]
|
||||
);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(1001, &mut bytes);
|
||||
assert_eq!(
|
||||
bytes.take().freeze(),
|
||||
b"\r\ncontent-length: 1001\r\n"[..]
|
||||
);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
|
||||
bytes.reserve(50);
|
||||
write_content_length(5909, &mut bytes);
|
||||
assert_eq!(
|
||||
bytes.take().freeze(),
|
||||
b"\r\ncontent-length: 5909\r\n"[..]
|
||||
);
|
||||
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ use std::net::Shutdown;
|
||||
use std::{io, time};
|
||||
|
||||
use actix;
|
||||
use futures::Poll;
|
||||
use bytes::BytesMut;
|
||||
use futures::{Async, Poll};
|
||||
use tokio_core::net::TcpStream;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
@ -24,6 +25,9 @@ mod worker;
|
||||
pub use self::settings::ServerSettings;
|
||||
pub use self::srv::HttpServer;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::helpers::write_content_length;
|
||||
|
||||
use body::Binary;
|
||||
use error::Error;
|
||||
use header::ContentEncoding;
|
||||
@ -132,13 +136,15 @@ impl HttpHandler for Box<HttpHandler> {
|
||||
#[doc(hidden)]
|
||||
pub trait HttpHandlerTask {
|
||||
/// 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
|
||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
|
||||
|
||||
/// Connection is disconnected
|
||||
fn disconnected(&mut self);
|
||||
fn disconnected(&mut self) {}
|
||||
}
|
||||
|
||||
/// Conversion helper trait
|
||||
@ -168,8 +174,16 @@ pub enum WriterState {
|
||||
#[doc(hidden)]
|
||||
/// Stream writer
|
||||
pub trait Writer {
|
||||
/// number of bytes written to the stream
|
||||
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(
|
||||
&mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse,
|
||||
encoding: ContentEncoding,
|
||||
|
@ -16,7 +16,6 @@ use body::Body;
|
||||
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
|
||||
|
||||
/// Various server settings
|
||||
#[derive(Clone)]
|
||||
pub struct ServerSettings {
|
||||
addr: Option<net::SocketAddr>,
|
||||
secure: bool,
|
||||
@ -28,6 +27,18 @@ pub struct ServerSettings {
|
||||
unsafe impl Sync 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 {
|
||||
cpu_pool: UnsafeCell<Option<CpuPool>>,
|
||||
}
|
||||
@ -255,10 +266,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_date_len() {
|
||||
assert_eq!(
|
||||
DATE_VALUE_LENGTH,
|
||||
"Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||
);
|
||||
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -25,6 +25,20 @@ use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker};
|
||||
use super::{IntoHttpHandler, IoStream, KeepAlive};
|
||||
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
|
||||
pub struct HttpServer<H>
|
||||
where
|
||||
@ -197,6 +211,19 @@ where
|
||||
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
|
||||
///
|
||||
/// HttpServer does not change any configuration for TcpListener,
|
||||
@ -211,6 +238,42 @@ where
|
||||
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>> {
|
||||
let mut err = None;
|
||||
let mut succ = false;
|
||||
@ -246,7 +309,7 @@ where
|
||||
|
||||
/// 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> {
|
||||
let sockets = self.bind2(addr)?;
|
||||
self.sockets.extend(sockets);
|
||||
@ -256,7 +319,7 @@ where
|
||||
#[cfg(feature = "tls")]
|
||||
/// 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>(
|
||||
mut self, addr: S, acceptor: TlsAcceptor,
|
||||
) -> io::Result<Self> {
|
||||
@ -277,15 +340,7 @@ where
|
||||
) -> io::Result<Self> {
|
||||
// alpn support
|
||||
if !self.no_http2 {
|
||||
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
||||
builder.set_alpn_select_callback(|_, protos| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
if protos.windows(3).any(|window| window == H2) {
|
||||
Ok(b"h2")
|
||||
} else {
|
||||
Err(AlpnError::NOACK)
|
||||
}
|
||||
});
|
||||
configure_alpn(&mut builder)?;
|
||||
}
|
||||
|
||||
let acceptor = builder.build();
|
||||
@ -395,7 +450,6 @@ impl<H: IntoHttpHandler> HttpServer<H> {
|
||||
self.accept.push(start_accept_thread(
|
||||
token,
|
||||
sock,
|
||||
self.backlog,
|
||||
tx.clone(),
|
||||
socks.clone(),
|
||||
workers.clone(),
|
||||
@ -727,7 +781,7 @@ enum Command {
|
||||
}
|
||||
|
||||
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>,
|
||||
mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
|
||||
) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) {
|
||||
@ -764,12 +818,9 @@ fn start_accept_thread(
|
||||
}
|
||||
|
||||
// Start listening for incoming commands
|
||||
if let Err(err) = poll.register(
|
||||
®,
|
||||
CMD,
|
||||
mio::Ready::readable(),
|
||||
mio::PollOpt::edge(),
|
||||
) {
|
||||
if let Err(err) =
|
||||
poll.register(®, CMD, mio::Ready::readable(), mio::PollOpt::edge())
|
||||
{
|
||||
panic!("Can not register Registration: {}", err);
|
||||
}
|
||||
|
||||
@ -840,8 +891,8 @@ fn start_accept_thread(
|
||||
},
|
||||
CMD => match rx.try_recv() {
|
||||
Ok(cmd) => match cmd {
|
||||
Command::Pause => if let Some(server) = server.take() {
|
||||
if let Err(err) = poll.deregister(&server) {
|
||||
Command::Pause => if let Some(ref server) = server {
|
||||
if let Err(err) = poll.deregister(server) {
|
||||
error!(
|
||||
"Can not deregister server socket {}",
|
||||
err
|
||||
@ -854,15 +905,6 @@ fn start_accept_thread(
|
||||
}
|
||||
},
|
||||
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 Err(err) = poll.register(
|
||||
server,
|
||||
|
@ -85,9 +85,7 @@ impl<H: HttpHandler + 'static> Worker<H> {
|
||||
|
||||
fn update_time(&self, ctx: &mut Context<Self>) {
|
||||
self.settings.update_date();
|
||||
ctx.run_later(time::Duration::new(1, 0), |slf, ctx| {
|
||||
slf.update_time(ctx)
|
||||
});
|
||||
ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx));
|
||||
}
|
||||
|
||||
fn shutdown_timeout(
|
||||
@ -195,18 +193,17 @@ impl StreamHandlerType {
|
||||
let io = TcpStream::from_stream(io, hnd)
|
||||
.expect("failed to associate TCP stream");
|
||||
|
||||
hnd.spawn(
|
||||
TlsAcceptorExt::accept_async(acceptor, io).then(move |res| {
|
||||
match res {
|
||||
Ok(io) => Arbiter::handle()
|
||||
.spawn(HttpChannel::new(h, io, peer, http2)),
|
||||
Err(err) => {
|
||||
trace!("Error during handling tls connection: {}", err)
|
||||
}
|
||||
};
|
||||
future::result(Ok(()))
|
||||
}),
|
||||
);
|
||||
hnd.spawn(TlsAcceptorExt::accept_async(acceptor, io).then(move |res| {
|
||||
match res {
|
||||
Ok(io) => {
|
||||
Arbiter::handle().spawn(HttpChannel::new(h, io, peer, http2))
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("Error during handling tls connection: {}", err)
|
||||
}
|
||||
};
|
||||
future::result(Ok(()))
|
||||
}));
|
||||
}
|
||||
#[cfg(feature = "alpn")]
|
||||
StreamHandlerType::Alpn(ref acceptor) => {
|
||||
@ -215,28 +212,36 @@ impl StreamHandlerType {
|
||||
let io = TcpStream::from_stream(io, hnd)
|
||||
.expect("failed to associate TCP stream");
|
||||
|
||||
hnd.spawn(
|
||||
SslAcceptorExt::accept_async(acceptor, io).then(move |res| {
|
||||
match res {
|
||||
Ok(io) => {
|
||||
let http2 = if let Some(p) =
|
||||
io.get_ref().ssl().selected_alpn_protocol()
|
||||
{
|
||||
p.len() == 2 && &p == b"h2"
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Arbiter::handle()
|
||||
.spawn(HttpChannel::new(h, io, peer, http2));
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("Error during handling tls connection: {}", err)
|
||||
}
|
||||
};
|
||||
future::result(Ok(()))
|
||||
}),
|
||||
);
|
||||
hnd.spawn(SslAcceptorExt::accept_async(acceptor, io).then(move |res| {
|
||||
match res {
|
||||
Ok(io) => {
|
||||
let http2 = if let Some(p) =
|
||||
io.get_ref().ssl().selected_alpn_protocol()
|
||||
{
|
||||
p.len() == 2 && &p == b"h2"
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Arbiter::handle()
|
||||
.spawn(HttpChannel::new(h, io, peer, http2));
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("Error during handling tls connection: {}", err)
|
||||
}
|
||||
};
|
||||
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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
src/test.rs
17
src/test.rs
@ -217,7 +217,7 @@ impl TestServer {
|
||||
|
||||
/// Create `POST` request
|
||||
pub fn post(&self) -> ClientRequestBuilder {
|
||||
ClientRequest::get(self.url("/").as_str())
|
||||
ClientRequest::post(self.url("/").as_str())
|
||||
}
|
||||
|
||||
/// Create `HEAD` request
|
||||
@ -355,12 +355,7 @@ impl<S: 'static> TestApp<S> {
|
||||
|
||||
/// Register handler for "/"
|
||||
pub fn handler<H: Handler<S>>(&mut self, handler: H) {
|
||||
self.app = Some(
|
||||
self.app
|
||||
.take()
|
||||
.unwrap()
|
||||
.resource("/", |r| r.h(handler)),
|
||||
);
|
||||
self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler)));
|
||||
}
|
||||
|
||||
/// Register middleware
|
||||
@ -562,8 +557,8 @@ impl<S: 'static> TestRequest<S> {
|
||||
cookies,
|
||||
payload,
|
||||
} = self;
|
||||
let req = HttpRequest::new(method, uri, version, headers, payload);
|
||||
req.as_mut().cookies = cookies;
|
||||
let mut req = HttpRequest::new(method, uri, version, headers, payload);
|
||||
req.set_cookies(cookies);
|
||||
req.as_mut().params = params;
|
||||
let (router, _) = Router::new::<S>("/", ServerSettings::default(), Vec::new());
|
||||
req.with_state(Rc::new(state), router)
|
||||
@ -583,8 +578,8 @@ impl<S: 'static> TestRequest<S> {
|
||||
payload,
|
||||
} = self;
|
||||
|
||||
let req = HttpRequest::new(method, uri, version, headers, payload);
|
||||
req.as_mut().cookies = cookies;
|
||||
let mut req = HttpRequest::new(method, uri, version, headers, payload);
|
||||
req.set_cookies(cookies);
|
||||
req.as_mut().params = params;
|
||||
req.with_state(Rc::new(state), router)
|
||||
}
|
||||
|
211
src/with.rs
211
src/with.rs
@ -9,6 +9,62 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
/// Extractor configuration
|
||||
///
|
||||
/// `Route::with()` and `Route::with_async()` returns instance
|
||||
/// of the `ExtractorConfig` type. It could be used for extractor configuration.
|
||||
///
|
||||
/// In this example `Form<FormData>` configured.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Form, Result, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct FormData {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(form: Form<FormData>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", form.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/index.html", |r| {
|
||||
/// r.method(http::Method::GET)
|
||||
/// .with(index)
|
||||
/// .limit(4096);} // <- change form extractor configuration
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Same could be donce with multiple extractors
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Form, Path, Result, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct FormData {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(data: (Path<(String,)>, Form<FormData>)) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", data.1.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/index.html", |r| {
|
||||
/// r.method(http::Method::GET)
|
||||
/// .with(index)
|
||||
/// .1.limit(4096);} // <- change form extractor configuration
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> {
|
||||
cfg: Rc<UnsafeCell<T::Config>>,
|
||||
}
|
||||
@ -167,6 +223,145 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WithAsync<T, S, F, R, I, E>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
R: Future<Item = I, Error = E>,
|
||||
I: Responder,
|
||||
E: Into<E>,
|
||||
T: FromRequest<S>,
|
||||
S: 'static,
|
||||
{
|
||||
hnd: Rc<UnsafeCell<F>>,
|
||||
cfg: ExtractorConfig<S, T>,
|
||||
_s: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<T, S, F, R, I, E> WithAsync<T, S, F, R, I, E>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
R: Future<Item = I, Error = E>,
|
||||
I: Responder,
|
||||
E: Into<Error>,
|
||||
T: FromRequest<S>,
|
||||
S: 'static,
|
||||
{
|
||||
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self {
|
||||
WithAsync {
|
||||
cfg,
|
||||
hnd: Rc::new(UnsafeCell::new(f)),
|
||||
_s: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, F, R, I, E> Handler<S> for WithAsync<T, S, F, R, I, E>
|
||||
where
|
||||
F: Fn(T) -> R + 'static,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Result = AsyncResult<HttpResponse>;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
let mut fut = WithAsyncHandlerFut {
|
||||
req,
|
||||
started: false,
|
||||
hnd: Rc::clone(&self.hnd),
|
||||
cfg: self.cfg.clone(),
|
||||
fut1: None,
|
||||
fut2: None,
|
||||
fut3: None,
|
||||
};
|
||||
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||
Err(e) => AsyncResult::err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WithAsyncHandlerFut<T, S, F, R, I, E>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
started: bool,
|
||||
hnd: Rc<UnsafeCell<F>>,
|
||||
cfg: ExtractorConfig<S, T>,
|
||||
req: HttpRequest<S>,
|
||||
fut1: Option<Box<Future<Item = T, Error = Error>>>,
|
||||
fut2: Option<R>,
|
||||
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
|
||||
}
|
||||
|
||||
impl<T, S, F, R, I, E> Future for WithAsyncHandlerFut<T, S, F, R, I, E>
|
||||
where
|
||||
F: Fn(T) -> R,
|
||||
R: Future<Item = I, Error = E> + 'static,
|
||||
I: Responder + 'static,
|
||||
E: Into<Error> + 'static,
|
||||
T: FromRequest<S> + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(ref mut fut) = self.fut3 {
|
||||
return fut.poll();
|
||||
}
|
||||
|
||||
if self.fut2.is_some() {
|
||||
return match self.fut2.as_mut().unwrap().poll() {
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(r)) => match r.respond_to(&self.req) {
|
||||
Ok(r) => match r.into().into() {
|
||||
AsyncResultItem::Err(err) => Err(err),
|
||||
AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut3 = Some(fut);
|
||||
self.poll()
|
||||
}
|
||||
},
|
||||
Err(e) => Err(e.into()),
|
||||
},
|
||||
Err(e) => Err(e.into()),
|
||||
};
|
||||
}
|
||||
|
||||
let item = if !self.started {
|
||||
self.started = true;
|
||||
let reply = T::from_request(&self.req, self.cfg.as_ref()).into();
|
||||
match reply.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
AsyncResultItem::Ok(msg) => msg,
|
||||
AsyncResultItem::Future(fut) => {
|
||||
self.fut1 = Some(fut);
|
||||
return self.poll();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self.fut1.as_mut().unwrap().poll()? {
|
||||
Async::Ready(item) => item,
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
self.fut2 = Some((*hnd)(item));
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct With2<T1, T2, S, F, R>
|
||||
where
|
||||
F: Fn(T1, T2) -> R,
|
||||
@ -599,15 +794,13 @@ where
|
||||
};
|
||||
|
||||
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
|
||||
let item = match (*hnd)(
|
||||
self.item1.take().unwrap(),
|
||||
self.item2.take().unwrap(),
|
||||
item,
|
||||
).respond_to(&self.req)
|
||||
{
|
||||
Ok(item) => item.into(),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
let item =
|
||||
match (*hnd)(self.item1.take().unwrap(), self.item2.take().unwrap(), item)
|
||||
.respond_to(&self.req)
|
||||
{
|
||||
Ok(item) => item.into(),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
match item.into() {
|
||||
AsyncResultItem::Err(err) => return Err(err),
|
||||
|
@ -22,8 +22,10 @@ use header::IntoHeaderValue;
|
||||
use httpmessage::HttpMessage;
|
||||
use payload::PayloadHelper;
|
||||
|
||||
use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse,
|
||||
HttpResponseParserError, SendRequest, SendRequestError};
|
||||
use client::{
|
||||
ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse,
|
||||
HttpResponseParserError, SendRequest, SendRequestError,
|
||||
};
|
||||
|
||||
use super::frame::Frame;
|
||||
use super::proto::{CloseReason, OpCode};
|
||||
@ -111,6 +113,7 @@ pub struct Client {
|
||||
protocols: Option<String>,
|
||||
conn: Addr<Unsync, ClientConnector>,
|
||||
max_size: usize,
|
||||
no_masking: bool,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@ -130,6 +133,7 @@ impl Client {
|
||||
origin: None,
|
||||
protocols: None,
|
||||
max_size: 65_536,
|
||||
no_masking: false,
|
||||
conn,
|
||||
};
|
||||
cl.request.uri(uri.as_ref());
|
||||
@ -184,6 +188,12 @@ impl Client {
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable payload masking. By default ws client masks frame payload.
|
||||
pub fn no_masking(mut self) -> Self {
|
||||
self.no_masking = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set request header
|
||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||
where
|
||||
@ -218,8 +228,7 @@ impl Client {
|
||||
self.request.upgrade();
|
||||
self.request.set_header(header::UPGRADE, "websocket");
|
||||
self.request.set_header(header::CONNECTION, "upgrade");
|
||||
self.request
|
||||
.set_header(header::SEC_WEBSOCKET_VERSION, "13");
|
||||
self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13");
|
||||
self.request.with_connector(self.conn.clone());
|
||||
|
||||
if let Some(protocols) = self.protocols.take() {
|
||||
@ -235,7 +244,9 @@ impl Client {
|
||||
return ClientHandshake::error(ClientError::InvalidUrl);
|
||||
}
|
||||
if let Some(scheme) = request.uri().scheme_part() {
|
||||
if scheme != "http" && scheme != "https" && scheme != "ws"
|
||||
if scheme != "http"
|
||||
&& scheme != "https"
|
||||
&& scheme != "ws"
|
||||
&& scheme != "wss"
|
||||
{
|
||||
return ClientHandshake::error(ClientError::InvalidUrl);
|
||||
@ -245,7 +256,7 @@ impl Client {
|
||||
}
|
||||
|
||||
// start handshake
|
||||
ClientHandshake::new(request, self.max_size)
|
||||
ClientHandshake::new(request, self.max_size, self.no_masking)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -266,10 +277,13 @@ pub struct ClientHandshake {
|
||||
key: String,
|
||||
error: Option<ClientError>,
|
||||
max_size: usize,
|
||||
no_masking: bool,
|
||||
}
|
||||
|
||||
impl ClientHandshake {
|
||||
fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake {
|
||||
fn new(
|
||||
mut request: ClientRequest, max_size: usize, no_masking: bool,
|
||||
) -> ClientHandshake {
|
||||
// Generate a random key for the `Sec-WebSocket-Key` header.
|
||||
// a base64-encoded (see Section 4 of [RFC4648]) value that,
|
||||
// when decoded, is 16 bytes in length (RFC 6455)
|
||||
@ -289,6 +303,7 @@ impl ClientHandshake {
|
||||
ClientHandshake {
|
||||
key,
|
||||
max_size,
|
||||
no_masking,
|
||||
request: Some(request.send()),
|
||||
tx: Some(tx),
|
||||
error: None,
|
||||
@ -302,6 +317,7 @@ impl ClientHandshake {
|
||||
tx: None,
|
||||
error: Some(err),
|
||||
max_size: 0,
|
||||
no_masking: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,10 +410,7 @@ impl Future for ClientHandshake {
|
||||
encoded,
|
||||
key
|
||||
);
|
||||
return Err(ClientError::InvalidChallengeResponse(
|
||||
encoded,
|
||||
key.clone(),
|
||||
));
|
||||
return Err(ClientError::InvalidChallengeResponse(encoded, key.clone()));
|
||||
}
|
||||
} else {
|
||||
trace!("Missing SEC-WEBSOCKET-ACCEPT header");
|
||||
@ -415,6 +428,7 @@ impl Future for ClientHandshake {
|
||||
ClientReader {
|
||||
inner: Rc::clone(&inner),
|
||||
max_size: self.max_size,
|
||||
no_masking: self.no_masking,
|
||||
},
|
||||
ClientWriter { inner },
|
||||
)))
|
||||
@ -424,6 +438,7 @@ impl Future for ClientHandshake {
|
||||
pub struct ClientReader {
|
||||
inner: Rc<UnsafeCell<Inner>>,
|
||||
max_size: usize,
|
||||
no_masking: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ClientReader {
|
||||
@ -445,13 +460,14 @@ impl Stream for ClientReader {
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
let max_size = self.max_size;
|
||||
let no_masking = self.no_masking;
|
||||
let inner = self.as_mut();
|
||||
if inner.closed {
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
|
||||
// read
|
||||
match Frame::parse(&mut inner.rx, false, max_size) {
|
||||
match Frame::parse(&mut inner.rx, no_masking, max_size) {
|
||||
Ok(Async::Ready(Some(frame))) => {
|
||||
let (_finished, opcode, payload) = frame.unpack();
|
||||
|
||||
@ -518,46 +534,66 @@ impl ClientWriter {
|
||||
fn as_mut(&mut self) -> &mut Inner {
|
||||
unsafe { &mut *self.inner.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl WsWriter for ClientWriter {
|
||||
/// Send text frame
|
||||
#[inline]
|
||||
fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||
pub fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||
self.write(Frame::message(text.into(), OpCode::Text, true, true));
|
||||
}
|
||||
|
||||
/// Send binary frame
|
||||
#[inline]
|
||||
fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
self.write(Frame::message(data, OpCode::Binary, true, true));
|
||||
}
|
||||
|
||||
/// Send ping frame
|
||||
#[inline]
|
||||
fn ping(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Ping,
|
||||
true,
|
||||
true,
|
||||
));
|
||||
pub fn ping(&mut self, message: &str) {
|
||||
self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true));
|
||||
}
|
||||
|
||||
/// Send pong frame
|
||||
#[inline]
|
||||
fn pong(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Pong,
|
||||
true,
|
||||
true,
|
||||
));
|
||||
pub fn pong(&mut self, message: &str) {
|
||||
self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true));
|
||||
}
|
||||
|
||||
/// Send close frame
|
||||
#[inline]
|
||||
fn close(&mut self, reason: Option<CloseReason>) {
|
||||
pub fn close(&mut self, reason: Option<CloseReason>) {
|
||||
self.write(Frame::close(reason, true));
|
||||
}
|
||||
}
|
||||
|
||||
impl WsWriter for ClientWriter {
|
||||
/// Send text frame
|
||||
#[inline]
|
||||
fn send_text<T: Into<Binary>>(&mut self, text: T) {
|
||||
self.text(text)
|
||||
}
|
||||
|
||||
/// Send binary frame
|
||||
#[inline]
|
||||
fn send_binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
self.binary(data)
|
||||
}
|
||||
|
||||
/// Send ping frame
|
||||
#[inline]
|
||||
fn send_ping(&mut self, message: &str) {
|
||||
self.ping(message)
|
||||
}
|
||||
|
||||
/// Send pong frame
|
||||
#[inline]
|
||||
fn send_pong(&mut self, message: &str) {
|
||||
self.pong(message)
|
||||
}
|
||||
|
||||
/// Send close frame
|
||||
#[inline]
|
||||
fn send_close(&mut self, reason: Option<CloseReason>) {
|
||||
self.close(reason);
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,10 @@ use smallvec::SmallVec;
|
||||
|
||||
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
|
||||
use actix::fut::ActorFuture;
|
||||
use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message,
|
||||
SpawnHandle, Syn, Unsync};
|
||||
use actix::{
|
||||
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
|
||||
Syn, Unsync,
|
||||
};
|
||||
|
||||
use body::{Binary, Body};
|
||||
use context::{ActorHttpContext, Drain, Frame as ContextFrame};
|
||||
@ -64,7 +66,8 @@ where
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
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
|
||||
}
|
||||
|
||||
@ -149,6 +152,46 @@ where
|
||||
Drain::new(rx)
|
||||
}
|
||||
|
||||
/// Send text frame
|
||||
#[inline]
|
||||
pub fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||
self.write(Frame::message(text.into(), OpCode::Text, true, false));
|
||||
}
|
||||
|
||||
/// Send binary frame
|
||||
#[inline]
|
||||
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
self.write(Frame::message(data, OpCode::Binary, true, false));
|
||||
}
|
||||
|
||||
/// Send ping frame
|
||||
#[inline]
|
||||
pub fn ping(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Ping,
|
||||
true,
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
/// Send pong frame
|
||||
#[inline]
|
||||
pub fn pong(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Pong,
|
||||
true,
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
/// Send close frame
|
||||
#[inline]
|
||||
pub fn close(&mut self, reason: Option<CloseReason>) {
|
||||
self.write(Frame::close(reason, false));
|
||||
}
|
||||
|
||||
/// Check if connection still open
|
||||
#[inline]
|
||||
pub fn connected(&self) -> bool {
|
||||
@ -181,42 +224,32 @@ where
|
||||
{
|
||||
/// Send text frame
|
||||
#[inline]
|
||||
fn text<T: Into<Binary>>(&mut self, text: T) {
|
||||
self.write(Frame::message(text.into(), OpCode::Text, true, false));
|
||||
fn send_text<T: Into<Binary>>(&mut self, text: T) {
|
||||
self.text(text)
|
||||
}
|
||||
|
||||
/// Send binary frame
|
||||
#[inline]
|
||||
fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
self.write(Frame::message(data, OpCode::Binary, true, false));
|
||||
fn send_binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
self.binary(data)
|
||||
}
|
||||
|
||||
/// Send ping frame
|
||||
#[inline]
|
||||
fn ping(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Ping,
|
||||
true,
|
||||
false,
|
||||
));
|
||||
fn send_ping(&mut self, message: &str) {
|
||||
self.ping(message)
|
||||
}
|
||||
|
||||
/// Send pong frame
|
||||
#[inline]
|
||||
fn pong(&mut self, message: &str) {
|
||||
self.write(Frame::message(
|
||||
Vec::from(message),
|
||||
OpCode::Pong,
|
||||
true,
|
||||
false,
|
||||
));
|
||||
fn send_pong(&mut self, message: &str) {
|
||||
self.pong(message)
|
||||
}
|
||||
|
||||
/// Send close frame
|
||||
#[inline]
|
||||
fn close(&mut self, reason: Option<CloseReason>) {
|
||||
self.write(Frame::close(reason, false));
|
||||
fn send_close(&mut self, reason: Option<CloseReason>) {
|
||||
self.close(reason)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,9 +123,7 @@ impl Frame {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Async::Ready(Some((
|
||||
idx, finished, opcode, length, mask,
|
||||
))))
|
||||
Ok(Async::Ready(Some((idx, finished, opcode, length, mask))))
|
||||
}
|
||||
|
||||
fn read_chunk_md(
|
||||
@ -284,10 +282,7 @@ impl Frame {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(CloseReason {
|
||||
code,
|
||||
description,
|
||||
})
|
||||
Some(CloseReason { code, description })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -62,7 +62,9 @@ mod frame;
|
||||
mod mask;
|
||||
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::frame::Frame;
|
||||
pub use self::proto::{CloseCode, CloseReason, OpCode};
|
||||
@ -216,9 +218,7 @@ pub fn handshake<S>(
|
||||
}
|
||||
|
||||
// check supported version
|
||||
if !req.headers()
|
||||
.contains_key(header::SEC_WEBSOCKET_VERSION)
|
||||
{
|
||||
if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
|
||||
return Err(HandshakeError::NoVersionHeader);
|
||||
}
|
||||
let supported_ver = {
|
||||
@ -343,15 +343,15 @@ where
|
||||
/// Common writing methods for a websocket.
|
||||
pub trait WsWriter {
|
||||
/// Send a text
|
||||
fn text<T: Into<Binary>>(&mut self, text: T);
|
||||
fn send_text<T: Into<Binary>>(&mut self, text: T);
|
||||
/// Send a binary
|
||||
fn binary<B: Into<Binary>>(&mut self, data: B);
|
||||
fn send_binary<B: Into<Binary>>(&mut self, data: B);
|
||||
/// Send a ping message
|
||||
fn ping(&mut self, message: &str);
|
||||
fn send_ping(&mut self, message: &str);
|
||||
/// Send a pong message
|
||||
fn pong(&mut self, message: &str);
|
||||
fn send_pong(&mut self, message: &str);
|
||||
/// Close the connection
|
||||
fn close(&mut self, reason: Option<CloseReason>);
|
||||
fn send_close(&mut self, reason: Option<CloseReason>);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -387,10 +387,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::UPGRADE,
|
||||
header::HeaderValue::from_static("test"),
|
||||
);
|
||||
headers.insert(header::UPGRADE, header::HeaderValue::from_static("test"));
|
||||
let req = HttpRequest::new(
|
||||
Method::GET,
|
||||
Uri::from_str("/").unwrap(),
|
||||
|
@ -73,10 +73,7 @@ fn test_with_query_parameter() {
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/?qp=5").as_str())
|
||||
.finish()
|
||||
.unwrap();
|
||||
let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap();
|
||||
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
@ -125,7 +122,8 @@ fn test_client_gzip_encoding() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.content_encoding(http::ContentEncoding::Gzip)
|
||||
.body(STR)
|
||||
.unwrap();
|
||||
@ -154,7 +152,8 @@ fn test_client_gzip_encoding_large() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.content_encoding(http::ContentEncoding::Gzip)
|
||||
.body(data.clone())
|
||||
.unwrap();
|
||||
@ -186,7 +185,8 @@ fn test_client_gzip_encoding_large_random() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.content_encoding(http::ContentEncoding::Gzip)
|
||||
.body(data.clone())
|
||||
.unwrap();
|
||||
@ -214,7 +214,8 @@ fn test_client_brotli_encoding() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.client(http::Method::POST, "/")
|
||||
let request = srv
|
||||
.client(http::Method::POST, "/")
|
||||
.content_encoding(http::ContentEncoding::Br)
|
||||
.body(STR)
|
||||
.unwrap();
|
||||
@ -247,7 +248,8 @@ fn test_client_brotli_encoding_large_random() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.client(http::Method::POST, "/")
|
||||
let request = srv
|
||||
.client(http::Method::POST, "/")
|
||||
.content_encoding(http::ContentEncoding::Br)
|
||||
.body(data.clone())
|
||||
.unwrap();
|
||||
@ -276,7 +278,8 @@ fn test_client_deflate_encoding() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.content_encoding(http::ContentEncoding::Deflate)
|
||||
.body(STR)
|
||||
.unwrap();
|
||||
@ -309,7 +312,8 @@ fn test_client_deflate_encoding_large_random() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.content_encoding(http::ContentEncoding::Deflate)
|
||||
.body(data.clone())
|
||||
.unwrap();
|
||||
@ -339,9 +343,7 @@ fn test_client_streaming_explicit() {
|
||||
|
||||
let body = once(Ok(Bytes::from_static(STR.as_ref())));
|
||||
|
||||
let request = srv.get()
|
||||
.body(Body::Streaming(Box::new(body)))
|
||||
.unwrap();
|
||||
let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
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(cookie2.clone())
|
||||
.finish()
|
||||
|
@ -9,6 +9,7 @@ extern crate tokio_core;
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix::*;
|
||||
@ -33,10 +34,7 @@ fn test_path_extractor() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/test/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
@ -54,7 +52,8 @@ fn test_query_extractor() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/index.html?username=test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
@ -66,10 +65,7 @@ fn test_query_extractor() {
|
||||
assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let request = srv.get().uri(srv.url("/index.html")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
@ -88,7 +84,8 @@ fn test_async_extractor_async() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
@ -112,7 +109,8 @@ fn test_path_and_query_extractor() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
@ -124,7 +122,8 @@ fn test_path_and_query_extractor() {
|
||||
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!"));
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
@ -144,7 +143,8 @@ fn test_path_and_query_extractor2() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
@ -156,7 +156,8 @@ fn test_path_and_query_extractor2() {
|
||||
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!"));
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
@ -168,21 +169,21 @@ fn test_path_and_query_extractor2() {
|
||||
fn test_path_and_query_extractor2_async() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with3(
|
||||
|p: Path<PParam>, _: Query<PParam>, data: Json<Value>| {
|
||||
r.route()
|
||||
.with3(|p: Path<PParam>, _: Query<PParam>, data: Json<Value>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
},
|
||||
)
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
@ -192,10 +193,7 @@ fn test_path_and_query_extractor2_async() {
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
|
||||
);
|
||||
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -214,7 +212,8 @@ fn test_path_and_query_extractor3_async() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
@ -239,7 +238,8 @@ fn test_path_and_query_extractor4_async() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
@ -252,21 +252,21 @@ fn test_path_and_query_extractor4_async() {
|
||||
fn test_path_and_query_extractor2_async2() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with3(
|
||||
|p: Path<PParam>, data: Json<Value>, _: Query<PParam>| {
|
||||
r.route()
|
||||
.with3(|p: Path<PParam>, data: Json<Value>, _: Query<PParam>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
},
|
||||
)
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
@ -276,13 +276,11 @@ fn test_path_and_query_extractor2_async2() {
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
|
||||
);
|
||||
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
@ -294,21 +292,21 @@ fn test_path_and_query_extractor2_async2() {
|
||||
fn test_path_and_query_extractor2_async3() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with3(
|
||||
|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
|
||||
r.route()
|
||||
.with3(|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| {
|
||||
Ok(format!("Welcome {} - {}!", p.username, data.0))
|
||||
})
|
||||
.responder()
|
||||
},
|
||||
)
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
@ -318,13 +316,11 @@ fn test_path_and_query_extractor2_async3() {
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
|
||||
);
|
||||
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
@ -341,11 +337,7 @@ fn test_path_and_query_extractor2_async4() {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| {
|
||||
Ok(format!(
|
||||
"Welcome {} - {}!",
|
||||
data.1.username,
|
||||
(data.0).0
|
||||
))
|
||||
Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))
|
||||
})
|
||||
.responder()
|
||||
})
|
||||
@ -353,7 +345,8 @@ fn test_path_and_query_extractor2_async4() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
@ -363,13 +356,11 @@ fn test_path_and_query_extractor2_async4() {
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
|
||||
);
|
||||
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
@ -377,6 +368,148 @@ fn test_path_and_query_extractor2_async4() {
|
||||
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)]
|
||||
fn test_impl_trait(
|
||||
data: (Json<Value>, Path<PParam>, Query<PParam>),
|
||||
) -> impl Future<Item = String, Error = io::Error> {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)))
|
||||
}
|
||||
|
||||
#[cfg(actix_impl_trait)]
|
||||
fn test_impl_trait_err(
|
||||
_data: (Json<Value>, Path<PParam>, Query<PParam>),
|
||||
) -> impl Future<Item = String, Error = io::Error> {
|
||||
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
|
||||
.unwrap()
|
||||
.and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other")))
|
||||
}
|
||||
|
||||
#[cfg(actix_impl_trait)]
|
||||
#[test]
|
||||
fn test_path_and_query_extractor2_async4_impl_trait() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with_async(test_impl_trait)
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
// read response
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
|
||||
|
||||
// client request
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/test1/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[cfg(actix_impl_trait)]
|
||||
#[test]
|
||||
fn test_path_and_query_extractor2_async4_impl_trait_err() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.resource("/{username}/index.html", |r| {
|
||||
r.route().with_async(test_impl_trait_err)
|
||||
});
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv
|
||||
.post()
|
||||
.uri(srv.url("/test1/index.html?username=test2"))
|
||||
.header("content-type", "application/json")
|
||||
.body("{\"test\": 1}")
|
||||
.unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_ascii_route() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
@ -384,7 +517,8 @@ fn test_non_ascii_route() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/中文/index.html"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
@ -405,7 +539,8 @@ fn test_unsafe_path_route() {
|
||||
});
|
||||
|
||||
// client request
|
||||
let request = srv.get()
|
||||
let request = srv
|
||||
.get()
|
||||
.uri(srv.url("/test/http%3A%2F%2Fexample.com"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
|
@ -9,6 +9,7 @@ use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix::*;
|
||||
use actix_web::error::{Error, ErrorInternalServerError};
|
||||
use actix_web::*;
|
||||
use futures::{future, Future};
|
||||
use tokio_core::reactor::Timeout;
|
||||
@ -21,28 +22,22 @@ struct MiddlewareTest {
|
||||
|
||||
impl<S> middleware::Middleware<S> for MiddlewareTest {
|
||||
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
|
||||
self.start.store(
|
||||
self.start.load(Ordering::Relaxed) + 1,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
self.start
|
||||
.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||
Ok(middleware::Started::Done)
|
||||
}
|
||||
|
||||
fn response(
|
||||
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
|
||||
) -> Result<middleware::Response> {
|
||||
self.response.store(
|
||||
self.response.load(Ordering::Relaxed) + 1,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
self.response
|
||||
.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||
Ok(middleware::Response::Done(resp))
|
||||
}
|
||||
|
||||
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
|
||||
self.finish.store(
|
||||
self.finish.load(Ordering::Relaxed) + 1,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
self.finish
|
||||
.store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||
middleware::Finished::Done
|
||||
}
|
||||
}
|
||||
@ -187,10 +182,7 @@ fn test_scope_middleware() {
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
@ -226,10 +218,7 @@ fn test_scope_middleware_multiple() {
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
@ -337,10 +326,7 @@ fn test_scope_middleware_async_handler() {
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
@ -402,10 +388,7 @@ fn test_scope_middleware_async_error() {
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
|
||||
|
||||
@ -555,6 +538,42 @@ fn test_async_middleware_multiple() {
|
||||
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]
|
||||
fn test_async_scope_middleware() {
|
||||
let num1 = Arc::new(AtomicUsize::new(0));
|
||||
@ -577,10 +596,7 @@ fn test_async_scope_middleware() {
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
@ -618,10 +634,45 @@ fn test_async_scope_middleware_multiple() {
|
||||
})
|
||||
});
|
||||
|
||||
let request = srv.get()
|
||||
.uri(srv.url("/scope/test"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
||||
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
||||
|
||||
thread::sleep(Duration::from_millis(20));
|
||||
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_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();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
@ -703,3 +754,246 @@ fn test_async_resource_middleware_multiple() {
|
||||
thread::sleep(Duration::from_millis(40));
|
||||
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);
|
||||
}
|
||||
|
@ -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";
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_start() {
|
||||
let _ = test::TestServer::unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
@ -86,12 +87,17 @@ fn test_start() {
|
||||
// pause
|
||||
let _ = srv_addr.send(server::PauseServer).wait();
|
||||
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
|
||||
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())
|
||||
.finish()
|
||||
@ -337,11 +343,7 @@ fn test_body_br_streaming() {
|
||||
#[test]
|
||||
fn test_head_empty() {
|
||||
let mut srv = test::TestServer::new(|app| {
|
||||
app.handler(|_| {
|
||||
HttpResponse::Ok()
|
||||
.content_length(STR.len() as u64)
|
||||
.finish()
|
||||
})
|
||||
app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish())
|
||||
});
|
||||
|
||||
let request = srv.head().finish().unwrap();
|
||||
@ -529,7 +531,8 @@ fn test_gzip_encoding() {
|
||||
e.write_all(STR.as_ref()).unwrap();
|
||||
let enc = e.finish().unwrap();
|
||||
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.header(http::header::CONTENT_ENCODING, "gzip")
|
||||
.body(enc.clone())
|
||||
.unwrap();
|
||||
@ -561,7 +564,8 @@ fn test_gzip_encoding_large() {
|
||||
e.write_all(data.as_ref()).unwrap();
|
||||
let enc = e.finish().unwrap();
|
||||
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.header(http::header::CONTENT_ENCODING, "gzip")
|
||||
.body(enc.clone())
|
||||
.unwrap();
|
||||
@ -597,7 +601,8 @@ fn test_reading_gzip_encoding_large_random() {
|
||||
e.write_all(data.as_ref()).unwrap();
|
||||
let enc = e.finish().unwrap();
|
||||
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.header(http::header::CONTENT_ENCODING, "gzip")
|
||||
.body(enc.clone())
|
||||
.unwrap();
|
||||
@ -629,7 +634,8 @@ fn test_reading_deflate_encoding() {
|
||||
let enc = e.finish().unwrap();
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.header(http::header::CONTENT_ENCODING, "deflate")
|
||||
.body(enc)
|
||||
.unwrap();
|
||||
@ -661,7 +667,8 @@ fn test_reading_deflate_encoding_large() {
|
||||
let enc = e.finish().unwrap();
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.header(http::header::CONTENT_ENCODING, "deflate")
|
||||
.body(enc)
|
||||
.unwrap();
|
||||
@ -697,7 +704,8 @@ fn test_reading_deflate_encoding_large_random() {
|
||||
let enc = e.finish().unwrap();
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.header(http::header::CONTENT_ENCODING, "deflate")
|
||||
.body(enc)
|
||||
.unwrap();
|
||||
@ -730,7 +738,8 @@ fn test_brotli_encoding() {
|
||||
let enc = e.finish().unwrap();
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.header(http::header::CONTENT_ENCODING, "br")
|
||||
.body(enc)
|
||||
.unwrap();
|
||||
@ -763,7 +772,8 @@ fn test_brotli_encoding_large() {
|
||||
let enc = e.finish().unwrap();
|
||||
|
||||
// client request
|
||||
let request = srv.post()
|
||||
let request = srv
|
||||
.post()
|
||||
.header(http::header::CONTENT_ENCODING, "br")
|
||||
.body(enc)
|
||||
.unwrap();
|
||||
@ -784,7 +794,8 @@ fn test_h2() {
|
||||
let handle = core.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| {
|
||||
let (mut client, h2) = res.unwrap();
|
||||
|
||||
|
@ -46,9 +46,7 @@ fn test_simple() {
|
||||
let (item, reader) = srv.execute(reader.into_future()).unwrap();
|
||||
assert_eq!(
|
||||
item,
|
||||
Some(ws::Message::Binary(
|
||||
Bytes::from_static(b"text").into()
|
||||
))
|
||||
Some(ws::Message::Binary(Bytes::from_static(b"text").into()))
|
||||
);
|
||||
|
||||
writer.ping("ping");
|
||||
@ -117,10 +115,7 @@ fn test_large_bin() {
|
||||
writer.binary(data.clone());
|
||||
let (item, r) = srv.execute(reader.into_future()).unwrap();
|
||||
reader = r;
|
||||
assert_eq!(
|
||||
item,
|
||||
Some(ws::Message::Binary(Binary::from(data.clone())))
|
||||
);
|
||||
assert_eq!(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")
|
||||
.unwrap();
|
||||
|
||||
let mut srv = test::TestServer::build()
|
||||
.ssl(builder.build())
|
||||
.start(|app| {
|
||||
app.handler(|req| {
|
||||
ws::start(
|
||||
req,
|
||||
Ws2 {
|
||||
count: 0,
|
||||
bin: false,
|
||||
},
|
||||
)
|
||||
})
|
||||
});
|
||||
let mut srv = test::TestServer::build().ssl(builder.build()).start(|app| {
|
||||
app.handler(|req| {
|
||||
ws::start(
|
||||
req,
|
||||
Ws2 {
|
||||
count: 0,
|
||||
bin: false,
|
||||
},
|
||||
)
|
||||
})
|
||||
});
|
||||
let (mut reader, _writer) = srv.ws().unwrap();
|
||||
|
||||
let data = Some(ws::Message::Text("0".repeat(65_536)));
|
||||
|
@ -82,43 +82,40 @@ fn main() {
|
||||
let perf = perf_counters.clone();
|
||||
let addr = Arbiter::new(format!("test {}", t));
|
||||
|
||||
addr.do_send(actix::msgs::Execute::new(
|
||||
move || -> Result<(), ()> {
|
||||
for _ in 0..concurrency {
|
||||
let pl2 = pl.clone();
|
||||
let perf2 = perf.clone();
|
||||
let ws2 = ws.clone();
|
||||
addr.do_send(actix::msgs::Execute::new(move || -> Result<(), ()> {
|
||||
for _ in 0..concurrency {
|
||||
let pl2 = pl.clone();
|
||||
let perf2 = perf.clone();
|
||||
let ws2 = ws.clone();
|
||||
|
||||
Arbiter::handle().spawn(
|
||||
ws::Client::new(&ws)
|
||||
.write_buffer_capacity(0)
|
||||
.connect()
|
||||
.map_err(|e| {
|
||||
println!("Error: {}", e);
|
||||
//Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
()
|
||||
})
|
||||
.map(move |(reader, writer)| {
|
||||
let addr: Addr<Syn, _> =
|
||||
ChatClient::create(move |ctx| {
|
||||
ChatClient::add_stream(reader, ctx);
|
||||
ChatClient {
|
||||
url: ws2,
|
||||
conn: writer,
|
||||
payload: pl2,
|
||||
bin: bin,
|
||||
ts: time::precise_time_ns(),
|
||||
perf_counters: perf2,
|
||||
sent: 0,
|
||||
max_payload_size: max_payload_size,
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
));
|
||||
Arbiter::handle().spawn(
|
||||
ws::Client::new(&ws)
|
||||
.write_buffer_capacity(0)
|
||||
.connect()
|
||||
.map_err(|e| {
|
||||
println!("Error: {}", e);
|
||||
//Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
()
|
||||
})
|
||||
.map(move |(reader, writer)| {
|
||||
let addr: Addr<Syn, _> = ChatClient::create(move |ctx| {
|
||||
ChatClient::add_stream(reader, ctx);
|
||||
ChatClient {
|
||||
url: ws2,
|
||||
conn: writer,
|
||||
payload: pl2,
|
||||
bin: bin,
|
||||
ts: time::precise_time_ns(),
|
||||
perf_counters: perf2,
|
||||
sent: 0,
|
||||
max_payload_size: max_payload_size,
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
let res = sys.run();
|
||||
@ -126,10 +123,7 @@ fn main() {
|
||||
|
||||
fn parse_u64_default(input: Option<&str>, default: u64) -> u64 {
|
||||
input
|
||||
.map(|v| {
|
||||
v.parse()
|
||||
.expect(&format!("not a valid number: {}", v))
|
||||
})
|
||||
.map(|v| v.parse().expect(&format!("not a valid number: {}", v)))
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
@ -314,7 +308,8 @@ impl PerfCounters {
|
||||
loop {
|
||||
let current = self.lat_max.load(Ordering::SeqCst);
|
||||
if current >= nanos
|
||||
|| self.lat_max
|
||||
|| self
|
||||
.lat_max
|
||||
.compare_and_swap(current, nanos, Ordering::SeqCst)
|
||||
== current
|
||||
{
|
||||
|
Reference in New Issue
Block a user