1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-22 17:07:19 +02:00

Compare commits

..

35 Commits

Author SHA1 Message Date
Nikolay Kim
1fcf1d4a49 prepare release 2018-06-21 09:47:46 +06:00
Nikolay Kim
4012606910 SendRequest execution fails with the entered unreachable code #329 2018-06-21 09:47:28 +06:00
Nikolay Kim
e975124630 Allow to disable masking for websockets client 2018-06-21 09:38:59 +06:00
Nikolay Kim
6862aa6ee7 prepare release 2018-06-13 05:04:59 -07:00
Nikolay Kim
8a22558f25 http/2 end-of-frame is not set if body is empty bytes #307 2018-06-12 14:47:45 -07:00
Nikolay Kim
b5b9f9656e do not allow stream or actor responses for internal error #301 2018-06-11 19:44:11 -07:00
Nikolay Kim
2fffc55d34 update changelog 2018-06-11 18:53:36 -07:00
Nikolay Kim
7d39f1582e InternalError can trigger memory unsafety #301 2018-06-11 18:52:54 -07:00
Nikolay Kim
75ed053a35 bump version 2018-06-11 12:32:31 -07:00
Nikolay Kim
cfedf5fff4 Merge branch '0.6' of github.com:actix/actix-web into 0.6 2018-06-11 12:32:05 -07:00
Nikolay Kim
be73a36339 use custom resolver 2018-06-11 12:28:43 -07:00
Nikolay Kim
1ad8ba2604 Fix docs.rs build 2018-06-11 12:25:20 -07:00
Armin Ronacher
6848a12095 prepare 0.6.12 release 2018-06-09 01:22:19 +02:00
Nikolay Kim
4797298706 Allow to use custom resolver for ClientConnector 2018-06-08 16:10:47 -07:00
Nikolay Kim
5eaf4cbefd update changelog 2018-06-08 08:42:10 -07:00
Nikolay Kim
7f1844e541 fix doc test 2018-06-07 20:22:50 -07:00
Nikolay Kim
6c7ac7fc22 update changelog 2018-06-07 20:07:58 -07:00
Nikolay Kim
42f9e1034b add Host predicate 2018-06-07 20:05:45 -07:00
Nikolay Kim
e3cd0fdd13 add application filters 2018-06-07 20:05:26 -07:00
Nikolay Kim
40ff550460 update changelog 2018-06-07 20:04:40 -07:00
Armin Ronacher
7119340d44 Added improved failure interoperability with downcasting (#285)
Deprecates Error::cause and introduces failure interoperability functions and downcasting.
2018-06-07 20:03:10 -07:00
Nikolay Kim
e140bc3906 prep release 2018-06-05 09:44:29 -07:00
Nikolay Kim
fdc08d365d metadata for docs.rs 2018-06-05 08:59:30 -07:00
Nikolay Kim
8f1b88e39e update changelog 2018-06-05 08:53:27 -07:00
Nikolay Kim
6a40a0a466 fix multipart boundary parsing #282 2018-06-05 08:52:46 -07:00
Nikolay Kim
89fc6b6ac9 changelog 2018-06-05 07:42:52 -07:00
Nikolay Kim
afa67b838a CORS: Do not validate Origin header on non-OPTION requests #271 2018-06-05 07:41:13 -07:00
Nikolay Kim
f7b7d282bf Middleware::response is not invoked if error result was returned by another Middleware::start #255 2018-06-04 13:57:54 -07:00
Nikolay Kim
09780ea9f3 changelog updates 2018-06-02 15:03:34 -07:00
Nikolay Kim
a7dab950f3 Support chunked encoding for UrlEncoded body #262 2018-06-02 15:02:42 -07:00
Nikolay Kim
ec0737e392 fix doc test 2018-06-02 13:48:37 -07:00
Nikolay Kim
d664993d56 remove debug prints 2018-06-02 11:58:11 -07:00
Nikolay Kim
a9c6c57a67 remove debug print 2018-06-02 11:55:27 -07:00
Nikolay Kim
08e7374eee update changelog 2018-06-02 11:46:53 -07:00
Nikolay Kim
42da1448fb fixed HttpRequest::url_for for a named route with no variables #265 2018-06-02 11:46:02 -07:00
97 changed files with 6925 additions and 10021 deletions

View File

@@ -3,13 +3,35 @@ environment:
PROJECT_NAME: actix
matrix:
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: 1.24.0
- TARGET: i686-pc-windows-msvc
CHANNEL: 1.24.0
- TARGET: x86_64-pc-windows-gnu
CHANNEL: 1.24.0
- TARGET: x86_64-pc-windows-msvc
CHANNEL: 1.24.0
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
# Beta channel
- TARGET: i686-pc-windows-gnu
CHANNEL: beta
- TARGET: i686-pc-windows-msvc
CHANNEL: beta
- TARGET: x86_64-pc-windows-gnu
CHANNEL: beta
- TARGET: x86_64-pc-windows-msvc
CHANNEL: beta
# Nightly channel
- TARGET: i686-pc-windows-gnu
CHANNEL: nightly
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu

View File

@@ -1,5 +1,5 @@
language: rust
sudo: required
sudo: false
dist: trusty
cache:
@@ -8,6 +8,7 @@ cache:
matrix:
include:
- rust: 1.24.0
- rust: stable
- rust: beta
- rust: nightly
@@ -22,7 +23,7 @@ env:
before_install:
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
- sudo apt-get update -qq
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
- sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
# Add clippy
before_script:
@@ -30,14 +31,14 @@ before_script:
script:
- |
if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then
if [[ "$TRAVIS_RUST_VERSION" != "1.24.0" ]]; then
cargo clean
cargo test --features="alpn,tls" -- --nocapture
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin
cargo tarpaulin --features="alpn,tls" --out Xml --no-count
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 --no-count
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi
@@ -45,7 +46,7 @@ script:
# Upload docs
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --features "alpn, tls, session" --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import.git &&

View File

@@ -1,95 +1,5 @@
# Changes
## [0.7.1] - 2018-07-21
### Fixed
* Fixed default_resource 'not yet implemented' panic #410
## [0.7.0] - 2018-07-21
### Added
* Add `fs::StaticFileConfig` to provide means of customizing static
file services. It allows to map `mime` to `Content-Disposition`,
specify whether to use `ETag` and `Last-Modified` and allowed methods.
* Add `.has_prefixed_resource()` method to `router::ResourceInfo`
for route matching with prefix awareness
* Add `HttpMessage::readlines()` for reading line by line.
* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests.
* Add method to configure custom error handler to `Form` extractor.
* Add methods to `HttpResponse` to retrieve, add, and delete cookies
* Add `.set_content_type()` and `.set_content_disposition()` methods
to `fs::NamedFile` to allow overriding the values inferred by default
* Add `fs::file_extension_to_mime()` helper function to get the MIME
type for a file extension
* Add `.content_disposition()` method to parse Content-Disposition of
multipart fields
* Re-export `actix::prelude::*` as `actix_web::actix` module.
* `HttpRequest::url_for_static()` for a named route with no variables segments
* Propagation of the application's default resource to scopes that haven't set a default resource.
### Changed
* Min rustc version is 1.26
* Use tokio instead of tokio-core
* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages.
* Became possible to use enums with query extractor.
Issue [#371](https://github.com/actix/actix-web/issues/371).
[Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134)
* `HttpResponse::into_builder()` now moves cookies into the builder
instead of dropping them
* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest`
* Added header `User-Agent: Actix-web/<current_version>` to default headers when building a request
* port `Extensions` type from http create, we don't need `Send + Sync`
* `HttpRequest::query()` returns `Ref<HashMap<String, String>>`
* `HttpRequest::cookies()` returns `Ref<Vec<Cookie<'static>>>`
* `StaticFiles::new()` returns `Result<StaticFiles<S>, Error>` instead of `StaticFiles<S>`
* `StaticFiles` uses the default handler if the file does not exist
### Removed
* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead.
* Remove `HttpMessage::range()`
## [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
@@ -101,12 +11,16 @@
* SendRequest execution fails with the "internal error: entered unreachable code" #329
## [0.6.13] - 2018-06-11
## [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
@@ -121,8 +35,15 @@
* 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
@@ -138,7 +59,7 @@
### Added
* Allow to use path without trailing slashes for scope registration #241
* Allow to use path without traling slashes for scope registration #241
* Allow to set encoding for exact NamedFile #239
@@ -499,7 +420,7 @@
* Server multi-threading
* Graceful shutdown support
* Gracefull shutdown support
## 0.2.1 (2017-11-03)

View File

@@ -1,13 +1,13 @@
[package]
name = "actix-web"
version = "0.7.1"
version = "0.6.14"
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://actix.rs"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://actix.rs/api/actix-web/stable/actix_web/"
documentation = "https://docs.rs/actix-web/"
categories = ["network-programming", "asynchronous",
"web-programming::http-server",
"web-programming::http-client",
@@ -16,9 +16,6 @@ license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs"
[package.metadata.docs.rs]
features = ["tls", "alpn", "session", "brotli", "flate2-c"]
[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
@@ -49,52 +46,51 @@ 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.7.0"
actix = "^0.5.8"
base64 = "0.9"
bitflags = "1.0"
failure = "0.1.1"
h2 = "0.1"
htmlescape = "0.3"
http = "^0.1.5"
httparse = "1.3"
httparse = "1.2"
http-range = "0.1"
libc = "0.2"
log = "0.4"
mime = "0.3"
mime_guess = "2.0.0-alpha"
num_cpus = "1.0"
percent-encoding = "1.0"
rand = "0.5"
rand = "0.4"
regex = "1.0"
serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.5"
sha1 = "0.6"
smallvec = "0.6"
time = "0.1"
encoding = "0.2"
language-tags = "0.2"
lazy_static = "1.0"
lazycell = "1.0.0"
parking_lot = "0.6"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="1.0", optional = true, default-features = false }
failure = "=0.1.1"
# io
mio = "^0.6.13"
net2 = "0.2"
bytes = "0.4"
byteorder = "1.2"
byteorder = "1"
futures = "0.1"
futures-cpupool = "0.1"
slab = "0.4"
tokio = "0.1"
tokio-io = "0.1"
tokio-tcp = "0.1"
tokio-timer = "0.2"
tokio-reactor = "0.1"
tokio-core = "0.1"
# native-tls
native-tls = { version="0.1", optional = true }
@@ -104,10 +100,6 @@ tokio-tls = { version="0.1", optional = true }
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }
# forked url_encoded
itoa = "0.4"
dtoa = "0.4"
[dev-dependencies]
env_logger = "0.5"
serde_derive = "1.0"
@@ -123,4 +115,5 @@ codegen-units = 1
[workspace]
members = [
"./",
"tools/wsload/",
]

View File

@@ -1,92 +1,4 @@
## 0.7
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
use `HttpMessage::payload()` method.
instead of
```rust
fn index(req: HttpRequest) -> impl Responder {
req
.from_err()
.fold(...)
....
}
```
use `.payload()`
```rust
fn index(req: HttpRequest) -> impl Responder {
req
.payload() // <- get request payload stream
.from_err()
.fold(...)
....
}
```
* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
instead of
```rust
fn index(query: Query<..>, info: Json<MyStruct) -> impl Responder {}
```
use tuple of extractors and use `.with()` for registration:
```rust
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
```
* `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
* Removed deprecated `HttpServer::threads()`, use
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
* Renamed `client::ClientConnectorError::Connector` to
`client::ClientConnectorError::Resolver`
* `Route::with()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_config()`
instead of
```rust
fn main() {
let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET)
.with(index)
.limit(4096); // <- limit size of the payload
});
}
```
use
```rust
fn main() {
let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET)
.with_config(index, |cfg| { // <- register handler
cfg.limit(4096); // <- limit size of the payload
})
});
}
```
* `Route::with_async()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_async_config()`
## 0.6
## Migration from 0.5 to 0.6
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
@@ -138,7 +50,7 @@
you need to use `use actix_web::ws::WsWriter`
## 0.5
## Migration from 0.4 to 0.5
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
methods return `HttpResponse` instead of `Result<HttpResponse>`

View File

@@ -1,6 +1,6 @@
.PHONY: default build test doc book clean
CARGO_FLAGS := --features "$(FEATURES) alpn tls"
CARGO_FLAGS := --features "$(FEATURES) alpn"
default: test

View File

@@ -25,10 +25,10 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/)
* [API Documentation (Releases)](https://docs.rs/actix-web/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.26 or later
* Minimum supported Rust version: 1.24 or later
## Example
@@ -69,7 +69,7 @@ You may consider checking out
## Benchmarks
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext)
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext)
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).

View File

@@ -1,5 +1,5 @@
max_width = 89
reorder_imports = true
#wrap_comments = true
wrap_comments = true
fn_args_density = "Compressed"
#use_small_heuristics = false

View File

@@ -1,91 +1,170 @@
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::rc::Rc;
use handler::{AsyncResult, FromRequest, Handler, Responder, WrapHandler};
use handler::{AsyncResult, FromRequest, Handler, Responder, RouteHandler, WrapHandler};
use header::ContentEncoding;
use http::Method;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::Middleware;
use pipeline::{Pipeline, PipelineHandler};
use pipeline::{HandlerType, Pipeline, PipelineHandler};
use pred::Predicate;
use resource::Resource;
use router::{ResourceDef, Router};
use resource::ResourceHandler;
use router::{Resource, Router};
use scope::Scope;
use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request};
use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, ServerSettings};
/// Application
pub struct HttpApplication<S = ()> {
state: Rc<S>,
prefix: String,
prefix_len: usize,
inner: Rc<Inner<S>>,
router: Router,
inner: Rc<UnsafeCell<Inner<S>>>,
filters: Option<Vec<Box<Predicate<S>>>>,
middlewares: Rc<Vec<Box<Middleware<S>>>>,
}
#[doc(hidden)]
pub struct Inner<S> {
router: Router<S>,
pub(crate) struct Inner<S> {
prefix: usize,
default: ResourceHandler<S>,
encoding: ContentEncoding,
resources: Vec<ResourceHandler<S>>,
handlers: Vec<PrefixHandlerType<S>>,
}
enum PrefixHandlerType<S> {
Handler(String, Box<RouteHandler<S>>),
Scope(Resource, Box<RouteHandler<S>>, Vec<Box<Predicate<S>>>),
}
impl<S: 'static> PipelineHandler<S> for Inner<S> {
#[inline]
fn encoding(&self) -> ContentEncoding {
self.encoding
}
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
self.router.handle(req)
fn handle(
&mut self, req: HttpRequest<S>, htype: HandlerType,
) -> AsyncResult<HttpResponse> {
match htype {
HandlerType::Normal(idx) => {
self.resources[idx].handle(req, Some(&mut self.default))
}
HandlerType::Handler(idx) => match self.handlers[idx] {
PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req),
PrefixHandlerType::Scope(_, ref mut hnd, _) => hnd.handle(req),
},
HandlerType::Default => self.default.handle(req, None),
}
}
}
impl<S: 'static> HttpApplication<S> {
#[cfg(test)]
pub(crate) fn run(&self, req: Request) -> AsyncResult<HttpResponse> {
let info = self
.inner
.router
.recognize(&req, &self.state, self.prefix_len);
let req = HttpRequest::new(req, Rc::clone(&self.state), info);
#[inline]
fn as_ref(&self) -> &Inner<S> {
unsafe { &*self.inner.get() }
}
self.inner.handle(&req)
#[inline]
fn get_handler(&self, req: &mut HttpRequest<S>) -> HandlerType {
if let Some(idx) = self.router.recognize(req) {
HandlerType::Normal(idx)
} else {
let inner = self.as_ref();
let path: &'static str =
unsafe { &*(&req.path()[inner.prefix..] as *const _) };
let path_len = path.len();
'outer: for idx in 0..inner.handlers.len() {
match inner.handlers[idx] {
PrefixHandlerType::Handler(ref prefix, _) => {
let m = {
path.starts_with(prefix)
&& (path_len == prefix.len()
|| path.split_at(prefix.len()).1.starts_with('/'))
};
if m {
let prefix_len = inner.prefix + prefix.len();
let path: &'static str =
unsafe { &*(&req.path()[prefix_len..] as *const _) };
req.set_prefix_len(prefix_len as u16);
if path.is_empty() {
req.match_info_mut().add("tail", "/");
} else {
req.match_info_mut().add("tail", path);
}
return HandlerType::Handler(idx);
}
}
PrefixHandlerType::Scope(ref pattern, _, ref filters) => {
if let Some(prefix_len) =
pattern.match_prefix_with_params(path, req.match_info_mut())
{
for filter in filters {
if !filter.check(req) {
continue 'outer;
}
}
let prefix_len = inner.prefix + prefix_len;
let path: &'static str =
unsafe { &*(&req.path()[prefix_len..] as *const _) };
req.set_prefix_len(prefix_len as u16);
req.match_info_mut().set("tail", path);
return HandlerType::Handler(idx);
}
}
}
}
HandlerType::Default
}
}
#[cfg(test)]
pub(crate) fn run(&mut self, mut req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
let tp = self.get_handler(&mut req);
unsafe { &mut *self.inner.get() }.handle(req, tp)
}
#[cfg(test)]
pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest<S> {
req.with_state(Rc::clone(&self.state), self.router.clone())
}
}
impl<S: 'static> HttpHandler for HttpApplication<S> {
type Task = Pipeline<S, Inner<S>>;
fn handle(&self, msg: Request) -> Result<Pipeline<S, Inner<S>>, Request> {
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest> {
let m = {
if self.prefix_len == 0 {
true
} else {
let path = msg.path();
path.starts_with(&self.prefix)
&& (path.len() == self.prefix_len
|| path.split_at(self.prefix_len).1.starts_with('/'))
}
let path = req.path();
path.starts_with(&self.prefix)
&& (path.len() == self.prefix_len
|| path.split_at(self.prefix_len).1.starts_with('/'))
};
if m {
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(&msg, &self.state) {
return Err(msg);
if !filter.check(&mut req2) {
return Err(req);
}
}
}
let info = self
.inner
.router
.recognize(&msg, &self.state, self.prefix_len);
let tp = self.get_handler(&mut req2);
let inner = Rc::clone(&self.inner);
let req = HttpRequest::new(msg, Rc::clone(&self.state), info);
Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner))
Ok(Box::new(Pipeline::new(
req2,
Rc::clone(&self.middlewares),
inner,
tp,
)))
} else {
Err(msg)
Err(req)
}
}
}
@@ -93,7 +172,11 @@ impl<S: 'static> HttpHandler for HttpApplication<S> {
struct ApplicationParts<S> {
state: S,
prefix: String,
router: Router<S>,
settings: ServerSettings,
default: ResourceHandler<S>,
resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
handlers: Vec<PrefixHandlerType<S>>,
external: HashMap<String, Resource>,
encoding: ContentEncoding,
middlewares: Vec<Box<Middleware<S>>>,
filters: Vec<Box<Predicate<S>>>,
@@ -109,7 +192,20 @@ impl App<()> {
/// Create application with empty state. Application can
/// be configured with a builder-like pattern.
pub fn new() -> App<()> {
App::with_state(())
App {
parts: Some(ApplicationParts {
state: (),
prefix: "/".to_owned(),
settings: ServerSettings::default(),
default: ResourceHandler::default_not_found(),
resources: Vec::new(),
handlers: Vec::new(),
external: HashMap::new(),
encoding: ContentEncoding::Auto,
filters: Vec::new(),
middlewares: Vec::new(),
}),
}
}
}
@@ -139,8 +235,12 @@ where
App {
parts: Some(ApplicationParts {
state,
prefix: "".to_owned(),
router: Router::new(),
prefix: "/".to_owned(),
settings: ServerSettings::default(),
default: ResourceHandler::default_not_found(),
resources: Vec::new(),
handlers: Vec::new(),
external: HashMap::new(),
middlewares: Vec::new(),
filters: Vec::new(),
encoding: ContentEncoding::Auto,
@@ -248,12 +348,26 @@ where
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_route(path, method, f);
{
let parts: &mut ApplicationParts<S> = unsafe {
&mut *(self.parts.as_mut().expect("Use after finish") as *mut _)
};
// get resource handler
for &mut (ref pattern, ref mut handler) in &mut parts.resources {
if let Some(ref mut handler) = *handler {
if pattern.pattern() == path {
handler.method(method).with(f);
return self;
}
}
}
let mut handler = ResourceHandler::default();
handler.method(method).with(f);
let pattern = Resource::new(handler.get_name(), path);
parts.resources.push((pattern, Some(handler)));
}
self
}
@@ -285,12 +399,17 @@ where
where
F: FnOnce(Scope<S>) -> Scope<S>,
{
let scope = f(Scope::new(path));
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_scope(scope);
{
let mut scope = Box::new(f(Scope::new()));
let parts = self.parts.as_mut().expect("Use after finish");
let filters = scope.take_filters();
parts.handlers.push(PrefixHandlerType::Scope(
Resource::prefix("", &path),
scope,
filters,
));
}
self
}
@@ -327,47 +446,41 @@ where
/// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> App<S>
where
F: FnOnce(&mut Resource<S>) -> R + 'static,
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
{
{
let parts = self.parts.as_mut().expect("Use after finish");
// create resource
let mut resource = Resource::new(ResourceDef::new(path));
// add resource handler
let mut handler = ResourceHandler::default();
f(&mut handler);
// configure
f(&mut resource);
parts.router.register_resource(resource);
let pattern = Resource::new(handler.get_name(), path);
parts.resources.push((pattern, Some(handler)));
}
self
}
/// Configure resource for a specific path.
#[doc(hidden)]
pub fn register_resource(&mut self, resource: Resource<S>) {
pub fn register_resource(&mut self, path: &str, resource: ResourceHandler<S>) {
let pattern = Resource::new(resource.get_name(), path);
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_resource(resource);
.resources
.push((pattern, Some(resource)));
}
/// Default resource to be used if no matching route could be found.
pub fn default_resource<F, R>(mut self, f: F) -> App<S>
where
F: FnOnce(&mut Resource<S>) -> R + 'static,
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
{
// create and configure default resource
let mut resource = Resource::new(ResourceDef::new(""));
f(&mut resource);
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_default_resource(resource.into());
{
let parts = self.parts.as_mut().expect("Use after finish");
f(&mut parts.default);
}
self
}
@@ -390,7 +503,7 @@ where
/// # extern crate actix_web;
/// use actix_web::{App, HttpRequest, HttpResponse, Result};
///
/// fn index(req: &HttpRequest) -> Result<HttpResponse> {
/// 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())
@@ -408,11 +521,17 @@ where
T: AsRef<str>,
U: AsRef<str>,
{
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_external(name.as_ref(), ResourceDef::external(url.as_ref()));
{
let parts = self.parts.as_mut().expect("Use after finish");
if parts.external.contains_key(name.as_ref()) {
panic!("External resource {:?} is registered.", name.as_ref());
}
parts.external.insert(
String::from(name.as_ref()),
Resource::external(name.as_ref(), url.as_ref()),
);
}
self
}
@@ -430,7 +549,7 @@ where
/// use actix_web::{http, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// let app = App::new().handler("/app", |req: &HttpRequest| match *req.method() {
/// let app = App::new().handler("/app", |req: HttpRequest| match *req.method() {
/// http::Method::GET => HttpResponse::Ok(),
/// http::Method::POST => HttpResponse::MethodNotAllowed(),
/// _ => HttpResponse::NotFound(),
@@ -446,11 +565,12 @@ where
if path.len() > 1 && path.ends_with('/') {
path.pop();
}
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_handler(&path, Box::new(WrapHandler::new(handler)), None);
let parts = self.parts.as_mut().expect("Use after finish");
parts.handlers.push(PrefixHandlerType::Handler(
path,
Box::new(WrapHandler::new(handler)),
));
}
self
}
@@ -488,7 +608,7 @@ where
/// let app = App::new()
/// .middleware(middleware::Logger::default())
/// .configure(config) // <- register resources
/// .handler("/static", fs::StaticFiles::new(".").unwrap());
/// .handler("/static", fs::StaticFiles::new("."));
/// }
/// ```
pub fn configure<F>(self, cfg: F) -> App<S>
@@ -500,14 +620,28 @@ where
/// Finish application configuration and create `HttpHandler` object.
pub fn finish(&mut self) -> HttpApplication<S> {
let mut parts = self.parts.take().expect("Use after finish");
let parts = self.parts.take().expect("Use after finish");
let prefix = parts.prefix.trim().trim_right_matches('/');
parts.router.finish();
let (prefix, prefix_len) = if prefix.is_empty() {
("/".to_owned(), 0)
} else {
(prefix.to_owned(), prefix.len())
};
let inner = Rc::new(Inner {
router: parts.router,
let mut resources = parts.resources;
for (_, pattern) in parts.external {
resources.push((pattern, None));
}
let (router, resources) = Router::new(&prefix, parts.settings, resources);
let inner = Rc::new(UnsafeCell::new(Inner {
prefix: prefix_len,
default: parts.default,
encoding: parts.encoding,
});
handlers: parts.handlers,
resources,
}));
let filters = if parts.filters.is_empty() {
None
} else {
@@ -515,12 +649,13 @@ where
};
HttpApplication {
state: Rc::new(parts.state),
router: router.clone(),
middlewares: Rc::new(parts.middlewares),
prefix,
prefix_len,
inner,
filters,
state: Rc::new(parts.state),
middlewares: Rc::new(parts.middlewares),
prefix: prefix.to_owned(),
prefix_len: prefix.len(),
}
}
@@ -539,7 +674,7 @@ where
/// struct State2;
///
/// fn main() {
/// # thread::spawn(|| {
/// # thread::spawn(|| {
/// server::new(|| {
/// vec![
/// App::with_state(State1)
@@ -554,33 +689,22 @@ where
/// }).bind("127.0.0.1:8080")
/// .unwrap()
/// .run()
/// # });
/// # });
/// }
/// ```
pub fn boxed(mut self) -> Box<HttpHandler<Task = Box<HttpHandlerTask>>> {
Box::new(BoxedApplication { app: self.finish() })
}
}
struct BoxedApplication<S> {
app: HttpApplication<S>,
}
impl<S: 'static> HttpHandler for BoxedApplication<S> {
type Task = Box<HttpHandlerTask>;
fn handle(&self, req: Request) -> Result<Self::Task, Request> {
self.app.handle(req).map(|t| {
let task: Self::Task = Box::new(t);
task
})
pub fn boxed(mut self) -> Box<HttpHandler> {
Box::new(self.finish())
}
}
impl<S: 'static> IntoHttpHandler for App<S> {
type Handler = HttpApplication<S>;
fn into_handler(mut self) -> HttpApplication<S> {
fn into_handler(mut self, settings: ServerSettings) -> HttpApplication<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.settings = settings;
}
self.finish()
}
}
@@ -588,7 +712,11 @@ impl<S: 'static> IntoHttpHandler for App<S> {
impl<'a, S: 'static> IntoHttpHandler for &'a mut App<S> {
type Handler = HttpApplication<S>;
fn into_handler(self) -> HttpApplication<S> {
fn into_handler(self, settings: ServerSettings) -> HttpApplication<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.settings = settings;
}
self.finish()
}
}
@@ -609,8 +737,6 @@ impl<S: 'static> Iterator for App<S> {
#[cfg(test)]
mod tests {
use super::*;
use body::{Binary, Body};
use fs;
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
@@ -619,206 +745,198 @@ mod tests {
#[test]
fn test_default_resource() {
let app = App::new()
let mut app = App::new()
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.finish();
let req = TestRequest::with_uri("/test").request();
let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/blah").request();
let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let app = App::new()
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
let mut app = App::new()
.default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed()))
.finish();
let req = TestRequest::with_uri("/blah").request();
let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
}
#[test]
fn test_unhandled_prefix() {
let app = App::new()
let mut app = App::new()
.prefix("/test")
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.finish();
let ctx = TestRequest::default().request();
assert!(app.handle(ctx).is_err());
assert!(app.handle(HttpRequest::default()).is_err());
}
#[test]
fn test_state() {
let app = App::with_state(10)
let mut app = App::with_state(10)
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
.finish();
let req = TestRequest::with_state(10).request();
let req =
HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone());
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
fn test_prefix() {
let app = App::new()
let mut app = App::new()
.prefix("/test")
.resource("/blah", |r| r.f(|_| HttpResponse::Ok()))
.finish();
let req = TestRequest::with_uri("/test").request();
let req = TestRequest::with_uri("/test").finish();
let resp = app.handle(req);
assert!(resp.is_ok());
let req = TestRequest::with_uri("/test/").request();
let req = TestRequest::with_uri("/test/").finish();
let resp = app.handle(req);
assert!(resp.is_ok());
let req = TestRequest::with_uri("/test/blah").request();
let req = TestRequest::with_uri("/test/blah").finish();
let resp = app.handle(req);
assert!(resp.is_ok());
let req = TestRequest::with_uri("/testing").request();
let req = TestRequest::with_uri("/testing").finish();
let resp = app.handle(req);
assert!(resp.is_err());
}
#[test]
fn test_handler() {
let app = App::new()
.handler("/test", |_: &_| HttpResponse::Ok())
.finish();
let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish();
let req = TestRequest::with_uri("/test").request();
let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/").request();
let req = TestRequest::with_uri("/test/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/app").request();
let req = TestRequest::with_uri("/test/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/testapp").request();
let req = TestRequest::with_uri("/testapp").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/blah").request();
let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_handler2() {
let app = App::new()
.handler("test", |_: &_| HttpResponse::Ok())
.finish();
let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish();
let req = TestRequest::with_uri("/test").request();
let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/").request();
let req = TestRequest::with_uri("/test/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/app").request();
let req = TestRequest::with_uri("/test/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/testapp").request();
let req = TestRequest::with_uri("/testapp").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/blah").request();
let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_handler_with_prefix() {
let app = App::new()
let mut app = App::new()
.prefix("prefix")
.handler("/test", |_: &_| HttpResponse::Ok())
.handler("/test", |_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/prefix/test").request();
let req = TestRequest::with_uri("/prefix/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/test/").request();
let req = TestRequest::with_uri("/prefix/test/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/test/app").request();
let req = TestRequest::with_uri("/prefix/test/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/testapp").request();
let req = TestRequest::with_uri("/prefix/testapp").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/prefix/blah").request();
let req = TestRequest::with_uri("/prefix/blah").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_route() {
let app = App::new()
let mut app = App::new()
.route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok())
.route("/test", Method::POST, |_: HttpRequest| {
HttpResponse::Created()
})
.finish();
let req = TestRequest::with_uri("/test").method(Method::GET).request();
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)
.request();
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)
.request();
let req = TestRequest::with_uri("/test").method(Method::HEAD).finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_handler_prefix() {
let app = App::new()
let mut app = App::new()
.prefix("/app")
.handler("/test", |_: &_| HttpResponse::Ok())
.handler("/test", |_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").request();
let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/test").request();
let req = TestRequest::with_uri("/app/test").finish();
let resp = app.run(req.clone());
assert_eq!(resp.as_msg().status(), StatusCode::OK);
assert_eq!(req.prefix_len(), 9);
let req = TestRequest::with_uri("/app/test/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/test/").request();
let req = TestRequest::with_uri("/app/test/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/test/app").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/testapp").request();
let req = TestRequest::with_uri("/app/testapp").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/blah").request();
let req = TestRequest::with_uri("/app/blah").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
@@ -828,7 +946,7 @@ mod tests {
let mut srv = TestServer::with_factory(|| {
App::new()
.filter(pred::Get())
.handler("/test", |_: &_| HttpResponse::Ok())
.handler("/test", |_| HttpResponse::Ok())
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
@@ -839,21 +957,4 @@ mod tests {
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_option_responder() {
let app = App::new()
.resource("/none", |r| r.f(|_| -> Option<&'static str> { None }))
.resource("/some", |r| r.f(|_| Some("some")))
.finish();
let req = TestRequest::with_uri("/none").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/some").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some")));
}
}

View File

@@ -1,5 +1,6 @@
use bytes::{Bytes, BytesMut};
use futures::Stream;
use std::rc::Rc;
use std::sync::Arc;
use std::{fmt, mem};
@@ -34,8 +35,10 @@ pub enum Binary {
/// Static slice
Slice(&'static [u8]),
/// Shared string body
SharedString(Rc<String>),
/// Shared string body
#[doc(hidden)]
SharedString(Arc<String>),
ArcSharedString(Arc<String>),
/// Shared vec body
SharedVec(Arc<Vec<u8>>),
}
@@ -127,18 +130,17 @@ impl From<Box<ActorHttpContext>> for Body {
impl Binary {
#[inline]
/// Returns `true` if body is empty
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
/// Length of body in bytes
pub fn len(&self) -> usize {
match *self {
Binary::Bytes(ref bytes) => bytes.len(),
Binary::Slice(slice) => slice.len(),
Binary::SharedString(ref s) => s.len(),
Binary::ArcSharedString(ref s) => s.len(),
Binary::SharedVec(ref s) => s.len(),
}
}
@@ -160,6 +162,7 @@ impl Clone for Binary {
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
Binary::SharedString(ref s) => Binary::SharedString(s.clone()),
Binary::ArcSharedString(ref s) => Binary::ArcSharedString(s.clone()),
Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()),
}
}
@@ -171,6 +174,7 @@ impl Into<Bytes> for Binary {
Binary::Bytes(bytes) => bytes,
Binary::Slice(slice) => Bytes::from(slice),
Binary::SharedString(s) => Bytes::from(s.as_str()),
Binary::ArcSharedString(s) => Bytes::from(s.as_str()),
Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())),
}
}
@@ -218,15 +222,27 @@ impl From<BytesMut> for Binary {
}
}
impl From<Rc<String>> for Binary {
fn from(body: Rc<String>) -> Binary {
Binary::SharedString(body)
}
}
impl<'a> From<&'a Rc<String>> for Binary {
fn from(body: &'a Rc<String>) -> Binary {
Binary::SharedString(Rc::clone(body))
}
}
impl From<Arc<String>> for Binary {
fn from(body: Arc<String>) -> Binary {
Binary::SharedString(body)
Binary::ArcSharedString(body)
}
}
impl<'a> From<&'a Arc<String>> for Binary {
fn from(body: &'a Arc<String>) -> Binary {
Binary::SharedString(Arc::clone(body))
Binary::ArcSharedString(Arc::clone(body))
}
}
@@ -249,6 +265,7 @@ impl AsRef<[u8]> for Binary {
Binary::Bytes(ref bytes) => bytes.as_ref(),
Binary::Slice(slice) => slice,
Binary::SharedString(ref s) => s.as_bytes(),
Binary::ArcSharedString(ref s) => s.as_bytes(),
Binary::SharedVec(ref s) => s.as_ref().as_ref(),
}
}
@@ -307,6 +324,22 @@ mod tests {
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
}
#[test]
fn test_ref_string() {
let b = Rc::new("test".to_owned());
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), b"test");
}
#[test]
fn test_rc_string() {
let b = Rc::new("test".to_owned());
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), b"test");
}
#[test]
fn test_arc_string() {
let b = Arc::new("test".to_owned());

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,29 @@
//! Http client api
//!
//! ```rust
//! # extern crate actix;
//! # extern crate actix_web;
//! # extern crate futures;
//! # extern crate tokio;
//! # use futures::Future;
//! # use std::process;
//! use actix_web::{actix, client};
//! use actix_web::client;
//!
//! fn main() {
//! actix::run(
//! || client::get("http://www.rust-lang.org") // <- Create request builder
//! let sys = actix::System::new("test");
//!
//! actix::Arbiter::handle().spawn({
//! client::get("http://www.rust-lang.org") // <- Create request builder
//! .header("User-Agent", "Actix-web")
//! .finish().unwrap()
//! .send() // <- Send http request
//! .map_err(|_| ())
//! .and_then(|response| { // <- server http response
//! println!("Response: {:?}", response);
//! # actix::System::current().stop();
//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
//! Ok(())
//! })
//! );
//! });
//!
//! sys.run();
//! }
//! ```
mod connector;
@@ -35,7 +38,6 @@ pub use self::connector::{
Pause, Resume,
};
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
pub(crate) use self::pipeline::Pipeline;
pub use self::pipeline::{SendRequest, SendRequestError};
pub use self::request::{ClientRequest, ClientRequestBuilder};
pub use self::response::ClientResponse;
@@ -58,29 +60,30 @@ impl ResponseError for SendRequestError {
/// Create request builder for `GET` requests
///
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate futures;
/// # extern crate tokio;
/// # extern crate env_logger;
/// # use futures::Future;
/// # use std::process;
/// use actix_web::{actix, client};
/// use actix_web::client;
///
/// fn main() {
/// actix::run(
/// || client::get("http://www.rust-lang.org") // <- Create request builder
/// let sys = actix::System::new("test");
///
/// actix::Arbiter::handle().spawn({
/// client::get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .finish().unwrap()
/// .send() // <- Send http request
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
/// .and_then(|response| { // <- server http response
/// println!("Response: {:?}", response);
/// # actix::System::current().stop();
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
/// Ok(())
/// }),
/// );
/// })
/// });
///
/// sys.run();
/// }
/// ```
pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {

View File

@@ -1,15 +1,14 @@
use std::mem;
use bytes::{Bytes, BytesMut};
use futures::{Async, Poll};
use http::header::{self, HeaderName, HeaderValue};
use http::{HeaderMap, StatusCode, Version};
use http::{HeaderMap, HttpTryFrom, StatusCode, Version};
use httparse;
use std::mem;
use error::{ParseError, PayloadError};
use server::h1decoder::{EncodingDecoder, HeaderIndex};
use server::IoStream;
use server::h1decoder::EncodingDecoder;
use server::{utils, IoStream};
use super::response::ClientMessage;
use super::ClientResponse;
@@ -40,11 +39,9 @@ impl HttpResponseParser {
{
// if buf is empty parse_message will always return NotReady, let's avoid that
if buf.is_empty() {
match io.read_available(buf) {
Ok(Async::Ready(true)) => {
return Err(HttpResponseParserError::Disconnect)
}
Ok(Async::Ready(false)) => (),
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect),
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => return Err(HttpResponseParserError::Error(err.into())),
}
@@ -62,11 +59,11 @@ impl HttpResponseParser {
if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(ParseError::TooLarge));
}
match io.read_available(buf) {
Ok(Async::Ready(true)) => {
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => {
return Err(HttpResponseParserError::Disconnect)
}
Ok(Async::Ready(false)) => (),
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => {
return Err(HttpResponseParserError::Error(err.into()))
@@ -86,11 +83,11 @@ impl HttpResponseParser {
if self.decoder.is_some() {
loop {
// read payload
let (not_ready, stream_finished) = match io.read_available(buf) {
Ok(Async::Ready(true)) => (false, true),
Ok(Async::Ready(false)) => (false, false),
Ok(Async::NotReady) => (true, false),
let (not_ready, stream_finished) = match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => (false, true),
Err(err) => return Err(err.into()),
Ok(Async::NotReady) => (true, false),
_ => (false, false),
};
match self.decoder.as_mut().unwrap().decode(buf) {
@@ -118,23 +115,24 @@ impl HttpResponseParser {
fn parse_message(
buf: &mut BytesMut,
) -> Poll<(ClientResponse, Option<EncodingDecoder>), ParseError> {
// Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() };
// Parse http message
let bytes_ptr = buf.as_ref().as_ptr() as usize;
let mut headers: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
let (len, version, status, headers_len) = {
let mut parsed: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
let mut resp = httparse::Response::new(&mut parsed);
match resp.parse(buf)? {
let b = unsafe {
let b: &[u8] = buf;
&*(b as *const _)
};
let mut resp = httparse::Response::new(&mut headers);
match resp.parse(b)? {
httparse::Status::Complete(len) => {
let version = if resp.version.unwrap_or(1) == 1 {
Version::HTTP_11
} else {
Version::HTTP_10
};
HeaderIndex::record(buf, resp.headers, &mut headers);
let status = StatusCode::from_u16(resp.code.unwrap())
.map_err(|_| ParseError::Status)?;
@@ -148,13 +146,12 @@ impl HttpResponseParser {
// convert headers
let mut hdrs = HeaderMap::new();
for idx in headers[..headers_len].iter() {
if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) {
// Unsafe: httparse check header value for valid utf-8
for header in headers[..headers_len].iter() {
if let Ok(name) = HeaderName::try_from(header.name) {
let v_start = header.value.as_ptr() as usize - bytes_ptr;
let v_end = v_start + header.value.len();
let value = unsafe {
HeaderValue::from_shared_unchecked(
slice.slice(idx.value.0, idx.value.1),
)
HeaderValue::from_shared_unchecked(slice.slice(v_start, v_end))
};
hdrs.append(name, value);
} else {

View File

@@ -1,25 +1,25 @@
use bytes::{Bytes, BytesMut};
use futures::sync::oneshot;
use futures::{Async, Future, Poll, Stream};
use futures::unsync::oneshot;
use futures::{Async, Future, Poll};
use http::header::CONTENT_ENCODING;
use std::time::{Duration, Instant};
use std::time::Duration;
use std::{io, mem};
use tokio_timer::Delay;
use tokio_core::reactor::Timeout;
use actix::{Addr, Request, SystemService};
use actix::prelude::*;
use super::{
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,
Connection, HttpClientWriter, HttpResponseParser, HttpResponseParserError,
};
use super::HttpClientWriter;
use super::{ClientConnector, ClientConnectorError, Connect, Connection};
use super::{ClientRequest, ClientResponse};
use super::{HttpResponseParser, HttpResponseParserError};
use body::{Body, BodyStream};
use context::{ActorHttpContext, Frame};
use error::Error;
use error::PayloadError;
use header::ContentEncoding;
use http::{Method, Uri};
use httpmessage::HttpMessage;
use server::input::PayloadStream;
use server::encoding::PayloadStream;
use server::shared::SharedBytes;
use server::WriterState;
/// A set of errors that can occur during request sending and response reading
@@ -56,7 +56,7 @@ impl From<ClientConnectorError> for SendRequestError {
enum State {
New,
Connect(Request<ClientConnector, Connect>),
Connect(actix::dev::Request<Unsync, ClientConnector, Connect>),
Connection(Connection),
Send(Box<Pipeline>),
None,
@@ -68,30 +68,23 @@ enum State {
pub struct SendRequest {
req: ClientRequest,
state: State,
conn: Option<Addr<ClientConnector>>,
conn: Addr<Unsync, ClientConnector>,
conn_timeout: Duration,
wait_timeout: Duration,
timeout: Option<Duration>,
timeout: Option<Timeout>,
}
impl SendRequest {
pub(crate) fn new(req: ClientRequest) -> SendRequest {
SendRequest {
req,
conn: None,
state: State::New,
timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
}
SendRequest::with_connector(req, ClientConnector::from_registry())
}
pub(crate) fn with_connector(
req: ClientRequest, conn: Addr<ClientConnector>,
req: ClientRequest, conn: Addr<Unsync, ClientConnector>,
) -> SendRequest {
SendRequest {
req,
conn: Some(conn),
conn,
state: State::New,
timeout: None,
wait_timeout: Duration::from_secs(5),
@@ -103,7 +96,7 @@ impl SendRequest {
SendRequest {
req,
state: State::Connection(conn),
conn: None,
conn: ClientConnector::from_registry(),
timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
@@ -115,7 +108,7 @@ impl SendRequest {
/// Request timeout is the total time before a response must be received.
/// Default value is 5 seconds.
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self.timeout = Some(Timeout::new(timeout, Arbiter::handle()).unwrap());
self
}
@@ -149,12 +142,7 @@ impl Future for SendRequest {
match state {
State::New => {
let conn = if let Some(conn) = self.conn.take() {
conn
} else {
ClientConnector::from_registry()
};
self.state = State::Connect(conn.send(Connect {
self.state = State::Connect(self.conn.send(Connect {
uri: self.req.uri().clone(),
wait_timeout: self.wait_timeout,
conn_timeout: self.conn_timeout,
@@ -172,11 +160,11 @@ impl Future for SendRequest {
Err(_) => {
return Err(SendRequestError::Connector(
ClientConnectorError::Disconnected,
));
))
}
},
State::Connection(conn) => {
let mut writer = HttpClientWriter::new();
let mut writer = HttpClientWriter::new(SharedBytes::default());
writer.start(&mut self.req)?;
let body = match self.req.replace_body(Body::Empty) {
@@ -185,10 +173,9 @@ impl Future for SendRequest {
_ => IoBody::Done,
};
let timeout = self
.timeout
.take()
.unwrap_or_else(|| Duration::from_secs(5));
let timeout = self.timeout.take().unwrap_or_else(|| {
Timeout::new(Duration::from_secs(5), Arbiter::handle()).unwrap()
});
let pl = Box::new(Pipeline {
body,
@@ -202,9 +189,7 @@ impl Future for SendRequest {
decompress: None,
should_decompress: self.req.response_decompress(),
write_state: RunningState::Running,
timeout: Some(Delay::new(Instant::now() + timeout)),
meth: self.req.method().clone(),
path: self.req.uri().clone(),
timeout: Some(timeout),
});
self.state = State::Send(pl);
}
@@ -216,9 +201,6 @@ impl Future for SendRequest {
match pl.parse() {
Ok(Async::Ready(mut resp)) => {
if self.req.method() == &Method::HEAD {
pl.parser.take();
}
resp.set_pipeline(pl);
return Ok(Async::Ready(resp));
}
@@ -226,9 +208,7 @@ impl Future for SendRequest {
self.state = State::Send(pl);
return Ok(Async::NotReady);
}
Err(err) => {
return Err(SendRequestError::ParseError(err));
}
Err(err) => return Err(SendRequestError::ParseError(err)),
}
}
State::None => unreachable!(),
@@ -237,7 +217,7 @@ impl Future for SendRequest {
}
}
pub struct Pipeline {
pub(crate) struct Pipeline {
body: IoBody,
body_completed: bool,
conn: Option<Connection>,
@@ -249,9 +229,7 @@ pub struct Pipeline {
decompress: Option<PayloadStream>,
should_decompress: bool,
write_state: RunningState,
timeout: Option<Delay>,
meth: Method,
path: Uri,
timeout: Option<Timeout>,
}
enum IoBody {
@@ -285,11 +263,7 @@ impl RunningState {
impl Pipeline {
fn release_conn(&mut self) {
if let Some(conn) = self.conn.take() {
if self.meth == Method::HEAD {
conn.close()
} else {
conn.release()
}
conn.release()
}
}
@@ -328,10 +302,13 @@ impl Pipeline {
}
#[inline]
pub(crate) fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
pub fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if self.conn.is_none() {
return Ok(Async::Ready(None));
}
let conn: &mut Connection =
unsafe { &mut *(self.conn.as_mut().unwrap() as *mut _) };
let mut need_run = false;
// need write?
@@ -349,8 +326,6 @@ impl Pipeline {
// need read?
if self.parser.is_some() {
let conn: &mut Connection = self.conn.as_mut().unwrap();
loop {
match self
.parser
@@ -405,7 +380,7 @@ impl Pipeline {
match self.timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
Ok(Async::NotReady) => (),
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e).into()),
Err(e) => return Err(e.into()),
}
}
Ok(())
@@ -429,7 +404,7 @@ impl Pipeline {
}
Async::Ready(Some(chunk)) => {
self.body = IoBody::Payload(body);
self.writer.write(chunk.as_ref())?
self.writer.write(chunk.into())?
}
Async::NotReady => {
done = true;
@@ -456,8 +431,7 @@ impl Pipeline {
break 'outter;
}
Frame::Chunk(Some(chunk)) => {
res =
Some(self.writer.write(chunk.as_ref())?)
res = Some(self.writer.write(chunk)?)
}
Frame::Drain(fut) => self.drain = Some(fut),
}
@@ -531,22 +505,7 @@ impl Pipeline {
impl Drop for Pipeline {
fn drop(&mut self) {
if let Some(conn) = self.conn.take() {
debug!(
"Client http transaction is not completed, dropping connection: {:?} {:?}",
self.meth,
self.path,
);
conn.close()
}
}
}
/// Future that resolves to a complete request body.
impl Stream for Box<Pipeline> {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
Pipeline::poll(self)
}
}

View File

@@ -3,14 +3,13 @@ use std::io::Write;
use std::time::Duration;
use std::{fmt, mem};
use actix::Addr;
use actix::{Addr, Unsync};
use bytes::{BufMut, Bytes, BytesMut};
use cookie::{Cookie, CookieJar};
use futures::Stream;
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use serde::Serialize;
use serde_json;
use serde_urlencoded;
use url::Url;
use super::connector::{ClientConnector, Connection};
@@ -26,26 +25,29 @@ use httprequest::HttpRequest;
/// An HTTP Client Request
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate futures;
/// # extern crate tokio;
/// # use futures::Future;
/// # use std::process;
/// use actix_web::{actix, client};
/// use actix_web::client::ClientRequest;
///
/// fn main() {
/// actix::run(
/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
/// let sys = actix::System::new("test");
///
/// actix::Arbiter::handle().spawn({
/// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .finish().unwrap()
/// .send() // <- Send http request
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
/// .and_then(|response| { // <- server http response
/// println!("Response: {:?}", response);
/// # actix::System::current().stop();
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
/// Ok(())
/// }),
/// );
/// })
/// });
///
/// sys.run();
/// }
/// ```
pub struct ClientRequest {
@@ -65,7 +67,7 @@ pub struct ClientRequest {
enum ConnectionType {
Default,
Connector(Addr<ClientConnector>),
Connector(Addr<Unsync, ClientConnector>),
Connection(Connection),
}
@@ -345,8 +347,7 @@ impl ClientRequestBuilder {
/// let req = client::ClientRequest::build()
/// .set(http::header::Date::now())
/// .set(http::header::ContentType(mime::TEXT_HTML))
/// .finish()
/// .unwrap();
/// .finish().unwrap();
/// }
/// ```
#[doc(hidden)]
@@ -378,8 +379,7 @@ impl ClientRequestBuilder {
/// let req = ClientRequest::build()
/// .header("X-TEST", "value")
/// .header(header::CONTENT_TYPE, "application/json")
/// .finish()
/// .unwrap();
/// .finish().unwrap();
/// }
/// ```
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
@@ -421,28 +421,6 @@ impl ClientRequestBuilder {
self
}
/// Set a header only if it is not yet set.
pub fn set_header_if_none<K, V>(&mut self, key: K, value: V) -> &mut Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => if !parts.headers.contains_key(&key) {
match value.try_into() {
Ok(value) => {
parts.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
}
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set content encoding.
///
/// By default `ContentEncoding::Identity` is used.
@@ -511,10 +489,8 @@ impl ClientRequestBuilder {
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .finish(),
/// )
/// .finish()
/// .unwrap();
/// .finish())
/// .finish().unwrap();
/// }
/// ```
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
@@ -529,7 +505,7 @@ impl ClientRequestBuilder {
}
/// Do not add default request headers.
/// By default `Accept-Encoding` and `User-Agent` headers are set.
/// By default `Accept-Encoding` header is set.
pub fn no_default_headers(&mut self) -> &mut Self {
self.default_headers = false;
self
@@ -565,7 +541,7 @@ impl ClientRequestBuilder {
}
/// Send request using custom connector
pub fn with_connector(&mut self, conn: Addr<ClientConnector>) -> &mut Self {
pub fn with_connector(&mut self, conn: Addr<Unsync, ClientConnector>) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connector(conn);
}
@@ -625,15 +601,10 @@ impl ClientRequestBuilder {
};
if https {
self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate");
self.header(header::ACCEPT_ENCODING, "br, gzip, deflate");
} else {
self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate");
self.header(header::ACCEPT_ENCODING, "gzip, deflate");
}
self.set_header_if_none(
header::USER_AGENT,
concat!("Actix-web/", env!("CARGO_PKG_VERSION")),
);
}
let mut request = self.request.take().expect("cannot reuse request builder");
@@ -673,24 +644,6 @@ impl ClientRequestBuilder {
self.body(body)
}
/// Set a urlencoded body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn form<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
let body = serde_urlencoded::to_string(&value)?;
let contains = if let Some(parts) = parts(&mut self.request, &self.err) {
parts.headers.contains_key(header::CONTENT_TYPE)
} else {
true
};
if !contains {
self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded");
}
self.body(body)
}
/// Set a streaming body and generate `ClientRequest`.
///
/// `ClientRequestBuilder` can not be used after this call.

View File

@@ -1,11 +1,14 @@
use std::cell::RefCell;
use std::cell::UnsafeCell;
use std::rc::Rc;
use std::{fmt, str};
use bytes::Bytes;
use cookie::Cookie;
use futures::{Async, Poll, Stream};
use http::header::{self, HeaderValue};
use http::{HeaderMap, StatusCode, Version};
use error::CookieParseError;
use error::{CookieParseError, PayloadError};
use httpmessage::HttpMessage;
use super::pipeline::Pipeline;
@@ -29,59 +32,64 @@ impl Default for ClientMessage {
}
/// An HTTP Client response
pub struct ClientResponse(ClientMessage, RefCell<Option<Box<Pipeline>>>);
pub struct ClientResponse(Rc<UnsafeCell<ClientMessage>>, Option<Box<Pipeline>>);
impl HttpMessage for ClientResponse {
type Stream = Box<Pipeline>;
/// Get the headers from the response.
#[inline]
fn headers(&self) -> &HeaderMap {
&self.0.headers
}
#[inline]
fn payload(&self) -> Box<Pipeline> {
self.1
.borrow_mut()
.take()
.expect("Payload is already consumed.")
&self.as_ref().headers
}
}
impl ClientResponse {
pub(crate) fn new(msg: ClientMessage) -> ClientResponse {
ClientResponse(msg, RefCell::new(None))
ClientResponse(Rc::new(UnsafeCell::new(msg)), None)
}
pub(crate) fn set_pipeline(&mut self, pl: Box<Pipeline>) {
*self.1.borrow_mut() = Some(pl);
self.1 = Some(pl);
}
#[inline]
fn as_ref(&self) -> &ClientMessage {
unsafe { &*self.0.get() }
}
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
fn as_mut(&self) -> &mut ClientMessage {
unsafe { &mut *self.0.get() }
}
/// Get the HTTP version of this response.
#[inline]
pub fn version(&self) -> Version {
self.0.version
self.as_ref().version
}
/// Get the status from the server.
#[inline]
pub fn status(&self) -> StatusCode {
self.0.status
self.as_ref().status
}
/// Load response cookies.
pub fn cookies(&self) -> Result<Vec<Cookie<'static>>, CookieParseError> {
let mut cookies = Vec::new();
for val in self.0.headers.get_all(header::SET_COOKIE).iter() {
let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
if self.as_ref().cookies.is_none() {
let msg = self.as_mut();
let mut cookies = Vec::new();
for val in msg.headers.get_all(header::SET_COOKIE).iter() {
let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
}
msg.cookies = Some(cookies)
}
Ok(cookies)
Ok(self.as_ref().cookies.as_ref().unwrap())
}
/// Return request cookie.
pub fn cookie(&self, name: &str) -> Option<Cookie> {
pub fn cookie(&self, name: &str) -> Option<&Cookie> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies {
if cookie.name() == name {
@@ -104,17 +112,31 @@ impl fmt::Debug for ClientResponse {
}
}
/// Future that resolves to a complete request body.
impl Stream for ClientResponse {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(ref mut pl) = self.1 {
pl.poll()
} else {
Ok(Async::Ready(None))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debug() {
let mut resp = ClientResponse::new(ClientMessage::default());
resp.0
let resp = ClientResponse::new(ClientMessage::default());
resp.as_mut()
.headers
.insert(header::COOKIE, HeaderValue::from_static("cookie1=value1"));
resp.0
resp.as_mut()
.headers
.insert(header::COOKIE, HeaderValue::from_static("cookie2=value2"));

View File

@@ -21,7 +21,8 @@ use tokio_io::AsyncWrite;
use body::{Binary, Body};
use header::ContentEncoding;
use server::output::{ContentEncoder, Output, TransferEncoding};
use server::encoding::{ContentEncoder, TransferEncoding};
use server::shared::SharedBytes;
use server::WriterState;
use client::ClientRequest;
@@ -41,18 +42,21 @@ pub(crate) struct HttpClientWriter {
flags: Flags,
written: u64,
headers_size: u32,
buffer: Output,
buffer: SharedBytes,
buffer_capacity: usize,
encoder: ContentEncoder,
}
impl HttpClientWriter {
pub fn new() -> HttpClientWriter {
pub fn new(buffer: SharedBytes) -> HttpClientWriter {
let encoder = ContentEncoder::Identity(TransferEncoding::eof(buffer.clone()));
HttpClientWriter {
flags: Flags::empty(),
written: 0,
headers_size: 0,
buffer_capacity: 0,
buffer: Output::Buffer(BytesMut::new()),
buffer,
encoder,
}
}
@@ -72,7 +76,7 @@ impl HttpClientWriter {
&mut self, stream: &mut T,
) -> io::Result<WriterState> {
while !self.buffer.is_empty() {
match stream.write(self.buffer.as_ref().as_ref()) {
match stream.write(self.buffer.as_ref()) {
Ok(0) => {
self.disconnected();
return Ok(WriterState::Done);
@@ -94,35 +98,21 @@ impl HttpClientWriter {
}
}
pub struct Writer<'a>(pub &'a mut BytesMut);
impl<'a> io::Write for Writer<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl HttpClientWriter {
pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> {
// prepare task
self.buffer = content_encoder(self.buffer.take(), msg);
self.flags.insert(Flags::STARTED);
self.encoder = content_encoder(self.buffer.clone(), msg);
if msg.upgrade() {
self.flags.insert(Flags::UPGRADE);
}
// render message
{
// output buffer
let buffer = self.buffer.as_mut();
// status line
writeln!(
Writer(buffer),
self.buffer,
"{} {} {:?}\r",
msg.method(),
msg.uri()
@@ -130,9 +120,10 @@ impl HttpClientWriter {
.map(|u| u.as_str())
.unwrap_or("/"),
msg.version()
).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
)?;
// write headers
let mut buffer = self.buffer.get_mut();
if let Body::Binary(ref bytes) = *msg.body() {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
} else {
@@ -152,29 +143,33 @@ impl HttpClientWriter {
// set date header
if !msg.headers().contains_key(DATE) {
buffer.extend_from_slice(b"date: ");
set_date(buffer);
set_date(&mut buffer);
buffer.extend_from_slice(b"\r\n\r\n");
} else {
buffer.extend_from_slice(b"\r\n");
}
}
self.headers_size = self.buffer.len() as u32;
self.headers_size = buffer.len() as u32;
if msg.body().is_binary() {
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
self.written += bytes.len() as u64;
self.buffer.write(bytes.as_ref())?;
if msg.body().is_binary() {
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
self.written += bytes.len() as u64;
self.encoder.write(bytes)?;
}
} else {
self.buffer_capacity = msg.write_buffer_capacity();
}
} else {
self.buffer_capacity = msg.write_buffer_capacity();
}
Ok(())
}
pub fn write(&mut self, payload: &[u8]) -> io::Result<WriterState> {
pub fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
self.written += payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) {
self.buffer.write(payload)?;
if self.flags.contains(Flags::UPGRADE) {
self.buffer.extend(payload);
} else {
self.encoder.write(payload)?;
}
}
if self.buffer.len() > self.buffer_capacity {
@@ -185,7 +180,9 @@ impl HttpClientWriter {
}
pub fn write_eof(&mut self) -> io::Result<()> {
if self.buffer.write_eof()? {
self.encoder.write_eof()?;
if self.encoder.is_eof() {
Ok(())
} else {
Err(io::Error::new(
@@ -213,7 +210,7 @@ impl HttpClientWriter {
}
}
fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder {
let version = req.version();
let mut body = req.replace_body(Body::Empty);
let mut encoding = req.content_encoding();
@@ -221,57 +218,45 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
let transfer = match body {
Body::Empty => {
req.headers_mut().remove(CONTENT_LENGTH);
return Output::Empty(buf);
TransferEncoding::length(0, buf)
}
Body::Binary(ref mut bytes) => {
#[cfg(any(feature = "flate2", feature = "brotli"))]
{
if encoding.is_compression() {
let mut tmp = BytesMut::new();
let mut transfer = TransferEncoding::eof(tmp);
let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::default()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
transfer,
Compression::default(),
)),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
ContentEncoder::Br(BrotliEncoder::new(transfer, 5))
}
ContentEncoding::Auto | ContentEncoding::Identity => {
unreachable!()
}
};
// TODO return error!
let _ = enc.write(bytes.as_ref());
let _ = enc.write_eof();
*bytes = Binary::from(enc.buf_mut().take());
if encoding.is_compression() {
let tmp = SharedBytes::default();
let transfer = TransferEncoding::eof(tmp.clone());
let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::default()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
transfer,
Compression::default(),
)),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
ContentEncoder::Br(BrotliEncoder::new(transfer, 5))
}
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
ContentEncoding::Auto => unreachable!(),
};
// TODO return error!
let _ = enc.write(bytes.clone());
let _ = enc.write_eof();
*bytes = Binary::from(tmp.take());
req.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
encoding = ContentEncoding::Identity;
}
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
}
#[cfg(not(any(feature = "flate2", feature = "brotli")))]
{
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
req.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
encoding = ContentEncoding::Identity;
}
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
}
Body::Streaming(_) | Body::Actor(_) => {
if req.upgrade() {
@@ -300,7 +285,7 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
}
req.replace_body(body);
let enc = match encoding {
match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
transfer,
@@ -312,13 +297,14 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
}
#[cfg(feature = "brotli")]
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 5)),
ContentEncoding::Identity | ContentEncoding::Auto => return Output::TE(transfer),
};
Output::Encoder(enc)
ContentEncoding::Identity | ContentEncoding::Auto => {
ContentEncoder::Identity(transfer)
}
}
}
fn streaming_encoding(
buf: BytesMut, version: Version, req: &mut ClientRequest,
buf: SharedBytes, version: Version, req: &mut ClientRequest,
) -> TransferEncoding {
if req.chunked() {
// Enable transfer encoding

View File

@@ -1,17 +1,14 @@
extern crate actix;
use futures::sync::oneshot;
use futures::sync::oneshot::Sender;
use futures::unsync::oneshot;
use futures::{Async, Future, Poll};
use smallvec::SmallVec;
use std::marker::PhantomData;
use self::actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope,
};
use self::actix::fut::ActorFuture;
use self::actix::{
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
use actix::fut::ActorFuture;
use actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
Syn, Unsync,
};
use body::{Binary, Body};
@@ -43,7 +40,7 @@ pub struct HttpContext<A, S = ()>
where
A: Actor<Context = HttpContext<A, S>>,
{
inner: ContextParts<A>,
inner: ContextImpl<A>,
stream: Option<SmallVec<[Frame; 4]>>,
request: HttpRequest<S>,
disconnected: bool,
@@ -93,9 +90,15 @@ where
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
self.inner.cancel_future(handle)
}
#[doc(hidden)]
#[inline]
fn address(&self) -> Addr<A> {
self.inner.address()
fn unsync_address(&mut self) -> Addr<Unsync, A> {
self.inner.unsync_address()
}
#[doc(hidden)]
#[inline]
fn sync_address(&mut self) -> Addr<Syn, A> {
self.inner.sync_address()
}
}
@@ -104,33 +107,21 @@ where
A: Actor<Context = Self>,
{
#[inline]
/// Create a new HTTP Context from a request and an actor
pub fn create(req: HttpRequest<S>, actor: A) -> Body {
let mb = Mailbox::default();
let ctx = HttpContext {
inner: ContextParts::new(mb.sender_producer()),
stream: None,
request: req,
disconnected: false,
};
Body::Actor(Box::new(HttpContextFut::new(ctx, actor, mb)))
pub fn new(req: HttpRequest<S>, actor: A) -> HttpContext<A, S> {
HttpContext::from_request(req).actor(actor)
}
/// Create a new HTTP Context
pub fn with_factory<F>(req: HttpRequest<S>, f: F) -> Body
where
F: FnOnce(&mut Self) -> A + 'static,
{
let mb = Mailbox::default();
let mut ctx = HttpContext {
inner: ContextParts::new(mb.sender_producer()),
pub fn from_request(req: HttpRequest<S>) -> HttpContext<A, S> {
HttpContext {
inner: ContextImpl::new(None),
stream: None,
request: req,
disconnected: false,
};
let act = f(&mut ctx);
Body::Actor(Box::new(HttpContextFut::new(ctx, act, mb)))
}
}
#[inline]
pub fn actor(mut self, actor: A) -> HttpContext<A, S> {
self.inner.set_actor(actor);
self
}
}
@@ -169,6 +160,7 @@ where
/// Returns drain future
pub fn drain(&mut self) -> Drain<A> {
let (tx, rx) = oneshot::channel();
self.inner.modify();
self.add_frame(Frame::Drain(tx));
Drain::new(rx)
}
@@ -187,6 +179,7 @@ where
if let Some(s) = self.stream.as_mut() {
s.push(frame)
}
self.inner.modify();
}
/// Handle of the running future
@@ -197,55 +190,32 @@ where
}
}
impl<A, S> AsyncContextParts<A> for HttpContext<A, S>
impl<A, S> ActorHttpContext for HttpContext<A, S>
where
A: Actor<Context = Self>,
{
fn parts(&mut self) -> &mut ContextParts<A> {
&mut self.inner
}
}
struct HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
{
fut: ContextFut<A, HttpContext<A, S>>,
}
impl<A, S> HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
{
fn new(ctx: HttpContext<A, S>, act: A, mailbox: Mailbox<A>) -> Self {
let fut = ContextFut::new(ctx, act, mailbox);
HttpContextFut { fut }
}
}
impl<A, S> ActorHttpContext for HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
S: 'static,
{
#[inline]
fn disconnected(&mut self) {
self.fut.ctx().disconnected = true;
self.fut.ctx().stop();
self.disconnected = true;
self.stop();
}
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
if self.fut.alive() {
match self.fut.poll() {
let ctx: &mut HttpContext<A, S> =
unsafe { &mut *(self as &mut HttpContext<A, S> as *mut _) };
if self.inner.alive() {
match self.inner.poll(ctx) {
Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
Err(_) => return Err(ErrorInternalServerError("error")),
}
}
// frames
if let Some(data) = self.fut.ctx().stream.take() {
if let Some(data) = self.stream.take() {
Ok(Async::Ready(Some(data)))
} else if self.fut.alive() {
} else if self.inner.alive() {
Ok(Async::NotReady)
} else {
Ok(Async::Ready(None))
@@ -253,25 +223,33 @@ where
}
}
impl<A, M, S> ToEnvelope<A, M> for HttpContext<A, S>
impl<A, M, S> ToEnvelope<Syn, A, M> for HttpContext<A, S>
where
A: Actor<Context = HttpContext<A, S>> + Handler<M>,
M: Message + Send + 'static,
M::Result: Send,
{
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
Envelope::new(msg, tx)
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> SyncEnvelope<A> {
SyncEnvelope::new(msg, tx)
}
}
impl<A, S> From<HttpContext<A, S>> for Body
where
A: Actor<Context = HttpContext<A, S>>,
S: 'static,
{
fn from(ctx: HttpContext<A, S>) -> Body {
Body::Actor(Box::new(ctx))
}
}
/// Consume a future
pub struct Drain<A> {
fut: oneshot::Receiver<()>,
_a: PhantomData<A>,
}
impl<A> Drain<A> {
/// Create a drain from a future
pub fn new(fut: oneshot::Receiver<()>) -> Self {
Drain {
fut,

View File

@@ -1,7 +1,9 @@
use serde::de::{self, Deserializer, Error as DeError, Visitor};
use std::borrow::Cow;
use std::convert::AsRef;
use std::slice::Iter;
use httprequest::HttpRequest;
use param::ParamsIter;
macro_rules! unsupported_type {
($trait_fn:ident, $name:expr) => {
@@ -189,7 +191,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
}
struct ParamsDeserializer<'de> {
params: ParamsIter<'de>,
params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>,
current: Option<(&'de str, &'de str)>,
}
@@ -200,7 +202,10 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
where
K: de::DeserializeSeed<'de>,
{
self.current = self.params.next().map(|ref item| (item.0, item.1));
self.current = self
.params
.next()
.map(|&(ref k, ref v)| (k.as_ref(), v.as_ref()));
match self.current {
Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)),
None => Ok(None),
@@ -376,7 +381,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
}
struct ParamsSeq<'de> {
params: ParamsIter<'de>,
params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>,
}
impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
@@ -387,7 +392,9 @@ impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
T: de::DeserializeSeed<'de>,
{
match self.params.next() {
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)),
Some(item) => Ok(Some(seed.deserialize(Value {
value: item.1.as_ref(),
})?)),
None => Ok(None),
}
}

View File

@@ -3,7 +3,7 @@ use std::io::Error as IoError;
use std::str::Utf8Error;
use std::string::FromUtf8Error;
use std::sync::Mutex;
use std::{fmt, io, result};
use std::{fmt, io, mem, result};
use actix::MailboxError;
use cookie;
@@ -12,19 +12,19 @@ use futures::Canceled;
use http::uri::InvalidUri;
use http::{header, Error as HttpError, StatusCode};
use http2::Error as Http2Error;
use http_range::HttpRangeParseError;
use httparse;
use serde::de::value::Error as DeError;
use serde_json::error::Error as JsonError;
use serde_urlencoded::ser::Error as FormError;
use tokio_timer::Error as TimerError;
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, HttpResponseParts};
use httpresponse::{HttpResponse, InnerHttpResponse};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations
@@ -37,7 +37,7 @@ pub type Result<T, E = Error> = result::Result<T, E>;
/// 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 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
@@ -106,8 +106,7 @@ impl Error {
pub struct CompatWrappedError {
error: failure::Error,
}
let compat: &CompatWrappedError =
unsafe { &*(compat as *const _ as *const CompatWrappedError) };
let compat: &CompatWrappedError = unsafe { ::std::mem::transmute(compat) };
compat.error.downcast_ref()
} else {
None
@@ -204,12 +203,6 @@ impl From<failure::Error> for Error {
/// `InternalServerError` for `JsonError`
impl ResponseError for JsonError {}
/// `InternalServerError` for `FormError`
impl ResponseError for FormError {}
/// `InternalServerError` for `TimerError`
impl ResponseError for TimerError {}
/// `InternalServerError` for `UrlParseError`
impl ResponseError for UrlParseError {}
@@ -373,18 +366,8 @@ impl From<IoError> for PayloadError {
}
}
/// `PayloadError` returns two possible results:
///
/// - `Overflow` returns `PayloadTooLarge`
/// - Other errors returns `BadRequest`
impl ResponseError for PayloadError {
fn error_response(&self) -> HttpResponse {
match *self {
PayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE),
_ => HttpResponse::new(StatusCode::BAD_REQUEST),
}
}
}
/// `InternalServerError` for `PayloadError`
impl ResponseError for PayloadError {}
/// Return `BadRequest` for `cookie::ParseError`
impl ResponseError for cookie::ParseError {
@@ -393,6 +376,37 @@ impl ResponseError for cookie::ParseError {
}
}
/// Http range header parsing error
#[derive(Fail, PartialEq, Debug)]
pub enum HttpRangeError {
/// Returned if range is invalid.
#[fail(display = "Range header is invalid")]
InvalidRange,
/// Returned if first-byte-pos of all of the byte-range-spec
/// values is greater than the content size.
/// See `https://github.com/golang/go/commit/aa9b3d7`
#[fail(
display = "First-byte-pos of all of the byte-range-spec values is greater than the content size"
)]
NoOverlap,
}
/// Return `BadRequest` for `HttpRangeError`
impl ResponseError for HttpRangeError {
fn error_response(&self) -> HttpResponse {
HttpResponse::with_body(StatusCode::BAD_REQUEST, "Invalid Range header provided")
}
}
impl From<HttpRangeParseError> for HttpRangeError {
fn from(err: HttpRangeParseError) -> HttpRangeError {
match err {
HttpRangeParseError::InvalidRange => HttpRangeError::InvalidRange,
HttpRangeParseError::NoOverlap => HttpRangeError::NoOverlap,
}
}
}
/// A set of errors that can occur during parsing multipart streams
#[derive(Fail, Debug)]
pub enum MultipartError {
@@ -476,10 +490,8 @@ pub enum UrlencodedError {
/// Can not decode chunked transfer encoding
#[fail(display = "Can not decode chunked transfer encoding")]
Chunked,
/// Payload size is bigger than allowed. (default: 256kB)
#[fail(
display = "Urlencoded payload size is bigger than allowed. (default: 256kB)"
)]
/// Payload size is bigger than 256k
#[fail(display = "Payload size is bigger than 256k")]
Overflow,
/// Payload size is now known
#[fail(display = "Payload size is now known")]
@@ -519,8 +531,8 @@ impl From<PayloadError> for UrlencodedError {
/// A set of errors that can occur during parsing json payloads
#[derive(Fail, Debug)]
pub enum JsonPayloadError {
/// Payload size is bigger than allowed. (default: 256kB)
#[fail(display = "Json payload size is bigger than allowed. (default: 256kB)")]
/// Payload size is bigger than 256k
#[fail(display = "Payload size is bigger than 256k")]
Overflow,
/// Content type error
#[fail(display = "Content type error")]
@@ -557,30 +569,6 @@ impl From<JsonError> for JsonPayloadError {
}
}
/// Error type returned when reading body as lines.
pub enum ReadlinesError {
/// Error when decoding a line.
EncodingError,
/// Payload error.
PayloadError(PayloadError),
/// Line limit exceeded.
LimitOverflow,
/// ContentType error.
ContentTypeError(ContentTypeError),
}
impl From<PayloadError> for ReadlinesError {
fn from(err: PayloadError) -> Self {
ReadlinesError::PayloadError(err)
}
}
impl From<ContentTypeError> for ReadlinesError {
fn from(err: ContentTypeError) -> Self {
ReadlinesError::ContentTypeError(err)
}
}
/// Errors which can occur when attempting to interpret a segment string as a
/// valid path segment.
#[derive(Fail, Debug, PartialEq)]
@@ -606,13 +594,12 @@ impl ResponseError for UriSegmentError {
/// Errors which can occur when attempting to generate resource uri.
#[derive(Fail, Debug, PartialEq)]
pub enum UrlGenerationError {
/// Resource not found
#[fail(display = "Resource not found")]
ResourceNotFound,
/// Not all path pattern covered
#[fail(display = "Not all path pattern covered")]
NotEnoughElements,
/// URL parse error
#[fail(display = "Router is not available")]
RouterNotAvailable,
#[fail(display = "{}", _0)]
ParseError(#[cause] UrlParseError),
}
@@ -626,24 +613,6 @@ impl From<UrlParseError> for UrlGenerationError {
}
}
/// Errors which can occur when serving static files.
#[derive(Fail, Debug, PartialEq)]
pub enum StaticFileError {
/// Path is not a directory
#[fail(display = "Path is not a directory. Unable to serve static files")]
IsNotDirectory,
/// Cannot render directory
#[fail(display = "Unable to render directory without index file")]
IsDirectory,
}
/// Return `NotFound` for `StaticFileError`
impl ResponseError for StaticFileError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::NOT_FOUND)
}
}
/// Helper type that can wrap any error and generate custom response.
///
/// In following example any `io::Error` will be converted into "BAD REQUEST"
@@ -669,7 +638,7 @@ pub struct InternalError<T> {
enum InternalErrorType {
Status(StatusCode),
Response(Box<Mutex<Option<HttpResponseParts>>>),
Response(Mutex<Option<Box<InnerHttpResponse>>>),
}
impl<T> InternalError<T> {
@@ -684,10 +653,21 @@ impl<T> InternalError<T> {
/// Create `InternalError` with predefined `HttpResponse`.
pub fn from_response(cause: T, response: HttpResponse) -> Self {
let resp = response.into_parts();
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(Box::new(Mutex::new(Some(resp)))),
status: InternalErrorType::Response(Mutex::new(Some(resp))),
backtrace: Backtrace::new(),
}
}
@@ -729,7 +709,7 @@ where
InternalErrorType::Status(st) => HttpResponse::new(st),
InternalErrorType::Response(ref resp) => {
if let Some(resp) = resp.lock().unwrap().take() {
HttpResponse::from_parts(resp)
HttpResponse::from_inner(resp)
} else {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
}
@@ -924,6 +904,9 @@ mod tests {
let resp: HttpResponse = ParseError::Incomplete.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = HttpRangeError::InvalidRange.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = CookieParseError::EmptyName.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
@@ -973,6 +956,14 @@ mod tests {
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_range_error() {
let e: HttpRangeError = HttpRangeParseError::InvalidRange.into();
assert_eq!(e, HttpRangeError::InvalidRange);
let e: HttpRangeError = HttpRangeParseError::NoOverlap.into();
assert_eq!(e, HttpRangeError::NoOverlap);
}
#[test]
fn test_expect_error() {
let resp: HttpResponse = ExpectError::Encoding.error_response();

View File

@@ -1,113 +0,0 @@
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::fmt;
use std::hash::{BuildHasherDefault, Hasher};
struct IdHasher {
id: u64,
}
impl Default for IdHasher {
fn default() -> IdHasher {
IdHasher { id: 0 }
}
}
impl Hasher for IdHasher {
fn write(&mut self, bytes: &[u8]) {
for &x in bytes {
self.id.wrapping_add(u64::from(x));
}
}
fn write_u64(&mut self, u: u64) {
self.id = u;
}
fn finish(&self) -> u64 {
self.id
}
}
type AnyMap = HashMap<TypeId, Box<Any>, BuildHasherDefault<IdHasher>>;
/// A type map of request extensions.
pub struct Extensions {
map: AnyMap,
}
impl Extensions {
/// Create an empty `Extensions`.
#[inline]
pub(crate) fn new() -> Extensions {
Extensions {
map: HashMap::default(),
}
}
/// Insert a type into this `Extensions`.
///
/// If a extension of this type already existed, it will
/// be returned.
pub fn insert<T: 'static>(&mut self, val: T) {
self.map.insert(TypeId::of::<T>(), Box::new(val));
}
/// Get a reference to a type previously inserted on this `Extensions`.
pub fn get<T: 'static>(&self) -> Option<&T> {
self.map
.get(&TypeId::of::<T>())
.and_then(|boxed| (&**boxed as &(Any + 'static)).downcast_ref())
}
/// Get a mutable reference to a type previously inserted on this `Extensions`.
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.map
.get_mut(&TypeId::of::<T>())
.and_then(|boxed| (&mut **boxed as &mut (Any + 'static)).downcast_mut())
}
/// Remove a type from this `Extensions`.
///
/// If a extension of this type existed, it will be returned.
pub fn remove<T: 'static>(&mut self) -> Option<T> {
self.map.remove(&TypeId::of::<T>()).and_then(|boxed| {
(boxed as Box<Any + 'static>)
.downcast()
.ok()
.map(|boxed| *boxed)
})
}
/// Clear the `Extensions` of all inserted extensions.
#[inline]
pub fn clear(&mut self) {
self.map.clear();
}
}
impl fmt::Debug for Extensions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Extensions").finish()
}
}
#[test]
fn test_extensions() {
#[derive(Debug, PartialEq)]
struct MyType(i32);
let mut extensions = Extensions::new();
extensions.insert(5i32);
extensions.insert(MyType(10));
assert_eq!(extensions.get(), Some(&5i32));
assert_eq!(extensions.get_mut(), Some(&mut 5i32));
assert_eq!(extensions.remove::<i32>(), Some(5i32));
assert!(extensions.get::<i32>().is_none());
assert_eq!(extensions.get::<bool>(), None);
assert_eq!(extensions.get(), Some(&MyType(10)));
}

View File

@@ -1,7 +1,6 @@
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use std::{fmt, str};
use std::str;
use bytes::Bytes;
use encoding::all::UTF_8;
@@ -12,7 +11,7 @@ use serde::de::{self, DeserializeOwned};
use serde_urlencoded;
use de::PathDeserializer;
use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError};
use error::{Error, ErrorBadRequest, ErrorNotFound};
use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest;
@@ -25,7 +24,7 @@ use httprequest::HttpRequest;
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// use actix_web::{http, App, Path, Result};
/// use actix_web::{App, Path, Result, http};
///
/// /// extract path info from "/{username}/{count}/index.html" url
/// /// {username} - deserializes to a String
@@ -36,9 +35,8 @@ use httprequest::HttpRequest;
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/{count}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// "/{username}/{count}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// }
/// ```
///
@@ -50,7 +48,7 @@ use httprequest::HttpRequest;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Path, Result};
/// use actix_web::{App, Path, Result, http};
///
/// #[derive(Deserialize)]
/// struct Info {
@@ -64,9 +62,8 @@ use httprequest::HttpRequest;
///
/// 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
/// }
/// ```
pub struct Path<T> {
@@ -116,18 +113,6 @@ where
}
}
impl<T: fmt::Debug> fmt::Debug for Path<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Path<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}
/// Extract typed information from from the request's query.
///
/// ## Example
@@ -139,24 +124,15 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Query, http};
///
///
///#[derive(Debug, Deserialize)]
///pub enum ResponseType {
/// Token,
/// Code
///}
///
///#[derive(Deserialize)]
///pub struct AuthRequest {
/// id: u64,
/// response_type: ResponseType,
///}
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// // use `with` extractor for query info
/// // this handler get called only if request's query contains `username` field
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
/// fn index(info: Query<AuthRequest>) -> String {
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
/// fn index(info: Query<Info>) -> String {
/// format!("Welcome {}!", info.username)
/// }
///
/// fn main() {
@@ -197,24 +173,13 @@ where
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone();
serde_urlencoded::from_str::<T>(req.query_string())
.map_err(|e| e.into())
.map(Query)
}
}
impl<T: fmt::Debug> fmt::Debug for Query<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Query<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
/// Extract typed information from the request's body.
///
/// To extract typed information from request's body, the type `T` must
@@ -271,40 +236,26 @@ where
T: DeserializeOwned + 'static,
S: 'static,
{
type Config = FormConfig<S>;
type Config = FormConfig;
type Result = Box<Future<Item = Self, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let req2 = req.clone();
let err = Rc::clone(&cfg.ehandler);
Box::new(
UrlEncoded::new(req)
UrlEncoded::new(req.clone())
.limit(cfg.limit)
.map_err(move |e| (*err)(e, &req2))
.from_err()
.map(Form),
)
}
}
impl<T: fmt::Debug> fmt::Debug for Form<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Form<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
/// Form extractor configuration
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Form, Result};
/// use actix_web::{App, Form, Result, http};
///
/// #[derive(Deserialize)]
/// struct FormData {
@@ -319,43 +270,28 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html",
/// |r| {
/// r.method(http::Method::GET)
/// // register form handler and change form extractor configuration
/// .with_config(index, |cfg| {cfg.limit(4096);})
/// },
/// "/index.html", |r| {
/// r.method(http::Method::GET)
/// .with(index)
/// .limit(4096);} // <- change form extractor configuration
/// );
/// }
/// ```
pub struct FormConfig<S> {
pub struct FormConfig {
limit: usize,
ehandler: Rc<Fn(UrlencodedError, &HttpRequest<S>) -> Error>,
}
impl<S> FormConfig<S> {
impl FormConfig {
/// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self {
self.limit = limit;
self
}
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
where
F: Fn(UrlencodedError, &HttpRequest<S>) -> Error + 'static,
{
self.ehandler = Rc::new(f);
self
}
}
impl<S> Default for FormConfig<S> {
impl Default for FormConfig {
fn default() -> Self {
FormConfig {
limit: 262_144,
ehandler: Rc::new(|e, _| e.into()),
}
FormConfig { limit: 262_144 }
}
}
@@ -379,8 +315,9 @@ impl<S> Default for FormConfig<S> {
/// }
///
/// fn main() {
/// let app = App::new()
/// .resource("/index.html", |r| r.method(http::Method::GET).with(index));
/// let app = App::new().resource(
/// "/index.html", |r|
/// r.method(http::Method::GET).with(index));
/// }
/// ```
impl<S: 'static> FromRequest<S> for Bytes {
@@ -392,7 +329,9 @@ impl<S: 'static> FromRequest<S> for Bytes {
// check content-type
cfg.check_mimetype(req)?;
Ok(Box::new(MessageBody::new(req).limit(cfg.limit).from_err()))
Ok(Box::new(
MessageBody::new(req.clone()).limit(cfg.limit).from_err(),
))
}
}
@@ -415,12 +354,12 @@ impl<S: 'static> FromRequest<S> for Bytes {
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET)
/// .with_config(index, |cfg| { // <- register handler with extractor params
/// cfg.limit(4096); // <- limit size of the payload
/// })
/// });
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::GET)
/// .with(index) // <- register handler with extractor params
/// .limit(4096); // <- limit size of the payload
/// });
/// }
/// ```
impl<S: 'static> FromRequest<S> for String {
@@ -436,7 +375,7 @@ impl<S: 'static> FromRequest<S> for String {
let encoding = req.encoding()?;
Ok(Box::new(
MessageBody::new(req)
MessageBody::new(req.clone())
.limit(cfg.limit)
.from_err()
.and_then(move |body| {
@@ -622,8 +561,9 @@ mod tests {
use futures::{Async, Future};
use http::header;
use mime;
use resource::Resource;
use router::{ResourceDef, Router};
use resource::ResourceHandler;
use router::{Resource, Router};
use server::ServerSettings;
use test::TestRequest;
#[derive(Deserialize, Debug, PartialEq)]
@@ -634,9 +574,9 @@ mod tests {
#[test]
fn test_bytes() {
let cfg = PayloadConfig::default();
let req = TestRequest::with_header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() {
Async::Ready(s) => {
@@ -649,9 +589,9 @@ mod tests {
#[test]
fn test_string() {
let cfg = PayloadConfig::default();
let req = TestRequest::with_header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
match String::from_request(&req, &cfg).unwrap().poll().unwrap() {
Async::Ready(s) => {
@@ -663,12 +603,13 @@ mod tests {
#[test]
fn test_form() {
let req = TestRequest::with_header(
let mut req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
let mut cfg = FormConfig::default();
cfg.limit(4096);
@@ -682,7 +623,7 @@ mod tests {
#[test]
fn test_payload_config() {
let req = TestRequest::default().finish();
let req = HttpRequest::default();
let mut cfg = PayloadConfig::default();
cfg.mimetype(mime::APPLICATION_JSON);
assert!(cfg.check_mimetype(&req).is_err());
@@ -717,12 +658,14 @@ mod tests {
#[test]
fn test_request_extract() {
let req = TestRequest::with_uri("/name/user1/?id=test").finish();
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
let mut router = Router::<()>::new();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut routes = Vec::new();
routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some());
let s = Path::<MyStruct>::from_request(&req, &()).unwrap();
assert_eq!(s.key, "name");
@@ -735,11 +678,8 @@ mod tests {
let s = Query::<Id>::from_request(&req, &()).unwrap();
assert_eq!(s.id, "test");
let mut router = Router::<()>::new();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let req = TestRequest::with_uri("/name/32/").finish();
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
let mut req = TestRequest::with_uri("/name/32/").finish();
assert!(router.recognize(&mut req).is_some());
let s = Path::<Test2>::from_request(&req, &()).unwrap();
assert_eq!(s.as_ref().key, "name");
@@ -756,23 +696,28 @@ mod tests {
#[test]
fn test_extract_path_single() {
let mut router = Router::<()>::new();
router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut routes = Vec::new();
routes.push((Resource::new("index", "/{value}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes);
let req = TestRequest::with_uri("/32/").finish();
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
assert_eq!(*Path::<i8>::from_request(&req, &()).unwrap(), 32);
let mut req = TestRequest::with_uri("/32/").finish();
assert!(router.recognize(&mut req).is_some());
assert_eq!(*Path::<i8>::from_request(&mut req, &()).unwrap(), 32);
}
#[test]
fn test_tuple_extract() {
let mut router = Router::<()>::new();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
let req = TestRequest::with_uri("/name/user1/?id=test").finish();
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut routes = Vec::new();
routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some());
let res = match <(Path<(String, String)>,)>::extract(&req).poll() {
Ok(Async::Ready(res)) => res,

1100
src/fs.rs

File diff suppressed because it is too large Load Diff

View File

@@ -5,10 +5,8 @@ use futures::future::{err, ok, Future};
use futures::{Async, Poll};
use error::Error;
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use resource::DefaultResource;
/// Trait defines object that could be registered as route handler
#[allow(unused_variables)]
@@ -17,7 +15,7 @@ pub trait Handler<S>: 'static {
type Result: Responder;
/// Handle request
fn handle(&self, req: &HttpRequest<S>) -> Self::Result;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result;
}
/// Trait implemented by types that generate responses for clients.
@@ -63,24 +61,22 @@ pub trait FromRequest<S>: Sized {
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::future::Future;
/// use actix_web::{AsyncResponder, Either, Error, HttpRequest, HttpResponse};
/// use futures::future::result;
/// use actix_web::{Either, Error, HttpRequest, HttpResponse, AsyncResponder};
///
/// type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>;
///
/// type RegisterResult =
/// Either<HttpResponse, Box<Future<Item = HttpResponse, Error = Error>>>;
///
/// fn index(req: HttpRequest) -> RegisterResult {
/// if is_a_variant() {
/// // <- choose variant A
/// Either::A(HttpResponse::BadRequest().body("Bad data"))
/// if is_a_variant() { // <- choose variant A
/// Either::A(
/// HttpResponse::BadRequest().body("Bad data"))
/// } else {
/// Either::B(
/// // <- variant B
/// Either::B( // <- variant B
/// result(Ok(HttpResponse::Ok()
/// .content_type("text/html")
/// .body("Hello!")))
/// .responder(),
/// )
/// .content_type("text/html")
/// .body("Hello!")))
/// .responder())
/// }
/// }
/// # fn is_a_variant() -> bool { true }
@@ -134,26 +130,6 @@ where
}
}
impl<T> Responder for Option<T>
where
T: Responder,
{
type Item = AsyncResult<HttpResponse>;
type Error = Error;
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
match self {
Some(t) => match t.respond_to(req) {
Ok(val) => Ok(val.into()),
Err(err) => Err(err.into()),
},
None => Ok(req.build_response(StatusCode::NOT_FOUND).finish().into()),
}
}
}
/// Convenience trait that converts `Future` object to a `Boxed` future
///
/// For example loading json from request's body is async operation.
@@ -162,17 +138,16 @@ where
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{
/// App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse,
/// };
/// use futures::future::Future;
/// use actix_web::{
/// App, HttpRequest, HttpResponse, HttpMessage, Error, AsyncResponder};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
@@ -184,7 +159,6 @@ where
/// # fn main() {}
/// ```
pub trait AsyncResponder<I, E>: Sized {
/// Convert to a boxed future
fn responder(self) -> Box<Future<Item = I, Error = E>>;
}
@@ -202,12 +176,12 @@ where
/// Handler<S> for Fn()
impl<F, R, S> Handler<S> for F
where
F: Fn(&HttpRequest<S>) -> R + 'static,
F: Fn(HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
type Result = R;
fn handle(&self, req: &HttpRequest<S>) -> R {
fn handle(&mut self, req: HttpRequest<S>) -> R {
(self)(req)
}
}
@@ -401,16 +375,9 @@ where
}
}
// /// Trait defines object that could be registered as resource route
pub(crate) trait RouteHandler<S>: 'static {
fn handle(&self, &HttpRequest<S>) -> AsyncResult<HttpResponse>;
fn has_default_resource(&self) -> bool {
false
}
fn default_resource(&mut self, _: DefaultResource<S>) {}
fn finish(&mut self) {}
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse>;
}
/// Route handler wrapper for Handler
@@ -441,8 +408,8 @@ where
R: Responder + 'static,
S: 'static,
{
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
match self.h.handle(req).respond_to(req) {
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
match self.h.handle(req.clone()).respond_to(&req) {
Ok(reply) => reply.into(),
Err(err) => AsyncResult::err(err.into()),
}
@@ -452,7 +419,7 @@ where
/// Async route handler
pub(crate) struct AsyncHandler<S, H, F, R, E>
where
H: Fn(&HttpRequest<S>) -> F + 'static,
H: Fn(HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
@@ -464,7 +431,7 @@ where
impl<S, H, F, R, E> AsyncHandler<S, H, F, R, E>
where
H: Fn(&HttpRequest<S>) -> F + 'static,
H: Fn(HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
@@ -480,15 +447,14 @@ where
impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
where
H: Fn(&HttpRequest<S>) -> F + 'static,
H: Fn(HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
S: 'static,
{
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
let req = req.clone();
let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| {
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
let fut = (self.h)(req.clone()).map_err(|e| e.into()).then(move |r| {
match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => Either::A(ok(resp)),
@@ -513,12 +479,10 @@ where
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Path, State};
/// use actix_web::{App, Path, State, http};
///
/// /// Application state
/// struct MyApp {
/// msg: &'static str,
/// }
/// struct MyApp {msg: &'static str}
///
/// #[derive(Deserialize)]
/// struct Info {
@@ -532,10 +496,9 @@ where
/// }
///
/// fn main() {
/// let app = App::with_state(MyApp { msg: "Welcome" }).resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// let app = App::with_state(MyApp{msg: "Welcome"}).resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// }
/// ```
pub struct State<S>(HttpRequest<S>);

View File

@@ -7,11 +7,12 @@
// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml
use language_tags::LanguageTag;
use header;
use header::{Header, IntoHeaderValue, Writer};
use header::shared::Charset;
use std::fmt;
use unicase;
use std::fmt::{self, Write};
use header::{Header, Raw, parsing};
use header::parsing::{parse_extended_value, http_percent_encode};
use header::shared::Charset;
/// The implied disposition of the content of the HTTP body.
#[derive(Clone, Debug, PartialEq)]
@@ -68,25 +69,17 @@ pub enum DispositionParam {
/// # Example
///
/// ```
/// use actix_web::http::header::{ContentDisposition, DispositionType, DispositionParam, Charset};
/// use hyper::header::{Headers, ContentDisposition, DispositionType, DispositionParam, Charset};
///
/// let cd1 = ContentDisposition {
/// let mut headers = Headers::new();
/// headers.set(ContentDisposition {
/// disposition: DispositionType::Attachment,
/// parameters: vec![DispositionParam::Filename(
/// Charset::Iso_8859_1, // The character set for the bytes of the filename
/// None, // The optional language tag (see `language-tag` crate)
/// b"\xa9 Copyright 1989.txt".to_vec() // the actual bytes of the filename
/// )]
/// };
///
/// let cd2 = ContentDisposition {
/// disposition: DispositionType::Inline,
/// parameters: vec![DispositionParam::Filename(
/// Charset::Ext("UTF-8".to_owned()),
/// None,
/// "\u{2764}".as_bytes().to_vec()
/// )]
/// };
/// });
/// ```
#[derive(Clone, Debug, PartialEq)]
pub struct ContentDisposition {
@@ -95,20 +88,25 @@ pub struct ContentDisposition {
/// Disposition parameters
pub parameters: Vec<DispositionParam>,
}
impl ContentDisposition {
/// Parse a raw Content-Disposition header value
pub fn from_raw(hv: &header::HeaderValue) -> Result<Self, ::error::ParseError> {
header::from_one_raw_str(Some(hv)).and_then(|s: String| {
impl Header for ContentDisposition {
fn header_name() -> &'static str {
static NAME: &'static str = "Content-Disposition";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<ContentDisposition> {
parsing::from_one_raw_str(raw).and_then(|s: String| {
let mut sections = s.split(';');
let disposition = match sections.next() {
Some(s) => s.trim(),
None => return Err(::error::ParseError::Header),
None => return Err(::Error::Header),
};
let mut cd = ContentDisposition {
disposition: if disposition.eq_ignore_ascii_case("inline") {
disposition: if unicase::eq_ascii(&*disposition, "inline") {
DispositionType::Inline
} else if disposition.eq_ignore_ascii_case("attachment") {
} else if unicase::eq_ascii(&*disposition, "attachment") {
DispositionType::Attachment
} else {
DispositionType::Ext(disposition.to_owned())
@@ -122,22 +120,22 @@ impl ContentDisposition {
let key = if let Some(key) = parts.next() {
key.trim()
} else {
return Err(::error::ParseError::Header);
return Err(::Error::Header);
};
let val = if let Some(val) = parts.next() {
val.trim()
} else {
return Err(::error::ParseError::Header);
return Err(::Error::Header);
};
cd.parameters.push(
if key.eq_ignore_ascii_case("filename") {
if unicase::eq_ascii(&*key, "filename") {
DispositionParam::Filename(
Charset::Ext("UTF-8".to_owned()), None,
val.trim_matches('"').as_bytes().to_owned())
} else if key.eq_ignore_ascii_case("filename*") {
let extended_value = try!(header::parse_extended_value(val));
} else if unicase::eq_ascii(&*key, "filename*") {
let extended_value = try!(parse_extended_value(val));
DispositionParam::Filename(extended_value.charset, extended_value.language_tag, extended_value.value)
} else {
DispositionParam::Ext(key.to_owned(), val.trim_matches('"').to_owned())
@@ -148,29 +146,10 @@ impl ContentDisposition {
Ok(cd)
})
}
}
impl IntoHeaderValue for ContentDisposition {
type Error = header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self);
header::HeaderValue::from_shared(writer.take())
}
}
impl Header for ContentDisposition {
fn name() -> header::HeaderName {
header::CONTENT_DISPOSITION
}
fn parse<T: ::HttpMessage>(msg: &T) -> Result<Self, ::error::ParseError> {
if let Some(h) = msg.headers().get(Self::name()) {
Self::from_raw(&h)
} else {
Err(::error::ParseError::Header)
}
#[inline]
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}
@@ -187,15 +166,14 @@ impl fmt::Display for ContentDisposition {
let mut use_simple_format: bool = false;
if opt_lang.is_none() {
if let Charset::Ext(ref ext) = *charset {
if ext.eq_ignore_ascii_case("utf-8") {
if unicase::eq_ascii(&**ext, "utf-8") {
use_simple_format = true;
}
}
}
if use_simple_format {
use std::str;
try!(write!(f, "; filename=\"{}\"",
match str::from_utf8(bytes) {
match String::from_utf8(bytes.clone()) {
Ok(s) => s,
Err(_) => return Err(fmt::Error),
}));
@@ -205,7 +183,7 @@ impl fmt::Display for ContentDisposition {
try!(write!(f, "{}", lang));
};
try!(write!(f, "'"));
try!(header::http_percent_encode(f, bytes))
try!(http_percent_encode(f, bytes))
}
},
DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)),
@@ -218,14 +196,15 @@ impl fmt::Display for ContentDisposition {
#[cfg(test)]
mod tests {
use super::{ContentDisposition,DispositionType,DispositionParam};
use header::HeaderValue;
use header::shared::Charset;
#[test]
fn test_from_raw() {
assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err());
use ::header::Header;
use ::header::shared::Charset;
let a = HeaderValue::from_static("form-data; dummy=3; name=upload; filename=\"sample.png\"");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
#[test]
fn test_parse_header() {
assert!(ContentDisposition::parse_header(&"".into()).is_err());
let a = "form-data; dummy=3; name=upload;\r\n filename=\"sample.png\"".into();
let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Ext("form-data".to_owned()),
parameters: vec![
@@ -238,8 +217,8 @@ mod tests {
};
assert_eq!(a, b);
let a = HeaderValue::from_static("attachment; filename=\"image.jpg\"");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let a = "attachment; filename=\"image.jpg\"".into();
let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![
@@ -250,8 +229,8 @@ mod tests {
};
assert_eq!(a, b);
let a = HeaderValue::from_static("attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let a = "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates".into();
let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![
@@ -267,18 +246,18 @@ mod tests {
#[test]
fn test_display() {
let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates";
let a = HeaderValue::from_static(as_string);
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let a = as_string.into();
let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
let display_rendered = format!("{}",a);
assert_eq!(as_string, display_rendered);
let a = HeaderValue::from_static("attachment; filename*=UTF-8''black%20and%20white.csv");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let a = "attachment; filename*=UTF-8''black%20and%20white.csv".into();
let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
let display_rendered = format!("{}",a);
assert_eq!("attachment; filename=\"black and white.csv\"".to_owned(), display_rendered);
let a = HeaderValue::from_static("attachment; filename=colourful.csv");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let a = "attachment; filename=colourful.csv".into();
let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
let display_rendered = format!("{}",a);
assert_eq!("attachment; filename=\"colourful.csv\"".to_owned(), display_rendered);
}

View File

@@ -35,7 +35,6 @@ header! {
}
impl Date {
/// Create a date instance set to the current system time
pub fn now() -> Date {
Date(SystemTime::now().into())
}

View File

@@ -13,7 +13,7 @@ pub use self::accept_language::AcceptLanguage;
pub use self::accept::Accept;
pub use self::allow::Allow;
pub use self::cache_control::{CacheControl, CacheDirective};
pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam};
//pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam};
pub use self::content_language::ContentLanguage;
pub use self::content_range::{ContentRange, ContentRangeSpec};
pub use self::content_type::ContentType;
@@ -74,6 +74,8 @@ macro_rules! test_header {
($id:ident, $raw:expr) => {
#[test]
fn $id() {
#[allow(unused, deprecated)]
use std::ascii::AsciiExt;
use test;
let raw = $raw;
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
@@ -334,7 +336,7 @@ mod accept_language;
mod accept;
mod allow;
mod cache_control;
mod content_disposition;
//mod content_disposition;
mod content_language;
mod content_range;
mod content_type;

View File

@@ -8,7 +8,6 @@ use bytes::{Bytes, BytesMut};
use mime::Mime;
use modhttp::header::GetAll;
use modhttp::Error as HttpError;
use percent_encoding;
pub use modhttp::header::*;
@@ -41,7 +40,7 @@ pub trait IntoHeaderValue: Sized {
/// The type returned in the event of a conversion error.
type Error: Into<HttpError>;
/// Try to convert value to a Header value.
/// Cast from PyObject to a concrete Python object type.
fn try_into(self) -> Result<HeaderValue, Self::Error>;
}
@@ -128,18 +127,16 @@ pub enum ContentEncoding {
impl ContentEncoding {
#[inline]
/// Is the content compressed?
pub fn is_compression(self) -> bool {
match self {
pub fn is_compression(&self) -> bool {
match *self {
ContentEncoding::Identity | ContentEncoding::Auto => false,
_ => true,
}
}
#[inline]
/// Convert content encoding to string
pub fn as_str(self) -> &'static str {
match self {
pub fn as_str(&self) -> &'static str {
match *self {
#[cfg(feature = "brotli")]
ContentEncoding::Br => "br",
#[cfg(feature = "flate2")]
@@ -152,8 +149,8 @@ impl ContentEncoding {
#[inline]
/// default quality value
pub fn quality(self) -> f64 {
match self {
pub fn quality(&self) -> f64 {
match *self {
#[cfg(feature = "brotli")]
ContentEncoding::Br => 1.1,
#[cfg(feature = "flate2")]
@@ -260,211 +257,3 @@ where
}
Ok(())
}
// From hyper v0.11.27 src/header/parsing.rs
/// An extended header parameter value (i.e., tagged with a character set and optionally,
/// a language), as defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
#[derive(Clone, Debug, PartialEq)]
pub struct ExtendedValue {
/// The character set that is used to encode the `value` to a string.
pub charset: Charset,
/// The human language details of the `value`, if available.
pub language_tag: Option<LanguageTag>,
/// The parameter value, as expressed in octets.
pub value: Vec<u8>,
}
/// Parses extended header parameter values (`ext-value`), as defined in
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
///
/// Extended values are denoted by parameter names that end with `*`.
///
/// ## ABNF
///
/// ```text
/// ext-value = charset "'" [ language ] "'" value-chars
/// ; like RFC 2231's <extended-initial-value>
/// ; (see [RFC2231], Section 7)
///
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
///
/// mime-charset = 1*mime-charsetc
/// mime-charsetc = ALPHA / DIGIT
/// / "!" / "#" / "$" / "%" / "&"
/// / "+" / "-" / "^" / "_" / "`"
/// / "{" / "}" / "~"
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
/// ; except that the single quote is not included
/// ; SHOULD be registered in the IANA charset registry
///
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
///
/// value-chars = *( pct-encoded / attr-char )
///
/// pct-encoded = "%" HEXDIG HEXDIG
/// ; see [RFC3986], Section 2.1
///
/// attr-char = ALPHA / DIGIT
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
/// / "^" / "_" / "`" / "|" / "~"
/// ; token except ( "*" / "'" / "%" )
/// ```
pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, ::error::ParseError> {
// Break into three pieces separated by the single-quote character
let mut parts = val.splitn(3, '\'');
// Interpret the first piece as a Charset
let charset: Charset = match parts.next() {
None => return Err(::error::ParseError::Header),
Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?,
};
// Interpret the second piece as a language tag
let language_tag: Option<LanguageTag> = match parts.next() {
None => return Err(::error::ParseError::Header),
Some("") => None,
Some(s) => match s.parse() {
Ok(lt) => Some(lt),
Err(_) => return Err(::error::ParseError::Header),
},
};
// Interpret the third piece as a sequence of value characters
let value: Vec<u8> = match parts.next() {
None => return Err(::error::ParseError::Header),
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
};
Ok(ExtendedValue {
value,
charset,
language_tag,
})
}
impl fmt::Display for ExtendedValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let encoded_value = percent_encoding::percent_encode(
&self.value[..],
self::percent_encoding_http::HTTP_VALUE,
);
if let Some(ref lang) = self.language_tag {
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
} else {
write!(f, "{}''{}", self.charset, encoded_value)
}
}
}
/// Percent encode a sequence of bytes with a character set defined in
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
///
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result {
let encoded =
percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE);
fmt::Display::fmt(&encoded, f)
}
mod percent_encoding_http {
use percent_encoding;
// internal module because macro is hard-coded to make a public item
// but we don't want to public export this item
define_encode_set! {
// This encode set is used for HTTP header values and is defined at
// https://tools.ietf.org/html/rfc5987#section-3.2
pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | {
' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?',
'[', '\\', ']', '{', '}'
}
}
}
#[cfg(test)]
mod tests {
use super::{parse_extended_value, ExtendedValue};
use header::shared::Charset;
use language_tags::LanguageTag;
#[test]
fn test_parse_extended_value_with_encoding_and_language_tag() {
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
assert!(extended_value.language_tag.is_some());
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
assert_eq!(
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_with_encoding() {
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
// and U+20AC (EURO SIGN)
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
assert!(extended_value.language_tag.is_none());
assert_eq!(
vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_missing_language_tag_and_encoding() {
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
let result = parse_extended_value("foo%20bar.html");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted() {
let result = parse_extended_value("UTF-8'missing third part");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted_blank() {
let result = parse_extended_value("blank second part'");
assert!(result.is_err());
}
#[test]
fn test_fmt_extended_value_with_encoding_and_language_tag() {
let extended_value = ExtendedValue {
charset: Charset::Iso_8859_1,
language_tag: Some("en".parse().expect("Could not parse language tag")),
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
};
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
}
#[test]
fn test_fmt_extended_value_with_encoding() {
let extended_value = ExtendedValue {
charset: Charset::Ext("UTF-8".to_string()),
language_tag: None,
value: vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
};
assert_eq!(
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
format!("{}", extended_value)
);
}
}

View File

@@ -1,3 +1,5 @@
#![allow(unused, deprecated)]
use std::ascii::AsciiExt;
use std::fmt::{self, Display};
use std::str::FromStr;
@@ -66,7 +68,7 @@ pub enum Charset {
}
impl Charset {
fn label(&self) -> &str {
fn name(&self) -> &str {
match *self {
Us_Ascii => "US-ASCII",
Iso_8859_1 => "ISO-8859-1",
@@ -90,7 +92,7 @@ impl Charset {
Iso_8859_8_E => "ISO-8859-8-E",
Iso_8859_8_I => "ISO-8859-8-I",
Gb2312 => "GB2312",
Big5 => "big5",
Big5 => "5",
Koi8_R => "KOI8-R",
Ext(ref s) => s,
}
@@ -99,7 +101,7 @@ impl Charset {
impl Display for Charset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.label())
f.write_str(self.name())
}
}
@@ -129,7 +131,7 @@ impl FromStr for Charset {
"ISO-8859-8-E" => Iso_8859_8_E,
"ISO-8859-8-I" => Iso_8859_8_I,
"GB2312" => Gb2312,
"big5" => Big5,
"5" => Big5,
"KOI8-R" => Koi8_R,
s => Ext(s.to_owned()),
})

View File

@@ -161,7 +161,7 @@ impl IntoHeaderValue for EntityTag {
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = Writer::new();
write!(wrt, "{}", self).unwrap();
HeaderValue::from_shared(wrt.take())
unsafe { Ok(HeaderValue::from_shared_unchecked(wrt.take())) }
}
}

View File

@@ -64,7 +64,11 @@ impl IntoHeaderValue for HttpDate {
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = BytesMut::with_capacity(29).writer();
write!(wrt, "{}", self.0.rfc822()).unwrap();
HeaderValue::from_shared(wrt.get_mut().take().freeze())
unsafe {
Ok(HeaderValue::from_shared_unchecked(
wrt.get_mut().take().freeze(),
))
}
}
}

View File

@@ -1,3 +1,5 @@
#![allow(unused, deprecated)]
use std::ascii::AsciiExt;
use std::cmp;
use std::default::Default;
use std::fmt;

View File

@@ -36,7 +36,7 @@ use httpresponse::HttpResponse;
/// # use actix_web::*;
/// use actix_web::http::NormalizePath;
///
/// # fn index(req: &HttpRequest) -> HttpResponse {
/// # fn index(req: HttpRequest) -> HttpResponse {
/// # HttpResponse::Ok().into()
/// # }
/// fn main() {
@@ -86,41 +86,57 @@ impl NormalizePath {
impl<S> Handler<S> for NormalizePath {
type Result = HttpResponse;
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
let query = req.query_string();
if self.merge {
// merge slashes
let p = self.re_merge.replace_all(req.path(), "/");
if p.len() != req.path().len() {
if req.resource().has_prefixed_resource(p.as_ref()) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref())
.finish();
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if req.resource().has_prefixed_resource(&p) {
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
if let Some(router) = req.router() {
let query = req.query_string();
if self.merge {
// merge slashes
let p = self.re_merge.replace_all(req.path(), "/");
if p.len() != req.path().len() {
if router.has_route(p.as_ref()) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.header(header::LOCATION, p.as_ref())
.finish();
}
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
}
}
// try to remove trailing slash
if p.ends_with('/') {
// try to remove trailing slash
if p.ends_with('/') {
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(
header::LOCATION,
(p.to_owned() + "?" + query).as_str(),
)
} else {
req.header(header::LOCATION, p)
}.finish();
}
}
} else if p.ends_with('/') {
// try to remove trailing slash
let p = p.as_ref().trim_right_matches('/');
if req.resource().has_prefixed_resource(p) {
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(
@@ -132,34 +148,20 @@ impl<S> Handler<S> for NormalizePath {
}.finish();
}
}
} else if p.ends_with('/') {
// try to remove trailing slash
let p = p.as_ref().trim_right_matches('/');
if req.resource().has_prefixed_resource(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(
header::LOCATION,
(p.to_owned() + "?" + query).as_str(),
)
} else {
req.header(header::LOCATION, p)
}.finish();
}
}
}
// append trailing slash
if self.append && !req.path().ends_with('/') {
let p = req.path().to_owned() + "/";
if req.resource().has_prefixed_resource(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
// append trailing slash
if self.append && !req.path().ends_with('/') {
let p = req.path().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
}
}
}
HttpResponse::new(self.not_found)
@@ -173,13 +175,13 @@ mod tests {
use http::{header, Method};
use test::TestRequest;
fn index(_req: &HttpRequest) -> HttpResponse {
fn index(_req: HttpRequest) -> HttpResponse {
HttpResponse::new(StatusCode::OK)
}
#[test]
fn test_normalize_path_trailing_slashes() {
let app = App::new()
let mut app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
@@ -205,59 +207,9 @@ mod tests {
("/resource2/?p1=1&p2=2", "", StatusCode::OK),
];
for (path, target, code) in params {
let req = TestRequest::with_uri(path).request();
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = &resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
);
}
}
}
#[test]
fn test_prefixed_normalize_path_trailing_slashes() {
let app = App::new()
.prefix("/test")
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/test/resource1", "", StatusCode::OK),
(
"/test/resource1/",
"/test/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/test/resource2",
"/test/resource2/",
StatusCode::MOVED_PERMANENTLY,
),
("/test/resource2/", "", StatusCode::OK),
("/test/resource1?p1=1&p2=2", "", StatusCode::OK),
(
"/test/resource1/?p1=1&p2=2",
"/test/resource1?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY,
),
(
"/test/resource2?p1=1&p2=2",
"/test/resource2/?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY,
),
("/test/resource2/?p1=1&p2=2", "", StatusCode::OK),
];
for (path, target, code) in params {
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = &resp.as_msg();
let r = resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
@@ -270,7 +222,7 @@ mod tests {
#[test]
fn test_normalize_path_trailing_slashes_disabled() {
let app = App::new()
let mut app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| {
@@ -294,16 +246,16 @@ mod tests {
("/resource2/?p1=1&p2=2", StatusCode::OK),
];
for (path, code) in params {
let req = TestRequest::with_uri(path).request();
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = &resp.as_msg();
let r = resp.as_msg();
assert_eq!(r.status(), code);
}
}
#[test]
fn test_normalize_path_merge_slashes() {
let app = App::new()
let mut app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
@@ -377,9 +329,9 @@ mod tests {
),
];
for (path, target, code) in params {
let req = TestRequest::with_uri(path).request();
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = &resp.as_msg();
let r = resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
@@ -392,7 +344,7 @@ mod tests {
#[test]
fn test_normalize_path_merge_and_append_slashes() {
let app = App::new()
let mut app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
@@ -557,9 +509,9 @@ mod tests {
),
];
for (path, target, code) in params {
let req = TestRequest::with_uri(path).request();
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = &resp.as_msg();
let r = resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(

View File

@@ -5,7 +5,7 @@ use httpresponse::{HttpResponse, HttpResponseBuilder};
macro_rules! STATIC_RESP {
($name:ident, $status:expr) => {
#[allow(non_snake_case, missing_docs)]
#[allow(non_snake_case)]
pub fn $name() -> HttpResponseBuilder {
HttpResponse::build($status)
}

View File

@@ -3,31 +3,26 @@ use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label;
use encoding::types::{DecoderTrap, Encoding};
use encoding::EncodingRef;
use futures::{Async, Future, Poll, Stream};
use futures::{Future, Poll, Stream};
use http::{header, HeaderMap};
use http_range::HttpRange;
use mime::Mime;
use serde::de::DeserializeOwned;
use serde_urlencoded;
use std::str;
use error::{
ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError,
ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError,
};
use header::Header;
use json::JsonBody;
use multipart::Multipart;
/// Trait that implements general purpose operations on http messages
pub trait HttpMessage: Sized {
/// Type of message payload stream
type Stream: Stream<Item = Bytes, Error = PayloadError> + Sized;
pub trait HttpMessage {
/// Read the message headers.
fn headers(&self) -> &HeaderMap;
/// Message payload stream
fn payload(&self) -> Self::Stream;
#[doc(hidden)]
/// Get a header
fn get_header<H: Header>(&self) -> Option<H>
@@ -99,6 +94,17 @@ pub trait HttpMessage: Sized {
}
}
/// Parses Range HTTP header string as per RFC 2616.
/// `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())
} else {
Ok(Vec::new())
}
}
/// Load http message body.
///
/// By default only 256Kb payload reads to a memory, then
@@ -112,11 +118,10 @@ pub trait HttpMessage: Sized {
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{
/// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse,
/// };
/// use bytes::Bytes;
/// use futures::future::Future;
/// use actix_web::{HttpMessage, HttpRequest, HttpResponse,
/// FutureResponse, AsyncResponder};
///
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
/// req.body() // <- get Body future
@@ -129,7 +134,10 @@ pub trait HttpMessage: Sized {
/// }
/// # fn main() {}
/// ```
fn body(&self) -> MessageBody<Self> {
fn body(self) -> MessageBody<Self>
where
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
{
MessageBody::new(self)
}
@@ -149,7 +157,7 @@ pub trait HttpMessage: Sized {
/// # extern crate futures;
/// # use futures::Future;
/// # use std::collections::HashMap;
/// use actix_web::{FutureResponse, HttpMessage, HttpRequest, HttpResponse};
/// use actix_web::{HttpMessage, HttpRequest, HttpResponse, FutureResponse};
///
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
/// Box::new(
@@ -158,12 +166,14 @@ pub trait HttpMessage: Sized {
/// .and_then(|params| { // <- url encoded parameters
/// println!("==== BODY ==== {:?}", params);
/// Ok(HttpResponse::Ok().into())
/// }),
/// )
/// }))
/// }
/// # fn main() {}
/// ```
fn urlencoded<T: DeserializeOwned>(&self) -> UrlEncoded<Self, T> {
fn urlencoded<T: DeserializeOwned>(self) -> UrlEncoded<Self, T>
where
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
{
UrlEncoded::new(self)
}
@@ -182,14 +192,14 @@ pub trait HttpMessage: Sized {
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use futures::future::{ok, Future};
/// use futures::future::{Future, ok};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
@@ -199,7 +209,10 @@ pub trait HttpMessage: Sized {
/// }
/// # fn main() {}
/// ```
fn json<T: DeserializeOwned>(&self) -> JsonBody<Self, T> {
fn json<T: DeserializeOwned>(self) -> JsonBody<Self, T>
where
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
{
JsonBody::new(self)
}
@@ -210,15 +223,16 @@ pub trait HttpMessage: Sized {
/// ## Server example
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate env_logger;
/// # extern crate futures;
/// # use std::str;
/// # use actix::*;
/// # use actix_web::*;
/// # use actix_web::actix::fut::FinishStream;
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.multipart().from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items
/// multipart::MultipartItem::Field(field) => {
@@ -238,191 +252,29 @@ pub trait HttpMessage: Sized {
/// }
/// # fn main() {}
/// ```
fn multipart(&self) -> Multipart<Self::Stream> {
fn multipart(self) -> Multipart<Self>
where
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
{
let boundary = Multipart::boundary(self.headers());
Multipart::new(boundary, self.payload())
}
/// Return stream of lines.
fn readlines(&self) -> Readlines<Self> {
Readlines::new(self)
}
}
/// Stream to read request line by line.
pub struct Readlines<T: HttpMessage> {
stream: T::Stream,
buff: BytesMut,
limit: usize,
checked_buff: bool,
encoding: EncodingRef,
err: Option<ReadlinesError>,
}
impl<T: HttpMessage> Readlines<T> {
/// Create a new stream to read request line by line.
fn new(req: &T) -> Self {
let encoding = match req.encoding() {
Ok(enc) => enc,
Err(err) => return Self::err(req, err.into()),
};
Readlines {
stream: req.payload(),
buff: BytesMut::with_capacity(262_144),
limit: 262_144,
checked_buff: true,
err: None,
encoding,
}
}
/// Change max line size. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
fn err(req: &T, err: ReadlinesError) -> Self {
Readlines {
stream: req.payload(),
buff: BytesMut::new(),
limit: 262_144,
checked_buff: true,
encoding: UTF_8,
err: Some(err),
}
}
}
impl<T: HttpMessage + 'static> Stream for Readlines<T> {
type Item = String;
type Error = ReadlinesError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(err) = self.err.take() {
return Err(err);
}
// check if there is a newline in the buffer
if !self.checked_buff {
let mut found: Option<usize> = None;
for (ind, b) in self.buff.iter().enumerate() {
if *b == b'\n' {
found = Some(ind);
break;
}
}
if let Some(ind) = found {
// check if line is longer than limit
if ind + 1 > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = self.encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&self.buff.split_to(ind + 1))
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
.decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
return Ok(Async::Ready(Some(line)));
}
self.checked_buff = true;
}
// poll req for more bytes
match self.stream.poll() {
Ok(Async::Ready(Some(mut bytes))) => {
// check if there is a newline in bytes
let mut found: Option<usize> = None;
for (ind, b) in bytes.iter().enumerate() {
if *b == b'\n' {
found = Some(ind);
break;
}
}
if let Some(ind) = found {
// check if line is longer than limit
if ind + 1 > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = self.encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&bytes.split_to(ind + 1))
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
.decode(&bytes.split_to(ind + 1), DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
// extend buffer with rest of the bytes;
self.buff.extend_from_slice(&bytes);
self.checked_buff = false;
return Ok(Async::Ready(Some(line)));
}
self.buff.extend_from_slice(&bytes);
Ok(Async::NotReady)
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
if self.buff.is_empty() {
return Ok(Async::Ready(None));
}
if self.buff.len() > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = self.encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&self.buff)
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
.decode(&self.buff, DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
self.buff.clear();
Ok(Async::Ready(Some(line)))
}
Err(e) => Err(ReadlinesError::from(e)),
}
Multipart::new(boundary, self)
}
}
/// Future that resolves to a complete http message body.
pub struct MessageBody<T: HttpMessage> {
pub struct MessageBody<T> {
limit: usize,
length: Option<usize>,
stream: Option<T::Stream>,
err: Option<PayloadError>,
req: Option<T>,
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
}
impl<T: HttpMessage> MessageBody<T> {
/// Create `MessageBody` for request.
pub fn new(req: &T) -> MessageBody<T> {
let mut len = None;
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
} else {
return Self::err(PayloadError::UnknownLength);
}
} else {
return Self::err(PayloadError::UnknownLength);
}
}
impl<T> MessageBody<T> {
/// Create `RequestBody` for request.
pub fn new(req: T) -> MessageBody<T> {
MessageBody {
limit: 262_144,
length: len,
stream: Some(req.payload()),
req: Some(req),
fut: None,
err: None,
}
}
@@ -431,114 +283,67 @@ impl<T: HttpMessage> MessageBody<T> {
self.limit = limit;
self
}
fn err(e: PayloadError) -> Self {
MessageBody {
stream: None,
limit: 262_144,
fut: None,
err: Some(e),
length: None,
}
}
}
impl<T> Future for MessageBody<T>
where
T: HttpMessage + 'static,
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
if let Some(err) = self.err.take() {
return Err(err);
}
if let Some(len) = self.length.take() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
self.stream
.take()
.expect("Can not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
if let Some(req) = self.req.take() {
if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<usize>() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
} else {
body.extend_from_slice(&chunk);
Ok(body)
return Err(PayloadError::UnknownLength);
}
})
.map(|body| body.freeze()),
));
self.poll()
} else {
return Err(PayloadError::UnknownLength);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
req.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.map(|body| body.freeze()),
));
}
self.fut
.as_mut()
.expect("UrlEncoded could not be used second time")
.poll()
}
}
/// Future that resolves to a parsed urlencoded values.
pub struct UrlEncoded<T: HttpMessage, U> {
stream: Option<T::Stream>,
pub struct UrlEncoded<T, U> {
req: Option<T>,
limit: usize,
length: Option<usize>,
encoding: EncodingRef,
err: Option<UrlencodedError>,
fut: Option<Box<Future<Item = U, Error = UrlencodedError>>>,
}
impl<T: HttpMessage, U> UrlEncoded<T, U> {
/// Create a new future to URL encode a request
pub fn new(req: &T) -> UrlEncoded<T, U> {
// check content type
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Self::err(UrlencodedError::ContentType);
}
let encoding = match req.encoding() {
Ok(enc) => enc,
Err(_) => return Self::err(UrlencodedError::ContentType),
};
let mut len = None;
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
} else {
return Self::err(UrlencodedError::UnknownLength);
}
} else {
return Self::err(UrlencodedError::UnknownLength);
}
};
impl<T, U> UrlEncoded<T, U> {
pub fn new(req: T) -> UrlEncoded<T, U> {
UrlEncoded {
encoding,
stream: Some(req.payload()),
limit: 262_144,
length: len,
fut: None,
err: None,
}
}
fn err(e: UrlencodedError) -> Self {
UrlEncoded {
stream: None,
req: Some(req),
limit: 262_144,
fut: None,
err: Some(e),
length: None,
encoding: UTF_8,
}
}
@@ -551,58 +356,66 @@ impl<T: HttpMessage, U> UrlEncoded<T, U> {
impl<T, U> Future for UrlEncoded<T, U>
where
T: HttpMessage + 'static,
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
U: DeserializeOwned + 'static,
{
type Item = U;
type Error = UrlencodedError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
if let Some(err) = self.err.take() {
return Err(err);
}
// payload size
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(UrlencodedError::Overflow);
if let Some(req) = self.req.take() {
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 {
return Err(UrlencodedError::Overflow);
}
} else {
return Err(UrlencodedError::UnknownLength);
}
} else {
return Err(UrlencodedError::UnknownLength);
}
}
// check content type
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Err(UrlencodedError::ContentType);
}
let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?;
// future
let limit = self.limit;
let fut = req
.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(move |body| {
let enc: *const Encoding = encoding as *const Encoding;
if enc == UTF_8 {
serde_urlencoded::from_bytes::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
} else {
let body = encoding
.decode(&body, DecoderTrap::Strict)
.map_err(|_| UrlencodedError::Parse)?;
serde_urlencoded::from_str::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
}
});
self.fut = Some(Box::new(fut));
}
// future
let encoding = self.encoding;
let fut = self
.stream
.take()
self.fut
.as_mut()
.expect("UrlEncoded could not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(move |body| {
if (encoding as *const Encoding) == UTF_8 {
serde_urlencoded::from_bytes::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
} else {
let body = encoding
.decode(&body, DecoderTrap::Strict)
.map_err(|_| UrlencodedError::Parse)?;
serde_urlencoded::from_str::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
}
});
self.fut = Some(Box::new(fut));
self.poll()
.poll()
}
}
@@ -612,7 +425,10 @@ mod tests {
use encoding::all::ISO_8859_2;
use encoding::Encoding;
use futures::Async;
use http::{Method, Uri, Version};
use httprequest::HttpRequest;
use mime;
use std::str::FromStr;
use test::TestRequest;
#[test]
@@ -623,7 +439,7 @@ mod tests {
TestRequest::with_header("content-type", "application/json; charset=utf=8")
.finish();
assert_eq!(req.content_type(), "application/json");
let req = TestRequest::default().finish();
let req = HttpRequest::default();
assert_eq!(req.content_type(), "");
}
@@ -631,7 +447,7 @@ mod tests {
fn test_mime_type() {
let req = TestRequest::with_header("content-type", "application/json").finish();
assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
let req = TestRequest::default().finish();
let req = HttpRequest::default();
assert_eq!(req.mime_type().unwrap(), None);
let req =
TestRequest::with_header("content-type", "application/json; charset=utf-8")
@@ -653,7 +469,7 @@ mod tests {
#[test]
fn test_encoding() {
let req = TestRequest::default().finish();
let req = HttpRequest::default();
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
let req = TestRequest::with_header("content-type", "application/json").finish();
@@ -681,21 +497,47 @@ mod tests {
);
}
#[test]
fn test_no_request_range_header() {
let req = HttpRequest::default();
let ranges = req.range(100).unwrap();
assert!(ranges.is_empty());
}
#[test]
fn test_request_range_header() {
let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish();
let ranges = req.range(100).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].start, 0);
assert_eq!(ranges[0].length, 5);
}
#[test]
fn test_chunked() {
let req = TestRequest::default().finish();
let req = HttpRequest::default();
assert!(!req.chunked().unwrap());
let req =
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert!(req.chunked().unwrap());
let req = TestRequest::default()
.header(
header::TRANSFER_ENCODING,
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
)
.finish();
let mut headers = HeaderMap::new();
let s = unsafe {
str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())
};
headers.insert(
header::TRANSFER_ENCODING,
header::HeaderValue::from_str(s).unwrap(),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
assert!(req.chunked().is_err());
}
@@ -761,12 +603,13 @@ mod tests {
#[test]
fn test_urlencoded() {
let req = TestRequest::with_header(
let mut req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
let result = req.urlencoded::<Info>().poll().ok().unwrap();
assert_eq!(
@@ -776,12 +619,13 @@ mod tests {
})
);
let req = TestRequest::with_header(
let mut req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded; charset=utf-8",
).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
let result = req.urlencoded().poll().ok().unwrap();
assert_eq!(
@@ -806,53 +650,19 @@ mod tests {
_ => unreachable!("error"),
}
let req = TestRequest::default()
.set_payload(Bytes::from_static(b"test"))
.finish();
let mut req = HttpRequest::default();
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"),
}
let req = TestRequest::default()
.set_payload(Bytes::from_static(b"11111111111111"))
.finish();
let mut req = HttpRequest::default();
req.payload_mut()
.unread_data(Bytes::from_static(b"11111111111111"));
match req.body().limit(5).poll().err().unwrap() {
PayloadError::Overflow => (),
_ => unreachable!("error"),
}
}
#[test]
fn test_readlines() {
let req = TestRequest::default()
.set_payload(Bytes::from_static(
b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\
industry. Lorem Ipsum has been the industry's standard dummy\n\
Contrary to popular belief, Lorem Ipsum is not simply random text.",
))
.finish();
let mut r = Readlines::new(&req);
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(
s,
"Lorem Ipsum is simply dummy text of the printing and typesetting\n"
),
_ => unreachable!("error"),
}
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(
s,
"industry. Lorem Ipsum has been the industry's standard dummy\n"
),
_ => unreachable!("error"),
}
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(
s,
"Contrary to popular belief, Lorem Ipsum is not simply random text."
),
_ => unreachable!("error"),
}
}
}

View File

@@ -1,171 +1,289 @@
//! HTTP Request message related code.
use std::cell::{Ref, RefMut};
use std::collections::HashMap;
#![cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ptr))]
use std::net::SocketAddr;
use std::ops::Deref;
use std::rc::Rc;
use std::{fmt, str};
use std::{cmp, fmt, io, mem, str};
use bytes::Bytes;
use cookie::Cookie;
use failure;
use futures::{Async, Poll, Stream};
use futures_cpupool::CpuPool;
use http::{header, HeaderMap, Method, StatusCode, Uri, Version};
use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version};
use tokio_io::AsyncRead;
use url::{form_urlencoded, Url};
use body::Body;
use error::{CookieParseError, UrlGenerationError};
use extensions::Extensions;
use error::{CookieParseError, PayloadError, UrlGenerationError};
use handler::FromRequest;
use httpmessage::HttpMessage;
use httpresponse::{HttpResponse, HttpResponseBuilder};
use info::ConnectionInfo;
use param::Params;
use payload::Payload;
use router::ResourceInfo;
use server::Request;
use router::{Resource, Router};
use server::helpers::SharedHttpInnerMessage;
use uri::Url as InnerUrl;
struct Query(HashMap<String, String>);
struct Cookies(Vec<Cookie<'static>>);
/// An HTTP Request
pub struct HttpRequest<S = ()> {
req: Option<Request>,
state: Rc<S>,
resource: ResourceInfo,
bitflags! {
pub(crate) struct MessageFlags: u8 {
const KEEPALIVE = 0b0000_0010;
}
}
impl<S> HttpMessage for HttpRequest<S> {
type Stream = Payload;
pub struct HttpInnerMessage {
pub version: Version,
pub method: Method,
pub(crate) url: InnerUrl,
pub(crate) flags: MessageFlags,
pub headers: HeaderMap,
pub extensions: Extensions,
pub params: Params<'static>,
pub addr: Option<SocketAddr>,
pub payload: Option<Payload>,
pub info: Option<ConnectionInfo<'static>>,
pub prefix: u16,
resource: RouterResource,
}
#[inline]
fn headers(&self) -> &HeaderMap {
self.request().headers()
}
struct Query(Params<'static>);
struct Cookies(Vec<Cookie<'static>>);
#[inline]
fn payload(&self) -> Payload {
if let Some(payload) = self.request().inner.payload.borrow_mut().take() {
payload
} else {
Payload::empty()
#[derive(Debug, Copy, Clone, PartialEq)]
enum RouterResource {
Notset,
Normal(u16),
}
impl Default for HttpInnerMessage {
fn default() -> HttpInnerMessage {
HttpInnerMessage {
method: Method::GET,
url: InnerUrl::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
flags: MessageFlags::empty(),
params: Params::new(),
addr: None,
payload: None,
extensions: Extensions::new(),
info: None,
prefix: 0,
resource: RouterResource::Notset,
}
}
}
impl<S> Deref for HttpRequest<S> {
type Target = Request;
impl HttpInnerMessage {
/// Checks if a connection should be kept alive.
#[inline]
pub fn keep_alive(&self) -> bool {
self.flags.contains(MessageFlags::KEEPALIVE)
}
fn deref(&self) -> &Request {
self.request()
#[inline]
pub(crate) fn reset(&mut self) {
self.headers.clear();
self.extensions.clear();
self.params.clear();
self.addr = None;
self.info = None;
self.flags = MessageFlags::empty();
self.payload = None;
self.prefix = 0;
self.resource = RouterResource::Notset;
}
}
lazy_static! {
static ref RESOURCE: Resource = Resource::unset();
}
/// An HTTP Request
pub struct HttpRequest<S = ()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>);
impl HttpRequest<()> {
/// Construct a new Request.
#[inline]
pub fn new(
method: Method, uri: Uri, version: Version, headers: HeaderMap,
payload: Option<Payload>,
) -> HttpRequest {
let url = InnerUrl::new(uri);
HttpRequest(
SharedHttpInnerMessage::from_message(HttpInnerMessage {
method,
url,
version,
headers,
payload,
params: Params::new(),
extensions: Extensions::new(),
addr: None,
info: None,
prefix: 0,
flags: MessageFlags::empty(),
resource: RouterResource::Notset,
}),
None,
None,
)
}
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
pub(crate) fn from_message(msg: SharedHttpInnerMessage) -> HttpRequest {
HttpRequest(msg, None, None)
}
#[inline]
/// Construct new http request with state.
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> {
#[inline]
fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
}
}
impl<S> HttpRequest<S> {
#[inline]
pub(crate) fn new(
req: Request, state: Rc<S>, resource: ResourceInfo,
) -> HttpRequest<S> {
HttpRequest {
state,
resource,
req: Some(req),
}
}
#[inline]
/// Construct new http request with state.
pub(crate) fn with_state<NS>(&self, state: Rc<NS>) -> HttpRequest<NS> {
HttpRequest {
state,
req: self.req.as_ref().map(|r| r.clone()),
resource: self.resource.clone(),
}
pub fn change_state<NS>(&self, state: Rc<NS>) -> HttpRequest<NS> {
HttpRequest(self.0.clone(), Some(state), self.2.clone())
}
#[inline]
/// Construct new http request with new RouteInfo.
pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest<S> {
resource.merge(&self.resource);
/// Construct new http request without state.
pub fn drop_state(&self) -> HttpRequest {
HttpRequest(self.0.clone(), None, self.2.clone())
}
HttpRequest {
resource,
req: self.req.as_ref().map(|r| r.clone()),
state: self.state.clone(),
}
/// get mutable reference for inner message
/// mutable reference should not be returned as result for request's method
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub(crate) fn as_mut(&self) -> &mut HttpInnerMessage {
self.0.get_mut()
}
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
fn as_ref(&self) -> &HttpInnerMessage {
self.0.get_ref()
}
#[inline]
pub(crate) fn get_inner(&mut self) -> &mut HttpInnerMessage {
self.as_mut()
}
/// Shared application state
#[inline]
pub fn state(&self) -> &S {
&self.state
}
#[inline]
/// Server request
pub fn request(&self) -> &Request {
self.req.as_ref().unwrap()
self.1.as_ref().unwrap()
}
/// Request extensions
#[inline]
pub fn extensions(&self) -> Ref<Extensions> {
self.request().extensions()
pub fn extensions(&self) -> &Extensions {
&self.as_ref().extensions
}
/// Mutable reference to a the request's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> {
self.request().extensions_mut()
pub fn extensions_mut(&mut self) -> &mut Extensions {
&mut self.as_mut().extensions
}
/// Default `CpuPool`
#[inline]
#[doc(hidden)]
pub fn cpu_pool(&self) -> &CpuPool {
self.request().server_settings().cpu_pool()
self.router()
.expect("HttpRequest has to have Router instance")
.server_settings()
.cpu_pool()
}
#[inline]
/// Create http response
pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse {
self.request().server_settings().get_response(status, body)
if let Some(router) = self.router() {
router.server_settings().get_response(status, body)
} else {
HttpResponse::with_body(status, body)
}
}
#[inline]
/// Create http response builder
pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder {
self.request()
.server_settings()
.get_response_builder(status)
if let Some(router) = self.router() {
router.server_settings().get_response_builder(status)
} else {
HttpResponse::build(status)
}
}
#[doc(hidden)]
pub fn prefix_len(&self) -> u16 {
self.as_ref().prefix as u16
}
#[doc(hidden)]
pub fn set_prefix_len(&mut self, len: u16) {
self.as_mut().prefix = len;
}
/// Read the Request Uri.
#[inline]
pub fn uri(&self) -> &Uri {
self.request().inner.url.uri()
self.as_ref().url.uri()
}
/// Read the Request method.
#[inline]
pub fn method(&self) -> &Method {
&self.request().inner.method
&self.as_ref().method
}
/// Read the Request Version.
#[inline]
pub fn version(&self) -> Version {
self.request().inner.version
self.as_ref().version
}
///Returns mutable Request's headers.
///
///This is intended to be used by middleware.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.as_mut().headers
}
/// The target path of this Request.
#[inline]
pub fn path(&self) -> &str {
self.request().inner.url.path()
self.as_ref().url.path()
}
/// Get *ConnectionInfo* for the correct request.
#[inline]
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
self.request().connection_info()
/// Get *ConnectionInfo* for correct request.
pub fn connection_info(&self) -> &ConnectionInfo {
if self.as_ref().info.is_none() {
let info: ConnectionInfo<'static> =
unsafe { mem::transmute(ConnectionInfo::new(self)) };
self.as_mut().info = Some(info);
}
self.as_ref().info.as_ref().unwrap()
}
/// Generate url for named resource
@@ -195,7 +313,22 @@ impl<S> HttpRequest<S> {
U: IntoIterator<Item = I>,
I: AsRef<str>,
{
self.resource.url_for(&self, name, elements)
if self.router().is_none() {
Err(UrlGenerationError::RouterNotAvailable)
} else {
let path = self.router().unwrap().resource_path(name, elements)?;
if path.starts_with('/') {
let conn = self.connection_info();
Ok(Url::parse(&format!(
"{}://{}{}",
conn.scheme(),
conn.host(),
path
))?)
} else {
Ok(Url::parse(&path)?)
}
}
}
/// Generate url for named resource
@@ -207,10 +340,25 @@ impl<S> HttpRequest<S> {
self.url_for(name, &NO_PARAMS)
}
/// This method returns reference to current `RouteInfo` object.
/// This method returns reference to current `Router` object.
#[inline]
pub fn resource(&self) -> &ResourceInfo {
&self.resource
pub fn router(&self) -> Option<&Router> {
self.2.as_ref()
}
/// This method returns reference to matched `Resource` object.
#[inline]
pub fn resource(&self) -> &Resource {
if let Some(ref router) = self.2 {
if let RouterResource::Normal(idx) = self.as_ref().resource {
return router.get_resource(idx as usize);
}
}
&*RESOURCE
}
pub(crate) fn set_resource(&mut self, res: usize) {
self.as_mut().resource = RouterResource::Normal(res as u16);
}
/// Peer socket address
@@ -222,19 +370,29 @@ impl<S> HttpRequest<S> {
/// be used.
#[inline]
pub fn peer_addr(&self) -> Option<SocketAddr> {
self.request().inner.addr
self.as_ref().addr
}
/// url query parameters.
pub fn query(&self) -> Ref<HashMap<String, String>> {
#[inline]
pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
self.as_mut().addr = addr;
}
#[doc(hidden)]
/// Get a reference to the Params object.
/// Params is a container for url query parameters.
pub fn query<'a>(&'a self) -> &'a Params {
if self.extensions().get::<Query>().is_none() {
let mut query = HashMap::new();
let mut params: Params<'a> = Params::new();
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
query.insert(key.as_ref().to_string(), val.to_string());
params.add(key, val);
}
self.extensions_mut().insert(Query(query));
let params: Params<'static> = unsafe { mem::transmute(params) };
self.as_mut().extensions.insert(Query(params));
}
Ref::map(self.extensions(), |ext| &ext.get::<Query>().unwrap().0)
let params: &Params<'a> =
unsafe { mem::transmute(&self.extensions().get::<Query>().unwrap().0) };
params
}
/// The query string in the URL.
@@ -250,11 +408,11 @@ impl<S> HttpRequest<S> {
}
/// Load request cookies.
#[inline]
pub fn cookies(&self) -> Result<Ref<Vec<Cookie<'static>>>, CookieParseError> {
if self.extensions().get::<Cookies>().is_none() {
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
if self.extensions().get::<Query>().is_none() {
let msg = self.as_mut();
let mut cookies = Vec::new();
for hdr in self.request().inner.headers.get_all(header::COOKIE) {
for hdr in msg.headers.get_all(header::COOKIE) {
let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
for cookie_str in s.split(';').map(|s| s.trim()) {
if !cookie_str.is_empty() {
@@ -262,20 +420,17 @@ impl<S> HttpRequest<S> {
}
}
}
self.extensions_mut().insert(Cookies(cookies));
msg.extensions.insert(Cookies(cookies));
}
Ok(Ref::map(self.extensions(), |ext| {
&ext.get::<Cookies>().unwrap().0
}))
Ok(&self.extensions().get::<Cookies>().unwrap().0)
}
/// Return request cookie.
#[inline]
pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
pub fn cookie(&self, name: &str) -> Option<&Cookie> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies.iter() {
for cookie in cookies {
if cookie.name() == name {
return Some(cookie.to_owned());
return Some(cookie);
}
}
}
@@ -296,39 +451,68 @@ impl<S> HttpRequest<S> {
/// access the matched value for that segment.
#[inline]
pub fn match_info(&self) -> &Params {
&self.resource.match_info()
unsafe { mem::transmute(&self.as_ref().params) }
}
/// Get mutable reference to request's Params.
#[inline]
pub fn match_info_mut(&mut self) -> &mut Params {
unsafe { mem::transmute(&mut self.as_mut().params) }
}
/// Checks if a connection should be kept alive.
pub fn keep_alive(&self) -> bool {
self.as_ref().flags.contains(MessageFlags::KEEPALIVE)
}
/// Check if request requires connection upgrade
pub(crate) fn upgrade(&self) -> bool {
self.request().upgrade()
if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) {
if let Ok(s) = conn.to_str() {
return s.to_lowercase().contains("upgrade");
}
}
self.as_ref().method == Method::CONNECT
}
/// Set read buffer capacity
///
/// Default buffer capacity is 32Kb.
pub fn set_read_buffer_capacity(&mut self, cap: usize) {
if let Some(payload) = self.request().inner.payload.borrow_mut().as_mut() {
if let Some(ref mut payload) = self.as_mut().payload {
payload.set_read_buffer_capacity(cap)
}
}
#[cfg(test)]
pub(crate) fn payload(&self) -> &Payload {
let msg = self.as_mut();
if msg.payload.is_none() {
msg.payload = Some(Payload::empty());
}
msg.payload.as_ref().unwrap()
}
#[cfg(test)]
pub(crate) fn payload_mut(&mut self) -> &mut Payload {
let msg = self.as_mut();
if msg.payload.is_none() {
msg.payload = Some(Payload::empty());
}
msg.payload.as_mut().unwrap()
}
}
impl<S> Drop for HttpRequest<S> {
fn drop(&mut self) {
if let Some(req) = self.req.take() {
req.release();
}
impl Default for HttpRequest<()> {
/// Construct default request
fn default() -> HttpRequest {
HttpRequest(SharedHttpInnerMessage::default(), None, None)
}
}
impl<S> Clone for HttpRequest<S> {
fn clone(&self) -> HttpRequest<S> {
HttpRequest {
req: self.req.as_ref().map(|r| r.clone()),
state: self.state.clone(),
resource: self.resource.clone(),
}
HttpRequest(self.0.clone(), self.1.clone(), self.2.clone())
}
}
@@ -342,23 +526,76 @@ impl<S> FromRequest<S> for HttpRequest<S> {
}
}
impl<S> Stream for HttpRequest<S> {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
let msg = self.as_mut();
if msg.payload.is_none() {
Ok(Async::Ready(None))
} else {
msg.payload.as_mut().unwrap().poll()
}
}
}
impl<S> io::Read for HttpRequest<S> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.as_mut().payload.is_some() {
match self.as_mut().payload.as_mut().unwrap().poll() {
Ok(Async::Ready(Some(mut b))) => {
let i = cmp::min(b.len(), buf.len());
buf.copy_from_slice(&b.split_to(i)[..i]);
if !b.is_empty() {
self.as_mut().payload.as_mut().unwrap().unread_data(b);
}
if i < buf.len() {
match self.read(&mut buf[i..]) {
Ok(n) => Ok(i + n),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(i),
Err(e) => Err(e),
}
} else {
Ok(i)
}
}
Ok(Async::Ready(None)) => Ok(0),
Ok(Async::NotReady) => {
Err(io::Error::new(io::ErrorKind::WouldBlock, "Not ready"))
}
Err(e) => Err(io::Error::new(
io::ErrorKind::Other,
failure::Error::from(e).compat(),
)),
}
} else {
Ok(0)
}
}
}
impl<S> AsyncRead for HttpRequest<S> {}
impl<S> fmt::Debug for HttpRequest<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = writeln!(
f,
"\nHttpRequest {:?} {}:{}",
self.version(),
self.method(),
self.as_ref().version,
self.as_ref().method,
self.path()
);
if !self.query_string().is_empty() {
let _ = writeln!(f, " query: ?{:?}", self.query_string());
}
if !self.match_info().is_empty() {
let _ = writeln!(f, " params: {:?}", self.match_info());
let _ = writeln!(f, " params: {:?}", self.as_ref().params);
}
let _ = writeln!(f, " headers:");
for (key, val) in self.headers().iter() {
for (key, val) in self.as_ref().headers.iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val);
}
res
@@ -368,8 +605,9 @@ impl<S> fmt::Debug for HttpRequest<S> {
#[cfg(test)]
mod tests {
use super::*;
use resource::Resource;
use router::{ResourceDef, Router};
use resource::ResourceHandler;
use router::Resource;
use server::ServerSettings;
use test::TestRequest;
#[test]
@@ -381,7 +619,7 @@ mod tests {
#[test]
fn test_no_request_cookies() {
let req = TestRequest::default().finish();
let req = HttpRequest::default();
assert!(req.cookies().unwrap().is_empty());
}
@@ -420,27 +658,33 @@ mod tests {
#[test]
fn test_request_match_info() {
let mut router = Router::<()>::new();
router.register_resource(Resource::new(ResourceDef::new("/{key}/")));
let mut req = TestRequest::with_uri("/value/?id=test").finish();
let req = TestRequest::with_uri("/value/?id=test").finish();
let info = router.recognize(&req, &(), 0);
assert_eq!(info.match_info().get("key"), Some("value"));
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut routes = Vec::new();
routes.push((Resource::new("index", "/{key}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some());
assert_eq!(req.match_info().get("key"), Some("value"));
}
#[test]
fn test_url_for() {
let mut router = Router::<()>::new();
let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}"));
resource.name("index");
router.register_resource(resource);
let req2 = HttpRequest::default();
assert_eq!(
req2.url_for("unknown", &["test"]),
Err(UrlGenerationError::RouterNotAvailable)
);
let info = router.default_route_info();
assert!(!info.has_prefixed_resource("/use/"));
assert!(info.has_resource("/user/test.html"));
assert!(info.has_prefixed_resource("/user/test.html"));
assert!(!info.has_resource("/test/unknown"));
assert!(!info.has_prefixed_resource("/test/unknown"));
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
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"));
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
.finish_with_router(router);
@@ -462,23 +706,16 @@ mod tests {
#[test]
fn test_url_for_with_prefix() {
let mut resource = Resource::new(ResourceDef::new("/user/{name}.html"));
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut router = Router::<()>::new();
router.register_resource(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 mut info = router.default_route_info();
info.set_prefix(7);
assert!(!info.has_prefixed_resource("/use/"));
assert!(info.has_resource("/user/test.html"));
assert!(!info.has_prefixed_resource("/user/test.html"));
assert!(!info.has_resource("/prefix/user/test.html"));
assert!(info.has_prefixed_resource("/prefix/user/test.html"));
let req = TestRequest::with_uri("/prefix/test")
.prefix(7)
.header(header::HOST, "www.rust-lang.org")
.finish_with_router(router);
let req = req.with_state(Rc::new(()), router);
let url = req.url_for("index", &["test"]);
assert_eq!(
url.ok().unwrap().as_str(),
@@ -488,22 +725,16 @@ mod tests {
#[test]
fn test_url_for_static() {
let mut resource = Resource::new(ResourceDef::new("/index.html"));
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut router = Router::<()>::new();
router.register_resource(resource);
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 mut info = router.default_route_info();
info.set_prefix(7);
assert!(info.has_resource("/index.html"));
assert!(!info.has_prefixed_resource("/index.html"));
assert!(!info.has_resource("/prefix/index.html"));
assert!(info.has_prefixed_resource("/prefix/index.html"));
let req = TestRequest::with_uri("/prefix/test")
.prefix(7)
.header(header::HOST, "www.rust-lang.org")
.finish_with_router(router);
let req = req.with_state(Rc::new(()), router);
let url = req.url_for_static("index");
assert_eq!(
url.ok().unwrap().as_str(),
@@ -513,17 +744,18 @@ mod tests {
#[test]
fn test_url_for_external() {
let mut router = Router::<()>::new();
router.register_external(
"youtube",
ResourceDef::external("https://youtube.com/watch/{video_id}"),
);
let req = HttpRequest::default();
let info = router.default_route_info();
assert!(!info.has_resource("https://youtube.com/watch/unknown"));
assert!(!info.has_prefixed_resource("https://youtube.com/watch/unknown"));
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let routes = vec![(
Resource::external("youtube", "https://youtube.com/watch/{video_id}"),
None,
)];
let (router, _) = Router::new::<()>("", ServerSettings::default(), routes);
assert!(!router.has_route("https://youtube.com/watch/unknown"));
let req = TestRequest::default().finish_with_router(router);
let req = req.with_state(Rc::new(()), router);
let url = req.url_for("youtube", &["oHg5SJYRHA0"]);
assert_eq!(
url.ok().unwrap().as_str(),

View File

@@ -1,7 +1,8 @@
//! Http response
use std::cell::RefCell;
use std::cell::UnsafeCell;
use std::collections::VecDeque;
use std::io::Write;
use std::rc::Rc;
use std::{fmt, mem, str};
use bytes::{BufMut, Bytes, BytesMut};
@@ -35,17 +36,30 @@ pub enum ConnectionType {
}
/// An HTTP Response
pub struct HttpResponse(Box<InnerHttpResponse>, &'static HttpResponsePool);
pub struct HttpResponse(
Option<Box<InnerHttpResponse>>,
Rc<UnsafeCell<HttpResponsePool>>,
);
impl Drop for HttpResponse {
fn drop(&mut self) {
if let Some(inner) = self.0.take() {
HttpResponsePool::release(&self.1, inner)
}
}
}
impl HttpResponse {
#[inline]
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
fn get_ref(&self) -> &InnerHttpResponse {
self.0.as_ref()
self.0.as_ref().unwrap()
}
#[inline]
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
fn get_mut(&mut self) -> &mut InnerHttpResponse {
self.0.as_mut()
self.0.as_mut().unwrap()
}
/// Create http response builder with specific status.
@@ -72,7 +86,7 @@ impl HttpResponse {
HttpResponsePool::with_body(status, body.into())
}
/// Constructs an error response
/// Constructs a error response
#[inline]
pub fn from_error(error: Error) -> HttpResponse {
let mut resp = error.as_response_error().error_response();
@@ -82,24 +96,15 @@ impl HttpResponse {
/// Convert `HttpResponse` to a `HttpResponseBuilder`
#[inline]
pub fn into_builder(self) -> HttpResponseBuilder {
// If this response has cookies, load them into a jar
let mut jar: Option<CookieJar> = None;
for c in self.cookies() {
if let Some(ref mut j) = jar {
j.add_original(c.into_owned());
} else {
let mut j = CookieJar::new();
j.add_original(c.into_owned());
jar = Some(j);
}
}
pub fn into_builder(mut self) -> HttpResponseBuilder {
let response = self.0.take();
let pool = Some(Rc::clone(&self.1));
HttpResponseBuilder {
pool: self.1,
response: Some(self.0),
response,
pool,
err: None,
cookies: jar,
cookies: None, // TODO: convert set-cookie headers
}
}
@@ -127,52 +132,6 @@ impl HttpResponse {
&mut self.get_mut().headers
}
/// Get an iterator for the cookies set by this response
#[inline]
pub fn cookies(&self) -> CookieIter {
CookieIter {
iter: self.get_ref().headers.get_all(header::SET_COOKIE).iter(),
}
}
/// Add a cookie to this response
#[inline]
pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> {
let h = &mut self.get_mut().headers;
HeaderValue::from_str(&cookie.to_string())
.map(|c| {
h.append(header::SET_COOKIE, c);
})
.map_err(|e| e.into())
}
/// Remove all cookies with the given name from this response. Returns
/// the number of cookies removed.
#[inline]
pub fn del_cookie(&mut self, name: &str) -> usize {
let h = &mut self.get_mut().headers;
let vals: Vec<HeaderValue> = h
.get_all(header::SET_COOKIE)
.iter()
.map(|v| v.to_owned())
.collect();
h.remove(header::SET_COOKIE);
let mut count: usize = 0;
for v in vals {
if let Ok(s) = v.to_str() {
if let Ok(c) = Cookie::parse_encoded(s) {
if c.name() == name {
count += 1;
continue;
}
}
}
h.append(header::SET_COOKIE, v);
}
count
}
/// Get the response status code
#[inline]
pub fn status(&self) -> StatusCode {
@@ -283,19 +242,12 @@ impl HttpResponse {
self.get_mut().write_capacity = cap;
}
pub(crate) fn release(self) {
self.1.release(self.0);
pub(crate) fn into_inner(mut self) -> Box<InnerHttpResponse> {
self.0.take().unwrap()
}
pub(crate) fn into_parts(self) -> HttpResponseParts {
self.0.into_parts()
}
pub(crate) fn from_parts(parts: HttpResponseParts) -> HttpResponse {
HttpResponse(
Box::new(InnerHttpResponse::from_parts(parts)),
HttpResponsePool::get_pool(),
)
pub(crate) fn from_inner(inner: Box<InnerHttpResponse>) -> HttpResponse {
HttpResponse(Some(inner), HttpResponsePool::pool())
}
}
@@ -317,31 +269,13 @@ impl fmt::Debug for HttpResponse {
}
}
pub struct CookieIter<'a> {
iter: header::ValueIter<'a, HeaderValue>,
}
impl<'a> Iterator for CookieIter<'a> {
type Item = Cookie<'a>;
#[inline]
fn next(&mut self) -> Option<Cookie<'a>> {
for v in self.iter.by_ref() {
if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) {
return Some(c);
}
}
None
}
}
/// An HTTP response builder
///
/// This type can be used to construct an instance of `HttpResponse` through a
/// builder-like pattern.
pub struct HttpResponseBuilder {
pool: &'static HttpResponsePool,
response: Option<Box<InnerHttpResponse>>,
pool: Option<Rc<UnsafeCell<HttpResponsePool>>>,
err: Option<HttpError>,
cookies: Option<CookieJar>,
}
@@ -535,6 +469,7 @@ impl HttpResponseBuilder {
/// )
/// .finish()
/// }
/// fn main() {}
/// ```
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
if self.cookies.is_none() {
@@ -547,22 +482,8 @@ impl HttpResponseBuilder {
self
}
/// Remove cookie
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, HttpRequest, HttpResponse, Result};
///
/// fn index(req: &HttpRequest) -> HttpResponse {
/// let mut builder = HttpResponse::Ok();
///
/// if let Some(ref cookie) = req.cookie("name") {
/// builder.del_cookie(cookie);
/// }
///
/// builder.finish()
/// }
/// ```
/// Remove cookie, cookie has to be cookie from `HttpRequest::cookies()`
/// method.
pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self {
{
if self.cookies.is_none() {
@@ -631,7 +552,7 @@ impl HttpResponseBuilder {
}
}
response.body = body.into();
HttpResponse(response, self.pool)
HttpResponse(Some(response), self.pool.take().unwrap())
}
#[inline]
@@ -679,8 +600,8 @@ impl HttpResponseBuilder {
/// This method construct new `HttpResponseBuilder`
pub fn take(&mut self) -> HttpResponseBuilder {
HttpResponseBuilder {
pool: self.pool,
response: self.response.take(),
pool: self.pool.take(),
err: self.err.take(),
cookies: self.cookies.take(),
}
@@ -860,19 +781,23 @@ impl<'a> From<&'a ClientResponse> for HttpResponseBuilder {
impl<'a, S> From<&'a HttpRequest<S>> for HttpResponseBuilder {
fn from(req: &'a HttpRequest<S>) -> HttpResponseBuilder {
req.request()
.server_settings()
.get_response_builder(StatusCode::OK)
if let Some(router) = req.router() {
router
.server_settings()
.get_response_builder(StatusCode::OK)
} else {
HttpResponse::Ok()
}
}
}
#[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>,
@@ -881,16 +806,8 @@ struct InnerHttpResponse {
error: Option<Error>,
}
pub(crate) struct HttpResponseParts {
version: Option<Version>,
headers: HeaderMap,
status: StatusCode,
reason: Option<&'static str>,
body: Option<Bytes>,
encoding: Option<ContentEncoding>,
connection_type: Option<ConnectionType>,
error: Option<Error>,
}
unsafe impl Sync for InnerHttpResponse {}
unsafe impl Send for InnerHttpResponse {}
impl InnerHttpResponse {
#[inline]
@@ -909,85 +826,38 @@ impl InnerHttpResponse {
error: None,
}
}
/// This is for failure, we can not have Send + Sync on Streaming and Actor response
fn into_parts(mut self) -> HttpResponseParts {
let body = match mem::replace(&mut self.body, Body::Empty) {
Body::Empty => None,
Body::Binary(mut bin) => Some(bin.take()),
Body::Streaming(_) | Body::Actor(_) => {
error!("Streaming or Actor body is not support by error response");
None
}
};
HttpResponseParts {
body,
version: self.version,
headers: self.headers,
status: self.status,
reason: self.reason,
encoding: self.encoding,
connection_type: self.connection_type,
error: self.error,
}
}
fn from_parts(parts: HttpResponseParts) -> InnerHttpResponse {
let body = if let Some(ref body) = parts.body {
Body::Binary(body.clone().into())
} else {
Body::Empty
};
InnerHttpResponse {
body,
status: parts.status,
version: parts.version,
headers: parts.headers,
reason: parts.reason,
chunked: None,
encoding: parts.encoding,
connection_type: parts.connection_type,
response_size: 0,
write_capacity: MAX_WRITE_BUFFER_SIZE,
error: parts.error,
}
}
}
/// Internal use only!
pub(crate) struct HttpResponsePool(RefCell<VecDeque<Box<InnerHttpResponse>>>);
/// Internal use only! unsafe
pub(crate) struct HttpResponsePool(VecDeque<Box<InnerHttpResponse>>);
thread_local!(static POOL: &'static HttpResponsePool = HttpResponsePool::pool());
thread_local!(static POOL: Rc<UnsafeCell<HttpResponsePool>> = HttpResponsePool::pool());
impl HttpResponsePool {
fn pool() -> &'static HttpResponsePool {
let pool = HttpResponsePool(RefCell::new(VecDeque::with_capacity(128)));
Box::leak(Box::new(pool))
}
pub fn get_pool() -> &'static HttpResponsePool {
POOL.with(|p| *p)
pub fn pool() -> Rc<UnsafeCell<HttpResponsePool>> {
Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity(
128,
))))
}
#[inline]
pub fn get_builder(
pool: &'static HttpResponsePool, status: StatusCode,
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode,
) -> HttpResponseBuilder {
if let Some(mut msg) = pool.0.borrow_mut().pop_front() {
let p = unsafe { &mut *pool.as_ref().get() };
if let Some(mut msg) = p.0.pop_front() {
msg.status = status;
HttpResponseBuilder {
pool,
response: Some(msg),
pool: Some(Rc::clone(pool)),
err: None,
cookies: None,
}
} else {
let msg = Box::new(InnerHttpResponse::new(status, Body::Empty));
HttpResponseBuilder {
pool,
response: Some(msg),
pool: Some(Rc::clone(pool)),
err: None,
cookies: None,
}
@@ -996,15 +866,16 @@ impl HttpResponsePool {
#[inline]
pub fn get_response(
pool: &'static HttpResponsePool, status: StatusCode, body: Body,
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode, body: Body,
) -> HttpResponse {
if let Some(mut msg) = pool.0.borrow_mut().pop_front() {
let p = unsafe { &mut *pool.as_ref().get() };
if let Some(mut msg) = p.0.pop_front() {
msg.status = status;
msg.body = body;
HttpResponse(msg, pool)
HttpResponse(Some(msg), Rc::clone(pool))
} else {
let msg = Box::new(InnerHttpResponse::new(status, body));
HttpResponse(msg, pool)
HttpResponse(Some(msg), Rc::clone(pool))
}
}
@@ -1018,10 +889,13 @@ impl HttpResponsePool {
POOL.with(|pool| HttpResponsePool::get_response(pool, status, body))
}
#[inline]
fn release(&self, mut inner: Box<InnerHttpResponse>) {
let mut p = self.0.borrow_mut();
if p.len() < 128 {
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))]
fn release(
pool: &Rc<UnsafeCell<HttpResponsePool>>, mut inner: Box<InnerHttpResponse>,
) {
let pool = unsafe { &mut *pool.as_ref().get() };
if pool.0.len() < 128 {
inner.headers.clear();
inner.version = None;
inner.chunked = None;
@@ -1031,7 +905,7 @@ impl HttpResponsePool {
inner.response_size = 0;
inner.error = None;
inner.write_capacity = MAX_WRITE_BUFFER_SIZE;
p.push_front(inner);
pool.0.push_front(inner);
}
}
}
@@ -1042,10 +916,10 @@ mod tests {
use body::Binary;
use http;
use http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
use http::{Method, Uri};
use std::str::FromStr;
use time::Duration;
use test::TestRequest;
#[test]
fn test_debug() {
let resp = HttpResponse::Ok()
@@ -1058,10 +932,17 @@ mod tests {
#[test]
fn test_response_cookies() {
let req = TestRequest::default()
.header(COOKIE, "cookie1=value1")
.header(COOKIE, "cookie2=value2")
.finish();
let mut headers = HeaderMap::new();
headers.insert(COOKIE, HeaderValue::from_static("cookie1=value1"));
headers.insert(COOKIE, HeaderValue::from_static("cookie2=value2"));
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let cookies = req.cookies().unwrap();
let resp = HttpResponse::Ok()
@@ -1083,36 +964,13 @@ mod tests {
.map(|v| v.to_str().unwrap().to_owned())
.collect();
val.sort();
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
assert!(val[0].starts_with("cookie2=; Max-Age=0;"));
assert_eq!(
val[1],
"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
);
}
#[test]
fn test_update_response_cookies() {
let mut r = HttpResponse::Ok()
.cookie(http::Cookie::new("original", "val100"))
.finish();
r.add_cookie(&http::Cookie::new("cookie2", "val200"))
.unwrap();
r.add_cookie(&http::Cookie::new("cookie2", "val250"))
.unwrap();
r.add_cookie(&http::Cookie::new("cookie3", "val300"))
.unwrap();
assert_eq!(r.cookies().count(), 4);
r.del_cookie("cookie2");
let mut iter = r.cookies();
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("original", "val100"));
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
}
#[test]
fn test_basic_builder() {
let resp = HttpResponse::Ok()
@@ -1197,7 +1055,7 @@ mod tests {
#[test]
fn test_into_response() {
let req = TestRequest::default().finish();
let req = HttpRequest::default();
let resp: HttpResponse = "test".into();
assert_eq!(resp.status(), StatusCode::OK);
@@ -1320,17 +1178,11 @@ mod tests {
#[test]
fn test_into_builder() {
let mut resp: HttpResponse = "test".into();
let resp: HttpResponse = "test".into();
assert_eq!(resp.status(), StatusCode::OK);
resp.add_cookie(&http::Cookie::new("cookie1", "val100"))
.unwrap();
let mut builder = resp.into_builder();
let resp = builder.status(StatusCode::BAD_REQUEST).finish();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let cookie = resp.cookies().next().unwrap();
assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100"));
}
}

View File

@@ -1,23 +1,24 @@
use http::header::{self, HeaderName};
use server::Request;
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use std::str::FromStr;
const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for";
const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host";
const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto";
const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR";
const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST";
const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO";
/// `HttpRequest` connection information
#[derive(Clone, Default)]
pub struct ConnectionInfo {
scheme: String,
host: String,
remote: Option<String>,
pub struct ConnectionInfo<'a> {
scheme: &'a str,
host: &'a str,
remote: Option<&'a str>,
peer: Option<String>,
}
impl ConnectionInfo {
impl<'a> ConnectionInfo<'a> {
/// Create *ConnectionInfo* instance for a request.
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
pub fn update(&mut self, req: &Request) {
pub fn new<S>(req: &'a HttpRequest<S>) -> ConnectionInfo<'a> {
let mut host = None;
let mut scheme = None;
let mut remote = None;
@@ -54,7 +55,7 @@ impl ConnectionInfo {
if scheme.is_none() {
if let Some(h) = req
.headers()
.get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap())
.get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap())
{
if let Ok(h) = h.to_str() {
scheme = h.split(',').next().map(|v| v.trim());
@@ -62,8 +63,12 @@ impl ConnectionInfo {
}
if scheme.is_none() {
scheme = req.uri().scheme_part().map(|a| a.as_str());
if scheme.is_none() && req.server_settings().secure() {
scheme = Some("https")
if scheme.is_none() {
if let Some(router) = req.router() {
if router.server_settings().secure() {
scheme = Some("https")
}
}
}
}
}
@@ -72,7 +77,7 @@ impl ConnectionInfo {
if host.is_none() {
if let Some(h) = req
.headers()
.get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap())
.get(HeaderName::from_str(X_FORWARDED_HOST).unwrap())
{
if let Ok(h) = h.to_str() {
host = h.split(',').next().map(|v| v.trim());
@@ -85,7 +90,9 @@ impl ConnectionInfo {
if host.is_none() {
host = req.uri().authority_part().map(|a| a.as_str());
if host.is_none() {
host = Some(req.server_settings().host());
if let Some(router) = req.router() {
host = Some(router.server_settings().host());
}
}
}
}
@@ -95,7 +102,7 @@ impl ConnectionInfo {
if remote.is_none() {
if let Some(h) = req
.headers()
.get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap())
.get(HeaderName::from_str(X_FORWARDED_FOR).unwrap())
{
if let Ok(h) = h.to_str() {
remote = h.split(',').next().map(|v| v.trim());
@@ -107,10 +114,12 @@ impl ConnectionInfo {
}
}
self.scheme = scheme.unwrap_or("http").to_owned();
self.host = host.unwrap_or("localhost").to_owned();
self.remote = remote.map(|s| s.to_owned());
self.peer = peer;
ConnectionInfo {
scheme: scheme.unwrap_or("http"),
host: host.unwrap_or("localhost"),
remote,
peer,
}
}
/// Scheme of the request.
@@ -122,7 +131,7 @@ impl ConnectionInfo {
/// - Uri
#[inline]
pub fn scheme(&self) -> &str {
&self.scheme
self.scheme
}
/// Hostname of the request.
@@ -135,7 +144,7 @@ impl ConnectionInfo {
/// - Uri
/// - Server hostname
pub fn host(&self) -> &str {
&self.host
self.host
}
/// Remote IP of client initiated HTTP request.
@@ -147,7 +156,7 @@ impl ConnectionInfo {
/// - peer name of opened socket
#[inline]
pub fn remote(&self) -> Option<&str> {
if let Some(ref r) = self.remote {
if let Some(r) = self.remote {
Some(r)
} else if let Some(ref peer) = self.peer {
Some(peer)
@@ -160,59 +169,60 @@ impl ConnectionInfo {
#[cfg(test)]
mod tests {
use super::*;
use test::TestRequest;
use http::header::HeaderValue;
#[test]
fn test_forwarded() {
let req = TestRequest::default().request();
let mut info = ConnectionInfo::default();
info.update(&req);
let req = HttpRequest::default();
let info = ConnectionInfo::new(&req);
assert_eq!(info.scheme(), "http");
assert_eq!(info.host(), "localhost:8080");
assert_eq!(info.host(), "localhost");
let req = TestRequest::default()
.header(
header::FORWARDED,
let mut req = HttpRequest::default();
req.headers_mut().insert(
header::FORWARDED,
HeaderValue::from_static(
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
)
.request();
),
);
let mut info = ConnectionInfo::default();
info.update(&req);
let info = ConnectionInfo::new(&req);
assert_eq!(info.scheme(), "https");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.remote(), Some("192.0.2.60"));
let req = TestRequest::default()
.header(header::HOST, "rust-lang.org")
.request();
let mut req = HttpRequest::default();
req.headers_mut()
.insert(header::HOST, HeaderValue::from_static("rust-lang.org"));
let mut info = ConnectionInfo::default();
info.update(&req);
let info = ConnectionInfo::new(&req);
assert_eq!(info.scheme(), "http");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.remote(), None);
let req = TestRequest::default()
.header(X_FORWARDED_FOR, "192.0.2.60")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
let mut req = HttpRequest::default();
req.headers_mut().insert(
HeaderName::from_str(X_FORWARDED_FOR).unwrap(),
HeaderValue::from_static("192.0.2.60"),
);
let info = ConnectionInfo::new(&req);
assert_eq!(info.remote(), Some("192.0.2.60"));
let req = TestRequest::default()
.header(X_FORWARDED_HOST, "192.0.2.60")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
let mut req = HttpRequest::default();
req.headers_mut().insert(
HeaderName::from_str(X_FORWARDED_HOST).unwrap(),
HeaderValue::from_static("192.0.2.60"),
);
let info = ConnectionInfo::new(&req);
assert_eq!(info.host(), "192.0.2.60");
assert_eq!(info.remote(), None);
let req = TestRequest::default()
.header(X_FORWARDED_PROTO, "https")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
let mut req = HttpRequest::default();
req.headers_mut().insert(
HeaderName::from_str(X_FORWARDED_PROTO).unwrap(),
HeaderValue::from_static("https"),
);
let info = ConnectionInfo::new(&req);
assert_eq!(info.scheme(), "https");
}
}

View File

@@ -1,4 +1,4 @@
use bytes::BytesMut;
use bytes::{Bytes, BytesMut};
use futures::{Future, Poll, Stream};
use http::header::CONTENT_LENGTH;
use std::fmt;
@@ -10,7 +10,7 @@ use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
use error::{Error, JsonPayloadError};
use error::{Error, JsonPayloadError, PayloadError};
use handler::{FromRequest, Responder};
use http::StatusCode;
use httpmessage::HttpMessage;
@@ -69,9 +69,7 @@ use httpresponse::HttpResponse;
/// }
///
/// fn index(req: HttpRequest) -> Result<Json<MyObj>> {
/// Ok(Json(MyObj {
/// name: req.match_info().query("name")?,
/// }))
/// Ok(Json(MyObj{name: req.match_info().query("name")?}))
/// }
/// # fn main() {}
/// ```
@@ -140,12 +138,12 @@ where
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let req2 = req.clone();
let req = req.clone();
let err = Rc::clone(&cfg.ehandler);
Box::new(
JsonBody::new(req)
JsonBody::new(req.clone())
.limit(cfg.limit)
.map_err(move |e| (*err)(e, &req2))
.map_err(move |e| (*err)(e, req))
.map(Json),
)
}
@@ -156,7 +154,7 @@ where
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{error, http, App, HttpResponse, Json, Result};
/// use actix_web::{App, Json, HttpResponse, Result, http, error};
///
/// #[derive(Deserialize)]
/// struct Info {
@@ -169,21 +167,21 @@ where
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::POST)
/// .with_config(index, |cfg| {
/// cfg.limit(4096) // <- change json extractor configuration
/// .error_handler(|err, req| { // <- create custom error response
/// error::InternalError::from_response(
/// err, HttpResponse::Conflict().finish()).into()
/// });
/// })
/// });
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::POST)
/// .with(index)
/// .limit(4096) // <- change json extractor configuration
/// .error_handler(|err, req| { // <- create custom error response
/// error::InternalError::from_response(
/// err, HttpResponse::Conflict().finish()).into()
/// });
/// });
/// }
/// ```
pub struct JsonConfig<S> {
limit: usize,
ehandler: Rc<Fn(JsonPayloadError, &HttpRequest<S>) -> Error>,
ehandler: Rc<Fn(JsonPayloadError, HttpRequest<S>) -> Error>,
}
impl<S> JsonConfig<S> {
@@ -196,7 +194,7 @@ impl<S> JsonConfig<S> {
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
where
F: Fn(JsonPayloadError, &HttpRequest<S>) -> Error + 'static,
F: Fn(JsonPayloadError, HttpRequest<S>) -> Error + 'static,
{
self.ehandler = Rc::new(f);
self
@@ -225,15 +223,15 @@ impl<S> Default for JsonConfig<S> {
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse};
/// use futures::future::Future;
/// use actix_web::{AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
@@ -243,48 +241,19 @@ impl<S> Default for JsonConfig<S> {
/// }
/// # fn main() {}
/// ```
pub struct JsonBody<T: HttpMessage, U: DeserializeOwned> {
pub struct JsonBody<T, U: DeserializeOwned> {
limit: usize,
length: Option<usize>,
stream: Option<T::Stream>,
err: Option<JsonPayloadError>,
req: Option<T>,
fut: Option<Box<Future<Item = U, Error = JsonPayloadError>>>,
}
impl<T: HttpMessage, U: DeserializeOwned> JsonBody<T, U> {
impl<T, U: DeserializeOwned> JsonBody<T, U> {
/// Create `JsonBody` for request.
pub fn new(req: &T) -> Self {
// check content-type
let json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
} else {
false
};
if !json {
return JsonBody {
limit: 262_144,
length: None,
stream: None,
fut: None,
err: Some(JsonPayloadError::ContentType),
};
}
let mut len = None;
if let Some(l) = req.headers().get(CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
}
}
}
pub fn new(req: T) -> Self {
JsonBody {
limit: 262_144,
length: len,
stream: Some(req.payload()),
req: Some(req),
fut: None,
err: None,
}
}
@@ -295,42 +264,56 @@ impl<T: HttpMessage, U: DeserializeOwned> JsonBody<T, U> {
}
}
impl<T: HttpMessage + 'static, U: DeserializeOwned + 'static> Future for JsonBody<T, U> {
impl<T, U: DeserializeOwned + 'static> Future for JsonBody<T, U>
where
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Item = U;
type Error = JsonPayloadError;
fn poll(&mut self) -> Poll<U, JsonPayloadError> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
if let Some(err) = self.err.take() {
return Err(err);
}
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(JsonPayloadError::Overflow);
}
}
let fut = self
.stream
.take()
.expect("JsonBody could not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
if let Some(req) = self.req.take() {
if let Some(len) = req.headers().get(CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<usize>() {
if len > self.limit {
return Err(JsonPayloadError::Overflow);
}
} else {
return Err(JsonPayloadError::Overflow);
}
}
})
.and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut));
self.poll()
}
// check content-type
let json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
} else {
false
};
if !json {
return Err(JsonPayloadError::ContentType);
}
let limit = self.limit;
let fut = req
.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut));
}
self.fut
.as_mut()
.expect("JsonBody could not be used second time")
.poll()
}
}
@@ -342,8 +325,7 @@ mod tests {
use http::header;
use handler::Handler;
use test::TestRequest;
use with::With;
use with::{ExtractorConfig, With};
impl PartialEq for JsonPayloadError {
fn eq(&self, other: &JsonPayloadError) -> bool {
@@ -371,7 +353,7 @@ mod tests {
let json = Json(MyObject {
name: "test".to_owned(),
});
let resp = json.respond_to(&TestRequest::default().finish()).unwrap();
let resp = json.respond_to(&HttpRequest::default()).unwrap();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"application/json"
@@ -380,44 +362,41 @@ mod tests {
#[test]
fn test_json_body() {
let req = TestRequest::default().finish();
let req = HttpRequest::default();
let mut json = req.json::<MyObject>();
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
let req = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
)
.finish();
let mut req = HttpRequest::default();
req.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
);
let mut json = req.json::<MyObject>();
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
let req = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
)
.finish();
let mut req = HttpRequest::default();
req.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
);
req.headers_mut().insert(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
);
let mut json = req.json::<MyObject>().limit(100);
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow);
let req = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
let mut req = HttpRequest::default();
req.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
);
req.headers_mut().insert(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
);
req.payload_mut()
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
let mut json = req.json::<MyObject>();
assert_eq!(
json.poll().ok().unwrap(),
@@ -429,22 +408,24 @@ mod tests {
#[test]
fn test_with_json() {
let mut cfg = JsonConfig::default();
let mut cfg = ExtractorConfig::<_, Json<MyObject>>::default();
cfg.limit(4096);
let handler = With::new(|data: Json<MyObject>| data, cfg);
let mut handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::default().finish();
assert!(handler.handle(&req).as_err().is_some());
let req = HttpRequest::default();
assert!(handler.handle(req).as_err().is_some());
let req = TestRequest::with_header(
let mut req = HttpRequest::default();
req.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
).header(
);
req.headers_mut().insert(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_none())
);
req.payload_mut()
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
assert!(handler.handle(req).as_err().is_none())
}
}

View File

@@ -2,21 +2,21 @@
//! for Rust.
//!
//! ```rust
//! use actix_web::{server, App, Path, Responder};
//! use actix_web::{server, App, Path};
//! # use std::thread;
//!
//! fn index(info: Path<(String, u32)>) -> impl Responder {
//! fn index(info: Path<(String, u32)>) -> String {
//! format!("Hello {}! id:{}", info.0, info.1)
//! }
//!
//! fn main() {
//! # thread::spawn(|| {
//! # thread::spawn(|| {
//! server::new(|| {
//! App::new().resource("/{name}/{id}/index.html", |r| r.with(index))
//! }).bind("127.0.0.1:8080")
//! .unwrap()
//! .run();
//! # });
//! # });
//! }
//! ```
//!
@@ -59,7 +59,7 @@
//! * SSL support with OpenSSL or `native-tls`
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
//! * Built on top of [Actix actor framework](https://github.com/actix/actix)
//! * Supported Rust version: 1.26 or later
//! * Supported Rust version: 1.24 or later
//!
//! ## Package feature
//!
@@ -83,7 +83,6 @@
feature = "cargo-clippy",
allow(decimal_literal_representation, suspicious_arithmetic_impl)
)]
#![warn(missing_docs)]
#[macro_use]
extern crate log;
@@ -103,23 +102,19 @@ extern crate lazy_static;
extern crate futures;
extern crate cookie;
extern crate futures_cpupool;
extern crate htmlescape;
extern crate http as modhttp;
extern crate http_range;
extern crate httparse;
extern crate language_tags;
extern crate lazycell;
extern crate libc;
extern crate mime;
extern crate mime_guess;
extern crate mio;
extern crate net2;
extern crate parking_lot;
extern crate rand;
extern crate slab;
extern crate tokio;
extern crate tokio_core;
extern crate tokio_io;
extern crate tokio_reactor;
extern crate tokio_tcp;
extern crate tokio_timer;
extern crate url;
#[macro_use]
extern crate serde;
@@ -130,12 +125,12 @@ extern crate encoding;
extern crate flate2;
extern crate h2 as http2;
extern crate num_cpus;
#[macro_use]
extern crate percent_encoding;
extern crate serde_json;
extern crate serde_urlencoded;
extern crate smallvec;
#[macro_use]
extern crate actix as actix_inner;
extern crate actix;
#[cfg(test)]
#[macro_use]
@@ -155,7 +150,6 @@ mod application;
mod body;
mod context;
mod de;
mod extensions;
mod extractor;
mod handler;
mod header;
@@ -173,7 +167,6 @@ mod resource;
mod route;
mod router;
mod scope;
mod serde_urlencoded;
mod uri;
mod with;
@@ -190,7 +183,6 @@ pub use application::App;
pub use body::{Binary, Body};
pub use context::HttpContext;
pub use error::{Error, ResponseError, Result};
pub use extensions::Extensions;
pub use extractor::{Form, Path, Query};
pub use handler::{
AsyncResponder, Either, FromRequest, FutureResponse, Responder, State,
@@ -200,19 +192,10 @@ pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse;
pub use json::Json;
pub use scope::Scope;
pub use server::Request;
pub mod actix {
//! Re-exports [actix's](https://docs.rs/actix/) prelude
extern crate actix;
pub use self::actix::actors::resolver;
pub use self::actix::actors::signal;
pub use self::actix::fut;
pub use self::actix::msgs;
pub use self::actix::prelude::*;
pub use self::actix::{run, spawn};
}
#[doc(hidden)]
#[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")]
pub use ws::WsWriter;
#[cfg(feature = "openssl")]
pub(crate) const HAS_OPENSSL: bool = true;
@@ -244,10 +227,10 @@ pub mod dev {
pub use info::ConnectionInfo;
pub use json::{JsonBody, JsonConfig};
pub use param::{FromParam, Params};
pub use payload::{Payload, PayloadBuffer};
pub use resource::Resource;
pub use resource::ResourceHandler;
pub use route::Route;
pub use router::{ResourceDef, ResourceInfo, ResourceType, Router};
pub use router::{Resource, ResourceType, Router};
pub use with::ExtractorConfig;
}
pub mod http {
@@ -260,13 +243,12 @@ pub mod http {
pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri};
pub use cookie::{Cookie, CookieBuilder};
pub use http_range::HttpRange;
pub use helpers::NormalizePath;
/// Various http headers
pub mod header {
pub use header::*;
pub use header::{ContentDisposition, DispositionType, DispositionParam, Charset, LanguageTag};
}
pub use header::ContentEncoding;
pub use httpresponse::ConnectionType;

View File

@@ -11,7 +11,7 @@
//! constructed backend.
//!
//! Cors middleware could be used as parameter for `App::middleware()` or
//! `Resource::middleware()` methods. But you have to use
//! `ResourceHandler::middleware()` methods. But you have to use
//! `Cors::for_app()` method to support *preflight* OPTIONS request.
//!
//!
@@ -59,9 +59,7 @@ use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started};
use resource::Resource;
use router::ResourceDef;
use server::Request;
use resource::ResourceHandler;
/// A set of errors that can occur during processing CORS
#[derive(Debug, Fail)]
@@ -209,7 +207,6 @@ impl Default for Cors {
}
impl Cors {
/// Build a new CORS middleware instance
pub fn build() -> CorsBuilder<()> {
CorsBuilder {
cors: Some(Inner {
@@ -278,16 +275,14 @@ impl Cors {
/// adds route for *OPTIONS* preflight requests.
///
/// It is possible to register *Cors* middleware with
/// `Resource::middleware()` method, but in that case *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 Resource<S>) {
resource
.method(Method::OPTIONS)
.h(|_: &_| HttpResponse::Ok());
pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) {
resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok());
resource.middleware(self);
}
fn validate_origin(&self, req: &Request) -> Result<(), CorsError> {
fn validate_origin<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> {
if let Some(hdr) = req.headers().get(header::ORIGIN) {
if let Ok(origin) = hdr.to_str() {
return match self.inner.origins {
@@ -307,7 +302,9 @@ impl Cors {
}
}
fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> {
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 Ok(meth) = hdr.to_str() {
if let Ok(method) = Method::try_from(meth) {
@@ -325,7 +322,9 @@ impl Cors {
}
}
fn validate_allowed_headers(&self, req: &Request) -> Result<(), CorsError> {
fn validate_allowed_headers<S>(
&self, req: &mut HttpRequest<S>,
) -> Result<(), CorsError> {
match self.inner.headers {
AllOrSome::All => Ok(()),
AllOrSome::Some(ref allowed_headers) => {
@@ -356,11 +355,11 @@ impl Cors {
}
impl<S> Middleware<S> for Cors {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
if self.inner.preflight && Method::OPTIONS == *req.method() {
self.validate_origin(req)?;
self.validate_allowed_method(&req)?;
self.validate_allowed_headers(&req)?;
self.validate_allowed_method(req)?;
self.validate_allowed_headers(req)?;
// allowed headers
let headers = if let Some(headers) = self.inner.headers.as_ref() {
@@ -434,7 +433,7 @@ impl<S> Middleware<S> for Cors {
}
fn response(
&self, req: &HttpRequest<S>, mut resp: HttpResponse,
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> {
match self.inner.origins {
AllOrSome::All => {
@@ -516,7 +515,7 @@ pub struct CorsBuilder<S = ()> {
methods: bool,
error: Option<http::Error>,
expose_hdrs: HashSet<HeaderName>,
resources: Vec<Resource<S>>,
resources: Vec<(String, ResourceHandler<S>)>,
app: Option<App<S>>,
}
@@ -796,13 +795,13 @@ impl<S: 'static> CorsBuilder<S> {
/// ```
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S>
where
F: FnOnce(&mut Resource<S>) -> R + 'static,
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
{
// add resource handler
let mut resource = Resource::new(ResourceDef::new(path));
f(&mut resource);
let mut handler = ResourceHandler::default();
f(&mut handler);
self.resources.push(resource);
self.resources.push((path.to_owned(), handler));
self
}
@@ -879,9 +878,9 @@ impl<S: 'static> CorsBuilder<S> {
.expect("CorsBuilder has to be constructed with Cors::for_app(app)");
// register resources
for mut resource in self.resources.drain(..) {
for (path, mut resource) in self.resources.drain(..) {
cors.clone().register(&mut resource);
app.register_resource(resource);
app.register_resource(&path, resource);
}
app
@@ -945,9 +944,10 @@ mod tests {
#[test]
fn validate_origin_allows_all_origins() {
let cors = Cors::default();
let req = TestRequest::with_header("Origin", "https://www.example.com").finish();
let mut req =
TestRequest::with_header("Origin", "https://www.example.com").finish();
assert!(cors.start(&req).ok().unwrap().is_done())
assert!(cors.start(&mut req).ok().unwrap().is_done())
}
#[test]
@@ -960,20 +960,20 @@ mod tests {
.allowed_header(header::CONTENT_TYPE)
.finish();
let req = TestRequest::with_header("Origin", "https://www.example.com")
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.finish();
assert!(cors.start(&req).is_err());
assert!(cors.start(&mut req).is_err());
let req = TestRequest::with_header("Origin", "https://www.example.com")
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
.method(Method::OPTIONS)
.finish();
assert!(cors.start(&req).is_err());
assert!(cors.start(&mut req).is_err());
let req = TestRequest::with_header("Origin", "https://www.example.com")
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.header(
header::ACCESS_CONTROL_REQUEST_HEADERS,
@@ -982,7 +982,7 @@ mod tests {
.method(Method::OPTIONS)
.finish();
let resp = cors.start(&req).unwrap().response();
let resp = cors.start(&mut req).unwrap().response();
assert_eq!(
&b"*"[..],
resp.headers()
@@ -1006,17 +1006,17 @@ mod tests {
// as_bytes());
Rc::get_mut(&mut cors.inner).unwrap().preflight = false;
assert!(cors.start(&req).unwrap().is_done());
assert!(cors.start(&mut req).unwrap().is_done());
}
// #[test]
// #[should_panic(expected = "MissingOrigin")]
// fn test_validate_missing_origin() {
// let cors = Cors::build()
// let mut cors = Cors::build()
// .allowed_origin("https://www.example.com")
// .finish();
// let mut req = HttpRequest::default();
// cors.start(&req).unwrap();
// cors.start(&mut req).unwrap();
// }
#[test]
@@ -1026,10 +1026,10 @@ mod tests {
.allowed_origin("https://www.example.com")
.finish();
let req = TestRequest::with_header("Origin", "https://www.unknown.com")
let mut req = TestRequest::with_header("Origin", "https://www.unknown.com")
.method(Method::GET)
.finish();
cors.start(&req).unwrap();
cors.start(&mut req).unwrap();
}
#[test]
@@ -1038,30 +1038,30 @@ mod tests {
.allowed_origin("https://www.example.com")
.finish();
let req = TestRequest::with_header("Origin", "https://www.example.com")
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::GET)
.finish();
assert!(cors.start(&req).unwrap().is_done());
assert!(cors.start(&mut req).unwrap().is_done());
}
#[test]
fn test_no_origin_response() {
let cors = Cors::build().finish();
let req = TestRequest::default().method(Method::GET).finish();
let mut req = TestRequest::default().method(Method::GET).finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&req, resp).unwrap().response();
let resp = cors.response(&mut req, resp).unwrap().response();
assert!(
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.is_none()
);
let req = TestRequest::with_header("Origin", "https://www.example.com")
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.finish();
let resp = cors.response(&req, resp).unwrap().response();
let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!(
&b"https://www.example.com"[..],
resp.headers()
@@ -1082,12 +1082,12 @@ mod tests {
.allowed_header(header::CONTENT_TYPE)
.finish();
let req = TestRequest::with_header("Origin", "https://www.example.com")
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&req, resp).unwrap().response();
let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!(
&b"*"[..],
resp.headers()
@@ -1102,7 +1102,7 @@ mod tests {
let resp: HttpResponse =
HttpResponse::Ok().header(header::VARY, "Accept").finish();
let resp = cors.response(&req, resp).unwrap().response();
let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!(
&b"Accept, Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes()
@@ -1113,7 +1113,7 @@ mod tests {
.allowed_origin("https://www.example.com")
.finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&req, resp).unwrap().response();
let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!(
&b"https://www.example.com"[..],
resp.headers()

View File

@@ -15,25 +15,25 @@
//! the allowed origins.
//!
//! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr)
//! if you want to allow requests with unprotected methods via
//! if you want to allow requests with unsafe methods via
//! [CORS](../cors/struct.Cors.html).
//!
//! # Example
//!
//! ```
//! # extern crate actix_web;
//! use actix_web::middleware::csrf;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//! use actix_web::middleware::csrf;
//!
//! fn handle_post(_: &HttpRequest) -> &'static str {
//! fn handle_post(_: HttpRequest) -> &'static str {
//! "This action should only be triggered with requests from the same site"
//! }
//!
//! fn main() {
//! let app = App::new()
//! .middleware(
//! csrf::CsrfFilter::new().allowed_origin("https://www.example.com"),
//! )
//! csrf::CsrfFilter::new()
//! .allowed_origin("https://www.example.com"))
//! .resource("/", |r| {
//! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(http::Method::POST).f(handle_post);
@@ -50,10 +50,10 @@ use std::collections::HashSet;
use bytes::Bytes;
use error::{ResponseError, Result};
use http::{header, HeaderMap, HttpTryFrom, Uri};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Started};
use server::Request;
/// Potential cross-site request forgery detected.
#[derive(Debug, Fail)]
@@ -120,12 +120,13 @@ fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
/// # Example
///
/// ```
/// use actix_web::middleware::csrf;
/// use actix_web::App;
/// use actix_web::middleware::csrf;
///
/// # fn main() {
/// let app = App::new()
/// .middleware(csrf::CsrfFilter::new().allowed_origin("https://www.example.com"));
/// let app = App::new().middleware(
/// csrf::CsrfFilter::new()
/// .allowed_origin("https://www.example.com"));
/// # }
/// ```
#[derive(Default)]
@@ -175,7 +176,7 @@ impl CsrfFilter {
///
/// The filter is conservative by default, but it should be safe to allow
/// missing `Origin` headers because a cross-site attacker cannot prevent
/// the browser from sending `Origin` on unprotected requests.
/// the browser from sending `Origin` on unsafe requests.
pub fn allow_missing_origin(mut self) -> CsrfFilter {
self.allow_missing_origin = true;
self
@@ -187,7 +188,7 @@ impl CsrfFilter {
self
}
fn validate(&self, req: &Request) -> Result<(), CsrfError> {
fn validate<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CsrfError> {
let is_upgrade = req.headers().contains_key(header::UPGRADE);
let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade);
@@ -209,7 +210,7 @@ impl CsrfFilter {
}
impl<S> Middleware<S> for CsrfFilter {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
self.validate(req)?;
Ok(Started::Done)
}
@@ -225,35 +226,35 @@ mod tests {
fn test_safe() {
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
let req = TestRequest::with_header("Origin", "https://www.w3.org")
let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::HEAD)
.finish();
assert!(csrf.start(&req).is_ok());
assert!(csrf.start(&mut req).is_ok());
}
#[test]
fn test_csrf() {
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
let req = TestRequest::with_header("Origin", "https://www.w3.org")
let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::POST)
.finish();
assert!(csrf.start(&req).is_err());
assert!(csrf.start(&mut req).is_err());
}
#[test]
fn test_referer() {
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
let req = TestRequest::with_header(
let mut req = TestRequest::with_header(
"Referer",
"https://www.example.com/some/path?query=param",
).method(Method::POST)
.finish();
assert!(csrf.start(&req).is_ok());
assert!(csrf.start(&mut req).is_ok());
}
#[test]
@@ -264,13 +265,13 @@ mod tests {
.allowed_origin("https://www.example.com")
.allow_upgrade();
let req = TestRequest::with_header("Origin", "https://cswsh.com")
let mut req = TestRequest::with_header("Origin", "https://cswsh.com")
.header("Connection", "Upgrade")
.header("Upgrade", "websocket")
.method(Method::GET)
.finish();
assert!(strict_csrf.start(&req).is_err());
assert!(lax_csrf.start(&req).is_ok());
assert!(strict_csrf.start(&mut req).is_err());
assert!(lax_csrf.start(&mut req).is_ok());
}
}

View File

@@ -17,11 +17,12 @@ use middleware::{Middleware, Response};
///
/// fn main() {
/// let app = App::new()
/// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
/// .middleware(
/// middleware::DefaultHeaders::new()
/// .header("X-Version", "0.2"))
/// .resource("/test", |r| {
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .finish();
/// }
@@ -74,7 +75,9 @@ impl DefaultHeaders {
}
impl<S> Middleware<S> for DefaultHeaders {
fn response(&self, _: &HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
fn response(
&self, _: &mut HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> {
for (key, value) in self.headers.iter() {
if !resp.headers().contains_key(key) {
resp.headers_mut().insert(key, value.clone());
@@ -95,23 +98,22 @@ impl<S> Middleware<S> for DefaultHeaders {
mod tests {
use super::*;
use http::header::CONTENT_TYPE;
use test::TestRequest;
#[test]
fn test_default_headers() {
let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001");
let req = TestRequest::default().finish();
let mut req = HttpRequest::default();
let resp = HttpResponse::Ok().finish();
let resp = match mw.response(&req, resp) {
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish();
let resp = match mw.response(&req, resp) {
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};

View File

@@ -6,7 +6,7 @@ use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response};
type ErrorHandler<S> = Fn(&HttpRequest<S>, HttpResponse) -> Result<Response>;
type ErrorHandler<S> = Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response>;
/// `Middleware` for allowing custom handlers for responses.
///
@@ -18,25 +18,23 @@ type ErrorHandler<S> = Fn(&HttpRequest<S>, HttpResponse) -> Result<Response>;
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::{ErrorHandlers, Response};
/// use actix_web::{http, App, HttpRequest, HttpResponse, Result};
/// use actix_web::middleware::{Response, ErrorHandlers};
///
/// fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
/// let mut builder = resp.into_builder();
/// builder.header(http::header::CONTENT_TYPE, "application/json");
/// Ok(Response::Done(builder.into()))
/// fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
/// let mut builder = resp.into_builder();
/// builder.header(http::header::CONTENT_TYPE, "application/json");
/// Ok(Response::Done(builder.into()))
/// }
///
/// fn main() {
/// let app = App::new()
/// .middleware(
/// ErrorHandlers::new()
/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500),
/// )
/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500))
/// .resource("/test", |r| {
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .finish();
/// }
@@ -62,7 +60,7 @@ impl<S> ErrorHandlers<S> {
/// Register error handler for specified status code
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
where
F: Fn(&HttpRequest<S>, HttpResponse) -> Result<Response> + 'static,
F: Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response> + 'static,
{
self.handlers.insert(status, Box::new(handler));
self
@@ -70,7 +68,9 @@ impl<S> ErrorHandlers<S> {
}
impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
if let Some(handler) = self.handlers.get(&resp.status()) {
handler(req, resp)
} else {
@@ -82,14 +82,10 @@ impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
#[cfg(test)]
mod tests {
use super::*;
use error::{Error, ErrorInternalServerError};
use http::header::CONTENT_TYPE;
use http::StatusCode;
use httpmessage::HttpMessage;
use middleware::Started;
use test::{self, TestRequest};
fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
let mut builder = resp.into_builder();
builder.header(CONTENT_TYPE, "0001");
Ok(Response::Done(builder.into()))
@@ -100,7 +96,7 @@ mod tests {
let mw =
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mut req = TestRequest::default().finish();
let mut req = HttpRequest::default();
let resp = HttpResponse::InternalServerError().finish();
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
@@ -115,27 +111,4 @@ mod tests {
};
assert!(!resp.headers().contains_key(CONTENT_TYPE));
}
struct MiddlewareOne;
impl<S> Middleware<S> for MiddlewareOne {
fn start(&self, _: &HttpRequest<S>) -> Result<Started, Error> {
Err(ErrorInternalServerError("middleware error"))
}
}
#[test]
fn test_middleware_start_error() {
let mut srv = test::TestServer::new(move |app| {
app.middleware(
ErrorHandlers::new()
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
).middleware(MiddlewareOne)
.handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
}

View File

@@ -3,7 +3,7 @@
//! [**IdentityService**](struct.IdentityService.html) middleware can be
//! used with different policies types to store identity information.
//!
//! By default, only cookie identity policy is implemented. Other backend
//! Bu default, only cookie identity policy is implemented. Other backend
//! implementations can be added separately.
//!
//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html)
@@ -62,8 +62,8 @@ use middleware::{Middleware, Response, Started};
/// The helper trait to obtain your identity from a request.
///
/// ```rust
/// use actix_web::middleware::identity::RequestIdentity;
/// use actix_web::*;
/// use actix_web::middleware::identity::RequestIdentity;
///
/// fn index(req: HttpRequest) -> Result<String> {
/// // access request identity
@@ -80,7 +80,7 @@ use middleware::{Middleware, Response, Started};
/// }
///
/// fn logout(mut req: HttpRequest) -> HttpResponse {
/// req.forget(); // <- remove identity
/// req.forget(); // <- remove identity
/// HttpResponse::Ok().finish()
/// }
/// # fn main() {}
@@ -88,31 +88,31 @@ use middleware::{Middleware, Response, Started};
pub trait RequestIdentity {
/// Return the claimed identity of the user associated request or
/// ``None`` if no identity can be found associated with the request.
fn identity(&self) -> Option<String>;
fn identity(&self) -> Option<&str>;
/// Remember identity.
fn remember(&self, identity: String);
fn remember(&mut self, identity: String);
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&self);
fn forget(&mut self);
}
impl<S> RequestIdentity for HttpRequest<S> {
fn identity(&self) -> Option<String> {
fn identity(&self) -> Option<&str> {
if let Some(id) = self.extensions().get::<IdentityBox>() {
return id.0.identity().map(|s| s.to_owned());
return id.0.identity();
}
None
}
fn remember(&self, identity: String) {
fn remember(&mut self, identity: String) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.as_mut().remember(identity);
return id.0.remember(identity);
}
}
fn forget(&self) {
fn forget(&mut self) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.forget();
}
@@ -121,15 +121,10 @@ impl<S> RequestIdentity for HttpRequest<S> {
/// An identity
pub trait Identity: 'static {
/// Return the claimed identity of the user associated request or
/// ``None`` if no identity can be found associated with the request.
fn identity(&self) -> Option<&str>;
/// Remember identity.
fn remember(&mut self, key: String);
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&mut self);
/// Write session to storage backend.
@@ -138,30 +133,28 @@ pub trait Identity: 'static {
/// Identity policy definition.
pub trait IdentityPolicy<S>: Sized + 'static {
/// The associated identity
type Identity: Identity;
/// The return type of the middleware
type Future: Future<Item = Self::Identity, Error = Error>;
/// Parse the session from request and load data from a service identity.
fn from_request(&self, request: &HttpRequest<S>) -> Self::Future;
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::Future;
}
/// Request identity middleware
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
/// use actix_web::App;
/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
///
/// fn main() {
/// let app = App::new().middleware(IdentityService::new(
/// // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
/// let app = App::new().middleware(
/// IdentityService::new( // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
/// .name("auth-cookie")
/// .secure(false),
/// ));
/// .secure(false))
/// );
/// }
/// ```
pub struct IdentityService<T> {
@@ -177,22 +170,33 @@ impl<T> IdentityService<T> {
struct IdentityBox(Box<Identity>);
#[doc(hidden)]
unsafe impl Send for IdentityBox {}
#[doc(hidden)]
unsafe impl Sync for IdentityBox {}
impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
let req = req.clone();
let fut = self.backend.from_request(&req).then(move |res| match res {
Ok(id) => {
req.extensions_mut().insert(IdentityBox(Box::new(id)));
FutOk(None)
}
Err(err) => FutErr(err),
});
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
let mut req = req.clone();
let fut = self
.backend
.from_request(&mut req)
.then(move |res| match res {
Ok(id) => {
req.extensions_mut().insert(IdentityBox(Box::new(id)));
FutOk(None)
}
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut)))
}
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(ref mut id) = req.extensions_mut().get_mut::<IdentityBox>() {
id.0.as_mut().write(resp)
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
if let Some(mut id) = req.extensions_mut().remove::<IdentityBox>() {
id.0.write(resp)
} else {
Ok(Response::Done(resp))
}
@@ -285,9 +289,9 @@ impl CookieIdentityInner {
Ok(())
}
fn load<S>(&self, req: &HttpRequest<S>) -> Option<String> {
fn load<S>(&self, req: &mut HttpRequest<S>) -> Option<String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() {
for cookie in cookies {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
@@ -314,18 +318,17 @@ impl CookieIdentityInner {
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
/// use actix_web::App;
/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
///
/// fn main() {
/// let app = App::new().middleware(IdentityService::new(
/// // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy
/// let app = App::new().middleware(
/// IdentityService::new( // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy
/// .domain("www.rust-lang.org")
/// .name("actix_auth")
/// .path("/")
/// .secure(true),
/// ));
/// .secure(true)));
/// }
/// ```
pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
@@ -376,7 +379,7 @@ impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
type Identity = CookieIdentity;
type Future = FutureResult<CookieIdentity, Error>;
fn from_request(&self, req: &HttpRequest<S>) -> Self::Future {
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::Future {
let identity = self.0.load(req);
FutOk(CookieIdentity {
identity,

View File

@@ -3,6 +3,7 @@ use std::collections::HashSet;
use std::env;
use std::fmt::{self, Display, Formatter};
use libc;
use regex::Regex;
use time;
@@ -30,8 +31,8 @@ use middleware::{Finished, Middleware, Started};
/// ```rust
/// # extern crate actix_web;
/// extern crate env_logger;
/// use actix_web::middleware::Logger;
/// use actix_web::App;
/// use actix_web::middleware::Logger;
///
/// fn main() {
/// std::env::set_var("RUST_LOG", "actix_web=info");
@@ -52,6 +53,8 @@ use middleware::{Finished, Middleware, Started};
///
/// `%t` Time when the request was started to process
///
/// `%P` The process ID of the child that serviced the request
///
/// `%r` First line of request
///
/// `%s` Response status code
@@ -107,7 +110,7 @@ impl Default for Logger {
struct StartTime(time::Tm);
impl Logger {
fn log<S>(&self, req: &HttpRequest<S>, resp: &HttpResponse) {
fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) {
if let Some(entry_time) = req.extensions().get::<StartTime>() {
let render = |fmt: &mut Formatter| {
for unit in &self.format.0 {
@@ -121,14 +124,14 @@ impl Logger {
}
impl<S> Middleware<S> for Logger {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
if !self.exclude.contains(req.path()) {
req.extensions_mut().insert(StartTime(time::now()));
}
Ok(Started::Done)
}
fn finish(&self, req: &HttpRequest<S>, resp: &HttpResponse) -> Finished {
fn finish(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) -> Finished {
self.log(req, resp);
Finished::Done
}
@@ -178,6 +181,7 @@ impl Format {
"%" => FormatText::Percent,
"a" => FormatText::RemoteAddr,
"t" => FormatText::RequestTime,
"P" => FormatText::Pid,
"r" => FormatText::RequestLine,
"s" => FormatText::ResponseStatus,
"b" => FormatText::ResponseSize,
@@ -201,6 +205,7 @@ impl Format {
#[derive(Debug, Clone)]
pub enum FormatText {
Str(String),
Pid,
Percent,
RequestLine,
RequestTime,
@@ -242,6 +247,7 @@ impl FormatText {
}
FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt),
FormatText::ResponseSize => resp.response_size().fmt(fmt),
FormatText::Pid => unsafe { libc::getpid().fmt(fmt) },
FormatText::Time => {
let rt = time::now() - entry_time;
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0;
@@ -308,30 +314,38 @@ impl<'a> fmt::Display for FormatDisplay<'a> {
#[cfg(test)]
mod tests {
use time;
use super::*;
use http::{header, StatusCode};
use test::TestRequest;
use http::header::{self, HeaderMap};
use http::{Method, StatusCode, Uri, Version};
use std::str::FromStr;
use time;
#[test]
fn test_logger() {
let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test");
let req = TestRequest::with_header(
let mut headers = HeaderMap::new();
headers.insert(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
).finish();
);
let mut req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let resp = HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt")
.force_close()
.finish();
match logger.start(&req) {
match logger.start(&mut req) {
Ok(Started::Done) => (),
_ => panic!(),
};
match logger.finish(&req, &resp) {
match logger.finish(&mut req, &resp) {
Finished::Done => (),
_ => panic!(),
}
@@ -350,10 +364,18 @@ mod tests {
fn test_default_format() {
let format = Format::default();
let req = TestRequest::with_header(
let mut headers = HeaderMap::new();
headers.insert(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
).finish();
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
let entry_time = time::now();
@@ -368,7 +390,13 @@ mod tests {
assert!(s.contains("200 0"));
assert!(s.contains("ACTIX-WEB"));
let req = TestRequest::with_uri("/?test").finish();
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/?test").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
let entry_time = time::now();

View File

@@ -51,18 +51,20 @@ pub enum Finished {
pub trait Middleware<S>: 'static {
/// Method is called when request is ready. It may return
/// future, which should resolve before next middleware get called.
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
Ok(Started::Done)
}
/// Method is called when handler returns response,
/// but before sending http message to peer.
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
Ok(Response::Done(resp))
}
/// Method is called after body stream get sent to peer.
fn finish(&self, req: &HttpRequest<S>, resp: &HttpResponse) -> Finished {
fn finish(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) -> Finished {
Finished::Done
}
}

View File

@@ -32,8 +32,9 @@
//! session data.
//!
//! ```rust
//! # extern crate actix;
//! # extern crate actix_web;
//! use actix_web::{actix, server, App, HttpRequest, Result};
//! use actix_web::{server, App, HttpRequest, Result};
//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
//!
//! fn index(req: HttpRequest) -> Result<&'static str> {
@@ -49,17 +50,17 @@
//! }
//!
//! fn main() {
//! actix::System::run(|| {
//! server::new(
//! || App::new().middleware(
//! SessionStorage::new( // <- create session middleware
//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
//! .secure(false)
//! )))
//! .bind("127.0.0.1:59880").unwrap()
//! .start();
//! # actix::System::current().stop();
//! });
//! let sys = actix::System::new("basic-example");
//! server::new(
//! || App::new().middleware(
//! SessionStorage::new( // <- create session middleware
//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
//! .secure(false)
//! )))
//! .bind("127.0.0.1:59880").unwrap()
//! .start();
//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
//! let _ = sys.run();
//! }
//! ```
use std::cell::RefCell;
@@ -68,7 +69,7 @@ use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::Arc;
use cookie::{Cookie, CookieJar, Key, SameSite};
use cookie::{Cookie, CookieJar, Key};
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
use futures::Future;
use http::header::{self, HeaderValue};
@@ -87,13 +88,13 @@ use middleware::{Middleware, Response, Started};
/// The helper trait to obtain your session data from a request.
///
/// ```rust
/// use actix_web::middleware::session::RequestSession;
/// use actix_web::*;
/// use actix_web::middleware::session::RequestSession;
///
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = req.session().get::<i32>("counter")? {
/// req.session().set("counter", count + 1)?;
/// req.session().set("counter", count+1)?;
/// } else {
/// req.session().set("counter", 1)?;
/// }
@@ -103,7 +104,6 @@ use middleware::{Middleware, Response, Started};
/// # fn main() {}
/// ```
pub trait RequestSession {
/// Get the session from the request
fn session(&self) -> Session;
}
@@ -123,13 +123,13 @@ impl<S> RequestSession for HttpRequest<S> {
/// method. `RequestSession` trait is implemented for `HttpRequest`.
///
/// ```rust
/// use actix_web::middleware::session::RequestSession;
/// use actix_web::*;
/// use actix_web::middleware::session::RequestSession;
///
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = req.session().get::<i32>("counter")? {
/// req.session().set("counter", count + 1)?;
/// req.session().set("counter", count+1)?;
/// } else {
/// req.session().set("counter", 1)?;
/// }
@@ -200,7 +200,7 @@ impl Session {
/// fn index(session: Session) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = session.get::<i32>("counter")? {
/// session.set("counter", count + 1)?;
/// session.set("counter", count+1)?;
/// } else {
/// session.set("counter", 1)?;
/// }
@@ -221,19 +221,25 @@ impl<S> FromRequest<S> for Session {
struct SessionImplCell(RefCell<Box<SessionImpl>>);
#[doc(hidden)]
unsafe impl Send for SessionImplCell {}
#[doc(hidden)]
unsafe impl Sync for SessionImplCell {}
/// Session storage middleware
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// use actix_web::middleware::session::{CookieSessionBackend, SessionStorage};
/// use actix_web::App;
/// use actix_web::middleware::session::{SessionStorage, CookieSessionBackend};
///
/// fn main() {
/// let app = App::new().middleware(SessionStorage::new(
/// // <- create session middleware
/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend
/// .secure(false),
/// ));
/// let app = App::new().middleware(
/// SessionStorage::new( // <- create session middleware
/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend
/// .secure(false))
/// );
/// }
/// ```
pub struct SessionStorage<T, S>(T, PhantomData<S>);
@@ -246,7 +252,7 @@ impl<S, T: SessionBackend<S>> SessionStorage<T, S> {
}
impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
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 {
@@ -260,8 +266,10 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
Ok(Started::Future(Box::new(fut)))
}
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(s_box) = req.extensions().get::<Arc<SessionImplCell>>() {
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
if let Some(s_box) = req.extensions_mut().remove::<Arc<SessionImplCell>>() {
s_box.0.borrow_mut().write(resp)
} else {
Ok(Response::Done(resp))
@@ -359,7 +367,6 @@ struct CookieSessionInner {
domain: Option<String>,
secure: bool,
max_age: Option<Duration>,
same_site: Option<SameSite>,
}
impl CookieSessionInner {
@@ -372,7 +379,6 @@ impl CookieSessionInner {
domain: None,
secure: true,
max_age: None,
same_site: None,
}
}
@@ -398,10 +404,6 @@ impl CookieSessionInner {
cookie.set_max_age(max_age);
}
if let Some(same_site) = self.same_site {
cookie.set_same_site(same_site);
}
let mut jar = CookieJar::new();
match self.security {
@@ -410,7 +412,7 @@ impl CookieSessionInner {
}
for cookie in jar.delta() {
let val = HeaderValue::from_str(&cookie.encoded().to_string())?;
let val = HeaderValue::from_str(&cookie.to_string())?;
resp.headers_mut().append(header::SET_COOKIE, val);
}
@@ -419,7 +421,7 @@ impl CookieSessionInner {
fn load<S>(&self, req: &mut HttpRequest<S>) -> HashMap<String, String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() {
for cookie in cookies {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
@@ -464,9 +466,6 @@ impl CookieSessionInner {
/// all session data is lost. The constructors will panic if the key is less
/// than 32 bytes in length.
///
/// The backend relies on `cookie` crate to create and read cookies.
/// By default all cookies are percent encoded, but certain symbols may
/// cause troubles when reading cookie, if they are not properly percent encoded.
///
/// # Example
///
@@ -532,12 +531,6 @@ impl CookieSessionBackend {
self
}
/// Sets the `same_site` field in the session cookie being built.
pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().same_site = Some(value);
self
}
/// Sets the `max-age` field in the session cookie being built.
pub fn max_age(mut self, value: Duration) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);

View File

@@ -1,5 +1,5 @@
//! Multipart requests support
use std::cell::{RefCell, UnsafeCell};
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use std::{cmp, fmt};
@@ -7,13 +7,13 @@ use std::{cmp, fmt};
use bytes::Bytes;
use futures::task::{current as current_task, Task};
use futures::{Async, Poll, Stream};
use http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue};
use http::header::{self, HeaderMap, HeaderName, HeaderValue};
use http::HttpTryFrom;
use httparse;
use mime;
use error::{MultipartError, ParseError, PayloadError};
use payload::PayloadBuffer;
use payload::PayloadHelper;
const MAX_HEADERS: usize = 32;
@@ -97,7 +97,7 @@ where
safety: Safety::new(),
inner: Some(Rc::new(RefCell::new(InnerMultipart {
boundary,
payload: PayloadRef::new(PayloadBuffer::new(stream)),
payload: PayloadRef::new(PayloadHelper::new(stream)),
state: InnerState::FirstBoundary,
item: InnerMultipartItem::None,
}))),
@@ -133,7 +133,7 @@ impl<S> InnerMultipart<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
fn read_headers(payload: &mut PayloadBuffer<S>) -> Poll<HeaderMap, MultipartError> {
fn read_headers(payload: &mut PayloadHelper<S>) -> Poll<HeaderMap, MultipartError> {
match payload.read_until(b"\r\n\r\n")? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
@@ -164,7 +164,7 @@ where
}
fn read_boundary(
payload: &mut PayloadBuffer<S>, boundary: &str,
payload: &mut PayloadHelper<S>, boundary: &str,
) -> Poll<bool, MultipartError> {
// TODO: need to read epilogue
match payload.readline()? {
@@ -190,7 +190,7 @@ where
}
fn skip_until_boundary(
payload: &mut PayloadBuffer<S>, boundary: &str,
payload: &mut PayloadHelper<S>, boundary: &str,
) -> Poll<bool, MultipartError> {
let mut eof = false;
loop {
@@ -399,28 +399,13 @@ where
}
}
/// Get a map of headers
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Get the content type of the field
pub fn content_type(&self) -> &mime::Mime {
&self.ct
}
/// Get the content disposition of the field, if it exists
pub fn content_disposition(&self) -> Option<ContentDisposition> {
// RFC 7578: 'Each part MUST contain a Content-Disposition header field
// where the disposition type is "form-data".'
if let Some(content_disposition) =
self.headers.get(::http::header::CONTENT_DISPOSITION)
{
ContentDisposition::from_raw(content_disposition).ok()
} else {
None
}
}
}
impl<S> Stream for Field<S>
@@ -490,7 +475,7 @@ where
/// Reads body part content chunk of the specified size.
/// The body part must has `Content-Length` header with proper value.
fn read_len(
payload: &mut PayloadBuffer<S>, size: &mut u64,
payload: &mut PayloadHelper<S>, size: &mut u64,
) -> Poll<Option<Bytes>, MultipartError> {
if *size == 0 {
Ok(Async::Ready(None))
@@ -503,7 +488,7 @@ where
*size -= len;
let ch = chunk.split_to(len as usize);
if !chunk.is_empty() {
payload.unprocessed(chunk);
payload.unread_data(chunk);
}
Ok(Async::Ready(Some(ch)))
}
@@ -515,14 +500,14 @@ where
/// Reads content chunk of body part with unknown length.
/// The `Content-Length` header for body part is not necessary.
fn read_stream(
payload: &mut PayloadBuffer<S>, boundary: &str,
payload: &mut PayloadHelper<S>, boundary: &str,
) -> Poll<Option<Bytes>, MultipartError> {
match payload.read_until(b"\r")? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(mut chunk)) => {
if chunk.len() == 1 {
payload.unprocessed(chunk);
payload.unread_data(chunk);
match payload.read_exact(boundary.len() + 4)? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
@@ -531,12 +516,12 @@ where
&& &chunk[2..4] == b"--"
&& &chunk[4..] == boundary.as_bytes()
{
payload.unprocessed(chunk);
payload.unread_data(chunk);
Ok(Async::Ready(None))
} else {
// \r might be part of data stream
let ch = chunk.split_to(1);
payload.unprocessed(chunk);
payload.unread_data(chunk);
Ok(Async::Ready(Some(ch)))
}
}
@@ -544,7 +529,7 @@ where
} else {
let to = chunk.len() - 1;
let ch = chunk.split_to(to);
payload.unprocessed(chunk);
payload.unread_data(chunk);
Ok(Async::Ready(Some(ch)))
}
}
@@ -592,27 +577,26 @@ where
}
struct PayloadRef<S> {
payload: Rc<UnsafeCell<PayloadBuffer<S>>>,
payload: Rc<PayloadHelper<S>>,
}
impl<S> PayloadRef<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
fn new(payload: PayloadBuffer<S>) -> PayloadRef<S> {
fn new(payload: PayloadHelper<S>) -> PayloadRef<S> {
PayloadRef {
payload: Rc::new(payload.into()),
payload: Rc::new(payload),
}
}
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer<S>>
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadHelper<S>>
where
'a: 'b,
{
// Unsafe: Invariant is inforced by Safety Safety is used as ref counter,
// only top most ref can have mutable access to payload.
if s.current() {
let payload: &mut PayloadBuffer<S> = unsafe { &mut *self.payload.get() };
let payload: &mut PayloadHelper<S> =
unsafe { &mut *(self.payload.as_ref() as *const _ as *mut _) };
Some(payload)
} else {
None
@@ -682,7 +666,7 @@ mod tests {
use bytes::Bytes;
use futures::future::{lazy, result};
use payload::{Payload, PayloadWriter};
use tokio::runtime::current_thread::Runtime;
use tokio_core::reactor::Core;
#[test]
fn test_boundary() {
@@ -729,15 +713,14 @@ mod tests {
#[test]
fn test_multipart() {
Runtime::new()
Core::new()
.unwrap()
.block_on(lazy(|| {
.run(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
@@ -753,18 +736,6 @@ mod tests {
match multipart.poll() {
Ok(Async::Ready(Some(item))) => match item {
MultipartItem::Field(mut field) => {
{
use http::header::{DispositionParam, DispositionType};
let cd = field.content_disposition().unwrap();
assert_eq!(
cd.disposition,
DispositionType::Ext("form-data".into())
);
assert_eq!(
cd.parameters[0],
DispositionParam::Ext("name".into(), "file".into())
);
}
assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN);

View File

@@ -1,14 +1,13 @@
use std;
use std::ops::Index;
use std::path::PathBuf;
use std::rc::Rc;
use std::str::FromStr;
use http::StatusCode;
use smallvec::SmallVec;
use std;
use std::borrow::Cow;
use std::ops::Index;
use std::path::PathBuf;
use std::slice::Iter;
use std::str::FromStr;
use error::{InternalError, ResponseError, UriSegmentError};
use uri::Url;
/// A trait to abstract the idea of creating a new instance of a type from a
/// path parameter.
@@ -20,92 +19,72 @@ pub trait FromParam: Sized {
fn from_param(s: &str) -> Result<Self, Self::Err>;
}
#[derive(Debug, Clone)]
pub(crate) enum ParamItem {
Static(&'static str),
UrlSegment(u16, u16),
}
/// Route match information
///
/// If resource path contains variable patterns, `Params` stores this variables.
#[derive(Debug, Clone)]
pub struct Params {
url: Url,
pub(crate) tail: u16,
pub(crate) segments: SmallVec<[(Rc<String>, ParamItem); 3]>,
}
#[derive(Debug)]
pub struct Params<'a>(SmallVec<[(Cow<'a, str>, Cow<'a, str>); 3]>);
impl Params {
pub(crate) fn new() -> Params {
Params {
url: Url::default(),
tail: 0,
segments: SmallVec::new(),
}
}
pub(crate) fn with_url(url: &Url) -> Params {
Params {
url: url.clone(),
tail: 0,
segments: SmallVec::new(),
}
impl<'a> Params<'a> {
pub(crate) fn new() -> Params<'a> {
Params(SmallVec::new())
}
pub(crate) fn clear(&mut self) {
self.segments.clear();
self.0.clear();
}
pub(crate) fn set_tail(&mut self, tail: u16) {
self.tail = tail;
pub(crate) fn add<N, V>(&mut self, name: N, value: V)
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.0.push((name.into(), value.into()));
}
pub(crate) fn set_url(&mut self, url: Url) {
self.url = url;
pub(crate) fn set<N, V>(&mut self, name: N, value: V)
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
let name = name.into();
let value = value.into();
for item in &mut self.0 {
if item.0 == name {
item.1 = value;
return;
}
}
self.0.push((name, value));
}
pub(crate) fn add(&mut self, name: Rc<String>, value: ParamItem) {
self.segments.push((name, value));
}
pub(crate) fn add_static(&mut self, name: &str, value: &'static str) {
self.segments
.push((Rc::new(name.to_string()), ParamItem::Static(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.segments.is_empty()
self.0.is_empty()
}
/// Check number of extracted parameters
pub fn len(&self) -> usize {
self.segments.len()
self.0.len()
}
/// Get matched parameter by name without type conversion
pub fn get(&self, key: &str) -> Option<&str> {
for item in self.segments.iter() {
if key == item.0.as_str() {
return match item.1 {
ParamItem::Static(ref s) => Some(&s),
ParamItem::UrlSegment(s, e) => {
Some(&self.url.path()[(s as usize)..(e as usize)])
}
};
pub fn get(&'a self, key: &str) -> Option<&'a str> {
for item in self.0.iter() {
if key == item.0 {
return Some(item.1.as_ref());
}
}
if key == "tail" {
Some(&self.url.path()[(self.tail as usize)..])
} else {
None
}
}
/// Get unprocessed part of path
pub fn unprocessed(&self) -> &str {
&self.url.path()[(self.tail as usize)..]
None
}
/// Get matched `FromParam` compatible parameter by name.
@@ -117,12 +96,12 @@ impl Params {
/// # extern crate actix_web;
/// # use actix_web::*;
/// fn index(req: HttpRequest) -> Result<String> {
/// let ivalue: isize = req.match_info().query("val")?;
/// Ok(format!("isuze value: {:?}", ivalue))
/// let ivalue: isize = req.match_info().query("val")?;
/// Ok(format!("isuze value: {:?}", ivalue))
/// }
/// # fn main() {}
/// ```
pub fn query<T: FromParam>(&self, key: &str) -> Result<T, <T as FromParam>::Err> {
pub fn query<T: FromParam>(&'a self, key: &str) -> Result<T, <T as FromParam>::Err> {
if let Some(s) = self.get(key) {
T::from_param(s)
} else {
@@ -131,57 +110,25 @@ impl Params {
}
/// Return iterator to items in parameter container
pub fn iter(&self) -> ParamsIter {
ParamsIter {
idx: 0,
params: self,
}
pub fn iter(&self) -> Iter<(Cow<'a, str>, Cow<'a, str>)> {
self.0.iter()
}
}
#[derive(Debug)]
pub struct ParamsIter<'a> {
idx: usize,
params: &'a Params,
}
impl<'a> Iterator for ParamsIter<'a> {
type Item = (&'a str, &'a str);
#[inline]
fn next(&mut self) -> Option<(&'a str, &'a str)> {
if self.idx < self.params.len() {
let idx = self.idx;
let res = match self.params.segments[idx].1 {
ParamItem::Static(ref s) => &s,
ParamItem::UrlSegment(s, e) => {
&self.params.url.path()[(s as usize)..(e as usize)]
}
};
self.idx += 1;
return Some((&self.params.segments[idx].0, res));
}
None
}
}
impl<'a> Index<&'a str> for Params {
impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> {
type Output = str;
fn index(&self, name: &'a str) -> &str {
fn index(&self, name: &'b str) -> &str {
self.get(name)
.expect("Value for parameter is not available")
}
}
impl Index<usize> for Params {
impl<'a, 'c: 'a> Index<usize> for &'c Params<'a> {
type Output = str;
fn index(&self, idx: usize) -> &str {
match self.segments[idx].1 {
ParamItem::Static(ref s) => &s,
ParamItem::UrlSegment(s, e) => &self.url.path()[(s as usize)..(e as usize)],
}
self.0[idx].1.as_ref()
}
}

View File

@@ -59,14 +59,20 @@ impl Payload {
}
}
/// Indicates EOF of payload
#[inline]
pub fn eof(&self) -> bool {
self.inner.borrow().eof()
}
/// Length of the data in this payload
#[cfg(test)]
#[inline]
pub fn len(&self) -> usize {
self.inner.borrow().len()
}
/// Is payload empty
#[cfg(test)]
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.borrow().len() == 0
}
@@ -219,7 +225,12 @@ impl Inner {
}
}
#[cfg(test)]
#[inline]
fn eof(&self) -> bool {
self.items.is_empty() && self.eof
}
#[inline]
fn len(&self) -> usize {
self.len
}
@@ -280,20 +291,18 @@ impl Inner {
}
}
/// Payload buffer
pub struct PayloadBuffer<S> {
pub struct PayloadHelper<S> {
len: usize,
items: VecDeque<Bytes>,
stream: S,
}
impl<S> PayloadBuffer<S>
impl<S> PayloadHelper<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
/// Create new `PayloadBuffer` instance
pub fn new(stream: S) -> Self {
PayloadBuffer {
PayloadHelper {
len: 0,
items: VecDeque::new(),
stream,
@@ -318,7 +327,6 @@ where
})
}
/// Read first available chunk of bytes
#[inline]
pub fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if let Some(data) = self.items.pop_front() {
@@ -333,7 +341,6 @@ where
}
}
/// Check if buffer contains enough bytes
#[inline]
pub fn can_read(&mut self, size: usize) -> Poll<Option<bool>, PayloadError> {
if size <= self.len {
@@ -347,7 +354,6 @@ where
}
}
/// Return reference to the first chunk of data
#[inline]
pub fn get_chunk(&mut self) -> Poll<Option<&[u8]>, PayloadError> {
if self.items.is_empty() {
@@ -363,7 +369,6 @@ where
}
}
/// Read exact number of bytes
#[inline]
pub fn read_exact(&mut self, size: usize) -> Poll<Option<Bytes>, PayloadError> {
if size <= self.len {
@@ -398,9 +403,8 @@ where
}
}
/// Remove specified amount if bytes from buffer
#[inline]
pub fn drop_bytes(&mut self, size: usize) {
pub fn drop_payload(&mut self, size: usize) {
if size <= self.len {
self.len -= size;
@@ -417,7 +421,6 @@ where
}
}
/// Copy buffered data
pub fn copy(&mut self, size: usize) -> Poll<Option<BytesMut>, PayloadError> {
if size <= self.len {
let mut buf = BytesMut::with_capacity(size);
@@ -439,7 +442,6 @@ where
}
}
/// Read until specified ending
pub fn read_until(&mut self, line: &[u8]) -> Poll<Option<Bytes>, PayloadError> {
let mut idx = 0;
let mut num = 0;
@@ -495,18 +497,16 @@ where
}
}
/// Read bytes until new line delimiter
pub fn readline(&mut self) -> Poll<Option<Bytes>, PayloadError> {
self.read_until(b"\n")
}
/// Put unprocessed data back to the buffer
pub fn unprocessed(&mut self, data: Bytes) {
pub fn unread_data(&mut self, data: Bytes) {
self.len += data.len();
self.items.push_front(data);
}
/// Get remaining data from the buffer
#[allow(dead_code)]
pub fn remaining(&mut self) -> Bytes {
self.items
.iter_mut()
@@ -524,7 +524,7 @@ mod tests {
use failure::Fail;
use futures::future::{lazy, result};
use std::io;
use tokio::runtime::current_thread::Runtime;
use tokio_core::reactor::Core;
#[test]
fn test_error() {
@@ -542,11 +542,11 @@ mod tests {
#[test]
fn test_basic() {
Runtime::new()
Core::new()
.unwrap()
.block_on(lazy(|| {
.run(lazy(|| {
let (_, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
let mut payload = PayloadHelper::new(payload);
assert_eq!(payload.len, 0);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
@@ -559,11 +559,11 @@ mod tests {
#[test]
fn test_eof() {
Runtime::new()
Core::new()
.unwrap()
.block_on(lazy(|| {
.run(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
let mut payload = PayloadHelper::new(payload);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
sender.feed_data(Bytes::from("data"));
@@ -584,11 +584,11 @@ mod tests {
#[test]
fn test_err() {
Runtime::new()
Core::new()
.unwrap()
.block_on(lazy(|| {
.run(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
let mut payload = PayloadHelper::new(payload);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
@@ -602,11 +602,11 @@ mod tests {
#[test]
fn test_readany() {
Runtime::new()
Core::new()
.unwrap()
.block_on(lazy(|| {
.run(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
let mut payload = PayloadHelper::new(payload);
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
@@ -631,11 +631,11 @@ mod tests {
#[test]
fn test_readexactly() {
Runtime::new()
Core::new()
.unwrap()
.block_on(lazy(|| {
.run(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
let mut payload = PayloadHelper::new(payload);
assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap());
@@ -665,11 +665,11 @@ mod tests {
#[test]
fn test_readuntil() {
Runtime::new()
Core::new()
.unwrap()
.block_on(lazy(|| {
.run(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadBuffer::new(payload);
let mut payload = PayloadHelper::new(payload);
assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap());
@@ -699,9 +699,9 @@ mod tests {
#[test]
fn test_unread_data() {
Runtime::new()
Core::new()
.unwrap()
.block_on(lazy(|| {
.run(lazy(|| {
let (_, mut payload) = Payload::new(false);
payload.unread_data(Bytes::from("data"));

View File

@@ -1,11 +1,13 @@
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::rc::Rc;
use std::{io, mem};
use futures::sync::oneshot;
use futures::unsync::oneshot;
use futures::{Async, Future, Poll, Stream};
use log::Level::Debug;
use application::Inner;
use body::{Body, BodyStream};
use context::{ActorHttpContext, Frame};
use error::Error;
@@ -16,19 +18,22 @@ use httpresponse::HttpResponse;
use middleware::{Finished, Middleware, Response, Started};
use server::{HttpHandlerTask, Writer, WriterState};
#[doc(hidden)]
pub trait PipelineHandler<S> {
fn encoding(&self) -> ContentEncoding;
fn handle(&self, &HttpRequest<S>) -> AsyncResult<HttpResponse>;
#[derive(Debug, Clone, Copy)]
pub(crate) enum HandlerType {
Normal(usize),
Handler(usize),
Default,
}
#[doc(hidden)]
pub struct Pipeline<S: 'static, H>(
PipelineInfo<S>,
PipelineState<S, H>,
Rc<Vec<Box<Middleware<S>>>>,
);
pub(crate) trait PipelineHandler<S> {
fn encoding(&self) -> ContentEncoding;
fn handle(
&mut self, req: HttpRequest<S>, htype: HandlerType,
) -> AsyncResult<HttpResponse>;
}
pub(crate) struct Pipeline<S, H>(PipelineInfo<S>, PipelineState<S, H>);
enum PipelineState<S, H> {
None,
@@ -49,14 +54,12 @@ impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> {
}
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
match *self {
PipelineState::Starting(ref mut state) => state.poll(info, mws),
PipelineState::Handler(ref mut state) => state.poll(info, mws),
PipelineState::RunMiddlewares(ref mut state) => state.poll(info, mws),
PipelineState::Finishing(ref mut state) => state.poll(info, mws),
PipelineState::Starting(ref mut state) => state.poll(info),
PipelineState::Handler(ref mut state) => state.poll(info),
PipelineState::RunMiddlewares(ref mut state) => state.poll(info),
PipelineState::Finishing(ref mut state) => state.poll(info),
PipelineState::Completed(ref mut state) => state.poll(info),
PipelineState::Response(_) | PipelineState::None | PipelineState::Error => {
None
@@ -65,16 +68,43 @@ impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> {
}
}
struct PipelineInfo<S: 'static> {
req: HttpRequest<S>,
struct PipelineInfo<S> {
req: UnsafeCell<HttpRequest<S>>,
count: u16,
mws: Rc<Vec<Box<Middleware<S>>>>,
context: Option<Box<ActorHttpContext>>,
error: Option<Error>,
disconnected: Option<bool>,
encoding: ContentEncoding,
}
impl<S: 'static> PipelineInfo<S> {
impl<S> PipelineInfo<S> {
fn new(req: HttpRequest<S>) -> PipelineInfo<S> {
PipelineInfo {
req: UnsafeCell::new(req),
count: 0,
mws: Rc::new(Vec::new()),
error: None,
context: None,
disconnected: None,
encoding: ContentEncoding::Auto,
}
}
#[inline]
fn req(&self) -> &HttpRequest<S> {
unsafe { &*self.req.get() }
}
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
fn req_mut(&self) -> &mut HttpRequest<S> {
#[allow(mutable_transmutes)]
unsafe {
&mut *self.req.get()
}
}
fn poll_context(&mut self) -> Poll<(), Error> {
if let Some(ref mut context) = self.context {
match context.poll() {
@@ -90,19 +120,30 @@ impl<S: 'static> PipelineInfo<S> {
impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
pub fn new(
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: Rc<H>,
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
handler: Rc<UnsafeCell<H>>, htype: HandlerType,
) -> Pipeline<S, H> {
let mut info = PipelineInfo {
req,
mws,
req: UnsafeCell::new(req),
count: 0,
error: None,
context: None,
disconnected: None,
encoding: handler.encoding(),
encoding: unsafe { &*handler.get() }.encoding(),
};
let state = StartMiddlewares::init(&mut info, &mws, handler);
let state = StartMiddlewares::init(&mut info, handler, htype);
Pipeline(info, state, mws)
Pipeline(info, state)
}
}
impl Pipeline<(), Inner<()>> {
pub fn error<R: Into<HttpResponse>>(err: R) -> Box<HttpHandlerTask> {
Box::new(Pipeline::<(), Inner<()>>(
PipelineInfo::new(HttpRequest::default()),
ProcessResponse::init(err.into()),
))
}
}
@@ -127,12 +168,13 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
let mut state = mem::replace(&mut self.1, PipelineState::None);
let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) };
loop {
if state.is_response() {
if self.1.is_response() {
let state = mem::replace(&mut self.1, PipelineState::None);
if let PipelineState::Response(st) = state {
match st.poll_io(io, &mut self.0, &self.2) {
match st.poll_io(io, info) {
Ok(state) => {
self.1 = state;
if let Some(error) = self.0.error.take() {
@@ -148,7 +190,7 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
}
}
}
match state {
match self.1 {
PipelineState::None => return Ok(Async::Ready(true)),
PipelineState::Error => {
return Err(
@@ -158,32 +200,27 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
_ => (),
}
match state.poll(&mut self.0, &self.2) {
Some(st) => state = st,
None => {
return {
self.1 = state;
Ok(Async::NotReady)
}
}
match self.1.poll(info) {
Some(state) => self.1 = state,
None => return Ok(Async::NotReady),
}
}
}
fn poll_completed(&mut self) -> Poll<(), Error> {
let mut state = mem::replace(&mut self.1, PipelineState::None);
fn poll(&mut self) -> Poll<(), Error> {
let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) };
loop {
match state {
match self.1 {
PipelineState::None | PipelineState::Error => {
return Ok(Async::Ready(()))
}
_ => (),
}
if let Some(st) = state.poll(&mut self.0, &self.2) {
state = st;
} else {
if let Some(state) = self.1.poll(info) {
self.1 = state;
} else {
return Ok(Async::NotReady);
}
}
@@ -194,88 +231,76 @@ type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
/// Middlewares start executor
struct StartMiddlewares<S, H> {
hnd: Rc<H>,
hnd: Rc<UnsafeCell<H>>,
htype: HandlerType,
fut: Option<Fut>,
_s: PhantomData<S>,
}
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
fn init(
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], hnd: Rc<H>,
info: &mut PipelineInfo<S>, hnd: Rc<UnsafeCell<H>>, htype: HandlerType,
) -> PipelineState<S, H> {
// execute middlewares, we need this stage because middlewares could be
// non-async and we can move to next state immediately
let len = mws.len() as u16;
let len = info.mws.len() as u16;
loop {
if info.count == len {
let reply = hnd.handle(&info.req);
return WaitingResponse::init(info, mws, reply);
let reply = unsafe { &mut *hnd.get() }.handle(info.req().clone(), htype);
return WaitingResponse::init(info, reply);
} else {
match mws[info.count as usize].start(&info.req) {
match info.mws[info.count as usize].start(info.req_mut()) {
Ok(Started::Done) => info.count += 1,
Ok(Started::Response(resp)) => {
return RunMiddlewares::init(info, mws, resp);
return RunMiddlewares::init(info, resp)
}
Ok(Started::Future(fut)) => {
return PipelineState::Starting(StartMiddlewares {
hnd,
htype,
fut: Some(fut),
_s: PhantomData,
})
}
Err(err) => {
return RunMiddlewares::init(info, mws, err.into());
}
Err(err) => return RunMiddlewares::init(info, err.into()),
}
}
}
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
let len = mws.len() as u16;
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
let len = info.mws.len() as u16;
'outer: loop {
match self.fut.as_mut().unwrap().poll() {
Ok(Async::NotReady) => {
return None;
}
Ok(Async::NotReady) => return None,
Ok(Async::Ready(resp)) => {
info.count += 1;
if let Some(resp) = resp {
return Some(RunMiddlewares::init(info, mws, resp));
return Some(RunMiddlewares::init(info, resp));
}
loop {
if info.count == len {
let reply = self.hnd.handle(&info.req);
return Some(WaitingResponse::init(info, mws, reply));
let reply = unsafe { &mut *self.hnd.get() }
.handle(info.req().clone(), self.htype);
return Some(WaitingResponse::init(info, reply));
} else {
let res = mws[info.count as usize].start(&info.req);
match res {
match info.mws[info.count as usize].start(info.req_mut()) {
Ok(Started::Done) => info.count += 1,
Ok(Started::Response(resp)) => {
return Some(RunMiddlewares::init(info, mws, resp));
return Some(RunMiddlewares::init(info, resp));
}
Ok(Started::Future(fut)) => {
self.fut = Some(fut);
continue 'outer;
}
Err(err) => {
return Some(RunMiddlewares::init(
info,
mws,
err.into(),
));
return Some(RunMiddlewares::init(info, err.into()))
}
}
}
}
}
Err(err) => {
return Some(RunMiddlewares::init(info, mws, err.into()));
}
Err(err) => return Some(RunMiddlewares::init(info, err.into())),
}
}
}
@@ -291,12 +316,11 @@ struct WaitingResponse<S, H> {
impl<S: 'static, H> WaitingResponse<S, H> {
#[inline]
fn init(
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
reply: AsyncResult<HttpResponse>,
info: &mut PipelineInfo<S>, reply: AsyncResult<HttpResponse>,
) -> PipelineState<S, H> {
match reply.into() {
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, mws, resp),
AsyncResultItem::Err(err) => RunMiddlewares::init(info, mws, err.into()),
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse {
fut,
_s: PhantomData,
@@ -305,13 +329,11 @@ impl<S: 'static, H> WaitingResponse<S, H> {
}
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, mws, resp)),
Err(err) => Some(RunMiddlewares::init(info, mws, err.into())),
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
Err(err) => Some(RunMiddlewares::init(info, err.into())),
}
}
}
@@ -326,18 +348,15 @@ struct RunMiddlewares<S, H> {
impl<S: 'static, H> RunMiddlewares<S, H> {
#[inline]
fn init(
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], mut resp: HttpResponse,
) -> PipelineState<S, H> {
fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H> {
if info.count == 0 {
return ProcessResponse::init(resp);
}
let mut curr = 0;
let len = mws.len();
let len = info.mws.len();
loop {
let state = mws[curr].response(&info.req, resp);
resp = match state {
resp = match info.mws[curr].response(info.req_mut(), resp) {
Err(err) => {
info.count = (curr + 1) as u16;
return ProcessResponse::init(err.into());
@@ -356,16 +375,14 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
fut: Some(fut),
_s: PhantomData,
_h: PhantomData,
});
})
}
};
}
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
let len = mws.len();
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
let len = info.mws.len();
loop {
// poll latest fut
@@ -382,8 +399,7 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
if self.curr == len {
return Some(ProcessResponse::init(resp));
} else {
let state = mws[self.curr].response(&info.req, resp);
match state {
match info.mws[self.curr].response(info.req_mut(), resp) {
Err(err) => return Some(ProcessResponse::init(err.into())),
Ok(Response::Done(r)) => {
self.curr += 1;
@@ -453,7 +469,6 @@ impl<S: 'static, H> ProcessResponse<S, H> {
fn poll_io(
mut self, io: &mut Writer, info: &mut PipelineInfo<S>,
mws: &[Box<Middleware<S>>],
) -> Result<PipelineState<S, H>, PipelineState<S, H>> {
loop {
if self.drain.is_none() && self.running != RunningState::Paused {
@@ -464,16 +479,19 @@ impl<S: 'static, H> ProcessResponse<S, H> {
let encoding =
self.resp.content_encoding().unwrap_or(info.encoding);
let result =
match io.start(&info.req, &mut self.resp, encoding) {
Ok(res) => res,
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info, mws, self.resp,
));
}
};
let result = match io.start(
info.req_mut().get_inner(),
&mut self.resp,
encoding,
) {
Ok(res) => res,
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info, self.resp,
));
}
};
if let Some(err) = self.resp.error() {
if self.resp.status().is_server_error() {
@@ -512,18 +530,18 @@ impl<S: 'static, H> ProcessResponse<S, H> {
if let Err(err) = io.write_eof() {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info, mws, self.resp,
info, self.resp,
));
}
break;
}
Ok(Async::Ready(Some(chunk))) => {
self.iostate = IOState::Payload(body);
match io.write(&chunk.into()) {
match io.write(chunk.into()) {
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info, mws, self.resp,
info, self.resp,
));
}
Ok(result) => result,
@@ -535,9 +553,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
}
Err(err) => {
info.error = Some(err);
return Ok(FinishingMiddlewares::init(
info, mws, self.resp,
));
return Ok(FinishingMiddlewares::init(info, self.resp));
}
},
IOState::Actor(mut ctx) => {
@@ -559,20 +575,19 @@ impl<S: 'static, H> ProcessResponse<S, H> {
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info, mws, self.resp,
info, self.resp,
),
);
}
break 'inner;
}
Frame::Chunk(Some(chunk)) => {
match io.write(&chunk) {
match io.write(chunk) {
Err(err) => {
info.context = Some(ctx);
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info, mws, self.resp,
info, self.resp,
),
);
}
@@ -595,10 +610,9 @@ impl<S: 'static, H> ProcessResponse<S, H> {
break;
}
Err(err) => {
info.context = Some(ctx);
info.error = Some(err);
return Ok(FinishingMiddlewares::init(
info, mws, self.resp,
info, self.resp,
));
}
}
@@ -631,14 +645,8 @@ impl<S: 'static, H> ProcessResponse<S, H> {
}
Ok(Async::NotReady) => return Err(PipelineState::Response(self)),
Err(err) => {
if let IOState::Actor(mut ctx) =
mem::replace(&mut self.iostate, IOState::Done)
{
ctx.disconnected();
info.context = Some(ctx);
}
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(info, mws, self.resp));
return Ok(FinishingMiddlewares::init(info, self.resp));
}
}
}
@@ -652,11 +660,11 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Ok(_) => (),
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(info, mws, self.resp));
return Ok(FinishingMiddlewares::init(info, self.resp));
}
}
self.resp.set_response_size(io.written());
Ok(FinishingMiddlewares::init(info, mws, self.resp))
Ok(FinishingMiddlewares::init(info, self.resp))
}
_ => Err(PipelineState::Response(self)),
}
@@ -665,7 +673,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
/// Middlewares start executor
struct FinishingMiddlewares<S, H> {
resp: Option<HttpResponse>,
resp: HttpResponse,
fut: Option<Box<Future<Item = (), Error = Error>>>,
_s: PhantomData<S>,
_h: PhantomData<H>,
@@ -673,20 +681,17 @@ struct FinishingMiddlewares<S, H> {
impl<S: 'static, H> FinishingMiddlewares<S, H> {
#[inline]
fn init(
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], resp: HttpResponse,
) -> PipelineState<S, H> {
fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> {
if info.count == 0 {
resp.release();
Completed::init(info)
} else {
let mut state = FinishingMiddlewares {
resp: Some(resp),
resp,
fut: None,
_s: PhantomData,
_h: PhantomData,
};
if let Some(st) = state.poll(info, mws) {
if let Some(st) = state.poll(info) {
st
} else {
PipelineState::Finishing(state)
@@ -694,9 +699,7 @@ impl<S: 'static, H> FinishingMiddlewares<S, H> {
}
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
loop {
// poll latest fut
let not_ready = if let Some(ref mut fut) = self.fut {
@@ -716,17 +719,13 @@ impl<S: 'static, H> FinishingMiddlewares<S, H> {
}
self.fut = None;
if info.count == 0 {
self.resp.take().unwrap().release();
return Some(Completed::init(info));
}
info.count -= 1;
let state =
mws[info.count as usize].finish(&info.req, self.resp.as_ref().unwrap());
match state {
match info.mws[info.count as usize].finish(info.req_mut(), &self.resp) {
Finished::Done => {
if info.count == 0 {
self.resp.take().unwrap().release();
return Some(Completed::init(info));
}
}
@@ -751,13 +750,7 @@ impl<S, H> Completed<S, H> {
if info.context.is_none() {
PipelineState::None
} else {
match info.poll_context() {
Ok(Async::NotReady) => {
PipelineState::Completed(Completed(PhantomData, PhantomData))
}
Ok(Async::Ready(())) => PipelineState::None,
Err(_) => PipelineState::Error,
}
PipelineState::Completed(Completed(PhantomData, PhantomData))
}
}
@@ -770,3 +763,68 @@ impl<S, H> Completed<S, H> {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use actix::*;
use context::HttpContext;
use futures::future::{lazy, result};
use tokio_core::reactor::Core;
impl<S, H> PipelineState<S, H> {
fn is_none(&self) -> Option<bool> {
if let PipelineState::None = *self {
Some(true)
} else {
None
}
}
fn completed(self) -> Option<Completed<S, H>> {
if let PipelineState::Completed(c) = self {
Some(c)
} else {
None
}
}
}
struct MyActor;
impl Actor for MyActor {
type Context = HttpContext<MyActor>;
}
#[test]
fn test_completed() {
Core::new()
.unwrap()
.run(lazy(|| {
let mut info = PipelineInfo::new(HttpRequest::default());
Completed::<(), Inner<()>>::init(&mut info)
.is_none()
.unwrap();
let req = HttpRequest::default();
let mut ctx = HttpContext::new(req.clone(), MyActor);
let addr: Addr<Unsync, _> = ctx.address();
let mut info = PipelineInfo::new(req);
info.context = Some(Box::new(ctx));
let mut state = Completed::<(), Inner<()>>::init(&mut info)
.completed()
.unwrap();
assert!(state.poll(&mut info).is_none());
let pp = Pipeline(info, PipelineState::Completed(state));
assert!(!pp.is_done());
let Pipeline(mut info, st) = pp;
let mut st = st.completed().unwrap();
drop(addr);
assert!(st.poll(&mut info).unwrap().is_none().unwrap());
result(Ok::<_, ()>(()))
}))
.unwrap();
}
}

View File

@@ -1,10 +1,10 @@
//! Route match predicates
#![allow(non_snake_case)]
use std::marker::PhantomData;
use http;
use http::{header, HttpTryFrom};
use server::message::Request;
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use std::marker::PhantomData;
/// Trait defines resource route predicate.
/// Predicate can modify request object. It is also possible to
@@ -12,7 +12,7 @@ use server::message::Request;
/// Extensions container available via `HttpRequest::extensions()` method.
pub trait Predicate<S> {
/// Check if request matches predicate
fn check(&self, &Request, &S) -> bool;
fn check(&self, &mut HttpRequest<S>) -> bool;
}
/// Return predicate that matches if any of supplied predicate matches.
@@ -22,11 +22,10 @@ pub trait Predicate<S> {
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new().resource("/index.html", |r| {
/// r.route()
/// App::new()
/// .resource("/index.html", |r| r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Post()))
/// .f(|r| HttpResponse::MethodNotAllowed())
/// });
/// .f(|r| HttpResponse::MethodNotAllowed()));
/// }
/// ```
pub fn Any<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AnyPredicate<S> {
@@ -45,9 +44,9 @@ impl<S> AnyPredicate<S> {
}
impl<S: 'static> Predicate<S> for AnyPredicate<S> {
fn check(&self, req: &Request, state: &S) -> bool {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
for p in &self.0 {
if p.check(req, state) {
if p.check(req) {
return true;
}
}
@@ -62,14 +61,11 @@ impl<S: 'static> Predicate<S> for AnyPredicate<S> {
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .filter(
/// pred::All(pred::Get())
/// .and(pred::Header("content-type", "text/plain")),
/// )
/// .f(|_| HttpResponse::MethodNotAllowed())
/// });
/// App::new()
/// .resource("/index.html", |r| r.route()
/// .filter(pred::All(pred::Get())
/// .and(pred::Header("content-type", "plain/text")))
/// .f(|_| HttpResponse::MethodNotAllowed()));
/// }
/// ```
pub fn All<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AllPredicate<S> {
@@ -88,9 +84,9 @@ impl<S> AllPredicate<S> {
}
impl<S: 'static> Predicate<S> for AllPredicate<S> {
fn check(&self, req: &Request, state: &S) -> bool {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
for p in &self.0 {
if !p.check(req, state) {
if !p.check(req) {
return false;
}
}
@@ -107,8 +103,8 @@ pub fn Not<S: 'static, P: Predicate<S> + 'static>(pred: P) -> NotPredicate<S> {
pub struct NotPredicate<S>(Box<Predicate<S>>);
impl<S: 'static> Predicate<S> for NotPredicate<S> {
fn check(&self, req: &Request, state: &S) -> bool {
!self.0.check(req, state)
fn check(&self, req: &mut HttpRequest<S>) -> bool {
!self.0.check(req)
}
}
@@ -117,7 +113,7 @@ impl<S: 'static> Predicate<S> for NotPredicate<S> {
pub struct MethodPredicate<S>(http::Method, PhantomData<S>);
impl<S: 'static> Predicate<S> for MethodPredicate<S> {
fn check(&self, req: &Request, _: &S) -> bool {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
*req.method() == self.0
}
}
@@ -188,7 +184,7 @@ pub fn Header<S: 'static>(
pub struct HeaderPredicate<S>(header::HeaderName, header::HeaderValue, PhantomData<S>);
impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
fn check(&self, req: &Request, _: &S) -> bool {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
if let Some(val) = req.headers().get(&self.0) {
return val == self.1;
}
@@ -225,7 +221,7 @@ impl<S> HostPredicate<S> {
}
impl<S: 'static> Predicate<S> for HostPredicate<S> {
fn check(&self, req: &Request, _: &S) -> bool {
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()
@@ -238,92 +234,167 @@ impl<S: 'static> Predicate<S> for HostPredicate<S> {
#[cfg(test)]
mod tests {
use super::*;
use http::{header, Method};
use test::TestRequest;
use http::header::{self, HeaderMap};
use http::{Method, Uri, Version};
use std::str::FromStr;
#[test]
fn test_header() {
let req = TestRequest::with_header(
let mut headers = HeaderMap::new();
headers.insert(
header::TRANSFER_ENCODING,
header::HeaderValue::from_static("chunked"),
).finish();
);
let mut req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let pred = Header("transfer-encoding", "chunked");
assert!(pred.check(&req, req.state()));
assert!(pred.check(&mut req));
let pred = Header("transfer-encoding", "other");
assert!(!pred.check(&req, req.state()));
assert!(!pred.check(&mut req));
let pred = Header("content-type", "other");
assert!(!pred.check(&req, req.state()));
assert!(!pred.check(&mut req));
}
#[test]
fn test_host() {
let req = TestRequest::default()
.header(
header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"),
)
.finish();
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(&req, req.state()));
assert!(pred.check(&mut req));
let pred = Host("localhost");
assert!(!pred.check(&req, req.state()));
assert!(!pred.check(&mut req));
}
#[test]
fn test_methods() {
let req = TestRequest::default().finish();
let req2 = TestRequest::default().method(Method::POST).finish();
let mut req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
let mut req2 = HttpRequest::new(
Method::POST,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Get().check(&req, req.state()));
assert!(!Get().check(&req2, req2.state()));
assert!(Post().check(&req2, req2.state()));
assert!(!Post().check(&req, req.state()));
assert!(Get().check(&mut req));
assert!(!Get().check(&mut req2));
assert!(Post().check(&mut req2));
assert!(!Post().check(&mut req));
let r = TestRequest::default().method(Method::PUT).finish();
assert!(Put().check(&r, r.state()));
assert!(!Put().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::PUT,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Put().check(&mut r));
assert!(!Put().check(&mut req));
let r = TestRequest::default().method(Method::DELETE).finish();
assert!(Delete().check(&r, r.state()));
assert!(!Delete().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::DELETE,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Delete().check(&mut r));
assert!(!Delete().check(&mut req));
let r = TestRequest::default().method(Method::HEAD).finish();
assert!(Head().check(&r, r.state()));
assert!(!Head().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::HEAD,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Head().check(&mut r));
assert!(!Head().check(&mut req));
let r = TestRequest::default().method(Method::OPTIONS).finish();
assert!(Options().check(&r, r.state()));
assert!(!Options().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::OPTIONS,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Options().check(&mut r));
assert!(!Options().check(&mut req));
let r = TestRequest::default().method(Method::CONNECT).finish();
assert!(Connect().check(&r, r.state()));
assert!(!Connect().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::CONNECT,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Connect().check(&mut r));
assert!(!Connect().check(&mut req));
let r = TestRequest::default().method(Method::PATCH).finish();
assert!(Patch().check(&r, r.state()));
assert!(!Patch().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::PATCH,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Patch().check(&mut r));
assert!(!Patch().check(&mut req));
let r = TestRequest::default().method(Method::TRACE).finish();
assert!(Trace().check(&r, r.state()));
assert!(!Trace().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::TRACE,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Trace().check(&mut r));
assert!(!Trace().check(&mut req));
}
#[test]
fn test_preds() {
let r = TestRequest::default().method(Method::TRACE).finish();
let mut r = HttpRequest::new(
Method::TRACE,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Not(Get()).check(&r, r.state()));
assert!(!Not(Trace()).check(&r, r.state()));
assert!(Not(Get()).check(&mut r));
assert!(!Not(Trace()).check(&mut r));
assert!(All(Trace()).and(Trace()).check(&r, r.state()));
assert!(!All(Get()).and(Trace()).check(&r, r.state()));
assert!(All(Trace()).and(Trace()).check(&mut r));
assert!(!All(Get()).and(Trace()).check(&mut r));
assert!(Any(Get()).or(Trace()).check(&r, r.state()));
assert!(!Any(Get()).or(Get()).check(&r, r.state()));
assert!(Any(Get()).or(Trace()).check(&mut r));
assert!(!Any(Get()).or(Get()).check(&mut r));
}
}

View File

@@ -1,8 +1,8 @@
use std::ops::Deref;
use std::marker::PhantomData;
use std::rc::Rc;
use futures::Future;
use http::Method;
use http::{Method, StatusCode};
use smallvec::SmallVec;
use error::Error;
@@ -12,10 +12,6 @@ use httpresponse::HttpResponse;
use middleware::Middleware;
use pred;
use route::Route;
use router::ResourceDef;
#[derive(Copy, Clone)]
pub(crate) struct RouteId(usize);
/// *Resource* is an entry in route table which corresponds to requested URL.
///
@@ -37,39 +33,45 @@ pub(crate) struct RouteId(usize);
/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok()))
/// .finish();
/// }
pub struct Resource<S = ()> {
rdef: ResourceDef,
pub struct ResourceHandler<S = ()> {
name: String,
state: PhantomData<S>,
routes: SmallVec<[Route<S>; 3]>,
middlewares: Rc<Vec<Box<Middleware<S>>>>,
}
impl<S> Resource<S> {
/// Create new resource with specified resource definition
pub fn new(rdef: ResourceDef) -> Self {
Resource {
rdef,
impl<S> Default for ResourceHandler<S> {
fn default() -> Self {
ResourceHandler {
name: String::new(),
state: PhantomData,
routes: SmallVec::new(),
middlewares: Rc::new(Vec::new()),
}
}
}
impl<S> ResourceHandler<S> {
pub(crate) fn default_not_found() -> Self {
ResourceHandler {
name: String::new(),
state: PhantomData,
routes: SmallVec::new(),
middlewares: Rc::new(Vec::new()),
}
}
/// Name of the resource
pub(crate) fn get_name(&self) -> &str {
self.rdef.name()
}
/// Set resource name
pub fn name(&mut self, name: &str) {
self.rdef.set_name(name);
pub fn name<T: Into<String>>(&mut self, name: T) {
self.name = name.into();
}
/// Resource definition
pub fn rdef(&self) -> &ResourceDef {
&self.rdef
pub(crate) fn get_name(&self) -> &str {
&self.name
}
}
impl<S: 'static> Resource<S> {
impl<S: 'static> ResourceHandler<S> {
/// Register a new route and return mutable reference to *Route* object.
/// *Route* is used for route configuration, i.e. adding predicates,
/// setting up handler.
@@ -80,12 +82,11 @@ impl<S: 'static> Resource<S> {
///
/// fn main() {
/// let app = App::new()
/// .resource("/", |r| {
/// r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
/// .filter(pred::Header("Content-Type", "text/plain"))
/// .f(|r| HttpResponse::Ok())
/// })
/// .resource(
/// "/", |r| r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
/// .filter(pred::Header("Content-Type", "text/plain"))
/// .f(|r| HttpResponse::Ok()))
/// .finish();
/// }
/// ```
@@ -126,21 +127,10 @@ impl<S: 'static> Resource<S> {
/// Register a new route and add method check to route.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.method(http::Method::GET).f(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index));
/// ```rust,ignore
/// Application::resource("/", |r| r.route().filter(pred::Get()).f(index)
/// ```
pub fn method(&mut self, method: Method) -> &mut Route<S> {
self.routes.push(Route::default());
@@ -149,21 +139,10 @@ impl<S: 'static> Resource<S> {
/// Register a new route and add handler object.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.h(handler));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().h(handler));
/// ```rust,ignore
/// Application::resource("/", |r| r.route().h(handler)
/// ```
pub fn h<H: Handler<S>>(&mut self, handler: H) {
self.routes.push(Route::default());
@@ -172,25 +151,14 @@ impl<S: 'static> Resource<S> {
/// Register a new route and add handler function.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.f(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().f(index));
/// ```rust,ignore
/// Application::resource("/", |r| r.route().f(index)
/// ```
pub fn f<F, R>(&mut self, handler: F)
where
F: Fn(&HttpRequest<S>) -> R + 'static,
F: Fn(HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
self.routes.push(Route::default());
@@ -199,21 +167,10 @@ impl<S: 'static> Resource<S> {
/// Register a new route and add handler.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.with(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().with(index));
/// ```rust,ignore
/// Application::resource("/", |r| r.route().with(index)
/// ```
pub fn with<T, F, R>(&mut self, handler: F)
where
@@ -227,30 +184,10 @@ impl<S: 'static> Resource<S> {
/// Register a new route and add async handler.
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// use actix_web::*;
/// use futures::future::Future;
///
/// fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// unimplemented!()
/// }
///
/// App::new().resource("/", |r| r.with_async(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # use actix_web::*;
/// # use futures::future::Future;
/// # fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// # unimplemented!()
/// # }
/// App::new().resource("/", |r| r.route().with_async(index));
/// ```rust,ignore
/// Application::resource("/", |r| r.route().with_async(index)
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where
@@ -277,47 +214,22 @@ impl<S: 'static> Resource<S> {
.push(Box::new(mw));
}
#[inline]
pub(crate) fn get_route_id(&self, req: &HttpRequest<S>) -> Option<RouteId> {
for idx in 0..self.routes.len() {
if (&self.routes[idx]).check(req) {
return Some(RouteId(idx));
pub(crate) fn handle(
&mut self, mut req: HttpRequest<S>, default: Option<&mut ResourceHandler<S>>,
) -> AsyncResult<HttpResponse> {
for route in &mut self.routes {
if route.check(&mut req) {
return if self.middlewares.is_empty() {
route.handle(req)
} else {
route.compose(req, Rc::clone(&self.middlewares))
};
}
}
None
}
#[inline]
pub(crate) fn handle(
&self, id: RouteId, req: &HttpRequest<S>,
) -> AsyncResult<HttpResponse> {
if self.middlewares.is_empty() {
(&self.routes[id.0]).handle(req)
if let Some(resource) = default {
resource.handle(req, None)
} else {
(&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares))
AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND))
}
}
}
/// Default resource
pub struct DefaultResource<S>(Rc<Resource<S>>);
impl<S> Deref for DefaultResource<S> {
type Target = Resource<S>;
fn deref(&self) -> &Resource<S> {
self.0.as_ref()
}
}
impl<S> Clone for DefaultResource<S> {
fn clone(&self) -> Self {
DefaultResource(self.0.clone())
}
}
impl<S> From<Resource<S>> for DefaultResource<S> {
fn from(res: Resource<S>) -> Self {
DefaultResource(Rc::new(res))
}
}

View File

@@ -1,3 +1,4 @@
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::rc::Rc;
@@ -16,7 +17,7 @@ use middleware::{
Started as MiddlewareStarted,
};
use pred::Predicate;
use with::{With, WithAsync};
use with::{ExtractorConfig, With, With2, With3, WithAsync};
/// Resource route definition
///
@@ -31,17 +32,16 @@ impl<S: 'static> Default for Route<S> {
fn default() -> Route<S> {
Route {
preds: Vec::new(),
handler: InnerHandler::new(|_: &_| HttpResponse::new(StatusCode::NOT_FOUND)),
handler: InnerHandler::new(|_| HttpResponse::new(StatusCode::NOT_FOUND)),
}
}
}
impl<S: 'static> Route<S> {
#[inline]
pub(crate) fn check(&self, req: &HttpRequest<S>) -> bool {
let state = req.state();
pub(crate) fn check(&self, req: &mut HttpRequest<S>) -> bool {
for pred in &self.preds {
if !pred.check(req, state) {
if !pred.check(req) {
return false;
}
}
@@ -49,13 +49,13 @@ impl<S: 'static> Route<S> {
}
#[inline]
pub(crate) fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
pub(crate) fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
self.handler.handle(req)
}
#[inline]
pub(crate) fn compose(
&self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
&mut self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
) -> AsyncResult<HttpResponse> {
AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
}
@@ -90,7 +90,7 @@ impl<S: 'static> Route<S> {
/// during route configuration, so it does not return reference to self.
pub fn f<F, R>(&mut self, handler: F)
where
F: Fn(&HttpRequest<S>) -> R + 'static,
F: Fn(HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
self.handler = InnerHandler::new(handler);
@@ -99,7 +99,7 @@ impl<S: 'static> Route<S> {
/// Set async handler function.
pub fn a<H, R, F, E>(&mut self, handler: H)
where
H: Fn(&HttpRequest<S>) -> F + 'static,
H: Fn(HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
@@ -164,49 +164,15 @@ impl<S: 'static> Route<S> {
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with<T, F, R>(&mut self, handler: F)
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
where
F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
self.h(With::new(handler, <T::Config as Default>::default()));
}
/// Set handler function. Same as `.with()` but it allows to configure
/// extractor.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Path, Result};
///
/// /// extract text data from request
/// fn index(body: String) -> Result<String> {
/// Ok(format!("Body {}!", body))
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET)
/// .with_config(index, |cfg| { // <- register handler
/// cfg.limit(4096); // <- limit size of the payload
/// })
/// });
/// }
/// ```
pub fn with_config<T, F, R, C>(&mut self, handler: F, cfg_f: C)
where
F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
C: FnOnce(&mut T::Config),
{
let mut cfg = <T::Config as Default>::default();
cfg_f(&mut cfg);
self.h(With::new(handler, cfg));
let cfg = ExtractorConfig::default();
self.h(With::new(handler, Clone::clone(&cfg)));
cfg
}
/// Set async handler function, use request extractor for parameters.
@@ -238,7 +204,7 @@ impl<S: 'static> Route<S> {
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
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,
@@ -246,79 +212,117 @@ impl<S: 'static> Route<S> {
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.h(WithAsync::new(handler, <T::Config as Default>::default()));
let cfg = ExtractorConfig::default();
self.h(WithAsync::new(handler, Clone::clone(&cfg)));
cfg
}
/// Set async handler function, use request extractor for parameters.
/// This method allows to configure extractor.
#[doc(hidden)]
/// Set handler function, use request extractor for both parameters.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Error, Form};
/// use futures::Future;
/// use actix_web::{http, App, Path, Query, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
/// struct PParam {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Form<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
/// unimplemented!()
/// #[derive(Deserialize)]
/// struct QParam {
/// count: u32,
/// }
///
/// /// extract path and query information using serde
/// fn index(p: Path<PParam>, q: Query<QParam>) -> Result<String> {
/// Ok(format!("Welcome {}!", p.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET)
/// .with_async_config(index, |cfg| {
/// cfg.limit(4096);
/// }),
/// |r| r.method(http::Method::GET).with2(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async_config<T, F, R, I, E, C>(&mut self, handler: F, cfg: C)
pub fn with2<T1, T2, F, R>(
&mut self, handler: F,
) -> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>)
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
C: FnOnce(&mut T::Config),
F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
{
let mut extractor_cfg = <T::Config as Default>::default();
cfg(&mut extractor_cfg);
self.h(WithAsync::new(handler, extractor_cfg));
let cfg1 = ExtractorConfig::default();
let cfg2 = ExtractorConfig::default();
self.h(With2::new(
handler,
Clone::clone(&cfg1),
Clone::clone(&cfg2),
));
(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,
) -> (
ExtractorConfig<S, T1>,
ExtractorConfig<S, T2>,
ExtractorConfig<S, T3>,
)
where
F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
{
let cfg1 = ExtractorConfig::default();
let cfg2 = ExtractorConfig::default();
let cfg3 = ExtractorConfig::default();
self.h(With3::new(
handler,
Clone::clone(&cfg1),
Clone::clone(&cfg2),
Clone::clone(&cfg3),
));
(cfg1, cfg2, cfg3)
}
}
/// `RouteHandler` wrapper. This struct is required because it needs to be
/// shared for resource level middlewares.
struct InnerHandler<S>(Rc<Box<RouteHandler<S>>>);
struct InnerHandler<S>(Rc<UnsafeCell<Box<RouteHandler<S>>>>);
impl<S: 'static> InnerHandler<S> {
#[inline]
fn new<H: Handler<S>>(h: H) -> Self {
InnerHandler(Rc::new(Box::new(WrapHandler::new(h))))
InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(h)))))
}
#[inline]
fn async<H, R, F, E>(h: H) -> Self
where
H: Fn(&HttpRequest<S>) -> F + 'static,
H: Fn(HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
{
InnerHandler(Rc::new(Box::new(AsyncHandler::new(h))))
InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(h)))))
}
#[inline]
pub fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
self.0.handle(req)
pub fn handle(&self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
// reason: handler is unique per thread, handler get called from async code only
let h = unsafe { &mut *self.0.as_ref().get() };
h.handle(req)
}
}
@@ -408,27 +412,23 @@ type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
impl<S: 'static> StartMiddlewares<S> {
fn init(info: &mut ComposeInfo<S>) -> ComposeState<S> {
let len = info.mws.len();
loop {
if info.count == len {
let reply = info.handler.handle(&info.req);
let reply = info.handler.handle(info.req.clone());
return WaitingResponse::init(info, reply);
} else {
let result = info.mws[info.count].start(&info.req);
match result {
match info.mws[info.count].start(&mut info.req) {
Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => {
return RunMiddlewares::init(info, resp);
return RunMiddlewares::init(info, resp)
}
Ok(MiddlewareStarted::Future(fut)) => {
return ComposeState::Starting(StartMiddlewares {
fut: Some(fut),
_s: PhantomData,
});
}
Err(err) => {
return RunMiddlewares::init(info, err.into());
})
}
Err(err) => return RunMiddlewares::init(info, err.into()),
}
}
}
@@ -436,12 +436,9 @@ impl<S: 'static> StartMiddlewares<S> {
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
let len = info.mws.len();
'outer: loop {
match self.fut.as_mut().unwrap().poll() {
Ok(Async::NotReady) => {
return None;
}
Ok(Async::NotReady) => return None,
Ok(Async::Ready(resp)) => {
info.count += 1;
if let Some(resp) = resp {
@@ -449,11 +446,10 @@ impl<S: 'static> StartMiddlewares<S> {
}
loop {
if info.count == len {
let reply = info.handler.handle(&info.req);
let reply = info.handler.handle(info.req.clone());
return Some(WaitingResponse::init(info, reply));
} else {
let result = info.mws[info.count].start(&info.req);
match result {
match info.mws[info.count].start(&mut info.req) {
Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => {
return Some(RunMiddlewares::init(info, resp));
@@ -463,25 +459,21 @@ impl<S: 'static> StartMiddlewares<S> {
continue 'outer;
}
Err(err) => {
return Some(RunMiddlewares::init(info, err.into()));
return Some(RunMiddlewares::init(info, err.into()))
}
}
}
}
}
Err(err) => {
return Some(RunMiddlewares::init(info, err.into()));
}
Err(err) => return Some(RunMiddlewares::init(info, err.into())),
}
}
}
}
type HandlerFuture = Future<Item = HttpResponse, Error = Error>;
// waiting for response
struct WaitingResponse<S> {
fut: Box<HandlerFuture>,
fut: Box<Future<Item = HttpResponse, Error = Error>>,
_s: PhantomData<S>,
}
@@ -491,8 +483,8 @@ impl<S: 'static> WaitingResponse<S> {
info: &mut ComposeInfo<S>, reply: AsyncResult<HttpResponse>,
) -> ComposeState<S> {
match reply.into() {
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse {
fut,
_s: PhantomData,
@@ -503,7 +495,7 @@ impl<S: 'static> WaitingResponse<S> {
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)),
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
Err(err) => Some(RunMiddlewares::init(info, err.into())),
}
}
@@ -522,8 +514,7 @@ impl<S: 'static> RunMiddlewares<S> {
let len = info.mws.len();
loop {
let state = info.mws[curr].response(&info.req, resp);
resp = match state {
resp = match info.mws[curr].response(&mut info.req, resp) {
Err(err) => {
info.count = curr + 1;
return FinishingMiddlewares::init(info, err.into());
@@ -541,7 +532,7 @@ impl<S: 'static> RunMiddlewares<S> {
curr,
fut: Some(fut),
_s: PhantomData,
});
})
}
};
}
@@ -565,8 +556,7 @@ impl<S: 'static> RunMiddlewares<S> {
if self.curr == len {
return Some(FinishingMiddlewares::init(info, resp));
} else {
let state = info.mws[self.curr].response(&info.req, resp);
match state {
match info.mws[self.curr].response(&mut info.req, resp) {
Err(err) => {
return Some(FinishingMiddlewares::init(info, err.into()))
}
@@ -634,10 +624,9 @@ impl<S: 'static> FinishingMiddlewares<S> {
}
info.count -= 1;
let state = info.mws[info.count as usize]
.finish(&info.req, self.resp.as_ref().unwrap());
match state {
match info.mws[info.count as usize]
.finish(&mut info.req, self.resp.as_ref().unwrap())
{
MiddlewareFinished::Done => {
if info.count == 0 {
return Some(Response::init(self.resp.take().unwrap()));

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::mem;
use std::rc::Rc;
@@ -14,9 +15,12 @@ use middleware::{
Started as MiddlewareStarted,
};
use pred::Predicate;
use resource::{DefaultResource, Resource};
use router::{ResourceDef, Router};
use server::Request;
use resource::ResourceHandler;
use router::Resource;
type Route<S> = UnsafeCell<Box<RouteHandler<S>>>;
type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>;
type NestedInfo<S> = (Resource, Route<S>, Vec<Box<Predicate<S>>>);
/// Resources scope
///
@@ -48,35 +52,27 @@ use server::Request;
/// * /{project_id}/path2 - `GET` requests
/// * /{project_id}/path3 - `HEAD` requests
///
pub struct Scope<S> {
rdef: ResourceDef,
router: Rc<Router<S>>,
#[derive(Default)]
pub struct Scope<S: 'static> {
filters: Vec<Box<Predicate<S>>>,
nested: Vec<NestedInfo<S>>,
middlewares: Rc<Vec<Box<Middleware<S>>>>,
default: Rc<UnsafeCell<ResourceHandler<S>>>,
resources: ScopeResources<S>,
}
#[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))]
impl<S: 'static> Scope<S> {
/// Create a new scope
// TODO: Why is this not exactly the default impl?
pub fn new(path: &str) -> Scope<S> {
pub fn new() -> Scope<S> {
Scope {
rdef: ResourceDef::prefix(path),
router: Rc::new(Router::new()),
filters: Vec::new(),
nested: Vec::new(),
resources: Rc::new(Vec::new()),
middlewares: Rc::new(Vec::new()),
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
}
}
#[inline]
pub(crate) fn rdef(&self) -> &ResourceDef {
&self.rdef
}
pub(crate) fn router(&self) -> &Router<S> {
self.router.as_ref()
}
#[inline]
pub(crate) fn take_filters(&mut self) -> Vec<Box<Predicate<S>>> {
mem::replace(&mut self.filters, Vec::new())
@@ -116,7 +112,7 @@ impl<S: 'static> Scope<S> {
///
/// struct AppState;
///
/// fn index(req: &HttpRequest<AppState>) -> &'static str {
/// fn index(req: HttpRequest<AppState>) -> &'static str {
/// "Welcome!"
/// }
///
@@ -133,10 +129,11 @@ impl<S: 'static> Scope<S> {
F: FnOnce(Scope<T>) -> Scope<T>,
{
let scope = Scope {
rdef: ResourceDef::prefix(path),
filters: Vec::new(),
router: Rc::new(Router::new()),
nested: Vec::new(),
resources: Rc::new(Vec::new()),
middlewares: Rc::new(Vec::new()),
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
};
let mut scope = f(scope);
@@ -145,13 +142,9 @@ impl<S: 'static> Scope<S> {
state: Rc::clone(&state),
filters: scope.take_filters(),
})];
let handler = Box::new(Wrapper { scope, state });
Rc::get_mut(&mut self.router).unwrap().register_handler(
path,
handler,
Some(filters),
);
let handler = UnsafeCell::new(Box::new(Wrapper { scope, state }));
self.nested
.push((Resource::prefix("", &path), handler, filters));
self
}
@@ -164,7 +157,7 @@ impl<S: 'static> Scope<S> {
///
/// struct AppState;
///
/// fn index(req: &HttpRequest<AppState>) -> &'static str {
/// fn index(req: HttpRequest<AppState>) -> &'static str {
/// "Welcome!"
/// }
///
@@ -179,14 +172,20 @@ impl<S: 'static> Scope<S> {
F: FnOnce(Scope<S>) -> Scope<S>,
{
let scope = Scope {
rdef: ResourceDef::prefix(&path),
filters: Vec::new(),
router: Rc::new(Router::new()),
nested: Vec::new(),
resources: Rc::new(Vec::new()),
middlewares: Rc::new(Vec::new()),
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
};
Rc::get_mut(&mut self.router)
.unwrap()
.register_scope(f(scope));
let mut scope = f(scope);
let filters = scope.take_filters();
self.nested.push((
Resource::prefix("", &path),
UnsafeCell::new(Box::new(scope)),
filters,
));
self
}
@@ -224,9 +223,28 @@ impl<S: 'static> Scope<S> {
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
Rc::get_mut(&mut self.router)
.unwrap()
.register_route(path, method, f);
// get resource handler
let slf: &Scope<S> = unsafe { &*(&self as *const _) };
for &(ref pattern, ref resource) in slf.resources.iter() {
if pattern.pattern() == path {
let resource = unsafe { &mut *resource.get() };
resource.method(method).with(f);
return self;
}
}
let mut handler = ResourceHandler::default();
handler.method(method).with(f);
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))));
self
}
@@ -255,36 +273,32 @@ impl<S: 'static> Scope<S> {
/// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> Scope<S>
where
F: FnOnce(&mut Resource<S>) -> R + 'static,
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
{
// add resource
let pattern = ResourceDef::with_prefix(
// add resource handler
let mut handler = ResourceHandler::default();
f(&mut handler);
let pattern = Resource::with_prefix(
handler.get_name(),
path,
if path.is_empty() { "" } else { "/" },
false,
);
let mut resource = Resource::new(pattern);
f(&mut resource);
Rc::get_mut(&mut self.resources)
.expect("Can not use after configuration")
.push((pattern, Rc::new(UnsafeCell::new(handler))));
Rc::get_mut(&mut self.router)
.unwrap()
.register_resource(resource);
self
}
/// Default resource to be used if no matching route could be found.
pub fn default_resource<F, R>(mut self, f: F) -> Scope<S>
pub fn default_resource<F, R>(self, f: F) -> Scope<S>
where
F: FnOnce(&mut Resource<S>) -> R + 'static,
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
{
// create and configure default resource
let mut resource = Resource::new(ResourceDef::new(""));
f(&mut resource);
Rc::get_mut(&mut self.router)
.expect("Multiple copies of scope router")
.register_default_resource(resource.into());
let default = unsafe { &mut *self.default.as_ref().get() };
f(default);
self
}
@@ -304,38 +318,68 @@ impl<S: 'static> Scope<S> {
}
impl<S: 'static> RouteHandler<S> for Scope<S> {
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
let tail = req.match_info().tail as usize;
fn handle(&mut self, mut req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
let path = unsafe { &*(&req.match_info()["tail"] as *const _) };
// recognize resources
let info = self.router.recognize(req, req.state(), tail);
let req2 = req.with_route_info(info);
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));
} else {
return AsyncResult::async(Box::new(Compose::new(
req,
Rc::clone(&self.middlewares),
Rc::clone(&resource),
Some(Rc::clone(&self.default)),
)));
}
}
}
// nested scopes
let len = req.prefix_len() as usize;
let path: &'static str = unsafe { &*(&req.path()[len..] as *const _) };
'outer: for &(ref prefix, ref handler, ref filters) in &self.nested {
if let Some(prefix_len) =
prefix.match_prefix_with_params(path, req.match_info_mut())
{
for filter in filters {
if !filter.check(&mut req) {
continue 'outer;
}
}
let prefix_len = len + prefix_len;
let path: &'static str =
unsafe { &*(&req.path()[prefix_len..] as *const _) };
req.set_prefix_len(prefix_len as u16);
req.match_info_mut().set("tail", path);
let hnd: &mut RouteHandler<_> =
unsafe { (&mut *(handler.get())).as_mut() };
return hnd.handle(req);
}
}
// default handler
let default = unsafe { &mut *self.default.as_ref().get() };
if self.middlewares.is_empty() {
self.router.handle(&req2)
default.handle(req, None)
} else {
AsyncResult::async(Box::new(Compose::new(
req2,
Rc::clone(&self.router),
req,
Rc::clone(&self.middlewares),
Rc::clone(&self.default),
None,
)))
}
}
fn has_default_resource(&self) -> bool {
self.router.has_default_resource()
}
fn default_resource(&mut self, default: DefaultResource<S>) {
Rc::get_mut(&mut self.router)
.expect("Can not use after configuration")
.register_default_resource(default);
}
fn finish(&mut self) {
Rc::get_mut(&mut self.router)
.expect("Can not use after configuration")
.finish();
}
}
struct Wrapper<S: 'static> {
@@ -344,9 +388,8 @@ struct Wrapper<S: 'static> {
}
impl<S: 'static, S2: 'static> RouteHandler<S2> for Wrapper<S> {
fn handle(&self, req: &HttpRequest<S2>) -> AsyncResult<HttpResponse> {
let req = req.with_state(Rc::clone(&self.state));
self.scope.handle(&req)
fn handle(&mut self, req: HttpRequest<S2>) -> AsyncResult<HttpResponse> {
self.scope.handle(req.change_state(Rc::clone(&self.state)))
}
}
@@ -356,9 +399,10 @@ struct FiltersWrapper<S: 'static> {
}
impl<S: 'static, S2: 'static> Predicate<S2> for FiltersWrapper<S> {
fn check(&self, req: &Request, _: &S2) -> bool {
fn check(&self, req: &mut HttpRequest<S2>) -> bool {
let mut req = req.change_state(Rc::clone(&self.state));
for filter in &self.filters {
if !filter.check(&req, &self.state) {
if !filter.check(&mut req) {
return false;
}
}
@@ -375,8 +419,9 @@ struct Compose<S: 'static> {
struct ComposeInfo<S: 'static> {
count: usize,
req: HttpRequest<S>,
router: Rc<Router<S>>,
mws: Rc<Vec<Box<Middleware<S>>>>,
default: Option<Rc<UnsafeCell<ResourceHandler<S>>>>,
resource: Rc<UnsafeCell<ResourceHandler<S>>>,
}
enum ComposeState<S: 'static> {
@@ -401,13 +446,16 @@ impl<S: 'static> ComposeState<S> {
impl<S: 'static> Compose<S> {
fn new(
req: HttpRequest<S>, router: Rc<Router<S>>, mws: Rc<Vec<Box<Middleware<S>>>>,
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
resource: Rc<UnsafeCell<ResourceHandler<S>>>,
default: Option<Rc<UnsafeCell<ResourceHandler<S>>>>,
) -> Self {
let mut info = ComposeInfo {
mws,
req,
router,
count: 0,
req,
mws,
resource,
default,
};
let state = StartMiddlewares::init(&mut info);
@@ -445,27 +493,29 @@ type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
impl<S: 'static> StartMiddlewares<S> {
fn init(info: &mut ComposeInfo<S>) -> ComposeState<S> {
let len = info.mws.len();
loop {
if info.count == len {
let reply = info.router.handle(&info.req);
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 WaitingResponse::init(info, reply);
} else {
let result = info.mws[info.count].start(&info.req);
match result {
match info.mws[info.count].start(&mut info.req) {
Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => {
return RunMiddlewares::init(info, resp);
return RunMiddlewares::init(info, resp)
}
Ok(MiddlewareStarted::Future(fut)) => {
return ComposeState::Starting(StartMiddlewares {
fut: Some(fut),
_s: PhantomData,
});
}
Err(err) => {
return RunMiddlewares::init(info, err.into());
})
}
Err(err) => return RunMiddlewares::init(info, err.into()),
}
}
}
@@ -473,25 +523,26 @@ impl<S: 'static> StartMiddlewares<S> {
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
let len = info.mws.len();
'outer: loop {
match self.fut.as_mut().unwrap().poll() {
Ok(Async::NotReady) => {
return None;
}
Ok(Async::NotReady) => return None,
Ok(Async::Ready(resp)) => {
info.count += 1;
if let Some(resp) = resp {
return Some(RunMiddlewares::init(info, resp));
}
loop {
if info.count == len {
let reply = info.router.handle(&info.req);
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 {
let result = info.mws[info.count].start(&info.req);
match result {
match info.mws[info.count].start(&mut info.req) {
Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => {
return Some(RunMiddlewares::init(info, resp));
@@ -501,15 +552,13 @@ impl<S: 'static> StartMiddlewares<S> {
continue 'outer;
}
Err(err) => {
return Some(RunMiddlewares::init(info, err.into()));
return Some(RunMiddlewares::init(info, err.into()))
}
}
}
}
}
Err(err) => {
return Some(RunMiddlewares::init(info, err.into()));
}
Err(err) => return Some(RunMiddlewares::init(info, err.into())),
}
}
}
@@ -539,7 +588,7 @@ impl<S: 'static> WaitingResponse<S> {
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)),
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
Err(err) => Some(RunMiddlewares::init(info, err.into())),
}
}
@@ -558,8 +607,7 @@ impl<S: 'static> RunMiddlewares<S> {
let len = info.mws.len();
loop {
let state = info.mws[curr].response(&info.req, resp);
resp = match state {
resp = match info.mws[curr].response(&mut info.req, resp) {
Err(err) => {
info.count = curr + 1;
return FinishingMiddlewares::init(info, err.into());
@@ -577,7 +625,7 @@ impl<S: 'static> RunMiddlewares<S> {
curr,
fut: Some(fut),
_s: PhantomData,
});
})
}
};
}
@@ -601,8 +649,7 @@ impl<S: 'static> RunMiddlewares<S> {
if self.curr == len {
return Some(FinishingMiddlewares::init(info, resp));
} else {
let state = info.mws[self.curr].response(&info.req, resp);
match state {
match info.mws[self.curr].response(&mut info.req, resp) {
Err(err) => {
return Some(FinishingMiddlewares::init(info, err.into()))
}
@@ -670,9 +717,9 @@ impl<S: 'static> FinishingMiddlewares<S> {
}
info.count -= 1;
let state = info.mws[info.count as usize]
.finish(&info.req, self.resp.as_ref().unwrap());
match state {
match info.mws[info.count as usize]
.finish(&mut info.req, self.resp.as_ref().unwrap())
{
MiddlewareFinished::Done => {
if info.count == 0 {
return Some(Response::init(self.resp.take().unwrap()));
@@ -714,20 +761,20 @@ mod tests {
#[test]
fn test_scope() {
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
})
.finish();
let req = TestRequest::with_uri("/app/path1").request();
let req = TestRequest::with_uri("/app/path1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
fn test_scope_root() {
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope
.resource("", |r| r.f(|_| HttpResponse::Ok()))
@@ -735,52 +782,52 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app").request();
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/").request();
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 app = App::new()
let mut app = App::new()
.scope("/app/", |scope| {
scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
})
.finish();
let req = TestRequest::with_uri("/app").request();
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/").request();
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 app = App::new()
let mut app = App::new()
.scope("/app/", |scope| {
scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
})
.finish();
let req = TestRequest::with_uri("/app").request();
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/").request();
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 app = App::new()
let mut app = App::new()
.scope("app", |scope| {
scope
.route("/path1", Method::GET, |_: HttpRequest<_>| {
@@ -792,26 +839,26 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app/path1").request();
let req = TestRequest::with_uri("/app/path1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/path1")
.method(Method::DELETE)
.request();
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/path1")
.method(Method::POST)
.request();
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_scope_filter() {
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope
.filter(pred::Get())
@@ -821,20 +868,20 @@ mod tests {
let req = TestRequest::with_uri("/app/path1")
.method(Method::POST)
.request();
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/path1")
.method(Method::GET)
.request();
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
fn test_scope_variable_segment() {
let app = App::new()
let mut app = App::new()
.scope("/ab-{project}", |scope| {
scope.resource("/path1", |r| {
r.f(|r| {
@@ -845,7 +892,7 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/ab-project1/path1").request();
let req = TestRequest::with_uri("/ab-project1/path1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
@@ -857,7 +904,7 @@ mod tests {
_ => panic!(),
}
let req = TestRequest::with_uri("/aa-project1/path1").request();
let req = TestRequest::with_uri("/aa-project1/path1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
@@ -866,7 +913,7 @@ mod tests {
fn test_scope_with_state() {
struct State;
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1", State, |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
@@ -874,7 +921,7 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app/t1/path1").request();
let req = TestRequest::with_uri("/app/t1/path1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
}
@@ -883,7 +930,7 @@ mod tests {
fn test_scope_with_state_root() {
struct State;
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1", State, |scope| {
scope
@@ -893,11 +940,11 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app/t1").request();
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/").request();
let req = TestRequest::with_uri("/app/t1/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
}
@@ -906,7 +953,7 @@ mod tests {
fn test_scope_with_state_root2() {
struct State;
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1/", State, |scope| {
scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
@@ -914,11 +961,11 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app/t1").request();
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/").request();
let req = TestRequest::with_uri("/app/t1/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
@@ -927,7 +974,7 @@ mod tests {
fn test_scope_with_state_root3() {
struct State;
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1/", State, |scope| {
scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
@@ -935,11 +982,11 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app/t1").request();
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/").request();
let req = TestRequest::with_uri("/app/t1/").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
@@ -948,7 +995,7 @@ mod tests {
fn test_scope_with_state_filter() {
struct State;
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1", State, |scope| {
scope
@@ -960,20 +1007,20 @@ mod tests {
let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::POST)
.request();
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::GET)
.request();
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
fn test_nested_scope() {
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.nested("/t1", |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
@@ -981,14 +1028,14 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app/t1/path1").request();
let req = TestRequest::with_uri("/app/t1/path1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
}
#[test]
fn test_nested_scope_root() {
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.nested("/t1", |scope| {
scope
@@ -998,18 +1045,18 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app/t1").request();
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/").request();
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 app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.nested("/t1", |scope| {
scope
@@ -1021,20 +1068,20 @@ mod tests {
let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::POST)
.request();
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::GET)
.request();
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
fn test_nested_scope_with_variable_segment() {
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.nested("/{project_id}", |scope| {
scope.resource("/path1", |r| {
@@ -1049,7 +1096,7 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app/project_1/path1").request();
let req = TestRequest::with_uri("/app/project_1/path1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
@@ -1064,7 +1111,7 @@ mod tests {
#[test]
fn test_nested2_scope_with_variable_segment() {
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope.nested("/{project}", |scope| {
scope.nested("/{id}", |scope| {
@@ -1082,7 +1129,7 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app/test/1/path1").request();
let req = TestRequest::with_uri("/app/test/1/path1").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
@@ -1094,14 +1141,14 @@ mod tests {
_ => panic!(),
}
let req = TestRequest::with_uri("/app/test/1/path2").request();
let req = TestRequest::with_uri("/app/test/1/path2").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_default_resource() {
let app = App::new()
let mut app = App::new()
.scope("/app", |scope| {
scope
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
@@ -1109,35 +1156,12 @@ mod tests {
})
.finish();
let req = TestRequest::with_uri("/app/path2").request();
let req = TestRequest::with_uri("/app/path2").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/path2").request();
let req = TestRequest::with_uri("/path2").finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_default_resource_propagation() {
let app = App::new()
.scope("/app1", |scope| {
scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest()))
})
.scope("/app2", |scope| scope)
.default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed()))
.finish();
let req = TestRequest::with_uri("/non-exist").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
let req = TestRequest::with_uri("/app1/non-exist").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/app2/non-exist").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
}
}

View File

@@ -1,305 +0,0 @@
//! Deserialization support for the `application/x-www-form-urlencoded` format.
use serde::de::Error as de_Error;
use serde::de::{
self, DeserializeSeed, EnumAccess, IntoDeserializer, VariantAccess, Visitor,
};
use serde::de::value::MapDeserializer;
use std::borrow::Cow;
use std::io::Read;
use url::form_urlencoded::parse;
use url::form_urlencoded::Parse as UrlEncodedParse;
#[doc(inline)]
pub use serde::de::value::Error;
/// Deserializes a `application/x-wwww-url-encoded` value from a `&[u8]`.
///
/// ```ignore
/// let meal = vec![
/// ("bread".to_owned(), "baguette".to_owned()),
/// ("cheese".to_owned(), "comté".to_owned()),
/// ("meat".to_owned(), "ham".to_owned()),
/// ("fat".to_owned(), "butter".to_owned()),
/// ];
///
/// assert_eq!(
/// serde_urlencoded::from_bytes::<Vec<(String, String)>>(
/// b"bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter"),
/// Ok(meal));
/// ```
pub fn from_bytes<'de, T>(input: &'de [u8]) -> Result<T, Error>
where
T: de::Deserialize<'de>,
{
T::deserialize(Deserializer::new(parse(input)))
}
/// Deserializes a `application/x-wwww-url-encoded` value from a `&str`.
///
/// ```ignore
/// let meal = vec![
/// ("bread".to_owned(), "baguette".to_owned()),
/// ("cheese".to_owned(), "comté".to_owned()),
/// ("meat".to_owned(), "ham".to_owned()),
/// ("fat".to_owned(), "butter".to_owned()),
/// ];
///
/// assert_eq!(
/// serde_urlencoded::from_str::<Vec<(String, String)>>(
/// "bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter"),
/// Ok(meal));
/// ```
pub fn from_str<'de, T>(input: &'de str) -> Result<T, Error>
where
T: de::Deserialize<'de>,
{
from_bytes(input.as_bytes())
}
#[allow(dead_code)]
/// Convenience function that reads all bytes from `reader` and deserializes
/// them with `from_bytes`.
pub fn from_reader<T, R>(mut reader: R) -> Result<T, Error>
where
T: de::DeserializeOwned,
R: Read,
{
let mut buf = vec![];
reader
.read_to_end(&mut buf)
.map_err(|e| de::Error::custom(format_args!("could not read input: {}", e)))?;
from_bytes(&buf)
}
/// A deserializer for the `application/x-www-form-urlencoded` format.
///
/// * Supported top-level outputs are structs, maps and sequences of pairs,
/// with or without a given length.
///
/// * Main `deserialize` methods defers to `deserialize_map`.
///
/// * Everything else but `deserialize_seq` and `deserialize_seq_fixed_size`
/// defers to `deserialize`.
pub struct Deserializer<'de> {
inner: MapDeserializer<'de, PartIterator<'de>, Error>,
}
impl<'de> Deserializer<'de> {
/// Returns a new `Deserializer`.
pub fn new(parser: UrlEncodedParse<'de>) -> Self {
Deserializer {
inner: MapDeserializer::new(PartIterator(parser)),
}
}
}
impl<'de> de::Deserializer<'de> for Deserializer<'de> {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
self.deserialize_map(visitor)
}
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_map(self.inner)
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_seq(self.inner)
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
self.inner.end()?;
visitor.visit_unit()
}
forward_to_deserialize_any! {
bool
u8
u16
u32
u64
i8
i16
i32
i64
f32
f64
char
str
string
option
bytes
byte_buf
unit_struct
newtype_struct
tuple_struct
struct
identifier
tuple
enum
ignored_any
}
}
struct PartIterator<'de>(UrlEncodedParse<'de>);
impl<'de> Iterator for PartIterator<'de> {
type Item = (Part<'de>, Part<'de>);
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|(k, v)| (Part(k), Part(v)))
}
}
struct Part<'de>(Cow<'de, str>);
impl<'de> IntoDeserializer<'de> for Part<'de> {
type Deserializer = Self;
fn into_deserializer(self) -> Self::Deserializer {
self
}
}
macro_rules! forward_parsed_value {
($($ty:ident => $method:ident,)*) => {
$(
fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: de::Visitor<'de>
{
match self.0.parse::<$ty>() {
Ok(val) => val.into_deserializer().$method(visitor),
Err(e) => Err(de::Error::custom(e))
}
}
)*
}
}
impl<'de> de::Deserializer<'de> for Part<'de> {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
self.0.into_deserializer().deserialize_any(visitor)
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_some(self)
}
fn deserialize_enum<V>(
self, _name: &'static str, _variants: &'static [&'static str], visitor: V,
) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_enum(ValueEnumAccess { value: self.0 })
}
forward_to_deserialize_any! {
char
str
string
unit
bytes
byte_buf
unit_struct
newtype_struct
tuple_struct
struct
identifier
tuple
ignored_any
seq
map
}
forward_parsed_value! {
bool => deserialize_bool,
u8 => deserialize_u8,
u16 => deserialize_u16,
u32 => deserialize_u32,
u64 => deserialize_u64,
i8 => deserialize_i8,
i16 => deserialize_i16,
i32 => deserialize_i32,
i64 => deserialize_i64,
f32 => deserialize_f32,
f64 => deserialize_f64,
}
}
/// Provides access to a keyword which can be deserialized into an enum variant. The enum variant
/// must be a unit variant, otherwise deserialization will fail.
struct ValueEnumAccess<'de> {
value: Cow<'de, str>,
}
impl<'de> EnumAccess<'de> for ValueEnumAccess<'de> {
type Error = Error;
type Variant = UnitOnlyVariantAccess;
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
where
V: DeserializeSeed<'de>,
{
let variant = seed.deserialize(self.value.into_deserializer())?;
Ok((variant, UnitOnlyVariantAccess))
}
}
/// A visitor for deserializing the contents of the enum variant. As we only support
/// `unit_variant`, all other variant types will return an error.
struct UnitOnlyVariantAccess;
impl<'de> VariantAccess<'de> for UnitOnlyVariantAccess {
type Error = Error;
fn unit_variant(self) -> Result<(), Self::Error> {
Ok(())
}
fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value, Self::Error>
where
T: DeserializeSeed<'de>,
{
Err(Error::custom("expected unit variant"))
}
fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(Error::custom("expected unit variant"))
}
fn struct_variant<V>(
self, _fields: &'static [&'static str], _visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(Error::custom("expected unit variant"))
}
}

View File

@@ -1,121 +0,0 @@
//! `x-www-form-urlencoded` meets Serde
extern crate dtoa;
extern crate itoa;
pub mod de;
pub mod ser;
#[doc(inline)]
pub use self::de::{from_bytes, from_reader, from_str, Deserializer};
#[doc(inline)]
pub use self::ser::{to_string, Serializer};
#[cfg(test)]
mod tests {
#[test]
fn deserialize_bytes() {
let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)];
assert_eq!(super::from_bytes(b"first=23&last=42"), Ok(result));
}
#[test]
fn deserialize_str() {
let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)];
assert_eq!(super::from_str("first=23&last=42"), Ok(result));
}
#[test]
fn deserialize_reader() {
let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)];
assert_eq!(super::from_reader(b"first=23&last=42" as &[_]), Ok(result));
}
#[test]
fn deserialize_option() {
let result = vec![
("first".to_owned(), Some(23)),
("last".to_owned(), Some(42)),
];
assert_eq!(super::from_str("first=23&last=42"), Ok(result));
}
#[test]
fn deserialize_unit() {
assert_eq!(super::from_str(""), Ok(()));
assert_eq!(super::from_str("&"), Ok(()));
assert_eq!(super::from_str("&&"), Ok(()));
assert!(super::from_str::<()>("first=23").is_err());
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
enum X {
A,
B,
C,
}
#[test]
fn deserialize_unit_enum() {
let result = vec![
("one".to_owned(), X::A),
("two".to_owned(), X::B),
("three".to_owned(), X::C),
];
assert_eq!(super::from_str("one=A&two=B&three=C"), Ok(result));
}
#[test]
fn serialize_option_map_int() {
let params = &[("first", Some(23)), ("middle", None), ("last", Some(42))];
assert_eq!(super::to_string(params), Ok("first=23&last=42".to_owned()));
}
#[test]
fn serialize_option_map_string() {
let params = &[
("first", Some("hello")),
("middle", None),
("last", Some("world")),
];
assert_eq!(
super::to_string(params),
Ok("first=hello&last=world".to_owned())
);
}
#[test]
fn serialize_option_map_bool() {
let params = &[("one", Some(true)), ("two", Some(false))];
assert_eq!(
super::to_string(params),
Ok("one=true&two=false".to_owned())
);
}
#[test]
fn serialize_map_bool() {
let params = &[("one", true), ("two", false)];
assert_eq!(
super::to_string(params),
Ok("one=true&two=false".to_owned())
);
}
#[test]
fn serialize_unit_enum() {
let params = &[("one", X::A), ("two", X::B), ("three", X::C)];
assert_eq!(
super::to_string(params),
Ok("one=A&two=B&three=C".to_owned())
);
}
}

View File

@@ -1,74 +0,0 @@
use super::super::ser::part::Sink;
use super::super::ser::Error;
use serde::Serialize;
use std::borrow::Cow;
use std::ops::Deref;
pub enum Key<'key> {
Static(&'static str),
Dynamic(Cow<'key, str>),
}
impl<'key> Deref for Key<'key> {
type Target = str;
fn deref(&self) -> &str {
match *self {
Key::Static(key) => key,
Key::Dynamic(ref key) => key,
}
}
}
impl<'key> From<Key<'key>> for Cow<'static, str> {
fn from(key: Key<'key>) -> Self {
match key {
Key::Static(key) => key.into(),
Key::Dynamic(key) => key.into_owned().into(),
}
}
}
pub struct KeySink<End> {
end: End,
}
impl<End, Ok> KeySink<End>
where
End: for<'key> FnOnce(Key<'key>) -> Result<Ok, Error>,
{
pub fn new(end: End) -> Self {
KeySink { end }
}
}
impl<End, Ok> Sink for KeySink<End>
where
End: for<'key> FnOnce(Key<'key>) -> Result<Ok, Error>,
{
type Ok = Ok;
fn serialize_static_str(self, value: &'static str) -> Result<Ok, Error> {
(self.end)(Key::Static(value))
}
fn serialize_str(self, value: &str) -> Result<Ok, Error> {
(self.end)(Key::Dynamic(value.into()))
}
fn serialize_string(self, value: String) -> Result<Ok, Error> {
(self.end)(Key::Dynamic(value.into()))
}
fn serialize_none(self) -> Result<Ok, Error> {
Err(self.unsupported())
}
fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<Ok, Error> {
Err(self.unsupported())
}
fn unsupported(self) -> Error {
Error::Custom("unsupported key".into())
}
}

View File

@@ -1,490 +0,0 @@
//! Serialization support for the `application/x-www-form-urlencoded` format.
mod key;
mod pair;
mod part;
mod value;
use serde::ser;
use std::borrow::Cow;
use std::error;
use std::fmt;
use std::str;
use url::form_urlencoded::Serializer as UrlEncodedSerializer;
use url::form_urlencoded::Target as UrlEncodedTarget;
/// Serializes a value into a `application/x-wwww-url-encoded` `String` buffer.
///
/// ```ignore
/// let meal = &[
/// ("bread", "baguette"),
/// ("cheese", "comté"),
/// ("meat", "ham"),
/// ("fat", "butter"),
/// ];
///
/// assert_eq!(
/// serde_urlencoded::to_string(meal),
/// Ok("bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter".to_owned()));
/// ```
pub fn to_string<T: ser::Serialize>(input: T) -> Result<String, Error> {
let mut urlencoder = UrlEncodedSerializer::new("".to_owned());
input.serialize(Serializer::new(&mut urlencoder))?;
Ok(urlencoder.finish())
}
/// A serializer for the `application/x-www-form-urlencoded` format.
///
/// * Supported top-level inputs are structs, maps and sequences of pairs,
/// with or without a given length.
///
/// * Supported keys and values are integers, bytes (if convertible to strings),
/// unit structs and unit variants.
///
/// * Newtype structs defer to their inner values.
pub struct Serializer<'output, Target: 'output + UrlEncodedTarget> {
urlencoder: &'output mut UrlEncodedSerializer<Target>,
}
impl<'output, Target: 'output + UrlEncodedTarget> Serializer<'output, Target> {
/// Returns a new `Serializer`.
pub fn new(urlencoder: &'output mut UrlEncodedSerializer<Target>) -> Self {
Serializer { urlencoder }
}
}
/// Errors returned during serializing to `application/x-www-form-urlencoded`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Error {
Custom(Cow<'static, str>),
Utf8(str::Utf8Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Custom(ref msg) => msg.fmt(f),
Error::Utf8(ref err) => write!(f, "invalid UTF-8: {}", err),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::Custom(ref msg) => msg,
Error::Utf8(ref err) => error::Error::description(err),
}
}
/// The lower-level cause of this error, in the case of a `Utf8` error.
fn cause(&self) -> Option<&error::Error> {
match *self {
Error::Custom(_) => None,
Error::Utf8(ref err) => Some(err),
}
}
}
impl ser::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Error::Custom(format!("{}", msg).into())
}
}
/// Sequence serializer.
pub struct SeqSerializer<'output, Target: 'output + UrlEncodedTarget> {
urlencoder: &'output mut UrlEncodedSerializer<Target>,
}
/// Tuple serializer.
///
/// Mostly used for arrays.
pub struct TupleSerializer<'output, Target: 'output + UrlEncodedTarget> {
urlencoder: &'output mut UrlEncodedSerializer<Target>,
}
/// Tuple struct serializer.
///
/// Never instantiated, tuple structs are not supported.
pub struct TupleStructSerializer<'output, T: 'output + UrlEncodedTarget> {
inner: ser::Impossible<&'output mut UrlEncodedSerializer<T>, Error>,
}
/// Tuple variant serializer.
///
/// Never instantiated, tuple variants are not supported.
pub struct TupleVariantSerializer<'output, T: 'output + UrlEncodedTarget> {
inner: ser::Impossible<&'output mut UrlEncodedSerializer<T>, Error>,
}
/// Map serializer.
pub struct MapSerializer<'output, Target: 'output + UrlEncodedTarget> {
urlencoder: &'output mut UrlEncodedSerializer<Target>,
key: Option<Cow<'static, str>>,
}
/// Struct serializer.
pub struct StructSerializer<'output, Target: 'output + UrlEncodedTarget> {
urlencoder: &'output mut UrlEncodedSerializer<Target>,
}
/// Struct variant serializer.
///
/// Never instantiated, struct variants are not supported.
pub struct StructVariantSerializer<'output, T: 'output + UrlEncodedTarget> {
inner: ser::Impossible<&'output mut UrlEncodedSerializer<T>, Error>,
}
impl<'output, Target> ser::Serializer for Serializer<'output, Target>
where
Target: 'output + UrlEncodedTarget,
{
type Ok = &'output mut UrlEncodedSerializer<Target>;
type Error = Error;
type SerializeSeq = SeqSerializer<'output, Target>;
type SerializeTuple = TupleSerializer<'output, Target>;
type SerializeTupleStruct = TupleStructSerializer<'output, Target>;
type SerializeTupleVariant = TupleVariantSerializer<'output, Target>;
type SerializeMap = MapSerializer<'output, Target>;
type SerializeStruct = StructSerializer<'output, Target>;
type SerializeStructVariant = StructVariantSerializer<'output, Target>;
/// Returns an error.
fn serialize_bool(self, _v: bool) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_i8(self, _v: i8) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_i16(self, _v: i16) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_i32(self, _v: i32) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_i64(self, _v: i64) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_u8(self, _v: u8) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_u16(self, _v: u16) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_u32(self, _v: u32) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_u64(self, _v: u64) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_f32(self, _v: f32) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_f64(self, _v: f64) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_char(self, _v: char) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_str(self, _value: &str) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_bytes(self, _value: &[u8]) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_unit(self) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_unit_variant(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Serializes the inner value, ignoring the newtype name.
fn serialize_newtype_struct<T: ?Sized + ser::Serialize>(
self, _name: &'static str, value: &T,
) -> Result<Self::Ok, Error> {
value.serialize(self)
}
/// Returns an error.
fn serialize_newtype_variant<T: ?Sized + ser::Serialize>(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
_value: &T,
) -> Result<Self::Ok, Error> {
Err(Error::top_level())
}
/// Returns `Ok`.
fn serialize_none(self) -> Result<Self::Ok, Error> {
Ok(self.urlencoder)
}
/// Serializes the given value.
fn serialize_some<T: ?Sized + ser::Serialize>(
self, value: &T,
) -> Result<Self::Ok, Error> {
value.serialize(self)
}
/// Serialize a sequence, given length (if any) is ignored.
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Error> {
Ok(SeqSerializer {
urlencoder: self.urlencoder,
})
}
/// Returns an error.
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Error> {
Ok(TupleSerializer {
urlencoder: self.urlencoder,
})
}
/// Returns an error.
fn serialize_tuple_struct(
self, _name: &'static str, _len: usize,
) -> Result<Self::SerializeTupleStruct, Error> {
Err(Error::top_level())
}
/// Returns an error.
fn serialize_tuple_variant(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Error> {
Err(Error::top_level())
}
/// Serializes a map, given length is ignored.
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Error> {
Ok(MapSerializer {
urlencoder: self.urlencoder,
key: None,
})
}
/// Serializes a struct, given length is ignored.
fn serialize_struct(
self, _name: &'static str, _len: usize,
) -> Result<Self::SerializeStruct, Error> {
Ok(StructSerializer {
urlencoder: self.urlencoder,
})
}
/// Returns an error.
fn serialize_struct_variant(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant, Error> {
Err(Error::top_level())
}
}
impl<'output, Target> ser::SerializeSeq for SeqSerializer<'output, Target>
where
Target: 'output + UrlEncodedTarget,
{
type Ok = &'output mut UrlEncodedSerializer<Target>;
type Error = Error;
fn serialize_element<T: ?Sized + ser::Serialize>(
&mut self, value: &T,
) -> Result<(), Error> {
value.serialize(pair::PairSerializer::new(self.urlencoder))
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(self.urlencoder)
}
}
impl<'output, Target> ser::SerializeTuple for TupleSerializer<'output, Target>
where
Target: 'output + UrlEncodedTarget,
{
type Ok = &'output mut UrlEncodedSerializer<Target>;
type Error = Error;
fn serialize_element<T: ?Sized + ser::Serialize>(
&mut self, value: &T,
) -> Result<(), Error> {
value.serialize(pair::PairSerializer::new(self.urlencoder))
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(self.urlencoder)
}
}
impl<'output, Target> ser::SerializeTupleStruct
for TupleStructSerializer<'output, Target>
where
Target: 'output + UrlEncodedTarget,
{
type Ok = &'output mut UrlEncodedSerializer<Target>;
type Error = Error;
fn serialize_field<T: ?Sized + ser::Serialize>(
&mut self, value: &T,
) -> Result<(), Error> {
self.inner.serialize_field(value)
}
fn end(self) -> Result<Self::Ok, Error> {
self.inner.end()
}
}
impl<'output, Target> ser::SerializeTupleVariant
for TupleVariantSerializer<'output, Target>
where
Target: 'output + UrlEncodedTarget,
{
type Ok = &'output mut UrlEncodedSerializer<Target>;
type Error = Error;
fn serialize_field<T: ?Sized + ser::Serialize>(
&mut self, value: &T,
) -> Result<(), Error> {
self.inner.serialize_field(value)
}
fn end(self) -> Result<Self::Ok, Error> {
self.inner.end()
}
}
impl<'output, Target> ser::SerializeMap for MapSerializer<'output, Target>
where
Target: 'output + UrlEncodedTarget,
{
type Ok = &'output mut UrlEncodedSerializer<Target>;
type Error = Error;
fn serialize_entry<K: ?Sized + ser::Serialize, V: ?Sized + ser::Serialize>(
&mut self, key: &K, value: &V,
) -> Result<(), Error> {
let key_sink = key::KeySink::new(|key| {
let value_sink = value::ValueSink::new(self.urlencoder, &key);
value.serialize(part::PartSerializer::new(value_sink))?;
self.key = None;
Ok(())
});
let entry_serializer = part::PartSerializer::new(key_sink);
key.serialize(entry_serializer)
}
fn serialize_key<T: ?Sized + ser::Serialize>(
&mut self, key: &T,
) -> Result<(), Error> {
let key_sink = key::KeySink::new(|key| Ok(key.into()));
let key_serializer = part::PartSerializer::new(key_sink);
self.key = Some(key.serialize(key_serializer)?);
Ok(())
}
fn serialize_value<T: ?Sized + ser::Serialize>(
&mut self, value: &T,
) -> Result<(), Error> {
{
let key = self.key.as_ref().ok_or_else(Error::no_key)?;
let value_sink = value::ValueSink::new(self.urlencoder, &key);
value.serialize(part::PartSerializer::new(value_sink))?;
}
self.key = None;
Ok(())
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(self.urlencoder)
}
}
impl<'output, Target> ser::SerializeStruct for StructSerializer<'output, Target>
where
Target: 'output + UrlEncodedTarget,
{
type Ok = &'output mut UrlEncodedSerializer<Target>;
type Error = Error;
fn serialize_field<T: ?Sized + ser::Serialize>(
&mut self, key: &'static str, value: &T,
) -> Result<(), Error> {
let value_sink = value::ValueSink::new(self.urlencoder, key);
value.serialize(part::PartSerializer::new(value_sink))
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(self.urlencoder)
}
}
impl<'output, Target> ser::SerializeStructVariant
for StructVariantSerializer<'output, Target>
where
Target: 'output + UrlEncodedTarget,
{
type Ok = &'output mut UrlEncodedSerializer<Target>;
type Error = Error;
fn serialize_field<T: ?Sized + ser::Serialize>(
&mut self, key: &'static str, value: &T,
) -> Result<(), Error> {
self.inner.serialize_field(key, value)
}
fn end(self) -> Result<Self::Ok, Error> {
self.inner.end()
}
}
impl Error {
fn top_level() -> Self {
let msg = "top-level serializer supports only maps and structs";
Error::Custom(msg.into())
}
fn no_key() -> Self {
let msg = "tried to serialize a value before serializing key";
Error::Custom(msg.into())
}
}

View File

@@ -1,239 +0,0 @@
use super::super::ser::key::KeySink;
use super::super::ser::part::PartSerializer;
use super::super::ser::value::ValueSink;
use super::super::ser::Error;
use serde::ser;
use std::borrow::Cow;
use std::mem;
use url::form_urlencoded::Serializer as UrlEncodedSerializer;
use url::form_urlencoded::Target as UrlEncodedTarget;
pub struct PairSerializer<'target, Target: 'target + UrlEncodedTarget> {
urlencoder: &'target mut UrlEncodedSerializer<Target>,
state: PairState,
}
impl<'target, Target> PairSerializer<'target, Target>
where
Target: 'target + UrlEncodedTarget,
{
pub fn new(urlencoder: &'target mut UrlEncodedSerializer<Target>) -> Self {
PairSerializer {
urlencoder,
state: PairState::WaitingForKey,
}
}
}
impl<'target, Target> ser::Serializer for PairSerializer<'target, Target>
where
Target: 'target + UrlEncodedTarget,
{
type Ok = ();
type Error = Error;
type SerializeSeq = ser::Impossible<(), Error>;
type SerializeTuple = Self;
type SerializeTupleStruct = ser::Impossible<(), Error>;
type SerializeTupleVariant = ser::Impossible<(), Error>;
type SerializeMap = ser::Impossible<(), Error>;
type SerializeStruct = ser::Impossible<(), Error>;
type SerializeStructVariant = ser::Impossible<(), Error>;
fn serialize_bool(self, _v: bool) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_i8(self, _v: i8) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_i16(self, _v: i16) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_i32(self, _v: i32) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_i64(self, _v: i64) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_u8(self, _v: u8) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_u16(self, _v: u16) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_u32(self, _v: u32) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_u64(self, _v: u64) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_f32(self, _v: f32) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_f64(self, _v: f64) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_char(self, _v: char) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_str(self, _value: &str) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_bytes(self, _value: &[u8]) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_unit(self) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_unit_variant(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_newtype_struct<T: ?Sized + ser::Serialize>(
self, _name: &'static str, value: &T,
) -> Result<(), Error> {
value.serialize(self)
}
fn serialize_newtype_variant<T: ?Sized + ser::Serialize>(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
_value: &T,
) -> Result<(), Error> {
Err(Error::unsupported_pair())
}
fn serialize_none(self) -> Result<(), Error> {
Ok(())
}
fn serialize_some<T: ?Sized + ser::Serialize>(self, value: &T) -> Result<(), Error> {
value.serialize(self)
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Error> {
Err(Error::unsupported_pair())
}
fn serialize_tuple(self, len: usize) -> Result<Self, Error> {
if len == 2 {
Ok(self)
} else {
Err(Error::unsupported_pair())
}
}
fn serialize_tuple_struct(
self, _name: &'static str, _len: usize,
) -> Result<Self::SerializeTupleStruct, Error> {
Err(Error::unsupported_pair())
}
fn serialize_tuple_variant(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Error> {
Err(Error::unsupported_pair())
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Error> {
Err(Error::unsupported_pair())
}
fn serialize_struct(
self, _name: &'static str, _len: usize,
) -> Result<Self::SerializeStruct, Error> {
Err(Error::unsupported_pair())
}
fn serialize_struct_variant(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant, Error> {
Err(Error::unsupported_pair())
}
}
impl<'target, Target> ser::SerializeTuple for PairSerializer<'target, Target>
where
Target: 'target + UrlEncodedTarget,
{
type Ok = ();
type Error = Error;
fn serialize_element<T: ?Sized + ser::Serialize>(
&mut self, value: &T,
) -> Result<(), Error> {
match mem::replace(&mut self.state, PairState::Done) {
PairState::WaitingForKey => {
let key_sink = KeySink::new(|key| Ok(key.into()));
let key_serializer = PartSerializer::new(key_sink);
self.state = PairState::WaitingForValue {
key: value.serialize(key_serializer)?,
};
Ok(())
}
PairState::WaitingForValue { key } => {
let result = {
let value_sink = ValueSink::new(self.urlencoder, &key);
let value_serializer = PartSerializer::new(value_sink);
value.serialize(value_serializer)
};
if result.is_ok() {
self.state = PairState::Done;
} else {
self.state = PairState::WaitingForValue { key };
}
result
}
PairState::Done => Err(Error::done()),
}
}
fn end(self) -> Result<(), Error> {
if let PairState::Done = self.state {
Ok(())
} else {
Err(Error::not_done())
}
}
}
enum PairState {
WaitingForKey,
WaitingForValue { key: Cow<'static, str> },
Done,
}
impl Error {
fn done() -> Self {
Error::Custom("this pair has already been serialized".into())
}
fn not_done() -> Self {
Error::Custom("this pair has not yet been serialized".into())
}
fn unsupported_pair() -> Self {
Error::Custom("unsupported pair".into())
}
}

View File

@@ -1,201 +0,0 @@
use serde;
use super::super::dtoa;
use super::super::itoa;
use super::super::ser::Error;
use std::str;
pub struct PartSerializer<S> {
sink: S,
}
impl<S: Sink> PartSerializer<S> {
pub fn new(sink: S) -> Self {
PartSerializer { sink }
}
}
pub trait Sink: Sized {
type Ok;
fn serialize_static_str(self, value: &'static str) -> Result<Self::Ok, Error>;
fn serialize_str(self, value: &str) -> Result<Self::Ok, Error>;
fn serialize_string(self, value: String) -> Result<Self::Ok, Error>;
fn serialize_none(self) -> Result<Self::Ok, Error>;
fn serialize_some<T: ?Sized + serde::ser::Serialize>(
self, value: &T,
) -> Result<Self::Ok, Error>;
fn unsupported(self) -> Error;
}
impl<S: Sink> serde::ser::Serializer for PartSerializer<S> {
type Ok = S::Ok;
type Error = Error;
type SerializeSeq = serde::ser::Impossible<S::Ok, Error>;
type SerializeTuple = serde::ser::Impossible<S::Ok, Error>;
type SerializeTupleStruct = serde::ser::Impossible<S::Ok, Error>;
type SerializeTupleVariant = serde::ser::Impossible<S::Ok, Error>;
type SerializeMap = serde::ser::Impossible<S::Ok, Error>;
type SerializeStruct = serde::ser::Impossible<S::Ok, Error>;
type SerializeStructVariant = serde::ser::Impossible<S::Ok, Error>;
fn serialize_bool(self, v: bool) -> Result<S::Ok, Error> {
self.sink
.serialize_static_str(if v { "true" } else { "false" })
}
fn serialize_i8(self, v: i8) -> Result<S::Ok, Error> {
self.serialize_integer(v)
}
fn serialize_i16(self, v: i16) -> Result<S::Ok, Error> {
self.serialize_integer(v)
}
fn serialize_i32(self, v: i32) -> Result<S::Ok, Error> {
self.serialize_integer(v)
}
fn serialize_i64(self, v: i64) -> Result<S::Ok, Error> {
self.serialize_integer(v)
}
fn serialize_u8(self, v: u8) -> Result<S::Ok, Error> {
self.serialize_integer(v)
}
fn serialize_u16(self, v: u16) -> Result<S::Ok, Error> {
self.serialize_integer(v)
}
fn serialize_u32(self, v: u32) -> Result<S::Ok, Error> {
self.serialize_integer(v)
}
fn serialize_u64(self, v: u64) -> Result<S::Ok, Error> {
self.serialize_integer(v)
}
fn serialize_f32(self, v: f32) -> Result<S::Ok, Error> {
self.serialize_floating(v)
}
fn serialize_f64(self, v: f64) -> Result<S::Ok, Error> {
self.serialize_floating(v)
}
fn serialize_char(self, v: char) -> Result<S::Ok, Error> {
self.sink.serialize_string(v.to_string())
}
fn serialize_str(self, value: &str) -> Result<S::Ok, Error> {
self.sink.serialize_str(value)
}
fn serialize_bytes(self, value: &[u8]) -> Result<S::Ok, Error> {
match str::from_utf8(value) {
Ok(value) => self.sink.serialize_str(value),
Err(err) => Err(Error::Utf8(err)),
}
}
fn serialize_unit(self) -> Result<S::Ok, Error> {
Err(self.sink.unsupported())
}
fn serialize_unit_struct(self, name: &'static str) -> Result<S::Ok, Error> {
self.sink.serialize_static_str(name)
}
fn serialize_unit_variant(
self, _name: &'static str, _variant_index: u32, variant: &'static str,
) -> Result<S::Ok, Error> {
self.sink.serialize_static_str(variant)
}
fn serialize_newtype_struct<T: ?Sized + serde::ser::Serialize>(
self, _name: &'static str, value: &T,
) -> Result<S::Ok, Error> {
value.serialize(self)
}
fn serialize_newtype_variant<T: ?Sized + serde::ser::Serialize>(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
_value: &T,
) -> Result<S::Ok, Error> {
Err(self.sink.unsupported())
}
fn serialize_none(self) -> Result<S::Ok, Error> {
self.sink.serialize_none()
}
fn serialize_some<T: ?Sized + serde::ser::Serialize>(
self, value: &T,
) -> Result<S::Ok, Error> {
self.sink.serialize_some(value)
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Error> {
Err(self.sink.unsupported())
}
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Error> {
Err(self.sink.unsupported())
}
fn serialize_tuple_struct(
self, _name: &'static str, _len: usize,
) -> Result<Self::SerializeTuple, Error> {
Err(self.sink.unsupported())
}
fn serialize_tuple_variant(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Error> {
Err(self.sink.unsupported())
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Error> {
Err(self.sink.unsupported())
}
fn serialize_struct(
self, _name: &'static str, _len: usize,
) -> Result<Self::SerializeStruct, Error> {
Err(self.sink.unsupported())
}
fn serialize_struct_variant(
self, _name: &'static str, _variant_index: u32, _variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant, Error> {
Err(self.sink.unsupported())
}
}
impl<S: Sink> PartSerializer<S> {
fn serialize_integer<I>(self, value: I) -> Result<S::Ok, Error>
where
I: itoa::Integer,
{
let mut buf = [b'\0'; 20];
let len = itoa::write(&mut buf[..], value).unwrap();
let part = unsafe { str::from_utf8_unchecked(&buf[0..len]) };
serde::ser::Serializer::serialize_str(self, part)
}
fn serialize_floating<F>(self, value: F) -> Result<S::Ok, Error>
where
F: dtoa::Floating,
{
let mut buf = [b'\0'; 24];
let len = dtoa::write(&mut buf[..], value).unwrap();
let part = unsafe { str::from_utf8_unchecked(&buf[0..len]) };
serde::ser::Serializer::serialize_str(self, part)
}
}

View File

@@ -1,59 +0,0 @@
use super::super::ser::part::{PartSerializer, Sink};
use super::super::ser::Error;
use serde::ser::Serialize;
use std::str;
use url::form_urlencoded::Serializer as UrlEncodedSerializer;
use url::form_urlencoded::Target as UrlEncodedTarget;
pub struct ValueSink<'key, 'target, Target>
where
Target: 'target + UrlEncodedTarget,
{
urlencoder: &'target mut UrlEncodedSerializer<Target>,
key: &'key str,
}
impl<'key, 'target, Target> ValueSink<'key, 'target, Target>
where
Target: 'target + UrlEncodedTarget,
{
pub fn new(
urlencoder: &'target mut UrlEncodedSerializer<Target>, key: &'key str,
) -> Self {
ValueSink { urlencoder, key }
}
}
impl<'key, 'target, Target> Sink for ValueSink<'key, 'target, Target>
where
Target: 'target + UrlEncodedTarget,
{
type Ok = ();
fn serialize_str(self, value: &str) -> Result<(), Error> {
self.urlencoder.append_pair(self.key, value);
Ok(())
}
fn serialize_static_str(self, value: &'static str) -> Result<(), Error> {
self.serialize_str(value)
}
fn serialize_string(self, value: String) -> Result<(), Error> {
self.serialize_str(&value)
}
fn serialize_none(self) -> Result<Self::Ok, Error> {
Ok(())
}
fn serialize_some<T: ?Sized + Serialize>(
self, value: &T,
) -> Result<Self::Ok, Error> {
value.serialize(PartSerializer::new(self))
}
fn unsupported(self) -> Error {
Error::Custom("unsupported value".into())
}
}

View File

@@ -7,11 +7,11 @@ use futures::{Async, Future, Poll};
use tokio_io::{AsyncRead, AsyncWrite};
use super::settings::WorkerSettings;
use super::{h1, h2, HttpHandler, IoStream};
use super::{h1, h2, utils, HttpHandler, IoStream};
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
enum HttpProtocol<T: IoStream, H: HttpHandler + 'static> {
enum HttpProtocol<T: IoStream, H: 'static> {
H1(h1::Http1<T, H>),
H2(h2::Http2<T, H>),
Unknown(Rc<WorkerSettings<H>>, Option<SocketAddr>, T, BytesMut),
@@ -94,13 +94,13 @@ where
self.node = Some(Node::new(el));
let _ = match self.proto {
Some(HttpProtocol::H1(ref mut h1)) => {
self.node.as_mut().map(|n| h1.settings().head().insert(n))
self.node.as_ref().map(|n| h1.settings().head().insert(n))
}
Some(HttpProtocol::H2(ref mut h2)) => {
self.node.as_mut().map(|n| h2.settings().head().insert(n))
self.node.as_ref().map(|n| h2.settings().head().insert(n))
}
Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => {
self.node.as_mut().map(|n| settings.head().insert(n))
self.node.as_ref().map(|n| settings.head().insert(n))
}
None => unreachable!(),
};
@@ -139,8 +139,8 @@ where
ref mut io,
ref mut buf,
)) => {
match io.read_available(buf) {
Ok(Async::Ready(true)) | Err(_) => {
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) | Err(_) => {
debug!("Ignored premature client disconnection");
settings.remove_channel();
if let Some(n) = self.node.as_mut() {
@@ -188,8 +188,8 @@ where
}
pub(crate) struct Node<T> {
next: Option<*mut Node<T>>,
prev: Option<*mut Node<T>>,
next: Option<*mut Node<()>>,
prev: Option<*mut Node<()>>,
element: *mut T,
}
@@ -202,18 +202,20 @@ impl<T> Node<T> {
}
}
fn insert<I>(&mut self, next: &mut Node<I>) {
fn insert<I>(&self, next: &Node<I>) {
#[allow(mutable_transmutes)]
unsafe {
let next: *mut Node<T> = next as *const _ as *mut _;
if let Some(ref mut next2) = self.next {
let n = next2.as_mut().unwrap();
n.prev = Some(next);
if let Some(ref next2) = self.next {
let n: &mut Node<()> =
&mut *(next2.as_ref().unwrap() as *const _ as *mut _);
n.prev = Some(next as *const _ as *mut _);
}
self.next = Some(next);
let slf: &mut Node<T> = &mut *(self as *const _ as *mut _);
let next: &mut Node<T> = &mut *next;
next.prev = Some(self as *mut _);
slf.next = Some(next as *const _ as *mut _);
let next: &mut Node<T> = &mut *(next as *const _ as *mut _);
next.prev = Some(slf as *const _ as *mut _);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +0,0 @@
use futures::{Async, Poll};
use super::{helpers, HttpHandlerTask, Writer};
use http::{StatusCode, Version};
use Error;
pub(crate) struct ServerError(Version, StatusCode);
impl ServerError {
pub fn err(ver: Version, status: StatusCode) -> Box<HttpHandlerTask> {
Box::new(ServerError(ver, status))
}
}
impl HttpHandlerTask for ServerError {
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
{
let bytes = io.buffer();
helpers::write_status_line(self.0, self.1.as_u16(), bytes);
}
io.set_date();
Ok(Async::Ready(true))
}
}

View File

@@ -1,25 +1,30 @@
use std::collections::VecDeque;
use std::io;
use std::net::SocketAddr;
use std::rc::Rc;
use std::time::{Duration, Instant};
use std::time::Duration;
use bytes::BytesMut;
use actix::Arbiter;
use bytes::{BufMut, BytesMut};
use futures::{Async, Future, Poll};
use tokio_timer::Delay;
use tokio_core::reactor::Timeout;
use error::{Error, PayloadError};
use http::{StatusCode, Version};
use error::PayloadError;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use payload::{Payload, PayloadStatus, PayloadWriter};
use pipeline::Pipeline;
use super::error::ServerError;
use super::encoding::PayloadType;
use super::h1decoder::{DecoderError, H1Decoder, Message};
use super::h1writer::H1Writer;
use super::input::PayloadType;
use super::settings::WorkerSettings;
use super::Writer;
use super::{HttpHandler, HttpHandlerTask, IoStream};
const MAX_PIPELINED_MESSAGES: usize = 16;
const LW_BUFFER_SIZE: usize = 4096;
const HW_BUFFER_SIZE: usize = 32_768;
bitflags! {
struct Flags: u8 {
@@ -28,7 +33,6 @@ bitflags! {
const KEEPALIVE = 0b0000_0100;
const SHUTDOWN = 0b0000_1000;
const DISCONNECTED = 0b0001_0000;
const POLLED = 0b0010_0000;
}
}
@@ -40,7 +44,7 @@ bitflags! {
}
}
pub(crate) struct Http1<T: IoStream, H: HttpHandler + 'static> {
pub(crate) struct Http1<T: IoStream, H: 'static> {
flags: Flags,
settings: Rc<WorkerSettings<H>>,
addr: Option<SocketAddr>,
@@ -48,38 +52,12 @@ pub(crate) struct Http1<T: IoStream, H: HttpHandler + 'static> {
decoder: H1Decoder,
payload: Option<PayloadType>,
buf: BytesMut,
tasks: VecDeque<Entry<H>>,
keepalive_timer: Option<Delay>,
tasks: VecDeque<Entry>,
keepalive_timer: Option<Timeout>,
}
enum EntryPipe<H: HttpHandler> {
Task(H::Task),
Error(Box<HttpHandlerTask>),
}
impl<H: HttpHandler> EntryPipe<H> {
fn disconnected(&mut self) {
match *self {
EntryPipe::Task(ref mut task) => task.disconnected(),
EntryPipe::Error(ref mut task) => task.disconnected(),
}
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
match *self {
EntryPipe::Task(ref mut task) => task.poll_io(io),
EntryPipe::Error(ref mut task) => task.poll_io(io),
}
}
fn poll_completed(&mut self) -> Poll<(), Error> {
match *self {
EntryPipe::Task(ref mut task) => task.poll_completed(),
EntryPipe::Error(ref mut task) => task.poll_completed(),
}
}
}
struct Entry<H: HttpHandler> {
pipe: EntryPipe<H>,
struct Entry {
pipe: Box<HttpHandlerTask>,
flags: EntryFlags,
}
@@ -92,9 +70,10 @@ where
settings: Rc<WorkerSettings<H>>, stream: T, addr: Option<SocketAddr>,
buf: BytesMut,
) -> Self {
let bytes = settings.get_shared_bytes();
Http1 {
flags: Flags::KEEPALIVE,
stream: H1Writer::new(stream, Rc::clone(&settings)),
stream: H1Writer::new(stream, bytes, Rc::clone(&settings)),
decoder: H1Decoder::new(),
payload: None,
tasks: VecDeque::new(),
@@ -124,14 +103,6 @@ where
}
}
fn notify_disconnect(&mut self) {
// notify all tasks
self.stream.disconnected();
for task in &mut self.tasks {
task.pipe.disconnected();
}
}
#[inline]
pub fn poll(&mut self) -> Poll<(), ()> {
// keep-alive timer
@@ -177,50 +148,29 @@ where
#[inline]
/// read data from stream
pub fn poll_io(&mut self) {
if !self.flags.contains(Flags::POLLED) {
self.parse();
self.flags.insert(Flags::POLLED);
return;
}
// read io from socket
if !self.flags.intersects(Flags::ERROR)
&& self.tasks.len() < MAX_PIPELINED_MESSAGES
&& self.can_read()
{
match self.stream.get_mut().read_available(&mut self.buf) {
Ok(Async::Ready(disconnected)) => {
if disconnected {
// notify all tasks
self.notify_disconnect();
// kill keepalive
self.keepalive_timer.take();
// on parse error, stop reading stream but tasks need to be
// completed
self.flags.insert(Flags::ERROR);
if let Some(mut payload) = self.payload.take() {
payload.set_error(PayloadError::Incomplete);
}
} else {
self.parse();
}
if self.read() {
// notify all tasks
self.stream.disconnected();
for entry in &mut self.tasks {
entry.pipe.disconnected()
}
Ok(Async::NotReady) => (),
Err(_) => {
// notify all tasks
self.notify_disconnect();
// kill keepalive
self.keepalive_timer.take();
// kill keepalive
self.keepalive_timer.take();
// on parse error, stop reading stream but tasks need to be
// completed
self.flags.insert(Flags::ERROR);
// on parse error, stop reading stream but tasks need to be
// completed
self.flags.insert(Flags::ERROR);
if let Some(mut payload) = self.payload.take() {
payload.set_error(PayloadError::Incomplete);
}
if let Some(mut payload) = self.payload.take() {
payload.set_error(PayloadError::Incomplete);
}
} else {
self.parse();
}
}
}
@@ -232,18 +182,19 @@ where
let mut io = false;
let mut idx = 0;
while idx < self.tasks.len() {
let item: &mut Entry = unsafe { &mut *(&mut self.tasks[idx] as *mut _) };
// only one task can do io operation in http/1
if !io && !self.tasks[idx].flags.contains(EntryFlags::EOF) {
if !io && !item.flags.contains(EntryFlags::EOF) {
// io is corrupted, send buffer
if self.tasks[idx].flags.contains(EntryFlags::ERROR) {
if item.flags.contains(EntryFlags::ERROR) {
if let Ok(Async::NotReady) = self.stream.poll_completed(true) {
return Ok(Async::NotReady);
}
self.flags.insert(Flags::ERROR);
return Err(());
}
match self.tasks[idx].pipe.poll_io(&mut self.stream) {
match item.pipe.poll_io(&mut self.stream) {
Ok(Async::Ready(ready)) => {
// override keep-alive state
if self.stream.keepalive() {
@@ -255,11 +206,9 @@ where
self.stream.reset();
if ready {
self.tasks[idx]
.flags
.insert(EntryFlags::EOF | EntryFlags::FINISHED);
item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED);
} else {
self.tasks[idx].flags.insert(EntryFlags::EOF);
item.flags.insert(EntryFlags::EOF);
}
}
// no more IO for this iteration
@@ -273,23 +222,23 @@ where
Err(err) => {
// it is not possible to recover from error
// during pipe handling, so just drop connection
self.notify_disconnect();
self.tasks[idx].flags.insert(EntryFlags::ERROR);
error!("Unhandled error1: {}", err);
continue;
error!("Unhandled error: {}", err);
item.flags.insert(EntryFlags::ERROR);
// check stream state, we still can have valid data in buffer
if let Ok(Async::NotReady) = self.stream.poll_completed(true) {
return Ok(Async::NotReady);
}
return Err(());
}
}
} else if !self.tasks[idx].flags.contains(EntryFlags::FINISHED) {
match self.tasks[idx].pipe.poll_completed() {
} else if !item.flags.contains(EntryFlags::FINISHED) {
match item.pipe.poll() {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => {
self.tasks[idx].flags.insert(EntryFlags::FINISHED)
}
Ok(Async::Ready(_)) => item.flags.insert(EntryFlags::FINISHED),
Err(err) => {
self.notify_disconnect();
self.tasks[idx].flags.insert(EntryFlags::ERROR);
item.flags.insert(EntryFlags::ERROR);
error!("Unhandled error: {}", err);
continue;
}
}
}
@@ -319,7 +268,6 @@ where
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => {
debug!("Error sending data: {}", err);
self.notify_disconnect();
return Err(());
}
Ok(Async::Ready(_)) => {
@@ -347,7 +295,8 @@ where
if self.keepalive_timer.is_none() && keep_alive > 0 {
trace!("Start keep-alive timer");
let mut timer =
Delay::new(Instant::now() + Duration::new(keep_alive, 0));
Timeout::new(Duration::new(keep_alive, 0), Arbiter::handle())
.unwrap();
// register timer
let _ = timer.poll();
self.keepalive_timer = Some(timer);
@@ -359,24 +308,27 @@ where
pub fn parse(&mut self) {
'outer: loop {
match self.decoder.decode(&mut self.buf, &self.settings) {
Ok(Some(Message::Message { mut msg, payload })) => {
Ok(Some(Message::Message { msg, payload })) => {
self.flags.insert(Flags::STARTED);
if payload {
let (ps, pl) = Payload::new(false);
*msg.inner.payload.borrow_mut() = Some(pl);
self.payload = Some(PayloadType::new(&msg.inner.headers, ps));
msg.get_mut().payload = Some(pl);
self.payload =
Some(PayloadType::new(&msg.get_ref().headers, ps));
}
let mut req = HttpRequest::from_message(msg);
// set remote addr
msg.inner_mut().addr = self.addr;
req.set_peer_addr(self.addr);
// stop keepalive timer
self.keepalive_timer.take();
// search handler for request
for h in self.settings.handlers().iter_mut() {
msg = match h.handle(msg) {
req = match h.handle(req) {
Ok(mut pipe) => {
if self.tasks.is_empty() {
match pipe.poll_io(&mut self.stream) {
@@ -392,7 +344,7 @@ where
if !ready {
let item = Entry {
pipe: EntryPipe::Task(pipe),
pipe,
flags: EntryFlags::EOF,
};
self.tasks.push_back(item);
@@ -408,21 +360,18 @@ where
}
}
self.tasks.push_back(Entry {
pipe: EntryPipe::Task(pipe),
pipe,
flags: EntryFlags::empty(),
});
continue 'outer;
}
Err(msg) => msg,
Err(req) => req,
}
}
// handler is not found
self.tasks.push_back(Entry {
pipe: EntryPipe::Error(ServerError::err(
Version::HTTP_11,
StatusCode::NOT_FOUND,
)),
pipe: Pipeline::error(HttpResponse::NotFound()),
flags: EntryFlags::empty(),
});
}
@@ -459,12 +408,35 @@ where
}
}
}
#[inline]
fn read(&mut self) -> bool {
loop {
unsafe {
if self.buf.remaining_mut() < LW_BUFFER_SIZE {
self.buf.reserve(HW_BUFFER_SIZE);
}
match self.stream.get_mut().read(self.buf.bytes_mut()) {
Ok(n) => {
if n == 0 {
return true;
} else {
self.buf.advance_mut(n);
}
}
Err(e) => {
return e.kind() != io::ErrorKind::WouldBlock;
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use std::net::Shutdown;
use std::{cmp, io, time};
use std::{cmp, time};
use bytes::{Buf, Bytes, BytesMut};
use http::{Method, Version};
@@ -474,11 +446,12 @@ mod tests {
use application::HttpApplication;
use httpmessage::HttpMessage;
use server::h1decoder::Message;
use server::settings::{ServerSettings, WorkerSettings};
use server::{KeepAlive, Request};
use server::helpers::SharedHttpInnerMessage;
use server::settings::WorkerSettings;
use server::KeepAlive;
impl Message {
fn message(self) -> Request {
fn message(self) -> SharedHttpInnerMessage {
match self {
Message::Message { msg, payload: _ } => msg,
_ => panic!("error"),
@@ -507,9 +480,9 @@ mod tests {
macro_rules! parse_ready {
($e:expr) => {{
let settings: WorkerSettings<HttpApplication> =
WorkerSettings::new(Vec::new(), KeepAlive::Os, ServerSettings::default());
WorkerSettings::new(Vec::new(), KeepAlive::Os);
match H1Decoder::new().decode($e, &settings) {
Ok(Some(msg)) => msg.message(),
Ok(Some(msg)) => HttpRequest::from_message(msg.message()),
Ok(_) => unreachable!("Eof during parsing http request"),
Err(err) => unreachable!("Error during parsing http request: {:?}", err),
}
@@ -519,7 +492,7 @@ mod tests {
macro_rules! expect_parse_err {
($e:expr) => {{
let settings: WorkerSettings<HttpApplication> =
WorkerSettings::new(Vec::new(), KeepAlive::Os, ServerSettings::default());
WorkerSettings::new(Vec::new(), KeepAlive::Os);
match H1Decoder::new().decode($e, &settings) {
Err(err) => match err {
@@ -543,6 +516,11 @@ mod tests {
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 {}
@@ -598,12 +576,11 @@ mod tests {
let settings = Rc::new(WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
));
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf);
h1.poll_io();
h1.poll_io();
h1.parse();
assert_eq!(h1.tasks.len(), 1);
}
@@ -614,28 +591,23 @@ mod tests {
let settings = Rc::new(WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
));
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf);
h1.poll_io();
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");
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let req = msg.message();
let req = HttpRequest::from_message(msg.message());
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
@@ -647,11 +619,7 @@ mod tests {
#[test]
fn test_parse_partial() {
let mut buf = BytesMut::from("PUT /test HTTP/1");
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
@@ -662,7 +630,7 @@ mod tests {
buf.extend(b".1\r\n\r\n");
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let mut req = msg.message();
let mut req = HttpRequest::from_message(msg.message());
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::PUT);
assert_eq!(req.path(), "/test");
@@ -674,16 +642,12 @@ mod tests {
#[test]
fn test_parse_post() {
let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n");
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let mut req = msg.message();
let mut req = HttpRequest::from_message(msg.message());
assert_eq!(req.version(), Version::HTTP_10);
assert_eq!(*req.method(), Method::POST);
assert_eq!(req.path(), "/test2");
@@ -696,16 +660,12 @@ mod tests {
fn test_parse_body() {
let mut buf =
BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let mut req = msg.message();
let mut req = HttpRequest::from_message(msg.message());
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
@@ -727,16 +687,12 @@ mod tests {
fn test_parse_body_crlf() {
let mut buf =
BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let mut req = msg.message();
let mut req = HttpRequest::from_message(msg.message());
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
@@ -757,18 +713,14 @@ mod tests {
#[test]
fn test_parse_partial_eof() {
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n");
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
assert!(reader.decode(&mut buf, &settings).unwrap().is_none());
buf.extend(b"\r\n");
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let req = msg.message();
let req = HttpRequest::from_message(msg.message());
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
@@ -780,11 +732,7 @@ mod tests {
#[test]
fn test_headers_split_field() {
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n");
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() }
@@ -798,7 +746,7 @@ mod tests {
buf.extend(b"t: value\r\n\r\n");
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let req = msg.message();
let req = HttpRequest::from_message(msg.message());
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
@@ -815,14 +763,10 @@ mod tests {
Set-Cookie: c1=cookie1\r\n\
Set-Cookie: c2=cookie2\r\n\r\n",
);
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
let req = msg.message();
let req = HttpRequest::from_message(msg.message());
let val: Vec<_> = req
.headers()
@@ -1015,11 +959,7 @@ mod tests {
#[test]
fn test_http_request_upgrade() {
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
connection: upgrade\r\n\
@@ -1029,7 +969,7 @@ mod tests {
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req = msg.message();
let req = HttpRequest::from_message(msg.message());
assert!(!req.keep_alive());
assert!(req.upgrade());
assert_eq!(
@@ -1085,16 +1025,12 @@ mod tests {
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req = msg.message();
let req = HttpRequest::from_message(msg.message());
assert!(req.chunked().unwrap());
buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n");
@@ -1125,15 +1061,11 @@ mod tests {
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req = msg.message();
let req = HttpRequest::from_message(msg.message());
assert!(req.chunked().unwrap());
buf.extend(
@@ -1151,7 +1083,7 @@ mod tests {
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req2 = msg.message();
let req2 = HttpRequest::from_message(msg.message());
assert!(req2.chunked().unwrap());
assert_eq!(*req2.method(), Method::POST);
assert!(req2.chunked().unwrap());
@@ -1163,16 +1095,12 @@ mod tests {
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req = msg.message();
let req = HttpRequest::from_message(msg.message());
assert!(req.chunked().unwrap());
buf.extend(b"4\r\n1111\r\n");
@@ -1214,16 +1142,13 @@ mod tests {
&"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n"[..],
);
let settings = WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
assert!(msg.message().chunked().unwrap());
let req = HttpRequest::from_message(msg.message());
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();

View File

@@ -4,11 +4,12 @@ use bytes::{Bytes, BytesMut};
use futures::{Async, Poll};
use httparse;
use super::message::{MessageFlags, Request};
use super::helpers::SharedHttpInnerMessage;
use super::settings::WorkerSettings;
use error::ParseError;
use http::header::{HeaderName, HeaderValue};
use http::{header, HttpTryFrom, Method, Uri, Version};
use httprequest::MessageFlags;
use uri::Url;
const MAX_BUFFER_SIZE: usize = 131_072;
@@ -19,7 +20,10 @@ pub(crate) struct H1Decoder {
}
pub(crate) enum Message {
Message { msg: Request, payload: bool },
Message {
msg: SharedHttpInnerMessage,
payload: bool,
},
Chunk(Bytes),
Eof,
}
@@ -80,24 +84,24 @@ impl H1Decoder {
fn parse_message<H>(
&self, buf: &mut BytesMut, settings: &WorkerSettings<H>,
) -> Poll<(Request, Option<EncodingDecoder>), ParseError> {
) -> Poll<(SharedHttpInnerMessage, Option<EncodingDecoder>), ParseError> {
// Parse http message
let mut has_upgrade = false;
let mut chunked = false;
let mut content_length = None;
let msg = {
// Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] =
let bytes_ptr = buf.as_ref().as_ptr() as usize;
let mut headers: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
let (len, method, path, version, headers_len) = {
let mut parsed: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
let mut req = httparse::Request::new(&mut parsed);
match req.parse(buf)? {
let b = unsafe {
let b: &[u8] = buf;
&*(b as *const [u8])
};
let mut req = httparse::Request::new(&mut headers);
match req.parse(b)? {
httparse::Status::Complete(len) => {
let method = Method::from_bytes(req.method.unwrap().as_bytes())
.map_err(|_| ParseError::Method)?;
@@ -107,8 +111,6 @@ impl H1Decoder {
} else {
Version::HTTP_10
};
HeaderIndex::record(buf, req.headers, &mut headers);
(len, method, path, version, req.headers.len())
}
httparse::Status::Partial => return Ok(Async::NotReady),
@@ -118,22 +120,22 @@ impl H1Decoder {
let slice = buf.split_to(len).freeze();
// convert headers
let mut msg = settings.get_request();
let msg = settings.get_http_message();
{
let inner = msg.inner_mut();
inner
let msg_mut = msg.get_mut();
msg_mut
.flags
.get_mut()
.set(MessageFlags::KEEPALIVE, version != Version::HTTP_10);
for idx in headers[..headers_len].iter() {
if let Ok(name) =
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1])
{
// Unsafe: httparse check header value for valid utf-8
for header in headers[..headers_len].iter() {
if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) {
has_upgrade = has_upgrade || name == header::UPGRADE;
let v_start = header.value.as_ptr() as usize - bytes_ptr;
let v_end = v_start + header.value.len();
let value = unsafe {
HeaderValue::from_shared_unchecked(
slice.slice(idx.value.0, idx.value.1),
slice.slice(v_start, v_end),
)
};
match name {
@@ -173,23 +175,20 @@ impl H1Decoder {
} else {
false
};
inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka);
}
header::UPGRADE => {
has_upgrade = true;
msg_mut.flags.set(MessageFlags::KEEPALIVE, ka);
}
_ => (),
}
inner.headers.append(name, value);
msg_mut.headers.append(name, value);
} else {
return Err(ParseError::Header);
}
}
inner.url = path;
inner.method = method;
inner.version = version;
msg_mut.url = path;
msg_mut.method = method;
msg_mut.version = version;
}
msg
};
@@ -201,7 +200,7 @@ impl H1Decoder {
} else if let Some(len) = content_length {
// Content-Length
Some(EncodingDecoder::length(len))
} else if has_upgrade || msg.inner.method == Method::CONNECT {
} else if has_upgrade || msg.get_ref().method == Method::CONNECT {
// upgrade(websocket) or connect
Some(EncodingDecoder::eof())
} else {
@@ -212,28 +211,6 @@ impl H1Decoder {
}
}
#[derive(Clone, Copy)]
pub(crate) struct HeaderIndex {
pub(crate) name: (usize, usize),
pub(crate) value: (usize, usize),
}
impl HeaderIndex {
pub(crate) fn record(
bytes: &[u8], headers: &[httparse::Header], indices: &mut [HeaderIndex],
) {
let bytes_ptr = bytes.as_ptr() as usize;
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
let name_start = header.name.as_ptr() as usize - bytes_ptr;
let name_end = name_start + header.name.len();
indices.name = (name_start, name_end);
let value_start = header.value.as_ptr() as usize - bytes_ptr;
let value_end = value_start + header.value.len();
indices.value = (value_start, value_end);
}
}
}
/// Decoders to handle different Transfer-Encodings.
///
/// If a message body does not include a Transfer-Encoding, it *should*

View File

@@ -1,23 +1,21 @@
// #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::io::{self, Write};
use std::rc::Rc;
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use bytes::{BufMut, BytesMut};
use futures::{Async, Poll};
use std::io;
use std::rc::Rc;
use tokio_io::AsyncWrite;
use super::encoding::ContentEncoder;
use super::helpers;
use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::WorkerSettings;
use super::Request;
use super::shared::SharedBytes;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
use body::{Binary, Body};
use header::ContentEncoding;
use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
use http::{Method, Version};
use httprequest::HttpInnerMessage;
use httpresponse::HttpResponse;
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
@@ -34,20 +32,24 @@ bitflags! {
pub(crate) struct H1Writer<T: AsyncWrite, H: 'static> {
flags: Flags,
stream: T,
encoder: ContentEncoder,
written: u64,
headers_size: u32,
buffer: Output,
buffer: SharedBytes,
buffer_capacity: usize,
settings: Rc<WorkerSettings<H>>,
}
impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
pub fn new(stream: T, settings: Rc<WorkerSettings<H>>) -> H1Writer<T, H> {
pub fn new(
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
) -> H1Writer<T, H> {
H1Writer {
flags: Flags::KEEPALIVE,
encoder: ContentEncoder::empty(buf.clone()),
written: 0,
headers_size: 0,
buffer: Output::Buffer(settings.get_bytes()),
buffer: buf,
buffer_capacity: 0,
stream,
settings,
@@ -63,17 +65,20 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
self.flags = Flags::KEEPALIVE;
}
pub fn disconnected(&mut self) {}
pub fn disconnected(&mut self) {
self.buffer.take();
}
pub fn keepalive(&self) -> bool {
self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE)
}
fn write_data(stream: &mut T, data: &[u8]) -> io::Result<usize> {
fn write_data(&mut self, data: &[u8]) -> io::Result<usize> {
let mut written = 0;
while written < data.len() {
match stream.write(&data[written..]) {
match self.stream.write(&data[written..]) {
Ok(0) => {
self.disconnected();
return Err(io::Error::new(io::ErrorKind::WriteZero, ""));
}
Ok(n) => {
@@ -89,14 +94,6 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
}
}
impl<T: AsyncWrite, H: 'static> Drop for H1Writer<T, H> {
fn drop(&mut self) {
if let Some(bytes) = self.buffer.take_option() {
self.settings.release_bytes(bytes);
}
}
}
impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
#[inline]
fn written(&self) -> u64 {
@@ -104,21 +101,22 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
}
#[inline]
fn set_date(&mut self) {
self.settings.set_date(self.buffer.as_mut(), true)
fn set_date(&self, dst: &mut BytesMut) {
self.settings.set_date(dst)
}
#[inline]
fn buffer(&mut self) -> &mut BytesMut {
self.buffer.as_mut()
fn buffer(&self) -> &mut BytesMut {
self.buffer.get_mut()
}
fn start(
&mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding,
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
encoding: ContentEncoding,
) -> io::Result<WriterState> {
// prepare task
let mut info = ResponseInfo::new(req.inner.method == Method::HEAD);
self.buffer.for_server(&mut info, &req.inner, msg, encoding);
self.encoder =
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
self.flags = Flags::STARTED | Flags::KEEPALIVE;
} else {
@@ -126,7 +124,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
}
// Connection upgrade
let version = msg.version().unwrap_or_else(|| req.inner.version);
let version = msg.version().unwrap_or_else(|| req.version);
if msg.upgrade() {
self.flags.insert(Flags::UPGRADE);
msg.headers_mut()
@@ -146,110 +144,86 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
// render message
{
// output buffer
let mut buffer = self.buffer.as_mut();
let mut buffer = self.buffer.get_mut();
let reason = msg.reason().as_bytes();
if let Body::Binary(ref bytes) = body {
let mut is_bin = if let Body::Binary(ref bytes) = body {
buffer.reserve(
256
+ msg.headers().len() * AVERAGE_HEADER_SIZE
+ bytes.len()
+ reason.len(),
);
true
} else {
buffer.reserve(
256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(),
);
}
false
};
// status line
helpers::write_status_line(version, msg.status().as_u16(), &mut buffer);
buffer.extend_from_slice(reason);
SharedBytes::extend_from_slice_(buffer, reason);
// content length
match info.length {
ResponseLength::Chunked => {
buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n")
match body {
Body::Empty => if req.method != Method::HEAD {
SharedBytes::put_slice(buffer, b"\r\ncontent-length: 0\r\n");
} else {
SharedBytes::put_slice(buffer, b"\r\n");
},
Body::Binary(ref bytes) => {
helpers::write_content_length(bytes.len(), &mut buffer)
}
ResponseLength::Zero => {
buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n")
}
ResponseLength::Length(len) => {
helpers::write_content_length(len, &mut buffer)
}
ResponseLength::Length64(len) => {
buffer.extend_from_slice(b"\r\ncontent-length: ");
write!(buffer.writer(), "{}", len)?;
buffer.extend_from_slice(b"\r\n");
}
ResponseLength::None => buffer.extend_from_slice(b"\r\n"),
}
if let Some(ce) = info.content_encoding {
buffer.extend_from_slice(b"content-encoding: ");
buffer.extend_from_slice(ce.as_ref());
buffer.extend_from_slice(b"\r\n");
_ => SharedBytes::put_slice(buffer, b"\r\n"),
}
// write headers
let mut pos = 0;
let mut has_date = false;
let mut remaining = buffer.remaining_mut();
unsafe {
let mut buf = &mut *(buffer.bytes_mut() as *mut [u8]);
for (key, value) in msg.headers() {
match *key {
TRANSFER_ENCODING => continue,
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
continue;
},
CONTENT_LENGTH => match info.length {
ResponseLength::None => (),
_ => continue,
},
DATE => {
has_date = true;
}
_ => (),
}
let v = value.as_ref();
let k = key.as_str().as_bytes();
let len = k.len() + v.len() + 4;
if len > remaining {
buffer.advance_mut(pos);
pos = 0;
buffer.reserve(len);
remaining = buffer.remaining_mut();
buf = &mut *(buffer.bytes_mut() as *mut _);
}
buf[pos..pos + k.len()].copy_from_slice(k);
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) };
for (key, value) in msg.headers() {
if is_bin && key == CONTENT_LENGTH {
is_bin = false;
continue;
}
buffer.advance_mut(pos);
has_date = has_date || key == DATE;
let v = value.as_ref();
let k = key.as_str().as_bytes();
let len = k.len() + v.len() + 4;
if len > remaining {
unsafe { buffer.advance_mut(pos) };
pos = 0;
buffer.reserve(len);
remaining = buffer.remaining_mut();
buf = unsafe { &mut *(buffer.bytes_mut() as *mut _) };
}
buf[pos..pos + k.len()].copy_from_slice(k);
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
}
unsafe { buffer.advance_mut(pos) };
// optimized date header, set_date writes \r\n
if !has_date {
self.settings.set_date(&mut buffer, true);
self.settings.set_date(&mut buffer);
} else {
// msg eof
buffer.extend_from_slice(b"\r\n");
SharedBytes::extend_from_slice_(buffer, b"\r\n");
}
self.headers_size = buffer.len() as u32;
}
if let Body::Binary(bytes) = body {
self.written = bytes.len() as u64;
self.buffer.write(bytes.as_ref())?;
self.encoder.write(bytes)?;
} else {
// capacity, makes sense only for streaming or actor
self.buffer_capacity = msg.write_buffer_capacity();
@@ -259,7 +233,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
Ok(WriterState::Done)
}
fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
self.written += payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) {
if self.flags.contains(Flags::STARTED) {
@@ -267,30 +241,21 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
if self.flags.contains(Flags::UPGRADE) {
if self.buffer.is_empty() {
let pl: &[u8] = payload.as_ref();
let n = match Self::write_data(&mut self.stream, pl) {
Err(err) => {
if err.kind() == io::ErrorKind::WriteZero {
self.disconnected();
}
return Err(err);
}
Ok(val) => val,
};
let n = self.write_data(pl)?;
if n < pl.len() {
self.buffer.write(&pl[n..])?;
self.buffer.extend_from_slice(&pl[n..]);
return Ok(WriterState::Done);
}
} else {
self.buffer.write(payload.as_ref())?;
self.buffer.extend(payload);
}
} else {
// TODO: add warning, write after EOF
self.buffer.write(payload.as_ref())?;
self.encoder.write(payload)?;
}
} else {
// could be response to EXCEPT header
self.buffer.write(payload.as_ref())?;
self.buffer.extend_from_slice(payload.as_ref())
}
}
@@ -302,7 +267,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
}
fn write_eof(&mut self) -> io::Result<WriterState> {
if !self.buffer.write_eof()? {
if !self.encoder.write_eof()? {
Err(io::Error::new(
io::ErrorKind::Other,
"Last payload item, but eof is not reached",
@@ -317,18 +282,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
#[inline]
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
if !self.buffer.is_empty() {
let written = {
match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) {
Err(err) => {
if err.kind() == io::ErrorKind::WriteZero {
self.disconnected();
}
return Err(err);
}
Ok(val) => val,
}
};
let buf: &[u8] =
unsafe { &mut *(self.buffer.as_ref() as *const _ as *mut _) };
let written = self.write_data(buf)?;
let _ = self.buffer.split_to(written);
if shutdown && !self.buffer.is_empty()
|| (self.buffer.len() > self.buffer_capacity)

View File

@@ -1,26 +1,31 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::collections::VecDeque;
use std::io::{Read, Write};
use std::net::SocketAddr;
use std::rc::Rc;
use std::time::{Duration, Instant};
use std::time::Duration;
use std::{cmp, io, mem};
use actix::Arbiter;
use bytes::{Buf, Bytes};
use futures::{Async, Future, Poll, Stream};
use http2::server::{self, Connection, Handshake, SendResponse};
use http2::{Reason, RecvStream};
use modhttp::request::Parts;
use tokio_core::reactor::Timeout;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::Delay;
use error::{Error, PayloadError};
use http::{StatusCode, Version};
use error::PayloadError;
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use payload::{Payload, PayloadStatus, PayloadWriter};
use pipeline::Pipeline;
use uri::Url;
use super::error::ServerError;
use super::encoding::PayloadType;
use super::h2writer::H2Writer;
use super::input::PayloadType;
use super::settings::WorkerSettings;
use super::{HttpHandler, HttpHandlerTask, Writer};
@@ -34,14 +39,14 @@ bitflags! {
pub(crate) struct Http2<T, H>
where
T: AsyncRead + AsyncWrite + 'static,
H: HttpHandler + 'static,
H: 'static,
{
flags: Flags,
settings: Rc<WorkerSettings<H>>,
addr: Option<SocketAddr>,
state: State<IoWrapper<T>>,
tasks: VecDeque<Entry<H>>,
keepalive_timer: Option<Delay>,
keepalive_timer: Option<Timeout>,
}
enum State<T: AsyncRead + AsyncWrite> {
@@ -62,7 +67,7 @@ where
flags: Flags::empty(),
tasks: VecDeque::new(),
state: State::Handshake(server::handshake(IoWrapper {
unread: if buf.is_empty() { None } else { Some(buf) },
unread: Some(buf),
inner: io,
})),
keepalive_timer: None,
@@ -138,7 +143,7 @@ where
break;
}
} else if !item.flags.contains(EntryFlags::FINISHED) {
match item.task.poll_completed() {
match item.task.poll() {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => {
not_ready = false;
@@ -213,10 +218,9 @@ where
let keep_alive = self.settings.keep_alive();
if keep_alive > 0 && self.keepalive_timer.is_none() {
trace!("Start keep-alive timer");
let mut timeout = Delay::new(
Instant::now()
+ Duration::new(keep_alive, 0),
);
let mut timeout = Timeout::new(
Duration::new(keep_alive, 0),
Arbiter::handle()).unwrap();
// register timeout
let _ = timeout.poll();
self.keepalive_timer = Some(timeout);
@@ -284,41 +288,15 @@ bitflags! {
}
}
enum EntryPipe<H: HttpHandler> {
Task(H::Task),
Error(Box<HttpHandlerTask>),
}
impl<H: HttpHandler> EntryPipe<H> {
fn disconnected(&mut self) {
match *self {
EntryPipe::Task(ref mut task) => task.disconnected(),
EntryPipe::Error(ref mut task) => task.disconnected(),
}
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
match *self {
EntryPipe::Task(ref mut task) => task.poll_io(io),
EntryPipe::Error(ref mut task) => task.poll_io(io),
}
}
fn poll_completed(&mut self) -> Poll<(), Error> {
match *self {
EntryPipe::Task(ref mut task) => task.poll_completed(),
EntryPipe::Error(ref mut task) => task.poll_completed(),
}
}
}
struct Entry<H: HttpHandler + 'static> {
task: EntryPipe<H>,
struct Entry<H: 'static> {
task: Box<HttpHandlerTask>,
payload: PayloadType,
recv: RecvStream,
stream: H2Writer<H>,
flags: EntryFlags,
}
impl<H: HttpHandler + 'static> Entry<H> {
impl<H: 'static> Entry<H> {
fn new(
parts: Parts, recv: RecvStream, resp: SendResponse<Bytes>,
addr: Option<SocketAddr>, settings: &Rc<WorkerSettings<H>>,
@@ -329,41 +307,39 @@ impl<H: HttpHandler + 'static> Entry<H> {
// Payload and Content-Encoding
let (psender, payload) = Payload::new(false);
let mut msg = settings.get_request();
{
let inner = msg.inner_mut();
inner.url = Url::new(parts.uri);
inner.method = parts.method;
inner.version = parts.version;
inner.headers = parts.headers;
*inner.payload.borrow_mut() = Some(payload);
inner.addr = addr;
}
let msg = settings.get_http_message();
msg.get_mut().url = Url::new(parts.uri);
msg.get_mut().method = parts.method;
msg.get_mut().version = parts.version;
msg.get_mut().headers = parts.headers;
msg.get_mut().payload = Some(payload);
msg.get_mut().addr = addr;
let mut req = HttpRequest::from_message(msg);
// Payload sender
let psender = PayloadType::new(msg.headers(), psender);
let psender = PayloadType::new(req.headers(), psender);
// start request processing
let mut task = None;
for h in settings.handlers().iter_mut() {
msg = match h.handle(msg) {
req = match h.handle(req) {
Ok(t) => {
task = Some(t);
break;
}
Err(msg) => msg,
Err(req) => req,
}
}
Entry {
task: task.map(EntryPipe::Task).unwrap_or_else(|| {
EntryPipe::Error(ServerError::err(
Version::HTTP_2,
StatusCode::NOT_FOUND,
))
}),
task: task.unwrap_or_else(|| Pipeline::error(HttpResponse::NotFound())),
payload: psender,
stream: H2Writer::new(resp, Rc::clone(settings)),
stream: H2Writer::new(
resp,
settings.get_shared_bytes(),
Rc::clone(settings),
),
flags: EntryFlags::empty(),
recv,
}

View File

@@ -9,15 +9,16 @@ use std::rc::Rc;
use std::{cmp, io};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
use http::{HttpTryFrom, Method, Version};
use http::{HttpTryFrom, Version};
use super::encoding::ContentEncoder;
use super::helpers;
use super::message::Request;
use super::output::{Output, ResponseInfo};
use super::settings::WorkerSettings;
use super::shared::SharedBytes;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
use body::{Binary, Body};
use header::ContentEncoding;
use httprequest::HttpInnerMessage;
use httpresponse::HttpResponse;
const CHUNK_SIZE: usize = 16_384;
@@ -34,25 +35,27 @@ bitflags! {
pub(crate) struct H2Writer<H: 'static> {
respond: SendResponse<Bytes>,
stream: Option<SendStream<Bytes>>,
encoder: ContentEncoder,
flags: Flags,
written: u64,
buffer: Output,
buffer: SharedBytes,
buffer_capacity: usize,
settings: Rc<WorkerSettings<H>>,
}
impl<H: 'static> H2Writer<H> {
pub fn new(
respond: SendResponse<Bytes>, settings: Rc<WorkerSettings<H>>,
respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
) -> H2Writer<H> {
H2Writer {
stream: None,
flags: Flags::empty(),
written: 0,
buffer: Output::Buffer(settings.get_bytes()),
buffer_capacity: 0,
respond,
settings,
stream: None,
encoder: ContentEncoder::empty(buf.clone()),
flags: Flags::empty(),
written: 0,
buffer: buf,
buffer_capacity: 0,
}
}
@@ -63,34 +66,29 @@ impl<H: 'static> H2Writer<H> {
}
}
impl<H: 'static> Drop for H2Writer<H> {
fn drop(&mut self) {
self.settings.release_bytes(self.buffer.take());
}
}
impl<H: 'static> Writer for H2Writer<H> {
fn written(&self) -> u64 {
self.written
}
#[inline]
fn set_date(&mut self) {
self.settings.set_date(self.buffer.as_mut(), true)
fn set_date(&self, dst: &mut BytesMut) {
self.settings.set_date(dst)
}
#[inline]
fn buffer(&mut self) -> &mut BytesMut {
self.buffer.as_mut()
fn buffer(&self) -> &mut BytesMut {
self.buffer.get_mut()
}
fn start(
&mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding,
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
encoding: ContentEncoding,
) -> io::Result<WriterState> {
// prepare response
self.flags.insert(Flags::STARTED);
let mut info = ResponseInfo::new(req.inner.method == Method::HEAD);
self.buffer.for_server(&mut info, &req.inner, msg, encoding);
self.encoder =
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
// http2 specific
msg.headers_mut().remove(CONNECTION);
@@ -99,7 +97,7 @@ impl<H: 'static> Writer for H2Writer<H> {
// using helpers::date is quite a lot faster
if !msg.headers().contains_key(DATE) {
let mut bytes = BytesMut::with_capacity(29);
self.settings.set_date(&mut bytes, false);
self.settings.set_date_simple(&mut bytes);
msg.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
}
@@ -152,7 +150,7 @@ impl<H: 'static> Writer for H2Writer<H> {
} else {
self.flags.insert(Flags::EOF);
self.written = bytes.len() as u64;
self.buffer.write(bytes.as_ref())?;
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));
@@ -166,16 +164,16 @@ impl<H: 'static> Writer for H2Writer<H> {
}
}
fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
self.written = payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) {
if self.flags.contains(Flags::STARTED) {
// TODO: add warning, write after EOF
self.buffer.write(payload.as_ref())?;
self.encoder.write(payload)?;
} else {
// might be response for EXCEPT
error!("Not supported");
self.buffer.extend_from_slice(payload.as_ref())
}
}
@@ -188,7 +186,7 @@ impl<H: 'static> Writer for H2Writer<H> {
fn write_eof(&mut self) -> io::Result<WriterState> {
self.flags.insert(Flags::EOF);
if !self.buffer.write_eof()? {
if !self.encoder.write_eof()? {
Err(io::Error::new(
io::ErrorKind::Other,
"Last payload item, but eof is not reached",

View File

@@ -1,7 +1,94 @@
use bytes::{BufMut, BytesMut};
use http::Version;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use std::{mem, ptr, slice};
use httprequest::HttpInnerMessage;
/// Internal use only! unsafe
pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpInnerMessage>>>);
impl SharedMessagePool {
pub fn new() -> SharedMessagePool {
SharedMessagePool(RefCell::new(VecDeque::with_capacity(128)))
}
#[inline]
pub fn get(&self) -> Rc<HttpInnerMessage> {
if let Some(msg) = self.0.borrow_mut().pop_front() {
msg
} else {
Rc::new(HttpInnerMessage::default())
}
}
#[inline]
pub fn release(&self, mut msg: Rc<HttpInnerMessage>) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
Rc::get_mut(&mut msg).unwrap().reset();
v.push_front(msg);
}
}
}
pub(crate) struct SharedHttpInnerMessage(
Option<Rc<HttpInnerMessage>>,
Option<Rc<SharedMessagePool>>,
);
impl Drop for SharedHttpInnerMessage {
fn drop(&mut self) {
if let Some(ref pool) = self.1 {
if let Some(msg) = self.0.take() {
if Rc::strong_count(&msg) == 1 {
pool.release(msg);
}
}
}
}
}
impl Clone for SharedHttpInnerMessage {
fn clone(&self) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(self.0.clone(), self.1.clone())
}
}
impl Default for SharedHttpInnerMessage {
fn default() -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(Rc::new(HttpInnerMessage::default())), None)
}
}
impl SharedHttpInnerMessage {
pub fn from_message(msg: HttpInnerMessage) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(Rc::new(msg)), None)
}
pub fn new(
msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>,
) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(msg), Some(pool))
}
#[inline(always)]
#[allow(mutable_transmutes)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub fn get_mut(&self) -> &mut HttpInnerMessage {
let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref();
unsafe { &mut *(r as *const _ as *mut _) }
}
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
pub fn get_ref(&self) -> &HttpInnerMessage {
self.0.as_ref().unwrap()
}
}
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
@@ -105,14 +192,14 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
}
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
unsafe {
let mut curr: isize = 39;
let mut buf: [u8; 41] = mem::uninitialized();
buf[39] = b'\r';
buf[40] = b'\n';
let buf_ptr = buf.as_mut_ptr();
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
let mut curr: isize = 39;
let mut buf: [u8; 41] = unsafe { mem::uninitialized() };
buf[39] = b'\r';
buf[40] = b'\n';
let buf_ptr = buf.as_mut_ptr();
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
unsafe {
// eagerly decode 4 characters at a time
while n >= 10_000 {
let rem = (n % 10_000) as isize;
@@ -145,7 +232,9 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
curr -= 2;
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
}
unsafe {
bytes.extend_from_slice(slice::from_raw_parts(
buf_ptr.offset(curr),
41 - curr as usize,

View File

@@ -1,357 +0,0 @@
use std::io::{Read, Write};
use std::{cmp, io};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliDecoder;
use bytes::{BufMut, Bytes, BytesMut};
use error::PayloadError;
#[cfg(feature = "flate2")]
use flate2::read::GzDecoder;
#[cfg(feature = "flate2")]
use flate2::write::DeflateDecoder;
use header::ContentEncoding;
use http::header::{HeaderMap, CONTENT_ENCODING};
use payload::{PayloadSender, PayloadStatus, PayloadWriter};
pub(crate) enum PayloadType {
Sender(PayloadSender),
Encoding(Box<EncodedPayload>),
}
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) {
if let Ok(enc) = enc.to_str() {
ContentEncoding::from(enc)
} else {
ContentEncoding::Auto
}
} else {
ContentEncoding::Auto
};
match enc {
ContentEncoding::Auto | ContentEncoding::Identity => {
PayloadType::Sender(sender)
}
_ => 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 {
#[inline]
fn set_error(&mut self, err: PayloadError) {
match *self {
PayloadType::Sender(ref mut sender) => sender.set_error(err),
PayloadType::Encoding(ref mut enc) => enc.set_error(err),
}
}
#[inline]
fn feed_eof(&mut self) {
match *self {
PayloadType::Sender(ref mut sender) => sender.feed_eof(),
PayloadType::Encoding(ref mut enc) => enc.feed_eof(),
}
}
#[inline]
fn feed_data(&mut self, data: Bytes) {
match *self {
PayloadType::Sender(ref mut sender) => sender.feed_data(data),
PayloadType::Encoding(ref mut enc) => enc.feed_data(data),
}
}
#[inline]
fn need_read(&self) -> PayloadStatus {
match *self {
PayloadType::Sender(ref sender) => sender.need_read(),
PayloadType::Encoding(ref enc) => enc.need_read(),
}
}
}
/// Payload wrapper with content decompression support
pub(crate) struct EncodedPayload {
inner: PayloadSender,
error: bool,
payload: PayloadStream,
}
impl EncodedPayload {
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload {
EncodedPayload {
inner,
error: false,
payload: PayloadStream::new(enc),
}
}
}
impl PayloadWriter for EncodedPayload {
fn set_error(&mut self, err: PayloadError) {
self.inner.set_error(err)
}
fn feed_eof(&mut self) {
if !self.error {
match self.payload.feed_eof() {
Err(err) => {
self.error = true;
self.set_error(PayloadError::Io(err));
}
Ok(value) => {
if let Some(b) = value {
self.inner.feed_data(b);
}
self.inner.feed_eof();
}
}
}
}
fn feed_data(&mut self, data: Bytes) {
if self.error {
return;
}
match self.payload.feed_data(data) {
Ok(Some(b)) => self.inner.feed_data(b),
Ok(None) => (),
Err(e) => {
self.error = true;
self.set_error(e.into());
}
}
}
#[inline]
fn need_read(&self) -> PayloadStatus {
self.inner.need_read()
}
}
pub(crate) enum Decoder {
#[cfg(feature = "flate2")]
Deflate(Box<DeflateDecoder<Writer>>),
#[cfg(feature = "flate2")]
Gzip(Option<Box<GzDecoder<Wrapper>>>),
#[cfg(feature = "brotli")]
Br(Box<BrotliDecoder<Writer>>),
Identity,
}
// should go after write::GzDecoder get implemented
#[derive(Debug)]
pub(crate) struct Wrapper {
pub buf: BytesMut,
pub eof: bool,
}
impl io::Read for Wrapper {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = cmp::min(buf.len(), self.buf.len());
buf[..len].copy_from_slice(&self.buf[..len]);
self.buf.split_to(len);
if len == 0 {
if self.eof {
Ok(0)
} else {
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
}
} else {
Ok(len)
}
}
}
impl io::Write for Wrapper {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub(crate) struct Writer {
buf: BytesMut,
}
impl Writer {
fn new() -> Writer {
Writer {
buf: BytesMut::with_capacity(8192),
}
}
fn take(&mut self) -> Bytes {
self.buf.take().freeze()
}
}
impl io::Write for Writer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// Payload stream with decompression support
pub(crate) struct PayloadStream {
decoder: Decoder,
dst: BytesMut,
}
impl PayloadStream {
pub fn new(enc: ContentEncoding) -> PayloadStream {
let dec = match enc {
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => {
Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => Decoder::Gzip(None),
_ => Decoder::Identity,
};
PayloadStream {
decoder: dec,
dst: BytesMut::new(),
}
}
}
impl PayloadStream {
pub fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
match self.decoder {
#[cfg(feature = "brotli")]
Decoder::Br(ref mut decoder) => match decoder.finish() {
Ok(mut writer) => {
let b = writer.take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => {
if let Some(ref mut decoder) = *decoder {
decoder.as_mut().get_mut().eof = true;
self.dst.reserve(8192);
match decoder.read(unsafe { self.dst.bytes_mut() }) {
Ok(n) => {
unsafe { self.dst.advance_mut(n) };
return Ok(Some(self.dst.take().freeze()));
}
Err(e) => return Err(e),
}
} else {
Ok(None)
}
}
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
Decoder::Identity => Ok(None),
}
}
pub fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
match self.decoder {
#[cfg(feature = "brotli")]
Decoder::Br(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => {
if decoder.is_none() {
*decoder = Some(Box::new(GzDecoder::new(Wrapper {
buf: BytesMut::from(data),
eof: false,
})));
} else {
let _ = decoder.as_mut().unwrap().write(&data);
}
loop {
self.dst.reserve(8192);
match decoder
.as_mut()
.as_mut()
.unwrap()
.read(unsafe { self.dst.bytes_mut() })
{
Ok(n) => {
if n != 0 {
unsafe { self.dst.advance_mut(n) };
}
if n == 0 {
return Ok(Some(self.dst.take().freeze()));
}
}
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock
&& !self.dst.is_empty()
{
return Ok(Some(self.dst.take().freeze()));
}
return Err(e);
}
}
}
}
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
Decoder::Identity => Ok(Some(data)),
}
}
}

View File

@@ -1,255 +0,0 @@
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::collections::VecDeque;
use std::net::SocketAddr;
use std::rc::Rc;
use http::{header, HeaderMap, Method, Uri, Version};
use extensions::Extensions;
use httpmessage::HttpMessage;
use info::ConnectionInfo;
use payload::Payload;
use server::ServerSettings;
use uri::Url as InnerUrl;
bitflags! {
pub(crate) struct MessageFlags: u8 {
const KEEPALIVE = 0b0000_0001;
const CONN_INFO = 0b0000_0010;
}
}
/// Request's context
pub struct Request {
pub(crate) inner: Rc<InnerRequest>,
}
pub(crate) struct InnerRequest {
pub(crate) version: Version,
pub(crate) method: Method,
pub(crate) url: InnerUrl,
pub(crate) flags: Cell<MessageFlags>,
pub(crate) headers: HeaderMap,
pub(crate) extensions: RefCell<Extensions>,
pub(crate) addr: Option<SocketAddr>,
pub(crate) info: RefCell<ConnectionInfo>,
pub(crate) payload: RefCell<Option<Payload>>,
pub(crate) settings: ServerSettings,
pool: &'static RequestPool,
}
impl InnerRequest {
#[inline]
/// Reset request instance
pub fn reset(&mut self) {
self.headers.clear();
self.extensions.borrow_mut().clear();
self.flags.set(MessageFlags::empty());
*self.payload.borrow_mut() = None;
}
}
impl HttpMessage for Request {
type Stream = Payload;
fn headers(&self) -> &HeaderMap {
&self.inner.headers
}
#[inline]
fn payload(&self) -> Payload {
if let Some(payload) = self.inner.payload.borrow_mut().take() {
payload
} else {
Payload::empty()
}
}
}
impl Request {
/// Create new RequestContext instance
pub(crate) fn new(pool: &'static RequestPool, settings: ServerSettings) -> Request {
Request {
inner: Rc::new(InnerRequest {
pool,
settings,
method: Method::GET,
url: InnerUrl::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
flags: Cell::new(MessageFlags::empty()),
addr: None,
info: RefCell::new(ConnectionInfo::default()),
payload: RefCell::new(None),
extensions: RefCell::new(Extensions::new()),
}),
}
}
#[inline]
pub(crate) fn inner(&self) -> &InnerRequest {
self.inner.as_ref()
}
#[inline]
pub(crate) fn inner_mut(&mut self) -> &mut InnerRequest {
Rc::get_mut(&mut self.inner).expect("Multiple copies exist")
}
#[inline]
pub(crate) fn url(&self) -> &InnerUrl {
&self.inner().url
}
/// Read the Request Uri.
#[inline]
pub fn uri(&self) -> &Uri {
self.inner().url.uri()
}
/// Read the Request method.
#[inline]
pub fn method(&self) -> &Method {
&self.inner().method
}
/// Read the Request Version.
#[inline]
pub fn version(&self) -> Version {
self.inner().version
}
/// The target path of this Request.
#[inline]
pub fn path(&self) -> &str {
self.inner().url.path()
}
#[inline]
/// Returns Request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.inner().headers
}
#[inline]
/// Returns mutable Request's headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.inner_mut().headers
}
/// Peer socket address
///
/// Peer address is actual socket address, if proxy is used in front of
/// actix http server, then peer address would be address of this proxy.
///
/// To get client connection information `connection_info()` method should
/// be used.
pub fn peer_addr(&self) -> Option<SocketAddr> {
self.inner().addr
}
/// Checks if a connection should be kept alive.
#[inline]
pub fn keep_alive(&self) -> bool {
self.inner().flags.get().contains(MessageFlags::KEEPALIVE)
}
/// Request extensions
#[inline]
pub fn extensions(&self) -> Ref<Extensions> {
self.inner().extensions.borrow()
}
/// Mutable reference to a the request's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> {
self.inner().extensions.borrow_mut()
}
/// Check if request requires connection upgrade
pub fn upgrade(&self) -> bool {
if let Some(conn) = self.inner().headers.get(header::CONNECTION) {
if let Ok(s) = conn.to_str() {
return s.to_lowercase().contains("upgrade");
}
}
self.inner().method == Method::CONNECT
}
/// Get *ConnectionInfo* for the correct request.
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
if self.inner().flags.get().contains(MessageFlags::CONN_INFO) {
self.inner().info.borrow()
} else {
let mut flags = self.inner().flags.get();
flags.insert(MessageFlags::CONN_INFO);
self.inner().flags.set(flags);
self.inner().info.borrow_mut().update(self);
self.inner().info.borrow()
}
}
/// Server settings
#[inline]
pub fn server_settings(&self) -> &ServerSettings {
&self.inner().settings
}
pub(crate) fn clone(&self) -> Self {
Request {
inner: self.inner.clone(),
}
}
pub(crate) fn release(self) {
let mut inner = self.inner;
if let Some(r) = Rc::get_mut(&mut inner) {
r.reset();
} else {
return;
}
inner.pool.release(inner);
}
}
pub(crate) struct RequestPool(
RefCell<VecDeque<Rc<InnerRequest>>>,
RefCell<ServerSettings>,
);
thread_local!(static POOL: &'static RequestPool = RequestPool::create());
impl RequestPool {
fn create() -> &'static RequestPool {
let pool = RequestPool(
RefCell::new(VecDeque::with_capacity(128)),
RefCell::new(ServerSettings::default()),
);
Box::leak(Box::new(pool))
}
pub fn pool(settings: ServerSettings) -> &'static RequestPool {
POOL.with(|p| {
*p.1.borrow_mut() = settings;
*p
})
}
#[inline]
pub fn get(pool: &'static RequestPool) -> Request {
if let Some(msg) = pool.0.borrow_mut().pop_front() {
Request { inner: msg }
} else {
Request::new(pool, pool.1.borrow().clone())
}
}
#[inline]
/// Release request instance
pub fn release(&self, msg: Rc<InnerRequest>) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
v.push_front(msg);
}
}
}

View File

@@ -2,55 +2,53 @@
use std::net::Shutdown;
use std::{io, time};
use bytes::{BufMut, BytesMut};
use actix;
use bytes::BytesMut;
use futures::{Async, Poll};
use tokio_core::net::TcpStream;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tcp::TcpStream;
mod channel;
mod error;
pub(crate) mod encoding;
pub(crate) mod h1;
pub(crate) mod h1decoder;
mod h1writer;
mod h2;
mod h2writer;
pub(crate) mod helpers;
pub(crate) mod input;
pub(crate) mod message;
pub(crate) mod output;
pub(crate) mod settings;
mod settings;
pub(crate) mod shared;
mod srv;
pub(crate) mod utils;
mod worker;
pub use self::message::Request;
pub use self::settings::ServerSettings;
pub use self::srv::HttpServer;
#[doc(hidden)]
pub use self::helpers::write_content_length;
use actix::Message;
use body::Binary;
use error::Error;
use header::ContentEncoding;
use httprequest::{HttpInnerMessage, HttpRequest};
use httpresponse::HttpResponse;
/// max buffer size 64k
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
const LW_BUFFER_SIZE: usize = 4096;
const HW_BUFFER_SIZE: usize = 32_768;
/// Create new http server with application factory.
///
/// This is shortcut for `server::HttpServer::new()` method.
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse};
/// use actix::*;
/// use actix_web::{server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system
/// let sys = actix::System::new("guide");
///
/// server::new(
/// || App::new()
@@ -58,8 +56,8 @@ const HW_BUFFER_SIZE: usize = 32_768;
/// .bind("127.0.0.1:59090").unwrap()
/// .start();
///
/// # actix::System::current().stop();
/// sys.run();
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
/// let _ = sys.run();
/// }
/// ```
pub fn new<F, U, H>(factory: F) -> HttpServer<H>
@@ -115,36 +113,30 @@ pub struct ResumeServer;
///
/// If server starts with `spawn()` method, then spawned thread get terminated.
pub struct StopServer {
/// Whether to try and shut down gracefully
pub graceful: bool,
}
impl Message for StopServer {
impl actix::Message for StopServer {
type Result = Result<(), ()>;
}
/// Low level http request handler
#[allow(unused_variables)]
pub trait HttpHandler: 'static {
/// Request handling task
type Task: HttpHandlerTask;
/// Handle request
fn handle(&self, req: Request) -> Result<Self::Task, Request>;
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest>;
}
impl HttpHandler for Box<HttpHandler<Task = Box<HttpHandlerTask>>> {
type Task = Box<HttpHandlerTask>;
fn handle(&self, req: Request) -> Result<Box<HttpHandlerTask>, Request> {
self.as_ref().handle(req)
impl HttpHandler for Box<HttpHandler> {
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest> {
self.as_mut().handle(req)
}
}
/// Low level http request handler
#[doc(hidden)]
pub trait HttpHandlerTask {
/// Poll task, this method is used before or after *io* object is available
fn poll_completed(&mut self) -> Poll<(), Error> {
fn poll(&mut self) -> Poll<(), Error> {
Ok(Async::Ready(()))
}
@@ -155,25 +147,19 @@ pub trait HttpHandlerTask {
fn disconnected(&mut self) {}
}
impl HttpHandlerTask for Box<HttpHandlerTask> {
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
self.as_mut().poll_io(io)
}
}
/// Conversion helper trait
pub trait IntoHttpHandler {
/// The associated type which is result of conversion.
type Handler: HttpHandler;
/// Convert into `HttpHandler` object.
fn into_handler(self) -> Self::Handler;
fn into_handler(self, settings: ServerSettings) -> Self::Handler;
}
impl<T: HttpHandler> IntoHttpHandler for T {
type Handler = T;
fn into_handler(self) -> Self::Handler {
fn into_handler(self, _: ServerSettings) -> Self::Handler {
self
}
}
@@ -192,16 +178,18 @@ pub trait Writer {
fn written(&self) -> u64;
#[doc(hidden)]
fn set_date(&mut self);
fn set_date(&self, st: &mut BytesMut);
#[doc(hidden)]
fn buffer(&mut self) -> &mut BytesMut;
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
fn buffer(&self) -> &mut BytesMut;
fn start(
&mut self, req: &Request, resp: &mut HttpResponse, encoding: ContentEncoding,
&mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse,
encoding: ContentEncoding,
) -> io::Result<WriterState>;
fn write(&mut self, payload: &Binary) -> io::Result<WriterState>;
fn write(&mut self, payload: Binary) -> io::Result<WriterState>;
fn write_eof(&mut self) -> io::Result<WriterState>;
@@ -216,38 +204,6 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static {
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>;
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
fn read_available(&mut self, buf: &mut BytesMut) -> Poll<bool, io::Error> {
let mut read_some = false;
loop {
if buf.remaining_mut() < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE);
}
unsafe {
match self.read(buf.bytes_mut()) {
Ok(n) => {
if n == 0 {
return Ok(Async::Ready(!read_some));
} else {
read_some = true;
buf.advance_mut(n);
}
}
Err(e) => {
return if e.kind() == io::ErrorKind::WouldBlock {
if read_some {
Ok(Async::Ready(false))
} else {
Ok(Async::NotReady)
}
} else {
Err(e)
};
}
}
}
}
}
}
impl IoStream for TcpStream {

View File

@@ -1,71 +1,81 @@
use bytes::BytesMut;
use futures_cpupool::{Builder, CpuPool};
use http::StatusCode;
use std::cell::{Cell, RefCell, RefMut, UnsafeCell};
use std::collections::VecDeque;
use std::fmt::Write;
use std::rc::Rc;
use std::{env, fmt, net};
use bytes::BytesMut;
use futures_cpupool::CpuPool;
use http::StatusCode;
use lazycell::LazyCell;
use parking_lot::Mutex;
use std::sync::Arc;
use std::{fmt, mem, net};
use time;
use super::channel::Node;
use super::message::{Request, RequestPool};
use super::helpers;
use super::shared::{SharedBytes, SharedBytesPool};
use super::KeepAlive;
use body::Body;
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
/// Env variable for default cpu pool size
const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL";
lazy_static! {
pub(crate) static ref DEFAULT_CPUPOOL: Mutex<CpuPool> = {
let default = match env::var(ENV_CPU_POOL_VAR) {
Ok(val) => {
if let Ok(val) = val.parse() {
val
} else {
error!("Can not parse ACTIX_CPU_POOL value");
20
}
}
Err(_) => 20,
};
Mutex::new(CpuPool::new(default))
};
}
/// Various server settings
pub struct ServerSettings {
addr: Option<net::SocketAddr>,
secure: bool,
host: String,
cpu_pool: LazyCell<CpuPool>,
responses: &'static HttpResponsePool,
cpu_pool: Arc<InnerCpuPool>,
responses: Rc<UnsafeCell<HttpResponsePool>>,
}
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: LazyCell::new(),
responses: HttpResponsePool::get_pool(),
cpu_pool: self.cpu_pool.clone(),
responses: HttpResponsePool::pool(),
}
}
}
struct InnerCpuPool {
cpu_pool: UnsafeCell<Option<CpuPool>>,
}
impl fmt::Debug for InnerCpuPool {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CpuPool")
}
}
impl InnerCpuPool {
fn new() -> Self {
InnerCpuPool {
cpu_pool: UnsafeCell::new(None),
}
}
fn cpu_pool(&self) -> &CpuPool {
unsafe {
let val = &mut *self.cpu_pool.get();
if val.is_none() {
*val = Some(Builder::new().create());
}
val.as_ref().unwrap()
}
}
}
unsafe impl Sync for InnerCpuPool {}
impl Default for ServerSettings {
fn default() -> Self {
ServerSettings {
addr: None,
secure: false,
host: "localhost:8080".to_owned(),
responses: HttpResponsePool::get_pool(),
cpu_pool: LazyCell::new(),
responses: HttpResponsePool::pool(),
cpu_pool: Arc::new(InnerCpuPool::new()),
}
}
}
@@ -82,8 +92,8 @@ impl ServerSettings {
} else {
"localhost".to_owned()
};
let cpu_pool = LazyCell::new();
let responses = HttpResponsePool::get_pool();
let cpu_pool = Arc::new(InnerCpuPool::new());
let responses = HttpResponsePool::pool();
ServerSettings {
addr,
secure,
@@ -93,21 +103,6 @@ impl ServerSettings {
}
}
pub(crate) fn parts(&self) -> (Option<net::SocketAddr>, String, bool) {
(self.addr, self.host.clone(), self.secure)
}
pub(crate) fn from_parts(parts: (Option<net::SocketAddr>, String, bool)) -> Self {
let (addr, host, secure) = parts;
ServerSettings {
addr,
host,
secure,
cpu_pool: LazyCell::new(),
responses: HttpResponsePool::get_pool(),
}
}
/// Returns the socket address of the local half of this TCP connection
pub fn local_addr(&self) -> Option<net::SocketAddr> {
self.addr
@@ -125,7 +120,7 @@ impl ServerSettings {
/// Returns default `CpuPool` for server
pub fn cpu_pool(&self) -> &CpuPool {
self.cpu_pool.borrow_with(|| DEFAULT_CPUPOOL.lock().clone())
self.cpu_pool.cpu_pool()
}
#[inline]
@@ -149,16 +144,14 @@ pub(crate) struct WorkerSettings<H> {
keep_alive: u64,
ka_enabled: bool,
bytes: Rc<SharedBytesPool>,
messages: &'static RequestPool,
messages: Rc<helpers::SharedMessagePool>,
channels: Cell<usize>,
node: RefCell<Node<()>>,
node: Box<Node<()>>,
date: UnsafeCell<Date>,
}
impl<H> WorkerSettings<H> {
pub(crate) fn new(
h: Vec<H>, keep_alive: KeepAlive, settings: ServerSettings,
) -> WorkerSettings<H> {
pub(crate) fn new(h: Vec<H>, keep_alive: KeepAlive) -> WorkerSettings<H> {
let (keep_alive, ka_enabled) = match keep_alive {
KeepAlive::Timeout(val) => (val as u64, true),
KeepAlive::Os | KeepAlive::Tcp(_) => (0, true),
@@ -166,14 +159,14 @@ impl<H> WorkerSettings<H> {
};
WorkerSettings {
h: RefCell::new(h),
bytes: Rc::new(SharedBytesPool::new()),
messages: RequestPool::pool(settings),
channels: Cell::new(0),
node: RefCell::new(Node::head()),
date: UnsafeCell::new(Date::new()),
keep_alive,
ka_enabled,
h: RefCell::new(h),
bytes: Rc::new(SharedBytesPool::new()),
messages: Rc::new(helpers::SharedMessagePool::new()),
channels: Cell::new(0),
node: Box::new(Node::head()),
date: UnsafeCell::new(Date::new()),
}
}
@@ -181,8 +174,8 @@ impl<H> WorkerSettings<H> {
self.channels.get()
}
pub fn head(&self) -> RefMut<Node<()>> {
self.node.borrow_mut()
pub fn head(&self) -> &Node<()> {
&self.node
}
pub fn handlers(&self) -> RefMut<Vec<H>> {
@@ -197,16 +190,15 @@ impl<H> WorkerSettings<H> {
self.ka_enabled
}
pub fn get_bytes(&self) -> BytesMut {
self.bytes.get_bytes()
pub fn get_shared_bytes(&self) -> SharedBytes {
SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes))
}
pub fn release_bytes(&self, bytes: BytesMut) {
self.bytes.release_bytes(bytes)
}
pub fn get_request(&self) -> Request {
RequestPool::get(self.messages)
pub fn get_http_message(&self) -> helpers::SharedHttpInnerMessage {
helpers::SharedHttpInnerMessage::new(
self.messages.get(),
Rc::clone(&self.messages),
)
}
pub fn add_channel(&self) {
@@ -223,22 +215,19 @@ impl<H> WorkerSettings<H> {
}
pub fn update_date(&self) {
// Unsafe: WorkerSetting is !Sync and !Send
unsafe { &mut *self.date.get() }.update();
}
pub fn set_date(&self, dst: &mut BytesMut, full: bool) {
// Unsafe: WorkerSetting is !Sync and !Send
let date_bytes = unsafe { &(*self.date.get()).bytes };
if full {
let mut buf: [u8; 39] = [0; 39];
buf[..6].copy_from_slice(b"date: ");
buf[6..35].copy_from_slice(date_bytes);
buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf);
} else {
dst.extend_from_slice(date_bytes);
}
pub fn set_date(&self, dst: &mut BytesMut) {
let mut buf: [u8; 39] = unsafe { mem::uninitialized() };
buf[..6].copy_from_slice(b"date: ");
buf[6..35].copy_from_slice(&(unsafe { &*self.date.get() }.bytes));
buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf);
}
pub fn set_date_simple(&self, dst: &mut BytesMut) {
dst.extend_from_slice(&(unsafe { &*self.date.get() }.bytes));
}
}
@@ -271,31 +260,6 @@ impl fmt::Write for Date {
}
}
#[derive(Debug)]
pub(crate) struct SharedBytesPool(RefCell<VecDeque<BytesMut>>);
impl SharedBytesPool {
pub fn new() -> SharedBytesPool {
SharedBytesPool(RefCell::new(VecDeque::with_capacity(128)))
}
pub fn get_bytes(&self) -> BytesMut {
if let Some(bytes) = self.0.borrow_mut().pop_front() {
bytes
} else {
BytesMut::new()
}
}
pub fn release_bytes(&self, mut bytes: BytesMut) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
bytes.clear();
v.push_front(bytes);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -307,15 +271,11 @@ mod tests {
#[test]
fn test_date() {
let settings = WorkerSettings::<()>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
);
let settings = WorkerSettings::<()>::new(Vec::new(), KeepAlive::Os);
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf1, true);
settings.set_date(&mut buf1);
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf2, true);
settings.set_date(&mut buf2);
assert_eq!(buf1, buf2);
}
}

147
src/server/shared.rs Normal file
View File

@@ -0,0 +1,147 @@
use bytes::{BufMut, BytesMut};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::io;
use std::rc::Rc;
use body::Binary;
/// Internal use only! unsafe
#[derive(Debug)]
pub(crate) struct SharedBytesPool(RefCell<VecDeque<Rc<BytesMut>>>);
impl SharedBytesPool {
pub fn new() -> SharedBytesPool {
SharedBytesPool(RefCell::new(VecDeque::with_capacity(128)))
}
pub fn get_bytes(&self) -> Rc<BytesMut> {
if let Some(bytes) = self.0.borrow_mut().pop_front() {
bytes
} else {
Rc::new(BytesMut::new())
}
}
pub fn release_bytes(&self, mut bytes: Rc<BytesMut>) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
Rc::get_mut(&mut bytes).unwrap().clear();
v.push_front(bytes);
}
}
}
#[derive(Debug)]
pub(crate) struct SharedBytes(Option<Rc<BytesMut>>, Option<Rc<SharedBytesPool>>);
impl Drop for SharedBytes {
fn drop(&mut self) {
if let Some(pool) = self.1.take() {
if let Some(bytes) = self.0.take() {
if Rc::strong_count(&bytes) == 1 {
pool.release_bytes(bytes);
}
}
}
}
}
impl SharedBytes {
pub fn empty() -> Self {
SharedBytes(None, None)
}
pub fn new(bytes: Rc<BytesMut>, pool: Rc<SharedBytesPool>) -> SharedBytes {
SharedBytes(Some(bytes), Some(pool))
}
#[inline(always)]
#[allow(mutable_transmutes)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub(crate) fn get_mut(&self) -> &mut BytesMut {
let r: &BytesMut = self.0.as_ref().unwrap().as_ref();
unsafe { &mut *(r as *const _ as *mut _) }
}
#[inline]
pub fn len(&self) -> usize {
self.0.as_ref().unwrap().len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.as_ref().unwrap().is_empty()
}
#[inline]
pub fn as_ref(&self) -> &[u8] {
self.0.as_ref().unwrap().as_ref()
}
pub fn split_to(&self, n: usize) -> BytesMut {
self.get_mut().split_to(n)
}
pub fn take(&self) -> BytesMut {
self.get_mut().take()
}
#[inline]
pub fn reserve(&self, cnt: usize) {
self.get_mut().reserve(cnt)
}
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
pub fn extend(&self, data: Binary) {
let buf = self.get_mut();
let data = data.as_ref();
buf.reserve(data.len());
SharedBytes::put_slice(buf, data);
}
#[inline]
pub fn extend_from_slice(&self, data: &[u8]) {
let buf = self.get_mut();
buf.reserve(data.len());
SharedBytes::put_slice(buf, data);
}
#[inline]
pub(crate) fn put_slice(buf: &mut BytesMut, src: &[u8]) {
let len = src.len();
unsafe {
buf.bytes_mut()[..len].copy_from_slice(src);
buf.advance_mut(len);
}
}
#[inline]
pub(crate) fn extend_from_slice_(buf: &mut BytesMut, data: &[u8]) {
buf.reserve(data.len());
SharedBytes::put_slice(buf, data);
}
}
impl Default for SharedBytes {
fn default() -> Self {
SharedBytes(Some(Rc::new(BytesMut::new())), None)
}
}
impl Clone for SharedBytes {
fn clone(&self) -> SharedBytes {
SharedBytes(self.0.clone(), self.1.clone())
}
}
impl io::Write for SharedBytes {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

View File

@@ -3,11 +3,8 @@ use std::sync::{mpsc as sync_mpsc, Arc};
use std::time::Duration;
use std::{io, net, thread};
use actix::{
fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler,
Response, StreamHandler, System, WrapFuture,
};
use actix::actors::signal;
use actix::prelude::*;
use futures::sync::mpsc;
use futures::{Future, Sink, Stream};
use mio;
@@ -54,16 +51,27 @@ where
keep_alive: KeepAlive,
factory: Arc<Fn() -> Vec<H> + Send + Sync>,
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
workers: Vec<(usize, Addr<Worker<H::Handler>>)>,
workers: Vec<(usize, Addr<Syn, Worker<H::Handler>>)>,
sockets: Vec<Socket>,
accept: Vec<(mio::SetReadiness, sync_mpsc::Sender<Command>)>,
exit: bool,
shutdown_timeout: u16,
signals: Option<Addr<signal::ProcessSignals>>,
signals: Option<Addr<Syn, signal::ProcessSignals>>,
no_http2: bool,
no_signals: bool,
}
unsafe impl<H> Sync for HttpServer<H>
where
H: IntoHttpHandler,
{
}
unsafe impl<H> Send for HttpServer<H>
where
H: IntoHttpHandler,
{
}
enum ServerCommand {
WorkerDied(usize, Slab<SocketInfo>),
}
@@ -159,16 +167,17 @@ where
self
}
/// Stop actix system.
/// Send `SystemExit` message to actix system
///
/// `SystemExit` message stops currently running system.
/// `SystemExit` message stops currently running system arbiter and all
/// nested arbiters.
pub fn system_exit(mut self) -> Self {
self.exit = true;
self
}
/// Set alternative address for `ProcessSignals` actor.
pub fn signals(mut self, addr: Addr<signal::ProcessSignals>) -> Self {
pub fn signals(mut self, addr: Addr<Syn, signal::ProcessSignals>) -> Self {
self.signals = Some(addr);
self
}
@@ -349,19 +358,19 @@ where
// start workers
let mut workers = Vec::new();
for idx in 0..self.threads {
let s = settings.clone();
let (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>();
let ka = self.keep_alive;
let socks = sockets.clone();
let factory = Arc::clone(&self.factory);
let parts = settings.parts();
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
let s = ServerSettings::from_parts(parts);
let apps: Vec<_> =
(*factory)().into_iter().map(|h| h.into_handler()).collect();
let apps: Vec<_> = (*factory)()
.into_iter()
.map(|h| h.into_handler(s.clone()))
.collect();
ctx.add_message_stream(rx);
Worker::new(apps, socks, ka, s)
Worker::new(apps, socks, ka)
});
workers.push((idx, tx));
self.workers.push((idx, addr));
@@ -371,12 +380,12 @@ where
}
// subscribe to os signals
fn subscribe_to_signals(&self) -> Option<Addr<signal::ProcessSignals>> {
fn subscribe_to_signals(&self) -> Option<Addr<Syn, signal::ProcessSignals>> {
if !self.no_signals {
if let Some(ref signals) = self.signals {
Some(signals.clone())
} else {
Some(System::current().registry().get::<signal::ProcessSignals>())
Some(Arbiter::system_registry().get::<signal::ProcessSignals>())
}
} else {
None
@@ -396,21 +405,24 @@ impl<H: IntoHttpHandler> HttpServer<H> {
/// This method requires to run within properly configured `Actix` system.
///
/// ```rust
/// extern crate actix;
/// extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse};
/// use actix_web::{server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system
///
/// server::new(|| App::new().resource("/", |r| r.h(|_: &_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .expect("Can not bind to 127.0.0.1:0")
/// server::new(
/// || App::new()
/// .resource("/", |r| r.h(|_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
/// .start();
/// # actix::System::current().stop();
/// sys.run(); // <- Run actix system, this method starts all async processes
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
///
/// let _ = sys.run(); // <- Run actix system, this method actually starts all async processes
/// }
/// ```
pub fn start(mut self) -> Addr<Self> {
pub fn start(mut self) -> Addr<Syn, Self> {
if self.sockets.is_empty() {
panic!("HttpServer::bind() has to be called before start()");
} else {
@@ -446,7 +458,7 @@ impl<H: IntoHttpHandler> HttpServer<H> {
// start http server actor
let signals = self.subscribe_to_signals();
let addr = Actor::create(move |ctx| {
let addr: Addr<Syn, _> = Actor::create(move |ctx| {
ctx.add_stream(rx);
self
});
@@ -466,21 +478,28 @@ impl<H: IntoHttpHandler> HttpServer<H> {
///
/// ```rust,ignore
/// # extern crate futures;
/// # extern crate actix;
/// # extern crate actix_web;
/// # use futures::Future;
/// use actix_web::*;
///
/// fn main() {
/// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .expect("Can not bind to 127.0.0.1:0")
/// HttpServer::new(
/// || App::new()
/// .resource("/", |r| r.h(|_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
/// .run();
/// }
/// ```
pub fn run(self) {
let sys = System::new("http-server");
self.start();
sys.run();
pub fn run(mut self) {
self.exit = true;
self.no_signals = false;
let _ = thread::spawn(move || {
let sys = System::new("http-server");
self.start();
let _ = sys.run();
}).join();
}
}
@@ -491,7 +510,7 @@ impl<H: IntoHttpHandler> HttpServer<H> {
)]
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming tls connections.
pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result<Addr<Self>> {
pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result<Addr<Syn, Self>> {
for sock in &mut self.sockets {
match sock.tp {
StreamHandlerType::Normal => (),
@@ -514,7 +533,7 @@ impl<H: IntoHttpHandler> HttpServer<H> {
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn start_ssl(
mut self, mut builder: SslAcceptorBuilder,
) -> io::Result<Addr<Self>> {
) -> io::Result<Addr<Syn, Self>> {
// alpn support
if !self.no_http2 {
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
@@ -544,28 +563,25 @@ impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming connections from a stream.
///
/// This method uses only one thread for handling incoming connections.
pub fn start_incoming<T, S>(mut self, stream: S, secure: bool) -> Addr<Self>
pub fn start_incoming<T, A, S>(mut self, stream: S, secure: bool) -> Addr<Syn, Self>
where
S: Stream<Item = T, Error = io::Error> + Send + 'static,
T: AsyncRead + AsyncWrite + Send + 'static,
S: Stream<Item = (T, A), Error = io::Error> + 'static,
T: AsyncRead + AsyncWrite + 'static,
A: 'static,
{
// set server settings
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
let settings = ServerSettings::new(Some(addr), &self.host, secure);
let apps: Vec<_> = (*self.factory)()
.into_iter()
.map(|h| h.into_handler())
.map(|h| h.into_handler(settings.clone()))
.collect();
self.h = Some(Rc::new(WorkerSettings::new(
apps,
self.keep_alive,
settings,
)));
self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive)));
// start server
let signals = self.subscribe_to_signals();
let addr = HttpServer::create(move |ctx| {
ctx.add_message_stream(stream.map_err(|_| ()).map(move |t| Conn {
let addr: Addr<Syn, _> = HttpServer::create(move |ctx| {
ctx.add_message_stream(stream.map_err(|_| ()).map(move |(t, _)| Conn {
io: WrapperStream::new(t),
token: 0,
peer: None,
@@ -573,7 +589,6 @@ impl<H: IntoHttpHandler> HttpServer<H> {
}));
self
});
if let Some(signals) = signals {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
}
@@ -582,7 +597,7 @@ impl<H: IntoHttpHandler> HttpServer<H> {
}
/// Signals support
/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system
/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)`
/// message to `System` actor.
impl<H: IntoHttpHandler> Handler<signal::Signal> for HttpServer<H> {
type Result = ();
@@ -612,7 +627,6 @@ impl<H: IntoHttpHandler> Handler<signal::Signal> for HttpServer<H> {
/// Commands from accept threads
impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
fn finished(&mut self, _: &mut Context<Self>) {}
fn handle(&mut self, msg: ServerCommand, _: &mut Context<Self>) {
match msg {
ServerCommand::WorkerDied(idx, socks) => {
@@ -642,15 +656,16 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
let ka = self.keep_alive;
let factory = Arc::clone(&self.factory);
let host = self.host.clone();
let addr = socks[0].addr;
let settings =
ServerSettings::new(Some(socks[0].addr), &self.host, false);
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
let settings = ServerSettings::new(Some(addr), &host, false);
let apps: Vec<_> =
(*factory)().into_iter().map(|h| h.into_handler()).collect();
let apps: Vec<_> = (*factory)()
.into_iter()
.map(|h| h.into_handler(settings.clone()))
.collect();
ctx.add_message_stream(rx);
Worker::new(apps, socks, ka, settings)
Worker::new(apps, socks, ka)
});
for item in &self.accept {
let _ = item.1.send(Command::Worker(new_idx, tx.clone()));
@@ -672,7 +687,7 @@ where
type Result = ();
fn handle(&mut self, msg: Conn<T>, _: &mut Context<Self>) -> Self::Result {
Arbiter::spawn(HttpChannel::new(
Arbiter::handle().spawn(HttpChannel::new(
Rc::clone(self.h.as_ref().unwrap()),
msg.io,
msg.peer,
@@ -704,7 +719,7 @@ impl<H: IntoHttpHandler> Handler<ResumeServer> for HttpServer<H> {
}
impl<H: IntoHttpHandler> Handler<StopServer> for HttpServer<H> {
type Result = Response<(), ()>;
type Result = actix::Response<(), ()>;
fn handle(&mut self, msg: StopServer, ctx: &mut Context<Self>) -> Self::Result {
// stop accept threads
@@ -723,26 +738,25 @@ impl<H: IntoHttpHandler> Handler<StopServer> for HttpServer<H> {
};
for worker in &self.workers {
let tx2 = tx.clone();
ctx.spawn(
worker
.1
.send(StopWorker { graceful: dur })
.into_actor(self)
.then(move |_, slf, ctx| {
slf.workers.pop();
if slf.workers.is_empty() {
let _ = tx2.send(());
worker
.1
.send(StopWorker { graceful: dur })
.into_actor(self)
.then(move |_, slf, ctx| {
slf.workers.pop();
if slf.workers.is_empty() {
let _ = tx2.send(());
// we need to stop system if server was spawned
if slf.exit {
ctx.run_later(Duration::from_millis(300), |_, _| {
System::current().stop();
});
}
// we need to stop system if server was spawned
if slf.exit {
ctx.run_later(Duration::from_millis(300), |_, _| {
Arbiter::system().do_send(actix::msgs::SystemExit(0))
});
}
fut::ok(())
}),
);
}
actix::fut::ok(())
})
.spawn(ctx);
}
if !self.workers.is_empty() {
@@ -751,7 +765,7 @@ impl<H: IntoHttpHandler> Handler<StopServer> for HttpServer<H> {
// we need to stop system if server was spawned
if self.exit {
ctx.run_later(Duration::from_millis(300), |_, _| {
System::current().stop();
Arbiter::system().do_send(actix::msgs::SystemExit(0))
});
}
Response::reply(Ok(()))

31
src/server/utils.rs Normal file
View File

@@ -0,0 +1,31 @@
use bytes::{BufMut, BytesMut};
use futures::{Async, Poll};
use std::io;
use super::IoStream;
const LW_BUFFER_SIZE: usize = 4096;
const HW_BUFFER_SIZE: usize = 32_768;
pub fn read_from_io<T: IoStream>(
io: &mut T, buf: &mut BytesMut,
) -> Poll<usize, io::Error> {
unsafe {
if buf.remaining_mut() < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE);
}
match io.read(buf.bytes_mut()) {
Ok(n) => {
buf.advance_mut(n);
Ok(Async::Ready(n))
}
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock {
Ok(Async::NotReady)
} else {
Err(e)
}
}
}
}
}

View File

@@ -1,12 +1,11 @@
use futures::sync::oneshot;
use futures::unsync::oneshot;
use futures::Future;
use net2::TcpStreamExt;
use slab::Slab;
use std::rc::Rc;
use std::{net, time};
use tokio::executor::current_thread;
use tokio_reactor::Handle;
use tokio_tcp::TcpStream;
use tokio_core::net::TcpStream;
use tokio_core::reactor::Handle;
#[cfg(any(feature = "tls", feature = "alpn"))]
use futures::future;
@@ -22,10 +21,10 @@ use openssl::ssl::SslAcceptor;
use tokio_openssl::SslAcceptorExt;
use actix::msgs::StopArbiter;
use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response};
use actix::*;
use server::channel::HttpChannel;
use server::settings::{ServerSettings, WorkerSettings};
use server::settings::WorkerSettings;
use server::{HttpHandler, KeepAlive};
#[derive(Message)]
@@ -61,6 +60,7 @@ where
H: HttpHandler + 'static,
{
settings: Rc<WorkerSettings<H>>,
hnd: Handle,
socks: Slab<SocketInfo>,
tcp_ka: Option<time::Duration>,
}
@@ -68,7 +68,6 @@ where
impl<H: HttpHandler + 'static> Worker<H> {
pub(crate) fn new(
h: Vec<H>, socks: Slab<SocketInfo>, keep_alive: KeepAlive,
settings: ServerSettings,
) -> Worker<H> {
let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive {
Some(time::Duration::new(val as u64, 0))
@@ -77,7 +76,8 @@ impl<H: HttpHandler + 'static> Worker<H> {
};
Worker {
settings: Rc::new(WorkerSettings::new(h, keep_alive, settings)),
settings: Rc::new(WorkerSettings::new(h, keep_alive)),
hnd: Arbiter::handle().clone(),
socks,
tcp_ka,
}
@@ -96,14 +96,14 @@ impl<H: HttpHandler + 'static> Worker<H> {
let num = slf.settings.num_channels();
if num == 0 {
let _ = tx.send(true);
Arbiter::current().do_send(StopArbiter(0));
Arbiter::arbiter().do_send(StopArbiter(0));
} else if let Some(d) = dur.checked_sub(time::Duration::new(1, 0)) {
slf.shutdown_timeout(ctx, tx, d);
} else {
info!("Force shutdown http worker, {} connections", num);
slf.settings.head().traverse::<TcpStream, H>();
let _ = tx.send(false);
Arbiter::current().do_send(StopArbiter(0));
Arbiter::arbiter().do_send(StopArbiter(0));
}
});
}
@@ -130,11 +130,11 @@ where
if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() {
error!("Can not set socket keep-alive option");
}
self.socks
.get_mut(msg.token)
.unwrap()
.htype
.handle(Rc::clone(&self.settings), msg);
self.socks.get_mut(msg.token).unwrap().htype.handle(
Rc::clone(&self.settings),
&self.hnd,
msg,
);
}
}
@@ -174,15 +174,15 @@ pub(crate) enum StreamHandlerType {
impl StreamHandlerType {
fn handle<H: HttpHandler>(
&mut self, h: Rc<WorkerSettings<H>>, msg: Conn<net::TcpStream>,
&mut self, h: Rc<WorkerSettings<H>>, hnd: &Handle, msg: Conn<net::TcpStream>,
) {
match *self {
StreamHandlerType::Normal => {
let _ = msg.io.set_nodelay(true);
let io = TcpStream::from_std(msg.io, &Handle::default())
let io = TcpStream::from_stream(msg.io, hnd)
.expect("failed to associate TCP stream");
current_thread::spawn(HttpChannel::new(h, io, msg.peer, msg.http2));
hnd.spawn(HttpChannel::new(h, io, msg.peer, msg.http2));
}
#[cfg(feature = "tls")]
StreamHandlerType::Tls(ref acceptor) => {
@@ -190,52 +190,47 @@ impl StreamHandlerType {
io, peer, http2, ..
} = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_std(io, &Handle::default())
let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream");
current_thread::spawn(TlsAcceptorExt::accept_async(acceptor, io).then(
move |res| {
match res {
Ok(io) => current_thread::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) => {
let Conn { io, peer, .. } = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_std(io, &Handle::default())
let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream");
current_thread::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
};
current_thread::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(()))
}));
}
}
}

View File

@@ -1,20 +1,21 @@
//! Various helpers for Actix applications to use during testing.
use std::rc::Rc;
use std::str::FromStr;
use std::sync::mpsc;
use std::{net, thread};
use actix_inner::{Actor, Addr, System};
use actix::{msgs, Actor, Addr, Arbiter, Syn, System, SystemRunner, Unsync};
use cookie::Cookie;
use futures::Future;
use http::header::HeaderName;
use http::{HeaderMap, HttpTryFrom, Method, Uri, Version};
use net2::TcpBuilder;
use tokio::runtime::current_thread::Runtime;
use tokio_core::net::TcpListener;
use tokio_core::reactor::Core;
#[cfg(feature = "alpn")]
use openssl::ssl::SslAcceptorBuilder;
use openssl::ssl::SslAcceptor;
use application::{App, HttpApplication};
use body::Binary;
@@ -27,11 +28,9 @@ use httpresponse::HttpResponse;
use middleware::Middleware;
use param::Params;
use payload::Payload;
use resource::Resource;
use resource::ResourceHandler;
use router::Router;
use server::message::{Request, RequestPool};
use server::{HttpServer, IntoHttpHandler, ServerSettings};
use uri::Url as InnerUrl;
use ws;
/// The `TestServer` type.
@@ -42,10 +41,11 @@ use ws;
/// # Examples
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # use actix_web::*;
/// #
/// # fn my_handler(req: &HttpRequest) -> HttpResponse {
/// # fn my_handler(req: HttpRequest) -> HttpResponse {
/// # HttpResponse::Ok().into()
/// # }
/// #
@@ -61,9 +61,11 @@ use ws;
/// ```
pub struct TestServer {
addr: net::SocketAddr,
thread: Option<thread::JoinHandle<()>>,
system: SystemRunner,
server_sys: Addr<Syn, System>,
ssl: bool,
conn: Addr<ClientConnector>,
rt: Runtime,
conn: Addr<Unsync, ClientConnector>,
}
impl TestServer {
@@ -106,32 +108,34 @@ impl TestServer {
let (tx, rx) = mpsc::channel();
// run server in separate thread
thread::spawn(move || {
let join = thread::spawn(move || {
let sys = System::new("actix-test-server");
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
let local_addr = tcp.local_addr().unwrap();
let tcp =
TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap();
HttpServer::new(factory)
.disable_signals()
.listen(tcp)
.start();
.start_incoming(tcp.incoming(), false);
tx.send((System::current(), local_addr, TestServer::get_conn()))
.unwrap();
sys.run();
tx.send((Arbiter::system(), local_addr)).unwrap();
let _ = sys.run();
});
let (system, addr, conn) = rx.recv().unwrap();
System::set_current(system);
let sys = System::new("actix-test");
let (server_sys, addr) = rx.recv().unwrap();
TestServer {
addr,
conn,
server_sys,
ssl: false,
rt: Runtime::new().unwrap(),
conn: TestServer::get_conn(),
thread: Some(join),
system: sys,
}
}
fn get_conn() -> Addr<ClientConnector> {
fn get_conn() -> Addr<Unsync, ClientConnector> {
#[cfg(feature = "alpn")]
{
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
@@ -182,7 +186,10 @@ impl TestServer {
/// Stop http server
fn stop(&mut self) {
System::current().stop();
if let Some(handle) = self.thread.take() {
self.server_sys.do_send(msgs::SystemExit(0));
let _ = handle.join();
}
}
/// Execute future on current core
@@ -190,7 +197,7 @@ impl TestServer {
where
F: Future<Item = I, Error = E>,
{
self.rt.block_on(fut)
self.system.run_until_complete(fut)
}
/// Connect to websocket server
@@ -198,8 +205,9 @@ impl TestServer {
&mut self,
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
let url = self.url("/");
self.rt
.block_on(ws::Client::with_connector(url, self.conn.clone()).connect())
self.system.run_until_complete(
ws::Client::with_connector(url, self.conn.clone()).connect(),
)
}
/// Create `GET` request
@@ -240,11 +248,10 @@ impl Drop for TestServer {
pub struct TestServerBuilder<S> {
state: Box<Fn() -> S + Sync + Send + 'static>,
#[cfg(feature = "alpn")]
ssl: Option<SslAcceptorBuilder>,
ssl: Option<SslAcceptor>,
}
impl<S: 'static> TestServerBuilder<S> {
/// Create a new test server
pub fn new<F>(state: F) -> TestServerBuilder<S>
where
F: Fn() -> S + Sync + Send + 'static,
@@ -258,7 +265,7 @@ impl<S: 'static> TestServerBuilder<S> {
#[cfg(feature = "alpn")]
/// Create ssl server
pub fn ssl(mut self, ssl: SslAcceptorBuilder) -> Self {
pub fn ssl(mut self, ssl: SslAcceptor) -> Self {
self.ssl = Some(ssl);
self
}
@@ -277,45 +284,60 @@ impl<S: 'static> TestServerBuilder<S> {
let ssl = false;
// run server in separate thread
thread::spawn(move || {
let join = thread::spawn(move || {
let sys = System::new("actix-test-server");
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
let local_addr = tcp.local_addr().unwrap();
let tcp =
TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap();
let sys = System::new("actix-test-server");
let state = self.state;
let srv = HttpServer::new(move || {
let mut app = TestApp::new(state());
config(&mut app);
vec![app]
}).workers(1)
.disable_signals();
tx.send((System::current(), local_addr, TestServer::get_conn()))
.unwrap();
}).disable_signals();
#[cfg(feature = "alpn")]
{
use futures::Stream;
use std::io;
use tokio_openssl::SslAcceptorExt;
let ssl = self.ssl.take();
if let Some(ssl) = ssl {
srv.listen_ssl(tcp, ssl).unwrap().start();
srv.start_incoming(
tcp.incoming().and_then(move |(sock, addr)| {
ssl.accept_async(sock)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
.map(move |s| (s, addr))
}),
false,
);
} else {
srv.listen(tcp).start();
srv.start_incoming(tcp.incoming(), false);
}
}
#[cfg(not(feature = "alpn"))]
{
srv.listen(tcp).start();
srv.start_incoming(tcp.incoming(), false);
}
sys.run();
tx.send((Arbiter::system(), local_addr)).unwrap();
let _ = sys.run();
});
let (system, addr, conn) = rx.recv().unwrap();
System::set_current(system);
let system = System::new("actix-test");
let (server_sys, addr) = rx.recv().unwrap();
TestServer {
addr,
server_sys,
ssl,
conn,
rt: Runtime::new().unwrap(),
system,
conn: TestServer::get_conn(),
thread: Some(join),
}
}
}
@@ -332,12 +354,8 @@ impl<S: 'static> TestApp<S> {
}
/// Register handler for "/"
pub fn handler<F, R>(&mut self, handler: F)
where
F: Fn(&HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
self.app = Some(self.app.take().unwrap().resource("/", |r| r.f(handler)));
pub fn handler<H: Handler<S>>(&mut self, handler: H) {
self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler)));
}
/// Register middleware
@@ -353,7 +371,7 @@ impl<S: 'static> TestApp<S> {
/// to `App::resource()` method.
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut TestApp<S>
where
F: FnOnce(&mut Resource<S>) -> R + 'static,
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
{
self.app = Some(self.app.take().unwrap().resource(path, f));
self
@@ -363,8 +381,8 @@ impl<S: 'static> TestApp<S> {
impl<S: 'static> IntoHttpHandler for TestApp<S> {
type Handler = HttpApplication<S>;
fn into_handler(mut self) -> HttpApplication<S> {
self.app.take().unwrap().into_handler()
fn into_handler(mut self, settings: ServerSettings) -> HttpApplication<S> {
self.app.take().unwrap().into_handler(settings)
}
}
@@ -390,7 +408,7 @@ impl<S: 'static> Iterator for TestApp<S> {
/// # use actix_web::*;
/// use actix_web::test::TestRequest;
///
/// fn index(req: &HttpRequest) -> HttpResponse {
/// fn index(req: HttpRequest) -> HttpResponse {
/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
/// HttpResponse::Ok().into()
/// } else {
@@ -400,11 +418,11 @@ impl<S: 'static> Iterator for TestApp<S> {
///
/// fn main() {
/// let resp = TestRequest::with_header("content-type", "text/plain")
/// .run(&index)
/// .unwrap();
/// .run(index).unwrap();
/// assert_eq!(resp.status(), StatusCode::OK);
///
/// let resp = TestRequest::default().run(&index).unwrap();
/// let resp = TestRequest::default()
/// .run(index).unwrap();
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
/// }
/// ```
@@ -414,10 +432,9 @@ pub struct TestRequest<S> {
method: Method,
uri: Uri,
headers: HeaderMap,
params: Params,
params: Params<'static>,
cookies: Option<Vec<Cookie<'static>>>,
payload: Option<Payload>,
prefix: u16,
}
impl Default for TestRequest<()> {
@@ -431,7 +448,6 @@ impl Default for TestRequest<()> {
params: Params::new(),
cookies: None,
payload: None,
prefix: 0,
}
}
}
@@ -469,7 +485,6 @@ impl<S: 'static> TestRequest<S> {
params: Params::new(),
cookies: None,
payload: None,
prefix: 0,
}
}
@@ -517,7 +532,7 @@ impl<S: 'static> TestRequest<S> {
/// Set request path pattern parameter
pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
self.params.add_static(name, value);
self.params.add(name, value);
self
}
@@ -530,12 +545,6 @@ impl<S: 'static> TestRequest<S> {
self
}
/// Set request's prefix
pub fn prefix(mut self, prefix: u16) -> Self {
self.prefix = prefix;
self
}
/// Complete request creation and generate `HttpRequest` instance
pub fn finish(self) -> HttpRequest<S> {
let TestRequest {
@@ -544,96 +553,44 @@ impl<S: 'static> TestRequest<S> {
uri,
version,
headers,
mut params,
params,
cookies,
payload,
prefix,
} = self;
let router = Router::<()>::new();
let pool = RequestPool::pool(ServerSettings::default());
let mut req = RequestPool::get(pool);
{
let inner = req.inner_mut();
inner.method = method;
inner.url = InnerUrl::new(uri);
inner.version = version;
inner.headers = headers;
*inner.payload.borrow_mut() = payload;
}
params.set_url(req.url().clone());
let mut info = router.route_info_params(0, params);
info.set_prefix(prefix);
let mut req = HttpRequest::new(req, Rc::new(state), info);
let mut req = HttpRequest::new(method, uri, version, headers, payload);
req.set_cookies(cookies);
req
req.as_mut().params = params;
let (router, _) = Router::new::<S>("/", ServerSettings::default(), Vec::new());
req.with_state(Rc::new(state), router)
}
#[cfg(test)]
/// Complete request creation and generate `HttpRequest` instance
pub(crate) fn finish_with_router(self, router: Router<S>) -> HttpRequest<S> {
pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest<S> {
let TestRequest {
state,
method,
uri,
version,
headers,
mut params,
params,
cookies,
payload,
prefix,
} = self;
let pool = RequestPool::pool(ServerSettings::default());
let mut req = RequestPool::get(pool);
{
let inner = req.inner_mut();
inner.method = method;
inner.url = InnerUrl::new(uri);
inner.version = version;
inner.headers = headers;
*inner.payload.borrow_mut() = payload;
}
params.set_url(req.url().clone());
let mut info = router.route_info_params(0, params);
info.set_prefix(prefix);
let mut req = HttpRequest::new(req, Rc::new(state), info);
let mut req = HttpRequest::new(method, uri, version, headers, payload);
req.set_cookies(cookies);
req
}
/// Complete request creation and generate server `Request` instance
pub fn request(self) -> Request {
let TestRequest {
method,
uri,
version,
headers,
payload,
..
} = self;
let pool = RequestPool::pool(ServerSettings::default());
let mut req = RequestPool::get(pool);
{
let inner = req.inner_mut();
inner.method = method;
inner.url = InnerUrl::new(uri);
inner.version = version;
inner.headers = headers;
*inner.payload.borrow_mut() = payload;
}
req
req.as_mut().params = params;
req.with_state(Rc::new(state), router)
}
/// This method generates `HttpRequest` instance and runs handler
/// with generated request.
///
/// This method panics is handler returns actor or async result.
pub fn run<H: Handler<S>>(self, h: &H) -> Result<HttpResponse, Error> {
pub fn run<H: Handler<S>>(self, mut h: H) -> Result<HttpResponse, Error> {
let req = self.finish();
let resp = h.handle(&req);
let resp = h.handle(req.clone());
match resp.respond_to(&req) {
Ok(resp) => match resp.into().into() {
@@ -659,8 +616,8 @@ impl<S: 'static> TestRequest<S> {
let req = self.finish();
let fut = h(req.clone());
let mut core = Runtime::new().unwrap();
match core.block_on(fut) {
let mut core = Core::new().unwrap();
match core.run(fut) {
Ok(r) => match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => Ok(resp),

View File

@@ -1,5 +1,4 @@
use http::Uri;
use std::rc::Rc;
#[allow(dead_code)]
const GEN_DELIMS: &[u8] = b":/?#[]@";
@@ -35,10 +34,10 @@ lazy_static! {
static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") };
}
#[derive(Default, Clone, Debug)]
#[derive(Default)]
pub(crate) struct Url {
uri: Uri,
path: Option<Rc<String>>,
path: Option<String>,
}
impl Url {
@@ -96,7 +95,7 @@ impl Quoter {
q
}
pub fn requote(&self, val: &[u8]) -> Option<Rc<String>> {
pub fn requote(&self, val: &[u8]) -> Option<String> {
let mut has_pct = 0;
let mut pct = [b'%', 0, 0];
let mut idx = 0;
@@ -146,9 +145,7 @@ impl Quoter {
}
if let Some(data) = cloned {
// Unsafe: we get data from http::Uri, which does utf-8 checks already
// this code only decodes valid pct encoded values
Some(unsafe { Rc::new(String::from_utf8_unchecked(data)) })
Some(unsafe { String::from_utf8_unchecked(data) })
} else {
None
}

View File

@@ -1,5 +1,7 @@
use futures::{Async, Future, Poll};
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use error::Error;
@@ -7,14 +9,110 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
pub(crate) struct With<T, S, F, R>
/// 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>>,
}
impl<S: 'static, T: FromRequest<S>> Default for ExtractorConfig<S, T> {
fn default() -> Self {
ExtractorConfig {
cfg: Rc::new(UnsafeCell::new(T::Config::default())),
}
}
}
impl<S: 'static, T: FromRequest<S>> Clone for ExtractorConfig<S, T> {
fn clone(&self) -> Self {
ExtractorConfig {
cfg: Rc::clone(&self.cfg),
}
}
}
impl<S: 'static, T: FromRequest<S>> AsRef<T::Config> for ExtractorConfig<S, T> {
fn as_ref(&self) -> &T::Config {
unsafe { &*self.cfg.get() }
}
}
impl<S: 'static, T: FromRequest<S>> Deref for ExtractorConfig<S, T> {
type Target = T::Config;
fn deref(&self) -> &T::Config {
unsafe { &*self.cfg.get() }
}
}
impl<S: 'static, T: FromRequest<S>> DerefMut for ExtractorConfig<S, T> {
fn deref_mut(&mut self) -> &mut T::Config {
unsafe { &mut *self.cfg.get() }
}
}
pub struct With<T, S, F, R>
where
F: Fn(T) -> R,
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<F>,
cfg: Rc<T::Config>,
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
_s: PhantomData<S>,
}
@@ -24,10 +122,10 @@ where
T: FromRequest<S>,
S: 'static,
{
pub fn new(f: F, cfg: T::Config) -> Self {
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self {
With {
cfg: Rc::new(cfg),
hnd: Rc::new(f),
cfg,
hnd: Rc::new(UnsafeCell::new(f)),
_s: PhantomData,
}
}
@@ -42,9 +140,9 @@ where
{
type Result = AsyncResult<HttpResponse>;
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut {
req: req.clone(),
req,
started: false,
hnd: Rc::clone(&self.hnd),
cfg: self.cfg.clone(),
@@ -68,8 +166,8 @@ where
S: 'static,
{
started: bool,
hnd: Rc<F>,
cfg: Rc<T::Config>,
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
@@ -108,7 +206,8 @@ where
}
};
let item = match (*self.hnd)(item).respond_to(&self.req) {
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)(item).respond_to(&self.req) {
Ok(item) => item.into(),
Err(e) => return Err(e.into()),
};
@@ -124,7 +223,7 @@ where
}
}
pub(crate) struct WithAsync<T, S, F, R, I, E>
pub struct WithAsync<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>,
@@ -133,8 +232,8 @@ where
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<F>,
cfg: Rc<T::Config>,
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
_s: PhantomData<S>,
}
@@ -147,10 +246,10 @@ where
T: FromRequest<S>,
S: 'static,
{
pub fn new(f: F, cfg: T::Config) -> Self {
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self {
WithAsync {
cfg: Rc::new(cfg),
hnd: Rc::new(f),
cfg,
hnd: Rc::new(UnsafeCell::new(f)),
_s: PhantomData,
}
}
@@ -167,12 +266,12 @@ where
{
type Result = AsyncResult<HttpResponse>;
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithAsyncHandlerFut {
req: req.clone(),
req,
started: false,
hnd: Rc::clone(&self.hnd),
cfg: Rc::clone(&self.cfg),
cfg: self.cfg.clone(),
fut1: None,
fut2: None,
fut3: None,
@@ -196,8 +295,8 @@ where
S: 'static,
{
started: bool,
hnd: Rc<F>,
cfg: Rc<T::Config>,
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<R>,
@@ -257,7 +356,458 @@ where
}
};
self.fut2 = Some((*self.hnd)(item));
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,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
_s: PhantomData<S>,
}
impl<T1, T2, S, F, R> With2<T1, T2, S, F, R>
where
F: Fn(T1, T2) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
S: 'static,
{
pub fn new(
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
) -> Self {
With2 {
hnd: Rc::new(UnsafeCell::new(f)),
cfg1,
cfg2,
_s: PhantomData,
}
}
}
impl<T1, T2, S, F, R> Handler<S> for With2<T1, T2, S, F, R>
where
F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut2 {
req,
started: false,
hnd: Rc::clone(&self.hnd),
cfg1: self.cfg1.clone(),
cfg2: self.cfg2.clone(),
item: None,
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::ok(e),
}
}
}
struct WithHandlerFut2<T1, T2, S, F, R>
where
F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
S: 'static,
{
started: bool,
hnd: Rc<UnsafeCell<F>>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
req: HttpRequest<S>,
item: Option<T1>,
fut1: Option<Box<Future<Item = T1, Error = Error>>>,
fut2: Option<Box<Future<Item = T2, Error = Error>>>,
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T1, T2, S, F, R> Future for WithHandlerFut2<T1, T2, S, F, R>
where
F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: 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.started {
self.started = true;
let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
let item1 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
};
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item = Some(item1);
self.fut2 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item1, item2).respond_to(&self.req) {
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut3 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
if self.fut1.is_some() {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => {
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item = Some(item);
self.fut2 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item, item2).respond_to(&self.req) {
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut3 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
Async::NotReady => return Ok(Async::NotReady),
}
}
let item = match self.fut2.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)(self.item.take().unwrap(), item).respond_to(&self.req) {
Ok(item) => item.into(),
Err(err) => return Err(err.into()),
};
match item.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => self.fut3 = Some(fut),
}
self.poll()
}
}
pub struct With3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
cfg3: ExtractorConfig<S, T3>,
_s: PhantomData<S>,
}
impl<T1, T2, T3, S, F, R> With3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
pub fn new(
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
cfg3: ExtractorConfig<S, T3>,
) -> Self {
With3 {
hnd: Rc::new(UnsafeCell::new(f)),
cfg1,
cfg2,
cfg3,
_s: PhantomData,
}
}
}
impl<T1, T2, T3, S, F, R> Handler<S> for With3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S>,
T2: FromRequest<S>,
T3: FromRequest<S>,
T1: 'static,
T2: 'static,
T3: 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut3 {
req,
hnd: Rc::clone(&self.hnd),
cfg1: self.cfg1.clone(),
cfg2: self.cfg2.clone(),
cfg3: self.cfg3.clone(),
started: false,
item1: None,
item2: None,
fut1: None,
fut2: None,
fut3: None,
fut4: 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 WithHandlerFut3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
req: HttpRequest<S>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
cfg3: ExtractorConfig<S, T3>,
started: bool,
item1: Option<T1>,
item2: Option<T2>,
fut1: Option<Box<Future<Item = T1, Error = Error>>>,
fut2: Option<Box<Future<Item = T2, Error = Error>>>,
fut3: Option<Box<Future<Item = T3, Error = Error>>>,
fut4: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T1, T2, T3, S, F, R> Future for WithHandlerFut3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: 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.fut4 {
return fut.poll();
}
if !self.started {
self.started = true;
let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
let item1 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
};
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item1 = Some(item1);
self.fut2 = Some(fut);
return self.poll();
}
};
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
let item3 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item1 = Some(item1);
self.item2 = Some(item2);
self.fut3 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item1, item2, item3).respond_to(&self.req) {
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut4 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
if self.fut1.is_some() {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => {
self.item1 = Some(item);
self.fut1.take();
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut2 = Some(fut);
return self.poll();
}
};
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
let item3 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item2 = Some(item2);
self.fut3 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(self.item1.take().unwrap(), item2, item3)
.respond_to(&self.req)
{
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut4 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
Async::NotReady => return Ok(Async::NotReady),
}
}
if self.fut2.is_some() {
match self.fut2.as_mut().unwrap().poll()? {
Async::Ready(item) => {
self.fut2.take();
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
let item3 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item2 = Some(item);
self.fut3 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(self.item1.take().unwrap(), item, item3)
.respond_to(&self.req)
{
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut4 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
Async::NotReady => return Ok(Async::NotReady),
}
}
let item = match self.fut3.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
};
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()),
};
match item.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => self.fut4 = Some(fut),
}
self.poll()
}
}

View File

@@ -1,5 +1,5 @@
//! Http client request
use std::cell::RefCell;
use std::cell::UnsafeCell;
use std::rc::Rc;
use std::time::Duration;
use std::{fmt, io, str};
@@ -7,73 +7,59 @@ use std::{fmt, io, str};
use base64;
use bytes::Bytes;
use cookie::Cookie;
use futures::sync::mpsc::{unbounded, UnboundedSender};
use futures::unsync::mpsc::{unbounded, UnboundedSender};
use futures::{Async, Future, Poll, Stream};
use http::header::{self, HeaderName, HeaderValue};
use http::{Error as HttpError, HttpTryFrom, StatusCode};
use rand;
use sha1::Sha1;
use actix::{Addr, SystemService};
use actix::prelude::*;
use body::{Binary, Body};
use error::{Error, UrlParseError};
use header::IntoHeaderValue;
use httpmessage::HttpMessage;
use payload::PayloadBuffer;
use payload::PayloadHelper;
use client::{
ClientConnector, ClientRequest, ClientRequestBuilder, HttpResponseParserError,
Pipeline, SendRequest, SendRequestError,
ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse,
HttpResponseParserError, SendRequest, SendRequestError,
};
use super::frame::{Frame, FramedMessage};
use super::frame::Frame;
use super::proto::{CloseReason, OpCode};
use super::{Message, ProtocolError, WsWriter};
/// Websocket client error
#[derive(Fail, Debug)]
pub enum ClientError {
/// Invalid url
#[fail(display = "Invalid url")]
InvalidUrl,
/// Invalid response status
#[fail(display = "Invalid response status")]
InvalidResponseStatus(StatusCode),
/// Invalid upgrade header
#[fail(display = "Invalid upgrade header")]
InvalidUpgradeHeader,
/// Invalid connection header
#[fail(display = "Invalid connection header")]
InvalidConnectionHeader(HeaderValue),
/// Missing CONNECTION header
#[fail(display = "Missing CONNECTION header")]
MissingConnectionHeader,
/// Missing SEC-WEBSOCKET-ACCEPT header
#[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")]
MissingWebSocketAcceptHeader,
/// Invalid challenge response
#[fail(display = "Invalid challenge response")]
InvalidChallengeResponse(String, HeaderValue),
/// Http parsing error
#[fail(display = "Http parsing error")]
Http(Error),
/// Url parsing error
#[fail(display = "Url parsing error")]
Url(UrlParseError),
/// Response parsing error
#[fail(display = "Response parsing error")]
ResponseParseError(HttpResponseParserError),
/// Send request error
#[fail(display = "{}", _0)]
SendRequest(SendRequestError),
/// Protocol error
#[fail(display = "{}", _0)]
Protocol(#[cause] ProtocolError),
/// IO Error
#[fail(display = "{}", _0)]
Io(io::Error),
/// "Disconnected"
#[fail(display = "Disconnected")]
Disconnected,
}
@@ -118,14 +104,14 @@ impl From<HttpResponseParserError> for ClientError {
///
/// Example of `WebSocket` client usage is available in
/// [websocket example](
/// https://github.com/actix/examples/blob/master/websocket/src/client.rs#L24)
/// https://github.com/actix/actix-web/blob/master/examples/websocket/src/client.rs#L24)
pub struct Client {
request: ClientRequestBuilder,
err: Option<ClientError>,
http_err: Option<HttpError>,
origin: Option<HeaderValue>,
protocols: Option<String>,
conn: Addr<ClientConnector>,
conn: Addr<Unsync, ClientConnector>,
max_size: usize,
no_masking: bool,
}
@@ -137,7 +123,9 @@ impl Client {
}
/// Create new websocket connection with custom `ClientConnector`
pub fn with_connector<S: AsRef<str>>(uri: S, conn: Addr<ClientConnector>) -> Client {
pub fn with_connector<S: AsRef<str>>(
uri: S, conn: Addr<Unsync, ClientConnector>,
) -> Client {
let mut cl = Client {
request: ClientRequest::build(),
err: None,
@@ -275,7 +263,7 @@ impl Client {
struct Inner {
tx: UnboundedSender<Bytes>,
rx: PayloadBuffer<Box<Pipeline>>,
rx: PayloadHelper<ClientResponse>,
closed: bool,
}
@@ -431,11 +419,11 @@ impl Future for ClientHandshake {
let inner = Inner {
tx: self.tx.take().unwrap(),
rx: PayloadBuffer::new(resp.payload()),
rx: PayloadHelper::new(resp),
closed: false,
};
let inner = Rc::new(RefCell::new(inner));
let inner = Rc::new(UnsafeCell::new(inner));
Ok(Async::Ready((
ClientReader {
inner: Rc::clone(&inner),
@@ -447,9 +435,8 @@ impl Future for ClientHandshake {
}
}
/// Websocket reader client
pub struct ClientReader {
inner: Rc<RefCell<Inner>>,
inner: Rc<UnsafeCell<Inner>>,
max_size: usize,
no_masking: bool,
}
@@ -460,6 +447,13 @@ impl fmt::Debug for ClientReader {
}
}
impl ClientReader {
#[inline]
fn as_mut(&mut self) -> &mut Inner {
unsafe { &mut *self.inner.get() }
}
}
impl Stream for ClientReader {
type Item = Message;
type Error = ProtocolError;
@@ -467,7 +461,7 @@ 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 mut inner = self.inner.borrow_mut();
let inner = self.as_mut();
if inner.closed {
return Ok(Async::Ready(None));
}
@@ -521,23 +515,26 @@ impl Stream for ClientReader {
}
}
/// Websocket writer client
pub struct ClientWriter {
inner: Rc<RefCell<Inner>>,
inner: Rc<UnsafeCell<Inner>>,
}
impl ClientWriter {
/// Write payload
#[inline]
fn write(&mut self, mut data: FramedMessage) {
let inner = self.inner.borrow_mut();
if !inner.closed {
let _ = inner.tx.unbounded_send(data.0.take());
fn write(&mut self, mut data: Binary) {
if !self.as_mut().closed {
let _ = self.as_mut().tx.unbounded_send(data.take());
} else {
warn!("Trying to write to disconnected response");
}
}
#[inline]
fn as_mut(&mut self) -> &mut Inner {
unsafe { &mut *self.inner.get() }
}
/// Send text frame
#[inline]
pub fn text<T: Into<Binary>>(&mut self, text: T) {

View File

@@ -1,35 +1,30 @@
extern crate actix;
use bytes::Bytes;
use futures::sync::oneshot::{self, Sender};
use futures::{Async, Future, Poll, Stream};
use futures::sync::oneshot::Sender;
use futures::unsync::oneshot;
use futures::{Async, Poll};
use smallvec::SmallVec;
use self::actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
ToEnvelope,
};
use self::actix::fut::ActorFuture;
use self::actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler,
Message as ActixMessage, SpawnHandle,
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
use actix::fut::ActorFuture;
use actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
Syn, Unsync,
};
use body::{Binary, Body};
use context::{ActorHttpContext, Drain, Frame as ContextFrame};
use error::{Error, ErrorInternalServerError, PayloadError};
use error::{Error, ErrorInternalServerError};
use httprequest::HttpRequest;
use ws::frame::{Frame, FramedMessage};
use ws::frame::Frame;
use ws::proto::{CloseReason, OpCode};
use ws::{Message, ProtocolError, WsStream, WsWriter};
use ws::WsWriter;
/// Execution context for `WebSockets` actors
pub struct WebsocketContext<A, S = ()>
where
A: Actor<Context = WebsocketContext<A, S>>,
{
inner: ContextParts<A>,
inner: ContextImpl<A>,
stream: Option<SmallVec<[ContextFrame; 4]>>,
request: HttpRequest<S>,
disconnected: bool,
@@ -80,9 +75,16 @@ where
self.inner.cancel_future(handle)
}
#[doc(hidden)]
#[inline]
fn address(&self) -> Addr<A> {
self.inner.address()
fn unsync_address(&mut self) -> Addr<Unsync, A> {
self.inner.unsync_address()
}
#[doc(hidden)]
#[inline]
fn sync_address(&mut self) -> Addr<Syn, A> {
self.inner.sync_address()
}
}
@@ -91,39 +93,23 @@ where
A: Actor<Context = Self>,
{
#[inline]
/// Create a new Websocket context from a request and an actor
pub fn create<P>(req: HttpRequest<S>, actor: A, stream: WsStream<P>) -> Body
where
A: StreamHandler<Message, ProtocolError>,
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
let mb = Mailbox::default();
let mut ctx = WebsocketContext {
inner: ContextParts::new(mb.sender_producer()),
stream: None,
request: req,
disconnected: false,
};
ctx.add_stream(stream);
Body::Actor(Box::new(WebsocketContextFut::new(ctx, actor, mb)))
pub fn new(req: HttpRequest<S>, actor: A) -> WebsocketContext<A, S> {
WebsocketContext::from_request(req).actor(actor)
}
/// Create a new Websocket context
pub fn with_factory<F>(req: HttpRequest<S>, f: F) -> Body
where
F: FnOnce(&mut Self) -> A + 'static,
{
let mb = Mailbox::default();
let mut ctx = WebsocketContext {
inner: ContextParts::new(mb.sender_producer()),
pub fn from_request(req: HttpRequest<S>) -> WebsocketContext<A, S> {
WebsocketContext {
inner: ContextImpl::new(None),
stream: None,
request: req,
disconnected: false,
};
}
}
let act = f(&mut ctx);
Body::Actor(Box::new(WebsocketContextFut::new(ctx, act, mb)))
#[inline]
pub fn actor(mut self, actor: A) -> WebsocketContext<A, S> {
self.inner.set_actor(actor);
self
}
}
@@ -132,19 +118,15 @@ where
A: Actor<Context = Self>,
{
/// Write payload
///
/// This is a low-level function that accepts framed messages that should
/// be created using `Frame::message()`. If you want to send text or binary
/// data you should prefer the `text()` or `binary()` convenience functions
/// that handle the framing for you.
#[inline]
pub fn write_raw(&mut self, data: FramedMessage) {
fn write(&mut self, data: Binary) {
if !self.disconnected {
if self.stream.is_none() {
self.stream = Some(SmallVec::new());
}
let stream = self.stream.as_mut().unwrap();
stream.push(ContextFrame::Chunk(Some(data.0)));
stream.push(ContextFrame::Chunk(Some(data)));
self.inner.modify();
} else {
warn!("Trying to write to disconnected response");
}
@@ -165,6 +147,7 @@ where
/// Returns drain future
pub fn drain(&mut self) -> Drain<A> {
let (tx, rx) = oneshot::channel();
self.inner.modify();
self.add_frame(ContextFrame::Drain(tx));
Drain::new(rx)
}
@@ -172,19 +155,19 @@ where
/// Send text frame
#[inline]
pub fn text<T: Into<Binary>>(&mut self, text: T) {
self.write_raw(Frame::message(text.into(), OpCode::Text, true, false));
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_raw(Frame::message(data, OpCode::Binary, true, false));
self.write(Frame::message(data, OpCode::Binary, true, false));
}
/// Send ping frame
#[inline]
pub fn ping(&mut self, message: &str) {
self.write_raw(Frame::message(
self.write(Frame::message(
Vec::from(message),
OpCode::Ping,
true,
@@ -195,7 +178,7 @@ where
/// Send pong frame
#[inline]
pub fn pong(&mut self, message: &str) {
self.write_raw(Frame::message(
self.write(Frame::message(
Vec::from(message),
OpCode::Pong,
true,
@@ -206,7 +189,7 @@ where
/// Send close frame
#[inline]
pub fn close(&mut self, reason: Option<CloseReason>) {
self.write_raw(Frame::close(reason, false));
self.write(Frame::close(reason, false));
}
/// Check if connection still open
@@ -223,6 +206,7 @@ where
if let Some(s) = self.stream.as_mut() {
s.push(frame)
}
self.inner.modify();
}
/// Handle of the running future
@@ -269,52 +253,28 @@ where
}
}
impl<A, S> AsyncContextParts<A> for WebsocketContext<A, S>
impl<A, S> ActorHttpContext for WebsocketContext<A, S>
where
A: Actor<Context = Self>,
{
fn parts(&mut self) -> &mut ContextParts<A> {
&mut self.inner
}
}
struct WebsocketContextFut<A, S>
where
A: Actor<Context = WebsocketContext<A, S>>,
{
fut: ContextFut<A, WebsocketContext<A, S>>,
}
impl<A, S> WebsocketContextFut<A, S>
where
A: Actor<Context = WebsocketContext<A, S>>,
{
fn new(ctx: WebsocketContext<A, S>, act: A, mailbox: Mailbox<A>) -> Self {
let fut = ContextFut::new(ctx, act, mailbox);
WebsocketContextFut { fut }
}
}
impl<A, S> ActorHttpContext for WebsocketContextFut<A, S>
where
A: Actor<Context = WebsocketContext<A, S>>,
S: 'static,
{
#[inline]
fn disconnected(&mut self) {
self.fut.ctx().disconnected = true;
self.fut.ctx().stop();
self.disconnected = true;
self.stop();
}
fn poll(&mut self) -> Poll<Option<SmallVec<[ContextFrame; 4]>>, Error> {
if self.fut.alive() && self.fut.poll().is_err() {
let ctx: &mut WebsocketContext<A, S> = unsafe { &mut *(self as *mut _) };
if self.inner.alive() && self.inner.poll(ctx).is_err() {
return Err(ErrorInternalServerError("error"));
}
// frames
if let Some(data) = self.fut.ctx().stream.take() {
if let Some(data) = self.stream.take() {
Ok(Async::Ready(Some(data)))
} else if self.fut.alive() {
} else if self.inner.alive() {
Ok(Async::NotReady)
} else {
Ok(Async::Ready(None))
@@ -322,13 +282,23 @@ where
}
}
impl<A, M, S> ToEnvelope<A, M> for WebsocketContext<A, S>
impl<A, M, S> ToEnvelope<Syn, A, M> for WebsocketContext<A, S>
where
A: Actor<Context = WebsocketContext<A, S>> + Handler<M>,
M: ActixMessage + Send + 'static,
M: Message + Send + 'static,
M::Result: Send,
{
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
Envelope::new(msg, tx)
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> SyncEnvelope<A> {
SyncEnvelope::new(msg, tx)
}
}
impl<A, S> From<WebsocketContext<A, S>> for Body
where
A: Actor<Context = WebsocketContext<A, S>>,
S: 'static,
{
fn from(ctx: WebsocketContext<A, S>) -> Body {
Body::Actor(Box::new(ctx))
}
}

View File

@@ -1,12 +1,13 @@
use byteorder::{ByteOrder, LittleEndian, NetworkEndian};
#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
use byteorder::{BigEndian, ByteOrder, NetworkEndian};
use bytes::{BufMut, Bytes, BytesMut};
use futures::{Async, Poll, Stream};
use rand;
use std::fmt;
use std::{fmt, ptr};
use body::Binary;
use error::PayloadError;
use payload::PayloadBuffer;
use payload::PayloadHelper;
use ws::mask::apply_mask;
use ws::proto::{CloseCode, CloseReason, OpCode};
@@ -28,7 +29,7 @@ impl Frame {
/// Create a new Close control frame.
#[inline]
pub fn close(reason: Option<CloseReason>, genmask: bool) -> FramedMessage {
pub fn close(reason: Option<CloseReason>, genmask: bool) -> Binary {
let payload = match reason {
None => Vec::new(),
Some(reason) => {
@@ -48,7 +49,7 @@ impl Frame {
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
fn read_copy_md<S>(
pl: &mut PayloadBuffer<S>, server: bool, max_size: usize,
pl: &mut PayloadHelper<S>, server: bool, max_size: usize,
) -> Poll<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError>
where
S: Stream<Item = Bytes, Error = PayloadError>,
@@ -94,12 +95,9 @@ impl Frame {
Async::Ready(None) => return Ok(Async::Ready(None)),
Async::NotReady => return Ok(Async::NotReady),
};
let len = NetworkEndian::read_uint(&buf[idx..], 8);
if len > max_size as u64 {
return Err(ProtocolError::Overflow);
}
let len = NetworkEndian::read_uint(&buf[idx..], 8) as usize;
idx += 8;
len as usize
len
} else {
len as usize
};
@@ -117,7 +115,8 @@ impl Frame {
};
let mask: &[u8] = &buf[idx..idx + 4];
let mask_u32 = LittleEndian::read_u32(mask);
let mask_u32: u32 =
unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) };
idx += 4;
Some(mask_u32)
} else {
@@ -168,12 +167,9 @@ impl Frame {
if chunk_len < 10 {
return Ok(Async::NotReady);
}
let len = NetworkEndian::read_uint(&chunk[idx..], 8);
if len > max_size as u64 {
return Err(ProtocolError::Overflow);
}
let len = NetworkEndian::read_uint(&chunk[idx..], 8) as usize;
idx += 8;
len as usize
len
} else {
len as usize
};
@@ -189,7 +185,8 @@ impl Frame {
}
let mask: &[u8] = &chunk[idx..idx + 4];
let mask_u32 = LittleEndian::read_u32(mask);
let mask_u32: u32 =
unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) };
idx += 4;
Some(mask_u32)
} else {
@@ -201,7 +198,7 @@ impl Frame {
/// Parse the input stream into a frame.
pub fn parse<S>(
pl: &mut PayloadBuffer<S>, server: bool, max_size: usize,
pl: &mut PayloadHelper<S>, server: bool, max_size: usize,
) -> Poll<Option<Frame>, ProtocolError>
where
S: Stream<Item = Bytes, Error = PayloadError>,
@@ -230,7 +227,7 @@ impl Frame {
}
// remove prefix
pl.drop_bytes(idx);
pl.drop_payload(idx);
// no need for body
if length == 0 {
@@ -260,14 +257,13 @@ impl Frame {
}
// unmask
let data = if let Some(mask) = mask {
let mut buf = BytesMut::new();
buf.extend_from_slice(&data);
apply_mask(&mut buf, mask);
buf.freeze()
} else {
data
};
if let Some(mask) = mask {
let p: &mut [u8] = unsafe {
let ptr: &[u8] = &data;
&mut *(ptr as *const _ as *mut _)
};
apply_mask(p, mask);
}
Ok(Async::Ready(Some(Frame {
finished,
@@ -279,7 +275,7 @@ impl Frame {
/// Parse the payload of a close frame.
pub fn parse_close_payload(payload: &Binary) -> Option<CloseReason> {
if payload.len() >= 2 {
let raw_code = NetworkEndian::read_u16(payload.as_ref());
let raw_code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
let code = CloseCode::from(raw_code);
let description = if payload.len() > 2 {
Some(String::from_utf8_lossy(&payload.as_ref()[2..]).into())
@@ -295,7 +291,7 @@ impl Frame {
/// Generate binary representation
pub fn message<B: Into<Binary>>(
data: B, code: OpCode, finished: bool, genmask: bool,
) -> FramedMessage {
) -> Binary {
let payload = data.into();
let one: u8 = if finished {
0x80 | Into::<u8>::into(code)
@@ -316,28 +312,39 @@ impl Frame {
} else if payload_len <= 65_535 {
let mut buf = BytesMut::with_capacity(p_len + 4);
buf.put_slice(&[one, two | 126]);
buf.put_u16_be(payload_len as u16);
{
let buf_mut = unsafe { buf.bytes_mut() };
BigEndian::write_u16(&mut buf_mut[..2], payload_len as u16);
}
unsafe { buf.advance_mut(2) };
buf
} else {
let mut buf = BytesMut::with_capacity(p_len + 10);
buf.put_slice(&[one, two | 127]);
buf.put_u64_be(payload_len as u64);
{
let buf_mut = unsafe { buf.bytes_mut() };
BigEndian::write_u64(&mut buf_mut[..8], payload_len as u64);
}
unsafe { buf.advance_mut(8) };
buf
};
let binary = if genmask {
if genmask {
let mask = rand::random::<u32>();
buf.put_u32_le(mask);
buf.extend_from_slice(payload.as_ref());
let pos = buf.len() - payload_len;
apply_mask(&mut buf[pos..], mask);
unsafe {
{
let buf_mut = buf.bytes_mut();
*(buf_mut as *mut _ as *mut u32) = mask;
buf_mut[4..payload_len + 4].copy_from_slice(payload.as_ref());
apply_mask(&mut buf_mut[4..], mask);
}
buf.advance_mut(payload_len + 4);
}
buf.into()
} else {
buf.put_slice(payload.as_ref());
buf.into()
};
FramedMessage(binary)
}
}
}
@@ -374,10 +381,6 @@ impl fmt::Display for Frame {
}
}
/// `WebSocket` message with framing.
#[derive(Debug)]
pub struct FramedMessage(pub(crate) Binary);
#[cfg(test)]
mod tests {
use super::*;
@@ -399,14 +402,14 @@ mod tests {
#[test]
fn test_parse() {
let mut buf = PayloadBuffer::new(once(Ok(BytesMut::from(
let mut buf = PayloadHelper::new(once(Ok(BytesMut::from(
&[0b0000_0001u8, 0b0000_0001u8][..],
).freeze())));
assert!(is_none(&Frame::parse(&mut buf, false, 1024)));
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
buf.extend(b"1");
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished);
@@ -417,7 +420,7 @@ mod tests {
#[test]
fn test_parse_length0() {
let buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]);
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished);
@@ -428,13 +431,13 @@ mod tests {
#[test]
fn test_parse_length2() {
let buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
assert!(is_none(&Frame::parse(&mut buf, false, 1024)));
let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
buf.extend(&[0u8, 4u8][..]);
buf.extend(b"1234");
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished);
@@ -445,13 +448,13 @@ mod tests {
#[test]
fn test_parse_length4() {
let buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
assert!(is_none(&Frame::parse(&mut buf, false, 1024)));
let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]);
buf.extend(b"1234");
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished);
@@ -464,7 +467,7 @@ mod tests {
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]);
buf.extend(b"0001");
buf.extend(b"1");
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
assert!(Frame::parse(&mut buf, false, 1024).is_err());
@@ -478,7 +481,7 @@ mod tests {
fn test_parse_frame_no_mask() {
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
buf.extend(&[1u8]);
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
assert!(Frame::parse(&mut buf, true, 1024).is_err());
@@ -492,7 +495,7 @@ mod tests {
fn test_parse_frame_max_size() {
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]);
buf.extend(&[1u8, 1u8]);
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
assert!(Frame::parse(&mut buf, true, 1).is_err());
@@ -508,7 +511,7 @@ mod tests {
let mut v = vec![137u8, 4u8];
v.extend(b"data");
assert_eq!(frame.0, v.into());
assert_eq!(frame, v.into());
}
#[test]
@@ -517,7 +520,7 @@ mod tests {
let mut v = vec![138u8, 4u8];
v.extend(b"data");
assert_eq!(frame.0, v.into());
assert_eq!(frame, v.into());
}
#[test]
@@ -527,12 +530,12 @@ mod tests {
let mut v = vec![136u8, 6u8, 3u8, 232u8];
v.extend(b"data");
assert_eq!(frame.0, v.into());
assert_eq!(frame, v.into());
}
#[test]
fn test_empty_close_frame() {
let frame = Frame::close(None, false);
assert_eq!(frame.0, vec![0x88, 0x00].into());
assert_eq!(frame, vec![0x88, 0x00].into());
}
}

View File

@@ -1,123 +1,119 @@
//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs)
#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
use std::cmp::min;
use std::mem::uninitialized;
use std::ptr::copy_nonoverlapping;
use std::slice;
// Holds a slice guaranteed to be shorter than 8 bytes
struct ShortSlice<'a>(&'a mut [u8]);
/// Mask/unmask a frame.
#[inline]
pub fn apply_mask(buf: &mut [u8], mask: u32) {
apply_mask_fast32(buf, mask)
}
impl<'a> ShortSlice<'a> {
unsafe fn new(slice: &'a mut [u8]) -> Self {
// Sanity check for debug builds
debug_assert!(slice.len() < 8);
ShortSlice(slice)
}
fn len(&self) -> usize {
self.0.len()
/// A safe unoptimized mask application.
#[inline]
#[allow(dead_code)]
fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) {
for (i, byte) in buf.iter_mut().enumerate() {
*byte ^= mask[i & 3];
}
}
/// Faster version of `apply_mask()` which operates on 8-byte blocks.
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))]
pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
// Extend the mask to 64 bits
let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64);
// Split the buffer into three segments
let (head, mid, tail) = align_buf(buf);
fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) {
let mut ptr = buf.as_mut_ptr();
let mut len = buf.len();
// Initial unaligned segment
let head_len = head.len();
if head_len > 0 {
xor_short(head, mask_u64);
if cfg!(target_endian = "big") {
mask_u64 = mask_u64.rotate_left(8 * head_len as u32);
// Possible first unaligned block.
let head = min(len, (8 - (ptr as usize & 0x7)) & 0x3);
let mask_u32 = if head > 0 {
let n = if head > 4 { head - 4 } else { head };
let mask_u32 = if n > 0 {
unsafe {
xor_mem(ptr, mask_u32, n);
ptr = ptr.offset(head as isize);
}
len -= n;
if cfg!(target_endian = "big") {
mask_u32.rotate_left(8 * n as u32)
} else {
mask_u32.rotate_right(8 * n as u32)
}
} else {
mask_u64 = mask_u64.rotate_right(8 * head_len as u32);
mask_u32
};
if head > 4 {
unsafe {
*(ptr as *mut u32) ^= mask_u32;
ptr = ptr.offset(4);
len -= 4;
}
}
mask_u32
} else {
mask_u32
};
if len > 0 {
debug_assert_eq!(ptr as usize % 4, 0);
}
// Properly aligned middle of the data.
if len >= 8 {
let mut mask_u64 = mask_u32 as u64;
mask_u64 = mask_u64 << 32 | mask_u32 as u64;
while len >= 8 {
unsafe {
*(ptr as *mut u64) ^= mask_u64;
ptr = ptr.offset(8);
len -= 8;
}
}
}
// Aligned segment
for v in mid {
*v ^= mask_u64;
while len >= 4 {
unsafe {
*(ptr as *mut u32) ^= mask_u32;
ptr = ptr.offset(4);
len -= 4;
}
}
// Final unaligned segment
if tail.len() > 0 {
xor_short(tail, mask_u64);
// Possible last block.
if len > 0 {
unsafe {
xor_mem(ptr, mask_u32, len);
}
}
}
#[inline]
// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so
// inefficient, it could be done better. The compiler does not understand that
// a `ShortSlice` must be smaller than a u64.
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
fn xor_short(buf: ShortSlice, mask: u64) {
// Unsafe: we know that a `ShortSlice` fits in a u64
unsafe {
let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len());
let mut b: u64 = 0;
#[allow(trivial_casts)]
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
b ^= mask;
#[allow(trivial_casts)]
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len);
}
}
#[inline]
// Unsafe: caller must ensure the buffer has the correct size and alignment
unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
// Assert correct size and alignment in debug builds
debug_assert!(buf.len().trailing_zeros() >= 3);
debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3);
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
}
#[inline]
// Splits a slice into three parts: an unaligned short head and tail, plus an aligned
// u64 mid section.
fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) {
let start_ptr = buf.as_ptr() as usize;
let end_ptr = start_ptr + buf.len();
// Round *up* to next aligned boundary for start
let start_aligned = (start_ptr + 7) & !0x7;
// Round *down* to last aligned boundary for end
let end_aligned = end_ptr & !0x7;
if end_aligned >= start_aligned {
// We have our three segments (head, mid, tail)
let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr);
let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr);
// Unsafe: we know the middle section is correctly aligned, and the outer
// sections are smaller than 8 bytes
unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) }
} else {
// We didn't cross even one aligned boundary!
// Unsafe: The outer sections are smaller than 8 bytes
unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) }
}
// inefficient, it could be done better. The compiler does not see that len is
// limited to 3.
unsafe fn xor_mem(ptr: *mut u8, mask: u32, len: usize) {
let mut b: u32 = uninitialized();
#[allow(trivial_casts)]
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
b ^= mask;
#[allow(trivial_casts)]
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len);
}
#[cfg(test)]
mod tests {
use super::apply_mask;
use byteorder::{ByteOrder, LittleEndian};
/// A safe unoptimized mask application.
fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) {
for (i, byte) in buf.iter_mut().enumerate() {
*byte ^= mask[i & 3];
}
}
use super::{apply_mask_fallback, apply_mask_fast32};
use std::ptr;
#[test]
fn test_apply_mask() {
let mask = [0x6d, 0xb6, 0xb2, 0x80];
let mask_u32: u32 = LittleEndian::read_u32(&mask);
let mask_u32: u32 = unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) };
let unmasked = vec![
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
@@ -130,7 +126,7 @@ mod tests {
apply_mask_fallback(&mut masked, &mask);
let mut masked_fast = unmasked.clone();
apply_mask(&mut masked_fast, mask_u32);
apply_mask_fast32(&mut masked_fast, mask_u32);
assert_eq!(masked, masked_fast);
}
@@ -141,7 +137,7 @@ mod tests {
apply_mask_fallback(&mut masked[1..], &mask);
let mut masked_fast = unmasked.clone();
apply_mask(&mut masked_fast[1..], mask_u32);
apply_mask_fast32(&mut masked_fast[1..], mask_u32);
assert_eq!(masked, masked_fast);
}

View File

@@ -7,13 +7,14 @@
//! ## Example
//!
//! ```rust
//! # extern crate actix;
//! # extern crate actix_web;
//! # use actix_web::actix::*;
//! # use actix::*;
//! # use actix_web::*;
//! use actix_web::{ws, HttpRequest, HttpResponse};
//!
//! // do websocket handshake and start actor
//! fn ws_index(req: &HttpRequest) -> Result<HttpResponse> {
//! fn ws_index(req: HttpRequest) -> Result<HttpResponse> {
//! ws::start(req, Ws)
//! }
//!
@@ -25,6 +26,7 @@
//!
//! // Handler for ws::Message messages
//! impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
//!
//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
//! match msg {
//! ws::Message::Ping(msg) => ctx.pong(&msg),
@@ -45,14 +47,14 @@ use bytes::Bytes;
use futures::{Async, Poll, Stream};
use http::{header, Method, StatusCode};
use super::actix::{Actor, StreamHandler};
use actix::{Actor, AsyncContext, StreamHandler};
use body::Binary;
use error::{Error, PayloadError, ResponseError};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder};
use payload::PayloadBuffer;
use payload::PayloadHelper;
mod client;
mod context;
@@ -64,7 +66,7 @@ pub use self::client::{
Client, ClientError, ClientHandshake, ClientReader, ClientWriter,
};
pub use self::context::WebsocketContext;
pub use self::frame::{Frame, FramedMessage};
pub use self::frame::Frame;
pub use self::proto::{CloseCode, CloseReason, OpCode};
/// Websocket protocol errors
@@ -158,29 +160,26 @@ impl ResponseError for HandshakeError {
/// `WebSocket` Message
#[derive(Debug, PartialEq, Message)]
pub enum Message {
/// Text message
Text(String),
/// Binary message
Binary(Binary),
/// Ping message
Ping(String),
/// Pong message
Pong(String),
/// Close message with optional reason
Close(Option<CloseReason>),
}
/// Do websocket handshake and start actor
pub fn start<A, S>(req: &HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
pub fn start<A, S>(req: HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
where
A: Actor<Context = WebsocketContext<A, S>> + StreamHandler<Message, ProtocolError>,
S: 'static,
{
let mut resp = handshake(req)?;
let stream = WsStream::new(req.payload());
let mut resp = handshake(&req)?;
let stream = WsStream::new(req.clone());
let body = WebsocketContext::create(req.clone(), actor, stream);
Ok(resp.body(body))
let mut ctx = WebsocketContext::new(req, actor);
ctx.add_stream(stream);
Ok(resp.body(ctx))
}
/// Prepare `WebSocket` handshake response.
@@ -252,7 +251,7 @@ pub fn handshake<S>(
/// Maps `Payload` stream into stream of `ws::Message` items
pub struct WsStream<S> {
rx: PayloadBuffer<S>,
rx: PayloadHelper<S>,
closed: bool,
max_size: usize,
}
@@ -264,7 +263,7 @@ where
/// Create new websocket frames stream
pub fn new(stream: S) -> WsStream<S> {
WsStream {
rx: PayloadBuffer::new(stream),
rx: PayloadHelper::new(stream),
closed: false,
max_size: 65_536,
}
@@ -358,113 +357,161 @@ pub trait WsWriter {
#[cfg(test)]
mod tests {
use super::*;
use http::{header, Method};
use test::TestRequest;
use http::{header, HeaderMap, Method, Uri, Version};
use std::str::FromStr;
#[test]
fn test_handshake() {
let req = TestRequest::default().method(Method::POST).finish();
let req = HttpRequest::new(
Method::POST,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert_eq!(
HandshakeError::GetMethodRequired,
handshake(&req).err().unwrap()
);
let req = TestRequest::default().finish();
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert_eq!(
HandshakeError::NoWebsocketUpgrade,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(header::UPGRADE, header::HeaderValue::from_static("test"))
.finish();
let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE, header::HeaderValue::from_static("test"));
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
assert_eq!(
HandshakeError::NoWebsocketUpgrade,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.finish();
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
assert_eq!(
HandshakeError::NoConnectionUpgrade,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.finish();
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
);
headers.insert(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
assert_eq!(
HandshakeError::NoVersionHeader,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("5"),
)
.finish();
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
);
headers.insert(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
);
headers.insert(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("5"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
assert_eq!(
HandshakeError::UnsupportedVersion,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
.finish();
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
);
headers.insert(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
);
headers.insert(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
assert_eq!(
HandshakeError::BadWebsocketKey,
handshake(&req).err().unwrap()
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
)
.header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
)
.header(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
)
.header(
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
)
.finish();
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
);
headers.insert(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
);
headers.insert(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
);
headers.insert(
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
assert_eq!(
StatusCode::SWITCHING_PROTOCOLS,
handshake(&req).unwrap().finish().status()

View File

@@ -180,11 +180,8 @@ impl From<u16> for CloseCode {
}
#[derive(Debug, Eq, PartialEq, Clone)]
/// Reason for closing the connection
pub struct CloseReason {
/// Exit code
pub code: CloseCode,
/// Optional description of the exit code
pub description: Option<String>,
}

View File

@@ -67,7 +67,7 @@ fn test_simple() {
#[test]
fn test_with_query_parameter() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| match req.query().get("qp") {
app.handler(|req: HttpRequest| match req.query().get("qp") {
Some(_) => HttpResponse::Ok().finish(),
None => HttpResponse::BadRequest().finish(),
})
@@ -110,7 +110,7 @@ fn test_no_decompress() {
#[test]
fn test_client_gzip_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -140,7 +140,7 @@ fn test_client_gzip_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -173,7 +173,7 @@ fn test_client_gzip_encoding_large_random() {
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -202,7 +202,7 @@ fn test_client_gzip_encoding_large_random() {
#[test]
fn test_client_brotli_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -236,7 +236,7 @@ fn test_client_brotli_encoding_large_random() {
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(move |bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -266,7 +266,7 @@ fn test_client_brotli_encoding_large_random() {
#[test]
fn test_client_deflate_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -300,7 +300,7 @@ fn test_client_deflate_encoding_large_random() {
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -328,7 +328,7 @@ fn test_client_deflate_encoding_large_random() {
#[test]
fn test_client_streaming_explicit() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.map_err(Error::from)
.and_then(|body| {
@@ -393,7 +393,7 @@ fn test_client_cookie_handling() {
let mut srv = test::TestServer::new(move |app| {
let cookie1 = cookie1b.clone();
let cookie2 = cookie2b.clone();
app.handler(move |req: &HttpRequest| {
app.handler(move |req: HttpRequest| {
// Check cookies were sent correctly
req.cookie("cookie1").ok_or_else(err)
.and_then(|c1| if c1.value() == "value1" {
@@ -425,37 +425,7 @@ fn test_client_cookie_handling() {
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
let c1 = response.cookie("cookie1").expect("Missing cookie1");
assert_eq!(c1, cookie1);
assert_eq!(c1, &cookie1);
let c2 = response.cookie("cookie2").expect("Missing cookie2");
assert_eq!(c2, cookie2);
}
#[test]
fn test_default_headers() {
let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let request = srv.get().finish().unwrap();
let repr = format!("{:?}", request);
assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
assert!(repr.contains(concat!(
"\"user-agent\": \"Actix-web/",
env!("CARGO_PKG_VERSION"),
"\""
)));
let request_override = srv
.get()
.header("User-Agent", "test")
.header("Accept-Encoding", "over_test")
.finish()
.unwrap();
let repr_override = format!("{:?}", request_override);
assert!(repr_override.contains("\"user-agent\": \"test\""));
assert!(repr_override.contains("\"accept-encoding\": \"over_test\""));
assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\""));
assert!(!repr_override.contains(concat!(
"\"user-agent\": \"Actix-web/",
env!("CARGO_PKG_VERSION"),
"\""
)));
assert_eq!(c2, &cookie2);
}

View File

@@ -4,20 +4,21 @@ extern crate bytes;
extern crate futures;
extern crate h2;
extern crate http;
extern crate tokio_timer;
extern crate tokio_core;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use std::io;
use std::time::{Duration, Instant};
use std::time::Duration;
use actix::*;
use actix_web::*;
use bytes::Bytes;
use futures::Future;
use http::StatusCode;
use serde_json::Value;
use tokio_timer::Delay;
use tokio_core::reactor::Timeout;
#[derive(Deserialize)]
struct PParam {
@@ -42,28 +43,6 @@ fn test_path_extractor() {
assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
}
#[test]
fn test_async_handler() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|p: Path<PParam>| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| Ok(format!("Welcome {}!", p.username)))
.responder()
})
});
});
// client request
let request = srv.get().uri(srv.url("/test/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 test!"));
}
#[test]
fn test_query_extractor() {
let mut srv = test::TestServer::new(|app| {
@@ -91,65 +70,13 @@ fn test_query_extractor() {
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[derive(Deserialize, Debug)]
pub enum ResponseType {
Token,
Code,
}
#[derive(Debug, Deserialize)]
pub struct AuthRequest {
id: u64,
response_type: ResponseType,
}
#[test]
fn test_query_enum_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/index.html", |r| {
r.with(|p: Query<AuthRequest>| format!("{:?}", p.into_inner()))
});
});
// client request
let request = srv
.get()
.uri(srv.url("/index.html?id=64&response_type=Code"))
.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"AuthRequest { id: 64, response_type: Code }")
);
let request = srv
.get()
.uri(srv.url("/index.html?id=64&response_type=Co"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv
.get()
.uri(srv.url("/index.html?response_type=Code"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_async_extractor_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|data: Json<Value>| {
Delay::new(Instant::now() + Duration::from_millis(10))
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| Ok(format!("{}", data.0)))
.responder()
})
@@ -171,70 +98,11 @@ fn test_async_extractor_async() {
assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}"));
}
#[derive(Deserialize, Serialize)]
struct FormData {
username: String,
}
#[test]
fn test_form_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|form: Form<FormData>| format!("{}", form.username))
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.form(FormData {
username: "test".to_string(),
})
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"test"));
}
#[test]
fn test_form_extractor2() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_config(
|form: Form<FormData>| format!("{}", form.username),
|cfg| {
cfg.error_handler(|err, _| {
error::InternalError::from_response(
err,
HttpResponse::Conflict().finish(),
).into()
});
},
);
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/x-www-form-urlencoded")
.body("918237129hdk:D:D:D:D:D:DjASHDKJhaswkjeq")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_client_error());
}
#[test]
fn test_path_and_query_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|(p, q): (Path<PParam>, Query<PParam>)| {
r.route().with2(|p: Path<PParam>, q: Query<PParam>| {
format!("Welcome {} - {}!", p.username, q.username)
})
});
@@ -268,7 +136,7 @@ fn test_path_and_query_extractor2() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|(_r, p, q): (HttpRequest, Path<PParam>, Query<PParam>)| {
.with3(|_: HttpRequest, p: Path<PParam>, q: Query<PParam>| {
format!("Welcome {} - {}!", p.username, q.username)
})
});
@@ -301,15 +169,15 @@ 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().with(
|(p, _q, data): (Path<PParam>, Query<PParam>, Json<Value>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
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()
},
)
})
});
});
@@ -332,8 +200,9 @@ fn test_path_and_query_extractor2_async() {
fn test_path_and_query_extractor3_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|(p, data): (Path<PParam>, Json<Value>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
r.route().with2(|p: Path<PParam>, data: Json<Value>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
})
@@ -357,8 +226,9 @@ fn test_path_and_query_extractor3_async() {
fn test_path_and_query_extractor4_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|(data, p): (Json<Value>, Path<PParam>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
r.route().with2(|data: Json<Value>, p: Path<PParam>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
})
@@ -382,15 +252,15 @@ 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().with(
|(p, data, _q): (Path<PParam>, Json<Value>, Query<PParam>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
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()
},
)
})
});
});
@@ -422,15 +292,15 @@ 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().with(
|(data, p, _q): (Json<Value>, Path<PParam>, Query<PParam>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
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()
},
)
})
});
});
@@ -464,7 +334,8 @@ fn test_path_and_query_extractor2_async4() {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|data: (Json<Value>, Path<PParam>, Query<PParam>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))
})
@@ -572,8 +443,8 @@ fn test_nested_scope_and_path_extractor() {
fn test_impl_trait(
data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Delay::new(Instant::now() + Duration::from_millis(10))
.map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout"))
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)))
}
@@ -581,8 +452,8 @@ fn test_impl_trait(
fn test_impl_trait_err(
_data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Delay::new(Instant::now() + Duration::from_millis(10))
.map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout"))
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other")))
}

View File

@@ -1,17 +1,18 @@
extern crate actix;
extern crate actix_web;
extern crate futures;
extern crate tokio_timer;
extern crate tokio_core;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use std::time::Duration;
use actix::*;
use actix_web::error::{Error, ErrorInternalServerError};
use actix_web::*;
use futures::{future, Future};
use tokio_timer::Delay;
use tokio_core::reactor::Timeout;
struct MiddlewareTest {
start: Arc<AtomicUsize>,
@@ -20,21 +21,21 @@ struct MiddlewareTest {
}
impl<S> middleware::Middleware<S> for MiddlewareTest {
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started> {
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
self.start
.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
Ok(middleware::Started::Done)
}
fn response(
&self, _: &HttpRequest<S>, resp: HttpResponse,
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<middleware::Response> {
self.response
.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
Ok(middleware::Response::Done(resp))
}
fn finish(&self, _: &HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
self.finish
.store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
middleware::Finished::Done
@@ -245,7 +246,8 @@ fn test_middleware_async_handler() {
})
.resource("/", |r| {
r.route().a(|_| {
Delay::new(Instant::now() + Duration::from_millis(10))
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(|_| Ok(HttpResponse::Ok()))
})
})
@@ -280,7 +282,8 @@ fn test_resource_middleware_async_handler() {
App::new().resource("/test", |r| {
r.middleware(mw);
r.route().a(|_| {
Delay::new(Instant::now() + Duration::from_millis(10))
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(|_| Ok(HttpResponse::Ok()))
})
})
@@ -315,7 +318,8 @@ fn test_scope_middleware_async_handler() {
})
.resource("/test", |r| {
r.route().a(|_| {
Delay::new(Instant::now() + Duration::from_millis(10))
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(|_| Ok(HttpResponse::Ok()))
})
})
@@ -331,7 +335,7 @@ fn test_scope_middleware_async_handler() {
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
fn index_test_middleware_async_error(_: &HttpRequest) -> FutureResponse<HttpResponse> {
fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse<HttpResponse> {
future::result(Err(error::ErrorBadRequest("TEST"))).responder()
}
@@ -412,7 +416,7 @@ fn test_resource_middleware_async_error() {
App::new().resource("/test", move |r| {
r.middleware(mw);
r.f(index_test_middleware_async_error);
r.h(index_test_middleware_async_error);
})
});
@@ -432,8 +436,8 @@ struct MiddlewareAsyncTest {
}
impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started> {
let to = Delay::new(Instant::now() + Duration::from_millis(10));
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
let start = Arc::clone(&self.start);
Ok(middleware::Started::Future(Box::new(
@@ -445,9 +449,9 @@ impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
}
fn response(
&self, _: &HttpRequest<S>, resp: HttpResponse,
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<middleware::Response> {
let to = Delay::new(Instant::now() + Duration::from_millis(10));
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
let response = Arc::clone(&self.response);
Ok(middleware::Response::Future(Box::new(
@@ -458,8 +462,8 @@ impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
)))
}
fn finish(&self, _: &HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
let to = Delay::new(Instant::now() + Duration::from_millis(10));
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
let finish = Arc::clone(&self.finish);
middleware::Finished::Future(Box::new(to.from_err().and_then(move |_| {
@@ -697,7 +701,7 @@ fn test_async_resource_middleware() {
};
App::new().resource("/test", move |r| {
r.middleware(mw);
r.f(|_| HttpResponse::Ok());
r.h(|_| HttpResponse::Ok());
})
});
@@ -736,7 +740,7 @@ fn test_async_resource_middleware_multiple() {
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(mw2);
r.f(|_| HttpResponse::Ok());
r.h(|_| HttpResponse::Ok());
})
});
@@ -775,7 +779,7 @@ fn test_async_sync_resource_middleware_multiple() {
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(mw2);
r.f(|_| HttpResponse::Ok());
r.h(|_| HttpResponse::Ok());
})
});
@@ -793,7 +797,7 @@ fn test_async_sync_resource_middleware_multiple() {
struct MiddlewareWithErr;
impl<S> middleware::Middleware<S> for MiddlewareWithErr {
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started, Error> {
fn start(&self, _req: &mut HttpRequest<S>) -> Result<middleware::Started, Error> {
Err(ErrorInternalServerError("middleware error"))
}
}
@@ -801,7 +805,7 @@ impl<S> middleware::Middleware<S> for MiddlewareWithErr {
struct MiddlewareAsyncWithErr;
impl<S> middleware::Middleware<S> for MiddlewareAsyncWithErr {
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started, Error> {
fn start(&self, _req: &mut HttpRequest<S>) -> Result<middleware::Started, Error> {
Ok(middleware::Started::Future(Box::new(future::err(
ErrorInternalServerError("middleware error"),
))))
@@ -827,11 +831,11 @@ fn test_middleware_chain_with_error() {
App::new()
.middleware(mw1)
.middleware(MiddlewareWithErr)
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
@@ -857,11 +861,11 @@ fn test_middleware_async_chain_with_error() {
App::new()
.middleware(mw1)
.middleware(MiddlewareAsyncWithErr)
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
@@ -888,12 +892,12 @@ fn test_scope_middleware_chain_with_error() {
scope
.middleware(mw1)
.middleware(MiddlewareWithErr)
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
@@ -920,12 +924,12 @@ fn test_scope_middleware_async_chain_with_error() {
scope
.middleware(mw1)
.middleware(MiddlewareAsyncWithErr)
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
@@ -951,12 +955,12 @@ fn test_resource_middleware_chain_with_error() {
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(MiddlewareWithErr);
r.f(|_| HttpResponse::Ok());
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
@@ -982,93 +986,14 @@ fn test_resource_middleware_async_chain_with_error() {
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(MiddlewareAsyncWithErr);
r.f(|_| HttpResponse::Ok());
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
srv.execute(request.send()).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);
}
#[cfg(feature = "session")]
#[test]
fn test_session_storage_middleware() {
use actix_web::middleware::session::{
CookieSessionBackend, RequestSession, SessionStorage,
};
const SIMPLE_NAME: &'static str = "simple";
const SIMPLE_PAYLOAD: &'static str = "kantan";
const COMPLEX_NAME: &'static str = "test";
const COMPLEX_PAYLOAD: &'static str = "url=https://test.com&generate_204";
//TODO: investigate how to handle below input
//const COMPLEX_PAYLOAD: &'static str = "FJc%26continue_url%3Dhttp%253A%252F%252Fconnectivitycheck.gstatic.com%252Fgenerate_204";
let mut srv = test::TestServer::with_factory(move || {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
))
.resource("/index", move |r| {
r.f(|req| {
let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD);
assert!(res.is_ok());
let value = req.session().get::<String>(COMPLEX_NAME);
assert!(value.is_ok());
let value = value.unwrap();
assert!(value.is_some());
assert_eq!(value.unwrap(), COMPLEX_PAYLOAD);
let res = req.session().set(SIMPLE_NAME, SIMPLE_PAYLOAD);
assert!(res.is_ok());
let value = req.session().get::<String>(SIMPLE_NAME);
assert!(value.is_ok());
let value = value.unwrap();
assert!(value.is_some());
assert_eq!(value.unwrap(), SIMPLE_PAYLOAD);
HttpResponse::Ok()
})
})
.resource("/expect_cookie", move |r| {
r.f(|req| {
let _cookies = req.cookies().expect("To get cookies");
let value = req.session().get::<String>(SIMPLE_NAME);
assert!(value.is_ok());
let value = value.unwrap();
assert!(value.is_some());
assert_eq!(value.unwrap(), SIMPLE_PAYLOAD);
let value = req.session().get::<String>(COMPLEX_NAME);
assert!(value.is_ok());
let value = value.unwrap();
assert!(value.is_some());
assert_eq!(value.unwrap(), COMPLEX_PAYLOAD);
HttpResponse::Ok()
})
})
});
let request = srv.get().uri(srv.url("/index")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.headers().contains_key("set-cookie"));
let set_cookie = response.headers().get("set-cookie");
assert!(set_cookie.is_some());
let set_cookie = set_cookie.unwrap().to_str().expect("Convert to str");
let request = srv
.get()
.uri(srv.url("/expect_cookie"))
.header("cookie", set_cookie.split(';').next().unwrap())
.finish()
.unwrap();
srv.execute(request.send()).unwrap();
}

View File

@@ -1,20 +1,15 @@
extern crate actix;
extern crate actix_web;
#[cfg(feature = "brotli")]
extern crate brotli2;
extern crate bytes;
extern crate flate2;
extern crate futures;
extern crate h2;
extern crate http as modhttp;
extern crate rand;
extern crate tokio;
extern crate tokio_reactor;
extern crate tokio_tcp;
extern crate tokio_core;
use std::io::{Read, Write};
use std::sync::{mpsc, Arc};
use std::{net, thread, time};
#[cfg(feature = "brotli")]
extern crate brotli2;
#[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder};
@@ -26,11 +21,12 @@ use futures::stream::once;
use futures::{Future, Stream};
use h2::client as h2client;
use modhttp::Request;
use rand::distributions::Alphanumeric;
use rand::Rng;
use tokio::executor::current_thread;
use tokio::runtime::current_thread::Runtime;
use tokio_tcp::TcpStream;
use std::io::{Read, Write};
use std::sync::{mpsc, Arc};
use std::{net, thread, time};
use tokio_core::net::TcpStream;
use tokio_core::reactor::Core;
use actix::System;
use actix_web::*;
@@ -63,29 +59,28 @@ fn test_start() {
let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel();
thread::spawn(|| {
System::run(move || {
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
});
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
let srv_addr = srv.start();
let _ = tx.send((addr, srv_addr, System::current()));
thread::spawn(move || {
let sys = System::new("test");
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
});
});
let (addr, srv_addr, sys) = rx.recv().unwrap();
System::set_current(sys.clone());
let mut rt = Runtime::new().unwrap();
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
let srv_addr = srv.start();
let _ = tx.send((addr, srv_addr));
sys.run();
});
let (addr, srv_addr) = rx.recv().unwrap();
let mut sys = System::new("test-server");
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send()).unwrap();
let response = sys.run_until_complete(req.send()).unwrap();
assert!(response.status().is_success());
}
@@ -97,21 +92,19 @@ fn test_start() {
.timeout(time::Duration::from_millis(200))
.finish()
.unwrap();
assert!(rt.block_on(req.send()).is_err());
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()
.unwrap();
let response = rt.block_on(req.send()).unwrap();
let response = sys.run_until_complete(req.send()).unwrap();
assert!(response.status().is_success());
}
let _ = sys.stop();
}
#[test]
@@ -120,37 +113,35 @@ fn test_shutdown() {
let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel();
thread::spawn(|| {
System::run(move || {
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
});
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
let srv_addr = srv.shutdown_timeout(1).start();
let _ = tx.send((addr, srv_addr, System::current()));
thread::spawn(move || {
let sys = System::new("test");
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
});
});
let (addr, srv_addr, sys) = rx.recv().unwrap();
System::set_current(sys.clone());
let mut rt = Runtime::new().unwrap();
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
let srv_addr = srv.shutdown_timeout(1).start();
let _ = tx.send((addr, srv_addr));
sys.run();
});
let (addr, srv_addr) = rx.recv().unwrap();
let mut sys = System::new("test-server");
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send()).unwrap();
let response = sys.run_until_complete(req.send()).unwrap();
srv_addr.do_send(server::StopServer { graceful: true });
assert!(response.status().is_success());
}
thread::sleep(time::Duration::from_millis(1000));
assert!(net::TcpStream::connect(addr).is_err());
let _ = sys.stop();
}
#[test]
@@ -269,7 +260,7 @@ fn test_body_gzip_large() {
#[test]
fn test_body_gzip_large_random() {
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.gen_ascii_chars()
.take(70_000)
.collect::<String>();
let srv_data = Arc::new(data.clone());
@@ -368,8 +359,8 @@ fn test_head_empty() {
}
// read response
let bytes = srv.execute(response.body()).unwrap();
assert!(bytes.is_empty());
//let bytes = srv.execute(response.body()).unwrap();
//assert!(bytes.is_empty());
}
#[test]
@@ -396,8 +387,8 @@ fn test_head_binary() {
}
// read response
let bytes = srv.execute(response.body()).unwrap();
assert!(bytes.is_empty());
//let bytes = srv.execute(response.body()).unwrap();
//assert!(bytes.is_empty());
}
#[test]
@@ -470,39 +461,6 @@ fn test_body_chunked_explicit() {
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_body_identity() {
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let enc = e.finish().unwrap();
let enc2 = enc.clone();
let mut srv = test::TestServer::new(move |app| {
let enc3 = enc2.clone();
app.handler(move |_| {
HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.header(http::header::CONTENT_ENCODING, "deflate")
.body(enc3.clone())
})
});
// client request
let request = srv
.get()
.header("accept-encoding", "deflate")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
// decode deflate
assert_eq!(bytes, Bytes::from(STR));
}
#[test]
fn test_body_deflate() {
let mut srv = test::TestServer::new(|app| {
@@ -557,7 +515,7 @@ fn test_body_brotli() {
#[test]
fn test_gzip_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -590,7 +548,7 @@ fn test_gzip_encoding() {
fn test_gzip_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -622,12 +580,12 @@ fn test_gzip_encoding_large() {
#[test]
fn test_reading_gzip_encoding_large_random() {
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.gen_ascii_chars()
.take(60_000)
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -660,7 +618,7 @@ fn test_reading_gzip_encoding_large_random() {
#[test]
fn test_reading_deflate_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -693,7 +651,7 @@ fn test_reading_deflate_encoding() {
fn test_reading_deflate_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -725,12 +683,12 @@ fn test_reading_deflate_encoding_large() {
#[test]
fn test_reading_deflate_encoding_large_random() {
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.gen_ascii_chars()
.take(160_000)
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -764,7 +722,7 @@ fn test_reading_deflate_encoding_large_random() {
#[test]
fn test_brotli_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -798,7 +756,7 @@ fn test_brotli_encoding() {
fn test_brotli_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| {
app.handler(|req: &HttpRequest| {
app.handler(|req: HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
@@ -832,8 +790,9 @@ fn test_h2() {
let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let addr = srv.addr();
let mut core = Runtime::new().unwrap();
let tcp = TcpStream::connect(&addr);
let mut core = Core::new().unwrap();
let handle = core.handle();
let tcp = TcpStream::connect(&addr, &handle);
let tcp = tcp
.then(|res| h2client::handshake(res.unwrap()))
@@ -847,7 +806,7 @@ fn test_h2() {
let (response, _) = client.send_request(request, false).unwrap();
// Spawn a task to run the conn...
current_thread::spawn(h2.map_err(|e| println!("GOT ERR={:?}", e)));
handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e)));
response.and_then(|response| {
assert_eq!(response.status(), http::StatusCode::OK);
@@ -860,7 +819,7 @@ fn test_h2() {
})
})
});
let _res = core.block_on(tcp);
let _res = core.run(tcp);
// assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref()));
}

View File

@@ -7,7 +7,6 @@ extern crate rand;
use bytes::Bytes;
use futures::Stream;
use rand::distributions::Alphanumeric;
use rand::Rng;
#[cfg(feature = "alpn")]
@@ -87,7 +86,7 @@ fn test_close_description() {
#[test]
fn test_large_text() {
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.gen_ascii_chars()
.take(65_536)
.collect::<String>();
@@ -105,7 +104,7 @@ fn test_large_text() {
#[test]
fn test_large_bin() {
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.gen_ascii_chars()
.take(65_536)
.collect::<String>();
@@ -120,31 +119,6 @@ fn test_large_bin() {
}
}
#[test]
fn test_client_frame_size() {
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(131_072)
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req| -> Result<HttpResponse> {
let mut resp = ws::handshake(req)?;
let stream = ws::WsStream::new(req.payload()).max_size(131_072);
let body = ws::WebsocketContext::create(req.clone(), Ws, stream);
Ok(resp.body(body))
})
});
let (reader, mut writer) = srv.ws().unwrap();
writer.binary(data.clone());
match srv.execute(reader.into_future()).err().unwrap().0 {
ws::ProtocolError::Overflow => (),
_ => panic!(),
}
}
struct Ws2 {
count: usize,
bin: bool,
@@ -252,7 +226,7 @@ fn test_ws_server_ssl() {
.set_certificate_chain_file("tests/cert.pem")
.unwrap();
let mut srv = test::TestServer::build().ssl(builder).start(|app| {
let mut srv = test::TestServer::build().ssl(builder.build()).start(|app| {
app.handler(|req| {
ws::start(
req,

21
tools/wsload/Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "wsclient"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "wsclient"
path = "src/wsclient.rs"
[dependencies]
env_logger = "*"
futures = "0.1"
clap = "2"
url = "1.6"
rand = "0.4"
time = "*"
num_cpus = "1"
tokio-core = "0.1"
actix = "0.5"
actix-web = { path="../../" }

View File

@@ -0,0 +1,320 @@
//! Simple websocket client.
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate clap;
extern crate env_logger;
extern crate futures;
extern crate num_cpus;
extern crate rand;
extern crate time;
extern crate tokio_core;
extern crate url;
use futures::Future;
use rand::{thread_rng, Rng};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use actix::prelude::*;
use actix_web::ws;
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let matches = clap::App::new("ws tool")
.version("0.1")
.about("Applies load to websocket server")
.args_from_usage(
"<url> 'WebSocket url'
[bin]... -b, 'use binary frames'
-s, --size=[NUMBER] 'size of PUBLISH packet payload to send in KB'
-w, --warm-up=[SECONDS] 'seconds before counter values are considered for reporting'
-r, --sample-rate=[SECONDS] 'seconds between average reports'
-c, --concurrency=[NUMBER] 'number of websocket connections to open and use concurrently for sending'
-t, --threads=[NUMBER] 'number of threads to use'
--max-payload=[NUMBER] 'max size of payload before reconnect KB'",
)
.get_matches();
let bin: bool = matches.value_of("bin").is_some();
let ws_url = matches.value_of("url").unwrap().to_owned();
let _ = url::Url::parse(&ws_url).map_err(|e| {
println!("Invalid url: {}", ws_url);
std::process::exit(0);
});
let threads = parse_u64_default(matches.value_of("threads"), num_cpus::get() as u64);
let concurrency = parse_u64_default(matches.value_of("concurrency"), 1);
let payload_size: usize = match matches.value_of("size") {
Some(s) => parse_u64_default(Some(s), 1) as usize * 1024,
None => 1024,
};
let max_payload_size: usize = match matches.value_of("max-payload") {
Some(s) => parse_u64_default(Some(s), 0) as usize * 1024,
None => 0,
};
let warmup_seconds = parse_u64_default(matches.value_of("warm-up"), 2) as u64;
let sample_rate = parse_u64_default(matches.value_of("sample-rate"), 1) as usize;
let perf_counters = Arc::new(PerfCounters::new());
let payload = Arc::new(
thread_rng()
.gen_ascii_chars()
.take(payload_size)
.collect::<String>(),
);
let sys = actix::System::new("ws-client");
let _: () = Perf {
counters: perf_counters.clone(),
payload: payload.len(),
sample_rate_secs: sample_rate,
}.start();
for t in 0..threads {
let pl = payload.clone();
let ws = ws_url.clone();
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();
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();
}
fn parse_u64_default(input: Option<&str>, default: u64) -> u64 {
input
.map(|v| v.parse().expect(&format!("not a valid number: {}", v)))
.unwrap_or(default)
}
struct Perf {
counters: Arc<PerfCounters>,
payload: usize,
sample_rate_secs: usize,
}
impl Actor for Perf {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Context<Self>) {
self.sample_rate(ctx);
}
}
impl Perf {
fn sample_rate(&self, ctx: &mut Context<Self>) {
ctx.run_later(
Duration::new(self.sample_rate_secs as u64, 0),
|act, ctx| {
let req_count = act.counters.pull_request_count();
if req_count != 0 {
let conns = act.counters.pull_connections_count();
let latency = act.counters.pull_latency_ns();
let latency_max = act.counters.pull_latency_max_ns();
println!(
"rate: {}, conns: {}, throughput: {:?} kb, latency: {}, latency max: {}",
req_count / act.sample_rate_secs,
conns / act.sample_rate_secs,
(((req_count * act.payload) as f64) / 1024.0)
/ act.sample_rate_secs as f64,
time::Duration::nanoseconds((latency / req_count as u64) as i64),
time::Duration::nanoseconds(latency_max as i64)
);
}
act.sample_rate(ctx);
},
);
}
}
struct ChatClient {
url: String,
conn: ws::ClientWriter,
payload: Arc<String>,
ts: u64,
bin: bool,
perf_counters: Arc<PerfCounters>,
sent: usize,
max_payload_size: usize,
}
impl Actor for ChatClient {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Context<Self>) {
self.send_text();
self.perf_counters.register_connection();
}
}
impl ChatClient {
fn send_text(&mut self) -> bool {
self.sent += self.payload.len();
if self.max_payload_size > 0 && self.sent > self.max_payload_size {
let ws = self.url.clone();
let pl = self.payload.clone();
let bin = self.bin;
let perf_counters = self.perf_counters.clone();
let max_payload_size = self.max_payload_size;
Arbiter::handle().spawn(
ws::Client::new(&self.url)
.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: ws,
conn: writer,
payload: pl,
bin: bin,
ts: time::precise_time_ns(),
perf_counters: perf_counters,
sent: 0,
max_payload_size: max_payload_size,
}
});
}),
);
false
} else {
self.ts = time::precise_time_ns();
if self.bin {
self.conn.binary(&self.payload);
} else {
self.conn.text(&self.payload);
}
true
}
}
}
/// Handle server websocket messages
impl StreamHandler<ws::Message, ws::ProtocolError> for ChatClient {
fn finished(&mut self, ctx: &mut Context<Self>) {
ctx.stop()
}
fn handle(&mut self, msg: ws::Message, ctx: &mut Context<Self>) {
match msg {
ws::Message::Text(txt) => {
if txt == self.payload.as_ref().as_str() {
self.perf_counters.register_request();
self.perf_counters
.register_latency(time::precise_time_ns() - self.ts);
if !self.send_text() {
ctx.stop();
}
} else {
println!("not equal");
}
}
_ => (),
}
}
}
pub struct PerfCounters {
req: AtomicUsize,
conn: AtomicUsize,
lat: AtomicUsize,
lat_max: AtomicUsize,
}
impl PerfCounters {
pub fn new() -> PerfCounters {
PerfCounters {
req: AtomicUsize::new(0),
conn: AtomicUsize::new(0),
lat: AtomicUsize::new(0),
lat_max: AtomicUsize::new(0),
}
}
pub fn pull_request_count(&self) -> usize {
self.req.swap(0, Ordering::SeqCst)
}
pub fn pull_connections_count(&self) -> usize {
self.conn.swap(0, Ordering::SeqCst)
}
pub fn pull_latency_ns(&self) -> u64 {
self.lat.swap(0, Ordering::SeqCst) as u64
}
pub fn pull_latency_max_ns(&self) -> u64 {
self.lat_max.swap(0, Ordering::SeqCst) as u64
}
pub fn register_request(&self) {
self.req.fetch_add(1, Ordering::SeqCst);
}
pub fn register_connection(&self) {
self.conn.fetch_add(1, Ordering::SeqCst);
}
pub fn register_latency(&self, nanos: u64) {
let nanos = nanos as usize;
self.lat.fetch_add(nanos, Ordering::SeqCst);
loop {
let current = self.lat_max.load(Ordering::SeqCst);
if current >= nanos
|| self
.lat_max
.compare_and_swap(current, nanos, Ordering::SeqCst)
== current
{
break;
}
}
}
}