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

Compare commits

...

192 Commits

Author SHA1 Message Date
Nikolay Kim
6862aa6ee7 prepare release 2018-06-13 05:04:59 -07:00
Nikolay Kim
8a22558f25 http/2 end-of-frame is not set if body is empty bytes #307 2018-06-12 14:47:45 -07:00
Nikolay Kim
b5b9f9656e do not allow stream or actor responses for internal error #301 2018-06-11 19:44:11 -07:00
Nikolay Kim
2fffc55d34 update changelog 2018-06-11 18:53:36 -07:00
Nikolay Kim
7d39f1582e InternalError can trigger memory unsafety #301 2018-06-11 18:52:54 -07:00
Nikolay Kim
75ed053a35 bump version 2018-06-11 12:32:31 -07:00
Nikolay Kim
cfedf5fff4 Merge branch '0.6' of github.com:actix/actix-web into 0.6 2018-06-11 12:32:05 -07:00
Nikolay Kim
be73a36339 use custom resolver 2018-06-11 12:28:43 -07:00
Nikolay Kim
1ad8ba2604 Fix docs.rs build 2018-06-11 12:25:20 -07:00
Armin Ronacher
6848a12095 prepare 0.6.12 release 2018-06-09 01:22:19 +02:00
Nikolay Kim
4797298706 Allow to use custom resolver for ClientConnector 2018-06-08 16:10:47 -07:00
Nikolay Kim
5eaf4cbefd update changelog 2018-06-08 08:42:10 -07:00
Nikolay Kim
7f1844e541 fix doc test 2018-06-07 20:22:50 -07:00
Nikolay Kim
6c7ac7fc22 update changelog 2018-06-07 20:07:58 -07:00
Nikolay Kim
42f9e1034b add Host predicate 2018-06-07 20:05:45 -07:00
Nikolay Kim
e3cd0fdd13 add application filters 2018-06-07 20:05:26 -07:00
Nikolay Kim
40ff550460 update changelog 2018-06-07 20:04:40 -07:00
Armin Ronacher
7119340d44 Added improved failure interoperability with downcasting (#285)
Deprecates Error::cause and introduces failure interoperability functions and downcasting.
2018-06-07 20:03:10 -07:00
Nikolay Kim
e140bc3906 prep release 2018-06-05 09:44:29 -07:00
Nikolay Kim
fdc08d365d metadata for docs.rs 2018-06-05 08:59:30 -07:00
Nikolay Kim
8f1b88e39e update changelog 2018-06-05 08:53:27 -07:00
Nikolay Kim
6a40a0a466 fix multipart boundary parsing #282 2018-06-05 08:52:46 -07:00
Nikolay Kim
89fc6b6ac9 changelog 2018-06-05 07:42:52 -07:00
Nikolay Kim
afa67b838a CORS: Do not validate Origin header on non-OPTION requests #271 2018-06-05 07:41:13 -07:00
Nikolay Kim
f7b7d282bf Middleware::response is not invoked if error result was returned by another Middleware::start #255 2018-06-04 13:57:54 -07:00
Nikolay Kim
09780ea9f3 changelog updates 2018-06-02 15:03:34 -07:00
Nikolay Kim
a7dab950f3 Support chunked encoding for UrlEncoded body #262 2018-06-02 15:02:42 -07:00
Nikolay Kim
ec0737e392 fix doc test 2018-06-02 13:48:37 -07:00
Nikolay Kim
d664993d56 remove debug prints 2018-06-02 11:58:11 -07:00
Nikolay Kim
a9c6c57a67 remove debug print 2018-06-02 11:55:27 -07:00
Nikolay Kim
08e7374eee update changelog 2018-06-02 11:46:53 -07:00
Nikolay Kim
42da1448fb fixed HttpRequest::url_for for a named route with no variables #265 2018-06-02 11:46:02 -07:00
Nikolay Kim
9f9e0b98ad change homepage link 2018-05-24 08:55:10 -07:00
Nikolay Kim
556646aaec update changelog 2018-05-24 07:56:51 -07:00
Nikolay Kim
174fb0b5f4 Merge pull request #239 from max-frai/master
Add ability to set encoding for exact NamedFile.
2018-05-24 07:46:53 -07:00
Nikolay Kim
836706653b Merge branch 'master' into master 2018-05-24 07:46:46 -07:00
Nikolay Kim
17f1a2b92a more scope tests 2018-05-23 14:11:01 -07:00
Nikolay Kim
3b08b16c11 bump version 2018-05-23 13:21:54 -07:00
Nikolay Kim
68eb2f26c9 Allow to use path without traling slashes for scope registration #241 2018-05-23 13:21:29 -07:00
Nikolay Kim
72757887c9 update doc links 2018-05-23 11:20:12 -07:00
Nikolay Kim
eb5dbd43ae update changelog 2018-05-23 10:37:17 -07:00
Nikolay Kim
1f1dfac3f9 Merge pull request #240 from ivanovaleksey/patch-2
Fix TestServer::post
2018-05-23 09:50:40 -07:00
Aleksey Ivanov
2479b14aba Fix TestServer::post 2018-05-23 19:07:42 +03:00
Max Frai
ac24703512 Add ability to set encoding for exact NamedFile. 2018-05-23 09:12:23 +03:00
Nikolay Kim
db0091ba6f disable server test for windows 2018-05-21 21:01:52 -07:00
Nikolay Kim
2159158c30 Fix streaming response with body compression 2018-05-21 20:50:10 -07:00
Nikolay Kim
76d790425f bump version 2018-05-21 19:07:56 -07:00
Nikolay Kim
90968d4333 Drop connection if request's payload is not fulle consumed #236 2018-05-21 18:54:17 -07:00
Nikolay Kim
577a509875 increase delay 2018-05-21 16:12:33 -07:00
Nikolay Kim
a9728abfc8 run coverage report on 1.24 2018-05-20 21:10:50 -07:00
Nikolay Kim
14d1b8e2b6 prepare release 2018-05-20 21:09:54 -07:00
Nikolay Kim
285c73e95e Re-use tcp listener on pause/resume 2018-05-20 20:47:20 -07:00
Nikolay Kim
483db7028c expose low level data 2018-05-20 20:37:19 -07:00
Nikolay Kim
082ff46041 Fix scope resource path extractor #234 2018-05-20 17:04:23 -07:00
Nikolay Kim
f32e8f22c8 Merge pull request #231 from qrvaelet/ranges
NamedFile: range upgrade
2018-05-20 09:18:46 -07:00
Nikolay Kim
766dde7c42 Merge branch 'master' into ranges 2018-05-20 08:51:07 -07:00
qrvaelet
b68687044e range header syntax fix, change range to content-range in responses, enabled accept ranges, tests for content-range, content-length, and range status code 2018-05-20 17:40:36 +02:00
Nikolay Kim
c9e84e9dd3 Merge pull request #233 from sindreij/patch-1
Fix some typos in server/srv.rs
2018-05-20 06:19:53 -07:00
Sindre Johansen
0126ac46fc Fix some typos in server/srv.rs
Hello! This looks like a great library, thanks for creating it! While reading through the documentation I found a few typos.
2018-05-20 14:43:26 +02:00
Nikolay Kim
9b7ea836d0 bump version 2018-05-17 18:34:09 -07:00
Nikolay Kim
537b420d35 Fix compilation with --no-default-features 2018-05-17 18:33:48 -07:00
Nikolay Kim
16906c5951 clippy warnings 2018-05-17 12:23:37 -07:00
Nikolay Kim
45e9aaa462 rustfmt 0.7 2018-05-17 12:20:20 -07:00
Nikolay Kim
564cc15c04 update changes 2018-05-17 12:20:04 -07:00
Nikolay Kim
8fd18d56a5 Merge pull request #227 from qrvaelet/ranges
NamedFile: added basic ranges header support, added content-length support
2018-05-17 12:18:10 -07:00
Nikolay Kim
a5692d4ecf Merge branch 'master' into ranges 2018-05-17 11:16:08 -07:00
qrvaelet
2d83f79433 NamedFile: added ranges support, content-length support 2018-05-17 20:09:41 +02:00
Nikolay Kim
f3ece74406 better error handling 2018-05-17 10:58:08 -07:00
Nikolay Kim
8de1f60347 add session extractor doc api 2018-05-16 21:05:59 -07:00
Nikolay Kim
b4252f8fd1 implement extractor for Session 2018-05-16 21:02:51 -07:00
Nikolay Kim
fe2b50a9ef update changelog 2018-05-16 11:02:50 -07:00
Nikolay Kim
d8ae8c3821 Merge pull request #225 from mitsuhiko/feature/addrs-with-scheme
Support returning addresses plus scheme from the server
2018-05-16 11:02:15 -07:00
Nikolay Kim
c9a026fabb Merge branch 'master' into feature/addrs-with-scheme 2018-05-16 11:01:45 -07:00
Nikolay Kim
64eca1546e Merge pull request #224 from mitsuhiko/feature/listen-tls
Add support for listen_tls/listen_ssl
2018-05-16 11:01:28 -07:00
Nikolay Kim
b19fe98ff4 Merge branch 'master' into feature/listen-tls 2018-05-16 11:01:21 -07:00
Nikolay Kim
b393ddf879 fix panic during middleware execution #226 2018-05-16 11:00:29 -07:00
Armin Ronacher
7bb7d85c1d Added support for returning addresses plus scheme from the server 2018-05-16 16:17:27 +02:00
Armin Ronacher
6e976153e7 Add support for listen_tls/listen_ssl 2018-05-16 15:20:47 +02:00
Nikolay Kim
03e758cee4 bump version 2018-05-15 19:08:34 -07:00
Nikolay Kim
0d36b8f826 fix 1.24 compatibility 2018-05-15 19:07:43 -07:00
Nikolay Kim
f82fa08d72 various optimizations 2018-05-15 16:49:03 -07:00
Nikolay Kim
d6787e6c56 prepare release 2018-05-15 10:20:32 -07:00
Nikolay Kim
b9d870645f store cookies in extensions 2018-05-15 10:09:48 -07:00
Nikolay Kim
ef89430f9b undeprecate query() and store query in extensions 2018-05-15 09:53:58 -07:00
Nikolay Kim
953a0d4e4a add test case for #222 2018-05-15 09:29:59 -07:00
Nikolay Kim
5ea2d68438 h1 decoder blocks on error #222 2018-05-15 07:55:36 -07:00
Nikolay Kim
d65a03f6ac use latest nightly for appveyor 2018-05-13 08:43:09 -07:00
Nikolay Kim
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
Nikolay Kim
d455e2cd13 Merge branch 'master' into patch-1 2018-05-11 21:56:35 -07:00
Nikolay Kim
9306631d6e Fix segfault in ServerSettings::get_response_builder() 2018-05-11 21:19:48 -07:00
skorgu
f735da504b Include mention of http client in README.md 2018-05-11 20:36:54 -04:00
Nikolay Kim
487a713ca0 update doc string 2018-05-11 15:01:15 -07:00
Nikolay Kim
095ad328ee prepare release 2018-05-10 15:45:06 -07:00
Nikolay Kim
a38afa0cec --no-count for tarpaulin 2018-05-10 13:05:56 -07:00
Nikolay Kim
9619698543 doc string 2018-05-10 13:04:56 -07:00
Nikolay Kim
4b1a471b35 add more examples for extractor config 2018-05-10 13:03:43 -07:00
Nikolay Kim
b6039b0bff add doc string 2018-05-10 11:04:03 -07:00
Nikolay Kim
d8fa43034f export ExtractorConfig type 2018-05-10 11:00:22 -07:00
Nikolay Kim
92f993e054 Fix client request timeout handling 2018-05-10 09:37:38 -07:00
Nikolay Kim
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
Nikolay Kim
dee6aed010 Merge branch 'master' into improve-readme 2018-05-10 09:15:44 -07:00
Nikolay Kim
76f021a6e3 add tests for ErrorXXX helpers 2018-05-10 09:13:26 -07:00
Benjamin Gröber
2f244ea028 fix order of name and id in readme example 2018-05-10 18:12:59 +02:00
Nikolay Kim
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
dowwie
8b473745cb added error response functions for 501,502,503,504 2018-05-10 11:26:38 -04:00
Nikolay Kim
18575ee1ee Add Router::with_async() method for async handler registration 2018-05-09 16:27:31 -07:00
Nikolay Kim
e58b38fd13 deprecate WsWrite from top level mod 2018-05-09 06:12:16 -07:00
Nikolay Kim
b043c34632 bump version 2018-05-09 06:05:44 -07:00
Nikolay Kim
b748bf3b0d make api public 2018-05-09 06:05:16 -07:00
Nikolay Kim
be12d5e6fc make WsWriter trait optional 2018-05-09 05:48:06 -07:00
Nikolay Kim
7c4941f868 update migration doc 2018-05-08 18:48:09 -07:00
Nikolay Kim
d1f5c457c4 Merge branch 'master' of github.com:actix/actix-web 2018-05-08 18:35:52 -07:00
Nikolay Kim
c26c5fd9a4 prep release 2018-05-08 18:34:36 -07:00
Nikolay Kim
4a73d1c8c1 Merge pull request #216 from lcowell/lcowell-scoupe
replace typo `scoupe` with `scope`
2018-05-08 17:47:20 -07:00
Luke Cowell
7c395fcc83 replace typo scoupe with scope 2018-05-08 17:40:18 -07:00
Nikolay Kim
54c33a7aff Allow to exclude certain endpoints from logging #211 2018-05-08 16:30:34 -07:00
Nikolay Kim
47d80382b2 Fix http/2 payload streaming #215 2018-05-08 15:44:50 -07:00
Nikolay Kim
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
Niklas Fiekas
6f75b0e95e let Path::from_request() fail with ErrorNotFound 2018-05-08 22:59:46 +02:00
Nikolay Kim
b3cc43bb9b Fix connector's default keep-alive and lifetime settings #212 2018-05-08 13:41:04 -07:00
Nikolay Kim
ecda97aadd update doc string 2018-05-08 05:54:06 -07:00
Nikolay Kim
8cda362866 simplify pipeline 2018-05-07 16:09:41 -07:00
Nikolay Kim
3c6c1268c9 travis cover report 2018-05-07 15:54:29 -07:00
Nikolay Kim
72908d974c test for Scope::route(); prep release 2018-05-07 15:19:03 -07:00
Nikolay Kim
c755d71a8b add filters support to scopes 2018-05-07 14:40:04 -07:00
Nikolay Kim
a817ddb57b add variable segments support for scope prefix 2018-05-07 13:50:43 -07:00
Nikolay Kim
44c36e93d1 Merge pull request #210 from andreevlex/feature/spelling-check-06-05
spelling check
2018-05-07 11:30:46 -07:00
Nikolay Kim
c92ebc22d7 Merge branch 'master' into feature/spelling-check-06-05 2018-05-07 11:30:39 -07:00
Alexander Andreev
599fd6af93 fix formatting 2018-05-07 20:53:45 +03:00
Nikolay Kim
fa81d97004 more handler tests 2018-05-06 20:05:31 -07:00
Nikolay Kim
c54f045b39 more handler tests 2018-05-06 15:11:36 -07:00
Alexander Andreev
cd11293c1f spelling check 2018-05-06 19:07:30 +03:00
Nikolay Kim
45325a5f75 more middleware tests 2018-05-06 08:33:41 -07:00
Nikolay Kim
a7c40024ce async handle middleware test 2018-05-05 18:40:16 -07:00
Nikolay Kim
0af4d01fe4 move middleware tests to seprate module 2018-05-05 12:18:43 -07:00
Nikolay Kim
bd6e18b7fe update migration doc 2018-05-04 13:38:17 -07:00
Nikolay Kim
f66cf16823 upgrade regex 2018-05-04 12:25:06 -07:00
Nikolay Kim
03d6b04eef update tests 2018-05-04 12:11:38 -07:00
Nikolay Kim
f37880d89c refactor Responder trait 2018-05-04 11:44:22 -07:00
Nikolay Kim
8b43574bd5 Merge branch 'master' of github.com:actix/actix-web 2018-05-03 16:27:12 -07:00
Nikolay Kim
b07d0e712f always provide backtrace for error 2018-05-03 16:26:42 -07:00
Nikolay Kim
acd7380865 rename Reply to a AsyncResult 2018-05-03 16:22:08 -07:00
Nikolay Kim
0208dfb6b2 Merge pull request #208 from DenisKolodin/ws-trait
Add WsWriter trait
2018-05-03 10:43:44 -07:00
Nikolay Kim
bb61dd41af Merge branch 'master' into ws-trait 2018-05-03 08:57:45 -07:00
Nikolay Kim
58079b5bbe add session test 2018-05-02 19:11:44 -07:00
Nikolay Kim
3623383e83 fix tests 2018-05-02 16:48:42 -07:00
Nikolay Kim
7036656ae4 make Reply generic over error too 2018-05-02 16:33:29 -07:00
Nikolay Kim
32a2866449 Allow to override files listing renderer for #203 2018-05-02 15:53:07 -07:00
Nikolay Kim
35a4078434 update changelog 2018-05-02 13:43:51 -07:00
Nikolay Kim
4ca5d8bcfc add FromRequest impl for tuples of various length 2018-05-02 13:38:25 -07:00
Nikolay Kim
a38acb41e5 better query example 2018-05-02 06:30:06 -07:00
Nikolay Kim
31e23d4ab1 add query deprecation info 2018-05-02 06:28:38 -07:00
Nikolay Kim
1aadfee6f7 rename from_default to extract 2018-05-02 06:09:50 -07:00
Nikolay Kim
76b644365f use read only ref for FromRequest; remove unnecessary static 2018-05-02 06:07:30 -07:00
Denis Kolodin
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
Nikolay Kim
a1958deaae add impl Future for Reply 2018-05-01 17:30:06 -07:00
Nikolay Kim
8d65468c58 refactor FromRequest trait 2018-05-01 17:19:15 -07:00
Nikolay Kim
195246573e rename threads to workers 2018-05-01 13:15:35 -07:00
Nikolay Kim
e01102bda2 no need for mut 2018-05-01 11:45:46 -07:00
Nikolay Kim
9b6343d54b refactor session impl 2018-05-01 09:40:23 -07:00
Nikolay Kim
d9a4fadaae make HttpRequest::extensions() readonly 2018-05-01 09:05:50 -07:00
Nikolay Kim
48e05a2d87 add nested scope support 2018-04-30 22:04:24 -07:00
Nikolay Kim
70d0c5c700 update changes 2018-04-30 19:56:17 -07:00
Nikolay Kim
d43ca96c5c Allow to use ssl and non-ssl connections with the same HttpServer #206 2018-04-30 19:51:55 -07:00
Nikolay Kim
bfd46e6a71 update doc string 2018-04-29 22:28:16 -07:00
Nikolay Kim
25b245ac72 allow to use custom state for scope 2018-04-29 22:19:52 -07:00
Nikolay Kim
eefbe19651 remove deprecated types and methods 2018-04-29 21:05:10 -07:00
Nikolay Kim
ab4e889f96 add middleware finished handler for route middleware 2018-04-29 20:50:38 -07:00
Nikolay Kim
91235ac816 fix reading from socket 2018-04-29 20:34:59 -07:00
Nikolay Kim
9c1bda3eca fix stable compiler compatibility 2018-04-29 19:49:26 -07:00
Nikolay Kim
4a29f12876 update doc string; missing file 2018-04-29 19:39:28 -07:00
Nikolay Kim
368730f5f1 Add route scopes #202 2018-04-29 19:35:50 -07:00
Nikolay Kim
aa757a5be8 Allow to access Error's backtrace object 2018-04-29 14:21:50 -07:00
Nikolay Kim
03ded62337 bump minimum supported rustc version because of minor version change of parking_lot crate 2018-04-29 14:13:46 -07:00
Nikolay Kim
c72d1381a6 clippy warnings 2018-04-29 09:09:08 -07:00
Nikolay Kim
d98d723f97 bump rustc version requirements 2018-04-29 08:24:19 -07:00
Nikolay Kim
eb6e618812 Merge pull request #204 from svenstaro/master
Add Content-Disposition to NamedFile (fixes #172)
2018-04-29 08:18:48 -07:00
Sven-Hendrik Haase
de222fe33b Merge and fix PR comments 2018-04-29 14:02:50 +02:00
Nikolay Kim
de49796fd1 clippy warnings; fmt 2018-04-28 22:55:47 -07:00
Nikolay Kim
a38c3985f6 refactor http1 parser 2018-04-28 22:20:32 -07:00
Sven-Hendrik Haase
492c072564 Add Content-Disposition to NamedFile (fixes #172) 2018-04-27 09:49:55 +02:00
Nikolay Kim
fd876efa68 allow to access application state during configuration stage 2018-04-26 09:05:07 -07:00
Nikolay Kim
c5b9bed478 update changes 2018-04-26 08:01:08 -07:00
Nikolay Kim
3eba383cdc Merge pull request #196 from fuchsnj/websocket_close_reason
Websocket close reason
2018-04-26 07:55:32 -07:00
Nikolay Kim
927f2e594e Merge branch 'master' into websocket_close_reason 2018-04-25 20:17:19 -07:00
Nikolay Kim
fa9edf2180 prep release 2018-04-24 12:34:10 -07:00
Nikolay Kim
5ca904d1db make flate crate optional 2018-04-24 12:24:04 -07:00
Nathan Fox
f8b75c157f fix style 2018-04-22 11:43:47 -04:00
Nathan Fox
b7b61afacc add ws close description parse test 2018-04-21 17:20:23 -04:00
Nathan Fox
507361c1df Merge branch 'master' into websocket_close_reason 2018-04-21 17:05:43 -04:00
Nathan Fox
f6fd9e70f9 code cleanup 2018-04-21 16:53:55 -04:00
Nathan Fox
de8a09254d use Optional with websocket close reason 2018-04-21 16:50:27 -04:00
85 changed files with 7649 additions and 3555 deletions

View File

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

View File

@@ -8,19 +8,13 @@ cache:
matrix: matrix:
include: include:
- rust: 1.21.0 - rust: 1.24.0
- rust: stable - rust: stable
- rust: beta - rust: beta
- rust: nightly - rust: nightly
allow_failures: allow_failures:
- rust: nightly - rust: nightly
#rust:
# - 1.21.0
# - stable
# - beta
# - nightly-2018-01-03
env: env:
global: global:
# - RUSTFLAGS="-C link-dead-code" # - RUSTFLAGS="-C link-dead-code"
@@ -37,24 +31,25 @@ before_script:
script: script:
- | - |
if [[ "$TRAVIS_RUST_VERSION" != "1.24.0" ]]; then
cargo clean cargo clean
cargo test --features="alpn,tls" -- --nocapture cargo test --features="alpn,tls" -- --nocapture
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "1.24.0" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi
# Upload docs # Upload docs
after_success: 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 && cargo doc --features "alpn, tls, session" --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html && 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 && 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 && ./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" echo "Uploaded documentation"
fi 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,149 @@
# Changes # Changes
## [0.6.13] - 2018-06-13
### Fixed
* http/2 end-of-frame is not set if body is empty bytes #307
* InternalError can trigger memory unsafety #301
* Fix docs.rs build
## [0.6.12] - 2018-06-08
### Added
* Add `Host` filter #287
* Allow to filter applications
* Improved failure interoperability with downcasting #285
* Allow to use custom resolver for `ClientConnector`
### Deprecated
* `Error::cause()` and introduces failure interoperability functions and downcasting.
## [0.6.11] - 2018-06-05
### Fixed
* Support chunked encoding for UrlEncoded body #262
* `HttpRequest::url_for()` for a named route with no variables segments #265
* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255
* CORS: Do not validate Origin header on non-OPTION requests #271
* Fix multipart upload "Incomplete" error #282
## [0.6.10] - 2018-05-24
### Added
* Allow to use path without traling slashes for scope registration #241
* Allow to set encoding for exact NamedFile #239
### Fixed
* `TestServer::post()` actually sends `GET` request #240
## 0.6.9 (2018-05-22)
* Drop connection if request's payload is not fully consumed #236
* Fix streaming response with body compression
## 0.6.8 (2018-05-20)
* Fix scope resource path extractor #234
* Re-use tcp listener on pause/resume
## 0.6.7 (2018-05-17)
* Fix compilation with --no-default-features
## 0.6.6 (2018-05-17)
* Panic during middleware execution #226
* Add support for listen_tls/listen_ssl #224
* Implement extractor for `Session`
* Ranges header support for NamedFile #60
## 0.6.5 (2018-05-15)
* Fix error handling during request decoding #222
## 0.6.4 (2018-05-11)
* Fix segfault in ServerSettings::get_response_builder()
## 0.6.3 (2018-05-10)
* 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) ## 0.5.5 (2018-04-24)
* Fix panic when Websocket is closed with no error code #191 * Fix panic when Websocket is closed with no error code #191

View File

@@ -1,11 +1,11 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "0.5.5" version = "0.6.13"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://github.com/actix/actix-web" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/" documentation = "https://docs.rs/actix-web/"
categories = ["network-programming", "asynchronous", categories = ["network-programming", "asynchronous",
@@ -46,8 +46,11 @@ flate2-c = ["flate2/miniz-sys"]
# rust backend for flate2 crate # rust backend for flate2 crate
flate2-rust = ["flate2/rust_backend"] flate2-rust = ["flate2/rust_backend"]
[package.metadata.docs.rs]
features = ["tls", "alpn", "session", "brotli", "flate2-c"]
[dependencies] [dependencies]
actix = "^0.5.5" actix = "^0.5.8"
base64 = "0.9" base64 = "0.9"
bitflags = "1.0" bitflags = "1.0"
@@ -63,7 +66,7 @@ mime_guess = "2.0.0-alpha"
num_cpus = "1.0" num_cpus = "1.0"
percent-encoding = "1.0" percent-encoding = "1.0"
rand = "0.4" rand = "0.4"
regex = "0.2" regex = "1.0"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.5" serde_urlencoded = "0.5"
@@ -76,7 +79,7 @@ lazy_static = "1.0"
url = { version="1.7", features=["query_encoding"] } url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode"] } cookie = { version="0.10", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true } brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="1.0", default-features = false } flate2 = { version="1.0", optional = true, default-features = false }
# io # io
mio = "^0.6.13" mio = "^0.6.13"
@@ -85,6 +88,7 @@ bytes = "0.4"
byteorder = "1" byteorder = "1"
futures = "0.1" futures = "0.1"
futures-cpupool = "0.1" futures-cpupool = "0.1"
slab = "0.4"
tokio-io = "0.1" tokio-io = "0.1"
tokio-core = "0.1" tokio-core = "0.1"

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. Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols * Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols
* Streaming and pipelining * Streaming and pipelining
* Keep-alive and slow requests handling * Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support * Client/server [WebSockets](https://actix.rs/docs/websockets/) support
* Transparent content compression/decompression (br, gzip, deflate) * Transparent content compression/decompression (br, gzip, deflate)
* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html) * Configurable [request routing](https://actix.rs/docs/url-dispatch/)
* Graceful server shutdown * Graceful server shutdown
* Multipart streams * Multipart streams
* Static assets * Static assets
@@ -18,25 +18,26 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers), [DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html), [CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html)) [CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Built on top of [Actix actor framework](https://github.com/actix/actix) * Built on top of [Actix actor framework](https://github.com/actix/actix)
## Documentation & community resources ## Documentation & community resources
* [User Guide](https://actix.rs/book/actix-web/) * [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
* [API Documentation (Releases)](https://docs.rs/actix-web/) * [API Documentation (Releases)](https://docs.rs/actix-web/)
* [Chat on gitter](https://gitter.im/actix/actix) * [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web) * 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 ## Example
```rust ```rust
extern crate actix_web; extern crate actix_web;
use actix_web::{http, server, App, Path}; use actix_web::{http, server, App, Path, Responder};
fn index(info: Path<(u32, String)>) -> String { fn index(info: Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.0, info.1) format!("Hello {}! id:{}", info.1, info.0)
} }
fn main() { fn main() {

View File

@@ -1,8 +1,15 @@
extern crate version_check; extern crate version_check;
fn main() { 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() { 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) => (), Some(false) => (),
None => (), None => (),
}; };

View File

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

View File

@@ -1,22 +1,20 @@
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::mem;
use std::rc::Rc; use std::rc::Rc;
use handler::Reply; use handler::{AsyncResult, FromRequest, Handler, Responder, RouteHandler, WrapHandler};
use handler::{FromRequest, Handler, Responder, RouteHandler, WrapHandler};
use header::ContentEncoding; use header::ContentEncoding;
use http::Method; use http::Method;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::Middleware; use middleware::Middleware;
use pipeline::{HandlerType, Pipeline, PipelineHandler}; use pipeline::{HandlerType, Pipeline, PipelineHandler};
use pred::Predicate;
use resource::ResourceHandler; use resource::ResourceHandler;
use router::{Resource, Router}; use router::{Resource, Router};
use scope::Scope;
use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, ServerSettings}; 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 /// Application
pub struct HttpApplication<S = ()> { pub struct HttpApplication<S = ()> {
state: Rc<S>, state: Rc<S>,
@@ -24,6 +22,7 @@ pub struct HttpApplication<S = ()> {
prefix_len: usize, prefix_len: usize,
router: Router, router: Router,
inner: Rc<UnsafeCell<Inner<S>>>, inner: Rc<UnsafeCell<Inner<S>>>,
filters: Option<Vec<Box<Predicate<S>>>>,
middlewares: Rc<Vec<Box<Middleware<S>>>>, middlewares: Rc<Vec<Box<Middleware<S>>>>,
} }
@@ -32,7 +31,12 @@ pub(crate) struct Inner<S> {
default: ResourceHandler<S>, default: ResourceHandler<S>,
encoding: ContentEncoding, encoding: ContentEncoding,
resources: Vec<ResourceHandler<S>>, 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> { impl<S: 'static> PipelineHandler<S> for Inner<S> {
@@ -40,12 +44,17 @@ impl<S: 'static> PipelineHandler<S> for Inner<S> {
self.encoding 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 { match htype {
HandlerType::Normal(idx) => { HandlerType::Normal(idx) => {
self.resources[idx].handle(req, Some(&mut self.default)) 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), HandlerType::Default => self.default.handle(req, None),
} }
} }
@@ -63,25 +72,51 @@ impl<S: 'static> HttpApplication<S> {
HandlerType::Normal(idx) HandlerType::Normal(idx)
} else { } else {
let inner = self.as_ref(); let inner = self.as_ref();
for idx in 0..inner.handlers.len() { let path: &'static str =
let &(ref prefix, _) = &inner.handlers[idx]; unsafe { &*(&req.path()[inner.prefix..] as *const _) };
let m = { let path_len = path.len();
let path = &req.path()[inner.prefix..]; 'outer: for idx in 0..inner.handlers.len() {
path.starts_with(prefix) match inner.handlers[idx] {
&& (path.len() == prefix.len() PrefixHandlerType::Handler(ref prefix, _) => {
|| path.split_at(prefix.len()).1.starts_with('/')) let m = {
}; path.starts_with(prefix)
&& (path_len == prefix.len()
|| path.split_at(prefix.len()).1.starts_with('/'))
};
if m { if m {
let path: &'static str = unsafe { let prefix_len = inner.prefix + prefix.len();
mem::transmute(&req.path()[inner.prefix + prefix.len()..]) let path: &'static str =
}; unsafe { &*(&req.path()[prefix_len..] as *const _) };
if path.is_empty() {
req.match_info_mut().add("tail", ""); req.set_prefix_len(prefix_len as u16);
} else { if path.is_empty() {
req.match_info_mut().add("tail", path.split_at(1).1); req.match_info_mut().add("tail", "/");
} else {
req.match_info_mut().add("tail", path);
}
return HandlerType::Handler(idx);
}
}
PrefixHandlerType::Scope(ref pattern, _, ref filters) => {
if let Some(prefix_len) =
pattern.match_prefix_with_params(path, req.match_info_mut())
{
for filter in filters {
if !filter.check(req) {
continue 'outer;
}
}
let prefix_len = inner.prefix + prefix_len;
let path: &'static str =
unsafe { &*(&req.path()[prefix_len..] as *const _) };
req.set_prefix_len(prefix_len as u16);
req.match_info_mut().set("tail", path);
return HandlerType::Handler(idx);
}
} }
return HandlerType::Handler(idx);
} }
} }
HandlerType::Default HandlerType::Default
@@ -89,7 +124,7 @@ impl<S: 'static> HttpApplication<S> {
} }
#[cfg(test)] #[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); let tp = self.get_handler(&mut req);
unsafe { &mut *self.inner.get() }.handle(req, tp) unsafe { &mut *self.inner.get() }.handle(req, tp)
} }
@@ -109,11 +144,21 @@ impl<S: 'static> HttpHandler for HttpApplication<S> {
|| path.split_at(self.prefix_len).1.starts_with('/')) || path.split_at(self.prefix_len).1.starts_with('/'))
}; };
if m { if m {
let mut req = req.with_state(Rc::clone(&self.state), self.router.clone()); let mut req2 =
let tp = self.get_handler(&mut req); req.clone_with_state(Rc::clone(&self.state), self.router.clone());
if let Some(ref filters) = self.filters {
for filter in filters {
if !filter.check(&mut req2) {
return Err(req);
}
}
}
let tp = self.get_handler(&mut req2);
let inner = Rc::clone(&self.inner); let inner = Rc::clone(&self.inner);
Ok(Box::new(Pipeline::new( Ok(Box::new(Pipeline::new(
req, req2,
Rc::clone(&self.middlewares), Rc::clone(&self.middlewares),
inner, inner,
tp, tp,
@@ -130,10 +175,11 @@ struct ApplicationParts<S> {
settings: ServerSettings, settings: ServerSettings,
default: ResourceHandler<S>, default: ResourceHandler<S>,
resources: Vec<(Resource, Option<ResourceHandler<S>>)>, resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
handlers: Vec<(String, Box<RouteHandler<S>>)>, handlers: Vec<PrefixHandlerType<S>>,
external: HashMap<String, Resource>, external: HashMap<String, Resource>,
encoding: ContentEncoding, encoding: ContentEncoding,
middlewares: Vec<Box<Middleware<S>>>, middlewares: Vec<Box<Middleware<S>>>,
filters: Vec<Box<Predicate<S>>>,
} }
/// Structure that follows the builder pattern for building application /// Structure that follows the builder pattern for building application
@@ -156,6 +202,7 @@ impl App<()> {
handlers: Vec::new(), handlers: Vec::new(),
external: HashMap::new(), external: HashMap::new(),
encoding: ContentEncoding::Auto, encoding: ContentEncoding::Auto,
filters: Vec::new(),
middlewares: Vec::new(), middlewares: Vec::new(),
}), }),
} }
@@ -195,11 +242,18 @@ where
handlers: Vec::new(), handlers: Vec::new(),
external: HashMap::new(), external: HashMap::new(),
middlewares: Vec::new(), middlewares: Vec::new(),
filters: Vec::new(),
encoding: ContentEncoding::Auto, encoding: ContentEncoding::Auto,
}), }),
} }
} }
/// 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. /// Set application prefix.
/// ///
/// Only requests that match the application's prefix get /// Only requests that match the application's prefix get
@@ -227,8 +281,8 @@ where
/// let app = App::new() /// let app = App::new()
/// .prefix("/app") /// .prefix("/app")
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok()); /// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
@@ -245,6 +299,26 @@ where
self self
} }
/// Add match predicate to application.
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn main() {
/// App::new()
/// .filter(pred::Host("www.rust-lang.org"))
/// .resource("/path", |r| r.f(|_| HttpResponse::Ok()))
/// # .finish();
/// # }
/// ```
pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> App<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.filters.push(Box::new(p));
}
self
}
/// Configure route for a specific path. /// Configure route for a specific path.
/// ///
/// This is a simplified version of the `App::resource()` method. /// This is a simplified version of the `App::resource()` method.
@@ -260,10 +334,12 @@ where
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new()
/// .route("/test", http::Method::GET, /// .route("/test", http::Method::GET, |_: HttpRequest| {
/// |_: HttpRequest| HttpResponse::Ok()) /// HttpResponse::Ok()
/// .route("/test", http::Method::POST, /// })
/// |_: HttpRequest| HttpResponse::MethodNotAllowed()); /// .route("/test", http::Method::POST, |_: HttpRequest| {
/// HttpResponse::MethodNotAllowed()
/// });
/// } /// }
/// ``` /// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S> pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S>
@@ -274,7 +350,7 @@ where
{ {
{ {
let parts: &mut ApplicationParts<S> = unsafe { 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 // get resource handler
@@ -295,6 +371,48 @@ where
self 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 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. /// Configure resource for a specific path.
/// ///
/// Resources may have variable path segments. For example, a /// Resources may have variable path segments. For example, a
@@ -320,11 +438,10 @@ where
/// use actix_web::{http, App, HttpResponse}; /// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().resource("/users/{userid}/{friend}", |r| {
/// .resource("/users/{userid}/{friend}", |r| { /// r.get().f(|_| HttpResponse::Ok());
/// r.get().f(|_| HttpResponse::Ok()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// });
/// });
/// } /// }
/// ``` /// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> App<S> pub fn resource<F, R>(mut self, path: &str, f: F) -> App<S>
@@ -387,9 +504,9 @@ where
/// use actix_web::{App, HttpRequest, HttpResponse, Result}; /// use actix_web::{App, HttpRequest, HttpResponse, Result};
/// ///
/// fn index(mut req: HttpRequest) -> Result<HttpResponse> { /// fn index(mut req: HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
/// Ok(HttpResponse::Ok().into()) /// Ok(HttpResponse::Ok().into())
/// } /// }
/// ///
/// fn main() { /// fn main() {
@@ -432,13 +549,11 @@ where
/// use actix_web::{http, App, HttpRequest, HttpResponse}; /// use actix_web::{http, App, HttpRequest, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().handler("/app", |req: HttpRequest| match *req.method() {
/// .handler("/app", |req: HttpRequest| { /// http::Method::GET => HttpResponse::Ok(),
/// match *req.method() { /// http::Method::POST => HttpResponse::MethodNotAllowed(),
/// http::Method::GET => HttpResponse::Ok(), /// _ => HttpResponse::NotFound(),
/// http::Method::POST => HttpResponse::MethodNotAllowed(), /// });
/// _ => HttpResponse::NotFound(),
/// }});
/// } /// }
/// ``` /// ```
pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> App<S> { pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> App<S> {
@@ -447,11 +562,15 @@ where
if !path.is_empty() && !path.starts_with('/') { if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/') path.insert(0, '/')
} }
if path.len() > 1 && path.ends_with('/') {
path.pop();
}
let parts = self.parts.as_mut().expect("Use after finish"); let parts = self.parts.as_mut().expect("Use after finish");
parts parts.handlers.push(PrefixHandlerType::Handler(
.handlers path,
.push((path, Box::new(WrapHandler::new(handler)))); Box::new(WrapHandler::new(handler)),
));
} }
self self
} }
@@ -475,15 +594,14 @@ where
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{App, HttpResponse, fs, middleware}; /// use actix_web::{fs, middleware, App, HttpResponse};
/// ///
/// // this function could be located in different module /// // this function could be located in different module
/// fn config(app: App) -> App { /// fn config(app: App) -> App {
/// app /// app.resource("/test", |r| {
/// .resource("/test", |r| { /// r.get().f(|_| HttpResponse::Ok());
/// r.get().f(|_| HttpResponse::Ok()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// })
/// })
/// } /// }
/// ///
/// fn main() { /// fn main() {
@@ -524,6 +642,11 @@ where
handlers: parts.handlers, handlers: parts.handlers,
resources, resources,
})); }));
let filters = if parts.filters.is_empty() {
None
} else {
Some(parts.filters)
};
HttpApplication { HttpApplication {
state: Rc::new(parts.state), state: Rc::new(parts.state),
@@ -532,6 +655,7 @@ where
prefix, prefix,
prefix_len, prefix_len,
inner, inner,
filters,
} }
} }
@@ -550,19 +674,22 @@ where
/// struct State2; /// struct State2;
/// ///
/// fn main() { /// fn main() {
/// # thread::spawn(|| { /// # thread::spawn(|| {
/// server::new(|| { vec![ /// server::new(|| {
/// App::with_state(State1) /// vec![
/// .prefix("/app1") /// App::with_state(State1)
/// .resource("/", |r| r.f(|r| HttpResponse::Ok())) /// .prefix("/app1")
/// .boxed(), /// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// App::with_state(State2) /// .boxed(),
/// .prefix("/app2") /// App::with_state(State2)
/// .resource("/", |r| r.f(|r| HttpResponse::Ok())) /// .prefix("/app2")
/// .boxed() ]}) /// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// .bind("127.0.0.1:8080").unwrap() /// .boxed(),
/// ]
/// }).bind("127.0.0.1:8080")
/// .unwrap()
/// .run() /// .run()
/// # }); /// # });
/// } /// }
/// ``` /// ```
pub fn boxed(mut self) -> Box<HttpHandler> { pub fn boxed(mut self) -> Box<HttpHandler> {
@@ -613,7 +740,8 @@ mod tests {
use http::StatusCode; use http::StatusCode;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use test::TestRequest; use pred;
use test::{TestRequest, TestServer};
#[test] #[test]
fn test_default_resource() { fn test_default_resource() {
@@ -623,24 +751,18 @@ mod tests {
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req); 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 req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
let mut app = App::new() let mut app = App::new()
.default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed()))
.finish(); .finish();
let req = TestRequest::with_uri("/blah").finish(); let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
resp.as_response().unwrap().status(),
StatusCode::METHOD_NOT_ALLOWED
);
} }
#[test] #[test]
@@ -660,7 +782,7 @@ mod tests {
let req = let req =
HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone());
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
} }
#[test] #[test]
@@ -688,68 +810,52 @@ mod tests {
#[test] #[test]
fn test_handler() { fn test_handler() {
let mut app = App::new() let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish();
.handler("/test", |_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req); let resp = app.run(req);
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 req = TestRequest::with_uri("/test/").finish();
let resp = app.run(req); 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 req = TestRequest::with_uri("/test/app").finish();
let resp = app.run(req); 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 req = TestRequest::with_uri("/testapp").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
let req = TestRequest::with_uri("/blah").finish(); let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
} }
#[test] #[test]
fn test_handler2() { fn test_handler2() {
let mut app = App::new() let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish();
.handler("test", |_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req); let resp = app.run(req);
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 req = TestRequest::with_uri("/test/").finish();
let resp = app.run(req); 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 req = TestRequest::with_uri("/test/app").finish();
let resp = app.run(req); 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 req = TestRequest::with_uri("/testapp").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
let req = TestRequest::with_uri("/blah").finish(); let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
} }
#[test] #[test]
@@ -761,65 +867,45 @@ mod tests {
let req = TestRequest::with_uri("/prefix/test").finish(); let req = TestRequest::with_uri("/prefix/test").finish();
let resp = app.run(req); 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 req = TestRequest::with_uri("/prefix/test/").finish();
let resp = app.run(req); 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 req = TestRequest::with_uri("/prefix/test/app").finish();
let resp = app.run(req); 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 req = TestRequest::with_uri("/prefix/testapp").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
let req = TestRequest::with_uri("/prefix/blah").finish(); let req = TestRequest::with_uri("/prefix/blah").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
} }
#[test] #[test]
fn test_route() { fn test_route() {
let mut app = App::new() let mut app = App::new()
.route("/test", Method::GET, |_: HttpRequest| { .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok())
HttpResponse::Ok()
})
.route("/test", Method::POST, |_: HttpRequest| { .route("/test", Method::POST, |_: HttpRequest| {
HttpResponse::Created() HttpResponse::Created()
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test").method(Method::GET).finish();
.method(Method::GET)
.finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test").method(Method::POST).finish();
.method(Method::POST)
.finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
resp.as_response().unwrap().status(),
StatusCode::CREATED
);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test").method(Method::HEAD).finish();
.method(Method::HEAD)
.finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
} }
#[test] #[test]
@@ -831,35 +917,44 @@ mod tests {
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
let req = TestRequest::with_uri("/app/test").finish(); let req = TestRequest::with_uri("/app/test").finish();
let resp = app.run(req); let resp = app.run(req.clone());
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
assert_eq!(req.prefix_len(), 9);
let req = TestRequest::with_uri("/app/test/").finish(); let req = TestRequest::with_uri("/app/test/").finish();
let resp = app.run(req); 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 req = TestRequest::with_uri("/app/test/app").finish();
let resp = app.run(req); 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 req = TestRequest::with_uri("/app/testapp").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(),
StatusCode::NOT_FOUND
);
let req = TestRequest::with_uri("/app/blah").finish(); let req = TestRequest::with_uri("/app/blah").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
resp.as_response().unwrap().status(), }
StatusCode::NOT_FOUND
); #[test]
fn test_filter() {
let mut srv = TestServer::with_factory(|| {
App::new()
.filter(pred::Get())
.handler("/test", |_| HttpResponse::Ok())
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv.post().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
} }
} }

View File

@@ -62,10 +62,28 @@ impl Body {
} }
} }
/// Is this binary empy.
#[inline]
pub fn is_empty(&self) -> bool {
match *self {
Body::Empty => true,
_ => false,
}
}
/// Create body from slice (copy) /// Create body from slice (copy)
pub fn from_slice(s: &[u8]) -> Body { pub fn from_slice(s: &[u8]) -> Body {
Body::Binary(Binary::Bytes(Bytes::from(s))) Body::Binary(Binary::Bytes(Bytes::from(s)))
} }
/// Is this binary body.
#[inline]
pub(crate) fn binary(self) -> Binary {
match self {
Body::Binary(b) => b,
_ => panic!(),
}
}
} }
impl PartialEq for Body { impl PartialEq for Body {
@@ -257,8 +275,8 @@ impl Responder for Binary {
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok() Ok(HttpResponse::build_from(req)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self)) .body(self))
} }

View File

@@ -8,8 +8,10 @@ use std::{fmt, io, mem, time};
use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError}; use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError};
use actix::fut::WrapFuture; use actix::fut::WrapFuture;
use actix::registry::ArbiterService; use actix::registry::ArbiterService;
use actix::{fut, Actor, ActorFuture, ActorResponse, Arbiter, AsyncContext, Context, use actix::{
ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn}; fut, Actor, ActorFuture, ActorResponse, Addr, Arbiter, AsyncContext, Context,
ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn, Unsync,
};
use futures::task::{current as current_task, Task}; use futures::task::{current as current_task, Task};
use futures::unsync::oneshot; use futures::unsync::oneshot;
@@ -179,6 +181,7 @@ pub struct ClientConnector {
pool: Rc<Pool>, pool: Rc<Pool>,
pool_modified: Rc<Cell<bool>>, pool_modified: Rc<Cell<bool>>,
resolver: Addr<Unsync, Connector>,
conn_lifetime: Duration, conn_lifetime: Duration,
conn_keep_alive: Duration, conn_keep_alive: Duration,
limit: usize, limit: usize,
@@ -223,8 +226,9 @@ impl Default for ClientConnector {
pool: Rc::new(Pool::new(Rc::clone(&_modified))), pool: Rc::new(Pool::new(Rc::clone(&_modified))),
pool_modified: _modified, pool_modified: _modified,
connector: builder.build().unwrap(), connector: builder.build().unwrap(),
conn_lifetime: Duration::from_secs(15), resolver: Connector::from_registry(),
conn_keep_alive: Duration::from_secs(75), conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100, limit: 100,
limit_per_host: 0, limit_per_host: 0,
acquired: 0, acquired: 0,
@@ -243,8 +247,9 @@ impl Default for ClientConnector {
subscriber: None, subscriber: None,
pool: Rc::new(Pool::new(Rc::clone(&_modified))), pool: Rc::new(Pool::new(Rc::clone(&_modified))),
pool_modified: _modified, pool_modified: _modified,
conn_lifetime: Duration::from_secs(15), resolver: Connector::from_registry(),
conn_keep_alive: Duration::from_secs(75), conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100, limit: 100,
limit_per_host: 0, limit_per_host: 0,
acquired: 0, acquired: 0,
@@ -275,9 +280,9 @@ impl ClientConnector {
/// # use std::io::Write; /// # use std::io::Write;
/// extern crate openssl; /// extern crate openssl;
/// use actix::prelude::*; /// use actix::prelude::*;
/// use actix_web::client::{Connect, ClientConnector}; /// use actix_web::client::{ClientConnector, Connect};
/// ///
/// use openssl::ssl::{SslMethod, SslConnector}; /// use openssl::ssl::{SslConnector, SslMethod};
/// ///
/// fn main() { /// fn main() {
/// let sys = System::new("test"); /// let sys = System::new("test");
@@ -310,6 +315,7 @@ impl ClientConnector {
subscriber: None, subscriber: None,
pool: Rc::new(Pool::new(Rc::clone(&modified))), pool: Rc::new(Pool::new(Rc::clone(&modified))),
pool_modified: modified, pool_modified: modified,
resolver: Connector::from_registry(),
conn_lifetime: Duration::from_secs(75), conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(15),
limit: 100, limit: 100,
@@ -347,6 +353,7 @@ impl ClientConnector {
/// Keep-alive period is the period between connection usage. If /// Keep-alive period is the period between connection usage. If
/// the delay between repeated usages of the same connection /// the delay between repeated usages of the same connection
/// exceeds this period, the connection is closed. /// exceeds this period, the connection is closed.
/// Default keep-alive period is 15 seconds.
pub fn conn_keep_alive(mut self, dur: Duration) -> Self { pub fn conn_keep_alive(mut self, dur: Duration) -> Self {
self.conn_keep_alive = dur; self.conn_keep_alive = dur;
self self
@@ -356,6 +363,7 @@ impl ClientConnector {
/// ///
/// Connection lifetime is max lifetime of any opened connection /// Connection lifetime is max lifetime of any opened connection
/// until it is closed regardless of keep-alive period. /// until it is closed regardless of keep-alive period.
/// Default lifetime period is 75 seconds.
pub fn conn_lifetime(mut self, dur: Duration) -> Self { pub fn conn_lifetime(mut self, dur: Duration) -> Self {
self.conn_lifetime = dur; self.conn_lifetime = dur;
self self
@@ -367,6 +375,12 @@ impl ClientConnector {
self self
} }
/// Use custom resolver actor
pub fn resolver(mut self, addr: Addr<Unsync, Connector>) -> Self {
self.resolver = addr;
self
}
fn acquire(&mut self, key: &Key) -> Acquire { fn acquire(&mut self, key: &Key) -> Acquire {
// check limits // check limits
if self.limit > 0 { if self.limit > 0 {
@@ -427,8 +441,7 @@ impl ClientConnector {
} else { } else {
0 0
}; };
self.acquired_per_host self.acquired_per_host.insert(key.clone(), per_host + 1);
.insert(key.clone(), per_host + 1);
} }
fn release_key(&mut self, key: &Key) { fn release_key(&mut self, key: &Key) {
@@ -439,8 +452,7 @@ impl ClientConnector {
return; return;
}; };
if per_host > 1 { if per_host > 1 {
self.acquired_per_host self.acquired_per_host.insert(key.clone(), per_host - 1);
.insert(key.clone(), per_host - 1);
} else { } else {
self.acquired_per_host.remove(key); self.acquired_per_host.remove(key);
} }
@@ -516,9 +528,7 @@ impl ClientConnector {
fn collect_periodic(&mut self, ctx: &mut Context<Self>) { fn collect_periodic(&mut self, ctx: &mut Context<Self>) {
self.collect(true); self.collect(true);
// re-schedule next collect period // re-schedule next collect period
ctx.run_later(Duration::from_secs(1), |act, ctx| { ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx));
act.collect_periodic(ctx)
});
// send stats // send stats
let stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); let stats = mem::replace(&mut self.stats, ClientConnectorStats::default());
@@ -570,7 +580,7 @@ impl ClientConnector {
} }
fn wait_for( 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>> { ) -> oneshot::Receiver<Result<Connection, ClientConnectorError>> {
// connection is not available, wait // connection is not available, wait
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
@@ -705,7 +715,7 @@ impl Handler<Connect> for ClientConnector {
{ {
ActorResponse::async( ActorResponse::async(
Connector::from_registry() self.resolver
.send( .send(
ResolveConnect::host_and_port(&conn.0.host, port) ResolveConnect::host_and_port(&conn.0.host, port)
.timeout(conn_timeout), .timeout(conn_timeout),
@@ -810,7 +820,7 @@ impl fut::ActorFuture for Maintenance {
type Actor = ClientConnector; type Actor = ClientConnector;
fn poll( 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> { ) -> Poll<Self::Item, Self::Error> {
// check pause duration // check pause duration
let done = if let Some(Some(ref pause)) = act.paused { let done = if let Some(Some(ref pause)) = act.paused {
@@ -831,7 +841,7 @@ impl fut::ActorFuture for Maintenance {
act.collect_waiters(); act.collect_waiters();
// check 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 { for (key, waiters) in &mut tmp.waiters {
while let Some(waiter) = waiters.pop_front() { while let Some(waiter) = waiters.pop_front() {
@@ -855,7 +865,7 @@ impl fut::ActorFuture for Maintenance {
let conn = AcquiredConn(key.clone(), Some(Rc::clone(&act.pool))); let conn = AcquiredConn(key.clone(), Some(Rc::clone(&act.pool)));
fut::WrapFuture::<ClientConnector>::actfuture( fut::WrapFuture::<ClientConnector>::actfuture(
Connector::from_registry().send( act.resolver.send(
ResolveConnect::host_and_port(&conn.0.host, conn.0.port) ResolveConnect::host_and_port(&conn.0.host, conn.0.port)
.timeout(waiter.conn_timeout), .timeout(waiter.conn_timeout),
), ),
@@ -1105,10 +1115,7 @@ impl Pool {
if self.to_close.borrow().is_empty() { if self.to_close.borrow().is_empty() {
None None
} else { } else {
Some(mem::replace( Some(mem::replace(&mut *self.to_close.borrow_mut(), Vec::new()))
&mut *self.to_close.borrow_mut(),
Vec::new(),
))
} }
} }
@@ -1116,10 +1123,7 @@ impl Pool {
if self.to_release.borrow().is_empty() { if self.to_release.borrow().is_empty() {
None None
} else { } else {
Some(mem::replace( Some(mem::replace(&mut *self.to_release.borrow_mut(), Vec::new()))
&mut *self.to_release.borrow_mut(),
Vec::new(),
))
} }
} }

View File

@@ -33,8 +33,10 @@ mod request;
mod response; mod response;
mod writer; mod writer;
pub use self::connector::{ClientConnector, ClientConnectorError, ClientConnectorStats, pub use self::connector::{
Connect, Connection, Pause, Resume}; ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection,
Pause, Resume,
};
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
pub use self::pipeline::{SendRequest, SendRequestError}; pub use self::pipeline::{SendRequest, SendRequestError};
pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::request::{ClientRequest, ClientRequestBuilder};
@@ -49,6 +51,7 @@ use httpresponse::HttpResponse;
impl ResponseError for SendRequestError { impl ResponseError for SendRequestError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
SendRequestError::Timeout => HttpResponse::GatewayTimeout(),
SendRequestError::Connector(_) => HttpResponse::BadGateway(), SendRequestError::Connector(_) => HttpResponse::BadGateway(),
_ => HttpResponse::InternalServerError(), _ => HttpResponse::InternalServerError(),
}.into() }.into()

View File

@@ -7,18 +7,18 @@ use std::mem;
use error::{ParseError, PayloadError}; use error::{ParseError, PayloadError};
use server::h1::{chunked, Decoder}; use server::h1decoder::EncodingDecoder;
use server::{utils, IoStream}; use server::{utils, IoStream};
use super::ClientResponse;
use super::response::ClientMessage; use super::response::ClientMessage;
use super::ClientResponse;
const MAX_BUFFER_SIZE: usize = 131_072; const MAX_BUFFER_SIZE: usize = 131_072;
const MAX_HEADERS: usize = 96; const MAX_HEADERS: usize = 96;
#[derive(Default)] #[derive(Default)]
pub struct HttpResponseParser { pub struct HttpResponseParser {
decoder: Option<Decoder>, decoder: Option<EncodingDecoder>,
} }
#[derive(Debug, Fail)] #[derive(Debug, Fail)]
@@ -32,7 +32,7 @@ pub enum HttpResponseParserError {
impl HttpResponseParser { impl HttpResponseParser {
pub fn parse<T>( pub fn parse<T>(
&mut self, io: &mut T, buf: &mut BytesMut &mut self, io: &mut T, buf: &mut BytesMut,
) -> Poll<ClientResponse, HttpResponseParserError> ) -> Poll<ClientResponse, HttpResponseParserError>
where where
T: IoStream, T: IoStream,
@@ -75,7 +75,7 @@ impl HttpResponseParser {
} }
pub fn parse_payload<T>( 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> ) -> Poll<Option<Bytes>, PayloadError>
where where
T: IoStream, T: IoStream,
@@ -113,8 +113,8 @@ impl HttpResponseParser {
} }
fn parse_message( fn parse_message(
buf: &mut BytesMut buf: &mut BytesMut,
) -> Poll<(ClientResponse, Option<Decoder>), ParseError> { ) -> Poll<(ClientResponse, Option<EncodingDecoder>), ParseError> {
// Parse http message // Parse http message
let bytes_ptr = buf.as_ref().as_ptr() as usize; let bytes_ptr = buf.as_ref().as_ptr() as usize;
let mut headers: [httparse::Header; MAX_HEADERS] = let mut headers: [httparse::Header; MAX_HEADERS] =
@@ -123,7 +123,7 @@ impl HttpResponseParser {
let (len, version, status, headers_len) = { let (len, version, status, headers_len) = {
let b = unsafe { let b = unsafe {
let b: &[u8] = buf; let b: &[u8] = buf;
mem::transmute(b) &*(b as *const _)
}; };
let mut resp = httparse::Response::new(&mut headers); let mut resp = httparse::Response::new(&mut headers);
match resp.parse(b)? { match resp.parse(b)? {
@@ -160,12 +160,12 @@ impl HttpResponseParser {
} }
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS { let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
Some(Decoder::eof()) Some(EncodingDecoder::eof())
} else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { } else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) {
// Content-Length // Content-Length
if let Ok(s) = len.to_str() { if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() { if let Ok(len) = s.parse::<u64>() {
Some(Decoder::length(len)) Some(EncodingDecoder::length(len))
} else { } else {
debug!("illegal Content-Length: {:?}", len); debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header); return Err(ParseError::Header);
@@ -176,7 +176,7 @@ impl HttpResponseParser {
} }
} else if chunked(&hdrs)? { } else if chunked(&hdrs)? {
// Chunked encoding // Chunked encoding
Some(Decoder::chunked()) Some(EncodingDecoder::chunked())
} else { } else {
None 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 error::PayloadError;
use header::ContentEncoding; use header::ContentEncoding;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use server::WriterState;
use server::encoding::PayloadStream; use server::encoding::PayloadStream;
use server::shared::SharedBytes; use server::shared::SharedBytes;
use server::WriterState;
/// A set of errors that can occur during request sending and response reading /// A set of errors that can occur during request sending and response reading
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
@@ -80,7 +80,7 @@ impl SendRequest {
} }
pub(crate) fn with_connector( pub(crate) fn with_connector(
req: ClientRequest, conn: Addr<Unsync, ClientConnector> req: ClientRequest, conn: Addr<Unsync, ClientConnector>,
) -> SendRequest { ) -> SendRequest {
SendRequest { SendRequest {
req, req,
@@ -194,6 +194,7 @@ impl Future for SendRequest {
self.state = State::Send(pl); self.state = State::Send(pl);
} }
State::Send(mut pl) => { State::Send(mut pl) => {
pl.poll_timeout()?;
pl.poll_write().map_err(|e| { pl.poll_write().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str()) io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str())
})?; })?;
@@ -269,7 +270,8 @@ impl Pipeline {
#[inline] #[inline]
fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> { fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
if let Some(ref mut conn) = self.conn { if let Some(ref mut conn) = self.conn {
match self.parser match self
.parser
.as_mut() .as_mut()
.unwrap() .unwrap()
.parse(conn, &mut self.parser_buf) .parse(conn, &mut self.parser_buf)
@@ -305,17 +307,18 @@ impl Pipeline {
return Ok(Async::Ready(None)); return Ok(Async::Ready(None));
} }
let conn: &mut Connection = 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; let mut need_run = false;
// need write? // need write?
match self.poll_write() match self
.poll_write()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))? .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?
{ {
Async::NotReady => need_run = true, Async::NotReady => need_run = true,
Async::Ready(_) => { Async::Ready(_) => {
let _ = self.poll_timeout().map_err(|e| { self.poll_timeout().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e)) io::Error::new(io::ErrorKind::Other, format!("{}", e))
})?; })?;
} }
@@ -324,7 +327,8 @@ impl Pipeline {
// need read? // need read?
if self.parser.is_some() { if self.parser.is_some() {
loop { loop {
match self.parser match self
.parser
.as_mut() .as_mut()
.unwrap() .unwrap()
.parse_payload(conn, &mut self.parser_buf)? .parse_payload(conn, &mut self.parser_buf)?
@@ -371,16 +375,15 @@ impl Pipeline {
} }
} }
fn poll_timeout(&mut self) -> Poll<(), SendRequestError> { fn poll_timeout(&mut self) -> Result<(), SendRequestError> {
if self.timeout.is_some() { if self.timeout.is_some() {
match self.timeout.as_mut().unwrap().poll() { match self.timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(())) => Err(SendRequestError::Timeout), Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::NotReady) => (),
Err(_) => unreachable!(), Err(_) => unreachable!(),
} }
} else {
Ok(Async::NotReady)
} }
Ok(())
} }
#[inline] #[inline]
@@ -469,7 +472,8 @@ impl Pipeline {
} }
// flush io but only if we need to // flush io but only if we need to
match self.writer match self
.writer
.poll_completed(self.conn.as_mut().unwrap(), false) .poll_completed(self.conn.as_mut().unwrap(), false)
{ {
Ok(Async::Ready(_)) => { Ok(Async::Ready(_)) => {

View File

@@ -499,10 +499,7 @@ impl ClientRequestBuilder {
jar.add(cookie.into_owned()); jar.add(cookie.into_owned());
self.cookies = Some(jar) self.cookies = Some(jar)
} else { } else {
self.cookies self.cookies.as_mut().unwrap().add(cookie.into_owned());
.as_mut()
.unwrap()
.add(cookie.into_owned());
} }
self self
} }
@@ -610,9 +607,7 @@ impl ClientRequestBuilder {
} }
} }
let mut request = self.request let mut request = self.request.take().expect("cannot reuse request builder");
.take()
.expect("cannot reuse request builder");
// set cookies // set cookies
if let Some(ref mut jar) = self.cookies { if let Some(ref mut jar) = self.cookies {
@@ -657,9 +652,7 @@ impl ClientRequestBuilder {
S: Stream<Item = Bytes, Error = E> + 'static, S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error>, E: Into<Error>,
{ {
self.body(Body::Streaming(Box::new( self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
stream.map_err(|e| e.into()),
)))
} }
/// Set an empty body and generate `ClientRequest` /// Set an empty body and generate `ClientRequest`
@@ -682,7 +675,7 @@ impl ClientRequestBuilder {
#[inline] #[inline]
fn parts<'a>( fn parts<'a>(
parts: &'a mut Option<ClientRequest>, err: &Option<HttpError> parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>,
) -> Option<&'a mut ClientRequest> { ) -> Option<&'a mut ClientRequest> {
if err.is_some() { if err.is_some() {
return None; return None;

View File

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

View File

@@ -7,20 +7,23 @@ use std::io::{self, Write};
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
use brotli2::write::BrotliEncoder; use brotli2::write::BrotliEncoder;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use flate2::Compression; #[cfg(feature = "flate2")]
use flate2::write::{DeflateEncoder, GzEncoder}; use flate2::write::{DeflateEncoder, GzEncoder};
#[cfg(feature = "flate2")]
use flate2::Compression;
use futures::{Async, Poll}; use futures::{Async, Poll};
use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, use http::header::{
TRANSFER_ENCODING}; HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::{HttpTryFrom, Version}; use http::{HttpTryFrom, Version};
use time::{self, Duration}; use time::{self, Duration};
use tokio_io::AsyncWrite; use tokio_io::AsyncWrite;
use body::{Binary, Body}; use body::{Binary, Body};
use header::ContentEncoding; use header::ContentEncoding;
use server::WriterState;
use server::encoding::{ContentEncoder, TransferEncoding}; use server::encoding::{ContentEncoder, TransferEncoding};
use server::shared::SharedBytes; use server::shared::SharedBytes;
use server::WriterState;
use client::ClientRequest; use client::ClientRequest;
@@ -70,7 +73,7 @@ impl HttpClientWriter {
// !self.flags.contains(Flags::UPGRADE) } // !self.flags.contains(Flags::UPGRADE) }
fn write_to_stream<T: AsyncWrite>( fn write_to_stream<T: AsyncWrite>(
&mut self, stream: &mut T &mut self, stream: &mut T,
) -> io::Result<WriterState> { ) -> io::Result<WriterState> {
while !self.buffer.is_empty() { while !self.buffer.is_empty() {
match stream.write(self.buffer.as_ref()) { match stream.write(self.buffer.as_ref()) {
@@ -191,7 +194,7 @@ impl HttpClientWriter {
#[inline] #[inline]
pub fn poll_completed<T: AsyncWrite>( pub fn poll_completed<T: AsyncWrite>(
&mut self, stream: &mut T, shutdown: bool &mut self, stream: &mut T, shutdown: bool,
) -> Poll<(), io::Error> { ) -> Poll<(), io::Error> {
match self.write_to_stream(stream) { match self.write_to_stream(stream) {
Ok(WriterState::Done) => { Ok(WriterState::Done) => {
@@ -222,9 +225,11 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
let tmp = SharedBytes::default(); let tmp = SharedBytes::default();
let transfer = TransferEncoding::eof(tmp.clone()); let transfer = TransferEncoding::eof(tmp.clone());
let mut enc = match encoding { let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate( ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::default()), DeflateEncoder::new(transfer, Compression::default()),
), ),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
transfer, transfer,
Compression::default(), Compression::default(),
@@ -249,10 +254,8 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
} }
let mut b = BytesMut::new(); let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len()); let _ = write!(b, "{}", bytes.len());
req.headers_mut().insert( req.headers_mut()
CONTENT_LENGTH, .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
HeaderValue::try_from(b.freeze()).unwrap(),
);
TransferEncoding::eof(buf) TransferEncoding::eof(buf)
} }
Body::Streaming(_) | Body::Actor(_) => { Body::Streaming(_) | Body::Actor(_) => {
@@ -283,10 +286,12 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
req.replace_body(body); req.replace_body(body);
match encoding { match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new( ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
transfer, transfer,
Compression::default(), Compression::default(),
)), )),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => { ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default())) ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default()))
} }
@@ -299,7 +304,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
} }
fn streaming_encoding( fn streaming_encoding(
buf: SharedBytes, version: Version, req: &mut ClientRequest buf: SharedBytes, version: Version, req: &mut ClientRequest,
) -> TransferEncoding { ) -> TransferEncoding {
if req.chunked() { if req.chunked() {
// Enable transfer encoding // Enable transfer encoding

View File

@@ -3,12 +3,13 @@ use futures::unsync::oneshot;
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem;
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
use actix::fut::ActorFuture; use actix::fut::ActorFuture;
use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, use actix::{
SpawnHandle, Syn, Unsync}; Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
Syn, Unsync,
};
use body::{Binary, Body}; use body::{Binary, Body};
use error::{Error, ErrorInternalServerError}; use error::{Error, ErrorInternalServerError};
@@ -81,7 +82,8 @@ where
#[doc(hidden)] #[doc(hidden)]
#[inline] #[inline]
fn waiting(&self) -> bool { fn waiting(&self) -> bool {
self.inner.waiting() || self.inner.state() == ActorState::Stopping self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped || self.inner.state() == ActorState::Stopped
} }
#[inline] #[inline]
@@ -174,7 +176,9 @@ where
if self.stream.is_none() { if self.stream.is_none() {
self.stream = Some(SmallVec::new()); 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(); self.inner.modify();
} }
@@ -199,7 +203,7 @@ where
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> { fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
let ctx: &mut HttpContext<A, S> = 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() { if self.inner.alive() {
match self.inner.poll(ctx) { match self.inner.poll(ctx) {
@@ -261,7 +265,7 @@ impl<A: Actor> ActorFuture for Drain<A> {
#[inline] #[inline]
fn poll( 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> { ) -> Poll<Self::Item, Self::Error> {
self.fut.poll().map_err(|_| ()) 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>( 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> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@@ -75,7 +75,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
} }
fn deserialize_unit_struct<V>( fn deserialize_unit_struct<V>(
self, _: &'static str, visitor: V self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@@ -84,7 +84,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
} }
fn deserialize_newtype_struct<V>( fn deserialize_newtype_struct<V>(
self, _: &'static str, visitor: V self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@@ -93,7 +93,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
} }
fn deserialize_tuple<V>( fn deserialize_tuple<V>(
self, len: usize, visitor: V self, len: usize, visitor: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@@ -114,7 +114,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
} }
fn deserialize_tuple_struct<V>( fn deserialize_tuple_struct<V>(
self, _: &'static str, len: usize, visitor: V self, _: &'static str, len: usize, visitor: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@@ -135,7 +135,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
} }
fn deserialize_enum<V>( fn deserialize_enum<V>(
self, _: &'static str, _: &'static [&'static str], _: V self, _: &'static str, _: &'static [&'static str], _: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@@ -202,7 +202,8 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
where where
K: de::DeserializeSeed<'de>, K: de::DeserializeSeed<'de>,
{ {
self.current = self.params self.current = self
.params
.next() .next()
.map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); .map(|&(ref k, ref v)| (k.as_ref(), v.as_ref()));
match self.current { match self.current {
@@ -301,7 +302,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
} }
fn deserialize_unit_struct<V>( fn deserialize_unit_struct<V>(
self, _: &'static str, visitor: V self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@@ -331,18 +332,16 @@ impl<'de> Deserializer<'de> for Value<'de> {
} }
fn deserialize_enum<V>( 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> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
visitor.visit_enum(ValueEnum { visitor.visit_enum(ValueEnum { value: self.value })
value: self.value,
})
} }
fn deserialize_newtype_struct<V>( fn deserialize_newtype_struct<V>(
self, _: &'static str, visitor: V self, _: &'static str, visitor: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@@ -358,7 +357,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
} }
fn deserialize_struct<V>( fn deserialize_struct<V>(
self, _: &'static str, _: &'static [&'static str], _: V self, _: &'static str, _: &'static [&'static str], _: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@@ -367,14 +366,12 @@ impl<'de> Deserializer<'de> for Value<'de> {
} }
fn deserialize_tuple_struct<V>( fn deserialize_tuple_struct<V>(
self, _: &'static str, _: usize, _: V self, _: &'static str, _: usize, _: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
Err(de::value::Error::custom( Err(de::value::Error::custom("unsupported type: tuple struct"))
"unsupported type: tuple struct",
))
} }
unsupported_type!(deserialize_any, "any"); unsupported_type!(deserialize_any, "any");
@@ -415,10 +412,7 @@ impl<'de> de::EnumAccess<'de> for ValueEnum<'de> {
where where
V: de::DeserializeSeed<'de>, V: de::DeserializeSeed<'de>,
{ {
Ok(( Ok((seed.deserialize(Key { key: self.value })?, UnitVariant))
seed.deserialize(Key { key: self.value })?,
UnitVariant,
))
} }
} }
@@ -446,7 +440,7 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
} }
fn struct_variant<V>( fn struct_variant<V>(
self, _: &'static [&'static str], _: V self, _: &'static [&'static str], _: V,
) -> Result<V::Value, Self::Error> ) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,

View File

@@ -1,9 +1,9 @@
//! Error and Result module //! Error and Result module
use std::cell::RefCell;
use std::io::Error as IoError; use std::io::Error as IoError;
use std::str::Utf8Error; use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use std::{fmt, io, result}; use std::sync::Mutex;
use std::{fmt, io, mem, result};
use actix::MailboxError; use actix::MailboxError;
use cookie; use cookie;
@@ -21,9 +21,10 @@ pub use url::ParseError as UrlParseError;
// re-exports // re-exports
pub use cookie::ParseError as CookieParseError; pub use cookie::ParseError as CookieParseError;
use body::Body;
use handler::Responder; use handler::Responder;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::{HttpResponse, InnerHttpResponse};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations /// for actix web operations
@@ -33,22 +34,110 @@ use httpresponse::HttpResponse;
/// `Result`. /// `Result`.
pub type Result<T, E = Error> = result::Result<T, E>; pub type Result<T, E = Error> = result::Result<T, E>;
/// General purpose actix web error /// General purpose actix web error.
///
/// An actix web error is used to carry errors from `failure` or `std::error`
/// through actix in a convenient way. It can be created through through
/// converting errors with `into()`.
///
/// Whenever it is created from an external object a response error is created
/// for it that can be used to create an http response from it this means that
/// if you have access to an actix `Error` you can always get a
/// `ResponseError` reference from it.
pub struct Error { pub struct Error {
cause: Box<ResponseError>, cause: Box<ResponseError>,
backtrace: Option<Backtrace>, backtrace: Option<Backtrace>,
} }
impl Error { impl Error {
/// Returns a reference to the underlying cause of this Error. /// Deprecated way to reference the underlying response error.
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665 #[deprecated(
since = "0.6.0", note = "please use `Error::as_response_error()` instead"
)]
pub fn cause(&self) -> &ResponseError { pub fn cause(&self) -> &ResponseError {
self.cause.as_ref() self.cause.as_ref()
} }
/// Returns a reference to the underlying cause of this `Error` as `Fail`
pub fn as_fail(&self) -> &Fail {
self.cause.as_fail()
}
/// Returns the reference to the underlying `ResponseError`.
pub fn as_response_error(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Returns a reference to the Backtrace carried by this error, if it
/// carries one.
///
/// This uses the same `Backtrace` type that `failure` uses.
pub fn backtrace(&self) -> &Backtrace {
if let Some(bt) = self.cause.backtrace() {
bt
} else {
self.backtrace.as_ref().unwrap()
}
}
/// Attempts to downcast this `Error` to a particular `Fail` type by
/// reference.
///
/// If the underlying error is not of type `T`, this will return `None`.
pub fn downcast_ref<T: Fail>(&self) -> Option<&T> {
// in the most trivial way the cause is directly of the requested type.
if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) {
return Some(rv);
}
// in the more complex case the error has been constructed from a failure
// error. This happens because we implement From<failure::Error> by
// calling compat() and then storing it here. In failure this is
// represented by a failure::Error being wrapped in a failure::Compat.
//
// So we first downcast into that compat, to then further downcast through
// the failure's Error downcasting system into the original failure.
//
// This currently requires a transmute. This could be avoided if failure
// provides a deref: https://github.com/rust-lang-nursery/failure/pull/213
let compat: Option<&failure::Compat<failure::Error>> =
Fail::downcast_ref(self.cause.as_fail());
if let Some(compat) = compat {
pub struct CompatWrappedError {
error: failure::Error,
}
let compat: &CompatWrappedError = unsafe { ::std::mem::transmute(compat) };
compat.error.downcast_ref()
} else {
None
}
}
}
/// Helper trait to downcast a response error into a fail.
///
/// This is currently not exposed because it's unclear if this is the best way
/// to achieve the downcasting on `Error` for which this is needed.
#[doc(hidden)]
pub trait InternalResponseErrorAsFail {
#[doc(hidden)]
fn as_fail(&self) -> &Fail;
#[doc(hidden)]
fn as_mut_fail(&mut self) -> &mut Fail;
}
#[doc(hidden)]
impl<T: ResponseError> InternalResponseErrorAsFail for T {
fn as_fail(&self) -> &Fail {
self
}
fn as_mut_fail(&mut self) -> &mut Fail {
self
}
} }
/// Error that can be converted to `HttpResponse` /// Error that can be converted to `HttpResponse`
pub trait ResponseError: Fail { pub trait ResponseError: Fail + InternalResponseErrorAsFail {
/// Create response for error /// Create response for error
/// ///
/// Internal server error is generated by default. /// Internal server error is generated by default.
@@ -78,7 +167,7 @@ impl fmt::Debug for Error {
} }
} }
/// `HttpResponse` for `Error` /// Convert `Error` to a `HttpResponse` instance
impl From<Error> for HttpResponse { impl From<Error> for HttpResponse {
fn from(err: Error) -> Self { fn from(err: Error) -> Self {
HttpResponse::from_error(err) HttpResponse::from_error(err)
@@ -101,11 +190,9 @@ impl<T: ResponseError> From<T> for Error {
} }
/// Compatibility for `failure::Error` /// Compatibility for `failure::Error`
impl<T> ResponseError for failure::Compat<T> impl<T> ResponseError for failure::Compat<T> where
where T: fmt::Display + fmt::Debug + Sync + Send + 'static
T: fmt::Display + fmt::Debug + Sync + Send + 'static, {}
{
}
impl From<failure::Error> for Error { impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error { fn from(err: failure::Error) -> Error {
@@ -298,17 +385,16 @@ pub enum HttpRangeError {
/// Returned if first-byte-pos of all of the byte-range-spec /// Returned if first-byte-pos of all of the byte-range-spec
/// values is greater than the content size. /// values is greater than the content size.
/// See `https://github.com/golang/go/commit/aa9b3d7` /// 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, NoOverlap,
} }
/// Return `BadRequest` for `HttpRangeError` /// Return `BadRequest` for `HttpRangeError`
impl ResponseError for HttpRangeError { impl ResponseError for HttpRangeError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::with_body( HttpResponse::with_body(StatusCode::BAD_REQUEST, "Invalid Range header provided")
StatusCode::BAD_REQUEST,
"Invalid Range header provided",
)
} }
} }
@@ -530,7 +616,7 @@ impl From<UrlParseError> for UrlGenerationError {
/// Helper type that can wrap any error and generate custom response. /// Helper type that can wrap any error and generate custom response.
/// ///
/// In following example any `io::Error` will be converted into "BAD REQUEST" /// 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. /// default.
/// ///
/// ```rust /// ```rust
@@ -539,8 +625,8 @@ impl From<UrlParseError> for UrlGenerationError {
/// use actix_web::fs::NamedFile; /// use actix_web::fs::NamedFile;
/// ///
/// fn index(req: HttpRequest) -> Result<fs::NamedFile> { /// fn index(req: HttpRequest) -> Result<fs::NamedFile> {
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?; /// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
/// Ok(f) /// Ok(f)
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
@@ -550,12 +636,9 @@ pub struct InternalError<T> {
backtrace: Backtrace, backtrace: Backtrace,
} }
unsafe impl<T> Sync for InternalError<T> {}
unsafe impl<T> Send for InternalError<T> {}
enum InternalErrorType { enum InternalErrorType {
Status(StatusCode), Status(StatusCode),
Response(RefCell<Option<HttpResponse>>), Response(Mutex<Option<Box<InnerHttpResponse>>>),
} }
impl<T> InternalError<T> { impl<T> InternalError<T> {
@@ -570,9 +653,21 @@ impl<T> InternalError<T> {
/// Create `InternalError` with predefined `HttpResponse`. /// Create `InternalError` with predefined `HttpResponse`.
pub fn from_response(cause: T, response: HttpResponse) -> Self { pub fn from_response(cause: T, response: HttpResponse) -> Self {
let mut resp = response.into_inner();
let body = mem::replace(&mut resp.body, Body::Empty);
match body {
Body::Empty => (),
Body::Binary(mut bin) => {
resp.body = Body::Binary(bin.take().into());
}
Body::Streaming(_) | Body::Actor(_) => {
error!("Streaming or Actor body is not support by error response");
}
}
InternalError { InternalError {
cause, cause,
status: InternalErrorType::Response(RefCell::new(Some(response))), status: InternalErrorType::Response(Mutex::new(Some(resp))),
backtrace: Backtrace::new(), backtrace: Backtrace::new(),
} }
} }
@@ -613,8 +708,8 @@ where
match self.status { match self.status {
InternalErrorType::Status(st) => HttpResponse::new(st), InternalErrorType::Status(st) => HttpResponse::new(st),
InternalErrorType::Response(ref resp) => { InternalErrorType::Response(ref resp) => {
if let Some(resp) = resp.borrow_mut().take() { if let Some(resp) = resp.lock().unwrap().take() {
resp HttpResponse::from_inner(resp)
} else { } else {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
} }
@@ -630,7 +725,7 @@ where
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> { fn respond_to<S>(self, _: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Err(self.into()) Err(self.into())
} }
} }
@@ -745,6 +840,46 @@ where
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -784,19 +919,25 @@ mod tests {
} }
#[test] #[test]
fn test_cause() { fn test_as_fail() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.description().to_owned();
let e = ParseError::Io(orig); let e = ParseError::Io(orig);
assert_eq!(format!("{}", e.cause().unwrap()), desc); assert_eq!(format!("{}", e.cause().unwrap()), desc);
} }
#[test]
fn test_backtrace() {
let e = ErrorBadRequest("err");
let _ = e.backtrace();
}
#[test] #[test]
fn test_error_cause() { fn test_error_cause() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.description().to_owned();
let e = Error::from(orig); let e = Error::from(orig);
assert_eq!(format!("{}", e.cause()), desc); assert_eq!(format!("{}", e.as_fail()), desc);
} }
#[test] #[test]
@@ -894,4 +1035,78 @@ mod tests {
let resp: HttpResponse = err.error_response(); let resp: HttpResponse = err.error_response();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test]
fn test_error_downcasting_direct() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = DemoError.into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_downcasting_compat() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = failure::Error::from(DemoError).into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
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::ops::{Deref, DerefMut};
use std::str; use std::str;
use bytes::Bytes; use bytes::Bytes;
use encoding::all::UTF_8; use encoding::all::UTF_8;
use encoding::types::{DecoderTrap, Encoding}; use encoding::types::{DecoderTrap, Encoding};
use futures::future::{result, Future, FutureResult}; use futures::{Async, Future, Poll};
use mime::Mime; use mime::Mime;
use serde::de::{self, DeserializeOwned}; use serde::de::{self, DeserializeOwned};
use serde_urlencoded; use serde_urlencoded;
use de::PathDeserializer; use de::PathDeserializer;
use error::{Error, ErrorBadRequest}; use error::{Error, ErrorBadRequest, ErrorNotFound};
use handler::{Either, FromRequest}; use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest; use httprequest::HttpRequest;
@@ -99,19 +100,16 @@ impl<T> Path<T> {
impl<T, S> FromRequest<S> for Path<T> impl<T, S> FromRequest<S> for Path<T>
where where
T: DeserializeOwned, T: DeserializeOwned,
S: 'static,
{ {
type Config = (); type Config = ();
type Result = FutureResult<Self, Error>; type Result = Result<Self, Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone(); let req = req.clone();
result( de::Deserialize::deserialize(PathDeserializer::new(&req))
de::Deserialize::deserialize(PathDeserializer::new(&req)) .map_err(ErrorNotFound)
.map_err(|e| e.into()) .map(|inner| Path { inner })
.map(|inner| Path { inner }),
)
} }
} }
@@ -169,19 +167,16 @@ impl<T> Query<T> {
impl<T, S> FromRequest<S> for Query<T> impl<T, S> FromRequest<S> for Query<T>
where where
T: de::DeserializeOwned, T: de::DeserializeOwned,
S: 'static,
{ {
type Config = (); type Config = ();
type Result = FutureResult<Self, Error>; type Result = Result<Self, Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone(); let req = req.clone();
result( serde_urlencoded::from_str::<T>(req.query_string())
serde_urlencoded::from_str::<T>(req.query_string()) .map_err(|e| e.into())
.map_err(|e| e.into()) .map(Query)
.map(Query),
)
} }
} }
@@ -327,20 +322,15 @@ impl Default for FormConfig {
/// ``` /// ```
impl<S: 'static> FromRequest<S> for Bytes { impl<S: 'static> FromRequest<S> for Bytes {
type Config = PayloadConfig; type Config = PayloadConfig;
type Result = type Result = Result<Box<Future<Item = Self, Error = Error>>, Error>;
Either<FutureResult<Self, Error>, Box<Future<Item = Self, Error = Error>>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
// check content-type // check content-type
if let Err(e) = cfg.check_mimetype(req) { cfg.check_mimetype(req)?;
return Either::A(result(Err(e)));
}
Either::B(Box::new( Ok(Box::new(
MessageBody::new(req.clone()) MessageBody::new(req.clone()).limit(cfg.limit).from_err(),
.limit(cfg.limit)
.from_err(),
)) ))
} }
} }
@@ -374,27 +364,17 @@ impl<S: 'static> FromRequest<S> for Bytes {
/// ``` /// ```
impl<S: 'static> FromRequest<S> for String { impl<S: 'static> FromRequest<S> for String {
type Config = PayloadConfig; type Config = PayloadConfig;
type Result = type Result = Result<Box<Future<Item = String, Error = Error>>, Error>;
Either<FutureResult<String, Error>, Box<Future<Item = String, Error = Error>>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
// check content-type // check content-type
if let Err(e) = cfg.check_mimetype(req) { cfg.check_mimetype(req)?;
return Either::A(result(Err(e)));
}
// check charset // check charset
let encoding = match req.encoding() { let encoding = req.encoding()?;
Err(_) => {
return Either::A(result(Err(ErrorBadRequest(
"Unknown request charset",
))))
}
Ok(encoding) => encoding,
};
Either::B(Box::new( Ok(Box::new(
MessageBody::new(req.clone()) MessageBody::new(req.clone())
.limit(cfg.limit) .limit(cfg.limit)
.from_err() .from_err()
@@ -464,6 +444,116 @@ 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -488,7 +578,7 @@ mod tests {
req.payload_mut() req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world")); .unread_data(Bytes::from_static(b"hello=world"));
match Bytes::from_request(&req, &cfg).poll().unwrap() { match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() {
Async::Ready(s) => { Async::Ready(s) => {
assert_eq!(s, Bytes::from_static(b"hello=world")); assert_eq!(s, Bytes::from_static(b"hello=world"));
} }
@@ -503,7 +593,7 @@ mod tests {
req.payload_mut() req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world")); .unread_data(Bytes::from_static(b"hello=world"));
match String::from_request(&req, &cfg).poll().unwrap() { match String::from_request(&req, &cfg).unwrap().poll().unwrap() {
Async::Ready(s) => { Async::Ready(s) => {
assert_eq!(s, "hello=world"); assert_eq!(s, "hello=world");
} }
@@ -573,80 +663,39 @@ mod tests {
let mut resource = ResourceHandler::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let mut routes = Vec::new(); let mut routes = Vec::new();
routes.push(( routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
Resource::new("index", "/{key}/{value}/"),
Some(resource),
));
let (router, _) = Router::new("", ServerSettings::default(), routes); let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some()); assert!(router.recognize(&mut req).is_some());
match Path::<MyStruct>::from_request(&req, &()) let s = Path::<MyStruct>::from_request(&req, &()).unwrap();
.poll() assert_eq!(s.key, "name");
.unwrap() assert_eq!(s.value, "user1");
{
Async::Ready(s) => {
assert_eq!(s.key, "name");
assert_eq!(s.value, "user1");
}
_ => unreachable!(),
}
match Path::<(String, String)>::from_request(&req, &()) let s = Path::<(String, String)>::from_request(&req, &()).unwrap();
.poll() assert_eq!(s.0, "name");
.unwrap() assert_eq!(s.1, "user1");
{
Async::Ready(s) => {
assert_eq!(s.0, "name");
assert_eq!(s.1, "user1");
}
_ => unreachable!(),
}
match Query::<Id>::from_request(&req, &()).poll().unwrap() { let s = Query::<Id>::from_request(&req, &()).unwrap();
Async::Ready(s) => { assert_eq!(s.id, "test");
assert_eq!(s.id, "test");
}
_ => unreachable!(),
}
let mut req = TestRequest::with_uri("/name/32/").finish(); let mut req = TestRequest::with_uri("/name/32/").finish();
assert!(router.recognize(&mut req).is_some()); assert!(router.recognize(&mut req).is_some());
match Path::<Test2>::from_request(&req, &()).poll().unwrap() { let s = Path::<Test2>::from_request(&req, &()).unwrap();
Async::Ready(s) => { assert_eq!(s.as_ref().key, "name");
assert_eq!(s.as_ref().key, "name"); assert_eq!(s.value, 32);
assert_eq!(s.value, 32);
}
_ => unreachable!(),
}
match Path::<(String, u8)>::from_request(&req, &()) let s = Path::<(String, u8)>::from_request(&req, &()).unwrap();
.poll() assert_eq!(s.0, "name");
.unwrap() assert_eq!(s.1, 32);
{
Async::Ready(s) => {
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
}
_ => unreachable!(),
}
match Path::<Vec<String>>::from_request(&req, &()) let res = Path::<Vec<String>>::extract(&req).unwrap();
.poll() assert_eq!(res[0], "name".to_owned());
.unwrap() assert_eq!(res[1], "32".to_owned());
{
Async::Ready(s) => {
assert_eq!(
s.into_inner(),
vec!["name".to_owned(), "32".to_owned()]
);
}
_ => unreachable!(),
}
} }
#[test] #[test]
fn test_extract_path_signle() { fn test_extract_path_single() {
let mut resource = ResourceHandler::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let mut routes = Vec::new(); let mut routes = Vec::new();
@@ -656,11 +705,36 @@ mod tests {
let mut req = TestRequest::with_uri("/32/").finish(); let mut req = TestRequest::with_uri("/32/").finish();
assert!(router.recognize(&mut req).is_some()); assert!(router.recognize(&mut req).is_some());
match Path::<i8>::from_request(&req, &()).poll().unwrap() { assert_eq!(*Path::<i8>::from_request(&mut req, &()).unwrap(), 32);
Async::Ready(s) => { }
assert_eq!(s.into_inner(), 32);
} #[test]
_ => unreachable!(), 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");
} }
} }

549
src/fs.rs
View File

@@ -14,12 +14,13 @@ use std::os::unix::fs::MetadataExt;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
use futures::{Async, Future, Poll, Stream}; use futures::{Async, Future, Poll, Stream};
use futures_cpupool::{CpuFuture, CpuPool}; use futures_cpupool::{CpuFuture, CpuPool};
use mime_guess::get_mime_type; use mime;
use mime_guess::{get_mime_type, guess_mime_type};
use error::Error; use error::Error;
use handler::{Handler, Reply, Responder, RouteHandler, WrapHandler}; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler};
use header; use header;
use http::{Method, StatusCode}; use http::{ContentEncoding, HttpRange, Method, StatusCode};
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@@ -37,6 +38,7 @@ pub struct NamedFile {
md: Metadata, md: Metadata,
modified: Option<SystemTime>, modified: Option<SystemTime>,
cpu_pool: Option<CpuPool>, cpu_pool: Option<CpuPool>,
encoding: Option<ContentEncoding>,
only_get: bool, only_get: bool,
status_code: StatusCode, status_code: StatusCode,
} }
@@ -57,12 +59,14 @@ impl NamedFile {
let path = path.as_ref().to_path_buf(); let path = path.as_ref().to_path_buf();
let modified = md.modified().ok(); let modified = md.modified().ok();
let cpu_pool = None; let cpu_pool = None;
let encoding = None;
Ok(NamedFile { Ok(NamedFile {
path, path,
file, file,
md, md,
modified, modified,
cpu_pool, cpu_pool,
encoding,
only_get: false, only_get: false,
status_code: StatusCode::OK, status_code: StatusCode::OK,
}) })
@@ -113,6 +117,13 @@ impl NamedFile {
self self
} }
/// Set content encoding for serving this file
#[inline]
pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self {
self.encoding = Some(enc);
self
}
fn etag(&self) -> Option<header::EntityTag> { fn etag(&self) -> Option<header::EntityTag> {
// This etag format is similar to Apache's. // This etag format is similar to Apache's.
self.modified.as_ref().map(|mtime| { self.modified.as_ref().map(|mtime| {
@@ -160,7 +171,7 @@ impl DerefMut for NamedFile {
} }
/// Returns true if `req` has no `If-Match` header or one which matches `etag`. /// 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>() { match req.get_header::<header::IfMatch>() {
None | Some(header::IfMatch::Any) => true, None | Some(header::IfMatch::Any) => true,
Some(header::IfMatch::Items(ref items)) => { Some(header::IfMatch::Items(ref items)) => {
@@ -177,7 +188,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`. /// 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>() { match req.get_header::<header::IfNoneMatch>() {
Some(header::IfNoneMatch::Any) => false, Some(header::IfNoneMatch::Any) => false,
Some(header::IfNoneMatch::Items(ref items)) => { Some(header::IfNoneMatch::Items(ref items)) => {
@@ -198,21 +209,36 @@ impl Responder for NamedFile {
type Item = HttpResponse; type Item = HttpResponse;
type Error = io::Error; 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 { if self.status_code != StatusCode::OK {
let mut resp = HttpResponse::build(self.status_code); let mut resp = HttpResponse::build(self.status_code);
resp.if_some(self.path().extension(), |ext, resp| { resp.if_some(self.path().extension(), |ext, resp| {
resp.set(header::ContentType(get_mime_type( resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
&ext.to_string_lossy(), }).if_some(self.path().file_name(), |file_name, resp| {
))); let mime_type = guess_mime_type(self.path());
let inline_or_attachment = match mime_type.type_() {
mime::IMAGE | mime::TEXT | mime::VIDEO => "inline",
_ => "attachment",
};
resp.header(
"Content-Disposition",
format!(
"{inline_or_attachment}; filename={filename}",
inline_or_attachment = inline_or_attachment,
filename = file_name.to_string_lossy()
),
);
}); });
if let Some(current_encoding) = self.encoding {
resp.content_encoding(current_encoding);
}
let reader = ChunkedReadFile { let reader = ChunkedReadFile {
size: self.md.len(), size: self.md.len(),
offset: 0, offset: 0,
cpu_pool: self.cpu_pool cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()),
.unwrap_or_else(|| req.cpu_pool().clone()),
file: Some(self.file), file: Some(self.file),
fut: None, fut: None,
counter: 0,
}; };
return Ok(resp.streaming(reader)); return Ok(resp.streaming(reader));
} }
@@ -229,7 +255,7 @@ impl Responder for NamedFile {
let last_modified = self.last_modified(); let last_modified = self.last_modified();
// check preconditions // check preconditions
let precondition_failed = if !any_match(etag.as_ref(), &req) { let precondition_failed = if !any_match(etag.as_ref(), req) {
true true
} else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) = } else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) =
(last_modified, req.get_header()) (last_modified, req.get_header())
@@ -240,7 +266,7 @@ impl Responder for NamedFile {
}; };
// check last modified // check last modified
let not_modified = if !none_match(etag.as_ref(), &req) { let not_modified = if !none_match(etag.as_ref(), req) {
true true
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
(last_modified, req.get_header()) (last_modified, req.get_header())
@@ -251,18 +277,66 @@ impl Responder for NamedFile {
}; };
let mut resp = HttpResponse::build(self.status_code); let mut resp = HttpResponse::build(self.status_code);
if let Some(current_encoding) = self.encoding {
resp.content_encoding(current_encoding);
}
resp.if_some(self.path().extension(), |ext, resp| { resp.if_some(self.path().extension(), |ext, resp| {
resp.set(header::ContentType(get_mime_type( resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
&ext.to_string_lossy(), }).if_some(self.path().file_name(), |file_name, resp| {
))); let mime_type = guess_mime_type(self.path());
}).if_some(last_modified, |lm, resp| { let inline_or_attachment = match mime_type.type_() {
mime::IMAGE | mime::TEXT | mime::VIDEO => "inline",
_ => "attachment",
};
resp.header(
"Content-Disposition",
format!(
"{inline_or_attachment}; filename={filename}",
inline_or_attachment = inline_or_attachment,
filename = file_name.to_string_lossy()
),
);
})
.if_some(last_modified, |lm, resp| {
resp.set(header::LastModified(lm)); resp.set(header::LastModified(lm));
}) })
.if_some(etag, |etag, resp| { .if_some(etag, |etag, resp| {
resp.set(header::ETag(etag)); resp.set(header::ETag(etag));
}); });
resp.header(header::ACCEPT_RANGES, "bytes");
let mut length = self.md.len();
let mut offset = 0;
// check for range header
if let Some(ranges) = req.headers().get(header::RANGE) {
if let Ok(rangesheader) = ranges.to_str() {
if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) {
length = rangesvec[0].length;
offset = rangesvec[0].start;
resp.content_encoding(ContentEncoding::Identity);
resp.header(
header::CONTENT_RANGE,
format!(
"bytes {}-{}/{}",
offset,
offset + length - 1,
self.md.len()
),
);
} else {
resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish());
};
} else {
return Ok(resp.status(StatusCode::BAD_REQUEST).finish());
};
};
resp.header(header::CONTENT_LENGTH, format!("{}", length));
if precondition_failed { if precondition_failed {
return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish());
} else if not_modified { } else if not_modified {
@@ -273,12 +347,15 @@ impl Responder for NamedFile {
Ok(resp.finish()) Ok(resp.finish())
} else { } else {
let reader = ChunkedReadFile { let reader = ChunkedReadFile {
size: self.md.len(), offset,
offset: 0, size: length,
cpu_pool: self.cpu_pool cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()),
.unwrap_or_else(|| req.cpu_pool().clone()),
file: Some(self.file), file: Some(self.file),
fut: None, fut: None,
counter: 0,
};
if offset != 0 || length != self.md.len() {
return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader));
}; };
Ok(resp.streaming(reader)) Ok(resp.streaming(reader))
} }
@@ -293,6 +370,7 @@ pub struct ChunkedReadFile {
cpu_pool: CpuPool, cpu_pool: CpuPool,
file: Option<File>, file: Option<File>,
fut: Option<CpuFuture<(File, Bytes), io::Error>>, fut: Option<CpuFuture<(File, Bytes), io::Error>>,
counter: u64,
} }
impl Stream for ChunkedReadFile { impl Stream for ChunkedReadFile {
@@ -306,6 +384,7 @@ impl Stream for ChunkedReadFile {
self.fut.take(); self.fut.take();
self.file = Some(file); self.file = Some(file);
self.offset += bytes.len() as u64; self.offset += bytes.len() as u64;
self.counter += bytes.len() as u64;
Ok(Async::Ready(Some(bytes))) Ok(Async::Ready(Some(bytes)))
} }
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
@@ -314,14 +393,16 @@ impl Stream for ChunkedReadFile {
let size = self.size; let size = self.size;
let offset = self.offset; let offset = self.offset;
let counter = self.counter;
if size == offset { if size == counter {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
let mut file = self.file.take().expect("Use after completion"); let mut file = self.file.take().expect("Use after completion");
self.fut = Some(self.cpu_pool.spawn_fn(move || { self.fut = Some(self.cpu_pool.spawn_fn(move || {
let max_bytes = cmp::min(size.saturating_sub(offset), 65_536) as usize; let max_bytes: usize;
let mut buf = BytesMut::with_capacity(max_bytes); max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
let mut buf = BytesMut::from(Vec::with_capacity(max_bytes));
file.seek(io::SeekFrom::Start(offset))?; file.seek(io::SeekFrom::Start(offset))?;
let nbytes = file.read(unsafe { buf.bytes_mut() })?; let nbytes = file.read(unsafe { buf.bytes_mut() })?;
if nbytes == 0 { if nbytes == 0 {
@@ -335,11 +416,14 @@ impl Stream for ChunkedReadFile {
} }
} }
type DirectoryRenderer<S> =
Fn(&Directory, &HttpRequest<S>) -> Result<HttpResponse, io::Error>;
/// A directory; responds with the generated directory listing. /// A directory; responds with the generated directory listing.
#[derive(Debug)] #[derive(Debug)]
pub struct Directory { pub struct Directory {
base: PathBuf, pub base: PathBuf,
path: PathBuf, pub path: PathBuf,
} }
impl Directory { impl Directory {
@@ -347,7 +431,7 @@ impl Directory {
Directory { base, path } 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 Ok(ref entry) = *entry {
if let Some(name) = entry.file_name().to_str() { if let Some(name) = entry.file_name().to_str() {
if name.starts_with('.') { if name.starts_with('.') {
@@ -363,61 +447,58 @@ impl Directory {
} }
} }
impl Responder for Directory { fn directory_listing<S>(
type Item = HttpResponse; dir: &Directory, req: &HttpRequest<S>,
type Error = io::Error; ) -> 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> { for entry in dir.path.read_dir()? {
let index_of = format!("Index of {}", req.path()); if dir.is_visible(&entry) {
let mut body = String::new(); let entry = entry.unwrap();
let base = Path::new(req.path()); 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 file is a directory, add '/' to the end of the name
if self.can_list(&entry) { if let Ok(metadata) = entry.metadata() {
let entry = entry.unwrap(); if metadata.is_dir() {
let p = match entry.path().strip_prefix(&self.path) { let _ = write!(
Ok(p) => base.join(p), body,
Err(_) => continue, "<li><a href=\"{}\">{}/</a></li>",
}; file_url,
// show file url as relative to static path entry.file_name().to_string_lossy()
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()
);
}
} else { } 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 /// Static files handling
@@ -442,6 +523,7 @@ pub struct StaticFiles<S> {
show_index: bool, show_index: bool,
cpu_pool: CpuPool, cpu_pool: CpuPool,
default: Box<RouteHandler<S>>, default: Box<RouteHandler<S>>,
renderer: Box<DirectoryRenderer<S>>,
_chunk_size: usize, _chunk_size: usize,
_follow_symlinks: bool, _follow_symlinks: bool,
} }
@@ -505,6 +587,7 @@ impl<S: 'static> StaticFiles<S> {
default: Box::new(WrapHandler::new(|_| { default: Box::new(WrapHandler::new(|_| {
HttpResponse::new(StatusCode::NOT_FOUND) HttpResponse::new(StatusCode::NOT_FOUND)
})), })),
renderer: Box::new(directory_listing),
_chunk_size: 0, _chunk_size: 0,
_follow_symlinks: false, _follow_symlinks: false,
} }
@@ -518,6 +601,17 @@ impl<S: 'static> StaticFiles<S> {
self 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 /// Set index file
/// ///
/// Redirects to specific index file for directory "/" instead of /// Redirects to specific index file for directory "/" instead of
@@ -535,15 +629,16 @@ impl<S: 'static> StaticFiles<S> {
} }
impl<S: 'static> Handler<S> for 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 { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
if !self.accessible { if !self.accessible {
Ok(self.default.handle(req)) Ok(self.default.handle(req))
} else { } else {
let relpath = match req.match_info() let relpath = match req
.match_info()
.get("tail") .get("tail")
.map(|tail| PathBuf::from_param(tail)) .map(|tail| PathBuf::from_param(tail.trim_left_matches('/')))
{ {
Some(Ok(path)) => path, Some(Ok(path)) => path,
_ => return Ok(self.default.handle(req)), _ => return Ok(self.default.handle(req)),
@@ -569,19 +664,18 @@ impl<S: 'static> Handler<S> for StaticFiles<S> {
HttpResponse::Found() HttpResponse::Found()
.header(header::LOCATION, new_path.as_str()) .header(header::LOCATION, new_path.as_str())
.finish() .finish()
.respond_to(req.drop_state()) .respond_to(&req)
} else if self.show_index { } else if self.show_index {
Directory::new(self.directory.clone(), path) let dir = Directory::new(self.directory.clone(), path);
.respond_to(req.drop_state())? Ok((*self.renderer)(&dir, &req)?.into())
.respond_to(req.drop_state())
} else { } else {
Ok(self.default.handle(req)) Ok(self.default.handle(req))
} }
} else { } else {
NamedFile::open(path)? NamedFile::open(path)?
.set_cpu_pool(self.cpu_pool.clone()) .set_cpu_pool(self.cpu_pool.clone())
.respond_to(req.drop_state())? .respond_to(&req)?
.respond_to(req.drop_state()) .respond_to(&req)
} }
} }
} }
@@ -595,7 +689,7 @@ mod tests {
use test::{self, TestRequest}; use test::{self, TestRequest};
#[test] #[test]
fn test_named_file() { fn test_named_file_text() {
assert!(NamedFile::open("test--").is_err()); assert!(NamedFile::open("test--").is_err());
let mut file = NamedFile::open("Cargo.toml") let mut file = NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
@@ -608,15 +702,67 @@ mod tests {
let _f: &mut File = &mut file; let _f: &mut File = &mut file;
} }
let resp = file.respond_to(HttpRequest::default()).unwrap(); let resp = file.respond_to(&HttpRequest::default()).unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-toml" "text/x-toml"
) );
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=Cargo.toml"
);
} }
#[test] #[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") let mut file = NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
.set_status_code(StatusCode::NOT_FOUND) .set_status_code(StatusCode::NOT_FOUND)
@@ -629,28 +775,208 @@ mod tests {
let _f: &mut File = &mut file; let _f: &mut File = &mut file;
} }
let resp = file.respond_to(HttpRequest::default()).unwrap(); let resp = file.respond_to(&HttpRequest::default()).unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/x-toml" "text/x-toml"
); );
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=Cargo.toml"
);
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
} }
#[test]
fn test_named_file_ranges_status_code() {
let mut srv = test::TestServer::with_factory(|| {
App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml"))
});
// Valid range header
let request = srv
.get()
.uri(srv.url("/t%65st/Cargo.toml"))
.header(header::RANGE, "bytes=10-20")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
// Invalid range header
let request = srv
.get()
.uri(srv.url("/t%65st/Cargo.toml"))
.header(header::RANGE, "bytes=1-0")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
}
#[test]
fn test_named_file_content_range_headers() {
let mut srv = test::TestServer::with_factory(|| {
App::new().handler(
"test",
StaticFiles::new(".").index_file("tests/test.binary"),
)
});
// Valid range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-20")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentrange = response
.headers()
.get(header::CONTENT_RANGE)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentrange, "bytes 10-20/100");
// Invalid range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-5")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentrange = response
.headers()
.get(header::CONTENT_RANGE)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentrange, "bytes */100");
}
#[test]
fn test_named_file_content_length_headers() {
let mut srv = test::TestServer::with_factory(|| {
App::new().handler(
"test",
StaticFiles::new(".").index_file("tests/test.binary"),
)
});
// Valid range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-20")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentlength = response
.headers()
.get(header::CONTENT_LENGTH)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentlength, "11");
// Invalid range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-8")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentlength = response
.headers()
.get(header::CONTENT_LENGTH)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentlength, "0");
// Without range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.no_default_headers()
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentlength = response
.headers()
.get(header::CONTENT_LENGTH)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentlength, "100");
// chunked
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let te = response
.headers()
.get(header::TRANSFER_ENCODING)
.unwrap()
.to_str()
.unwrap();
assert_eq!(te, "chunked");
}
#[test] #[test]
fn test_named_file_not_allowed() { fn test_named_file_not_allowed() {
let req = TestRequest::default().method(Method::POST).finish(); let req = TestRequest::default().method(Method::POST).finish();
let file = NamedFile::open("Cargo.toml").unwrap(); 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); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
} }
#[test]
fn test_named_file_content_encoding() {
let req = TestRequest::default().method(Method::GET).finish();
let file = NamedFile::open("Cargo.toml").unwrap();
assert!(file.encoding.is_none());
let resp = file
.set_content_encoding(ContentEncoding::Identity)
.respond_to(&req)
.unwrap();
assert!(resp.content_encoding().is_some());
assert_eq!(resp.content_encoding().unwrap().as_str(), "identity");
}
#[test] #[test]
fn test_named_file_any_method() { fn test_named_file_any_method() {
let req = TestRequest::default().method(Method::POST).finish(); let req = TestRequest::default().method(Method::POST).finish();
let file = NamedFile::open("Cargo.toml").unwrap(); 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); assert_eq!(resp.status(), StatusCode::OK);
} }
@@ -658,28 +984,28 @@ mod tests {
fn test_static_files() { fn test_static_files() {
let mut st = StaticFiles::new(".").show_files_listing(); let mut st = StaticFiles::new(".").show_files_listing();
st.accessible = false; st.accessible = false;
let resp = st.handle(HttpRequest::default()) let resp = st
.respond_to(HttpRequest::default()) .handle(HttpRequest::default())
.respond_to(&HttpRequest::default())
.unwrap(); .unwrap();
let resp = resp.as_response().expect("HTTP Response"); let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
st.accessible = true; st.accessible = true;
st.show_index = false; st.show_index = false;
let resp = st.handle(HttpRequest::default()) let resp = st
.respond_to(HttpRequest::default()) .handle(HttpRequest::default())
.respond_to(&HttpRequest::default())
.unwrap(); .unwrap();
let resp = resp.as_response().expect("HTTP Response"); let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.match_info_mut().add("tail", ""); req.match_info_mut().add("tail", "");
st.show_index = true; st.show_index = true;
let resp = st.handle(req) let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
.respond_to(HttpRequest::default()) let resp = resp.as_msg();
.unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/html; charset=utf-8" "text/html; charset=utf-8"
@@ -694,10 +1020,8 @@ mod tests {
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "tests"); req.match_info_mut().add("tail", "tests");
let resp = st.handle(req) let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
.respond_to(HttpRequest::default()) let resp = resp.as_msg();
.unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!( assert_eq!(
resp.headers().get(header::LOCATION).unwrap(), resp.headers().get(header::LOCATION).unwrap(),
@@ -707,10 +1031,8 @@ mod tests {
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "tests/"); req.match_info_mut().add("tail", "tests/");
let resp = st.handle(req) let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
.respond_to(HttpRequest::default()) let resp = resp.as_msg();
.unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!( assert_eq!(
resp.headers().get(header::LOCATION).unwrap(), resp.headers().get(header::LOCATION).unwrap(),
@@ -724,10 +1046,8 @@ mod tests {
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "tools/wsload"); req.match_info_mut().add("tail", "tools/wsload");
let resp = st.handle(req) let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap();
.respond_to(HttpRequest::default()) let resp = resp.as_msg();
.unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!( assert_eq!(
resp.headers().get(header::LOCATION).unwrap(), resp.headers().get(header::LOCATION).unwrap(),
@@ -801,7 +1121,8 @@ mod tests {
App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml"))
}); });
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test/%43argo.toml")) .uri(srv.url("/test/%43argo.toml"))
.finish() .finish()
.unwrap(); .unwrap();

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ use self::Charset::*;
/// A Mime 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]. /// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url].
/// ///

View File

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

View File

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

View File

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

View File

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

View File

@@ -190,16 +190,8 @@ mod tests {
// trailing slashes // trailing slashes
let params = vec![ let params = vec![
("/resource1", "", StatusCode::OK), ("/resource1", "", StatusCode::OK),
( ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
"/resource1/", ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource2",
"/resource2/",
StatusCode::MOVED_PERMANENTLY,
),
("/resource2/", "", StatusCode::OK), ("/resource2/", "", StatusCode::OK),
("/resource1?p1=1&p2=2", "", StatusCode::OK), ("/resource1?p1=1&p2=2", "", StatusCode::OK),
( (
@@ -217,16 +209,12 @@ mod tests {
for (path, target, code) in params { for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish()); let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req); let resp = app.run(req);
let r = resp.as_response().unwrap(); let r = resp.as_msg();
assert_eq!(r.status(), code); assert_eq!(r.status(), code);
if !target.is_empty() { if !target.is_empty() {
assert_eq!( assert_eq!(
target, target,
r.headers() r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
); );
} }
} }
@@ -260,7 +248,7 @@ mod tests {
for (path, code) in params { for (path, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish()); let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req); let resp = app.run(req);
let r = resp.as_response().unwrap(); let r = resp.as_msg();
assert_eq!(r.status(), code); assert_eq!(r.status(), code);
} }
} }
@@ -276,16 +264,8 @@ mod tests {
// trailing slashes // trailing slashes
let params = vec![ let params = vec![
("/resource1/a/b", "", StatusCode::OK), ("/resource1/a/b", "", StatusCode::OK),
( ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
"/resource1/", ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource1//",
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
( (
"//resource1//a//b", "//resource1//a//b",
"/resource1/a/b", "/resource1/a/b",
@@ -351,16 +331,12 @@ mod tests {
for (path, target, code) in params { for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish()); let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req); let resp = app.run(req);
let r = resp.as_response().unwrap(); let r = resp.as_msg();
assert_eq!(r.status(), code); assert_eq!(r.status(), code);
if !target.is_empty() { if !target.is_empty() {
assert_eq!( assert_eq!(
target, target,
r.headers() r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
); );
} }
} }
@@ -535,16 +511,12 @@ mod tests {
for (path, target, code) in params { for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish()); let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req); let resp = app.run(req);
let r = resp.as_response().unwrap(); let r = resp.as_msg();
assert_eq!(r.status(), code); assert_eq!(r.status(), code);
if !target.is_empty() { if !target.is_empty() {
assert_eq!( assert_eq!(
target, target,
r.headers() r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
); );
} }
} }

View File

@@ -1,213 +1,8 @@
//! Basic http responses //! Basic http responses
#![allow(non_upper_case_globals, deprecated)] #![allow(non_upper_case_globals)]
use http::StatusCode; use http::StatusCode;
use body::Body;
use error::Error;
use handler::{Handler, Reply, Responder, RouteHandler};
use httprequest::HttpRequest;
use httpresponse::{HttpResponse, HttpResponseBuilder}; 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 { macro_rules! STATIC_RESP {
($name:ident, $status:expr) => { ($name:ident, $status:expr) => {
#[allow(non_snake_case)] #[allow(non_snake_case)]
@@ -260,10 +55,7 @@ impl HttpResponse {
STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG);
STATIC_RESP!( STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
UnsupportedMediaType,
StatusCode::UNSUPPORTED_MEDIA_TYPE
);
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
@@ -272,48 +64,21 @@ impl HttpResponse {
STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY);
STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
STATIC_RESP!( STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
VersionNotSupported, STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
StatusCode::HTTP_VERSION_NOT_SUPPORTED
);
STATIC_RESP!(
VariantAlsoNegotiates,
StatusCode::VARIANT_ALSO_NEGOTIATES
);
STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED);
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Body, HttpBadRequest, HttpOk, HttpResponse}; use body::Body;
use http::StatusCode; use http::StatusCode;
use httpresponse::HttpResponse;
#[test] #[test]
fn test_build() { fn test_build() {
let resp = HttpOk.build().body(Body::Empty); let resp = HttpResponse::Ok().body(Body::Empty);
assert_eq!(resp.status(), StatusCode::OK); 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 bytes::{Bytes, BytesMut};
use encoding::EncodingRef;
use encoding::all::UTF_8; use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label; use encoding::label::encoding_from_whatwg_label;
use encoding::types::{DecoderTrap, Encoding}; use encoding::types::{DecoderTrap, Encoding};
use encoding::EncodingRef;
use futures::{Future, Poll, Stream}; use futures::{Future, Poll, Stream};
use http::{header, HeaderMap}; use http::{header, HeaderMap};
use http_range::HttpRange; use http_range::HttpRange;
@@ -11,7 +11,9 @@ use serde::de::DeserializeOwned;
use serde_urlencoded; use serde_urlencoded;
use std::str; use std::str;
use error::{ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError}; use error::{
ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError,
};
use header::Header; use header::Header;
use json::JsonBody; use json::JsonBody;
use multipart::Multipart; use multipart::Multipart;
@@ -96,10 +98,8 @@ pub trait HttpMessage {
/// `size` is full size of response (file). /// `size` is full size of response (file).
fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> { fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> {
if let Some(range) = self.headers().get(header::RANGE) { if let Some(range) = self.headers().get(header::RANGE) {
HttpRange::parse( HttpRange::parse(unsafe { str::from_utf8_unchecked(range.as_bytes()) }, size)
unsafe { str::from_utf8_unchecked(range.as_bytes()) }, .map_err(|e| e.into())
size,
).map_err(|e| e.into())
} else { } else {
Ok(Vec::new()) Ok(Vec::new())
} }
@@ -148,7 +148,6 @@ pub trait HttpMessage {
/// Returns error: /// Returns error:
/// ///
/// * content type is not `application/x-www-form-urlencoded` /// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k /// * content-length is greater than 256k
/// ///
/// ## Server example /// ## Server example
@@ -365,9 +364,7 @@ where
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(req) = self.req.take() { if let Some(req) = self.req.take() {
if req.chunked().unwrap_or(false) { if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
return Err(UrlencodedError::Chunked);
} else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() { if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() { if let Ok(len) = s.parse::<u64>() {
if len > 262_144 { if len > 262_144 {
@@ -385,12 +382,12 @@ where
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Err(UrlencodedError::ContentType); return Err(UrlencodedError::ContentType);
} }
let encoding = req.encoding() let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?;
.map_err(|_| UrlencodedError::ContentType)?;
// future // future
let limit = self.limit; let limit = self.limit;
let fut = req.from_err() let fut = req
.from_err()
.fold(BytesMut::new(), move |mut body, chunk| { .fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit { if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow) Err(UrlencodedError::Overflow)
@@ -425,8 +422,8 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use encoding::Encoding;
use encoding::all::ISO_8859_2; use encoding::all::ISO_8859_2;
use encoding::Encoding;
use futures::Async; use futures::Async;
use http::{Method, Uri, Version}; use http::{Method, Uri, Version};
use httprequest::HttpRequest; use httprequest::HttpRequest;
@@ -488,10 +485,7 @@ mod tests {
#[test] #[test]
fn test_encoding_error() { fn test_encoding_error() {
let req = TestRequest::with_header("content-type", "applicatjson").finish(); let req = TestRequest::with_header("content-type", "applicatjson").finish();
assert_eq!( assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
Some(ContentTypeError::ParseError),
req.encoding().err()
);
let req = TestRequest::with_header( let req = TestRequest::with_header(
"content-type", "content-type",
@@ -578,13 +572,6 @@ mod tests {
#[test] #[test]
fn test_urlencoded_error() { fn test_urlencoded_error() {
let req =
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::Chunked
);
let req = TestRequest::with_header( let req = TestRequest::with_header(
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
@@ -664,8 +651,7 @@ mod tests {
} }
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.payload_mut() req.payload_mut().unread_data(Bytes::from_static(b"test"));
.unread_data(Bytes::from_static(b"test"));
match req.body().poll().ok().unwrap() { match req.body().poll().ok().unwrap() {
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
_ => unreachable!("error"), _ => unreachable!("error"),

View File

@@ -1,19 +1,20 @@
//! HTTP Request message related code. //! HTTP Request message related code.
use bytes::Bytes; #![cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ptr))]
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 std::net::SocketAddr; use std::net::SocketAddr;
use std::rc::Rc; use std::rc::Rc;
use std::{cmp, fmt, io, mem, str}; use std::{cmp, fmt, io, mem, str};
use bytes::Bytes;
use cookie::Cookie;
use failure;
use futures::{Async, Poll, Stream};
use futures_cpupool::CpuPool;
use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version};
use tokio_io::AsyncRead; use tokio_io::AsyncRead;
use url::{form_urlencoded, Url}; use url::{form_urlencoded, Url};
use body::Body; use body::Body;
use error::{CookieParseError, Error, PayloadError, UrlGenerationError}; use error::{CookieParseError, PayloadError, UrlGenerationError};
use handler::FromRequest; use handler::FromRequest;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use httpresponse::{HttpResponse, HttpResponseBuilder}; use httpresponse::{HttpResponse, HttpResponseBuilder};
@@ -24,23 +25,30 @@ use router::{Resource, Router};
use server::helpers::SharedHttpInnerMessage; use server::helpers::SharedHttpInnerMessage;
use uri::Url as InnerUrl; use uri::Url as InnerUrl;
bitflags! {
pub(crate) struct MessageFlags: u8 {
const KEEPALIVE = 0b0000_0010;
}
}
pub struct HttpInnerMessage { pub struct HttpInnerMessage {
pub version: Version, pub version: Version,
pub method: Method, pub method: Method,
pub(crate) url: InnerUrl, pub(crate) url: InnerUrl,
pub(crate) flags: MessageFlags,
pub headers: HeaderMap, pub headers: HeaderMap,
pub extensions: Extensions, pub extensions: Extensions,
pub params: Params<'static>, pub params: Params<'static>,
pub cookies: Option<Vec<Cookie<'static>>>,
pub query: Params<'static>,
pub query_loaded: bool,
pub addr: Option<SocketAddr>, pub addr: Option<SocketAddr>,
pub payload: Option<Payload>, pub payload: Option<Payload>,
pub info: Option<ConnectionInfo<'static>>, pub info: Option<ConnectionInfo<'static>>,
pub keep_alive: bool, pub prefix: u16,
resource: RouterResource, resource: RouterResource,
} }
struct Query(Params<'static>);
struct Cookies(Vec<Cookie<'static>>);
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
enum RouterResource { enum RouterResource {
Notset, Notset,
@@ -54,15 +62,13 @@ impl Default for HttpInnerMessage {
url: InnerUrl::default(), url: InnerUrl::default(),
version: Version::HTTP_11, version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16), headers: HeaderMap::with_capacity(16),
flags: MessageFlags::empty(),
params: Params::new(), params: Params::new(),
query: Params::new(),
query_loaded: false,
addr: None, addr: None,
cookies: None,
payload: None, payload: None,
extensions: Extensions::new(), extensions: Extensions::new(),
info: None, info: None,
keep_alive: true, prefix: 0,
resource: RouterResource::Notset, resource: RouterResource::Notset,
} }
} }
@@ -72,7 +78,7 @@ impl HttpInnerMessage {
/// Checks if a connection should be kept alive. /// Checks if a connection should be kept alive.
#[inline] #[inline]
pub fn keep_alive(&self) -> bool { pub fn keep_alive(&self) -> bool {
self.keep_alive self.flags.contains(MessageFlags::KEEPALIVE)
} }
#[inline] #[inline]
@@ -82,10 +88,9 @@ impl HttpInnerMessage {
self.params.clear(); self.params.clear();
self.addr = None; self.addr = None;
self.info = None; self.info = None;
self.query_loaded = false; self.flags = MessageFlags::empty();
self.cookies = None;
self.payload = None; self.payload = None;
self.keep_alive = true; self.prefix = 0;
self.resource = RouterResource::Notset; self.resource = RouterResource::Notset;
} }
} }
@@ -113,13 +118,11 @@ impl HttpRequest<()> {
headers, headers,
payload, payload,
params: Params::new(), params: Params::new(),
query: Params::new(),
query_loaded: false,
extensions: Extensions::new(), extensions: Extensions::new(),
cookies: None,
addr: None, addr: None,
info: None, info: None,
keep_alive: true, prefix: 0,
flags: MessageFlags::empty(),
resource: RouterResource::Notset, resource: RouterResource::Notset,
}), }),
None, None,
@@ -138,6 +141,12 @@ impl HttpRequest<()> {
pub fn with_state<S>(self, state: Rc<S>, router: Router) -> HttpRequest<S> { pub fn with_state<S>(self, state: Rc<S>, router: Router) -> HttpRequest<S> {
HttpRequest(self.0, Some(state), Some(router)) HttpRequest(self.0, Some(state), Some(router))
} }
pub(crate) fn clone_with_state<S>(
&self, state: Rc<S>, router: Router,
) -> HttpRequest<S> {
HttpRequest(self.0.clone(), Some(state), Some(router))
}
} }
impl<S> HttpMessage for HttpRequest<S> { impl<S> HttpMessage for HttpRequest<S> {
@@ -187,18 +196,11 @@ impl<S> HttpRequest<S> {
/// Request extensions /// Request extensions
#[inline] #[inline]
pub fn extensions(&mut self) -> &mut Extensions { pub fn extensions(&self) -> &Extensions {
&mut self.as_mut().extensions
}
/// Request extensions
#[inline]
#[doc(hidden)]
pub fn extensions_ro(&self) -> &Extensions {
&self.as_ref().extensions &self.as_ref().extensions
} }
/// Mutable refernece to a the request's extensions /// Mutable reference to a the request's extensions
#[inline] #[inline]
pub fn extensions_mut(&mut self) -> &mut Extensions { pub fn extensions_mut(&mut self) -> &mut Extensions {
&mut self.as_mut().extensions &mut self.as_mut().extensions
@@ -233,12 +235,13 @@ impl<S> HttpRequest<S> {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn prefix_len(&self) -> usize { pub fn prefix_len(&self) -> u16 {
if let Some(router) = self.router() { self.as_ref().prefix as u16
router.prefix().len() }
} else {
0 #[doc(hidden)]
} pub fn set_prefix_len(&mut self, len: u16) {
self.as_mut().prefix = len;
} }
/// Read the Request Uri. /// Read the Request Uri.
@@ -247,16 +250,6 @@ impl<S> HttpRequest<S> {
self.as_ref().url.uri() self.as_ref().url.uri()
} }
#[doc(hidden)]
#[deprecated(since = "0.5.3")]
/// Returns mutable the Request Uri.
///
/// This might be useful for middlewares, e.g. path normalization.
#[inline]
pub fn uri_mut(&mut self) -> &mut Uri {
self.as_mut().url.uri_mut()
}
/// Read the Request method. /// Read the Request method.
#[inline] #[inline]
pub fn method(&self) -> &Method { pub fn method(&self) -> &Method {
@@ -314,7 +307,7 @@ impl<S> HttpRequest<S> {
/// } /// }
/// ``` /// ```
pub fn url_for<U, I>( pub fn url_for<U, I>(
&self, name: &str, elements: U &self, name: &str, elements: U,
) -> Result<Url, UrlGenerationError> ) -> Result<Url, UrlGenerationError>
where where
U: IntoIterator<Item = I>, U: IntoIterator<Item = I>,
@@ -338,6 +331,15 @@ impl<S> HttpRequest<S> {
} }
} }
/// Generate url for named resource
///
/// This method is similar to `HttpRequest::url_for()` but it can be used
/// for urls that do not contain variable parts.
pub fn url_for_static(&self, name: &str) -> Result<Url, UrlGenerationError> {
const NO_PARAMS: [&str; 0] = [];
self.url_for(name, &NO_PARAMS)
}
/// This method returns reference to current `Router` object. /// This method returns reference to current `Router` object.
#[inline] #[inline]
pub fn router(&self) -> Option<&Router> { pub fn router(&self) -> Option<&Router> {
@@ -376,19 +378,21 @@ impl<S> HttpRequest<S> {
self.as_mut().addr = addr; self.as_mut().addr = addr;
} }
#[doc(hidden)]
/// Get a reference to the Params object. /// Get a reference to the Params object.
/// Params is a container for url query parameters. /// Params is a container for url query parameters.
pub fn query(&self) -> &Params { pub fn query<'a>(&'a self) -> &'a Params {
if !self.as_ref().query_loaded { if self.extensions().get::<Query>().is_none() {
let params: &mut Params = let mut params: Params<'a> = Params::new();
unsafe { mem::transmute(&mut self.as_mut().query) };
params.clear();
self.as_mut().query_loaded = true;
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
params.add(key, val); params.add(key, val);
} }
let params: Params<'static> = unsafe { mem::transmute(params) };
self.as_mut().extensions.insert(Query(params));
} }
unsafe { mem::transmute(&self.as_ref().query) } let params: &Params<'a> =
unsafe { mem::transmute(&self.extensions().get::<Query>().unwrap().0) };
params
} }
/// The query string in the URL. /// The query string in the URL.
@@ -405,7 +409,7 @@ impl<S> HttpRequest<S> {
/// Load request cookies. /// Load request cookies.
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> { pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
if self.as_ref().cookies.is_none() { if self.extensions().get::<Query>().is_none() {
let msg = self.as_mut(); let msg = self.as_mut();
let mut cookies = Vec::new(); let mut cookies = Vec::new();
for hdr in msg.headers.get_all(header::COOKIE) { for hdr in msg.headers.get_all(header::COOKIE) {
@@ -416,9 +420,9 @@ impl<S> HttpRequest<S> {
} }
} }
} }
msg.cookies = Some(cookies); msg.extensions.insert(Cookies(cookies));
} }
Ok(&self.as_ref().cookies.as_ref().unwrap()) Ok(&self.extensions().get::<Cookies>().unwrap().0)
} }
/// Return request cookie. /// Return request cookie.
@@ -433,6 +437,12 @@ impl<S> HttpRequest<S> {
None None
} }
pub(crate) fn set_cookies(&mut self, cookies: Option<Vec<Cookie<'static>>>) {
if let Some(cookies) = cookies {
self.extensions_mut().insert(Cookies(cookies));
}
}
/// Get a reference to the Params object. /// Get a reference to the Params object.
/// ///
/// Params is a container for url parameters. /// Params is a container for url parameters.
@@ -452,7 +462,7 @@ impl<S> HttpRequest<S> {
/// Checks if a connection should be kept alive. /// Checks if a connection should be kept alive.
pub fn keep_alive(&self) -> bool { pub fn keep_alive(&self) -> bool {
self.as_ref().keep_alive() self.as_ref().flags.contains(MessageFlags::KEEPALIVE)
} }
/// Check if request requires connection upgrade /// Check if request requires connection upgrade
@@ -506,13 +516,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 Config = ();
type Result = FutureResult<Self, Error>; type Result = Self;
#[inline] #[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
result(Ok(req.clone())) req.clone()
} }
} }
@@ -594,10 +604,7 @@ impl<S> fmt::Debug for HttpRequest<S> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(deprecated)]
use super::*; use super::*;
use http::{HttpTryFrom, Uri};
use resource::ResourceHandler; use resource::ResourceHandler;
use router::Resource; use router::Resource;
use server::ServerSettings; use server::ServerSettings;
@@ -610,14 +617,6 @@ mod tests {
assert!(dbg.contains("HttpRequest")); 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] #[test]
fn test_no_request_cookies() { fn test_no_request_cookies() {
let req = HttpRequest::default(); let req = HttpRequest::default();
@@ -681,12 +680,8 @@ mod tests {
let mut resource = ResourceHandler::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let routes = vec![ let routes =
( vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
Resource::new("index", "/user/{name}.{ext}"),
Some(resource),
),
];
let (router, _) = Router::new("/", ServerSettings::default(), routes); let (router, _) = Router::new("/", ServerSettings::default(), routes);
assert!(router.has_route("/user/test.html")); assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/test/unknown")); assert!(!router.has_route("/test/unknown"));
@@ -715,36 +710,48 @@ mod tests {
let mut resource = ResourceHandler::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let routes = vec![ let routes = vec![(Resource::new("index", "/user/{name}.html"), Some(resource))];
(
Resource::new("index", "/user/{name}.{ext}"),
Some(resource),
),
];
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
assert!(router.has_route("/user/test.html")); assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/prefix/user/test.html")); assert!(!router.has_route("/prefix/user/test.html"));
let req = req.with_state(Rc::new(()), router); let req = req.with_state(Rc::new(()), router);
let url = req.url_for("index", &["test", "html"]); let url = req.url_for("index", &["test"]);
assert_eq!( assert_eq!(
url.ok().unwrap().as_str(), url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/user/test.html" "http://www.rust-lang.org/prefix/user/test.html"
); );
} }
#[test]
fn test_url_for_static() {
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let routes = vec![(Resource::new("index", "/index.html"), Some(resource))];
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
assert!(router.has_route("/index.html"));
assert!(!router.has_route("/prefix/index.html"));
let req = req.with_state(Rc::new(()), router);
let url = req.url_for_static("index");
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/index.html"
);
}
#[test] #[test]
fn test_url_for_external() { fn test_url_for_external() {
let req = HttpRequest::default(); let req = HttpRequest::default();
let mut resource = ResourceHandler::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let routes = vec![ let routes = vec![(
( Resource::external("youtube", "https://youtube.com/watch/{video_id}"),
Resource::external("youtube", "https://youtube.com/watch/{video_id}"), None,
None, )];
),
];
let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); let (router, _) = Router::new::<()>("", ServerSettings::default(), routes);
assert!(!router.has_route("https://youtube.com/watch/unknown")); assert!(!router.has_route("https://youtube.com/watch/unknown"));

View File

@@ -89,7 +89,7 @@ impl HttpResponse {
/// Constructs a error response /// Constructs a error response
#[inline] #[inline]
pub fn from_error(error: Error) -> HttpResponse { pub fn from_error(error: Error) -> HttpResponse {
let mut resp = error.cause().error_response(); let mut resp = error.as_response_error().error_response();
resp.get_mut().error = Some(error); resp.get_mut().error = Some(error);
resp resp
} }
@@ -241,6 +241,14 @@ impl HttpResponse {
pub fn set_write_buffer_capacity(&mut self, cap: usize) { pub fn set_write_buffer_capacity(&mut self, cap: usize) {
self.get_mut().write_capacity = cap; self.get_mut().write_capacity = cap;
} }
pub(crate) fn into_inner(mut self) -> Box<InnerHttpResponse> {
self.0.take().unwrap()
}
pub(crate) fn from_inner(inner: Box<InnerHttpResponse>) -> HttpResponse {
HttpResponse(Some(inner), HttpResponsePool::pool())
}
} }
impl fmt::Debug for HttpResponse { impl fmt::Debug for HttpResponse {
@@ -297,11 +305,13 @@ impl HttpResponseBuilder {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{HttpRequest, HttpResponse, Result, http}; /// use actix_web::{http, HttpRequest, HttpResponse, Result};
/// ///
/// fn index(req: HttpRequest) -> Result<HttpResponse> { /// fn index(req: HttpRequest) -> Result<HttpResponse> {
/// Ok(HttpResponse::Ok() /// Ok(HttpResponse::Ok()
/// .set(http::header::IfModifiedSince("Sun, 07 Nov 1994 08:48:37 GMT".parse()?)) /// .set(http::header::IfModifiedSince(
/// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?,
/// ))
/// .finish()) /// .finish())
/// } /// }
/// fn main() {} /// fn main() {}
@@ -455,7 +465,8 @@ impl HttpResponseBuilder {
/// .path("/") /// .path("/")
/// .secure(true) /// .secure(true)
/// .http_only(true) /// .http_only(true)
/// .finish()) /// .finish(),
/// )
/// .finish() /// .finish()
/// } /// }
/// fn main() {} /// fn main() {}
@@ -466,10 +477,7 @@ impl HttpResponseBuilder {
jar.add(cookie.into_owned()); jar.add(cookie.into_owned());
self.cookies = Some(jar) self.cookies = Some(jar)
} else { } else {
self.cookies self.cookies.as_mut().unwrap().add(cookie.into_owned());
.as_mut()
.unwrap()
.add(cookie.into_owned());
} }
self self
} }
@@ -534,9 +542,7 @@ impl HttpResponseBuilder {
if let Some(e) = self.err.take() { if let Some(e) = self.err.take() {
return Error::from(e).into(); return Error::from(e).into();
} }
let mut response = self.response let mut response = self.response.take().expect("cannot reuse response builder");
.take()
.expect("cannot reuse response builder");
if let Some(ref jar) = self.cookies { if let Some(ref jar) = self.cookies {
for cookie in jar.delta() { for cookie in jar.delta() {
match HeaderValue::from_str(&cookie.to_string()) { match HeaderValue::from_str(&cookie.to_string()) {
@@ -558,9 +564,7 @@ impl HttpResponseBuilder {
S: Stream<Item = Bytes, Error = E> + 'static, S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error>, E: Into<Error>,
{ {
self.body(Body::Streaming(Box::new( self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
stream.map_err(|e| e.into()),
)))
} }
/// Set a json body and generate `HttpResponse` /// Set a json body and generate `HttpResponse`
@@ -607,7 +611,7 @@ impl HttpResponseBuilder {
#[inline] #[inline]
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] #[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
fn parts<'a>( 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>> { ) -> Option<&'a mut Box<InnerHttpResponse>> {
if err.is_some() { if err.is_some() {
return None; return None;
@@ -636,7 +640,7 @@ impl Responder for HttpResponseBuilder {
type Error = Error; type Error = Error;
#[inline] #[inline]
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, Error> { fn respond_to<S>(mut self, _: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(self.finish()) Ok(self.finish())
} }
} }
@@ -653,8 +657,9 @@ impl Responder for &'static str {
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; 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) Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(self)) .body(self))
} }
@@ -672,8 +677,9 @@ impl Responder for &'static [u8] {
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; 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) Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self)) .body(self))
} }
@@ -691,8 +697,9 @@ impl Responder for String {
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; 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) Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(self)) .body(self))
} }
@@ -710,8 +717,9 @@ impl<'a> Responder for &'a String {
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; 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) Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(self)) .body(self))
} }
@@ -729,8 +737,9 @@ impl Responder for Bytes {
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; 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) Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self)) .body(self))
} }
@@ -748,8 +757,9 @@ impl Responder for BytesMut {
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; 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) Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self)) .body(self))
} }
@@ -782,12 +792,12 @@ impl<'a, S> From<&'a HttpRequest<S>> for HttpResponseBuilder {
} }
#[derive(Debug)] #[derive(Debug)]
struct InnerHttpResponse { pub(crate) struct InnerHttpResponse {
version: Option<Version>, version: Option<Version>,
headers: HeaderMap, headers: HeaderMap,
status: StatusCode, status: StatusCode,
reason: Option<&'static str>, reason: Option<&'static str>,
body: Body, pub(crate) body: Body,
chunked: Option<bool>, chunked: Option<bool>,
encoding: Option<ContentEncoding>, encoding: Option<ContentEncoding>,
connection_type: Option<ConnectionType>, connection_type: Option<ConnectionType>,
@@ -796,6 +806,9 @@ struct InnerHttpResponse {
error: Option<Error>, error: Option<Error>,
} }
unsafe impl Sync for InnerHttpResponse {}
unsafe impl Send for InnerHttpResponse {}
impl InnerHttpResponse { impl InnerHttpResponse {
#[inline] #[inline]
fn new(status: StatusCode, body: Body) -> InnerHttpResponse { fn new(status: StatusCode, body: Body) -> InnerHttpResponse {
@@ -822,14 +835,14 @@ thread_local!(static POOL: Rc<UnsafeCell<HttpResponsePool>> = HttpResponsePool::
impl HttpResponsePool { impl HttpResponsePool {
pub fn pool() -> Rc<UnsafeCell<HttpResponsePool>> { pub fn pool() -> Rc<UnsafeCell<HttpResponsePool>> {
Rc::new(UnsafeCell::new(HttpResponsePool( Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity(
VecDeque::with_capacity(128), 128,
))) ))))
} }
#[inline] #[inline]
pub fn get_builder( pub fn get_builder(
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode,
) -> HttpResponseBuilder { ) -> HttpResponseBuilder {
let p = unsafe { &mut *pool.as_ref().get() }; let p = unsafe { &mut *pool.as_ref().get() };
if let Some(mut msg) = p.0.pop_front() { if let Some(mut msg) = p.0.pop_front() {
@@ -853,7 +866,7 @@ impl HttpResponsePool {
#[inline] #[inline]
pub fn get_response( pub fn get_response(
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode, body: Body pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode, body: Body,
) -> HttpResponse { ) -> HttpResponse {
let p = unsafe { &mut *pool.as_ref().get() }; let p = unsafe { &mut *pool.as_ref().get() };
if let Some(mut msg) = p.0.pop_front() { if let Some(mut msg) = p.0.pop_front() {
@@ -879,7 +892,7 @@ impl HttpResponsePool {
#[inline(always)] #[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))]
fn release( 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() }; let pool = unsafe { &mut *pool.as_ref().get() };
if pool.0.len() < 128 { if pool.0.len() < 128 {
@@ -944,7 +957,8 @@ mod tests {
.del_cookie(&cookies[0]) .del_cookie(&cookies[0])
.finish(); .finish();
let mut val: Vec<_> = resp.headers() let mut val: Vec<_> = resp
.headers()
.get_all("Set-Cookie") .get_all("Set-Cookie")
.iter() .iter()
.map(|v| v.to_str().unwrap().to_owned()) .map(|v| v.to_str().unwrap().to_owned())
@@ -975,9 +989,7 @@ mod tests {
#[test] #[test]
fn test_force_close() { fn test_force_close() {
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
.force_close()
.finish();
assert!(!resp.keep_alive().unwrap()) assert!(!resp.keep_alive().unwrap())
} }
@@ -986,10 +998,7 @@ mod tests {
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK)
.content_type("text/plain") .content_type("text/plain")
.body(Body::Empty); .body(Body::Empty);
assert_eq!( assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
resp.headers().get(CONTENT_TYPE).unwrap(),
"text/plain"
)
} }
#[test] #[test]
@@ -1036,10 +1045,10 @@ mod tests {
} }
impl Body { impl Body {
pub(crate) fn binary(&self) -> Option<&Binary> { pub(crate) fn bin_ref(&self) -> &Binary {
match *self { match *self {
Body::Binary(ref bin) => Some(bin), Body::Binary(ref bin) => bin,
_ => None, _ => panic!(),
} }
} }
} }
@@ -1055,16 +1064,16 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test")); assert_eq!(resp.body().bin_ref(), &Binary::from("test"));
let resp: HttpResponse = "test".respond_to(req.clone()).ok().unwrap(); let resp: HttpResponse = "test".respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test")); assert_eq!(resp.body().bin_ref(), &Binary::from("test"));
let resp: HttpResponse = b"test".as_ref().into(); let resp: HttpResponse = b"test".as_ref().into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@@ -1073,22 +1082,16 @@ mod tests {
HeaderValue::from_static("application/octet-stream") HeaderValue::from_static("application/octet-stream")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref()));
resp.body().binary().unwrap(),
&Binary::from(b"test".as_ref())
);
let resp: HttpResponse = b"test".as_ref().respond_to(req.clone()).ok().unwrap(); let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream") HeaderValue::from_static("application/octet-stream")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref()));
resp.body().binary().unwrap(),
&Binary::from(b"test".as_ref())
);
let resp: HttpResponse = "test".to_owned().into(); let resp: HttpResponse = "test".to_owned().into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@@ -1097,26 +1100,16 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned()));
resp.body().binary().unwrap(),
&Binary::from("test".to_owned())
);
let resp: HttpResponse = "test" let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap();
.to_owned()
.respond_to(req.clone())
.ok()
.unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned()));
resp.body().binary().unwrap(),
&Binary::from("test".to_owned())
);
let resp: HttpResponse = (&"test".to_owned()).into(); let resp: HttpResponse = (&"test".to_owned()).into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@@ -1125,25 +1118,16 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned()));
resp.body().binary().unwrap(),
&Binary::from(&"test".to_owned())
);
let resp: HttpResponse = (&"test".to_owned()) let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap();
.respond_to(req.clone())
.ok()
.unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned()));
resp.body().binary().unwrap(),
&Binary::from(&"test".to_owned())
);
let b = Bytes::from_static(b"test"); let b = Bytes::from_static(b"test");
let resp: HttpResponse = b.into(); let resp: HttpResponse = b.into();
@@ -1154,12 +1138,12 @@ mod tests {
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.body().binary().unwrap(), resp.body().bin_ref(),
&Binary::from(Bytes::from_static(b"test")) &Binary::from(Bytes::from_static(b"test"))
); );
let b = Bytes::from_static(b"test"); 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.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
@@ -1167,7 +1151,7 @@ mod tests {
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.body().binary().unwrap(), resp.body().bin_ref(),
&Binary::from(Bytes::from_static(b"test")) &Binary::from(Bytes::from_static(b"test"))
); );
@@ -1179,23 +1163,17 @@ mod tests {
HeaderValue::from_static("application/octet-stream") HeaderValue::from_static("application/octet-stream")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test")));
resp.body().binary().unwrap(),
&Binary::from(BytesMut::from("test"))
);
let b = BytesMut::from("test"); let b = BytesMut::from("test");
let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap(); let resp: HttpResponse = b.respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream") HeaderValue::from_static("application/octet-stream")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test")));
resp.body().binary().unwrap(),
&Binary::from(BytesMut::from("test"))
);
} }
#[test] #[test]

View File

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

View File

@@ -6,8 +6,8 @@ use std::ops::{Deref, DerefMut};
use std::rc::Rc; use std::rc::Rc;
use mime; use mime;
use serde::Serialize;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json; use serde_json;
use error::{Error, JsonPayloadError, PayloadError}; use error::{Error, JsonPayloadError, PayloadError};
@@ -118,10 +118,11 @@ impl<T: Serialize> Responder for Json<T> {
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; 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)?; let body = serde_json::to_string(&self.0)?;
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("application/json") .content_type("application/json")
.body(body)) .body(body))
} }
@@ -295,7 +296,8 @@ where
} }
let limit = self.limit; let limit = self.limit;
let fut = req.from_err() let fut = req
.from_err()
.fold(BytesMut::new(), move |mut body, chunk| { .fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit { if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow) Err(JsonPayloadError::Overflow)
@@ -351,7 +353,7 @@ mod tests {
let json = Json(MyObject { let json = Json(MyObject {
name: "test".to_owned(), name: "test".to_owned(),
}); });
let resp = json.respond_to(HttpRequest::default()).unwrap(); let resp = json.respond_to(&HttpRequest::default()).unwrap();
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"application/json" "application/json"
@@ -362,10 +364,7 @@ mod tests {
fn test_json_body() { fn test_json_body() {
let req = HttpRequest::default(); let req = HttpRequest::default();
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!( assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
json.poll().err().unwrap(),
JsonPayloadError::ContentType
);
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.headers_mut().insert( req.headers_mut().insert(
@@ -373,10 +372,7 @@ mod tests {
header::HeaderValue::from_static("application/text"), header::HeaderValue::from_static("application/text"),
); );
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!( assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
json.poll().err().unwrap(),
JsonPayloadError::ContentType
);
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.headers_mut().insert( req.headers_mut().insert(
@@ -417,13 +413,7 @@ mod tests {
let mut handler = With::new(|data: Json<MyObject>| data, cfg); let mut handler = With::new(|data: Json<MyObject>| data, cfg);
let req = HttpRequest::default(); let req = HttpRequest::default();
let err = handler assert!(handler.handle(req).as_err().is_some());
.handle(req)
.as_response()
.unwrap()
.error()
.is_some();
assert!(err);
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.headers_mut().insert( req.headers_mut().insert(
@@ -436,12 +426,6 @@ mod tests {
); );
req.payload_mut() req.payload_mut()
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); .unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
let ok = handler assert!(handler.handle(req).as_err().is_none())
.handle(req)
.as_response()
.unwrap()
.error()
.is_none();
assert!(ok)
} }
} }

View File

@@ -6,15 +6,15 @@
//! # use std::thread; //! # use std::thread;
//! //!
//! fn index(info: Path<(String, u32)>) -> String { //! fn index(info: Path<(String, u32)>) -> String {
//! format!("Hello {}! id:{}", info.0, info.1) //! format!("Hello {}! id:{}", info.0, info.1)
//! } //! }
//! //!
//! fn main() { //! fn main() {
//! # thread::spawn(|| { //! # thread::spawn(|| {
//! server::new( //! server::new(|| {
//! || App::new() //! App::new().resource("/{name}/{id}/index.html", |r| r.with(index))
//! .resource("/{name}/{id}/index.html", |r| r.with(index))) //! }).bind("127.0.0.1:8080")
//! .bind("127.0.0.1:8080").unwrap() //! .unwrap()
//! .run(); //! .run();
//! # }); //! # });
//! } //! }
@@ -25,7 +25,7 @@
//! Besides the API documentation (which you are currently looking //! Besides the API documentation (which you are currently looking
//! at!), several other resources are available: //! at!), several other resources are available:
//! //!
//! * [User Guide](https://actix.rs/book/actix-web/) //! * [User Guide](https://actix.rs/docs/)
//! * [Chat on gitter](https://gitter.im/actix/actix) //! * [Chat on gitter](https://gitter.im/actix/actix)
//! * [GitHub repository](https://github.com/actix/actix-web) //! * [GitHub repository](https://github.com/actix/actix-web)
//! * [Cargo package](https://crates.io/crates/actix-web) //! * [Cargo package](https://crates.io/crates/actix-web)
@@ -44,7 +44,7 @@
//! * [HttpRequest](struct.HttpRequest.html) and //! * [HttpRequest](struct.HttpRequest.html) and
//! [HttpResponse](struct.HttpResponse.html): These structs //! [HttpResponse](struct.HttpResponse.html): These structs
//! represent HTTP requests and responses and expose various methods //! represent HTTP requests and responses and expose various methods
//! for inspecting, creating and otherwise utilising them. //! for inspecting, creating and otherwise utilizing them.
//! //!
//! ## Features //! ## Features
//! //!
@@ -59,13 +59,30 @@
//! * SSL support with OpenSSL or `native-tls` //! * SSL support with OpenSSL or `native-tls`
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) //! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
//! * Built on top of [Actix actor framework](https://github.com/actix/actix) //! * Built on top of [Actix actor framework](https://github.com/actix/actix)
//! * Supported Rust version: 1.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( #![cfg_attr(actix_nightly, feature(
specialization, // for impl ErrorResponse for std::error::Error specialization, // for impl ErrorResponse for std::error::Error
extern_prelude,
))] ))]
#![cfg_attr(feature = "cargo-clippy", #![cfg_attr(
allow(decimal_literal_representation, suspicious_arithmetic_impl))] feature = "cargo-clippy",
allow(decimal_literal_representation, suspicious_arithmetic_impl)
)]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
@@ -95,6 +112,7 @@ extern crate mime_guess;
extern crate mio; extern crate mio;
extern crate net2; extern crate net2;
extern crate rand; extern crate rand;
extern crate slab;
extern crate tokio_core; extern crate tokio_core;
extern crate tokio_io; extern crate tokio_io;
extern crate url; extern crate url;
@@ -103,6 +121,7 @@ extern crate serde;
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
extern crate brotli2; extern crate brotli2;
extern crate encoding; extern crate encoding;
#[cfg(feature = "flate2")]
extern crate flate2; extern crate flate2;
extern crate h2 as http2; extern crate h2 as http2;
extern crate num_cpus; extern crate num_cpus;
@@ -135,6 +154,7 @@ mod extractor;
mod handler; mod handler;
mod header; mod header;
mod helpers; mod helpers;
mod httpcodes;
mod httpmessage; mod httpmessage;
mod httprequest; mod httprequest;
mod httpresponse; mod httpresponse;
@@ -146,6 +166,7 @@ mod pipeline;
mod resource; mod resource;
mod route; mod route;
mod router; mod router;
mod scope;
mod uri; mod uri;
mod with; mod with;
@@ -163,18 +184,18 @@ pub use body::{Binary, Body};
pub use context::HttpContext; pub use context::HttpContext;
pub use error::{Error, ResponseError, Result}; pub use error::{Error, ResponseError, Result};
pub use extractor::{Form, Path, Query}; pub use extractor::{Form, Path, Query};
pub use handler::{AsyncResponder, Either, FromRequest, FutureResponse, Responder, State}; pub use handler::{
AsyncResponder, Either, FromRequest, FutureResponse, Responder, State,
};
pub use httpmessage::HttpMessage; pub use httpmessage::HttpMessage;
pub use httprequest::HttpRequest; pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse; pub use httpresponse::HttpResponse;
pub use json::Json; pub use json::Json;
pub use scope::Scope;
#[doc(hidden)] #[doc(hidden)]
pub mod httpcodes; #[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")]
pub use ws::WsWriter;
#[doc(hidden)]
#[allow(deprecated)]
pub use application::Application;
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
pub(crate) const HAS_OPENSSL: bool = true; pub(crate) const HAS_OPENSSL: bool = true;
@@ -200,7 +221,7 @@ pub mod dev {
pub use body::BodyStream; pub use body::BodyStream;
pub use context::Drain; pub use context::Drain;
pub use extractor::{FormConfig, PayloadConfig}; pub use extractor::{FormConfig, PayloadConfig};
pub use handler::{Handler, Reply}; pub use handler::{AsyncResult, Handler};
pub use httpmessage::{MessageBody, UrlEncoded}; pub use httpmessage::{MessageBody, UrlEncoded};
pub use httpresponse::HttpResponseBuilder; pub use httpresponse::HttpResponseBuilder;
pub use info::ConnectionInfo; pub use info::ConnectionInfo;
@@ -209,6 +230,7 @@ pub mod dev {
pub use resource::ResourceHandler; pub use resource::ResourceHandler;
pub use route::Route; pub use route::Route;
pub use router::{Resource, ResourceType, Router}; pub use router::{Resource, ResourceType, Router};
pub use with::ExtractorConfig;
} }
pub mod http { pub mod http {

View File

@@ -19,16 +19,16 @@
//! //!
//! ```rust //! ```rust
//! # extern crate actix_web; //! # extern crate actix_web;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//! use actix_web::middleware::cors::Cors; //! use actix_web::middleware::cors::Cors;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//! //!
//! fn index(mut req: HttpRequest) -> &'static str { //! fn index(mut req: HttpRequest) -> &'static str {
//! "Hello world" //! "Hello world"
//! } //! }
//! //!
//! fn main() { //! fn main() {
//! let app = App::new() //! let app = App::new().configure(|app| {
//! .configure(|app| Cors::for_app(app) // <- Construct CORS middleware builder //! Cors::for_app(app) // <- Construct CORS middleware builder
//! .allowed_origin("https://www.rust-lang.org/") //! .allowed_origin("https://www.rust-lang.org/")
//! .allowed_methods(vec!["GET", "POST"]) //! .allowed_methods(vec!["GET", "POST"])
//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) //! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
@@ -38,7 +38,8 @@
//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); //! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); //! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
//! }) //! })
//! .register()); //! .register()
//! });
//! } //! }
//! ``` //! ```
//! In this example custom *CORS* middleware get registered for "/index.html" //! In this example custom *CORS* middleware get registered for "/index.html"
@@ -64,26 +65,36 @@ use resource::ResourceHandler;
#[derive(Debug, Fail)] #[derive(Debug, Fail)]
pub enum CorsError { pub enum CorsError {
/// The HTTP request header `Origin` is required but was not provided /// 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, MissingOrigin,
/// The HTTP request header `Origin` could not be parsed correctly. /// The HTTP request header `Origin` could not be parsed correctly.
#[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")] #[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")]
BadOrigin, BadOrigin,
/// The request header `Access-Control-Request-Method` is required but is /// The request header `Access-Control-Request-Method` is required but is
/// missing /// 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, MissingRequestMethod,
/// The request header `Access-Control-Request-Method` has an invalid value /// 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, BadRequestMethod,
/// The request header `Access-Control-Request-Headers` has an invalid /// The request header `Access-Control-Request-Headers` has an invalid
/// value /// 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, BadRequestHeaders,
/// The request header `Access-Control-Request-Headers` is required but is /// The request header `Access-Control-Request-Headers` is required but is
/// missing. /// missing.
#[fail(display = "The request header `Access-Control-Request-Headers` is required but is #[fail(
missing")] display = "The request header `Access-Control-Request-Headers` is required but is
missing"
)]
MissingRequestHeaders, MissingRequestHeaders,
/// Origin is not allowed to make this request /// Origin is not allowed to make this request
#[fail(display = "Origin is not allowed to make this request")] #[fail(display = "Origin is not allowed to make this request")]
@@ -222,18 +233,20 @@ impl Cors {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors; /// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().configure(
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder /// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/") /// .allowed_origin("https://www.rust-lang.org/")
/// .resource("/resource", |r| { // register resource /// .resource("/resource", |r| { // register resource
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); /// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// }) /// })
/// .register() // construct CORS and return application instance /// .register()
/// ); /// }, // construct CORS and return application instance
/// );
/// } /// }
/// ``` /// ```
pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> { pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> {
@@ -265,9 +278,7 @@ impl Cors {
/// `ResourceHandler::middleware()` method, but in that case *Cors* /// `ResourceHandler::middleware()` method, but in that case *Cors*
/// middleware wont be able to handle *OPTIONS* requests. /// middleware wont be able to handle *OPTIONS* requests.
pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) { pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) {
resource resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok());
.method(Method::OPTIONS)
.h(|_| HttpResponse::Ok());
resource.middleware(self); resource.middleware(self);
} }
@@ -292,14 +303,13 @@ impl Cors {
} }
fn validate_allowed_method<S>( fn validate_allowed_method<S>(
&self, req: &mut HttpRequest<S> &self, req: &mut HttpRequest<S>,
) -> Result<(), CorsError> { ) -> Result<(), CorsError> {
if let Some(hdr) = req.headers() if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
.get(header::ACCESS_CONTROL_REQUEST_METHOD)
{
if let Ok(meth) = hdr.to_str() { if let Ok(meth) = hdr.to_str() {
if let Ok(method) = Method::try_from(meth) { if let Ok(method) = Method::try_from(meth) {
return self.inner return self
.inner
.methods .methods
.get(&method) .get(&method)
.and_then(|_| Some(())) .and_then(|_| Some(()))
@@ -313,13 +323,13 @@ impl Cors {
} }
fn validate_allowed_headers<S>( fn validate_allowed_headers<S>(
&self, req: &mut HttpRequest<S> &self, req: &mut HttpRequest<S>,
) -> Result<(), CorsError> { ) -> Result<(), CorsError> {
match self.inner.headers { match self.inner.headers {
AllOrSome::All => Ok(()), AllOrSome::All => Ok(()),
AllOrSome::Some(ref allowed_headers) => { AllOrSome::Some(ref allowed_headers) => {
if let Some(hdr) = req.headers() if let Some(hdr) =
.get(header::ACCESS_CONTROL_REQUEST_HEADERS) req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
{ {
if let Ok(headers) = hdr.to_str() { if let Ok(headers) = hdr.to_str() {
let mut hdrs = HashSet::new(); let mut hdrs = HashSet::new();
@@ -361,8 +371,8 @@ impl<S> Middleware<S> for Cors {
.as_str()[1..], .as_str()[1..],
).unwrap(), ).unwrap(),
) )
} else if let Some(hdr) = req.headers() } else if let Some(hdr) =
.get(header::ACCESS_CONTROL_REQUEST_HEADERS) req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
{ {
Some(hdr.clone()) Some(hdr.clone())
} else { } else {
@@ -403,7 +413,8 @@ impl<S> Middleware<S> for Cors {
}) })
.header( .header(
header::ACCESS_CONTROL_ALLOW_METHODS, header::ACCESS_CONTROL_ALLOW_METHODS,
&self.inner &self
.inner
.methods .methods
.iter() .iter()
.fold(String::new(), |s, v| s + "," + v.as_str()) .fold(String::new(), |s, v| s + "," + v.as_str())
@@ -412,14 +423,17 @@ impl<S> Middleware<S> for Cors {
.finish(), .finish(),
)) ))
} else { } else {
self.validate_origin(req)?; // Only check requests with a origin header.
if req.headers().contains_key(header::ORIGIN) {
self.validate_origin(req)?;
}
Ok(Started::Done) Ok(Started::Done)
} }
} }
fn response( fn response(
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse &self, req: &mut HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> { ) -> Result<Response> {
match self.inner.origins { match self.inner.origins {
AllOrSome::All => { AllOrSome::All => {
@@ -483,8 +497,8 @@ impl<S> Middleware<S> for Cors {
/// ```rust /// ```rust
/// # extern crate http; /// # extern crate http;
/// # extern crate actix_web; /// # extern crate actix_web;
/// use http::header;
/// use actix_web::middleware::cors; /// use actix_web::middleware::cors;
/// use http::header;
/// ///
/// # fn main() { /// # fn main() {
/// let cors = cors::Cors::build() /// let cors = cors::Cors::build()
@@ -506,7 +520,7 @@ pub struct CorsBuilder<S = ()> {
} }
fn cors<'a>( 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> { ) -> Option<&'a mut Inner> {
if err.is_some() { if err.is_some() {
return None; return None;
@@ -756,12 +770,13 @@ impl<S: 'static> CorsBuilder<S> {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors; /// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().configure(
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder /// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/") /// .allowed_origin("https://www.rust-lang.org/")
/// .allowed_methods(vec!["GET", "POST"]) /// .allowed_methods(vec!["GET", "POST"])
/// .allowed_header(http::header::CONTENT_TYPE) /// .allowed_header(http::header::CONTENT_TYPE)
@@ -773,8 +788,9 @@ impl<S: 'static> CorsBuilder<S> {
/// r.method(http::Method::HEAD) /// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed()); /// .f(|_| HttpResponse::MethodNotAllowed());
/// }) /// })
/// .register() // construct CORS and return application instance /// .register()
/// ); /// }, // construct CORS and return application instance
/// );
/// } /// }
/// ``` /// ```
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S> pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S>
@@ -815,7 +831,7 @@ impl<S: 'static> CorsBuilder<S> {
if let AllOrSome::Some(ref origins) = cors.origins { if let AllOrSome::Some(ref origins) = cors.origins {
let s = origins let s = origins
.iter() .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()); cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap());
} }
@@ -856,7 +872,8 @@ impl<S: 'static> CorsBuilder<S> {
} }
let cors = self.construct(); let cors = self.construct();
let mut app = self.app let mut app = self
.app
.take() .take()
.expect("CorsBuilder has to be constructed with Cors::for_app(app)"); .expect("CorsBuilder has to be constructed with Cors::for_app(app)");
@@ -992,16 +1009,15 @@ mod tests {
assert!(cors.start(&mut req).unwrap().is_done()); assert!(cors.start(&mut req).unwrap().is_done());
} }
#[test] // #[test]
#[should_panic(expected = "MissingOrigin")] // #[should_panic(expected = "MissingOrigin")]
fn test_validate_missing_origin() { // fn test_validate_missing_origin() {
let cors = Cors::build() // let mut cors = Cors::build()
.allowed_origin("https://www.example.com") // .allowed_origin("https://www.example.com")
.finish(); // .finish();
// let mut req = HttpRequest::default();
let mut req = HttpRequest::default(); // cors.start(&mut req).unwrap();
cors.start(&mut req).unwrap(); // }
}
#[test] #[test]
#[should_panic(expected = "OriginNotAllowed")] #[should_panic(expected = "OriginNotAllowed")]
@@ -1084,9 +1100,8 @@ mod tests {
resp.headers().get(header::VARY).unwrap().as_bytes() resp.headers().get(header::VARY).unwrap().as_bytes()
); );
let resp: HttpResponse = HttpResponse::Ok() let resp: HttpResponse =
.header(header::VARY, "Accept") HttpResponse::Ok().header(header::VARY, "Accept").finish();
.finish();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"Accept, Origin"[..], &b"Accept, Origin"[..],
@@ -1119,11 +1134,21 @@ mod tests {
}) })
}); });
let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let request = srv
.get()
.uri(srv.url("/test"))
.header("ORIGIN", "https://www.example2.com")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv.get() let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv
.get()
.uri(srv.url("/test")) .uri(srv.url("/test"))
.header("ORIGIN", "https://www.example.com") .header("ORIGIN", "https://www.example.com")
.finish() .finish()

View File

@@ -76,7 +76,7 @@ impl DefaultHeaders {
impl<S> Middleware<S> for DefaultHeaders { impl<S> Middleware<S> for DefaultHeaders {
fn response( fn response(
&self, _: &mut HttpRequest<S>, mut resp: HttpResponse &self, _: &mut HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> { ) -> Result<Response> {
for (key, value) in self.headers.iter() { for (key, value) in self.headers.iter() {
if !resp.headers().contains_key(key) { if !resp.headers().contains_key(key) {
@@ -112,9 +112,7 @@ mod tests {
}; };
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let resp = HttpResponse::Ok() let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish();
.header(CONTENT_TYPE, "0002")
.finish();
let resp = match mw.response(&mut req, resp) { let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp, Ok(Response::Done(resp)) => resp,
_ => panic!(), _ => panic!(),

View File

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

View File

@@ -49,8 +49,8 @@
use std::rc::Rc; use std::rc::Rc;
use cookie::{Cookie, CookieJar, Key}; use cookie::{Cookie, CookieJar, Key};
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
use futures::Future; use futures::Future;
use futures::future::{FutureResult, err as FutErr, ok as FutOk};
use time::Duration; use time::Duration;
use error::{Error, Result}; use error::{Error, Result};
@@ -100,7 +100,7 @@ pub trait RequestIdentity {
impl<S> RequestIdentity for HttpRequest<S> { impl<S> RequestIdentity for HttpRequest<S> {
fn identity(&self) -> Option<&str> { fn identity(&self) -> Option<&str> {
if let Some(id) = self.extensions_ro().get::<IdentityBox>() { if let Some(id) = self.extensions().get::<IdentityBox>() {
return id.0.identity(); return id.0.identity();
} }
None None
@@ -179,11 +179,12 @@ impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> { fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
let mut req = req.clone(); let mut req = req.clone();
let fut = self.backend let fut = self
.backend
.from_request(&mut req) .from_request(&mut req)
.then(move |res| match res { .then(move |res| match res {
Ok(id) => { Ok(id) => {
req.extensions().insert(IdentityBox(Box::new(id))); req.extensions_mut().insert(IdentityBox(Box::new(id)));
FutOk(None) FutOk(None)
} }
Err(err) => FutErr(err), Err(err) => FutErr(err),
@@ -192,9 +193,9 @@ impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
} }
fn response( fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse &self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> { ) -> Result<Response> {
if let Some(mut id) = req.extensions().remove::<IdentityBox>() { if let Some(mut id) = req.extensions_mut().remove::<IdentityBox>() {
id.0.write(resp) id.0.write(resp)
} else { } else {
Ok(Response::Done(resp)) Ok(Response::Done(resp))

View File

@@ -1,7 +1,7 @@
//! Request logging middleware //! Request logging middleware
use std::collections::HashSet;
use std::env; use std::env;
use std::fmt; use std::fmt::{self, Display, Formatter};
use std::fmt::{Display, Formatter};
use libc; use libc;
use regex::Regex; use regex::Regex;
@@ -74,6 +74,7 @@ use middleware::{Finished, Middleware, Started};
/// ///
pub struct Logger { pub struct Logger {
format: Format, format: Format,
exclude: HashSet<String>,
} }
impl Logger { impl Logger {
@@ -81,8 +82,15 @@ impl Logger {
pub fn new(format: &str) -> Logger { pub fn new(format: &str) -> Logger {
Logger { Logger {
format: Format::new(format), 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 { impl Default for Logger {
@@ -94,6 +102,7 @@ impl Default for Logger {
fn default() -> Logger { fn default() -> Logger {
Logger { Logger {
format: Format::default(), format: Format::default(),
exclude: HashSet::new(),
} }
} }
} }
@@ -102,21 +111,23 @@ struct StartTime(time::Tm);
impl Logger { impl Logger {
fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) { fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) {
let entry_time = req.extensions().get::<StartTime>().unwrap().0; if let Some(entry_time) = req.extensions().get::<StartTime>() {
let render = |fmt: &mut Formatter| {
let render = |fmt: &mut Formatter| { for unit in &self.format.0 {
for unit in &self.format.0 { unit.render(fmt, req, resp, entry_time.0)?;
unit.render(fmt, req, resp, entry_time)?; }
} Ok(())
Ok(()) };
}; info!("{}", FormatDisplay(&render));
info!("{}", FormatDisplay(&render)); }
} }
} }
impl<S> Middleware<S> for Logger { impl<S> Middleware<S> for Logger {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> { 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) Ok(Started::Done)
} }
@@ -365,9 +376,7 @@ mod tests {
headers, headers,
None, None,
); );
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
.force_close()
.finish();
let entry_time = time::now(); let entry_time = time::now();
let render = |fmt: &mut Formatter| { let render = |fmt: &mut Formatter| {
@@ -388,9 +397,7 @@ mod tests {
HeaderMap::new(), HeaderMap::new(),
None, None,
); );
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
.force_close()
.finish();
let entry_time = time::now(); let entry_time = time::now();
let render = |fmt: &mut Formatter| { let render = |fmt: &mut Formatter| {

View File

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

View File

@@ -35,9 +35,9 @@
//! # extern crate actix; //! # extern crate actix;
//! # extern crate actix_web; //! # extern crate actix_web;
//! use actix_web::{server, App, HttpRequest, Result}; //! use actix_web::{server, App, HttpRequest, Result};
//! use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend}; //! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
//! //!
//! fn index(mut req: HttpRequest) -> Result<&'static str> { //! fn index(req: HttpRequest) -> Result<&'static str> {
//! // access session data //! // access session data
//! if let Some(count) = req.session().get::<i32>("counter")? { //! if let Some(count) = req.session().get::<i32>("counter")? {
//! println!("SESSION value: {}", count); //! println!("SESSION value: {}", count);
@@ -63,21 +63,24 @@
//! let _ = sys.run(); //! let _ = sys.run();
//! } //! }
//! ``` //! ```
use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use cookie::{Cookie, CookieJar, Key}; use cookie::{Cookie, CookieJar, Key};
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
use futures::Future; use futures::Future;
use futures::future::{FutureResult, err as FutErr, ok as FutOk};
use http::header::{self, HeaderValue}; use http::header::{self, HeaderValue};
use serde::{Deserialize, Serialize}; use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json; use serde_json;
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use time::Duration; use time::Duration;
use error::{Error, ResponseError, Result}; use error::{Error, ResponseError, Result};
use handler::FromRequest;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started}; use middleware::{Middleware, Response, Started};
@@ -86,7 +89,7 @@ use middleware::{Middleware, Response, Started};
/// ///
/// ```rust /// ```rust
/// use actix_web::*; /// use actix_web::*;
/// use actix_web::middleware::RequestSession; /// use actix_web::middleware::session::RequestSession;
/// ///
/// fn index(mut req: HttpRequest) -> Result<&'static str> { /// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data /// // access session data
@@ -101,17 +104,15 @@ use middleware::{Middleware, Response, Started};
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
pub trait RequestSession { pub trait RequestSession {
fn session(&mut self) -> Session; fn session(&self) -> Session;
} }
impl<S> RequestSession for HttpRequest<S> { impl<S> RequestSession for HttpRequest<S> {
fn session(&mut self) -> Session { fn session(&self) -> Session {
if let Some(s_impl) = self.extensions().get_mut::<Arc<SessionImplBox>>() { if let Some(s_impl) = self.extensions().get::<Arc<SessionImplCell>>() {
if let Some(s) = Arc::get_mut(s_impl) { return Session(SessionInner::Session(Arc::clone(&s_impl)));
return Session(s.0.as_mut());
}
} }
Session(unsafe { &mut DUMMY }) Session(SessionInner::None)
} }
} }
@@ -123,7 +124,7 @@ impl<S> RequestSession for HttpRequest<S> {
/// ///
/// ```rust /// ```rust
/// use actix_web::*; /// use actix_web::*;
/// use actix_web::middleware::RequestSession; /// use actix_web::middleware::session::RequestSession;
/// ///
/// fn index(mut req: HttpRequest) -> Result<&'static str> { /// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data /// // access session data
@@ -137,41 +138,93 @@ impl<S> RequestSession for HttpRequest<S> {
/// } /// }
/// # fn main() {} /// # 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. /// Get a `value` from the session.
pub fn get<T: Deserialize<'a>>(&'a self, key: &str) -> Result<Option<T>> { pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
if let Some(s) = self.0.get(key) { match self.0 {
Ok(Some(serde_json::from_str(s)?)) SessionInner::Session(ref sess) => {
} else { if let Some(s) = sess.as_ref().0.borrow().get(key) {
Ok(None) Ok(Some(serde_json::from_str(s)?))
} else {
Ok(None)
}
}
SessionInner::None => Ok(None),
} }
} }
/// Set a `value` from the session. /// Set a `value` from the session.
pub fn set<T: Serialize>(&mut self, key: &str, value: T) -> Result<()> { pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<()> {
self.0.set(key, serde_json::to_string(&value)?); match self.0 {
Ok(()) 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. /// Remove value from the session.
pub fn remove(&'a mut self, key: &str) { pub fn remove(&self, key: &str) {
self.0.remove(key) match self.0 {
SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().remove(key),
SessionInner::None => (),
}
} }
/// Clear the session. /// Clear the session.
pub fn clear(&'a mut self) { pub fn clear(&self) {
self.0.clear() match self.0 {
SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().clear(),
SessionInner::None => (),
}
} }
} }
struct SessionImplBox(Box<SessionImpl>); /// Extractor implementation for Session type.
///
/// ```rust
/// # use actix_web::*;
/// use actix_web::middleware::session::Session;
///
/// fn index(session: Session) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = session.get::<i32>("counter")? {
/// session.set("counter", count+1)?;
/// } else {
/// session.set("counter", 1)?;
/// }
///
/// Ok("Welcome!")
/// }
/// # fn main() {}
/// ```
impl<S> FromRequest<S> for Session {
type Config = ();
type Result = Session;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
req.session()
}
}
struct SessionImplCell(RefCell<Box<SessionImpl>>);
#[doc(hidden)] #[doc(hidden)]
unsafe impl Send for SessionImplBox {} unsafe impl Send for SessionImplCell {}
#[doc(hidden)] #[doc(hidden)]
unsafe impl Sync for SessionImplBox {} unsafe impl Sync for SessionImplCell {}
/// Session storage middleware /// Session storage middleware
/// ///
@@ -179,7 +232,7 @@ unsafe impl Sync for SessionImplBox {}
/// # extern crate actix; /// # extern crate actix;
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::App; /// use actix_web::App;
/// use actix_web::middleware::{SessionStorage, CookieSessionBackend}; /// use actix_web::middleware::session::{SessionStorage, CookieSessionBackend};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().middleware( /// let app = App::new().middleware(
@@ -202,24 +255,22 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> { fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
let mut req = req.clone(); let mut req = req.clone();
let fut = self.0 let fut = self.0.from_request(&mut req).then(move |res| match res {
.from_request(&mut req) Ok(sess) => {
.then(move |res| match res { req.extensions_mut()
Ok(sess) => { .insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess)))));
req.extensions() FutOk(None)
.insert(Arc::new(SessionImplBox(Box::new(sess)))); }
FutOk(None) Err(err) => FutErr(err),
} });
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut))) Ok(Started::Future(Box::new(fut)))
} }
fn response( fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse &self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> { ) -> Result<Response> {
if let Some(s_box) = req.extensions().remove::<Arc<SessionImplBox>>() { if let Some(s_box) = req.extensions_mut().remove::<Arc<SessionImplCell>>() {
s_box.0.write(resp) s_box.0.borrow_mut().write(resp)
} else { } else {
Ok(Response::Done(resp)) Ok(Response::Done(resp))
} }
@@ -251,23 +302,6 @@ pub trait SessionBackend<S>: Sized + 'static {
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::ReadFuture; 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 /// Session that uses signed cookies as session storage
pub struct CookieSession { pub struct CookieSession {
changed: bool, changed: bool,
@@ -349,7 +383,7 @@ impl CookieSessionInner {
} }
fn set_cookie( fn set_cookie(
&self, resp: &mut HttpResponse, state: &HashMap<String, String> &self, resp: &mut HttpResponse, state: &HashMap<String, String>,
) -> Result<()> { ) -> Result<()> {
let value = let value =
serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?;
@@ -437,7 +471,7 @@ impl CookieSessionInner {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::middleware::CookieSessionBackend; /// use actix_web::middleware::session::CookieSessionBackend;
/// ///
/// # fn main() { /// # fn main() {
/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32]) /// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32])
@@ -517,3 +551,50 @@ 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());
}
#[test]
fn cookie_session_extractor() {
let mut srv = test::TestServer::with_factory(|| {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
))
.resource("/", |r| {
r.with(|ses: Session| {
let _ = ses.set("counter", 100);
"test"
})
})
});
let request = srv.get().uri(srv.url("/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.cookie("actix-session").is_some());
}
}

View File

@@ -7,8 +7,8 @@ use std::{cmp, fmt};
use bytes::Bytes; use bytes::Bytes;
use futures::task::{current as current_task, Task}; use futures::task::{current as current_task, Task};
use futures::{Async, Poll, Stream}; use futures::{Async, Poll, Stream};
use http::HttpTryFrom;
use http::header::{self, HeaderMap, HeaderName, HeaderValue}; use http::header::{self, HeaderMap, HeaderName, HeaderValue};
use http::HttpTryFrom;
use httparse; use httparse;
use mime; use mime;
@@ -122,11 +122,7 @@ where
if let Some(err) = self.error.take() { if let Some(err) = self.error.take() {
Err(err) Err(err)
} else if self.safety.current() { } else if self.safety.current() {
self.inner self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety)
.as_mut()
.unwrap()
.borrow_mut()
.poll(&self.safety)
} else { } else {
Ok(Async::NotReady) Ok(Async::NotReady)
} }
@@ -168,18 +164,20 @@ where
} }
fn read_boundary( fn read_boundary(
payload: &mut PayloadHelper<S>, boundary: &str payload: &mut PayloadHelper<S>, boundary: &str,
) -> Poll<bool, MultipartError> { ) -> Poll<bool, MultipartError> {
// TODO: need to read epilogue // TODO: need to read epilogue
match payload.readline()? { match payload.readline()? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete), Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => { Async::Ready(Some(chunk)) => {
if chunk.len() == boundary.len() + 4 && &chunk[..2] == b"--" if chunk.len() == boundary.len() + 4
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes() && &chunk[2..boundary.len() + 2] == boundary.as_bytes()
{ {
Ok(Async::Ready(false)) Ok(Async::Ready(false))
} else if chunk.len() == boundary.len() + 6 && &chunk[..2] == b"--" } else if chunk.len() == boundary.len() + 6
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes() && &chunk[2..boundary.len() + 2] == boundary.as_bytes()
&& &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
{ {
@@ -192,7 +190,7 @@ where
} }
fn skip_until_boundary( fn skip_until_boundary(
payload: &mut PayloadHelper<S>, boundary: &str payload: &mut PayloadHelper<S>, boundary: &str,
) -> Poll<bool, MultipartError> { ) -> Poll<bool, MultipartError> {
let mut eof = false; let mut eof = false;
loop { loop {
@@ -230,7 +228,7 @@ where
} }
fn poll( fn poll(
&mut self, safety: &Safety &mut self, safety: &Safety,
) -> Poll<Option<MultipartItem<S>>, MultipartError> { ) -> Poll<Option<MultipartItem<S>>, MultipartError> {
if self.state == InnerState::Eof { if self.state == InnerState::Eof {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
@@ -450,7 +448,7 @@ where
S: Stream<Item = Bytes, Error = PayloadError>, S: Stream<Item = Bytes, Error = PayloadError>,
{ {
fn new( fn new(
payload: PayloadRef<S>, boundary: String, headers: &HeaderMap payload: PayloadRef<S>, boundary: String, headers: &HeaderMap,
) -> Result<InnerField<S>, PayloadError> { ) -> Result<InnerField<S>, PayloadError> {
let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() { if let Ok(s) = len.to_str() {
@@ -477,7 +475,7 @@ where
/// Reads body part content chunk of the specified size. /// Reads body part content chunk of the specified size.
/// The body part must has `Content-Length` header with proper value. /// The body part must has `Content-Length` header with proper value.
fn read_len( fn read_len(
payload: &mut PayloadHelper<S>, size: &mut u64 payload: &mut PayloadHelper<S>, size: &mut u64,
) -> Poll<Option<Bytes>, MultipartError> { ) -> Poll<Option<Bytes>, MultipartError> {
if *size == 0 { if *size == 0 {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
@@ -502,7 +500,7 @@ where
/// Reads content chunk of body part with unknown length. /// Reads content chunk of body part with unknown length.
/// The `Content-Length` header for body part is not necessary. /// The `Content-Length` header for body part is not necessary.
fn read_stream( fn read_stream(
payload: &mut PayloadHelper<S>, boundary: &str payload: &mut PayloadHelper<S>, boundary: &str,
) -> Poll<Option<Bytes>, MultipartError> { ) -> Poll<Option<Bytes>, MultipartError> {
match payload.read_until(b"\r")? { match payload.read_until(b"\r")? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
@@ -513,14 +511,18 @@ where
match payload.read_exact(boundary.len() + 4)? { match payload.read_exact(boundary.len() + 4)? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete), Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => { Async::Ready(Some(mut chunk)) => {
if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" if &chunk[..2] == b"\r\n"
&& &chunk[2..4] == b"--"
&& &chunk[4..] == boundary.as_bytes() && &chunk[4..] == boundary.as_bytes()
{ {
payload.unread_data(chunk); payload.unread_data(chunk);
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
Ok(Async::Ready(Some(chunk))) // \r might be part of data stream
let ch = chunk.split_to(1);
payload.unread_data(chunk);
Ok(Async::Ready(Some(ch)))
} }
} }
} }

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ use application::Inner;
use body::{Body, BodyStream}; use body::{Body, BodyStream};
use context::{ActorHttpContext, Frame}; use context::{ActorHttpContext, Frame};
use error::Error; use error::Error;
use handler::{Reply, ReplyItem}; use handler::{AsyncResult, AsyncResultItem};
use header::ContentEncoding; use header::ContentEncoding;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@@ -28,7 +28,9 @@ pub(crate) enum HandlerType {
pub(crate) trait PipelineHandler<S> { pub(crate) trait PipelineHandler<S> {
fn encoding(&self) -> ContentEncoding; 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>); 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> { struct PipelineInfo<S> {
req: HttpRequest<S>, req: UnsafeCell<HttpRequest<S>>,
count: u16, count: u16,
mws: Rc<Vec<Box<Middleware<S>>>>, mws: Rc<Vec<Box<Middleware<S>>>>,
context: Option<Box<ActorHttpContext>>, context: Option<Box<ActorHttpContext>>,
@@ -79,7 +81,7 @@ struct PipelineInfo<S> {
impl<S> PipelineInfo<S> { impl<S> PipelineInfo<S> {
fn new(req: HttpRequest<S>) -> PipelineInfo<S> { fn new(req: HttpRequest<S>) -> PipelineInfo<S> {
PipelineInfo { PipelineInfo {
req, req: UnsafeCell::new(req),
count: 0, count: 0,
mws: Rc::new(Vec::new()), mws: Rc::new(Vec::new()),
error: None, 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))] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
fn req_mut(&self) -> &mut HttpRequest<S> { fn req_mut(&self) -> &mut HttpRequest<S> {
#[allow(mutable_transmutes)] #[allow(mutable_transmutes)]
unsafe { 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, handler: Rc<UnsafeCell<H>>, htype: HandlerType,
) -> Pipeline<S, H> { ) -> Pipeline<S, H> {
let mut info = PipelineInfo { let mut info = PipelineInfo {
req,
mws, mws,
req: UnsafeCell::new(req),
count: 0, count: 0,
error: None, error: None,
context: None, context: None,
@@ -140,6 +148,7 @@ impl Pipeline<(), Inner<()>> {
} }
impl<S: 'static, H> Pipeline<S, H> { impl<S: 'static, H> Pipeline<S, H> {
#[inline]
fn is_done(&self) -> bool { fn is_done(&self) -> bool {
match self.1 { match self.1 {
PipelineState::None PipelineState::None
@@ -159,7 +168,7 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
} }
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> { 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 { loop {
if self.1.is_response() { if self.1.is_response() {
@@ -184,7 +193,9 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
match self.1 { match self.1 {
PipelineState::None => return Ok(Async::Ready(true)), PipelineState::None => return Ok(Async::Ready(true)),
PipelineState::Error => { PipelineState::Error => {
return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into()) return Err(
io::Error::new(io::ErrorKind::Other, "Internal error").into()
)
} }
_ => (), _ => (),
} }
@@ -197,7 +208,7 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
} }
fn poll(&mut self) -> Poll<(), Error> { 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 { loop {
match self.1 { match self.1 {
@@ -228,39 +239,30 @@ struct StartMiddlewares<S, H> {
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> { impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
fn init( 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> { ) -> PipelineState<S, H> {
// execute middlewares, we need this stage because middlewares could be // execute middlewares, we need this stage because middlewares could be
// non-async and we can move to next state immediately // non-async and we can move to next state immediately
let len = info.mws.len() as u16; let len = info.mws.len() as u16;
loop { loop {
if info.count == len { 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); return WaitingResponse::init(info, reply);
} else { } 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::Done) => info.count += 1,
Ok(Started::Response(resp)) => { Ok(Started::Response(resp)) => {
return RunMiddlewares::init(info, resp) return RunMiddlewares::init(info, resp)
} }
Ok(Started::Future(mut fut)) => match fut.poll() { Ok(Started::Future(fut)) => {
Ok(Async::NotReady) => { return PipelineState::Starting(StartMiddlewares {
return PipelineState::Starting(StartMiddlewares { hnd,
hnd, htype,
htype, fut: Some(fut),
fut: Some(fut), _s: PhantomData,
_s: PhantomData, })
}) }
} Err(err) => return RunMiddlewares::init(info, err.into()),
Ok(Async::Ready(resp)) => {
if let Some(resp) = resp {
return RunMiddlewares::init(info, resp);
}
info.count += 1;
}
Err(err) => return ProcessResponse::init(err.into()),
},
Err(err) => return ProcessResponse::init(err.into()),
} }
} }
} }
@@ -276,12 +278,12 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
if let Some(resp) = resp { if let Some(resp) = resp {
return Some(RunMiddlewares::init(info, resp)); return Some(RunMiddlewares::init(info, resp));
} }
if info.count == len { loop {
let reply = unsafe { &mut *self.hnd.get() } if info.count == len {
.handle(info.req.clone(), self.htype); let reply = unsafe { &mut *self.hnd.get() }
return Some(WaitingResponse::init(info, reply)); .handle(info.req().clone(), self.htype);
} else { return Some(WaitingResponse::init(info, reply));
loop { } else {
match info.mws[info.count as usize].start(info.req_mut()) { match info.mws[info.count as usize].start(info.req_mut()) {
Ok(Started::Done) => info.count += 1, Ok(Started::Done) => info.count += 1,
Ok(Started::Response(resp)) => { Ok(Started::Response(resp)) => {
@@ -292,13 +294,13 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
continue 'outer; continue 'outer;
} }
Err(err) => { Err(err) => {
return Some(ProcessResponse::init(err.into())) return Some(RunMiddlewares::init(info, err.into()))
} }
} }
} }
} }
} }
Err(err) => return Some(ProcessResponse::init(err.into())), Err(err) => return Some(RunMiddlewares::init(info, err.into())),
} }
} }
} }
@@ -313,10 +315,13 @@ struct WaitingResponse<S, H> {
impl<S: 'static, H> WaitingResponse<S, H> { impl<S: 'static, H> WaitingResponse<S, H> {
#[inline] #[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() { match reply.into() {
ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
ReplyItem::Future(fut) => PipelineState::Handler(WaitingResponse { AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse {
fut, fut,
_s: PhantomData, _s: PhantomData,
_h: PhantomData, _h: PhantomData,
@@ -342,6 +347,7 @@ struct RunMiddlewares<S, H> {
} }
impl<S: 'static, H> RunMiddlewares<S, H> { impl<S: 'static, H> RunMiddlewares<S, H> {
#[inline]
fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H> { fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H> {
if info.count == 0 { if info.count == 0 {
return ProcessResponse::init(resp); return ProcessResponse::init(resp);
@@ -462,7 +468,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
} }
fn poll_io( 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>> { ) -> Result<PipelineState<S, H>, PipelineState<S, H>> {
loop { loop {
if self.drain.is_none() && self.running != RunningState::Paused { if self.drain.is_none() && self.running != RunningState::Paused {
@@ -482,8 +488,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Err(err) => { Err(err) => {
info.error = Some(err.into()); info.error = Some(err.into());
return Ok(FinishingMiddlewares::init( return Ok(FinishingMiddlewares::init(
info, info, self.resp,
self.resp,
)); ));
} }
}; };
@@ -491,8 +496,8 @@ impl<S: 'static, H> ProcessResponse<S, H> {
if let Some(err) = self.resp.error() { if let Some(err) = self.resp.error() {
if self.resp.status().is_server_error() { if self.resp.status().is_server_error() {
error!( error!(
"Error occured during request handling: {}", "Error occured during request handling, status: {} {}",
err self.resp.status(), err
); );
} else { } else {
warn!( warn!(
@@ -525,8 +530,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
if let Err(err) = io.write_eof() { if let Err(err) = io.write_eof() {
info.error = Some(err.into()); info.error = Some(err.into());
return Ok(FinishingMiddlewares::init( return Ok(FinishingMiddlewares::init(
info, info, self.resp,
self.resp,
)); ));
} }
break; break;
@@ -537,8 +541,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Err(err) => { Err(err) => {
info.error = Some(err.into()); info.error = Some(err.into());
return Ok(FinishingMiddlewares::init( return Ok(FinishingMiddlewares::init(
info, info, self.resp,
self.resp,
)); ));
} }
Ok(result) => result, Ok(result) => result,
@@ -572,8 +575,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
info.error = Some(err.into()); info.error = Some(err.into());
return Ok( return Ok(
FinishingMiddlewares::init( FinishingMiddlewares::init(
info, info, self.resp,
self.resp,
), ),
); );
} }
@@ -585,8 +587,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
info.error = Some(err.into()); info.error = Some(err.into());
return Ok( return Ok(
FinishingMiddlewares::init( FinishingMiddlewares::init(
info, info, self.resp,
self.resp,
), ),
); );
} }
@@ -611,8 +612,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Err(err) => { Err(err) => {
info.error = Some(err); info.error = Some(err);
return Ok(FinishingMiddlewares::init( return Ok(FinishingMiddlewares::init(
info, info, self.resp,
self.resp,
)); ));
} }
} }
@@ -680,6 +680,7 @@ struct FinishingMiddlewares<S, H> {
} }
impl<S: 'static, H> FinishingMiddlewares<S, H> { impl<S: 'static, H> FinishingMiddlewares<S, H> {
#[inline]
fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> { fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> {
if info.count == 0 { if info.count == 0 {
Completed::init(info) Completed::init(info)
@@ -717,8 +718,11 @@ impl<S: 'static, H> FinishingMiddlewares<S, H> {
return None; return None;
} }
self.fut = 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) { match info.mws[info.count as usize].finish(info.req_mut(), &self.resp) {
Finished::Done => { Finished::Done => {
if info.count == 0 { 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 /// Return predicate that matches if request contains specified header and
/// value. /// value.
pub fn Header<S: 'static>( pub fn Header<S: 'static>(
name: &'static str, value: &'static str name: &'static str, value: &'static str,
) -> HeaderPredicate<S> { ) -> HeaderPredicate<S> {
HeaderPredicate( HeaderPredicate(
header::HeaderName::try_from(name).unwrap(), header::HeaderName::try_from(name).unwrap(),
@@ -181,11 +181,7 @@ pub fn Header<S: 'static>(
} }
#[doc(hidden)] #[doc(hidden)]
pub struct HeaderPredicate<S>( pub struct HeaderPredicate<S>(header::HeaderName, header::HeaderValue, PhantomData<S>);
header::HeaderName,
header::HeaderValue,
PhantomData<S>,
);
impl<S: 'static> Predicate<S> for HeaderPredicate<S> { impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool { fn check(&self, req: &mut HttpRequest<S>) -> bool {
@@ -196,6 +192,45 @@ impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
} }
} }
/// Return predicate that matches if request contains specified Host name.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .filter(pred::Host("www.rust-lang.org"))
/// .f(|_| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn Host<S: 'static, H: AsRef<str>>(host: H) -> HostPredicate<S> {
HostPredicate(host.as_ref().to_string(), None, PhantomData)
}
#[doc(hidden)]
pub struct HostPredicate<S>(String, Option<String>, PhantomData<S>);
impl<S> HostPredicate<S> {
/// Set reuest scheme to match
pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
self.1 = Some(scheme.as_ref().to_string())
}
}
impl<S: 'static> Predicate<S> for HostPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
let info = req.connection_info();
if let Some(ref scheme) = self.1 {
self.0 == info.host() && scheme == info.scheme()
} else {
self.0 == info.host()
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -228,6 +263,28 @@ mod tests {
assert!(!pred.check(&mut req)); assert!(!pred.check(&mut req));
} }
#[test]
fn test_host() {
let mut headers = HeaderMap::new();
headers.insert(
header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"),
);
let mut req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let pred = Host("www.rust-lang.org");
assert!(pred.check(&mut req));
let pred = Host("localhost");
assert!(!pred.check(&mut req));
}
#[test] #[test]
fn test_methods() { fn test_methods() {
let mut req = HttpRequest::new( let mut req = HttpRequest::new(

View File

@@ -1,10 +1,12 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use futures::Future;
use http::{Method, StatusCode}; use http::{Method, StatusCode};
use smallvec::SmallVec; use smallvec::SmallVec;
use handler::{FromRequest, Handler, Reply, Responder}; use error::Error;
use handler::{AsyncResult, FromRequest, Handler, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::Middleware; use middleware::Middleware;
@@ -132,10 +134,7 @@ impl<S: 'static> ResourceHandler<S> {
/// ``` /// ```
pub fn method(&mut self, method: Method) -> &mut Route<S> { pub fn method(&mut self, method: Method) -> &mut Route<S> {
self.routes.push(Route::default()); self.routes.push(Route::default());
self.routes self.routes.last_mut().unwrap().filter(pred::Method(method))
.last_mut()
.unwrap()
.filter(pred::Method(method))
} }
/// Register a new route and add handler object. /// Register a new route and add handler object.
@@ -183,10 +182,32 @@ impl<S: 'static> ResourceHandler<S> {
self.routes.last_mut().unwrap().with(handler); 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 /// Register a resource middleware
/// ///
/// This is similar to `App's` middlewares, but /// This is similar to `App's` middlewares, but
/// middlewares get invoked on resource level. /// 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) { pub fn middleware<M: Middleware<S>>(&mut self, mw: M) {
Rc::get_mut(&mut self.middlewares) Rc::get_mut(&mut self.middlewares)
.unwrap() .unwrap()
@@ -194,8 +215,8 @@ impl<S: 'static> ResourceHandler<S> {
} }
pub(crate) fn handle( pub(crate) fn handle(
&mut self, mut req: HttpRequest<S>, default: Option<&mut ResourceHandler<S>> &mut self, mut req: HttpRequest<S>, default: Option<&mut ResourceHandler<S>>,
) -> Reply { ) -> AsyncResult<HttpResponse> {
for route in &mut self.routes { for route in &mut self.routes {
if route.check(&mut req) { if route.check(&mut req) {
return if self.middlewares.is_empty() { return if self.middlewares.is_empty() {
@@ -208,7 +229,7 @@ impl<S: 'static> ResourceHandler<S> {
if let Some(resource) = default { if let Some(resource) = default {
resource.handle(req, None) resource.handle(req, None)
} else { } else {
Reply::response(HttpResponse::new(StatusCode::NOT_FOUND)) AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND))
} }
} }
} }

View File

@@ -1,18 +1,23 @@
use futures::{Async, Future, Poll}; use std::cell::UnsafeCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem;
use std::rc::Rc; use std::rc::Rc;
use futures::{Async, Future, Poll};
use error::Error; use error::Error;
use handler::{AsyncHandler, FromRequest, Handler, Reply, ReplyItem, Responder, use handler::{
RouteHandler, WrapHandler}; AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder,
RouteHandler, WrapHandler,
};
use http::StatusCode; use http::StatusCode;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::{Middleware, Response as MiddlewareResponse, use middleware::{
Started as MiddlewareStarted}; Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse,
Started as MiddlewareStarted,
};
use pred::Predicate; use pred::Predicate;
use with::{ExtractorConfig, With, With2, With3}; use with::{ExtractorConfig, With, With2, With3, WithAsync};
/// Resource route definition /// Resource route definition
/// ///
@@ -44,15 +49,15 @@ impl<S: 'static> Route<S> {
} }
#[inline] #[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) self.handler.handle(req)
} }
#[inline] #[inline]
pub(crate) fn compose( pub(crate) fn compose(
&mut self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>> &mut self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
) -> Reply { ) -> AsyncResult<HttpResponse> {
Reply::async(Compose::new(req, mws, self.handler.clone())) AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
} }
/// Add match predicate to route. /// Add match predicate to route.
@@ -61,13 +66,12 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::*; /// # use actix_web::*;
/// # fn main() { /// # fn main() {
/// App::new() /// App::new().resource("/path", |r| {
/// .resource("/path", |r| /// r.route()
/// r.route() /// .filter(pred::Get())
/// .filter(pred::Get()) /// .filter(pred::Header("content-type", "text/plain"))
/// .filter(pred::Header("content-type", "text/plain")) /// .f(|req| HttpResponse::Ok())
/// .f(|req| HttpResponse::Ok()) /// })
/// )
/// # .finish(); /// # .finish();
/// # } /// # }
/// ``` /// ```
@@ -103,14 +107,14 @@ impl<S: 'static> Route<S> {
self.handler = InnerHandler::async(handler); self.handler = InnerHandler::async(handler);
} }
/// Set handler function, use request extractor for paramters. /// Set handler function, use request extractor for parameters.
/// ///
/// ```rust /// ```rust
/// # extern crate bytes; /// # extern crate bytes;
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Result, http}; /// use actix_web::{http, App, Path, Result};
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// struct Info {
@@ -124,8 +128,40 @@ impl<S: 'static> Route<S> {
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters /// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
///
/// It is possible to use tuples for specifing multiple extractors for one
/// handler function.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// # use std::collections::HashMap;
/// use actix_web::{http, App, Json, Path, Query, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(
/// info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>),
/// ) -> Result<String> {
/// Ok(format!("Welcome {}!", info.0.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// } /// }
/// ``` /// ```
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T> pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
@@ -139,14 +175,57 @@ impl<S: 'static> Route<S> {
cfg 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 /// ```rust
/// # extern crate bytes; /// # extern crate bytes;
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Query, Path, Result, http}; /// use actix_web::{http, App, Error, Path};
/// use futures::Future;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Path<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
/// unimplemented!()
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with_async(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F) -> ExtractorConfig<S, T>
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
let cfg = ExtractorConfig::default();
self.h(WithAsync::new(handler, Clone::clone(&cfg)));
cfg
}
#[doc(hidden)]
/// Set handler function, use request extractor for both parameters.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Path, Query, Result};
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct PParam { /// struct PParam {
@@ -165,12 +244,13 @@ impl<S: 'static> Route<S> {
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters /// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor /// |r| r.method(http::Method::GET).with2(index),
/// ); // <- use `with` extractor
/// } /// }
/// ``` /// ```
pub fn with2<T1, T2, F, R>( pub fn with2<T1, T2, F, R>(
&mut self, handler: F &mut self, handler: F,
) -> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>) ) -> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>)
where where
F: Fn(T1, T2) -> R + 'static, F: Fn(T1, T2) -> R + 'static,
@@ -188,9 +268,10 @@ impl<S: 'static> Route<S> {
(cfg1, cfg2) (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>( pub fn with3<T1, T2, T3, F, R>(
&mut self, handler: F &mut self, handler: F,
) -> ( ) -> (
ExtractorConfig<S, T1>, ExtractorConfig<S, T1>,
ExtractorConfig<S, T2>, ExtractorConfig<S, T2>,
@@ -218,12 +299,12 @@ impl<S: 'static> Route<S> {
/// `RouteHandler` wrapper. This struct is required because it needs to be /// `RouteHandler` wrapper. This struct is required because it needs to be
/// shared for resource level middlewares. /// 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> { impl<S: 'static> InnerHandler<S> {
#[inline] #[inline]
fn new<H: Handler<S>>(h: H) -> Self { fn new<H: Handler<S>>(h: H) -> Self {
InnerHandler(Rc::new(Box::new(WrapHandler::new(h)))) InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(h)))))
} }
#[inline] #[inline]
@@ -234,16 +315,13 @@ impl<S: 'static> InnerHandler<S> {
R: Responder + 'static, R: Responder + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
{ {
InnerHandler(Rc::new(Box::new(AsyncHandler::new(h)))) InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(h)))))
} }
#[inline] #[inline]
pub fn handle(&self, req: HttpRequest<S>) -> Reply { pub fn handle(&self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
// reason: handler is unique per thread, // reason: handler is unique per thread, handler get called from async code only
// handler get called from async code only let h = unsafe { &mut *self.0.as_ref().get() };
#[allow(mutable_transmutes)]
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
let h: &mut Box<RouteHandler<S>> = unsafe { mem::transmute(self.0.as_ref()) };
h.handle(req) h.handle(req)
} }
} }
@@ -272,7 +350,8 @@ enum ComposeState<S: 'static> {
Starting(StartMiddlewares<S>), Starting(StartMiddlewares<S>),
Handler(WaitingResponse<S>), Handler(WaitingResponse<S>),
RunMiddlewares(RunMiddlewares<S>), RunMiddlewares(RunMiddlewares<S>),
Response(Response<S>), Finishing(FinishingMiddlewares<S>),
Completed(Response<S>),
} }
impl<S: 'static> ComposeState<S> { impl<S: 'static> ComposeState<S> {
@@ -281,14 +360,15 @@ impl<S: 'static> ComposeState<S> {
ComposeState::Starting(ref mut state) => state.poll(info), ComposeState::Starting(ref mut state) => state.poll(info),
ComposeState::Handler(ref mut state) => state.poll(info), ComposeState::Handler(ref mut state) => state.poll(info),
ComposeState::RunMiddlewares(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> { impl<S: 'static> Compose<S> {
fn new( 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 { ) -> Self {
let mut info = ComposeInfo { let mut info = ComposeInfo {
count: 0, count: 0,
@@ -308,7 +388,7 @@ impl<S> Future for Compose<S> {
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop { 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(); let resp = resp.resp.take().unwrap();
return Ok(Async::Ready(resp)); return Ok(Async::Ready(resp));
} }
@@ -342,22 +422,13 @@ impl<S: 'static> StartMiddlewares<S> {
Ok(MiddlewareStarted::Response(resp)) => { Ok(MiddlewareStarted::Response(resp)) => {
return RunMiddlewares::init(info, resp) return RunMiddlewares::init(info, resp)
} }
Ok(MiddlewareStarted::Future(mut fut)) => match fut.poll() { Ok(MiddlewareStarted::Future(fut)) => {
Ok(Async::NotReady) => { return ComposeState::Starting(StartMiddlewares {
return ComposeState::Starting(StartMiddlewares { fut: Some(fut),
fut: Some(fut), _s: PhantomData,
_s: PhantomData, })
}) }
} Err(err) => return RunMiddlewares::init(info, err.into()),
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()),
} }
} }
} }
@@ -373,11 +444,11 @@ impl<S: 'static> StartMiddlewares<S> {
if let Some(resp) = resp { if let Some(resp) = resp {
return Some(RunMiddlewares::init(info, resp)); return Some(RunMiddlewares::init(info, resp));
} }
if info.count == len { loop {
let reply = info.handler.handle(info.req.clone()); if info.count == len {
return Some(WaitingResponse::init(info, reply)); let reply = info.handler.handle(info.req.clone());
} else { return Some(WaitingResponse::init(info, reply));
loop { } else {
match info.mws[info.count].start(&mut info.req) { match info.mws[info.count].start(&mut info.req) {
Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => { Ok(MiddlewareStarted::Response(resp)) => {
@@ -387,12 +458,14 @@ impl<S: 'static> StartMiddlewares<S> {
self.fut = Some(fut); self.fut = Some(fut);
continue 'outer; continue 'outer;
} }
Err(err) => return Some(Response::init(err.into())), Err(err) => {
return Some(RunMiddlewares::init(info, err.into()))
}
} }
} }
} }
} }
Err(err) => return Some(Response::init(err.into())), Err(err) => return Some(RunMiddlewares::init(info, err.into())),
} }
} }
} }
@@ -406,10 +479,13 @@ struct WaitingResponse<S> {
impl<S: 'static> WaitingResponse<S> { impl<S: 'static> WaitingResponse<S> {
#[inline] #[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() { match reply.into() {
ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
ReplyItem::Future(fut) => ComposeState::Handler(WaitingResponse { AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse {
fut, fut,
_s: PhantomData, _s: PhantomData,
}), }),
@@ -441,12 +517,12 @@ impl<S: 'static> RunMiddlewares<S> {
resp = match info.mws[curr].response(&mut info.req, resp) { resp = match info.mws[curr].response(&mut info.req, resp) {
Err(err) => { Err(err) => {
info.count = curr + 1; info.count = curr + 1;
return Response::init(err.into()); return FinishingMiddlewares::init(info, err.into());
} }
Ok(MiddlewareResponse::Done(r)) => { Ok(MiddlewareResponse::Done(r)) => {
curr += 1; curr += 1;
if curr == len { if curr == len {
return Response::init(r); return FinishingMiddlewares::init(info, r);
} else { } else {
r r
} }
@@ -473,15 +549,17 @@ impl<S: 'static> RunMiddlewares<S> {
self.curr += 1; self.curr += 1;
resp resp
} }
Err(err) => return Some(Response::init(err.into())), Err(err) => return Some(FinishingMiddlewares::init(info, err.into())),
}; };
loop { loop {
if self.curr == len { if self.curr == len {
return Some(Response::init(resp)); return Some(FinishingMiddlewares::init(info, resp));
} else { } else {
match info.mws[self.curr].response(&mut info.req, resp) { 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)) => { Ok(MiddlewareResponse::Done(r)) => {
self.curr += 1; self.curr += 1;
resp = r resp = r
@@ -497,6 +575,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> { struct Response<S> {
resp: Option<HttpResponse>, resp: Option<HttpResponse>,
_s: PhantomData<S>, _s: PhantomData<S>,
@@ -504,7 +647,7 @@ struct Response<S> {
impl<S: 'static> Response<S> { impl<S: 'static> Response<S> {
fn init(resp: HttpResponse) -> ComposeState<S> { fn init(resp: HttpResponse) -> ComposeState<S> {
ComposeState::Response(Response { ComposeState::Completed(Response {
resp: Some(resp), resp: Some(resp),
_s: PhantomData, _s: PhantomData,
}) })

View File

@@ -1,6 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::mem;
use std::rc::Rc; use std::rc::Rc;
use regex::{escape, Regex}; use regex::{escape, Regex};
@@ -79,12 +78,13 @@ impl Router {
if self.0.prefix_len > req.path().len() { if self.0.prefix_len > req.path().len() {
return None; 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 route_path = if path.is_empty() { "/" } else { path };
for (idx, pattern) in self.0.patterns.iter().enumerate() { for (idx, pattern) in self.0.patterns.iter().enumerate() {
if pattern.match_with_params(route_path, req.match_info_mut()) { if pattern.match_with_params(route_path, req.match_info_mut()) {
req.set_resource(idx); req.set_resource(idx);
req.set_prefix_len(self.0.prefix_len as u16);
return Some(idx); return Some(idx);
} }
} }
@@ -113,7 +113,7 @@ impl Router {
/// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method.
/// url_for) for detailed information. /// url_for) for detailed information.
pub fn resource_path<U, I>( pub fn resource_path<U, I>(
&self, name: &str, elements: U &self, name: &str, elements: U,
) -> Result<String, UrlGenerationError> ) -> Result<String, UrlGenerationError>
where where
U: IntoIterator<Item = I>, U: IntoIterator<Item = I>,
@@ -142,7 +142,8 @@ enum PatternElement {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum PatternType { enum PatternType {
Static(String), Static(String),
Dynamic(Regex, Vec<String>), Prefix(String),
Dynamic(Regex, Vec<String>, usize),
} }
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
@@ -150,7 +151,7 @@ enum PatternType {
pub enum ResourceType { pub enum ResourceType {
/// Normal resource /// Normal resource
Normal, Normal,
/// Resource for applicaiton default handler /// Resource for application default handler
Default, Default,
/// External resource /// External resource
External, External,
@@ -158,7 +159,7 @@ pub enum ResourceType {
Unset, Unset,
} }
/// Reslource type describes an entry in resources table /// Resource type describes an entry in resources table
#[derive(Clone)] #[derive(Clone)]
pub struct Resource { pub struct Resource {
tp: PatternType, tp: PatternType,
@@ -173,14 +174,23 @@ impl Resource {
/// ///
/// Panics if path pattern is wrong. /// Panics if path pattern is wrong.
pub fn new(name: &str, path: &str) -> Self { 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 /// Construct external resource
/// ///
/// Panics if path pattern is wrong. /// Panics if path pattern is wrong.
pub fn external(name: &str, path: &str) -> Self { 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.rtp = ResourceType::External;
resource resource
} }
@@ -197,18 +207,22 @@ impl Resource {
} }
/// Parse path pattern and create new `Resource` instance with custom prefix /// Parse path pattern and create new `Resource` instance with custom prefix
pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self { pub fn with_prefix(name: &str, path: &str, prefix: &str, for_prefix: bool) -> Self {
let (pattern, elements, is_dynamic) = Resource::parse(path, prefix); let (pattern, elements, is_dynamic, len) =
Resource::parse(path, prefix, for_prefix);
let tp = if is_dynamic { let tp = if is_dynamic {
let re = match Regex::new(&pattern) { let re = match Regex::new(&pattern) {
Ok(re) => re, Ok(re) => re,
Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err), Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err),
}; };
let names = re.capture_names() let names = re
.capture_names()
.filter_map(|name| name.map(|name| name.to_owned())) .filter_map(|name| name.map(|name| name.to_owned()))
.collect(); .collect();
PatternType::Dynamic(re, names) PatternType::Dynamic(re, names, len)
} else if for_prefix {
PatternType::Prefix(pattern.clone())
} else { } else {
PatternType::Static(pattern.clone()) PatternType::Static(pattern.clone())
}; };
@@ -240,16 +254,17 @@ impl Resource {
pub fn is_match(&self, path: &str) -> bool { pub fn is_match(&self, path: &str) -> bool {
match self.tp { match self.tp {
PatternType::Static(ref s) => s == path, 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>( 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 { ) -> bool {
match self.tp { match self.tp {
PatternType::Static(ref s) => s == path, 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) { if let Some(captures) = re.captures(path) {
let mut idx = 0; let mut idx = 0;
for capture in captures.iter() { for capture in captures.iter() {
@@ -265,39 +280,104 @@ impl Resource {
false 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 == s {
Some(s.len())
} else if path.starts_with(s)
&& (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/'))
{
if s.ends_with('/') {
Some(s.len() - 1)
} else {
Some(s.len())
}
} else {
None
},
}
}
/// Build resource path.
pub fn resource_path<U, I>( pub fn resource_path<U, I>(
&self, router: &Router, elements: U &self, router: &Router, elements: U,
) -> Result<String, UrlGenerationError> ) -> Result<String, UrlGenerationError>
where where
U: IntoIterator<Item = I>, U: IntoIterator<Item = I>,
I: AsRef<str>, I: AsRef<str>,
{ {
let mut iter = elements.into_iter(); let mut path = match self.tp {
let mut path = if self.rtp != ResourceType::External { PatternType::Prefix(ref p) => p.to_owned(),
format!("{}/", router.prefix()) PatternType::Static(ref p) => p.to_owned(),
} else { PatternType::Dynamic(..) => {
String::new() let mut path = String::new();
}; let mut iter = elements.into_iter();
for el in &self.elements { for el in &self.elements {
match *el { match *el {
PatternElement::Str(ref s) => path.push_str(s), PatternElement::Str(ref s) => path.push_str(s),
PatternElement::Var(_) => { PatternElement::Var(_) => {
if let Some(val) = iter.next() { if let Some(val) = iter.next() {
path.push_str(val.as_ref()) path.push_str(val.as_ref())
} else { } else {
return Err(UrlGenerationError::NotEnoughElements); return Err(UrlGenerationError::NotEnoughElements);
}
}
} }
} }
path
}
};
if self.rtp != ResourceType::External {
let prefix = router.prefix();
if prefix.ends_with('/') {
if path.starts_with('/') {
path.insert_str(0, &prefix[..prefix.len() - 1]);
} else {
path.insert_str(0, prefix);
}
} else {
if !path.starts_with('/') {
path.insert(0, '/');
}
path.insert_str(0, prefix);
} }
} }
Ok(path) Ok(path)
} }
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 = "[^/]+"; const DEFAULT_PATTERN: &str = "[^/]+";
let mut re1 = String::from("^") + prefix; let mut re1 = String::from("^") + prefix;
@@ -309,6 +389,7 @@ impl Resource {
let mut param_pattern = String::from(DEFAULT_PATTERN); let mut param_pattern = String::from(DEFAULT_PATTERN);
let mut is_dynamic = false; let mut is_dynamic = false;
let mut elems = Vec::new(); let mut elems = Vec::new();
let mut len = 0;
for (index, ch) in pattern.chars().enumerate() { for (index, ch) in pattern.chars().enumerate() {
// All routes must have a leading slash so its optional to have one // All routes must have a leading slash so its optional to have one
@@ -325,6 +406,7 @@ impl Resource {
param_name.clear(); param_name.clear();
param_pattern = String::from(DEFAULT_PATTERN); param_pattern = String::from(DEFAULT_PATTERN);
len = 0;
in_param_pattern = false; in_param_pattern = false;
in_param = false; in_param = false;
} else if ch == ':' { } else if ch == ':' {
@@ -348,16 +430,23 @@ impl Resource {
re1.push_str(escape(&ch.to_string()).as_str()); re1.push_str(escape(&ch.to_string()).as_str());
re2.push(ch); re2.push(ch);
el.push(ch); el.push(ch);
len += 1;
} }
} }
if !el.is_empty() {
elems.push(PatternElement::Str(el.clone()));
}
let re = if is_dynamic { let re = if is_dynamic {
re1.push('$'); if !for_prefix {
re1.push('$');
}
re1 re1
} else { } else {
re2 re2
}; };
(re, elems, is_dynamic) (re, elems, is_dynamic, len)
} }
} }
@@ -381,12 +470,9 @@ mod tests {
use test::TestRequest; use test::TestRequest;
#[test] #[test]
fn test_recognizer() { fn test_recognizer10() {
let routes = vec![ let routes = vec![
( (Resource::new("", "/name"), Some(ResourceHandler::default())),
Resource::new("", "/name"),
Some(ResourceHandler::default()),
),
( (
Resource::new("", "/name/{val}"), Resource::new("", "/name/{val}"),
Some(ResourceHandler::default()), Some(ResourceHandler::default()),
@@ -407,6 +493,10 @@ mod tests {
Resource::new("", "/v/{tail:.*}"), Resource::new("", "/v/{tail:.*}"),
Some(ResourceHandler::default()), Some(ResourceHandler::default()),
), ),
(
Resource::new("", "/test2/{test}.html"),
Some(ResourceHandler::default()),
),
( (
Resource::new("", "{test}/index.html"), Resource::new("", "{test}/index.html"),
Some(ResourceHandler::default()), Some(ResourceHandler::default()),
@@ -444,8 +534,12 @@ mod tests {
"blah-blah/index.html" "blah-blah/index.html"
); );
let mut req = TestRequest::with_uri("/bbb/index.html").finish(); let mut req = TestRequest::with_uri("/test2/index.html").finish();
assert_eq!(rec.recognize(&mut req), Some(6)); assert_eq!(rec.recognize(&mut req), Some(6));
assert_eq!(req.match_info().get("test").unwrap(), "index");
let mut req = TestRequest::with_uri("/bbb/index.html").finish();
assert_eq!(rec.recognize(&mut req), Some(7));
assert_eq!(req.match_info().get("test").unwrap(), "bbb"); assert_eq!(req.match_info().get("test").unwrap(), "bbb");
} }
@@ -473,10 +567,7 @@ mod tests {
#[test] #[test]
fn test_recognizer_with_prefix() { fn test_recognizer_with_prefix() {
let routes = vec![ let routes = vec![
( (Resource::new("", "/name"), Some(ResourceHandler::default())),
Resource::new("", "/name"),
Some(ResourceHandler::default()),
),
( (
Resource::new("", "/name/{val}"), Resource::new("", "/name/{val}"),
Some(ResourceHandler::default()), Some(ResourceHandler::default()),
@@ -497,10 +588,7 @@ mod tests {
// same patterns // same patterns
let routes = vec![ let routes = vec![
( (Resource::new("", "/name"), Some(ResourceHandler::default())),
Resource::new("", "/name"),
Some(ResourceHandler::default()),
),
( (
Resource::new("", "/name/{val}"), Resource::new("", "/name/{val}"),
Some(ResourceHandler::default()), Some(ResourceHandler::default()),
@@ -570,6 +658,41 @@ mod tests {
assert_eq!(req.match_info().get("id").unwrap(), "adahg32"); 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] #[test]
fn test_request_resource() { fn test_request_resource() {
let routes = vec![ let routes = vec![

1167
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::net::{Shutdown, SocketAddr};
use std::rc::Rc; use std::rc::Rc;
use std::{io, mem, ptr, time}; use std::{io, ptr, time};
use bytes::{Buf, BufMut, Bytes, BytesMut}; use bytes::{Buf, BufMut, Bytes, BytesMut};
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use super::settings::WorkerSettings; 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"; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
@@ -61,7 +61,7 @@ where
settings, settings,
peer, peer,
io, io,
BytesMut::with_capacity(4096), BytesMut::with_capacity(8192),
)), )),
} }
} }
@@ -93,12 +93,12 @@ where
let el = self as *mut _; let el = self as *mut _;
self.node = Some(Node::new(el)); self.node = Some(Node::new(el));
let _ = match self.proto { let _ = match self.proto {
Some(HttpProtocol::H1(ref mut h1)) => self.node Some(HttpProtocol::H1(ref mut h1)) => {
.as_ref() self.node.as_ref().map(|n| h1.settings().head().insert(n))
.map(|n| h1.settings().head().insert(n)), }
Some(HttpProtocol::H2(ref mut h2)) => self.node Some(HttpProtocol::H2(ref mut h2)) => {
.as_ref() self.node.as_ref().map(|n| h2.settings().head().insert(n))
.map(|n| h2.settings().head().insert(n)), }
Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => { Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => {
self.node.as_ref().map(|n| settings.head().insert(n)) self.node.as_ref().map(|n| settings.head().insert(n))
} }
@@ -112,7 +112,9 @@ where
match result { match result {
Ok(Async::Ready(())) | Err(_) => { Ok(Async::Ready(())) | Err(_) => {
h1.settings().remove_channel(); 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 { match result {
Ok(Async::Ready(())) | Err(_) => { Ok(Async::Ready(())) | Err(_) => {
h2.settings().remove_channel(); 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(_) => { Ok(Async::Ready(0)) | Err(_) => {
debug!("Ignored premature client disconnection"); debug!("Ignored premature client disconnection");
settings.remove_channel(); settings.remove_channel();
self.node.as_mut().map(|n| n.remove()); if let Some(n) = self.node.as_mut() {
n.remove()
};
return Err(()); return Err(());
} }
_ => (), _ => (),
@@ -162,12 +168,8 @@ where
if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() { if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() {
match kind { match kind {
ProtocolKind::Http1 => { ProtocolKind::Http1 => {
self.proto = Some(HttpProtocol::H1(h1::Http1::new( self.proto =
settings, Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf)));
io,
addr,
buf,
)));
return self.poll(); return self.poll();
} }
ProtocolKind::Http2 => { ProtocolKind::Http2 => {
@@ -204,13 +206,15 @@ impl<T> Node<T> {
#[allow(mutable_transmutes)] #[allow(mutable_transmutes)]
unsafe { unsafe {
if let Some(ref next2) = self.next { 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 _); 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 _); 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 _); next.prev = Some(slf as *const _ as *mut _);
} }
} }
@@ -246,12 +250,12 @@ impl Node<()> {
loop { loop {
if let Some(n) = next { if let Some(n) = next {
unsafe { unsafe {
let n: &Node<()> = mem::transmute(n.as_ref().unwrap()); let n: &Node<()> = &*(n.as_ref().unwrap() as *const _);
next = n.next.as_ref(); next = n.next.as_ref();
if !n.element.is_null() { if !n.element.is_null() {
let ch: &mut HttpChannel<T, H> = 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(); ch.shutdown();
} }
} }

View File

@@ -6,11 +6,16 @@ use std::{cmp, io, mem};
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder}; use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
use flate2::Compression; #[cfg(feature = "flate2")]
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
#[cfg(feature = "flate2")]
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, #[cfg(feature = "flate2")]
CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; use flate2::Compression;
use http::header::{
HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
TRANSFER_ENCODING,
};
use http::{HttpTryFrom, Method, Version}; use http::{HttpTryFrom, Method, Version};
use body::{Binary, Body}; use body::{Binary, Body};
@@ -28,6 +33,7 @@ pub(crate) enum PayloadType {
} }
impl PayloadType { impl PayloadType {
#[cfg(any(feature = "brotli", feature = "flate2"))]
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
// check content-encoding // check content-encoding
let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) {
@@ -47,6 +53,11 @@ impl PayloadType {
_ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))),
} }
} }
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
PayloadType::Sender(sender)
}
} }
impl PayloadWriter for PayloadType { impl PayloadWriter for PayloadType {
@@ -144,7 +155,9 @@ impl PayloadWriter for EncodedPayload {
} }
pub(crate) enum Decoder { pub(crate) enum Decoder {
#[cfg(feature = "flate2")]
Deflate(Box<DeflateDecoder<Writer>>), Deflate(Box<DeflateDecoder<Writer>>),
#[cfg(feature = "flate2")]
Gzip(Option<Box<GzDecoder<Wrapper>>>), Gzip(Option<Box<GzDecoder<Wrapper>>>),
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
Br(Box<BrotliDecoder<Writer>>), Br(Box<BrotliDecoder<Writer>>),
@@ -223,9 +236,11 @@ impl PayloadStream {
ContentEncoding::Br => { ContentEncoding::Br => {
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new()))) Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
} }
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => { ContentEncoding::Deflate => {
Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new()))) Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new())))
} }
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => Decoder::Gzip(None), ContentEncoding::Gzip => Decoder::Gzip(None),
_ => Decoder::Identity, _ => Decoder::Identity,
}; };
@@ -251,6 +266,7 @@ impl PayloadStream {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => { Decoder::Gzip(ref mut decoder) => {
if let Some(ref mut decoder) = *decoder { if let Some(ref mut decoder) = *decoder {
decoder.as_mut().get_mut().eof = true; decoder.as_mut().get_mut().eof = true;
@@ -267,6 +283,7 @@ impl PayloadStream {
Ok(None) Ok(None)
} }
} }
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() { Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
Ok(_) => { Ok(_) => {
let b = decoder.get_mut().take(); let b = decoder.get_mut().take();
@@ -297,6 +314,7 @@ impl PayloadStream {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => { Decoder::Gzip(ref mut decoder) => {
if decoder.is_none() { if decoder.is_none() {
*decoder = Some(Box::new(GzDecoder::new(Wrapper { *decoder = Some(Box::new(GzDecoder::new(Wrapper {
@@ -334,6 +352,7 @@ impl PayloadStream {
} }
} }
} }
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => { Ok(_) => {
decoder.flush()?; decoder.flush()?;
@@ -352,7 +371,9 @@ impl PayloadStream {
} }
pub(crate) enum ContentEncoder { pub(crate) enum ContentEncoder {
#[cfg(feature = "flate2")]
Deflate(DeflateEncoder<TransferEncoding>), Deflate(DeflateEncoder<TransferEncoding>),
#[cfg(feature = "flate2")]
Gzip(GzEncoder<TransferEncoding>), Gzip(GzEncoder<TransferEncoding>),
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
Br(BrotliEncoder<TransferEncoding>), Br(BrotliEncoder<TransferEncoding>),
@@ -370,17 +391,21 @@ impl ContentEncoder {
) -> ContentEncoder { ) -> ContentEncoder {
let version = resp.version().unwrap_or_else(|| req.version); let version = resp.version().unwrap_or_else(|| req.version);
let is_head = req.method == Method::HEAD; let is_head = req.method == Method::HEAD;
let mut body = resp.replace_body(Body::Empty); let mut len = 0;
let has_body = match body {
Body::Empty => false, #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
Body::Binary(ref bin) => { let has_body = match resp.body() {
!(response_encoding == ContentEncoding::Auto && bin.len() < 96) &Body::Empty => false,
&Body::Binary(ref bin) => {
len = bin.len();
!(response_encoding == ContentEncoding::Auto && len < 96)
} }
_ => true, _ => true,
}; };
// Enable content encoding only if response does not contain Content-Encoding // Enable content encoding only if response does not contain Content-Encoding
// header // header
#[cfg(any(feature = "brotli", feature = "flate2"))]
let mut encoding = if has_body { let mut encoding = if has_body {
let encoding = match response_encoding { let encoding = match response_encoding {
ContentEncoding::Auto => { ContentEncoding::Auto => {
@@ -407,45 +432,59 @@ impl ContentEncoder {
} else { } else {
ContentEncoding::Identity ContentEncoding::Identity
}; };
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
let mut encoding = ContentEncoding::Identity;
let mut transfer = match body { #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
Body::Empty => { let mut transfer = match resp.body() {
&Body::Empty => {
if req.method != Method::HEAD { if req.method != Method::HEAD {
resp.headers_mut().remove(CONTENT_LENGTH); resp.headers_mut().remove(CONTENT_LENGTH);
} }
TransferEncoding::length(0, buf) TransferEncoding::length(0, buf)
} }
Body::Binary(ref mut bytes) => { &Body::Binary(_) => {
if !(encoding == ContentEncoding::Identity #[cfg(any(feature = "brotli", feature = "flate2"))]
|| encoding == ContentEncoding::Auto)
{ {
let tmp = SharedBytes::default(); if !(encoding == ContentEncoding::Identity
let transfer = TransferEncoding::eof(tmp.clone()); || encoding == ContentEncoding::Auto)
let mut enc = match encoding { {
ContentEncoding::Deflate => ContentEncoder::Deflate( let tmp = SharedBytes::default();
DeflateEncoder::new(transfer, Compression::fast()), let transfer = TransferEncoding::eof(tmp.clone());
), let mut enc = match encoding {
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( #[cfg(feature = "flate2")]
transfer, ContentEncoding::Deflate => ContentEncoder::Deflate(
Compression::fast(), DeflateEncoder::new(transfer, Compression::fast()),
)), ),
#[cfg(feature = "brotli")] #[cfg(feature = "flate2")]
ContentEncoding::Br => { ContentEncoding::Gzip => ContentEncoder::Gzip(
ContentEncoder::Br(BrotliEncoder::new(transfer, 3)) GzEncoder::new(transfer, Compression::fast()),
} ),
ContentEncoding::Identity => ContentEncoder::Identity(transfer), #[cfg(feature = "brotli")]
ContentEncoding::Auto => unreachable!(), ContentEncoding::Br => {
}; ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
// TODO return error! }
let _ = enc.write(bytes.clone()); ContentEncoding::Identity | ContentEncoding::Auto => {
let _ = enc.write_eof(); unreachable!()
}
};
*bytes = Binary::from(tmp.take()); let bin = resp.replace_body(Body::Empty).binary();
encoding = ContentEncoding::Identity;
// TODO return error!
let _ = enc.write(bin);
let _ = enc.write_eof();
let body = tmp.take();
len = body.len();
encoding = ContentEncoding::Identity;
resp.replace_body(Binary::from(body));
}
} }
if is_head { if is_head {
let mut b = BytesMut::new(); let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len()); let _ = write!(b, "{}", len);
resp.headers_mut().insert( resp.headers_mut().insert(
CONTENT_LENGTH, CONTENT_LENGTH,
HeaderValue::try_from(b.freeze()).unwrap(), HeaderValue::try_from(b.freeze()).unwrap(),
@@ -455,7 +494,7 @@ impl ContentEncoder {
} }
TransferEncoding::eof(buf) TransferEncoding::eof(buf)
} }
Body::Streaming(_) | Body::Actor(_) => { &Body::Streaming(_) | &Body::Actor(_) => {
if resp.upgrade() { if resp.upgrade() {
if version == Version::HTTP_2 { if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2"); error!("Connection upgrade is forbidden for HTTP/2");
@@ -466,22 +505,28 @@ impl ContentEncoder {
} }
TransferEncoding::eof(buf) TransferEncoding::eof(buf)
} else { } else {
if !(encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto)
{
resp.headers_mut().remove(CONTENT_LENGTH);
}
ContentEncoder::streaming_encoding(buf, version, resp) ContentEncoder::streaming_encoding(buf, version, resp)
} }
} }
}; };
// // check for head response
if is_head { if is_head {
resp.set_body(Body::Empty);
transfer.kind = TransferEncodingKind::Length(0); transfer.kind = TransferEncodingKind::Length(0);
} else {
resp.replace_body(body);
} }
match encoding { match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new( ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
transfer, transfer,
Compression::fast(), Compression::fast(),
)), )),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => { ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast())) ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
} }
@@ -494,7 +539,7 @@ impl ContentEncoder {
} }
fn streaming_encoding( fn streaming_encoding(
buf: SharedBytes, version: Version, resp: &mut HttpResponse buf: SharedBytes, version: Version, resp: &mut HttpResponse,
) -> TransferEncoding { ) -> TransferEncoding {
match resp.chunked() { match resp.chunked() {
Some(true) => { Some(true) => {
@@ -563,7 +608,9 @@ impl ContentEncoder {
match *self { match *self {
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(), ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(), ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(), ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(),
ContentEncoder::Identity(ref encoder) => encoder.is_eof(), ContentEncoder::Identity(ref encoder) => encoder.is_eof(),
} }
@@ -571,7 +618,7 @@ impl ContentEncoder {
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)] #[inline(always)]
pub fn write_eof(&mut self) -> Result<(), io::Error> { pub fn write_eof(&mut self) -> Result<bool, io::Error> {
let encoder = mem::replace( let encoder = mem::replace(
self, self,
ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())), ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())),
@@ -583,30 +630,32 @@ impl ContentEncoder {
Ok(mut writer) => { Ok(mut writer) => {
writer.encode_eof(); writer.encode_eof();
*self = ContentEncoder::Identity(writer); *self = ContentEncoder::Identity(writer);
Ok(()) Ok(true)
} }
Err(err) => Err(err), Err(err) => Err(err),
}, },
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(encoder) => match encoder.finish() { ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(mut writer) => { Ok(mut writer) => {
writer.encode_eof(); writer.encode_eof();
*self = ContentEncoder::Identity(writer); *self = ContentEncoder::Identity(writer);
Ok(()) Ok(true)
} }
Err(err) => Err(err), Err(err) => Err(err),
}, },
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(encoder) => match encoder.finish() { ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(mut writer) => { Ok(mut writer) => {
writer.encode_eof(); writer.encode_eof();
*self = ContentEncoder::Identity(writer); *self = ContentEncoder::Identity(writer);
Ok(()) Ok(true)
} }
Err(err) => Err(err), Err(err) => Err(err),
}, },
ContentEncoder::Identity(mut writer) => { ContentEncoder::Identity(mut writer) => {
writer.encode_eof(); let res = writer.encode_eof();
*self = ContentEncoder::Identity(writer); *self = ContentEncoder::Identity(writer);
Ok(()) Ok(res)
} }
} }
} }
@@ -625,6 +674,7 @@ impl ContentEncoder {
} }
} }
} }
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => { ContentEncoder::Gzip(ref mut encoder) => {
match encoder.write_all(data.as_ref()) { match encoder.write_all(data.as_ref()) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@@ -634,6 +684,7 @@ impl ContentEncoder {
} }
} }
} }
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => { ContentEncoder::Deflate(ref mut encoder) => {
match encoder.write_all(data.as_ref()) { match encoder.write_all(data.as_ref()) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@@ -740,8 +791,7 @@ impl TransferEncoding {
return Ok(*remaining == 0); return Ok(*remaining == 0);
} }
let len = cmp::min(*remaining, msg.len() as u64); let len = cmp::min(*remaining, msg.len() as u64);
self.buffer self.buffer.extend(msg.take().split_to(len as usize).into());
.extend(msg.take().split_to(len as usize).into());
*remaining -= len as u64; *remaining -= len as u64;
Ok(*remaining == 0) Ok(*remaining == 0)
@@ -754,14 +804,16 @@ impl TransferEncoding {
/// Encode eof. Return `EOF` state of encoder /// Encode eof. Return `EOF` state of encoder
#[inline] #[inline]
pub fn encode_eof(&mut self) { pub fn encode_eof(&mut self) -> bool {
match self.kind { match self.kind {
TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (), TransferEncodingKind::Eof => true,
TransferEncodingKind::Length(rem) => rem == 0,
TransferEncodingKind::Chunked(ref mut eof) => { TransferEncodingKind::Chunked(ref mut eof) => {
if !*eof { if !*eof {
*eof = true; *eof = true;
self.buffer.extend_from_slice(b"0\r\n\r\n"); self.buffer.extend_from_slice(b"0\r\n\r\n");
} }
true
} }
} }
} }
@@ -825,15 +877,13 @@ impl AcceptEncoding {
Err(_) => 0.0, Err(_) => 0.0,
}, },
}; };
Some(AcceptEncoding { Some(AcceptEncoding { encoding, quality })
encoding,
quality,
})
} }
/// Parse a raw Accept-Encoding header value into an ordered list. /// Parse a raw Accept-Encoding header value into an ordered list.
pub fn parse(raw: &str) -> ContentEncoding { pub fn parse(raw: &str) -> ContentEncoding {
let mut encodings: Vec<_> = raw.replace(' ', "") let mut encodings: Vec<_> = raw
.replace(' ', "")
.split(',') .split(',')
.map(|l| AcceptEncoding::new(l)) .map(|l| AcceptEncoding::new(l))
.collect(); .collect();
@@ -856,9 +906,7 @@ mod tests {
fn test_chunked_te() { fn test_chunked_te() {
let bytes = SharedBytes::default(); let bytes = SharedBytes::default();
let mut enc = TransferEncoding::chunked(bytes.clone()); let mut enc = TransferEncoding::chunked(bytes.clone());
assert!(!enc.encode(Binary::from(b"test".as_ref())) assert!(!enc.encode(Binary::from(b"test".as_ref())).ok().unwrap());
.ok()
.unwrap());
assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap()); assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap());
assert_eq!( assert_eq!(
bytes.get_mut().take().freeze(), bytes.get_mut().take().freeze(),

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,485 @@
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)) => {
self.decoder = decoder;
Ok(Some(Message::Message {
msg,
payload: self.decoder.is_some(),
}))
}
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

@@ -1,9 +1,9 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use bytes::BufMut; use bytes::{BufMut, BytesMut};
use futures::{Async, Poll}; use futures::{Async, Poll};
use std::io;
use std::rc::Rc; use std::rc::Rc;
use std::{io, mem};
use tokio_io::AsyncWrite; use tokio_io::AsyncWrite;
use super::encoding::ContentEncoder; use super::encoding::ContentEncoder;
@@ -13,10 +13,10 @@ use super::shared::SharedBytes;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
use body::{Binary, Body}; use body::{Binary, Body};
use header::ContentEncoding; use header::ContentEncoding;
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
use http::{Method, Version};
use httprequest::HttpInnerMessage; use httprequest::HttpInnerMessage;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use http::{Method, Version};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
@@ -42,10 +42,10 @@ pub(crate) struct H1Writer<T: AsyncWrite, H: 'static> {
impl<T: AsyncWrite, H: 'static> H1Writer<T, H> { impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
pub fn new( pub fn new(
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>> stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
) -> H1Writer<T, H> { ) -> H1Writer<T, H> {
H1Writer { H1Writer {
flags: Flags::empty(), flags: Flags::KEEPALIVE,
encoder: ContentEncoder::empty(buf.clone()), encoder: ContentEncoder::empty(buf.clone()),
written: 0, written: 0,
headers_size: 0, headers_size: 0,
@@ -62,7 +62,7 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.written = 0; self.written = 0;
self.flags = Flags::empty(); self.flags = Flags::KEEPALIVE;
} }
pub fn disconnected(&mut self) { pub fn disconnected(&mut self) {
@@ -100,6 +100,16 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
self.written self.written
} }
#[inline]
fn set_date(&self, dst: &mut BytesMut) {
self.settings.set_date(dst)
}
#[inline]
fn buffer(&self) -> &mut BytesMut {
self.buffer.get_mut()
}
fn start( fn start(
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
encoding: ContentEncoding, encoding: ContentEncoding,
@@ -108,9 +118,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
self.encoder = self.encoder =
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
self.flags.insert(Flags::STARTED | Flags::KEEPALIVE); self.flags = Flags::STARTED | Flags::KEEPALIVE;
} else { } else {
self.flags.insert(Flags::STARTED); self.flags = Flags::STARTED;
} }
// Connection upgrade // Connection upgrade
@@ -138,7 +148,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
let reason = msg.reason().as_bytes(); let reason = msg.reason().as_bytes();
let mut is_bin = if let Body::Binary(ref bytes) = body { let mut is_bin = if let Body::Binary(ref bytes) = body {
buffer.reserve( buffer.reserve(
256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len() 256
+ msg.headers().len() * AVERAGE_HEADER_SIZE
+ bytes.len()
+ reason.len(), + reason.len(),
); );
true true
@@ -169,7 +181,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
let mut pos = 0; let mut pos = 0;
let mut has_date = false; let mut has_date = false;
let mut remaining = buffer.remaining_mut(); 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() { for (key, value) in msg.headers() {
if is_bin && key == CONTENT_LENGTH { if is_bin && key == CONTENT_LENGTH {
is_bin = false; is_bin = false;
@@ -184,7 +196,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
pos = 0; pos = 0;
buffer.reserve(len); buffer.reserve(len);
remaining = buffer.remaining_mut(); 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); buf[pos..pos + k.len()].copy_from_slice(k);
@@ -255,9 +267,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
} }
fn write_eof(&mut self) -> io::Result<WriterState> { fn write_eof(&mut self) -> io::Result<WriterState> {
self.encoder.write_eof()?; if !self.encoder.write_eof()? {
if !self.encoder.is_eof() {
Err(io::Error::new( Err(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"Last payload item, but eof is not reached", "Last payload item, but eof is not reached",
@@ -272,10 +282,13 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
#[inline] #[inline]
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> { fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
if !self.buffer.is_empty() { 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 written = self.write_data(buf)?;
let _ = self.buffer.split_to(written); let _ = self.buffer.split_to(written);
if self.buffer.len() > self.buffer_capacity { if shutdown && !self.buffer.is_empty()
|| (self.buffer.len() > self.buffer_capacity)
{
return Ok(Async::NotReady); return Ok(Async::NotReady);
} }
} }

View File

@@ -61,7 +61,7 @@ where
H: HttpHandler + 'static, H: HttpHandler + 'static,
{ {
pub fn new( 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 { ) -> Self {
Http2 { Http2 {
flags: Flags::empty(), flags: Flags::empty(),
@@ -133,7 +133,8 @@ where
Err(err) => { Err(err) => {
error!("Unhandled error: {}", err); error!("Unhandled error: {}", err);
item.flags.insert( item.flags.insert(
EntryFlags::EOF | EntryFlags::ERROR EntryFlags::EOF
| EntryFlags::ERROR
| EntryFlags::WRITE_DONE, | EntryFlags::WRITE_DONE,
); );
item.stream.reset(Reason::INTERNAL_ERROR); item.stream.reset(Reason::INTERNAL_ERROR);
@@ -150,7 +151,8 @@ where
} }
Err(err) => { Err(err) => {
item.flags.insert( item.flags.insert(
EntryFlags::ERROR | EntryFlags::WRITE_DONE EntryFlags::ERROR
| EntryFlags::WRITE_DONE
| EntryFlags::FINISHED, | EntryFlags::FINISHED,
); );
error!("Unhandled error: {}", err); error!("Unhandled error: {}", err);
@@ -248,7 +250,8 @@ where
if not_ready { if not_ready {
if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED)
{ {
return conn.poll_close() return conn
.poll_close()
.map_err(|e| error!("Error during connection close: {}", e)); .map_err(|e| error!("Error during connection close: {}", e));
} else { } else {
return Ok(Async::NotReady); return Ok(Async::NotReady);
@@ -343,24 +346,27 @@ impl<H: 'static> Entry<H> {
} }
fn poll_payload(&mut self) { fn poll_payload(&mut self) {
if !self.flags.contains(EntryFlags::REOF) { while !self.flags.contains(EntryFlags::REOF)
if self.payload.need_read() == PayloadStatus::Read { && 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))
}
match self.recv.poll() { match self.recv.poll() {
Ok(Async::Ready(Some(chunk))) => { Ok(Async::Ready(Some(chunk))) => {
let l = chunk.len();
self.payload.feed_data(chunk); 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)) => { Ok(Async::Ready(None)) => {
self.flags.insert(EntryFlags::REOF); 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> { impl<H: 'static> H2Writer<H> {
pub fn new( pub fn new(
respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<H>> respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
) -> H2Writer<H> { ) -> H2Writer<H> {
H2Writer { H2Writer {
respond, respond,
@@ -71,6 +71,16 @@ impl<H: 'static> Writer for H2Writer<H> {
self.written self.written
} }
#[inline]
fn set_date(&self, dst: &mut BytesMut) {
self.settings.set_date(dst)
}
#[inline]
fn buffer(&self) -> &mut BytesMut {
self.buffer.get_mut()
}
fn start( fn start(
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
encoding: ContentEncoding, encoding: ContentEncoding,
@@ -79,9 +89,6 @@ impl<H: 'static> Writer for H2Writer<H> {
self.flags.insert(Flags::STARTED); self.flags.insert(Flags::STARTED);
self.encoder = self.encoder =
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
if let Body::Empty = *msg.body() {
self.flags.insert(Flags::EOF);
}
// http2 specific // http2 specific
msg.headers_mut().remove(CONNECTION); msg.headers_mut().remove(CONNECTION);
@@ -98,15 +105,22 @@ impl<H: 'static> Writer for H2Writer<H> {
let body = msg.replace_body(Body::Empty); let body = msg.replace_body(Body::Empty);
match body { match body {
Body::Binary(ref bytes) => { Body::Binary(ref bytes) => {
let mut val = BytesMut::new(); if bytes.is_empty() {
helpers::convert_usize(bytes.len(), &mut val); msg.headers_mut()
let l = val.len(); .insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
msg.headers_mut().insert( self.flags.insert(Flags::EOF);
CONTENT_LENGTH, } else {
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(), let mut val = BytesMut::new();
); helpers::convert_usize(bytes.len(), &mut val);
let l = val.len();
msg.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
);
}
} }
Body::Empty => { Body::Empty => {
self.flags.insert(Flags::EOF);
msg.headers_mut() msg.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0")); .insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
} }
@@ -120,7 +134,8 @@ impl<H: 'static> Writer for H2Writer<H> {
resp.headers_mut().insert(key, value.clone()); resp.headers_mut().insert(key, value.clone());
} }
match self.respond match self
.respond
.send_response(resp, self.flags.contains(Flags::EOF)) .send_response(resp, self.flags.contains(Flags::EOF))
{ {
Ok(stream) => self.stream = Some(stream), Ok(stream) => self.stream = Some(stream),
@@ -130,14 +145,18 @@ impl<H: 'static> Writer for H2Writer<H> {
trace!("Response: {:?}", msg); trace!("Response: {:?}", msg);
if let Body::Binary(bytes) = body { if let Body::Binary(bytes) = body {
self.flags.insert(Flags::EOF); if bytes.is_empty() {
self.written = bytes.len() as u64; Ok(WriterState::Done)
self.encoder.write(bytes)?; } else {
if let Some(ref mut stream) = self.stream { self.flags.insert(Flags::EOF);
self.flags.insert(Flags::RESERVED); self.written = bytes.len() as u64;
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); self.encoder.write(bytes)?;
if let Some(ref mut stream) = self.stream {
self.flags.insert(Flags::RESERVED);
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
}
Ok(WriterState::Pause)
} }
Ok(WriterState::Pause)
} else { } else {
msg.replace_body(body); msg.replace_body(body);
self.buffer_capacity = msg.write_buffer_capacity(); self.buffer_capacity = msg.write_buffer_capacity();
@@ -166,10 +185,8 @@ impl<H: 'static> Writer for H2Writer<H> {
} }
fn write_eof(&mut self) -> io::Result<WriterState> { fn write_eof(&mut self) -> io::Result<WriterState> {
self.encoder.write_eof()?;
self.flags.insert(Flags::EOF); self.flags.insert(Flags::EOF);
if !self.encoder.is_eof() { if !self.encoder.write_eof()? {
Err(io::Error::new( Err(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"Last payload item, but eof is not reached", "Last payload item, but eof is not reached",

View File

@@ -69,7 +69,7 @@ impl SharedHttpInnerMessage {
} }
pub fn new( pub fn new(
msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool> msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>,
) -> SharedHttpInnerMessage { ) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(msg), Some(pool)) SharedHttpInnerMessage(Some(msg), Some(pool))
} }
@@ -79,7 +79,7 @@ impl SharedHttpInnerMessage {
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub fn get_mut(&self) -> &mut HttpInnerMessage { pub fn get_mut(&self) -> &mut HttpInnerMessage {
let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref(); let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref();
unsafe { mem::transmute(r) } unsafe { &mut *(r as *const _ as *mut _) }
} }
#[inline(always)] #[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) { pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
let mut buf: [u8; 13] = [ 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 { match version {
Version::HTTP_2 => buf[5] = b'2', Version::HTTP_2 => buf[5] = b'2',
@@ -143,7 +143,7 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM
} }
/// NOTE: bytes object has to contain enough space /// NOTE: bytes object has to contain enough space
pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) { pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
if n < 10 { if n < 10 {
let mut buf: [u8; 21] = [ let mut buf: [u8; 21] = [
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e', b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e',
@@ -251,63 +251,33 @@ mod tests {
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
bytes.reserve(50); bytes.reserve(50);
write_content_length(0, &mut bytes); write_content_length(0, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 0\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(9, &mut bytes); write_content_length(9, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 9\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(10, &mut bytes); write_content_length(10, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 10\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(99, &mut bytes); write_content_length(99, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 99\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(100, &mut bytes); write_content_length(100, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 100\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(101, &mut bytes); write_content_length(101, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 101\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(998, &mut bytes); write_content_length(998, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 998\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(1000, &mut bytes); write_content_length(1000, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 1000\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(1001, &mut bytes); write_content_length(1001, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 1001\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(5909, &mut bytes); write_content_length(5909, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 5909\r\n"[..]
);
} }
} }

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::io;
use std::rc::Rc; use std::rc::Rc;
use std::{io, mem};
use body::Binary; use body::Binary;
@@ -61,7 +61,7 @@ impl SharedBytes {
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub(crate) fn get_mut(&self) -> &mut BytesMut { pub(crate) fn get_mut(&self) -> &mut BytesMut {
let r: &BytesMut = self.0.as_ref().unwrap().as_ref(); let r: &BytesMut = self.0.as_ref().unwrap().as_ref();
unsafe { mem::transmute(r) } unsafe { &mut *(r as *const _ as *mut _) }
} }
#[inline] #[inline]

View File

@@ -10,6 +10,7 @@ use futures::{Future, Sink, Stream};
use mio; use mio;
use net2::TcpBuilder; use net2::TcpBuilder;
use num_cpus; use num_cpus;
use slab::Slab;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
@@ -20,10 +21,24 @@ use openssl::ssl::{AlpnError, SslAcceptorBuilder};
use super::channel::{HttpChannel, WrapperStream}; use super::channel::{HttpChannel, WrapperStream};
use super::settings::{ServerSettings, WorkerSettings}; 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::{IntoHttpHandler, IoStream, KeepAlive};
use super::{PauseServer, ResumeServer, StopServer}; use super::{PauseServer, ResumeServer, StopServer};
#[cfg(feature = "alpn")]
fn configure_alpn(builder: &mut SslAcceptorBuilder) -> io::Result<()> {
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";
if protos.windows(3).any(|window| window == H2) {
Ok(b"h2")
} else {
Err(AlpnError::NOACK)
}
});
Ok(())
}
/// An HTTP Server /// An HTTP Server
pub struct HttpServer<H> pub struct HttpServer<H>
where where
@@ -37,7 +52,7 @@ where
factory: Arc<Fn() -> Vec<H> + Send + Sync>, factory: Arc<Fn() -> Vec<H> + Send + Sync>,
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
workers: Vec<(usize, Addr<Syn, Worker<H::Handler>>)>, 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>)>, accept: Vec<(mio::SetReadiness, sync_mpsc::Sender<Command>)>,
exit: bool, exit: bool,
shutdown_timeout: u16, shutdown_timeout: u16,
@@ -57,14 +72,8 @@ where
{ {
} }
#[derive(Clone)]
struct Info {
addr: net::SocketAddr,
handler: StreamHandlerType,
}
enum ServerCommand { enum ServerCommand {
WorkerDied(usize, Info), WorkerDied(usize, Slab<SocketInfo>),
} }
impl<H> Actor for HttpServer<H> impl<H> Actor for HttpServer<H>
@@ -74,6 +83,12 @@ where
type Context = Context<Self>; type Context = Context<Self>;
} }
struct Socket {
lst: net::TcpListener,
addr: net::SocketAddr,
tp: StreamHandlerType,
}
impl<H> HttpServer<H> impl<H> HttpServer<H>
where where
H: IntoHttpHandler + 'static, H: IntoHttpHandler + 'static,
@@ -108,11 +123,17 @@ where
/// ///
/// By default http server uses number of available logical cpu as threads /// By default http server uses number of available logical cpu as threads
/// count. /// count.
pub fn threads(mut self, num: usize) -> Self { pub fn workers(mut self, num: usize) -> Self {
self.threads = num; self.threads = num;
self 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. /// Set the maximum number of pending connections.
/// ///
/// This refers to the number of clients that can be waiting to be served. /// This refers to the number of clients that can be waiting to be served.
@@ -187,7 +208,20 @@ where
/// Get addresses of bound sockets. /// Get addresses of bound sockets.
pub fn addrs(&self) -> Vec<net::SocketAddr> { pub fn addrs(&self) -> Vec<net::SocketAddr> {
self.sockets.iter().map(|s| s.0).collect() self.sockets.iter().map(|s| s.addr).collect()
}
/// Get addresses of bound sockets and the scheme for it.
///
/// This is useful when the server is bound from different sources
/// with some sockets listening on http and some listening on https
/// and the user should be presented with an enumeration of which
/// socket requires which protocol.
pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> {
self.sockets
.iter()
.map(|s| (s.addr, s.tp.scheme()))
.collect()
} }
/// Use listener for accepting incoming connection requests /// Use listener for accepting incoming connection requests
@@ -195,21 +229,65 @@ where
/// HttpServer does not change any configuration for TcpListener, /// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method. /// it needs to be configured before passing it to listen() method.
pub fn listen(mut self, lst: net::TcpListener) -> Self { 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 self
} }
/// The socket address to bind #[cfg(feature = "tls")]
/// Use listener for accepting incoming tls connection requests
/// ///
/// To mind multiple addresses this method can be call multiple times. /// HttpServer does not change any configuration for TcpListener,
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> { /// it needs to be configured before passing it to listen() method.
pub fn listen_tls(mut self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self {
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Tls(acceptor.clone()),
});
self
}
#[cfg(feature = "alpn")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_ssl(
mut self, lst: net::TcpListener, mut builder: SslAcceptorBuilder,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
configure_alpn(&mut builder)?;
}
let acceptor = builder.build();
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Alpn(acceptor.clone()),
});
Ok(self)
}
fn bind2<S: net::ToSocketAddrs>(&mut self, addr: S) -> io::Result<Vec<Socket>> {
let mut err = None; let mut err = None;
let mut succ = false; let mut succ = false;
let mut sockets = Vec::new();
for addr in addr.to_socket_addrs()? { for addr in addr.to_socket_addrs()? {
match create_tcp_listener(addr, self.backlog) { match create_tcp_listener(addr, self.backlog) {
Ok(lst) => { Ok(lst) => {
succ = true; 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), Err(e) => err = Some(e),
} }
@@ -225,12 +303,57 @@ where
)) ))
} }
} else { } else {
Ok(self) Ok(sockets)
} }
} }
/// The socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets);
Ok(self)
}
#[cfg(feature = "tls")]
/// The ssl socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind_tls<S: net::ToSocketAddrs>(
mut self, addr: S, acceptor: TlsAcceptor,
) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(|mut s| {
s.tp = StreamHandlerType::Tls(acceptor.clone());
s
}));
Ok(self)
}
#[cfg(feature = "alpn")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_ssl<S: net::ToSocketAddrs>(
mut self, addr: S, mut builder: SslAcceptorBuilder,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
configure_alpn(&mut builder)?;
}
let acceptor = builder.build();
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(|mut s| {
s.tp = StreamHandlerType::Alpn(acceptor.clone());
s
}));
Ok(self)
}
fn start_workers( fn start_workers(
&mut self, settings: &ServerSettings, handler: &StreamHandlerType &mut self, settings: &ServerSettings, sockets: &Slab<SocketInfo>,
) -> Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)> { ) -> Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)> {
// start workers // start workers
let mut workers = Vec::new(); let mut workers = Vec::new();
@@ -238,8 +361,8 @@ where
let s = settings.clone(); let s = settings.clone();
let (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>(); let (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>();
let h = handler.clone();
let ka = self.keep_alive; let ka = self.keep_alive;
let socks = sockets.clone();
let factory = Arc::clone(&self.factory); let factory = Arc::clone(&self.factory);
let addr = Arbiter::start(move |ctx: &mut Context<_>| { let addr = Arbiter::start(move |ctx: &mut Context<_>| {
let apps: Vec<_> = (*factory)() let apps: Vec<_> = (*factory)()
@@ -247,7 +370,7 @@ where
.map(|h| h.into_handler(s.clone())) .map(|h| h.into_handler(s.clone()))
.collect(); .collect();
ctx.add_message_stream(rx); ctx.add_message_stream(rx);
Worker::new(apps, h, ka) Worker::new(apps, socks, ka)
}); });
workers.push((idx, tx)); workers.push((idx, tx));
self.workers.push((idx, addr)); self.workers.push((idx, addr));
@@ -304,24 +427,31 @@ impl<H: IntoHttpHandler> HttpServer<H> {
panic!("HttpServer::bind() has to be called before start()"); panic!("HttpServer::bind() has to be called before start()");
} else { } else {
let (tx, rx) = mpsc::unbounded(); let (tx, rx) = mpsc::unbounded();
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
self.sockets.drain(..).collect(); let mut socks = Slab::new();
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let mut addrs: Vec<(usize, Socket)> = Vec::new();
let workers = self.start_workers(&settings, &StreamHandlerType::Normal);
let info = Info { for socket in self.sockets.drain(..) {
addr: addrs[0].0, let entry = socks.vacant_entry();
handler: StreamHandlerType::Normal, 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 // start acceptors threads
for (addr, sock) in addrs { for (token, sock) in addrs {
info!("Starting server on http://{}", addr); info!("Starting server on http://{}", sock.addr);
self.accept.push(start_accept_thread( self.accept.push(start_accept_thread(
token,
sock, sock,
addr,
self.backlog,
tx.clone(), tx.clone(),
info.clone(), socks.clone(),
workers.clone(), workers.clone(),
)); ));
} }
@@ -332,9 +462,9 @@ impl<H: IntoHttpHandler> HttpServer<H> {
ctx.add_stream(rx); ctx.add_stream(rx);
self self
}); });
signals.map(|signals| { if let Some(signals) = signals {
signals.do_send(signal::Subscribe(addr.clone().recipient())) signals.do_send(signal::Subscribe(addr.clone().recipient()))
}); }
addr addr
} }
} }
@@ -373,119 +503,59 @@ impl<H: IntoHttpHandler> HttpServer<H> {
} }
} }
#[doc(hidden)]
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
#[deprecated(
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_tls` instead"
)]
impl<H: IntoHttpHandler> HttpServer<H> { impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming tls connections. /// Start listening for incoming tls connections.
pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result<Addr<Syn, Self>> { pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result<Addr<Syn, Self>> {
if self.sockets.is_empty() { for sock in &mut self.sockets {
Err(io::Error::new( match sock.tp {
io::ErrorKind::Other, StreamHandlerType::Normal => (),
"No socket addresses are bound", _ => continue,
))
} 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(),
));
} }
sock.tp = StreamHandlerType::Tls(acceptor.clone());
// 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)
} }
Ok(self.start())
} }
} }
#[doc(hidden)]
#[cfg(feature = "alpn")] #[cfg(feature = "alpn")]
#[deprecated(
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_ssl` instead"
)]
impl<H: IntoHttpHandler> HttpServer<H> { impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming tls connections. /// Start listening for incoming tls connections.
/// ///
/// This method sets alpn protocols to "h2" and "http/1.1" /// This method sets alpn protocols to "h2" and "http/1.1"
pub fn start_ssl( pub fn start_ssl(
mut self, mut builder: SslAcceptorBuilder mut self, mut builder: SslAcceptorBuilder,
) -> io::Result<Addr<Syn, Self>> { ) -> io::Result<Addr<Syn, Self>> {
if self.sockets.is_empty() { // alpn support
Err(io::Error::new( if !self.no_http2 {
io::ErrorKind::Other, builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
"No socket addresses are bound", builder.set_alpn_select_callback(|_, protos| {
)) const H2: &[u8] = b"\x02h2";
} else { if protos.windows(3).any(|window| window == H2) {
// alpn support Ok(b"h2")
if !self.no_http2 { } else {
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?; Err(AlpnError::NOACK)
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
}); });
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 +569,6 @@ impl<H: IntoHttpHandler> HttpServer<H> {
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + 'static,
A: '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 // set server settings
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
let settings = ServerSettings::new(Some(addr), &self.host, secure); let settings = ServerSettings::new(Some(addr), &self.host, secure);
@@ -537,16 +581,17 @@ impl<H: IntoHttpHandler> HttpServer<H> {
// start server // start server
let signals = self.subscribe_to_signals(); let signals = self.subscribe_to_signals();
let addr: Addr<Syn, _> = HttpServer::create(move |ctx| { let addr: Addr<Syn, _> = HttpServer::create(move |ctx| {
ctx.add_stream(rx);
ctx.add_message_stream(stream.map_err(|_| ()).map(move |(t, _)| Conn { ctx.add_message_stream(stream.map_err(|_| ()).map(move |(t, _)| Conn {
io: WrapperStream::new(t), io: WrapperStream::new(t),
token: 0,
peer: None, peer: None,
http2: false, http2: false,
})); }));
self self
}); });
signals if let Some(signals) = signals {
.map(|signals| signals.do_send(signal::Subscribe(addr.clone().recipient()))); signals.do_send(signal::Subscribe(addr.clone().recipient()))
}
addr addr
} }
} }
@@ -584,7 +629,7 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
fn finished(&mut self, _: &mut Context<Self>) {} fn finished(&mut self, _: &mut Context<Self>) {}
fn handle(&mut self, msg: ServerCommand, _: &mut Context<Self>) { fn handle(&mut self, msg: ServerCommand, _: &mut Context<Self>) {
match msg { match msg {
ServerCommand::WorkerDied(idx, info) => { ServerCommand::WorkerDied(idx, socks) => {
let mut found = false; let mut found = false;
for i in 0..self.workers.len() { for i in 0..self.workers.len() {
if self.workers[i].0 == idx { if self.workers[i].0 == idx {
@@ -609,11 +654,10 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
break; break;
} }
let h = info.handler;
let ka = self.keep_alive; let ka = self.keep_alive;
let factory = Arc::clone(&self.factory); let factory = Arc::clone(&self.factory);
let settings = 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 addr = Arbiter::start(move |ctx: &mut Context<_>| {
let apps: Vec<_> = (*factory)() let apps: Vec<_> = (*factory)()
@@ -621,7 +665,7 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
.map(|h| h.into_handler(settings.clone())) .map(|h| h.into_handler(settings.clone()))
.collect(); .collect();
ctx.add_message_stream(rx); ctx.add_message_stream(rx);
Worker::new(apps, h, ka) Worker::new(apps, socks, ka)
}); });
for item in &self.accept { for item in &self.accept {
let _ = item.1.send(Command::Worker(new_idx, tx.clone())); let _ = item.1.send(Command::Worker(new_idx, tx.clone()));
@@ -737,8 +781,8 @@ enum Command {
} }
fn start_accept_thread( fn start_accept_thread(
sock: net::TcpListener, addr: net::SocketAddr, backlog: i32, token: usize, sock: Socket, srv: mpsc::UnboundedSender<ServerCommand>,
srv: mpsc::UnboundedSender<ServerCommand>, info: Info, socks: Slab<SocketInfo>,
mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>, mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) { ) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) {
let (tx, rx) = sync_mpsc::channel(); let (tx, rx) = sync_mpsc::channel();
@@ -747,13 +791,14 @@ fn start_accept_thread(
// start accept thread // start accept thread
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
let _ = thread::Builder::new() let _ = thread::Builder::new()
.name(format!("Accept on {}", addr)) .name(format!("Accept on {}", sock.addr))
.spawn(move || { .spawn(move || {
const SRV: mio::Token = mio::Token(0); const SRV: mio::Token = mio::Token(0);
const CMD: mio::Token = mio::Token(1); const CMD: mio::Token = mio::Token(1);
let addr = sock.addr;
let mut server = Some( let mut server = Some(
mio::net::TcpListener::from_std(sock) mio::net::TcpListener::from_std(sock.lst)
.expect("Can not create mio::net::TcpListener"), .expect("Can not create mio::net::TcpListener"),
); );
@@ -773,12 +818,9 @@ fn start_accept_thread(
} }
// Start listening for incoming commands // Start listening for incoming commands
if let Err(err) = poll.register( if let Err(err) =
&reg, poll.register(&reg, CMD, mio::Ready::readable(), mio::PollOpt::edge())
CMD, {
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err); panic!("Can not register Registration: {}", err);
} }
@@ -799,9 +841,10 @@ fn start_accept_thread(
SRV => if let Some(ref server) = server { SRV => if let Some(ref server) = server {
loop { loop {
match server.accept_std() { match server.accept_std() {
Ok((sock, addr)) => { Ok((io, addr)) => {
let mut msg = Conn { let mut msg = Conn {
io: sock, io,
token,
peer: Some(addr), peer: Some(addr),
http2: false, http2: false,
}; };
@@ -812,7 +855,7 @@ fn start_accept_thread(
let _ = srv.unbounded_send( let _ = srv.unbounded_send(
ServerCommand::WorkerDied( ServerCommand::WorkerDied(
workers[next].0, workers[next].0,
info.clone(), socks.clone(),
), ),
); );
msg = err.into_inner(); msg = err.into_inner();
@@ -848,8 +891,8 @@ fn start_accept_thread(
}, },
CMD => match rx.try_recv() { CMD => match rx.try_recv() {
Ok(cmd) => match cmd { Ok(cmd) => match cmd {
Command::Pause => if let Some(server) = server.take() { Command::Pause => if let Some(ref server) = server {
if let Err(err) = poll.deregister(&server) { if let Err(err) = poll.deregister(server) {
error!( error!(
"Can not deregister server socket {}", "Can not deregister server socket {}",
err err
@@ -862,15 +905,6 @@ fn start_accept_thread(
} }
}, },
Command::Resume => { Command::Resume => {
let lst = create_tcp_listener(addr, backlog)
.expect("Can not create net::TcpListener");
server = Some(
mio::net::TcpListener::from_std(lst).expect(
"Can not create mio::net::TcpListener",
),
);
if let Some(ref server) = server { if let Some(ref server) = server {
if let Err(err) = poll.register( if let Err(err) = poll.register(
server, server,
@@ -915,7 +949,7 @@ fn start_accept_thread(
} }
fn create_tcp_listener( fn create_tcp_listener(
addr: net::SocketAddr, backlog: i32 addr: net::SocketAddr, backlog: i32,
) -> io::Result<net::TcpListener> { ) -> io::Result<net::TcpListener> {
let builder = match addr { let builder = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?, 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; const HW_BUFFER_SIZE: usize = 32_768;
pub fn read_from_io<T: IoStream>( pub fn read_from_io<T: IoStream>(
io: &mut T, buf: &mut BytesMut io: &mut T, buf: &mut BytesMut,
) -> Poll<usize, io::Error> { ) -> Poll<usize, io::Error> {
unsafe { unsafe {
if buf.remaining_mut() < LW_BUFFER_SIZE { if buf.remaining_mut() < LW_BUFFER_SIZE {

View File

@@ -1,6 +1,7 @@
use futures::Future;
use futures::unsync::oneshot; use futures::unsync::oneshot;
use futures::Future;
use net2::TcpStreamExt; use net2::TcpStreamExt;
use slab::Slab;
use std::rc::Rc; use std::rc::Rc;
use std::{net, time}; use std::{net, time};
use tokio_core::net::TcpStream; use tokio_core::net::TcpStream;
@@ -29,10 +30,17 @@ use server::{HttpHandler, KeepAlive};
#[derive(Message)] #[derive(Message)]
pub(crate) struct Conn<T> { pub(crate) struct Conn<T> {
pub io: T, pub io: T,
pub token: usize,
pub peer: Option<net::SocketAddr>, pub peer: Option<net::SocketAddr>,
pub http2: bool, pub http2: bool,
} }
#[derive(Clone)]
pub(crate) struct SocketInfo {
pub addr: net::SocketAddr,
pub htype: StreamHandlerType,
}
/// Stop worker message. Returns `true` on successful shutdown /// Stop worker message. Returns `true` on successful shutdown
/// and `false` if some connections still alive. /// and `false` if some connections still alive.
pub(crate) struct StopWorker { pub(crate) struct StopWorker {
@@ -53,13 +61,13 @@ where
{ {
settings: Rc<WorkerSettings<H>>, settings: Rc<WorkerSettings<H>>,
hnd: Handle, hnd: Handle,
handler: StreamHandlerType, socks: Slab<SocketInfo>,
tcp_ka: Option<time::Duration>, tcp_ka: Option<time::Duration>,
} }
impl<H: HttpHandler + 'static> Worker<H> { impl<H: HttpHandler + 'static> Worker<H> {
pub(crate) fn new( pub(crate) fn new(
h: Vec<H>, handler: StreamHandlerType, keep_alive: KeepAlive h: Vec<H>, socks: Slab<SocketInfo>, keep_alive: KeepAlive,
) -> Worker<H> { ) -> Worker<H> {
let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive {
Some(time::Duration::new(val as u64, 0)) Some(time::Duration::new(val as u64, 0))
@@ -70,20 +78,18 @@ impl<H: HttpHandler + 'static> Worker<H> {
Worker { Worker {
settings: Rc::new(WorkerSettings::new(h, keep_alive)), settings: Rc::new(WorkerSettings::new(h, keep_alive)),
hnd: Arbiter::handle().clone(), hnd: Arbiter::handle().clone(),
handler, socks,
tcp_ka, tcp_ka,
} }
} }
fn update_time(&self, ctx: &mut Context<Self>) { fn update_time(&self, ctx: &mut Context<Self>) {
self.settings.update_date(); self.settings.update_date();
ctx.run_later(time::Duration::new(1, 0), |slf, ctx| { ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx));
slf.update_time(ctx)
});
} }
fn shutdown_timeout( fn shutdown_timeout(
&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 // sleep for 1 second and then check again
ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| { ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| {
@@ -124,8 +130,11 @@ where
if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() { if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() {
error!("Can not set socket keep-alive option"); error!("Can not set socket keep-alive option");
} }
self.handler self.socks.get_mut(msg.token).unwrap().htype.handle(
.handle(Rc::clone(&self.settings), &self.hnd, msg); Rc::clone(&self.settings),
&self.hnd,
msg,
);
} }
} }
@@ -165,7 +174,7 @@ pub(crate) enum StreamHandlerType {
impl StreamHandlerType { impl StreamHandlerType {
fn handle<H: HttpHandler>( 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 { match *self {
StreamHandlerType::Normal => { StreamHandlerType::Normal => {
@@ -177,27 +186,24 @@ impl StreamHandlerType {
} }
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
StreamHandlerType::Tls(ref acceptor) => { StreamHandlerType::Tls(ref acceptor) => {
let Conn { io, peer, http2 } = msg; let Conn {
io, peer, http2, ..
} = msg;
let _ = io.set_nodelay(true); let _ = io.set_nodelay(true);
let io = TcpStream::from_stream(io, hnd) let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream"); .expect("failed to associate TCP stream");
hnd.spawn( hnd.spawn(TlsAcceptorExt::accept_async(acceptor, io).then(move |res| {
TlsAcceptorExt::accept_async(acceptor, io).then(move |res| { match res {
match res { Ok(io) => {
Ok(io) => Arbiter::handle().spawn(HttpChannel::new( Arbiter::handle().spawn(HttpChannel::new(h, io, peer, http2))
h, }
io, Err(err) => {
peer, trace!("Error during handling tls connection: {}", err)
http2, }
)), };
Err(err) => { future::result(Ok(()))
trace!("Error during handling tls connection: {}", err) }));
}
};
future::result(Ok(()))
}),
);
} }
#[cfg(feature = "alpn")] #[cfg(feature = "alpn")]
StreamHandlerType::Alpn(ref acceptor) => { StreamHandlerType::Alpn(ref acceptor) => {
@@ -206,32 +212,36 @@ impl StreamHandlerType {
let io = TcpStream::from_stream(io, hnd) let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream"); .expect("failed to associate TCP stream");
hnd.spawn( hnd.spawn(SslAcceptorExt::accept_async(acceptor, io).then(move |res| {
SslAcceptorExt::accept_async(acceptor, io).then(move |res| { match res {
match res { Ok(io) => {
Ok(io) => { let http2 = if let Some(p) =
let http2 = if let Some(p) = io.get_ref().ssl().selected_alpn_protocol()
io.get_ref().ssl().selected_alpn_protocol() {
{ p.len() == 2 && &p == b"h2"
p.len() == 2 && &p == b"h2" } else {
} else { false
false };
}; Arbiter::handle()
Arbiter::handle().spawn(HttpChannel::new( .spawn(HttpChannel::new(h, io, peer, http2));
h, }
io, Err(err) => {
peer, trace!("Error during handling tls connection: {}", err)
http2, }
)); };
} future::result(Ok(()))
Err(err) => { }));
trace!("Error during handling tls connection: {}", err)
}
};
future::result(Ok(()))
}),
);
} }
} }
} }
pub(crate) fn scheme(&self) -> &'static str {
match *self {
StreamHandlerType::Normal => "http",
#[cfg(feature = "tls")]
StreamHandlerType::Tls(_) => "https",
#[cfg(feature = "alpn")]
StreamHandlerType::Alpn(_) => "https",
}
}
} }

View File

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

View File

@@ -51,10 +51,6 @@ impl Url {
&self.uri &self.uri
} }
pub fn uri_mut(&mut self) -> &mut Uri {
&mut self.uri
}
pub fn path(&self) -> &str { pub fn path(&self) -> &str {
if let Some(ref s) = self.path { if let Some(ref s) = self.path {
s s

View File

@@ -5,10 +5,66 @@ use std::ops::{Deref, DerefMut};
use std::rc::Rc; use std::rc::Rc;
use error::Error; use error::Error;
use handler::{FromRequest, Handler, Reply, ReplyItem, Responder}; use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; 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>> { pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> {
cfg: Rc<UnsafeCell<T::Config>>, cfg: Rc<UnsafeCell<T::Config>>,
} }
@@ -82,7 +138,7 @@ where
T: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
S: 'static, S: 'static,
{ {
type Result = Reply; type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut { let mut fut = WithHandlerFut {
@@ -95,9 +151,9 @@ where
}; };
match fut.poll() { match fut.poll() {
Ok(Async::Ready(resp)) => Reply::response(resp), Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => Reply::async(fut), Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => Reply::response(e), Err(e) => AsyncResult::err(e),
} }
} }
} }
@@ -134,14 +190,14 @@ where
let item = if !self.started { let item = if !self.started {
self.started = true; self.started = true;
let mut fut = T::from_request(&self.req, self.cfg.as_ref()); let reply = T::from_request(&self.req, self.cfg.as_ref()).into();
match fut.poll() { match reply.into() {
Ok(Async::Ready(item)) => item, AsyncResultItem::Err(err) => return Err(err),
Ok(Async::NotReady) => { AsyncResultItem::Ok(msg) => msg,
self.fut1 = Some(Box::new(fut)); AsyncResultItem::Future(fut) => {
return Ok(Async::NotReady); self.fut1 = Some(fut);
return self.poll();
} }
Err(e) => return Err(e),
} }
} else { } else {
match self.fut1.as_mut().unwrap().poll()? { match self.fut1.as_mut().unwrap().poll()? {
@@ -151,14 +207,15 @@ where
}; };
let hnd: &mut F = unsafe { &mut *self.hnd.get() }; 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(), Ok(item) => item.into(),
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
match item.into() { match item.into() {
ReplyItem::Message(resp) => Ok(Async::Ready(resp)), AsyncResultItem::Err(err) => Err(err),
ReplyItem::Future(fut) => { AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut2 = Some(fut); self.fut2 = Some(fut);
self.poll() 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> pub struct With2<T1, T2, S, F, R>
where where
F: Fn(T1, T2) -> R, F: Fn(T1, T2) -> R,
@@ -187,7 +383,7 @@ where
S: 'static, S: 'static,
{ {
pub fn new( pub fn new(
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2> f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
) -> Self { ) -> Self {
With2 { With2 {
hnd: Rc::new(UnsafeCell::new(f)), hnd: Rc::new(UnsafeCell::new(f)),
@@ -206,7 +402,7 @@ where
T2: FromRequest<S> + 'static, T2: FromRequest<S> + 'static,
S: 'static, S: 'static,
{ {
type Result = Reply; type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut2 { let mut fut = WithHandlerFut2 {
@@ -221,9 +417,9 @@ where
fut3: None, fut3: None,
}; };
match fut.poll() { match fut.poll() {
Ok(Async::Ready(resp)) => Reply::response(resp), Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => Reply::async(fut), Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => Reply::response(e), Err(e) => AsyncResult::ok(e),
} }
} }
} }
@@ -265,52 +461,67 @@ where
if !self.started { if !self.started {
self.started = true; self.started = true;
let mut fut = T1::from_request(&self.req, self.cfg1.as_ref()); let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
match fut.poll() { let item1 = match reply.into() {
Ok(Async::Ready(item1)) => { AsyncResultItem::Err(err) => return Err(err),
let mut fut = T2::from_request(&self.req, self.cfg2.as_ref()); AsyncResultItem::Ok(msg) => msg,
match fut.poll() { AsyncResultItem::Future(fut) => {
Ok(Async::Ready(item2)) => { self.fut1 = Some(fut);
let hnd: &mut F = unsafe { &mut *self.hnd.get() }; return self.poll();
match (*hnd)(item1, item2).respond_to(self.req.drop_state()) }
{ };
Ok(item) => match item.into().into() {
ReplyItem::Message(resp) => { let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
return Ok(Async::Ready(resp)) let item2 = match reply.into() {
} AsyncResultItem::Err(err) => return Err(err),
ReplyItem::Future(fut) => { AsyncResultItem::Ok(msg) => msg,
self.fut3 = Some(fut); AsyncResultItem::Future(fut) => {
return self.poll(); self.item = Some(item1);
} self.fut2 = Some(fut);
}, return self.poll();
Err(e) => return Err(e.into()), }
} };
}
Ok(Async::NotReady) => { let hnd: &mut F = unsafe { &mut *self.hnd.get() };
self.item = Some(item1); match (*hnd)(item1, item2).respond_to(&self.req) {
self.fut2 = Some(Box::new(fut)); Ok(item) => match item.into().into() {
return Ok(Async::NotReady); AsyncResultItem::Err(err) => return Err(err),
} AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
Err(e) => return Err(e), AsyncResultItem::Future(fut) => {
self.fut3 = Some(fut);
return self.poll();
} }
} },
Ok(Async::NotReady) => { Err(e) => return Err(e.into()),
self.fut1 = Some(Box::new(fut));
return Ok(Async::NotReady);
}
Err(e) => return Err(e),
} }
} }
if self.fut1.is_some() { if self.fut1.is_some() {
match self.fut1.as_mut().unwrap().poll()? { match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => { Async::Ready(item) => {
self.item = Some(item); let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
self.fut1.take(); let item2 = match reply.into() {
self.fut2 = Some(Box::new(T2::from_request( AsyncResultItem::Err(err) => return Err(err),
&self.req, AsyncResultItem::Ok(msg) => msg,
self.cfg2.as_ref(), 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), Async::NotReady => return Ok(Async::NotReady),
} }
@@ -322,16 +533,15 @@ where
}; };
let hnd: &mut F = unsafe { &mut *self.hnd.get() }; let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)(self.item.take().unwrap(), item) let item = match (*hnd)(self.item.take().unwrap(), item).respond_to(&self.req) {
.respond_to(self.req.drop_state())
{
Ok(item) => item.into(), Ok(item) => item.into(),
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };
match item.into() { match item.into() {
ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), AsyncResultItem::Err(err) => return Err(err),
ReplyItem::Future(fut) => self.fut3 = Some(fut), AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => self.fut3 = Some(fut),
} }
self.poll() self.poll()
@@ -387,7 +597,7 @@ where
T3: 'static, T3: 'static,
S: 'static, S: 'static,
{ {
type Result = Reply; type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut3 { let mut fut = WithHandlerFut3 {
@@ -405,9 +615,9 @@ where
fut4: None, fut4: None,
}; };
match fut.poll() { match fut.poll() {
Ok(Async::Ready(resp)) => Reply::response(resp), Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => Reply::async(fut), Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => Reply::response(e), Err(e) => AsyncResult::err(e),
} }
} }
} }
@@ -454,54 +664,50 @@ where
if !self.started { if !self.started {
self.started = true; self.started = true;
let mut fut = T1::from_request(&self.req, self.cfg1.as_ref()); let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
match fut.poll() { let item1 = match reply.into() {
Ok(Async::Ready(item1)) => { AsyncResultItem::Err(err) => return Err(err),
let mut fut = T2::from_request(&self.req, self.cfg2.as_ref()); AsyncResultItem::Ok(msg) => msg,
match fut.poll() { AsyncResultItem::Future(fut) => {
Ok(Async::Ready(item2)) => { self.fut1 = Some(fut);
let mut fut = return self.poll();
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() }; let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
match (*hnd)(item1, item2, item3) let item2 = match reply.into() {
.respond_to(self.req.drop_state()) AsyncResultItem::Err(err) => return Err(err),
{ AsyncResultItem::Ok(msg) => msg,
Ok(item) => match item.into().into() { AsyncResultItem::Future(fut) => {
ReplyItem::Message(resp) => { self.item1 = Some(item1);
return Ok(Async::Ready(resp)) self.fut2 = Some(fut);
} return self.poll();
ReplyItem::Future(fut) => { }
self.fut4 = Some(fut); };
return self.poll();
} let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
}, let item3 = match reply.into() {
Err(e) => return Err(e.into()), AsyncResultItem::Err(err) => return Err(err),
} AsyncResultItem::Ok(msg) => msg,
} AsyncResultItem::Future(fut) => {
Ok(Async::NotReady) => { self.item1 = Some(item1);
self.item1 = Some(item1); self.item2 = Some(item2);
self.item2 = Some(item2); self.fut3 = Some(fut);
self.fut3 = Some(Box::new(fut)); return self.poll();
return Ok(Async::NotReady); }
} };
Err(e) => return Err(e),
} let hnd: &mut F = unsafe { &mut *self.hnd.get() };
} match (*hnd)(item1, item2, item3).respond_to(&self.req) {
Ok(Async::NotReady) => { Ok(item) => match item.into().into() {
self.item1 = Some(item1); AsyncResultItem::Err(err) => return Err(err),
self.fut2 = Some(Box::new(fut)); AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
return Ok(Async::NotReady); AsyncResultItem::Future(fut) => {
} self.fut4 = Some(fut);
Err(e) => return Err(e), return self.poll();
} }
} },
Ok(Async::NotReady) => { Err(e) => return Err(e.into()),
self.fut1 = Some(Box::new(fut));
return Ok(Async::NotReady);
}
Err(e) => return Err(e),
} }
} }
@@ -510,10 +716,40 @@ where
Async::Ready(item) => { Async::Ready(item) => {
self.item1 = Some(item); self.item1 = Some(item);
self.fut1.take(); self.fut1.take();
self.fut2 = Some(Box::new(T2::from_request( let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
&self.req, let item2 = match reply.into() {
self.cfg2.as_ref(), 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), Async::NotReady => return Ok(Async::NotReady),
} }
@@ -522,12 +758,31 @@ where
if self.fut2.is_some() { if self.fut2.is_some() {
match self.fut2.as_mut().unwrap().poll()? { match self.fut2.as_mut().unwrap().poll()? {
Async::Ready(item) => { Async::Ready(item) => {
self.item2 = Some(item);
self.fut2.take(); self.fut2.take();
self.fut3 = Some(Box::new(T3::from_request( let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
&self.req, let item3 = match reply.into() {
self.cfg3.as_ref(), 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), Async::NotReady => return Ok(Async::NotReady),
} }
@@ -539,19 +794,18 @@ where
}; };
let hnd: &mut F = unsafe { &mut *self.hnd.get() }; let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)( let item =
self.item1.take().unwrap(), match (*hnd)(self.item1.take().unwrap(), self.item2.take().unwrap(), item)
self.item2.take().unwrap(), .respond_to(&self.req)
item, {
).respond_to(self.req.drop_state()) Ok(item) => item.into(),
{ Err(err) => return Err(err.into()),
Ok(item) => item.into(), };
Err(err) => return Err(err.into()),
};
match item.into() { match item.into() {
ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), AsyncResultItem::Err(err) => return Err(err),
ReplyItem::Future(fut) => self.fut4 = Some(fut), AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => self.fut4 = Some(fut),
} }
self.poll() self.poll()

View File

@@ -5,7 +5,6 @@ use std::time::Duration;
use std::{fmt, io, str}; use std::{fmt, io, str};
use base64; use base64;
use byteorder::{ByteOrder, NetworkEndian};
use bytes::Bytes; use bytes::Bytes;
use cookie::Cookie; use cookie::Cookie;
use futures::unsync::mpsc::{unbounded, UnboundedSender}; use futures::unsync::mpsc::{unbounded, UnboundedSender};
@@ -23,12 +22,14 @@ use header::IntoHeaderValue;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use payload::PayloadHelper; use payload::PayloadHelper;
use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, use client::{
HttpResponseParserError, SendRequest, SendRequestError}; ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse,
HttpResponseParserError, SendRequest, SendRequestError,
};
use super::frame::Frame; use super::frame::Frame;
use super::proto::{CloseCode, OpCode}; use super::proto::{CloseReason, OpCode};
use super::{Message, ProtocolError}; use super::{Message, ProtocolError, WsWriter};
/// Websocket client error /// Websocket client error
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
@@ -122,7 +123,7 @@ impl Client {
/// Create new websocket connection with custom `ClientConnector` /// Create new websocket connection with custom `ClientConnector`
pub fn with_connector<S: AsRef<str>>( pub fn with_connector<S: AsRef<str>>(
uri: S, conn: Addr<Unsync, ClientConnector> uri: S, conn: Addr<Unsync, ClientConnector>,
) -> Client { ) -> Client {
let mut cl = Client { let mut cl = Client {
request: ClientRequest::build(), request: ClientRequest::build(),
@@ -219,8 +220,7 @@ impl Client {
self.request.upgrade(); self.request.upgrade();
self.request.set_header(header::UPGRADE, "websocket"); self.request.set_header(header::UPGRADE, "websocket");
self.request.set_header(header::CONNECTION, "upgrade"); self.request.set_header(header::CONNECTION, "upgrade");
self.request self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13");
.set_header(header::SEC_WEBSOCKET_VERSION, "13");
self.request.with_connector(self.conn.clone()); self.request.with_connector(self.conn.clone());
if let Some(protocols) = self.protocols.take() { if let Some(protocols) = self.protocols.take() {
@@ -236,7 +236,9 @@ impl Client {
return ClientHandshake::error(ClientError::InvalidUrl); return ClientHandshake::error(ClientError::InvalidUrl);
} }
if let Some(scheme) = request.uri().scheme_part() { if let Some(scheme) = request.uri().scheme_part() {
if scheme != "http" && scheme != "https" && scheme != "ws" if scheme != "http"
&& scheme != "https"
&& scheme != "ws"
&& scheme != "wss" && scheme != "wss"
{ {
return ClientHandshake::error(ClientError::InvalidUrl); return ClientHandshake::error(ClientError::InvalidUrl);
@@ -259,7 +261,7 @@ struct Inner {
/// Future that implementes client websocket handshake process. /// 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. /// can be used for reading and writing websocket frames.
pub struct ClientHandshake { pub struct ClientHandshake {
request: Option<SendRequest>, request: Option<SendRequest>,
@@ -395,10 +397,7 @@ impl Future for ClientHandshake {
encoded, encoded,
key key
); );
return Err(ClientError::InvalidChallengeResponse( return Err(ClientError::InvalidChallengeResponse(encoded, key.clone()));
encoded,
key.clone(),
));
} }
} else { } else {
trace!("Missing SEC-WEBSOCKET-ACCEPT header"); trace!("Missing SEC-WEBSOCKET-ACCEPT header");
@@ -468,10 +467,8 @@ impl Stream for ClientReader {
} }
OpCode::Close => { OpCode::Close => {
inner.closed = true; inner.closed = true;
let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; let close_reason = Frame::parse_close_payload(&payload);
Ok(Async::Ready(Some(Message::Close(CloseCode::from( Ok(Async::Ready(Some(Message::Close(close_reason))))
code,
)))))
} }
OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( OpCode::Ping => Ok(Async::Ready(Some(Message::Ping(
String::from_utf8_lossy(payload.as_ref()).into(), String::from_utf8_lossy(payload.as_ref()).into(),
@@ -506,13 +503,6 @@ pub struct ClientWriter {
inner: Rc<UnsafeCell<Inner>>, inner: Rc<UnsafeCell<Inner>>,
} }
impl ClientWriter {
#[inline]
fn as_mut(&mut self) -> &mut Inner {
unsafe { &mut *self.inner.get() }
}
}
impl ClientWriter { impl ClientWriter {
/// Write payload /// Write payload
#[inline] #[inline]
@@ -524,6 +514,11 @@ impl ClientWriter {
} }
} }
#[inline]
fn as_mut(&mut self) -> &mut Inner {
unsafe { &mut *self.inner.get() }
}
/// Send text frame /// Send text frame
#[inline] #[inline]
pub fn text<T: Into<Binary>>(&mut self, text: T) { pub fn text<T: Into<Binary>>(&mut self, text: T) {
@@ -539,28 +534,50 @@ impl ClientWriter {
/// Send ping frame /// Send ping frame
#[inline] #[inline]
pub fn ping(&mut self, message: &str) { pub fn ping(&mut self, message: &str) {
self.write(Frame::message( self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true));
Vec::from(message),
OpCode::Ping,
true,
true,
));
} }
/// Send pong frame /// Send pong frame
#[inline] #[inline]
pub fn pong(&mut self, message: &str) { pub fn pong(&mut self, message: &str) {
self.write(Frame::message( self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true));
Vec::from(message),
OpCode::Pong,
true,
true,
));
} }
/// Send close frame /// Send close frame
#[inline] #[inline]
pub fn close(&mut self, code: CloseCode, reason: &str) { pub fn close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(code, reason, true)); 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,12 +2,13 @@ use futures::sync::oneshot::Sender;
use futures::unsync::oneshot; use futures::unsync::oneshot;
use futures::{Async, Poll}; use futures::{Async, Poll};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::mem;
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
use actix::fut::ActorFuture; use actix::fut::ActorFuture;
use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, use actix::{
SpawnHandle, Syn, Unsync}; Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
Syn, Unsync,
};
use body::{Binary, Body}; use body::{Binary, Body};
use context::{ActorHttpContext, Drain, Frame as ContextFrame}; use context::{ActorHttpContext, Drain, Frame as ContextFrame};
@@ -15,7 +16,8 @@ use error::{Error, ErrorInternalServerError};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use ws::frame::Frame; use ws::frame::Frame;
use ws::proto::{CloseCode, OpCode}; use ws::proto::{CloseReason, OpCode};
use ws::WsWriter;
/// Execution context for `WebSockets` actors /// Execution context for `WebSockets` actors
pub struct WebsocketContext<A, S = ()> pub struct WebsocketContext<A, S = ()>
@@ -64,7 +66,8 @@ where
#[doc(hidden)] #[doc(hidden)]
#[inline] #[inline]
fn waiting(&self) -> bool { fn waiting(&self) -> bool {
self.inner.waiting() || self.inner.state() == ActorState::Stopping self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped || self.inner.state() == ActorState::Stopped
} }
@@ -141,6 +144,14 @@ where
&mut self.request &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 /// Send text frame
#[inline] #[inline]
pub fn text<T: Into<Binary>>(&mut self, text: T) { pub fn text<T: Into<Binary>>(&mut self, text: T) {
@@ -177,16 +188,8 @@ where
/// Send close frame /// Send close frame
#[inline] #[inline]
pub fn close(&mut self, code: CloseCode, reason: &str) { pub fn close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(code, reason, false)); self.write(Frame::close(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)
} }
/// Check if connection still open /// Check if connection still open
@@ -200,7 +203,9 @@ where
if self.stream.is_none() { if self.stream.is_none() {
self.stream = Some(SmallVec::new()); 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(); self.inner.modify();
} }
@@ -212,6 +217,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> impl<A, S> ActorHttpContext for WebsocketContext<A, S>
where where
A: Actor<Context = Self>, A: Actor<Context = Self>,
@@ -224,8 +265,7 @@ where
} }
fn poll(&mut self) -> Poll<Option<SmallVec<[ContextFrame; 4]>>, Error> { fn poll(&mut self) -> Poll<Option<SmallVec<[ContextFrame; 4]>>, Error> {
let ctx: &mut WebsocketContext<A, S> = let ctx: &mut WebsocketContext<A, S> = unsafe { &mut *(self as *mut _) };
unsafe { mem::transmute(self as &mut WebsocketContext<A, S>) };
if self.inner.alive() && self.inner.poll(ctx).is_err() { if self.inner.alive() && self.inner.poll(ctx).is_err() {
return Err(ErrorInternalServerError("error")); 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 byteorder::{BigEndian, ByteOrder, NetworkEndian};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
use futures::{Async, Poll, Stream}; use futures::{Async, Poll, Stream};
use rand; use rand;
use std::iter::FromIterator; use std::{fmt, ptr};
use std::{fmt, mem, ptr};
use body::Binary; use body::Binary;
use error::PayloadError; use error::PayloadError;
use payload::PayloadHelper; use payload::PayloadHelper;
use ws::ProtocolError;
use ws::mask::apply_mask; 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. /// A struct representing a `WebSocket` frame.
#[derive(Debug)] #[derive(Debug)]
@@ -29,21 +29,19 @@ impl Frame {
/// Create a new Close control frame. /// Create a new Close control frame.
#[inline] #[inline]
pub fn close(code: CloseCode, reason: &str, genmask: bool) -> Binary { pub fn close(reason: Option<CloseReason>, genmask: bool) -> Binary {
let raw: [u8; 2] = unsafe { let payload = match reason {
let u: u16 = code.into(); None => Vec::new(),
mem::transmute(u.to_be()) Some(reason) => {
}; let mut code_bytes = [0; 2];
NetworkEndian::write_u16(&mut code_bytes, reason.code.into());
let payload = if let CloseCode::Empty = code { let mut payload = Vec::from(&code_bytes[..]);
Vec::new() if let Some(description) = reason.description {
} else { payload.extend(description.as_bytes());
Vec::from_iter( }
raw[..] payload
.iter() }
.chain(reason.as_bytes().iter())
.cloned(),
)
}; };
Frame::message(payload, OpCode::Close, true, genmask) Frame::message(payload, OpCode::Close, true, genmask)
@@ -51,7 +49,7 @@ impl Frame {
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
fn read_copy_md<S>( 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> ) -> Poll<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError>
where where
S: Stream<Item = Bytes, Error = PayloadError>, S: Stream<Item = Bytes, Error = PayloadError>,
@@ -125,17 +123,11 @@ impl Frame {
None None
}; };
Ok(Async::Ready(Some(( Ok(Async::Ready(Some((idx, finished, opcode, length, mask))))
idx,
finished,
opcode,
length,
mask,
))))
} }
fn read_chunk_md( fn read_chunk_md(
chunk: &[u8], server: bool, max_size: usize chunk: &[u8], server: bool, max_size: usize,
) -> Poll<(usize, bool, OpCode, usize, Option<u32>), ProtocolError> { ) -> Poll<(usize, bool, OpCode, usize, Option<u32>), ProtocolError> {
let chunk_len = chunk.len(); let chunk_len = chunk.len();
@@ -206,7 +198,7 @@ impl Frame {
/// Parse the input stream into a frame. /// Parse the input stream into a frame.
pub fn parse<S>( 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> ) -> Poll<Option<Frame>, ProtocolError>
where where
S: Stream<Item = Bytes, Error = PayloadError>, S: Stream<Item = Bytes, Error = PayloadError>,
@@ -266,10 +258,9 @@ impl Frame {
// unmask // unmask
if let Some(mask) = mask { if let Some(mask) = mask {
#[allow(mutable_transmutes)]
let p: &mut [u8] = unsafe { let p: &mut [u8] = unsafe {
let ptr: &[u8] = &data; let ptr: &[u8] = &data;
mem::transmute(ptr) &mut *(ptr as *const _ as *mut _)
}; };
apply_mask(p, mask); apply_mask(p, mask);
} }
@@ -281,9 +272,25 @@ 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 /// Generate binary representation
pub fn message<B: Into<Binary>>( pub fn message<B: Into<Binary>>(
data: B, code: OpCode, finished: bool, genmask: bool data: B, code: OpCode, finished: bool, genmask: bool,
) -> Binary { ) -> Binary {
let payload = data.into(); let payload = data.into();
let one: u8 = if finished { let one: u8 = if finished {
@@ -518,10 +525,17 @@ mod tests {
#[test] #[test]
fn test_close_frame() { 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]; let mut v = vec![136u8, 6u8, 3u8, 232u8];
v.extend(b"data"); v.extend(b"data");
assert_eq!(frame, v.into()); 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) //! 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::cmp::min;
use std::mem::uninitialized; use std::mem::uninitialized;
use std::ptr::copy_nonoverlapping; use std::ptr::copy_nonoverlapping;

View File

@@ -43,7 +43,6 @@
//! # .finish(); //! # .finish();
//! # } //! # }
//! ``` //! ```
use byteorder::{ByteOrder, NetworkEndian};
use bytes::Bytes; use bytes::Bytes;
use futures::{Async, Poll, Stream}; use futures::{Async, Poll, Stream};
use http::{header, Method, StatusCode}; use http::{header, Method, StatusCode};
@@ -63,11 +62,12 @@ mod frame;
mod mask; mod mask;
mod proto; mod proto;
pub use self::client::{Client, ClientError, ClientHandshake, ClientReader, ClientWriter}; pub use self::client::{
Client, ClientError, ClientHandshake, ClientReader, ClientWriter,
};
pub use self::context::WebsocketContext; pub use self::context::WebsocketContext;
pub use self::frame::Frame; pub use self::frame::Frame;
pub use self::proto::CloseCode; pub use self::proto::{CloseCode, CloseReason, OpCode};
pub use self::proto::OpCode;
/// Websocket protocol errors /// Websocket protocol errors
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
@@ -164,7 +164,7 @@ pub enum Message {
Binary(Binary), Binary(Binary),
Ping(String), Ping(String),
Pong(String), Pong(String),
Close(CloseCode), Close(Option<CloseReason>),
} }
/// Do websocket handshake and start actor /// Do websocket handshake and start actor
@@ -191,7 +191,7 @@ where
// /// the returned response headers contain the first protocol in this list // /// the returned response headers contain the first protocol in this list
// /// which the server also knows. // /// which the server also knows.
pub fn handshake<S>( pub fn handshake<S>(
req: &HttpRequest<S> req: &HttpRequest<S>,
) -> Result<HttpResponseBuilder, HandshakeError> { ) -> Result<HttpResponseBuilder, HandshakeError> {
// WebSocket accepts only GET // WebSocket accepts only GET
if *req.method() != Method::GET { if *req.method() != Method::GET {
@@ -218,9 +218,7 @@ pub fn handshake<S>(
} }
// check supported version // check supported version
if !req.headers() if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
.contains_key(header::SEC_WEBSOCKET_VERSION)
{
return Err(HandshakeError::NoVersionHeader); return Err(HandshakeError::NoVersionHeader);
} }
let supported_ver = { let supported_ver = {
@@ -310,15 +308,8 @@ where
} }
OpCode::Close => { OpCode::Close => {
self.closed = true; self.closed = true;
let close_code = if payload.len() >= 2 { let close_reason = Frame::parse_close_payload(&payload);
let raw_code = Ok(Async::Ready(Some(Message::Close(close_reason))))
NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
CloseCode::from(raw_code)
} else {
CloseCode::Status
};
Ok(Async::Ready(Some(Message::Close(close_code))))
} }
OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( OpCode::Ping => Ok(Async::Ready(Some(Message::Ping(
String::from_utf8_lossy(payload.as_ref()).into(), String::from_utf8_lossy(payload.as_ref()).into(),
@@ -349,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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -382,10 +387,7 @@ mod tests {
); );
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert( headers.insert(header::UPGRADE, header::HeaderValue::from_static("test"));
header::UPGRADE,
header::HeaderValue::from_static("test"),
);
let req = HttpRequest::new( let req = HttpRequest::new(
Method::GET, Method::GET,
Uri::from_str("/").unwrap(), Uri::from_str("/").unwrap(),

View File

@@ -90,10 +90,6 @@ pub enum CloseCode {
/// endpoint that understands only text data MAY send this if it /// endpoint that understands only text data MAY send this if it
/// receives a binary message). /// receives a binary message).
Unsupported, 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 /// 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 /// 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 /// of the handler will be called with the error. However, if the connection
@@ -138,8 +134,6 @@ pub enum CloseCode {
#[doc(hidden)] #[doc(hidden)]
Tls, Tls,
#[doc(hidden)] #[doc(hidden)]
Empty,
#[doc(hidden)]
Other(u16), Other(u16),
} }
@@ -150,7 +144,6 @@ impl Into<u16> for CloseCode {
Away => 1001, Away => 1001,
Protocol => 1002, Protocol => 1002,
Unsupported => 1003, Unsupported => 1003,
Status => 1005,
Abnormal => 1006, Abnormal => 1006,
Invalid => 1007, Invalid => 1007,
Policy => 1008, Policy => 1008,
@@ -160,7 +153,6 @@ impl Into<u16> for CloseCode {
Restart => 1012, Restart => 1012,
Again => 1013, Again => 1013,
Tls => 1015, Tls => 1015,
Empty => 0,
Other(code) => code, Other(code) => code,
} }
} }
@@ -173,7 +165,6 @@ impl From<u16> for CloseCode {
1001 => Away, 1001 => Away,
1002 => Protocol, 1002 => Protocol,
1003 => Unsupported, 1003 => Unsupported,
1005 => Status,
1006 => Abnormal, 1006 => Abnormal,
1007 => Invalid, 1007 => Invalid,
1008 => Policy, 1008 => Policy,
@@ -183,12 +174,35 @@ impl From<u16> for CloseCode {
1012 => Restart, 1012 => Restart,
1013 => Again, 1013 => Again,
1015 => Tls, 1015 => Tls,
0 => Empty,
_ => Other(code), _ => 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"; static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
// TODO: hash is always same size, we dont need String // 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(1001u16), CloseCode::Away);
assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol); assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol);
assert_eq!(CloseCode::from(1003u16), CloseCode::Unsupported); 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(1006u16), CloseCode::Abnormal);
assert_eq!(CloseCode::from(1007u16), CloseCode::Invalid); assert_eq!(CloseCode::from(1007u16), CloseCode::Invalid);
assert_eq!(CloseCode::from(1008u16), CloseCode::Policy); assert_eq!(CloseCode::from(1008u16), CloseCode::Policy);
@@ -279,7 +292,6 @@ mod test {
assert_eq!(CloseCode::from(1012u16), CloseCode::Restart); assert_eq!(CloseCode::from(1012u16), CloseCode::Restart);
assert_eq!(CloseCode::from(1013u16), CloseCode::Again); assert_eq!(CloseCode::from(1013u16), CloseCode::Again);
assert_eq!(CloseCode::from(1015u16), CloseCode::Tls); assert_eq!(CloseCode::from(1015u16), CloseCode::Tls);
assert_eq!(CloseCode::from(0u16), CloseCode::Empty);
assert_eq!(CloseCode::from(2000u16), CloseCode::Other(2000)); assert_eq!(CloseCode::from(2000u16), CloseCode::Other(2000));
} }
@@ -289,7 +301,6 @@ mod test {
assert_eq!(1001u16, Into::<u16>::into(CloseCode::Away)); assert_eq!(1001u16, Into::<u16>::into(CloseCode::Away));
assert_eq!(1002u16, Into::<u16>::into(CloseCode::Protocol)); assert_eq!(1002u16, Into::<u16>::into(CloseCode::Protocol));
assert_eq!(1003u16, Into::<u16>::into(CloseCode::Unsupported)); 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!(1006u16, Into::<u16>::into(CloseCode::Abnormal));
assert_eq!(1007u16, Into::<u16>::into(CloseCode::Invalid)); assert_eq!(1007u16, Into::<u16>::into(CloseCode::Invalid));
assert_eq!(1008u16, Into::<u16>::into(CloseCode::Policy)); assert_eq!(1008u16, Into::<u16>::into(CloseCode::Policy));
@@ -299,7 +310,6 @@ mod test {
assert_eq!(1012u16, Into::<u16>::into(CloseCode::Restart)); assert_eq!(1012u16, Into::<u16>::into(CloseCode::Restart));
assert_eq!(1013u16, Into::<u16>::into(CloseCode::Again)); assert_eq!(1013u16, Into::<u16>::into(CloseCode::Again));
assert_eq!(1015u16, Into::<u16>::into(CloseCode::Tls)); 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))); 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;
extern crate actix_web; extern crate actix_web;
extern crate bytes; extern crate bytes;
@@ -9,8 +10,8 @@ use std::io::Read;
use bytes::Bytes; use bytes::Bytes;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use futures::Future;
use futures::stream::once; use futures::stream::once;
use futures::Future;
use rand::Rng; use rand::Rng;
use actix_web::*; use actix_web::*;
@@ -72,10 +73,7 @@ fn test_with_query_parameter() {
}) })
}); });
let request = srv.get() let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap();
.uri(srv.url("/?qp=5").as_str())
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@@ -124,7 +122,8 @@ fn test_client_gzip_encoding() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(STR) .body(STR)
.unwrap(); .unwrap();
@@ -153,7 +152,8 @@ fn test_client_gzip_encoding_large() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@@ -185,7 +185,8 @@ fn test_client_gzip_encoding_large_random() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@@ -213,7 +214,8 @@ fn test_client_brotli_encoding() {
}); });
// client request // client request
let request = srv.client(http::Method::POST, "/") let request = srv
.client(http::Method::POST, "/")
.content_encoding(http::ContentEncoding::Br) .content_encoding(http::ContentEncoding::Br)
.body(STR) .body(STR)
.unwrap(); .unwrap();
@@ -246,7 +248,8 @@ fn test_client_brotli_encoding_large_random() {
}); });
// client request // client request
let request = srv.client(http::Method::POST, "/") let request = srv
.client(http::Method::POST, "/")
.content_encoding(http::ContentEncoding::Br) .content_encoding(http::ContentEncoding::Br)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@@ -275,7 +278,8 @@ fn test_client_deflate_encoding() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(STR) .body(STR)
.unwrap(); .unwrap();
@@ -308,7 +312,8 @@ fn test_client_deflate_encoding_large_random() {
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@@ -338,9 +343,7 @@ fn test_client_streaming_explicit() {
let body = once(Ok(Bytes::from_static(STR.as_ref()))); let body = once(Ok(Bytes::from_static(STR.as_ref())));
let request = srv.get() let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
.body(Body::Streaming(Box::new(body)))
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@@ -413,7 +416,8 @@ fn test_client_cookie_handling() {
}) })
}); });
let request = srv.get() let request = srv
.get()
.cookie(cookie1.clone()) .cookie(cookie1.clone())
.cookie(cookie2.clone()) .cookie(cookie2.clone())
.finish() .finish()

View File

@@ -7,10 +7,18 @@ extern crate http;
extern crate tokio_core; extern crate tokio_core;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
extern crate serde_json;
use std::io;
use std::time::Duration;
use actix::*;
use actix_web::*; use actix_web::*;
use bytes::Bytes; use bytes::Bytes;
use futures::Future;
use http::StatusCode; use http::StatusCode;
use serde_json::Value;
use tokio_core::reactor::Timeout;
#[derive(Deserialize)] #[derive(Deserialize)]
struct PParam { struct PParam {
@@ -26,10 +34,7 @@ fn test_path_extractor() {
}); });
// client request // client request
let request = srv.get() let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap();
.uri(srv.url("/test/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@@ -47,7 +52,8 @@ fn test_query_extractor() {
}); });
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/index.html?username=test")) .uri(srv.url("/index.html?username=test"))
.finish() .finish()
.unwrap(); .unwrap();
@@ -59,14 +65,39 @@ fn test_query_extractor() {
assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
// client request // client request
let request = srv.get() let request = srv.get().uri(srv.url("/index.html")).finish().unwrap();
.uri(srv.url("/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!(response.status(), StatusCode::BAD_REQUEST);
} }
#[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] #[test]
fn test_path_and_query_extractor() { fn test_path_and_query_extractor() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
@@ -78,7 +109,8 @@ fn test_path_and_query_extractor() {
}); });
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html?username=test2")) .uri(srv.url("/test1/index.html?username=test2"))
.finish() .finish()
.unwrap(); .unwrap();
@@ -90,7 +122,8 @@ fn test_path_and_query_extractor() {
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!"));
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.finish() .finish()
.unwrap(); .unwrap();
@@ -110,7 +143,8 @@ fn test_path_and_query_extractor2() {
}); });
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html?username=test2")) .uri(srv.url("/test1/index.html?username=test2"))
.finish() .finish()
.unwrap(); .unwrap();
@@ -122,7 +156,8 @@ fn test_path_and_query_extractor2() {
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!"));
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.finish() .finish()
.unwrap(); .unwrap();
@@ -130,6 +165,351 @@ fn test_path_and_query_extractor2() {
assert_eq!(response.status(), StatusCode::BAD_REQUEST); 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);
}
#[test]
fn test_scope_and_path_extractor() {
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/sc", |scope| {
scope.resource("/{num}/index.html", |r| {
r.route()
.with(|p: Path<(usize,)>| format!("Welcome {}!", p.0))
})
})
});
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome 10!"));
// client request
let request = srv
.get()
.uri(srv.url("/sc/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_nested_scope_and_path_extractor() {
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/sc", |scope| {
scope.nested("/{num}", |scope| {
scope.resource("/{num}/index.html", |r| {
r.route().with(|p: Path<(usize, usize)>| {
format!("Welcome {} {}!", p.0, p.1)
})
})
})
})
});
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/12/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome 10 12!"));
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[cfg(actix_impl_trait)]
fn test_impl_trait(
data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)))
}
#[cfg(actix_impl_trait)]
fn test_impl_trait_err(
_data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
.and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other")))
}
#[cfg(actix_impl_trait)]
#[test]
fn test_path_and_query_extractor2_async4_impl_trait() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_async(test_impl_trait)
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
// client request
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[cfg(actix_impl_trait)]
#[test]
fn test_path_and_query_extractor2_async4_impl_trait_err() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_async(test_impl_trait_err)
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test] #[test]
fn test_non_ascii_route() { fn test_non_ascii_route() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
@@ -137,7 +517,8 @@ fn test_non_ascii_route() {
}); });
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/中文/index.html")) .uri(srv.url("/中文/index.html"))
.finish() .finish()
.unwrap(); .unwrap();
@@ -158,7 +539,8 @@ fn test_unsafe_path_route() {
}); });
// client request // client request
let request = srv.get() let request = srv
.get()
.uri(srv.url("/test/http%3A%2F%2Fexample.com")) .uri(srv.url("/test/http%3A%2F%2Fexample.com"))
.finish() .finish()
.unwrap(); .unwrap();

999
tests/test_middleware.rs Normal file
View File

@@ -0,0 +1,999 @@
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::error::{Error, ErrorInternalServerError};
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_sync_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new()
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
thread::sleep(Duration::from_millis(50));
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
#[test]
fn test_async_scope_middleware() {
let num1 = Arc::new(AtomicUsize::new(0));
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_async_scope_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/scope", |scope| {
scope
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
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);
}
#[test]
fn test_async_sync_resource_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
let mw2 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(mw2);
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
thread::sleep(Duration::from_millis(40));
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
struct MiddlewareWithErr;
impl<S> middleware::Middleware<S> for MiddlewareWithErr {
fn start(&self, _req: &mut HttpRequest<S>) -> Result<middleware::Started, Error> {
Err(ErrorInternalServerError("middleware error"))
}
}
struct MiddlewareAsyncWithErr;
impl<S> middleware::Middleware<S> for MiddlewareAsyncWithErr {
fn start(&self, _req: &mut HttpRequest<S>) -> Result<middleware::Started, Error> {
Ok(middleware::Started::Future(Box::new(future::err(
ErrorInternalServerError("middleware error"),
))))
}
}
#[test]
fn test_middleware_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new()
.middleware(mw1)
.middleware(MiddlewareWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_middleware_async_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new()
.middleware(mw1)
.middleware(MiddlewareAsyncWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().scope("/scope", |scope| {
scope
.middleware(mw1)
.middleware(MiddlewareWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_async_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().scope("/scope", |scope| {
scope
.middleware(mw1)
.middleware(MiddlewareAsyncWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(MiddlewareWithErr);
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_async_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(MiddlewareAsyncWithErr);
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}

View File

@@ -14,16 +14,15 @@ extern crate brotli2;
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder}; use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use flate2::Compression;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
use flate2::Compression;
use futures::stream::once; use futures::stream::once;
use futures::{future, Future, Stream}; use futures::{Future, Stream};
use h2::client as h2client; use h2::client as h2client;
use modhttp::Request; use modhttp::Request;
use rand::Rng; use rand::Rng;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{mpsc, Arc}; use std::sync::{mpsc, Arc};
use std::{net, thread, time}; use std::{net, thread, time};
use tokio_core::net::TcpStream; use tokio_core::net::TcpStream;
@@ -55,6 +54,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World"; Hello World Hello World Hello World Hello World Hello World";
#[test] #[test]
#[cfg(unix)]
fn test_start() { fn test_start() {
let _ = test::TestServer::unused_addr(); let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
@@ -62,11 +62,9 @@ fn test_start() {
thread::spawn(move || { thread::spawn(move || {
let sys = System::new("test"); let sys = System::new("test");
let srv = server::new(|| { let srv = server::new(|| {
vec![ vec![App::new().resource("/", |r| {
App::new().resource("/", |r| { r.method(http::Method::GET).f(|_| HttpResponse::Ok())
r.method(http::Method::GET).f(|_| HttpResponse::Ok()) })]
}),
]
}); });
let srv = srv.bind("127.0.0.1:0").unwrap(); let srv = srv.bind("127.0.0.1:0").unwrap();
@@ -89,12 +87,17 @@ fn test_start() {
// pause // pause
let _ = srv_addr.send(server::PauseServer).wait(); let _ = srv_addr.send(server::PauseServer).wait();
thread::sleep(time::Duration::from_millis(200)); thread::sleep(time::Duration::from_millis(200));
assert!(net::TcpStream::connect(addr).is_err()); {
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.timeout(time::Duration::from_millis(200))
.finish()
.unwrap();
assert!(sys.run_until_complete(req.send()).is_err());
}
// resume // resume
let _ = srv_addr.send(server::ResumeServer).wait(); let _ = srv_addr.send(server::ResumeServer).wait();
thread::sleep(time::Duration::from_millis(200)); thread::sleep(time::Duration::from_millis(400));
{ {
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish() .finish()
@@ -113,11 +116,9 @@ fn test_shutdown() {
thread::spawn(move || { thread::spawn(move || {
let sys = System::new("test"); let sys = System::new("test");
let srv = server::new(|| { let srv = server::new(|| {
vec![ vec![App::new().resource("/", |r| {
App::new().resource("/", |r| { r.method(http::Method::GET).f(|_| HttpResponse::Ok())
r.method(http::Method::GET).f(|_| HttpResponse::Ok()) })]
}),
]
}); });
let srv = srv.bind("127.0.0.1:0").unwrap(); let srv = srv.bind("127.0.0.1:0").unwrap();
@@ -342,11 +343,7 @@ fn test_body_br_streaming() {
#[test] #[test]
fn test_head_empty() { fn test_head_empty() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.handler(|_| { app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish())
HttpResponse::Ok()
.content_length(STR.len() as u64)
.finish()
})
}); });
let request = srv.head().finish().unwrap(); let request = srv.head().finish().unwrap();
@@ -534,7 +531,8 @@ fn test_gzip_encoding() {
e.write_all(STR.as_ref()).unwrap(); e.write_all(STR.as_ref()).unwrap();
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "gzip") .header(http::header::CONTENT_ENCODING, "gzip")
.body(enc.clone()) .body(enc.clone())
.unwrap(); .unwrap();
@@ -566,7 +564,8 @@ fn test_gzip_encoding_large() {
e.write_all(data.as_ref()).unwrap(); e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "gzip") .header(http::header::CONTENT_ENCODING, "gzip")
.body(enc.clone()) .body(enc.clone())
.unwrap(); .unwrap();
@@ -602,7 +601,8 @@ fn test_reading_gzip_encoding_large_random() {
e.write_all(data.as_ref()).unwrap(); e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "gzip") .header(http::header::CONTENT_ENCODING, "gzip")
.body(enc.clone()) .body(enc.clone())
.unwrap(); .unwrap();
@@ -634,7 +634,8 @@ fn test_reading_deflate_encoding() {
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "deflate") .header(http::header::CONTENT_ENCODING, "deflate")
.body(enc) .body(enc)
.unwrap(); .unwrap();
@@ -666,7 +667,8 @@ fn test_reading_deflate_encoding_large() {
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "deflate") .header(http::header::CONTENT_ENCODING, "deflate")
.body(enc) .body(enc)
.unwrap(); .unwrap();
@@ -702,7 +704,8 @@ fn test_reading_deflate_encoding_large_random() {
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "deflate") .header(http::header::CONTENT_ENCODING, "deflate")
.body(enc) .body(enc)
.unwrap(); .unwrap();
@@ -735,7 +738,8 @@ fn test_brotli_encoding() {
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "br") .header(http::header::CONTENT_ENCODING, "br")
.body(enc) .body(enc)
.unwrap(); .unwrap();
@@ -768,7 +772,8 @@ fn test_brotli_encoding_large() {
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
// client request // client request
let request = srv.post() let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "br") .header(http::header::CONTENT_ENCODING, "br")
.body(enc) .body(enc)
.unwrap(); .unwrap();
@@ -789,7 +794,8 @@ fn test_h2() {
let handle = core.handle(); let handle = core.handle();
let tcp = TcpStream::connect(&addr, &handle); let tcp = TcpStream::connect(&addr, &handle);
let tcp = tcp.then(|res| h2client::handshake(res.unwrap())) let tcp = tcp
.then(|res| h2client::handshake(res.unwrap()))
.then(move |res| { .then(move |res| {
let (mut client, h2) = res.unwrap(); let (mut client, h2) = res.unwrap();
@@ -814,7 +820,7 @@ fn test_h2() {
}) })
}); });
let _res = core.run(tcp); 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] #[test]
@@ -827,122 +833,3 @@ fn test_application() {
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); 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);
}
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);
}

View File

@@ -27,7 +27,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text), ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Close(reason) => ctx.close(reason, ""), ws::Message::Close(reason) => ctx.close(reason),
_ => (), _ => (),
} }
} }
@@ -46,18 +46,19 @@ fn test_simple() {
let (item, reader) = srv.execute(reader.into_future()).unwrap(); let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!( assert_eq!(
item, item,
Some(ws::Message::Binary( Some(ws::Message::Binary(Bytes::from_static(b"text").into()))
Bytes::from_static(b"text").into()
))
); );
writer.ping("ping"); writer.ping("ping");
let (item, reader) = srv.execute(reader.into_future()).unwrap(); let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); 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(); 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] #[test]
@@ -65,9 +66,21 @@ fn test_empty_close_code() {
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
let (reader, mut writer) = srv.ws().unwrap(); let (reader, mut writer) = srv.ws().unwrap();
writer.close(ws::CloseCode::Empty, ""); writer.close(None);
let (item, _) = srv.execute(reader.into_future()).unwrap(); let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Status))); 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] #[test]
@@ -102,10 +115,7 @@ fn test_large_bin() {
writer.binary(data.clone()); writer.binary(data.clone());
let (item, r) = srv.execute(reader.into_future()).unwrap(); let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r; reader = r;
assert_eq!( assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone()))));
item,
Some(ws::Message::Binary(Binary::from(data.clone())))
);
} }
} }
@@ -147,7 +157,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for Ws2 {
ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text), ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Close(reason) => ctx.close(reason, ""), ws::Message::Close(reason) => ctx.close(reason),
_ => (), _ => (),
} }
} }
@@ -216,19 +226,17 @@ fn test_ws_server_ssl() {
.set_certificate_chain_file("tests/cert.pem") .set_certificate_chain_file("tests/cert.pem")
.unwrap(); .unwrap();
let mut srv = test::TestServer::build() let mut srv = test::TestServer::build().ssl(builder.build()).start(|app| {
.ssl(builder.build()) app.handler(|req| {
.start(|app| { ws::start(
app.handler(|req| { req,
ws::start( Ws2 {
req, count: 0,
Ws2 { bin: false,
count: 0, },
bin: false, )
}, })
) });
})
});
let (mut reader, _writer) = srv.ws().unwrap(); let (mut reader, _writer) = srv.ws().unwrap();
let data = Some(ws::Message::Text("0".repeat(65_536))); let data = Some(ws::Message::Text("0".repeat(65_536)));

View File

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