1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-03 09:36:36 +02:00

Compare commits

...

142 Commits

Author SHA1 Message Date
d6787e6c56 prepare release 2018-05-15 10:20:32 -07:00
b9d870645f store cookies in extensions 2018-05-15 10:09:48 -07:00
ef89430f9b undeprecate query() and store query in extensions 2018-05-15 09:53:58 -07:00
953a0d4e4a add test case for #222 2018-05-15 09:29:59 -07:00
5ea2d68438 h1 decoder blocks on error #222 2018-05-15 07:55:36 -07:00
d65a03f6ac use latest nightly for appveyor 2018-05-13 08:43:09 -07:00
b588b2bf5c Merge pull request #221 from skorgu/patch-1
Include mention of http client in README.md
2018-05-11 21:56:46 -07:00
d455e2cd13 Merge branch 'master' into patch-1 2018-05-11 21:56:35 -07:00
9306631d6e Fix segfault in ServerSettings::get_response_builder() 2018-05-11 21:19:48 -07:00
f735da504b Include mention of http client in README.md 2018-05-11 20:36:54 -04:00
487a713ca0 update doc string 2018-05-11 15:01:15 -07:00
095ad328ee prepare release 2018-05-10 15:45:06 -07:00
a38afa0cec --no-count for tarpaulin 2018-05-10 13:05:56 -07:00
9619698543 doc string 2018-05-10 13:04:56 -07:00
4b1a471b35 add more examples for extractor config 2018-05-10 13:03:43 -07:00
b6039b0bff add doc string 2018-05-10 11:04:03 -07:00
d8fa43034f export ExtractorConfig type 2018-05-10 11:00:22 -07:00
92f993e054 Fix client request timeout handling 2018-05-10 09:37:38 -07:00
c172deb0f3 Merge pull request #219 from benjamingroeber/improve-readme
correct order of format arguments in readme example
2018-05-10 09:15:50 -07:00
dee6aed010 Merge branch 'master' into improve-readme 2018-05-10 09:15:44 -07:00
76f021a6e3 add tests for ErrorXXX helpers 2018-05-10 09:13:26 -07:00
2f244ea028 fix order of name and id in readme example 2018-05-10 18:12:59 +02:00
5f5ddc8f01 Merge pull request #218 from Dowwie/master
added error response functions for 501,502,503,504
2018-05-10 08:59:23 -07:00
8b473745cb added error response functions for 501,502,503,504 2018-05-10 11:26:38 -04:00
18575ee1ee Add Router::with_async() method for async handler registration 2018-05-09 16:27:31 -07:00
e58b38fd13 deprecate WsWrite from top level mod 2018-05-09 06:12:16 -07:00
b043c34632 bump version 2018-05-09 06:05:44 -07:00
b748bf3b0d make api public 2018-05-09 06:05:16 -07:00
be12d5e6fc make WsWriter trait optional 2018-05-09 05:48:06 -07:00
7c4941f868 update migration doc 2018-05-08 18:48:09 -07:00
d1f5c457c4 Merge branch 'master' of github.com:actix/actix-web 2018-05-08 18:35:52 -07:00
c26c5fd9a4 prep release 2018-05-08 18:34:36 -07:00
4a73d1c8c1 Merge pull request #216 from lcowell/lcowell-scoupe
replace typo `scoupe` with `scope`
2018-05-08 17:47:20 -07:00
7c395fcc83 replace typo scoupe with scope 2018-05-08 17:40:18 -07:00
54c33a7aff Allow to exclude certain endpoints from logging #211 2018-05-08 16:30:34 -07:00
47d80382b2 Fix http/2 payload streaming #215 2018-05-08 15:44:50 -07:00
ba816a8562 Merge pull request #214 from niklasf/de-path-404
let Path::from_request() fail with ErrorNotFound
2018-05-08 14:41:05 -07:00
6f75b0e95e let Path::from_request() fail with ErrorNotFound 2018-05-08 22:59:46 +02:00
b3cc43bb9b Fix connector's default keep-alive and lifetime settings #212 2018-05-08 13:41:04 -07:00
ecda97aadd update doc string 2018-05-08 05:54:06 -07:00
8cda362866 simplify pipeline 2018-05-07 16:09:41 -07:00
3c6c1268c9 travis cover report 2018-05-07 15:54:29 -07:00
72908d974c test for Scope::route(); prep release 2018-05-07 15:19:03 -07:00
c755d71a8b add filters support to scopes 2018-05-07 14:40:04 -07:00
a817ddb57b add variable segments support for scope prefix 2018-05-07 13:50:43 -07:00
44c36e93d1 Merge pull request #210 from andreevlex/feature/spelling-check-06-05
spelling check
2018-05-07 11:30:46 -07:00
c92ebc22d7 Merge branch 'master' into feature/spelling-check-06-05 2018-05-07 11:30:39 -07:00
599fd6af93 fix formatting 2018-05-07 20:53:45 +03:00
fa81d97004 more handler tests 2018-05-06 20:05:31 -07:00
c54f045b39 more handler tests 2018-05-06 15:11:36 -07:00
cd11293c1f spelling check 2018-05-06 19:07:30 +03:00
45325a5f75 more middleware tests 2018-05-06 08:33:41 -07:00
a7c40024ce async handle middleware test 2018-05-05 18:40:16 -07:00
0af4d01fe4 move middleware tests to seprate module 2018-05-05 12:18:43 -07:00
bd6e18b7fe update migration doc 2018-05-04 13:38:17 -07:00
f66cf16823 upgrade regex 2018-05-04 12:25:06 -07:00
03d6b04eef update tests 2018-05-04 12:11:38 -07:00
f37880d89c refactor Responder trait 2018-05-04 11:44:22 -07:00
8b43574bd5 Merge branch 'master' of github.com:actix/actix-web 2018-05-03 16:27:12 -07:00
b07d0e712f always provide backtrace for error 2018-05-03 16:26:42 -07:00
acd7380865 rename Reply to a AsyncResult 2018-05-03 16:22:08 -07:00
0208dfb6b2 Merge pull request #208 from DenisKolodin/ws-trait
Add WsWriter trait
2018-05-03 10:43:44 -07:00
bb61dd41af Merge branch 'master' into ws-trait 2018-05-03 08:57:45 -07:00
58079b5bbe add session test 2018-05-02 19:11:44 -07:00
3623383e83 fix tests 2018-05-02 16:48:42 -07:00
7036656ae4 make Reply generic over error too 2018-05-02 16:33:29 -07:00
32a2866449 Allow to override files listing renderer for #203 2018-05-02 15:53:07 -07:00
35a4078434 update changelog 2018-05-02 13:43:51 -07:00
4ca5d8bcfc add FromRequest impl for tuples of various length 2018-05-02 13:38:25 -07:00
a38acb41e5 better query example 2018-05-02 06:30:06 -07:00
31e23d4ab1 add query deprecation info 2018-05-02 06:28:38 -07:00
1aadfee6f7 rename from_default to extract 2018-05-02 06:09:50 -07:00
76b644365f use read only ref for FromRequest; remove unnecessary static 2018-05-02 06:07:30 -07:00
80f385e703 Add WsWriter trait
`WsWriter` trait is a common interface for writing to a websocket and
it's implemented for both: `WebScoketContext` and `ClientWriter`.
2018-05-02 08:35:50 +03:00
a1958deaae add impl Future for Reply 2018-05-01 17:30:06 -07:00
8d65468c58 refactor FromRequest trait 2018-05-01 17:19:15 -07:00
195246573e rename threads to workers 2018-05-01 13:15:35 -07:00
e01102bda2 no need for mut 2018-05-01 11:45:46 -07:00
9b6343d54b refactor session impl 2018-05-01 09:40:23 -07:00
d9a4fadaae make HttpRequest::extensions() readonly 2018-05-01 09:05:50 -07:00
48e05a2d87 add nested scope support 2018-04-30 22:04:24 -07:00
70d0c5c700 update changes 2018-04-30 19:56:17 -07:00
d43ca96c5c Allow to use ssl and non-ssl connections with the same HttpServer #206 2018-04-30 19:51:55 -07:00
bfd46e6a71 update doc string 2018-04-29 22:28:16 -07:00
25b245ac72 allow to use custom state for scope 2018-04-29 22:19:52 -07:00
eefbe19651 remove deprecated types and methods 2018-04-29 21:05:10 -07:00
ab4e889f96 add middleware finished handler for route middleware 2018-04-29 20:50:38 -07:00
91235ac816 fix reading from socket 2018-04-29 20:34:59 -07:00
9c1bda3eca fix stable compiler compatibility 2018-04-29 19:49:26 -07:00
4a29f12876 update doc string; missing file 2018-04-29 19:39:28 -07:00
368730f5f1 Add route scopes #202 2018-04-29 19:35:50 -07:00
aa757a5be8 Allow to access Error's backtrace object 2018-04-29 14:21:50 -07:00
03ded62337 bump minimum supported rustc version because of minor version change of parking_lot crate 2018-04-29 14:13:46 -07:00
c72d1381a6 clippy warnings 2018-04-29 09:09:08 -07:00
d98d723f97 bump rustc version requirements 2018-04-29 08:24:19 -07:00
eb6e618812 Merge pull request #204 from svenstaro/master
Add Content-Disposition to NamedFile (fixes #172)
2018-04-29 08:18:48 -07:00
de222fe33b Merge and fix PR comments 2018-04-29 14:02:50 +02:00
de49796fd1 clippy warnings; fmt 2018-04-28 22:55:47 -07:00
a38c3985f6 refactor http1 parser 2018-04-28 22:20:32 -07:00
492c072564 Add Content-Disposition to NamedFile (fixes #172) 2018-04-27 09:49:55 +02:00
fd876efa68 allow to access application state during configuration stage 2018-04-26 09:05:07 -07:00
c5b9bed478 update changes 2018-04-26 08:01:08 -07:00
3eba383cdc Merge pull request #196 from fuchsnj/websocket_close_reason
Websocket close reason
2018-04-26 07:55:32 -07:00
927f2e594e Merge branch 'master' into websocket_close_reason 2018-04-25 20:17:19 -07:00
fa9edf2180 prep release 2018-04-24 12:34:10 -07:00
5ca904d1db make flate crate optional 2018-04-24 12:24:04 -07:00
2e7d323e1a add r2d2 example link 2018-04-24 09:34:38 -07:00
b66566f610 comments 2018-04-24 09:32:19 -07:00
2477afcf30 Allow to use rust backend for flate2 crate #199 2018-04-24 09:29:15 -07:00
bcd03a9c62 link to askama example 2018-04-24 09:16:46 -07:00
f8af3ef7f4 refactor keep-alive 2018-04-22 15:28:04 -07:00
f8b75c157f fix style 2018-04-22 11:43:47 -04:00
b7b61afacc add ws close description parse test 2018-04-21 17:20:23 -04:00
507361c1df Merge branch 'master' into websocket_close_reason 2018-04-21 17:05:43 -04:00
f6fd9e70f9 code cleanup 2018-04-21 16:53:55 -04:00
de8a09254d use Optional with websocket close reason 2018-04-21 16:50:27 -04:00
f89b7a9bb8 Merge pull request #194 from actix/brandur-allowed-origin-into
Let CSRF's `allowed_origin()` be specified as a type supporting `Into<String>`
2018-04-21 10:37:18 -07:00
59244b203c Let CSRF's allowed_origin() be specified as a type supporting Into<String>
A very minor addition: I'm using this middleware on specific resources,
and given a non-static string, I often have to `clone()` already to get
a string into a closure. Take this code for example:

``` rust
let server = actix_web::server::new(move || {
    let csrf_origin_graphql = csrf_origin.clone();

    ...

    .resource("/graphql", move |r| {
	r.middleware(
	    csrf::CsrfFilter::new().allowed_origin(csrf_origin_graphql.as_str()),
	);

	r.method(Method::POST).a(graphql::handlers::graphql_post);
    })
```

Letting `allowed_origin()` take an `Into<String>` instead of `&str` would
prevent a second `clone()` in the code above, and also make the code a little
nicer to read (you eliminate the `.as_str()` above). This is a pattern that
seems to be common throughout actix-web already anyway, so it should also be
fine to have here.
2018-04-21 08:41:06 -07:00
2adf8a3a48 add changelog entry 2018-04-21 07:56:11 -07:00
805dbea8e7 Merge pull request #192 from fuchsnj/check_if_close_code_exists
check if close code exists before reading it
2018-04-21 07:54:25 -07:00
dc9a24a189 add websocket empty close status test 2018-04-20 21:55:07 -04:00
5528cf62f0 check if close code exists before reading it 2018-04-20 21:30:18 -04:00
9880a95603 Merge pull request #189 from drklee3/patch-1
Update README links to use new guide
2018-04-19 19:24:40 -07:00
2579c49865 Update README links to use new guide 2018-04-19 18:51:01 -07:00
01a0f3f5a0 remove unused dependency 2018-04-19 09:54:22 -07:00
2c8d987241 Use Display formatting for InternalError Display implementation #188 2018-04-19 07:55:09 -07:00
813d1d6e66 doc strings layout 2018-04-18 20:41:03 -07:00
48b02abee7 fmt 2018-04-18 20:16:29 -07:00
ce1081432b export session module 2018-04-18 20:11:49 -07:00
e9bdba57a0 Add identity service middleware 2018-04-18 19:05:24 -07:00
f907be585e Middleware response() is not invoked if there was an error in async handler #187 2018-04-18 14:15:53 -07:00
022f9800ed formatting 2018-04-18 10:49:03 -07:00
a9a54ac4c6 prep release 2018-04-18 10:45:59 -07:00
50b9fee3a7 Merge branch 'master' of github.com:actix/actix-web 2018-04-17 16:24:02 -07:00
bf9a90293f fix doc strings 2018-04-17 16:22:25 -07:00
17ec3a3a26 Merge pull request #185 from kornelski/master
Replace use of try!() with ?
2018-04-17 15:57:09 -07:00
5b4b885fd6 Replace use of try!() with ? 2018-04-17 23:20:47 +01:00
65b8197876 better doc string for Application::with_state() 2018-04-17 13:59:55 -07:00
a826d113ee add custom request path quoter #182 2018-04-17 12:55:13 -07:00
3a79505a44 update doc string 2018-04-17 07:51:06 -07:00
5f3a7a6a52 Merge pull request #184 from ivanovaleksey/patch-1
Fix route in App::resource example
2018-04-17 07:49:09 -07:00
6a7b097bcf Fix route in App::resource example 2018-04-17 16:01:34 +03:00
81 changed files with 6418 additions and 2669 deletions

View File

@ -4,13 +4,13 @@ environment:
matrix:
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: 1.21.0
CHANNEL: 1.24.0
- TARGET: i686-pc-windows-msvc
CHANNEL: 1.21.0
CHANNEL: 1.24.0
- TARGET: x86_64-pc-windows-gnu
CHANNEL: 1.21.0
CHANNEL: 1.24.0
- TARGET: x86_64-pc-windows-msvc
CHANNEL: 1.21.0
CHANNEL: 1.24.0
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: stable
@ -31,13 +31,13 @@ environment:
CHANNEL: beta
# Nightly channel
- TARGET: i686-pc-windows-gnu
CHANNEL: nightly-2017-12-21
CHANNEL: nightly
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly-2017-12-21
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly-2017-12-21
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly-2017-12-21
CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
@ -59,4 +59,4 @@ build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo test --no-default-features
- cargo test --no-default-features --features="flate2-rust"

View File

@ -8,19 +8,13 @@ cache:
matrix:
include:
- rust: 1.21.0
- rust: 1.24.0
- rust: stable
- rust: beta
- rust: nightly
allow_failures:
- rust: nightly
#rust:
# - 1.21.0
# - stable
# - beta
# - nightly-2018-01-03
env:
global:
# - RUSTFLAGS="-C link-dead-code"
@ -37,24 +31,25 @@ before_script:
script:
- |
if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then
cargo clean
cargo test --features="alpn,tls" -- --nocapture
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi
# Upload docs
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --features "alpn, tls, session" --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import.git &&
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
echo "Uploaded documentation"
fi
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi

View File

@ -1,5 +1,81 @@
# Changes
## 0.6.5 (2018-05-15)
* Fix error handling during request decoding #222
## 0.6.4 (2018-05-11)
* Fix segfault in ServerSettings::get_response_builder()
## 0.6.3 (2018-05-10)
* Add `Router::with_async()` method for async handler registration.
* Added error response functions for 501,502,503,504
* Fix client request timeout handling
## 0.6.2 (2018-05-09)
* WsWriter trait is optional.
## 0.6.1 (2018-05-08)
* Fix http/2 payload streaming #215
* Fix connector's default `keep-alive` and `lifetime` settings #212
* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214
* Allow to exclude certain endpoints from logging #211
## 0.6.0 (2018-05-08)
* Add route scopes #202
* Allow to use ssl and non-ssl connections at the same time #206
* Websocket CloseCode Empty/Status is ambiguous #193
* Add Content-Disposition to NamedFile #204
* Allow to access Error's backtrace object
* Allow to override files listing renderer for `StaticFiles` #203
* Various extractor usability improvements #207
## 0.5.6 (2018-04-24)
* Make flate2 crate optional #200
## 0.5.5 (2018-04-24)
* Fix panic when Websocket is closed with no error code #191
* Allow to use rust backend for flate2 crate #199
## 0.5.4 (2018-04-19)
* Add identity service middleware
* Middleware response() is not invoked if there was an error in async handler #187
* Use Display formatting for InternalError Display implementation #188
## 0.5.3 (2018-04-18)
* Impossible to quote slashes in path parameters #182
## 0.5.2 (2018-04-16)

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "0.5.2"
version = "0.6.5"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@ -26,7 +26,7 @@ name = "actix_web"
path = "src/lib.rs"
[features]
default = ["session", "brotli"]
default = ["session", "brotli", "flate2-c"]
# tls
tls = ["native-tls", "tokio-tls"]
@ -34,19 +34,24 @@ tls = ["native-tls", "tokio-tls"]
# openssl
alpn = ["openssl", "tokio-openssl"]
# sessions
# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]
# brotli encoding
# brotli encoding, requires c compiler
brotli = ["brotli2"]
# miniz-sys backend for flate2 crate
flate2-c = ["flate2/miniz-sys"]
# rust backend for flate2 crate
flate2-rust = ["flate2/rust_backend"]
[dependencies]
actix = "^0.5.5"
base64 = "0.9"
bitflags = "1.0"
failure = "0.1.1"
flate2 = "1.0"
h2 = "0.1"
http = "^0.1.5"
httparse = "1.2"
@ -58,7 +63,7 @@ mime_guess = "2.0.0-alpha"
num_cpus = "1.0"
percent-encoding = "1.0"
rand = "0.4"
regex = "0.2"
regex = "1.0"
serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.5"
@ -71,6 +76,7 @@ lazy_static = "1.0"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="1.0", optional = true, default-features = false }
# io
mio = "^0.6.13"
@ -79,9 +85,9 @@ bytes = "0.4"
byteorder = "1"
futures = "0.1"
futures-cpupool = "0.1"
slab = "0.4"
tokio-io = "0.1"
tokio-core = "0.1"
trust-dns-resolver = "0.8"
# native-tls
native-tls = { version="0.1", optional = true }

View File

@ -1,30 +0,0 @@
# Migration from 0.4 to 0.5
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
methods return `HttpResponse` instead of `Result<HttpResponse>`
* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
moved to `actix_web::http` module
* `actix_web::header` moved to `actix_web::http::header`
* `NormalizePath` moved to `actix_web::http` module
* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
shortcut for `actix_web::server::HttpServer::new()`
* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
functions should be used instead
* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
instead of `Result<_, http::Error>`
* `Application` renamed to a `App`
* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`

82
MIGRATION.md Normal file
View File

@ -0,0 +1,82 @@
## Migration from 0.5 to 0.6
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
* `ws::Message::Close` now includes optional close reason.
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
* `HttpServer::threads()` renamed to `HttpServer::workers()`.
* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
* `HttpRequest::extensions()` returns read only reference to the request's Extension
`HttpRequest::extensions_mut()` returns mutable reference.
* Instead of
`use actix_web::middleware::{
CookieSessionBackend, CookieSessionError, RequestSession,
Session, SessionBackend, SessionImpl, SessionStorage};`
use `actix_web::middleware::session`
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
* `FromRequest::from_request()` accepts mutable reference to a request
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
* [`Responder::respond_to()`](
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
is generic over `S`
* Use `Query` extractor instead of HttpRequest::query()`.
```rust
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
...
}
```
or
```rust
let q = Query::<HashMap<String, String>>::extract(req);
```
* Websocket operations are implemented as `WsWriter` trait.
you need to use `use actix_web::ws::WsWriter`
## Migration from 0.4 to 0.5
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
methods return `HttpResponse` instead of `Result<HttpResponse>`
* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
moved to `actix_web::http` module
* `actix_web::header` moved to `actix_web::http::header`
* `NormalizePath` moved to `actix_web::http` module
* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
shortcut for `actix_web::server::HttpServer::new()`
* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
functions should be used instead
* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
instead of `Result<_, http::Error>`
* `Application` renamed to a `App`
* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`

View File

@ -2,12 +2,12 @@
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/actix-web/guide/qs_13.html) protocols
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.rs/actix-web/guide/qs_9.html) support
* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable [request routing](https://actix.rs/actix-web/guide/qs_5.html)
* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html)
* Graceful server shutdown
* Multipart streams
* Static assets
@ -18,6 +18,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
[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://github.com/actix/actix-web/blob/master/src/client/mod.rs)
* Built on top of [Actix actor framework](https://github.com/actix/actix)
## Documentation & community resources
@ -27,7 +28,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* [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.21 or later
* Minimum supported Rust version: 1.24 or later
## Example
@ -36,7 +37,7 @@ extern crate actix_web;
use actix_web::{http, server, App, Path};
fn index(info: Path<(u32, String)>) -> String {
format!("Hello {}! id:{}", info.0, info.1)
format!("Hello {}! id:{}", info.1, info.0)
}
fn main() {
@ -54,9 +55,11 @@ fn main() {
* [Stateful](https://github.com/actix/examples/tree/master/state/)
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
* [Simple websocket session](https://github.com/actix/examples/tree/master/websocket/)
* [Tera templates](https://github.com/actix/examples/tree/master/template_tera/)
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/)
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
* [Json](https://github.com/actix/examples/tree/master/json/)

View File

@ -1,8 +1,15 @@
extern crate version_check;
fn main() {
match version_check::is_min_version("1.26.0") {
Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"),
_ => (),
};
match version_check::is_nightly() {
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
Some(true) => {
println!("cargo:rustc-cfg=actix_nightly");
println!("cargo:rustc-cfg=actix_impl_trait");
}
Some(false) => (),
None => (),
};

View File

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

View File

@ -1,22 +1,20 @@
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::mem;
use std::rc::Rc;
use handler::Reply;
use handler::{FromRequest, Handler, Responder, RouteHandler, 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::{HandlerType, Pipeline, PipelineHandler};
use pred::Predicate;
use resource::ResourceHandler;
use router::{Resource, Router};
use scope::Scope;
use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, ServerSettings};
#[deprecated(since = "0.5.0", note = "please use `actix_web::App` instead")]
pub type Application<S> = App<S>;
/// Application
pub struct HttpApplication<S = ()> {
state: Rc<S>,
@ -32,7 +30,12 @@ pub(crate) struct Inner<S> {
default: ResourceHandler<S>,
encoding: ContentEncoding,
resources: Vec<ResourceHandler<S>>,
handlers: Vec<(String, Box<RouteHandler<S>>)>,
handlers: Vec<PrefixHandlerType<S>>,
}
enum PrefixHandlerType<S> {
Handler(String, Box<RouteHandler<S>>),
Scope(Resource, Box<RouteHandler<S>>, Vec<Box<Predicate<S>>>),
}
impl<S: 'static> PipelineHandler<S> for Inner<S> {
@ -40,12 +43,17 @@ impl<S: 'static> PipelineHandler<S> for Inner<S> {
self.encoding
}
fn handle(&mut self, req: HttpRequest<S>, htype: HandlerType) -> Reply {
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) => self.handlers[idx].1.handle(req),
HandlerType::Handler(idx) => match self.handlers[idx] {
PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req),
PrefixHandlerType::Scope(_, ref mut hnd, _) => hnd.handle(req),
},
HandlerType::Default => self.default.handle(req, None),
}
}
@ -63,25 +71,55 @@ impl<S: 'static> HttpApplication<S> {
HandlerType::Normal(idx)
} else {
let inner = self.as_ref();
for idx in 0..inner.handlers.len() {
let &(ref prefix, _) = &inner.handlers[idx];
let m = {
let path = &req.path()[inner.prefix..];
path.starts_with(prefix)
&& (path.len() == prefix.len()
|| path.split_at(prefix.len()).1.starts_with('/'))
};
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 path: &'static str = unsafe {
mem::transmute(&req.path()[inner.prefix + prefix.len()..])
};
if path.is_empty() {
req.match_info_mut().add("tail", "");
} else {
req.match_info_mut().add("tail", path.split_at(1).1);
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 - 1;
let path: &'static str =
unsafe { &*(&req.path()[prefix_len..] as *const _) };
req.set_prefix_len(prefix_len as u16);
if path.is_empty() {
req.match_info_mut().set("tail", "/");
} else {
req.match_info_mut().set("tail", path);
}
return HandlerType::Handler(idx);
}
}
return HandlerType::Handler(idx);
}
}
HandlerType::Default
@ -89,7 +127,7 @@ impl<S: 'static> HttpApplication<S> {
}
#[cfg(test)]
pub(crate) fn run(&mut self, mut req: HttpRequest<S>) -> Reply {
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)
}
@ -130,7 +168,7 @@ struct ApplicationParts<S> {
settings: ServerSettings,
default: ResourceHandler<S>,
resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
handlers: Vec<(String, Box<RouteHandler<S>>)>,
handlers: Vec<PrefixHandlerType<S>>,
external: HashMap<String, Resource>,
encoding: ContentEncoding,
middlewares: Vec<Box<Middleware<S>>>,
@ -177,6 +215,13 @@ where
///
/// State is shared with all resources within same application and
/// could be accessed with `HttpRequest::state()` method.
///
/// **Note**: http server accepts an application factory rather than
/// an application instance. Http server constructs an application
/// instance for each thread, thus application state must be constructed
/// multiple times. If you want to share state between different
/// threads, a shared object should be used, e.g. `Arc`. Application
/// state does not need to be `Send` and `Sync`.
pub fn with_state(state: S) -> App<S> {
App {
parts: Some(ApplicationParts {
@ -193,6 +238,12 @@ where
}
}
/// Get reference to the application state
pub fn state(&self) -> &S {
let parts = self.parts.as_ref().expect("Use after finish");
&parts.state
}
/// Set application prefix.
///
/// Only requests that match the application's prefix get
@ -267,7 +318,7 @@ where
{
{
let parts: &mut ApplicationParts<S> = unsafe {
mem::transmute(self.parts.as_mut().expect("Use after finish"))
&mut *(self.parts.as_mut().expect("Use after finish") as *mut _)
};
// get resource handler
@ -288,6 +339,56 @@ where
self
}
/// Configure scope for common root path.
///
/// Scopes collect multiple paths under a common path prefix.
/// Scope path can contain variable path segments as resources.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .scope("/{project_id}", |scope| {
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
/// });
/// }
/// ```
///
/// In the above example, three routes get added:
/// * /{project_id}/path1
/// * /{project_id}/path2
/// * /{project_id}/path3
///
pub fn scope<F>(mut self, path: &str, f: F) -> App<S>
where
F: FnOnce(Scope<S>) -> Scope<S>,
{
{
let mut scope = Box::new(f(Scope::new()));
let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/')
}
if !path.ends_with('/') {
path.push('/');
}
let parts = self.parts.as_mut().expect("Use after finish");
let filters = scope.take_filters();
parts.handlers.push(PrefixHandlerType::Scope(
Resource::prefix("", &path),
scope,
filters,
));
}
self
}
/// Configure resource for a specific path.
///
/// Resources may have variable path segments. For example, a
@ -314,7 +415,7 @@ where
///
/// fn main() {
/// let app = App::new()
/// .resource("/test", |r| {
/// .resource("/users/{userid}/{friend}", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// });
@ -418,6 +519,8 @@ where
/// `/app/test` would match, but the path `/application` would
/// not.
///
/// Path tail is available as `tail` parameter in request's match_dict.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpRequest, HttpResponse};
@ -438,11 +541,15 @@ where
if !path.is_empty() && !path.starts_with('/') {
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((path, Box::new(WrapHandler::new(handler))));
parts.handlers.push(PrefixHandlerType::Handler(
path,
Box::new(WrapHandler::new(handler)),
));
}
self
}
@ -614,24 +721,18 @@ mod tests {
let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let mut app = App::new()
.default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed()))
.finish();
let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::METHOD_NOT_ALLOWED
);
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
}
#[test]
@ -651,7 +752,7 @@ mod tests {
let req =
HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone());
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
@ -685,29 +786,23 @@ mod tests {
let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/testapp").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
@ -718,29 +813,23 @@ mod tests {
let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/testapp").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
@ -752,29 +841,23 @@ mod tests {
let req = TestRequest::with_uri("/prefix/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/test/").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/test/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/testapp").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/prefix/blah").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
@ -792,25 +875,19 @@ mod tests {
.method(Method::GET)
.finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test")
.method(Method::POST)
.finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::CREATED
);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test")
.method(Method::HEAD)
.finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
@ -822,35 +899,27 @@ mod tests {
let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
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_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/test/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/testapp").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/blah").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
}

View File

@ -257,8 +257,8 @@ impl Responder for Binary {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok()
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(HttpResponse::build_from(req)
.content_type("application/octet-stream")
.body(self))
}

View File

@ -223,8 +223,8 @@ impl Default for ClientConnector {
pool: Rc::new(Pool::new(Rc::clone(&_modified))),
pool_modified: _modified,
connector: builder.build().unwrap(),
conn_lifetime: Duration::from_secs(15),
conn_keep_alive: Duration::from_secs(75),
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100,
limit_per_host: 0,
acquired: 0,
@ -243,8 +243,8 @@ impl Default for ClientConnector {
subscriber: None,
pool: Rc::new(Pool::new(Rc::clone(&_modified))),
pool_modified: _modified,
conn_lifetime: Duration::from_secs(15),
conn_keep_alive: Duration::from_secs(75),
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100,
limit_per_host: 0,
acquired: 0,
@ -347,6 +347,7 @@ impl ClientConnector {
/// Keep-alive period is the period between connection usage. If
/// the delay between repeated usages of the same connection
/// exceeds this period, the connection is closed.
/// Default keep-alive period is 15 seconds.
pub fn conn_keep_alive(mut self, dur: Duration) -> Self {
self.conn_keep_alive = dur;
self
@ -356,6 +357,7 @@ impl ClientConnector {
///
/// Connection lifetime is max lifetime of any opened connection
/// until it is closed regardless of keep-alive period.
/// Default lifetime period is 75 seconds.
pub fn conn_lifetime(mut self, dur: Duration) -> Self {
self.conn_lifetime = dur;
self
@ -570,7 +572,7 @@ impl ClientConnector {
}
fn wait_for(
&mut self, key: Key, wait: Duration, conn_timeout: Duration
&mut self, key: Key, wait: Duration, conn_timeout: Duration,
) -> oneshot::Receiver<Result<Connection, ClientConnectorError>> {
// connection is not available, wait
let (tx, rx) = oneshot::channel();
@ -810,7 +812,7 @@ impl fut::ActorFuture for Maintenance {
type Actor = ClientConnector;
fn poll(
&mut self, act: &mut ClientConnector, ctx: &mut Context<ClientConnector>
&mut self, act: &mut ClientConnector, ctx: &mut Context<ClientConnector>,
) -> Poll<Self::Item, Self::Error> {
// check pause duration
let done = if let Some(Some(ref pause)) = act.paused {
@ -831,7 +833,7 @@ impl fut::ActorFuture for Maintenance {
act.collect_waiters();
// check waiters
let tmp: &mut ClientConnector = unsafe { mem::transmute(act as &mut _) };
let tmp: &mut ClientConnector = unsafe { &mut *(act as *mut _) };
for (key, waiters) in &mut tmp.waiters {
while let Some(waiter) = waiters.pop_front() {

View File

@ -49,6 +49,7 @@ use httpresponse::HttpResponse;
impl ResponseError for SendRequestError {
fn error_response(&self) -> HttpResponse {
match *self {
SendRequestError::Timeout => HttpResponse::GatewayTimeout(),
SendRequestError::Connector(_) => HttpResponse::BadGateway(),
_ => HttpResponse::InternalServerError(),
}.into()

View File

@ -7,18 +7,18 @@ use std::mem;
use error::{ParseError, PayloadError};
use server::h1::{chunked, Decoder};
use server::h1decoder::EncodingDecoder;
use server::{utils, IoStream};
use super::ClientResponse;
use super::response::ClientMessage;
use super::ClientResponse;
const MAX_BUFFER_SIZE: usize = 131_072;
const MAX_HEADERS: usize = 96;
#[derive(Default)]
pub struct HttpResponseParser {
decoder: Option<Decoder>,
decoder: Option<EncodingDecoder>,
}
#[derive(Debug, Fail)]
@ -32,7 +32,7 @@ pub enum HttpResponseParserError {
impl HttpResponseParser {
pub fn parse<T>(
&mut self, io: &mut T, buf: &mut BytesMut
&mut self, io: &mut T, buf: &mut BytesMut,
) -> Poll<ClientResponse, HttpResponseParserError>
where
T: IoStream,
@ -75,7 +75,7 @@ impl HttpResponseParser {
}
pub fn parse_payload<T>(
&mut self, io: &mut T, buf: &mut BytesMut
&mut self, io: &mut T, buf: &mut BytesMut,
) -> Poll<Option<Bytes>, PayloadError>
where
T: IoStream,
@ -113,8 +113,8 @@ impl HttpResponseParser {
}
fn parse_message(
buf: &mut BytesMut
) -> Poll<(ClientResponse, Option<Decoder>), ParseError> {
buf: &mut BytesMut,
) -> 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] =
@ -123,7 +123,7 @@ impl HttpResponseParser {
let (len, version, status, headers_len) = {
let b = unsafe {
let b: &[u8] = buf;
mem::transmute(b)
&*(b as *const _)
};
let mut resp = httparse::Response::new(&mut headers);
match resp.parse(b)? {
@ -160,12 +160,12 @@ impl HttpResponseParser {
}
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
Some(Decoder::eof())
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(Decoder::length(len))
Some(EncodingDecoder::length(len))
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header);
@ -176,7 +176,7 @@ impl HttpResponseParser {
}
} else if chunked(&hdrs)? {
// Chunked encoding
Some(Decoder::chunked())
Some(EncodingDecoder::chunked())
} else {
None
};
@ -204,3 +204,16 @@ impl HttpResponseParser {
}
}
}
/// Check if request has chunked transfer encoding
pub fn chunked(headers: &HeaderMap) -> Result<bool, ParseError> {
if let Some(encodings) = headers.get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() {
Ok(s.to_lowercase().contains("chunked"))
} else {
Err(ParseError::Header)
}
} else {
Ok(false)
}
}

View File

@ -18,9 +18,9 @@ use error::Error;
use error::PayloadError;
use header::ContentEncoding;
use httpmessage::HttpMessage;
use server::WriterState;
use server::encoding::PayloadStream;
use server::shared::SharedBytes;
use server::WriterState;
/// A set of errors that can occur during request sending and response reading
#[derive(Fail, Debug)]
@ -80,7 +80,7 @@ impl SendRequest {
}
pub(crate) fn with_connector(
req: ClientRequest, conn: Addr<Unsync, ClientConnector>
req: ClientRequest, conn: Addr<Unsync, ClientConnector>,
) -> SendRequest {
SendRequest {
req,
@ -194,6 +194,7 @@ impl Future for SendRequest {
self.state = State::Send(pl);
}
State::Send(mut pl) => {
pl.poll_timeout()?;
pl.poll_write().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str())
})?;
@ -305,7 +306,7 @@ impl Pipeline {
return Ok(Async::Ready(None));
}
let conn: &mut Connection =
unsafe { mem::transmute(self.conn.as_mut().unwrap()) };
unsafe { &mut *(self.conn.as_mut().unwrap() as *mut _) };
let mut need_run = false;
@ -315,7 +316,7 @@ impl Pipeline {
{
Async::NotReady => need_run = true,
Async::Ready(_) => {
let _ = self.poll_timeout().map_err(|e| {
self.poll_timeout().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e))
})?;
}
@ -371,16 +372,15 @@ impl Pipeline {
}
}
fn poll_timeout(&mut self) -> Poll<(), SendRequestError> {
fn poll_timeout(&mut self) -> Result<(), SendRequestError> {
if self.timeout.is_some() {
match self.timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(())) => Err(SendRequestError::Timeout),
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
Ok(Async::NotReady) => (),
Err(_) => unreachable!(),
}
} else {
Ok(Async::NotReady)
}
Ok(())
}
#[inline]

View File

@ -682,7 +682,7 @@ impl ClientRequestBuilder {
#[inline]
fn parts<'a>(
parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>
parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>,
) -> Option<&'a mut ClientRequest> {
if err.is_some() {
return None;

View File

@ -7,8 +7,10 @@ use std::io::{self, Write};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliEncoder;
use bytes::{BufMut, BytesMut};
use flate2::Compression;
#[cfg(feature = "flate2")]
use flate2::write::{DeflateEncoder, GzEncoder};
#[cfg(feature = "flate2")]
use flate2::Compression;
use futures::{Async, Poll};
use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE,
TRANSFER_ENCODING};
@ -18,9 +20,9 @@ use tokio_io::AsyncWrite;
use body::{Binary, Body};
use header::ContentEncoding;
use server::WriterState;
use server::encoding::{ContentEncoder, TransferEncoding};
use server::shared::SharedBytes;
use server::WriterState;
use client::ClientRequest;
@ -70,7 +72,7 @@ impl HttpClientWriter {
// !self.flags.contains(Flags::UPGRADE) }
fn write_to_stream<T: AsyncWrite>(
&mut self, stream: &mut T
&mut self, stream: &mut T,
) -> io::Result<WriterState> {
while !self.buffer.is_empty() {
match stream.write(self.buffer.as_ref()) {
@ -191,7 +193,7 @@ impl HttpClientWriter {
#[inline]
pub fn poll_completed<T: AsyncWrite>(
&mut self, stream: &mut T, shutdown: bool
&mut self, stream: &mut T, shutdown: bool,
) -> Poll<(), io::Error> {
match self.write_to_stream(stream) {
Ok(WriterState::Done) => {
@ -222,9 +224,11 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
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(),
@ -283,10 +287,12 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
req.replace_body(body);
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()))
}
@ -299,7 +305,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
}
fn streaming_encoding(
buf: SharedBytes, version: Version, req: &mut ClientRequest
buf: SharedBytes, version: Version, req: &mut ClientRequest,
) -> TransferEncoding {
if req.chunked() {
// Enable transfer encoding

View File

@ -3,7 +3,6 @@ use futures::unsync::oneshot;
use futures::{Async, Future, Poll};
use smallvec::SmallVec;
use std::marker::PhantomData;
use std::mem;
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
use actix::fut::ActorFuture;
@ -174,7 +173,9 @@ where
if self.stream.is_none() {
self.stream = Some(SmallVec::new());
}
self.stream.as_mut().map(|s| s.push(frame));
if let Some(s) = self.stream.as_mut() {
s.push(frame)
}
self.inner.modify();
}
@ -199,7 +200,7 @@ where
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
let ctx: &mut HttpContext<A, S> =
unsafe { mem::transmute(self as &mut HttpContext<A, S>) };
unsafe { &mut *(self as &mut HttpContext<A, S> as *mut _) };
if self.inner.alive() {
match self.inner.poll(ctx) {
@ -261,7 +262,7 @@ impl<A: Actor> ActorFuture for Drain<A> {
#[inline]
fn poll(
&mut self, _: &mut A, _: &mut <Self::Actor as Actor>::Context
&mut self, _: &mut A, _: &mut <Self::Actor as Actor>::Context,
) -> Poll<Self::Item, Self::Error> {
self.fut.poll().map_err(|_| ())
}

View File

@ -59,7 +59,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
}
fn deserialize_struct<V>(
self, _: &'static str, _: &'static [&'static str], visitor: V
self, _: &'static str, _: &'static [&'static str], visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -75,7 +75,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
}
fn deserialize_unit_struct<V>(
self, _: &'static str, visitor: V
self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -84,7 +84,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
}
fn deserialize_newtype_struct<V>(
self, _: &'static str, visitor: V
self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -93,7 +93,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
}
fn deserialize_tuple<V>(
self, len: usize, visitor: V
self, len: usize, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -114,7 +114,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
}
fn deserialize_tuple_struct<V>(
self, _: &'static str, len: usize, visitor: V
self, _: &'static str, len: usize, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -135,7 +135,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
}
fn deserialize_enum<V>(
self, _: &'static str, _: &'static [&'static str], _: V
self, _: &'static str, _: &'static [&'static str], _: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -301,7 +301,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
}
fn deserialize_unit_struct<V>(
self, _: &'static str, visitor: V
self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -331,7 +331,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
}
fn deserialize_enum<V>(
self, _: &'static str, _: &'static [&'static str], visitor: V
self, _: &'static str, _: &'static [&'static str], visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -342,7 +342,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
}
fn deserialize_newtype_struct<V>(
self, _: &'static str, visitor: V
self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -358,7 +358,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
}
fn deserialize_struct<V>(
self, _: &'static str, _: &'static [&'static str], _: V
self, _: &'static str, _: &'static [&'static str], _: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -367,7 +367,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
}
fn deserialize_tuple_struct<V>(
self, _: &'static str, _: usize, _: V
self, _: &'static str, _: usize, _: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
@ -446,7 +446,7 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
}
fn struct_variant<V>(
self, _: &'static [&'static str], _: V
self, _: &'static [&'static str], _: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,

View File

@ -45,6 +45,16 @@ impl Error {
pub fn cause(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Returns a reference to the Backtrace carried by this error, if it
/// carries one.
pub fn backtrace(&self) -> &Backtrace {
if let Some(bt) = self.cause.backtrace() {
bt
} else {
self.backtrace.as_ref().unwrap()
}
}
}
/// Error that can be converted to `HttpResponse`
@ -298,7 +308,9 @@ pub enum HttpRangeError {
/// 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")]
#[fail(
display = "First-byte-pos of all of the byte-range-spec values is greater than the content size"
)]
NoOverlap,
}
@ -530,7 +542,7 @@ impl From<UrlParseError> for UrlGenerationError {
/// Helper type that can wrap any error and generate custom response.
///
/// In following example any `io::Error` will be converted into "BAD REQUEST"
/// response as opposite to *INNTERNAL SERVER ERROR* which is defined by
/// response as opposite to *INTERNAL SERVER ERROR* which is defined by
/// default.
///
/// ```rust
@ -580,7 +592,7 @@ impl<T> InternalError<T> {
impl<T> Fail for InternalError<T>
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
fn backtrace(&self) -> Option<&Backtrace> {
Some(&self.backtrace)
@ -598,16 +610,16 @@ where
impl<T> fmt::Display for InternalError<T>
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Display + 'static,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.cause, f)
fmt::Display::fmt(&self.cause, f)
}
}
impl<T> ResponseError for InternalError<T>
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
fn error_response(&self) -> HttpResponse {
match self.status {
@ -625,12 +637,12 @@ where
impl<T> Responder for InternalError<T>
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
fn respond_to<S>(self, _: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Err(self.into())
}
}
@ -640,7 +652,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorBadRequest<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::BAD_REQUEST).into()
}
@ -650,7 +662,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorUnauthorized<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
}
@ -660,7 +672,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorForbidden<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::FORBIDDEN).into()
}
@ -670,7 +682,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorNotFound<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_FOUND).into()
}
@ -680,7 +692,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorMethodNotAllowed<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
}
@ -690,7 +702,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorRequestTimeout<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into()
}
@ -700,7 +712,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorConflict<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::CONFLICT).into()
}
@ -710,7 +722,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorGone<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::GONE).into()
}
@ -720,7 +732,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorPreconditionFailed<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
}
@ -730,7 +742,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorExpectationFailed<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
}
@ -740,11 +752,51 @@ where
#[allow(non_snake_case)]
pub fn ErrorInternalServerError<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
}
/// Helper function that creates wrapper of any error and
/// generate *NOT IMPLEMENTED* response.
#[allow(non_snake_case)]
pub fn ErrorNotImplemented<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *BAD GATEWAY* response.
#[allow(non_snake_case)]
pub fn ErrorBadGateway<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::BAD_GATEWAY).into()
}
/// Helper function that creates wrapper of any error and
/// generate *SERVICE UNAVAILABLE* response.
#[allow(non_snake_case)]
pub fn ErrorServiceUnavailable<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into()
}
/// Helper function that creates wrapper of any error and
/// generate *GATEWAY TIMEOUT* response.
#[allow(non_snake_case)]
pub fn ErrorGatewayTimeout<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
}
#[cfg(test)]
mod tests {
use super::*;
@ -791,6 +843,12 @@ mod tests {
assert_eq!(format!("{}", e.cause().unwrap()), desc);
}
#[test]
fn test_backtrace() {
let e = ErrorBadRequest("err");
let _ = e.backtrace();
}
#[test]
fn test_error_cause() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
@ -888,8 +946,58 @@ mod tests {
#[test]
fn test_internal_error() {
let err = InternalError::from_response(
ExpectError::Encoding, HttpResponse::Ok().into());
ExpectError::Encoding,
HttpResponse::Ok().into(),
);
let resp: HttpResponse = err.error_response();
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_error_helpers() {
let r: HttpResponse = ErrorBadRequest("err").into();
assert_eq!(r.status(), StatusCode::BAD_REQUEST);
let r: HttpResponse = ErrorUnauthorized("err").into();
assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
let r: HttpResponse = ErrorForbidden("err").into();
assert_eq!(r.status(), StatusCode::FORBIDDEN);
let r: HttpResponse = ErrorNotFound("err").into();
assert_eq!(r.status(), StatusCode::NOT_FOUND);
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
let r: HttpResponse = ErrorRequestTimeout("err").into();
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
let r: HttpResponse = ErrorConflict("err").into();
assert_eq!(r.status(), StatusCode::CONFLICT);
let r: HttpResponse = ErrorGone("err").into();
assert_eq!(r.status(), StatusCode::GONE);
let r: HttpResponse = ErrorPreconditionFailed("err").into();
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
let r: HttpResponse = ErrorExpectationFailed("err").into();
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
let r: HttpResponse = ErrorInternalServerError("err").into();
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
let r: HttpResponse = ErrorNotImplemented("err").into();
assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED);
let r: HttpResponse = ErrorBadGateway("err").into();
assert_eq!(r.status(), StatusCode::BAD_GATEWAY);
let r: HttpResponse = ErrorServiceUnavailable("err").into();
assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE);
let r: HttpResponse = ErrorGatewayTimeout("err").into();
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
}
}

View File

@ -1,17 +1,18 @@
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::str;
use bytes::Bytes;
use encoding::all::UTF_8;
use encoding::types::{DecoderTrap, Encoding};
use futures::future::{result, Future, FutureResult};
use futures::{Async, Future, Poll};
use mime::Mime;
use serde::de::{self, DeserializeOwned};
use serde_urlencoded;
use de::PathDeserializer;
use error::{Error, ErrorBadRequest};
use handler::{Either, FromRequest};
use error::{Error, ErrorNotFound, ErrorBadRequest};
use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest;
@ -25,7 +26,7 @@ use httprequest::HttpRequest;
/// # extern crate futures;
/// use actix_web::{App, Path, Result, http};
///
/// /// extract path info from "/{username}/{count}/?index.html" url
/// /// extract path info from "/{username}/{count}/index.html" url
/// /// {username} - deserializes to a String
/// /// {count} - - deserializes to a u32
/// fn index(info: Path<(String, u32)>) -> Result<String> {
@ -34,7 +35,7 @@ use httprequest::HttpRequest;
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/{count}/?index.html", // <- define path parameters
/// "/{username}/{count}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// }
/// ```
@ -99,19 +100,16 @@ impl<T> Path<T> {
impl<T, S> FromRequest<S> for Path<T>
where
T: DeserializeOwned,
S: 'static,
{
type Config = ();
type Result = FutureResult<Self, Error>;
type Result = Result<Self, Error>;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone();
result(
de::Deserialize::deserialize(PathDeserializer::new(&req))
.map_err(|e| e.into())
.map(|inner| Path { inner }),
)
de::Deserialize::deserialize(PathDeserializer::new(&req))
.map_err(ErrorNotFound)
.map(|inner| Path { inner })
}
}
@ -169,19 +167,16 @@ impl<T> Query<T> {
impl<T, S> FromRequest<S> for Query<T>
where
T: de::DeserializeOwned,
S: 'static,
{
type Config = ();
type Result = FutureResult<Self, Error>;
type Result = Result<Self, Error>;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone();
result(
serde_urlencoded::from_str::<T>(req.query_string())
.map_err(|e| e.into())
.map(Query),
)
serde_urlencoded::from_str::<T>(req.query_string())
.map_err(|e| e.into())
.map(Query)
}
}
@ -195,9 +190,6 @@ where
///
/// ## Example
///
/// It is possible to extract path information to a specific type that
/// implements `Deserialize` trait from *serde*.
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
@ -330,17 +322,14 @@ impl Default for FormConfig {
/// ```
impl<S: 'static> FromRequest<S> for Bytes {
type Config = PayloadConfig;
type Result =
Either<FutureResult<Self, Error>, Box<Future<Item = Self, Error = Error>>>;
type Result = Result<Box<Future<Item = Self, Error = Error>>, Error>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
// check content-type
if let Err(e) = cfg.check_mimetype(req) {
return Either::A(result(Err(e)));
}
cfg.check_mimetype(req)?;
Either::B(Box::new(
Ok(Box::new(
MessageBody::new(req.clone())
.limit(cfg.limit)
.from_err(),
@ -377,27 +366,17 @@ impl<S: 'static> FromRequest<S> for Bytes {
/// ```
impl<S: 'static> FromRequest<S> for String {
type Config = PayloadConfig;
type Result =
Either<FutureResult<String, Error>, Box<Future<Item = String, Error = Error>>>;
type Result = Result<Box<Future<Item = String, Error = Error>>, Error>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
// check content-type
if let Err(e) = cfg.check_mimetype(req) {
return Either::A(result(Err(e)));
}
cfg.check_mimetype(req)?;
// check charset
let encoding = match req.encoding() {
Err(_) => {
return Either::A(result(Err(ErrorBadRequest(
"Unknown request charset",
))))
}
Ok(encoding) => encoding,
};
let encoding = req.encoding()?;
Either::B(Box::new(
Ok(Box::new(
MessageBody::new(req.clone())
.limit(cfg.limit)
.from_err()
@ -467,6 +446,123 @@ impl Default for PayloadConfig {
}
}
macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
/// FromRequest implementation for tuple
impl<S, $($T: FromRequest<S> + 'static),+> FromRequest<S> for ($($T,)+)
where
S: 'static,
{
type Config = ($($T::Config,)+);
type Result = Box<Future<Item = ($($T,)+), Error = Error>>;
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new($fut_type {
s: PhantomData,
items: <($(Option<$T>,)+)>::default(),
futs: ($(Some($T::from_request(req, &cfg.$n).into()),)+),
})
}
}
struct $fut_type<S, $($T: FromRequest<S>),+>
where
S: 'static,
{
s: PhantomData<S>,
items: ($(Option<$T>,)+),
futs: ($(Option<AsyncResult<$T>>,)+),
}
impl<S, $($T: FromRequest<S>),+> Future for $fut_type<S, $($T),+>
where
S: 'static,
{
type Item = ($($T,)+);
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut ready = true;
$(
if self.futs.$n.is_some() {
match self.futs.$n.as_mut().unwrap().poll() {
Ok(Async::Ready(item)) => {
self.items.$n = Some(item);
self.futs.$n.take();
}
Ok(Async::NotReady) => ready = false,
Err(e) => return Err(e),
}
}
)+
if ready {
Ok(Async::Ready(
($(self.items.$n.take().unwrap(),)+)
))
} else {
Ok(Async::NotReady)
}
}
}
});
tuple_from_req!(TupleFromRequest1, (0, A));
tuple_from_req!(TupleFromRequest2, (0, A), (1, B));
tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C));
tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D));
tuple_from_req!(
TupleFromRequest5,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E)
);
tuple_from_req!(
TupleFromRequest6,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F)
);
tuple_from_req!(
TupleFromRequest7,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G)
);
tuple_from_req!(
TupleFromRequest8,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G),
(7, H)
);
tuple_from_req!(
TupleFromRequest9,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G),
(7, H),
(8, I)
);
#[cfg(test)]
mod tests {
use super::*;
@ -491,7 +587,11 @@ mod tests {
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
match Bytes::from_request(&req, &cfg).poll().unwrap() {
match Bytes::from_request(&req, &cfg)
.unwrap()
.poll()
.unwrap()
{
Async::Ready(s) => {
assert_eq!(s, Bytes::from_static(b"hello=world"));
}
@ -506,7 +606,11 @@ mod tests {
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
match String::from_request(&req, &cfg).poll().unwrap() {
match String::from_request(&req, &cfg)
.unwrap()
.poll()
.unwrap()
{
Async::Ready(s) => {
assert_eq!(s, "hello=world");
}
@ -583,73 +687,35 @@ mod tests {
let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some());
match Path::<MyStruct>::from_request(&req, &())
.poll()
.unwrap()
{
Async::Ready(s) => {
assert_eq!(s.key, "name");
assert_eq!(s.value, "user1");
}
_ => unreachable!(),
}
let s = Path::<MyStruct>::from_request(&req, &()).unwrap();
assert_eq!(s.key, "name");
assert_eq!(s.value, "user1");
match Path::<(String, String)>::from_request(&req, &())
.poll()
.unwrap()
{
Async::Ready(s) => {
assert_eq!(s.0, "name");
assert_eq!(s.1, "user1");
}
_ => unreachable!(),
}
let s = Path::<(String, String)>::from_request(&req, &()).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, "user1");
match Query::<Id>::from_request(&req, &()).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.id, "test");
}
_ => unreachable!(),
}
let s = Query::<Id>::from_request(&req, &()).unwrap();
assert_eq!(s.id, "test");
let mut req = TestRequest::with_uri("/name/32/").finish();
assert!(router.recognize(&mut req).is_some());
match Path::<Test2>::from_request(&req, &()).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32);
}
_ => unreachable!(),
}
let s = Path::<Test2>::from_request(&req, &()).unwrap();
assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32);
match Path::<(String, u8)>::from_request(&req, &())
.poll()
.unwrap()
{
Async::Ready(s) => {
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
}
_ => unreachable!(),
}
let s = Path::<(String, u8)>::from_request(&req, &()).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
match Path::<Vec<String>>::from_request(&req, &())
.poll()
.unwrap()
{
Async::Ready(s) => {
assert_eq!(
s.into_inner(),
vec!["name".to_owned(), "32".to_owned()]
);
}
_ => unreachable!(),
}
let res = Path::<Vec<String>>::extract(&req).unwrap();
assert_eq!(res[0], "name".to_owned());
assert_eq!(res[1], "32".to_owned());
}
#[test]
fn test_extract_path_signle() {
fn test_extract_path_single() {
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut routes = Vec::new();
@ -659,11 +725,39 @@ mod tests {
let mut req = TestRequest::with_uri("/32/").finish();
assert!(router.recognize(&mut req).is_some());
match Path::<i8>::from_request(&req, &()).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.into_inner(), 32);
}
_ => unreachable!(),
}
assert_eq!(*Path::<i8>::from_request(&mut req, &()).unwrap(), 32);
}
#[test]
fn test_tuple_extract() {
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
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,
_ => panic!("error"),
};
assert_eq!((res.0).0, "name");
assert_eq!((res.0).1, "user1");
let res = match <(Path<(String, String)>, Path<(String, String)>)>::extract(&req)
.poll()
{
Ok(Async::Ready(res)) => res,
_ => panic!("error"),
};
assert_eq!((res.0).0, "name");
assert_eq!((res.0).1, "user1");
assert_eq!((res.1).0, "name");
assert_eq!((res.1).1, "user1");
}
}

287
src/fs.rs
View File

@ -14,11 +14,11 @@ use std::os::unix::fs::MetadataExt;
use bytes::{BufMut, Bytes, BytesMut};
use futures::{Async, Future, Poll, Stream};
use futures_cpupool::{CpuFuture, CpuPool};
use mime_guess::get_mime_type;
use percent_encoding::percent_decode;
use mime;
use mime_guess::{get_mime_type, guess_mime_type};
use error::Error;
use handler::{Handler, Reply, Responder, RouteHandler, WrapHandler};
use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler};
use header;
use http::{Method, StatusCode};
use httpmessage::HttpMessage;
@ -161,7 +161,7 @@ impl DerefMut for NamedFile {
}
/// Returns true if `req` has no `If-Match` header or one which matches `etag`.
fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
fn any_match<S>(etag: Option<&header::EntityTag>, req: &HttpRequest<S>) -> bool {
match req.get_header::<header::IfMatch>() {
None | Some(header::IfMatch::Any) => true,
Some(header::IfMatch::Items(ref items)) => {
@ -178,7 +178,7 @@ fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
}
/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`.
fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
fn none_match<S>(etag: Option<&header::EntityTag>, req: &HttpRequest<S>) -> bool {
match req.get_header::<header::IfNoneMatch>() {
Some(header::IfNoneMatch::Any) => false,
Some(header::IfNoneMatch::Items(ref items)) => {
@ -199,14 +199,28 @@ impl Responder for NamedFile {
type Item = HttpResponse;
type Error = io::Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, io::Error> {
if self.status_code != StatusCode::OK {
let mut resp = HttpResponse::build(self.status_code);
resp.if_some(self.path().extension(), |ext, resp| {
resp.set(header::ContentType(get_mime_type(
&ext.to_string_lossy(),
)));
});
}).if_some(self.path().file_name(), |file_name, resp| {
let mime_type = guess_mime_type(self.path());
let inline_or_attachment = match mime_type.type_() {
mime::IMAGE | mime::TEXT => "inline",
_ => "attachment",
};
resp.header(
"Content-Disposition",
format!(
"{inline_or_attachment}; filename={filename}",
inline_or_attachment = inline_or_attachment,
filename = file_name.to_string_lossy()
),
);
});
let reader = ChunkedReadFile {
size: self.md.len(),
offset: 0,
@ -230,7 +244,7 @@ impl Responder for NamedFile {
let last_modified = self.last_modified();
// check preconditions
let precondition_failed = if !any_match(etag.as_ref(), &req) {
let precondition_failed = if !any_match(etag.as_ref(), req) {
true
} else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) =
(last_modified, req.get_header())
@ -241,7 +255,7 @@ impl Responder for NamedFile {
};
// check last modified
let not_modified = if !none_match(etag.as_ref(), &req) {
let not_modified = if !none_match(etag.as_ref(), req) {
true
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
(last_modified, req.get_header())
@ -257,7 +271,22 @@ impl Responder for NamedFile {
resp.set(header::ContentType(get_mime_type(
&ext.to_string_lossy(),
)));
}).if_some(last_modified, |lm, resp| {
}).if_some(self.path().file_name(), |file_name, resp| {
let mime_type = guess_mime_type(self.path());
let inline_or_attachment = match mime_type.type_() {
mime::IMAGE | mime::TEXT => "inline",
_ => "attachment",
};
resp.header(
"Content-Disposition",
format!(
"{inline_or_attachment}; filename={filename}",
inline_or_attachment = inline_or_attachment,
filename = file_name.to_string_lossy()
),
);
})
.if_some(last_modified, |lm, resp| {
resp.set(header::LastModified(lm));
})
.if_some(etag, |etag, resp| {
@ -336,11 +365,14 @@ impl Stream for ChunkedReadFile {
}
}
type DirectoryRenderer<S> =
Fn(&Directory, &HttpRequest<S>) -> Result<HttpResponse, io::Error>;
/// A directory; responds with the generated directory listing.
#[derive(Debug)]
pub struct Directory {
base: PathBuf,
path: PathBuf,
pub base: PathBuf,
pub path: PathBuf,
}
impl Directory {
@ -348,7 +380,7 @@ impl Directory {
Directory { base, path }
}
fn can_list(&self, entry: &io::Result<DirEntry>) -> bool {
pub fn is_visible(&self, entry: &io::Result<DirEntry>) -> bool {
if let Ok(ref entry) = *entry {
if let Some(name) = entry.file_name().to_str() {
if name.starts_with('.') {
@ -364,61 +396,58 @@ impl Directory {
}
}
impl Responder for Directory {
type Item = HttpResponse;
type Error = io::Error;
fn directory_listing<S>(
dir: &Directory, req: &HttpRequest<S>,
) -> Result<HttpResponse, io::Error> {
let index_of = format!("Index of {}", req.path());
let mut body = String::new();
let base = Path::new(req.path());
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
let index_of = format!("Index of {}", req.path());
let mut body = String::new();
let base = Path::new(req.path());
for entry in dir.path.read_dir()? {
if dir.is_visible(&entry) {
let entry = entry.unwrap();
let p = match entry.path().strip_prefix(&dir.path) {
Ok(p) => base.join(p),
Err(_) => continue,
};
// show file url as relative to static path
let file_url = format!("{}", p.to_string_lossy());
for entry in self.path.read_dir()? {
if self.can_list(&entry) {
let entry = entry.unwrap();
let p = match entry.path().strip_prefix(&self.path) {
Ok(p) => base.join(p),
Err(_) => continue,
};
// show file url as relative to static path
let file_url = format!("{}", p.to_string_lossy());
// if file is a directory, add '/' to the end of the name
if let Ok(metadata) = entry.metadata() {
if metadata.is_dir() {
let _ = write!(
body,
"<li><a href=\"{}\">{}/</a></li>",
file_url,
entry.file_name().to_string_lossy()
);
} else {
let _ = write!(
body,
"<li><a href=\"{}\">{}</a></li>",
file_url,
entry.file_name().to_string_lossy()
);
}
// if file is a directory, add '/' to the end of the name
if let Ok(metadata) = entry.metadata() {
if metadata.is_dir() {
let _ = write!(
body,
"<li><a href=\"{}\">{}/</a></li>",
file_url,
entry.file_name().to_string_lossy()
);
} else {
continue;
let _ = write!(
body,
"<li><a href=\"{}\">{}</a></li>",
file_url,
entry.file_name().to_string_lossy()
);
}
} else {
continue;
}
}
let html = format!(
"<html>\
<head><title>{}</title></head>\
<body><h1>{}</h1>\
<ul>\
{}\
</ul></body>\n</html>",
index_of, index_of, body
);
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(html))
}
let html = format!(
"<html>\
<head><title>{}</title></head>\
<body><h1>{}</h1>\
<ul>\
{}\
</ul></body>\n</html>",
index_of, index_of, body
);
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(html))
}
/// Static files handling
@ -443,6 +472,7 @@ pub struct StaticFiles<S> {
show_index: bool,
cpu_pool: CpuPool,
default: Box<RouteHandler<S>>,
renderer: Box<DirectoryRenderer<S>>,
_chunk_size: usize,
_follow_symlinks: bool,
}
@ -457,7 +487,7 @@ lazy_static! {
error!("Can not parse ACTIX_FS_POOL value");
20
}
},
}
Err(_) => 20,
};
Mutex::new(CpuPool::new(default))
@ -477,7 +507,8 @@ impl<S: 'static> StaticFiles<S> {
StaticFiles::with_pool(dir, pool)
}
/// Create new `StaticFiles` instance for specified base directory and `CpuPool`.
/// Create new `StaticFiles` instance for specified base directory and
/// `CpuPool`.
pub fn with_pool<T: Into<PathBuf>>(dir: T, pool: CpuPool) -> StaticFiles<S> {
let dir = dir.into();
@ -505,6 +536,7 @@ impl<S: 'static> StaticFiles<S> {
default: Box::new(WrapHandler::new(|_| {
HttpResponse::new(StatusCode::NOT_FOUND)
})),
renderer: Box::new(directory_listing),
_chunk_size: 0,
_follow_symlinks: false,
}
@ -518,6 +550,17 @@ impl<S: 'static> StaticFiles<S> {
self
}
/// Set custom directory renderer
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
where
for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest<S>)
-> Result<HttpResponse, io::Error>
+ 'static,
{
self.renderer = Box::new(f);
self
}
/// Set index file
///
/// Redirects to specific index file for directory "/" instead of
@ -535,7 +578,7 @@ impl<S: 'static> StaticFiles<S> {
}
impl<S: 'static> Handler<S> for StaticFiles<S> {
type Result = Result<Reply, Error>;
type Result = Result<AsyncResult<HttpResponse>, Error>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
if !self.accessible {
@ -543,8 +586,7 @@ impl<S: 'static> Handler<S> for StaticFiles<S> {
} else {
let relpath = match req.match_info()
.get("tail")
.map(|tail| percent_decode(tail.as_bytes()).decode_utf8().unwrap())
.map(|tail| PathBuf::from_param(tail.as_ref()))
.map(|tail| PathBuf::from_param(tail.trim_left_matches('/')))
{
Some(Ok(path)) => path,
_ => return Ok(self.default.handle(req)),
@ -570,19 +612,18 @@ impl<S: 'static> Handler<S> for StaticFiles<S> {
HttpResponse::Found()
.header(header::LOCATION, new_path.as_str())
.finish()
.respond_to(req.drop_state())
.respond_to(&req)
} else if self.show_index {
Directory::new(self.directory.clone(), path)
.respond_to(req.drop_state())?
.respond_to(req.drop_state())
let dir = Directory::new(self.directory.clone(), path);
Ok((*self.renderer)(&dir, &req)?.into())
} else {
Ok(self.default.handle(req))
}
} else {
NamedFile::open(path)?
.set_cpu_pool(self.cpu_pool.clone())
.respond_to(req.drop_state())?
.respond_to(req.drop_state())
.respond_to(&req)?
.respond_to(&req)
}
}
}
@ -596,7 +637,7 @@ mod tests {
use test::{self, TestRequest};
#[test]
fn test_named_file() {
fn test_named_file_text() {
assert!(NamedFile::open("test--").is_err());
let mut file = NamedFile::open("Cargo.toml")
.unwrap()
@ -609,15 +650,73 @@ mod tests {
let _f: &mut File = &mut file;
}
let resp = file.respond_to(HttpRequest::default()).unwrap();
let resp = file.respond_to(&HttpRequest::default()).unwrap();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-toml"
)
);
assert_eq!(
resp.headers()
.get(header::CONTENT_DISPOSITION)
.unwrap(),
"inline; filename=Cargo.toml"
);
}
#[test]
fn test_named_file_status_code() {
fn test_named_file_image() {
let mut file = NamedFile::open("tests/test.png")
.unwrap()
.set_cpu_pool(CpuPool::new(1));
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let resp = file.respond_to(&HttpRequest::default()).unwrap();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"image/png"
);
assert_eq!(
resp.headers()
.get(header::CONTENT_DISPOSITION)
.unwrap(),
"inline; filename=test.png"
);
}
#[test]
fn test_named_file_binary() {
let mut file = NamedFile::open("tests/test.binary")
.unwrap()
.set_cpu_pool(CpuPool::new(1));
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let resp = file.respond_to(&HttpRequest::default()).unwrap();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"application/octet-stream"
);
assert_eq!(
resp.headers()
.get(header::CONTENT_DISPOSITION)
.unwrap(),
"attachment; filename=test.binary"
);
}
#[test]
fn test_named_file_status_code_text() {
let mut file = NamedFile::open("Cargo.toml")
.unwrap()
.set_status_code(StatusCode::NOT_FOUND)
@ -630,11 +729,17 @@ mod tests {
let _f: &mut File = &mut file;
}
let resp = file.respond_to(HttpRequest::default()).unwrap();
let resp = file.respond_to(&HttpRequest::default()).unwrap();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-toml"
);
assert_eq!(
resp.headers()
.get(header::CONTENT_DISPOSITION)
.unwrap(),
"inline; filename=Cargo.toml"
);
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
@ -643,7 +748,7 @@ mod tests {
let req = TestRequest::default().method(Method::POST).finish();
let file = NamedFile::open("Cargo.toml").unwrap();
let resp = file.only_get().respond_to(req).unwrap();
let resp = file.only_get().respond_to(&req).unwrap();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
}
@ -651,7 +756,7 @@ mod tests {
fn test_named_file_any_method() {
let req = TestRequest::default().method(Method::POST).finish();
let file = NamedFile::open("Cargo.toml").unwrap();
let resp = file.respond_to(req).unwrap();
let resp = file.respond_to(&req).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
@ -660,17 +765,17 @@ mod tests {
let mut st = StaticFiles::new(".").show_files_listing();
st.accessible = false;
let resp = st.handle(HttpRequest::default())
.respond_to(HttpRequest::default())
.respond_to(&HttpRequest::default())
.unwrap();
let resp = resp.as_response().expect("HTTP Response");
let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
st.accessible = true;
st.show_index = false;
let resp = st.handle(HttpRequest::default())
.respond_to(HttpRequest::default())
.respond_to(&HttpRequest::default())
.unwrap();
let resp = resp.as_response().expect("HTTP Response");
let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut req = HttpRequest::default();
@ -678,9 +783,9 @@ mod tests {
st.show_index = true;
let resp = st.handle(req)
.respond_to(HttpRequest::default())
.respond_to(&HttpRequest::default())
.unwrap();
let resp = resp.as_response().expect("HTTP Response");
let resp = resp.as_msg();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/html; charset=utf-8"
@ -696,9 +801,9 @@ mod tests {
req.match_info_mut().add("tail", "tests");
let resp = st.handle(req)
.respond_to(HttpRequest::default())
.respond_to(&HttpRequest::default())
.unwrap();
let resp = resp.as_response().expect("HTTP Response");
let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(
resp.headers().get(header::LOCATION).unwrap(),
@ -709,9 +814,9 @@ mod tests {
req.match_info_mut().add("tail", "tests/");
let resp = st.handle(req)
.respond_to(HttpRequest::default())
.respond_to(&HttpRequest::default())
.unwrap();
let resp = resp.as_response().expect("HTTP Response");
let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(
resp.headers().get(header::LOCATION).unwrap(),
@ -726,9 +831,9 @@ mod tests {
req.match_info_mut().add("tail", "tools/wsload");
let resp = st.handle(req)
.respond_to(HttpRequest::default())
.respond_to(&HttpRequest::default())
.unwrap();
let resp = resp.as_response().expect("HTTP Response");
let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(
resp.headers().get(header::LOCATION).unwrap(),

View File

@ -1,8 +1,9 @@
use futures::Poll;
use futures::future::{err, ok, Future, FutureResult};
use std::marker::PhantomData;
use std::ops::Deref;
use futures::future::{err, ok, Future};
use futures::{Async, Poll};
use error::Error;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
@ -22,30 +23,36 @@ pub trait Handler<S>: 'static {
/// Types that implement this trait can be used as the return type of a handler.
pub trait Responder {
/// The associated item which can be returned.
type Item: Into<Reply>;
type Item: Into<AsyncResult<HttpResponse>>;
/// The associated error which can be returned.
type Error: Into<Error>;
/// Convert itself to `Reply` or `Error`.
fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error>;
/// Convert itself to `AsyncResult` or `Error`.
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<Self::Item, Self::Error>;
}
/// Trait implemented by types that can be extracted from request.
///
/// Types that implement this trait can be used with `Route::with()` method.
pub trait FromRequest<S>: Sized
where
S: 'static,
{
pub trait FromRequest<S>: Sized {
/// Configuration for conversion process
type Config: Default;
/// Future that resolves to a Self
type Result: Future<Item = Self, Error = Error>;
type Result: Into<AsyncResult<Self>>;
/// Convert request to a Self
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result;
/// Convert request to a Self
///
/// This method uses default extractor configuration
fn extract(req: &HttpRequest<S>) -> Self::Result {
Self::from_request(req, &Self::Config::default())
}
}
/// Combines two different responder types into a single type
@ -88,10 +95,12 @@ where
A: Responder,
B: Responder,
{
type Item = Reply;
type Item = AsyncResult<HttpResponse>;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<Reply, Error> {
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
match self {
Either::A(a) => match a.respond_to(req) {
Ok(val) => Ok(val.into()),
@ -177,67 +186,109 @@ where
}
}
/// Represents response process.
pub struct Reply(ReplyItem);
/// Represents async result
///
/// Result could be in tree different forms.
/// * Ok(T) - ready item
/// * Err(E) - error happen during reply process
/// * Future<T, E> - reply process completes in the future
pub struct AsyncResult<I, E = Error>(Option<AsyncResultItem<I, E>>);
pub(crate) enum ReplyItem {
Message(HttpResponse),
Future(Box<Future<Item = HttpResponse, Error = Error>>),
impl<I, E> Future for AsyncResult<I, E> {
type Item = I;
type Error = E;
fn poll(&mut self) -> Poll<I, E> {
let res = self.0.take().expect("use after resolve");
match res {
AsyncResultItem::Ok(msg) => Ok(Async::Ready(msg)),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(mut fut) => match fut.poll() {
Ok(Async::NotReady) => {
self.0 = Some(AsyncResultItem::Future(fut));
Ok(Async::NotReady)
}
Ok(Async::Ready(msg)) => Ok(Async::Ready(msg)),
Err(err) => Err(err),
},
}
}
}
impl Reply {
pub(crate) enum AsyncResultItem<I, E> {
Ok(I),
Err(E),
Future(Box<Future<Item = I, Error = E>>),
}
impl<I, E> AsyncResult<I, E> {
/// Create async response
#[inline]
pub fn async<F>(fut: F) -> Reply
where
F: Future<Item = HttpResponse, Error = Error> + 'static,
{
Reply(ReplyItem::Future(Box::new(fut)))
pub fn async(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
AsyncResult(Some(AsyncResultItem::Future(fut)))
}
/// Send response
#[inline]
pub fn response<R: Into<HttpResponse>>(response: R) -> Reply {
Reply(ReplyItem::Message(response.into()))
pub fn ok<R: Into<I>>(ok: R) -> AsyncResult<I, E> {
AsyncResult(Some(AsyncResultItem::Ok(ok.into())))
}
/// Send error
#[inline]
pub fn err<R: Into<E>>(err: R) -> AsyncResult<I, E> {
AsyncResult(Some(AsyncResultItem::Err(err.into())))
}
#[inline]
pub(crate) fn into(self) -> ReplyItem {
self.0
pub(crate) fn into(self) -> AsyncResultItem<I, E> {
self.0.expect("use after resolve")
}
#[cfg(test)]
pub(crate) fn as_response(&self) -> Option<&HttpResponse> {
match self.0 {
ReplyItem::Message(ref resp) => Some(resp),
pub(crate) fn as_msg(&self) -> &I {
match self.0.as_ref().unwrap() {
&AsyncResultItem::Ok(ref resp) => resp,
_ => panic!(),
}
}
#[cfg(test)]
pub(crate) fn as_err(&self) -> Option<&E> {
match self.0.as_ref().unwrap() {
&AsyncResultItem::Err(ref err) => Some(err),
_ => None,
}
}
}
impl Responder for Reply {
type Item = Reply;
impl Responder for AsyncResult<HttpResponse> {
type Item = AsyncResult<HttpResponse>;
type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<Reply, Error> {
fn respond_to<S>(
self, _: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
Ok(self)
}
}
impl Responder for HttpResponse {
type Item = Reply;
type Item = AsyncResult<HttpResponse>;
type Error = Error;
#[inline]
fn respond_to(self, _: HttpRequest) -> Result<Reply, Error> {
Ok(Reply(ReplyItem::Message(self)))
fn respond_to<S>(
self, _: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
Ok(AsyncResult(Some(AsyncResultItem::Ok(self))))
}
}
impl From<HttpResponse> for Reply {
impl<T> From<T> for AsyncResult<T> {
#[inline]
fn from(resp: HttpResponse) -> Reply {
Reply(ReplyItem::Message(resp))
fn from(resp: T) -> AsyncResult<T> {
AsyncResult(Some(AsyncResultItem::Ok(resp)))
}
}
@ -245,7 +296,7 @@ impl<T: Responder, E: Into<Error>> Responder for Result<T, E> {
type Item = <T as Responder>::Item;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error> {
fn respond_to<S: 'static>(self, req: &HttpRequest<S>) -> Result<Self::Item, Error> {
match self {
Ok(val) => match val.respond_to(req) {
Ok(val) => Ok(val),
@ -256,30 +307,42 @@ impl<T: Responder, E: Into<Error>> Responder for Result<T, E> {
}
}
impl<E: Into<Error>> From<Result<Reply, E>> for Reply {
impl<T, E: Into<Error>> From<Result<AsyncResult<T>, E>> for AsyncResult<T> {
#[inline]
fn from(res: Result<Reply, E>) -> Self {
fn from(res: Result<AsyncResult<T>, E>) -> Self {
match res {
Ok(val) => val,
Err(err) => Reply(ReplyItem::Message(err.into().into())),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
}
impl<E: Into<Error>> From<Result<HttpResponse, E>> for Reply {
impl<T, E: Into<Error>> From<Result<T, E>> for AsyncResult<T> {
#[inline]
fn from(res: Result<HttpResponse, E>) -> Self {
fn from(res: Result<T, E>) -> Self {
match res {
Ok(val) => Reply(ReplyItem::Message(val)),
Err(err) => Reply(ReplyItem::Message(err.into().into())),
Ok(val) => AsyncResult(Some(AsyncResultItem::Ok(val))),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
}
impl From<Box<Future<Item = HttpResponse, Error = Error>>> for Reply {
impl<T, E: Into<Error>> From<Result<Box<Future<Item = T, Error = Error>>, E>>
for AsyncResult<T>
{
#[inline]
fn from(fut: Box<Future<Item = HttpResponse, Error = Error>>) -> Reply {
Reply(ReplyItem::Future(fut))
fn from(res: Result<Box<Future<Item = T, Error = Error>>, E>) -> Self {
match res {
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(fut))),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
}
impl<T> From<Box<Future<Item = T, Error = Error>>> for AsyncResult<T> {
#[inline]
fn from(fut: Box<Future<Item = T, Error = Error>>) -> AsyncResult<T> {
AsyncResult(Some(AsyncResultItem::Future(fut)))
}
}
@ -291,26 +354,29 @@ where
I: Responder + 'static,
E: Into<Error> + 'static,
{
type Item = Reply;
type Item = AsyncResult<HttpResponse>;
type Error = Error;
#[inline]
fn respond_to(self, req: HttpRequest) -> Result<Reply, Error> {
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
let req = req.clone();
let fut = self.map_err(|e| e.into())
.then(move |r| match r.respond_to(req) {
Ok(reply) => match reply.into().0 {
ReplyItem::Message(resp) => ok(resp),
.then(move |r| match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => ok(resp),
_ => panic!("Nested async replies are not supported"),
},
Err(e) => err(e),
});
Ok(Reply::async(fut))
Ok(AsyncResult::async(Box::new(fut)))
}
}
/// Trait defines object that could be registered as resource route
// /// Trait defines object that could be registered as resource route
pub(crate) trait RouteHandler<S>: 'static {
fn handle(&mut self, req: HttpRequest<S>) -> Reply;
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse>;
}
/// Route handler wrapper for Handler
@ -344,11 +410,10 @@ where
R: Responder + 'static,
S: 'static,
{
fn handle(&mut self, req: HttpRequest<S>) -> Reply {
let req2 = req.drop_state();
match self.h.handle(req).respond_to(req2) {
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
match self.h.handle(req.clone()).respond_to(&req) {
Ok(reply) => reply.into(),
Err(err) => Reply::response(err.into()),
Err(err) => AsyncResult::err(err.into()),
}
}
}
@ -390,18 +455,18 @@ where
E: Into<Error> + 'static,
S: 'static,
{
fn handle(&mut self, req: HttpRequest<S>) -> Reply {
let req2 = req.drop_state();
let fut = (self.h)(req).map_err(|e| e.into()).then(move |r| {
match r.respond_to(req2) {
Ok(reply) => match reply.into().0 {
ReplyItem::Message(resp) => ok(resp),
_ => panic!("Nested async replies are not supported"),
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)),
AsyncResultItem::Err(e) => Either::A(err(e)),
AsyncResultItem::Future(fut) => Either::B(fut),
},
Err(e) => err(e),
}
});
Reply::async(fut)
Err(e) => Either::A(err(e)),
});
AsyncResult::async(Box::new(fut))
}
}
@ -427,14 +492,15 @@ where
/// }
///
/// /// extract path info using serde
/// fn index(state: State<MyApp>, info: Path<Info>) -> String {
/// format!("{} {}!", state.msg, info.username)
/// fn index(data: (State<MyApp>, Path<Info>)) -> String {
/// let (state, path) = data;
/// format!("{} {}!", state.msg, path.username)
/// }
///
/// fn main() {
/// let app = App::with_state(MyApp{msg: "Welcome"}).resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// }
/// ```
pub struct State<S>(HttpRequest<S>);
@ -447,12 +513,12 @@ impl<S> Deref for State<S> {
}
}
impl<S: 'static> FromRequest<S> for State<S> {
impl<S> FromRequest<S> for State<S> {
type Config = ();
type Result = FutureResult<Self, Error>;
type Result = State<S>;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
ok(State(req.clone()))
State(req.clone())
}
}

View File

@ -171,16 +171,16 @@ impl Display for ContentRangeSpec {
range,
instance_length,
} => {
try!(f.write_str("bytes "));
f.write_str("bytes ")?;
match range {
Some((first_byte, last_byte)) => {
try!(write!(f, "{}-{}", first_byte, last_byte));
write!(f, "{}-{}", first_byte, last_byte)?;
}
None => {
try!(f.write_str("*"));
f.write_str("*")?;
}
};
try!(f.write_str("/"));
f.write_str("/")?;
if let Some(v) = instance_length {
write!(f, "{}", v)
} else {
@ -191,8 +191,8 @@ impl Display for ContentRangeSpec {
ref unit,
ref resp,
} => {
try!(f.write_str(unit));
try!(f.write_str(" "));
f.write_str(unit)?;
f.write_str(" ")?;
f.write_str(resp)
}
}

View File

@ -6,8 +6,8 @@ use std::str::FromStr;
use bytes::{Bytes, BytesMut};
use mime::Mime;
use modhttp::Error as HttpError;
use modhttp::header::GetAll;
use modhttp::Error as HttpError;
pub use modhttp::header::*;
@ -116,8 +116,10 @@ pub enum ContentEncoding {
#[cfg(feature = "brotli")]
Br,
/// A format using the zlib structure with deflate algorithm
#[cfg(feature = "flate2")]
Deflate,
/// Gzip algorithm
#[cfg(feature = "flate2")]
Gzip,
/// Indicates the identity function (i.e. no compression, nor modification)
Identity,
@ -137,7 +139,9 @@ impl ContentEncoding {
match *self {
#[cfg(feature = "brotli")]
ContentEncoding::Br => "br",
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => "gzip",
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => "deflate",
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
}
@ -149,7 +153,9 @@ impl ContentEncoding {
match *self {
#[cfg(feature = "brotli")]
ContentEncoding::Br => 1.1,
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => 1.0,
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => 0.9,
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
}
@ -159,10 +165,12 @@ impl ContentEncoding {
// TODO: remove memory allocation
impl<'a> From<&'a str> for ContentEncoding {
fn from(s: &'a str) -> ContentEncoding {
match s.trim().to_lowercase().as_ref() {
match AsRef::<str>::as_ref(&s.trim().to_lowercase()) {
#[cfg(feature = "brotli")]
"br" => ContentEncoding::Br,
#[cfg(feature = "flate2")]
"gzip" => ContentEncoding::Gzip,
#[cfg(feature = "flate2")]
"deflate" => ContentEncoding::Deflate,
_ => ContentEncoding::Identity,
}
@ -202,7 +210,7 @@ impl fmt::Write for Writer {
#[doc(hidden)]
/// Reads a comma-delimited raw header into a Vec.
pub fn from_comma_delimited<T: FromStr>(
all: GetAll<HeaderValue>
all: GetAll<HeaderValue>,
) -> Result<Vec<T>, ParseError> {
let mut result = Vec::new();
for h in all {

View File

@ -7,7 +7,7 @@ use self::Charset::*;
/// A Mime charset.
///
/// The string representation is normalised to upper case.
/// The string representation is normalized to upper case.
///
/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url].
///

View File

@ -59,7 +59,7 @@ impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
impl<T: fmt::Display> fmt::Display for QualityItem<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(fmt::Display::fmt(&self.item, f));
fmt::Display::fmt(&self.item, f)?;
match self.quality.0 {
1000 => Ok(()),
0 => f.write_str("; q=0"),

View File

@ -217,7 +217,7 @@ mod tests {
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
let r = resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
@ -260,7 +260,7 @@ mod tests {
for (path, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
let r = resp.as_msg();
assert_eq!(r.status(), code);
}
}
@ -351,7 +351,7 @@ mod tests {
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
let r = resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
@ -535,7 +535,7 @@ mod tests {
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
let r = resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(

View File

@ -1,213 +1,8 @@
//! Basic http responses
#![allow(non_upper_case_globals, deprecated)]
#![allow(non_upper_case_globals)]
use http::StatusCode;
use body::Body;
use error::Error;
use handler::{Handler, Reply, Responder, RouteHandler};
use httprequest::HttpRequest;
use httpresponse::{HttpResponse, HttpResponseBuilder};
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Ok()` instead")]
pub const HttpOk: StaticResponse = StaticResponse(StatusCode::OK);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Created()` instead")]
pub const HttpCreated: StaticResponse = StaticResponse(StatusCode::CREATED);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Accepted()` instead")]
pub const HttpAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::pNonAuthoritativeInformation()` instead")]
pub const HttpNonAuthoritativeInformation: StaticResponse =
StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NoContent()` instead")]
pub const HttpNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::ResetContent()` instead")]
pub const HttpResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::PartialContent()` instead")]
pub const HttpPartialContent: StaticResponse =
StaticResponse(StatusCode::PARTIAL_CONTENT);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::MultiStatus()` instead")]
pub const HttpMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::AlreadyReported()` instead")]
pub const HttpAlreadyReported: StaticResponse =
StaticResponse(StatusCode::ALREADY_REPORTED);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::MultipleChoices()` instead")]
pub const HttpMultipleChoices: StaticResponse =
StaticResponse(StatusCode::MULTIPLE_CHOICES);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::MovedPermanently()` instead")]
pub const HttpMovedPermanently: StaticResponse =
StaticResponse(StatusCode::MOVED_PERMANENTLY);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Found()` instead")]
pub const HttpFound: StaticResponse = StaticResponse(StatusCode::FOUND);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::SeeOther()` instead")]
pub const HttpSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NotModified()` instead")]
pub const HttpNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::UseProxy()` instead")]
pub const HttpUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::TemporaryRedirect()` instead")]
pub const HttpTemporaryRedirect: StaticResponse =
StaticResponse(StatusCode::TEMPORARY_REDIRECT);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::PermanentRedirect()` instead")]
pub const HttpPermanentRedirect: StaticResponse =
StaticResponse(StatusCode::PERMANENT_REDIRECT);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::BadRequest()` instead")]
pub const HttpBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::Unauthorized()` instead")]
pub const HttpUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::PaymentRequired()` instead")]
pub const HttpPaymentRequired: StaticResponse =
StaticResponse(StatusCode::PAYMENT_REQUIRED);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Forbidden()` instead")]
pub const HttpForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NotFound()` instead")]
pub const HttpNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::MethodNotAllowed()` instead")]
pub const HttpMethodNotAllowed: StaticResponse =
StaticResponse(StatusCode::METHOD_NOT_ALLOWED);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::NotAcceptable()` instead")]
pub const HttpNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::ProxyAuthenticationRequired()` instead")]
pub const HttpProxyAuthenticationRequired: StaticResponse =
StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::RequestTimeout()` instead")]
pub const HttpRequestTimeout: StaticResponse =
StaticResponse(StatusCode::REQUEST_TIMEOUT);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Conflict()` instead")]
pub const HttpConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Gone()` instead")]
pub const HttpGone: StaticResponse = StaticResponse(StatusCode::GONE);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::LengthRequired()` instead")]
pub const HttpLengthRequired: StaticResponse =
StaticResponse(StatusCode::LENGTH_REQUIRED);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::PreconditionFailed()` instead")]
pub const HttpPreconditionFailed: StaticResponse =
StaticResponse(StatusCode::PRECONDITION_FAILED);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::PayloadTooLarge()` instead")]
pub const HttpPayloadTooLarge: StaticResponse =
StaticResponse(StatusCode::PAYLOAD_TOO_LARGE);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::UriTooLong()` instead")]
pub const HttpUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::UnsupportedMediaType()` instead")]
pub const HttpUnsupportedMediaType: StaticResponse =
StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::RangeNotSatisfiable()` instead")]
pub const HttpRangeNotSatisfiable: StaticResponse =
StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::ExpectationFailed()` instead")]
pub const HttpExpectationFailed: StaticResponse =
StaticResponse(StatusCode::EXPECTATION_FAILED);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::InternalServerError()` instead")]
pub const HttpInternalServerError: StaticResponse =
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::NotImplemented()` instead")]
pub const HttpNotImplemented: StaticResponse =
StaticResponse(StatusCode::NOT_IMPLEMENTED);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse::BadGateway()` instead")]
pub const HttpBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::ServiceUnavailable()` instead")]
pub const HttpServiceUnavailable: StaticResponse =
StaticResponse(StatusCode::SERVICE_UNAVAILABLE);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::GatewayTimeout()` instead")]
pub const HttpGatewayTimeout: StaticResponse =
StaticResponse(StatusCode::GATEWAY_TIMEOUT);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::VersionNotSupported()` instead")]
pub const HttpVersionNotSupported: StaticResponse =
StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::VariantAlsoNegotiates()` instead")]
pub const HttpVariantAlsoNegotiates: StaticResponse =
StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::InsufficientStorage()` instead")]
pub const HttpInsufficientStorage: StaticResponse =
StaticResponse(StatusCode::INSUFFICIENT_STORAGE);
#[deprecated(since = "0.5.0",
note = "please use `HttpResponse::LoopDetected()` instead")]
pub const HttpLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED);
#[deprecated(since = "0.5.0", note = "please use `HttpResponse` instead")]
#[derive(Copy, Clone, Debug)]
pub struct StaticResponse(StatusCode);
impl StaticResponse {
pub fn build(&self) -> HttpResponseBuilder {
HttpResponse::build(self.0)
}
pub fn build_from<S>(&self, req: &HttpRequest<S>) -> HttpResponseBuilder {
req.build_response(self.0)
}
pub fn with_reason(self, reason: &'static str) -> HttpResponse {
let mut resp = HttpResponse::new(self.0);
resp.set_reason(reason);
resp
}
pub fn with_body<B: Into<Body>>(self, body: B) -> HttpResponse {
HttpResponse::with_body(self.0, body.into())
}
}
impl<S> Handler<S> for StaticResponse {
type Result = HttpResponse;
fn handle(&mut self, _: HttpRequest<S>) -> HttpResponse {
HttpResponse::new(self.0)
}
}
impl<S> RouteHandler<S> for StaticResponse {
fn handle(&mut self, _: HttpRequest<S>) -> Reply {
Reply::response(HttpResponse::new(self.0))
}
}
impl Responder for StaticResponse {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
Ok(self.build().finish())
}
}
impl From<StaticResponse> for HttpResponse {
fn from(st: StaticResponse) -> Self {
HttpResponse::new(st.0)
}
}
impl From<StaticResponse> for Reply {
fn from(st: StaticResponse) -> Self {
HttpResponse::new(st.0).into()
}
}
macro_rules! STATIC_RESP {
($name:ident, $status:expr) => {
#[allow(non_snake_case)]
@ -286,34 +81,13 @@ impl HttpResponse {
#[cfg(test)]
mod tests {
use super::{Body, HttpBadRequest, HttpOk, HttpResponse};
use body::Body;
use http::StatusCode;
use httpresponse::HttpResponse;
#[test]
fn test_build() {
let resp = HttpOk.build().body(Body::Empty);
let resp = HttpResponse::Ok().body(Body::Empty);
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_response() {
let resp: HttpResponse = HttpOk.into();
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_from() {
let resp: HttpResponse = HttpOk.into();
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_with_reason() {
let resp: HttpResponse = HttpOk.into();
assert_eq!(resp.reason(), "OK");
let resp = HttpBadRequest.with_reason("test");
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
assert_eq!(resp.reason(), "test");
}
}

View File

@ -1,8 +1,8 @@
use bytes::{Bytes, BytesMut};
use encoding::EncodingRef;
use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label;
use encoding::types::{DecoderTrap, Encoding};
use encoding::EncodingRef;
use futures::{Future, Poll, Stream};
use http::{header, HeaderMap};
use http_range::HttpRange;
@ -425,8 +425,8 @@ where
#[cfg(test)]
mod tests {
use super::*;
use encoding::Encoding;
use encoding::all::ISO_8859_2;
use encoding::Encoding;
use futures::Async;
use http::{Method, Uri, Version};
use httprequest::HttpRequest;

View File

@ -1,21 +1,20 @@
//! HTTP Request message related code.
use bytes::Bytes;
use cookie::Cookie;
use failure;
use futures::future::{result, FutureResult};
use futures::{Async, Poll, Stream};
use futures_cpupool::CpuPool;
use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version};
use percent_encoding::percent_decode;
use std::borrow::Cow;
#![cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ptr))]
use std::net::SocketAddr;
use std::rc::Rc;
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, Extensions, HeaderMap, Method, StatusCode, Uri, Version};
use tokio_io::AsyncRead;
use url::{form_urlencoded, Url};
use body::Body;
use error::{CookieParseError, Error, PayloadError, UrlGenerationError};
use error::{CookieParseError, PayloadError, UrlGenerationError};
use handler::FromRequest;
use httpmessage::HttpMessage;
use httpresponse::{HttpResponse, HttpResponseBuilder};
@ -24,23 +23,32 @@ use param::Params;
use payload::Payload;
use router::{Resource, Router};
use server::helpers::SharedHttpInnerMessage;
use uri::Url as InnerUrl;
bitflags! {
pub(crate) struct MessageFlags: u8 {
const KEEPALIVE = 0b0000_0010;
}
}
pub struct HttpInnerMessage {
pub version: Version,
pub method: Method,
pub uri: Uri,
pub(crate) url: InnerUrl,
pub(crate) flags: MessageFlags,
pub headers: HeaderMap,
pub extensions: Extensions,
pub params: Params<'static>,
pub cookies: Option<Vec<Cookie<'static>>>,
pub query: Params<'static>,
pub query_loaded: bool,
pub addr: Option<SocketAddr>,
pub payload: Option<Payload>,
pub info: Option<ConnectionInfo<'static>>,
pub prefix: u16,
resource: RouterResource,
}
struct Query(Params<'static>);
struct Cookies(Vec<Cookie<'static>>);
#[derive(Debug, Copy, Clone, PartialEq)]
enum RouterResource {
Notset,
@ -51,17 +59,16 @@ impl Default for HttpInnerMessage {
fn default() -> HttpInnerMessage {
HttpInnerMessage {
method: Method::GET,
uri: Uri::default(),
url: InnerUrl::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
flags: MessageFlags::empty(),
params: Params::new(),
query: Params::new(),
query_loaded: false,
cookies: None,
addr: None,
payload: None,
extensions: Extensions::new(),
info: None,
prefix: 0,
resource: RouterResource::Notset,
}
}
@ -71,20 +78,7 @@ impl HttpInnerMessage {
/// Checks if a connection should be kept alive.
#[inline]
pub fn keep_alive(&self) -> bool {
if let Some(conn) = self.headers.get(header::CONNECTION) {
if let Ok(conn) = conn.to_str() {
if self.version == Version::HTTP_10 && conn.contains("keep-alive") {
true
} else {
self.version == Version::HTTP_11
&& !(conn.contains("close") || conn.contains("upgrade"))
}
} else {
false
}
} else {
self.version != Version::HTTP_10
}
self.flags.contains(MessageFlags::KEEPALIVE)
}
#[inline]
@ -92,12 +86,11 @@ impl HttpInnerMessage {
self.headers.clear();
self.extensions.clear();
self.params.clear();
self.query.clear();
self.query_loaded = false;
self.cookies = None;
self.addr = None;
self.info = None;
self.flags = MessageFlags::empty();
self.payload = None;
self.prefix = 0;
self.resource = RouterResource::Notset;
}
}
@ -113,23 +106,26 @@ impl HttpRequest<()> {
/// Construct a new Request.
#[inline]
pub fn new(
method: Method, uri: Uri, version: Version, headers: HeaderMap,
method: Method,
uri: Uri,
version: Version,
headers: HeaderMap,
payload: Option<Payload>,
) -> HttpRequest {
let url = InnerUrl::new(uri);
HttpRequest(
SharedHttpInnerMessage::from_message(HttpInnerMessage {
method,
uri,
url,
version,
headers,
payload,
params: Params::new(),
query: Params::new(),
query_loaded: false,
cookies: None,
addr: None,
extensions: Extensions::new(),
addr: None,
info: None,
prefix: 0,
flags: MessageFlags::empty(),
resource: RouterResource::Notset,
}),
None,
@ -197,7 +193,13 @@ impl<S> HttpRequest<S> {
/// Request extensions
#[inline]
pub fn extensions(&mut self) -> &mut Extensions {
pub fn extensions(&self) -> &Extensions {
&self.as_ref().extensions
}
/// Mutable reference to a the request's extensions
#[inline]
pub fn extensions_mut(&mut self) -> &mut Extensions {
&mut self.as_mut().extensions
}
@ -230,26 +232,19 @@ impl<S> HttpRequest<S> {
}
#[doc(hidden)]
pub fn prefix_len(&self) -> usize {
if let Some(router) = self.router() {
router.prefix().len()
} else {
0
}
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.as_ref().uri
}
/// Returns mutable the Request Uri.
///
/// This might be useful for middlewares, e.g. path normalization.
#[inline]
pub fn uri_mut(&mut self) -> &mut Uri {
&mut self.as_mut().uri
self.as_ref().url.uri()
}
/// Read the Request method.
@ -275,15 +270,7 @@ impl<S> HttpRequest<S> {
/// The target path of this Request.
#[inline]
pub fn path(&self) -> &str {
self.uri().path()
}
/// Percent decoded path of this Request.
#[inline]
pub fn path_decoded(&self) -> Cow<str> {
percent_decode(self.uri().path().as_bytes())
.decode_utf8()
.unwrap()
self.as_ref().url.path()
}
/// Get *ConnectionInfo* for correct request.
@ -317,7 +304,9 @@ impl<S> HttpRequest<S> {
/// }
/// ```
pub fn url_for<U, I>(
&self, name: &str, elements: U
&self,
name: &str,
elements: U,
) -> Result<Url, UrlGenerationError>
where
U: IntoIterator<Item = I>,
@ -370,27 +359,30 @@ impl<S> HttpRequest<S> {
/// To get client connection information `connection_info()` method should
/// be used.
#[inline]
pub fn peer_addr(&self) -> Option<&SocketAddr> {
self.as_ref().addr.as_ref()
pub fn peer_addr(&self) -> Option<SocketAddr> {
self.as_ref().addr
}
#[inline]
pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
self.as_mut().addr = addr
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(&self) -> &Params {
if !self.as_ref().query_loaded {
let params: &mut Params =
unsafe { mem::transmute(&mut self.as_mut().query) };
self.as_mut().query_loaded = true;
pub fn query<'a>(&'a self) -> &'a Params {
if let None = self.extensions().get::<Query>() {
let mut params: Params<'a> = Params::new();
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
params.add(key, val);
}
let params: Params<'static> = unsafe { mem::transmute(params) };
self.as_mut().extensions.insert(Query(params));
}
unsafe { mem::transmute(&self.as_ref().query) }
let params: &Params<'a> =
unsafe { mem::transmute(&self.extensions().get::<Query>().unwrap().0) };
params
}
/// The query string in the URL.
@ -407,7 +399,7 @@ impl<S> HttpRequest<S> {
/// Load request cookies.
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
if self.as_ref().cookies.is_none() {
if let None = self.extensions().get::<Query>() {
let msg = self.as_mut();
let mut cookies = Vec::new();
for hdr in msg.headers.get_all(header::COOKIE) {
@ -418,9 +410,9 @@ impl<S> HttpRequest<S> {
}
}
}
msg.cookies = Some(cookies)
msg.extensions.insert(Cookies(cookies));
}
Ok(self.as_ref().cookies.as_ref().unwrap())
Ok(&self.extensions().get::<Cookies>().unwrap().0)
}
/// Return request cookie.
@ -435,12 +427,18 @@ impl<S> HttpRequest<S> {
None
}
pub(crate) fn set_cookies(&mut self, cookies: Option<Vec<Cookie<'static>>>) {
if let Some(cookies) = cookies {
self.extensions_mut().insert(Cookies(cookies));
}
}
/// Get a reference to the Params object.
///
/// Params is a container for url parameters.
/// Route supports glob patterns: * for a single wildcard segment and :param
/// for matching storing that segment of the request url in the Params
/// object.
/// A variable segment is specified in the form `{identifier}`,
/// where the identifier can be used later in a request handler to
/// access the matched value for that segment.
#[inline]
pub fn match_info(&self) -> &Params {
unsafe { mem::transmute(&self.as_ref().params) }
@ -454,7 +452,7 @@ impl<S> HttpRequest<S> {
/// Checks if a connection should be kept alive.
pub fn keep_alive(&self) -> bool {
self.as_ref().keep_alive()
self.as_ref().flags.contains(MessageFlags::KEEPALIVE)
}
/// Check if request requires connection upgrade
@ -508,13 +506,13 @@ impl<S> Clone for HttpRequest<S> {
}
}
impl<S: 'static> FromRequest<S> for HttpRequest<S> {
impl<S> FromRequest<S> for HttpRequest<S> {
type Config = ();
type Result = FutureResult<Self, Error>;
type Result = Self;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
result(Ok(req.clone()))
req.clone()
}
}
@ -578,7 +576,7 @@ impl<S> fmt::Debug for HttpRequest<S> {
"\nHttpRequest {:?} {}:{}",
self.as_ref().version,
self.as_ref().method,
self.path_decoded()
self.path()
);
if !self.query_string().is_empty() {
let _ = writeln!(f, " query: ?{:?}", self.query_string());
@ -597,7 +595,6 @@ impl<S> fmt::Debug for HttpRequest<S> {
#[cfg(test)]
mod tests {
use super::*;
use http::{HttpTryFrom, Uri};
use resource::ResourceHandler;
use router::Resource;
use server::ServerSettings;
@ -610,14 +607,6 @@ mod tests {
assert!(dbg.contains("HttpRequest"));
}
#[test]
fn test_uri_mut() {
let mut req = HttpRequest::default();
assert_eq!(req.path(), "/");
*req.uri_mut() = Uri::try_from("/test").unwrap();
assert_eq!(req.path(), "/test");
}
#[test]
fn test_no_request_cookies() {
let req = HttpRequest::default();
@ -681,12 +670,8 @@ mod tests {
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let routes = vec![
(
Resource::new("index", "/user/{name}.{ext}"),
Some(resource),
),
];
let routes =
vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
let (router, _) = Router::new("/", ServerSettings::default(), routes);
assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/test/unknown"));
@ -715,12 +700,8 @@ mod tests {
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let routes = vec![
(
Resource::new("index", "/user/{name}.{ext}"),
Some(resource),
),
];
let routes =
vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/prefix/user/test.html"));
@ -739,12 +720,10 @@ mod tests {
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let routes = vec![
(
Resource::external("youtube", "https://youtube.com/watch/{video_id}"),
None,
),
];
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"));

View File

@ -607,7 +607,7 @@ impl HttpResponseBuilder {
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
fn parts<'a>(
parts: &'a mut Option<Box<InnerHttpResponse>>, err: &Option<HttpError>
parts: &'a mut Option<Box<InnerHttpResponse>>, err: &Option<HttpError>,
) -> Option<&'a mut Box<InnerHttpResponse>> {
if err.is_some() {
return None;
@ -636,7 +636,7 @@ impl Responder for HttpResponseBuilder {
type Error = Error;
#[inline]
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, Error> {
fn respond_to<S>(mut self, _: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(self.finish())
}
}
@ -653,7 +653,7 @@ impl Responder for &'static str {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
@ -672,7 +672,7 @@ impl Responder for &'static [u8] {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
@ -691,7 +691,7 @@ impl Responder for String {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
@ -710,7 +710,7 @@ impl<'a> Responder for &'a String {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
@ -729,7 +729,7 @@ impl Responder for Bytes {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
@ -748,7 +748,7 @@ impl Responder for BytesMut {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
@ -829,7 +829,7 @@ impl HttpResponsePool {
#[inline]
pub fn get_builder(
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode,
) -> HttpResponseBuilder {
let p = unsafe { &mut *pool.as_ref().get() };
if let Some(mut msg) = p.0.pop_front() {
@ -853,7 +853,7 @@ impl HttpResponsePool {
#[inline]
pub fn get_response(
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode, body: Body
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode, body: Body,
) -> HttpResponse {
let p = unsafe { &mut *pool.as_ref().get() };
if let Some(mut msg) = p.0.pop_front() {
@ -879,7 +879,7 @@ impl HttpResponsePool {
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))]
fn release(
pool: &Rc<UnsafeCell<HttpResponsePool>>, mut inner: Box<InnerHttpResponse>
pool: &Rc<UnsafeCell<HttpResponsePool>>, mut inner: Box<InnerHttpResponse>,
) {
let pool = unsafe { &mut *pool.as_ref().get() };
if pool.0.len() < 128 {
@ -1057,7 +1057,7 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test"));
let resp: HttpResponse = "test".respond_to(req.clone()).ok().unwrap();
let resp: HttpResponse = "test".respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -1078,7 +1078,7 @@ mod tests {
&Binary::from(b"test".as_ref())
);
let resp: HttpResponse = b"test".as_ref().respond_to(req.clone()).ok().unwrap();
let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -1102,11 +1102,7 @@ mod tests {
&Binary::from("test".to_owned())
);
let resp: HttpResponse = "test"
.to_owned()
.respond_to(req.clone())
.ok()
.unwrap();
let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -1130,10 +1126,7 @@ mod tests {
&Binary::from(&"test".to_owned())
);
let resp: HttpResponse = (&"test".to_owned())
.respond_to(req.clone())
.ok()
.unwrap();
let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -1159,7 +1152,7 @@ mod tests {
);
let b = Bytes::from_static(b"test");
let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap();
let resp: HttpResponse = b.respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
@ -1185,7 +1178,7 @@ mod tests {
);
let b = BytesMut::from("test");
let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap();
let resp: HttpResponse = b.respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),

View File

@ -2,12 +2,12 @@ use bytes::{Bytes, BytesMut};
use futures::{Future, Poll, Stream};
use http::header::CONTENT_LENGTH;
use std::fmt;
use std::rc::Rc;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use mime;
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
use error::{Error, JsonPayloadError, PayloadError};
@ -118,7 +118,7 @@ impl<T: Serialize> Responder for Json<T> {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
let body = serde_json::to_string(&self.0)?;
Ok(req.build_response(StatusCode::OK)
@ -193,7 +193,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
@ -202,8 +202,10 @@ impl<S> JsonConfig<S> {
impl<S> Default for JsonConfig<S> {
fn default() -> Self {
JsonConfig { limit: 262_144,
ehandler: Rc::new(|e, _| e.into()) }
JsonConfig {
limit: 262_144,
ehandler: Rc::new(|e, _| e.into()),
}
}
}
@ -349,7 +351,7 @@ mod tests {
let json = Json(MyObject {
name: "test".to_owned(),
});
let resp = json.respond_to(HttpRequest::default()).unwrap();
let resp = json.respond_to(&HttpRequest::default()).unwrap();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"application/json"
@ -415,13 +417,7 @@ mod tests {
let mut handler = With::new(|data: Json<MyObject>| data, cfg);
let req = HttpRequest::default();
let err = handler
.handle(req)
.as_response()
.unwrap()
.error()
.is_some();
assert!(err);
assert!(handler.handle(req).as_err().is_some());
let mut req = HttpRequest::default();
req.headers_mut().insert(
@ -434,12 +430,6 @@ mod tests {
);
req.payload_mut()
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
let ok = handler
.handle(req)
.as_response()
.unwrap()
.error()
.is_none();
assert!(ok)
assert!(handler.handle(req).as_err().is_none())
}
}

View File

@ -44,7 +44,7 @@
//! * [HttpRequest](struct.HttpRequest.html) and
//! [HttpResponse](struct.HttpResponse.html): These structs
//! represent HTTP requests and responses and expose various methods
//! for inspecting, creating and otherwise utilising them.
//! for inspecting, creating and otherwise utilizing them.
//!
//! ## Features
//!
@ -59,13 +59,29 @@
//! * 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.21 or later
//! * Supported Rust version: 1.24 or later
//!
//! ## Package feature
//!
//! * `tls` - enables ssl support via `native-tls` crate
//! * `alpn` - enables ssl support via `openssl` crate, require for `http/2`
//! support
//! * `session` - enables session support, includes `ring` crate as
//! dependency
//! * `brotli` - enables `brotli` compression support, requires `c`
//! compiler
//! * `flate-c` - enables `gzip`, `deflate` compression support, requires
//! `c` compiler
//! * `flate-rust` - experimental rust based implementation for
//! `gzip`, `deflate` compression.
//!
#![cfg_attr(actix_nightly, feature(
specialization, // for impl ErrorResponse for std::error::Error
))]
#![cfg_attr(feature = "cargo-clippy",
allow(decimal_literal_representation, suspicious_arithmetic_impl))]
#![cfg_attr(
feature = "cargo-clippy",
allow(decimal_literal_representation, suspicious_arithmetic_impl)
)]
#[macro_use]
extern crate log;
@ -95,6 +111,7 @@ extern crate mime_guess;
extern crate mio;
extern crate net2;
extern crate rand;
extern crate slab;
extern crate tokio_core;
extern crate tokio_io;
extern crate url;
@ -103,6 +120,7 @@ extern crate serde;
#[cfg(feature = "brotli")]
extern crate brotli2;
extern crate encoding;
#[cfg(feature = "flate2")]
extern crate flate2;
extern crate h2 as http2;
extern crate num_cpus;
@ -110,7 +128,6 @@ extern crate percent_encoding;
extern crate serde_json;
extern crate serde_urlencoded;
extern crate smallvec;
extern crate trust_dns_resolver;
#[macro_use]
extern crate actix;
@ -136,6 +153,7 @@ mod extractor;
mod handler;
mod header;
mod helpers;
mod httpcodes;
mod httpmessage;
mod httprequest;
mod httpresponse;
@ -147,6 +165,8 @@ mod pipeline;
mod resource;
mod route;
mod router;
mod scope;
mod uri;
mod with;
pub mod client;
@ -163,18 +183,18 @@ pub use body::{Binary, Body};
pub use context::HttpContext;
pub use error::{Error, ResponseError, Result};
pub use extractor::{Form, Path, Query};
pub use handler::{AsyncResponder, Either, FromRequest, FutureResponse, Responder, State};
pub use handler::{
AsyncResponder, Either, FromRequest, FutureResponse, Responder, State,
};
pub use httpmessage::HttpMessage;
pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse;
pub use json::Json;
pub use scope::Scope;
#[doc(hidden)]
pub mod httpcodes;
#[doc(hidden)]
#[allow(deprecated)]
pub use application::Application;
#[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;
@ -200,7 +220,7 @@ pub mod dev {
pub use body::BodyStream;
pub use context::Drain;
pub use extractor::{FormConfig, PayloadConfig};
pub use handler::{Handler, Reply};
pub use handler::{AsyncResult, Handler};
pub use httpmessage::{MessageBody, UrlEncoded};
pub use httpresponse::HttpResponseBuilder;
pub use info::ConnectionInfo;
@ -209,6 +229,7 @@ pub mod dev {
pub use resource::ResourceHandler;
pub use route::Route;
pub use router::{Resource, ResourceType, Router};
pub use with::ExtractorConfig;
}
pub mod http {

View File

@ -7,8 +7,8 @@
//!
//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
//! 2. Use any of the builder methods to set fields in the backend.
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the
//! constructed backend.
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the
//! constructed backend.
//!
//! Cors middleware could be used as parameter for `App::middleware()` or
//! `ResourceHandler::middleware()` methods. But you have to use
@ -64,26 +64,36 @@ use resource::ResourceHandler;
#[derive(Debug, Fail)]
pub enum CorsError {
/// The HTTP request header `Origin` is required but was not provided
#[fail(display = "The HTTP request header `Origin` is required but was not provided")]
#[fail(
display = "The HTTP request header `Origin` is required but was not provided"
)]
MissingOrigin,
/// The HTTP request header `Origin` could not be parsed correctly.
#[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")]
BadOrigin,
/// The request header `Access-Control-Request-Method` is required but is
/// missing
#[fail(display = "The request header `Access-Control-Request-Method` is required but is missing")]
#[fail(
display = "The request header `Access-Control-Request-Method` is required but is missing"
)]
MissingRequestMethod,
/// The request header `Access-Control-Request-Method` has an invalid value
#[fail(display = "The request header `Access-Control-Request-Method` has an invalid value")]
#[fail(
display = "The request header `Access-Control-Request-Method` has an invalid value"
)]
BadRequestMethod,
/// The request header `Access-Control-Request-Headers` has an invalid
/// value
#[fail(display = "The request header `Access-Control-Request-Headers` has an invalid value")]
#[fail(
display = "The request header `Access-Control-Request-Headers` has an invalid value"
)]
BadRequestHeaders,
/// The request header `Access-Control-Request-Headers` is required but is
/// missing.
#[fail(display = "The request header `Access-Control-Request-Headers` is required but is
missing")]
#[fail(
display = "The request header `Access-Control-Request-Headers` is required but is
missing"
)]
MissingRequestHeaders,
/// Origin is not allowed to make this request
#[fail(display = "Origin is not allowed to make this request")]
@ -292,7 +302,7 @@ impl Cors {
}
fn validate_allowed_method<S>(
&self, req: &mut HttpRequest<S>
&self, req: &mut HttpRequest<S>,
) -> Result<(), CorsError> {
if let Some(hdr) = req.headers()
.get(header::ACCESS_CONTROL_REQUEST_METHOD)
@ -313,7 +323,7 @@ impl Cors {
}
fn validate_allowed_headers<S>(
&self, req: &mut HttpRequest<S>
&self, req: &mut HttpRequest<S>,
) -> Result<(), CorsError> {
match self.inner.headers {
AllOrSome::All => Ok(()),
@ -419,7 +429,7 @@ impl<S> Middleware<S> for Cors {
}
fn response(
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> {
match self.inner.origins {
AllOrSome::All => {
@ -506,7 +516,7 @@ pub struct CorsBuilder<S = ()> {
}
fn cors<'a>(
parts: &'a mut Option<Inner>, err: &Option<http::Error>
parts: &'a mut Option<Inner>, err: &Option<http::Error>,
) -> Option<&'a mut Inner> {
if err.is_some() {
return None;
@ -815,7 +825,7 @@ impl<S: 'static> CorsBuilder<S> {
if let AllOrSome::Some(ref origins) = cors.origins {
let s = origins
.iter()
.fold(String::new(), |s, v| s + &format!("{}", v));
.fold(String::new(), |s, v| s + &v.to_string());
cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap());
}

View File

@ -150,8 +150,8 @@ impl CsrfFilter {
/// Add an origin that is allowed to make requests. Will be verified
/// against the `Origin` request header.
pub fn allowed_origin(mut self, origin: &str) -> CsrfFilter {
self.origins.insert(origin.to_owned());
pub fn allowed_origin<T: Into<String>>(mut self, origin: T) -> CsrfFilter {
self.origins.insert(origin.into());
self
}

View File

@ -76,7 +76,7 @@ impl DefaultHeaders {
impl<S> Middleware<S> for DefaultHeaders {
fn response(
&self, _: &mut HttpRequest<S>, mut resp: HttpResponse
&self, _: &mut HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> {
for (key, value) in self.headers.iter() {
if !resp.headers().contains_key(key) {

View File

@ -12,7 +12,7 @@ type ErrorHandler<S> = Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response>
///
/// You can use `ErrorHandlers::handler()` method to register a custom error
/// handler for specific status code. You can modify existing response or
/// create completly new one.
/// create completely new one.
///
/// ## Example
///
@ -69,7 +69,7 @@ impl<S> ErrorHandlers<S> {
impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
if let Some(handler) = self.handlers.get(&resp.status()) {
handler(req, resp)
@ -82,8 +82,8 @@ impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
#[cfg(test)]
mod tests {
use super::*;
use http::StatusCode;
use http::header::CONTENT_TYPE;
use http::StatusCode;
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
let mut builder = resp.into_builder();

389
src/middleware/identity.rs Normal file
View File

@ -0,0 +1,389 @@
//! Request identity service for Actix applications.
//!
//! [**IdentityService**](struct.IdentityService.html) middleware can be
//! used with different policies types to store identity information.
//!
//! Bu default, only cookie identity policy is implemented. Other backend
//! implementations can be added separately.
//!
//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html)
//! uses cookies as identity storage.
//!
//! To access current request identity
//! [**RequestIdentity**](trait.RequestIdentity.html) should be used.
//! *HttpRequest* implements *RequestIdentity* trait.
//!
//! ```rust
//! use actix_web::middleware::identity::RequestIdentity;
//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
//! use actix_web::*;
//!
//! fn index(req: HttpRequest) -> Result<String> {
//! // access request identity
//! if let Some(id) = req.identity() {
//! Ok(format!("Welcome! {}", id))
//! } else {
//! Ok("Welcome Anonymous!".to_owned())
//! }
//! }
//!
//! fn login(mut req: HttpRequest) -> HttpResponse {
//! req.remember("User1".to_owned()); // <- remember identity
//! HttpResponse::Ok().finish()
//! }
//!
//! fn logout(mut req: HttpRequest) -> HttpResponse {
//! req.forget(); // <- remove identity
//! HttpResponse::Ok().finish()
//! }
//!
//! fn main() {
//! let app = App::new().middleware(IdentityService::new(
//! // <- create identity middleware
//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
//! .name("auth-cookie")
//! .secure(false),
//! ));
//! }
//! ```
use std::rc::Rc;
use cookie::{Cookie, CookieJar, Key};
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
use futures::Future;
use time::Duration;
use error::{Error, Result};
use http::header::{self, HeaderValue};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started};
/// The helper trait to obtain your identity from a request.
///
/// ```rust
/// use actix_web::*;
/// use actix_web::middleware::identity::RequestIdentity;
///
/// fn index(req: HttpRequest) -> Result<String> {
/// // access request identity
/// if let Some(id) = req.identity() {
/// Ok(format!("Welcome! {}", id))
/// } else {
/// Ok("Welcome Anonymous!".to_owned())
/// }
/// }
///
/// fn login(mut req: HttpRequest) -> HttpResponse {
/// req.remember("User1".to_owned()); // <- remember identity
/// HttpResponse::Ok().finish()
/// }
///
/// fn logout(mut req: HttpRequest) -> HttpResponse {
/// req.forget(); // <- remove identity
/// HttpResponse::Ok().finish()
/// }
/// # fn main() {}
/// ```
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<&str>;
/// Remember identity.
fn remember(&mut self, identity: String);
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&mut self);
}
impl<S> RequestIdentity for HttpRequest<S> {
fn identity(&self) -> Option<&str> {
if let Some(id) = self.extensions().get::<IdentityBox>() {
return id.0.identity();
}
None
}
fn remember(&mut self, identity: String) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.remember(identity);
}
}
fn forget(&mut self) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.forget();
}
}
}
/// An identity
pub trait Identity: 'static {
fn identity(&self) -> Option<&str>;
fn remember(&mut self, key: String);
fn forget(&mut self);
/// Write session to storage backend.
fn write(&mut self, resp: HttpResponse) -> Result<Response>;
}
/// Identity policy definition.
pub trait IdentityPolicy<S>: Sized + 'static {
type Identity: Identity;
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: &mut HttpRequest<S>) -> Self::Future;
}
/// Request identity middleware
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// 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
/// .name("auth-cookie")
/// .secure(false))
/// );
/// }
/// ```
pub struct IdentityService<T> {
backend: T,
}
impl<T> IdentityService<T> {
/// Create new identity service with specified backend.
pub fn new(backend: T) -> Self {
IdentityService { backend }
}
}
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: &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: &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))
}
}
}
#[doc(hidden)]
/// Identity that uses private cookies as identity storage.
pub struct CookieIdentity {
changed: bool,
identity: Option<String>,
inner: Rc<CookieIdentityInner>,
}
impl Identity for CookieIdentity {
fn identity(&self) -> Option<&str> {
self.identity.as_ref().map(|s| s.as_ref())
}
fn remember(&mut self, value: String) {
self.changed = true;
self.identity = Some(value);
}
fn forget(&mut self) {
self.changed = true;
self.identity = None;
}
fn write(&mut self, mut resp: HttpResponse) -> Result<Response> {
if self.changed {
let _ = self.inner.set_cookie(&mut resp, self.identity.take());
}
Ok(Response::Done(resp))
}
}
struct CookieIdentityInner {
key: Key,
name: String,
path: String,
domain: Option<String>,
secure: bool,
max_age: Option<Duration>,
}
impl CookieIdentityInner {
fn new(key: &[u8]) -> CookieIdentityInner {
CookieIdentityInner {
key: Key::from_master(key),
name: "actix-identity".to_owned(),
path: "/".to_owned(),
domain: None,
secure: true,
max_age: None,
}
}
fn set_cookie(&self, resp: &mut HttpResponse, id: Option<String>) -> Result<()> {
let some = id.is_some();
{
let id = id.unwrap_or_else(String::new);
let mut cookie = Cookie::new(self.name.clone(), id);
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(true);
if let Some(ref domain) = self.domain {
cookie.set_domain(domain.clone());
}
if let Some(max_age) = self.max_age {
cookie.set_max_age(max_age);
}
let mut jar = CookieJar::new();
if some {
jar.private(&self.key).add(cookie);
} else {
jar.add_original(cookie.clone());
jar.private(&self.key).remove(cookie);
}
for cookie in jar.delta() {
let val = HeaderValue::from_str(&cookie.to_string())?;
resp.headers_mut().append(header::SET_COOKIE, val);
}
}
Ok(())
}
fn load<S>(&self, req: &mut HttpRequest<S>) -> Option<String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
let cookie_opt = jar.private(&self.key).get(&self.name);
if let Some(cookie) = cookie_opt {
return Some(cookie.value().into());
}
}
}
}
None
}
}
/// Use cookies for request identity storage.
///
/// The constructors take a key as an argument.
/// This is the private key for cookie - when this value is changed,
/// all identities are lost. The constructors will panic if the key is less
/// than 32 bytes in length.
///
/// # Example
///
/// ```rust
/// # extern crate actix_web;
/// 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
/// .domain("www.rust-lang.org")
/// .name("actix_auth")
/// .path("/")
/// .secure(true)));
/// }
/// ```
pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
impl CookieIdentityPolicy {
/// Construct new `CookieIdentityPolicy` instance.
///
/// Panics if key length is less than 32 bytes.
pub fn new(key: &[u8]) -> CookieIdentityPolicy {
CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key)))
}
/// Sets the `path` field in the session cookie being built.
pub fn path<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().path = value.into();
self
}
/// Sets the `name` field in the session cookie being built.
pub fn name<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().name = value.into();
self
}
/// Sets the `domain` field in the session cookie being built.
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
self
}
/// Sets the `secure` field in the session cookie being built.
///
/// If the `secure` field is set, a cookie will only be transmitted when the
/// connection is secure - i.e. `https`
pub fn secure(mut self, value: bool) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().secure = value;
self
}
/// Sets the `max-age` field in the session cookie being built.
pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self
}
}
impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
type Identity = CookieIdentity;
type Future = FutureResult<CookieIdentity, Error>;
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::Future {
let identity = self.0.load(req);
FutOk(CookieIdentity {
identity,
changed: false,
inner: Rc::clone(&self.0),
})
}
}

View File

@ -1,7 +1,7 @@
//! Request logging middleware
use std::collections::HashSet;
use std::env;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::fmt::{self, Display, Formatter};
use libc;
use regex::Regex;
@ -14,6 +14,7 @@ use httpresponse::HttpResponse;
use middleware::{Finished, Middleware, Started};
/// `Middleware` for logging request and response info to the terminal.
///
/// `Logger` middleware uses standard log crate to log information. You should
/// enable logger for `actix_web` package to see access log.
/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar)
@ -73,6 +74,7 @@ use middleware::{Finished, Middleware, Started};
///
pub struct Logger {
format: Format,
exclude: HashSet<String>,
}
impl Logger {
@ -80,8 +82,15 @@ impl Logger {
pub fn new(format: &str) -> Logger {
Logger {
format: Format::new(format),
exclude: HashSet::new(),
}
}
/// Ignore and do not log access info for specified path.
pub fn exclude<T: Into<String>>(mut self, path: T) -> Self {
self.exclude.insert(path.into());
self
}
}
impl Default for Logger {
@ -93,6 +102,7 @@ impl Default for Logger {
fn default() -> Logger {
Logger {
format: Format::default(),
exclude: HashSet::new(),
}
}
}
@ -101,21 +111,23 @@ struct StartTime(time::Tm);
impl Logger {
fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) {
let entry_time = req.extensions().get::<StartTime>().unwrap().0;
let render = |fmt: &mut Formatter| {
for unit in &self.format.0 {
unit.render(fmt, req, resp, entry_time)?;
}
Ok(())
};
info!("{}", FormatDisplay(&render));
if let Some(entry_time) = req.extensions().get::<StartTime>() {
let render = |fmt: &mut Formatter| {
for unit in &self.format.0 {
unit.render(fmt, req, resp, entry_time.0)?;
}
Ok(())
};
info!("{}", FormatDisplay(&render));
}
}
}
impl<S> Middleware<S> for Logger {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
req.extensions().insert(StartTime(time::now()));
if !self.exclude.contains(req.path()) {
req.extensions_mut().insert(StartTime(time::now()));
}
Ok(Started::Done)
}

View File

@ -12,18 +12,16 @@ pub mod csrf;
mod defaultheaders;
mod errhandlers;
#[cfg(feature = "session")]
mod session;
pub mod identity;
#[cfg(feature = "session")]
pub mod session;
pub use self::defaultheaders::DefaultHeaders;
pub use self::errhandlers::ErrorHandlers;
pub use self::logger::Logger;
#[cfg(feature = "session")]
pub use self::session::{CookieSessionBackend, CookieSessionError, RequestSession,
Session, SessionBackend, SessionImpl, SessionStorage};
/// Middleware start result
pub enum Started {
/// Execution completed
/// Middleware is completed, continue to next middleware
Done,
/// New http response got generated. If middleware generates response
/// handler execution halts.
@ -60,7 +58,7 @@ pub trait Middleware<S>: 'static {
/// Method is called when handler returns response,
/// but before sending http message to peer.
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
Ok(Response::Done(resp))
}

View File

@ -1,13 +1,80 @@
//! User sessions.
//!
//! Actix provides a general solution for session management. The
//! [**SessionStorage**](struct.SessionStorage.html)
//! middleware can be used with different backend types to store session
//! data in different backends.
//!
//! By default, only cookie session backend is implemented. Other
//! backend implementations can be added.
//!
//! [**CookieSessionBackend**](struct.CookieSessionBackend.html)
//! uses cookies as session storage. `CookieSessionBackend` creates sessions
//! which are limited to storing fewer than 4000 bytes of data, as the payload
//! must fit into a single cookie. An internal server error is generated if a
//! session contains more than 4000 bytes.
//!
//! A cookie may have a security policy of *signed* or *private*. Each has
//! a respective `CookieSessionBackend` constructor.
//!
//! A *signed* cookie may be viewed but not modified by the client. A *private*
//! cookie may neither be viewed nor modified by the client.
//!
//! The constructors take a key as an argument. This is the private key
//! for cookie session - when this value is changed, all session data is lost.
//!
//! In general, you create a `SessionStorage` middleware and initialize it
//! with specific backend implementation, such as a `CookieSessionBackend`.
//! To access session data,
//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session)
//! must be used. This method returns a
//! [*Session*](struct.Session.html) object, which allows us to get or set
//! session data.
//!
//! ```rust
//! # extern crate actix;
//! # extern crate actix_web;
//! use actix_web::{server, App, HttpRequest, Result};
//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
//!
//! fn index(req: HttpRequest) -> Result<&'static str> {
//! // access session data
//! if let Some(count) = req.session().get::<i32>("counter")? {
//! println!("SESSION value: {}", count);
//! req.session().set("counter", count+1)?;
//! } else {
//! req.session().set("counter", 1)?;
//! }
//!
//! Ok("Welcome!")
//! }
//!
//! fn main() {
//! 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;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::Arc;
use cookie::{Cookie, CookieJar, Key};
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
use futures::Future;
use futures::future::{FutureResult, err as FutErr, ok as FutOk};
use http::header::{self, HeaderValue};
use serde::{Deserialize, Serialize};
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
use serde_json::error::Error as JsonError;
use time::Duration;
@ -21,7 +88,7 @@ use middleware::{Middleware, Response, Started};
///
/// ```rust
/// use actix_web::*;
/// use actix_web::middleware::RequestSession;
/// use actix_web::middleware::session::RequestSession;
///
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data
@ -36,17 +103,15 @@ use middleware::{Middleware, Response, Started};
/// # fn main() {}
/// ```
pub trait RequestSession {
fn session(&mut self) -> Session;
fn session(&self) -> Session;
}
impl<S> RequestSession for HttpRequest<S> {
fn session(&mut self) -> Session {
if let Some(s_impl) = self.extensions().get_mut::<Arc<SessionImplBox>>() {
if let Some(s) = Arc::get_mut(s_impl) {
return Session(s.0.as_mut());
}
fn session(&self) -> Session {
if let Some(s_impl) = self.extensions().get::<Arc<SessionImplCell>>() {
return Session(SessionInner::Session(Arc::clone(&s_impl)));
}
Session(unsafe { &mut DUMMY })
Session(SessionInner::None)
}
}
@ -58,7 +123,7 @@ impl<S> RequestSession for HttpRequest<S> {
///
/// ```rust
/// use actix_web::*;
/// use actix_web::middleware::RequestSession;
/// use actix_web::middleware::session::RequestSession;
///
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data
@ -72,41 +137,65 @@ impl<S> RequestSession for HttpRequest<S> {
/// }
/// # fn main() {}
/// ```
pub struct Session<'a>(&'a mut SessionImpl);
pub struct Session(SessionInner);
impl<'a> Session<'a> {
enum SessionInner {
Session(Arc<SessionImplCell>),
None,
}
impl Session {
/// Get a `value` from the session.
pub fn get<T: Deserialize<'a>>(&'a self, key: &str) -> Result<Option<T>> {
if let Some(s) = self.0.get(key) {
Ok(Some(serde_json::from_str(s)?))
} else {
Ok(None)
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
match self.0 {
SessionInner::Session(ref sess) => {
if let Some(s) = sess.as_ref().0.borrow().get(key) {
Ok(Some(serde_json::from_str(s)?))
} else {
Ok(None)
}
}
SessionInner::None => Ok(None),
}
}
/// Set a `value` from the session.
pub fn set<T: Serialize>(&mut self, key: &str, value: T) -> Result<()> {
self.0.set(key, serde_json::to_string(&value)?);
Ok(())
pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<()> {
match self.0 {
SessionInner::Session(ref sess) => {
sess.as_ref()
.0
.borrow_mut()
.set(key, serde_json::to_string(&value)?);
Ok(())
}
SessionInner::None => Ok(()),
}
}
/// Remove value from the session.
pub fn remove(&'a mut self, key: &str) {
self.0.remove(key)
pub fn remove(&self, key: &str) {
match self.0 {
SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().remove(key),
SessionInner::None => (),
}
}
/// Clear the session.
pub fn clear(&'a mut self) {
self.0.clear()
pub fn clear(&self) {
match self.0 {
SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().clear(),
SessionInner::None => (),
}
}
}
struct SessionImplBox(Box<SessionImpl>);
struct SessionImplCell(RefCell<Box<SessionImpl>>);
#[doc(hidden)]
unsafe impl Send for SessionImplBox {}
unsafe impl Send for SessionImplCell {}
#[doc(hidden)]
unsafe impl Sync for SessionImplBox {}
unsafe impl Sync for SessionImplCell {}
/// Session storage middleware
///
@ -114,7 +203,7 @@ unsafe impl Sync for SessionImplBox {}
/// # extern crate actix;
/// # extern crate actix_web;
/// use actix_web::App;
/// use actix_web::middleware::{SessionStorage, CookieSessionBackend};
/// use actix_web::middleware::session::{SessionStorage, CookieSessionBackend};
///
/// fn main() {
/// let app = App::new().middleware(
@ -141,8 +230,9 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
.from_request(&mut req)
.then(move |res| match res {
Ok(sess) => {
req.extensions()
.insert(Arc::new(SessionImplBox(Box::new(sess))));
req.extensions_mut().insert(Arc::new(SessionImplCell(
RefCell::new(Box::new(sess)),
)));
FutOk(None)
}
Err(err) => FutErr(err),
@ -151,10 +241,10 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
}
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
if let Some(s_box) = req.extensions().remove::<Arc<SessionImplBox>>() {
s_box.0.write(resp)
if let Some(s_box) = req.extensions_mut().remove::<Arc<SessionImplCell>>() {
s_box.0.borrow_mut().write(resp)
} else {
Ok(Response::Done(resp))
}
@ -186,23 +276,6 @@ pub trait SessionBackend<S>: Sized + 'static {
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::ReadFuture;
}
/// Dummy session impl, does not do anything
struct DummySessionImpl;
static mut DUMMY: DummySessionImpl = DummySessionImpl;
impl SessionImpl for DummySessionImpl {
fn get(&self, _: &str) -> Option<&str> {
None
}
fn set(&mut self, _: &str, _: String) {}
fn remove(&mut self, _: &str) {}
fn clear(&mut self) {}
fn write(&self, resp: HttpResponse) -> Result<Response> {
Ok(Response::Done(resp))
}
}
/// Session that uses signed cookies as session storage
pub struct CookieSession {
changed: bool,
@ -284,7 +357,7 @@ impl CookieSessionInner {
}
fn set_cookie(
&self, resp: &mut HttpResponse, state: &HashMap<String, String>
&self, resp: &mut HttpResponse, state: &HashMap<String, String>,
) -> Result<()> {
let value =
serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?;
@ -372,7 +445,7 @@ impl CookieSessionInner {
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::CookieSessionBackend;
/// use actix_web::middleware::session::CookieSessionBackend;
///
/// # fn main() {
/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32])
@ -452,3 +525,30 @@ impl<S> SessionBackend<S> for CookieSessionBackend {
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use application::App;
use test;
#[test]
fn cookie_session() {
let mut srv = test::TestServer::with_factory(|| {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
))
.resource("/", |r| {
r.f(|req| {
let _ = req.session().set("counter", 100);
"test"
})
})
});
let request = srv.get().uri(srv.url("/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.cookie("actix-session").is_some());
}
}

View File

@ -7,8 +7,8 @@ use std::{cmp, fmt};
use bytes::Bytes;
use futures::task::{current as current_task, Task};
use futures::{Async, Poll, Stream};
use http::HttpTryFrom;
use http::header::{self, HeaderMap, HeaderName, HeaderValue};
use http::HttpTryFrom;
use httparse;
use mime;
@ -168,7 +168,7 @@ where
}
fn read_boundary(
payload: &mut PayloadHelper<S>, boundary: &str
payload: &mut PayloadHelper<S>, boundary: &str,
) -> Poll<bool, MultipartError> {
// TODO: need to read epilogue
match payload.readline()? {
@ -192,7 +192,7 @@ where
}
fn skip_until_boundary(
payload: &mut PayloadHelper<S>, boundary: &str
payload: &mut PayloadHelper<S>, boundary: &str,
) -> Poll<bool, MultipartError> {
let mut eof = false;
loop {
@ -230,7 +230,7 @@ where
}
fn poll(
&mut self, safety: &Safety
&mut self, safety: &Safety,
) -> Poll<Option<MultipartItem<S>>, MultipartError> {
if self.state == InnerState::Eof {
Ok(Async::Ready(None))
@ -450,7 +450,7 @@ where
S: Stream<Item = Bytes, Error = PayloadError>,
{
fn new(
payload: PayloadRef<S>, boundary: String, headers: &HeaderMap
payload: PayloadRef<S>, boundary: String, headers: &HeaderMap,
) -> Result<InnerField<S>, PayloadError> {
let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
@ -477,7 +477,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 PayloadHelper<S>, size: &mut u64
payload: &mut PayloadHelper<S>, size: &mut u64,
) -> Poll<Option<Bytes>, MultipartError> {
if *size == 0 {
Ok(Async::Ready(None))
@ -502,7 +502,7 @@ 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 PayloadHelper<S>, boundary: &str
payload: &mut PayloadHelper<S>, boundary: &str,
) -> Poll<Option<Bytes>, MultipartError> {
match payload.read_until(b"\r")? {
Async::NotReady => Ok(Async::NotReady),

View File

@ -42,6 +42,22 @@ impl<'a> Params<'a> {
self.0.push((name.into(), value.into()));
}
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));
}
/// Check if there are any matched patterns
pub fn is_empty(&self) -> bool {
self.0.is_empty()

View File

@ -11,7 +11,7 @@ use application::Inner;
use body::{Body, BodyStream};
use context::{ActorHttpContext, Frame};
use error::Error;
use handler::{Reply, ReplyItem};
use handler::{AsyncResult, AsyncResultItem};
use header::ContentEncoding;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
@ -28,7 +28,9 @@ pub(crate) enum HandlerType {
pub(crate) trait PipelineHandler<S> {
fn encoding(&self) -> ContentEncoding;
fn handle(&mut self, req: HttpRequest<S>, htype: HandlerType) -> Reply;
fn handle(
&mut self, req: HttpRequest<S>, htype: HandlerType,
) -> AsyncResult<HttpResponse>;
}
pub(crate) struct Pipeline<S, H>(PipelineInfo<S>, PipelineState<S, H>);
@ -67,7 +69,7 @@ impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> {
}
struct PipelineInfo<S> {
req: HttpRequest<S>,
req: UnsafeCell<HttpRequest<S>>,
count: u16,
mws: Rc<Vec<Box<Middleware<S>>>>,
context: Option<Box<ActorHttpContext>>,
@ -79,7 +81,7 @@ struct PipelineInfo<S> {
impl<S> PipelineInfo<S> {
fn new(req: HttpRequest<S>) -> PipelineInfo<S> {
PipelineInfo {
req,
req: UnsafeCell::new(req),
count: 0,
mws: Rc::new(Vec::new()),
error: None,
@ -89,11 +91,17 @@ impl<S> PipelineInfo<S> {
}
}
#[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 {
mem::transmute(&self.req)
&mut *self.req.get()
}
}
@ -116,8 +124,8 @@ impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
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,
@ -159,7 +167,7 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
let info: &mut PipelineInfo<_> = unsafe { mem::transmute(&mut self.0) };
let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) };
loop {
if self.1.is_response() {
@ -197,7 +205,7 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
}
fn poll(&mut self) -> Poll<(), Error> {
let info: &mut PipelineInfo<_> = unsafe { mem::transmute(&mut self.0) };
let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) };
loop {
match self.1 {
@ -228,38 +236,29 @@ struct StartMiddlewares<S, H> {
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
fn init(
info: &mut PipelineInfo<S>, hnd: Rc<UnsafeCell<H>>, htype: HandlerType
info: &mut PipelineInfo<S>, hnd: Rc<UnsafeCell<H>>, htype: HandlerType,
) -> PipelineState<S, H> {
// execute middlewares, we need this stage because middlewares could be
// non-async and we can move to next state immediately
let len = info.mws.len() as u16;
loop {
if info.count == len {
let reply = unsafe { &mut *hnd.get() }.handle(info.req.clone(), htype);
let reply = unsafe { &mut *hnd.get() }.handle(info.req().clone(), htype);
return WaitingResponse::init(info, reply);
} else {
match info.mws[info.count as usize].start(&mut 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, resp)
}
Ok(Started::Future(mut fut)) => match fut.poll() {
Ok(Async::NotReady) => {
return PipelineState::Starting(StartMiddlewares {
hnd,
htype,
fut: Some(fut),
_s: PhantomData,
})
}
Ok(Async::Ready(resp)) => {
if let Some(resp) = resp {
return RunMiddlewares::init(info, resp);
}
info.count += 1;
}
Err(err) => return ProcessResponse::init(err.into()),
},
Ok(Started::Future(fut)) => {
return PipelineState::Starting(StartMiddlewares {
hnd,
htype,
fut: Some(fut),
_s: PhantomData,
})
}
Err(err) => return ProcessResponse::init(err.into()),
}
}
@ -278,7 +277,7 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
}
if info.count == len {
let reply = unsafe { &mut *self.hnd.get() }
.handle(info.req.clone(), self.htype);
.handle(info.req().clone(), self.htype);
return Some(WaitingResponse::init(info, reply));
} else {
loop {
@ -313,10 +312,13 @@ struct WaitingResponse<S, H> {
impl<S: 'static, H> WaitingResponse<S, H> {
#[inline]
fn init(info: &mut PipelineInfo<S>, reply: Reply) -> PipelineState<S, H> {
fn init(
info: &mut PipelineInfo<S>, reply: AsyncResult<HttpResponse>,
) -> PipelineState<S, H> {
match reply.into() {
ReplyItem::Message(resp) => RunMiddlewares::init(info, resp),
ReplyItem::Future(fut) => PipelineState::Handler(WaitingResponse {
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse {
fut,
_s: PhantomData,
_h: PhantomData,
@ -328,7 +330,7 @@ impl<S: 'static, H> WaitingResponse<S, H> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
Err(err) => Some(ProcessResponse::init(err.into())),
Err(err) => Some(RunMiddlewares::init(info, err.into())),
}
}
}
@ -462,7 +464,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
}
fn poll_io(
mut self, io: &mut Writer, info: &mut PipelineInfo<S>
mut self, io: &mut Writer, info: &mut PipelineInfo<S>,
) -> Result<PipelineState<S, H>, PipelineState<S, H>> {
loop {
if self.drain.is_none() && self.running != RunningState::Paused {
@ -482,8 +484,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info,
self.resp,
info, self.resp,
));
}
};
@ -491,8 +492,8 @@ impl<S: 'static, H> ProcessResponse<S, H> {
if let Some(err) = self.resp.error() {
if self.resp.status().is_server_error() {
error!(
"Error occured during request handling: {}",
err
"Error occured during request handling, status: {} {}",
self.resp.status(), err
);
} else {
warn!(
@ -525,8 +526,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
if let Err(err) = io.write_eof() {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info,
self.resp,
info, self.resp,
));
}
break;
@ -537,8 +537,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info,
self.resp,
info, self.resp,
));
}
Ok(result) => result,
@ -572,8 +571,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info,
self.resp,
info, self.resp,
),
);
}
@ -585,8 +583,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info,
self.resp,
info, self.resp,
),
);
}
@ -611,8 +608,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Err(err) => {
info.error = Some(err);
return Ok(FinishingMiddlewares::init(
info,
self.resp,
info, self.resp,
));
}
}
@ -717,8 +713,11 @@ impl<S: 'static, H> FinishingMiddlewares<S, H> {
return None;
}
self.fut = None;
info.count -= 1;
if info.count == 0 {
return Some(Completed::init(info));
}
info.count -= 1;
match info.mws[info.count as usize].finish(info.req_mut(), &self.resp) {
Finished::Done => {
if info.count == 0 {

View File

@ -171,7 +171,7 @@ pub fn Method<S: 'static>(method: http::Method) -> MethodPredicate<S> {
/// Return predicate that matches if request contains specified header and
/// value.
pub fn Header<S: 'static>(
name: &'static str, value: &'static str
name: &'static str, value: &'static str,
) -> HeaderPredicate<S> {
HeaderPredicate(
header::HeaderName::try_from(name).unwrap(),

View File

@ -1,10 +1,12 @@
use std::marker::PhantomData;
use std::rc::Rc;
use futures::Future;
use http::{Method, StatusCode};
use smallvec::SmallVec;
use handler::{FromRequest, Handler, Reply, Responder};
use error::Error;
use handler::{AsyncResult, FromRequest, Handler, Responder};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::Middleware;
@ -183,10 +185,32 @@ impl<S: 'static> ResourceHandler<S> {
self.routes.last_mut().unwrap().with(handler);
}
/// Register a new route and add async handler.
///
/// This is shortcut for:
///
/// ```rust,ignore
/// Application::resource("/", |r| r.route().with_async(index)
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.routes.push(Route::default());
self.routes.last_mut().unwrap().with_async(handler);
}
/// Register a resource middleware
///
/// This is similar to `App's` middlewares, but
/// middlewares get invoked on resource level.
///
/// *Note* `Middleware::finish()` fires right after response get
/// prepared. It does not wait until body get sent to peer.
pub fn middleware<M: Middleware<S>>(&mut self, mw: M) {
Rc::get_mut(&mut self.middlewares)
.unwrap()
@ -194,8 +218,8 @@ impl<S: 'static> ResourceHandler<S> {
}
pub(crate) fn handle(
&mut self, mut req: HttpRequest<S>, default: Option<&mut ResourceHandler<S>>
) -> Reply {
&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() {
@ -208,7 +232,7 @@ impl<S: 'static> ResourceHandler<S> {
if let Some(resource) = default {
resource.handle(req, None)
} else {
Reply::response(HttpResponse::new(StatusCode::NOT_FOUND))
AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND))
}
}
}

View File

@ -1,18 +1,19 @@
use futures::{Async, Future, Poll};
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::mem;
use std::rc::Rc;
use futures::{Async, Future, Poll};
use error::Error;
use handler::{AsyncHandler, FromRequest, Handler, Reply, ReplyItem, Responder,
RouteHandler, WrapHandler};
use handler::{AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler,
Responder, RouteHandler, WrapHandler};
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response as MiddlewareResponse,
Started as MiddlewareStarted};
use middleware::{Finished as MiddlewareFinished, Middleware,
Response as MiddlewareResponse, Started as MiddlewareStarted};
use pred::Predicate;
use with::{ExtractorConfig, With, With2, With3};
use with::{ExtractorConfig, With, With2, With3, WithAsync};
/// Resource route definition
///
@ -44,15 +45,15 @@ impl<S: 'static> Route<S> {
}
#[inline]
pub(crate) fn handle(&mut self, req: HttpRequest<S>) -> Reply {
pub(crate) fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
self.handler.handle(req)
}
#[inline]
pub(crate) fn compose(
&mut self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>
) -> Reply {
Reply::async(Compose::new(req, mws, self.handler.clone()))
&mut self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
) -> AsyncResult<HttpResponse> {
AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
}
/// Add match predicate to route.
@ -103,7 +104,7 @@ impl<S: 'static> Route<S> {
self.handler = InnerHandler::async(handler);
}
/// Set handler function, use request extractor for paramters.
/// Set handler function, use request extractor for parameters.
///
/// ```rust
/// # extern crate bytes;
@ -128,6 +129,34 @@ impl<S: 'static> Route<S> {
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// }
/// ```
///
/// It is possible to use tuples for specifing multiple extractors for one
/// handler function.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// # use std::collections::HashMap;
/// use actix_web::{http, App, Query, Path, Result, Json};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>)) -> Result<String> {
/// Ok(format!("Welcome {}!", info.0.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// }
/// ```
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
where
F: Fn(T) -> R + 'static,
@ -139,7 +168,50 @@ impl<S: 'static> Route<S> {
cfg
}
/// Set handler function, use request extractor for both paramters.
/// Set async handler function, use request extractor for parameters.
/// Also this method needs to be used if your handler function returns
/// `impl Future<>`
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Error, http};
/// use futures::Future;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Path<Info>) -> Box<Future<Item=&'static str, Error=Error>> {
/// unimplemented!()
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET)
/// .with_async(index)); // <- use `with` extractor
/// }
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F) -> ExtractorConfig<S, T>
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
let cfg = ExtractorConfig::default();
self.h(WithAsync::new(handler, Clone::clone(&cfg)));
cfg
}
#[doc(hidden)]
/// Set handler function, use request extractor for both parameters.
///
/// ```rust
/// # extern crate bytes;
@ -170,7 +242,7 @@ impl<S: 'static> Route<S> {
/// }
/// ```
pub fn with2<T1, T2, F, R>(
&mut self, handler: F
&mut self, handler: F,
) -> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>)
where
F: Fn(T1, T2) -> R + 'static,
@ -188,9 +260,10 @@ impl<S: 'static> Route<S> {
(cfg1, cfg2)
}
/// Set handler function, use request extractor for all paramters.
#[doc(hidden)]
/// Set handler function, use request extractor for all parameters.
pub fn with3<T1, T2, T3, F, R>(
&mut self, handler: F
&mut self, handler: F,
) -> (
ExtractorConfig<S, T1>,
ExtractorConfig<S, T2>,
@ -218,12 +291,14 @@ impl<S: 'static> Route<S> {
/// `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]
@ -234,16 +309,15 @@ impl<S: 'static> InnerHandler<S> {
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>) -> Reply {
// reason: handler is unique per thread,
// handler get called from async code only
#[allow(mutable_transmutes)]
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
let h: &mut Box<RouteHandler<S>> = unsafe { mem::transmute(self.0.as_ref()) };
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)
}
}
@ -272,7 +346,8 @@ enum ComposeState<S: 'static> {
Starting(StartMiddlewares<S>),
Handler(WaitingResponse<S>),
RunMiddlewares(RunMiddlewares<S>),
Response(Response<S>),
Finishing(FinishingMiddlewares<S>),
Completed(Response<S>),
}
impl<S: 'static> ComposeState<S> {
@ -281,14 +356,15 @@ impl<S: 'static> ComposeState<S> {
ComposeState::Starting(ref mut state) => state.poll(info),
ComposeState::Handler(ref mut state) => state.poll(info),
ComposeState::RunMiddlewares(ref mut state) => state.poll(info),
ComposeState::Response(_) => None,
ComposeState::Finishing(ref mut state) => state.poll(info),
ComposeState::Completed(_) => None,
}
}
}
impl<S: 'static> Compose<S> {
fn new(
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: InnerHandler<S>
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: InnerHandler<S>,
) -> Self {
let mut info = ComposeInfo {
count: 0,
@ -308,7 +384,7 @@ impl<S> Future for Compose<S> {
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
if let ComposeState::Response(ref mut resp) = self.state {
if let ComposeState::Completed(ref mut resp) = self.state {
let resp = resp.resp.take().unwrap();
return Ok(Async::Ready(resp));
}
@ -342,22 +418,13 @@ impl<S: 'static> StartMiddlewares<S> {
Ok(MiddlewareStarted::Response(resp)) => {
return RunMiddlewares::init(info, resp)
}
Ok(MiddlewareStarted::Future(mut fut)) => match fut.poll() {
Ok(Async::NotReady) => {
return ComposeState::Starting(StartMiddlewares {
fut: Some(fut),
_s: PhantomData,
})
}
Ok(Async::Ready(resp)) => {
if let Some(resp) = resp {
return RunMiddlewares::init(info, resp);
}
info.count += 1;
}
Err(err) => return Response::init(err.into()),
},
Err(err) => return Response::init(err.into()),
Ok(MiddlewareStarted::Future(fut)) => {
return ComposeState::Starting(StartMiddlewares {
fut: Some(fut),
_s: PhantomData,
})
}
Err(err) => return FinishingMiddlewares::init(info, err.into()),
}
}
}
@ -387,12 +454,17 @@ impl<S: 'static> StartMiddlewares<S> {
self.fut = Some(fut);
continue 'outer;
}
Err(err) => return Some(Response::init(err.into())),
Err(err) => {
return Some(FinishingMiddlewares::init(
info,
err.into(),
))
}
}
}
}
}
Err(err) => return Some(Response::init(err.into())),
Err(err) => return Some(FinishingMiddlewares::init(info, err.into())),
}
}
}
@ -406,10 +478,13 @@ struct WaitingResponse<S> {
impl<S: 'static> WaitingResponse<S> {
#[inline]
fn init(info: &mut ComposeInfo<S>, reply: Reply) -> ComposeState<S> {
fn init(
info: &mut ComposeInfo<S>, reply: AsyncResult<HttpResponse>,
) -> ComposeState<S> {
match reply.into() {
ReplyItem::Message(resp) => RunMiddlewares::init(info, resp),
ReplyItem::Future(fut) => ComposeState::Handler(WaitingResponse {
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse {
fut,
_s: PhantomData,
}),
@ -420,7 +495,7 @@ impl<S: 'static> WaitingResponse<S> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
Err(err) => Some(Response::init(err.into())),
Err(err) => Some(RunMiddlewares::init(info, err.into())),
}
}
}
@ -441,12 +516,12 @@ impl<S: 'static> RunMiddlewares<S> {
resp = match info.mws[curr].response(&mut info.req, resp) {
Err(err) => {
info.count = curr + 1;
return Response::init(err.into());
return FinishingMiddlewares::init(info, err.into());
}
Ok(MiddlewareResponse::Done(r)) => {
curr += 1;
if curr == len {
return Response::init(r);
return FinishingMiddlewares::init(info, r);
} else {
r
}
@ -473,15 +548,17 @@ impl<S: 'static> RunMiddlewares<S> {
self.curr += 1;
resp
}
Err(err) => return Some(Response::init(err.into())),
Err(err) => return Some(FinishingMiddlewares::init(info, err.into())),
};
loop {
if self.curr == len {
return Some(Response::init(resp));
return Some(FinishingMiddlewares::init(info, resp));
} else {
match info.mws[self.curr].response(&mut info.req, resp) {
Err(err) => return Some(Response::init(err.into())),
Err(err) => {
return Some(FinishingMiddlewares::init(info, err.into()))
}
Ok(MiddlewareResponse::Done(r)) => {
self.curr += 1;
resp = r
@ -497,6 +574,71 @@ impl<S: 'static> RunMiddlewares<S> {
}
}
/// Middlewares start executor
struct FinishingMiddlewares<S> {
resp: Option<HttpResponse>,
fut: Option<Box<Future<Item = (), Error = Error>>>,
_s: PhantomData<S>,
}
impl<S: 'static> FinishingMiddlewares<S> {
fn init(info: &mut ComposeInfo<S>, resp: HttpResponse) -> ComposeState<S> {
if info.count == 0 {
Response::init(resp)
} else {
let mut state = FinishingMiddlewares {
resp: Some(resp),
fut: None,
_s: PhantomData,
};
if let Some(st) = state.poll(info) {
st
} else {
ComposeState::Finishing(state)
}
}
}
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
loop {
// poll latest fut
let not_ready = if let Some(ref mut fut) = self.fut {
match fut.poll() {
Ok(Async::NotReady) => true,
Ok(Async::Ready(())) => false,
Err(err) => {
error!("Middleware finish error: {}", err);
false
}
}
} else {
false
};
if not_ready {
return None;
}
self.fut = None;
if info.count == 0 {
return Some(Response::init(self.resp.take().unwrap()));
}
info.count -= 1;
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()));
}
}
MiddlewareFinished::Future(fut) => {
self.fut = Some(fut);
}
}
}
}
}
struct Response<S> {
resp: Option<HttpResponse>,
_s: PhantomData<S>,
@ -504,7 +646,7 @@ struct Response<S> {
impl<S: 'static> Response<S> {
fn init(resp: HttpResponse) -> ComposeState<S> {
ComposeState::Response(Response {
ComposeState::Completed(Response {
resp: Some(resp),
_s: PhantomData,
})

View File

@ -1,9 +1,7 @@
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::mem;
use std::rc::Rc;
use percent_encoding::percent_decode;
use regex::{escape, Regex};
use error::UrlGenerationError;
@ -80,15 +78,13 @@ impl Router {
if self.0.prefix_len > req.path().len() {
return None;
}
let path: &str = unsafe { mem::transmute(&req.path()[self.0.prefix_len..]) };
let path = unsafe { &*(&req.path()[self.0.prefix_len..] as *const str) };
let route_path = if path.is_empty() { "/" } else { path };
let p = percent_decode(route_path.as_bytes())
.decode_utf8()
.unwrap();
for (idx, pattern) in self.0.patterns.iter().enumerate() {
if pattern.match_with_params(p.as_ref(), req.match_info_mut()) {
if pattern.match_with_params(route_path, req.match_info_mut()) {
req.set_resource(idx);
req.set_prefix_len(self.0.prefix_len as u16);
return Some(idx);
}
}
@ -117,7 +113,7 @@ impl Router {
/// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method.
/// url_for) for detailed information.
pub fn resource_path<U, I>(
&self, name: &str, elements: U
&self, name: &str, elements: U,
) -> Result<String, UrlGenerationError>
where
U: IntoIterator<Item = I>,
@ -146,7 +142,8 @@ enum PatternElement {
#[derive(Clone, Debug)]
enum PatternType {
Static(String),
Dynamic(Regex, Vec<String>),
Prefix(String),
Dynamic(Regex, Vec<String>, usize),
}
#[derive(Debug, Copy, Clone, PartialEq)]
@ -154,7 +151,7 @@ enum PatternType {
pub enum ResourceType {
/// Normal resource
Normal,
/// Resource for applicaiton default handler
/// Resource for application default handler
Default,
/// External resource
External,
@ -162,7 +159,7 @@ pub enum ResourceType {
Unset,
}
/// Reslource type describes an entry in resources table
/// Resource type describes an entry in resources table
#[derive(Clone)]
pub struct Resource {
tp: PatternType,
@ -177,14 +174,23 @@ impl Resource {
///
/// Panics if path pattern is wrong.
pub fn new(name: &str, path: &str) -> Self {
Resource::with_prefix(name, path, "/")
Resource::with_prefix(name, path, "/", false)
}
/// Parse path pattern and create new `Resource` instance.
///
/// Use `prefix` type instead of `static`.
///
/// Panics if path regex pattern is wrong.
pub fn prefix(name: &str, path: &str) -> Self {
Resource::with_prefix(name, path, "/", true)
}
/// Construct external resource
///
/// Panics if path pattern is wrong.
pub fn external(name: &str, path: &str) -> Self {
let mut resource = Resource::with_prefix(name, path, "/");
let mut resource = Resource::with_prefix(name, path, "/", false);
resource.rtp = ResourceType::External;
resource
}
@ -201,8 +207,9 @@ impl Resource {
}
/// Parse path pattern and create new `Resource` instance with custom prefix
pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self {
let (pattern, elements, is_dynamic) = Resource::parse(path, prefix);
pub fn with_prefix(name: &str, path: &str, prefix: &str, for_prefix: bool) -> Self {
let (pattern, elements, is_dynamic, len) =
Resource::parse(path, prefix, for_prefix);
let tp = if is_dynamic {
let re = match Regex::new(&pattern) {
@ -212,7 +219,9 @@ impl Resource {
let names = re.capture_names()
.filter_map(|name| name.map(|name| name.to_owned()))
.collect();
PatternType::Dynamic(re, names)
PatternType::Dynamic(re, names, len)
} else if for_prefix {
PatternType::Prefix(pattern.clone())
} else {
PatternType::Static(pattern.clone())
};
@ -244,16 +253,17 @@ impl Resource {
pub fn is_match(&self, path: &str) -> bool {
match self.tp {
PatternType::Static(ref s) => s == path,
PatternType::Dynamic(ref re, _) => re.is_match(path),
PatternType::Dynamic(ref re, _, _) => re.is_match(path),
PatternType::Prefix(ref s) => path.starts_with(s),
}
}
pub fn match_with_params<'a>(
&'a self, path: &'a str, params: &'a mut Params<'a>
&'a self, path: &'a str, params: &'a mut Params<'a>,
) -> bool {
match self.tp {
PatternType::Static(ref s) => s == path,
PatternType::Dynamic(ref re, ref names) => {
PatternType::Dynamic(ref re, ref names, _) => {
if let Some(captures) = re.captures(path) {
let mut idx = 0;
for capture in captures.iter() {
@ -269,12 +279,48 @@ impl Resource {
false
}
}
PatternType::Prefix(ref s) => path.starts_with(s),
}
}
/// Build reousrce path.
pub fn match_prefix_with_params<'a>(
&'a self, path: &'a str, params: &'a mut Params<'a>,
) -> Option<usize> {
match self.tp {
PatternType::Static(ref s) => if s == path {
Some(s.len())
} else {
None
},
PatternType::Dynamic(ref re, ref names, len) => {
if let Some(captures) = re.captures(path) {
let mut idx = 0;
let mut pos = 0;
for capture in captures.iter() {
if let Some(ref m) = capture {
if idx != 0 {
params.add(names[idx - 1].as_str(), m.as_str());
}
idx += 1;
pos = m.end();
}
}
Some(pos + len)
} else {
None
}
}
PatternType::Prefix(ref s) => if path.starts_with(s) {
Some(s.len())
} else {
None
},
}
}
/// Build resource path.
pub fn resource_path<U, I>(
&self, router: &Router, elements: U
&self, router: &Router, elements: U,
) -> Result<String, UrlGenerationError>
where
U: IntoIterator<Item = I>,
@ -301,7 +347,9 @@ impl Resource {
Ok(path)
}
fn parse(pattern: &str, prefix: &str) -> (String, Vec<PatternElement>, bool) {
fn parse(
pattern: &str, prefix: &str, for_prefix: bool,
) -> (String, Vec<PatternElement>, bool, usize) {
const DEFAULT_PATTERN: &str = "[^/]+";
let mut re1 = String::from("^") + prefix;
@ -313,6 +361,7 @@ impl Resource {
let mut param_pattern = String::from(DEFAULT_PATTERN);
let mut is_dynamic = false;
let mut elems = Vec::new();
let mut len = 0;
for (index, ch) in pattern.chars().enumerate() {
// All routes must have a leading slash so its optional to have one
@ -329,6 +378,7 @@ impl Resource {
param_name.clear();
param_pattern = String::from(DEFAULT_PATTERN);
len = 0;
in_param_pattern = false;
in_param = false;
} else if ch == ':' {
@ -352,16 +402,19 @@ impl Resource {
re1.push_str(escape(&ch.to_string()).as_str());
re2.push(ch);
el.push(ch);
len += 1;
}
}
let re = if is_dynamic {
re1.push('$');
if !for_prefix {
re1.push('$');
}
re1
} else {
re2
};
(re, elems, is_dynamic)
(re, elems, is_dynamic, len)
}
}
@ -574,6 +627,41 @@ mod tests {
assert_eq!(req.match_info().get("id").unwrap(), "adahg32");
}
#[test]
fn test_resource_prefix() {
let re = Resource::prefix("test", "/name");
assert!(re.is_match("/name"));
assert!(re.is_match("/name/"));
assert!(re.is_match("/name/test/test"));
assert!(re.is_match("/name1"));
assert!(re.is_match("/name~"));
let re = Resource::prefix("test", "/name/");
assert!(re.is_match("/name/"));
assert!(re.is_match("/name/gs"));
assert!(!re.is_match("/name"));
}
#[test]
fn test_reousrce_prefix_dynamic() {
let re = Resource::prefix("test", "/{name}/");
assert!(re.is_match("/name/"));
assert!(re.is_match("/name/gs"));
assert!(!re.is_match("/name"));
let mut req = TestRequest::with_uri("/test2/").finish();
assert!(re.match_with_params("/test2/", req.match_info_mut()));
assert_eq!(&req.match_info()["name"], "test2");
let mut req =
TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish();
assert!(re.match_with_params(
"/test2/subpath1/subpath2/index.html",
req.match_info_mut()
));
assert_eq!(&req.match_info()["name"], "test2");
}
#[test]
fn test_request_resource() {
let routes = vec![

1038
src/scope.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
use std::net::{Shutdown, SocketAddr};
use std::rc::Rc;
use std::{io, mem, ptr, time};
use std::{io, ptr, time};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use futures::{Async, Future, Poll};
use tokio_io::{AsyncRead, AsyncWrite};
use super::settings::WorkerSettings;
use super::{utils, HttpHandler, IoStream, h1, h2};
use super::{h1, h2, utils, HttpHandler, IoStream};
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
@ -112,7 +112,9 @@ where
match result {
Ok(Async::Ready(())) | Err(_) => {
h1.settings().remove_channel();
self.node.as_mut().map(|n| n.remove());
if let Some(n) = self.node.as_mut() {
n.remove()
};
}
_ => (),
}
@ -123,7 +125,9 @@ where
match result {
Ok(Async::Ready(())) | Err(_) => {
h2.settings().remove_channel();
self.node.as_mut().map(|n| n.remove());
if let Some(n) = self.node.as_mut() {
n.remove()
};
}
_ => (),
}
@ -139,7 +143,9 @@ where
Ok(Async::Ready(0)) | Err(_) => {
debug!("Ignored premature client disconnection");
settings.remove_channel();
self.node.as_mut().map(|n| n.remove());
if let Some(n) = self.node.as_mut() {
n.remove()
};
return Err(());
}
_ => (),
@ -163,10 +169,7 @@ where
match kind {
ProtocolKind::Http1 => {
self.proto = Some(HttpProtocol::H1(h1::Http1::new(
settings,
io,
addr,
buf,
settings, io, addr, buf,
)));
return self.poll();
}
@ -204,13 +207,15 @@ impl<T> Node<T> {
#[allow(mutable_transmutes)]
unsafe {
if let Some(ref next2) = self.next {
let n: &mut Node<()> = mem::transmute(next2.as_ref().unwrap());
let n: &mut Node<()> =
&mut *(next2.as_ref().unwrap() as *const _ as *mut _);
n.prev = Some(next as *const _ as *mut _);
}
let slf: &mut Node<T> = mem::transmute(self);
let slf: &mut Node<T> = &mut *(self as *const _ as *mut _);
slf.next = Some(next as *const _ as *mut _);
let next: &mut Node<T> = mem::transmute(next);
let next: &mut Node<T> = &mut *(next as *const _ as *mut _);
next.prev = Some(slf as *const _ as *mut _);
}
}
@ -246,12 +251,12 @@ impl Node<()> {
loop {
if let Some(n) = next {
unsafe {
let n: &Node<()> = mem::transmute(n.as_ref().unwrap());
let n: &Node<()> = &*(n.as_ref().unwrap() as *const _);
next = n.next.as_ref();
if !n.element.is_null() {
let ch: &mut HttpChannel<T, H> =
mem::transmute(&mut *(n.element as *mut _));
&mut *(&mut *(n.element as *mut _) as *mut () as *mut _);
ch.shutdown();
}
}

View File

@ -6,11 +6,14 @@ use std::{cmp, io, mem};
#[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::{BufMut, Bytes, BytesMut};
use flate2::Compression;
#[cfg(feature = "flate2")]
use flate2::read::GzDecoder;
#[cfg(feature = "flate2")]
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONNECTION,
CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
#[cfg(feature = "flate2")]
use flate2::Compression;
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING,
CONTENT_LENGTH, TRANSFER_ENCODING};
use http::{HttpTryFrom, Method, Version};
use body::{Binary, Body};
@ -144,7 +147,9 @@ impl PayloadWriter for EncodedPayload {
}
pub(crate) enum Decoder {
#[cfg(feature = "flate2")]
Deflate(Box<DeflateDecoder<Writer>>),
#[cfg(feature = "flate2")]
Gzip(Option<Box<GzDecoder<Wrapper>>>),
#[cfg(feature = "brotli")]
Br(Box<BrotliDecoder<Writer>>),
@ -223,9 +228,11 @@ impl PayloadStream {
ContentEncoding::Br => {
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => {
Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => Decoder::Gzip(None),
_ => Decoder::Identity,
};
@ -251,6 +258,7 @@ impl PayloadStream {
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => {
if let Some(ref mut decoder) = *decoder {
decoder.as_mut().get_mut().eof = true;
@ -267,6 +275,7 @@ impl PayloadStream {
Ok(None)
}
}
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
Ok(_) => {
let b = decoder.get_mut().take();
@ -297,6 +306,7 @@ impl PayloadStream {
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => {
if decoder.is_none() {
*decoder = Some(Box::new(GzDecoder::new(Wrapper {
@ -334,6 +344,7 @@ impl PayloadStream {
}
}
}
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
@ -352,7 +363,9 @@ impl PayloadStream {
}
pub(crate) enum ContentEncoder {
#[cfg(feature = "flate2")]
Deflate(DeflateEncoder<TransferEncoding>),
#[cfg(feature = "flate2")]
Gzip(GzEncoder<TransferEncoding>),
#[cfg(feature = "brotli")]
Br(BrotliEncoder<TransferEncoding>),
@ -422,9 +435,11 @@ impl ContentEncoder {
let tmp = SharedBytes::default();
let transfer = TransferEncoding::eof(tmp.clone());
let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::fast()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
transfer,
Compression::fast(),
@ -459,9 +474,6 @@ impl ContentEncoder {
if resp.upgrade() {
if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2");
} else {
resp.headers_mut()
.insert(CONNECTION, HeaderValue::from_static("upgrade"));
}
if encoding != ContentEncoding::Identity {
encoding = ContentEncoding::Identity;
@ -481,10 +493,12 @@ impl ContentEncoder {
}
match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
transfer,
Compression::fast(),
)),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
}
@ -497,7 +511,7 @@ impl ContentEncoder {
}
fn streaming_encoding(
buf: SharedBytes, version: Version, resp: &mut HttpResponse
buf: SharedBytes, version: Version, resp: &mut HttpResponse,
) -> TransferEncoding {
match resp.chunked() {
Some(true) => {
@ -566,7 +580,9 @@ impl ContentEncoder {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(),
ContentEncoder::Identity(ref encoder) => encoder.is_eof(),
}
@ -590,6 +606,7 @@ impl ContentEncoder {
}
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
@ -598,6 +615,7 @@ impl ContentEncoder {
}
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
@ -628,6 +646,7 @@ impl ContentEncoder {
}
}
}
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => {
match encoder.write_all(data.as_ref()) {
Ok(_) => Ok(()),
@ -637,6 +656,7 @@ impl ContentEncoder {
}
}
}
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => {
match encoder.write_all(data.as_ref()) {
Ok(_) => Ok(()),

File diff suppressed because it is too large Load Diff

491
src/server/h1decoder.rs Normal file
View File

@ -0,0 +1,491 @@
use std::{io, mem};
use bytes::{Bytes, BytesMut};
use futures::{Async, Poll};
use httparse;
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;
const MAX_HEADERS: usize = 96;
pub(crate) struct H1Decoder {
decoder: Option<EncodingDecoder>,
}
pub(crate) enum Message {
Message {
msg: SharedHttpInnerMessage,
payload: bool,
},
Chunk(Bytes),
Eof,
}
#[derive(Debug)]
pub(crate) enum DecoderError {
Io(io::Error),
Error(ParseError),
}
impl From<io::Error> for DecoderError {
fn from(err: io::Error) -> DecoderError {
DecoderError::Io(err)
}
}
impl H1Decoder {
pub fn new() -> H1Decoder {
H1Decoder { decoder: None }
}
pub fn decode<H>(
&mut self, src: &mut BytesMut, settings: &WorkerSettings<H>,
) -> Result<Option<Message>, DecoderError> {
// read payload
if self.decoder.is_some() {
match self.decoder.as_mut().unwrap().decode(src)? {
Async::Ready(Some(bytes)) => return Ok(Some(Message::Chunk(bytes))),
Async::Ready(None) => {
self.decoder.take();
return Ok(Some(Message::Eof));
}
Async::NotReady => return Ok(None),
}
}
match self.parse_message(src, settings)
.map_err(DecoderError::Error)?
{
Async::Ready((msg, decoder)) => {
if let Some(decoder) = decoder {
self.decoder = Some(decoder);
Ok(Some(Message::Message {
msg,
payload: true,
}))
} else {
Ok(Some(Message::Message {
msg,
payload: false,
}))
}
}
Async::NotReady => {
if src.len() >= MAX_BUFFER_SIZE {
error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
Err(DecoderError::Error(ParseError::TooLarge))
} else {
Ok(None)
}
}
}
}
fn parse_message<H>(
&self, buf: &mut BytesMut, settings: &WorkerSettings<H>,
) -> Poll<(SharedHttpInnerMessage, Option<EncodingDecoder>), ParseError> {
// Parse http message
let mut has_upgrade = false;
let mut chunked = false;
let mut content_length = None;
let msg = {
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 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)?;
let path = Url::new(Uri::try_from(req.path.unwrap())?);
let version = if req.version.unwrap() == 1 {
Version::HTTP_11
} else {
Version::HTTP_10
};
(len, method, path, version, req.headers.len())
}
httparse::Status::Partial => return Ok(Async::NotReady),
}
};
let slice = buf.split_to(len).freeze();
// convert headers
let msg = settings.get_http_message();
{
let msg_mut = msg.get_mut();
msg_mut
.flags
.set(MessageFlags::KEEPALIVE, version != Version::HTTP_10);
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(v_start, v_end),
)
};
match name {
header::CONTENT_LENGTH => {
if let Ok(s) = value.to_str() {
if let Ok(len) = s.parse::<u64>() {
content_length = Some(len)
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header);
}
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header);
}
}
// transfer-encoding
header::TRANSFER_ENCODING => {
if let Ok(s) = value.to_str() {
chunked = s.to_lowercase().contains("chunked");
} else {
return Err(ParseError::Header);
}
}
// connection keep-alive state
header::CONNECTION => {
let ka = if let Ok(conn) = value.to_str() {
if version == Version::HTTP_10
&& conn.contains("keep-alive")
{
true
} else {
version == Version::HTTP_11
&& !(conn.contains("close")
|| conn.contains("upgrade"))
}
} else {
false
};
msg_mut.flags.set(MessageFlags::KEEPALIVE, ka);
}
_ => (),
}
msg_mut.headers.append(name, value);
} else {
return Err(ParseError::Header);
}
}
msg_mut.url = path;
msg_mut.method = method;
msg_mut.version = version;
}
msg
};
// https://tools.ietf.org/html/rfc7230#section-3.3.3
let decoder = if chunked {
// Chunked encoding
Some(EncodingDecoder::chunked())
} else if let Some(len) = content_length {
// Content-Length
Some(EncodingDecoder::length(len))
} else if has_upgrade || msg.get_ref().method == Method::CONNECT {
// upgrade(websocket) or connect
Some(EncodingDecoder::eof())
} else {
None
};
Ok(Async::Ready((msg, decoder)))
}
}
/// Decoders to handle different Transfer-Encodings.
///
/// If a message body does not include a Transfer-Encoding, it *should*
/// include a Content-Length header.
#[derive(Debug, Clone, PartialEq)]
pub struct EncodingDecoder {
kind: Kind,
}
impl EncodingDecoder {
pub fn length(x: u64) -> EncodingDecoder {
EncodingDecoder {
kind: Kind::Length(x),
}
}
pub fn chunked() -> EncodingDecoder {
EncodingDecoder {
kind: Kind::Chunked(ChunkedState::Size, 0),
}
}
pub fn eof() -> EncodingDecoder {
EncodingDecoder {
kind: Kind::Eof(false),
}
}
}
#[derive(Debug, Clone, PartialEq)]
enum Kind {
/// A Reader used when a Content-Length header is passed with a positive
/// integer.
Length(u64),
/// A Reader used when Transfer-Encoding is `chunked`.
Chunked(ChunkedState, u64),
/// A Reader used for responses that don't indicate a length or chunked.
///
/// Note: This should only used for `Response`s. It is illegal for a
/// `Request` to be made with both `Content-Length` and
/// `Transfer-Encoding: chunked` missing, as explained from the spec:
///
/// > If a Transfer-Encoding header field is present in a response and
/// > the chunked transfer coding is not the final encoding, the
/// > message body length is determined by reading the connection until
/// > it is closed by the server. If a Transfer-Encoding header field
/// > is present in a request and the chunked transfer coding is not
/// > the final encoding, the message body length cannot be determined
/// > reliably; the server MUST respond with the 400 (Bad Request)
/// > status code and then close the connection.
Eof(bool),
}
#[derive(Debug, PartialEq, Clone)]
enum ChunkedState {
Size,
SizeLws,
Extension,
SizeLf,
Body,
BodyCr,
BodyLf,
EndCr,
EndLf,
End,
}
impl EncodingDecoder {
pub fn decode(&mut self, body: &mut BytesMut) -> Poll<Option<Bytes>, io::Error> {
match self.kind {
Kind::Length(ref mut remaining) => {
if *remaining == 0 {
Ok(Async::Ready(None))
} else {
if body.is_empty() {
return Ok(Async::NotReady);
}
let len = body.len() as u64;
let buf;
if *remaining > len {
buf = body.take().freeze();
*remaining -= len;
} else {
buf = body.split_to(*remaining as usize).freeze();
*remaining = 0;
}
trace!("Length read: {}", buf.len());
Ok(Async::Ready(Some(buf)))
}
}
Kind::Chunked(ref mut state, ref mut size) => {
loop {
let mut buf = None;
// advances the chunked state
*state = try_ready!(state.step(body, size, &mut buf));
if *state == ChunkedState::End {
trace!("End of chunked stream");
return Ok(Async::Ready(None));
}
if let Some(buf) = buf {
return Ok(Async::Ready(Some(buf)));
}
if body.is_empty() {
return Ok(Async::NotReady);
}
}
}
Kind::Eof(ref mut is_eof) => {
if *is_eof {
Ok(Async::Ready(None))
} else if !body.is_empty() {
Ok(Async::Ready(Some(body.take().freeze())))
} else {
Ok(Async::NotReady)
}
}
}
}
}
macro_rules! byte (
($rdr:ident) => ({
if $rdr.len() > 0 {
let b = $rdr[0];
$rdr.split_to(1);
b
} else {
return Ok(Async::NotReady)
}
})
);
impl ChunkedState {
fn step(
&self, body: &mut BytesMut, size: &mut u64, buf: &mut Option<Bytes>,
) -> Poll<ChunkedState, io::Error> {
use self::ChunkedState::*;
match *self {
Size => ChunkedState::read_size(body, size),
SizeLws => ChunkedState::read_size_lws(body),
Extension => ChunkedState::read_extension(body),
SizeLf => ChunkedState::read_size_lf(body, size),
Body => ChunkedState::read_body(body, size, buf),
BodyCr => ChunkedState::read_body_cr(body),
BodyLf => ChunkedState::read_body_lf(body),
EndCr => ChunkedState::read_end_cr(body),
EndLf => ChunkedState::read_end_lf(body),
End => Ok(Async::Ready(ChunkedState::End)),
}
}
fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll<ChunkedState, io::Error> {
let radix = 16;
match byte!(rdr) {
b @ b'0'...b'9' => {
*size *= radix;
*size += u64::from(b - b'0');
}
b @ b'a'...b'f' => {
*size *= radix;
*size += u64::from(b + 10 - b'a');
}
b @ b'A'...b'F' => {
*size *= radix;
*size += u64::from(b + 10 - b'A');
}
b'\t' | b' ' => return Ok(Async::Ready(ChunkedState::SizeLws)),
b';' => return Ok(Async::Ready(ChunkedState::Extension)),
b'\r' => return Ok(Async::Ready(ChunkedState::SizeLf)),
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk size line: Invalid Size",
));
}
}
Ok(Async::Ready(ChunkedState::Size))
}
fn read_size_lws(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
trace!("read_size_lws");
match byte!(rdr) {
// LWS can follow the chunk size, but no more digits can come
b'\t' | b' ' => Ok(Async::Ready(ChunkedState::SizeLws)),
b';' => Ok(Async::Ready(ChunkedState::Extension)),
b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk size linear white space",
)),
}
}
fn read_extension(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
match byte!(rdr) {
b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)),
_ => Ok(Async::Ready(ChunkedState::Extension)), // no supported extensions
}
}
fn read_size_lf(
rdr: &mut BytesMut, size: &mut u64,
) -> Poll<ChunkedState, io::Error> {
match byte!(rdr) {
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)),
b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk size LF",
)),
}
}
fn read_body(
rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option<Bytes>,
) -> Poll<ChunkedState, io::Error> {
trace!("Chunked read, remaining={:?}", rem);
let len = rdr.len() as u64;
if len == 0 {
Ok(Async::Ready(ChunkedState::Body))
} else {
let slice;
if *rem > len {
slice = rdr.take().freeze();
*rem -= len;
} else {
slice = rdr.split_to(*rem as usize).freeze();
*rem = 0;
}
*buf = Some(slice);
if *rem > 0 {
Ok(Async::Ready(ChunkedState::Body))
} else {
Ok(Async::Ready(ChunkedState::BodyCr))
}
}
}
fn read_body_cr(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
match byte!(rdr) {
b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk body CR",
)),
}
}
fn read_body_lf(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
match byte!(rdr) {
b'\n' => Ok(Async::Ready(ChunkedState::Size)),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk body LF",
)),
}
}
fn read_end_cr(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
match byte!(rdr) {
b'\r' => Ok(Async::Ready(ChunkedState::EndLf)),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk end CR",
)),
}
}
fn read_end_lf(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
match byte!(rdr) {
b'\n' => Ok(Async::Ready(ChunkedState::End)),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk end LF",
)),
}
}
}

View File

@ -2,10 +2,8 @@
use bytes::BufMut;
use futures::{Async, Poll};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
use http::{Method, Version};
use std::io;
use std::rc::Rc;
use std::{io, mem};
use tokio_io::AsyncWrite;
use super::encoding::ContentEncoder;
@ -15,6 +13,8 @@ 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_LENGTH, DATE};
use http::{Method, Version};
use httprequest::HttpInnerMessage;
use httpresponse::HttpResponse;
@ -42,7 +42,7 @@ pub(crate) struct H1Writer<T: AsyncWrite, H: 'static> {
impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
pub fn new(
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
) -> H1Writer<T, H> {
H1Writer {
flags: Flags::empty(),
@ -169,7 +169,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
let mut pos = 0;
let mut has_date = false;
let mut remaining = buffer.remaining_mut();
let mut buf: &mut [u8] = unsafe { mem::transmute(buffer.bytes_mut()) };
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;
@ -184,7 +184,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
pos = 0;
buffer.reserve(len);
remaining = buffer.remaining_mut();
buf = unsafe { mem::transmute(buffer.bytes_mut()) };
buf = unsafe { &mut *(buffer.bytes_mut() as *mut _) };
}
buf[pos..pos + k.len()].copy_from_slice(k);
@ -272,7 +272,8 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
#[inline]
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
if !self.buffer.is_empty() {
let buf: &[u8] = unsafe { mem::transmute(self.buffer.as_ref()) };
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 self.buffer.len() > self.buffer_capacity {

View File

@ -22,6 +22,7 @@ use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use payload::{Payload, PayloadStatus, PayloadWriter};
use pipeline::Pipeline;
use uri::Url;
use super::encoding::PayloadType;
use super::h2writer::H2Writer;
@ -60,7 +61,7 @@ where
H: HttpHandler + 'static,
{
pub fn new(
settings: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes
settings: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes,
) -> Self {
Http2 {
flags: Flags::empty(),
@ -304,7 +305,7 @@ impl<H: 'static> Entry<H> {
let (psender, payload) = Payload::new(false);
let msg = settings.get_http_message();
msg.get_mut().uri = parts.uri;
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;
@ -342,24 +343,27 @@ impl<H: 'static> Entry<H> {
}
fn poll_payload(&mut self) {
if !self.flags.contains(EntryFlags::REOF) {
if self.payload.need_read() == PayloadStatus::Read {
if let Err(err) = self.recv.release_capacity().release_capacity(32_768) {
self.payload.set_error(PayloadError::Http2(err))
}
} else if let Err(err) = self.recv.release_capacity().release_capacity(0) {
self.payload.set_error(PayloadError::Http2(err))
}
while !self.flags.contains(EntryFlags::REOF)
&& self.payload.need_read() == PayloadStatus::Read
{
match self.recv.poll() {
Ok(Async::Ready(Some(chunk))) => {
let l = chunk.len();
self.payload.feed_data(chunk);
if let Err(err) = self.recv.release_capacity().release_capacity(l) {
self.payload.set_error(PayloadError::Http2(err));
break;
}
}
Ok(Async::Ready(None)) => {
self.flags.insert(EntryFlags::REOF);
self.payload.feed_eof();
}
Ok(Async::NotReady) => break,
Err(err) => {
self.payload.set_error(PayloadError::Http2(err));
break;
}
Ok(Async::NotReady) => (),
Err(err) => self.payload.set_error(PayloadError::Http2(err)),
}
}
}

View File

@ -45,7 +45,7 @@ pub(crate) struct H2Writer<H: 'static> {
impl<H: 'static> H2Writer<H> {
pub fn new(
respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<H>>
respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
) -> H2Writer<H> {
H2Writer {
respond,

View File

@ -69,7 +69,7 @@ impl SharedHttpInnerMessage {
}
pub fn new(
msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>
msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>,
) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(msg), Some(pool))
}
@ -79,7 +79,7 @@ impl SharedHttpInnerMessage {
#[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 { mem::transmute(r) }
unsafe { &mut *(r as *const _ as *mut _) }
}
#[inline(always)]
@ -97,7 +97,7 @@ const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
let mut buf: [u8; 13] = [
b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' '
b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ',
];
match version {
Version::HTTP_2 => buf[5] = b'2',

View File

@ -10,6 +10,7 @@ use tokio_io::{AsyncRead, AsyncWrite};
mod channel;
pub(crate) mod encoding;
pub(crate) mod h1;
pub(crate) mod h1decoder;
mod h1writer;
mod h2;
mod h2writer;

View File

@ -8,15 +8,14 @@ use std::sync::Arc;
use std::{fmt, mem, net};
use time;
use super::KeepAlive;
use super::channel::Node;
use super::helpers;
use super::shared::{SharedBytes, SharedBytesPool};
use super::KeepAlive;
use body::Body;
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
/// Various server settings
#[derive(Clone)]
pub struct ServerSettings {
addr: Option<net::SocketAddr>,
secure: bool,
@ -28,6 +27,18 @@ pub struct ServerSettings {
unsafe impl Sync for ServerSettings {}
unsafe impl Send for ServerSettings {}
impl Clone for ServerSettings {
fn clone(&self) -> Self {
ServerSettings {
addr: self.addr,
secure: self.secure,
host: self.host.clone(),
cpu_pool: self.cpu_pool.clone(),
responses: HttpResponsePool::pool(),
}
}
}
struct InnerCpuPool {
cpu_pool: UnsafeCell<Option<CpuPool>>,
}
@ -72,7 +83,7 @@ impl Default for ServerSettings {
impl ServerSettings {
/// Crate server settings instance
pub(crate) fn new(
addr: Option<net::SocketAddr>, host: &Option<String>, secure: bool
addr: Option<net::SocketAddr>, host: &Option<String>, secure: bool,
) -> ServerSettings {
let host = if let Some(ref host) = *host {
host.clone()
@ -119,7 +130,7 @@ impl ServerSettings {
#[inline]
pub(crate) fn get_response_builder(
&self, status: StatusCode
&self, status: StatusCode,
) -> HttpResponseBuilder {
HttpResponsePool::get_builder(&self.responses, status)
}

View File

@ -1,8 +1,8 @@
use bytes::{BufMut, BytesMut};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::io;
use std::rc::Rc;
use std::{io, mem};
use body::Binary;
@ -61,7 +61,7 @@ impl SharedBytes {
#[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 { mem::transmute(r) }
unsafe { &mut *(r as *const _ as *mut _) }
}
#[inline]

View File

@ -10,6 +10,7 @@ use futures::{Future, Sink, Stream};
use mio;
use net2::TcpBuilder;
use num_cpus;
use slab::Slab;
use tokio_io::{AsyncRead, AsyncWrite};
#[cfg(feature = "tls")]
@ -20,7 +21,7 @@ use openssl::ssl::{AlpnError, SslAcceptorBuilder};
use super::channel::{HttpChannel, WrapperStream};
use super::settings::{ServerSettings, WorkerSettings};
use super::worker::{Conn, StopWorker, StreamHandlerType, Worker};
use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker};
use super::{IntoHttpHandler, IoStream, KeepAlive};
use super::{PauseServer, ResumeServer, StopServer};
@ -37,7 +38,7 @@ where
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<(net::SocketAddr, net::TcpListener)>,
sockets: Vec<Socket>,
accept: Vec<(mio::SetReadiness, sync_mpsc::Sender<Command>)>,
exit: bool,
shutdown_timeout: u16,
@ -57,14 +58,8 @@ where
{
}
#[derive(Clone)]
struct Info {
addr: net::SocketAddr,
handler: StreamHandlerType,
}
enum ServerCommand {
WorkerDied(usize, Info),
WorkerDied(usize, Slab<SocketInfo>),
}
impl<H> Actor for HttpServer<H>
@ -74,6 +69,12 @@ where
type Context = Context<Self>;
}
struct Socket {
lst: net::TcpListener,
addr: net::SocketAddr,
tp: StreamHandlerType,
}
impl<H> HttpServer<H>
where
H: IntoHttpHandler + 'static,
@ -108,11 +109,17 @@ where
///
/// By default http server uses number of available logical cpu as threads
/// count.
pub fn threads(mut self, num: usize) -> Self {
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.
@ -187,7 +194,7 @@ where
/// Get addresses of bound sockets.
pub fn addrs(&self) -> Vec<net::SocketAddr> {
self.sockets.iter().map(|s| s.0).collect()
self.sockets.iter().map(|s| s.addr).collect()
}
/// Use listener for accepting incoming connection requests
@ -195,21 +202,29 @@ where
/// 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 {
self.sockets.push((lst.local_addr().unwrap(), lst));
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Normal,
});
self
}
/// The socket address to bind
///
/// To mind multiple addresses this method can be call multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<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;
self.sockets.push((lst.local_addr().unwrap(), lst));
let addr = lst.local_addr().unwrap();
sockets.push(Socket {
lst,
addr,
tp: StreamHandlerType::Normal,
});
}
Err(e) => err = Some(e),
}
@ -225,12 +240,65 @@ where
))
}
} else {
Ok(self)
Ok(sockets)
}
}
/// The socket address to bind
///
/// To mind multiple addresses this method can be call 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 mind multiple addresses this method can be call 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 {
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();
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, handler: &StreamHandlerType
&mut self, settings: &ServerSettings, sockets: &Slab<SocketInfo>,
) -> Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)> {
// start workers
let mut workers = Vec::new();
@ -238,8 +306,8 @@ where
let s = settings.clone();
let (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>();
let h = handler.clone();
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)()
@ -247,7 +315,7 @@ where
.map(|h| h.into_handler(s.clone()))
.collect();
ctx.add_message_stream(rx);
Worker::new(apps, h, ka)
Worker::new(apps, socks, ka)
});
workers.push((idx, tx));
self.workers.push((idx, addr));
@ -304,24 +372,32 @@ impl<H: IntoHttpHandler> HttpServer<H> {
panic!("HttpServer::bind() has to be called before start()");
} else {
let (tx, rx) = mpsc::unbounded();
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
self.sockets.drain(..).collect();
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
let workers = self.start_workers(&settings, &StreamHandlerType::Normal);
let info = Info {
addr: addrs[0].0,
handler: StreamHandlerType::Normal,
};
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 (addr, sock) in addrs {
info!("Starting server on http://{}", addr);
for (token, sock) in addrs {
info!("Starting server on http://{}", sock.addr);
self.accept.push(start_accept_thread(
token,
sock,
addr,
self.backlog,
tx.clone(),
info.clone(),
socks.clone(),
workers.clone(),
));
}
@ -332,9 +408,9 @@ impl<H: IntoHttpHandler> HttpServer<H> {
ctx.add_stream(rx);
self
});
signals.map(|signals| {
if let Some(signals) = signals {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
});
}
addr
}
}
@ -373,119 +449,59 @@ impl<H: IntoHttpHandler> HttpServer<H> {
}
}
#[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>> {
if self.sockets.is_empty() {
Err(io::Error::new(
io::ErrorKind::Other,
"No socket addresses are bound",
))
} else {
let (tx, rx) = mpsc::unbounded();
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
self.sockets.drain(..).collect();
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
let workers =
self.start_workers(&settings, &StreamHandlerType::Tls(acceptor.clone()));
let info = Info {
addr: addrs[0].0,
handler: StreamHandlerType::Tls(acceptor),
};
// start acceptors threads
for (addr, sock) in addrs {
info!("Starting server on https://{}", addr);
self.accept.push(start_accept_thread(
sock,
addr,
self.backlog,
tx.clone(),
info.clone(),
workers.clone(),
));
for sock in &mut self.sockets {
match sock.tp {
StreamHandlerType::Normal => (),
_ => continue,
}
// start http server actor
let signals = self.subscribe_to_signals();
let addr: Addr<Syn, _> = Actor::create(|ctx| {
ctx.add_stream(rx);
self
});
signals.map(|signals| {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
});
Ok(addr)
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
mut self, mut builder: SslAcceptorBuilder,
) -> io::Result<Addr<Syn, Self>> {
if self.sockets.is_empty() {
Err(io::Error::new(
io::ErrorKind::Other,
"No socket addresses are bound",
))
} else {
// 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 (tx, rx) = mpsc::unbounded();
let acceptor = builder.build();
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
self.sockets.drain(..).collect();
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
let workers = self.start_workers(
&settings,
&StreamHandlerType::Alpn(acceptor.clone()),
);
let info = Info {
addr: addrs[0].0,
handler: StreamHandlerType::Alpn(acceptor),
};
// start acceptors threads
for (addr, sock) in addrs {
info!("Starting server on https://{}", addr);
self.accept.push(start_accept_thread(
sock,
addr,
self.backlog,
tx.clone(),
info.clone(),
workers.clone(),
));
}
// start http server actor
let signals = self.subscribe_to_signals();
let addr: Addr<Syn, _> = Actor::create(|ctx| {
ctx.add_stream(rx);
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)
}
});
signals.map(|signals| {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
});
Ok(addr)
}
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())
}
}
@ -499,32 +515,6 @@ impl<H: IntoHttpHandler> HttpServer<H> {
T: AsyncRead + AsyncWrite + 'static,
A: 'static,
{
let (tx, rx) = mpsc::unbounded();
if !self.sockets.is_empty() {
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
self.sockets.drain(..).collect();
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
let workers = self.start_workers(&settings, &StreamHandlerType::Normal);
let info = Info {
addr: addrs[0].0,
handler: StreamHandlerType::Normal,
};
// start acceptors threads
for (addr, sock) in addrs {
info!("Starting server on http://{}", addr);
self.accept.push(start_accept_thread(
sock,
addr,
self.backlog,
tx.clone(),
info.clone(),
workers.clone(),
));
}
}
// set server settings
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
let settings = ServerSettings::new(Some(addr), &self.host, secure);
@ -537,16 +527,17 @@ impl<H: IntoHttpHandler> HttpServer<H> {
// start server
let signals = self.subscribe_to_signals();
let addr: Addr<Syn, _> = HttpServer::create(move |ctx| {
ctx.add_stream(rx);
ctx.add_message_stream(stream.map_err(|_| ()).map(move |(t, _)| Conn {
io: WrapperStream::new(t),
token: 0,
peer: None,
http2: false,
}));
self
});
signals
.map(|signals| signals.do_send(signal::Subscribe(addr.clone().recipient())));
if let Some(signals) = signals {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
}
addr
}
}
@ -584,7 +575,7 @@ 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, info) => {
ServerCommand::WorkerDied(idx, socks) => {
let mut found = false;
for i in 0..self.workers.len() {
if self.workers[i].0 == idx {
@ -609,11 +600,10 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
break;
}
let h = info.handler;
let ka = self.keep_alive;
let factory = Arc::clone(&self.factory);
let settings =
ServerSettings::new(Some(info.addr), &self.host, false);
ServerSettings::new(Some(socks[0].addr), &self.host, false);
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
let apps: Vec<_> = (*factory)()
@ -621,7 +611,7 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
.map(|h| h.into_handler(settings.clone()))
.collect();
ctx.add_message_stream(rx);
Worker::new(apps, h, ka)
Worker::new(apps, socks, ka)
});
for item in &self.accept {
let _ = item.1.send(Command::Worker(new_idx, tx.clone()));
@ -737,8 +727,8 @@ enum Command {
}
fn start_accept_thread(
sock: net::TcpListener, addr: net::SocketAddr, backlog: i32,
srv: mpsc::UnboundedSender<ServerCommand>, info: Info,
token: usize, sock: Socket, backlog: i32, 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();
@ -747,13 +737,14 @@ fn start_accept_thread(
// start accept thread
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
let _ = thread::Builder::new()
.name(format!("Accept on {}", addr))
.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)
mio::net::TcpListener::from_std(sock.lst)
.expect("Can not create mio::net::TcpListener"),
);
@ -799,9 +790,10 @@ fn start_accept_thread(
SRV => if let Some(ref server) = server {
loop {
match server.accept_std() {
Ok((sock, addr)) => {
Ok((io, addr)) => {
let mut msg = Conn {
io: sock,
io,
token,
peer: Some(addr),
http2: false,
};
@ -812,7 +804,7 @@ fn start_accept_thread(
let _ = srv.unbounded_send(
ServerCommand::WorkerDied(
workers[next].0,
info.clone(),
socks.clone(),
),
);
msg = err.into_inner();
@ -915,7 +907,7 @@ fn start_accept_thread(
}
fn create_tcp_listener(
addr: net::SocketAddr, backlog: i32
addr: net::SocketAddr, backlog: i32,
) -> io::Result<net::TcpListener> {
let builder = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,

View File

@ -8,7 +8,7 @@ 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
io: &mut T, buf: &mut BytesMut,
) -> Poll<usize, io::Error> {
unsafe {
if buf.remaining_mut() < LW_BUFFER_SIZE {

View File

@ -1,6 +1,7 @@
use futures::Future;
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;
@ -29,10 +30,17 @@ use server::{HttpHandler, KeepAlive};
#[derive(Message)]
pub(crate) struct Conn<T> {
pub io: T,
pub token: usize,
pub peer: Option<net::SocketAddr>,
pub http2: bool,
}
#[derive(Clone)]
pub(crate) struct SocketInfo {
pub addr: net::SocketAddr,
pub htype: StreamHandlerType,
}
/// Stop worker message. Returns `true` on successful shutdown
/// and `false` if some connections still alive.
pub(crate) struct StopWorker {
@ -53,13 +61,13 @@ where
{
settings: Rc<WorkerSettings<H>>,
hnd: Handle,
handler: StreamHandlerType,
socks: Slab<SocketInfo>,
tcp_ka: Option<time::Duration>,
}
impl<H: HttpHandler + 'static> Worker<H> {
pub(crate) fn new(
h: Vec<H>, handler: StreamHandlerType, keep_alive: KeepAlive
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))
@ -70,7 +78,7 @@ impl<H: HttpHandler + 'static> Worker<H> {
Worker {
settings: Rc::new(WorkerSettings::new(h, keep_alive)),
hnd: Arbiter::handle().clone(),
handler,
socks,
tcp_ka,
}
}
@ -83,7 +91,7 @@ impl<H: HttpHandler + 'static> Worker<H> {
}
fn shutdown_timeout(
&self, ctx: &mut Context<Self>, 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| {
@ -124,8 +132,11 @@ where
if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() {
error!("Can not set socket keep-alive option");
}
self.handler
.handle(Rc::clone(&self.settings), &self.hnd, msg);
self.socks.get_mut(msg.token).unwrap().htype.handle(
Rc::clone(&self.settings),
&self.hnd,
msg,
);
}
}
@ -165,7 +176,7 @@ pub(crate) enum StreamHandlerType {
impl StreamHandlerType {
fn handle<H: HttpHandler>(
&mut self, h: Rc<WorkerSettings<H>>, hnd: &Handle, msg: Conn<net::TcpStream>
&mut self, h: Rc<WorkerSettings<H>>, hnd: &Handle, msg: Conn<net::TcpStream>,
) {
match *self {
StreamHandlerType::Normal => {
@ -177,7 +188,9 @@ impl StreamHandlerType {
}
#[cfg(feature = "tls")]
StreamHandlerType::Tls(ref acceptor) => {
let Conn { io, peer, http2 } = msg;
let Conn {
io, peer, http2, ..
} = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream");
@ -185,12 +198,8 @@ impl StreamHandlerType {
hnd.spawn(
TlsAcceptorExt::accept_async(acceptor, io).then(move |res| {
match res {
Ok(io) => Arbiter::handle().spawn(HttpChannel::new(
h,
io,
peer,
http2,
)),
Ok(io) => Arbiter::handle()
.spawn(HttpChannel::new(h, io, peer, http2)),
Err(err) => {
trace!("Error during handling tls connection: {}", err)
}
@ -217,12 +226,8 @@ impl StreamHandlerType {
} else {
false
};
Arbiter::handle().spawn(HttpChannel::new(
h,
io,
peer,
http2,
));
Arbiter::handle()
.spawn(HttpChannel::new(h, io, peer, http2));
}
Err(err) => {
trace!("Error during handling tls connection: {}", err)

View File

@ -21,7 +21,7 @@ use application::{App, HttpApplication};
use body::Binary;
use client::{ClientConnector, ClientRequest, ClientRequestBuilder};
use error::Error;
use handler::{Handler, ReplyItem, Responder};
use handler::{AsyncResultItem, Handler, Responder};
use header::{Header, IntoHeaderValue};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
@ -88,7 +88,7 @@ impl TestServer {
/// Create test server builder with specific state factory
///
/// This method can be used for constructing application state.
/// Also it can be used for external dependecy initialization,
/// Also it can be used for external dependency initialization,
/// like creating sync actors for diesel integration.
pub fn build_with_state<F, S>(state: F) -> TestServerBuilder<S>
where
@ -202,7 +202,7 @@ impl TestServer {
/// Connect to websocket server
pub fn ws(
&mut self
&mut self,
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
let url = self.url("/");
self.system.run_until_complete(
@ -355,12 +355,7 @@ impl<S: 'static> TestApp<S> {
/// Register handler for "/"
pub fn handler<H: Handler<S>>(&mut self, handler: H) {
self.app = Some(
self.app
.take()
.unwrap()
.resource("/", |r| r.h(handler)),
);
self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler)));
}
/// Register middleware
@ -478,7 +473,7 @@ impl TestRequest<()> {
}
}
impl<S> TestRequest<S> {
impl<S: 'static> TestRequest<S> {
/// Start HttpRequest build process with application state
pub fn with_state(state: S) -> TestRequest<S> {
TestRequest {
@ -562,8 +557,8 @@ impl<S> TestRequest<S> {
cookies,
payload,
} = self;
let req = HttpRequest::new(method, uri, version, headers, payload);
req.as_mut().cookies = cookies;
let mut req = HttpRequest::new(method, uri, version, headers, payload);
req.set_cookies(cookies);
req.as_mut().params = params;
let (router, _) = Router::new::<S>("/", ServerSettings::default(), Vec::new());
req.with_state(Rc::new(state), router)
@ -583,8 +578,8 @@ impl<S> TestRequest<S> {
payload,
} = self;
let req = HttpRequest::new(method, uri, version, headers, payload);
req.as_mut().cookies = cookies;
let mut req = HttpRequest::new(method, uri, version, headers, payload);
req.set_cookies(cookies);
req.as_mut().params = params;
req.with_state(Rc::new(state), router)
}
@ -593,18 +588,17 @@ impl<S> TestRequest<S> {
/// with generated request.
///
/// This method panics is handler returns actor or async result.
pub fn run<H: Handler<S>>(
self, mut h: H
) -> Result<HttpResponse, <<H as Handler<S>>::Result as Responder>::Error> {
pub fn run<H: Handler<S>>(self, mut h: H) -> Result<HttpResponse, Error> {
let req = self.finish();
let resp = h.handle(req.clone());
match resp.respond_to(req.drop_state()) {
match resp.respond_to(&req) {
Ok(resp) => match resp.into().into() {
ReplyItem::Message(resp) => Ok(resp),
ReplyItem::Future(_) => panic!("Async handler is not supported."),
AsyncResultItem::Ok(resp) => Ok(resp),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(_) => panic!("Async handler is not supported."),
},
Err(err) => Err(err),
Err(err) => Err(err.into()),
}
}
@ -624,9 +618,9 @@ impl<S> TestRequest<S> {
let mut core = Core::new().unwrap();
match core.run(fut) {
Ok(r) => match r.respond_to(req.drop_state()) {
Ok(r) => match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
ReplyItem::Message(resp) => Ok(resp),
AsyncResultItem::Ok(resp) => Ok(resp),
_ => panic!("Nested async replies are not supported"),
},
Err(e) => Err(e),

171
src/uri.rs Normal file
View File

@ -0,0 +1,171 @@
use http::Uri;
#[allow(dead_code)]
const GEN_DELIMS: &[u8] = b":/?#[]@";
#[allow(dead_code)]
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
#[allow(dead_code)]
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
#[allow(dead_code)]
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
#[allow(dead_code)]
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
-._~";
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
-._~
!$'()*,";
const QS: &[u8] = b"+&=;b";
#[inline]
fn bit_at(array: &[u8], ch: u8) -> bool {
array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0
}
#[inline]
fn set_bit(array: &mut [u8], ch: u8) {
array[(ch >> 3) as usize] |= 1 << (ch & 7)
}
lazy_static! {
static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") };
}
#[derive(Default)]
pub(crate) struct Url {
uri: Uri,
path: Option<String>,
}
impl Url {
pub fn new(uri: Uri) -> Url {
let path = DEFAULT_QUOTER.requote(uri.path().as_bytes());
Url { uri, path }
}
pub fn uri(&self) -> &Uri {
&self.uri
}
pub fn path(&self) -> &str {
if let Some(ref s) = self.path {
s
} else {
self.uri.path()
}
}
}
pub(crate) struct Quoter {
safe_table: [u8; 16],
protected_table: [u8; 16],
}
impl Quoter {
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
let mut q = Quoter {
safe_table: [0; 16],
protected_table: [0; 16],
};
// prepare safe table
for i in 0..128 {
if ALLOWED.contains(&i) {
set_bit(&mut q.safe_table, i);
}
if QS.contains(&i) {
set_bit(&mut q.safe_table, i);
}
}
for ch in safe {
set_bit(&mut q.safe_table, *ch)
}
// prepare protected table
for ch in protected {
set_bit(&mut q.safe_table, *ch);
set_bit(&mut q.protected_table, *ch);
}
q
}
pub fn requote(&self, val: &[u8]) -> Option<String> {
let mut has_pct = 0;
let mut pct = [b'%', 0, 0];
let mut idx = 0;
let mut cloned: Option<Vec<u8>> = None;
let len = val.len();
while idx < len {
let ch = val[idx];
if has_pct != 0 {
pct[has_pct] = val[idx];
has_pct += 1;
if has_pct == 3 {
has_pct = 0;
let buf = cloned.as_mut().unwrap();
if let Some(ch) = restore_ch(pct[1], pct[2]) {
if ch < 128 {
if bit_at(&self.protected_table, ch) {
buf.extend_from_slice(&pct);
idx += 1;
continue;
}
if bit_at(&self.safe_table, ch) {
buf.push(ch);
idx += 1;
continue;
}
}
buf.push(ch);
} else {
buf.extend_from_slice(&pct[..]);
}
}
} else if ch == b'%' {
has_pct = 1;
if cloned.is_none() {
let mut c = Vec::with_capacity(len);
c.extend_from_slice(&val[..idx]);
cloned = Some(c);
}
} else if let Some(ref mut cloned) = cloned {
cloned.push(ch)
}
idx += 1;
}
if let Some(data) = cloned {
Some(unsafe { String::from_utf8_unchecked(data) })
} else {
None
}
}
}
#[inline]
fn from_hex(v: u8) -> Option<u8> {
if v >= b'0' && v <= b'9' {
Some(v - 0x30) // ord('0') == 0x30
} else if v >= b'A' && v <= b'F' {
Some(v - 0x41 + 10) // ord('A') == 0x41
} else if v > b'a' && v <= b'f' {
Some(v - 0x61 + 10) // ord('a') == 0x61
} else {
None
}
}
#[inline]
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2)))
}

View File

@ -5,10 +5,66 @@ use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use error::Error;
use handler::{FromRequest, Handler, Reply, ReplyItem, Responder};
use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Extractor configuration
///
/// `Route::with()` and `Route::with_async()` returns instance
/// of the `ExtractorConfig` type. It could be used for extractor configuration.
///
/// In this example `Form<FormData>` configured.
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Form, Result, http};
///
/// #[derive(Deserialize)]
/// struct FormData {
/// username: String,
/// }
///
/// fn index(form: Form<FormData>) -> Result<String> {
/// Ok(format!("Welcome {}!", form.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::GET)
/// .with(index)
/// .limit(4096);} // <- change form extractor configuration
/// );
/// }
/// ```
///
/// Same could be donce with multiple extractors
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Form, Path, Result, http};
///
/// #[derive(Deserialize)]
/// struct FormData {
/// username: String,
/// }
///
/// fn index(data: (Path<(String,)>, Form<FormData>)) -> Result<String> {
/// Ok(format!("Welcome {}!", data.1.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::GET)
/// .with(index)
/// .1.limit(4096);} // <- change form extractor configuration
/// );
/// }
/// ```
pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> {
cfg: Rc<UnsafeCell<T::Config>>,
}
@ -82,7 +138,7 @@ where
T: FromRequest<S> + 'static,
S: 'static,
{
type Result = Reply;
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut {
@ -95,9 +151,9 @@ where
};
match fut.poll() {
Ok(Async::Ready(resp)) => Reply::response(resp),
Ok(Async::NotReady) => Reply::async(fut),
Err(e) => Reply::response(e),
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::err(e),
}
}
}
@ -134,14 +190,14 @@ where
let item = if !self.started {
self.started = true;
let mut fut = T::from_request(&self.req, self.cfg.as_ref());
match fut.poll() {
Ok(Async::Ready(item)) => item,
Ok(Async::NotReady) => {
self.fut1 = Some(Box::new(fut));
return Ok(Async::NotReady);
let reply = T::from_request(&self.req, self.cfg.as_ref()).into();
match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
Err(e) => return Err(e),
}
} else {
match self.fut1.as_mut().unwrap().poll()? {
@ -151,14 +207,15 @@ where
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)(item).respond_to(self.req.drop_state()) {
let item = match (*hnd)(item).respond_to(&self.req) {
Ok(item) => item.into(),
Err(e) => return Err(e.into()),
};
match item.into() {
ReplyItem::Message(resp) => Ok(Async::Ready(resp)),
ReplyItem::Future(fut) => {
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut2 = Some(fut);
self.poll()
}
@ -166,6 +223,145 @@ where
}
}
pub struct WithAsync<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<E>,
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
_s: PhantomData<S>,
}
impl<T, S, F, R, I, E> WithAsync<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<Error>,
T: FromRequest<S>,
S: 'static,
{
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self {
WithAsync {
cfg,
hnd: Rc::new(UnsafeCell::new(f)),
_s: PhantomData,
}
}
}
impl<T, S, F, R, I, E> Handler<S> for WithAsync<T, S, F, R, I, E>
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithAsyncHandlerFut {
req,
started: false,
hnd: Rc::clone(&self.hnd),
cfg: self.cfg.clone(),
fut1: None,
fut2: None,
fut3: None,
};
match fut.poll() {
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::err(e),
}
}
}
struct WithAsyncHandlerFut<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
started: bool,
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<R>,
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T, S, F, R, I, E> Future for WithAsyncHandlerFut<T, S, F, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Item = HttpResponse;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut3 {
return fut.poll();
}
if self.fut2.is_some() {
return match self.fut2.as_mut().unwrap().poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(r)) => match r.respond_to(&self.req) {
Ok(r) => match r.into().into() {
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut3 = Some(fut);
self.poll()
}
},
Err(e) => Err(e.into()),
},
Err(e) => Err(e.into()),
};
}
let item = if !self.started {
self.started = true;
let reply = T::from_request(&self.req, self.cfg.as_ref()).into();
match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
}
} else {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
self.fut2 = Some((*hnd)(item));
self.poll()
}
}
pub struct With2<T1, T2, S, F, R>
where
F: Fn(T1, T2) -> R,
@ -187,7 +383,7 @@ where
S: 'static,
{
pub fn new(
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
) -> Self {
With2 {
hnd: Rc::new(UnsafeCell::new(f)),
@ -206,7 +402,7 @@ where
T2: FromRequest<S> + 'static,
S: 'static,
{
type Result = Reply;
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut2 {
@ -221,9 +417,9 @@ where
fut3: None,
};
match fut.poll() {
Ok(Async::Ready(resp)) => Reply::response(resp),
Ok(Async::NotReady) => Reply::async(fut),
Err(e) => Reply::response(e),
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::ok(e),
}
}
}
@ -265,52 +461,67 @@ where
if !self.started {
self.started = true;
let mut fut = T1::from_request(&self.req, self.cfg1.as_ref());
match fut.poll() {
Ok(Async::Ready(item1)) => {
let mut fut = T2::from_request(&self.req, self.cfg2.as_ref());
match fut.poll() {
Ok(Async::Ready(item2)) => {
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item1, item2).respond_to(self.req.drop_state())
{
Ok(item) => match item.into().into() {
ReplyItem::Message(resp) => {
return Ok(Async::Ready(resp))
}
ReplyItem::Future(fut) => {
self.fut3 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
Ok(Async::NotReady) => {
self.item = Some(item1);
self.fut2 = Some(Box::new(fut));
return Ok(Async::NotReady);
}
Err(e) => return Err(e),
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();
}
}
Ok(Async::NotReady) => {
self.fut1 = Some(Box::new(fut));
return Ok(Async::NotReady);
}
Err(e) => return Err(e),
},
Err(e) => return Err(e.into()),
}
}
if self.fut1.is_some() {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => {
self.item = Some(item);
self.fut1.take();
self.fut2 = Some(Box::new(T2::from_request(
&self.req,
self.cfg2.as_ref(),
)));
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),
}
@ -322,16 +533,15 @@ where
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)(self.item.take().unwrap(), item)
.respond_to(self.req.drop_state())
{
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() {
ReplyItem::Message(resp) => return Ok(Async::Ready(resp)),
ReplyItem::Future(fut) => self.fut3 = Some(fut),
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => self.fut3 = Some(fut),
}
self.poll()
@ -387,7 +597,7 @@ where
T3: 'static,
S: 'static,
{
type Result = Reply;
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut3 {
@ -405,9 +615,9 @@ where
fut4: None,
};
match fut.poll() {
Ok(Async::Ready(resp)) => Reply::response(resp),
Ok(Async::NotReady) => Reply::async(fut),
Err(e) => Reply::response(e),
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::err(e),
}
}
}
@ -454,54 +664,50 @@ where
if !self.started {
self.started = true;
let mut fut = T1::from_request(&self.req, self.cfg1.as_ref());
match fut.poll() {
Ok(Async::Ready(item1)) => {
let mut fut = T2::from_request(&self.req, self.cfg2.as_ref());
match fut.poll() {
Ok(Async::Ready(item2)) => {
let mut fut =
T3::from_request(&self.req, self.cfg3.as_ref());
match fut.poll() {
Ok(Async::Ready(item3)) => {
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item1, item2, item3)
.respond_to(self.req.drop_state())
{
Ok(item) => match item.into().into() {
ReplyItem::Message(resp) => {
return Ok(Async::Ready(resp))
}
ReplyItem::Future(fut) => {
self.fut4 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
Ok(Async::NotReady) => {
self.item1 = Some(item1);
self.item2 = Some(item2);
self.fut3 = Some(Box::new(fut));
return Ok(Async::NotReady);
}
Err(e) => return Err(e),
}
}
Ok(Async::NotReady) => {
self.item1 = Some(item1);
self.fut2 = Some(Box::new(fut));
return Ok(Async::NotReady);
}
Err(e) => return Err(e),
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();
}
}
Ok(Async::NotReady) => {
self.fut1 = Some(Box::new(fut));
return Ok(Async::NotReady);
}
Err(e) => return Err(e),
},
Err(e) => return Err(e.into()),
}
}
@ -510,10 +716,40 @@ where
Async::Ready(item) => {
self.item1 = Some(item);
self.fut1.take();
self.fut2 = Some(Box::new(T2::from_request(
&self.req,
self.cfg2.as_ref(),
)));
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),
}
@ -522,12 +758,31 @@ where
if self.fut2.is_some() {
match self.fut2.as_mut().unwrap().poll()? {
Async::Ready(item) => {
self.item2 = Some(item);
self.fut2.take();
self.fut3 = Some(Box::new(T3::from_request(
&self.req,
self.cfg3.as_ref(),
)));
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),
}
@ -543,15 +798,16 @@ where
self.item1.take().unwrap(),
self.item2.take().unwrap(),
item,
).respond_to(self.req.drop_state())
).respond_to(&self.req)
{
Ok(item) => item.into(),
Err(err) => return Err(err.into()),
};
match item.into() {
ReplyItem::Message(resp) => return Ok(Async::Ready(resp)),
ReplyItem::Future(fut) => self.fut4 = Some(fut),
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

@ -5,7 +5,6 @@ use std::time::Duration;
use std::{fmt, io, str};
use base64;
use byteorder::{ByteOrder, NetworkEndian};
use bytes::Bytes;
use cookie::Cookie;
use futures::unsync::mpsc::{unbounded, UnboundedSender};
@ -27,8 +26,8 @@ use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientRespons
HttpResponseParserError, SendRequest, SendRequestError};
use super::frame::Frame;
use super::proto::{CloseCode, OpCode};
use super::{Message, ProtocolError};
use super::proto::{CloseReason, OpCode};
use super::{Message, ProtocolError, WsWriter};
/// Websocket client error
#[derive(Fail, Debug)]
@ -122,7 +121,7 @@ impl Client {
/// Create new websocket connection with custom `ClientConnector`
pub fn with_connector<S: AsRef<str>>(
uri: S, conn: Addr<Unsync, ClientConnector>
uri: S, conn: Addr<Unsync, ClientConnector>,
) -> Client {
let mut cl = Client {
request: ClientRequest::build(),
@ -259,7 +258,7 @@ struct Inner {
/// Future that implementes client websocket handshake process.
///
/// It resolves to a pair of `ClientReadr` and `ClientWriter` that
/// It resolves to a pair of `ClientReader` and `ClientWriter` that
/// can be used for reading and writing websocket frames.
pub struct ClientHandshake {
request: Option<SendRequest>,
@ -468,10 +467,8 @@ impl Stream for ClientReader {
}
OpCode::Close => {
inner.closed = true;
let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
Ok(Async::Ready(Some(Message::Close(CloseCode::from(
code,
)))))
let close_reason = Frame::parse_close_payload(&payload);
Ok(Async::Ready(Some(Message::Close(close_reason))))
}
OpCode::Ping => Ok(Async::Ready(Some(Message::Ping(
String::from_utf8_lossy(payload.as_ref()).into(),
@ -506,13 +503,6 @@ pub struct ClientWriter {
inner: Rc<UnsafeCell<Inner>>,
}
impl ClientWriter {
#[inline]
fn as_mut(&mut self) -> &mut Inner {
unsafe { &mut *self.inner.get() }
}
}
impl ClientWriter {
/// Write payload
#[inline]
@ -524,6 +514,11 @@ impl ClientWriter {
}
}
#[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) {
@ -560,7 +555,39 @@ impl ClientWriter {
/// Send close frame
#[inline]
pub fn close(&mut self, code: CloseCode, reason: &str) {
self.write(Frame::close(code, reason, true));
pub fn close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(reason, true));
}
}
impl WsWriter for ClientWriter {
/// Send text frame
#[inline]
fn send_text<T: Into<Binary>>(&mut self, text: T) {
self.text(text)
}
/// Send binary frame
#[inline]
fn send_binary<B: Into<Binary>>(&mut self, data: B) {
self.binary(data)
}
/// Send ping frame
#[inline]
fn send_ping(&mut self, message: &str) {
self.ping(message)
}
/// Send pong frame
#[inline]
fn send_pong(&mut self, message: &str) {
self.pong(message)
}
/// Send close frame
#[inline]
fn send_close(&mut self, reason: Option<CloseReason>) {
self.close(reason);
}
}

View File

@ -2,7 +2,6 @@ use futures::sync::oneshot::Sender;
use futures::unsync::oneshot;
use futures::{Async, Poll};
use smallvec::SmallVec;
use std::mem;
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
use actix::fut::ActorFuture;
@ -15,7 +14,8 @@ use error::{Error, ErrorInternalServerError};
use httprequest::HttpRequest;
use ws::frame::Frame;
use ws::proto::{CloseCode, OpCode};
use ws::proto::{CloseReason, OpCode};
use ws::WsWriter;
/// Execution context for `WebSockets` actors
pub struct WebsocketContext<A, S = ()>
@ -141,6 +141,14 @@ where
&mut self.request
}
/// 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)
}
/// Send text frame
#[inline]
pub fn text<T: Into<Binary>>(&mut self, text: T) {
@ -177,16 +185,8 @@ where
/// Send close frame
#[inline]
pub fn close(&mut self, code: CloseCode, reason: &str) {
self.write(Frame::close(code, reason, false));
}
/// 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)
pub fn close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(reason, false));
}
/// Check if connection still open
@ -200,7 +200,9 @@ where
if self.stream.is_none() {
self.stream = Some(SmallVec::new());
}
self.stream.as_mut().map(|s| s.push(frame));
if let Some(s) = self.stream.as_mut() {
s.push(frame)
}
self.inner.modify();
}
@ -212,6 +214,42 @@ where
}
}
impl<A, S> WsWriter for WebsocketContext<A, S>
where
A: Actor<Context = Self>,
S: 'static,
{
/// Send text frame
#[inline]
fn send_text<T: Into<Binary>>(&mut self, text: T) {
self.text(text)
}
/// Send binary frame
#[inline]
fn send_binary<B: Into<Binary>>(&mut self, data: B) {
self.binary(data)
}
/// Send ping frame
#[inline]
fn send_ping(&mut self, message: &str) {
self.ping(message)
}
/// Send pong frame
#[inline]
fn send_pong(&mut self, message: &str) {
self.pong(message)
}
/// Send close frame
#[inline]
fn send_close(&mut self, reason: Option<CloseReason>) {
self.close(reason)
}
}
impl<A, S> ActorHttpContext for WebsocketContext<A, S>
where
A: Actor<Context = Self>,
@ -224,8 +262,7 @@ where
}
fn poll(&mut self) -> Poll<Option<SmallVec<[ContextFrame; 4]>>, Error> {
let ctx: &mut WebsocketContext<A, S> =
unsafe { mem::transmute(self as &mut WebsocketContext<A, S>) };
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"));

View File

@ -1,17 +1,17 @@
#![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::iter::FromIterator;
use std::{fmt, mem, ptr};
use std::{fmt, ptr};
use body::Binary;
use error::PayloadError;
use payload::PayloadHelper;
use ws::ProtocolError;
use ws::mask::apply_mask;
use ws::proto::{CloseCode, OpCode};
use ws::proto::{CloseCode, CloseReason, OpCode};
use ws::ProtocolError;
/// A struct representing a `WebSocket` frame.
#[derive(Debug)]
@ -29,21 +29,19 @@ impl Frame {
/// Create a new Close control frame.
#[inline]
pub fn close(code: CloseCode, reason: &str, genmask: bool) -> Binary {
let raw: [u8; 2] = unsafe {
let u: u16 = code.into();
mem::transmute(u.to_be())
};
pub fn close(reason: Option<CloseReason>, genmask: bool) -> Binary {
let payload = match reason {
None => Vec::new(),
Some(reason) => {
let mut code_bytes = [0; 2];
NetworkEndian::write_u16(&mut code_bytes, reason.code.into());
let payload = if let CloseCode::Empty = code {
Vec::new()
} else {
Vec::from_iter(
raw[..]
.iter()
.chain(reason.as_bytes().iter())
.cloned(),
)
let mut payload = Vec::from(&code_bytes[..]);
if let Some(description) = reason.description {
payload.extend(description.as_bytes());
}
payload
}
};
Frame::message(payload, OpCode::Close, true, genmask)
@ -51,7 +49,7 @@ impl Frame {
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
fn read_copy_md<S>(
pl: &mut PayloadHelper<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>,
@ -126,16 +124,12 @@ impl Frame {
};
Ok(Async::Ready(Some((
idx,
finished,
opcode,
length,
mask,
idx, finished, opcode, length, mask,
))))
}
fn read_chunk_md(
chunk: &[u8], server: bool, max_size: usize
chunk: &[u8], server: bool, max_size: usize,
) -> Poll<(usize, bool, OpCode, usize, Option<u32>), ProtocolError> {
let chunk_len = chunk.len();
@ -206,7 +200,7 @@ impl Frame {
/// Parse the input stream into a frame.
pub fn parse<S>(
pl: &mut PayloadHelper<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>,
@ -266,10 +260,9 @@ impl Frame {
// unmask
if let Some(mask) = mask {
#[allow(mutable_transmutes)]
let p: &mut [u8] = unsafe {
let ptr: &[u8] = &data;
mem::transmute(ptr)
&mut *(ptr as *const _ as *mut _)
};
apply_mask(p, mask);
}
@ -281,9 +274,28 @@ 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_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())
} else {
None
};
Some(CloseReason {
code,
description,
})
} else {
None
}
}
/// Generate binary representation
pub fn message<B: Into<Binary>>(
data: B, code: OpCode, finished: bool, genmask: bool
data: B, code: OpCode, finished: bool, genmask: bool,
) -> Binary {
let payload = data.into();
let one: u8 = if finished {
@ -518,10 +530,17 @@ mod tests {
#[test]
fn test_close_frame() {
let frame = Frame::close(CloseCode::Normal, "data", false);
let reason = (CloseCode::Normal, "data");
let frame = Frame::close(Some(reason.into()), false);
let mut v = vec![136u8, 6u8, 3u8, 232u8];
v.extend(b"data");
assert_eq!(frame, v.into());
}
#[test]
fn test_empty_close_frame() {
let frame = Frame::close(None, false);
assert_eq!(frame, vec![0x88, 0x00].into());
}
}

View File

@ -1,4 +1,5 @@
//! 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;

View File

@ -43,7 +43,6 @@
//! # .finish();
//! # }
//! ```
use byteorder::{ByteOrder, NetworkEndian};
use bytes::Bytes;
use futures::{Async, Poll, Stream};
use http::{header, Method, StatusCode};
@ -66,8 +65,7 @@ mod proto;
pub use self::client::{Client, ClientError, ClientHandshake, ClientReader, ClientWriter};
pub use self::context::WebsocketContext;
pub use self::frame::Frame;
pub use self::proto::CloseCode;
pub use self::proto::OpCode;
pub use self::proto::{CloseCode, CloseReason, OpCode};
/// Websocket protocol errors
#[derive(Fail, Debug)]
@ -164,7 +162,7 @@ pub enum Message {
Binary(Binary),
Ping(String),
Pong(String),
Close(CloseCode),
Close(Option<CloseReason>),
}
/// Do websocket handshake and start actor
@ -191,7 +189,7 @@ where
// /// the returned response headers contain the first protocol in this list
// /// which the server also knows.
pub fn handshake<S>(
req: &HttpRequest<S>
req: &HttpRequest<S>,
) -> Result<HttpResponseBuilder, HandshakeError> {
// WebSocket accepts only GET
if *req.method() != Method::GET {
@ -310,10 +308,8 @@ where
}
OpCode::Close => {
self.closed = true;
let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
Ok(Async::Ready(Some(Message::Close(CloseCode::from(
code,
)))))
let close_reason = Frame::parse_close_payload(&payload);
Ok(Async::Ready(Some(Message::Close(close_reason))))
}
OpCode::Ping => Ok(Async::Ready(Some(Message::Ping(
String::from_utf8_lossy(payload.as_ref()).into(),
@ -344,6 +340,20 @@ where
}
}
/// Common writing methods for a websocket.
pub trait WsWriter {
/// Send a text
fn send_text<T: Into<Binary>>(&mut self, text: T);
/// Send a binary
fn send_binary<B: Into<Binary>>(&mut self, data: B);
/// Send a ping message
fn send_ping(&mut self, message: &str);
/// Send a pong message
fn send_pong(&mut self, message: &str);
/// Close the connection
fn send_close(&mut self, reason: Option<CloseReason>);
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -90,10 +90,6 @@ pub enum CloseCode {
/// endpoint that understands only text data MAY send this if it
/// receives a binary message).
Unsupported,
/// Indicates that no status code was included in a closing frame. This
/// close code makes it possible to use a single method, `on_close` to
/// handle even cases where no close code was provided.
Status,
/// Indicates an abnormal closure. If the abnormal closure was due to an
/// error, this close code will not be used. Instead, the `on_error` method
/// of the handler will be called with the error. However, if the connection
@ -138,8 +134,6 @@ pub enum CloseCode {
#[doc(hidden)]
Tls,
#[doc(hidden)]
Empty,
#[doc(hidden)]
Other(u16),
}
@ -150,7 +144,6 @@ impl Into<u16> for CloseCode {
Away => 1001,
Protocol => 1002,
Unsupported => 1003,
Status => 1005,
Abnormal => 1006,
Invalid => 1007,
Policy => 1008,
@ -160,7 +153,6 @@ impl Into<u16> for CloseCode {
Restart => 1012,
Again => 1013,
Tls => 1015,
Empty => 0,
Other(code) => code,
}
}
@ -173,7 +165,6 @@ impl From<u16> for CloseCode {
1001 => Away,
1002 => Protocol,
1003 => Unsupported,
1005 => Status,
1006 => Abnormal,
1007 => Invalid,
1008 => Policy,
@ -183,12 +174,35 @@ impl From<u16> for CloseCode {
1012 => Restart,
1013 => Again,
1015 => Tls,
0 => Empty,
_ => Other(code),
}
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct CloseReason {
pub code: CloseCode,
pub description: Option<String>,
}
impl From<CloseCode> for CloseReason {
fn from(code: CloseCode) -> Self {
CloseReason {
code,
description: None,
}
}
}
impl<T: Into<String>> From<(CloseCode, T)> for CloseReason {
fn from(info: (CloseCode, T)) -> Self {
CloseReason {
code: info.0,
description: Some(info.1.into()),
}
}
}
static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
// TODO: hash is always same size, we dont need String
@ -269,7 +283,6 @@ mod test {
assert_eq!(CloseCode::from(1001u16), CloseCode::Away);
assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol);
assert_eq!(CloseCode::from(1003u16), CloseCode::Unsupported);
assert_eq!(CloseCode::from(1005u16), CloseCode::Status);
assert_eq!(CloseCode::from(1006u16), CloseCode::Abnormal);
assert_eq!(CloseCode::from(1007u16), CloseCode::Invalid);
assert_eq!(CloseCode::from(1008u16), CloseCode::Policy);
@ -279,7 +292,6 @@ mod test {
assert_eq!(CloseCode::from(1012u16), CloseCode::Restart);
assert_eq!(CloseCode::from(1013u16), CloseCode::Again);
assert_eq!(CloseCode::from(1015u16), CloseCode::Tls);
assert_eq!(CloseCode::from(0u16), CloseCode::Empty);
assert_eq!(CloseCode::from(2000u16), CloseCode::Other(2000));
}
@ -289,7 +301,6 @@ mod test {
assert_eq!(1001u16, Into::<u16>::into(CloseCode::Away));
assert_eq!(1002u16, Into::<u16>::into(CloseCode::Protocol));
assert_eq!(1003u16, Into::<u16>::into(CloseCode::Unsupported));
assert_eq!(1005u16, Into::<u16>::into(CloseCode::Status));
assert_eq!(1006u16, Into::<u16>::into(CloseCode::Abnormal));
assert_eq!(1007u16, Into::<u16>::into(CloseCode::Invalid));
assert_eq!(1008u16, Into::<u16>::into(CloseCode::Policy));
@ -299,7 +310,6 @@ mod test {
assert_eq!(1012u16, Into::<u16>::into(CloseCode::Restart));
assert_eq!(1013u16, Into::<u16>::into(CloseCode::Again));
assert_eq!(1015u16, Into::<u16>::into(CloseCode::Tls));
assert_eq!(0u16, Into::<u16>::into(CloseCode::Empty));
assert_eq!(2000u16, Into::<u16>::into(CloseCode::Other(2000)));
}
}

1
tests/test.binary Normal file
View File

@ -0,0 +1 @@
<EFBFBD>TǑɂV<EFBFBD>2<EFBFBD>vI<EFBFBD><EFBFBD><EFBFBD>\<5C><52><CB99><EFBFBD>e<EFBFBD><04>vD<76>:藽<>RV<03>Yp<59><70>;<3B><>G<><47>p!2<7F>C<EFBFBD>.<2E> <0C><><EFBFBD><EFBFBD>pA !<21>ߦ<EFBFBD>x j+Uc<55><63><EFBFBD>X<13>c%<17>;<3B>"y<10><>AI

BIN
tests/test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

View File

@ -1,3 +1,4 @@
#![allow(deprecated)]
extern crate actix;
extern crate actix_web;
extern crate bytes;
@ -9,8 +10,8 @@ use std::io::Read;
use bytes::Bytes;
use flate2::read::GzDecoder;
use futures::Future;
use futures::stream::once;
use futures::Future;
use rand::Rng;
use actix_web::*;

View File

@ -7,10 +7,18 @@ extern crate http;
extern crate tokio_core;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use std::io;
use std::time::Duration;
use actix::*;
use actix_web::*;
use bytes::Bytes;
use futures::Future;
use http::StatusCode;
use serde_json::Value;
use tokio_core::reactor::Timeout;
#[derive(Deserialize)]
struct PParam {
@ -67,6 +75,33 @@ fn test_query_extractor() {
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>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| Ok(format!("{}", data.0)))
.responder()
})
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}"));
}
#[test]
fn test_path_and_query_extractor() {
let mut srv = test::TestServer::new(|app| {
@ -130,6 +165,296 @@ fn test_path_and_query_extractor2() {
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_path_and_query_extractor2_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with3(
|p: Path<PParam>, _: Query<PParam>, data: Json<Value>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
})
.responder()
},
)
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
}
#[test]
fn test_path_and_query_extractor3_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
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()
})
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_path_and_query_extractor4_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
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()
})
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_path_and_query_extractor2_async2() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with3(
|p: Path<PParam>, data: Json<Value>, _: Query<PParam>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
})
.responder()
},
)
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
// client request
let request = srv.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_path_and_query_extractor2_async3() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with3(
|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
})
.responder()
},
)
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
// client request
let request = srv.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_path_and_query_extractor2_async4() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|data: (Json<Value>, Path<PParam>, Query<PParam>)| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| {
Ok(format!(
"Welcome {} - {}!",
data.1.username,
(data.0).0
))
})
.responder()
})
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
// client request
let request = srv.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[cfg(actix_impl_trait)]
fn test_impl_trait(
data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| {
Ok(format!(
"Welcome {} - {}!",
data.1.username,
(data.0).0
))
})
}
#[cfg(actix_impl_trait)]
fn test_impl_trait_err(
_data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other")))
}
#[cfg(actix_impl_trait)]
#[test]
fn test_path_and_query_extractor2_async4_impl_trait() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_async(test_impl_trait)
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
// client request
let request = srv.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[cfg(actix_impl_trait)]
#[test]
fn test_path_and_query_extractor2_async4_impl_trait_err() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_async(test_impl_trait_err)
});
});
// client request
let request = srv.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_non_ascii_route() {
let mut srv = test::TestServer::new(|app| {
@ -148,3 +473,27 @@ fn test_non_ascii_route() {
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"success"));
}
#[test]
fn test_unsafe_path_route() {
let mut srv = test::TestServer::new(|app| {
app.resource("/test/{url}", |r| {
r.f(|r| format!("success: {}", &r.match_info()["url"]))
});
});
// client request
let request = srv.get()
.uri(srv.url("/test/http%3A%2F%2Fexample.com"))
.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"success: http:%2F%2Fexample.com")
);
}

705
tests/test_middleware.rs Normal file
View File

@ -0,0 +1,705 @@
extern crate actix;
extern crate actix_web;
extern crate futures;
extern crate tokio_core;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use actix::*;
use actix_web::*;
use futures::{future, Future};
use tokio_core::reactor::Timeout;
struct MiddlewareTest {
start: Arc<AtomicUsize>,
response: Arc<AtomicUsize>,
finish: Arc<AtomicUsize>,
}
impl<S> middleware::Middleware<S> for MiddlewareTest {
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
self.start.store(
self.start.load(Ordering::Relaxed) + 1,
Ordering::Relaxed,
);
Ok(middleware::Started::Done)
}
fn response(
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<middleware::Response> {
self.response.store(
self.response.load(Ordering::Relaxed) + 1,
Ordering::Relaxed,
);
Ok(middleware::Response::Done(resp))
}
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
self.finish.store(
self.finish.load(Ordering::Relaxed) + 1,
Ordering::Relaxed,
);
middleware::Finished::Done
}
}
#[test]
fn test_middleware() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::new(move |app| {
app.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::new(move |app| {
app.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
#[test]
fn test_resource_middleware() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::new(move |app| {
app.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::new(move |app| {
app.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
#[test]
fn test_scope_middleware() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/scope", |scope| {
scope
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/scope", |scope| {
scope
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
#[test]
fn test_middleware_async_handler() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new()
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/", |r| {
r.route().a(|_| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(|_| Ok(HttpResponse::Ok()))
})
})
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
thread::sleep(Duration::from_millis(20));
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_async_handler() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw = MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", |r| {
r.middleware(mw);
r.route().a(|_| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(|_| Ok(HttpResponse::Ok()))
})
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_async_handler() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/scope", |scope| {
scope
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| {
r.route().a(|_| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(|_| Ok(HttpResponse::Ok()))
})
})
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse<HttpResponse> {
future::result(Err(error::ErrorBadRequest("TEST"))).responder()
}
#[test]
fn test_middleware_async_error() {
let req = Arc::new(AtomicUsize::new(0));
let resp = Arc::new(AtomicUsize::new(0));
let fin = Arc::new(AtomicUsize::new(0));
let act_req = Arc::clone(&req);
let act_resp = Arc::clone(&resp);
let act_fin = Arc::clone(&fin);
let mut srv = test::TestServer::new(move |app| {
app.middleware(MiddlewareTest {
start: Arc::clone(&act_req),
response: Arc::clone(&act_resp),
finish: Arc::clone(&act_fin),
}).handler(index_test_middleware_async_error)
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
assert_eq!(req.load(Ordering::Relaxed), 1);
assert_eq!(resp.load(Ordering::Relaxed), 1);
assert_eq!(fin.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_async_error() {
let req = Arc::new(AtomicUsize::new(0));
let resp = Arc::new(AtomicUsize::new(0));
let fin = Arc::new(AtomicUsize::new(0));
let act_req = Arc::clone(&req);
let act_resp = Arc::clone(&resp);
let act_fin = Arc::clone(&fin);
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/scope", |scope| {
scope
.middleware(MiddlewareAsyncTest {
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))
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
assert_eq!(req.load(Ordering::Relaxed), 1);
assert_eq!(resp.load(Ordering::Relaxed), 1);
assert_eq!(fin.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_async_error() {
let req = Arc::new(AtomicUsize::new(0));
let resp = Arc::new(AtomicUsize::new(0));
let fin = Arc::new(AtomicUsize::new(0));
let act_req = Arc::clone(&req);
let act_resp = Arc::clone(&resp);
let act_fin = Arc::clone(&fin);
let mut srv = test::TestServer::with_factory(move || {
let mw = MiddlewareAsyncTest {
start: Arc::clone(&act_req),
response: Arc::clone(&act_resp),
finish: Arc::clone(&act_fin),
};
App::new().resource("/test", move |r| {
r.middleware(mw);
r.h(index_test_middleware_async_error);
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
assert_eq!(req.load(Ordering::Relaxed), 1);
assert_eq!(resp.load(Ordering::Relaxed), 1);
assert_eq!(fin.load(Ordering::Relaxed), 1);
}
struct MiddlewareAsyncTest {
start: Arc<AtomicUsize>,
response: Arc<AtomicUsize>,
finish: Arc<AtomicUsize>,
}
impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
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(
to.from_err().and_then(move |_| {
start.fetch_add(1, Ordering::Relaxed);
Ok(None)
}),
)))
}
fn response(
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<middleware::Response> {
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
let response = Arc::clone(&self.response);
Ok(middleware::Response::Future(Box::new(
to.from_err().and_then(move |_| {
response.fetch_add(1, Ordering::Relaxed);
Ok(resp)
}),
)))
}
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 |_| {
finish.fetch_add(1, Ordering::Relaxed);
Ok(())
})))
}
}
#[test]
fn test_async_middleware() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::new(move |app| {
app.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
thread::sleep(Duration::from_millis(20));
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_async_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new()
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
thread::sleep(Duration::from_millis(50));
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
#[test]
fn test_async_scope_middleware() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/scope", |scope| {
scope
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
thread::sleep(Duration::from_millis(20));
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_async_scope_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/scope", |scope| {
scope
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
thread::sleep(Duration::from_millis(20));
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
#[test]
fn test_async_resource_middleware() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw = MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", move |r| {
r.middleware(mw);
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
thread::sleep(Duration::from_millis(40));
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_async_resource_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
let mw2 = MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(mw2);
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
thread::sleep(Duration::from_millis(40));
assert_eq!(num3.load(Ordering::Relaxed), 2);
}

View File

@ -14,16 +14,15 @@ extern crate brotli2;
#[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::{Bytes, BytesMut};
use flate2::Compression;
use flate2::read::GzDecoder;
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::Rng;
use std::io::{Read, Write};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{mpsc, Arc};
use std::{net, thread, time};
use tokio_core::net::TcpStream;
@ -62,11 +61,9 @@ fn test_start() {
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())
}),
]
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
});
let srv = srv.bind("127.0.0.1:0").unwrap();
@ -113,11 +110,9 @@ fn test_shutdown() {
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())
}),
]
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
});
let srv = srv.bind("127.0.0.1:0").unwrap();
@ -814,7 +809,7 @@ fn test_h2() {
})
});
let _res = core.run(tcp);
// assert_eq!(res.unwrap(), Bytes::from_static(STR.as_ref()));
// assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref()));
}
#[test]
@ -827,91 +822,3 @@ fn test_application() {
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
struct MiddlewareTest {
start: Arc<AtomicUsize>,
response: Arc<AtomicUsize>,
finish: Arc<AtomicUsize>,
}
impl<S> middleware::Middleware<S> for MiddlewareTest {
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
self.start.store(
self.start.load(Ordering::Relaxed) + 1,
Ordering::Relaxed,
);
Ok(middleware::Started::Done)
}
fn response(
&self, _: &mut HttpRequest<S>, resp: HttpResponse
) -> Result<middleware::Response> {
self.response.store(
self.response.load(Ordering::Relaxed) + 1,
Ordering::Relaxed,
);
Ok(middleware::Response::Done(resp))
}
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
self.finish.store(
self.finish.load(Ordering::Relaxed) + 1,
Ordering::Relaxed,
);
middleware::Finished::Done
}
}
#[test]
fn test_middlewares() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::new(move |app| {
app.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middlewares() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::new(move |app| {
app.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
// assert_eq!(num3.load(Ordering::Relaxed), 1);
}

View File

@ -27,7 +27,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Close(reason) => ctx.close(reason, ""),
ws::Message::Close(reason) => ctx.close(reason),
_ => (),
}
}
@ -55,9 +55,34 @@ fn test_simple() {
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Pong("ping".to_owned())));
writer.close(ws::CloseCode::Normal, "");
writer.close(Some(ws::CloseCode::Normal.into()));
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Normal)));
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)));
let (reader, mut writer) = srv.ws().unwrap();
writer.close(None);
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Close(None)));
}
#[test]
fn test_close_description() {
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
let (reader, mut writer) = srv.ws().unwrap();
let close_reason: ws::CloseReason =
(ws::CloseCode::Normal, "close description").into();
writer.close(Some(close_reason.clone()));
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Close(Some(close_reason))));
}
#[test]
@ -137,7 +162,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for Ws2 {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Close(reason) => ctx.close(reason, ""),
ws::Message::Close(reason) => ctx.close(reason),
_ => (),
}
}

View File

@ -14,8 +14,8 @@ extern crate url;
use futures::Future;
use rand::{thread_rng, Rng};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use actix::prelude::*;
@ -259,7 +259,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for ChatClient {
ctx.stop();
}
} else {
println!("not eaqual");
println!("not equal");
}
}
_ => (),