1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-23 17:28: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
98 changed files with 8712 additions and 13546 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,rust-tls" -- --nocapture
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,rust-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,8 +46,8 @@ script:
# Upload docs
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then
cargo doc --features "alpn, tls, rust-tls, session" --no-deps &&
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --features "alpn, tls, session" --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import.git &&
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&

View File

@@ -1,212 +1,5 @@
# Changes
## [0.7.5] - 2018-09-04
### Added
* Added the ability to pass a custom `TlsConnector`.
* Allow to register handlers on scope level #465
### Fixed
* Handle socket read disconnect
* Handling scoped paths without leading slashes #460
### Changed
* Read client response until eof if connection header set to close #464
## [0.7.4] - 2018-08-23
### Added
* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`,
accept backpressure #250
* Allow to customize connection handshake process via `HttpServer::listen_with()`
and `HttpServer::bind_with()` methods
* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472
### Changed
* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`.
`Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
* native-tls - 0.2
* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461
### Fixed
* Use zlib instead of raw deflate for decoding and encoding payloads with
`Content-Encoding: deflate`.
* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436
* Fix adding multiple response headers #446
* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448
* Panic during access without routing being set #452
* Fixed http/2 error handling
### Deprecated
* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or
`RustlsAcceptor::with_flags()` instead
* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been
deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`.
* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been
deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`.
## [0.7.3] - 2018-08-01
### Added
* Support HTTP/2 with rustls #36
* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433
### Fixed
* Fixed failure 0.1.2 compatibility
* Do not override HOST header for client request #428
* Gz streaming, use `flate2::write::GzDecoder` #228
* HttpRequest::url_for is not working with scopes #429
* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436
## [0.7.2] - 2018-07-26
### Added
* Add implementation of `FromRequest<S>` for `Option<T>` and `Result<T, Error>`
* Allow to handle application prefix, i.e. allow to handle `/app` path
for application with `/app` prefix.
Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix)
api doc.
* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies
### Changed
* Upgrade to cookie 0.11
* Removed the timestamp from the default logger middleware
### Fixed
* Missing response header "content-encoding" #421
* Fix stream draining for http/2 connections #290
## [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
@@ -218,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
@@ -238,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
@@ -255,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
@@ -616,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.5"
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", "rust-tls", "session", "brotli", "flate2-c"]
[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
@@ -37,12 +34,6 @@ tls = ["native-tls", "tokio-tls"]
# openssl
alpn = ["openssl", "tokio-openssl"]
# rustls
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"]
# unix sockets
uds = ["tokio-uds"]
# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]
@@ -55,72 +46,60 @@ 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.8"
httparse = "1.3"
http = "^0.1.5"
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.11", features=["percent-encode"] }
cookie = { version="0.10", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="^1.0.2", optional = true, default-features = false }
failure = "^0.1.2"
flate2 = { version="1.0", optional = true, default-features = false }
# 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.2", optional = true }
tokio-tls = { version="0.2", optional = true }
native-tls = { version="0.1", optional = true }
tokio-tls = { version="0.1", optional = true }
# openssl
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }
#rustls
rustls = { version = "^0.13.1", optional = true }
tokio-rustls = { version = "^0.7.2", optional = true }
webpki = { version = "0.18", optional = true }
webpki-roots = { version = "0.15", optional = true }
# unix sockets
tokio-uds = { version="0.2", optional = true }
serde_urlencoded = "^0.5.3"
[dev-dependencies]
env_logger = "0.5"
serde_derive = "1.0"
@@ -136,4 +115,5 @@ codegen-units = 1
[workspace]
members = [
"./",
"tools/wsload/",
]

View File

@@ -1,98 +1,4 @@
## 0.7.4
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
## 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`
@@ -144,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

@@ -12,7 +12,12 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Multipart streams
* Static assets
* SSL support with OpenSSL or `native-tls`
* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/))
* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging),
[Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions),
[Redis sessions](https://github.com/actix/actix-redis),
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Built on top of [Actix actor framework](https://github.com/actix/actix)
@@ -20,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
@@ -64,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,92 +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 with::WithFactory;
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)
}
}
}
@@ -94,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>>>,
@@ -110,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(),
}),
}
}
}
@@ -140,8 +235,12 @@ where
App {
parts: Some(ApplicationParts {
state,
prefix: "".to_owned(),
router: Router::new(ResourceDef::prefix("")),
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,
@@ -172,9 +271,7 @@ where
/// In the following example only requests with an `/app/` path
/// prefix get handled. Requests with path `/app/test/` would be
/// handled, while requests with the paths `/application` or
/// `/other/...` would return `NOT FOUND`. It is also possible to
/// handle `/app` path, to do this you can register resource for
/// empty string `""`
/// `/other/...` would return `NOT FOUND`.
///
/// ```rust
/// # extern crate actix_web;
@@ -183,8 +280,6 @@ where
/// fn main() {
/// let app = App::new()
/// .prefix("/app")
/// .resource("", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app` path
/// .resource("/", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app/` path
/// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
@@ -199,7 +294,6 @@ where
if !prefix.starts_with('/') {
prefix.insert(0, '/')
}
parts.router.set_prefix(&prefix);
parts.prefix = prefix;
}
self
@@ -250,16 +344,30 @@ where
/// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S>
where
F: WithFactory<T, S, R>,
F: Fn(T) -> R + 'static,
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
}
@@ -291,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
}
@@ -333,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
}
@@ -396,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())
@@ -414,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
}
@@ -436,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(),
@@ -447,13 +560,17 @@ where
{
let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/');
};
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_handler(&path, Box::new(WrapHandler::new(handler)), None);
path.insert(0, '/')
}
if path.len() > 1 && path.ends_with('/') {
path.pop();
}
let parts = self.parts.as_mut().expect("Use after finish");
parts.handlers.push(PrefixHandlerType::Handler(
path,
Box::new(WrapHandler::new(handler)),
));
}
self
}
@@ -491,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>
@@ -503,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 {
@@ -518,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(),
}
}
@@ -542,7 +674,7 @@ where
/// struct State2;
///
/// fn main() {
/// # thread::spawn(|| {
/// # thread::spawn(|| {
/// server::new(|| {
/// vec![
/// App::with_state(State1)
@@ -557,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()
}
}
@@ -591,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()
}
}
@@ -612,7 +737,6 @@ impl<S: 'static> Iterator for App<S> {
#[cfg(test)]
mod tests {
use super::*;
use body::{Binary, Body};
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
@@ -621,232 +745,208 @@ 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();
})
.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);
}
#[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")));
}
#[test]
fn test_filter() {
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();
@@ -857,23 +957,4 @@ mod tests {
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_prefix_root() {
let mut srv = TestServer::with_factory(|| {
App::new()
.prefix("/test")
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
.resource("", |r| r.f(|_| HttpResponse::Created()))
});
let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
}
}

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;
@@ -20,7 +19,6 @@ const MAX_HEADERS: usize = 96;
#[derive(Default)]
pub struct HttpResponseParser {
decoder: Option<EncodingDecoder>,
eof: bool, // indicate that we read payload until stream eof
}
#[derive(Debug, Fail)]
@@ -39,42 +37,41 @@ impl HttpResponseParser {
where
T: IoStream,
{
loop {
// Don't call parser until we have data to parse.
if !buf.is_empty() {
match HttpResponseParser::parse_message(buf)
.map_err(HttpResponseParserError::Error)?
{
Async::Ready((msg, info)) => {
if let Some((decoder, eof)) = info {
self.eof = eof;
self.decoder = Some(decoder);
} else {
self.eof = false;
self.decoder = None;
}
return Ok(Async::Ready(msg));
}
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(
ParseError::TooLarge,
));
}
// Parser needs more data.
}
}
}
// Read some more data into the buffer for the parser.
match io.read_available(buf) {
Ok(Async::Ready((false, true))) => {
return Err(HttpResponseParserError::Disconnect)
}
// if buf is empty parse_message will always return NotReady, let's avoid that
if buf.is_empty() {
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())),
}
}
loop {
match HttpResponseParser::parse_message(buf)
.map_err(HttpResponseParserError::Error)?
{
Async::Ready((msg, decoder)) => {
self.decoder = decoder;
return Ok(Async::Ready(msg));
}
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(ParseError::TooLarge));
}
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()))
}
}
}
}
}
}
pub fn parse_payload<T>(
@@ -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) {
@@ -104,12 +101,7 @@ impl HttpResponseParser {
return Ok(Async::NotReady);
}
if stream_finished {
// read untile eof?
if self.eof {
return Ok(Async::Ready(None));
} else {
return Err(PayloadError::Incomplete);
}
return Err(PayloadError::Incomplete);
}
}
Err(err) => return Err(err.into()),
@@ -122,24 +114,25 @@ impl HttpResponseParser {
fn parse_message(
buf: &mut BytesMut,
) -> Poll<(ClientResponse, Option<(EncodingDecoder, bool)>), 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() };
) -> Poll<(ClientResponse, Option<EncodingDecoder>), ParseError> {
// Parse http message
let bytes_ptr = buf.as_ref().as_ptr() as usize;
let mut headers: [httparse::Header; MAX_HEADERS] =
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)?;
@@ -153,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 {
@@ -168,12 +160,12 @@ impl HttpResponseParser {
}
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
Some((EncodingDecoder::eof(), true))
Some(EncodingDecoder::eof())
} else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
Some((EncodingDecoder::length(len), false))
Some(EncodingDecoder::length(len))
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header);
@@ -184,18 +176,7 @@ impl HttpResponseParser {
}
} else if chunked(&hdrs)? {
// Chunked encoding
Some((EncodingDecoder::chunked(), false))
} else if let Some(value) = hdrs.get(header::CONNECTION) {
let close = if let Ok(s) = value.to_str() {
s == "close"
} else {
false
};
if close {
Some((EncodingDecoder::eof(), true))
} else {
None
}
Some(EncodingDecoder::chunked())
} else {
None
};

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),
}
@@ -291,6 +293,10 @@ impl ClientRequestBuilder {
fn _uri(&mut self, url: &str) -> &mut Self {
match Uri::try_from(url) {
Ok(uri) => {
// set request host header
if let Some(host) = uri.host() {
self.set_header(header::HOST, host);
}
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.uri = uri;
}
@@ -312,7 +318,8 @@ impl ClientRequestBuilder {
/// Set HTTP method of this request.
#[inline]
pub fn get_method(&mut self) -> &Method {
let parts = self.request.as_ref().expect("cannot reuse request builder");
let parts =
parts(&mut self.request, &self.err).expect("cannot reuse request builder");
&parts.method
}
@@ -340,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)]
@@ -373,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
@@ -416,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.
@@ -506,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 {
@@ -524,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
@@ -560,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);
}
@@ -620,37 +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");
}
// set request host header
if let Some(parts) = parts(&mut self.request, &self.err) {
if let Some(host) = parts.uri.host() {
if !parts.headers.contains_key(header::HOST) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match parts.uri.port() {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
parts.headers.insert(header::HOST, value);
}
Err(e) => self.err = Some(e.into()),
}
}
}
}
// user agent
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");
@@ -690,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

@@ -8,7 +8,7 @@ use std::io::{self, Write};
use brotli2::write::BrotliEncoder;
use bytes::{BufMut, BytesMut};
#[cfg(feature = "flate2")]
use flate2::write::{GzEncoder, ZlibEncoder};
use flate2::write::{DeflateEncoder, GzEncoder};
#[cfg(feature = "flate2")]
use flate2::Compression;
use futures::{Async, Poll};
@@ -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(
ZlibEncoder::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,24 +285,26 @@ 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(ZlibEncoder::new(transfer, Compression::default()))
}
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 | 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
@@ -52,8 +52,7 @@ pub struct Error {
impl Error {
/// Deprecated way to reference the underlying response error.
#[deprecated(
since = "0.6.0",
note = "please use `Error::as_response_error()` instead"
since = "0.6.0", note = "please use `Error::as_response_error()` instead"
)]
pub fn cause(&self) -> &ResponseError {
self.cause.as_ref()
@@ -98,9 +97,20 @@ impl Error {
//
// So we first downcast into that compat, to then further downcast through
// the failure's Error downcasting system into the original failure.
//
// This currently requires a transmute. This could be avoided if failure
// provides a deref: https://github.com/rust-lang-nursery/failure/pull/213
let compat: Option<&failure::Compat<failure::Error>> =
Fail::downcast_ref(self.cause.as_fail());
compat.and_then(|e| e.get_ref().downcast_ref())
if let Some(compat) = compat {
pub struct CompatWrappedError {
error: failure::Error,
}
let compat: &CompatWrappedError = unsafe { ::std::mem::transmute(compat) };
compat.error.downcast_ref()
} else {
None
}
}
}
@@ -193,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 {}
@@ -362,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 {
@@ -382,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 {
@@ -465,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")]
@@ -508,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")]
@@ -546,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)]
@@ -595,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),
}
@@ -615,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"
@@ -658,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> {
@@ -673,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(),
}
}
@@ -718,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)
}
@@ -913,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);
@@ -962,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 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,23 +1,21 @@
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;
use encoding::types::{DecoderTrap, Encoding};
use futures::{future, Async, Future, Poll};
use futures::{Async, Future, Poll};
use mime::Mime;
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;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's path.
///
/// ## Example
@@ -26,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
@@ -37,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
/// }
/// ```
///
@@ -51,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 {
@@ -65,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> {
@@ -101,12 +97,6 @@ impl<T> Path<T> {
}
}
impl<T> From<T> for Path<T> {
fn from(inner: T) -> Path<T> {
Path { inner }
}
}
impl<T, S> FromRequest<S> for Path<T>
where
T: DeserializeOwned,
@@ -123,19 +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)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from from the request's query.
///
/// ## Example
@@ -147,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() {
@@ -205,25 +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)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's body.
///
/// To extract typed information from request's body, the type `T` must
@@ -280,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 {
@@ -328,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.0.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 }
}
}
@@ -388,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 {
@@ -401,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(),
))
}
}
@@ -424,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.0.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 {
@@ -445,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| {
@@ -464,126 +394,6 @@ impl<S: 'static> FromRequest<S> for String {
}
}
/// Optionally extract a field from the request
///
/// If the FromRequest for T fails, return None rather than returning an error response
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// extern crate rand;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing { name: String }
///
/// impl<S> FromRequest<S> for Thing {
/// type Config = ();
/// type Result = Result<Thing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
///
/// }
/// }
///
/// /// extract text data from request
/// fn index(supplied_thing: Option<Thing>) -> Result<String> {
/// match supplied_thing {
/// // Puns not intended
/// Some(thing) => Ok(format!("Got something: {:?}", thing)),
/// None => Ok(format!("No thing!"))
/// }
/// }
///
/// fn main() {
/// let app = App::new().resource("/users/:first", |r| {
/// r.method(http::Method::POST).with(index)
/// });
/// }
/// ```
impl<T: 'static, S: 'static> FromRequest<S> for Option<T>
where
T: FromRequest<S>,
{
type Config = T::Config;
type Result = Box<Future<Item = Option<T>, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new(T::from_request(req, cfg).into().then(|r| match r {
Ok(v) => future::ok(Some(v)),
Err(_) => future::ok(None),
}))
}
}
/// Optionally extract a field from the request or extract the Error if unsuccessful
///
/// If the FromRequest for T fails, inject Err into handler rather than returning an error response
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// extern crate rand;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing { name: String }
///
/// impl<S> FromRequest<S> for Thing {
/// type Config = ();
/// type Result = Result<Thing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
///
/// }
/// }
///
/// /// extract text data from request
/// fn index(supplied_thing: Result<Thing>) -> Result<String> {
/// match supplied_thing {
/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)),
/// Err(e) => Ok(format!("Error extracting thing: {}", e))
/// }
/// }
///
/// fn main() {
/// let app = App::new().resource("/users/:first", |r| {
/// r.method(http::Method::POST).with(index)
/// });
/// }
/// ```
impl<T: 'static, S: 'static> FromRequest<S> for Result<T, Error>
where
T: FromRequest<S>,
{
type Config = T::Config;
type Result = Box<Future<Item = Result<T, Error>, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new(T::from_request(req, cfg).into().then(future::ok))
}
}
/// Payload configuration for request's payload.
pub struct PayloadConfig {
limit: usize,
@@ -696,12 +506,6 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
}
});
impl<S> FromRequest<S> for () {
type Config = ();
type Result = Self;
fn from_request(_req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {}
}
tuple_from_req!(TupleFromRequest1, (0, A));
tuple_from_req!(TupleFromRequest2, (0, A), (1, B));
tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C));
@@ -757,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)]
@@ -769,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) => {
@@ -784,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) => {
@@ -798,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();
.finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
let mut cfg = FormConfig::default();
cfg.limit(4096);
@@ -815,101 +621,9 @@ mod tests {
}
}
#[test]
fn test_option() {
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).finish();
let mut cfg = FormConfig::default();
cfg.limit(4096);
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(r, None),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(
r,
Some(Form(Info {
hello: "world".into()
}))
),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world"))
.finish();
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(r, None),
_ => unreachable!(),
}
}
#[test]
fn test_result() {
let 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();
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default())
.poll()
.unwrap()
{
Async::Ready(Ok(r)) => assert_eq!(
r,
Form(Info {
hello: "world".into()
})
),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world"))
.finish();
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default())
.poll()
.unwrap()
{
Async::Ready(r) => assert!(r.is_err()),
_ => unreachable!(),
}
}
#[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());
@@ -944,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::<()>::default();
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");
@@ -962,11 +678,8 @@ mod tests {
let s = Query::<Id>::from_request(&req, &()).unwrap();
assert_eq!(s.id, "test");
let mut router = Router::<()>::default();
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");
@@ -983,23 +696,28 @@ mod tests {
#[test]
fn test_extract_path_single() {
let mut router = Router::<()>::default();
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::<()>::default();
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,
@@ -1018,7 +736,5 @@ mod tests {
assert_eq!((res.0).1, "user1");
assert_eq!((res.1).0, "name");
assert_eq!((res.1).1, "user1");
let () = <()>::extract(&req);
}
}

1088
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)
}
}
@@ -353,17 +327,13 @@ impl<T, E: Into<Error>> From<Result<T, E>> for AsyncResult<T> {
}
}
impl<T, E> From<Result<Box<Future<Item = T, Error = E>>, E>> for AsyncResult<T>
where
T: 'static,
E: Into<Error> + 'static,
impl<T, E: Into<Error>> From<Result<Box<Future<Item = T, Error = Error>>, E>>
for AsyncResult<T>
{
#[inline]
fn from(res: Result<Box<Future<Item = T, Error = E>>, E>) -> Self {
fn from(res: Result<Box<Future<Item = T, Error = Error>>, E>) -> Self {
match res {
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(Box::new(
fut.map_err(|e| e.into()),
)))),
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(fut))),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
@@ -405,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
@@ -445,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()),
}
@@ -456,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,
@@ -468,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,
@@ -484,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)),
@@ -517,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 {
@@ -536,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>);

File diff suppressed because it is too large Load Diff

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")]
@@ -223,7 +220,8 @@ pub fn from_comma_delimited<T: FromStr>(
.filter_map(|x| match x.trim() {
"" => None,
y => Some(y),
}).filter_map(|x| x.trim().parse().ok()),
})
.filter_map(|x| x.trim().parse().ok()),
)
}
Ok(result)
@@ -259,213 +257,3 @@ where
}
Ok(())
}
// From hyper v0.11.27 src/header/parsing.rs
/// The value part of an extended parameter consisting of three parts:
/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
/// and a character sequence representing the actual value (`value`), separated by single quote
/// characters. It is 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| {
@@ -279,7 +231,8 @@ mod tests {
true,
StatusCode::MOVED_PERMANENTLY,
))
}).finish();
})
.finish();
// trailing slashes
let params = vec![
@@ -293,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()))
@@ -376,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!(
@@ -391,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))
@@ -556,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,113 +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,
}
}
@@ -550,57 +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()
}
}
@@ -610,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]
@@ -621,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(), "");
}
@@ -629,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")
@@ -651,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();
@@ -679,20 +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());
}
@@ -731,7 +576,7 @@ mod tests {
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "xxxx")
.finish();
.finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::UnknownLength
@@ -741,7 +586,7 @@ mod tests {
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "1000000")
.finish();
.finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::Overflow
@@ -758,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();
.finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
let result = req.urlencoded::<Info>().poll().ok().unwrap();
assert_eq!(
@@ -773,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();
.finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
let result = req.urlencoded().poll().ok().unwrap();
assert_eq!(
@@ -803,52 +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,180 +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(),
}
}
/// Construct new http request with empty state.
pub fn drop_state(&self) -> HttpRequest {
HttpRequest {
state: Rc::new(()),
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
@@ -204,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
@@ -216,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
@@ -231,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.
@@ -259,33 +408,29 @@ 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) {
let s =
str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
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() {
cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
}
}
}
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);
}
}
}
@@ -306,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())
}
}
@@ -352,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
@@ -378,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]
@@ -391,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());
}
@@ -430,27 +658,33 @@ mod tests {
#[test]
fn test_request_match_info() {
let mut router = Router::<()>::default();
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::<()>::default();
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);
@@ -472,24 +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::<()>::default();
router.set_prefix("/prefix");
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(),
@@ -499,23 +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::<()>::default();
router.set_prefix("/prefix");
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(),
@@ -525,17 +744,18 @@ mod tests {
#[test]
fn test_url_for_external() {
let mut router = Router::<()>::default();
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,51 +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 {
@@ -282,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())
}
}
@@ -316,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>,
}
@@ -534,6 +469,7 @@ impl HttpResponseBuilder {
/// )
/// .finish()
/// }
/// fn main() {}
/// ```
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
if self.cookies.is_none() {
@@ -546,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() {
@@ -630,7 +552,7 @@ impl HttpResponseBuilder {
}
}
response.body = body.into();
HttpResponse(response, self.pool)
HttpResponse(Some(response), self.pool.take().unwrap())
}
#[inline]
@@ -649,14 +571,7 @@ impl HttpResponseBuilder {
///
/// `HttpResponseBuilder` can not be used after this call.
pub fn json<T: Serialize>(&mut self, value: T) -> HttpResponse {
self.json2(&value)
}
/// Set a json body and generate `HttpResponse`
///
/// `HttpResponseBuilder` can not be used after this call.
pub fn json2<T: Serialize>(&mut self, value: &T) -> HttpResponse {
match serde_json::to_string(value) {
match serde_json::to_string(&value) {
Ok(body) => {
let contains = if let Some(parts) = parts(&mut self.response, &self.err)
{
@@ -685,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(),
}
@@ -866,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>,
@@ -887,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]
@@ -915,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,
}
@@ -1002,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))
}
}
@@ -1024,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;
@@ -1037,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);
}
}
}
@@ -1048,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()
@@ -1064,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()
@@ -1078,7 +953,8 @@ mod tests {
.http_only(true)
.max_age(Duration::days(1))
.finish(),
).del_cookie(&cookies[0])
)
.del_cookie(&cookies[0])
.finish();
let mut val: Vec<_> = resp
@@ -1088,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()
@@ -1191,30 +1044,6 @@ mod tests {
);
}
#[test]
fn test_json2() {
let resp = HttpResponse::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("application/json"));
assert_eq!(
*resp.body(),
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
);
}
#[test]
fn test_json2_ct() {
let resp = HttpResponse::build(StatusCode::OK)
.header(CONTENT_TYPE, "text/json")
.json2(&vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("text/json"));
assert_eq!(
*resp.body(),
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
);
}
impl Body {
pub(crate) fn bin_ref(&self) -> &Binary {
match *self {
@@ -1226,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);
@@ -1349,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,58 +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.0.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,41 +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()
}
}
@@ -341,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 {
@@ -370,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"
@@ -379,39 +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(),
@@ -423,21 +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,22 +59,20 @@
//! * 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
//!
//! * `tls` - enables ssl support via `native-tls` crate
//! * `alpn` - enables ssl support via `openssl` crate, require for `http/2`
//! support
//! * `uds` - enables support for making client requests via Unix Domain Sockets.
//! Unix only. Not necessary for *serving* requests.
//! * `session` - enables session support, includes `ring` crate as
//! dependency
//! * `brotli` - enables `brotli` compression support, requires `c`
//! compiler
//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires
//! * `flate-c` - enables `gzip`, `deflate` compression support, requires
//! `c` compiler
//! * `flate2-rust` - experimental rust based implementation for
//! * `flate-rust` - experimental rust based implementation for
//! `gzip`, `deflate` compression.
//!
#![cfg_attr(actix_nightly, feature(
@@ -85,7 +83,6 @@
feature = "cargo-clippy",
allow(decimal_literal_representation, suspicious_arithmetic_impl)
)]
#![warn(missing_docs)]
#[macro_use]
extern crate log;
@@ -105,25 +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;
#[cfg(all(unix, feature = "uds"))]
extern crate tokio_uds;
extern crate url;
#[macro_use]
extern crate serde;
@@ -134,13 +125,12 @@ extern crate encoding;
extern crate flate2;
extern crate h2 as http2;
extern crate num_cpus;
extern crate serde_urlencoded;
#[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]
@@ -156,20 +146,10 @@ extern crate openssl;
#[cfg(feature = "openssl")]
extern crate tokio_openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
#[cfg(feature = "rust-tls")]
extern crate tokio_rustls;
#[cfg(feature = "rust-tls")]
extern crate webpki;
#[cfg(feature = "rust-tls")]
extern crate webpki_roots;
mod application;
mod body;
mod context;
mod de;
mod extensions;
mod extractor;
mod handler;
mod header;
@@ -203,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,
@@ -213,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;
@@ -237,11 +207,6 @@ pub(crate) const HAS_TLS: bool = true;
#[cfg(not(feature = "tls"))]
pub(crate) const HAS_TLS: bool = false;
#[cfg(feature = "rust-tls")]
pub(crate) const HAS_RUSTLS: bool = true;
#[cfg(not(feature = "rust-tls"))]
pub(crate) const HAS_RUSTLS: bool = false;
pub mod dev {
//! The `actix-web` prelude for library developers
//!
@@ -257,16 +222,15 @@ pub mod dev {
pub use context::Drain;
pub use extractor::{FormConfig, PayloadConfig};
pub use handler::{AsyncResult, Handler};
pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
pub use httpmessage::{MessageBody, UrlEncoded};
pub use httpresponse::HttpResponseBuilder;
pub use info::ConnectionInfo;
pub use json::{JsonBody, JsonConfig};
pub use param::{FromParam, Params};
pub use payload::{Payload, PayloadBuffer};
pub use pipeline::Pipeline;
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 {
@@ -279,15 +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::{
Charset, ContentDisposition, DispositionParam, DispositionType, 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() {
@@ -387,10 +386,12 @@ impl<S> Middleware<S> for Cors {
header::ACCESS_CONTROL_MAX_AGE,
format!("{}", max_age).as_str(),
);
}).if_some(headers, |headers, resp| {
})
.if_some(headers, |headers, resp| {
let _ =
resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
}).if_true(self.inner.origins.is_all(), |resp| {
})
.if_true(self.inner.origins.is_all(), |resp| {
if self.inner.send_wildcard {
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
} else {
@@ -400,14 +401,17 @@ impl<S> Middleware<S> for Cors {
origin.clone(),
);
}
}).if_true(self.inner.origins.is_some(), |resp| {
})
.if_true(self.inner.origins.is_some(), |resp| {
resp.header(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
self.inner.origins_str.as_ref().unwrap().clone(),
);
}).if_true(self.inner.supports_credentials, |resp| {
})
.if_true(self.inner.supports_credentials, |resp| {
resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
}).header(
})
.header(
header::ACCESS_CONTROL_ALLOW_METHODS,
&self
.inner
@@ -415,7 +419,8 @@ impl<S> Middleware<S> for Cors {
.iter()
.fold(String::new(), |s, v| s + "," + v.as_str())
.as_str()[1..],
).finish(),
)
.finish(),
))
} else {
// Only check requests with a origin header.
@@ -428,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 => {
@@ -510,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>>,
}
@@ -790,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
}
@@ -834,7 +839,7 @@ impl<S: 'static> CorsBuilder<S> {
cors.expose_hdrs = Some(
self.expose_hdrs
.iter()
.fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..]
.fold(String::new(), |s, v| s + v.as_str())[1..]
.to_owned(),
);
}
@@ -873,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
@@ -939,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]
@@ -954,28 +960,29 @@ 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,
"AUTHORIZATION,ACCEPT",
).method(Method::OPTIONS)
)
.method(Method::OPTIONS)
.finish();
let resp = cors.start(&req).unwrap().response();
let resp = cors.start(&mut req).unwrap().response();
assert_eq!(
&b"*"[..],
resp.headers()
@@ -999,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]
@@ -1019,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]
@@ -1031,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()
@@ -1066,23 +1073,21 @@ mod tests {
#[test]
fn test_response() {
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
let cors = Cors::build()
.send_wildcard()
.disable_preflight()
.max_age(3600)
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone())
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.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()
@@ -1095,25 +1100,9 @@ mod tests {
resp.headers().get(header::VARY).unwrap().as_bytes()
);
{
let headers = resp
.headers()
.get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
.unwrap()
.to_str()
.unwrap()
.split(',')
.map(|s| s.trim())
.collect::<Vec<&str>>();
for h in exposed_headers {
assert!(headers.contains(&h.as_str()));
}
}
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()
@@ -1124,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)]
@@ -93,7 +93,8 @@ fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
.to_str()
.map_err(|_| CsrfError::BadOrigin)
.map(|o| o.into())
}).or_else(|| {
})
.or_else(|| {
headers.get(header::REFERER).map(|referer| {
Uri::try_from(Bytes::from(referer.as_bytes()))
.ok()
@@ -119,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)]
@@ -174,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
@@ -186,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);
@@ -208,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)
}
@@ -224,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();
.finish();
assert!(csrf.start(&req).is_ok());
assert!(csrf.start(&mut req).is_ok());
}
#[test]
@@ -263,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;
@@ -25,13 +26,13 @@ use middleware::{Finished, Middleware, Started};
/// default format:
///
/// ```ignore
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
/// ```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
@@ -94,7 +97,7 @@ impl Default for Logger {
/// Create `Logger` middleware with format:
///
/// ```ignore
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
fn default() -> Logger {
Logger {
@@ -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
}
@@ -143,7 +146,7 @@ struct Format(Vec<FormatText>);
impl Default for Format {
/// Return the default formatting style for the `Logger`:
fn default() -> Format {
Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#)
Format::new(r#"%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#)
}
}
@@ -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))
@@ -358,9 +366,7 @@ struct CookieSessionInner {
path: String,
domain: Option<String>,
secure: bool,
http_only: bool,
max_age: Option<Duration>,
same_site: Option<SameSite>,
}
impl CookieSessionInner {
@@ -372,9 +378,7 @@ impl CookieSessionInner {
path: "/".to_owned(),
domain: None,
secure: true,
http_only: true,
max_age: None,
same_site: None,
}
}
@@ -390,7 +394,7 @@ impl CookieSessionInner {
let mut cookie = Cookie::new(self.name.clone(), value);
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(self.http_only);
cookie.set_http_only(true);
if let Some(ref domain) = self.domain {
cookie.set_domain(domain.clone());
@@ -400,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 {
@@ -412,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);
}
@@ -421,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());
@@ -466,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
///
@@ -534,18 +531,6 @@ impl CookieSessionBackend {
self
}
/// Sets the `http_only` field in the session cookie being built.
pub fn http_only(mut self, value: bool) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().http_only = value;
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);
@@ -579,7 +564,8 @@ mod tests {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
)).resource("/", |r| {
))
.resource("/", |r| {
r.f(|req| {
let _ = req.session().set("counter", 100);
"test"
@@ -598,7 +584,8 @@ mod tests {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
)).resource("/", |r| {
))
.resource("/", |r| {
r.with(|ses: Session| {
let _ = ses.set("counter", 100);
"test"

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,15 +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::FormData);
assert_eq!(
cd.parameters[0],
DispositionParam::Name("file".into())
);
}
assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN);
@@ -810,6 +784,7 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}))
.unwrap();
}
}

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()
}
}
@@ -236,6 +183,7 @@ macro_rules! FROM_STR {
($type:ty) => {
impl FromParam for $type {
type Err = InternalError<<$type as FromStr>::Err>;
fn from_param(val: &str) -> Result<Self, Self::Err> {
<$type as FromStr>::from_str(val)
.map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST))

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,25 +497,24 @@ 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()
.fold(BytesMut::new(), |mut b, c| {
b.extend_from_slice(c);
b
}).freeze()
})
.freeze()
}
}
@@ -523,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() {
@@ -541,27 +542,28 @@ 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());
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}))
.unwrap();
}
#[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"));
@@ -576,16 +578,17 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}))
.unwrap();
}
#[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());
@@ -593,16 +596,17 @@ mod tests {
payload.readany().err().unwrap();
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}))
.unwrap();
}
#[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"));
@@ -621,16 +625,17 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}))
.unwrap();
}
#[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());
@@ -654,16 +659,17 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}))
.unwrap();
}
#[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());
@@ -687,14 +693,15 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}))
.unwrap();
}
#[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"));
@@ -708,6 +715,7 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
})).unwrap();
}))
.unwrap();
}
}

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,
@@ -42,31 +47,64 @@ enum PipelineState<S, H> {
}
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 is_response(&self) -> bool {
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::Response(_) => true,
_ => false,
}
}
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
match *self {
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(ref mut state) => state.poll(info, mws),
PipelineState::None | PipelineState::Error => None,
PipelineState::Response(_) | PipelineState::None | PipelineState::Error => {
None
}
}
}
}
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() {
@@ -81,20 +119,31 @@ impl<S: 'static> PipelineInfo<S> {
}
impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
pub(crate) fn new(
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: Rc<H>,
pub fn new(
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()),
))
}
}
@@ -119,26 +168,29 @@ 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 let PipelineState::Response(st) = state {
match st.poll_io(io, &mut self.0, &self.2) {
Ok(state) => {
self.1 = state;
if let Some(error) = self.0.error.take() {
return Err(error);
} else {
return Ok(Async::Ready(self.is_done()));
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, info) {
Ok(state) => {
self.1 = state;
if let Some(error) = self.0.error.take() {
return Err(error);
} else {
return Ok(Async::Ready(self.is_done()));
}
}
Err(state) => {
self.1 = state;
return Ok(Async::NotReady);
}
}
Err(state) => {
self.1 = state;
return Ok(Async::NotReady);
}
}
}
match state {
match self.1 {
PipelineState::None => return Ok(Async::Ready(true)),
PipelineState::Error => {
return Err(
@@ -148,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);
}
}
@@ -184,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())),
}
}
}
@@ -281,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,
@@ -295,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())),
}
}
}
@@ -316,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());
@@ -346,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
@@ -372,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;
@@ -391,7 +417,7 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
}
struct ProcessResponse<S, H> {
resp: Option<HttpResponse>,
resp: HttpResponse,
iostate: IOState,
running: RunningState,
drain: Option<oneshot::Sender<()>>,
@@ -399,7 +425,7 @@ struct ProcessResponse<S, H> {
_h: PhantomData<H>,
}
#[derive(PartialEq, Debug)]
#[derive(PartialEq)]
enum RunningState {
Running,
Paused,
@@ -432,7 +458,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
#[inline]
fn init(resp: HttpResponse) -> PipelineState<S, H> {
PipelineState::Response(ProcessResponse {
resp: Some(resp),
resp,
iostate: IOState::Response,
running: RunningState::Running,
drain: None,
@@ -441,82 +467,8 @@ impl<S: 'static, H> ProcessResponse<S, H> {
})
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
// connection is dead at this point
match mem::replace(&mut self.iostate, IOState::Done) {
IOState::Response => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
IOState::Payload(_) => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
IOState::Actor(mut ctx) => {
if info.disconnected.take().is_some() {
ctx.disconnected();
}
loop {
match ctx.poll() {
Ok(Async::Ready(Some(vec))) => {
if vec.is_empty() {
continue;
}
for frame in vec {
match frame {
Frame::Chunk(None) => {
info.context = Some(ctx);
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
Frame::Chunk(Some(_)) => (),
Frame::Drain(fut) => {
let _ = fut.send(());
}
}
}
}
Ok(Async::Ready(None)) => {
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
))
}
Ok(Async::NotReady) => {
self.iostate = IOState::Actor(ctx);
return None;
}
Err(err) => {
info.context = Some(ctx);
info.error = Some(err);
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
}
}
}
IOState::Done => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
}
}
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 {
@@ -524,35 +476,28 @@ impl<S: 'static, H> ProcessResponse<S, H> {
'inner: loop {
let result = match mem::replace(&mut self.iostate, IOState::Done) {
IOState::Response => {
let encoding = self
.resp
.as_ref()
.unwrap()
.content_encoding()
.unwrap_or(info.encoding);
let encoding =
self.resp.content_encoding().unwrap_or(info.encoding);
let result = match io.start(
&info.req,
self.resp.as_mut().unwrap(),
info.req_mut().get_inner(),
&mut self.resp,
encoding,
) {
Ok(res) => res,
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
info, self.resp,
));
}
};
if let Some(err) = self.resp.as_ref().unwrap().error() {
if self.resp.as_ref().unwrap().status().is_server_error()
{
if let Some(err) = self.resp.error() {
if self.resp.status().is_server_error() {
error!(
"Error occured during request handling, status: {} {}",
self.resp.as_ref().unwrap().status(), err
self.resp.status(), err
);
} else {
warn!(
@@ -566,7 +511,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
}
// always poll stream or actor for the first time
match self.resp.as_mut().unwrap().replace_body(Body::Empty) {
match self.resp.replace_body(Body::Empty) {
Body::Streaming(stream) => {
self.iostate = IOState::Payload(stream);
continue 'inner;
@@ -585,22 +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.take().unwrap(),
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.take().unwrap(),
info, self.resp,
));
}
Ok(result) => result,
@@ -612,11 +553,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
}
Err(err) => {
info.error = Some(err);
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
return Ok(FinishingMiddlewares::init(info, self.resp));
}
},
IOState::Actor(mut ctx) => {
@@ -638,30 +575,25 @@ impl<S: 'static, H> ProcessResponse<S, H> {
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
info, self.resp,
),
);
}
break 'inner;
}
Frame::Chunk(Some(chunk)) => match io
.write(&chunk)
{
Err(err) => {
info.context = Some(ctx);
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
),
);
Frame::Chunk(Some(chunk)) => {
match io.write(chunk) {
Err(err) => {
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info, self.resp,
),
);
}
Ok(result) => res = Some(result),
}
Ok(result) => res = Some(result),
},
}
Frame::Drain(fut) => self.drain = Some(fut),
}
}
@@ -678,12 +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.take().unwrap(),
info, self.resp,
));
}
}
@@ -716,18 +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.take().unwrap(),
));
return Ok(FinishingMiddlewares::init(info, self.resp));
}
}
}
@@ -741,19 +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.take().unwrap(),
));
return Ok(FinishingMiddlewares::init(info, self.resp));
}
}
self.resp.as_mut().unwrap().set_response_size(io.written());
Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
))
self.resp.set_response_size(io.written());
Ok(FinishingMiddlewares::init(info, self.resp))
}
_ => Err(PipelineState::Response(self)),
}
@@ -762,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>,
@@ -770,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)
@@ -791,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 {
@@ -813,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));
}
}
@@ -848,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))
}
}
@@ -867,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,91 +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,11 +12,6 @@ use httpresponse::HttpResponse;
use middleware::Middleware;
use pred;
use route::Route;
use router::ResourceDef;
use with::WithFactory;
#[derive(Copy, Clone)]
pub(crate) struct RouteId(usize);
/// *Resource* is an entry in route table which corresponds to requested URL.
///
@@ -38,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.
@@ -81,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();
/// }
/// ```
@@ -127,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());
@@ -150,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());
@@ -173,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());
@@ -200,25 +167,14 @@ 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
F: WithFactory<T, S, R>,
F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
@@ -228,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
@@ -278,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::{WithAsyncFactory, WithFactory};
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: WithFactory<T, S, R> + 'static,
F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
self.h(handler.create());
}
/// Set handler function. Same as `.with()` but it allows to configure
/// extractor. Configuration closure accepts config objects as tuple.
///
/// ```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.0.limit(4096); // <- limit size of the payload
/// })
/// });
/// }
/// ```
pub fn with_config<T, F, R, C>(&mut self, handler: F, cfg_f: C)
where
F: WithFactory<T, S, R>,
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(handler.create_with_config(cfg));
let cfg = ExtractorConfig::default();
self.h(With::new(handler, Clone::clone(&cfg)));
cfg
}
/// Set async handler function, use request extractor for parameters.
@@ -238,88 +204,125 @@ 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: WithAsyncFactory<T, S, R, I, E>,
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.h(handler.create());
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. Configuration closure
/// accepts config objects as tuple.
#[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.0.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: WithAsyncFactory<T, S, R, I, E>,
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(handler.create_with_config(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)
}
}
@@ -409,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()),
}
}
}
@@ -437,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 {
@@ -450,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));
@@ -464,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>,
}
@@ -492,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,
@@ -504,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())),
}
}
@@ -523,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());
@@ -542,7 +532,7 @@ impl<S: 'static> RunMiddlewares<S> {
curr,
fut: Some(fut),
_s: PhantomData,
});
})
}
};
}
@@ -566,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()))
}
@@ -635,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

File diff suppressed because it is too large Load Diff

View File

@@ -1,472 +0,0 @@
use std::sync::mpsc as sync_mpsc;
use std::time::{Duration, Instant};
use std::{io, net, thread};
use futures::{sync::mpsc, Future};
use mio;
use slab::Slab;
use tokio_timer::Delay;
use actix::{msgs::Execute, Arbiter, System};
use super::server::ServerCommand;
use super::worker::{Conn, WorkerClient};
use super::Token;
pub(crate) enum Command {
Pause,
Resume,
Stop,
Worker(WorkerClient),
}
struct ServerSocketInfo {
addr: net::SocketAddr,
token: Token,
handler: Token,
sock: mio::net::TcpListener,
timeout: Option<Instant>,
}
#[derive(Clone)]
pub(crate) struct AcceptNotify(mio::SetReadiness);
impl AcceptNotify {
pub(crate) fn new(ready: mio::SetReadiness) -> Self {
AcceptNotify(ready)
}
pub(crate) fn notify(&self) {
let _ = self.0.set_readiness(mio::Ready::readable());
}
}
impl Default for AcceptNotify {
fn default() -> Self {
AcceptNotify::new(mio::Registration::new2().1)
}
}
pub(crate) struct AcceptLoop {
cmd_reg: Option<mio::Registration>,
cmd_ready: mio::SetReadiness,
notify_reg: Option<mio::Registration>,
notify_ready: mio::SetReadiness,
tx: sync_mpsc::Sender<Command>,
rx: Option<sync_mpsc::Receiver<Command>>,
srv: Option<(
mpsc::UnboundedSender<ServerCommand>,
mpsc::UnboundedReceiver<ServerCommand>,
)>,
}
impl AcceptLoop {
pub fn new() -> AcceptLoop {
let (tx, rx) = sync_mpsc::channel();
let (cmd_reg, cmd_ready) = mio::Registration::new2();
let (notify_reg, notify_ready) = mio::Registration::new2();
AcceptLoop {
tx,
cmd_ready,
cmd_reg: Some(cmd_reg),
notify_ready,
notify_reg: Some(notify_reg),
rx: Some(rx),
srv: Some(mpsc::unbounded()),
}
}
pub fn send(&self, msg: Command) {
let _ = self.tx.send(msg);
let _ = self.cmd_ready.set_readiness(mio::Ready::readable());
}
pub fn get_notify(&self) -> AcceptNotify {
AcceptNotify::new(self.notify_ready.clone())
}
pub(crate) fn start(
&mut self, socks: Vec<Vec<(Token, net::TcpListener)>>,
workers: Vec<WorkerClient>,
) -> mpsc::UnboundedReceiver<ServerCommand> {
let (tx, rx) = self.srv.take().expect("Can not re-use AcceptInfo");
Accept::start(
self.rx.take().expect("Can not re-use AcceptInfo"),
self.cmd_reg.take().expect("Can not re-use AcceptInfo"),
self.notify_reg.take().expect("Can not re-use AcceptInfo"),
socks,
tx,
workers,
);
rx
}
}
struct Accept {
poll: mio::Poll,
rx: sync_mpsc::Receiver<Command>,
sockets: Slab<ServerSocketInfo>,
workers: Vec<WorkerClient>,
srv: mpsc::UnboundedSender<ServerCommand>,
timer: (mio::Registration, mio::SetReadiness),
next: usize,
backpressure: bool,
}
const DELTA: usize = 100;
const CMD: mio::Token = mio::Token(0);
const TIMER: mio::Token = mio::Token(1);
const NOTIFY: mio::Token = mio::Token(2);
/// This function defines errors that are per-connection. Which basically
/// means that if we get this error from `accept()` system call it means
/// next connection might be ready to be accepted.
///
/// All other errors will incur a timeout before next `accept()` is performed.
/// The timeout is useful to handle resource exhaustion errors like ENFILE
/// and EMFILE. Otherwise, could enter into tight loop.
fn connection_error(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::ConnectionAborted
|| e.kind() == io::ErrorKind::ConnectionReset
}
impl Accept {
#![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
pub(crate) fn start(
rx: sync_mpsc::Receiver<Command>, cmd_reg: mio::Registration,
notify_reg: mio::Registration, socks: Vec<Vec<(Token, net::TcpListener)>>,
srv: mpsc::UnboundedSender<ServerCommand>, workers: Vec<WorkerClient>,
) {
let sys = System::current();
// start accept thread
let _ = thread::Builder::new()
.name("actix-web accept loop".to_owned())
.spawn(move || {
System::set_current(sys);
let mut accept = Accept::new(rx, socks, workers, srv);
// Start listening for incoming commands
if let Err(err) = accept.poll.register(
&cmd_reg,
CMD,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err);
}
// Start listening for notify updates
if let Err(err) = accept.poll.register(
&notify_reg,
NOTIFY,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err);
}
accept.poll();
});
}
fn new(
rx: sync_mpsc::Receiver<Command>, socks: Vec<Vec<(Token, net::TcpListener)>>,
workers: Vec<WorkerClient>, srv: mpsc::UnboundedSender<ServerCommand>,
) -> Accept {
// Create a poll instance
let poll = match mio::Poll::new() {
Ok(poll) => poll,
Err(err) => panic!("Can not create mio::Poll: {}", err),
};
// Start accept
let mut sockets = Slab::new();
for (idx, srv_socks) in socks.into_iter().enumerate() {
for (hnd_token, lst) in srv_socks {
let addr = lst.local_addr().unwrap();
let server = mio::net::TcpListener::from_std(lst)
.expect("Can not create mio::net::TcpListener");
let entry = sockets.vacant_entry();
let token = entry.key();
// Start listening for incoming connections
if let Err(err) = poll.register(
&server,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register io: {}", err);
}
entry.insert(ServerSocketInfo {
addr,
token: hnd_token,
handler: Token(idx),
sock: server,
timeout: None,
});
}
}
// Timer
let (tm, tmr) = mio::Registration::new2();
if let Err(err) =
poll.register(&tm, TIMER, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register Registration: {}", err);
}
Accept {
poll,
rx,
sockets,
workers,
srv,
next: 0,
timer: (tm, tmr),
backpressure: false,
}
}
fn poll(&mut self) {
// Create storage for events
let mut events = mio::Events::with_capacity(128);
loop {
if let Err(err) = self.poll.poll(&mut events, None) {
panic!("Poll error: {}", err);
}
for event in events.iter() {
let token = event.token();
match token {
CMD => if !self.process_cmd() {
return;
},
TIMER => self.process_timer(),
NOTIFY => self.backpressure(false),
_ => {
let token = usize::from(token);
if token < DELTA {
continue;
}
self.accept(token - DELTA);
}
}
}
}
}
fn process_timer(&mut self) {
let now = Instant::now();
for (token, info) in self.sockets.iter_mut() {
if let Some(inst) = info.timeout.take() {
if now > inst {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not register server socket {}", err);
} else {
info!("Resume accepting connections on {}", info.addr);
}
} else {
info.timeout = Some(inst);
}
}
}
}
fn process_cmd(&mut self) -> bool {
loop {
match self.rx.try_recv() {
Ok(cmd) => match cmd {
Command::Pause => {
for (_, info) in self.sockets.iter_mut() {
if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err);
} else {
info!("Paused accepting connections on {}", info.addr);
}
}
}
Command::Resume => {
for (token, info) in self.sockets.iter() {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err);
} else {
info!(
"Accepting connections on {} has been resumed",
info.addr
);
}
}
}
Command::Stop => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
Command::Worker(worker) => {
self.backpressure(false);
self.workers.push(worker);
}
},
Err(err) => match err {
sync_mpsc::TryRecvError::Empty => break,
sync_mpsc::TryRecvError::Disconnected => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
},
}
}
true
}
fn backpressure(&mut self, on: bool) {
if self.backpressure {
if !on {
self.backpressure = false;
for (token, info) in self.sockets.iter() {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err);
} else {
info!("Accepting connections on {} has been resumed", info.addr);
}
}
}
} else if on {
self.backpressure = true;
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
}
}
fn accept_one(&mut self, mut msg: Conn<net::TcpStream>) {
if self.backpressure {
while !self.workers.is_empty() {
match self.workers[self.next].send(msg) {
Ok(_) => (),
Err(err) => {
let _ = self.srv.unbounded_send(ServerCommand::WorkerDied(
self.workers[self.next].idx,
));
msg = err.into_inner();
self.workers.swap_remove(self.next);
if self.workers.is_empty() {
error!("No workers");
return;
} else if self.workers.len() <= self.next {
self.next = 0;
}
continue;
}
}
self.next = (self.next + 1) % self.workers.len();
break;
}
} else {
let mut idx = 0;
while idx < self.workers.len() {
idx += 1;
if self.workers[self.next].available() {
match self.workers[self.next].send(msg) {
Ok(_) => {
self.next = (self.next + 1) % self.workers.len();
return;
}
Err(err) => {
let _ = self.srv.unbounded_send(ServerCommand::WorkerDied(
self.workers[self.next].idx,
));
msg = err.into_inner();
self.workers.swap_remove(self.next);
if self.workers.is_empty() {
error!("No workers");
self.backpressure(true);
return;
} else if self.workers.len() <= self.next {
self.next = 0;
}
continue;
}
}
}
self.next = (self.next + 1) % self.workers.len();
}
// enable backpressure
self.backpressure(true);
self.accept_one(msg);
}
}
fn accept(&mut self, token: usize) {
loop {
let msg = if let Some(info) = self.sockets.get_mut(token) {
match info.sock.accept_std() {
Ok((io, addr)) => Conn {
io,
token: info.token,
handler: info.handler,
peer: Some(addr),
},
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return,
Err(ref e) if connection_error(e) => continue,
Err(e) => {
error!("Error accepting connection: {}", e);
if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err);
}
// sleep after error
info.timeout = Some(Instant::now() + Duration::from_millis(500));
let r = self.timer.1.clone();
System::current().arbiter().do_send(Execute::new(
move || -> Result<(), ()> {
Arbiter::spawn(
Delay::new(
Instant::now() + Duration::from_millis(510),
).map_err(|_| ())
.and_then(move |_| {
let _ = r.set_readiness(mio::Ready::readable());
Ok(())
}),
);
Ok(())
},
));
return;
}
}
} else {
return;
};
self.accept_one(msg);
}
}
}

View File

@@ -2,16 +2,16 @@ use std::net::{Shutdown, SocketAddr};
use std::rc::Rc;
use std::{io, ptr, time};
use bytes::{Buf, BufMut, BytesMut};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use futures::{Async, Future, Poll};
use tokio_io::{AsyncRead, AsyncWrite};
use super::settings::WorkerSettings;
use super::{h1, h2, ConnectionTag, 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),
@@ -30,7 +30,6 @@ where
{
proto: Option<HttpProtocol<T, H>>,
node: Option<Node<HttpChannel<T, H>>>,
_tag: ConnectionTag,
}
impl<T, H> HttpChannel<T, H>
@@ -39,19 +38,32 @@ where
H: HttpHandler + 'static,
{
pub(crate) fn new(
settings: Rc<WorkerSettings<H>>, io: T, peer: Option<SocketAddr>,
settings: Rc<WorkerSettings<H>>, mut io: T, peer: Option<SocketAddr>,
http2: bool,
) -> HttpChannel<T, H> {
let _tag = settings.connection();
settings.add_channel();
let _ = io.set_nodelay(true);
HttpChannel {
_tag,
node: None,
proto: Some(HttpProtocol::Unknown(
settings,
peer,
io,
BytesMut::with_capacity(8192),
)),
if http2 {
HttpChannel {
node: None,
proto: Some(HttpProtocol::H2(h2::Http2::new(
settings,
io,
peer,
Bytes::new(),
))),
}
} else {
HttpChannel {
node: None,
proto: Some(HttpProtocol::Unknown(
settings,
peer,
io,
BytesMut::with_capacity(8192),
)),
}
}
}
@@ -82,24 +94,24 @@ 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!(),
};
}
let mut is_eof = false;
let kind = match self.proto {
Some(HttpProtocol::H1(ref mut h1)) => {
let result = h1.poll();
match result {
Ok(Async::Ready(())) | Err(_) => {
h1.settings().remove_channel();
if let Some(n) = self.node.as_mut() {
n.remove()
};
@@ -112,6 +124,7 @@ where
let result = h2.poll();
match result {
Ok(Async::Ready(())) | Err(_) => {
h2.settings().remove_channel();
if let Some(n) = self.node.as_mut() {
n.remove()
};
@@ -120,28 +133,23 @@ where
}
return result;
}
Some(HttpProtocol::Unknown(_, _, ref mut io, ref mut buf)) => {
let mut disconnect = false;
match io.read_available(buf) {
Ok(Async::Ready((read_some, stream_closed))) => {
is_eof = stream_closed;
// Only disconnect if no data was read.
if is_eof && !read_some {
disconnect = true;
}
}
Err(_) => {
disconnect = true;
Some(HttpProtocol::Unknown(
ref mut settings,
_,
ref mut io,
ref mut buf,
)) => {
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() {
n.remove()
};
return Err(());
}
_ => (),
}
if disconnect {
debug!("Ignored premature client disconnection");
if let Some(n) = self.node.as_mut() {
n.remove()
};
return Err(());
}
if buf.len() >= 14 {
if buf[..14] == HTTP2_PREFACE[..] {
@@ -160,9 +168,8 @@ where
if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() {
match kind {
ProtocolKind::Http1 => {
self.proto = Some(HttpProtocol::H1(h1::Http1::new(
settings, io, addr, buf, is_eof,
)));
self.proto =
Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf)));
return self.poll();
}
ProtocolKind::Http2 => {
@@ -181,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,
}
@@ -195,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,33 +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();
// Buffer should have sufficient capacity for status line
// and extra space
bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1);
helpers::write_status_line(self.0, self.1.as_u16(), bytes);
}
// Convert Status Code to Reason.
let reason = self.1.canonical_reason().unwrap_or("");
io.buffer().extend_from_slice(reason.as_bytes());
// No response body.
io.buffer().extend_from_slice(b"\r\ncontent-length: 0\r\n");
// date header
io.set_date();
Ok(Async::Ready(true))
}
}

View File

@@ -1,35 +1,38 @@
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! {
pub struct Flags: u8 {
const STARTED = 0b0000_0001;
const ERROR = 0b0000_0010;
const KEEPALIVE = 0b0000_0100;
const SHUTDOWN = 0b0000_1000;
const READ_DISCONNECTED = 0b0001_0000;
const WRITE_DISCONNECTED = 0b0010_0000;
const POLLED = 0b0100_0000;
struct Flags: u8 {
const STARTED = 0b0000_0001;
const ERROR = 0b0000_0010;
const KEEPALIVE = 0b0000_0100;
const SHUTDOWN = 0b0000_1000;
const DISCONNECTED = 0b0001_0000;
}
}
@@ -41,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>,
@@ -49,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,
}
@@ -91,15 +68,12 @@ where
{
pub fn new(
settings: Rc<WorkerSettings<H>>, stream: T, addr: Option<SocketAddr>,
buf: BytesMut, is_eof: bool,
buf: BytesMut,
) -> Self {
let bytes = settings.get_shared_bytes();
Http1 {
flags: if is_eof {
Flags::READ_DISCONNECTED
} else {
Flags::KEEPALIVE
},
stream: H1Writer::new(stream, Rc::clone(&settings)),
flags: Flags::KEEPALIVE,
stream: H1Writer::new(stream, bytes, Rc::clone(&settings)),
decoder: H1Decoder::new(),
payload: None,
tasks: VecDeque::new(),
@@ -122,13 +96,6 @@ where
#[inline]
fn can_read(&self) -> bool {
if self
.flags
.intersects(Flags::ERROR | Flags::READ_DISCONNECTED)
{
return false;
}
if let Some(ref info) = self.payload {
info.need_read() == PayloadStatus::Read
} else {
@@ -136,31 +103,6 @@ where
}
}
fn notify_disconnect(&mut self) {
self.flags.insert(Flags::WRITE_DISCONNECTED);
// notify all tasks
self.stream.disconnected();
for task in &mut self.tasks {
task.pipe.disconnected();
}
}
fn client_disconnect(&mut self) {
// 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);
}
}
#[inline]
pub fn poll(&mut self) -> Poll<(), ()> {
// keep-alive timer
@@ -177,11 +119,6 @@ where
// shutdown
if self.flags.contains(Flags::SHUTDOWN) {
if self.flags.intersects(
Flags::ERROR | Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED,
) {
return Ok(Async::Ready(()));
}
match self.stream.poll_completed(true) {
Ok(Async::NotReady) => return Ok(Async::NotReady),
Ok(Async::Ready(_)) => return Ok(Async::Ready(())),
@@ -211,31 +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.can_read() && self.tasks.len() < MAX_PIPELINED_MESSAGES {
match self.stream.get_mut().read_available(&mut self.buf) {
Ok(Async::Ready((read_some, disconnected))) => {
if read_some {
self.parse();
}
if disconnected {
// delay disconnect until all tasks have finished.
self.flags.insert(Flags::READ_DISCONNECTED);
if self.tasks.is_empty() {
self.client_disconnect();
}
}
if !self.flags.intersects(Flags::ERROR)
&& self.tasks.len() < MAX_PIPELINED_MESSAGES
&& self.can_read()
{
if self.read() {
// notify all tasks
self.stream.disconnected();
for entry in &mut self.tasks {
entry.pipe.disconnected()
}
Ok(Async::NotReady) => (),
Err(_) => {
self.client_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();
}
}
}
@@ -247,21 +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)
&& !self.flags.contains(Flags::WRITE_DISCONNECTED)
{
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() {
@@ -273,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
@@ -291,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;
}
}
}
@@ -315,6 +246,7 @@ where
}
// cleanup finished tasks
let max = self.tasks.len() >= MAX_PIPELINED_MESSAGES;
while !self.tasks.is_empty() {
if self.tasks[0]
.flags
@@ -325,6 +257,10 @@ where
break;
}
}
// read more message
if max && self.tasks.len() >= MAX_PIPELINED_MESSAGES {
return Ok(Async::Ready(true));
}
// check stream state
if self.flags.contains(Flags::STARTED) {
@@ -332,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(_)) => {
@@ -344,12 +279,8 @@ where
}
}
// deal with keep-alive and steam eof (client-side write shutdown)
// deal with keep-alive
if self.tasks.is_empty() {
// handle stream eof
if self.flags.contains(Flags::READ_DISCONNECTED) {
return Ok(Async::Ready(false));
}
// no keep-alive
if self.flags.contains(Flags::ERROR)
|| (!self.flags.contains(Flags::KEEPALIVE)
@@ -364,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);
@@ -376,28 +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));
}
// stream extensions
msg.inner_mut().stream_extensions =
self.stream.get_mut().extensions();
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() {
msg = match h.handle(msg) {
for h in self.settings.handlers().iter_mut() {
req = match h.handle(req) {
Ok(mut pipe) => {
if self.tasks.is_empty() {
match pipe.poll_io(&mut self.stream) {
@@ -413,7 +344,7 @@ where
if !ready {
let item = Entry {
pipe: EntryPipe::Task(pipe),
pipe,
flags: EntryFlags::EOF,
};
self.tasks.push_back(item);
@@ -429,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(),
});
}
@@ -465,14 +393,7 @@ where
break;
}
}
Ok(None) => {
if self.flags.contains(Flags::READ_DISCONNECTED)
&& self.tasks.is_empty()
{
self.client_disconnect();
}
break;
}
Ok(None) => break,
Err(e) => {
self.flags.insert(Flags::ERROR);
if let Some(mut payload) = self.payload.take() {
@@ -487,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};
@@ -502,20 +446,12 @@ mod tests {
use application::HttpApplication;
use httpmessage::HttpMessage;
use server::h1decoder::Message;
use server::settings::{ServerSettings, WorkerSettings};
use server::{Connections, KeepAlive, Request};
fn wrk_settings() -> Rc<WorkerSettings<HttpApplication>> {
Rc::new(WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
Connections::default(),
))
}
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"),
@@ -543,9 +479,10 @@ mod tests {
macro_rules! parse_ready {
($e:expr) => {{
let settings = wrk_settings();
let settings: WorkerSettings<HttpApplication> =
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),
}
@@ -554,7 +491,8 @@ mod tests {
macro_rules! expect_parse_err {
($e:expr) => {{
let settings = wrk_settings();
let settings: WorkerSettings<HttpApplication> =
WorkerSettings::new(Vec::new(), KeepAlive::Os);
match H1Decoder::new().decode($e, &settings) {
Err(err) => match err {
@@ -578,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 {}
@@ -627,26 +570,17 @@ mod tests {
}
#[test]
fn test_req_parse1() {
fn test_req_parse() {
let buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n");
let readbuf = BytesMut::new();
let settings = Rc::new(wrk_settings());
let settings = Rc::new(WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
));
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false);
h1.poll_io();
h1.poll_io();
assert_eq!(h1.tasks.len(), 1);
}
#[test]
fn test_req_parse2() {
let buf = Buffer::new("");
let readbuf =
BytesMut::from(Vec::<u8>::from(&b"GET /test HTTP/1.1\r\n\r\n"[..]));
let settings = Rc::new(wrk_settings());
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true);
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf);
h1.poll_io();
h1.parse();
assert_eq!(h1.tasks.len(), 1);
}
@@ -654,23 +588,26 @@ mod tests {
fn test_req_parse_err() {
let buf = Buffer::new("GET /test HTTP/1\r\n\r\n");
let readbuf = BytesMut::new();
let settings = Rc::new(wrk_settings());
let settings = Rc::new(WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
));
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false);
h1.poll_io();
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf);
h1.poll_io();
h1.parse();
assert!(h1.flags.contains(Flags::ERROR));
}
#[test]
fn test_parse() {
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n");
let settings = wrk_settings();
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");
@@ -682,7 +619,7 @@ mod tests {
#[test]
fn test_parse_partial() {
let mut buf = BytesMut::from("PUT /test HTTP/1");
let settings = wrk_settings();
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
@@ -693,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");
@@ -705,12 +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 = wrk_settings();
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");
@@ -723,12 +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 = wrk_settings();
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");
@@ -750,12 +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 = wrk_settings();
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");
@@ -776,14 +713,14 @@ mod tests {
#[test]
fn test_parse_partial_eof() {
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n");
let settings = wrk_settings();
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");
@@ -795,7 +732,7 @@ mod tests {
#[test]
fn test_headers_split_field() {
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n");
let settings = wrk_settings();
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let mut reader = H1Decoder::new();
assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() }
@@ -809,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");
@@ -826,10 +763,10 @@ mod tests {
Set-Cookie: c1=cookie1\r\n\
Set-Cookie: c2=cookie2\r\n\r\n",
);
let settings = wrk_settings();
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()
@@ -1022,7 +959,7 @@ mod tests {
#[test]
fn test_http_request_upgrade() {
let settings = wrk_settings();
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\
@@ -1032,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!(
@@ -1088,11 +1025,12 @@ mod tests {
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let settings = wrk_settings();
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");
@@ -1123,11 +1061,11 @@ mod tests {
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let settings = wrk_settings();
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(
@@ -1145,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());
@@ -1157,12 +1095,12 @@ mod tests {
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let settings = wrk_settings();
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");
@@ -1204,12 +1142,13 @@ mod tests {
&"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n"[..],
);
let settings = wrk_settings();
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 {
@@ -166,30 +168,27 @@ impl H1Decoder {
{
true
} else {
version == Version::HTTP_11 && !(conn
.contains("close")
|| conn.contains("upgrade"))
version == Version::HTTP_11
&& !(conn.contains("close")
|| conn.contains("upgrade"))
}
} 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,
@@ -64,18 +66,19 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
}
pub fn disconnected(&mut self) {
self.flags.insert(Flags::DISCONNECTED);
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) => {
@@ -91,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 {
@@ -106,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 {
@@ -128,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()
@@ -148,109 +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
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();
@@ -260,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) {
@@ -268,27 +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) => {
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())
}
}
@@ -300,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",
@@ -314,20 +281,10 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
#[inline]
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
if self.flags.contains(Flags::DISCONNECTED) {
return Err(io::Error::new(io::ErrorKind::Other, "disconnected"));
}
if !self.buffer.is_empty() {
let written = {
match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) {
Err(err) => {
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)
@@ -336,10 +293,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
}
}
if shutdown {
self.stream.poll_flush()?;
self.stream.shutdown()
} else {
Ok(self.stream.poll_flush()?)
Ok(Async::Ready(()))
}
}
}

View File

@@ -1,29 +1,33 @@
#![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 extensions::Extensions;
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, IoStream, Writer};
use super::{HttpHandler, HttpHandlerTask, Writer};
bitflags! {
struct Flags: u8 {
@@ -35,15 +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>,
extensions: Option<Rc<Extensions>>,
keepalive_timer: Option<Timeout>,
}
enum State<T: AsyncRead + AsyncWrite> {
@@ -54,24 +57,22 @@ enum State<T: AsyncRead + AsyncWrite> {
impl<T, H> Http2<T, H>
where
T: IoStream + 'static,
T: AsyncRead + AsyncWrite + 'static,
H: HttpHandler + 'static,
{
pub fn new(
settings: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes,
) -> Self {
let extensions = io.extensions();
Http2 {
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,
addr,
settings,
extensions,
}
}
@@ -102,64 +103,51 @@ where
loop {
let mut not_ready = true;
let disconnected = self.flags.contains(Flags::DISCONNECTED);
// check in-flight connections
for item in &mut self.tasks {
// read payload
if !disconnected {
item.poll_payload();
}
item.poll_payload();
if !item.flags.contains(EntryFlags::EOF) {
if disconnected {
item.flags.insert(EntryFlags::EOF);
} else {
let retry = item.payload.need_read() == PayloadStatus::Read;
loop {
match item.task.poll_io(&mut item.stream) {
Ok(Async::Ready(ready)) => {
if ready {
item.flags.insert(
EntryFlags::EOF | EntryFlags::FINISHED,
);
} else {
item.flags.insert(EntryFlags::EOF);
}
not_ready = false;
}
Ok(Async::NotReady) => {
if item.payload.need_read()
== PayloadStatus::Read
&& !retry
{
continue;
}
}
Err(err) => {
error!("Unhandled error: {}", err);
let retry = item.payload.need_read() == PayloadStatus::Read;
loop {
match item.task.poll_io(&mut item.stream) {
Ok(Async::Ready(ready)) => {
if ready {
item.flags.insert(
EntryFlags::EOF
| EntryFlags::ERROR
| EntryFlags::WRITE_DONE,
EntryFlags::EOF | EntryFlags::FINISHED,
);
item.stream.reset(Reason::INTERNAL_ERROR);
} else {
item.flags.insert(EntryFlags::EOF);
}
not_ready = false;
}
Ok(Async::NotReady) => {
if item.payload.need_read() == PayloadStatus::Read
&& !retry
{
continue;
}
}
break;
Err(err) => {
error!("Unhandled error: {}", err);
item.flags.insert(
EntryFlags::EOF
| EntryFlags::ERROR
| EntryFlags::WRITE_DONE,
);
item.stream.reset(Reason::INTERNAL_ERROR);
}
}
break;
}
}
if item.flags.contains(EntryFlags::EOF)
&& !item.flags.contains(EntryFlags::FINISHED)
{
match item.task.poll_completed() {
} else if !item.flags.contains(EntryFlags::FINISHED) {
match item.task.poll() {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => {
item.flags.insert(
EntryFlags::FINISHED | EntryFlags::WRITE_DONE,
);
not_ready = false;
item.flags.insert(EntryFlags::FINISHED);
}
Err(err) => {
item.flags.insert(
@@ -172,17 +160,14 @@ where
}
}
if item.flags.contains(EntryFlags::FINISHED)
&& !item.flags.contains(EntryFlags::WRITE_DONE)
&& !disconnected
{
if !item.flags.contains(EntryFlags::WRITE_DONE) {
match item.stream.poll_completed(false) {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => {
not_ready = false;
item.flags.insert(EntryFlags::WRITE_DONE);
}
Err(_) => {
Err(_err) => {
item.flags.insert(EntryFlags::ERROR);
}
}
@@ -191,7 +176,7 @@ where
// cleanup finished tasks
while !self.tasks.is_empty() {
if self.tasks[0].flags.contains(EntryFlags::FINISHED)
if self.tasks[0].flags.contains(EntryFlags::EOF)
&& self.tasks[0].flags.contains(EntryFlags::WRITE_DONE)
|| self.tasks[0].flags.contains(EntryFlags::ERROR)
{
@@ -224,7 +209,6 @@ where
resp,
self.addr,
&self.settings,
self.extensions.clone(),
));
}
Ok(Async::NotReady) => {
@@ -234,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);
@@ -305,45 +288,18 @@ 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>>,
extensions: Option<Rc<Extensions>>,
) -> Entry<H>
where
H: HttpHandler + 'static,
@@ -351,42 +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.stream_extensions = extensions;
*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() {
msg = match h.handle(msg) {
for h in settings.handlers().iter_mut() {
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

@@ -8,18 +8,17 @@ use modhttp::Response;
use std::rc::Rc;
use std::{cmp, io};
use http::{HttpTryFrom, Method, Version};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
use http::{HttpTryFrom, Version};
use super::encoding::ContentEncoder;
use super::helpers;
use super::message::Request;
use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::WorkerSettings;
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 httprequest::HttpInnerMessage;
use httpresponse::HttpResponse;
const CHUNK_SIZE: usize = 16_384;
@@ -36,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,
}
}
@@ -65,94 +66,74 @@ 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);
msg.headers_mut().remove(TRANSFER_ENCODING);
// 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_simple(&mut bytes);
msg.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
}
let body = msg.replace_body(Body::Empty);
match body {
Body::Binary(ref bytes) => {
if bytes.is_empty() {
msg.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
self.flags.insert(Flags::EOF);
} else {
let mut val = BytesMut::new();
helpers::convert_usize(bytes.len(), &mut val);
let l = val.len();
msg.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
);
}
}
Body::Empty => {
self.flags.insert(Flags::EOF);
msg.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
}
_ => (),
}
let mut has_date = false;
let mut resp = Response::new(());
*resp.status_mut() = msg.status();
*resp.version_mut() = Version::HTTP_2;
for (key, value) in msg.headers().iter() {
match *key {
// http2 specific
CONNECTION | TRANSFER_ENCODING => continue,
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
continue;
},
CONTENT_LENGTH => match info.length {
ResponseLength::None => (),
_ => continue,
},
DATE => has_date = true,
_ => (),
}
resp.headers_mut().append(key, value.clone());
resp.headers_mut().insert(key, value.clone());
}
// set date header
if !has_date {
let mut bytes = BytesMut::with_capacity(29);
self.settings.set_date(&mut bytes, false);
resp.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
}
// content length
match info.length {
ResponseLength::Zero => {
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
self.flags.insert(Flags::EOF);
}
ResponseLength::Length(len) => {
let mut val = BytesMut::new();
helpers::convert_usize(len, &mut val);
let l = val.len();
resp.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
);
}
ResponseLength::Length64(len) => {
let l = format!("{}", len);
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap());
}
_ => (),
}
if let Some(ce) = info.content_encoding {
resp.headers_mut()
.insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap());
}
trace!("Response: {:?}", resp);
match self
.respond
.send_response(resp, self.flags.contains(Flags::EOF))
@@ -161,13 +142,15 @@ impl<H: 'static> Writer for H2Writer<H> {
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")),
}
let body = msg.replace_body(Body::Empty);
trace!("Response: {:?}", msg);
if let Body::Binary(bytes) = body {
if bytes.is_empty() {
Ok(WriterState::Done)
} else {
self.flags.insert(Flags::EOF);
self.buffer.write(bytes.as_ref())?;
self.written = bytes.len() as u64;
self.encoder.write(bytes)?;
if let Some(ref mut stream) = self.stream {
self.flags.insert(Flags::RESERVED);
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
@@ -181,14 +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())
}
}
@@ -201,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",
@@ -242,18 +227,14 @@ impl<H: 'static> Writer for H2Writer<H> {
let cap = cmp::min(self.buffer.len(), CHUNK_SIZE);
stream.reserve_capacity(cap);
} else {
if eof {
stream.reserve_capacity(0);
continue;
}
self.flags.remove(Flags::RESERVED);
return Ok(Async::Ready(()));
return Ok(Async::NotReady);
}
}
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
}
}
}
Ok(Async::Ready(()))
Ok(Async::NotReady)
}
}

View File

@@ -1,17 +1,102 @@
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\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
pub(crate) const STATUS_LINE_BUF_SIZE: usize = 13;
pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
let mut buf: [u8; STATUS_LINE_BUF_SIZE] = [
let mut buf: [u8; 13] = [
b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ',
];
match version {
@@ -107,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;
@@ -147,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,812 +0,0 @@
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::Arc;
use std::{io, mem, net, time};
use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, System};
use futures::{Future, Stream};
use net2::{TcpBuilder, TcpStreamExt};
use num_cpus;
use tokio::executor::current_thread;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tcp::TcpStream;
#[cfg(feature = "tls")]
use native_tls::TlsAcceptor;
#[cfg(feature = "alpn")]
use openssl::ssl::SslAcceptorBuilder;
#[cfg(feature = "rust-tls")]
use rustls::ServerConfig;
use super::channel::{HttpChannel, WrapperStream};
use super::server::{Connections, Server, Service, ServiceHandler};
use super::settings::{ServerSettings, WorkerSettings};
use super::worker::{Conn, Socket};
use super::{
AcceptorService, HttpHandler, IntoAsyncIo, IntoHttpHandler, IoStream, KeepAlive,
Token,
};
/// An HTTP Server
///
/// By default it serves HTTP2 when HTTPs is enabled,
/// in order to change it, use `ServerFlags` that can be provided
/// to acceptor service.
pub struct HttpServer<H>
where
H: IntoHttpHandler + 'static,
{
factory: Arc<Fn() -> Vec<H> + Send + Sync>,
host: Option<String>,
keep_alive: KeepAlive,
backlog: i32,
threads: usize,
exit: bool,
shutdown_timeout: u16,
no_http2: bool,
no_signals: bool,
maxconn: usize,
maxconnrate: usize,
sockets: Vec<Socket>,
handlers: Vec<Box<IoStreamHandler<H::Handler, net::TcpStream>>>,
}
impl<H> HttpServer<H>
where
H: IntoHttpHandler + 'static,
{
/// Create new http server with application factory
pub fn new<F, U>(factory: F) -> Self
where
F: Fn() -> U + Sync + Send + 'static,
U: IntoIterator<Item = H> + 'static,
{
let f = move || (factory)().into_iter().collect();
HttpServer {
threads: num_cpus::get(),
factory: Arc::new(f),
host: None,
backlog: 2048,
keep_alive: KeepAlive::Os,
shutdown_timeout: 30,
exit: true,
no_http2: false,
no_signals: false,
maxconn: 102_400,
maxconnrate: 256,
// settings: None,
sockets: Vec::new(),
handlers: Vec::new(),
}
}
/// Set number of workers to start.
///
/// By default http server uses number of available logical cpu as threads
/// count.
pub fn workers(mut self, num: usize) -> Self {
self.threads = num;
self
}
/// Set the maximum number of pending connections.
///
/// This refers to the number of clients that can be waiting to be served.
/// Exceeding this number results in the client getting an error when
/// attempting to connect. It should only affect servers under significant
/// load.
///
/// Generally set in the 64-2048 range. Default value is 2048.
///
/// This method should be called before `bind()` method call.
pub fn backlog(mut self, num: i32) -> Self {
self.backlog = num;
self
}
/// Sets the maximum per-worker number of concurrent connections.
///
/// All socket listeners will stop accepting connections when this limit is reached
/// for each worker.
///
/// By default max connections is set to a 100k.
pub fn maxconn(mut self, num: usize) -> Self {
self.maxconn = num;
self
}
/// Sets the maximum per-worker concurrent connection establish process.
///
/// All listeners will stop accepting connections when this limit is reached. It
/// can be used to limit the global SSL CPU usage.
///
/// By default max connections is set to a 256.
pub fn maxconnrate(mut self, num: usize) -> Self {
self.maxconnrate = num;
self
}
/// Set server keep-alive setting.
///
/// By default keep alive is set to a `Os`.
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
self.keep_alive = val.into();
self
}
/// Set server host name.
///
/// Host name is used by application router aa a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
pub fn server_hostname(mut self, val: String) -> Self {
self.host = Some(val);
self
}
/// Stop actix system.
///
/// `SystemExit` message stops currently running system.
pub fn system_exit(mut self) -> Self {
self.exit = true;
self
}
/// Disable signal handling
pub fn disable_signals(mut self) -> Self {
self.no_signals = true;
self
}
/// Timeout for graceful workers shutdown.
///
/// After receiving a stop signal, workers have this much time to finish
/// serving requests. Workers still alive after the timeout are force
/// dropped.
///
/// By default shutdown timeout sets to 30 seconds.
pub fn shutdown_timeout(mut self, sec: u16) -> Self {
self.shutdown_timeout = sec;
self
}
/// Disable `HTTP/2` support
// #[doc(hidden)]
// #[deprecated(
// since = "0.7.4",
// note = "please use acceptor service with proper ServerFlags parama"
// )]
pub fn no_http2(mut self) -> Self {
self.no_http2 = true;
self
}
/// Get addresses of bound sockets.
pub fn addrs(&self) -> Vec<net::SocketAddr> {
self.sockets.iter().map(|s| s.addr).collect()
}
/// Get addresses of bound sockets and the scheme for it.
///
/// This is useful when the server is bound from different sources
/// with some sockets listening on http and some listening on https
/// and the user should be presented with an enumeration of which
/// socket requires which protocol.
pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> {
self.handlers
.iter()
.map(|s| (s.addr(), s.scheme()))
.collect()
}
/// Use listener for accepting incoming connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen(mut self, lst: net::TcpListener) -> Self {
let token = Token(self.handlers.len());
let addr = lst.local_addr().unwrap();
self.handlers
.push(Box::new(SimpleHandler::new(lst.local_addr().unwrap())));
self.sockets.push(Socket { lst, addr, token });
self
}
#[doc(hidden)]
/// Use listener for accepting incoming connection requests
pub fn listen_with<A>(mut self, lst: net::TcpListener, acceptor: A) -> Self
where
A: AcceptorService<TcpStream> + Send + 'static,
{
let token = Token(self.handlers.len());
let addr = lst.local_addr().unwrap();
self.handlers.push(Box::new(StreamHandler::new(
lst.local_addr().unwrap(),
acceptor,
)));
self.sockets.push(Socket { lst, addr, token });
self
}
#[cfg(feature = "tls")]
/// Use listener for accepting incoming tls connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self {
use super::NativeTlsAcceptor;
self.listen_with(lst, NativeTlsAcceptor::new(acceptor))
}
#[cfg(feature = "alpn")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_ssl(
self, lst: net::TcpListener, builder: SslAcceptorBuilder,
) -> io::Result<Self> {
use super::{OpensslAcceptor, ServerFlags};
// alpn support
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
Ok(self.listen_with(lst, OpensslAcceptor::with_flags(builder, flags)?))
}
#[cfg(feature = "rust-tls")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_rustls(self, lst: net::TcpListener, builder: ServerConfig) -> Self {
use super::{RustlsAcceptor, ServerFlags};
// alpn support
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
self.listen_with(lst, RustlsAcceptor::with_flags(builder, flags))
}
/// The socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
for lst in sockets {
let token = Token(self.handlers.len());
let addr = lst.local_addr().unwrap();
self.handlers
.push(Box::new(SimpleHandler::new(lst.local_addr().unwrap())));
self.sockets.push(Socket { lst, addr, token })
}
Ok(self)
}
/// Start listening for incoming connections with supplied acceptor.
#[doc(hidden)]
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
pub fn bind_with<S, A>(mut self, addr: S, acceptor: A) -> io::Result<Self>
where
S: net::ToSocketAddrs,
A: AcceptorService<TcpStream> + Send + 'static,
{
let sockets = self.bind2(addr)?;
for lst in sockets {
let token = Token(self.handlers.len());
let addr = lst.local_addr().unwrap();
self.handlers.push(Box::new(StreamHandler::new(
lst.local_addr().unwrap(),
acceptor.clone(),
)));
self.sockets.push(Socket { lst, addr, token })
}
Ok(self)
}
fn bind2<S: net::ToSocketAddrs>(
&self, addr: S,
) -> io::Result<Vec<net::TcpListener>> {
let mut err = None;
let mut succ = false;
let mut sockets = Vec::new();
for addr in addr.to_socket_addrs()? {
match create_tcp_listener(addr, self.backlog) {
Ok(lst) => {
succ = true;
sockets.push(lst);
}
Err(e) => err = Some(e),
}
}
if !succ {
if let Some(e) = err.take() {
Err(e)
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Can not bind to address.",
))
}
} else {
Ok(sockets)
}
}
#[cfg(feature = "tls")]
/// The ssl socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind_tls<S: net::ToSocketAddrs>(
self, addr: S, acceptor: TlsAcceptor,
) -> io::Result<Self> {
use super::NativeTlsAcceptor;
self.bind_with(addr, NativeTlsAcceptor::new(acceptor))
}
#[cfg(feature = "alpn")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_ssl<S>(self, addr: S, builder: SslAcceptorBuilder) -> io::Result<Self>
where
S: net::ToSocketAddrs,
{
use super::{OpensslAcceptor, ServerFlags};
// alpn support
let flags = if !self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
self.bind_with(addr, OpensslAcceptor::with_flags(builder, flags)?)
}
#[cfg(feature = "rust-tls")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_rustls<S: net::ToSocketAddrs>(
self, addr: S, builder: ServerConfig,
) -> io::Result<Self> {
use super::{RustlsAcceptor, ServerFlags};
// alpn support
let flags = if !self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
self.bind_with(addr, RustlsAcceptor::with_flags(builder, flags))
}
}
impl<H: IntoHttpHandler> Into<(Box<Service>, Vec<(Token, net::TcpListener)>)>
for HttpServer<H>
{
fn into(mut self) -> (Box<Service>, Vec<(Token, net::TcpListener)>) {
let sockets: Vec<_> = mem::replace(&mut self.sockets, Vec::new())
.into_iter()
.map(|item| (item.token, item.lst))
.collect();
(
Box::new(HttpService {
factory: self.factory,
host: self.host,
keep_alive: self.keep_alive,
handlers: self.handlers,
}),
sockets,
)
}
}
struct HttpService<H: IntoHttpHandler> {
factory: Arc<Fn() -> Vec<H> + Send + Sync>,
host: Option<String>,
keep_alive: KeepAlive,
handlers: Vec<Box<IoStreamHandler<H::Handler, net::TcpStream>>>,
}
impl<H: IntoHttpHandler + 'static> Service for HttpService<H> {
fn clone(&self) -> Box<Service> {
Box::new(HttpService {
factory: self.factory.clone(),
host: self.host.clone(),
keep_alive: self.keep_alive,
handlers: self.handlers.iter().map(|v| v.clone()).collect(),
})
}
fn create(&self, conns: Connections) -> Box<ServiceHandler> {
let addr = self.handlers[0].addr();
let s = ServerSettings::new(Some(addr), &self.host, false);
let apps: Vec<_> = (*self.factory)()
.into_iter()
.map(|h| h.into_handler())
.collect();
let handlers = self.handlers.iter().map(|h| h.clone()).collect();
Box::new(HttpServiceHandler::new(
apps,
handlers,
self.keep_alive,
s,
conns,
))
}
}
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming connections.
///
/// This method starts number of http workers in separate threads.
/// For each address this method starts separate thread which does
/// `accept()` in a loop.
///
/// This methods panics if no socket addresses get bound.
///
/// This method requires to run within properly configured `Actix` system.
///
/// ```rust
/// extern crate actix_web;
/// use actix_web::{actix, 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")
/// .start();
/// # actix::System::current().stop();
/// sys.run(); // <- Run actix system, this method starts all async processes
/// }
/// ```
pub fn start(self) -> Addr<Server> {
let mut srv = Server::new()
.workers(self.threads)
.maxconn(self.maxconn)
.maxconnrate(self.maxconnrate)
.shutdown_timeout(self.shutdown_timeout);
srv = if self.exit { srv.system_exit() } else { srv };
srv = if self.no_signals {
srv.disable_signals()
} else {
srv
};
srv.service(self).start()
}
/// Spawn new thread and start listening for incoming connections.
///
/// This method spawns new thread and starts new actix system. Other than
/// that it is similar to `start()` method. This method blocks.
///
/// This methods panics if no socket addresses get bound.
///
/// ```rust,ignore
/// # extern crate futures;
/// # 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")
/// .run();
/// }
/// ```
pub fn run(self) {
let sys = System::new("http-server");
self.start();
sys.run();
}
}
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>(self, stream: S, secure: bool)
where
S: Stream<Item = T, Error = io::Error> + Send + 'static,
T: AsyncRead + AsyncWrite + Send + 'static,
{
// set server settings
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
let srv_settings = ServerSettings::new(Some(addr), &self.host, secure);
let apps: Vec<_> = (*self.factory)()
.into_iter()
.map(|h| h.into_handler())
.collect();
let settings = WorkerSettings::create(
apps,
self.keep_alive,
srv_settings,
Connections::default(),
);
// start server
HttpIncoming::create(move |ctx| {
ctx.add_message_stream(stream.map_err(|_| ()).map(move |t| Conn {
io: WrapperStream::new(t),
handler: Token::new(0),
token: Token::new(0),
peer: None,
}));
HttpIncoming { settings }
});
}
}
struct HttpIncoming<H: HttpHandler> {
settings: Rc<WorkerSettings<H>>,
}
impl<H> Actor for HttpIncoming<H>
where
H: HttpHandler,
{
type Context = Context<Self>;
}
impl<T, H> Handler<Conn<T>> for HttpIncoming<H>
where
T: IoStream,
H: HttpHandler,
{
type Result = ();
fn handle(&mut self, msg: Conn<T>, _: &mut Context<Self>) -> Self::Result {
Arbiter::spawn(HttpChannel::new(
Rc::clone(&self.settings),
msg.io,
msg.peer,
));
}
}
struct HttpServiceHandler<H>
where
H: HttpHandler + 'static,
{
settings: Rc<WorkerSettings<H>>,
handlers: Vec<Box<IoStreamHandler<H, net::TcpStream>>>,
tcp_ka: Option<time::Duration>,
}
impl<H: HttpHandler + 'static> HttpServiceHandler<H> {
fn new(
apps: Vec<H>, handlers: Vec<Box<IoStreamHandler<H, net::TcpStream>>>,
keep_alive: KeepAlive, settings: ServerSettings, conns: Connections,
) -> HttpServiceHandler<H> {
let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive {
Some(time::Duration::new(val as u64, 0))
} else {
None
};
let settings = WorkerSettings::create(apps, keep_alive, settings, conns);
HttpServiceHandler {
handlers,
tcp_ka,
settings,
}
}
}
impl<H> ServiceHandler for HttpServiceHandler<H>
where
H: HttpHandler + 'static,
{
fn handle(
&mut self, token: Token, io: net::TcpStream, peer: Option<net::SocketAddr>,
) {
if self.tcp_ka.is_some() && io.set_keepalive(self.tcp_ka).is_err() {
error!("Can not set socket keep-alive option");
}
self.handlers[token.0].handle(Rc::clone(&self.settings), io, peer);
}
fn shutdown(&self, force: bool) {
if force {
self.settings.head().traverse::<TcpStream, H>();
}
}
}
struct SimpleHandler<Io> {
addr: net::SocketAddr,
io: PhantomData<Io>,
}
impl<Io: IntoAsyncIo> Clone for SimpleHandler<Io> {
fn clone(&self) -> Self {
SimpleHandler {
addr: self.addr,
io: PhantomData,
}
}
}
impl<Io: IntoAsyncIo> SimpleHandler<Io> {
fn new(addr: net::SocketAddr) -> Self {
SimpleHandler {
addr,
io: PhantomData,
}
}
}
impl<H, Io> IoStreamHandler<H, Io> for SimpleHandler<Io>
where
H: HttpHandler,
Io: IntoAsyncIo + Send + 'static,
Io::Io: IoStream,
{
fn addr(&self) -> net::SocketAddr {
self.addr
}
fn clone(&self) -> Box<IoStreamHandler<H, Io>> {
Box::new(Clone::clone(self))
}
fn scheme(&self) -> &'static str {
"http"
}
fn handle(&self, h: Rc<WorkerSettings<H>>, io: Io, peer: Option<net::SocketAddr>) {
let mut io = match io.into_async_io() {
Ok(io) => io,
Err(err) => {
trace!("Failed to create async io: {}", err);
return;
}
};
let _ = io.set_nodelay(true);
current_thread::spawn(HttpChannel::new(h, io, peer));
}
}
struct StreamHandler<A, Io> {
acceptor: A,
addr: net::SocketAddr,
io: PhantomData<Io>,
}
impl<Io: IntoAsyncIo, A: AcceptorService<Io::Io>> StreamHandler<A, Io> {
fn new(addr: net::SocketAddr, acceptor: A) -> Self {
StreamHandler {
addr,
acceptor,
io: PhantomData,
}
}
}
impl<Io: IntoAsyncIo, A: AcceptorService<Io::Io>> Clone for StreamHandler<A, Io> {
fn clone(&self) -> Self {
StreamHandler {
addr: self.addr,
acceptor: self.acceptor.clone(),
io: PhantomData,
}
}
}
impl<H, Io, A> IoStreamHandler<H, Io> for StreamHandler<A, Io>
where
H: HttpHandler,
Io: IntoAsyncIo + Send + 'static,
Io::Io: IoStream,
A: AcceptorService<Io::Io> + Send + 'static,
{
fn addr(&self) -> net::SocketAddr {
self.addr
}
fn clone(&self) -> Box<IoStreamHandler<H, Io>> {
Box::new(Clone::clone(self))
}
fn scheme(&self) -> &'static str {
self.acceptor.scheme()
}
fn handle(&self, h: Rc<WorkerSettings<H>>, io: Io, peer: Option<net::SocketAddr>) {
let mut io = match io.into_async_io() {
Ok(io) => io,
Err(err) => {
trace!("Failed to create async io: {}", err);
return;
}
};
let _ = io.set_nodelay(true);
let rate = h.connection_rate();
current_thread::spawn(self.acceptor.accept(io).then(move |res| {
drop(rate);
match res {
Ok(io) => current_thread::spawn(HttpChannel::new(h, io, peer)),
Err(err) => trace!("Can not establish connection: {}", err),
}
Ok(())
}))
}
}
impl<H, Io: 'static> IoStreamHandler<H, Io> for Box<IoStreamHandler<H, Io>>
where
H: HttpHandler,
Io: IntoAsyncIo,
{
fn addr(&self) -> net::SocketAddr {
self.as_ref().addr()
}
fn clone(&self) -> Box<IoStreamHandler<H, Io>> {
self.as_ref().clone()
}
fn scheme(&self) -> &'static str {
self.as_ref().scheme()
}
fn handle(&self, h: Rc<WorkerSettings<H>>, io: Io, peer: Option<net::SocketAddr>) {
self.as_ref().handle(h, io, peer)
}
}
trait IoStreamHandler<H, Io>: Send
where
H: HttpHandler,
{
fn clone(&self) -> Box<IoStreamHandler<H, Io>>;
fn addr(&self) -> net::SocketAddr;
fn scheme(&self) -> &'static str;
fn handle(&self, h: Rc<WorkerSettings<H>>, io: Io, peer: Option<net::SocketAddr>);
}
fn create_tcp_listener(
addr: net::SocketAddr, backlog: i32,
) -> io::Result<net::TcpListener> {
let builder = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
};
builder.reuse_address(true)?;
builder.bind(addr)?;
Ok(builder.listen(backlog)?)
}

View File

@@ -1,288 +0,0 @@
use std::io::{self, Write};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliDecoder;
use bytes::{Bytes, BytesMut};
use error::PayloadError;
#[cfg(feature = "flate2")]
use flate2::write::{GzDecoder, ZlibDecoder};
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<ZlibDecoder<Writer>>),
#[cfg(feature = "flate2")]
Gzip(Box<GzDecoder<Writer>>),
#[cfg(feature = "brotli")]
Br(Box<BrotliDecoder<Writer>>),
Identity,
}
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,
}
impl PayloadStream {
pub fn new(enc: ContentEncoding) -> PayloadStream {
let decoder = match enc {
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => {
Decoder::Deflate(Box::new(ZlibDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => {
Decoder::Gzip(Box::new(GzDecoder::new(Writer::new())))
}
_ => Decoder::Identity,
};
PayloadStream { decoder }
}
}
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) => 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),
},
#[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) => 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::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,263 +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,
pub(crate) stream_extensions: Option<Rc<Extensions>>,
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()),
stream_extensions: None,
}),
}
}
#[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()
}
}
/// Io stream extensions
#[inline]
pub fn stream_extensions(&self) -> Option<&Extensions> {
self.inner().stream_extensions.as_ref().map(|e| e.as_ref())
}
/// 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

@@ -1,178 +1,54 @@
//! Http server module
//!
//! The module contains everything necessary to setup
//! HTTP server.
//!
//! In order to start HTTP server, first you need to create and configure it
//! using factory that can be supplied to [new](fn.new.html).
//!
//! ## Factory
//!
//! Factory is a function that returns Application, describing how
//! to serve incoming HTTP requests.
//!
//! As the server uses worker pool, the factory function is restricted to trait bounds
//! `Sync + Send + 'static` so that each worker would be able to accept Application
//! without a need for synchronization.
//!
//! If you wish to share part of state among all workers you should
//! wrap it in `Arc` and potentially synchronization primitive like
//! [RwLock](https://doc.rust-lang.org/std/sync/struct.RwLock.html)
//! If the wrapped type is not thread safe.
//!
//! Note though that locking is not advisable for asynchronous programming
//! and you should minimize all locks in your request handlers
//!
//! ## HTTPS Support
//!
//! Actix-web provides support for major crates that provides TLS.
//! Each TLS implementation is provided with [AcceptorService](trait.AcceptorService.html)
//! that describes how HTTP Server accepts connections.
//!
//! For `bind` and `listen` there are corresponding `bind_with` and `listen_with` that accepts
//! these services.
//!
//! By default, acceptor would work with both HTTP2 and HTTP1 protocols.
//! But it can be controlled using [ServerFlags](struct.ServerFlags.html) which
//! can be supplied when creating `AcceptorService`.
//!
//! **NOTE:** `native-tls` doesn't support `HTTP2` yet
//!
//! ## Signal handling and shutdown
//!
//! By default HTTP Server listens for system signals
//! and, gracefully shuts down at most after 30 seconds.
//!
//! Both signal handling and shutdown timeout can be controlled
//! using corresponding methods.
//!
//! If worker, for some reason, unable to shut down within timeout
//! it is forcibly dropped.
//!
//! ## Example
//!
//! ```rust,ignore
//!extern crate actix;
//!extern crate actix_web;
//!extern crate rustls;
//!
//!use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse, Responder};
//!use std::io::BufReader;
//!use rustls::internal::pemfile::{certs, rsa_private_keys};
//!use rustls::{NoClientAuth, ServerConfig};
//!
//!fn index(req: &HttpRequest) -> Result<HttpResponse, Error> {
//! Ok(HttpResponse::Ok().content_type("text/plain").body("Welcome!"))
//!}
//!
//!fn load_ssl() -> ServerConfig {
//! use std::io::BufReader;
//!
//! const CERT: &'static [u8] = include_bytes!("../cert.pem");
//! const KEY: &'static [u8] = include_bytes!("../key.pem");
//!
//! let mut cert = BufReader::new(CERT);
//! let mut key = BufReader::new(KEY);
//!
//! let mut config = ServerConfig::new(NoClientAuth::new());
//! let cert_chain = certs(&mut cert).unwrap();
//! let mut keys = rsa_private_keys(&mut key).unwrap();
//! config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
//!
//! config
//!}
//!
//!fn main() {
//! let sys = actix::System::new("http-server");
//! // load ssl keys
//! let config = load_ssl();
//!
//! // Create acceptor service for only HTTP1 protocol
//! // You can use ::new(config) to leave defaults
//! let acceptor = server::RustlsAcceptor::with_flags(config, actix_web::server::ServerFlags::HTTP1);
//!
//! // create and start server at once
//! server::new(|| {
//! App::new()
//! // register simple handler, handle all methods
//! .resource("/index.html", |r| r.f(index))
//! }))
//! }).bind_with("127.0.0.1:8080", acceptor)
//! .unwrap()
//! .start();
//!
//! println!("Started http server: 127.0.0.1:8080");
//! //Run system so that server would start accepting connections
//! let _ = sys.run();
//!}
//! ```
//! Http server
use std::net::Shutdown;
use std::rc::Rc;
use std::{io, net, time};
use std::{io, time};
use bytes::{BufMut, BytesMut};
use futures::{Async, Future, Poll};
use actix;
use bytes::BytesMut;
use futures::{Async, Poll};
use tokio_core::net::TcpStream;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_reactor::Handle;
use tokio_tcp::TcpStream;
pub(crate) mod accept;
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;
mod http;
pub(crate) mod input;
pub(crate) mod message;
pub(crate) mod output;
mod server;
pub(crate) mod settings;
mod ssl;
mod settings;
pub(crate) mod shared;
mod srv;
pub(crate) mod utils;
mod worker;
use actix::Message;
pub use self::message::Request;
pub use self::http::HttpServer;
#[doc(hidden)]
pub use self::server::{
ConnectionRateTag, ConnectionTag, Connections, Server, Service, ServiceHandler,
};
pub use self::settings::ServerSettings;
#[doc(hidden)]
pub use self::ssl::*;
pub use self::srv::HttpServer;
#[doc(hidden)]
pub use self::helpers::write_content_length;
use body::Binary;
use error::Error;
use extensions::Extensions;
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()
@@ -180,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>
@@ -193,17 +69,6 @@ where
HttpServer::new(factory)
}
#[doc(hidden)]
bitflags! {
///Flags that can be used to configure HTTP Server.
pub struct ServerFlags: u8 {
///Use HTTP1 protocol
const HTTP1 = 0b0000_0001;
///Use HTTP2 protocol
const HTTP2 = 0b0000_0010;
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
/// Server keep-alive setting
pub enum KeepAlive {
@@ -248,47 +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<(), ()>;
}
/// Socket id token
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct Token(usize);
impl Token {
pub(crate) fn new(val: usize) -> Token {
Token(val)
}
}
/// 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(()))
}
@@ -299,58 +147,23 @@ 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
}
}
pub(crate) trait IntoAsyncIo {
type Io: AsyncRead + AsyncWrite;
fn into_async_io(self) -> Result<Self::Io, io::Error>;
}
impl IntoAsyncIo for net::TcpStream {
type Io = TcpStream;
fn into_async_io(self) -> Result<Self::Io, io::Error> {
TcpStream::from_std(self, &Handle::default())
}
}
#[doc(hidden)]
/// Trait implemented by types that could accept incomming socket connections.
pub trait AcceptorService<Io: AsyncRead + AsyncWrite>: Clone {
/// Established connection type
type Accepted: IoStream;
/// Future describes async accept process.
type Future: Future<Item = Self::Accepted, Error = io::Error> + 'static;
/// Establish new connection
fn accept(&self, io: Io) -> Self::Future;
/// Scheme
fn scheme(&self) -> &'static str;
}
#[doc(hidden)]
#[derive(Debug)]
pub enum WriterState {
@@ -365,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>;
@@ -389,61 +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, 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, true)));
} else {
read_some = true;
buf.advance_mut(n);
}
}
Err(e) => {
return if e.kind() == io::ErrorKind::WouldBlock {
if read_some {
Ok(Async::Ready((read_some, false)))
} else {
Ok(Async::NotReady)
}
} else {
Err(e)
};
}
}
}
}
}
/// Extra io stream extensions
fn extensions(&self) -> Option<Rc<Extensions>> {
None
}
}
#[cfg(all(unix, feature = "uds"))]
impl IoStream for ::tokio_uds::UnixStream {
#[inline]
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
::tokio_uds::UnixStream::shutdown(self, how)
}
#[inline]
fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_linger(&mut self, _dur: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
}
impl IoStream for TcpStream {
@@ -462,3 +222,47 @@ impl IoStream for TcpStream {
TcpStream::set_linger(self, dur)
}
}
#[cfg(feature = "alpn")]
use tokio_openssl::SslStream;
#[cfg(feature = "alpn")]
impl IoStream for SslStream<TcpStream> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
}
#[cfg(feature = "tls")]
use tokio_tls::TlsStream;
#[cfg(feature = "tls")]
impl IoStream for TlsStream<TcpStream> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
}

View File

@@ -1,528 +0,0 @@
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use std::time::Duration;
use std::{mem, net};
use futures::sync::{mpsc, mpsc::unbounded};
use futures::{Future, Sink, Stream};
use num_cpus;
use actix::{
fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler,
Response, StreamHandler, System, WrapFuture,
};
use super::accept::{AcceptLoop, AcceptNotify, Command};
use super::worker::{Conn, StopWorker, Worker, WorkerClient};
use super::{PauseServer, ResumeServer, StopServer, Token};
#[doc(hidden)]
/// Describes service that could be used
/// with [Server](struct.Server.html)
pub trait Service: Send + 'static {
/// Clone service
fn clone(&self) -> Box<Service>;
/// Create service handler for this service
fn create(&self, conn: Connections) -> Box<ServiceHandler>;
}
impl Service for Box<Service> {
fn clone(&self) -> Box<Service> {
self.as_ref().clone()
}
fn create(&self, conn: Connections) -> Box<ServiceHandler> {
self.as_ref().create(conn)
}
}
#[doc(hidden)]
/// Describes the way serivce handles incoming
/// TCP connections.
pub trait ServiceHandler {
/// Handle incoming stream
fn handle(
&mut self, token: Token, io: net::TcpStream, peer: Option<net::SocketAddr>,
);
/// Shutdown open handlers
fn shutdown(&self, _: bool) {}
}
pub(crate) enum ServerCommand {
WorkerDied(usize),
}
/// Generic server
#[doc(hidden)]
pub struct Server {
threads: usize,
workers: Vec<(usize, Addr<Worker>)>,
services: Vec<Box<Service>>,
sockets: Vec<Vec<(Token, net::TcpListener)>>,
accept: AcceptLoop,
exit: bool,
shutdown_timeout: u16,
signals: Option<Addr<signal::ProcessSignals>>,
no_signals: bool,
maxconn: usize,
maxconnrate: usize,
}
impl Default for Server {
fn default() -> Self {
Self::new()
}
}
impl Server {
/// Create new Server instance
pub fn new() -> Server {
Server {
threads: num_cpus::get(),
workers: Vec::new(),
services: Vec::new(),
sockets: Vec::new(),
accept: AcceptLoop::new(),
exit: false,
shutdown_timeout: 30,
signals: None,
no_signals: false,
maxconn: 102_400,
maxconnrate: 256,
}
}
/// Set number of workers to start.
///
/// By default http server uses number of available logical cpu as threads
/// count.
pub fn workers(mut self, num: usize) -> Self {
self.threads = num;
self
}
/// Sets the maximum per-worker number of concurrent connections.
///
/// All socket listeners will stop accepting connections when this limit is reached
/// for each worker.
///
/// By default max connections is set to a 100k.
pub fn maxconn(mut self, num: usize) -> Self {
self.maxconn = num;
self
}
/// Sets the maximum per-worker concurrent connection establish process.
///
/// All listeners will stop accepting connections when this limit is reached. It
/// can be used to limit the global SSL CPU usage.
///
/// By default max connections is set to a 256.
pub fn maxconnrate(mut self, num: usize) -> Self {
self.maxconnrate = num;
self
}
/// Stop actix system.
///
/// `SystemExit` message stops currently running system.
pub fn system_exit(mut self) -> Self {
self.exit = true;
self
}
#[doc(hidden)]
/// Set alternative address for `ProcessSignals` actor.
pub fn signals(mut self, addr: Addr<signal::ProcessSignals>) -> Self {
self.signals = Some(addr);
self
}
/// Disable signal handling
pub fn disable_signals(mut self) -> Self {
self.no_signals = true;
self
}
/// Timeout for graceful workers shutdown.
///
/// After receiving a stop signal, workers have this much time to finish
/// serving requests. Workers still alive after the timeout are force
/// dropped.
///
/// By default shutdown timeout sets to 30 seconds.
pub fn shutdown_timeout(mut self, sec: u16) -> Self {
self.shutdown_timeout = sec;
self
}
/// Add new service to server
pub fn service<T>(mut self, srv: T) -> Self
where
T: Into<(Box<Service>, Vec<(Token, net::TcpListener)>)>,
{
let (srv, sockets) = srv.into();
self.services.push(srv);
self.sockets.push(sockets);
self
}
/// Spawn new thread and start listening for incoming connections.
///
/// This method spawns new thread and starts new actix system. Other than
/// that it is similar to `start()` method. This method blocks.
///
/// This methods panics if no socket addresses get bound.
///
/// ```rust,ignore
/// # extern crate futures;
/// # extern crate actix_web;
/// # use futures::Future;
/// use actix_web::*;
///
/// fn main() {
/// Server::new().
/// .service(
/// 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();
}
/// Starts Server Actor and returns its address
pub fn start(mut self) -> Addr<Server> {
if self.sockets.is_empty() {
panic!("Service should have at least one bound socket");
} else {
info!("Starting {} http workers", self.threads);
// start workers
let mut workers = Vec::new();
for idx in 0..self.threads {
let (addr, worker) = self.start_worker(idx, self.accept.get_notify());
workers.push(worker);
self.workers.push((idx, addr));
}
// start accept thread
for sock in &self.sockets {
for s in sock.iter() {
info!("Starting server on http://{}", s.1.local_addr().unwrap());
}
}
let rx = self
.accept
.start(mem::replace(&mut self.sockets, Vec::new()), workers);
// start http server actor
let signals = self.subscribe_to_signals();
let addr = Actor::create(move |ctx| {
ctx.add_stream(rx);
self
});
if let Some(signals) = signals {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
}
addr
}
}
// subscribe to os signals
fn subscribe_to_signals(&self) -> Option<Addr<signal::ProcessSignals>> {
if !self.no_signals {
if let Some(ref signals) = self.signals {
Some(signals.clone())
} else {
Some(System::current().registry().get::<signal::ProcessSignals>())
}
} else {
None
}
}
fn start_worker(
&self, idx: usize, notify: AcceptNotify,
) -> (Addr<Worker>, WorkerClient) {
let (tx, rx) = unbounded::<Conn<net::TcpStream>>();
let conns = Connections::new(notify, self.maxconn, self.maxconnrate);
let worker = WorkerClient::new(idx, tx, conns.clone());
let services: Vec<_> = self.services.iter().map(|v| v.clone()).collect();
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
ctx.add_message_stream(rx);
let handlers: Vec<_> = services
.into_iter()
.map(|s| s.create(conns.clone()))
.collect();
Worker::new(conns, handlers)
});
(addr, worker)
}
}
impl Actor for Server {
type Context = Context<Self>;
}
/// Signals support
/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system
/// message to `System` actor.
impl Handler<signal::Signal> for Server {
type Result = ();
fn handle(&mut self, msg: signal::Signal, ctx: &mut Context<Self>) {
match msg.0 {
signal::SignalType::Int => {
info!("SIGINT received, exiting");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: false }, ctx);
}
signal::SignalType::Term => {
info!("SIGTERM received, stopping");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: true }, ctx);
}
signal::SignalType::Quit => {
info!("SIGQUIT received, exiting");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: false }, ctx);
}
_ => (),
}
}
}
impl Handler<PauseServer> for Server {
type Result = ();
fn handle(&mut self, _: PauseServer, _: &mut Context<Self>) {
self.accept.send(Command::Pause);
}
}
impl Handler<ResumeServer> for Server {
type Result = ();
fn handle(&mut self, _: ResumeServer, _: &mut Context<Self>) {
self.accept.send(Command::Resume);
}
}
impl Handler<StopServer> for Server {
type Result = Response<(), ()>;
fn handle(&mut self, msg: StopServer, ctx: &mut Context<Self>) -> Self::Result {
// stop accept thread
self.accept.send(Command::Stop);
// stop workers
let (tx, rx) = mpsc::channel(1);
let dur = if msg.graceful {
Some(Duration::new(u64::from(self.shutdown_timeout), 0))
} else {
None
};
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(());
// we need to stop system if server was spawned
if slf.exit {
ctx.run_later(Duration::from_millis(300), |_, _| {
System::current().stop();
});
}
}
fut::ok(())
}),
);
}
if !self.workers.is_empty() {
Response::async(rx.into_future().map(|_| ()).map_err(|_| ()))
} else {
// we need to stop system if server was spawned
if self.exit {
ctx.run_later(Duration::from_millis(300), |_, _| {
System::current().stop();
});
}
Response::reply(Ok(()))
}
}
}
/// Commands from accept threads
impl StreamHandler<ServerCommand, ()> for Server {
fn finished(&mut self, _: &mut Context<Self>) {}
fn handle(&mut self, msg: ServerCommand, _: &mut Context<Self>) {
match msg {
ServerCommand::WorkerDied(idx) => {
let mut found = false;
for i in 0..self.workers.len() {
if self.workers[i].0 == idx {
self.workers.swap_remove(i);
found = true;
break;
}
}
if found {
error!("Worker has died {:?}, restarting", idx);
let mut new_idx = self.workers.len();
'found: loop {
for i in 0..self.workers.len() {
if self.workers[i].0 == new_idx {
new_idx += 1;
continue 'found;
}
}
break;
}
let (addr, worker) =
self.start_worker(new_idx, self.accept.get_notify());
self.workers.push((new_idx, addr));
self.accept.send(Command::Worker(worker));
}
}
}
}
}
#[derive(Clone, Default)]
///Contains information about connection.
pub struct Connections(Arc<ConnectionsInner>);
impl Connections {
fn new(notify: AcceptNotify, maxconn: usize, maxconnrate: usize) -> Self {
let maxconn_low = if maxconn > 10 { maxconn - 10 } else { 0 };
let maxconnrate_low = if maxconnrate > 10 {
maxconnrate - 10
} else {
0
};
Connections(Arc::new(ConnectionsInner {
notify,
maxconn,
maxconnrate,
maxconn_low,
maxconnrate_low,
conn: AtomicUsize::new(0),
connrate: AtomicUsize::new(0),
}))
}
pub(crate) fn available(&self) -> bool {
self.0.available()
}
pub(crate) fn num_connections(&self) -> usize {
self.0.conn.load(Ordering::Relaxed)
}
/// Report opened connection
pub fn connection(&self) -> ConnectionTag {
ConnectionTag::new(self.0.clone())
}
/// Report rate connection, rate is usually ssl handshake
pub fn connection_rate(&self) -> ConnectionRateTag {
ConnectionRateTag::new(self.0.clone())
}
}
#[derive(Default)]
struct ConnectionsInner {
notify: AcceptNotify,
conn: AtomicUsize,
connrate: AtomicUsize,
maxconn: usize,
maxconnrate: usize,
maxconn_low: usize,
maxconnrate_low: usize,
}
impl ConnectionsInner {
fn available(&self) -> bool {
if self.maxconnrate <= self.connrate.load(Ordering::Relaxed) {
false
} else {
self.maxconn > self.conn.load(Ordering::Relaxed)
}
}
fn notify_maxconn(&self, maxconn: usize) {
if maxconn > self.maxconn_low && maxconn <= self.maxconn {
self.notify.notify();
}
}
fn notify_maxconnrate(&self, connrate: usize) {
if connrate > self.maxconnrate_low && connrate <= self.maxconnrate {
self.notify.notify();
}
}
}
/// Type responsible for max connection stat.
///
/// Max connections stat get updated on drop.
pub struct ConnectionTag(Arc<ConnectionsInner>);
impl ConnectionTag {
fn new(inner: Arc<ConnectionsInner>) -> Self {
inner.conn.fetch_add(1, Ordering::Relaxed);
ConnectionTag(inner)
}
}
impl Drop for ConnectionTag {
fn drop(&mut self) {
let conn = self.0.conn.fetch_sub(1, Ordering::Relaxed);
self.0.notify_maxconn(conn);
}
}
/// Type responsible for max connection rate stat.
///
/// Max connections rate stat get updated on drop.
pub struct ConnectionRateTag(Arc<ConnectionsInner>);
impl ConnectionRateTag {
fn new(inner: Arc<ConnectionsInner>) -> Self {
inner.connrate.fetch_add(1, Ordering::Relaxed);
ConnectionRateTag(inner)
}
}
impl Drop for ConnectionRateTag {
fn drop(&mut self) {
let connrate = self.0.connrate.fetch_sub(1, Ordering::Relaxed);
self.0.notify_maxconnrate(connrate);
}
}

View File

@@ -1,76 +1,81 @@
use std::cell::{RefCell, RefMut, UnsafeCell};
use std::collections::VecDeque;
use bytes::BytesMut;
use futures_cpupool::{Builder, CpuPool};
use http::StatusCode;
use std::cell::{Cell, RefCell, RefMut, UnsafeCell};
use std::fmt::Write;
use std::rc::Rc;
use std::time::{Duration, Instant};
use std::{env, fmt, net};
use actix::Arbiter;
use bytes::BytesMut;
use futures::Stream;
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 tokio_timer::Interval;
use super::channel::Node;
use super::message::{Request, RequestPool};
use super::server::{ConnectionRateTag, ConnectionTag, Connections};
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()),
}
}
}
@@ -87,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,
@@ -115,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]
@@ -135,42 +140,18 @@ impl ServerSettings {
const DATE_VALUE_LENGTH: usize = 29;
pub(crate) struct WorkerSettings<H> {
h: Vec<H>,
h: RefCell<Vec<H>>,
keep_alive: u64,
ka_enabled: bool,
bytes: Rc<SharedBytesPool>,
messages: &'static RequestPool,
conns: Connections,
node: RefCell<Node<()>>,
messages: Rc<helpers::SharedMessagePool>,
channels: Cell<usize>,
node: Box<Node<()>>,
date: UnsafeCell<Date>,
}
impl<H: 'static> WorkerSettings<H> {
pub(crate) fn create(
apps: Vec<H>, keep_alive: KeepAlive, settings: ServerSettings,
conns: Connections,
) -> Rc<WorkerSettings<H>> {
let settings = Rc::new(Self::new(apps, keep_alive, settings, conns));
// periodic date update
let s = settings.clone();
Arbiter::spawn(
Interval::new(Instant::now(), Duration::from_secs(1))
.map_err(|_| ())
.and_then(move |_| {
s.update_date();
Ok(())
}).fold((), |(), _| Ok(())),
);
settings
}
}
impl<H> WorkerSettings<H> {
pub(crate) fn new(
h: Vec<H>, keep_alive: KeepAlive, settings: ServerSettings, conns: Connections,
) -> 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),
@@ -178,23 +159,27 @@ impl<H> WorkerSettings<H> {
};
WorkerSettings {
h,
bytes: Rc::new(SharedBytesPool::new()),
messages: RequestPool::pool(settings),
node: RefCell::new(Node::head()),
date: UnsafeCell::new(Date::new()),
keep_alive,
ka_enabled,
conns,
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()),
}
}
pub fn head(&self) -> RefMut<Node<()>> {
self.node.borrow_mut()
pub fn num_channels(&self) -> usize {
self.channels.get()
}
pub fn handlers(&self) -> &Vec<H> {
&self.h
pub fn head(&self) -> &Node<()> {
&self.node
}
pub fn handlers(&self) -> RefMut<Vec<H>> {
self.h.borrow_mut()
}
pub fn keep_alive(&self) -> u64 {
@@ -205,44 +190,44 @@ 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_http_message(&self) -> helpers::SharedHttpInnerMessage {
helpers::SharedHttpInnerMessage::new(
self.messages.get(),
Rc::clone(&self.messages),
)
}
pub fn get_request(&self) -> Request {
RequestPool::get(self.messages)
pub fn add_channel(&self) {
self.channels.set(self.channels.get() + 1);
}
pub fn connection(&self) -> ConnectionTag {
self.conns.connection()
}
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);
pub fn remove_channel(&self) {
let num = self.channels.get();
if num > 0 {
self.channels.set(num - 1);
} else {
dst.extend_from_slice(date_bytes);
error!("Number of removed channels is bigger than added channel. Bug in actix-web");
}
}
#[allow(dead_code)]
pub(crate) fn connection_rate(&self) -> ConnectionRateTag {
self.conns.connection_rate()
pub fn update_date(&self) {
unsafe { &mut *self.date.get() }.update();
}
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));
}
}
@@ -275,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::*;
@@ -311,16 +271,11 @@ mod tests {
#[test]
fn test_date() {
let settings = WorkerSettings::<()>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
Connections::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(())
}
}

974
src/server/srv.rs Normal file
View File

@@ -0,0 +1,974 @@
use std::rc::Rc;
use std::sync::{mpsc as sync_mpsc, Arc};
use std::time::Duration;
use std::{io, net, thread};
use actix::actors::signal;
use actix::prelude::*;
use futures::sync::mpsc;
use futures::{Future, Sink, Stream};
use mio;
use net2::TcpBuilder;
use num_cpus;
use slab::Slab;
use tokio_io::{AsyncRead, AsyncWrite};
#[cfg(feature = "tls")]
use native_tls::TlsAcceptor;
#[cfg(feature = "alpn")]
use openssl::ssl::{AlpnError, SslAcceptorBuilder};
use super::channel::{HttpChannel, WrapperStream};
use super::settings::{ServerSettings, WorkerSettings};
use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker};
use super::{IntoHttpHandler, IoStream, KeepAlive};
use super::{PauseServer, ResumeServer, StopServer};
#[cfg(feature = "alpn")]
fn configure_alpn(builder: &mut SslAcceptorBuilder) -> io::Result<()> {
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";
if protos.windows(3).any(|window| window == H2) {
Ok(b"h2")
} else {
Err(AlpnError::NOACK)
}
});
Ok(())
}
/// An HTTP Server
pub struct HttpServer<H>
where
H: IntoHttpHandler + 'static,
{
h: Option<Rc<WorkerSettings<H::Handler>>>,
threads: usize,
backlog: i32,
host: Option<String>,
keep_alive: KeepAlive,
factory: Arc<Fn() -> Vec<H> + Send + Sync>,
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
workers: Vec<(usize, Addr<Syn, Worker<H::Handler>>)>,
sockets: Vec<Socket>,
accept: Vec<(mio::SetReadiness, sync_mpsc::Sender<Command>)>,
exit: bool,
shutdown_timeout: u16,
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>),
}
impl<H> Actor for HttpServer<H>
where
H: IntoHttpHandler,
{
type Context = Context<Self>;
}
struct Socket {
lst: net::TcpListener,
addr: net::SocketAddr,
tp: StreamHandlerType,
}
impl<H> HttpServer<H>
where
H: IntoHttpHandler + 'static,
{
/// Create new http server with application factory
pub fn new<F, U>(factory: F) -> Self
where
F: Fn() -> U + Sync + Send + 'static,
U: IntoIterator<Item = H> + 'static,
{
let f = move || (factory)().into_iter().collect();
HttpServer {
h: None,
threads: num_cpus::get(),
backlog: 2048,
host: None,
keep_alive: KeepAlive::Os,
factory: Arc::new(f),
workers: Vec::new(),
sockets: Vec::new(),
accept: Vec::new(),
exit: false,
shutdown_timeout: 30,
signals: None,
no_http2: false,
no_signals: false,
}
}
/// Set number of workers to start.
///
/// By default http server uses number of available logical cpu as threads
/// count.
pub fn workers(mut self, num: usize) -> Self {
self.threads = num;
self
}
#[doc(hidden)]
#[deprecated(since = "0.6.0", note = "please use `HttpServer::workers()` instead")]
pub fn threads(self, num: usize) -> Self {
self.workers(num)
}
/// Set the maximum number of pending connections.
///
/// This refers to the number of clients that can be waiting to be served.
/// Exceeding this number results in the client getting an error when
/// attempting to connect. It should only affect servers under significant
/// load.
///
/// Generally set in the 64-2048 range. Default value is 2048.
///
/// This method should be called before `bind()` method call.
pub fn backlog(mut self, num: i32) -> Self {
self.backlog = num;
self
}
/// Set server keep-alive setting.
///
/// By default keep alive is set to a `Os`.
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
self.keep_alive = val.into();
self
}
/// Set server host name.
///
/// Host name is used by application router aa a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
pub fn server_hostname(mut self, val: String) -> Self {
self.host = Some(val);
self
}
/// Send `SystemExit` message to actix 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<Syn, signal::ProcessSignals>) -> Self {
self.signals = Some(addr);
self
}
/// Disable signal handling
pub fn disable_signals(mut self) -> Self {
self.no_signals = true;
self
}
/// Timeout for graceful workers shutdown.
///
/// After receiving a stop signal, workers have this much time to finish
/// serving requests. Workers still alive after the timeout are force
/// dropped.
///
/// By default shutdown timeout sets to 30 seconds.
pub fn shutdown_timeout(mut self, sec: u16) -> Self {
self.shutdown_timeout = sec;
self
}
/// Disable `HTTP/2` support
pub fn no_http2(mut self) -> Self {
self.no_http2 = true;
self
}
/// Get addresses of bound sockets.
pub fn addrs(&self) -> Vec<net::SocketAddr> {
self.sockets.iter().map(|s| s.addr).collect()
}
/// Get addresses of bound sockets and the scheme for it.
///
/// This is useful when the server is bound from different sources
/// with some sockets listening on http and some listening on https
/// and the user should be presented with an enumeration of which
/// socket requires which protocol.
pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> {
self.sockets
.iter()
.map(|s| (s.addr, s.tp.scheme()))
.collect()
}
/// Use listener for accepting incoming connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen(mut self, lst: net::TcpListener) -> Self {
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Normal,
});
self
}
#[cfg(feature = "tls")]
/// Use listener for accepting incoming tls connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen_tls(mut self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self {
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Tls(acceptor.clone()),
});
self
}
#[cfg(feature = "alpn")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_ssl(
mut self, lst: net::TcpListener, mut builder: SslAcceptorBuilder,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
configure_alpn(&mut builder)?;
}
let acceptor = builder.build();
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Alpn(acceptor.clone()),
});
Ok(self)
}
fn bind2<S: net::ToSocketAddrs>(&mut self, addr: S) -> io::Result<Vec<Socket>> {
let mut err = None;
let mut succ = false;
let mut sockets = Vec::new();
for addr in addr.to_socket_addrs()? {
match create_tcp_listener(addr, self.backlog) {
Ok(lst) => {
succ = true;
let addr = lst.local_addr().unwrap();
sockets.push(Socket {
lst,
addr,
tp: StreamHandlerType::Normal,
});
}
Err(e) => err = Some(e),
}
}
if !succ {
if let Some(e) = err.take() {
Err(e)
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Can not bind to address.",
))
}
} else {
Ok(sockets)
}
}
/// The socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets);
Ok(self)
}
#[cfg(feature = "tls")]
/// The ssl socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind_tls<S: net::ToSocketAddrs>(
mut self, addr: S, acceptor: TlsAcceptor,
) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(|mut s| {
s.tp = StreamHandlerType::Tls(acceptor.clone());
s
}));
Ok(self)
}
#[cfg(feature = "alpn")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_ssl<S: net::ToSocketAddrs>(
mut self, addr: S, mut builder: SslAcceptorBuilder,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
configure_alpn(&mut builder)?;
}
let acceptor = builder.build();
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(|mut s| {
s.tp = StreamHandlerType::Alpn(acceptor.clone());
s
}));
Ok(self)
}
fn start_workers(
&mut self, settings: &ServerSettings, sockets: &Slab<SocketInfo>,
) -> Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)> {
// 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 addr = Arbiter::start(move |ctx: &mut Context<_>| {
let apps: Vec<_> = (*factory)()
.into_iter()
.map(|h| h.into_handler(s.clone()))
.collect();
ctx.add_message_stream(rx);
Worker::new(apps, socks, ka)
});
workers.push((idx, tx));
self.workers.push((idx, addr));
}
info!("Starting {} http workers", self.threads);
workers
}
// subscribe to os signals
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(Arbiter::system_registry().get::<signal::ProcessSignals>())
}
} else {
None
}
}
}
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming connections.
///
/// This method starts number of http handler workers in separate threads.
/// For each address this method starts separate thread which does
/// `accept()` in a loop.
///
/// This methods panics if no socket addresses get bound.
///
/// This method requires to run within properly configured `Actix` system.
///
/// ```rust
/// extern crate actix;
/// extern crate actix_web;
/// 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")
/// .start();
/// # 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<Syn, Self> {
if self.sockets.is_empty() {
panic!("HttpServer::bind() has to be called before start()");
} else {
let (tx, rx) = mpsc::unbounded();
let mut socks = Slab::new();
let mut addrs: Vec<(usize, Socket)> = Vec::new();
for socket in self.sockets.drain(..) {
let entry = socks.vacant_entry();
let token = entry.key();
entry.insert(SocketInfo {
addr: socket.addr,
htype: socket.tp.clone(),
});
addrs.push((token, socket));
}
let settings = ServerSettings::new(Some(addrs[0].1.addr), &self.host, false);
let workers = self.start_workers(&settings, &socks);
// start acceptors threads
for (token, sock) in addrs {
info!("Starting server on http://{}", sock.addr);
self.accept.push(start_accept_thread(
token,
sock,
tx.clone(),
socks.clone(),
workers.clone(),
));
}
// start http server actor
let signals = self.subscribe_to_signals();
let addr: Addr<Syn, _> = Actor::create(move |ctx| {
ctx.add_stream(rx);
self
});
if let Some(signals) = signals {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
}
addr
}
}
/// Spawn new thread and start listening for incoming connections.
///
/// This method spawns new thread and starts new actix system. Other than
/// that it is similar to `start()` method. This method blocks.
///
/// This methods panics if no socket addresses get bound.
///
/// ```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")
/// .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();
}
}
#[doc(hidden)]
#[cfg(feature = "tls")]
#[deprecated(
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_tls` instead"
)]
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming tls connections.
pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result<Addr<Syn, Self>> {
for sock in &mut self.sockets {
match sock.tp {
StreamHandlerType::Normal => (),
_ => continue,
}
sock.tp = StreamHandlerType::Tls(acceptor.clone());
}
Ok(self.start())
}
}
#[doc(hidden)]
#[cfg(feature = "alpn")]
#[deprecated(
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_ssl` instead"
)]
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn start_ssl(
mut self, mut builder: SslAcceptorBuilder,
) -> io::Result<Addr<Syn, Self>> {
// alpn support
if !self.no_http2 {
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";
if protos.windows(3).any(|window| window == H2) {
Ok(b"h2")
} else {
Err(AlpnError::NOACK)
}
});
}
let acceptor = builder.build();
for sock in &mut self.sockets {
match sock.tp {
StreamHandlerType::Normal => (),
_ => continue,
}
sock.tp = StreamHandlerType::Alpn(acceptor.clone());
}
Ok(self.start())
}
}
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, A, S>(mut self, stream: S, secure: bool) -> Addr<Syn, Self>
where
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(settings.clone()))
.collect();
self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive)));
// start server
let signals = self.subscribe_to_signals();
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,
http2: false,
}));
self
});
if let Some(signals) = signals {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
}
addr
}
}
/// Signals support
/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)`
/// message to `System` actor.
impl<H: IntoHttpHandler> Handler<signal::Signal> for HttpServer<H> {
type Result = ();
fn handle(&mut self, msg: signal::Signal, ctx: &mut Context<Self>) {
match msg.0 {
signal::SignalType::Int => {
info!("SIGINT received, exiting");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: false }, ctx);
}
signal::SignalType::Term => {
info!("SIGTERM received, stopping");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: true }, ctx);
}
signal::SignalType::Quit => {
info!("SIGQUIT received, exiting");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: false }, ctx);
}
_ => (),
}
}
}
/// 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) => {
let mut found = false;
for i in 0..self.workers.len() {
if self.workers[i].0 == idx {
self.workers.swap_remove(i);
found = true;
break;
}
}
if found {
error!("Worker has died {:?}, restarting", idx);
let (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>();
let mut new_idx = self.workers.len();
'found: loop {
for i in 0..self.workers.len() {
if self.workers[i].0 == new_idx {
new_idx += 1;
continue 'found;
}
}
break;
}
let ka = self.keep_alive;
let factory = Arc::clone(&self.factory);
let settings =
ServerSettings::new(Some(socks[0].addr), &self.host, false);
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
let apps: Vec<_> = (*factory)()
.into_iter()
.map(|h| h.into_handler(settings.clone()))
.collect();
ctx.add_message_stream(rx);
Worker::new(apps, socks, ka)
});
for item in &self.accept {
let _ = item.1.send(Command::Worker(new_idx, tx.clone()));
let _ = item.0.set_readiness(mio::Ready::readable());
}
self.workers.push((new_idx, addr));
}
}
}
}
}
impl<T, H> Handler<Conn<T>> for HttpServer<H>
where
T: IoStream,
H: IntoHttpHandler,
{
type Result = ();
fn handle(&mut self, msg: Conn<T>, _: &mut Context<Self>) -> Self::Result {
Arbiter::handle().spawn(HttpChannel::new(
Rc::clone(self.h.as_ref().unwrap()),
msg.io,
msg.peer,
msg.http2,
));
}
}
impl<H: IntoHttpHandler> Handler<PauseServer> for HttpServer<H> {
type Result = ();
fn handle(&mut self, _: PauseServer, _: &mut Context<Self>) {
for item in &self.accept {
let _ = item.1.send(Command::Pause);
let _ = item.0.set_readiness(mio::Ready::readable());
}
}
}
impl<H: IntoHttpHandler> Handler<ResumeServer> for HttpServer<H> {
type Result = ();
fn handle(&mut self, _: ResumeServer, _: &mut Context<Self>) {
for item in &self.accept {
let _ = item.1.send(Command::Resume);
let _ = item.0.set_readiness(mio::Ready::readable());
}
}
}
impl<H: IntoHttpHandler> Handler<StopServer> for HttpServer<H> {
type Result = actix::Response<(), ()>;
fn handle(&mut self, msg: StopServer, ctx: &mut Context<Self>) -> Self::Result {
// stop accept threads
for item in &self.accept {
let _ = item.1.send(Command::Stop);
let _ = item.0.set_readiness(mio::Ready::readable());
}
// stop workers
let (tx, rx) = mpsc::channel(1);
let dur = if msg.graceful {
Some(Duration::new(u64::from(self.shutdown_timeout), 0))
} else {
None
};
for worker in &self.workers {
let tx2 = tx.clone();
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), |_, _| {
Arbiter::system().do_send(actix::msgs::SystemExit(0))
});
}
}
actix::fut::ok(())
})
.spawn(ctx);
}
if !self.workers.is_empty() {
Response::async(rx.into_future().map(|_| ()).map_err(|_| ()))
} else {
// we need to stop system if server was spawned
if self.exit {
ctx.run_later(Duration::from_millis(300), |_, _| {
Arbiter::system().do_send(actix::msgs::SystemExit(0))
});
}
Response::reply(Ok(()))
}
}
}
enum Command {
Pause,
Resume,
Stop,
Worker(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>),
}
fn start_accept_thread(
token: usize, sock: Socket, srv: mpsc::UnboundedSender<ServerCommand>,
socks: Slab<SocketInfo>,
mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) {
let (tx, rx) = sync_mpsc::channel();
let (reg, readiness) = mio::Registration::new2();
// start accept thread
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
let _ = thread::Builder::new()
.name(format!("Accept on {}", sock.addr))
.spawn(move || {
const SRV: mio::Token = mio::Token(0);
const CMD: mio::Token = mio::Token(1);
let addr = sock.addr;
let mut server = Some(
mio::net::TcpListener::from_std(sock.lst)
.expect("Can not create mio::net::TcpListener"),
);
// Create a poll instance
let poll = match mio::Poll::new() {
Ok(poll) => poll,
Err(err) => panic!("Can not create mio::Poll: {}", err),
};
// Start listening for incoming connections
if let Some(ref srv) = server {
if let Err(err) =
poll.register(srv, SRV, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register io: {}", err);
}
}
// Start listening for incoming commands
if let Err(err) =
poll.register(&reg, CMD, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register Registration: {}", err);
}
// Create storage for events
let mut events = mio::Events::with_capacity(128);
// Sleep on error
let sleep = Duration::from_millis(100);
let mut next = 0;
loop {
if let Err(err) = poll.poll(&mut events, None) {
panic!("Poll error: {}", err);
}
for event in events.iter() {
match event.token() {
SRV => if let Some(ref server) = server {
loop {
match server.accept_std() {
Ok((io, addr)) => {
let mut msg = Conn {
io,
token,
peer: Some(addr),
http2: false,
};
while !workers.is_empty() {
match workers[next].1.unbounded_send(msg) {
Ok(_) => (),
Err(err) => {
let _ = srv.unbounded_send(
ServerCommand::WorkerDied(
workers[next].0,
socks.clone(),
),
);
msg = err.into_inner();
workers.swap_remove(next);
if workers.is_empty() {
error!("No workers");
thread::sleep(sleep);
break;
} else if workers.len() <= next {
next = 0;
}
continue;
}
}
next = (next + 1) % workers.len();
break;
}
}
Err(ref e)
if e.kind() == io::ErrorKind::WouldBlock =>
{
break
}
Err(ref e) if connection_error(e) => continue,
Err(e) => {
error!("Error accepting connection: {}", e);
// sleep after error
thread::sleep(sleep);
break;
}
}
}
},
CMD => match rx.try_recv() {
Ok(cmd) => match cmd {
Command::Pause => if let Some(ref server) = server {
if let Err(err) = poll.deregister(server) {
error!(
"Can not deregister server socket {}",
err
);
} else {
info!(
"Paused accepting connections on {}",
addr
);
}
},
Command::Resume => {
if let Some(ref server) = server {
if let Err(err) = poll.register(
server,
SRV,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err);
} else {
info!("Accepting connections on {} has been resumed",
addr);
}
}
}
Command::Stop => {
if let Some(server) = server.take() {
let _ = poll.deregister(&server);
}
return;
}
Command::Worker(idx, addr) => {
workers.push((idx, addr));
}
},
Err(err) => match err {
sync_mpsc::TryRecvError::Empty => (),
sync_mpsc::TryRecvError::Disconnected => {
if let Some(server) = server.take() {
let _ = poll.deregister(&server);
}
return;
}
},
},
_ => unreachable!(),
}
}
}
});
(readiness, tx)
}
fn create_tcp_listener(
addr: net::SocketAddr, backlog: i32,
) -> io::Result<net::TcpListener> {
let builder = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
};
builder.reuse_address(true)?;
builder.bind(addr)?;
Ok(builder.listen(backlog)?)
}
/// This function defines errors that are per-connection. Which basically
/// means that if we get this error from `accept()` system call it means
/// next connection might be ready to be accepted.
///
/// All other errors will incur a timeout before next `accept()` is performed.
/// The timeout is useful to handle resource exhaustion errors like ENFILE
/// and EMFILE. Otherwise, could enter into tight loop.
fn connection_error(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::ConnectionAborted
|| e.kind() == io::ErrorKind::ConnectionReset
}

View File

@@ -1,14 +0,0 @@
#[cfg(feature = "alpn")]
mod openssl;
#[cfg(feature = "alpn")]
pub use self::openssl::OpensslAcceptor;
#[cfg(feature = "tls")]
mod nativetls;
#[cfg(feature = "tls")]
pub use self::nativetls::{NativeTlsAcceptor, TlsStream};
#[cfg(feature = "rust-tls")]
mod rustls;
#[cfg(feature = "rust-tls")]
pub use self::rustls::RustlsAcceptor;

View File

@@ -1,143 +0,0 @@
use std::net::Shutdown;
use std::{io, time};
use futures::{Async, Future, Poll};
use native_tls::{self, HandshakeError, TlsAcceptor};
use tokio_io::{AsyncRead, AsyncWrite};
use server::{AcceptorService, IoStream};
#[derive(Clone)]
/// Support `SSL` connections via native-tls package
///
/// `tls` feature enables `NativeTlsAcceptor` type
pub struct NativeTlsAcceptor {
acceptor: TlsAcceptor,
}
/// A wrapper around an underlying raw stream which implements the TLS or SSL
/// protocol.
///
/// A `TlsStream<S>` represents a handshake that has been completed successfully
/// and both the server and the client are ready for receiving and sending
/// data. Bytes read from a `TlsStream` are decrypted from `S` and bytes written
/// to a `TlsStream` are encrypted when passing through to `S`.
#[derive(Debug)]
pub struct TlsStream<S> {
inner: native_tls::TlsStream<S>,
}
/// Future returned from `NativeTlsAcceptor::accept` which will resolve
/// once the accept handshake has finished.
pub struct Accept<S> {
inner: Option<Result<native_tls::TlsStream<S>, HandshakeError<S>>>,
}
impl NativeTlsAcceptor {
/// Create `NativeTlsAcceptor` instance
pub fn new(acceptor: TlsAcceptor) -> Self {
NativeTlsAcceptor {
acceptor: acceptor.into(),
}
}
}
impl<Io: IoStream> AcceptorService<Io> for NativeTlsAcceptor {
type Accepted = TlsStream<Io>;
type Future = Accept<Io>;
fn scheme(&self) -> &'static str {
"https"
}
fn accept(&self, io: Io) -> Self::Future {
Accept {
inner: Some(self.acceptor.accept(io)),
}
}
}
impl<Io: IoStream> IoStream for TlsStream<Io> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
}
impl<Io: IoStream> Future for Accept<Io> {
type Item = TlsStream<Io>;
type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.inner.take().expect("cannot poll MidHandshake twice") {
Ok(stream) => Ok(TlsStream { inner: stream }.into()),
Err(HandshakeError::Failure(e)) => {
Err(io::Error::new(io::ErrorKind::Other, e))
}
Err(HandshakeError::WouldBlock(s)) => match s.handshake() {
Ok(stream) => Ok(TlsStream { inner: stream }.into()),
Err(HandshakeError::Failure(e)) => {
Err(io::Error::new(io::ErrorKind::Other, e))
}
Err(HandshakeError::WouldBlock(s)) => {
self.inner = Some(Err(HandshakeError::WouldBlock(s)));
Ok(Async::NotReady)
}
},
}
}
}
impl<S> TlsStream<S> {
/// Get access to the internal `native_tls::TlsStream` stream which also
/// transitively allows access to `S`.
pub fn get_ref(&self) -> &native_tls::TlsStream<S> {
&self.inner
}
/// Get mutable access to the internal `native_tls::TlsStream` stream which
/// also transitively allows mutable access to `S`.
pub fn get_mut(&mut self) -> &mut native_tls::TlsStream<S> {
&mut self.inner
}
}
impl<S: io::Read + io::Write> io::Read for TlsStream<S> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<S: io::Read + io::Write> io::Write for TlsStream<S> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<S: AsyncRead + AsyncWrite> AsyncRead for TlsStream<S> {}
impl<S: AsyncRead + AsyncWrite> AsyncWrite for TlsStream<S> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
match self.inner.shutdown() {
Ok(_) => (),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (),
Err(e) => return Err(e),
}
self.inner.get_mut().shutdown()
}
}

View File

@@ -1,96 +0,0 @@
use std::net::Shutdown;
use std::{io, time};
use futures::{Future, Poll};
use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder};
use tokio_openssl::{AcceptAsync, SslAcceptorExt, SslStream};
use server::{AcceptorService, IoStream, ServerFlags};
#[derive(Clone)]
/// Support `SSL` connections via openssl package
///
/// `alpn` feature enables `OpensslAcceptor` type
pub struct OpensslAcceptor {
acceptor: SslAcceptor,
}
impl OpensslAcceptor {
/// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support.
pub fn new(builder: SslAcceptorBuilder) -> io::Result<Self> {
OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2)
}
/// Create `OpensslAcceptor` with custom server flags.
pub fn with_flags(
mut builder: SslAcceptorBuilder, flags: ServerFlags,
) -> io::Result<Self> {
let mut protos = Vec::new();
if flags.contains(ServerFlags::HTTP1) {
protos.extend(b"\x08http/1.1");
}
if flags.contains(ServerFlags::HTTP2) {
protos.extend(b"\x02h2");
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";
if protos.windows(3).any(|window| window == H2) {
Ok(b"h2")
} else {
Err(AlpnError::NOACK)
}
});
}
if !protos.is_empty() {
builder.set_alpn_protos(&protos)?;
}
Ok(OpensslAcceptor {
acceptor: builder.build(),
})
}
}
pub struct AcceptorFut<Io>(AcceptAsync<Io>);
impl<Io: IoStream> Future for AcceptorFut<Io> {
type Item = SslStream<Io>;
type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.0
.poll()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}
}
impl<Io: IoStream> AcceptorService<Io> for OpensslAcceptor {
type Accepted = SslStream<Io>;
type Future = AcceptorFut<Io>;
fn scheme(&self) -> &'static str {
"https"
}
fn accept(&self, io: Io) -> Self::Future {
AcceptorFut(SslAcceptorExt::accept_async(&self.acceptor, io))
}
}
impl<T: IoStream> IoStream for SslStream<T> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
}

View File

@@ -1,91 +0,0 @@
use std::net::Shutdown;
use std::sync::Arc;
use std::{io, time};
use rustls::{ClientSession, ServerConfig, ServerSession};
use tokio_io::AsyncWrite;
use tokio_rustls::{AcceptAsync, ServerConfigExt, TlsStream};
use server::{AcceptorService, IoStream, ServerFlags};
#[derive(Clone)]
/// Support `SSL` connections via rustls package
///
/// `rust-tls` feature enables `RustlsAcceptor` type
pub struct RustlsAcceptor {
config: Arc<ServerConfig>,
}
impl RustlsAcceptor {
/// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support.
pub fn new(config: ServerConfig) -> Self {
RustlsAcceptor::with_flags(config, ServerFlags::HTTP1 | ServerFlags::HTTP2)
}
/// Create `OpensslAcceptor` with custom server flags.
pub fn with_flags(mut config: ServerConfig, flags: ServerFlags) -> Self {
let mut protos = Vec::new();
if flags.contains(ServerFlags::HTTP2) {
protos.push("h2".to_string());
}
if flags.contains(ServerFlags::HTTP1) {
protos.push("http/1.1".to_string());
}
if !protos.is_empty() {
config.set_protocols(&protos);
}
RustlsAcceptor {
config: Arc::new(config),
}
}
}
impl<Io: IoStream> AcceptorService<Io> for RustlsAcceptor {
type Accepted = TlsStream<Io, ServerSession>;
type Future = AcceptAsync<Io>;
fn scheme(&self) -> &'static str {
"https"
}
fn accept(&self, io: Io) -> Self::Future {
ServerConfigExt::accept_async(&self.config, io)
}
}
impl<Io: IoStream> IoStream for TlsStream<Io, ClientSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
}
impl<Io: IoStream> IoStream for TlsStream<Io, ServerSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
}

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,52 +1,44 @@
use std::{net, time};
use futures::sync::mpsc::{SendError, UnboundedSender};
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_core::net::TcpStream;
use tokio_core::reactor::Handle;
#[cfg(any(feature = "tls", feature = "alpn"))]
use futures::future;
#[cfg(feature = "tls")]
use native_tls::TlsAcceptor;
#[cfg(feature = "tls")]
use tokio_tls::TlsAcceptorExt;
#[cfg(feature = "alpn")]
use openssl::ssl::SslAcceptor;
#[cfg(feature = "alpn")]
use tokio_openssl::SslAcceptorExt;
use actix::msgs::StopArbiter;
use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response};
use actix::*;
use super::server::{Connections, ServiceHandler};
use super::Token;
use server::channel::HttpChannel;
use server::settings::WorkerSettings;
use server::{HttpHandler, KeepAlive};
#[derive(Message)]
pub(crate) struct Conn<T> {
pub io: T,
pub handler: Token,
pub token: Token,
pub token: usize,
pub peer: Option<net::SocketAddr>,
}
pub(crate) struct Socket {
pub lst: net::TcpListener,
pub addr: net::SocketAddr,
pub token: Token,
pub http2: bool,
}
#[derive(Clone)]
pub(crate) struct WorkerClient {
pub idx: usize,
tx: UnboundedSender<Conn<net::TcpStream>>,
conns: Connections,
}
impl WorkerClient {
pub fn new(
idx: usize, tx: UnboundedSender<Conn<net::TcpStream>>, conns: Connections,
) -> Self {
WorkerClient { idx, tx, conns }
}
pub fn send(
&self, msg: Conn<net::TcpStream>,
) -> Result<(), SendError<Conn<net::TcpStream>>> {
self.tx.unbounded_send(msg)
}
pub fn available(&self) -> bool {
self.conns.available()
}
pub(crate) struct SocketInfo {
pub addr: net::SocketAddr,
pub htype: StreamHandlerType,
}
/// Stop worker message. Returns `true` on successful shutdown
@@ -63,77 +55,193 @@ impl Message for StopWorker {
///
/// Worker accepts Socket objects via unbounded channel and start requests
/// processing.
pub(crate) struct Worker {
conns: Connections,
handlers: Vec<Box<ServiceHandler>>,
pub(crate) struct Worker<H>
where
H: HttpHandler + 'static,
{
settings: Rc<WorkerSettings<H>>,
hnd: Handle,
socks: Slab<SocketInfo>,
tcp_ka: Option<time::Duration>,
}
impl Actor for Worker {
type Context = Context<Self>;
}
impl<H: HttpHandler + 'static> Worker<H> {
pub(crate) fn new(
h: Vec<H>, socks: Slab<SocketInfo>, keep_alive: KeepAlive,
) -> Worker<H> {
let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive {
Some(time::Duration::new(val as u64, 0))
} else {
None
};
impl Worker {
pub(crate) fn new(conns: Connections, handlers: Vec<Box<ServiceHandler>>) -> Self {
Worker { conns, handlers }
Worker {
settings: Rc::new(WorkerSettings::new(h, keep_alive)),
hnd: Arbiter::handle().clone(),
socks,
tcp_ka,
}
}
fn shutdown(&self, force: bool) {
self.handlers.iter().for_each(|h| h.shutdown(force));
fn update_time(&self, ctx: &mut Context<Self>) {
self.settings.update_date();
ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx));
}
fn shutdown_timeout(
&self, ctx: &mut Context<Worker>, tx: oneshot::Sender<bool>, dur: time::Duration,
&self, ctx: &mut Context<Self>, tx: oneshot::Sender<bool>, dur: time::Duration,
) {
// sleep for 1 second and then check again
ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| {
let num = slf.conns.num_connections();
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.shutdown(true);
slf.settings.head().traverse::<TcpStream, H>();
let _ = tx.send(false);
Arbiter::current().do_send(StopArbiter(0));
Arbiter::arbiter().do_send(StopArbiter(0));
}
});
}
}
impl Handler<Conn<net::TcpStream>> for Worker {
impl<H: 'static> Actor for Worker<H>
where
H: HttpHandler + 'static,
{
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
self.update_time(ctx);
}
}
impl<H> Handler<Conn<net::TcpStream>> for Worker<H>
where
H: HttpHandler + 'static,
{
type Result = ();
fn handle(&mut self, msg: Conn<net::TcpStream>, _: &mut Context<Self>) {
self.handlers[msg.handler.0].handle(msg.token, msg.io, msg.peer)
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),
&self.hnd,
msg,
);
}
}
/// `StopWorker` message handler
impl Handler<StopWorker> for Worker {
impl<H> Handler<StopWorker> for Worker<H>
where
H: HttpHandler + 'static,
{
type Result = Response<bool, ()>;
fn handle(&mut self, msg: StopWorker, ctx: &mut Context<Self>) -> Self::Result {
let num = self.conns.num_connections();
let num = self.settings.num_channels();
if num == 0 {
info!("Shutting down http worker, 0 connections");
Response::reply(Ok(true))
} else if let Some(dur) = msg.graceful {
self.shutdown(false);
info!("Graceful http worker shutdown, {} connections", num);
let (tx, rx) = oneshot::channel();
let num = self.conns.num_connections();
if num != 0 {
info!("Graceful http worker shutdown, {} connections", num);
self.shutdown_timeout(ctx, tx, dur);
Response::reply(Ok(true))
} else {
Response::async(rx.map_err(|_| ()))
}
self.shutdown_timeout(ctx, tx, dur);
Response::async(rx.map_err(|_| ()))
} else {
info!("Force shutdown http worker, {} connections", num);
self.shutdown(true);
self.settings.head().traverse::<TcpStream, H>();
Response::reply(Ok(false))
}
}
}
#[derive(Clone)]
pub(crate) enum StreamHandlerType {
Normal,
#[cfg(feature = "tls")]
Tls(TlsAcceptor),
#[cfg(feature = "alpn")]
Alpn(SslAcceptor),
}
impl StreamHandlerType {
fn handle<H: HttpHandler>(
&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_stream(msg.io, hnd)
.expect("failed to associate TCP stream");
hnd.spawn(HttpChannel::new(h, io, msg.peer, msg.http2));
}
#[cfg(feature = "tls")]
StreamHandlerType::Tls(ref acceptor) => {
let Conn {
io, peer, http2, ..
} = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream");
hnd.spawn(TlsAcceptorExt::accept_async(acceptor, io).then(move |res| {
match res {
Ok(io) => {
Arbiter::handle().spawn(HttpChannel::new(h, io, peer, http2))
}
Err(err) => {
trace!("Error during handling tls connection: {}", err)
}
};
future::result(Ok(()))
}));
}
#[cfg(feature = "alpn")]
StreamHandlerType::Alpn(ref acceptor) => {
let Conn { io, peer, .. } = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream");
hnd.spawn(SslAcceptorExt::accept_async(acceptor, io).then(move |res| {
match res {
Ok(io) => {
let http2 = if let Some(p) =
io.get_ref().ssl().selected_alpn_protocol()
{
p.len() == 2 && &p == b"h2"
} else {
false
};
Arbiter::handle()
.spawn(HttpChannel::new(h, io, peer, http2));
}
Err(err) => {
trace!("Error during handling tls connection: {}", err)
}
};
future::result(Ok(()))
}));
}
}
}
pub(crate) fn scheme(&self) -> &'static str {
match *self {
StreamHandlerType::Normal => "http",
#[cfg(feature = "tls")]
StreamHandlerType::Tls(_) => "https",
#[cfg(feature = "alpn")]
StreamHandlerType::Alpn(_) => "https",
}
}
}

View File

@@ -1,43 +1,36 @@
//! 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;
#[cfg(feature = "rust-tls")]
use rustls::ServerConfig;
#[cfg(feature = "alpn")]
use server::OpensslAcceptor;
#[cfg(feature = "rust-tls")]
use server::RustlsAcceptor;
use openssl::ssl::SslAcceptor;
use application::{App, HttpApplication};
use body::Binary;
use client::{ClientConnector, ClientRequest, ClientRequestBuilder};
use error::Error;
use handler::{AsyncResult, AsyncResultItem, Handler, Responder};
use handler::{AsyncResultItem, Handler, Responder};
use header::{Header, IntoHeaderValue};
use httprequest::HttpRequest;
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.
@@ -48,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()
/// # }
/// #
@@ -67,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 {
@@ -112,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};
@@ -146,17 +144,7 @@ impl TestServer {
builder.set_verify(SslVerifyMode::NONE);
ClientConnector::with_connector(builder.build()).start()
}
#[cfg(all(feature = "rust-tls", not(feature = "alpn")))]
{
use rustls::ClientConfig;
use std::fs::File;
use std::io::BufReader;
let mut config = ClientConfig::new();
let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
config.root_store.add_pem_file(pem_file).unwrap();
ClientConnector::with_connector(config).start()
}
#[cfg(not(any(feature = "alpn", feature = "rust-tls")))]
#[cfg(not(feature = "alpn"))]
{
ClientConnector::default().start()
}
@@ -181,16 +169,16 @@ impl TestServer {
pub fn url(&self, uri: &str) -> String {
if uri.starts_with('/') {
format!(
"{}://localhost:{}{}",
"{}://{}{}",
if self.ssl { "https" } else { "http" },
self.addr.port(),
self.addr,
uri
)
} else {
format!(
"{}://localhost:{}/{}",
"{}://{}/{}",
if self.ssl { "https" } else { "http" },
self.addr.port(),
self.addr,
uri
)
}
@@ -198,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
@@ -206,23 +197,17 @@ impl TestServer {
where
F: Future<Item = I, Error = E>,
{
self.rt.block_on(fut)
self.system.run_until_complete(fut)
}
/// Connect to websocket server at a given path
pub fn ws_at(
&mut self, path: &str,
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
let url = self.url(path);
self.rt
.block_on(ws::Client::with_connector(url, self.conn.clone()).connect())
}
/// Connect to a websocket server
/// Connect to websocket server
pub fn ws(
&mut self,
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
self.ws_at("/")
let url = self.url("/");
self.system.run_until_complete(
ws::Client::with_connector(url, self.conn.clone()).connect(),
)
}
/// Create `GET` request
@@ -263,13 +248,10 @@ impl Drop for TestServer {
pub struct TestServerBuilder<S> {
state: Box<Fn() -> S + Sync + Send + 'static>,
#[cfg(feature = "alpn")]
ssl: Option<SslAcceptorBuilder>,
#[cfg(feature = "rust-tls")]
rust_ssl: Option<ServerConfig>,
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,
@@ -278,25 +260,16 @@ impl<S: 'static> TestServerBuilder<S> {
state: Box::new(state),
#[cfg(feature = "alpn")]
ssl: None,
#[cfg(feature = "rust-tls")]
rust_ssl: None,
}
}
#[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
}
#[cfg(feature = "rust-tls")]
/// Create rust tls server
pub fn rustls(mut self, ssl: ServerConfig) -> Self {
self.rust_ssl = Some(ssl);
self
}
#[allow(unused_mut)]
/// Configure test application and run test server
pub fn start<F>(mut self, config: F) -> TestServer
@@ -305,66 +278,66 @@ impl<S: 'static> TestServerBuilder<S> {
{
let (tx, rx) = mpsc::channel();
let mut has_ssl = false;
#[cfg(feature = "alpn")]
{
has_ssl = has_ssl || self.ssl.is_some();
}
#[cfg(feature = "rust-tls")]
{
has_ssl = has_ssl || self.rust_ssl.is_some();
}
let ssl = self.ssl.is_some();
#[cfg(not(feature = "alpn"))]
let ssl = false;
// run server in separate thread
thread::spawn(move || {
let addr = TestServer::unused_addr();
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 state = self.state;
let mut srv = HttpServer::new(move || {
let srv = HttpServer::new(move || {
let mut app = TestApp::new(state());
config(&mut app);
vec![app]
}).workers(1)
.disable_signals();
tx.send((System::current(), 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 {
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen_with(tcp, OpensslAcceptor::new(ssl).unwrap());
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.start_incoming(tcp.incoming(), false);
}
}
#[cfg(feature = "rust-tls")]
#[cfg(not(feature = "alpn"))]
{
let ssl = self.rust_ssl.take();
if let Some(ssl) = ssl {
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen_with(tcp, RustlsAcceptor::new(ssl));
}
srv.start_incoming(tcp.incoming(), false);
}
if !has_ssl {
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen(tcp);
}
srv.start();
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,
conn,
ssl: has_ssl,
rt: Runtime::new().unwrap(),
server_sys,
ssl,
system,
conn: TestServer::get_conn(),
thread: Some(join),
}
}
}
@@ -381,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
@@ -402,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
@@ -412,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)
}
}
@@ -439,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 {
@@ -449,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);
/// }
/// ```
@@ -463,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<()> {
@@ -480,7 +448,6 @@ impl Default for TestRequest<()> {
params: Params::new(),
cookies: None,
payload: None,
prefix: 0,
}
}
}
@@ -518,7 +485,6 @@ impl<S: 'static> TestRequest<S> {
params: Params::new(),
cookies: None,
payload: None,
prefix: 0,
}
}
@@ -566,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
}
@@ -579,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 {
@@ -593,103 +553,50 @@ impl<S: 'static> TestRequest<S> {
uri,
version,
headers,
mut params,
params,
cookies,
payload,
prefix,
} = self;
let router = Router::<()>::default();
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.
pub fn run<H: Handler<S>>(self, h: &H) -> Result<HttpResponse, Error> {
///
/// This method panics is handler returns actor or async result.
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() {
AsyncResultItem::Ok(resp) => Ok(resp),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(fut) => {
let mut sys = System::new("test");
sys.block_on(fut)
}
AsyncResultItem::Future(_) => panic!("Async handler is not supported."),
},
Err(err) => Err(err.into()),
}
@@ -709,8 +616,8 @@ impl<S: 'static> TestRequest<S> {
let req = self.finish();
let fut = h(req.clone());
let mut sys = System::new("test");
match sys.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),
@@ -721,45 +628,4 @@ impl<S: 'static> TestRequest<S> {
Err(err) => Err(err),
}
}
/// This method generates `HttpRequest` instance and executes handler
pub fn run_async_result<F, R, I, E>(self, f: F) -> Result<I, E>
where
F: FnOnce(&HttpRequest<S>) -> R,
R: Into<AsyncResult<I, E>>,
{
let req = self.finish();
let res = f(&req);
match res.into().into() {
AsyncResultItem::Ok(resp) => Ok(resp),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(fut) => {
let mut sys = System::new("test");
sys.block_on(fut)
}
}
}
/// This method generates `HttpRequest` instance and executes handler
pub fn execute<F, R>(self, f: F) -> Result<HttpResponse, Error>
where
F: FnOnce(&HttpRequest<S>) -> R,
R: Responder + 'static,
{
let req = self.finish();
let resp = f(&req);
match resp.respond_to(&req) {
Ok(resp) => match resp.into().into() {
AsyncResultItem::Ok(resp) => Ok(resp),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(fut) => {
let mut sys = System::new("test");
sys.block_on(fut)
}
},
Err(err) => Err(err.into()),
}
}
}

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,95 +9,140 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
trait FnWith<T, R>: 'static {
fn call_with(self: &Self, T) -> 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<T, R, F: Fn(T) -> R + 'static> FnWith<T, R> for F {
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local))]
fn call_with(self: &Self, arg: T) -> R {
(*self)(arg)
impl<S: 'static, T: FromRequest<S>> Default for ExtractorConfig<S, T> {
fn default() -> Self {
ExtractorConfig {
cfg: Rc::new(UnsafeCell::new(T::Config::default())),
}
}
}
#[doc(hidden)]
pub trait WithFactory<T, S, R>: 'static
where
T: FromRequest<S>,
R: Responder,
{
fn create(self) -> With<T, S, R>;
fn create_with_config(self, T::Config) -> With<T, S, R>;
impl<S: 'static, T: FromRequest<S>> Clone for ExtractorConfig<S, T> {
fn clone(&self) -> Self {
ExtractorConfig {
cfg: Rc::clone(&self.cfg),
}
}
}
#[doc(hidden)]
pub trait WithAsyncFactory<T, S, R, I, E>: 'static
where
T: FromRequest<S>,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<Error>,
{
fn create(self) -> WithAsync<T, S, R, I, E>;
fn create_with_config(self, T::Config) -> WithAsync<T, S, R, I, E>;
impl<S: 'static, T: FromRequest<S>> AsRef<T::Config> for ExtractorConfig<S, T> {
fn as_ref(&self) -> &T::Config {
unsafe { &*self.cfg.get() }
}
}
// impl<T1, T2, T3, S, F, R> WithFactory<(T1, T2, T3), S, R> for F
// where F: Fn(T1, T2, T3) -> R + 'static,
// T1: FromRequest<S> + 'static,
// T2: FromRequest<S> + 'static,
// T3: FromRequest<S> + 'static,
// R: Responder + 'static,
// S: 'static,
// {
// fn create(self) -> With<(T1, T2, T3), S, R> {
// With::new(move |(t1, t2, t3)| (self)(t1, t2, t3), (
// T1::Config::default(), T2::Config::default(), T3::Config::default()))
// }
impl<S: 'static, T: FromRequest<S>> Deref for ExtractorConfig<S, T> {
type Target = T::Config;
// fn create_with_config(self, cfg: (T1::Config, T2::Config, T3::Config,)) -> With<(T1, T2, T3), S, R> {
// With::new(move |(t1, t2, t3)| (self)(t1, t2, t3), cfg)
// }
// }
fn deref(&self) -> &T::Config {
unsafe { &*self.cfg.get() }
}
}
#[doc(hidden)]
pub struct With<T, S, R>
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<FnWith<T, R>>,
cfg: Rc<T::Config>,
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
_s: PhantomData<S>,
}
impl<T, S, R> With<T, S, R>
impl<T, S, F, R> With<T, S, F, R>
where
F: Fn(T) -> R,
T: FromRequest<S>,
S: 'static,
{
pub fn new<F: Fn(T) -> R + 'static>(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,
}
}
}
impl<T, S, R> Handler<S> for With<T, S, R>
impl<T, S, F, R> Handler<S> for With<T, S, F, R>
where
F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
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(),
@@ -111,22 +158,24 @@ where
}
}
struct WithHandlerFut<T, S, R>
struct WithHandlerFut<T, S, F, R>
where
F: Fn(T) -> R,
R: Responder,
T: FromRequest<S> + 'static,
S: 'static,
{
started: bool,
hnd: Rc<FnWith<T, R>>,
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>>>,
}
impl<T, S, R> Future for WithHandlerFut<T, S, R>
impl<T, S, F, R> Future for WithHandlerFut<T, S, F, R>
where
F: Fn(T) -> R,
R: Responder + 'static,
T: FromRequest<S> + 'static,
S: 'static,
@@ -157,7 +206,8 @@ where
}
};
let item = match self.hnd.as_ref().call_with(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()),
};
@@ -173,39 +223,41 @@ where
}
}
#[doc(hidden)]
pub struct WithAsync<T, S, R, I, E>
pub struct WithAsync<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<E>,
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
_s: PhantomData<S>,
}
impl<T, S, R, I, E> WithAsync<T, S, R, I, E>
impl<T, S, F, R, I, E> WithAsync<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<Error>,
T: FromRequest<S>,
S: 'static,
{
pub fn new<F: Fn(T) -> R + 'static>(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,
}
}
}
impl<T, S, R, I, E> Handler<S> for WithAsync<T, S, R, I, E>
impl<T, S, F, R, I, E> Handler<S> for WithAsync<T, S, F, R, I, E>
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
@@ -214,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,
@@ -233,8 +285,9 @@ where
}
}
struct WithAsyncHandlerFut<T, S, R, I, E>
struct WithAsyncHandlerFut<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
@@ -242,16 +295,17 @@ where
S: 'static,
{
started: bool,
hnd: Rc<FnWith<T, R>>,
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>,
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T, S, R, I, E> Future for WithAsyncHandlerFut<T, S, R, I, E>
impl<T, S, F, R, I, E> Future for WithAsyncHandlerFut<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
@@ -302,101 +356,458 @@ where
}
};
self.fut2 = Some(self.hnd.as_ref().call_with(item));
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
self.fut2 = Some((*hnd)(item));
self.poll()
}
}
macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => {
impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func
where Func: Fn($($T,)+) -> Res + 'static,
$($T: FromRequest<State> + 'static,)+
Res: Responder + 'static,
State: 'static,
{
fn create(self) -> With<($($T,)+), State, Res> {
With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
}
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>,
}
fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> {
With::new(move |($($n,)+)| (self)($($n,)+), cfg)
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,
}
}
});
}
macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => {
impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func
where Func: Fn($($T,)+) -> Res + 'static,
$($T: FromRequest<State> + 'static,)+
Res: Future<Item=Item, Error=Err>,
Item: Responder + 'static,
Err: Into<Error>,
State: 'static,
{
fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> {
WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
}
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 create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> {
WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg)
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),
}
}
});
}
with_factory_tuple!((a, A));
with_factory_tuple!((a, A), (b, B));
with_factory_tuple!((a, A), (b, B), (c, C));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
with_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H)
);
with_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H),
(i, I)
);
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>>>,
}
with_async_factory_tuple!((a, A));
with_async_factory_tuple!((a, A), (b, B));
with_async_factory_tuple!((a, A), (b, B), (c, C));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
with_async_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H)
);
with_async_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H),
(i, I)
);
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,100 +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

@@ -1,31 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFXTCCA0WgAwIBAgIJAJ3tqfd0MLLNMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDRjELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBh
bnkxDDAKBgNVBAsMA09yZzEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMB4XDTE4
MDcyOTE4MDgzNFoXDTE5MDcyOTE4MDgzNFowYTELMAkGA1UEBhMCVVMxCzAJBgNV
BAgMAkNGMQswCQYDVQQHDAJTRjEQMA4GA1UECgwHQ29tcGFueTEMMAoGA1UECwwD
T3JnMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDZbMgDYilVH1Nv0QWEhOXG6ETmtjZrdLqrNg3NBWBIWCDF
cQ+fyTWxARx6vkF8A/3zpJyTcfQW8HgG38jw/A61QKaHBxzwq0HlNwY9Hh+Neeuk
L4wgrlQ0uTC7IEMrOJjNN0GPyRQVfVbGa8QcSCpOg85l8GCxLvVwkBH/M5atoMtJ
EzniNfK+gtk3hOL2tBqBCu9NDjhXPnJwNDLtTG1tQaHUJW/r281Wvv9I46H83DkU
05lYtauh0bKh5znCH2KpFmBGqJNRzou3tXZFZzZfaCPBJPZR8j5TjoinehpDtkPh
4CSio0PF2eIFkDKRUbdz/327HgEARJMXx+w1yHpS2JwHFgy5O76i68/Smx8j3DDA
2WIkOYAJFRMH0CBHKdsvUDOGpCgN+xv3whl+N806nCfC4vCkwA+FuB3ko11logng
dvr+y0jIUSU4THF3dMDEXYayF3+WrUlw0cBnUNJdXky85ZP81aBfBsjNSBDx4iL4
e4NhfZRS5oHpHy1t3nYfuttS/oet+Ke5KUpaqNJguSIoeTBSmgzDzL1TJxFLOzUT
2c/A9M69FdvSY0JB4EJX0W9K01Vd0JRNPwsY+/zvFIPama3suKOUTqYcsbwxx9xa
TMDr26cIQcgUAUOKZO43sQGWNzXX3FYVNwczKhkB8UX6hOrBJsEYiau4LGdokQID
AQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIB
AIX+Qb4QRBxHl5X2UjRyLfWVkimtGlwI8P+eJZL3DrHBH/TpqAaCvTf0EbRC32nm
ASDMwIghaMvyrW40QN6V/CWRRi25cXUfsIZr1iHAHK0eZJV8SWooYtt4iNrcUs3g
4OTvDxhNmDyNwV9AXhJsBKf80dCW6/84jItqVAj20/OO4Rkd2tEeI8NomiYBc6a1
hgwvv02myYF5hG/xZ9YSqeroBCZHwGYoJJnSpMPqJsxbCVnx2/U9FzGwcRmNHFCe
0g7EJZd3//8Plza6nkTBjJ/V7JnLqMU+ltx4mAgZO8rfzIr84qZdt0YN33VJQhYq
seuMySxrsuaAoxAmm8IoK9cW4IPzx1JveBQiroNlq5YJGf2UW7BTc3gz6c2tINZi
7ailBVdhlMnDXAf3/9xiiVlRAHOxgZh/7sRrKU7kDEHM4fGoc0YyZBTQKndPYMwO
3Bd82rlQ4sd46XYutTrB+mBYClVrJs+OzbNedTsR61DVNKKsRG4mNPyKSAIgOfM5
XmSvCMPN5JK9U0DsNIV2/SnVsmcklQczT35FLTxl9ntx8ys7ZYK+SppD7XuLfWMq
GT9YMWhlpw0aRDg/aayeeOcnsNBhzAFMcOpQj1t6Fgv4+zbS9BM2bT0hbX86xjkr
E6wWgkuCslMgQlEJ+TM5RhYrI5/rVZQhvmgcob/9gPZv
MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww
CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx
NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY
MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1
sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U
NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy
voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr
odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND
xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA
CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI
yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U
UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO
vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un
CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN
BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk
3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI
JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD
JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL
d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu
ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC
CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur
y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7
YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh
g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt
tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y
1QU=
-----END CERTIFICATE-----

View File

@@ -5,11 +5,8 @@ extern crate bytes;
extern crate flate2;
extern crate futures;
extern crate rand;
#[cfg(all(unix, feature = "uds"))]
extern crate tokio_uds;
use std::io::{Read, Write};
use std::{net, thread};
use std::io::Read;
use bytes::Bytes;
use flate2::read::GzDecoder;
@@ -67,20 +64,10 @@ fn test_simple() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_connection_close() {
let mut srv =
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let request = srv.get().header("Connection", "close").finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
#[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(),
})
@@ -123,13 +110,14 @@ 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()
.content_encoding(http::ContentEncoding::Deflate)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -152,13 +140,14 @@ 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()
.content_encoding(http::ContentEncoding::Deflate)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -184,13 +173,14 @@ 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()
.content_encoding(http::ContentEncoding::Deflate)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -208,24 +198,18 @@ fn test_client_gzip_encoding_large_random() {
assert_eq!(bytes, Bytes::from(data));
}
#[cfg(all(unix, feature = "uds"))]
#[test]
fn test_compatible_with_unix_socket_stream() {
let (stream, _) = tokio_uds::UnixStream::pair().unwrap();
let _ = client::Connection::from_stream(stream);
}
#[cfg(feature = "brotli")]
#[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()
.content_encoding(http::ContentEncoding::Gzip)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -252,13 +236,14 @@ 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()
.content_encoding(http::ContentEncoding::Gzip)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -281,13 +266,14 @@ 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()
.content_encoding(http::ContentEncoding::Br)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -314,13 +300,14 @@ 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()
.content_encoding(http::ContentEncoding::Br)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -341,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| {
@@ -349,7 +336,8 @@ fn test_client_streaming_explicit() {
.chunked()
.content_encoding(http::ContentEncoding::Identity)
.body(body))
}).responder()
})
.responder()
})
});
@@ -405,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" {
@@ -437,67 +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"),
"\""
)));
}
#[test]
fn client_read_until_eof() {
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
let lst = net::TcpListener::bind(addr).unwrap();
for stream in lst.incoming() {
let mut stream = stream.unwrap();
let mut b = [0; 1000];
let _ = stream.read(&mut b).unwrap();
let _ = stream
.write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!");
}
});
let mut sys = actix::System::new("test");
// client request
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.block_on(req.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = sys.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"welcome!"));
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,69 +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.0.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)
})
});
@@ -267,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)
})
});
@@ -300,14 +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()
},
)
})
.responder()
})
});
});
@@ -330,11 +200,13 @@ 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))
}).responder()
})
.responder()
})
});
});
@@ -354,11 +226,13 @@ 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))
}).responder()
})
.responder()
})
});
});
@@ -378,14 +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()
},
)
})
.responder()
})
});
});
@@ -418,11 +293,13 @@ fn test_path_and_query_extractor2_async3() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
Delay::new(Instant::now() + Duration::from_millis(10))
.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()
})
.responder()
})
});
});
@@ -457,10 +334,12 @@ 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))
}).responder()
})
.responder()
})
});
});
@@ -564,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)))
}
@@ -573,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
@@ -84,10 +85,11 @@ fn test_middleware_multiple() {
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).handler(|_| HttpResponse::Ok())
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
@@ -142,10 +144,11 @@ fn test_resource_middleware_multiple() {
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).handler(|_| HttpResponse::Ok())
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
@@ -174,7 +177,8 @@ fn test_scope_middleware() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
@@ -204,11 +208,13 @@ fn test_scope_middleware_multiple() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest {
})
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
@@ -237,9 +243,11 @@ fn test_middleware_async_handler() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/", |r| {
})
.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()))
})
})
@@ -274,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()))
})
})
@@ -306,9 +315,11 @@ fn test_scope_middleware_async_handler() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| {
})
.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()))
})
})
@@ -324,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()
}
@@ -372,7 +383,8 @@ fn test_scope_middleware_async_error() {
start: Arc::clone(&act_req),
response: Arc::clone(&act_resp),
finish: Arc::clone(&act_fin),
}).resource("/test", |r| r.f(index_test_middleware_async_error))
})
.resource("/test", |r| r.f(index_test_middleware_async_error))
})
});
@@ -404,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);
})
});
@@ -424,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(
@@ -437,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(
@@ -450,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 |_| {
@@ -506,11 +518,13 @@ fn test_async_middleware_multiple() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareAsyncTest {
})
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
@@ -540,11 +554,13 @@ fn test_async_sync_middleware_multiple() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest {
})
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
@@ -575,7 +591,8 @@ fn test_async_scope_middleware() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
@@ -607,11 +624,13 @@ fn test_async_scope_middleware_multiple() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareAsyncTest {
})
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
@@ -643,11 +662,13 @@ fn test_async_async_scope_middleware_multiple() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest {
})
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
@@ -680,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());
})
});
@@ -719,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());
})
});
@@ -758,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());
})
});
@@ -776,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"))
}
}
@@ -784,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"),
))))
@@ -810,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);
@@ -840,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);
@@ -871,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);
@@ -903,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);
@@ -934,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);
@@ -965,91 +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,37 +1,34 @@
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::Arc;
use std::{thread, time};
#[cfg(feature = "brotli")]
extern crate brotli2;
#[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::{Bytes, BytesMut};
use flate2::read::GzDecoder;
use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder};
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
use flate2::Compression;
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::*;
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
@@ -59,35 +56,31 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
#[test]
#[cfg(unix)]
fn test_start() {
use actix::System;
use std::sync::mpsc;
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());
}
@@ -99,122 +92,56 @@ 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]
#[cfg(unix)]
fn test_shutdown() {
use actix::System;
use std::net;
use std::sync::mpsc;
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]
#[cfg(unix)]
fn test_panic() {
use actix::System;
use std::sync::mpsc;
let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel();
thread::spawn(|| {
System::run(move || {
let srv = server::new(|| {
App::new()
.resource("/panic", |r| {
r.method(http::Method::GET).f(|_| -> &'static str {
panic!("error");
});
}).resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})
}).workers(1);
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
srv.start();
let _ = tx.send((addr, System::current()));
});
});
let (addr, sys) = rx.recv().unwrap();
System::set_current(sys.clone());
let mut rt = Runtime::new().unwrap();
{
let req = client::ClientRequest::get(format!("http://{}/panic", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send());
assert!(response.is_err());
}
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send());
assert!(response.is_err());
}
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send()).unwrap();
assert!(response.status().is_success());
}
let _ = sys.stop();
}
#[test]
@@ -333,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());
@@ -432,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]
@@ -460,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]
@@ -534,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 = ZlibEncoder::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| {
@@ -586,7 +480,7 @@ fn test_body_deflate() {
let bytes = srv.execute(response.body()).unwrap();
// decode deflate
let mut e = ZlibDecoder::new(Vec::new());
let mut e = DeflateDecoder::new(Vec::new());
e.write_all(bytes.as_ref()).unwrap();
let dec = e.finish().unwrap();
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
@@ -621,13 +515,14 @@ 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()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -653,13 +548,14 @@ 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()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -684,18 +580,19 @@ 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()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -721,17 +618,18 @@ 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()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
.responder()
})
});
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let enc = e.finish().unwrap();
@@ -753,17 +651,18 @@ 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()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
.responder()
})
});
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
@@ -784,22 +683,23 @@ 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()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
.responder()
})
});
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
@@ -822,13 +722,14 @@ 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()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -855,13 +756,14 @@ 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()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
.responder()
})
});
@@ -887,10 +789,10 @@ fn test_brotli_encoding_large() {
fn test_h2() {
let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let addr = srv.addr();
thread::sleep(time::Duration::from_millis(500));
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()))
@@ -904,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);
@@ -917,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()));
}
@@ -931,80 +833,3 @@ fn test_application() {
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_default_404_handler_response() {
let mut srv = test::TestServer::with_factory(|| {
App::new()
.prefix("/app")
.resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
});
let addr = srv.addr();
let mut buf = [0; 24];
let request = TcpStream::connect(&addr)
.and_then(|sock| {
tokio::io::write_all(sock, "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n")
.and_then(|(sock, _)| tokio::io::read_exact(sock, &mut buf))
.and_then(|(_, buf)| Ok(buf))
}).map_err(|e| panic!("{:?}", e));
let response = srv.execute(request).unwrap();
let rep = String::from_utf8_lossy(&response[..]);
assert!(rep.contains("HTTP/1.1 404 Not Found"));
}
#[test]
fn test_server_cookies() {
use actix_web::http;
let mut srv = test::TestServer::with_factory(|| {
App::new().resource("/", |r| {
r.f(|_| {
HttpResponse::Ok()
.cookie(
http::CookieBuilder::new("first", "first_value")
.http_only(true)
.finish(),
).cookie(http::Cookie::new("second", "first_value"))
.cookie(http::Cookie::new("second", "second_value"))
.finish()
})
})
});
let first_cookie = http::CookieBuilder::new("first", "first_value")
.http_only(true)
.finish();
let second_cookie = http::Cookie::new("second", "second_value");
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
let cookies = response.cookies().expect("To have cookies");
assert_eq!(cookies.len(), 2);
if cookies[0] == first_cookie {
assert_eq!(cookies[1], second_cookie);
} else {
assert_eq!(cookies[0], second_cookie);
assert_eq!(cookies[1], first_cookie);
}
let first_cookie = first_cookie.to_string();
let second_cookie = second_cookie.to_string();
//Check that we have exactly two instances of raw cookie headers
let cookies = response
.headers()
.get_all(http::header::SET_COOKIE)
.iter()
.map(|header| header.to_str().expect("To str").to_string())
.collect::<Vec<_>>();
assert_eq!(cookies.len(), 2);
if cookies[0] == first_cookie {
assert_eq!(cookies[1], second_cookie);
} else {
assert_eq!(cookies[0], second_cookie);
assert_eq!(cookies[1], first_cookie);
}
}

View File

@@ -7,13 +7,10 @@ extern crate rand;
use bytes::Bytes;
use futures::Stream;
use rand::distributions::Alphanumeric;
use rand::Rng;
#[cfg(feature = "alpn")]
extern crate openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
use actix::prelude::*;
use actix_web::*;
@@ -64,45 +61,6 @@ fn test_simple() {
);
}
// websocket resource helper function
fn start_ws_resource(req: &HttpRequest) -> Result<HttpResponse, Error> {
ws::start(req, Ws)
}
#[test]
fn test_simple_path() {
const PATH: &str = "/v1/ws/";
// Create a websocket at a specific path.
let mut srv = test::TestServer::new(|app| {
app.resource(PATH, |r| r.route().f(start_ws_resource));
});
// fetch the sockets for the resource at a given path.
let (reader, mut writer) = srv.ws_at(PATH).unwrap();
writer.text("text");
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
writer.binary(b"text".as_ref());
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Binary(Bytes::from_static(b"text").into()))
);
writer.ping("ping");
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Pong("ping".to_owned())));
writer.close(Some(ws::CloseCode::Normal.into()));
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
);
}
#[test]
fn test_empty_close_code() {
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
@@ -128,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>();
@@ -146,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>();
@@ -161,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,
@@ -213,7 +146,8 @@ impl Ws2 {
act.send(ctx);
}
actix::fut::ok(())
}).wait(ctx);
})
.wait(ctx);
}
}
@@ -292,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,
@@ -312,42 +246,3 @@ fn test_ws_server_ssl() {
assert_eq!(item, data);
}
}
#[test]
#[cfg(feature = "rust-tls")]
fn test_ws_server_rust_tls() {
extern crate rustls;
use rustls::internal::pemfile::{certs, rsa_private_keys};
use rustls::{NoClientAuth, ServerConfig};
use std::fs::File;
use std::io::BufReader;
// load ssl keys
let mut config = ServerConfig::new(NoClientAuth::new());
let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap());
let cert_chain = certs(cert_file).unwrap();
let mut keys = rsa_private_keys(key_file).unwrap();
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
let mut srv = test::TestServer::build().rustls(config).start(|app| {
app.handler(|req| {
ws::start(
req,
Ws2 {
count: 0,
bin: false,
},
)
})
});
let (mut reader, _writer) = srv.ws().unwrap();
let data = Some(ws::Message::Text("0".repeat(65_536)));
for _ in 0..10_000 {
let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r;
assert_eq!(item, data);
}
}

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;
}
}
}
}