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

Compare commits

...

215 Commits

Author SHA1 Message Date
88f66d49d0 openssl features 2018-04-10 11:07:54 -07:00
be288fa00a for NamedFile process etag and last modified only if status code is 200 2018-04-10 10:57:53 -07:00
5e6a0aa3df simplier example in readme 2018-04-10 10:39:16 -07:00
fd87eb59f8 remove reference to master 2018-04-10 10:29:10 -07:00
81ac905c7b fix prefix and static file serving #168 2018-04-10 10:16:00 -07:00
bb11fb3d24 update client mod doc string 2018-04-09 21:57:40 -07:00
23eea54776 update cors doc string 2018-04-09 21:39:32 -07:00
2881859400 proper test for CorsBuilder::resource 2018-04-09 21:29:57 -07:00
1686682c19 extend CorsBuilder api to make it more user friendly 2018-04-09 21:11:15 -07:00
d04ff13955 update version 2018-04-09 14:27:13 -07:00
e757dc5a71 clippy warnings 2018-04-09 14:25:30 -07:00
be358db422 CorsBuilder::finish() panics on any configuration error 2018-04-09 14:20:12 -07:00
7df2d6b12a clippy warnings; extend url_for example in user guide 2018-04-09 13:30:38 -07:00
458e6bdcc2 Merge pull request #170 from adwhit/private-cookies
Public, signed and private cookies
2018-04-09 12:54:15 -07:00
0b0bbd6bd9 Merge branch 'master' into private-cookies 2018-04-09 12:54:08 -07:00
5617896780 cleanup doc tests 2018-04-09 10:40:12 -07:00
2b803f30c9 remove CookieSessionBackend::new 2018-04-09 18:33:29 +01:00
9b152acc32 add signed and private cookies 2018-04-09 17:59:28 +01:00
eb66685d1a simplify csrf middleware 2018-04-09 09:49:07 -07:00
b505e682d4 fix session doc test 2018-04-09 09:31:11 -07:00
48e7013997 update guide examples 2018-04-09 07:57:50 -07:00
ff14633b3d simplify CookieSessionBackend; expose max_age cookie setting 2018-04-08 11:05:37 -07:00
37db7d8168 allow to override status code for NamedFile 2018-04-08 10:53:58 -07:00
89bf12605d Merge pull request #165 from tazjin/docs/various-doc-fixes
Various minor documentation fixes
2018-04-07 09:53:19 -07:00
9fb0498437 docs(lib): Add a note about getting started with the API docs
Adds some initial pointers for newcomers to the documentation that
direct them at some of the most commonly used API types.

I based these links on what *I* usually end up looking at when I open
the actix_web docs.
2018-04-07 17:27:53 +02:00
b2a43a3c8d docs(application): Formatting & spelling fixes in module docs 2018-04-07 17:19:11 +02:00
38063b9873 docs(client): Minor formatting and spelling fixes in module docs 2018-04-07 17:00:57 +02:00
1045a6c6f0 docs(README): Minor formatting and spelling fixes 2018-04-07 17:00:39 +02:00
7243c58fce stable rust compatibility 2018-04-06 21:57:45 -07:00
fffaf2bb2d App::route method 2018-04-06 21:18:42 -07:00
7becb95a97 fix guide example 2018-04-06 20:24:49 -07:00
a4b837a1c1 flaky test 2018-04-06 19:45:14 -07:00
542315ce7f simplify StaticFiles 2018-04-06 19:34:55 -07:00
602d78b76c Merge pull request #163 from memoryruins/guide
Guide: edits to the second half
2018-04-06 19:11:12 -07:00
18b706d4fb Guide: tweak to websocket and testing. 2018-04-06 19:44:52 -04:00
94b41fd484 Guide: tweak to database integration. 2018-04-06 19:42:18 -04:00
3a80cb7bf3 Guide: tweak to http/2. 2018-04-06 19:37:14 -04:00
e4a85a53f4 Guide: additional tweaks to Middleware. 2018-04-06 19:35:11 -04:00
1a45dbd768 Guide: additional tweak to testing chapter. 2018-04-06 19:26:07 -04:00
7cff5d9ade Guide: additional tweaks to request and response chapter. 2018-04-06 19:17:03 -04:00
c04e0fdec4 Merge branch 'master' into guide 2018-04-06 19:04:50 -04:00
0f0fe5f148 Guide: updates to the Database integration chapter. 2018-04-06 19:02:11 -04:00
e7f9f5b46d Guide: updates to HTTP/2 chapter. 2018-04-06 18:46:56 -04:00
c3fbba2678 Guide: updates to static file handling chapter. 2018-04-06 18:40:57 -04:00
a88e97edba Guide: updates to Middleware chapter. 2018-04-06 18:29:18 -04:00
1f08100f6f Guide: updates to the WebSockets chapter. 2018-04-06 18:04:42 -04:00
ab60ec6e1d Guide: updates to the Testing chapter. 2018-04-06 18:03:30 -04:00
0fbd05009d Guide: tweaks to the request and response chapter. 2018-04-06 17:31:18 -04:00
fdb7419e24 use actix-web from master 2018-04-06 14:11:04 -07:00
191b53bd7c pin futures 0.1 2018-04-06 13:22:27 -07:00
2d4ee0ee01 make Pause::new public 2018-04-06 12:34:24 -07:00
5bd5f67d79 add Pause message constructors 2018-04-06 12:31:31 -07:00
5d8cbccfe9 Remove article. 2018-04-06 15:12:06 -04:00
8d5fa6ee71 added Pause/Resume for client connector 2018-04-06 11:08:41 -07:00
084104d058 update doc strings for extractors 2018-04-06 10:24:57 -07:00
2c411a04a9 no need for export in doc example 2018-04-06 10:15:06 -07:00
af0c8d893d add shortcut method for client requests 2018-04-06 10:09:31 -07:00
691457fbfe update tests 2018-04-06 09:45:10 -07:00
2dafd9c681 do not re-export HttpServer from server module 2018-04-06 08:40:11 -07:00
12586db15c Merge pull request #160 from memoryruins/guide
Guide: edits to first half
2018-04-05 19:44:42 -07:00
b847bda8ca Merge branch 'master' into guide 2018-04-05 19:44:34 -07:00
2a543001e0 Tweaks to the URL Dispatch chapter. 2018-04-05 22:12:20 -04:00
0f86c596fa Tweaks to Errors chapter. 2018-04-05 21:54:39 -04:00
6c55501252 client connector wait timeout 2018-04-05 18:33:58 -07:00
961edfd21a Tweaks to the Handler chapter. 2018-04-05 21:30:52 -04:00
7f0de705a3 Tweaks to Server chapter. 2018-04-05 20:55:19 -04:00
c2ad65a61d Various tweaks to Application chapter. 2018-04-05 19:43:17 -04:00
3c93e0c654 Add newline for reading source. 2018-04-05 19:25:41 -04:00
a0f1ff7eb3 Add src directory to main.rs and list on first codeblock. 2018-04-05 19:21:29 -04:00
9f45cfe492 Expand note about actix. 2018-04-05 19:12:23 -04:00
46e6641528 Add repository hyperlink and trim repeat. 2018-04-05 18:46:36 -04:00
a3f124685a Remove redundant quickstart paragraph. 2018-04-05 18:32:04 -04:00
800f711cc1 add PayloadConfig 2018-04-04 21:13:48 -07:00
7be4b1f399 clippy warns 2018-04-04 20:24:09 -07:00
eeae0ddab4 start client timeout for response only 2018-04-04 20:15:47 -07:00
c1af59c618 update juniper example 2018-04-04 17:57:02 -07:00
d8a9606162 add connection limits to pool 2018-04-04 16:39:01 -07:00
8038a52287 run test coverage on beta 2018-04-04 08:12:33 -07:00
c273b7ac3f update json example 2018-04-04 08:08:31 -07:00
df21892b5b added extractor configuration 2018-04-03 22:06:18 -07:00
a255a6fb69 use build_response method 2018-04-03 17:37:17 -07:00
b693d5491b Merge pull request #157 from krircc/master
only use diesel::r2d2 feature. no need r2d2_diesel create
2018-04-03 08:18:16 -07:00
56a31ea0ee only use diesel::r2d2 feature. no need r2d2_diesel create 2018-04-03 22:37:53 +08:00
2a269f1111 update changes 2018-04-02 22:08:04 -07:00
fee30d6f47 fix doc test compatibility 2018-04-02 22:01:20 -07:00
476b1fb36a simplify DefaultHeaders middleware 2018-04-02 21:43:50 -07:00
3b93bff602 add ErrorHandlers middleware 2018-04-02 21:37:00 -07:00
d292c5023f add String and Bytes extractor 2018-04-02 16:19:18 -07:00
ef6f310060 update urlencoded example in guide 2018-04-02 15:08:49 -07:00
a6cbdde43f add extractor for Binary type; move all extractors to separate module 2018-04-02 14:55:42 -07:00
cbf4c61eb5 add urlencoded body extractor 2018-04-02 14:00:18 -07:00
280c8d87f8 expose ResourceType 2018-04-02 11:18:31 -07:00
83bf852192 Fix logger request duration calculation 2018-04-02 11:09:24 -07:00
9d39f441e9 Merge pull request #153 from rofrol/add-header-for-juniper-example
Add header for juniper example
2018-04-02 10:51:42 -07:00
03d851680b Merge branch 'master' into add-header-for-juniper-example 2018-04-02 10:51:35 -07:00
0ddd018214 Merge pull request #154 from dholbert/patch-1
Use https (not http) url for meritbadge
2018-04-02 10:50:17 -07:00
8219a7aebe Use https (not http) url for meritbadge
Right now this readme file uses an HTTP url to reference a meritbadge image, which ends up producing "broken https" UI on the crates.io page https://crates.io/crates/actix-web. This patch just upgrades this to an HTTPS url (which still works), to avoid that problem. (Literally a 1-character change, changing "http" to "https" in "http://meritbadge.herokuapp.com/actix-web")
2018-04-02 10:44:46 -07:00
6c906b08e1 match resource path before executing middlewares 2018-04-02 10:27:37 -07:00
220cbe40e5 Add header for juniper example 2018-04-02 19:10:33 +02:00
74d0656d27 update diesel example 2018-04-01 18:24:07 -07:00
17c27ef42d HttpRequest::resource() returns current matched resource 2018-04-01 17:37:22 -07:00
b2e771df2c use r2d2 for diesel example 2018-04-01 08:20:15 -07:00
a5a36ff194 update readme example 2018-03-31 17:15:44 -07:00
97e2bcd055 allow primitive types for Path extractor 2018-03-31 17:12:08 -07:00
23cfa649f4 update tests 2018-03-31 10:21:54 -07:00
8791c0f880 simplify With handlers 2018-03-31 09:58:33 -07:00
16c212f853 add extractors info to guide 2018-03-31 09:18:25 -07:00
3ee228005d rename Application 2018-03-31 00:16:55 -07:00
7a743fa6b5 update examples 2018-03-30 23:37:15 -07:00
44e3df82f6 simplify http response construction; deprecate httpcodes 2018-03-30 23:07:33 -07:00
8d8f6bedad update examples 2018-03-30 18:54:38 -07:00
9e751de707 re-arrange modules and exports 2018-03-30 17:31:18 -07:00
b16419348e add from HttpRequest to a HttpRequestBuilder 2018-03-30 14:30:24 -07:00
3ccaa04575 unhide AsyncResponder; remove unused code 2018-03-30 09:34:03 -07:00
d80b84c915 add test builder guide information 2018-03-29 19:23:45 -07:00
145010a2b0 use unreachable instead of panic 2018-03-29 15:55:27 -07:00
3e98177fad added State extractor 2018-03-29 15:41:13 -07:00
d24752d9bc update example in readme 2018-03-29 15:07:12 -07:00
92fe2e96de update doc strings 2018-03-29 15:00:18 -07:00
3cf54bc0fd proper serde deserializer implementation for path 2018-03-29 14:30:45 -07:00
86dd732704 use FromRequest instead of HttpRequestExtractor 2018-03-29 13:12:28 -07:00
dfd8f1058e move NormalizePath type to separate module 2018-03-29 11:39:21 -07:00
f5636f321b drop deprecated code 2018-03-29 11:06:44 -07:00
ae6c9cb7fa re-arrange exports, some doc string updates 2018-03-29 10:44:26 -07:00
32052c2750 update guide 2018-03-29 10:01:07 -07:00
7d6deab9fb drop request's extract_xxx methods 2018-03-29 09:26:01 -07:00
9e61c67128 do not re-export Version 2018-03-28 22:00:36 -07:00
13bb5f20d2 fix export name 2018-03-28 21:58:08 -07:00
d14991ec96 update doc strings 2018-03-28 21:49:50 -07:00
45dec8d0c0 optimize with and with2 method impls and tests 2018-03-28 21:33:40 -07:00
90e3aaaf8a fix router cannot parse Non-ASCII characters in URL #137 2018-03-28 16:10:58 -07:00
4f7d45ee9c remove unneeded import 2018-03-28 14:38:01 -07:00
e1d2536d85 remove unused code 2018-03-28 14:34:17 -07:00
65700281e8 add support for multiple extractors 2018-03-28 14:24:32 -07:00
80f6b93714 Merge pull request #138 from bwasty/guide
Guide: improve wording & style
2018-03-28 13:40:05 -07:00
5585465859 Merge branch 'master' into guide 2018-03-28 13:39:59 -07:00
368103dd09 guide: improve wording & style 2018-03-28 22:16:01 +02:00
df7ffe14f2 add PathAndQuery extractor 2018-03-28 11:20:06 -07:00
36161aba99 update Path and Query doc strings 2018-03-28 07:27:06 -07:00
9f5a91ae3c export types 2018-03-27 21:59:55 -07:00
4e61e0db34 mdbook 2018-03-27 21:33:35 -07:00
2dfccdd924 allow to fail nightly 2018-03-27 20:57:02 -07:00
4358da9926 refactor WithHandler trait 2018-03-27 20:33:24 -07:00
62fb75ff95 add Application::configure method, it simplifies configuration process 2018-03-27 11:16:02 -07:00
29a0feb415 fix guide example 2018-03-27 07:47:29 -07:00
dcc5eb7ace pass request as value 2018-03-26 23:34:31 -07:00
81f4e12a27 fix doc string test 2018-03-26 23:29:53 -07:00
2f60a4b89d add handler with exatractor 2018-03-26 23:10:31 -07:00
b03c7051ff add extractor info to the guide 2018-03-26 18:35:08 -07:00
8fff2c7595 remove Path and Query from public api 2018-03-26 18:18:38 -07:00
052d5f0bc5 silence AsciiExt deprecation warn 2018-03-26 16:12:25 -07:00
68cf32e848 add path and query extractors 2018-03-26 15:58:30 -07:00
a56e5113ee process transfer-encoding before content-length, fix tests on 32bit platform 2018-03-24 09:22:34 -07:00
5127b85672 Merge pull request #132 from andreevlex/spell-check-24-03
spelling check
2018-03-24 11:47:11 +03:00
2d80c5053d spelling check 2018-03-24 09:35:52 +03:00
d46854b315 bump version 2018-03-22 21:16:42 -07:00
47f836cd1b add helper method for response creation 2018-03-22 21:14:57 -07:00
449709dd7e add 0.5 sec deley before exit 2018-03-22 18:41:02 -07:00
5a25fd95f5 Fix panic on invalid URL characters #130 2018-03-22 18:08:12 -07:00
b942bcc4a6 Fix long client urls #129 2018-03-22 07:44:16 -07:00
1107fdec9d fix guide 2018-03-21 21:02:57 -07:00
04515e4697 update guide 2018-03-21 21:02:04 -07:00
93d99b5a49 Use more ergonomic actix_web::Error instead of http::Error for ClientRequestBuilder::body() 2018-03-21 20:19:31 -07:00
e49910cdab Use more ergonomic actix_web::Error instead of http::Error for HttpResponseBuilder::body() 2018-03-21 20:15:52 -07:00
e8a1850c79 add helper conversion from ClientResponse for HttpResponseBuilder 2018-03-21 20:04:35 -07:00
afb81b6b8f add convinience ClientRequest::build_from() from HttpRequest 2018-03-21 19:54:21 -07:00
4866a26578 make streaming method more ergonomic 2018-03-21 19:14:18 -07:00
2d75ced4ed fix client connection pooling 2018-03-21 11:51:08 -07:00
7bcc258b09 Use fast compression setting 2018-03-21 08:56:21 -07:00
d5fa0a9418 disable brotli if feature is not enabled, faster compression 2018-03-21 08:03:21 -07:00
ce6d237cc1 prepare 0.4.10 release 2018-03-20 15:53:39 -07:00
70caa2552b simplify httpresponse release 2018-03-20 15:51:19 -07:00
ee7d58dd7f disable h2 2018-03-20 12:35:44 -07:00
c4f4cadb43 Fix http/2 date header generation 2018-03-20 11:40:05 -07:00
978091cedb wake up io task when next chunk of data is needed 2018-03-20 11:37:13 -07:00
8198f5e10a Refactor TestServer configuration 2018-03-20 11:23:35 -07:00
6cd40df387 Fix server websockets big payloads support 2018-03-19 17:27:03 -07:00
35ee5d36d8 actix 0.5.5, ws test 2018-03-19 13:12:36 -07:00
e7ec0f9fd7 ws tests and proper write payload ref 2018-03-19 09:30:58 -07:00
f4a47ef71e allow set client request/ws timeout 2018-03-18 19:27:51 -07:00
6b1a79fab8 update example 2018-03-18 16:27:34 -07:00
ab73da4a1a use Error instead of InternalError for helper methods error::ErrorXXX 2018-03-18 14:18:47 -07:00
e0c8da567c various optimizations 2018-03-18 11:05:44 -07:00
c10dedf7e4 Merge pull request #124 from DoumanAsh/show_hidden
Show Request's hidden methods
2018-03-17 18:39:21 +03:00
ec192e0ab1 Show Request's hidden methods 2018-03-17 18:10:22 +03:00
6d792d9948 simplify h1 parse 2018-03-16 20:56:23 -07:00
1fe4315c94 use actix 0.5.4 2018-03-16 13:37:47 -07:00
381b90e9a1 bump version 2018-03-16 12:31:29 -07:00
2d18dba40a fix compilation 2018-03-16 12:28:08 -07:00
d2693d58a8 clippy warnings 2018-03-16 12:12:55 -07:00
84bf282c17 add basic client connection pooling 2018-03-16 12:04:01 -07:00
b15b5e5246 check number of available workers 2018-03-16 11:17:27 -07:00
52b3b0c362 Merge pull request #119 from DoumanAsh/default_static_files
Add default resource for StaticFiles
2018-03-16 20:12:07 +03:00
64c4cefa8f Merge branch 'master' into default_static_files 2018-03-16 09:31:36 -07:00
7e8b231f57 disable test 2018-03-16 09:13:36 -07:00
8a344d0c94 Add default resource for StaticFiles 2018-03-16 19:04:36 +03:00
4096089a3f allow to disable http/2 support 2018-03-16 08:48:44 -07:00
b16f2d5f05 proper check for actor context poll 2018-03-16 08:04:26 -07:00
5baf15822a always start actors 2018-03-16 07:46:27 -07:00
5368ce823e Merge pull request #123 from h416/patch-1
fix typo
2018-03-16 05:31:10 -07:00
4effdf065b fix typo 2018-03-16 19:03:16 +09:00
61970ab190 always poll stream or actor for the first time 2018-03-15 17:11:49 -07:00
484b00a0f9 Merge branch 'master' of github.com:actix/actix-web 2018-03-15 16:55:33 -07:00
73bf2068aa allow to use NamedFile with any request method 2018-03-15 16:55:22 -07:00
1cda949204 Merge pull request #122 from mockersf/test_qp
test for query parameters in client
2018-03-14 16:10:31 -07:00
ad6b823255 test for query parameters in client 2018-03-14 21:45:49 +01:00
0f064db31d Move brotli encoding to a feature 2018-03-13 17:21:22 -07:00
fd0bb54469 add debug formatter for ClientRequestBuilder 2018-03-13 15:09:05 -07:00
e27bbaa55c Update CHANGES.md 2018-03-13 13:15:21 -07:00
8a50eae1e2 Merge pull request #121 from glademiller/master
Send Query Parameters in client requests
2018-03-13 13:14:51 -07:00
38080f67b3 If no path is available from the URI request / 2018-03-13 13:35:11 -06:00
08504e0892 Move path call inline into write 2018-03-13 13:26:13 -06:00
401c0ad809 https://github.com/actix/actix-web/issues/120 - Send Query Parameters in client requests 2018-03-13 13:17:55 -06:00
b4b0deb7fa Wake payload reading task when data is available 2018-03-12 16:29:13 -07:00
05ff35d383 Fix server keep-alive handling 2018-03-12 16:16:17 -07:00
127 changed files with 8363 additions and 3687 deletions

View File

@ -14,7 +14,6 @@ matrix:
- rust: nightly - rust: nightly
allow_failures: allow_failures:
- rust: nightly - rust: nightly
- rust: beta
#rust: #rust:
# - 1.21.0 # - 1.21.0
@ -77,10 +76,10 @@ script:
# Upload docs # Upload docs
after_success: after_success:
- | - |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; 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 &&
cargo install mdbook && curl -sL https://github.com/rust-lang-nursery/mdBook/releases/download/v0.1.5/mdbook-v0.1.5-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $HOME/.cargo/bin &&
cd guide && mdbook build -d ../target/doc/guide && cd .. && cd guide && mdbook build -d ../target/doc/guide && cd .. &&
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 &&
@ -88,7 +87,7 @@ after_success:
fi fi
- | - |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "1.21.0" ]]; then if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml USE_SKEPTIC=1 cargo tarpaulin --out Xml
bash <(curl -s https://codecov.io/bash) bash <(curl -s https://codecov.io/bash)

View File

@ -1,5 +1,65 @@
# Changes # Changes
## 0.5.0
* Type-safe path/query/form parameter handling, using serde #70
* HttpResponse builder's methods `.body()`, `.finish()`, `.json()`
return `HttpResponse` instead of `Result`
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
* Added `HttpRequest::resource()`, returns current matched resource
* Added `ErrorHandlers` middleware
* Fix router cannot parse Non-ASCII characters in URL #137
* Fix client connection pooling
* Fix long client urls #129
* Fix panic on invalid URL characters #130
* Fix logger request duration calculation #152
* Fix prefix and static file serving #168
* Add `signed` and `private` `CookieSessionBackend`s
## 0.4.10 (2018-03-20)
* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods
* Allow to set client request timeout
* Allow to set client websocket handshake timeout
* Refactor `TestServer` configuration
* Fix server websockets big payloads support
* Fix http/2 date header generation
## 0.4.9 (2018-03-16)
* Allow to disable http/2 support
* Wake payload reading task when data is available
* Fix server keep-alive handling
* Send Query Parameters in client requests #120
* Move brotli encoding to a feature
* Add option of default handler for `StaticFiles` handler #57
* Add basic client connection pooling
## 0.4.8 (2018-03-12) ## 0.4.8 (2018-03-12)
* Allow to set read buffer capacity for server request * Allow to set read buffer capacity for server request

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "0.4.8" version = "0.5.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust." description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust."
readme = "README.md" readme = "README.md"
@ -27,23 +27,25 @@ name = "actix_web"
path = "src/lib.rs" path = "src/lib.rs"
[features] [features]
default = ["session"] default = ["session", "brotli"]
# tls # tls
tls = ["native-tls", "tokio-tls"] tls = ["native-tls", "tokio-tls"]
# openssl # openssl
alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"] alpn = ["openssl", "tokio-openssl"]
# sessions # sessions
session = ["cookie/secure"] session = ["cookie/secure"]
# brotli encoding
brotli = ["brotli2"]
[dependencies] [dependencies]
actix = "^0.5.2" actix = "^0.5.5"
base64 = "0.9" base64 = "0.9"
bitflags = "1.0" bitflags = "1.0"
brotli2 = "^0.3.2"
failure = "0.1.1" failure = "0.1.1"
flate2 = "1.0" flate2 = "1.0"
h2 = "0.1" h2 = "0.1"
@ -60,13 +62,16 @@ rand = "0.4"
regex = "0.2" regex = "0.2"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.5"
sha1 = "0.6" sha1 = "0.6"
smallvec = "0.6" smallvec = "0.6"
time = "0.1" time = "0.1"
encoding = "0.2" encoding = "0.2"
language-tags = "0.2" language-tags = "0.2"
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 }
# io # io
mio = "^0.6.13" mio = "^0.6.13"

View File

@ -1,4 +1,4 @@
Copyright (c) 2017 Nikilay Kim Copyright (c) 2017 Nikolay Kim
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated person obtaining a copy of this software and associated

View File

@ -1,6 +1,6 @@
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) # Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Actix web is a simple, pragmatic, 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.github.io/actix-web/guide/qs_13.html) protocols * Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.github.io/actix-web/guide/qs_13.html) protocols
* Streaming and pipelining * Streaming and pipelining
@ -11,16 +11,16 @@ Actix web is a simple, pragmatic, extremely fast, web framework for Rust.
* Graceful server shutdown * Graceful server shutdown
* Multipart streams * Multipart streams
* Static assets * Static assets
* SSL support with openssl or native-tls * SSL support with OpenSSL or `native-tls`
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), * Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions), [Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
[Redis sessions](https://github.com/actix/actix-redis), [Redis sessions](https://github.com/actix/actix-redis),
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers), [DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers),
[CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html), [CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html),
[CSRF](https://actix.github.io/actix-web/actix_web/middleware/csrf/index.html)) [CSRF](https://actix.github.io/actix-web/actix_web/middleware/csrf/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 ## Documentation & community resources
* [User Guide](http://actix.github.io/actix-web/guide/) * [User Guide](http://actix.github.io/actix-web/guide/)
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/) * [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
@ -33,16 +33,16 @@ Actix web is a simple, pragmatic, extremely fast, web framework for Rust.
```rust ```rust
extern crate actix_web; extern crate actix_web;
use actix_web::*; use actix_web::{http, server, App, Path};
fn index(req: HttpRequest) -> String { fn index(info: Path<(u32, String)>) -> String {
format!("Hello {}!", &req.match_info()["name"]) format!("Hello {}! id:{}", info.0, info.1)
} }
fn main() { fn main() {
HttpServer::new( server::new(
|| Application::new() || App::new()
.resource("/{name}", |r| r.f(index))) .route("/{id}/{name}/index.html", http::Method::GET, index))
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.run(); .run();
} }

View File

@ -5,7 +5,7 @@ authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.." workspace = "../.."
[dependencies] [dependencies]
futures = "*" futures = "0.1"
env_logger = "0.5" env_logger = "0.5"
actix = "0.5" actix = "0.5"
actix-web = { path="../.." } actix-web = { path="../.." }

View File

@ -8,8 +8,10 @@ extern crate futures;
use futures::Stream; use futures::Stream;
use std::{io, env}; use std::{io, env};
use actix_web::*; use actix_web::{error, fs, pred, server,
use actix_web::middleware::RequestSession; App, HttpRequest, HttpResponse, Result, Error};
use actix_web::http::{Method, StatusCode};
use actix_web::middleware::{self, RequestSession};
use futures::future::{FutureResult, result}; use futures::future::{FutureResult, result};
/// favicon handler /// favicon handler
@ -49,7 +51,7 @@ fn index(mut req: HttpRequest) -> Result<HttpResponse> {
// response // response
Ok(HttpResponse::build(StatusCode::OK) Ok(HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(&html).unwrap()) .body(&html))
} }
@ -67,7 +69,7 @@ fn p404(req: HttpRequest) -> Result<HttpResponse> {
// response // response
Ok(HttpResponse::build(StatusCode::NOT_FOUND) Ok(HttpResponse::build(StatusCode::NOT_FOUND)
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(html).unwrap()) .body(html))
} }
@ -76,20 +78,19 @@ fn index_async(req: HttpRequest) -> FutureResult<HttpResponse, Error>
{ {
println!("{:?}", req); println!("{:?}", req);
result(HttpResponse::Ok() result(Ok(HttpResponse::Ok()
.content_type("text/html") .content_type("text/html")
.body(format!("Hello {}!", req.match_info().get("name").unwrap())) .body(format!("Hello {}!", req.match_info().get("name").unwrap()))))
.map_err(|e| e.into()))
} }
/// handler with path parameters like `/user/{name}/` /// handler with path parameters like `/user/{name}/`
fn with_param(req: HttpRequest) -> Result<HttpResponse> fn with_param(req: HttpRequest) -> HttpResponse
{ {
println!("{:?}", req); println!("{:?}", req);
Ok(HttpResponse::Ok() HttpResponse::Ok()
.content_type("test/plain") .content_type("test/plain")
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))?) .body(format!("Hello {}!", req.match_info().get("name").unwrap()))
} }
fn main() { fn main() {
@ -98,15 +99,13 @@ fn main() {
env_logger::init(); env_logger::init();
let sys = actix::System::new("basic-example"); let sys = actix::System::new("basic-example");
let addr = HttpServer::new( let addr = server::new(
|| Application::new() || App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
// cookie session middleware // cookie session middleware
.middleware(middleware::SessionStorage::new( .middleware(middleware::SessionStorage::new(
middleware::CookieSessionBackend::build(&[0; 32]) middleware::CookieSessionBackend::signed(&[0; 32]).secure(false)
.secure(false)
.finish()
)) ))
// register favicon // register favicon
.resource("/favicon.ico", |r| r.f(favicon)) .resource("/favicon.ico", |r| r.f(favicon))
@ -118,16 +117,17 @@ fn main() {
.resource("/async/{name}", |r| r.method(Method::GET).a(index_async)) .resource("/async/{name}", |r| r.method(Method::GET).a(index_async))
.resource("/test", |r| r.f(|req| { .resource("/test", |r| r.f(|req| {
match *req.method() { match *req.method() {
Method::GET => httpcodes::HTTPOk, Method::GET => HttpResponse::Ok(),
Method::POST => httpcodes::HTTPMethodNotAllowed, Method::POST => HttpResponse::MethodNotAllowed(),
_ => httpcodes::HTTPNotFound, _ => HttpResponse::NotFound(),
} }
})) }))
.resource("/error.html", |r| r.f(|req| { .resource("/error.html", |r| r.f(|req| {
error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "test")) error::InternalError::new(
io::Error::new(io::ErrorKind::Other, "test"), StatusCode::OK)
})) }))
// static files // static files
.handler("/static/", fs::StaticFiles::new("../static/", true)) .handler("/static/", fs::StaticFiles::new("../static/"))
// redirect // redirect
.resource("/", |r| r.method(Method::GET).f(|req| { .resource("/", |r| r.method(Method::GET).f(|req| {
println!("{:?}", req); println!("{:?}", req);
@ -139,7 +139,8 @@ fn main() {
// default // default
.default_resource(|r| { .default_resource(|r| {
r.method(Method::GET).f(p404); r.method(Method::GET).f(p404);
r.route().filter(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed); r.route().filter(pred::Not(pred::Get())).f(
|req| HttpResponse::MethodNotAllowed());
})) }))
.bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080") .bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080")

View File

@ -15,5 +15,6 @@ serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
diesel = { version = "1.0.0-beta1", features = ["sqlite"] } diesel = { version = "^1.1.0", features = ["sqlite", "r2d2"] }
r2d2 = "0.8"
dotenv = "0.10" dotenv = "0.10"

View File

@ -4,12 +4,13 @@ use diesel;
use actix_web::*; use actix_web::*;
use actix::prelude::*; use actix::prelude::*;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::r2d2::{Pool, ConnectionManager};
use models; use models;
use schema; use schema;
/// This is db executor actor. We are going to run 3 of them in parallel. /// This is db executor actor. We are going to run 3 of them in parallel.
pub struct DbExecutor(pub SqliteConnection); pub struct DbExecutor(pub Pool<ConnectionManager<SqliteConnection>>);
/// This is only message that this actor can handle, but it is easy to extend number of /// This is only message that this actor can handle, but it is easy to extend number of
/// messages. /// messages.
@ -37,14 +38,16 @@ impl Handler<CreateUser> for DbExecutor {
name: &msg.name, name: &msg.name,
}; };
let conn: &SqliteConnection = &self.0.get().unwrap();
diesel::insert_into(users) diesel::insert_into(users)
.values(&new_user) .values(&new_user)
.execute(&self.0) .execute(conn)
.expect("Error inserting person"); .expect("Error inserting person");
let mut items = users let mut items = users
.filter(id.eq(&uuid)) .filter(id.eq(&uuid))
.load::<models::User>(&self.0) .load::<models::User>(conn)
.expect("Error loading person"); .expect("Error loading person");
Ok(items.pop().unwrap()) Ok(items.pop().unwrap())

View File

@ -10,16 +10,19 @@ extern crate serde_json;
extern crate serde_derive; extern crate serde_derive;
#[macro_use] #[macro_use]
extern crate diesel; extern crate diesel;
extern crate r2d2;
extern crate uuid; extern crate uuid;
extern crate futures; extern crate futures;
extern crate actix; extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate env_logger; extern crate env_logger;
use actix::*; use actix::prelude::*;
use actix_web::*; use actix_web::{http, server, middleware,
App, Path, State, HttpResponse, AsyncResponder, FutureResponse};
use diesel::prelude::*; use diesel::prelude::*;
use diesel::r2d2::{ Pool, ConnectionManager };
use futures::future::Future; use futures::future::Future;
mod db; mod db;
@ -30,21 +33,19 @@ use db::{CreateUser, DbExecutor};
/// State with DbExecutor address /// State with DbExecutor address
struct State { struct AppState {
db: Addr<Syn, DbExecutor>, db: Addr<Syn, DbExecutor>,
} }
/// Async request handler /// Async request handler
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index(name: Path<String>, state: State<AppState>) -> FutureResponse<HttpResponse> {
let name = &req.match_info()["name"];
// send async `CreateUser` message to a `DbExecutor` // send async `CreateUser` message to a `DbExecutor`
req.state().db.send(CreateUser{name: name.to_owned()}) state.db.send(CreateUser{name: name.into_inner()})
.from_err() .from_err()
.and_then(|res| { .and_then(|res| {
match res { match res {
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) Err(_) => Ok(HttpResponse::InternalServerError().into())
} }
}) })
.responder() .responder()
@ -52,20 +53,23 @@ fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>>
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("diesel-example"); let sys = actix::System::new("diesel-example");
// Start 3 db executor actors // Start 3 db executor actors
let addr = SyncArbiter::start(3, || { let manager = ConnectionManager::<SqliteConnection>::new("test.db");
DbExecutor(SqliteConnection::establish("test.db").unwrap()) let pool = r2d2::Pool::builder().build(manager).expect("Failed to create pool.");
let addr = SyncArbiter::start(3, move || {
DbExecutor(pool.clone())
}); });
// Start http server // Start http server
let _addr = HttpServer::new(move || { server::new(move || {
Application::with_state(State{db: addr.clone()}) App::with_state(AppState{db: addr.clone()})
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/{name}", |r| r.method(Method::GET).a(index))}) .resource("/{name}", |r| r.method(http::Method::GET).with2(index))})
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.start(); .start();

View File

@ -2,7 +2,7 @@ extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate env_logger; extern crate env_logger;
use actix_web::*; use actix_web::{App, HttpRequest, server, middleware};
fn index(_req: HttpRequest) -> &'static str { fn index(_req: HttpRequest) -> &'static str {
@ -11,11 +11,11 @@ fn index(_req: HttpRequest) -> &'static str {
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("ws-example"); let sys = actix::System::new("hello-world");
let _addr = HttpServer::new( server::new(
|| Application::new() || App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/index.html", |r| r.f(|_| "Hello world!")) .resource("/index.html", |r| r.f(|_| "Hello world!"))

View File

@ -3,8 +3,10 @@ extern crate actix_web;
extern crate futures; extern crate futures;
extern crate env_logger; extern crate env_logger;
use actix_web::*;
use futures::{Future, Stream}; use futures::{Future, Stream};
use actix_web::{
client, server, middleware,
App, AsyncResponder, Body, HttpRequest, HttpResponse, HttpMessage, Error};
/// Stream client request response and then send body to a server response /// Stream client request response and then send body to a server response
@ -12,14 +14,12 @@ fn index(_req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
client::ClientRequest::get("https://www.rust-lang.org/en-US/") client::ClientRequest::get("https://www.rust-lang.org/en-US/")
.finish().unwrap() .finish().unwrap()
.send() .send()
.map_err(error::Error::from) // <- convert SendRequestError to an Error .map_err(Error::from) // <- convert SendRequestError to an Error
.and_then( .and_then(
|resp| resp.body() // <- this is MessageBody type, resolves to complete body |resp| resp.body() // <- this is MessageBody type, resolves to complete body
.from_err() // <- convet PayloadError to a Error .from_err() // <- convert PayloadError to a Error
.and_then(|body| { // <- we got complete body, now send as server response .and_then(|body| { // <- we got complete body, now send as server response
httpcodes::HttpOk.build() Ok(HttpResponse::Ok().body(body))
.body(body)
.map_err(error::Error::from)
})) }))
.responder() .responder()
} }
@ -30,13 +30,12 @@ fn streaming(_req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
client::ClientRequest::get("https://www.rust-lang.org/en-US/") client::ClientRequest::get("https://www.rust-lang.org/en-US/")
.finish().unwrap() .finish().unwrap()
.send() // <- connect to host and send request .send() // <- connect to host and send request
.map_err(error::Error::from) // <- convert SendRequestError to an Error .map_err(Error::from) // <- convert SendRequestError to an Error
.and_then(|resp| { // <- we received client response .and_then(|resp| { // <- we received client response
httpcodes::HttpOk.build() Ok(HttpResponse::Ok()
// read one chunk from client response and send this chunk to a server response // read one chunk from client response and send this chunk to a server response
// .from_err() converts PayloadError to a Error // .from_err() converts PayloadError to a Error
.body(Body::Streaming(Box::new(resp.from_err()))) .body(Body::Streaming(Box::new(resp.from_err()))))
.map_err(|e| e.into()) // HttpOk::build() mayb return HttpError, we need to convert it to a Error
}) })
.responder() .responder()
} }
@ -46,8 +45,8 @@ fn main() {
env_logger::init(); env_logger::init();
let sys = actix::System::new("http-proxy"); let sys = actix::System::new("http-proxy");
let _addr = HttpServer::new( server::new(
|| Application::new() || App::new()
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/streaming", |r| r.f(streaming)) .resource("/streaming", |r| r.f(streaming))
.resource("/", |r| r.f(index))) .resource("/", |r| r.f(index)))

View File

@ -7,7 +7,9 @@ extern crate serde_json;
#[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_derive;
#[macro_use] extern crate json; #[macro_use] extern crate json;
use actix_web::*; use actix_web::{
middleware, http, error, server,
App, AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error, Json};
use bytes::BytesMut; use bytes::BytesMut;
use futures::{Future, Stream}; use futures::{Future, Stream};
@ -19,21 +21,26 @@ struct MyObj {
number: i32, number: i32,
} }
/// This handler uses `HttpRequest::json()` for loading serde json object. /// This handler uses `HttpRequest::json()` for loading json object.
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.json() req.json()
.from_err() // convert all errors into `Error` .from_err() // convert all errors into `Error`
.and_then(|val: MyObj| { .and_then(|val: MyObj| {
println!("model: {:?}", val); println!("model: {:?}", val);
Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response Ok(HttpResponse::Ok().json(val)) // <- send response
}) })
.responder() .responder()
} }
/// This handler uses json extractor
fn extract_item(item: Json<MyObj>) -> HttpResponse {
println!("model: {:?}", &item);
HttpResponse::Ok().json(item.0) // <- send response
}
const MAX_SIZE: usize = 262_144; // max payload size is 256k const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// This handler manually load request payload and parse serde json /// This handler manually load request payload and parse json object
fn index_manual(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index_manual(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// HttpRequest is stream of Bytes objects // HttpRequest is stream of Bytes objects
req req
@ -57,7 +64,7 @@ fn index_manual(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
.and_then(|body| { .and_then(|body| {
// body is loaded, now we can deserialize serde-json // body is loaded, now we can deserialize serde-json
let obj = serde_json::from_slice::<MyObj>(&body)?; let obj = serde_json::from_slice::<MyObj>(&body)?;
Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response Ok(HttpResponse::Ok().json(obj)) // <- send response
}) })
.responder() .responder()
} }
@ -70,26 +77,30 @@ fn index_mjsonrust(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Erro
// body is loaded, now we can deserialize json-rust // body is loaded, now we can deserialize json-rust
let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result
let injson: JsonValue = match result { Ok(v) => v, Err(e) => object!{"err" => e.to_string() } }; let injson: JsonValue = match result { Ok(v) => v, Err(e) => object!{"err" => e.to_string() } };
Ok(HttpResponse::build(StatusCode::OK) Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(injson.dump()).unwrap()) .body(injson.dump()))
}) })
.responder() .responder()
} }
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("json-example"); let sys = actix::System::new("json-example");
let addr = HttpServer::new(|| { server::new(|| {
Application::new() App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/manual", |r| r.method(Method::POST).f(index_manual)) .resource("/extractor", |r| {
.resource("/mjsonrust", |r| r.method(Method::POST).f(index_mjsonrust)) r.method(http::Method::POST)
.resource("/", |r| r.method(Method::POST).f(index))}) .with(extract_item)
.limit(4096); // <- limit size of the payload
})
.resource("/manual", |r| r.method(http::Method::POST).f(index_manual))
.resource("/mjsonrust", |r| r.method(http::Method::POST).f(index_mjsonrust))
.resource("/", |r| r.method(http::Method::POST).f(index))})
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.shutdown_timeout(1) .shutdown_timeout(1)
.start(); .start();

View File

@ -12,11 +12,12 @@ extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate env_logger; extern crate env_logger;
use actix::*; use actix::prelude::*;
use actix_web::*; use actix_web::{
middleware, http, server,
App, AsyncResponder, HttpRequest, HttpResponse, FutureResponse, Error, State, Json};
use juniper::http::graphiql::graphiql_source; use juniper::http::graphiql::graphiql_source;
use juniper::http::GraphQLRequest; use juniper::http::GraphQLRequest;
use futures::future::Future; use futures::future::Future;
mod schema; mod schema;
@ -24,7 +25,7 @@ mod schema;
use schema::Schema; use schema::Schema;
use schema::create_schema; use schema::create_schema;
struct State { struct AppState {
executor: Addr<Syn, GraphQLExecutor>, executor: Addr<Syn, GraphQLExecutor>,
} }
@ -61,33 +62,30 @@ impl Handler<GraphQLData> for GraphQLExecutor {
} }
} }
fn graphiql(_req: HttpRequest<State>) -> Result<HttpResponse> { fn graphiql(_req: HttpRequest<AppState>) -> Result<HttpResponse, Error> {
let html = graphiql_source("http://127.0.0.1:8080/graphql"); let html = graphiql_source("http://127.0.0.1:8080/graphql");
Ok(HttpResponse::build(StatusCode::OK) Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(html).unwrap()) .body(html))
} }
fn graphql(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> { fn graphql(st: State<AppState>, data: Json<GraphQLData>) -> FutureResponse<HttpResponse> {
let executor = req.state().executor.clone(); st.executor.send(data.0)
req.json()
.from_err()
.and_then(move |val: GraphQLData| {
executor.send(val)
.from_err() .from_err()
.and_then(|res| { .and_then(|res| {
match res { match res {
Ok(user) => Ok(httpcodes::HTTPOk.build().body(user)?), Ok(user) => Ok(HttpResponse::Ok()
Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) .content_type("application/json")
.body(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into())
} }
}) })
})
.responder() .responder()
} }
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("juniper-example"); let sys = actix::System::new("juniper-example");
let schema = std::sync::Arc::new(create_schema()); let schema = std::sync::Arc::new(create_schema());
@ -96,12 +94,12 @@ fn main() {
}); });
// Start http server // Start http server
let _addr = HttpServer::new(move || { server::new(move || {
Application::with_state(State{executor: addr.clone()}) App::with_state(AppState{executor: addr.clone()})
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/graphql", |r| r.method(Method::POST).a(graphql)) .resource("/graphql", |r| r.method(http::Method::POST).with2(graphql))
.resource("/graphiql", |r| r.method(Method::GET).f(graphiql))}) .resource("/graphiql", |r| r.method(http::Method::GET).h(graphiql))})
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.start(); .start();

View File

@ -5,7 +5,9 @@ extern crate env_logger;
extern crate futures; extern crate futures;
use actix::*; use actix::*;
use actix_web::*; use actix_web::{
http, middleware, multipart, server,
App, AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error};
use futures::{Future, Stream}; use futures::{Future, Stream};
use futures::future::{result, Either}; use futures::future::{result, Either};
@ -38,7 +40,7 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
} }
}) })
.finish() // <- Stream::finish() combinator from actix .finish() // <- Stream::finish() combinator from actix
.map(|_| httpcodes::HTTPOk.into()) .map(|_| HttpResponse::Ok().into())
.responder() .responder()
} }
@ -47,10 +49,10 @@ fn main() {
let _ = env_logger::init(); let _ = env_logger::init();
let sys = actix::System::new("multipart-example"); let sys = actix::System::new("multipart-example");
let addr = HttpServer::new( server::new(
|| Application::new() || App::new()
.middleware(middleware::Logger::default()) // <- logger .middleware(middleware::Logger::default()) // <- logger
.resource("/multipart", |r| r.method(Method::POST).a(index))) .resource("/multipart", |r| r.method(http::Method::POST).a(index)))
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.start(); .start();

View File

@ -9,8 +9,10 @@ extern crate prost;
#[macro_use] #[macro_use]
extern crate prost_derive; extern crate prost_derive;
use actix_web::*;
use futures::Future; use futures::Future;
use actix_web::{
http, middleware, server,
App, AsyncResponder, HttpRequest, HttpResponse, Error};
mod protobuf; mod protobuf;
use protobuf::ProtoBufResponseBuilder; use protobuf::ProtoBufResponseBuilder;
@ -31,7 +33,7 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
.from_err() // convert all errors into `Error` .from_err() // convert all errors into `Error`
.and_then(|val: MyObj| { .and_then(|val: MyObj| {
println!("model: {:?}", val); println!("model: {:?}", val);
Ok(httpcodes::HTTPOk.build().protobuf(val)?) // <- send response Ok(HttpResponse::Ok().protobuf(val)?) // <- send response
}) })
.responder() .responder()
} }
@ -39,13 +41,13 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("protobuf-example"); let sys = actix::System::new("protobuf-example");
let addr = HttpServer::new(|| { server::new(|| {
Application::new() App::new()
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/", |r| r.method(Method::POST).f(index))}) .resource("/", |r| r.method(http::Method::POST).f(index))})
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.shutdown_timeout(1) .shutdown_timeout(1)
.start(); .start();

View File

@ -6,11 +6,10 @@ use prost::Message;
use prost::DecodeError as ProtoBufDecodeError; use prost::DecodeError as ProtoBufDecodeError;
use prost::EncodeError as ProtoBufEncodeError; use prost::EncodeError as ProtoBufEncodeError;
use actix_web::header::http::{CONTENT_TYPE, CONTENT_LENGTH}; use actix_web::http::header::{CONTENT_TYPE, CONTENT_LENGTH};
use actix_web::{Responder, HttpMessage, HttpRequest, HttpResponse}; use actix_web::{Responder, HttpMessage, HttpRequest, HttpResponse};
use actix_web::dev::HttpResponseBuilder; use actix_web::dev::HttpResponseBuilder;
use actix_web::error::{Error, PayloadError, ResponseError}; use actix_web::error::{Error, PayloadError, ResponseError};
use actix_web::httpcodes::{HttpBadRequest, HttpPayloadTooLarge};
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
@ -36,8 +35,8 @@ impl ResponseError for ProtoBufPayloadError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
ProtoBufPayloadError::Overflow => HttpPayloadTooLarge.into(), ProtoBufPayloadError::Overflow => HttpResponse::PayloadTooLarge().into(),
_ => HttpBadRequest.into(), _ => HttpResponse::BadRequest().into(),
} }
} }
} }
@ -164,6 +163,6 @@ impl ProtoBufResponseBuilder for HttpResponseBuilder {
let mut body = Vec::new(); let mut body = Vec::new();
value.encode(&mut body).map_err(|e| ProtoBufPayloadError::Serialize(e))?; value.encode(&mut body).map_err(|e| ProtoBufPayloadError::Serialize(e))?;
Ok(self.body(body)?) Ok(self.body(body))
} }
} }

View File

@ -10,8 +10,9 @@ extern crate r2d2;
extern crate r2d2_sqlite; extern crate r2d2_sqlite;
extern crate rusqlite; extern crate rusqlite;
use actix::*; use actix::prelude::*;
use actix_web::*; use actix_web::{
middleware, http, server, App, AsyncResponder, HttpRequest, HttpResponse, Error};
use futures::future::Future; use futures::future::Future;
use r2d2_sqlite::SqliteConnectionManager; use r2d2_sqlite::SqliteConnectionManager;
@ -32,8 +33,8 @@ fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>>
.from_err() .from_err()
.and_then(|res| { .and_then(|res| {
match res { match res {
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) Err(_) => Ok(HttpResponse::InternalServerError().into())
} }
}) })
.responder() .responder()
@ -41,7 +42,7 @@ fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>>
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=debug"); ::std::env::set_var("RUST_LOG", "actix_web=debug");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("r2d2-example"); let sys = actix::System::new("r2d2-example");
// r2d2 pool // r2d2 pool
@ -52,11 +53,11 @@ fn main() {
let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone())); let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone()));
// Start http server // Start http server
let _addr = HttpServer::new(move || { server::new(move || {
Application::with_state(State{db: addr.clone()}) App::with_state(State{db: addr.clone()})
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/{name}", |r| r.method(Method::GET).a(index))}) .resource("/{name}", |r| r.method(http::Method::GET).a(index))})
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.start(); .start();

View File

@ -5,7 +5,7 @@ authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.." workspace = "../.."
[dependencies] [dependencies]
futures = "*" futures = "0.1"
env_logger = "0.5" env_logger = "0.5"
actix = "0.5" actix = "0.5"
actix-web = { path = "../../" } actix-web = { path = "../../" }

View File

@ -9,21 +9,21 @@ extern crate env_logger;
use std::cell::Cell; use std::cell::Cell;
use actix::*; use actix::prelude::*;
use actix_web::*; use actix_web::{
http, server, ws, middleware, App, HttpRequest, HttpResponse};
/// Application state /// Application state
struct AppState { struct AppState {
counter: Cell<usize>, counter: Cell<usize>,
} }
/// somple handle /// simple handle
fn index(req: HttpRequest<AppState>) -> HttpResponse { fn index(req: HttpRequest<AppState>) -> HttpResponse {
println!("{:?}", req); println!("{:?}", req);
req.state().counter.set(req.state().counter.get() + 1); req.state().counter.set(req.state().counter.get() + 1);
httpcodes::HTTPOk.with_body( HttpResponse::Ok().body(format!("Num of requests: {}", req.state().counter.get()))
format!("Num of requests: {}", req.state().counter.get()))
} }
/// `MyWebSocket` counts how many messages it receives from peer, /// `MyWebSocket` counts how many messages it receives from peer,
@ -55,17 +55,18 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("ws-example"); let sys = actix::System::new("ws-example");
let addr = HttpServer::new( server::new(
|| Application::with_state(AppState{counter: Cell::new(0)}) || App::with_state(AppState{counter: Cell::new(0)})
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
// websocket route // websocket route
.resource( .resource(
"/ws/", |r| "/ws/", |r|
r.method(Method::GET).f(|req| ws::start(req, MyWebSocket{counter: 0}))) r.method(http::Method::GET).f(
|req| ws::start(req, MyWebSocket{counter: 0})))
// register simple handler, handle all methods // register simple handler, handle all methods
.resource("/", |r| r.f(index))) .resource("/", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()

View File

@ -4,14 +4,15 @@ extern crate env_logger;
#[macro_use] #[macro_use]
extern crate tera; extern crate tera;
use actix_web::*; use actix_web::{
http, error, middleware, server, App, HttpRequest, HttpResponse, Error};
struct State { struct State {
template: tera::Tera, // <- store tera template in application state template: tera::Tera, // <- store tera template in application state
} }
fn index(req: HttpRequest<State>) -> Result<HttpResponse> { fn index(req: HttpRequest<State>) -> Result<HttpResponse, Error> {
let s = if let Some(name) = req.query().get("name") { // <- submitted form let s = if let Some(name) = req.query().get("name") { // <- submitted form
let mut ctx = tera::Context::new(); let mut ctx = tera::Context::new();
ctx.add("name", &name.to_owned()); ctx.add("name", &name.to_owned());
@ -22,23 +23,23 @@ fn index(req: HttpRequest<State>) -> Result<HttpResponse> {
req.state().template.render("index.html", &tera::Context::new()) req.state().template.render("index.html", &tera::Context::new())
.map_err(|_| error::ErrorInternalServerError("Template error"))? .map_err(|_| error::ErrorInternalServerError("Template error"))?
}; };
Ok(httpcodes::HTTPOk.build() Ok(HttpResponse::Ok()
.content_type("text/html") .content_type("text/html")
.body(s)?) .body(s))
} }
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("tera-example"); let sys = actix::System::new("tera-example");
let addr = HttpServer::new(|| { server::new(|| {
let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")); let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"));
Application::with_state(State{template: tera}) App::with_state(State{template: tera})
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/", |r| r.method(Method::GET).f(index))}) .resource("/", |r| r.method(http::Method::GET).f(index))})
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.start(); .start();

View File

@ -4,24 +4,24 @@ extern crate actix_web;
extern crate env_logger; extern crate env_logger;
extern crate openssl; extern crate openssl;
use actix_web::*;
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype}; use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
use actix_web::{
http, middleware, server, App, HttpRequest, HttpResponse, Error};
/// simple handle /// simple handle
fn index(req: HttpRequest) -> Result<HttpResponse> { fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
println!("{:?}", req); println!("{:?}", req);
Ok(httpcodes::HTTPOk Ok(HttpResponse::Ok()
.build()
.content_type("text/plain") .content_type("text/plain")
.body("Welcome!")?) .body("Welcome!"))
} }
fn main() { fn main() {
if ::std::env::var("RUST_LOG").is_err() { if ::std::env::var("RUST_LOG").is_err() {
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
} }
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("ws-example"); let sys = actix::System::new("ws-example");
// load ssl keys // load ssl keys
@ -29,18 +29,17 @@ fn main() {
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap(); builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
builder.set_certificate_chain_file("cert.pem").unwrap(); builder.set_certificate_chain_file("cert.pem").unwrap();
let addr = HttpServer::new( server::new(
|| Application::new() || App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
// register simple handler, handle all methods // register simple handler, handle all methods
.resource("/index.html", |r| r.f(index)) .resource("/index.html", |r| r.f(index))
// with path parameters // with path parameters
.resource("/", |r| r.method(Method::GET).f(|req| { .resource("/", |r| r.method(http::Method::GET).f(|req| {
httpcodes::HTTPFound HttpResponse::Found()
.build()
.header("LOCATION", "/index.html") .header("LOCATION", "/index.html")
.body(Body::Empty) .finish()
}))) })))
.bind("127.0.0.1:8443").unwrap() .bind("127.0.0.1:8443").unwrap()
.start_ssl(builder).unwrap(); .start_ssl(builder).unwrap();

View File

@ -4,7 +4,7 @@ extern crate env_logger;
extern crate tokio_uds; extern crate tokio_uds;
use actix::*; use actix::*;
use actix_web::*; use actix_web::{middleware, server, App, HttpRequest};
use tokio_uds::UnixListener; use tokio_uds::UnixListener;
@ -14,12 +14,13 @@ fn index(_req: HttpRequest) -> &'static str {
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("unix-socket"); let sys = actix::System::new("unix-socket");
let listener = UnixListener::bind("/tmp/actix-uds.socket", Arbiter::handle()).expect("bind failed"); let listener = UnixListener::bind(
let _addr = HttpServer::new( "/tmp/actix-uds.socket", Arbiter::handle()).expect("bind failed");
|| Application::new() server::new(
|| App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/index.html", |r| r.f(|_| "Hello world!")) .resource("/index.html", |r| r.f(|_| "Hello world!"))

View File

@ -5,12 +5,9 @@ extern crate futures;
extern crate actix; extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate env_logger; extern crate env_logger;
extern crate http;
use std::env; use std::env;
use http::header; use actix_web::{http, middleware, server, App};
use actix_web::*;
use actix_web::middleware::cors;
mod user; mod user;
use user::info; use user::info;
@ -22,20 +19,21 @@ fn main() {
let sys = actix::System::new("Actix-web-CORS"); let sys = actix::System::new("Actix-web-CORS");
HttpServer::new( server::new(
|| Application::new() || App::new()
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/user/info", |r| { .resource("/user/info", |r| {
cors::Cors::build() middleware::cors::Cors::build()
.allowed_origin("http://localhost:1234") .allowed_origin("http://localhost:1234")
.allowed_methods(vec!["GET", "POST"]) .allowed_methods(vec!["GET", "POST"])
.allowed_headers( .allowed_headers(
vec![header::AUTHORIZATION, vec![http::header::AUTHORIZATION,
header::ACCEPT, header::CONTENT_TYPE]) http::header::ACCEPT,
http::header::CONTENT_TYPE])
.max_age(3600) .max_age(3600)
.finish().expect("Can not create CORS middleware") .finish().expect("Can not create CORS middleware")
.register(r); .register(r);
r.method(Method::POST).a(info); r.method(http::Method::POST).a(info);
})) }))
.bind("127.0.0.1:8000").unwrap() .bind("127.0.0.1:8000").unwrap()
.shutdown_timeout(200) .shutdown_timeout(200)

View File

@ -1,4 +1,4 @@
use actix_web::*; use actix_web::{AsyncResponder, Error, HttpMessage, HttpResponse, HttpRequest};
use futures::Future; use futures::Future;
@ -14,6 +14,6 @@ pub fn info(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.json() req.json()
.from_err() .from_err()
.and_then(|res: Info| { .and_then(|res: Info| {
Ok(httpcodes::HTTPOk.build().json(res)?) Ok(HttpResponse::Ok().json(res))
}).responder() }).responder()
} }

View File

@ -17,7 +17,8 @@ extern crate actix_web;
use std::time::Instant; use std::time::Instant;
use actix::*; use actix::*;
use actix_web::*; use actix_web::server::HttpServer;
use actix_web::{http, fs, ws, App, HttpRequest, HttpResponse, Error};
mod codec; mod codec;
mod server; mod server;
@ -30,7 +31,7 @@ struct WsChatSessionState {
} }
/// Entry point for our route /// Entry point for our route
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse> { fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse, Error> {
ws::start( ws::start(
req, req,
WsChatSession { WsChatSession {
@ -183,23 +184,22 @@ fn main() {
})); }));
// Create Http server with websocket support // Create Http server with websocket support
let addr = HttpServer::new( HttpServer::new(
move || { move || {
// Websocket sessions state // Websocket sessions state
let state = WsChatSessionState { addr: server.clone() }; let state = WsChatSessionState { addr: server.clone() };
Application::with_state(state) App::with_state(state)
// redirect to websocket.html // redirect to websocket.html
.resource("/", |r| r.method(Method::GET).f(|_| { .resource("/", |r| r.method(http::Method::GET).f(|_| {
httpcodes::HTTPFound HttpResponse::Found()
.build()
.header("LOCATION", "/static/websocket.html") .header("LOCATION", "/static/websocket.html")
.finish() .finish()
})) }))
// websocket // websocket
.resource("/ws/", |r| r.route().f(chat_route)) .resource("/ws/", |r| r.route().f(chat_route))
// static resources // static resources
.handler("/static/", fs::StaticFiles::new("static/", true)) .handler("/static/", fs::StaticFiles::new("static/"))
}) })
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.start(); .start();

View File

@ -1,4 +1,4 @@
# websockect # websocket
Simple echo websocket server. Simple echo websocket server.

View File

@ -8,11 +8,12 @@ extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate env_logger; extern crate env_logger;
use actix::*; use actix::prelude::*;
use actix_web::*; use actix_web::{
http, middleware, server, fs, ws, App, HttpRequest, HttpResponse, Error};
/// do websocket handshake and start `MyWebSocket` actor /// do websocket handshake and start `MyWebSocket` actor
fn ws_index(r: HttpRequest) -> Result<HttpResponse> { fn ws_index(r: HttpRequest) -> Result<HttpResponse, Error> {
ws::start(r, MyWebSocket) ws::start(r, MyWebSocket)
} }
@ -44,17 +45,17 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("ws-example"); let sys = actix::System::new("ws-example");
let _addr = HttpServer::new( server::new(
|| Application::new() || App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
// websocket route // websocket route
.resource("/ws/", |r| r.method(Method::GET).f(ws_index)) .resource("/ws/", |r| r.method(http::Method::GET).f(ws_index))
// static files // static files
.handler("/", fs::StaticFiles::new("../static/", true) .handler("/", fs::StaticFiles::new("../static/")
.index_file("index.html"))) .index_file("index.html")))
// start http server on 127.0.0.1:8080 // start http server on 127.0.0.1:8080
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()

View File

@ -1,11 +1,8 @@
# Quick start # Quick start
Before you can start writing a actix web application, youll need a version of Rust installed.
We recommend you use rustup to install or configure such a version.
## Install Rust ## Install Rust
Before we begin, we need to install Rust using the [rustup](https://www.rustup.rs/) installer: Before we begin, we need to install Rust using [rustup](https://www.rustup.rs/):
```bash ```bash
curl https://sh.rustup.rs -sSf | sh curl https://sh.rustup.rs -sSf | sh
@ -21,9 +18,10 @@ Actix web framework requires rust version 1.21 and up.
## Running Examples ## Running Examples
The fastest way to start experimenting with actix web is to clone the actix web repository The fastest way to start experimenting with actix web is to clone the
and run the included examples in the examples/ directory. The following set of [repository](https://github.com/actix/actix-web) and run the included examples.
commands runs the `basics` example:
The following set of commands runs the `basics` example:
```bash ```bash
git clone https://github.com/actix/actix-web git clone https://github.com/actix/actix-web

View File

@ -1,29 +1,31 @@
# Middlewares # Middleware
Actix middlewares system allows to add additional behavior to request/response processing. Actix's middleware system allows us to add additional behavior to request/response processing.
Middleware can hook into incoming request process and modify request or halt request Middleware can hook into an incoming request process, enabling us to modify requests
processing and return response early. Also it can hook into response processing. as well as halt request processing to return a response early.
Typically middlewares involves in following actions: Middleware can also hook into response processing.
Typically, middleware is involved in the following actions:
* Pre-process the Request * Pre-process the Request
* Post-process a Response * Post-process a Response
* Modify application state * Modify application state
* Access external services (redis, logging, sessions) * Access external services (redis, logging, sessions)
Middlewares are registered for each application and get executed in same order as Middleware is registered for each application and executed in same order as
registration order. In general, *middleware* is a type that implements registration. In general, a *middleware* is a type that implements the
[*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method [*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method
in this trait has default implementation. Each method can return result immediately in this trait has a default implementation. Each method can return a result immediately
or *future* object. or a *future* object.
Here is example of simple middleware that adds request and response headers: The following demonstrates using middleware to add request and response headers:
```rust ```rust
# extern crate http; # extern crate http;
# extern crate actix_web; # extern crate actix_web;
use http::{header, HttpTryFrom}; use http::{header, HttpTryFrom};
use actix_web::*; use actix_web::{App, HttpRequest, HttpResponse, Result};
use actix_web::middleware::{Middleware, Started, Response}; use actix_web::middleware::{Middleware, Started, Response};
struct Headers; // <- Our middleware struct Headers; // <- Our middleware
@ -51,49 +53,51 @@ impl<S> Middleware<S> for Headers {
} }
fn main() { fn main() {
Application::new() App::new()
.middleware(Headers) // <- Register middleware, this method could be called multiple times .middleware(Headers) // <- Register middleware, this method can be called multiple times
.resource("/", |r| r.h(httpcodes::HttpOk)); .resource("/", |r| r.f(|_| HttpResponse::Ok()));
} }
``` ```
Active provides several useful middlewares, like *logging*, *user sessions*, etc. > Actix provides several useful middlewares, such as *logging*, *user sessions*, etc.
## Logging ## Logging
Logging is implemented as middleware. Logging is implemented as a middleware.
It is common to register logging middleware as first middleware for application. It is common to register a logging middleware as the first middleware for the application.
Logging middleware has to be registered for each application. *Logger* middleware Logging middleware must be registered for each application.
uses standard log crate to log information. You should enable logger for *actix_web*
package to see access log. ([env_logger](https://docs.rs/env_logger/*/env_logger/) or similar) The `Logger` middleware uses the standard log crate to log information. You should enable logger
for *actix_web* package to see access log ([env_logger](https://docs.rs/env_logger/*/env_logger/)
or similar).
### Usage ### Usage
Create `Logger` middleware with the specified `format`. Create `Logger` middleware with the specified `format`.
Default `Logger` could be created with `default` method, it uses the default format: Default `Logger` can be created with `default` method, it uses the default format:
```ignore ```ignore
%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
``` ```
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
extern crate env_logger; extern crate env_logger;
use actix_web::Application; use actix_web::App;
use actix_web::middleware::Logger; use actix_web::middleware::Logger;
fn main() { fn main() {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
Application::new() App::new()
.middleware(Logger::default()) .middleware(Logger::default())
.middleware(Logger::new("%a %{User-Agent}i")) .middleware(Logger::new("%a %{User-Agent}i"))
.finish(); .finish();
} }
``` ```
Here is example of default logging format: The following is an example of the default logging format:
``` ```
INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397 INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397
@ -126,26 +130,24 @@ INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800]
`%{FOO}e` os.environ['FOO'] `%{FOO}e` os.environ['FOO']
## Default headers ## Default headers
To set default response headers `DefaultHeaders` middleware could be used. To set default response headers, the `DefaultHeaders` middleware can be used. The
*DefaultHeaders* middleware does not set header if response headers already contains *DefaultHeaders* middleware does not set the header if response headers already contain
specified header. a specified header.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
use actix_web::*; use actix_web::{http, middleware, App, HttpResponse};
fn main() { fn main() {
let app = Application::new() let app = App::new()
.middleware( .middleware(
middleware::DefaultHeaders::build() middleware::DefaultHeaders::new()
.header("X-Version", "0.2") .header("X-Version", "0.2"))
.finish())
.resource("/test", |r| { .resource("/test", |r| {
r.method(Method::GET).f(|req| httpcodes::HttpOk); r.method(http::Method::GET).f(|req| HttpResponse::Ok());
r.method(Method::HEAD).f(|req| httpcodes::HttpMethodNotAllowed); r.method(http::Method::HEAD).f(|req| HttpResponse::MethodNotAllowed());
}) })
.finish(); .finish();
} }
@ -153,34 +155,38 @@ fn main() {
## User sessions ## User sessions
Actix provides general solution for session management. Actix provides a general solution for session management. The
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware can be [**SessionStorage**](../actix_web/middleware/struct.SessionStorage.html) middleware can be
use with different backend types to store session data in different backends. used with different backend types to store session data in different backends.
By default only cookie session backend is implemented. Other backend implementations
could be added later.
[*Cookie session backend*](../actix_web/middleware/struct.CookieSessionBackend.html) > By default, only cookie session backend is implemented. Other backend implementations
uses signed cookies as session storage. *Cookie session backend* creates sessions which > can be added.
are limited to storing fewer than 4000 bytes of data (as the payload must fit into a
single cookie). Internal server error get generated if session contains more than 4000 bytes.
You need to pass a random value to the constructor of *CookieSessionBackend*. [**CookieSessionBackend**](../actix_web/middleware/struct.CookieSessionBackend.html)
This is private key for cookie session. When this value is changed, all session data is lost. uses cookies as session storage. `CookieSessionBackend` creates sessions which
Note that whatever you write into your session is visible by the user (but not modifiable). are limited to storing fewer than 4000 bytes of data, as the payload must fit into a
single cookie. An internal server error is generated if a session contains more than 4000 bytes.
In general case, you create A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor.
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware
and initializes it with specific backend implementation, like *CookieSessionBackend*. A *signed* cookie may be viewed but not modified by the client. A *private* cookie may neither be viewed nor modified by the client.
To access session data
The constructors take a key as an argument. This is the private key for cookie session - when this value is changed, all session data is lost.
In general, you create a
`SessionStorage` middleware and initialize it with specific backend implementation,
such as a `CookieSessionBackend`. To access session data,
[*HttpRequest::session()*](../actix_web/middleware/trait.RequestSession.html#tymethod.session) [*HttpRequest::session()*](../actix_web/middleware/trait.RequestSession.html#tymethod.session)
method has to be used. This method returns must be used. This method returns a
[*Session*](../actix_web/middleware/struct.Session.html) object, which allows to get or set [*Session*](../actix_web/middleware/struct.Session.html) object, which allows us to get or set
session data. session data.
```rust ```rust
# extern crate actix; # extern crate actix;
# extern crate actix_web; # extern crate actix_web;
use actix_web::*; use actix_web::{server, App, HttpRequest, Result};
use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend}; use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend};
fn index(mut req: HttpRequest) -> Result<&'static str> { fn index(mut req: HttpRequest) -> Result<&'static str> {
@ -197,12 +203,11 @@ fn index(mut req: HttpRequest) -> Result<&'static str> {
fn main() { fn main() {
# let sys = actix::System::new("basic-example"); # let sys = actix::System::new("basic-example");
HttpServer::new( server::new(
|| Application::new() || App::new()
.middleware(SessionStorage::new( // <- create session middleware .middleware(SessionStorage::new( // <- create session middleware
CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
.secure(false) .secure(false)
.finish()
))) )))
.bind("127.0.0.1:59880").unwrap() .bind("127.0.0.1:59880").unwrap()
.start(); .start();
@ -210,3 +215,37 @@ fn main() {
# let _ = sys.run(); # let _ = sys.run();
} }
``` ```
## Error handlers
`ErrorHandlers` middleware allows us to provide custom handlers for responses.
You can use the `ErrorHandlers::handler()` method to register a custom error handler
for a specific status code. You can modify an existing response or create a completly new
one. The error handler can return a response immediately or return a future that resolves
into a response.
```rust
# extern crate actix_web;
use actix_web::{
App, HttpRequest, HttpResponse, Result,
http, middleware::Response, middleware::ErrorHandlers};
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
let mut builder = resp.into_builder();
builder.header(http::header::CONTENT_TYPE, "application/json");
Ok(Response::Done(builder.into()))
}
fn main() {
let app = App::new()
.middleware(
ErrorHandlers::new()
.handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500))
.resource("/test", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok());
r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
})
.finish();
}
```

View File

@ -2,21 +2,21 @@
## Individual file ## Individual file
It is possible to serve static files with custom path pattern and `NamedFile`. To It is possible to serve static files with a custom path pattern and `NamedFile`. To
match path tail we can use `[.*]` regex. match a path tail, we can use a `[.*]` regex.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
use actix_web::*;
use std::path::PathBuf; use std::path::PathBuf;
use actix_web::{App, HttpRequest, Result, http::Method, fs::NamedFile};
fn index(req: HttpRequest) -> Result<fs::NamedFile> { fn index(req: HttpRequest) -> Result<NamedFile> {
let path: PathBuf = req.match_info().query("tail")?; let path: PathBuf = req.match_info().query("tail")?;
Ok(fs::NamedFile::open(path)?) Ok(NamedFile::open(path)?)
} }
fn main() { fn main() {
Application::new() App::new()
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
.finish(); .finish();
} }
@ -24,26 +24,31 @@ fn main() {
## Directory ## Directory
To serve files from specific directory and sub-directories `StaticFiles` could be used. To serve files from specific directories and sub-directories, `StaticFiles` can be used.
`StaticFiles` must be registered with `Application::handler()` method otherwise `StaticFiles` must be registered with an `App::handler()` method, otherwise
it won't be able to server sub-paths. it will be unable to serve sub-paths.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
use actix_web::*; use actix_web::*;
fn main() { fn main() {
Application::new() App::new()
.handler("/static", fs::StaticFiles::new(".", true)) .handler(
"/static",
fs::StaticFiles::new(".")
.show_files_listing())
.finish(); .finish();
} }
``` ```
First parameter is a base directory. Second parameter is *show_index*, if it is set to *true* The parameter is the base directory. By default files listing for sub-directories
directory listing would be returned for directories, if it is set to *false* is disabled. Attempt to load directory listing will return *404 Not Found* response.
then *404 Not Found* would be returned instead of directory listing. To enable files listing, use
[*StaticFiles::show_files_listing()*](../actix_web/s/struct.StaticFiles.html#method.show_files_listing)
method.
Instead of showing files listing for directory, it is possible to redirect to specific Instead of showing files listing for directory, it is possible to redirect
index file. Use to a specific index file. Use the
[*StaticFiles::index_file()*](../actix_web/s/struct.StaticFiles.html#method.index_file) [*StaticFiles::index_file()*](../actix_web/s/struct.StaticFiles.html#method.index_file)
method to configure this redirect. method to configure this redirect.

View File

@ -1,13 +1,15 @@
# HTTP/2.0 # HTTP/2.0
Actix web automatically upgrades connection to *HTTP/2.0* if possible. Actix web automatically upgrades connections to *HTTP/2.0* if possible.
## Negotiation ## Negotiation
*HTTP/2.0* protocol over tls without prior knowledge requires *HTTP/2.0* protocol over tls without prior knowledge requires
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only [tls alpn](https://tools.ietf.org/html/rfc7301).
`rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation.
With enable `alpn` feature `HttpServer` provides > Currently, only `rust-openssl` has support.
`alpn` negotiation requires enabling the feature. When enabled, `HttpServer` provides the
[serve_tls](../actix_web/struct.HttpServer.html#method.serve_tls) method. [serve_tls](../actix_web/struct.HttpServer.html#method.serve_tls) method.
```toml ```toml
@ -28,17 +30,17 @@ fn main() {
builder.set_certificate_chain_file("cert.pem").unwrap(); builder.set_certificate_chain_file("cert.pem").unwrap();
HttpServer::new( HttpServer::new(
|| Application::new() || App::new()
.resource("/index.html", |r| r.f(index))) .resource("/index.html", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap(); .bind("127.0.0.1:8080").unwrap();
.serve_ssl(builder).unwrap(); .serve_ssl(builder).unwrap();
} }
``` ```
Upgrade to *HTTP/2.0* schema described in Upgrades to *HTTP/2.0* schema described in
[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported. [rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported.
Starting *HTTP/2* with prior knowledge is supported for both clear text connection Starting *HTTP/2* with prior knowledge is supported for both clear text connection
and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4) and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4)
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls) > Check out [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls)
for concrete example. > for a concrete example.

View File

@ -2,14 +2,15 @@
## Diesel ## Diesel
At the moment of 1.0 release Diesel does not support asynchronous operations. At the moment, Diesel 1.0 does not support asynchronous operations,
But it possible to use `actix` synchronous actor system as a db interface api. but it possible to use the `actix` synchronous actor system as a database interface api.
Technically sync actors are worker style actors, multiple of them
can be run in parallel and process messages from same queue (sync actors work in mpmc mode).
Let's create simple db api that can insert new user row into sqlite table. Technically, sync actors are worker style actors. Multiple sync actors
We have to define sync actor and connection that this actor will use. Same approach can be run in parallel and process messages from same queue. Sync actors work in mpsc mode.
could be used for other databases.
Let's create a simple database api that can insert a new user row into a SQLite table.
We must define a sync actor and a connection that this actor will use. The same approach
can be used for other databases.
```rust,ignore ```rust,ignore
use actix::prelude::*; use actix::prelude::*;
@ -21,7 +22,7 @@ impl Actor for DbExecutor {
} }
``` ```
This is definition of our actor. Now we need to define *create user* message and response. This is the definition of our actor. Now, we must define the *create user* message and response.
```rust,ignore ```rust,ignore
struct CreateUser { struct CreateUser {
@ -33,8 +34,8 @@ impl Message for CreateUser {
} }
``` ```
We can send `CreateUser` message to `DbExecutor` actor, and as result we get We can send a `CreateUser` message to the `DbExecutor` actor, and as a result, we will receive a
`User` model. Now we need to define actual handler implementation for this message. `User` model instance. Next, we must define the handler implementation for this message.
```rust,ignore ```rust,ignore
impl Handler<CreateUser> for DbExecutor { impl Handler<CreateUser> for DbExecutor {
@ -51,7 +52,7 @@ impl Handler<CreateUser> for DbExecutor {
name: &msg.name, name: &msg.name,
}; };
// normal diesl operations // normal diesel operations
diesel::insert_into(users) diesel::insert_into(users)
.values(&new_user) .values(&new_user)
.execute(&self.0) .execute(&self.0)
@ -67,8 +68,8 @@ impl Handler<CreateUser> for DbExecutor {
} }
``` ```
That is it. Now we can use *DbExecutor* actor from any http handler or middleware. That's it! Now, we can use the *DbExecutor* actor from any http handler or middleware.
All we need is to start *DbExecutor* actors and store address in a state where http handler All we need is to start *DbExecutor* actors and store the address in a state where http handler
can access it. can access it.
```rust,ignore ```rust,ignore
@ -87,7 +88,7 @@ fn main() {
// Start http server // Start http server
HttpServer::new(move || { HttpServer::new(move || {
Application::with_state(State{db: addr.clone()}) App::with_state(State{db: addr.clone()})
.resource("/{name}", |r| r.method(Method::GET).a(index))}) .resource("/{name}", |r| r.method(Method::GET).a(index))})
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.start().unwrap(); .start().unwrap();
@ -97,9 +98,9 @@ fn main() {
} }
``` ```
And finally we can use address in a request handler. We get message response We will use the address in a request handler. The handle returns a future object;
asynchronously, so handler needs to return future object, also `Route::a()` needs to be thus, we receive the message response asynchronously.
used for async handler registration. `Route::a()` must be used for async handler registration.
```rust,ignore ```rust,ignore
@ -112,16 +113,16 @@ fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>>
.from_err() .from_err()
.and_then(|res| { .and_then(|res| {
match res { match res {
Ok(user) => Ok(httpcodes::HttpOk.build().json(user)?), Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(httpcodes::HttpInternalServerError.into()) Err(_) => Ok(HttpResponse::InternalServerError().into())
} }
}) })
.responder() .responder()
} }
``` ```
Full example is available in > A full example is available in the
[examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/). > [examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/).
More information on sync actors could be found in > More information on sync actors can be found in the
[actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html). > [actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html).

View File

@ -1,14 +1,10 @@
# Getting Started # Getting Started
Lets create and run our first actix web application. Well create a new Cargo project Lets write our first actix web application!
that depends on actix web and then run the application.
In previous section we already installed required rust version. Now let's create new cargo projects.
## Hello, world! ## Hello, world!
Lets write our first actix web application! Start by creating a new binary-based Start by creating a new binary-based Cargo project and changing into the new directory:
Cargo project and changing into the new directory:
```bash ```bash
cargo new hello-world --bin cargo new hello-world --bin
@ -21,14 +17,15 @@ contains the following:
```toml ```toml
[dependencies] [dependencies]
actix = "0.5" actix = "0.5"
actix-web = "0.4" actix-web = "0.5"
``` ```
In order to implement a web server, first we need to create a request handler. In order to implement a web server, we first need to create a request handler.
A request handler is a function that accepts a `HttpRequest` instance as its only parameter A request handler is a function that accepts an `HttpRequest` instance as its only parameter
and returns a type that can be converted into `HttpResponse`: and returns a type that can be converted into `HttpResponse`:
Filename: src/main.rs
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# use actix_web::*; # use actix_web::*;
@ -48,31 +45,31 @@ request handler with the application's `resource` on a particular *HTTP method*
# "Hello world!" # "Hello world!"
# } # }
# fn main() { # fn main() {
Application::new() App::new()
.resource("/", |r| r.f(index)); .resource("/", |r| r.f(index));
# } # }
``` ```
After that, application instance can be used with `HttpServer` to listen for incoming After that, the application instance can be used with `HttpServer` to listen for incoming
connections. Server accepts function that should return `HttpHandler` instance: connections. The server accepts a function that should return an `HttpHandler` instance:
```rust,ignore ```rust,ignore
HttpServer::new( HttpServer::new(
|| Application::new() || App::new()
.resource("/", |r| r.f(index))) .resource("/", |r| r.f(index)))
.bind("127.0.0.1:8088")? .bind("127.0.0.1:8088")?
.run(); .run();
``` ```
That's it. Now, compile and run the program with cargo run. That's it! Now, compile and run the program with `cargo run`.
Head over to ``http://localhost:8088/`` to see the results. Head over to ``http://localhost:8088/`` to see the results.
Here is full source of main.rs file: The full source of src/main.rs is listed below:
```rust ```rust
# use std::thread; # use std::thread;
extern crate actix_web; extern crate actix_web;
use actix_web::*; use actix_web::{server, App, HttpRequest, HttpResponse};
fn index(req: HttpRequest) -> &'static str { fn index(req: HttpRequest) -> &'static str {
"Hello world!" "Hello world!"
@ -80,11 +77,11 @@ fn index(req: HttpRequest) -> &'static str {
fn main() { fn main() {
# // In the doctest suite we can't run blocking code - deliberately leak a thread # // In the doctest suite we can't run blocking code - deliberately leak a thread
# // If copying this example in show-all mode make sure you skip the thread spawn # // If copying this example in show-all mode, make sure you skip the thread spawn
# // call. # // call.
# thread::spawn(|| { # thread::spawn(|| {
HttpServer::new( server::HttpServer::new(
|| Application::new() || App::new()
.resource("/", |r| r.f(index))) .resource("/", |r| r.f(index)))
.bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088") .bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088")
.run(); .run();
@ -92,7 +89,11 @@ fn main() {
} }
``` ```
Note on `actix` crate. Actix web framework is built on top of actix actor library. > **Note**: actix web is built upon [actix](https://github.com/actix/actix),
`actix::System` initializes actor system, `HttpServer` is an actor and must run within > an [actor model](https://en.wikipedia.org/wiki/Actor_model) framework in Rust.
properly configured actix system. For more information please check
[actix documentation](https://actix.github.io/actix/actix/) `actix::System` initializes actor system, `HttpServer` is an actor and must run within a
properly configured actix system.
> For more information, check out the [actix documentation](https://actix.github.io/actix/actix/)
> and [actix guide](https://actix.github.io/actix/guide/).

View File

@ -1,87 +1,88 @@
# Application # Application
Actix web provides some primitives to build web servers and applications with Rust. Actix web provides various primitives to build web servers and applications with Rust.
It provides routing, middlewares, pre-processing of requests, and post-processing of responses, It provides routing, middlewares, pre-processing of requests, post-processing of responses,
websocket protocol handling, multipart streams, etc. websocket protocol handling, multipart streams, etc.
All actix web server is built around `Application` instance. All actix web servers are built around the `App` instance.
It is used for registering routes for resources, middlewares. It is used for registering routes for resources and middlewares.
Also it stores application specific state that is shared across all handlers It also stores application state shared across all handlers within same application.
within same application.
Application acts as namespace for all routes, i.e all routes for specific application Applications act as a namespace for all routes, i.e all routes for a specific application
has same url path prefix. Application prefix always contains leading "/" slash. have the same url path prefix. The application prefix always contains a leading "/" slash.
If supplied prefix does not contain leading slash, it get inserted. If a supplied prefix does not contain leading slash, it is automatically inserted.
Prefix should consists of value path segments. i.e for application with prefix `/app` The prefix should consist of value path segments.
any request with following paths `/app`, `/app/` or `/app/test` would match,
but path `/application` would not match. > For an application with prefix `/app`,
> any request with the paths `/app`, `/app/`, or `/app/test` would match;
> however, the path `/application` would not match.
```rust,ignore ```rust,ignore
# extern crate actix_web; # extern crate actix_web;
# extern crate tokio_core; # extern crate tokio_core;
# use actix_web::*; # use actix_web::{*, http::Method};
# fn index(req: HttpRequest) -> &'static str { # fn index(req: HttpRequest) -> &'static str {
# "Hello world!" # "Hello world!"
# } # }
# fn main() { # fn main() {
let app = Application::new() let app = App::new()
.prefix("/app") .prefix("/app")
.resource("/index.html", |r| r.method(Method::GET).f(index)) .resource("/index.html", |r| r.method(Method::GET).f(index))
.finish() .finish()
# } # }
``` ```
In this example application with `/app` prefix and `index.html` resource In this example, an application with the `/app` prefix and a `index.html` resource
get created. This resource is available as on `/app/index.html` url. are created. This resource is available through the `/app/index.html` url.
For more information check
[*URL Matching*](./qs_5.html#using-a-application-prefix-to-compose-applications) section.
Multiple applications could be served with one server: > For more information, check the
> [URL Dispatch](./qs_5.html#using-a-application-prefix-to-compose-applications) section.
Multiple applications can be served with one server:
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# extern crate tokio_core; # extern crate tokio_core;
# use tokio_core::net::TcpStream; # use tokio_core::net::TcpStream;
# use std::net::SocketAddr; # use std::net::SocketAddr;
use actix_web::*; use actix_web::{server, App, HttpResponse};
fn main() { fn main() {
HttpServer::new(|| vec![ server::new(|| vec![
Application::new() App::new()
.prefix("/app1") .prefix("/app1")
.resource("/", |r| r.f(|r| httpcodes::HttpOk)), .resource("/", |r| r.f(|r| HttpResponse::Ok())),
Application::new() App::new()
.prefix("/app2") .prefix("/app2")
.resource("/", |r| r.f(|r| httpcodes::HttpOk)), .resource("/", |r| r.f(|r| HttpResponse::Ok())),
Application::new() App::new()
.resource("/", |r| r.f(|r| httpcodes::HttpOk)), .resource("/", |r| r.f(|r| HttpResponse::Ok())),
]); ]);
} }
``` ```
All `/app1` requests route to first application, `/app2` to second and then all other to third. All `/app1` requests route to the first application, `/app2` to the second, and all other to the third.
Applications get matched based on registration order, if application with more general **Applications get matched based on registration order**. If an application with a more generic
prefix is registered before less generic, that would effectively block less generic prefix is registered before a less generic one, it would effectively block the less generic
application to get matched. For example if *application* with prefix "/" get registered application matching. For example, if an `App` with the prefix `"/"` was registered
as first application, it would match all incoming requests. as the first application, it would match all incoming requests.
## State ## State
Application state is shared with all routes and resources within same application. Application state is shared with all routes and resources within the same application.
State could be accessed with `HttpRequest::state()` method as a read-only item When using an http actor,state can be accessed with the `HttpRequest::state()` as read-only,
but interior mutability pattern with `RefCell` could be used to archive state mutability. but interior mutability with `RefCell` can be used to achieve state mutability.
State could be accessed with `HttpContext::state()` in case of http actor. State is also available for route matching predicates and middlewares.
State also available to route matching predicates and middlewares.
Let's write simple application that uses shared state. We are going to store requests count Let's write a simple application that uses shared state. We are going to store request count
in the state: in the state:
```rust ```rust
# extern crate actix; # extern crate actix;
# extern crate actix_web; # extern crate actix_web;
# #
use actix_web::*;
use std::cell::Cell; use std::cell::Cell;
use actix_web::{App, HttpRequest, http};
// This struct represents state // This struct represents state
struct AppState { struct AppState {
@ -96,14 +97,14 @@ fn index(req: HttpRequest<AppState>) -> String {
} }
fn main() { fn main() {
Application::with_state(AppState{counter: Cell::new(0)}) App::with_state(AppState{counter: Cell::new(0)})
.resource("/", |r| r.method(Method::GET).f(index)) .resource("/", |r| r.method(http::Method::GET).f(index))
.finish(); .finish();
} }
``` ```
Note on application state, http server accepts application factory rather than application > **Note**: http server accepts an application factory rather than an application
instance. Http server construct application instance for each thread, so application state > instance. Http server constructs an application instance for each thread, thus application state
must be constructed multiple times. If you want to share state between different thread > must be constructed multiple times. If you want to share state between different threads, a
shared object should be used, like `Arc`. Application state does not need to be `Send` and `Sync` > shared object should be used, e.g. `Arc`. Application state does not need to be `Send` and `Sync`,
but application factory must be `Send` + `Sync`. > but the application factory must be `Send` + `Sync`.

View File

@ -1,26 +1,31 @@
# Server # Server
[*HttpServer*](../actix_web/struct.HttpServer.html) type is responsible for The [**HttpServer**](../actix_web/server/struct.HttpServer.html) type is responsible for
serving http requests. *HttpServer* accept application factory as a parameter, serving http requests.
Application factory must have `Send` + `Sync` boundaries. More about that in
*multi-threading* section. To bind to specific socket address `bind()` must be used. `HttpServer` accepts an application factory as a parameter, and the
This method could be called multiple times. To start http server one of the *start* application factory must have `Send` + `Sync` boundaries. More about that in the
methods could be used. `start()` method start simple server, `start_tls()` or `start_ssl()` *multi-threading* section.
starts ssl server. *HttpServer* is an actix actor, it has to be initialized
within properly configured actix system: To bind to a specific socket address, `bind()` must be used, and it may be called multiple times.
To start the http server, one of the start methods.
- use `start()` for a simple server
- use `start_tls()` or `start_ssl()` for a ssl server
`HttpServer` is an actix actor. It must be initialized within a properly configured actix system:
```rust ```rust
# extern crate actix; # extern crate actix;
# extern crate actix_web; # extern crate actix_web;
use actix::*; use actix_web::{server::HttpServer, App, HttpResponse};
use actix_web::*;
fn main() { fn main() {
let sys = actix::System::new("guide"); let sys = actix::System::new("guide");
HttpServer::new( HttpServer::new(
|| Application::new() || App::new()
.resource("/", |r| r.h(httpcodes::HttpOk))) .resource("/", |r| r.f(|_| HttpResponse::Ok())))
.bind("127.0.0.1:59080").unwrap() .bind("127.0.0.1:59080").unwrap()
.start(); .start();
@ -29,35 +34,35 @@ fn main() {
} }
``` ```
It is possible to start server in separate thread with *spawn()* method. In that > It is possible to start a server in a separate thread with the `spawn()` method. In that
case server spawns new thread and create new actix system in it. To stop > case the server spawns a new thread and creates a new actix system in it. To stop
this server send `StopServer` message. > this server, send a `StopServer` message.
Http server is implemented as an actix actor. It is possible to communicate with server `HttpServer` is implemented as an actix actor. It is possible to communicate with the server
via messaging system. All start methods like `start()`, `start_ssl()`, etc returns via a messaging system. All start methods, e.g. `start()` and `start_ssl()`, return the
address of the started http server. Actix http server accept several messages: address of the started http server. It accepts several messages:
* `PauseServer` - Pause accepting incoming connections - `PauseServer` - Pause accepting incoming connections
* `ResumeServer` - Resume accepting incoming connections - `ResumeServer` - Resume accepting incoming connections
* `StopServer` - Stop incoming connection processing, stop all workers and exit - `StopServer` - Stop incoming connection processing, stop all workers and exit
```rust ```rust
# extern crate futures; # extern crate futures;
# extern crate actix; # extern crate actix;
# extern crate actix_web; # extern crate actix_web;
# use futures::Future; # use futures::Future;
use actix_web::*;
use std::thread; use std::thread;
use std::sync::mpsc; use std::sync::mpsc;
use actix_web::{server, App, HttpResponse};
fn main() { fn main() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
thread::spawn(move || { thread::spawn(move || {
let sys = actix::System::new("http-server"); let sys = actix::System::new("http-server");
let addr = HttpServer::new( let addr = server::new(
|| Application::new() || App::new()
.resource("/", |r| r.h(httpcodes::HttpOk))) .resource("/", |r| r.f(|_| HttpResponse::Ok())))
.bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
.shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds .shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds
.start(); .start();
@ -73,30 +78,32 @@ fn main() {
## Multi-threading ## Multi-threading
Http server automatically starts number of http workers, by default `HttpServer` automatically starts an number of http workers, by default
this number is equal to number of logical cpu in the system. This number this number is equal to number of logical CPUs in the system. This number
could be overridden with `HttpServer::threads()` method. can be overridden with the `HttpServer::threads()` method.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# extern crate tokio_core; # extern crate tokio_core;
use actix_web::*; use actix_web::{App, HttpResponse, server::HttpServer};
fn main() { fn main() {
HttpServer::new( HttpServer::new(
|| Application::new() || App::new()
.resource("/", |r| r.h(httpcodes::HttpOk))) .resource("/", |r| r.f(|_| HttpResponse::Ok())))
.threads(4); // <- Start 4 workers .threads(4); // <- Start 4 workers
} }
``` ```
Server create separate application instance for each created worker. Application state The server creates a separate application instance for each created worker. Application state
is not shared between threads, to share state `Arc` could be used. Application state is not shared between threads. To share state, `Arc` could be used.
does not need to be `Send` and `Sync` but application factory must be `Send` + `Sync`.
> Application state does not need to be `Send` and `Sync`,
> but factories must be `Send` + `Sync`.
## SSL ## SSL
There are two `tls` and `alpn` features for ssl server. `tls` feature is for `native-tls` There are two features for ssl server: `tls` and `alpn`. The `tls` feature is for `native-tls`
integration and `alpn` is for `openssl`. integration and `alpn` is for `openssl`.
```toml ```toml
@ -109,96 +116,95 @@ use std::fs::File;
use actix_web::*; use actix_web::*;
fn main() { fn main() {
let mut file = File::open("identity.pfx").unwrap(); // load ssl keys
let mut pkcs12 = vec![]; let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
file.read_to_end(&mut pkcs12).unwrap(); builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap(); builder.set_certificate_chain_file("cert.pem").unwrap();
HttpServer::new( server::new(
|| Application::new() || App::new()
.resource("/index.html", |r| r.f(index))) .resource("/index.html", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.serve_ssl(pkcs12).unwrap(); .serve_ssl(builder).unwrap();
} }
``` ```
Note on *HTTP/2.0* protocol over tls without prior knowledge, it requires > **Note**: the *HTTP/2.0* protocol requires
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only > [tls alpn](https://tools.ietf.org/html/rfc7301).
`openssl` has `alpn ` support. > At the moment, only `openssl` has `alpn` support.
> For a full example, check out
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls) > [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls).
for full example.
## Keep-Alive ## Keep-Alive
Actix can wait for requests on a keep-alive connection. *Keep alive* Actix can wait for requests on a keep-alive connection.
connection behavior is defined by server settings.
* `75` or `Some(75)` or `KeepAlive::Timeout(75)` - enable 75 sec *keep alive* timer according > *keep alive* connection behavior is defined by server settings.
request and response settings.
* `None` or `KeepAlive::Disabled` - disable *keep alive*. - `75`, `Some(75)`, `KeepAlive::Timeout(75)` - enable 75 second *keep alive* timer.
* `KeepAlive::Tcp(75)` - Use `SO_KEEPALIVE` socket option. - `None` or `KeepAlive::Disabled` - disable *keep alive*.
- `KeepAlive::Tcp(75)` - use `SO_KEEPALIVE` socket option.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# extern crate tokio_core; # extern crate tokio_core;
use actix_web::*; use actix_web::{server, App, HttpResponse};
fn main() { fn main() {
HttpServer::new(|| server::new(||
Application::new() App::new()
.resource("/", |r| r.h(httpcodes::HttpOk))) .resource("/", |r| r.f(|_| HttpResponse::Ok())))
.keep_alive(75); // <- Set keep-alive to 75 seconds .keep_alive(75); // <- Set keep-alive to 75 seconds
HttpServer::new(|| server::new(||
Application::new() App::new()
.resource("/", |r| r.h(httpcodes::HttpOk))) .resource("/", |r| r.f(|_| HttpResponse::Ok())))
.keep_alive(server::KeepAlive::Tcp(75)); // <- Use `SO_KEEPALIVE` socket option. .keep_alive(server::KeepAlive::Tcp(75)); // <- Use `SO_KEEPALIVE` socket option.
HttpServer::new(|| server::new(||
Application::new() App::new()
.resource("/", |r| r.h(httpcodes::HttpOk))) .resource("/", |r| r.f(|_| HttpResponse::Ok())))
.keep_alive(None); // <- Disable keep-alive .keep_alive(None); // <- Disable keep-alive
} }
``` ```
If first option is selected then *keep alive* state If the first option is selected, then *keep alive* state is
calculated based on response's *connection-type*. By default calculated based on the response's *connection-type*. By default
`HttpResponse::connection_type` is not defined in that case *keep alive* `HttpResponse::connection_type` is not defined. In that case *keep alive* is
defined by request's http version. Keep alive is off for *HTTP/1.0* defined by the request's http version.
and is on for *HTTP/1.1* and *HTTP/2.0*.
*Connection type* could be change with `HttpResponseBuilder::connection_type()` method. > *keep alive* is **off** for *HTTP/1.0* and is **on** for *HTTP/1.1* and *HTTP/2.0*.
*Connection type* can be change with `HttpResponseBuilder::connection_type()` method.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# use actix_web::httpcodes::*; use actix_web::{HttpRequest, HttpResponse, http};
use actix_web::*;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
HttpOk.build() HttpResponse::Ok()
.connection_type(headers::ConnectionType::Close) // <- Close connection .connection_type(http::ConnectionType::Close) // <- Close connection
.force_close() // <- Alternative method .force_close() // <- Alternative method
.finish().unwrap() .finish()
} }
# fn main() {} # fn main() {}
``` ```
## Graceful shutdown ## Graceful shutdown
Actix http server support graceful shutdown. After receiving a stop signal, workers `HttpServer` supports graceful shutdown. After receiving a stop signal, workers
have specific amount of time to finish serving requests. Workers still alive after the have a specific amount of time to finish serving requests. Any workers still alive after the
timeout are force dropped. By default shutdown timeout sets to 30 seconds. timeout are force-dropped. By default the shutdown timeout is set to 30 seconds.
You can change this parameter with `HttpServer::shutdown_timeout()` method. You can change this parameter with the `HttpServer::shutdown_timeout()` method.
You can send stop message to server with server address and specify if you what You can send a stop message to the server with the server address and specify if you want
graceful shutdown or not. `start()` methods return address of the server. graceful shutdown or not. The `start()` methods returns address of the server.
Http server handles several OS signals. *CTRL-C* is available on all OSs, `HttpServer` handles several OS signals. *CTRL-C* is available on all OSs,
other signals are available on unix systems. other signals are available on unix systems.
* *SIGINT* - Force shutdown workers - *SIGINT* - Force shutdown workers
* *SIGTERM* - Graceful shutdown workers - *SIGTERM* - Graceful shutdown workers
* *SIGQUIT* - Force shutdown workers - *SIGQUIT* - Force shutdown workers
It is possible to disable signals handling with `HttpServer::disable_signals()` method. > It is possible to disable signal handling with `HttpServer::disable_signals()` method.

View File

@ -1,17 +1,18 @@
# Handler # Handler
A request handler can by any object that implements A request handler can be any object that implements
[*Handler trait*](../actix_web/dev/trait.Handler.html). [`Handler` trait](../actix_web/dev/trait.Handler.html).
Request handling happen in two stages. First handler object get called.
Handle can return any object that implements Request handling happens in two stages. First the handler object is called,
[*Responder trait*](../actix_web/trait.Responder.html#foreign-impls). returning any object that implements the
Then `respond_to()` get called on returned object. And finally [`Responder` trait](../actix_web/trait.Responder.html#foreign-impls).
result of the `respond_to()` call get converted to `Reply` object. Then, `respond_to()` is called on the returned object, converting itself to a `Reply` or `Error`.
By default actix provides `Responder` implementations for some standard types, By default actix provides `Responder` implementations for some standard types,
like `&'static str`, `String`, etc. such as `&'static str`, `String`, etc.
For complete list of implementations check
[*Responder documentation*](../actix_web/trait.Responder.html#foreign-impls). > For a complete list of implementations, check
> [*Responder documentation*](../actix_web/trait.Responder.html#foreign-impls).
Examples of valid handlers: Examples of valid handlers:
@ -39,23 +40,22 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
} }
``` ```
Some notes on shared application state and handler state. If you noticed *Handler* trait is generic over *S*, which defines the application state's type.
*Handler* trait is generic over *S*, which defines application state type. So Application state is accessible from the handler with the `HttpRequest::state()` method;
application state is accessible from handler with `HttpRequest::state()` method. however, state is accessible as a read-only reference. If you need mutable access to state,
But state is accessible as a read-only reference, if you need mutable access to state it must be implemented.
you have to implement it yourself. On other hand handler can mutable access it's own state
as `handle` method takes mutable reference to *self*. Beware, actix creates multiple copies
of application state and handlers, unique for each thread, so if you run your
application in several threads actix will create same amount as number of threads
of application state objects and handler objects.
Here is example of handler that stores number of processed requests: > **Note**: Alternatively, the handler can mutably access its own state because the `handle` method takes
> mutable reference to *self*. **Beware**, actix creates multiple copies
> of the application state and the handlers, unique for each thread. If you run your
> application in several threads, actix will create the same amount as number of threads
> of application state objects and handler objects.
Here is an example of a handler that stores the number of processed requests:
```rust ```rust
# extern crate actix;
# extern crate actix_web; # extern crate actix_web;
use actix_web::*; use actix_web::{App, HttpRequest, HttpResponse, dev::Handler};
use actix_web::dev::Handler;
struct MyHandler(usize); struct MyHandler(usize);
@ -65,20 +65,19 @@ impl<S> Handler<S> for MyHandler {
/// Handle request /// Handle request
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
self.0 += 1; self.0 += 1;
httpcodes::HttpOk.into() HttpResponse::Ok().into()
} }
} }
# fn main() {} # fn main() {}
``` ```
This handler will work, but `self.0` value will be different depends on number of threads and Although this handler will work, `self.0` will be different depending on the number of threads and
number of requests processed per thread. Proper implementation would use `Arc` and `AtomicUsize` number of requests processed per thread. A proper implementation would use `Arc` and `AtomicUsize`.
```rust ```rust
# extern crate actix; # extern crate actix;
# extern crate actix_web; # extern crate actix_web;
use actix_web::*; use actix_web::{server, App, HttpRequest, HttpResponse, dev::Handler};
use actix_web::dev::Handler;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
@ -90,7 +89,7 @@ impl<S> Handler<S> for MyHandler {
/// Handle request /// Handle request
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
self.0.fetch_add(1, Ordering::Relaxed); self.0.fetch_add(1, Ordering::Relaxed);
httpcodes::HttpOk.into() HttpResponse::Ok().into()
} }
} }
@ -99,10 +98,10 @@ fn main() {
let inc = Arc::new(AtomicUsize::new(0)); let inc = Arc::new(AtomicUsize::new(0));
HttpServer::new( server::new(
move || { move || {
let cloned = inc.clone(); let cloned = inc.clone();
Application::new() App::new()
.resource("/", move |r| r.h(MyHandler(cloned))) .resource("/", move |r| r.h(MyHandler(cloned)))
}) })
.bind("127.0.0.1:8088").unwrap() .bind("127.0.0.1:8088").unwrap()
@ -114,15 +113,16 @@ fn main() {
} }
``` ```
Be careful with synchronization primitives like *Mutex* or *RwLock*. Actix web framework > Be careful with synchronization primitives like `Mutex` or `RwLock`. Actix web framework
handles request asynchronously, by blocking thread execution all concurrent > handles requests asynchronously. By blocking thread execution, all concurrent
request handling processes would block. If you need to share or update some state > request handling processes would block. If you need to share or update some state
from multiple threads consider using [actix](https://actix.github.io/actix/actix/) actor system. > from multiple threads, consider using the [actix](https://actix.github.io/actix/actix/) actor system.
## Response with custom type ## Response with custom type
To return custom type directly from handler function, type needs to implement `Responder` trait. To return a custom type directly from a handler function, the type needs to implement the `Responder` trait.
Let's create response for custom type that serializes to `application/json` response:
Let's create a response for a custom type that serializes to an `application/json` response:
```rust ```rust
# extern crate actix; # extern crate actix;
@ -130,7 +130,7 @@ Let's create response for custom type that serializes to `application/json` resp
extern crate serde; extern crate serde;
extern crate serde_json; extern crate serde_json;
#[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_derive;
use actix_web::*; use actix_web::{server, App, HttpRequest, HttpResponse, Error, Responder, http};
#[derive(Serialize)] #[derive(Serialize)]
struct MyObj { struct MyObj {
@ -142,13 +142,13 @@ impl Responder for MyObj {
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse> { fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
let body = serde_json::to_string(&self)?; let body = serde_json::to_string(&self)?;
// Create response and set content type // Create response and set content type
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(body)?) .body(body))
} }
} }
@ -160,9 +160,9 @@ fn index(req: HttpRequest) -> MyObj {
fn main() { fn main() {
let sys = actix::System::new("example"); let sys = actix::System::new("example");
HttpServer::new( server::new(
|| Application::new() || App::new()
.resource("/", |r| r.method(Method::GET).f(index))) .resource("/", |r| r.method(http::Method::GET).f(index)))
.bind("127.0.0.1:8088").unwrap() .bind("127.0.0.1:8088").unwrap()
.start(); .start();
@ -174,11 +174,10 @@ fn main() {
## Async handlers ## Async handlers
There are two different types of async handlers. There are two different types of async handlers. Response objects can be generated asynchronously
or more precisely, any type that implements the [*Responder*](../actix_web/trait.Responder.html) trait.
Response object could be generated asynchronously or more precisely, any type In this case, the handler must return a `Future` object that resolves to the *Responder* type, i.e:
that implements [*Responder*](../actix_web/trait.Responder.html) trait. In this case handle must
return `Future` object that resolves to *Responder* type, i.e:
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
@ -187,29 +186,30 @@ return `Future` object that resolves to *Responder* type, i.e:
# use actix_web::*; # use actix_web::*;
# use bytes::Bytes; # use bytes::Bytes;
# use futures::stream::once; # use futures::stream::once;
# use futures::future::{FutureResult, result}; # use futures::future::{Future, result};
fn index(req: HttpRequest) -> FutureResult<HttpResponse, Error> { fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
result(HttpResponse::Ok() result(Ok(HttpResponse::Ok()
.content_type("text/html") .content_type("text/html")
.body(format!("Hello!")) .body(format!("Hello!"))))
.map_err(|e| e.into())) .responder()
} }
fn index2(req: HttpRequest) -> FutureResult<&'static str, Error> { fn index2(req: HttpRequest) -> Box<Future<Item=&'static str, Error=Error>> {
result(Ok("Welcome!")) result(Ok("Welcome!"))
.responder()
} }
fn main() { fn main() {
Application::new() App::new()
.resource("/async", |r| r.route().a(index)) .resource("/async", |r| r.route().a(index))
.resource("/", |r| r.route().a(index2)) .resource("/", |r| r.route().a(index2))
.finish(); .finish();
} }
``` ```
Or response body can be generated asynchronously. In this case body Or the response body can be generated asynchronously. In this case, body
must implement stream trait `Stream<Item=Bytes, Error=Error>`, i.e: must implement the stream trait `Stream<Item=Bytes, Error=Error>`, i.e:
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
@ -223,25 +223,56 @@ fn index(req: HttpRequest) -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(Body::Streaming(Box::new(body))).unwrap() .body(Body::Streaming(Box::new(body)))
} }
fn main() { fn main() {
Application::new() App::new()
.resource("/async", |r| r.f(index)) .resource("/async", |r| r.f(index))
.finish(); .finish();
} }
``` ```
Both methods could be combined. (i.e Async response with streaming body) Both methods can be combined. (i.e Async response with streaming body)
It is possible to return a `Result` where the `Result::Item` type can be `Future`.
In this example, the `index` handler can return an error immediately or return a
future that resolves to a `HttpResponse`.
```rust
# extern crate actix_web;
# extern crate futures;
# extern crate bytes;
# use actix_web::*;
# use bytes::Bytes;
# use futures::stream::once;
# use futures::future::{Future, result};
fn index(req: HttpRequest) -> Result<Box<Future<Item=HttpResponse, Error=Error>>, Error> {
if is_error() {
Err(error::ErrorBadRequest("bad request"))
} else {
Ok(Box::new(
result(Ok(HttpResponse::Ok()
.content_type("text/html")
.body(format!("Hello!"))))))
}
}
#
# fn is_error() -> bool { true }
# fn main() {
# App::new()
# .resource("/async", |r| r.route().f(index))
# .finish();
# }
```
## Different return types (Either) ## Different return types (Either)
Sometimes you need to return different types of responses. For example Sometimes, you need to return different types of responses. For example,
you can do error check and return error and return async response otherwise. you can error check and return errors, return async responses, or any result that requires two different types.
Or any result that requires two different types.
For this case [*Either*](../actix_web/enum.Either.html) type can be used. For this case, the [`Either`](../actix_web/enum.Either.html) type can be used.
*Either* allows to combine two different responder types into a single type. `Either` allows combining two different responder types into a single type.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
@ -249,25 +280,24 @@ For this case [*Either*](../actix_web/enum.Either.html) type can be used.
# use actix_web::*; # use actix_web::*;
# use futures::future::Future; # use futures::future::Future;
use futures::future::result; use futures::future::result;
use actix_web::{Either, Error, HttpResponse, httpcodes}; use actix_web::{Either, Error, HttpResponse};
type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>; type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>;
fn index(req: HttpRequest) -> RegisterResult { fn index(req: HttpRequest) -> RegisterResult {
if is_a_variant() { // <- choose variant A if is_a_variant() { // <- choose variant A
Either::A( Either::A(
httpcodes::HttpBadRequest.with_body("Bad data")) HttpResponse::BadRequest().body("Bad data"))
} else { } else {
Either::B( // <- variant B Either::B( // <- variant B
result(HttpResponse::Ok() result(Ok(HttpResponse::Ok()
.content_type("text/html") .content_type("text/html")
.body(format!("Hello!")) .body(format!("Hello!")))).responder())
.map_err(|e| e.into())).responder())
} }
} }
# fn is_a_variant() -> bool { true } # fn is_a_variant() -> bool { true }
# fn main() { # fn main() {
# Application::new() # App::new()
# .resource("/register", |r| r.f(index)) # .resource("/register", |r| r.f(index))
# .finish(); # .finish();
# } # }
@ -275,9 +305,9 @@ fn index(req: HttpRequest) -> RegisterResult {
## Tokio core handle ## Tokio core handle
Any actix web handler runs within properly configured Any actix web handler runs within a properly configured
[actix system](https://actix.github.io/actix/actix/struct.System.html) [actix system](https://actix.github.io/actix/actix/struct.System.html)
and [arbiter](https://actix.github.io/actix/actix/struct.Arbiter.html). and [arbiter](https://actix.github.io/actix/actix/struct.Arbiter.html).
You can always get access to tokio handle via You can always get access to the tokio handle via the
[Arbiter::handle()](https://actix.github.io/actix/actix/struct.Arbiter.html#method.handle) [Arbiter::handle()](https://actix.github.io/actix/actix/struct.Arbiter.html#method.handle)
method. method.

View File

@ -1,20 +1,22 @@
# Errors # Errors
Actix uses [`Error` type](../actix_web/error/struct.Error.html) Actix uses the [`Error` type](../actix_web/error/struct.Error.html)
and [`ResponseError` trait](../actix_web/error/trait.ResponseError.html) and [`ResponseError` trait](../actix_web/error/trait.ResponseError.html)
for handling handler's errors. for handling handler's errors.
Any error that implements `ResponseError` trait can be returned as error value.
*Handler* can return *Result* object, actix by default provides Any error that implements the `ResponseError` trait can be returned as an error value.
`Responder` implementation for compatible result object. Here is implementation `Handler` can return an `Result` object. By default, actix provides a
`Responder` implementation for compatible result types. Here is the implementation
definition: definition:
```rust,ignore ```rust,ignore
impl<T: Responder, E: Into<Error>> Responder for Result<T, E> impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
``` ```
And any error that implements `ResponseError` can be converted into `Error` object. Any error that implements `ResponseError` can be converted into an `Error` object.
For example if *handler* function returns `io::Error`, it would be converted
into `HttpInternalServerError` response. Implementation for `io::Error` is provided For example, if the *handler* function returns `io::Error`, it would be converted
into an `HttpInternalServerError` response. Implementation for `io::Error` is provided
by default. by default.
```rust ```rust
@ -27,7 +29,7 @@ fn index(req: HttpRequest) -> io::Result<fs::NamedFile> {
} }
# #
# fn main() { # fn main() {
# Application::new() # App::new()
# .resource(r"/a/index.html", |r| r.f(index)) # .resource(r"/a/index.html", |r| r.f(index))
# .finish(); # .finish();
# } # }
@ -35,9 +37,9 @@ fn index(req: HttpRequest) -> io::Result<fs::NamedFile> {
## Custom error response ## Custom error response
To add support for custom errors, all we need to do is just implement `ResponseError` trait To add support for custom errors, all we need to do is implement the `ResponseError` trait
for custom error. `ResponseError` trait has default implementation for the custom error type. The `ResponseError` trait has a default implementation
for `error_response()` method, it generates *500* response. for the `error_response()` method: it generates a *500* response.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
@ -58,19 +60,19 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> {
} }
# #
# fn main() { # fn main() {
# Application::new() # App::new()
# .resource(r"/a/index.html", |r| r.f(index)) # .resource(r"/a/index.html", |r| r.f(index))
# .finish(); # .finish();
# } # }
``` ```
In this example *index* handler will always return *500* response. But it is easy In this example the *index* handler always returns a *500* response. But it is easy
to return different responses for different type of errors. to return different responses for different types of errors.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
#[macro_use] extern crate failure; #[macro_use] extern crate failure;
use actix_web::*; use actix_web::{App, HttpRequest, HttpResponse, http, error};
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
enum MyError { enum MyError {
@ -86,11 +88,11 @@ impl error::ResponseError for MyError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
MyError::InternalError => HttpResponse::new( MyError::InternalError => HttpResponse::new(
StatusCode::INTERNAL_SERVER_ERROR, Body::Empty), http::StatusCode::INTERNAL_SERVER_ERROR),
MyError::BadClientData => HttpResponse::new( MyError::BadClientData => HttpResponse::new(
StatusCode::BAD_REQUEST, Body::Empty), http::StatusCode::BAD_REQUEST),
MyError::Timeout => HttpResponse::new( MyError::Timeout => HttpResponse::new(
StatusCode::GATEWAY_TIMEOUT, Body::Empty), http::StatusCode::GATEWAY_TIMEOUT),
} }
} }
} }
@ -100,7 +102,7 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> {
} }
# #
# fn main() { # fn main() {
# Application::new() # App::new()
# .resource(r"/a/index.html", |r| r.f(index)) # .resource(r"/a/index.html", |r| r.f(index))
# .finish(); # .finish();
# } # }
@ -108,8 +110,8 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> {
## Error helpers ## Error helpers
Actix provides set of error helper types. It is possible to use them to generate Actix provides a set of error helper types. It is possible to use them for generating
specific error response. We can use helper types for first example with custom error. specific error responses. We can use the helper types for the first example with a custom error.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
@ -124,25 +126,25 @@ struct MyError {
fn index(req: HttpRequest) -> Result<&'static str> { fn index(req: HttpRequest) -> Result<&'static str> {
let result: Result<&'static str, MyError> = Err(MyError{name: "test"}); let result: Result<&'static str, MyError> = Err(MyError{name: "test"});
Ok(result.map_err(error::ErrorBadRequest)?) Ok(result.map_err(|e| error::ErrorBadRequest(e))?)
} }
# fn main() { # fn main() {
# Application::new() # App::new()
# .resource(r"/a/index.html", |r| r.f(index)) # .resource(r"/a/index.html", |r| r.f(index))
# .finish(); # .finish();
# } # }
``` ```
In this example *BAD REQUEST* response get generated for `MyError` error. In this example, a *BAD REQUEST* response is generated for the `MyError` error.
## Error logging ## Error logging
Actix logs all errors with `WARN` log level. If log level set to `DEBUG` Actix logs all errors with the log level `WARN`. If log level set to `DEBUG`
and `RUST_BACKTRACE` is enabled, backtrace get logged. The Error type uses and `RUST_BACKTRACE` is enabled, the backtrace gets logged. The Error type uses
cause's error backtrace if available, if the underlying failure does not provide the cause's error backtrace if available. If the underlying failure does not provide
a backtrace, a new backtrace is constructed pointing to that conversion point a backtrace, a new backtrace is constructed pointing to that conversion point
(rather than the origin of the error). This construction only happens if there (rather than the origin of the error). This construction only happens if there
is no underlying backtrace; if it does have a backtrace no new backtrace is constructed. is no underlying backtrace; if it does have a backtrace, no new backtrace is constructed.
You can enable backtrace and debug logging with following command: You can enable backtrace and debug logging with following command:

View File

@ -1,105 +1,131 @@
# URL Dispatch # URL Dispatch
URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching URL dispatch provides a simple way for mapping URLs to `Handler` code using a simple pattern
language. If one of the patterns matches the path information associated with a request, matching language. If one of the patterns matches the path information associated with a request,
a particular handler object is invoked. A handler is a specific object that implements a particular handler object is invoked.
`Handler` trait, defined in your application, that receives the request and returns
a response object. More information is available in [handler section](../qs_4.html). > A handler is a specific object that implements the
> `Handler` trait, defined in your application, that receives the request and returns
> a response object. More information is available in the [handler section](../qs_4.html).
## Resource configuration ## Resource configuration
Resource configuration is the act of adding a new resource to an application. Resource configuration is the act of adding a new resources to an application.
A resource has a name, which acts as an identifier to be used for URL generation. A resource has a name, which acts as an identifier to be used for URL generation.
The name also allows developers to add routes to existing resources. The name also allows developers to add routes to existing resources.
A resource also has a pattern, meant to match against the *PATH* portion of a *URL*, A resource also has a pattern, meant to match against the *PATH* portion of a *URL*.
it does not match against *QUERY* portion (the portion following the scheme and It does not match against the *QUERY* portion (the portion following the scheme and
port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*). port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*).
The [Application::resource](../actix_web/struct.Application.html#method.resource) methods The [App::route](../actix_web/struct.App.html#method.route) method provides
add a single resource to application routing table. This method accepts *path pattern* simple way of registering routes. This method adds a single route to application
and resource configuration function. routing table. This method accepts a *path pattern*,
*http method* and a handler function. `route()` method could be called multiple times
for the same path, in that case, multiple routes register for the same resource path.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# use actix_web::*; use actix_web::{App, HttpRequest, HttpResponse, http::Method};
# use actix_web::httpcodes::*;
# fn index(req: HttpRequest) -> HttpResponse {
# fn index(req: HttpRequest) -> HttpResponse { unimplemented!()
# unimplemented!() }
# }
#
fn main() { fn main() {
Application::new() App::new()
.resource("/prefix", |r| r.f(index)) .route("/user/{name}", Method::GET, index)
.resource("/user/{name}", .route("/user/{name}", Method::POST, index)
|r| r.method(Method::GET).f(|req| HttpOk))
.finish(); .finish();
} }
``` ```
*Configuration function* has following type: While *App::route()* provides simple way of registering routes, to access
complete resource configuration, different method has to be used.
The [App::resource](../actix_web/struct.App.html#method.resource) method
adds a single resource to application routing table. This method accepts a *path pattern*
and a resource configuration function.
```rust
# extern crate actix_web;
use actix_web::{App, HttpRequest, HttpResponse, http::Method};
fn index(req: HttpRequest) -> HttpResponse {
unimplemented!()
}
fn main() {
App::new()
.resource("/prefix", |r| r.f(index))
.resource("/user/{name}",
|r| r.method(Method::GET).f(|req| HttpResponse::Ok()))
.finish();
}
```
The *Configuration function* has the following type:
```rust,ignore ```rust,ignore
FnOnce(&mut Resource<_>) -> () FnOnce(&mut Resource<_>) -> ()
``` ```
*Configuration function* can set name and register specific routes. The *Configuration function* can set a name and register specific routes.
If resource does not contain any route or does not have any matching routes it If a resource does not contain any route or does not have any matching routes, it
returns *NOT FOUND* http resources. returns *NOT FOUND* http response.
## Configuring a Route ## Configuring a Route
Resource contains set of routes. Each route in turn has set of predicates and handler. Resource contains a set of routes. Each route in turn has a set of predicates and a handler.
New route could be created with `Resource::route()` method which returns reference New routes can be created with `Resource::route()` method which returns a reference
to new *Route* instance. By default *route* does not contain any predicates, so matches to new *Route* instance. By default the *route* does not contain any predicates, so matches
all requests and default handler is `HttpNotFound`. all requests and the default handler is `HttpNotFound`.
Application routes incoming requests based on route criteria which is defined during The application routes incoming requests based on route criteria which are defined during
resource registration and route registration. Resource matches all routes it contains in resource registration and route registration. Resource matches all routes it contains in
the order that the routes were registered via `Resource::route()`. *Route* can contain the order the routes were registered via `Resource::route()`.
any number of *predicates* but only one handler.
> A *Route* can contain any number of *predicates* but only one handler.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# use actix_web::*; # use actix_web::*;
# use actix_web::httpcodes::*;
fn main() { fn main() {
Application::new() App::new()
.resource("/path", |resource| .resource("/path", |resource|
resource.route() resource.route()
.filter(pred::Get()) .filter(pred::Get())
.filter(pred::Header("content-type", "text/plain")) .filter(pred::Header("content-type", "text/plain"))
.f(|req| HttpOk) .f(|req| HttpResponse::Ok())
) )
.finish(); .finish();
} }
``` ```
In this example `index` get called for *GET* request, In this example, `HttpResponse::Ok()` is returned for *GET* requests.
if request contains `Content-Type` header and value of this header is *text/plain* If a request contains `Content-Type` header, the value of this header is *text/plain*,
and path equals to `/test`. Resource calls handle of the first matches route. and path equals to `/path`, Resource calls handle of the first matching route.
If resource can not match any route "NOT FOUND" response get returned.
[*Resource::route()*](../actix_web/struct.Resource.html#method.route) method returns If a resource can not match any route, a "NOT FOUND" response is returned.
[*Route*](../actix_web/struct.Route.html) object. Route can be configured with
[*Resource::route()*](../actix_web/struct.Resource.html#method.route) returns a
[*Route*](../actix_web/struct.Route.html) object. Route can be configured with a
builder-like pattern. Following configuration methods are available: builder-like pattern. Following configuration methods are available:
* [*Route::filter()*](../actix_web/struct.Route.html#method.filter) method registers new predicate, * [*Route::filter()*](../actix_web/struct.Route.html#method.filter) registers a new predicate.
any number of predicates could be registered for each route. Any number of predicates can be registered for each route.
* [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function * [*Route::f()*](../actix_web/struct.Route.html#method.f) registers handler function
for this route. Only one handler could be registered. Usually handler registration for this route. Only one handler can be registered. Usually handler registration
is the last config operation. Handler function could be function or closure and has type is the last config operation. Handler function can be a function or closure and has the type
`Fn(HttpRequest<S>) -> R + 'static` `Fn(HttpRequest<S>) -> R + 'static`
* [*Route::h()*](../actix_web/struct.Route.html#method.h) method registers handler object * [*Route::h()*](../actix_web/struct.Route.html#method.h) registers a handler object
that implements `Handler` trait. This is similar to `f()` method, only one handler could that implements the `Handler` trait. This is similar to `f()` method - only one handler can
be registered. Handler registration is the last config operation. be registered. Handler registration is the last config operation.
* [*Route::a()*](../actix_web/struct.Route.html#method.a) method registers async handler * [*Route::a()*](../actix_web/struct.Route.html#method.a) registers an async handler
function for this route. Only one handler could be registered. Handler registration function for this route. Only one handler can be registered. Handler registration
is the last config operation. Handler function could be function or closure and has type is the last config operation. Handler function can be a function or closure and has the type
`Fn(HttpRequest<S>) -> Future<Item = HttpResponse, Error = Error> + 'static` `Fn(HttpRequest<S>) -> Future<Item = HttpResponse, Error = Error> + 'static`
## Route matching ## Route matching
@ -110,8 +136,8 @@ against a URL path pattern. `path` represents the path portion of the URL that w
The way that *actix* does this is very simple. When a request enters the system, The way that *actix* does this is very simple. When a request enters the system,
for each resource configuration declaration present in the system, actix checks for each resource configuration declaration present in the system, actix checks
the request's path against the pattern declared. This checking happens in the order that the request's path against the pattern declared. This checking happens in the order that
the routes were declared via `Application::resource()` method. If resource could not be found, the routes were declared via `App::resource()` method. If resource can not be found,
*default resource* get used as matched resource. the *default resource* is used as the matched resource.
When a route configuration is declared, it may contain route predicate arguments. All route When a route configuration is declared, it may contain route predicate arguments. All route
predicates associated with a route declaration must be `true` for the route configuration to predicates associated with a route declaration must be `true` for the route configuration to
@ -120,13 +146,11 @@ arguments provided to a route configuration returns `false` during a check, that
skipped and route matching continues through the ordered set of routes. skipped and route matching continues through the ordered set of routes.
If any route matches, the route matching process stops and the handler associated with If any route matches, the route matching process stops and the handler associated with
route get invoked. the route is invoked. If no route matches after all route patterns are exhausted, a *NOT FOUND* response get returned.
If no route matches after all route patterns are exhausted, *NOT FOUND* response get returned.
## Resource pattern syntax ## Resource pattern syntax
The syntax of the pattern matching language used by the actix in the pattern The syntax of the pattern matching language used by actix in the pattern
argument is straightforward. argument is straightforward.
The pattern used in route configuration may start with a slash character. If the pattern The pattern used in route configuration may start with a slash character. If the pattern
@ -210,8 +234,9 @@ For example, for the URL */abc/*:
* */abc/{foo}* will not match. * */abc/{foo}* will not match.
* */{foo}/* will match. * */{foo}/* will match.
Note that path will be URL-unquoted and decoded into valid unicode string before > **Note**: path will be URL-unquoted and decoded into valid unicode string before
matching pattern and values representing matched path segments will be URL-unquoted too. > matching pattern and values representing matched path segments will be URL-unquoted too.
So for instance, the following pattern: So for instance, the following pattern:
``` ```
@ -261,12 +286,12 @@ foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'}
All values representing matched path segments are available in All values representing matched path segments are available in
[`HttpRequest::match_info`](../actix_web/struct.HttpRequest.html#method.match_info). [`HttpRequest::match_info`](../actix_web/struct.HttpRequest.html#method.match_info).
Specific value can be received with Specific values can be retrieved with
[`Params::get()`](../actix_web/dev/struct.Params.html#method.get) method. [`Params::get()`](../actix_web/dev/struct.Params.html#method.get).
Any matched parameter can be deserialized into specific type if this type Any matched parameter can be deserialized into a specific type if the type
implements `FromParam` trait. For example most of standard integer types implements the `FromParam` trait. For example most standard integer types
implements `FromParam` trait. i.e.: the trait, i.e.:
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
@ -279,7 +304,7 @@ fn index(req: HttpRequest) -> Result<String> {
} }
fn main() { fn main() {
Application::new() App::new()
.resource(r"/a/{v1}/{v2}/", |r| r.f(index)) .resource(r"/a/{v1}/{v2}/", |r| r.f(index))
.finish(); .finish();
} }
@ -305,8 +330,8 @@ safe to interpolate within, or use as a suffix of, a path without additional che
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
use actix_web::*;
use std::path::PathBuf; use std::path::PathBuf;
use actix_web::{App, HttpRequest, Result, http::Method};
fn index(req: HttpRequest) -> Result<String> { fn index(req: HttpRequest) -> Result<String> {
let path: PathBuf = req.match_info().query("tail")?; let path: PathBuf = req.match_info().query("tail")?;
@ -314,64 +339,122 @@ fn index(req: HttpRequest) -> Result<String> {
} }
fn main() { fn main() {
Application::new() App::new()
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
.finish(); .finish();
} }
``` ```
List of `FromParam` implementation could be found in List of `FromParam` implementations can be found in
[api docs](../actix_web/dev/trait.FromParam.html#foreign-impls) [api docs](../actix_web/dev/trait.FromParam.html#foreign-impls)
## Path information extractor
Actix provides functionality for type safe path information extraction.
[Path](../actix_web/struct.Path.html) extracts information, destination type
could be defined in several different forms. Simplest approach is to use
`tuple` type. Each element in tuple must correpond to one element from
path pattern. i.e. you can match path pattern `/{id}/{username}/` against
`Pyth<(u32, String)>` type, but `Path<(String, String, String)>` type will
always fail.
```rust
# extern crate actix_web;
use actix_web::{App, Path, Result, http::Method};
// extract path info using serde
fn index(info: Path<(String, u32)>) -> Result<String> {
Ok(format!("Welcome {}! id: {}", info.0, info.1))
}
fn main() {
let app = App::new()
.resource("/{username}/{id}/index.html", // <- define path parameters
|r| r.method(Method::GET).with(index));
}
```
It also possible to extract path pattern information to a struct. In this case,
this struct must implement *serde's *`Deserialize` trait.
```rust
# extern crate actix_web;
#[macro_use] extern crate serde_derive;
use actix_web::{App, Path, Result, http::Method};
#[derive(Deserialize)]
struct Info {
username: String,
}
// extract path info using serde
fn index(info: Path<Info>) -> Result<String> {
Ok(format!("Welcome {}!", info.username))
}
fn main() {
let app = App::new()
.resource("/{username}/index.html", // <- define path parameters
|r| r.method(Method::GET).with(index));
}
```
[Query](../actix_web/struct.Query.html) provides similar functionality for
request query parameters.
## Generating resource URLs ## Generating resource URLs
Use the [HttpRequest.url_for()](../actix_web/struct.HttpRequest.html#method.url_for) Use the [HttpRequest.url_for()](../actix_web/struct.HttpRequest.html#method.url_for)
method to generate URLs based on resource patterns. For example, if you've configured a method to generate URLs based on resource patterns. For example, if you've configured a
resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this. resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this:
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# use actix_web::*; # use actix_web::{App, Result, HttpRequest, HttpResponse, http::Method, http::header};
# use actix_web::httpcodes::*;
# #
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> Result<HttpResponse> {
let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource let url = req.url_for("foo", &["1", "2", "3"])?; // <- generate url for "foo" resource
HttpOk.into() Ok(HttpResponse::Found()
.header(header::LOCATION, url.as_str())
.finish())
} }
fn main() { fn main() {
let app = Application::new() let app = App::new()
.resource("/test/{a}/{b}/{c}", |r| { .resource("/test/{a}/{b}/{c}", |r| {
r.name("foo"); // <- set resource name, then it could be used in `url_for` r.name("foo"); // <- set resource name, then it could be used in `url_for`
r.method(Method::GET).f(|_| httpcodes::HttpOk); r.method(Method::GET).f(|_| HttpResponse::Ok());
}) })
.route("/test/", Method::GET, index)
.finish(); .finish();
} }
``` ```
This would return something like the string *http://example.com/test/1/2/3* (at least if This would return something like the string *http://example.com/test/1/2/3* (at least if
the current protocol and hostname implied http://example.com). the current protocol and hostname implied http://example.com).
`url_for()` method return [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you `url_for()` method returns [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you
can modify this url (add query parameters, anchor, etc). can modify this url (add query parameters, anchor, etc).
`url_for()` could be called only for *named* resources otherwise error get returned. `url_for()` could be called only for *named* resources otherwise error get returned.
## External resources ## External resources
Resources that are valid URLs, could be registered as external resources. They are useful Resources that are valid URLs, can be registered as external resources. They are useful
for URL generation purposes only and are never considered for matching at request time. for URL generation purposes only and are never considered for matching at request time.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
use actix_web::*; use actix_web::{App, HttpRequest, HttpResponse, Error};
fn index(mut req: HttpRequest) -> Result<HttpResponse> { fn index(mut req: HttpRequest) -> Result<HttpResponse, Error> {
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(httpcodes::HttpOk.into()) Ok(HttpResponse::Ok().into())
} }
fn main() { fn main() {
let app = Application::new() let app = App::new()
.resource("/index.html", |r| r.f(index)) .resource("/index.html", |r| r.f(index))
.external_resource("youtube", "https://youtube.com/watch/{video_id}") .external_resource("youtube", "https://youtube.com/watch/{video_id}")
.finish(); .finish();
@ -382,66 +465,67 @@ fn main() {
By normalizing it means: By normalizing it means:
- Add a trailing slash to the path. * Add a trailing slash to the path.
- Double slashes are replaced by one. * Double slashes are replaced by one.
The handler returns as soon as it finds a path that resolves The handler returns as soon as it finds a path that resolves
correctly. The order if all enable is 1) merge, 3) both merge and append correctly. The order if all enable is 1) merge, 3) both merge and append
and 3) append. If the path resolves with and 3) append. If the path resolves with
at least one of those conditions, it will redirect to the new path. at least one of those conditions, it will redirect to the new path.
If *append* is *true* append slash when needed. If a resource is If *append* is *true*, append slash when needed. If a resource is
defined with trailing slash and the request comes without it, it will defined with trailing slash and the request doesn't have one, it will
append it automatically. be appended automatically.
If *merge* is *true*, merge multiple consecutive slashes in the path into one. If *merge* is *true*, merge multiple consecutive slashes in the path into one.
This handler designed to be use as a handler for application's *default resource*. This handler designed to be used as a handler for application's *default resource*.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# #[macro_use] extern crate serde_derive; # #[macro_use] extern crate serde_derive;
# use actix_web::*; # use actix_web::*;
use actix_web::http::NormalizePath;
# #
# fn index(req: HttpRequest) -> httpcodes::StaticResponse { # fn index(req: HttpRequest) -> HttpResponse {
# httpcodes::HttpOk # HttpResponse::Ok().into()
# } # }
fn main() { fn main() {
let app = Application::new() let app = App::new()
.resource("/resource/", |r| r.f(index)) .resource("/resource/", |r| r.f(index))
.default_resource(|r| r.h(NormalizePath::default())) .default_resource(|r| r.h(NormalizePath::default()))
.finish(); .finish();
} }
``` ```
In this example `/resource`, `//resource///` will be redirected to `/resource/` url. In this example `/resource`, `//resource///` will be redirected to `/resource/`.
In this example path normalization handler get registered for all method, In this example, the path normalization handler is registered for all methods,
but you should not rely on this mechanism to redirect *POST* requests. The redirect of the but you should not rely on this mechanism to redirect *POST* requests. The redirect of the
slash-appending *Not Found* will turn a *POST* request into a GET, losing any slash-appending *Not Found* will turn a *POST* request into a GET, losing any
*POST* data in the original request. *POST* data in the original request.
It is possible to register path normalization only for *GET* requests only It is possible to register path normalization only for *GET* requests only:
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# #[macro_use] extern crate serde_derive; # #[macro_use] extern crate serde_derive;
# use actix_web::*; use actix_web::{App, HttpRequest, http::Method, http::NormalizePath};
# #
# fn index(req: HttpRequest) -> httpcodes::StaticResponse { # fn index(req: HttpRequest) -> &'static str {
# httpcodes::HttpOk # "test"
# } # }
fn main() { fn main() {
let app = Application::new() let app = App::new()
.resource("/resource/", |r| r.f(index)) .resource("/resource/", |r| r.f(index))
.default_resource(|r| r.method(Method::GET).h(NormalizePath::default())) .default_resource(|r| r.method(Method::GET).h(NormalizePath::default()))
.finish(); .finish();
} }
``` ```
## Using a Application Prefix to Compose Applications ## Using an Application Prefix to Compose Applications
The `Application::prefix()`" method allows to set specific application prefix. The `App::prefix()` method allows to set a specific application prefix.
This prefix represents a resource prefix that will be prepended to all resource patterns added This prefix represents a resource prefix that will be prepended to all resource patterns added
by the resource configuration. This can be used to help mount a set of routes at a different by the resource configuration. This can be used to help mount a set of routes at a different
location than the included callable's author intended while still maintaining the same location than the included callable's author intended while still maintaining the same
@ -458,7 +542,7 @@ fn show_users(req: HttpRequest) -> HttpResponse {
} }
fn main() { fn main() {
Application::new() App::new()
.prefix("/users") .prefix("/users")
.resource("/show", |r| r.f(show_users)) .resource("/show", |r| r.f(show_users))
.finish(); .finish();
@ -473,77 +557,73 @@ it will generate a URL with that same path.
## Custom route predicates ## Custom route predicates
You can think of predicate as simple function that accept *request* object reference You can think of a predicate as a simple function that accepts a *request* object reference
and returns *true* or *false*. Formally predicate is any object that implements and returns *true* or *false*. Formally, a predicate is any object that implements the
[`Predicate`](../actix_web/pred/trait.Predicate.html) trait. Actix provides [`Predicate`](../actix_web/pred/trait.Predicate.html) trait. Actix provides
several predicates, you can check [functions section](../actix_web/pred/index.html#functions) several predicates, you can check [functions section](../actix_web/pred/index.html#functions)
of api docs. of api docs.
Here is simple predicates that check that request contains specific *header*: Here is a simple predicate that check that a request contains a specific *header*:
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# extern crate http;
# use actix_web::*; # use actix_web::*;
# use actix_web::httpcodes::*; use actix_web::{http, pred::Predicate, App, HttpRequest};
use http::header::CONTENT_TYPE;
use actix_web::pred::Predicate;
struct ContentTypeHeader; struct ContentTypeHeader;
impl<S: 'static> Predicate<S> for ContentTypeHeader { impl<S: 'static> Predicate<S> for ContentTypeHeader {
fn check(&self, req: &mut HttpRequest<S>) -> bool { fn check(&self, req: &mut HttpRequest<S>) -> bool {
req.headers().contains_key(CONTENT_TYPE) req.headers().contains_key(http::header::CONTENT_TYPE)
} }
} }
fn main() { fn main() {
Application::new() App::new()
.resource("/index.html", |r| .resource("/index.html", |r|
r.route() r.route()
.filter(ContentTypeHeader) .filter(ContentTypeHeader)
.h(HttpOk)); .f(|_| HttpResponse::Ok()));
} }
``` ```
In this example *index* handler will be called only if request contains *CONTENT-TYPE* header. In this example, *index* handler will be called only if request contains *CONTENT-TYPE* header.
Predicates can have access to application's state via `HttpRequest::state()` method. Predicates have access to the application's state via `HttpRequest::state()`.
Also predicates can store extra information in Also predicates can store extra information in
[requests`s extensions](../actix_web/struct.HttpRequest.html#method.extensions). [request extensions](../actix_web/struct.HttpRequest.html#method.extensions).
### Modifying predicate values ### Modifying predicate values
You can invert the meaning of any predicate value by wrapping it in a `Not` predicate. You can invert the meaning of any predicate value by wrapping it in a `Not` predicate.
For example if you want to return "METHOD NOT ALLOWED" response for all methods For example, if you want to return "METHOD NOT ALLOWED" response for all methods
except "GET": except "GET":
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# extern crate http; # extern crate http;
# use actix_web::*; # use actix_web::*;
# use actix_web::httpcodes::*; use actix_web::{pred, App, HttpResponse};
use actix_web::pred;
fn main() { fn main() {
Application::new() App::new()
.resource("/index.html", |r| .resource("/index.html", |r|
r.route() r.route()
.filter(pred::Not(pred::Get())) .filter(pred::Not(pred::Get()))
.f(|req| HttpMethodNotAllowed)) .f(|req| HttpResponse::MethodNotAllowed()))
.finish(); .finish();
} }
``` ```
`Any` predicate accept list of predicates and matches if any of the supplied The `Any` predicate accepts a list of predicates and matches if any of the supplied
predicates match. i.e: predicates match. i.e:
```rust,ignore ```rust,ignore
pred::Any(pred::Get()).or(pred::Post()) pred::Any(pred::Get()).or(pred::Post())
``` ```
`All` predicate accept list of predicates and matches if all of the supplied The `All` predicate accepts a list of predicates and matches if all of the supplied
predicates match. i.e: predicates match. i.e:
```rust,ignore ```rust,ignore
@ -552,23 +632,22 @@ predicates match. i.e:
## Changing the default Not Found response ## Changing the default Not Found response
If path pattern can not be found in routing table or resource can not find matching If the path pattern can not be found in the routing table or a resource can not find matching
route, default resource is used. Default response is *NOT FOUND* response. route, the default resource is used. The default response is *NOT FOUND*.
It is possible to override *NOT FOUND* response with `Application::default_resource()` method. It is possible to override the *NOT FOUND* response with `App::default_resource()`.
This method accepts *configuration function* same as normal resource configuration This method accepts a *configuration function* same as normal resource configuration
with `Application::resource()` method. with `App::resource()` method.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# extern crate http; use actix_web::{App, HttpResponse, http::Method, pred};
use actix_web::*;
use actix_web::httpcodes::*;
fn main() { fn main() {
Application::new() App::new()
.default_resource(|r| { .default_resource(|r| {
r.method(Method::GET).f(|req| HttpNotFound); r.method(Method::GET).f(|req| HttpResponse::NotFound());
r.route().filter(pred::Not(pred::Get())).f(|req| HttpMethodNotAllowed); r.route().filter(pred::Not(pred::Get()))
.f(|req| HttpResponse::MethodNotAllowed());
}) })
# .finish(); # .finish();
} }

View File

@ -2,69 +2,98 @@
## Response ## Response
Builder-like patter is used to construct an instance of `HttpResponse`. A builder-like pattern is used to construct an instance of `HttpResponse`.
`HttpResponse` provides several method that returns `HttpResponseBuilder` instance, `HttpResponse` provides several methods that return a `HttpResponseBuilder` instance,
which is implements various convenience methods that helps build response. which implements various convenience methods for building responses.
Check [documentation](../actix_web/dev/struct.HttpResponseBuilder.html)
for type description. Methods `.body`, `.finish`, `.json` finalizes response creation and > Check the [documentation](../actix_web/dev/struct.HttpResponseBuilder.html)
returns constructed *HttpResponse* instance. if this methods get called for the same > for type descriptions.
builder instance multiple times, builder will panic.
The methods `.body`, `.finish`, and `.json` finalize response creation and
return a constructed *HttpResponse* instance. If this methods is called on the same
builder instance multiple times, the builder will panic.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
use actix_web::*; use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding};
use actix_web::headers::ContentEncoding;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.content_encoding(ContentEncoding::Br) .content_encoding(ContentEncoding::Br)
.content_type("plain/text") .content_type("plain/text")
.header("X-Hdr", "sample") .header("X-Hdr", "sample")
.body("data").unwrap() .body("data")
} }
# fn main() {} # fn main() {}
``` ```
## Content encoding ## Content encoding
Actix automatically *compress*/*decompress* payload. Following codecs are supported: Actix automatically *compresses*/*decompresses* payloads. The following codecs are supported:
* Brotli * Brotli
* Gzip * Gzip
* Deflate * Deflate
* Identity * Identity
If request headers contains `Content-Encoding` header, request payload get decompressed If request headers contain a `Content-Encoding` header, the request payload is decompressed
according to header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`. according to the header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`.
Response payload get compressed based on *content_encoding* parameter. Response payload is compressed based on the *content_encoding* parameter.
By default `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected By default, `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected,
then compression depends on request's `Accept-Encoding` header. then the compression depends on the request's `Accept-Encoding` header.
`ContentEncoding::Identity` could be used to disable compression.
If other content encoding is selected the compression is enforced for this codec. For example, > `ContentEncoding::Identity` can be used to disable compression.
to enable `brotli` response's body compression use `ContentEncoding::Br`: > If another content encoding is selected, the compression is enforced for that codec.
For example, to enable `brotli` use `ContentEncoding::Br`:
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
use actix_web::*; use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding};
use actix_web::headers::ContentEncoding;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.content_encoding(ContentEncoding::Br) .content_encoding(ContentEncoding::Br)
.body("data").unwrap() .body("data")
} }
# fn main() {} # fn main() {}
``` ```
## JSON Request ## JSON Request
There are two options of json body deserialization. There are several options for json body deserialization.
First option is to use *HttpResponse::json()* method. This method returns The first option is to use *Json* extractor. First, you define a handler function
that accepts `Json<T>` as a parameter, then, you use the `.with()` method for registering
this handler. It is also possible to accept arbitrary valid json object by
using `serde_json::Value` as a type `T`.
```rust
# extern crate actix_web;
#[macro_use] extern crate serde_derive;
use actix_web::{App, Json, Result, http};
#[derive(Deserialize)]
struct Info {
username: String,
}
/// extract `Info` using serde
fn index(info: Json<Info>) -> Result<String> {
Ok(format!("Welcome {}!", info.username))
}
fn main() {
let app = App::new().resource(
"/index.html",
|r| r.method(http::Method::POST).with(index)); // <- use `with` extractor
}
```
Another option is to use *HttpResponse::json()*. This method returns a
[*JsonBody*](../actix_web/dev/struct.JsonBody.html) object which resolves into [*JsonBody*](../actix_web/dev/struct.JsonBody.html) object which resolves into
deserialized value. the deserialized value.
```rust ```rust
# extern crate actix; # extern crate actix;
@ -84,16 +113,17 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.json().from_err() req.json().from_err()
.and_then(|val: MyObj| { .and_then(|val: MyObj| {
println!("model: {:?}", val); println!("model: {:?}", val);
Ok(httpcodes::HttpOk.build().json(val)?) // <- send response Ok(HttpResponse::Ok().json(val)) // <- send response
}) })
.responder() .responder()
} }
# fn main() {} # fn main() {}
``` ```
Or you can manually load payload into memory and then deserialize it. You may also manually load the payload into memory and then deserialize it.
Here is simple example. We will deserialize *MyObj* struct. We need to load request
body first and then deserialize json into object. In the following example, we will deserialize a *MyObj* struct. We need to load the request
body first and then deserialize the json into an object.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
@ -117,27 +147,26 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// synchronous workflow // synchronous workflow
.and_then(|body| { // <- body is loaded, now we can deserialize json .and_then(|body| { // <- body is loaded, now we can deserialize json
let obj = serde_json::from_slice::<MyObj>(&body)?; let obj = serde_json::from_slice::<MyObj>(&body)?;
Ok(httpcodes::HttpOk.build().json(obj)?) // <- send response Ok(HttpResponse::Ok().json(obj)) // <- send response
}) })
.responder() .responder()
} }
# fn main() {} # fn main() {}
``` ```
Complete example for both options is available in > A complete example for both options is available in
[examples directory](https://github.com/actix/actix-web/tree/master/examples/json/). > [examples directory](https://github.com/actix/actix-web/tree/master/examples/json/).
## JSON Response ## JSON Response
The `Json` type allows you to respond with well-formed JSON data: simply return a value of The `Json` type allows to respond with well-formed JSON data: simply return a value of
type Json<T> where T is the type of a structure to serialize into *JSON*. The type Json<T> where `T` is the type of a structure to serialize into *JSON*.
type `T` must implement the `Serialize` trait from *serde*. The type `T` must implement the `Serialize` trait from *serde*.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
#[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_derive;
use actix_web::*; use actix_web::{App, HttpRequest, Json, Result, http::Method};
#[derive(Serialize)] #[derive(Serialize)]
struct MyObj { struct MyObj {
@ -149,7 +178,7 @@ fn index(req: HttpRequest) -> Result<Json<MyObj>> {
} }
fn main() { fn main() {
Application::new() App::new()
.resource(r"/a/{name}", |r| r.method(Method::GET).f(index)) .resource(r"/a/{name}", |r| r.method(Method::GET).f(index))
.finish(); .finish();
} }
@ -157,16 +186,16 @@ fn main() {
## Chunked transfer encoding ## Chunked transfer encoding
Actix automatically decode *chunked* encoding. `HttpRequest::payload()` already contains Actix automatically decodes *chunked* encoding. `HttpRequest::payload()` already contains
decoded bytes stream. If request payload compressed with one of supported the decoded byte stream. If the request payload is compressed with one of the supported
compression codecs (br, gzip, deflate) bytes stream get decompressed. compression codecs (br, gzip, deflate), then the byte stream is decompressed.
Chunked encoding on response could be enabled with `HttpResponseBuilder::chunked()` method. Chunked encoding on a response can be enabled with `HttpResponseBuilder::chunked()`.
But this takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies. This takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies.
Also if response payload compression is enabled and streaming body is used, chunked encoding If the response payload compression is enabled and a streaming body is used, chunked encoding
get enabled automatically. is enabled automatically.
Enabling chunked encoding for *HTTP/2.0* responses is forbidden. > Enabling chunked encoding for *HTTP/2.0* responses is forbidden.
```rust ```rust
# extern crate bytes; # extern crate bytes;
@ -180,7 +209,7 @@ use futures::stream::once;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.chunked() .chunked()
.body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data")))))).unwrap() .body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data"))))))
} }
# fn main() {} # fn main() {}
``` ```
@ -189,11 +218,11 @@ fn index(req: HttpRequest) -> HttpResponse {
Actix provides multipart stream support. Actix provides multipart stream support.
[*Multipart*](../actix_web/multipart/struct.Multipart.html) is implemented as [*Multipart*](../actix_web/multipart/struct.Multipart.html) is implemented as
a stream of multipart items, each item could be a stream of multipart items. Each item can be a
[*Field*](../actix_web/multipart/struct.Field.html) or nested *Multipart* stream. [*Field*](../actix_web/multipart/struct.Field.html) or a nested *Multipart* stream.
`HttpResponse::multipart()` method returns *Multipart* stream for current request. `HttpResponse::multipart()` returns the *Multipart* stream for the current request.
In simple form multipart stream handling could be implemented similar to this example The following demonstrates multipart stream handling for a simple form:
```rust,ignore ```rust,ignore
# extern crate actix_web; # extern crate actix_web;
@ -223,48 +252,54 @@ fn index(req: HttpRequest) -> Box<Future<...>> {
} }
``` ```
Full example is available in > A full example is available in the
[examples directory](https://github.com/actix/actix-web/tree/master/examples/multipart/). > [examples directory](https://github.com/actix/actix-web/tree/master/examples/multipart/).
## Urlencoded body ## Urlencoded body
Actix provides support for *application/x-www-form-urlencoded* encoded body. Actix provides support for *application/x-www-form-urlencoded* encoded bodies.
`HttpResponse::urlencoded()` method returns `HttpResponse::urlencoded()` returns a
[*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, it resolves [*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, which resolves
into `HashMap<String, String>` which contains decoded parameters. to the deserialized instance. The type of the instance must implement the
*UrlEncoded* future can resolve into a error in several cases: `Deserialize` trait from *serde*.
The *UrlEncoded* future can resolve into an error in several cases:
* content type is not `application/x-www-form-urlencoded` * content type is not `application/x-www-form-urlencoded`
* transfer encoding is `chunked`. * transfer encoding is `chunked`.
* content-length is greater than 256k * content-length is greater than 256k
* payload terminates with error. * payload terminates with error.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# extern crate futures; # extern crate futures;
#[macro_use] extern crate serde_derive;
use actix_web::*; use actix_web::*;
use futures::future::{Future, ok}; use futures::future::{Future, ok};
#[derive(Deserialize)]
struct FormData {
username: String,
}
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.urlencoded() // <- get UrlEncoded future req.urlencoded::<FormData>() // <- get UrlEncoded future
.from_err() .from_err()
.and_then(|params| { // <- url encoded parameters .and_then(|data| { // <- deserialized instance
println!("==== BODY ==== {:?}", params); println!("USERNAME: {:?}", data.username);
ok(httpcodes::HttpOk.into()) ok(HttpResponse::Ok().into())
}) })
.responder() .responder()
} }
# fn main() {} # fn main() {}
``` ```
## Streaming request ## Streaming request
*HttpRequest* is a stream of `Bytes` objects. It could be used to read request *HttpRequest* is a stream of `Bytes` objects. It can be used to read the request
body payload. body payload.
In this example handle reads request payload chunk by chunk and prints every chunk. In the following example, we read and print the request payload chunk by chunk:
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
@ -280,7 +315,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
println!("Chunk: {:?}", chunk); println!("Chunk: {:?}", chunk);
result::<_, error::PayloadError>(Ok(())) result::<_, error::PayloadError>(Ok(()))
}) })
.map(|_| HttpResponse::Ok().finish().unwrap()) .map(|_| HttpResponse::Ok().finish())
.responder() .responder()
} }
# fn main() {} # fn main() {}

View File

@ -1,65 +1,63 @@
# Testing # Testing
Every application should be well tested and. Actix provides the tools to perform unit and Every application should be well tested. Actix provides tools to perform unit and
integration tests. integration tests.
## Unit tests ## Unit tests
For unit testing actix provides request builder type and simple handler runner. For unit testing, actix provides a request builder type and a simple handler runner.
[*TestRequest*](../actix_web/test/struct.TestRequest.html) implements builder-like pattern. [*TestRequest*](../actix_web/test/struct.TestRequest.html) implements a builder-like pattern.
You can generate `HttpRequest` instance with `finish()` method or you can You can generate a `HttpRequest` instance with `finish()`, or you can
run your handler with `run()` or `run_async()` methods. run your handler with `run()` or `run_async()`.
```rust ```rust
# extern crate http;
# extern crate actix_web; # extern crate actix_web;
use http::{header, StatusCode}; use actix_web::{http, test, HttpRequest, HttpResponse, HttpMessage};
use actix_web::*;
use actix_web::test::TestRequest;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { if let Some(hdr) = req.headers().get(http::header::CONTENT_TYPE) {
if let Ok(s) = hdr.to_str() { if let Ok(s) = hdr.to_str() {
return httpcodes::HttpOk.into() return HttpResponse::Ok().into()
} }
} }
httpcodes::HttpBadRequest.into() HttpResponse::BadRequest().into()
} }
fn main() { fn main() {
let resp = TestRequest::with_header("content-type", "text/plain") let resp = test::TestRequest::with_header("content-type", "text/plain")
.run(index) .run(index)
.unwrap(); .unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), http::StatusCode::OK);
let resp = TestRequest::default() let resp = test::TestRequest::default()
.run(index) .run(index)
.unwrap(); .unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST);
} }
``` ```
## Integration tests ## Integration tests
There are several methods how you can test your application. Actix provides There are several methods for testing your application. Actix provides
[*TestServer*](../actix_web/test/struct.TestServer.html) [*TestServer*](../actix_web/test/struct.TestServer.html), which can be used
server that could be used to run whole application of just specific handlers to run the application with specific handlers in a real http server.
in real http server. *TrstServer::get()*, *TrstServer::post()* or *TrstServer::client()*
methods could be used to send request to test server.
In simple form *TestServer* could be configured to use handler. *TestServer::new* method `TestServer::get()`, `TestServer::post()`, and `TestServer::client()`
accepts configuration function, only argument for this function is *test application* methods can be used to send requests to the test server.
instance. You can check [api documentation](../actix_web/test/struct.TestApp.html)
for more information. A simple form `TestServer` can be configured to use a handler.
`TestServer::new` method accepts a configuration function, and the only argument
for this function is a *test application* instance.
> Check the [api documentation](../actix_web/test/struct.TestApp.html) for more information.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
use actix_web::*; use actix_web::{HttpRequest, HttpResponse, HttpMessage};
use actix_web::test::TestServer; use actix_web::test::TestServer;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
httpcodes::HttpOk.into() HttpResponse::Ok().into()
} }
fn main() { fn main() {
@ -73,45 +71,69 @@ fn main() {
} }
``` ```
Other option is to use application factory. In this case you need to pass factory function The other option is to use an application factory. In this case, you need to pass the factory
same as you use for real http server configuration. function the same way as you would for real http server configuration.
```rust ```rust
# extern crate http;
# extern crate actix_web; # extern crate actix_web;
use http::Method; use actix_web::{http, test, App, HttpRequest, HttpResponse};
use actix_web::*;
use actix_web::test::TestServer;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
httpcodes::HttpOk.into() HttpResponse::Ok().into()
} }
/// This function get called by http server. /// This function get called by http server.
fn create_app() -> Application { fn create_app() -> App {
Application::new() App::new()
.resource("/test", |r| r.h(index)) .resource("/test", |r| r.h(index))
} }
fn main() { fn main() {
let mut srv = TestServer::with_factory(create_app); // <- Start new test server let mut srv = test::TestServer::with_factory(create_app); // <- Start new test server
let request = srv.client(Method::GET, "/test").finish().unwrap(); // <- create client request let request = srv.client(
http::Method::GET, "/test").finish().unwrap(); // <- create client request
let response = srv.execute(request.send()).unwrap(); // <- send request to the server let response = srv.execute(request.send()).unwrap(); // <- send request to the server
assert!(response.status().is_success()); // <- check response assert!(response.status().is_success()); // <- check response
} }
``` ```
If you need more complex application configuration, use the `TestServer::build_with_state()`
method. For example, you may need to initialize application state or start `SyncActor`'s for diesel
interation. This method accepts a closure that constructs the application state,
and it runs when the actix system is configured. Thus, you can initialize any additional actors.
```rust,ignore
#[test]
fn test() {
let srv = TestServer::build_with_state(|| { // <- construct builder with config closure
// we can start diesel actors
let addr = SyncArbiter::start(3, || {
DbExecutor(SqliteConnection::establish("test.db").unwrap())
});
// then we can construct custom state, or it could be `()`
MyState{addr: addr}
})
.start(|app| { // <- register server handlers and start test server
app.resource(
"/{username}/index.html", |r| r.with(
|p: Path<PParam>| format!("Welcome {}!", p.username)));
});
// now we can run our test code
);
```
## WebSocket server tests ## WebSocket server tests
It is possible to register *handler* with `TestApp::handler()` method that It is possible to register a *handler* with `TestApp::handler()`, which
initiate web socket connection. *TestServer* provides `ws()` which connects to initiates a web socket connection. *TestServer* provides the method `ws()`, which connects to
websocket server and returns ws reader and writer objects. *TestServer* also the websocket server and returns ws reader and writer objects. *TestServer* also
provides `execute()` method which runs future object to completion and returns provides an `execute()` method, which runs future objects to completion and returns
result of the future computation. result of the future computation.
Here is simple example, that shows how to test server websocket handler. The following example demonstrates how to test a websocket handler:
```rust ```rust
# extern crate actix; # extern crate actix;
@ -130,7 +152,7 @@ impl Actor for Ws {
type Context = ws::WebsocketContext<Self>; type Context = ws::WebsocketContext<Self>;
} }
impl StreamHandler<ws::Message, ws::WsError> for Ws { impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg { match msg {

View File

@ -1,12 +1,12 @@
# WebSockets # WebSockets
Actix supports WebSockets out-of-the-box. It is possible to convert request's `Payload` Actix supports WebSockets out-of-the-box. It is possible to convert a request's `Payload`
to a stream of [*ws::Message*](../actix_web/ws/enum.Message.html) with to a stream of [*ws::Message*](../actix_web/ws/enum.Message.html) with
a [*ws::WsStream*](../actix_web/ws/struct.WsStream.html) and then use stream a [*ws::WsStream*](../actix_web/ws/struct.WsStream.html) and then use stream
combinators to handle actual messages. But it is simpler to handle websocket communications combinators to handle actual messages, but it is simpler to handle websocket communications
with http actor. with an http actor.
This is example of simple websocket echo server: The following is an example of a simple websocket echo server:
```rust ```rust
# extern crate actix; # extern crate actix;
@ -35,14 +35,14 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
} }
fn main() { fn main() {
Application::new() App::new()
.resource("/ws/", |r| r.f(|req| ws::start(req, Ws))) // <- register websocket route .resource("/ws/", |r| r.f(|req| ws::start(req, Ws))) // <- register websocket route
.finish(); .finish();
} }
``` ```
Simple websocket echo server example is available in > A simple websocket echo server example is available in the
[examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket). > [examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket).
Example chat server with ability to chat over websocket connection or tcp connection > An example chat server with the ability to chat over a websocket or tcp connection
is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/) > is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)

View File

@ -1,33 +1,37 @@
use std::mem; use std::mem;
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::UnsafeCell;
use std::collections::HashMap; use std::collections::HashMap;
use http::Method;
use handler::Reply; use handler::Reply;
use router::{Router, Pattern}; use router::{Router, Resource};
use resource::Resource; use resource::{ResourceHandler};
use headers::ContentEncoding; use header::ContentEncoding;
use handler::{Handler, RouteHandler, WrapHandler}; use handler::{Handler, RouteHandler, WrapHandler, FromRequest, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use pipeline::{Pipeline, PipelineHandler}; use pipeline::{Pipeline, PipelineHandler, HandlerType};
use middleware::Middleware; use middleware::Middleware;
use server::{HttpHandler, IntoHttpHandler, HttpHandlerTask, ServerSettings}; use server::{HttpHandler, IntoHttpHandler, HttpHandlerTask, 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>,
prefix: String, prefix: String,
prefix_len: usize,
router: Router, router: Router,
inner: Rc<RefCell<Inner<S>>>, inner: Rc<UnsafeCell<Inner<S>>>,
middlewares: Rc<Vec<Box<Middleware<S>>>>, middlewares: Rc<Vec<Box<Middleware<S>>>>,
} }
pub(crate) struct Inner<S> { pub(crate) struct Inner<S> {
prefix: usize, prefix: usize,
default: Resource<S>, default: ResourceHandler<S>,
encoding: ContentEncoding, encoding: ContentEncoding,
router: Router, resources: Vec<ResourceHandler<S>>,
resources: Vec<Resource<S>>,
handlers: Vec<(String, Box<RouteHandler<S>>)>, handlers: Vec<(String, Box<RouteHandler<S>>)>,
} }
@ -37,39 +41,61 @@ impl<S: 'static> PipelineHandler<S> for Inner<S> {
self.encoding self.encoding
} }
fn handle(&mut self, mut req: HttpRequest<S>) -> Reply { fn handle(&mut self, req: HttpRequest<S>, htype: HandlerType) -> Reply {
if let Some(idx) = self.router.recognize(&mut req) { match htype {
self.resources[idx].handle(req.clone(), Some(&mut self.default)) HandlerType::Normal(idx) =>
} else { self.resources[idx].handle(req, Some(&mut self.default)),
for &mut (ref prefix, ref mut handler) in &mut self.handlers { HandlerType::Handler(idx) =>
let m = { self.handlers[idx].1.handle(req),
let path = &req.path()[self.prefix..]; HandlerType::Default =>
path.starts_with(prefix) && (
path.len() == prefix.len() ||
path.split_at(prefix.len()).1.starts_with('/'))
};
if m {
let path: &'static str = unsafe {
mem::transmute(&req.path()[self.prefix+prefix.len()..]) };
if path.is_empty() {
req.match_info_mut().add("tail", "");
} else {
req.match_info_mut().add("tail", path.split_at(1).1);
}
return handler.handle(req)
}
}
self.default.handle(req, None) self.default.handle(req, None)
} }
} }
} }
#[cfg(test)]
impl<S: 'static> HttpApplication<S> { impl<S: 'static> HttpApplication<S> {
#[cfg(test)]
pub(crate) fn run(&mut self, req: HttpRequest<S>) -> Reply { #[inline]
self.inner.borrow_mut().handle(req) fn as_ref(&self) -> &Inner<S> {
unsafe{&*self.inner.get()}
} }
#[inline]
fn get_handler(&self, req: &mut HttpRequest<S>) -> HandlerType {
if let Some(idx) = self.router.recognize(req) {
HandlerType::Normal(idx)
} else {
let inner = self.as_ref();
for idx in 0..inner.handlers.len() {
let &(ref prefix, _) = &inner.handlers[idx];
let m = {
let path = &req.path()[inner.prefix..];
path.starts_with(prefix) && (
path.len() == prefix.len() ||
path.split_at(prefix.len()).1.starts_with('/'))
};
if m {
let path: &'static str = unsafe {
mem::transmute(&req.path()[inner.prefix+prefix.len()..]) };
if path.is_empty() {
req.match_info_mut().add("tail", "");
} else {
req.match_info_mut().add("tail", path.split_at(1).1);
}
return HandlerType::Handler(idx)
}
}
HandlerType::Default
}
}
#[cfg(test)]
pub(crate) fn run(&mut self, mut req: HttpRequest<S>) -> Reply {
let tp = self.get_handler(&mut req);
unsafe{&mut *self.inner.get()}.handle(req, tp)
}
#[cfg(test)] #[cfg(test)]
pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest<S> { pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest<S> {
req.with_state(Rc::clone(&self.state), self.router.clone()) req.with_state(Rc::clone(&self.state), self.router.clone())
@ -82,14 +108,14 @@ impl<S: 'static> HttpHandler for HttpApplication<S> {
let m = { let m = {
let path = req.path(); let path = req.path();
path.starts_with(&self.prefix) && ( path.starts_with(&self.prefix) && (
path.len() == self.prefix.len() || path.len() == self.prefix_len ||
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 tp = self.get_handler(&mut req);
let inner = Rc::clone(&self.inner); let inner = Rc::clone(&self.inner);
let req = req.with_state(Rc::clone(&self.state), self.router.clone()); Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), inner, tp)))
Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), inner)))
} else { } else {
Err(req) Err(req)
} }
@ -100,30 +126,30 @@ struct ApplicationParts<S> {
state: S, state: S,
prefix: String, prefix: String,
settings: ServerSettings, settings: ServerSettings,
default: Resource<S>, default: ResourceHandler<S>,
resources: Vec<(Pattern, Option<Resource<S>>)>, resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
handlers: Vec<(String, Box<RouteHandler<S>>)>, handlers: Vec<(String, Box<RouteHandler<S>>)>,
external: HashMap<String, Pattern>, external: HashMap<String, Resource>,
encoding: ContentEncoding, encoding: ContentEncoding,
middlewares: Vec<Box<Middleware<S>>>, middlewares: Vec<Box<Middleware<S>>>,
} }
/// Structure that follows the builder pattern for building `Application` structs. /// Structure that follows the builder pattern for building application instances.
pub struct Application<S=()> { pub struct App<S=()> {
parts: Option<ApplicationParts<S>>, parts: Option<ApplicationParts<S>>,
} }
impl Application<()> { impl App<()> {
/// Create application with empty state. Application can /// Create application with empty state. Application can
/// be configured with builder-like pattern. /// be configured with a builder-like pattern.
pub fn new() -> Application<()> { pub fn new() -> App<()> {
Application { App {
parts: Some(ApplicationParts { parts: Some(ApplicationParts {
state: (), state: (),
prefix: "/".to_owned(), prefix: "/".to_owned(),
settings: ServerSettings::default(), settings: ServerSettings::default(),
default: Resource::default_not_found(), default: ResourceHandler::default_not_found(),
resources: Vec::new(), resources: Vec::new(),
handlers: Vec::new(), handlers: Vec::new(),
external: HashMap::new(), external: HashMap::new(),
@ -134,26 +160,26 @@ impl Application<()> {
} }
} }
impl Default for Application<()> { impl Default for App<()> {
fn default() -> Self { fn default() -> Self {
Application::new() App::new()
} }
} }
impl<S> Application<S> where S: 'static { impl<S> App<S> where S: 'static {
/// Create application with specific state. Application can be /// Create application with specified state. Application can be
/// configured with builder-like pattern. /// configured with a builder-like pattern.
/// ///
/// State is shared with all resources within same application and could be /// State is shared with all resources within same application and
/// accessed with `HttpRequest::state()` method. /// could be accessed with `HttpRequest::state()` method.
pub fn with_state(state: S) -> Application<S> { pub fn with_state(state: S) -> App<S> {
Application { App {
parts: Some(ApplicationParts { parts: Some(ApplicationParts {
state, state,
prefix: "/".to_owned(), prefix: "/".to_owned(),
settings: ServerSettings::default(), settings: ServerSettings::default(),
default: Resource::default_not_found(), default: ResourceHandler::default_not_found(),
resources: Vec::new(), resources: Vec::new(),
handlers: Vec::new(), handlers: Vec::new(),
external: HashMap::new(), external: HashMap::new(),
@ -163,34 +189,40 @@ impl<S> Application<S> where S: 'static {
} }
} }
/// Set application prefix /// Set application prefix.
/// ///
/// Only requests that matches application's prefix get processed by this application. /// Only requests that match the application's prefix get
/// Application prefix always contains leading "/" slash. If supplied prefix /// processed by this application.
/// does not contain leading slash, it get inserted. Prefix should
/// consists valid path segments. i.e for application with
/// prefix `/app` any request with following paths `/app`, `/app/` or `/app/test`
/// would match, but path `/application` would not match.
/// ///
/// In the following example only requests with "/app/" path prefix /// The application prefix always contains a leading slash (`/`).
/// get handled. Request with path "/app/test/" would be handled, /// If the supplied prefix does not contain leading slash, it is
/// but request with path "/application" or "/other/..." would return *NOT FOUND* /// inserted.
///
/// Prefix should consist of valid path segments. i.e for an
/// application with the prefix `/app` any request with the paths
/// `/app`, `/app/` or `/app/test` would match, but the path
/// `/application` would not.
///
/// In the following example only requests with an `/app/` path
/// prefix get handled. Requests with path `/app/test/` would be
/// handled, while requests with the paths `/application` or
/// `/other/...` would return `NOT FOUND`.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::*; /// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = App::new()
/// .prefix("/app") /// .prefix("/app")
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.method(Method::GET).f(|_| httpcodes::HttpOk); /// r.get().f(|_| HttpResponse::Ok());
/// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
/// ``` /// ```
pub fn prefix<P: Into<String>>(mut self, prefix: P) -> Application<S> { pub fn prefix<P: Into<String>>(mut self, prefix: P) -> App<S> {
{ {
let parts = self.parts.as_mut().expect("Use after finish"); let parts = self.parts.as_mut().expect("Use after finish");
let mut prefix = prefix.into(); let mut prefix = prefix.into();
@ -202,55 +234,113 @@ impl<S> Application<S> where S: 'static {
self self
} }
/// Configure resource for specific path. /// Configure route for a specific path.
/// ///
/// Resource may have variable path also. For instance, a resource with /// This is a simplified version of the `App::resource()` method.
/// the path */a/{name}/c* would match all incoming requests with paths /// Handler functions need to accept one request extractor
/// such as */a/b/c*, */a/1/c*, and */a/etc/c*. /// argument.
/// ///
/// A variable part is specified in the form `{identifier}`, where /// This method could be called multiple times, in that case
/// the identifier can be used later in a request handler to access the matched /// multiple routes would be registered for same resource path.
/// value for that part. This is done by looking up the identifier
/// in the `Params` object returned by `HttpRequest.match_info()` method.
///
/// By default, each part matches the regular expression `[^{}/]+`.
///
/// You can also specify a custom regex in the form `{identifier:regex}`:
///
/// For instance, to route Get requests on any route matching `/users/{userid}/{friend}` and
/// store userid and friend in the exposed Params object:
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::*; /// use actix_web::{http, App, HttpRequest, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = App::new()
/// .resource("/test", |r| { /// .route("/test", http::Method::GET,
/// r.method(Method::GET).f(|_| httpcodes::HttpOk); /// |_: HttpRequest| HttpResponse::Ok())
/// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed); /// .route("/test", http::Method::POST,
/// }); /// |_: HttpRequest| HttpResponse::MethodNotAllowed());
/// } /// }
/// ``` /// ```
pub fn resource<F>(mut self, path: &str, f: F) -> Application<S> pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S>
where F: FnOnce(&mut Resource<S>) + 'static where F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{ {
{ {
let parts = self.parts.as_mut().expect("Use after finish"); let parts: &mut ApplicationParts<S> = unsafe{
mem::transmute(self.parts.as_mut().expect("Use after finish"))};
// add resource // get resource handler
let mut resource = Resource::default(); for &mut (ref pattern, ref mut handler) in &mut parts.resources {
f(&mut resource); if let Some(ref mut handler) = *handler {
if pattern.pattern() == path {
handler.method(method).with(f);
return self
}
}
}
let pattern = Pattern::new(resource.get_name(), path); let mut handler = ResourceHandler::default();
parts.resources.push((pattern, Some(resource))); handler.method(method).with(f);
let pattern = Resource::new(handler.get_name(), path);
parts.resources.push((pattern, Some(handler)));
} }
self self
} }
/// Default resource is used if no matched route could be found. /// Configure resource for a specific path.
pub fn default_resource<F>(mut self, f: F) -> Application<S> ///
where F: FnOnce(&mut Resource<S>) + 'static /// Resources may have variable path segments. For example, a
/// resource with the path `/a/{name}/c` would match all incoming
/// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`.
///
/// A variable segment is specified in the form `{identifier}`,
/// where the identifier can be used later in a request handler to
/// access the matched value for that segment. This is done by
/// looking up the identifier in the `Params` object returned by
/// `HttpRequest.match_info()` method.
///
/// By default, each segment matches the regular expression `[^{}/]+`.
///
/// You can also specify a custom regex in the form `{identifier:regex}`:
///
/// For instance, to route `GET`-requests on any route matching
/// `/users/{userid}/{friend}` and store `userid` and `friend` in
/// the exposed `Params` object:
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// });
/// }
/// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> App<S>
where F: FnOnce(&mut ResourceHandler<S>) -> R + 'static
{
{
let parts = self.parts.as_mut().expect("Use after finish");
// add resource handler
let mut handler = ResourceHandler::default();
f(&mut handler);
let pattern = Resource::new(handler.get_name(), path);
parts.resources.push((pattern, Some(handler)));
}
self
}
/// Configure resource for a specific path.
#[doc(hidden)]
pub fn register_resource(&mut self, path: &str, resource: ResourceHandler<S>) {
let pattern = Resource::new(resource.get_name(), path);
self.parts.as_mut().expect("Use after finish")
.resources.push((pattern, Some(resource)));
}
/// Default resource to be used if no matching route could be found.
pub fn default_resource<F, R>(mut self, f: F) -> App<S>
where F: FnOnce(&mut ResourceHandler<S>) -> R + 'static
{ {
{ {
let parts = self.parts.as_mut().expect("Use after finish"); let parts = self.parts.as_mut().expect("Use after finish");
@ -260,7 +350,7 @@ impl<S> Application<S> where S: 'static {
} }
/// Set default content encoding. `ContentEncoding::Auto` is set by default. /// Set default content encoding. `ContentEncoding::Auto` is set by default.
pub fn default_encoding<F>(mut self, encoding: ContentEncoding) -> Application<S> pub fn default_encoding<F>(mut self, encoding: ContentEncoding) -> App<S>
{ {
{ {
let parts = self.parts.as_mut().expect("Use after finish"); let parts = self.parts.as_mut().expect("Use after finish");
@ -269,30 +359,30 @@ impl<S> Application<S> where S: 'static {
self self
} }
/// Register external resource. /// Register an external resource.
/// ///
/// External resources are useful for URL generation purposes only and /// External resources are useful for URL generation purposes only
/// are never considered for matching at request time. /// and are never considered for matching at request time. Calls to
/// Call to `HttpRequest::url_for()` will work as expected. /// `HttpRequest::url_for()` will work as expected.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::*; /// 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(httpcodes::HttpOk.into()) /// Ok(HttpResponse::Ok().into())
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = App::new()
/// .resource("/index.html", |r| r.f(index)) /// .resource("/index.html", |r| r.get().f(index))
/// .external_resource("youtube", "https://youtube.com/watch/{video_id}") /// .external_resource("youtube", "https://youtube.com/watch/{video_id}")
/// .finish(); /// .finish();
/// } /// }
/// ``` /// ```
pub fn external_resource<T, U>(mut self, name: T, url: U) -> Application<S> pub fn external_resource<T, U>(mut self, name: T, url: U) -> App<S>
where T: AsRef<str>, U: AsRef<str> where T: AsRef<str>, U: AsRef<str>
{ {
{ {
@ -302,66 +392,109 @@ impl<S> Application<S> where S: 'static {
panic!("External resource {:?} is registered.", name.as_ref()); panic!("External resource {:?} is registered.", name.as_ref());
} }
parts.external.insert( parts.external.insert(
String::from(name.as_ref()), Pattern::new(name.as_ref(), url.as_ref())); String::from(name.as_ref()),
Resource::external(name.as_ref(), url.as_ref()));
} }
self self
} }
/// Configure handler for specific path prefix. /// Configure handler for specific path prefix.
/// ///
/// Path prefix consists valid path segments. i.e for prefix `/app` /// A path prefix consists of valid path segments, i.e for the
/// any request with following paths `/app`, `/app/` or `/app/test` /// prefix `/app` any request with the paths `/app`, `/app/` or
/// would match, but path `/application` would not match. /// `/app/test` would match, but the path `/application` would
/// not.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::*; /// use actix_web::{http, App, HttpRequest, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = App::new()
/// .handler("/app", |req: HttpRequest| { /// .handler("/app", |req: HttpRequest| {
/// match *req.method() { /// match *req.method() {
/// Method::GET => httpcodes::HttpOk, /// http::Method::GET => HttpResponse::Ok(),
/// Method::POST => httpcodes::HttpMethodNotAllowed, /// http::Method::POST => HttpResponse::MethodNotAllowed(),
/// _ => httpcodes::HttpNotFound, /// _ => HttpResponse::NotFound(),
/// }}); /// }});
/// } /// }
/// ``` /// ```
pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> Application<S> pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> App<S>
{ {
{ {
let path = path.trim().trim_right_matches('/').to_owned(); let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/')
}
let parts = self.parts.as_mut().expect("Use after finish"); let parts = self.parts.as_mut().expect("Use after finish");
parts.handlers.push((path, Box::new(WrapHandler::new(handler)))); parts.handlers.push((path, Box::new(WrapHandler::new(handler))));
} }
self self
} }
/// Register a middleware /// Register a middleware.
pub fn middleware<M: Middleware<S>>(mut self, mw: M) -> Application<S> { pub fn middleware<M: Middleware<S>>(mut self, mw: M) -> App<S> {
self.parts.as_mut().expect("Use after finish") self.parts.as_mut().expect("Use after finish")
.middlewares.push(Box::new(mw)); .middlewares.push(Box::new(mw));
self self
} }
/// Finish application configuration and create HttpHandler object /// Run external configuration as part of the application building
/// process
///
/// This function is useful for moving parts of configuration to a
/// different module or event library. For example we can move
/// some of the resources' configuration to different module.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{App, HttpResponse, fs, middleware};
///
/// // this function could be located in different module
/// fn config(app: App) -> App {
/// app
/// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// })
/// }
///
/// fn main() {
/// let app = App::new()
/// .middleware(middleware::Logger::default())
/// .configure(config) // <- register resources
/// .handler("/static", fs::StaticFiles::new("."));
/// }
/// ```
pub fn configure<F>(self, cfg: F) -> App<S>
where F: Fn(App<S>) -> App<S>
{
cfg(self)
}
/// Finish application configuration and create `HttpHandler` object.
pub fn finish(&mut self) -> HttpApplication<S> { pub fn finish(&mut self) -> HttpApplication<S> {
let parts = self.parts.take().expect("Use after finish"); let parts = self.parts.take().expect("Use after finish");
let prefix = parts.prefix.trim().trim_right_matches('/'); let prefix = parts.prefix.trim().trim_right_matches('/');
let (prefix, prefix_len) = if prefix.is_empty() {
("/".to_owned(), 0)
} else {
(prefix.to_owned(), prefix.len())
};
let mut resources = parts.resources; let mut resources = parts.resources;
for (_, pattern) in parts.external { for (_, pattern) in parts.external {
resources.push((pattern, None)); resources.push((pattern, None));
} }
let (router, resources) = Router::new(prefix, parts.settings, resources); let (router, resources) = Router::new(&prefix, parts.settings, resources);
let inner = Rc::new(RefCell::new( let inner = Rc::new(UnsafeCell::new(
Inner { Inner {
prefix: prefix.len(), prefix: prefix_len,
default: parts.default, default: parts.default,
encoding: parts.encoding, encoding: parts.encoding,
router: router.clone(),
handlers: parts.handlers, handlers: parts.handlers,
resources, resources,
} }
@ -369,22 +502,23 @@ impl<S> Application<S> where S: 'static {
HttpApplication { HttpApplication {
state: Rc::new(parts.state), state: Rc::new(parts.state),
prefix: prefix.to_owned(),
router: router.clone(), router: router.clone(),
middlewares: Rc::new(parts.middlewares), middlewares: Rc::new(parts.middlewares),
prefix,
prefix_len,
inner, inner,
} }
} }
/// Convenience method for creating `Box<HttpHandler>` instance. /// Convenience method for creating `Box<HttpHandler>` instances.
/// ///
/// This method is useful if you need to register several application instances /// This method is useful if you need to register multiple
/// with different state. /// application instances with different state.
/// ///
/// ```rust /// ```rust
/// # use std::thread; /// # use std::thread;
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::*; /// use actix_web::{server, App, HttpResponse};
/// ///
/// struct State1; /// struct State1;
/// ///
@ -392,14 +526,14 @@ impl<S> Application<S> where S: 'static {
/// ///
/// fn main() { /// fn main() {
/// # thread::spawn(|| { /// # thread::spawn(|| {
/// HttpServer::new(|| { vec![ /// server::new(|| { vec![
/// Application::with_state(State1) /// App::with_state(State1)
/// .prefix("/app1") /// .prefix("/app1")
/// .resource("/", |r| r.h(httpcodes::HttpOk)) /// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// .boxed(), /// .boxed(),
/// Application::with_state(State2) /// App::with_state(State2)
/// .prefix("/app2") /// .prefix("/app2")
/// .resource("/", |r| r.h(httpcodes::HttpOk)) /// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// .boxed() ]}) /// .boxed() ]})
/// .bind("127.0.0.1:8080").unwrap() /// .bind("127.0.0.1:8080").unwrap()
/// .run() /// .run()
@ -411,7 +545,7 @@ impl<S> Application<S> where S: 'static {
} }
} }
impl<S: 'static> IntoHttpHandler for Application<S> { impl<S: 'static> IntoHttpHandler for App<S> {
type Handler = HttpApplication<S>; type Handler = HttpApplication<S>;
fn into_handler(mut self, settings: ServerSettings) -> HttpApplication<S> { fn into_handler(mut self, settings: ServerSettings) -> HttpApplication<S> {
@ -423,7 +557,7 @@ impl<S: 'static> IntoHttpHandler for Application<S> {
} }
} }
impl<'a, S: 'static> IntoHttpHandler for &'a mut Application<S> { impl<'a, S: 'static> IntoHttpHandler for &'a mut App<S> {
type Handler = HttpApplication<S>; type Handler = HttpApplication<S>;
fn into_handler(self, settings: ServerSettings) -> HttpApplication<S> { fn into_handler(self, settings: ServerSettings) -> HttpApplication<S> {
@ -436,7 +570,7 @@ impl<'a, S: 'static> IntoHttpHandler for &'a mut Application<S> {
} }
#[doc(hidden)] #[doc(hidden)]
impl<S: 'static> Iterator for Application<S> { impl<S: 'static> Iterator for App<S> {
type Item = HttpApplication<S>; type Item = HttpApplication<S>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@ -455,12 +589,12 @@ mod tests {
use super::*; use super::*;
use test::TestRequest; use test::TestRequest;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpcodes; use httpresponse::HttpResponse;
#[test] #[test]
fn test_default_resource() { fn test_default_resource() {
let mut app = Application::new() let mut app = App::new()
.resource("/test", |r| r.h(httpcodes::HttpOk)) .resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
@ -471,8 +605,8 @@ mod tests {
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
let mut app = Application::new() let mut app = App::new()
.default_resource(|r| r.h(httpcodes::HttpMethodNotAllowed)) .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);
@ -481,17 +615,17 @@ mod tests {
#[test] #[test]
fn test_unhandled_prefix() { fn test_unhandled_prefix() {
let mut app = Application::new() let mut app = App::new()
.prefix("/test") .prefix("/test")
.resource("/test", |r| r.h(httpcodes::HttpOk)) .resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.finish(); .finish();
assert!(app.handle(HttpRequest::default()).is_err()); assert!(app.handle(HttpRequest::default()).is_err());
} }
#[test] #[test]
fn test_state() { fn test_state() {
let mut app = Application::with_state(10) let mut app = App::with_state(10)
.resource("/", |r| r.h(httpcodes::HttpOk)) .resource("/", |r| r.f(|_| HttpResponse::Ok()))
.finish(); .finish();
let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone());
let resp = app.run(req); let resp = app.run(req);
@ -500,9 +634,9 @@ mod tests {
#[test] #[test]
fn test_prefix() { fn test_prefix() {
let mut app = Application::new() let mut app = App::new()
.prefix("/test") .prefix("/test")
.resource("/blah", |r| r.h(httpcodes::HttpOk)) .resource("/blah", |r| r.f(|_| HttpResponse::Ok()))
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.handle(req); let resp = app.handle(req);
@ -523,8 +657,8 @@ mod tests {
#[test] #[test]
fn test_handler() { fn test_handler() {
let mut app = Application::new() let mut app = App::new()
.handler("/test", httpcodes::HttpOk) .handler("/test", |_| HttpResponse::Ok())
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
@ -548,11 +682,86 @@ mod tests {
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
} }
#[test]
fn test_handler2() {
let mut app = App::new()
.handler("test", |_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
let req = TestRequest::with_uri("/testapp").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_handler_with_prefix() {
let mut app = App::new()
.prefix("prefix")
.handler("/test", |_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/prefix/test").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/test/").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/test/app").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/testapp").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/prefix/blah").finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_route() {
let mut app = App::new()
.route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok())
.route("/test", Method::POST, |_: HttpRequest| HttpResponse::Created())
.finish();
let req = TestRequest::with_uri("/test").method(Method::GET).finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test").method(Method::POST).finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test").method(Method::HEAD).finish();
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
}
#[test] #[test]
fn test_handler_prefix() { fn test_handler_prefix() {
let mut app = Application::new() let mut app = App::new()
.prefix("/app") .prefix("/app")
.handler("/test", httpcodes::HttpOk) .handler("/test", |_| HttpResponse::Ok())
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();

View File

@ -6,6 +6,10 @@ use futures::Stream;
use error::Error; use error::Error;
use context::ActorHttpContext; use context::ActorHttpContext;
use handler::Responder;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Type represent streaming body /// Type represent streaming body
pub type BodyStream = Box<Stream<Item=Bytes, Error=Error>>; pub type BodyStream = Box<Stream<Item=Bytes, Error=Error>>;
@ -235,6 +239,7 @@ impl<'a> From<&'a Arc<Vec<u8>>> for Binary {
} }
impl AsRef<[u8]> for Binary { impl AsRef<[u8]> for Binary {
#[inline]
fn as_ref(&self) -> &[u8] { fn as_ref(&self) -> &[u8] {
match *self { match *self {
Binary::Bytes(ref bytes) => bytes.as_ref(), Binary::Bytes(ref bytes) => bytes.as_ref(),
@ -246,6 +251,17 @@ impl AsRef<[u8]> for Binary {
} }
} }
impl Responder for Binary {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok()
.content_type("application/octet-stream")
.body(self))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -265,61 +281,61 @@ mod tests {
#[test] #[test]
fn test_static_str() { fn test_static_str() {
assert_eq!(Binary::from("test").len(), 4); assert_eq!(Binary::from("test").len(), 4);
assert_eq!(Binary::from("test").as_ref(), "test".as_bytes()); assert_eq!(Binary::from("test").as_ref(), b"test");
} }
#[test] #[test]
fn test_static_bytes() { fn test_static_bytes() {
assert_eq!(Binary::from(b"test".as_ref()).len(), 4); assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
assert_eq!(Binary::from(b"test".as_ref()).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(b"test".as_ref()).as_ref(), b"test");
assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4); assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4);
assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), "test".as_bytes()); assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), b"test");
} }
#[test] #[test]
fn test_vec() { fn test_vec() {
assert_eq!(Binary::from(Vec::from("test")).len(), 4); assert_eq!(Binary::from(Vec::from("test")).len(), 4);
assert_eq!(Binary::from(Vec::from("test")).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(Vec::from("test")).as_ref(), b"test");
} }
#[test] #[test]
fn test_bytes() { fn test_bytes() {
assert_eq!(Binary::from(Bytes::from("test")).len(), 4); assert_eq!(Binary::from(Bytes::from("test")).len(), 4);
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
} }
#[test] #[test]
fn test_ref_string() { fn test_ref_string() {
let b = Rc::new("test".to_owned()); let b = Rc::new("test".to_owned());
assert_eq!(Binary::from(&b).len(), 4); assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(&b).as_ref(), b"test");
} }
#[test] #[test]
fn test_rc_string() { fn test_rc_string() {
let b = Rc::new("test".to_owned()); let b = Rc::new("test".to_owned());
assert_eq!(Binary::from(b.clone()).len(), 4); assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
assert_eq!(Binary::from(&b).len(), 4); assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(&b).as_ref(), b"test");
} }
#[test] #[test]
fn test_arc_string() { fn test_arc_string() {
let b = Arc::new("test".to_owned()); let b = Arc::new("test".to_owned());
assert_eq!(Binary::from(b.clone()).len(), 4); assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
assert_eq!(Binary::from(&b).len(), 4); assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(&b).as_ref(), b"test");
} }
#[test] #[test]
fn test_string() { fn test_string() {
let b = "test".to_owned(); let b = "test".to_owned();
assert_eq!(Binary::from(b.clone()).len(), 4); assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
assert_eq!(Binary::from(&b).len(), 4); assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(&b).as_ref(), b"test");
} }
#[test] #[test]
@ -335,7 +351,7 @@ mod tests {
fn test_bytes_mut() { fn test_bytes_mut() {
let b = BytesMut::from("test"); let b = BytesMut::from("test");
assert_eq!(Binary::from(b.clone()).len(), 4); assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b).as_ref(), "test".as_bytes()); assert_eq!(Binary::from(b).as_ref(), b"test");
} }
#[test] #[test]

View File

@ -1,41 +1,44 @@
use std::{io, time}; use std::{fmt, mem, io, time};
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::net::Shutdown; use std::net::Shutdown;
use std::time::Duration; use std::time::{Duration, Instant};
use std::collections::{HashMap, VecDeque};
use actix::{fut, Actor, ActorFuture, Context, use actix::{fut, Actor, ActorFuture, Arbiter, Context, AsyncContext,
Handler, Message, ActorResponse, Supervised}; Handler, Message, ActorResponse, Supervised, ContextFutureSpawner};
use actix::registry::ArbiterService; use actix::registry::ArbiterService;
use actix::fut::WrapFuture; use actix::fut::WrapFuture;
use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect}; use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect};
use http::{Uri, HttpTryFrom, Error as HttpError}; use http::{Uri, HttpTryFrom, Error as HttpError};
use futures::Poll; use futures::{Async, Future, Poll};
use futures::task::{Task, current as current_task};
use futures::unsync::oneshot;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_core::reactor::Timeout;
#[cfg(feature="alpn")] #[cfg(feature="alpn")]
use openssl::ssl::{SslMethod, SslConnector, Error as OpensslError}; use openssl::ssl::{SslMethod, SslConnector, Error as OpensslError};
#[cfg(feature="alpn")] #[cfg(feature="alpn")]
use tokio_openssl::SslConnectorExt; use tokio_openssl::SslConnectorExt;
#[cfg(feature="alpn")]
use futures::Future;
#[cfg(all(feature="tls", not(feature="alpn")))] #[cfg(all(feature="tls", not(feature="alpn")))]
use native_tls::{TlsConnector, Error as TlsError}; use native_tls::{TlsConnector, Error as TlsError};
#[cfg(all(feature="tls", not(feature="alpn")))] #[cfg(all(feature="tls", not(feature="alpn")))]
use tokio_tls::TlsConnectorExt; use tokio_tls::TlsConnectorExt;
#[cfg(all(feature="tls", not(feature="alpn")))]
use futures::Future;
use {HAS_OPENSSL, HAS_TLS}; use {HAS_OPENSSL, HAS_TLS};
use server::IoStream; use server::IoStream;
#[derive(Debug)] #[derive(Debug)]
/// `Connect` type represents message that can be send to `ClientConnector` /// `Connect` type represents a message that can be sent to
/// with connection request. /// `ClientConnector` with a connection request.
pub struct Connect { pub struct Connect {
pub uri: Uri, pub(crate) uri: Uri,
pub conn_timeout: Duration, pub(crate) wait_timeout: Duration,
pub(crate) conn_timeout: Duration,
} }
impl Connect { impl Connect {
@ -43,20 +46,64 @@ impl Connect {
pub fn new<U>(uri: U) -> Result<Connect, HttpError> where Uri: HttpTryFrom<U> { pub fn new<U>(uri: U) -> Result<Connect, HttpError> where Uri: HttpTryFrom<U> {
Ok(Connect { Ok(Connect {
uri: Uri::try_from(uri).map_err(|e| e.into())?, uri: Uri::try_from(uri).map_err(|e| e.into())?,
conn_timeout: Duration::from_secs(1) wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
}) })
} }
/// Connection timeout, i.e. max time to connect to remote host.
/// Set to 1 second by default.
pub fn conn_timeout(mut self, timeout: Duration) -> Self {
self.conn_timeout = timeout;
self
}
/// If connection pool limits are enabled, wait time indicates
/// max time to wait for a connection to become available.
/// Set to 5 seconds by default.
pub fn wait_timeout(mut self, timeout: Duration) -> Self {
self.wait_timeout = timeout;
self
}
} }
impl Message for Connect { impl Message for Connect {
type Result = Result<Connection, ClientConnectorError>; type Result = Result<Connection, ClientConnectorError>;
} }
/// A set of errors that can occur during connecting to a http host /// Pause connection process for `ClientConnector`
///
/// All connect requests enter wait state during connector pause.
pub struct Pause {
time: Option<Duration>,
}
impl Pause {
/// Create message with pause duration parameter
pub fn new(time: Duration) -> Pause {
Pause{time: Some(time)}
}
}
impl Default for Pause {
fn default() -> Pause {
Pause{time: None}
}
}
impl Message for Pause {
type Result = ();
}
/// Resume connection process for `ClientConnector`
#[derive(Message)]
pub struct Resume;
/// A set of errors that can occur while connecting to an HTTP host
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
pub enum ClientConnectorError { pub enum ClientConnectorError {
/// Invalid url /// Invalid URL
#[fail(display="Invalid url")] #[fail(display="Invalid URL")]
InvalidUrl, InvalidUrl,
/// SSL feature is not enabled /// SSL feature is not enabled
@ -78,14 +125,14 @@ pub enum ClientConnectorError {
Connector(#[cause] ConnectorError), Connector(#[cause] ConnectorError),
/// Connection took too long /// Connection took too long
#[fail(display = "Timeout out while establishing connection")] #[fail(display = "Timeout while establishing connection")]
Timeout, Timeout,
/// Connector has been disconnected /// Connector has been disconnected
#[fail(display = "Internal error: connector has been disconnected")] #[fail(display = "Internal error: connector has been disconnected")]
Disconnected, Disconnected,
/// Connection io error /// Connection IO error
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
IoError(#[cause] io::Error), IoError(#[cause] io::Error),
} }
@ -99,15 +146,43 @@ impl From<ConnectorError> for ClientConnectorError {
} }
} }
struct Waiter {
tx: oneshot::Sender<Result<Connection, ClientConnectorError>>,
wait: Instant,
conn_timeout: Duration,
}
/// `ClientConnector` type is responsible for transport layer of a
/// client connection.
pub struct ClientConnector { pub struct ClientConnector {
#[cfg(all(feature="alpn"))] #[cfg(all(feature="alpn"))]
connector: SslConnector, connector: SslConnector,
#[cfg(all(feature="tls", not(feature="alpn")))] #[cfg(all(feature="tls", not(feature="alpn")))]
connector: TlsConnector, connector: TlsConnector,
pool: Rc<Pool>,
pool_modified: Rc<Cell<bool>>,
conn_lifetime: Duration,
conn_keep_alive: Duration,
limit: usize,
limit_per_host: usize,
acquired: usize,
acquired_per_host: HashMap<Key, usize>,
available: HashMap<Key, VecDeque<Conn>>,
to_close: Vec<Connection>,
waiters: HashMap<Key, VecDeque<Waiter>>,
wait_timeout: Option<(Instant, Timeout)>,
paused: Option<Option<(Instant, Timeout)>>,
} }
impl Actor for ClientConnector { impl Actor for ClientConnector {
type Context = Context<ClientConnector>; type Context = Context<ClientConnector>;
fn started(&mut self, ctx: &mut Self::Context) {
self.collect_periodic(ctx);
ctx.spawn(Maintenance);
}
} }
impl Supervised for ClientConnector {} impl Supervised for ClientConnector {}
@ -116,23 +191,49 @@ impl ArbiterService for ClientConnector {}
impl Default for ClientConnector { impl Default for ClientConnector {
fn default() -> ClientConnector { fn default() -> ClientConnector {
let _modified = Rc::new(Cell::new(false));
#[cfg(all(feature="alpn"))] #[cfg(all(feature="alpn"))]
{ {
let builder = SslConnector::builder(SslMethod::tls()).unwrap(); let builder = SslConnector::builder(SslMethod::tls()).unwrap();
ClientConnector { ClientConnector::with_connector(builder.build())
connector: builder.build()
}
} }
#[cfg(all(feature="tls", not(feature="alpn")))] #[cfg(all(feature="tls", not(feature="alpn")))]
{ {
let builder = TlsConnector::builder().unwrap(); let builder = TlsConnector::builder().unwrap();
ClientConnector { ClientConnector {
connector: builder.build().unwrap() pool: Rc::new(Pool::new(Rc::clone(&_modified))),
pool_modified: _modified,
connector: builder.build().unwrap(),
conn_lifetime: Duration::from_secs(15),
conn_keep_alive: Duration::from_secs(75),
limit: 100,
limit_per_host: 0,
acquired: 0,
acquired_per_host: HashMap::new(),
available: HashMap::new(),
to_close: Vec::new(),
waiters: HashMap::new(),
wait_timeout: None,
paused: None,
} }
} }
#[cfg(not(any(feature="alpn", feature="tls")))] #[cfg(not(any(feature="alpn", feature="tls")))]
ClientConnector {} ClientConnector {pool: Rc::new(Pool::new(Rc::clone(&_modified))),
pool_modified: _modified,
conn_lifetime: Duration::from_secs(15),
conn_keep_alive: Duration::from_secs(75),
limit: 100,
limit_per_host: 0,
acquired: 0,
acquired_per_host: HashMap::new(),
available: HashMap::new(),
to_close: Vec::new(),
waiters: HashMap::new(),
wait_timeout: None,
paused: None,
}
} }
} }
@ -141,9 +242,9 @@ impl ClientConnector {
#[cfg(feature="alpn")] #[cfg(feature="alpn")]
/// Create `ClientConnector` actor with custom `SslConnector` instance. /// Create `ClientConnector` actor with custom `SslConnector` instance.
/// ///
/// By default `ClientConnector` uses very simple ssl configuration. /// By default `ClientConnector` uses very a simple SSL configuration.
/// With `with_connector` method it is possible to use custom `SslConnector` /// With `with_connector` method it is possible to use a custom
/// object. /// `SslConnector` object.
/// ///
/// ```rust /// ```rust
/// # #![cfg(feature="alpn")] /// # #![cfg(feature="alpn")]
@ -182,7 +283,286 @@ impl ClientConnector {
/// } /// }
/// ``` /// ```
pub fn with_connector(connector: SslConnector) -> ClientConnector { pub fn with_connector(connector: SslConnector) -> ClientConnector {
ClientConnector { connector } let modified = Rc::new(Cell::new(false));
ClientConnector {
connector,
pool: Rc::new(Pool::new(Rc::clone(&modified))),
pool_modified: modified,
conn_lifetime: Duration::from_secs(15),
conn_keep_alive: Duration::from_secs(75),
limit: 100,
limit_per_host: 0,
acquired: 0,
acquired_per_host: HashMap::new(),
available: HashMap::new(),
to_close: Vec::new(),
waiters: HashMap::new(),
wait_timeout: None,
paused: None,
}
}
/// Set total number of simultaneous connections.
///
/// If limit is 0, the connector has no limit.
/// The default limit size is 100.
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
/// Set total number of simultaneous connections to the same endpoint.
///
/// Endpoints are the same if they have equal (host, port, ssl) triplets.
/// If limit is 0, the connector has no limit. The default limit size is 0.
pub fn limit_per_host(mut self, limit: usize) -> Self {
self.limit_per_host = limit;
self
}
/// Set keep-alive period for opened connection.
///
/// Keep-alive period is the period between connection usage. If
/// the delay between repeated usages of the same connection
/// exceeds this period, the connection is closed.
pub fn conn_keep_alive(mut self, dur: Duration) -> Self {
self.conn_keep_alive = dur;
self
}
/// Set max lifetime period for connection.
///
/// Connection lifetime is max lifetime of any opened connection
/// until it is closed regardless of keep-alive period.
pub fn conn_lifetime(mut self, dur: Duration) -> Self {
self.conn_lifetime = dur;
self
}
fn acquire(&mut self, key: &Key) -> Acquire {
// check limits
if self.limit > 0 {
if self.acquired >= self.limit {
return Acquire::NotAvailable
}
if self.limit_per_host > 0 {
if let Some(per_host) = self.acquired_per_host.get(key) {
if self.limit_per_host >= *per_host {
return Acquire::NotAvailable
}
}
}
}
else if self.limit_per_host > 0 {
if let Some(per_host) = self.acquired_per_host.get(key) {
if self.limit_per_host >= *per_host {
return Acquire::NotAvailable
}
}
}
self.reserve(key);
// check if open connection is available
// cleanup stale connections at the same time
if let Some(ref mut connections) = self.available.get_mut(key) {
let now = Instant::now();
while let Some(conn) = connections.pop_back() {
// check if it still usable
if (now - conn.0) > self.conn_keep_alive
|| (now - conn.1.ts) > self.conn_lifetime
{
self.to_close.push(conn.1);
} else {
let mut conn = conn.1;
let mut buf = [0; 2];
match conn.stream().read(&mut buf) {
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (),
Ok(n) if n > 0 => {
self.to_close.push(conn);
continue
},
Ok(_) | Err(_) => continue,
}
return Acquire::Acquired(conn)
}
}
}
Acquire::Available
}
fn reserve(&mut self, key: &Key) {
self.acquired += 1;
let per_host =
if let Some(per_host) = self.acquired_per_host.get(key) {
*per_host
} else {
0
};
self.acquired_per_host.insert(key.clone(), per_host + 1);
}
fn release_key(&mut self, key: &Key) {
self.acquired -= 1;
let per_host =
if let Some(per_host) = self.acquired_per_host.get(key) {
*per_host
} else {
return
};
if per_host > 1 {
self.acquired_per_host.insert(key.clone(), per_host - 1);
} else {
self.acquired_per_host.remove(key);
}
}
fn collect(&mut self, periodic: bool) {
let now = Instant::now();
if self.pool_modified.get() {
// collect half acquire keys
if let Some(keys) = self.pool.collect_keys() {
for key in keys {
self.release_key(&key);
}
}
// collect connections for close
if let Some(to_close) = self.pool.collect_close() {
for conn in to_close {
self.release_key(&conn.key);
self.to_close.push(conn);
}
}
// connection connections
if let Some(to_release) = self.pool.collect_release() {
for conn in to_release {
self.release_key(&conn.key);
// check connection lifetime and the return to available pool
if (now - conn.ts) < self.conn_lifetime {
self.available.entry(conn.key.clone())
.or_insert_with(VecDeque::new)
.push_back(Conn(Instant::now(), conn));
}
}
}
}
// check keep-alive
for conns in self.available.values_mut() {
while !conns.is_empty() {
if (now > conns[0].0) && (now - conns[0].0) > self.conn_keep_alive
|| (now - conns[0].1.ts) > self.conn_lifetime
{
let conn = conns.pop_front().unwrap().1;
self.to_close.push(conn);
} else {
break
}
}
}
// check connections for shutdown
if periodic {
let mut idx = 0;
while idx < self.to_close.len() {
match AsyncWrite::shutdown(&mut self.to_close[idx]) {
Ok(Async::NotReady) => idx += 1,
_ => {
self.to_close.swap_remove(idx);
},
}
}
}
self.pool_modified.set(false);
}
fn collect_periodic(&mut self, ctx: &mut Context<Self>) {
self.collect(true);
// re-schedule next collect period
ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx));
}
fn collect_waiters(&mut self) {
let now = Instant::now();
let mut next = None;
for waiters in self.waiters.values_mut() {
let mut idx = 0;
while idx < waiters.len() {
if waiters[idx].wait <= now {
let waiter = waiters.swap_remove_back(idx).unwrap();
let _ = waiter.tx.send(Err(ClientConnectorError::Timeout));
} else {
if let Some(n) = next {
if waiters[idx].wait < n {
next = Some(waiters[idx].wait);
}
} else {
next = Some(waiters[idx].wait);
}
idx += 1;
}
}
}
if next.is_some() {
self.install_wait_timeout(next.unwrap());
}
}
fn install_wait_timeout(&mut self, time: Instant) {
if let Some(ref mut wait) = self.wait_timeout {
if wait.0 < time {
return
}
}
let mut timeout = Timeout::new(time-Instant::now(), Arbiter::handle()).unwrap();
let _ = timeout.poll();
self.wait_timeout = Some((time, timeout));
}
fn wait_for(&mut self, key: Key,
wait: Duration, conn_timeout: Duration)
-> oneshot::Receiver<Result<Connection, ClientConnectorError>>
{
// connection is not available, wait
let (tx, rx) = oneshot::channel();
let wait = Instant::now() + wait;
self.install_wait_timeout(wait);
let waiter = Waiter{ tx, wait, conn_timeout };
self.waiters.entry(key).or_insert_with(VecDeque::new).push_back(waiter);
rx
}
}
impl Handler<Pause> for ClientConnector {
type Result = ();
fn handle(&mut self, msg: Pause, _: &mut Self::Context) {
if let Some(time) = msg.time {
let when = Instant::now() + time;
let mut timeout = Timeout::new(time, Arbiter::handle()).unwrap();
let _ = timeout.poll();
self.paused = Some(Some((when, timeout)));
} else if self.paused.is_none() {
self.paused = Some(None);
}
}
}
impl Handler<Resume> for ClientConnector {
type Result = ();
fn handle(&mut self, _: Resume, _: &mut Self::Context) {
self.paused.take();
} }
} }
@ -190,7 +570,12 @@ impl Handler<Connect> for ClientConnector {
type Result = ActorResponse<ClientConnector, Connection, ClientConnectorError>; type Result = ActorResponse<ClientConnector, Connection, ClientConnectorError>;
fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result {
if self.pool_modified.get() {
self.collect(false);
}
let uri = &msg.uri; let uri = &msg.uri;
let wait_timeout = msg.wait_timeout;
let conn_timeout = msg.conn_timeout; let conn_timeout = msg.conn_timeout;
// host name is required // host name is required
@ -212,12 +597,60 @@ impl Handler<Connect> for ClientConnector {
return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported)) return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported))
} }
// check if pool has task reference
if self.pool.task.borrow().is_none() {
*self.pool.task.borrow_mut() = Some(current_task());
}
let host = uri.host().unwrap().to_owned(); let host = uri.host().unwrap().to_owned();
let port = uri.port().unwrap_or_else(|| proto.port()); let port = uri.port().unwrap_or_else(|| proto.port());
let key = Key {host, port, ssl: proto.is_secure()};
// check pause state
if self.paused.is_some() {
let rx = self.wait_for(key, wait_timeout, conn_timeout);
return ActorResponse::async(
rx.map_err(|_| ClientConnectorError::Disconnected)
.into_actor(self)
.and_then(|res, _, _| match res {
Ok(conn) => fut::ok(conn),
Err(err) => fut::err(err),
}));
}
// acquire connection
let pool = if proto.is_http() {
match self.acquire(&key) {
Acquire::Acquired(mut conn) => {
// use existing connection
conn.pool = Some(AcquiredConn(key, Some(Rc::clone(&self.pool))));
return ActorResponse::async(fut::ok(conn))
},
Acquire::NotAvailable => {
// connection is not available, wait
let rx = self.wait_for(key, wait_timeout, conn_timeout);
return ActorResponse::async(
rx.map_err(|_| ClientConnectorError::Disconnected)
.into_actor(self)
.and_then(|res, _, _| match res {
Ok(conn) => fut::ok(conn),
Err(err) => fut::err(err),
}));
}
Acquire::Available => {
Some(Rc::clone(&self.pool))
},
}
} else {
None
};
let conn = AcquiredConn(key, pool);
{
ActorResponse::async( ActorResponse::async(
Connector::from_registry() Connector::from_registry()
.send(ResolveConnect::host_and_port(&host, port) .send(ResolveConnect::host_and_port(&conn.0.host, port)
.timeout(conn_timeout)) .timeout(conn_timeout))
.into_actor(self) .into_actor(self)
.map_err(|_, _, _| ClientConnectorError::Disconnected) .map_err(|_, _, _| ClientConnectorError::Disconnected)
@ -228,12 +661,15 @@ impl Handler<Connect> for ClientConnector {
Ok(stream) => { Ok(stream) => {
if proto.is_secure() { if proto.is_secure() {
fut::Either::A( fut::Either::A(
_act.connector.connect_async(&host, stream) _act.connector.connect_async(&conn.0.host, stream)
.map_err(ClientConnectorError::SslError) .map_err(ClientConnectorError::SslError)
.map(|stream| Connection{stream: Box::new(stream)}) .map(|stream| Connection::new(
conn.0.clone(), Some(conn), Box::new(stream)))
.into_actor(_act)) .into_actor(_act))
} else { } else {
fut::Either::B(fut::ok(Connection{stream: Box::new(stream)})) fut::Either::B(fut::ok(
Connection::new(
conn.0.clone(), Some(conn), Box::new(stream))))
} }
} }
} }
@ -244,12 +680,15 @@ impl Handler<Connect> for ClientConnector {
Ok(stream) => { Ok(stream) => {
if proto.is_secure() { if proto.is_secure() {
fut::Either::A( fut::Either::A(
_act.connector.connect_async(&host, stream) _act.connector.connect_async(&conn.0.host, stream)
.map_err(ClientConnectorError::SslError) .map_err(ClientConnectorError::SslError)
.map(|stream| Connection{stream: Box::new(stream)}) .map(|stream| Connection::new(
conn.0.clone(), Some(conn), Box::new(stream)))
.into_actor(_act)) .into_actor(_act))
} else { } else {
fut::Either::B(fut::ok(Connection{stream: Box::new(stream)})) fut::Either::B(fut::ok(
Connection::new(
conn.0.clone(), Some(conn), Box::new(stream))))
} }
} }
} }
@ -261,13 +700,165 @@ impl Handler<Connect> for ClientConnector {
if proto.is_secure() { if proto.is_secure() {
fut::err(ClientConnectorError::SslIsNotSupported) fut::err(ClientConnectorError::SslIsNotSupported)
} else { } else {
fut::ok(Connection{stream: Box::new(stream)}) fut::ok(Connection::new(
conn.0.clone(), Some(conn), Box::new(stream)))
} }
} }
} }
})) }))
} }
} }
}
struct Maintenance;
impl fut::ActorFuture for Maintenance
{
type Item = ();
type Error = ();
type Actor = ClientConnector;
fn poll(&mut self, act: &mut ClientConnector, ctx: &mut Context<ClientConnector>)
-> Poll<Self::Item, Self::Error>
{
// check pause duration
let done = if let Some(Some(ref pause)) = act.paused {
pause.0 <= Instant::now() } else { false };
if done {
act.paused.take();
}
// collect connections
if act.pool_modified.get() {
act.collect(false);
}
// collect wait timers
act.collect_waiters();
// check waiters
let tmp: &mut ClientConnector = unsafe{mem::transmute(act as &mut _)};
for (key, waiters) in &mut tmp.waiters {
while let Some(waiter) = waiters.pop_front() {
if waiter.tx.is_canceled() { continue }
match act.acquire(key) {
Acquire::Acquired(mut conn) => {
// use existing connection
conn.pool = Some(
AcquiredConn(key.clone(), Some(Rc::clone(&act.pool))));
let _ = waiter.tx.send(Ok(conn));
},
Acquire::NotAvailable => {
waiters.push_front(waiter);
break
}
Acquire::Available =>
{
let conn = AcquiredConn(key.clone(), Some(Rc::clone(&act.pool)));
fut::WrapFuture::<ClientConnector>::actfuture(
Connector::from_registry()
.send(ResolveConnect::host_and_port(&conn.0.host, conn.0.port)
.timeout(waiter.conn_timeout)))
.map_err(|_, _, _| ())
.and_then(move |res, _act, _| {
#[cfg(feature="alpn")]
match res {
Err(err) => {
let _ = waiter.tx.send(Err(err.into()));
fut::Either::B(fut::err(()))
},
Ok(stream) => {
if conn.0.ssl {
fut::Either::A(
_act.connector.connect_async(&key.host, stream)
.then(move |res| {
match res {
Err(e) => {
let _ = waiter.tx.send(Err(
ClientConnectorError::SslError(e)));
},
Ok(stream) => {
let _ = waiter.tx.send(Ok(
Connection::new(
conn.0.clone(),
Some(conn), Box::new(stream))));
}
}
Ok(())
})
.actfuture())
} else {
let _ = waiter.tx.send(Ok(Connection::new(
conn.0.clone(), Some(conn), Box::new(stream))));
fut::Either::B(fut::ok(()))
}
}
}
#[cfg(all(feature="tls", not(feature="alpn")))]
match res {
Err(err) => {
let _ = waiter.tx.send(Err(err.into()));
fut::Either::B(fut::err(()))
},
Ok(stream) => {
if conn.0.ssl {
fut::Either::A(
_act.connector.connect_async(&conn.0.host, stream)
.then(|res| {
match res {
Err(e) => {
let _ = waiter.tx.send(Err(
ClientConnectorError::SslError(e)));
},
Ok(stream) => {
let _ = waiter.tx.send(Ok(
Connection::new(
conn.0.clone(), Some(conn),
Box::new(stream))));
}
}
Ok(())
})
.into_actor(_act))
} else {
let _ = waiter.tx.send(Ok(Connection::new(
conn.0.clone(), Some(conn), Box::new(stream))));
fut::Either::B(fut::ok(()))
}
}
}
#[cfg(not(any(feature="alpn", feature="tls")))]
match res {
Err(err) => {
let _ = waiter.tx.send(Err(err.into()));
fut::err(())
},
Ok(stream) => {
if conn.0.ssl {
let _ = waiter.tx.send(
Err(ClientConnectorError::SslIsNotSupported));
} else {
let _ = waiter.tx.send(Ok(Connection::new(
conn.0.clone(), Some(conn), Box::new(stream))));
};
fut::ok(())
},
}
})
.spawn(ctx);
}
}
}
}
Ok(Async::NotReady)
}
}
#[derive(PartialEq, Hash, Debug, Clone, Copy)] #[derive(PartialEq, Hash, Debug, Clone, Copy)]
enum Protocol { enum Protocol {
@ -288,6 +879,13 @@ impl Protocol {
} }
} }
fn is_http(&self) -> bool {
match *self {
Protocol::Https | Protocol::Http => true,
_ => false,
}
}
fn is_secure(&self) -> bool { fn is_secure(&self) -> bool {
match *self { match *self {
Protocol::Https | Protocol::Wss => true, Protocol::Https | Protocol::Wss => true,
@ -303,18 +901,156 @@ impl Protocol {
} }
} }
#[derive(Hash, Eq, PartialEq, Clone, Debug)]
struct Key {
host: String,
port: u16,
ssl: bool,
}
impl Key {
fn empty() -> Key {
Key{host: String::new(), port: 0, ssl: false}
}
}
#[derive(Debug)]
struct Conn(Instant, Connection);
enum Acquire {
Acquired(Connection),
Available,
NotAvailable,
}
struct AcquiredConn(Key, Option<Rc<Pool>>);
impl AcquiredConn {
fn close(&mut self, conn: Connection) {
if let Some(pool) = self.1.take() {
pool.close(conn);
}
}
fn release(&mut self, conn: Connection) {
if let Some(pool) = self.1.take() {
pool.release(conn);
}
}
}
impl Drop for AcquiredConn {
fn drop(&mut self) {
if let Some(pool) = self.1.take() {
pool.release_key(self.0.clone());
}
}
}
pub struct Pool {
keys: RefCell<Vec<Key>>,
to_close: RefCell<Vec<Connection>>,
to_release: RefCell<Vec<Connection>>,
task: RefCell<Option<Task>>,
modified: Rc<Cell<bool>>,
}
impl Pool {
fn new(modified: Rc<Cell<bool>>) -> Pool {
Pool {
modified,
keys: RefCell::new(Vec::new()),
to_close: RefCell::new(Vec::new()),
to_release: RefCell::new(Vec::new()),
task: RefCell::new(None),
}
}
fn collect_keys(&self) -> Option<Vec<Key>> {
if self.keys.borrow().is_empty() {
None
} else {
Some(mem::replace(&mut *self.keys.borrow_mut(), Vec::new()))
}
}
fn collect_close(&self) -> Option<Vec<Connection>> {
if self.to_close.borrow().is_empty() {
None
} else {
Some(mem::replace(&mut *self.to_close.borrow_mut(), Vec::new()))
}
}
fn collect_release(&self) -> Option<Vec<Connection>> {
if self.to_release.borrow().is_empty() {
None
} else {
Some(mem::replace(&mut *self.to_release.borrow_mut(), Vec::new()))
}
}
fn close(&self, conn: Connection) {
self.modified.set(true);
self.to_close.borrow_mut().push(conn);
if let Some(ref task) = *self.task.borrow() {
task.notify()
}
}
fn release(&self, conn: Connection) {
self.modified.set(true);
self.to_release.borrow_mut().push(conn);
if let Some(ref task) = *self.task.borrow() {
task.notify()
}
}
fn release_key(&self, key: Key) {
self.modified.set(true);
self.keys.borrow_mut().push(key);
if let Some(ref task) = *self.task.borrow() {
task.notify()
}
}
}
pub struct Connection { pub struct Connection {
key: Key,
stream: Box<IoStream>, stream: Box<IoStream>,
pool: Option<AcquiredConn>,
ts: Instant,
}
impl fmt::Debug for Connection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Connection {}:{}", self.key.host, self.key.port)
}
} }
impl Connection { impl Connection {
fn new(key: Key, pool: Option<AcquiredConn>, stream: Box<IoStream>) -> Self {
Connection {key, stream, pool, ts: Instant::now()}
}
pub fn stream(&mut self) -> &mut IoStream { pub fn stream(&mut self) -> &mut IoStream {
&mut *self.stream &mut *self.stream
} }
pub fn from_stream<T: IoStream>(io: T) -> Connection { pub fn from_stream<T: IoStream>(io: T) -> Connection {
Connection{stream: Box::new(io)} Connection::new(Key::empty(), None, Box::new(io))
}
pub fn close(mut self) {
if let Some(mut pool) = self.pool.take() {
pool.close(self)
}
}
pub fn release(mut self) {
if let Some(mut pool) = self.pool.take() {
pool.release(self)
}
} }
} }

View File

@ -1,4 +1,31 @@
//! Http client //! Http client api
//!
//! ```rust
//! # extern crate actix;
//! # extern crate actix_web;
//! # extern crate futures;
//! # use futures::Future;
//! use actix_web::client;
//!
//! fn main() {
//! let sys = actix::System::new("test");
//!
//! actix::Arbiter::handle().spawn({
//! client::get("http://www.rust-lang.org") // <- Create request builder
//! .header("User-Agent", "Actix-web")
//! .finish().unwrap()
//! .send() // <- Send http request
//! .map_err(|_| ())
//! .and_then(|response| { // <- server http response
//! println!("Response: {:?}", response);
//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
//! Ok(())
//! })
//! });
//!
//! sys.run();
//! }
//! ```
mod connector; mod connector;
mod parser; mod parser;
mod request; mod request;
@ -9,23 +36,86 @@ mod writer;
pub use self::pipeline::{SendRequest, SendRequestError}; pub use self::pipeline::{SendRequest, SendRequestError};
pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::request::{ClientRequest, ClientRequestBuilder};
pub use self::response::ClientResponse; pub use self::response::ClientResponse;
pub use self::connector::{Connect, Connection, ClientConnector, ClientConnectorError}; pub use self::connector::{
Connect, Pause, Resume,
Connection, ClientConnector, ClientConnectorError};
pub(crate) use self::writer::HttpClientWriter; pub(crate) use self::writer::HttpClientWriter;
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
use httpcodes;
use httpresponse::HttpResponse;
use error::ResponseError; use error::ResponseError;
use http::Method;
use httpresponse::HttpResponse;
/// Convert `SendRequestError` to a `HttpResponse` /// Convert `SendRequestError` to a `HttpResponse`
impl ResponseError for SendRequestError { impl ResponseError for SendRequestError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
SendRequestError::Connector(_) => httpcodes::HttpBadGateway.into(), SendRequestError::Connector(_) => HttpResponse::BadGateway(),
_ => httpcodes::HttpInternalServerError.into(), _ => HttpResponse::InternalServerError(),
}
.into()
} }
} }
/// Create request builder for `GET` requests
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::Future;
/// use actix_web::client;
///
/// fn main() {
/// let sys = actix::System::new("test");
///
/// actix::Arbiter::handle().spawn({
/// client::get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .finish().unwrap()
/// .send() // <- Send http request
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
/// println!("Response: {:?}", response);
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
/// Ok(())
/// })
/// });
///
/// sys.run();
/// }
/// ```
pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::GET).uri(uri);
builder
}
/// Create request builder for `HEAD` requests
pub fn head<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::HEAD).uri(uri);
builder
}
/// Create request builder for `POST` requests
pub fn post<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::POST).uri(uri);
builder
}
/// Create request builder for `PUT` requests
pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::PUT).uri(uri);
builder
}
/// Create request builder for `DELETE` requests
pub fn delete<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::DELETE).uri(uri);
builder
} }

View File

@ -126,7 +126,7 @@ impl HttpResponseParser {
let mut resp = httparse::Response::new(&mut headers); let mut resp = httparse::Response::new(&mut headers);
match resp.parse(b)? { match resp.parse(b)? {
httparse::Status::Complete(len) => { httparse::Status::Complete(len) => {
let version = if resp.version.unwrap() == 1 { let version = if resp.version.unwrap_or(1) == 1 {
Version::HTTP_11 Version::HTTP_11
} else { } else {
Version::HTTP_10 Version::HTTP_10

View File

@ -11,7 +11,7 @@ use actix::prelude::*;
use error::Error; use error::Error;
use body::{Body, BodyStream}; use body::{Body, BodyStream};
use context::{Frame, ActorHttpContext}; use context::{Frame, ActorHttpContext};
use headers::ContentEncoding; use header::ContentEncoding;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use error::PayloadError; use error::PayloadError;
use server::WriterState; use server::WriterState;
@ -22,11 +22,11 @@ use super::{Connect, Connection, ClientConnector, ClientConnectorError};
use super::HttpClientWriter; use super::HttpClientWriter;
use super::{HttpResponseParser, HttpResponseParserError}; use super::{HttpResponseParser, HttpResponseParserError};
/// A set of errors that can occur during sending request and reading response /// A set of errors that can occur during request sending and response reading
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
pub enum SendRequestError { pub enum SendRequestError {
/// Response took too long /// Response took too long
#[fail(display = "Timeout out while waiting for response")] #[fail(display = "Timeout while waiting for response")]
Timeout, Timeout,
/// Failed to connect to host /// Failed to connect to host
#[fail(display="Failed to connect to host: {}", _0)] #[fail(display="Failed to connect to host: {}", _0)]
@ -62,13 +62,15 @@ enum State {
None, None,
} }
/// `SendRequest` is a `Future` which represents asynchronous request sending process. /// `SendRequest` is a `Future` which represents an asynchronous
/// request sending process.
#[must_use = "SendRequest does nothing unless polled"] #[must_use = "SendRequest does nothing unless polled"]
pub struct SendRequest { pub struct SendRequest {
req: ClientRequest, req: ClientRequest,
state: State, state: State,
conn: Addr<Unsync, ClientConnector>, conn: Addr<Unsync, ClientConnector>,
conn_timeout: Duration, conn_timeout: Duration,
wait_timeout: Duration,
timeout: Option<Timeout>, timeout: Option<Timeout>,
} }
@ -83,7 +85,8 @@ impl SendRequest {
SendRequest{req, conn, SendRequest{req, conn,
state: State::New, state: State::New,
timeout: None, timeout: None,
conn_timeout: Duration::from_secs(1) wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
} }
} }
@ -93,13 +96,14 @@ impl SendRequest {
state: State::Connection(conn), state: State::Connection(conn),
conn: ClientConnector::from_registry(), conn: ClientConnector::from_registry(),
timeout: None, timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1), conn_timeout: Duration::from_secs(1),
} }
} }
/// Set request timeout /// Set request timeout
/// ///
/// Request timeout is a total time before response should be received. /// Request timeout is the total time before a response must be received.
/// Default value is 5 seconds. /// Default value is 5 seconds.
pub fn timeout(mut self, timeout: Duration) -> Self { pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(Timeout::new(timeout, Arbiter::handle()).unwrap()); self.timeout = Some(Timeout::new(timeout, Arbiter::handle()).unwrap());
@ -116,17 +120,13 @@ impl SendRequest {
self self
} }
fn poll_timeout(&mut self) -> Poll<(), SendRequestError> { /// Set wait timeout
if self.timeout.is_none() { ///
self.timeout = Some(Timeout::new( /// If connections pool limits are enabled, wait time indicates max time
Duration::from_secs(5), Arbiter::handle()).unwrap()); /// to wait for available connection. Default value is 5 seconds.
} pub fn wait_timeout(mut self, timeout: Duration) -> Self {
self.wait_timeout = timeout;
match self.timeout.as_mut().unwrap().poll() { self
Ok(Async::Ready(())) => Err(SendRequestError::Timeout),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(_) => unreachable!()
}
} }
} }
@ -135,8 +135,6 @@ impl Future for SendRequest {
type Error = SendRequestError; type Error = SendRequestError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.poll_timeout()?;
loop { loop {
let state = mem::replace(&mut self.state, State::None); let state = mem::replace(&mut self.state, State::None);
@ -144,6 +142,7 @@ impl Future for SendRequest {
State::New => State::New =>
self.state = State::Connect(self.conn.send(Connect { self.state = State::Connect(self.conn.send(Connect {
uri: self.req.uri().clone(), uri: self.req.uri().clone(),
wait_timeout: self.wait_timeout,
conn_timeout: self.conn_timeout, conn_timeout: self.conn_timeout,
})), })),
State::Connect(mut conn) => match conn.poll() { State::Connect(mut conn) => match conn.poll() {
@ -170,8 +169,13 @@ impl Future for SendRequest {
_ => IoBody::Done, _ => IoBody::Done,
}; };
let timeout = self.timeout.take().unwrap_or_else(||
Timeout::new(
Duration::from_secs(5), Arbiter::handle()).unwrap());
let pl = Box::new(Pipeline { let pl = Box::new(Pipeline {
body, conn, writer, body, writer,
conn: Some(conn),
parser: Some(HttpResponseParser::default()), parser: Some(HttpResponseParser::default()),
parser_buf: BytesMut::new(), parser_buf: BytesMut::new(),
disconnected: false, disconnected: false,
@ -179,6 +183,7 @@ impl Future for SendRequest {
decompress: None, decompress: None,
should_decompress: self.req.response_decompress(), should_decompress: self.req.response_decompress(),
write_state: RunningState::Running, write_state: RunningState::Running,
timeout: Some(timeout),
}); });
self.state = State::Send(pl); self.state = State::Send(pl);
}, },
@ -208,7 +213,7 @@ impl Future for SendRequest {
pub(crate) struct Pipeline { pub(crate) struct Pipeline {
body: IoBody, body: IoBody,
conn: Connection, conn: Option<Connection>,
writer: HttpClientWriter, writer: HttpClientWriter,
parser: Option<HttpResponseParser>, parser: Option<HttpResponseParser>,
parser_buf: BytesMut, parser_buf: BytesMut,
@ -217,6 +222,7 @@ pub(crate) struct Pipeline {
decompress: Option<PayloadStream>, decompress: Option<PayloadStream>,
should_decompress: bool, should_decompress: bool,
write_state: RunningState, write_state: RunningState,
timeout: Option<Timeout>,
} }
enum IoBody { enum IoBody {
@ -249,9 +255,16 @@ impl RunningState {
impl Pipeline { impl Pipeline {
fn release_conn(&mut self) {
if let Some(conn) = self.conn.take() {
conn.release()
}
}
#[inline] #[inline]
pub fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> { fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
match self.parser.as_mut().unwrap().parse(&mut self.conn, &mut self.parser_buf) { if let Some(ref mut conn) = self.conn {
match self.parser.as_mut().unwrap().parse(conn, &mut self.parser_buf) {
Ok(Async::Ready(resp)) => { Ok(Async::Ready(resp)) => {
// check content-encoding // check content-encoding
if self.should_decompress { if self.should_decompress {
@ -269,24 +282,36 @@ impl Pipeline {
} }
val => val, val => val,
} }
} else {
Ok(Async::NotReady)
}
} }
#[inline] #[inline]
pub fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> { pub fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if self.conn.is_none() {
return Ok(Async::Ready(None))
}
let conn: &mut Connection = unsafe{ mem::transmute(self.conn.as_mut().unwrap())};
let mut need_run = false; let mut need_run = false;
// need write? // need write?
if let Async::NotReady = 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)))?
{ {
need_run = true; Async::NotReady => need_run = true,
Async::Ready(_) => {
let _ = self.poll_timeout()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?;
}
} }
// need read? // need read?
if self.parser.is_some() { if self.parser.is_some() {
loop { loop {
match self.parser.as_mut().unwrap() match self.parser.as_mut().unwrap()
.parse_payload(&mut self.conn, &mut self.parser_buf)? .parse_payload(conn, &mut self.parser_buf)?
{ {
Async::Ready(Some(b)) => { Async::Ready(Some(b)) => {
if let Some(ref mut decompress) = self.decompress { if let Some(ref mut decompress) = self.decompress {
@ -314,6 +339,7 @@ impl Pipeline {
if let Some(mut decompress) = self.decompress.take() { if let Some(mut decompress) = self.decompress.take() {
let res = decompress.feed_eof(); let res = decompress.feed_eof();
if let Some(b) = res? { if let Some(b) = res? {
self.release_conn();
return Ok(Async::Ready(Some(b))) return Ok(Async::Ready(Some(b)))
} }
} }
@ -321,18 +347,30 @@ impl Pipeline {
if need_run { if need_run {
Ok(Async::NotReady) Ok(Async::NotReady)
} else { } else {
self.release_conn();
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} }
} }
fn poll_timeout(&mut self) -> Poll<(), SendRequestError> {
if self.timeout.is_some() {
match self.timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(())) => Err(SendRequestError::Timeout),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(_) => unreachable!()
}
} else {
Ok(Async::NotReady)
}
}
#[inline] #[inline]
pub fn poll_write(&mut self) -> Poll<(), Error> { fn poll_write(&mut self) -> Poll<(), Error> {
if self.write_state == RunningState::Done { if self.write_state == RunningState::Done || self.conn.is_none() {
return Ok(Async::Ready(())) return Ok(Async::Ready(()))
} }
let mut done = false; let mut done = false;
if self.drain.is_none() && self.write_state != RunningState::Paused { if self.drain.is_none() && self.write_state != RunningState::Paused {
'outter: loop { 'outter: loop {
let result = match mem::replace(&mut self.body, IoBody::Done) { let result = match mem::replace(&mut self.body, IoBody::Done) {
@ -416,7 +454,7 @@ impl Pipeline {
} }
// flush io but only if we need to // flush io but only if we need to
match self.writer.poll_completed(&mut self.conn, false) { match self.writer.poll_completed(self.conn.as_mut().unwrap(), false) {
Ok(Async::Ready(_)) => { Ok(Async::Ready(_)) => {
if self.disconnected { if self.disconnected {
self.write_state = RunningState::Done; self.write_state = RunningState::Done;
@ -440,3 +478,11 @@ impl Pipeline {
} }
} }
} }
impl Drop for Pipeline {
fn drop(&mut self) {
if let Some(conn) = self.conn.take() {
conn.close()
}
}
}

View File

@ -1,23 +1,55 @@
use std::{fmt, mem}; use std::{fmt, mem};
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::io::Write; use std::io::Write;
use std::time::Duration;
use actix::{Addr, Unsync}; use actix::{Addr, Unsync};
use cookie::{Cookie, CookieJar}; use cookie::{Cookie, CookieJar};
use bytes::{BytesMut, BufMut}; use bytes::{Bytes, BytesMut, BufMut};
use http::{uri, HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError}; use futures::Stream;
use http::header::{self, HeaderName, HeaderValue};
use serde_json; use serde_json;
use serde::Serialize; use serde::Serialize;
use url::Url;
use percent_encoding::{USERINFO_ENCODE_SET, percent_encode}; use percent_encoding::{USERINFO_ENCODE_SET, percent_encode};
use body::Body; use body::Body;
use error::Error; use error::Error;
use header::{ContentEncoding, Header, IntoHeaderValue}; use header::{ContentEncoding, Header, IntoHeaderValue};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use http::{uri, HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError};
use http::header::{self, HeaderName, HeaderValue};
use super::pipeline::SendRequest; use super::pipeline::SendRequest;
use super::connector::{Connection, ClientConnector}; use super::connector::{Connection, ClientConnector};
/// An HTTP Client Request /// An HTTP Client Request
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::Future;
/// use actix_web::client::ClientRequest;
///
/// fn main() {
/// let sys = actix::System::new("test");
///
/// actix::Arbiter::handle().spawn({
/// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .finish().unwrap()
/// .send() // <- Send http request
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
/// println!("Response: {:?}", response);
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
/// Ok(())
/// })
/// });
///
/// sys.run();
/// }
/// ```
pub struct ClientRequest { pub struct ClientRequest {
uri: Uri, uri: Uri,
method: Method, method: Method,
@ -26,6 +58,7 @@ pub struct ClientRequest {
body: Body, body: Body,
chunked: bool, chunked: bool,
upgrade: bool, upgrade: bool,
timeout: Option<Duration>,
encoding: ContentEncoding, encoding: ContentEncoding,
response_decompress: bool, response_decompress: bool,
buffer_capacity: usize, buffer_capacity: usize,
@ -49,6 +82,7 @@ impl Default for ClientRequest {
body: Body::Empty, body: Body::Empty,
chunked: false, chunked: false,
upgrade: false, upgrade: false,
timeout: None,
encoding: ContentEncoding::Auto, encoding: ContentEncoding::Auto,
response_decompress: true, response_decompress: true,
buffer_capacity: 32_768, buffer_capacity: 32_768,
@ -60,35 +94,35 @@ impl Default for ClientRequest {
impl ClientRequest { impl ClientRequest {
/// Create request builder for `GET` request /// Create request builder for `GET` request
pub fn get<U>(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom<U> { pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build(); let mut builder = ClientRequest::build();
builder.method(Method::GET).uri(uri); builder.method(Method::GET).uri(uri);
builder builder
} }
/// Create request builder for `HEAD` request /// Create request builder for `HEAD` request
pub fn head<U>(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom<U> { pub fn head<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build(); let mut builder = ClientRequest::build();
builder.method(Method::HEAD).uri(uri); builder.method(Method::HEAD).uri(uri);
builder builder
} }
/// Create request builder for `POST` request /// Create request builder for `POST` request
pub fn post<U>(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom<U> { pub fn post<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build(); let mut builder = ClientRequest::build();
builder.method(Method::POST).uri(uri); builder.method(Method::POST).uri(uri);
builder builder
} }
/// Create request builder for `PUT` request /// Create request builder for `PUT` request
pub fn put<U>(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom<U> { pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build(); let mut builder = ClientRequest::build();
builder.method(Method::PUT).uri(uri); builder.method(Method::PUT).uri(uri);
builder builder
} }
/// Create request builder for `DELETE` request /// Create request builder for `DELETE` request
pub fn delete<U>(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom<U> { pub fn delete<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build(); let mut builder = ClientRequest::build();
builder.method(Method::DELETE).uri(uri); builder.method(Method::DELETE).uri(uri);
builder builder
@ -107,13 +141,18 @@ impl ClientRequest {
} }
} }
/// Get the request uri /// Create client request builder
pub fn build_from<T: Into<ClientRequestBuilder>>(source: T) -> ClientRequestBuilder {
source.into()
}
/// Get the request URI
#[inline] #[inline]
pub fn uri(&self) -> &Uri { pub fn uri(&self) -> &Uri {
&self.uri &self.uri
} }
/// Set client request uri /// Set client request URI
#[inline] #[inline]
pub fn set_uri(&mut self, uri: Uri) { pub fn set_uri(&mut self, uri: Uri) {
self.uri = uri self.uri = uri
@ -125,13 +164,13 @@ impl ClientRequest {
&self.method &self.method
} }
/// Set http `Method` for the request /// Set HTTP `Method` for the request
#[inline] #[inline]
pub fn set_method(&mut self, method: Method) { pub fn set_method(&mut self, method: Method) {
self.method = method self.method = method
} }
/// Get http version for the request /// Get HTTP version for the request
#[inline] #[inline]
pub fn version(&self) -> Version { pub fn version(&self) -> Version {
self.version self.version
@ -184,7 +223,7 @@ impl ClientRequest {
self.buffer_capacity self.buffer_capacity
} }
/// Get body os this response /// Get body of this response
#[inline] #[inline]
pub fn body(&self) -> &Body { pub fn body(&self) -> &Body {
&self.body &self.body
@ -195,36 +234,41 @@ impl ClientRequest {
self.body = body.into(); self.body = body.into();
} }
/// Extract body, replace it with Empty /// Extract body, replace it with `Empty`
pub(crate) fn replace_body(&mut self, body: Body) -> Body { pub(crate) fn replace_body(&mut self, body: Body) -> Body {
mem::replace(&mut self.body, body) mem::replace(&mut self.body, body)
} }
/// Send request /// Send request
/// ///
/// This method returns future that resolves to a ClientResponse /// This method returns a future that resolves to a ClientResponse
pub fn send(mut self) -> SendRequest { pub fn send(mut self) -> SendRequest {
match mem::replace(&mut self.conn, ConnectionType::Default) { let timeout = self.timeout.take();
let send = match mem::replace(&mut self.conn, ConnectionType::Default) {
ConnectionType::Default => SendRequest::new(self), ConnectionType::Default => SendRequest::new(self),
ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn), ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn),
ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn), ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn),
};
if let Some(timeout) = timeout {
send.timeout(timeout)
} else {
send
} }
} }
} }
impl fmt::Debug for ClientRequest { impl fmt::Debug for ClientRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nClientRequest {:?} {}:{}\n", let res = writeln!(f, "\nClientRequest {:?} {}:{}",
self.version, self.method, self.uri); self.version, self.method, self.uri);
let _ = write!(f, " headers:\n"); let _ = writeln!(f, " headers:");
for (key, val) in self.headers.iter() { for (key, val) in self.headers.iter() {
let _ = write!(f, " {:?}: {:?}\n", key, val); let _ = writeln!(f, " {:?}: {:?}", key, val);
} }
res res
} }
} }
/// An HTTP Client request builder /// An HTTP Client request builder
/// ///
/// This type can be used to construct an instance of `ClientRequest` through a /// This type can be used to construct an instance of `ClientRequest` through a
@ -237,10 +281,17 @@ pub struct ClientRequestBuilder {
} }
impl ClientRequestBuilder { impl ClientRequestBuilder {
/// Set HTTP uri of request. /// Set HTTP URI of request.
#[inline] #[inline]
pub fn uri<U>(&mut self, uri: U) -> &mut Self where Uri: HttpTryFrom<U> { pub fn uri<U: AsRef<str>>(&mut self, uri: U) -> &mut Self {
match Uri::try_from(uri) { match Url::parse(uri.as_ref()) {
Ok(url) => self._uri(url.as_str()),
Err(_) => self._uri(uri.as_ref()),
}
}
fn _uri(&mut self, url: &str) -> &mut Self {
match Uri::try_from(url) {
Ok(uri) => { Ok(uri) => {
// set request host header // set request host header
if let Some(host) = uri.host() { if let Some(host) = uri.host() {
@ -274,7 +325,7 @@ impl ClientRequestBuilder {
/// Set HTTP version of this request. /// Set HTTP version of this request.
/// ///
/// By default requests's http version depends on network stream /// By default requests's HTTP version depends on network stream
#[inline] #[inline]
pub fn version(&mut self, version: Version) -> &mut Self { pub fn version(&mut self, version: Version) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) { if let Some(parts) = parts(&mut self.request, &self.err) {
@ -288,16 +339,14 @@ impl ClientRequestBuilder {
/// ```rust /// ```rust
/// # extern crate mime; /// # extern crate mime;
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::*;
/// # use actix_web::httpcodes::*;
/// # use actix_web::client::*; /// # use actix_web::client::*;
/// # /// #
/// use actix_web::header; /// use actix_web::{client, http};
/// ///
/// fn main() { /// fn main() {
/// let req = ClientRequest::build() /// let req = client::ClientRequest::build()
/// .set(header::Date::now()) /// .set(http::header::Date::now())
/// .set(header::ContentType(mime::TEXT_HTML)) /// .set(http::header::ContentType(mime::TEXT_HTML))
/// .finish().unwrap(); /// .finish().unwrap();
/// } /// }
/// ``` /// ```
@ -315,7 +364,7 @@ impl ClientRequestBuilder {
/// Append a header. /// Append a header.
/// ///
/// Header get appended to existing header. /// Header gets appended to existing header.
/// To override header use `set_header()` method. /// To override header use `set_header()` method.
/// ///
/// ```rust /// ```rust
@ -422,16 +471,12 @@ impl ClientRequestBuilder {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::*; /// use actix_web::{client, http};
/// # use actix_web::httpcodes::*;
/// #
/// use actix_web::headers::Cookie;
/// use actix_web::client::ClientRequest;
/// ///
/// fn main() { /// fn main() {
/// let req = ClientRequest::build() /// let req = client::ClientRequest::build()
/// .cookie( /// .cookie(
/// Cookie::build("name", "value") /// http::Cookie::build("name", "value")
/// .domain("www.rust-lang.org") /// .domain("www.rust-lang.org")
/// .path("/") /// .path("/")
/// .secure(true) /// .secure(true)
@ -476,6 +521,17 @@ impl ClientRequestBuilder {
self self
} }
/// Set request timeout
///
/// Request timeout is a total time before response should be received.
/// Default value is 5 seconds.
pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.timeout = Some(timeout);
}
self
}
/// Send request using custom connector /// Send request using custom connector
pub fn with_connector(&mut self, conn: Addr<Unsync, ClientConnector>) -> &mut Self { pub fn with_connector(&mut self, conn: Addr<Unsync, ClientConnector>) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) { if let Some(parts) = parts(&mut self.request, &self.err) {
@ -484,7 +540,7 @@ impl ClientRequestBuilder {
self self
} }
/// Send request using existing Connection /// Send request using existing `Connection`
pub fn with_connection(&mut self, conn: Connection) -> &mut Self { pub fn with_connection(&mut self, conn: Connection) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) { if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connection(conn); parts.conn = ConnectionType::Connection(conn);
@ -492,7 +548,8 @@ impl ClientRequestBuilder {
self self
} }
/// This method calls provided closure with builder reference if value is true. /// This method calls provided closure with builder reference if
/// value is `true`.
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
where F: FnOnce(&mut ClientRequestBuilder) where F: FnOnce(&mut ClientRequestBuilder)
{ {
@ -502,7 +559,8 @@ impl ClientRequestBuilder {
self self
} }
/// This method calls provided closure with builder reference if value is Some. /// This method calls provided closure with builder reference if
/// value is `Some`.
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
where F: FnOnce(T, &mut ClientRequestBuilder) where F: FnOnce(T, &mut ClientRequestBuilder)
{ {
@ -515,9 +573,9 @@ impl ClientRequestBuilder {
/// Set a body and generate `ClientRequest`. /// Set a body and generate `ClientRequest`.
/// ///
/// `ClientRequestBuilder` can not be used after this call. /// `ClientRequestBuilder` can not be used after this call.
pub fn body<B: Into<Body>>(&mut self, body: B) -> Result<ClientRequest, HttpError> { pub fn body<B: Into<Body>>(&mut self, body: B) -> Result<ClientRequest, Error> {
if let Some(e) = self.err.take() { if let Some(e) = self.err.take() {
return Err(e) return Err(e.into())
} }
if self.default_headers { if self.default_headers {
@ -554,7 +612,7 @@ impl ClientRequestBuilder {
Ok(request) Ok(request)
} }
/// Set a json body and generate `ClientRequest` /// Set a JSON body and generate `ClientRequest`
/// ///
/// `ClientRequestBuilder` can not be used after this call. /// `ClientRequestBuilder` can not be used after this call.
pub fn json<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> { pub fn json<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
@ -569,13 +627,23 @@ impl ClientRequestBuilder {
self.header(header::CONTENT_TYPE, "application/json"); self.header(header::CONTENT_TYPE, "application/json");
} }
Ok(self.body(body)?) self.body(body)
}
/// Set a streaming body and generate `ClientRequest`.
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn streaming<S, E>(&mut self, stream: S) -> Result<ClientRequest, Error>
where S: Stream<Item=Bytes, Error=E> + 'static,
E: Into<Error>,
{
self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
} }
/// Set an empty body and generate `ClientRequest` /// Set an empty body and generate `ClientRequest`
/// ///
/// `ClientRequestBuilder` can not be used after this call. /// `ClientRequestBuilder` can not be used after this call.
pub fn finish(&mut self) -> Result<ClientRequest, HttpError> { pub fn finish(&mut self) -> Result<ClientRequest, Error> {
self.body(Body::Empty) self.body(Body::Empty)
} }
@ -599,3 +667,34 @@ fn parts<'a>(parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>)
} }
parts.as_mut() parts.as_mut()
} }
impl fmt::Debug for ClientRequestBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref parts) = self.request {
let res = writeln!(f, "\nClientRequestBuilder {:?} {}:{}",
parts.version, parts.method, parts.uri);
let _ = writeln!(f, " headers:");
for (key, val) in parts.headers.iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val);
}
res
} else {
write!(f, "ClientRequestBuilder(Consumed)")
}
}
}
/// Create `ClientRequestBuilder` from `HttpRequest`
///
/// It is useful for proxy requests. This implementation
/// copies all request headers and the method.
impl<'a, S: 'static> From<&'a HttpRequest<S>> for ClientRequestBuilder {
fn from(req: &'a HttpRequest<S>) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
for (key, value) in req.headers() {
builder.header(key.clone(), value.clone());
}
builder.method(req.method().clone());
builder
}
}

View File

@ -106,11 +106,11 @@ 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 = write!( let res = writeln!(
f, "\nClientResponse {:?} {}\n", self.version(), self.status()); f, "\nClientResponse {:?} {}", self.version(), self.status());
let _ = write!(f, " headers:\n"); let _ = writeln!(f, " headers:");
for (key, val) in self.headers().iter() { for (key, val) in self.headers().iter() {
let _ = write!(f, " {:?}: {:?}\n", key, val); let _ = writeln!(f, " {:?}: {:?}", key, val);
} }
res res
} }

View File

@ -13,10 +13,11 @@ use http::header::{HeaderValue, DATE,
CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
use flate2::Compression; use flate2::Compression;
use flate2::write::{GzEncoder, DeflateEncoder}; use flate2::write::{GzEncoder, DeflateEncoder};
#[cfg(feature="brotli")]
use brotli2::write::BrotliEncoder; use brotli2::write::BrotliEncoder;
use body::{Body, Binary}; use body::{Body, Binary};
use headers::ContentEncoding; use header::ContentEncoding;
use server::WriterState; use server::WriterState;
use server::shared::SharedBytes; use server::shared::SharedBytes;
use server::encoding::{ContentEncoder, TransferEncoding}; use server::encoding::{ContentEncoder, TransferEncoding};
@ -97,24 +98,26 @@ impl HttpClientWriter {
self.flags.insert(Flags::STARTED); self.flags.insert(Flags::STARTED);
self.encoder = content_encoder(self.buffer.clone(), msg); self.encoder = content_encoder(self.buffer.clone(), msg);
// render message
{
let mut buffer = self.buffer.get_mut();
if let Body::Binary(ref bytes) = *msg.body() {
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
} else {
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE);
}
if msg.upgrade() { if msg.upgrade() {
self.flags.insert(Flags::UPGRADE); self.flags.insert(Flags::UPGRADE);
} }
// render message
{
// status line // status line
let _ = write!(buffer, "{} {} {:?}\r\n", writeln!(self.buffer, "{} {} {:?}\r",
msg.method(), msg.uri().path(), msg.version()); msg.method(),
msg.uri().path_and_query().map(|u| u.as_str()).unwrap_or("/"),
msg.version())?;
// write headers // write headers
let mut buffer = self.buffer.get_mut();
if let Body::Binary(ref bytes) = *msg.body() {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
} else {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE);
}
for (key, value) in msg.headers() { for (key, value) in msg.headers() {
let v = value.as_ref(); let v = value.as_ref();
let k = key.as_str().as_bytes(); let k = key.as_str().as_bytes();
@ -213,6 +216,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
DeflateEncoder::new(transfer, Compression::default())), DeflateEncoder::new(transfer, Compression::default())),
ContentEncoding::Gzip => ContentEncoder::Gzip( ContentEncoding::Gzip => ContentEncoder::Gzip(
GzEncoder::new(transfer, Compression::default())), GzEncoder::new(transfer, Compression::default())),
#[cfg(feature="brotli")]
ContentEncoding::Br => ContentEncoder::Br( ContentEncoding::Br => ContentEncoder::Br(
BrotliEncoder::new(transfer, 5)), BrotliEncoder::new(transfer, 5)),
ContentEncoding::Identity => ContentEncoder::Identity(transfer), ContentEncoding::Identity => ContentEncoder::Identity(transfer),
@ -262,6 +266,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
DeflateEncoder::new(transfer, Compression::default())), DeflateEncoder::new(transfer, Compression::default())),
ContentEncoding::Gzip => ContentEncoder::Gzip( ContentEncoding::Gzip => ContentEncoder::Gzip(
GzEncoder::new(transfer, Compression::default())), GzEncoder::new(transfer, Compression::default())),
#[cfg(feature="brotli")]
ContentEncoding::Br => ContentEncoder::Br( ContentEncoding::Br => ContentEncoder::Br(
BrotliEncoder::new(transfer, 5)), BrotliEncoder::new(transfer, 5)),
ContentEncoding::Identity | ContentEncoding::Auto => ContentEncoder::Identity(transfer), ContentEncoding::Identity | ContentEncoding::Auto => ContentEncoder::Identity(transfer),

View File

@ -35,7 +35,7 @@ impl Frame {
} }
} }
/// Http actor execution context /// Execution context for http actors
pub struct HttpContext<A, S=()> where A: Actor<Context=HttpContext<A, S>>, pub struct HttpContext<A, S=()> where A: Actor<Context=HttpContext<A, S>>,
{ {
inner: ContextImpl<A>, inner: ContextImpl<A>,
@ -191,7 +191,7 @@ impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>,
if self.inner.alive() { if self.inner.alive() {
match self.inner.poll(ctx) { match self.inner.poll(ctx) {
Ok(Async::NotReady) | Ok(Async::Ready(())) => (), Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
Err(_) => return Err(ErrorInternalServerError("error").into()), Err(_) => return Err(ErrorInternalServerError("error")),
} }
} }

390
src/de.rs Normal file
View File

@ -0,0 +1,390 @@
use std::slice::Iter;
use std::borrow::Cow;
use std::convert::AsRef;
use serde::de::{self, Deserializer, Visitor, Error as DeError};
use httprequest::HttpRequest;
macro_rules! unsupported_type {
($trait_fn:ident, $name:expr) => {
fn $trait_fn<V>(self, _: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
Err(de::value::Error::custom(concat!("unsupported type: ", $name)))
}
};
}
macro_rules! parse_single_value {
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
if self.req.match_info().len() != 1 {
Err(de::value::Error::custom(
format!("wrong number of parameters: {} expected 1",
self.req.match_info().len()).as_str()))
} else {
let v = self.req.match_info()[0].parse().map_err(
|_| de::value::Error::custom(
format!("can not parse {:?} to a {}",
&self.req.match_info()[0], $tp)))?;
visitor.$visit_fn(v)
}
}
}
}
pub struct PathDeserializer<'de, S: 'de> {
req: &'de HttpRequest<S>
}
impl<'de, S: 'de> PathDeserializer<'de, S> {
pub fn new(req: &'de HttpRequest<S>) -> Self {
PathDeserializer{req}
}
}
impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S>
{
type Error = de::value::Error;
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_map(ParamsDeserializer{
params: self.req.match_info().iter(),
current: None,
})
}
fn deserialize_struct<V>(self, _: &'static str, _: &'static [&'static str], visitor: V)
-> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
self.deserialize_map(visitor)
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_unit_struct<V>(self, _: &'static str, visitor: V)
-> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
self.deserialize_unit(visitor)
}
fn deserialize_newtype_struct<V>(self, _: &'static str, visitor: V)
-> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
if self.req.match_info().len() < len {
Err(de::value::Error::custom(
format!("wrong number of parameters: {} expected {}",
self.req.match_info().len(), len).as_str()))
} else {
visitor.visit_seq(ParamsSeq{params: self.req.match_info().iter()})
}
}
fn deserialize_tuple_struct<V>(self, _: &'static str, len: usize, visitor: V)
-> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
if self.req.match_info().len() < len {
Err(de::value::Error::custom(
format!("wrong number of parameters: {} expected {}",
self.req.match_info().len(), len).as_str()))
} else {
visitor.visit_seq(ParamsSeq{params: self.req.match_info().iter()})
}
}
fn deserialize_enum<V>(self, _: &'static str, _: &'static [&'static str], _: V)
-> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
Err(de::value::Error::custom("unsupported type: enum"))
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
if self.req.match_info().len() != 1 {
Err(de::value::Error::custom(
format!("wrong number of parameters: {} expected 1",
self.req.match_info().len()).as_str()))
} else {
visitor.visit_str(&self.req.match_info()[0])
}
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
visitor.visit_seq(ParamsSeq{params: self.req.match_info().iter()})
}
unsupported_type!(deserialize_any, "'any'");
unsupported_type!(deserialize_bytes, "bytes");
unsupported_type!(deserialize_option, "Option<T>");
unsupported_type!(deserialize_identifier, "identifier");
unsupported_type!(deserialize_ignored_any, "ignored_any");
parse_single_value!(deserialize_bool, visit_bool, "bool");
parse_single_value!(deserialize_i8, visit_i8, "i8");
parse_single_value!(deserialize_i16, visit_i16, "i16");
parse_single_value!(deserialize_i32, visit_i32, "i16");
parse_single_value!(deserialize_i64, visit_i64, "i64");
parse_single_value!(deserialize_u8, visit_u8, "u8");
parse_single_value!(deserialize_u16, visit_u16, "u16");
parse_single_value!(deserialize_u32, visit_u32, "u32");
parse_single_value!(deserialize_u64, visit_u64, "u64");
parse_single_value!(deserialize_f32, visit_f32, "f32");
parse_single_value!(deserialize_f64, visit_f64, "f64");
parse_single_value!(deserialize_string, visit_string, "String");
parse_single_value!(deserialize_byte_buf, visit_string, "String");
parse_single_value!(deserialize_char, visit_char, "char");
}
struct ParamsDeserializer<'de> {
params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>,
current: Option<(&'de str, &'de str)>,
}
impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de>
{
type Error = de::value::Error;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
where K: de::DeserializeSeed<'de>,
{
self.current = self.params.next().map(|&(ref k, ref v)| (k.as_ref(), v.as_ref()));
match self.current {
Some((key, _)) => Ok(Some(seed.deserialize(Key{key})?)),
None => Ok(None),
}
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
where V: de::DeserializeSeed<'de>,
{
if let Some((_, value)) = self.current.take() {
seed.deserialize(Value { value })
} else {
Err(de::value::Error::custom("unexpected item"))
}
}
}
struct Key<'de> {
key: &'de str,
}
impl<'de> Deserializer<'de> for Key<'de> {
type Error = de::value::Error;
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_str(self.key)
}
fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
Err(de::value::Error::custom("Unexpected"))
}
forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
byte_buf option unit unit_struct newtype_struct seq tuple
tuple_struct map struct enum ignored_any
}
}
macro_rules! parse_value {
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
let v = self.value.parse().map_err(
|_| de::value::Error::custom(
format!("can not parse {:?} to a {}", self.value, $tp)))?;
visitor.$visit_fn(v)
}
}
}
struct Value<'de> {
value: &'de str,
}
impl<'de> Deserializer<'de> for Value<'de>
{
type Error = de::value::Error;
parse_value!(deserialize_bool, visit_bool, "bool");
parse_value!(deserialize_i8, visit_i8, "i8");
parse_value!(deserialize_i16, visit_i16, "i16");
parse_value!(deserialize_i32, visit_i32, "i16");
parse_value!(deserialize_i64, visit_i64, "i64");
parse_value!(deserialize_u8, visit_u8, "u8");
parse_value!(deserialize_u16, visit_u16, "u16");
parse_value!(deserialize_u32, visit_u32, "u32");
parse_value!(deserialize_u64, visit_u64, "u64");
parse_value!(deserialize_f32, visit_f32, "f32");
parse_value!(deserialize_f64, visit_f64, "f64");
parse_value!(deserialize_string, visit_string, "String");
parse_value!(deserialize_byte_buf, visit_string, "String");
parse_value!(deserialize_char, visit_char, "char");
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_unit()
}
fn deserialize_unit_struct<V>(
self, _: &'static str, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
visitor.visit_unit()
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_borrowed_bytes(self.value.as_bytes())
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_borrowed_str(self.value)
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_some(self)
}
fn deserialize_enum<V>(self, _: &'static str, _: &'static [&'static str], visitor: V)
-> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_enum(ValueEnum {value: self.value})
}
fn deserialize_newtype_struct<V>(self, _: &'static str, visitor: V)
-> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
fn deserialize_tuple<V>(self, _: usize, _: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
Err(de::value::Error::custom("unsupported type: tuple"))
}
fn deserialize_struct<V>(self, _: &'static str, _: &'static [&'static str], _: V)
-> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
Err(de::value::Error::custom("unsupported type: struct"))
}
fn deserialize_tuple_struct<V>(self, _: &'static str, _: usize, _: V)
-> Result<V::Value, Self::Error>
where V: Visitor<'de>
{
Err(de::value::Error::custom("unsupported type: tuple struct"))
}
unsupported_type!(deserialize_any, "any");
unsupported_type!(deserialize_seq, "seq");
unsupported_type!(deserialize_map, "map");
unsupported_type!(deserialize_identifier, "identifier");
}
struct ParamsSeq<'de> {
params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>,
}
impl<'de> de::SeqAccess<'de> for ParamsSeq<'de>
{
type Error = de::value::Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
where T: de::DeserializeSeed<'de>,
{
match self.params.next() {
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1.as_ref() })?)),
None => Ok(None),
}
}
}
struct ValueEnum<'de> {
value: &'de str,
}
impl<'de> de::EnumAccess<'de> for ValueEnum<'de> {
type Error = de::value::Error;
type Variant = UnitVariant;
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
where V: de::DeserializeSeed<'de>,
{
Ok((seed.deserialize(Key { key: self.value })?, UnitVariant))
}
}
struct UnitVariant;
impl<'de> de::VariantAccess<'de> for UnitVariant {
type Error = de::value::Error;
fn unit_variant(self) -> Result<(), Self::Error> {
Ok(())
}
fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value, Self::Error>
where T: de::DeserializeSeed<'de>,
{
Err(de::value::Error::custom("not supported"))
}
fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
Err(de::value::Error::custom("not supported"))
}
fn struct_variant<V>(self, _: &'static [&'static str], _: V)
-> Result<V::Value, Self::Error>
where V: Visitor<'de>,
{
Err(de::value::Error::custom("not supported"))
}
}

View File

@ -8,23 +8,21 @@ use cookie;
use httparse; use httparse;
use actix::MailboxError; use actix::MailboxError;
use futures::Canceled; use futures::Canceled;
use failure; use failure::{self, Fail, Backtrace};
use failure::{Fail, Backtrace};
use http2::Error as Http2Error; use http2::Error as Http2Error;
use http::{header, StatusCode, Error as HttpError}; use http::{header, StatusCode, Error as HttpError};
use http::uri::InvalidUriBytes; use http::uri::InvalidUri;
use http_range::HttpRangeParseError; use http_range::HttpRangeParseError;
use serde::de::value::Error as DeError;
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
pub use url::ParseError as UrlParseError; 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;
use httpcodes::{self, HttpExpectationFailed};
/// 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
@ -55,7 +53,7 @@ pub trait ResponseError: Fail {
/// ///
/// Internal server error is generated by default. /// Internal server error is generated by default.
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty) HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
} }
} }
@ -110,6 +108,20 @@ impl ResponseError for JsonError {}
/// `InternalServerError` for `UrlParseError` /// `InternalServerError` for `UrlParseError`
impl ResponseError for UrlParseError {} impl ResponseError for UrlParseError {}
/// Return `BAD_REQUEST` for `de::value::Error`
impl ResponseError for DeError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
/// Return `BAD_REQUEST` for `Utf8Error`
impl ResponseError for Utf8Error {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
/// Return `InternalServerError` for `HttpError`, /// Return `InternalServerError` for `HttpError`,
/// Response generation can return `HttpError`, so it is internal error /// Response generation can return `HttpError`, so it is internal error
impl ResponseError for HttpError {} impl ResponseError for HttpError {}
@ -120,11 +132,11 @@ impl ResponseError for io::Error {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match self.kind() { match self.kind() {
io::ErrorKind::NotFound => io::ErrorKind::NotFound =>
HttpResponse::new(StatusCode::NOT_FOUND, Body::Empty), HttpResponse::new(StatusCode::NOT_FOUND),
io::ErrorKind::PermissionDenied => io::ErrorKind::PermissionDenied =>
HttpResponse::new(StatusCode::FORBIDDEN, Body::Empty), HttpResponse::new(StatusCode::FORBIDDEN),
_ => _ =>
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty) HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
} }
} }
} }
@ -132,14 +144,14 @@ impl ResponseError for io::Error {
/// `BadRequest` for `InvalidHeaderValue` /// `BadRequest` for `InvalidHeaderValue`
impl ResponseError for header::InvalidHeaderValue { impl ResponseError for header::InvalidHeaderValue {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) HttpResponse::new(StatusCode::BAD_REQUEST)
} }
} }
/// `BadRequest` for `InvalidHeaderValue` /// `BadRequest` for `InvalidHeaderValue`
impl ResponseError for header::InvalidHeaderValueBytes { impl ResponseError for header::InvalidHeaderValueBytes {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) HttpResponse::new(StatusCode::BAD_REQUEST)
} }
} }
@ -157,7 +169,7 @@ pub enum ParseError {
Method, Method,
/// An invalid `Uri`, such as `exam ple.domain`. /// An invalid `Uri`, such as `exam ple.domain`.
#[fail(display="Uri error: {}", _0)] #[fail(display="Uri error: {}", _0)]
Uri(InvalidUriBytes), Uri(InvalidUri),
/// An invalid `HttpVersion`, such as `HTP/1.1` /// An invalid `HttpVersion`, such as `HTP/1.1`
#[fail(display="Invalid HTTP version specified")] #[fail(display="Invalid HTTP version specified")]
Version, Version,
@ -188,7 +200,7 @@ pub enum ParseError {
/// Return `BadRequest` for `ParseError` /// Return `BadRequest` for `ParseError`
impl ResponseError for ParseError { impl ResponseError for ParseError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) HttpResponse::new(StatusCode::BAD_REQUEST)
} }
} }
@ -198,6 +210,12 @@ impl From<IoError> for ParseError {
} }
} }
impl From<InvalidUri> for ParseError {
fn from(err: InvalidUri) -> ParseError {
ParseError::Uri(err)
}
}
impl From<Utf8Error> for ParseError { impl From<Utf8Error> for ParseError {
fn from(err: Utf8Error) -> ParseError { fn from(err: Utf8Error) -> ParseError {
ParseError::Utf8(err) ParseError::Utf8(err)
@ -257,7 +275,7 @@ impl ResponseError for PayloadError {}
/// Return `BadRequest` for `cookie::ParseError` /// Return `BadRequest` for `cookie::ParseError`
impl ResponseError for cookie::ParseError { impl ResponseError for cookie::ParseError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) HttpResponse::new(StatusCode::BAD_REQUEST)
} }
} }
@ -277,8 +295,8 @@ pub enum HttpRangeError {
/// 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::new( HttpResponse::with_body(
StatusCode::BAD_REQUEST, Body::from("Invalid Range header provided")) StatusCode::BAD_REQUEST, "Invalid Range header provided")
} }
} }
@ -330,7 +348,7 @@ impl From<PayloadError> for MultipartError {
impl ResponseError for MultipartError { impl ResponseError for MultipartError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) HttpResponse::new(StatusCode::BAD_REQUEST)
} }
} }
@ -347,7 +365,7 @@ pub enum ExpectError {
impl ResponseError for ExpectError { impl ResponseError for ExpectError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpExpectationFailed.with_body("Unknown Expect") HttpResponse::with_body(StatusCode::EXPECTATION_FAILED, "Unknown Expect")
} }
} }
@ -365,7 +383,7 @@ pub enum ContentTypeError {
/// Return `BadRequest` for `ContentTypeError` /// Return `BadRequest` for `ContentTypeError`
impl ResponseError for ContentTypeError { impl ResponseError for ContentTypeError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) HttpResponse::new(StatusCode::BAD_REQUEST)
} }
} }
@ -397,9 +415,12 @@ impl ResponseError for UrlencodedError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
UrlencodedError::Overflow => httpcodes::HttpPayloadTooLarge.into(), UrlencodedError::Overflow =>
UrlencodedError::UnknownLength => httpcodes::HttpLengthRequired.into(), HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE),
_ => httpcodes::HttpBadRequest.into(), UrlencodedError::UnknownLength =>
HttpResponse::new(StatusCode::LENGTH_REQUIRED),
_ =>
HttpResponse::new(StatusCode::BAD_REQUEST),
} }
} }
} }
@ -432,8 +453,10 @@ impl ResponseError for JsonPayloadError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
JsonPayloadError::Overflow => httpcodes::HttpPayloadTooLarge.into(), JsonPayloadError::Overflow =>
_ => httpcodes::HttpBadRequest.into(), HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE),
_ =>
HttpResponse::new(StatusCode::BAD_REQUEST),
} }
} }
} }
@ -469,7 +492,7 @@ pub enum UriSegmentError {
impl ResponseError for UriSegmentError { impl ResponseError for UriSegmentError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) HttpResponse::new(StatusCode::BAD_REQUEST)
} }
} }
@ -558,7 +581,7 @@ impl<T> ResponseError for InternalError<T>
where T: Send + Sync + fmt::Debug + 'static where T: Send + Sync + fmt::Debug + 'static
{ {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(self.status, Body::Empty) HttpResponse::new(self.status)
} }
} }
@ -575,68 +598,91 @@ impl<T> Responder for InternalError<T>
/// Helper function that creates wrapper of any error and generate *BAD REQUEST* response. /// Helper function that creates wrapper of any error and generate *BAD REQUEST* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorBadRequest<T>(err: T) -> InternalError<T> { pub fn ErrorBadRequest<T>(err: T) -> Error
InternalError::new(err, StatusCode::BAD_REQUEST) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::BAD_REQUEST).into()
} }
/// Helper function that creates wrapper of any error and generate *UNAUTHORIZED* response. /// Helper function that creates wrapper of any error and generate *UNAUTHORIZED* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorUnauthorized<T>(err: T) -> InternalError<T> { pub fn ErrorUnauthorized<T>(err: T) -> Error
InternalError::new(err, StatusCode::UNAUTHORIZED) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
} }
/// Helper function that creates wrapper of any error and generate *FORBIDDEN* response. /// Helper function that creates wrapper of any error and generate *FORBIDDEN* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorForbidden<T>(err: T) -> InternalError<T> { pub fn ErrorForbidden<T>(err: T) -> Error
InternalError::new(err, StatusCode::FORBIDDEN) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::FORBIDDEN).into()
} }
/// Helper function that creates wrapper of any error and generate *NOT FOUND* response. /// Helper function that creates wrapper of any error and generate *NOT FOUND* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorNotFound<T>(err: T) -> InternalError<T> { pub fn ErrorNotFound<T>(err: T) -> Error
InternalError::new(err, StatusCode::NOT_FOUND) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::NOT_FOUND).into()
} }
/// Helper function that creates wrapper of any error and generate *METHOD NOT ALLOWED* response. /// Helper function that creates wrapper of any error and generate *METHOD NOT ALLOWED* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorMethodNotAllowed<T>(err: T) -> InternalError<T> { pub fn ErrorMethodNotAllowed<T>(err: T) -> Error
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
} }
/// Helper function that creates wrapper of any error and generate *REQUEST TIMEOUT* response. /// Helper function that creates wrapper of any error and generate *REQUEST TIMEOUT* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorRequestTimeout<T>(err: T) -> InternalError<T> { pub fn ErrorRequestTimeout<T>(err: T) -> Error
InternalError::new(err, StatusCode::REQUEST_TIMEOUT) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into()
} }
/// Helper function that creates wrapper of any error and generate *CONFLICT* response. /// Helper function that creates wrapper of any error and generate *CONFLICT* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorConflict<T>(err: T) -> InternalError<T> { pub fn ErrorConflict<T>(err: T) -> Error
InternalError::new(err, StatusCode::CONFLICT) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::CONFLICT).into()
} }
/// Helper function that creates wrapper of any error and generate *GONE* response. /// Helper function that creates wrapper of any error and generate *GONE* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorGone<T>(err: T) -> InternalError<T> { pub fn ErrorGone<T>(err: T) -> Error
InternalError::new(err, StatusCode::GONE) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::GONE).into()
} }
/// Helper function that creates wrapper of any error and generate *PRECONDITION FAILED* response. /// Helper function that creates wrapper of any error and generate *PRECONDITION FAILED* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorPreconditionFailed<T>(err: T) -> InternalError<T> { pub fn ErrorPreconditionFailed<T>(err: T) -> Error
InternalError::new(err, StatusCode::PRECONDITION_FAILED) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
} }
/// Helper function that creates wrapper of any error and generate *EXPECTATION FAILED* response. /// Helper function that creates wrapper of any error and generate *EXPECTATION FAILED* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorExpectationFailed<T>(err: T) -> InternalError<T> { pub fn ErrorExpectationFailed<T>(err: T) -> Error
InternalError::new(err, StatusCode::EXPECTATION_FAILED) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
} }
/// Helper function that creates wrapper of any error and generate *INTERNAL SERVER ERROR* response. /// Helper function that creates wrapper of any error and
/// generate *INTERNAL SERVER ERROR* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ErrorInternalServerError<T>(err: T) -> InternalError<T> { pub fn ErrorInternalServerError<T>(err: T) -> Error
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR) where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
} }
#[cfg(test)] #[cfg(test)]
@ -730,7 +776,7 @@ mod tests {
e @ $error => { e @ $error => {
assert!(format!("{}", e).len() >= 5); assert!(format!("{}", e).len() >= 5);
} , } ,
e => panic!("{:?}", e) e => unreachable!("{:?}", e)
} }
} }
} }
@ -742,7 +788,7 @@ mod tests {
let desc = format!("{}", e.cause().unwrap()); let desc = format!("{}", e.cause().unwrap());
assert_eq!(desc, $from.description().to_owned()); assert_eq!(desc, $from.description().to_owned());
}, },
_ => panic!("{:?}", $from) _ => unreachable!("{:?}", $from)
} }
} }
} }

606
src/extractor.rs Normal file
View File

@ -0,0 +1,606 @@
use std::str;
use std::ops::{Deref, DerefMut};
use mime::Mime;
use bytes::Bytes;
use serde_urlencoded;
use serde::de::{self, DeserializeOwned};
use futures::future::{Future, FutureResult, result};
use encoding::all::UTF_8;
use encoding::types::{Encoding, DecoderTrap};
use error::{Error, ErrorBadRequest};
use handler::{Either, FromRequest};
use httprequest::HttpRequest;
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use de::PathDeserializer;
/// Extract typed information from the request's path.
///
/// ## Example
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// use actix_web::{App, Path, Result, http};
///
/// /// extract path info from "/{username}/{count}/?index.html" url
/// /// {username} - deserializes to a String
/// /// {count} - - deserializes to a u32
/// fn index(info: Path<(String, u32)>) -> Result<String> {
/// Ok(format!("Welcome {}! {}", info.0, info.1))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/{count}/?index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// }
/// ```
///
/// It is possible to extract path information to a specific type that implements
/// `Deserialize` trait from *serde*.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Result, http};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Path<Info>) -> Result<String> {
/// Ok(format!("Welcome {}!", info.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 struct Path<T>{
inner: T
}
impl<T> AsRef<T> for Path<T> {
fn as_ref(&self) -> &T {
&self.inner
}
}
impl<T> Deref for Path<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T> DerefMut for Path<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T> Path<T> {
/// Deconstruct to an inner value
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T, S> FromRequest<S> for Path<T>
where T: DeserializeOwned, S: 'static
{
type Config = ();
type Result = FutureResult<Self, Error>;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone();
result(de::Deserialize::deserialize(PathDeserializer::new(&req))
.map_err(|e| e.into())
.map(|inner| Path{inner}))
}
}
/// Extract typed information from from the request's query.
///
/// ## Example
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Query, http};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// // use `with` extractor for query info
/// // this handler get called only if request's query contains `username` field
/// fn index(info: Query<Info>) -> String {
/// format!("Welcome {}!", info.username)
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html",
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// }
/// ```
pub struct Query<T>(T);
impl<T> Deref for Query<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for Query<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T> Query<T> {
/// Deconstruct to a inner value
pub fn into_inner(self) -> T {
self.0
}
}
impl<T, S> FromRequest<S> for Query<T>
where T: de::DeserializeOwned, S: 'static
{
type Config = ();
type Result = FutureResult<Self, Error>;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone();
result(serde_urlencoded::from_str::<T>(req.query_string())
.map_err(|e| e.into())
.map(Query))
}
}
/// Extract typed information from the request's body.
///
/// To extract typed information from request's body, the type `T` must implement the
/// `Deserialize` trait from *serde*.
///
/// [**FormConfig**](dev/struct.FormConfig.html) allows to configure extraction
/// process.
///
/// ## Example
///
/// It is possible to extract path information to a specific type that implements
/// `Deserialize` trait from *serde*.
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Form, Result};
///
/// #[derive(Deserialize)]
/// struct FormData {
/// username: String,
/// }
///
/// /// extract form data using serde
/// /// this handler get called only if content type is *x-www-form-urlencoded*
/// /// and content of the request could be deserialized to a `FormData` struct
/// fn index(form: Form<FormData>) -> Result<String> {
/// Ok(format!("Welcome {}!", form.username))
/// }
/// # fn main() {}
/// ```
pub struct Form<T>(pub T);
impl<T> Deref for Form<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for Form<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T, S> FromRequest<S> for Form<T>
where T: DeserializeOwned + 'static, S: 'static
{
type Config = FormConfig;
type Result = Box<Future<Item=Self, Error=Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new(UrlEncoded::new(req.clone())
.limit(cfg.limit)
.from_err()
.map(Form))
}
}
/// Form extractor configuration
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Form, Result, http};
///
/// #[derive(Deserialize)]
/// struct FormData {
/// username: String,
/// }
///
/// /// extract form data using serde.
/// /// custom configuration is used for this handler, max payload size is 4k
/// 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
/// );
/// }
/// ```
pub struct FormConfig {
limit: usize,
}
impl FormConfig {
/// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self {
self.limit = limit;
self
}
}
impl Default for FormConfig {
fn default() -> Self {
FormConfig{limit: 262_144}
}
}
/// Request payload extractor.
///
/// Loads request's payload and construct Bytes instance.
///
/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure extraction
/// process.
///
/// ## Example
///
/// ```rust
/// extern crate bytes;
/// # extern crate actix_web;
/// use actix_web::{App, Result};
///
/// /// extract text data from request
/// fn index(body: bytes::Bytes) -> Result<String> {
/// Ok(format!("Body {:?}!", body))
/// }
/// # fn main() {}
/// ```
impl<S: 'static> FromRequest<S> for Bytes
{
type Config = PayloadConfig;
type Result = Either<FutureResult<Self, Error>,
Box<Future<Item=Self, Error=Error>>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
// check content-type
if let Err(e) = cfg.check_mimetype(req) {
return Either::A(result(Err(e)));
}
Either::B(Box::new(MessageBody::new(req.clone())
.limit(cfg.limit)
.from_err()))
}
}
/// Extract text information from the request's body.
///
/// Text extractor automatically decode body according to the request's charset.
///
/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure
/// extraction process.
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{App, Result};
///
/// /// extract text data from request
/// fn index(body: String) -> Result<String> {
/// Ok(format!("Body {}!", body))
/// }
/// # fn main() {}
/// ```
impl<S: 'static> FromRequest<S> for String
{
type Config = PayloadConfig;
type Result = Either<FutureResult<String, Error>,
Box<Future<Item=String, Error=Error>>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
// check content-type
if let Err(e) = cfg.check_mimetype(req) {
return Either::A(result(Err(e)));
}
// check charset
let encoding = match req.encoding() {
Err(_) => return Either::A(
result(Err(ErrorBadRequest("Unknown request charset")))),
Ok(encoding) => encoding,
};
Either::B(Box::new(
MessageBody::new(req.clone())
.limit(cfg.limit)
.from_err()
.and_then(move |body| {
let enc: *const Encoding = encoding as *const Encoding;
if enc == UTF_8 {
Ok(str::from_utf8(body.as_ref())
.map_err(|_| ErrorBadRequest("Can not decode body"))?
.to_owned())
} else {
Ok(encoding.decode(&body, DecoderTrap::Strict)
.map_err(|_| ErrorBadRequest("Can not decode body"))?)
}
})))
}
}
/// Payload configuration for request's payload.
pub struct PayloadConfig {
limit: usize,
mimetype: Option<Mime>,
}
impl PayloadConfig {
/// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self {
self.limit = limit;
self
}
/// Set required mime-type of the request. By default mime type is not enforced.
pub fn mimetype(&mut self, mt: Mime) -> &mut Self {
self.mimetype = Some(mt);
self
}
fn check_mimetype<S>(&self, req: &HttpRequest<S>) -> Result<(), Error> {
// check content-type
if let Some(ref mt) = self.mimetype {
match req.mime_type() {
Ok(Some(ref req_mt)) => {
if mt != req_mt {
return Err(ErrorBadRequest("Unexpected Content-Type"));
}
},
Ok(None) => {
return Err(ErrorBadRequest("Content-Type is expected"));
},
Err(err) => {
return Err(err.into());
},
}
}
Ok(())
}
}
impl Default for PayloadConfig {
fn default() -> Self {
PayloadConfig{limit: 262_144, mimetype: None}
}
}
#[cfg(test)]
mod tests {
use super::*;
use mime;
use bytes::Bytes;
use futures::{Async, Future};
use http::header;
use router::{Router, Resource};
use resource::ResourceHandler;
use test::TestRequest;
use server::ServerSettings;
#[derive(Deserialize, Debug, PartialEq)]
struct Info {
hello: String,
}
#[test]
fn test_bytes() {
let cfg = PayloadConfig::default();
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish();
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
match Bytes::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s, Bytes::from_static(b"hello=world"));
},
_ => unreachable!(),
}
}
#[test]
fn test_string() {
let cfg = PayloadConfig::default();
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish();
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
match String::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s, "hello=world");
},
_ => unreachable!(),
}
}
#[test]
fn test_form() {
let mut req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "11")
.finish();
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
let mut cfg = FormConfig::default();
cfg.limit(4096);
match Form::<Info>::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.hello, "world");
},
_ => unreachable!(),
}
}
#[test]
fn test_payload_config() {
let req = HttpRequest::default();
let mut cfg = PayloadConfig::default();
cfg.mimetype(mime::APPLICATION_JSON);
assert!(cfg.check_mimetype(&req).is_err());
let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded").finish();
assert!(cfg.check_mimetype(&req).is_err());
let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json").finish();
assert!(cfg.check_mimetype(&req).is_ok());
}
#[derive(Deserialize)]
struct MyStruct {
key: String,
value: String,
}
#[derive(Deserialize)]
struct Id {
id: String,
}
#[derive(Deserialize)]
struct Test2 {
key: String,
value: u32,
}
#[test]
fn test_request_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());
match Path::<MyStruct>::from_request(&req, &()).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.key, "name");
assert_eq!(s.value, "user1");
},
_ => unreachable!(),
}
match Path::<(String, String)>::from_request(&req, &()).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.0, "name");
assert_eq!(s.1, "user1");
},
_ => unreachable!(),
}
match Query::<Id>::from_request(&req, &()).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.id, "test");
},
_ => unreachable!(),
}
let mut req = TestRequest::with_uri("/name/32/").finish();
assert!(router.recognize(&mut req).is_some());
match Path::<Test2>::from_request(&req, &()).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32);
},
_ => unreachable!(),
}
match Path::<(String, u8)>::from_request(&req, &()).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
},
_ => unreachable!(),
}
match Path::<Vec<String>>::from_request(&req, &()).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.into_inner(), vec!["name".to_owned(), "32".to_owned()]);
},
_ => unreachable!(),
}
}
#[test]
fn test_extract_path_signle() {
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut routes = Vec::new();
routes.push((Resource::new("index", "/{value}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes);
let mut req = TestRequest::with_uri("/32/").finish();
assert!(router.recognize(&mut req).is_some());
match Path::<i8>::from_request(&req, &()).poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s.into_inner(), 32);
},
_ => unreachable!(),
}
}
}

258
src/fs.rs
View File

@ -1,6 +1,4 @@
//! Static files support. //! Static files support
// //! TODO: needs to re-implement actual files handling, current impl blocks
use std::{io, cmp}; use std::{io, cmp};
use std::io::{Read, Seek}; use std::io::{Read, Seek};
use std::fmt::Write; use std::fmt::Write;
@ -13,7 +11,6 @@ use std::time::{SystemTime, UNIX_EPOCH};
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use bytes::{Bytes, BytesMut, BufMut}; use bytes::{Bytes, BytesMut, BufMut};
use http::{Method, StatusCode};
use futures::{Async, Poll, Future, Stream}; use futures::{Async, Poll, Future, Stream};
use futures_cpupool::{CpuPool, CpuFuture}; use futures_cpupool::{CpuPool, CpuFuture};
use mime_guess::get_mime_type; use mime_guess::get_mime_type;
@ -21,11 +18,11 @@ use mime_guess::get_mime_type;
use header; use header;
use error::Error; use error::Error;
use param::FromParam; use param::FromParam;
use handler::{Handler, Responder}; use handler::{Handler, RouteHandler, WrapHandler, Responder, Reply};
use http::{Method, StatusCode};
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use httpcodes::{HttpOk, HttpFound, HttpMethodNotAllowed};
/// A file with an associated name; responds with the Content-Type based on the /// A file with an associated name; responds with the Content-Type based on the
/// file extension. /// file extension.
@ -36,6 +33,8 @@ pub struct NamedFile {
md: Metadata, md: Metadata,
modified: Option<SystemTime>, modified: Option<SystemTime>,
cpu_pool: Option<CpuPool>, cpu_pool: Option<CpuPool>,
only_get: bool,
status_code: StatusCode,
} }
impl NamedFile { impl NamedFile {
@ -54,7 +53,16 @@ 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;
Ok(NamedFile{path, file, md, modified, cpu_pool}) Ok(NamedFile{path, file, md, modified, cpu_pool,
only_get: false,
status_code: StatusCode::OK})
}
/// Allow only GET and HEAD methods
#[inline]
pub fn only_get(mut self) -> Self {
self.only_get = true;
self
} }
/// Returns reference to the underlying `File` object. /// Returns reference to the underlying `File` object.
@ -89,6 +97,12 @@ impl NamedFile {
self self
} }
/// Set response **Status Code**
pub fn set_status_code(mut self, status: StatusCode) -> Self {
self.status_code = status;
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| {
@ -168,11 +182,26 @@ impl Responder for NamedFile {
type Error = io::Error; type Error = io::Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> { fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
if *req.method() != Method::GET && *req.method() != Method::HEAD { if self.status_code != StatusCode::OK {
return Ok(HttpMethodNotAllowed.build() let mut resp = HttpResponse::build(self.status_code);
.header(header::http::CONTENT_TYPE, "text/plain") resp.if_some(self.path().extension(), |ext, resp| {
.header(header::http::ALLOW, "GET, HEAD") resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
.body("This resource only supports GET and HEAD.").unwrap()) });
let reader = ChunkedReadFile {
size: self.md.len(),
offset: 0,
cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()),
file: Some(self.file),
fut: None,
};
return Ok(resp.streaming(reader))
}
if self.only_get && *req.method() != Method::GET && *req.method() != Method::HEAD {
return Ok(HttpResponse::MethodNotAllowed()
.header(header::CONTENT_TYPE, "text/plain")
.header(header::ALLOW, "GET, HEAD")
.body("This resource only supports GET and HEAD."))
} }
let etag = self.etag(); let etag = self.etag();
@ -200,7 +229,7 @@ impl Responder for NamedFile {
false false
}; };
let mut resp = HttpOk.build(); let mut resp = HttpResponse::build(self.status_code);
resp resp
.if_some(self.path().extension(), |ext, resp| { .if_some(self.path().extension(), |ext, resp| {
@ -210,12 +239,14 @@ impl Responder for NamedFile {
.if_some(etag, |etag, resp| {resp.set(header::ETag(etag));}); .if_some(etag, |etag, resp| {resp.set(header::ETag(etag));});
if precondition_failed { if precondition_failed {
return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish().unwrap()) return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish())
} else if not_modified { } else if not_modified {
return Ok(resp.status(StatusCode::NOT_MODIFIED).finish().unwrap()) return Ok(resp.status(StatusCode::NOT_MODIFIED).finish())
} }
if *req.method() == Method::GET { if *req.method() == Method::HEAD {
Ok(resp.finish())
} else {
let reader = ChunkedReadFile { let reader = ChunkedReadFile {
size: self.md.len(), size: self.md.len(),
offset: 0, offset: 0,
@ -223,9 +254,7 @@ impl Responder for NamedFile {
file: Some(self.file), file: Some(self.file),
fut: None, fut: None,
}; };
Ok(resp.streaming(reader).unwrap()) Ok(resp.streaming(reader))
} else {
Ok(resp.finish().unwrap())
} }
} }
} }
@ -348,65 +377,42 @@ impl Responder for Directory {
<ul>\ <ul>\
{}\ {}\
</ul></body>\n</html>", index_of, index_of, body); </ul></body>\n</html>", index_of, index_of, body);
Ok(HttpOk.build() Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(html).unwrap()) .body(html))
} }
} }
/// This enum represents all filesystem elements.
pub enum FilesystemElement {
File(NamedFile),
Directory(Directory),
Redirect(HttpResponse),
}
impl Responder for FilesystemElement {
type Item = HttpResponse;
type Error = io::Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
match self {
FilesystemElement::File(file) => file.respond_to(req),
FilesystemElement::Directory(dir) => dir.respond_to(req),
FilesystemElement::Redirect(resp) => Ok(resp),
}
}
}
/// Static files handling /// Static files handling
/// ///
/// `StaticFile` handler must be registered with `Application::handler()` method, /// `StaticFile` handler must be registered with `App::handler()` method,
/// because `StaticFile` handler requires access sub-path information. /// because `StaticFile` handler requires access sub-path information.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{fs, Application}; /// use actix_web::{fs, App};
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = App::new()
/// .handler("/static", fs::StaticFiles::new(".", true)) /// .handler("/static", fs::StaticFiles::new("."))
/// .finish(); /// .finish();
/// } /// }
/// ``` /// ```
pub struct StaticFiles { pub struct StaticFiles<S> {
directory: PathBuf, directory: PathBuf,
accessible: bool, accessible: bool,
index: Option<String>, index: Option<String>,
show_index: bool, show_index: bool,
cpu_pool: CpuPool, cpu_pool: CpuPool,
default: Box<RouteHandler<S>>,
_chunk_size: usize, _chunk_size: usize,
_follow_symlinks: bool, _follow_symlinks: bool,
} }
impl StaticFiles { impl<S: 'static> StaticFiles<S> {
/// Create new `StaticFiles` instance
/// /// Create new `StaticFiles` instance for specified base directory.
/// `dir` - base directory pub fn new<T: Into<PathBuf>>(dir: T) -> StaticFiles<S> {
///
/// `index` - show index for directory
pub fn new<T: Into<PathBuf>>(dir: T, index: bool) -> StaticFiles {
let dir = dir.into(); let dir = dir.into();
let (dir, access) = match dir.canonicalize() { let (dir, access) = match dir.canonicalize() {
@ -428,39 +434,51 @@ impl StaticFiles {
directory: dir, directory: dir,
accessible: access, accessible: access,
index: None, index: None,
show_index: index, show_index: false,
cpu_pool: CpuPool::new(40), cpu_pool: CpuPool::new(40),
default: Box::new(WrapHandler::new(
|_| HttpResponse::new(StatusCode::NOT_FOUND))),
_chunk_size: 0, _chunk_size: 0,
_follow_symlinks: false, _follow_symlinks: false,
} }
} }
/// Show files listing for directories.
///
/// By default show files listing is disabled.
pub fn show_files_listing(mut self) -> Self {
self.show_index = true;
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
/// showing files listing. /// showing files listing.
pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles { pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles<S> {
self.index = Some(index.into()); self.index = Some(index.into());
self self
} }
/// Sets default handler which is used when no matched file could be found.
pub fn default_handler<H: Handler<S>>(mut self, handler: H) -> StaticFiles<S> {
self.default = Box::new(WrapHandler::new(handler));
self
}
} }
impl<S> Handler<S> for StaticFiles { impl<S: 'static> Handler<S> for StaticFiles<S> {
type Result = Result<FilesystemElement, io::Error>; type Result = Result<Reply, 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 {
Err(io::Error::new(io::ErrorKind::NotFound, "not found")) Ok(self.default.handle(req))
} else { } else {
let path = if let Some(path) = req.match_info().get("tail") { let relpath = match req.match_info().get("tail").map(PathBuf::from_param) {
path Some(Ok(path)) => path,
} else { _ => return Ok(self.default.handle(req))
return Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
}; };
let relpath = PathBuf::from_param(path)
.map_err(|_| io::Error::new(io::ErrorKind::NotFound, "not found"))?;
// full filepath // full filepath
let path = self.directory.join(&relpath).canonicalize()?; let path = self.directory.join(&relpath).canonicalize()?;
@ -473,21 +491,25 @@ impl<S> Handler<S> for StaticFiles {
new_path.push_str(&el.to_string_lossy()); new_path.push_str(&el.to_string_lossy());
new_path.push('/'); new_path.push('/');
} }
if !new_path.ends_with('/') {
new_path.push('/');
}
new_path.push_str(redir_index); new_path.push_str(redir_index);
Ok(FilesystemElement::Redirect( HttpResponse::Found()
HttpFound .header(header::LOCATION, new_path.as_str())
.build() .finish()
.header(header::http::LOCATION, new_path.as_str()) .respond_to(req.without_state())
.finish().unwrap()))
} else if self.show_index { } else if self.show_index {
Ok(FilesystemElement::Directory( Directory::new(self.directory.clone(), path)
Directory::new(self.directory.clone(), path))) .respond_to(req.without_state())?
.respond_to(req.without_state())
} else { } else {
Err(io::Error::new(io::ErrorKind::NotFound, "not found")) Ok(self.default.handle(req))
} }
} else { } else {
Ok(FilesystemElement::File( NamedFile::open(path)?.set_cpu_pool(self.cpu_pool.clone())
NamedFile::open(path)?.set_cpu_pool(self.cpu_pool.clone()))) .respond_to(req.without_state())?
.respond_to(req.without_state())
} }
} }
} }
@ -496,7 +518,8 @@ impl<S> Handler<S> for StaticFiles {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use test::TestRequest; use application::App;
use test::{self, TestRequest};
use http::{header, Method, StatusCode}; use http::{header, Method, StatusCode};
#[test] #[test]
@ -512,30 +535,57 @@ mod tests {
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml") assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml")
} }
#[test]
fn test_named_file_status_code() {
let mut file = NamedFile::open("Cargo.toml").unwrap()
.set_status_code(StatusCode::NOT_FOUND)
.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(), "text/x-toml");
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[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.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_any_method() {
let req = TestRequest::default().method(Method::POST).finish();
let file = NamedFile::open("Cargo.toml").unwrap();
let resp = file.respond_to(req).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[test] #[test]
fn test_static_files() { fn test_static_files() {
let mut st = StaticFiles::new(".", true); let mut st = StaticFiles::new(".").show_files_listing();
st.accessible = false; st.accessible = false;
assert!(st.handle(HttpRequest::default()).is_err()); let resp = st.handle(HttpRequest::default()).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
st.accessible = true; st.accessible = true;
st.show_index = false; st.show_index = false;
assert!(st.handle(HttpRequest::default()).is_err()); let resp = st.handle(HttpRequest::default()).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
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).respond_to(HttpRequest::default()).unwrap(); let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8"); assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8");
assert!(resp.body().is_binary()); assert!(resp.body().is_binary());
assert!(format!("{:?}", resp.body()).contains("README.md")); assert!(format!("{:?}", resp.body()).contains("README.md"));
@ -543,11 +593,12 @@ mod tests {
#[test] #[test]
fn test_redirect_to_index() { fn test_redirect_to_index() {
let mut st = StaticFiles::new(".", false).index_file("index.html"); let mut st = StaticFiles::new(".").index_file("index.html");
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "guide"); req.match_info_mut().add("tail", "guide");
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html"); assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");
@ -555,18 +606,59 @@ mod tests {
req.match_info_mut().add("tail", "guide/"); req.match_info_mut().add("tail", "guide/");
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html"); assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");
} }
#[test] #[test]
fn test_redirect_to_index_nested() { fn test_redirect_to_index_nested() {
let mut st = StaticFiles::new(".", false).index_file("Cargo.toml"); let mut st = StaticFiles::new(".").index_file("Cargo.toml");
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "examples/basics"); req.match_info_mut().add("tail", "examples/basics");
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/examples/basics/Cargo.toml"); assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/examples/basics/Cargo.toml");
} }
#[test]
fn integration_redirect_to_index_with_prefix() {
let mut srv = test::TestServer::with_factory(
|| App::new()
.prefix("public")
.handler("/", StaticFiles::new(".").index_file("Cargo.toml")));
let request = srv.get().uri(srv.url("/public")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND);
let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap();
assert_eq!(loc, "/public/Cargo.toml");
let request = srv.get().uri(srv.url("/public/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND);
let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap();
assert_eq!(loc, "/public/Cargo.toml");
}
#[test]
fn integration_redirect_to_index() {
let mut srv = test::TestServer::with_factory(
|| App::new()
.handler("test", StaticFiles::new(".").index_file("Cargo.toml")));
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND);
let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap();
assert_eq!(loc, "/test/Cargo.toml");
let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND);
let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap();
assert_eq!(loc, "/test/Cargo.toml");
}
} }

View File

@ -1,10 +1,8 @@
use std::ops::Deref;
use std::marker::PhantomData; use std::marker::PhantomData;
use futures::Poll;
use futures::future::{Future, FutureResult, ok, err};
use regex::Regex;
use futures::future::{Future, ok, err};
use http::{header, StatusCode, Error as HttpError};
use body::Body;
use error::Error; use error::Error;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -34,15 +32,29 @@ pub trait Responder {
fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error>; fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error>;
} }
/// Trait implemented by types that can be extracted from request.
///
/// Types that implement this trait can be used with `Route::with()` method.
pub trait FromRequest<S>: Sized where S: 'static
{
/// Configuration for conversion process
type Config: Default;
/// Future that resolves to a Self
type Result: Future<Item=Self, Error=Error>;
/// Convert request to a Self
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result;
}
/// Combines two different responder types into a single type /// Combines two different responder types into a single type
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// # use futures::future::Future; /// # use futures::future::Future;
/// use actix_web::AsyncResponder;
/// use futures::future::result; /// use futures::future::result;
/// use actix_web::{Either, Error, HttpRequest, HttpResponse, httpcodes}; /// use actix_web::{Either, Error, HttpRequest, HttpResponse, AsyncResponder};
/// ///
/// type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>; /// type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>;
/// ///
@ -50,13 +62,13 @@ pub trait Responder {
/// fn index(req: HttpRequest) -> RegisterResult { /// fn index(req: HttpRequest) -> RegisterResult {
/// if is_a_variant() { // <- choose variant A /// if is_a_variant() { // <- choose variant A
/// Either::A( /// Either::A(
/// httpcodes::HttpBadRequest.with_body("Bad data")) /// HttpResponse::BadRequest().body("Bad data"))
/// } else { /// } else {
/// Either::B( // <- variant B /// Either::B( // <- variant B
/// result(HttpResponse::Ok() /// result(Ok(HttpResponse::Ok()
/// .content_type("text/html") /// .content_type("text/html")
/// .body(format!("Hello!")) /// .body("Hello!")))
/// .map_err(|e| e.into())).responder()) /// .responder())
/// } /// }
/// } /// }
/// # fn is_a_variant() -> bool { true } /// # fn is_a_variant() -> bool { true }
@ -90,9 +102,49 @@ impl<A, B> Responder for Either<A, B>
} }
} }
impl<A, B, I, E> Future for Either<A, B>
where A: Future<Item=I, Error=E>,
B: Future<Item=I, Error=E>,
{
type Item = I;
type Error = E;
#[doc(hidden)] fn poll(&mut self) -> Poll<I, E> {
/// Convenience trait that convert `Future` object into `Boxed` future match *self {
Either::A(ref mut fut) => fut.poll(),
Either::B(ref mut fut) => fut.poll(),
}
}
}
/// Convenience trait that converts `Future` object to a `Boxed` future
///
/// For example loading json from request's body is async operation.
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use futures::future::Future;
/// use actix_web::{
/// App, HttpRequest, HttpResponse, HttpMessage, Error, AsyncResponder};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
/// Ok(HttpResponse::Ok().into())
/// })
/// // Construct boxed future by using `AsyncResponder::responder()` method
/// .responder()
/// }
/// # fn main() {}
/// ```
pub trait AsyncResponder<I, E>: Sized { pub trait AsyncResponder<I, E>: Sized {
fn responder(self) -> Box<Future<Item=I, Error=E>>; fn responder(self) -> Box<Future<Item=I, Error=E>>;
} }
@ -227,6 +279,9 @@ impl From<Box<Future<Item=HttpResponse, Error=Error>>> for Reply {
} }
} }
/// Convenience type alias
pub type FutureResponse<I, E=Error> = Box<Future<Item=I, Error=E>>;
impl<I, E> Responder for Box<Future<Item=I, Error=E>> impl<I, E> Responder for Box<Future<Item=I, Error=E>>
where I: Responder + 'static, where I: Responder + 'static,
E: Into<Error> + 'static E: Into<Error> + 'static
@ -339,315 +394,55 @@ impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
} }
} }
/// Path normalization helper /// Access an application state
/// ///
/// By normalizing it means: /// `S` - application state type
/// ///
/// - Add a trailing slash to the path. /// ## Example
/// - Remove a trailing slash from the path.
/// - Double slashes are replaced by one.
///
/// The handler returns as soon as it finds a path that resolves
/// correctly. The order if all enable is 1) merge, 3) both merge and append
/// and 3) append. If the path resolves with
/// at least one of those conditions, it will redirect to the new path.
///
/// If *append* is *true* append slash when needed. If a resource is
/// defined with trailing slash and the request comes without it, it will
/// append it automatically.
///
/// If *merge* is *true*, merge multiple consecutive slashes in the path into one.
///
/// This handler designed to be use as a handler for application's *default resource*.
/// ///
/// ```rust /// ```rust
/// # extern crate bytes;
/// # extern crate actix_web; /// # extern crate actix_web;
/// # #[macro_use] extern crate serde_derive; /// # extern crate futures;
/// # use actix_web::*; /// #[macro_use] extern crate serde_derive;
/// # /// use actix_web::{App, Path, State, http};
/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse { ///
/// # httpcodes::HttpOk /// /// Application state
/// # } /// struct MyApp {msg: &'static str}
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(state: State<MyApp>, info: Path<Info>) -> String {
/// format!("{} {}!", state.msg, info.username)
/// }
///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = App::with_state(MyApp{msg: "Welcome"}).resource(
/// .resource("/test/", |r| r.f(index)) /// "/{username}/index.html", // <- define path parameters
/// .default_resource(|r| r.h(NormalizePath::default())) /// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor
/// .finish();
/// } /// }
/// ``` /// ```
/// In this example `/test`, `/test///` will be redirected to `/test/` url. pub struct State<S> (HttpRequest<S>);
pub struct NormalizePath {
append: bool,
merge: bool,
re_merge: Regex,
redirect: StatusCode,
not_found: StatusCode,
}
impl Default for NormalizePath { impl<S> Deref for State<S> {
/// Create default `NormalizePath` instance, *append* is set to *true*, type Target = S;
/// *merge* is set to *true* and *redirect* is set to `StatusCode::MOVED_PERMANENTLY`
fn default() -> NormalizePath { fn deref(&self) -> &S {
NormalizePath { self.0.state()
append: true,
merge: true,
re_merge: Regex::new("//+").unwrap(),
redirect: StatusCode::MOVED_PERMANENTLY,
not_found: StatusCode::NOT_FOUND,
}
} }
} }
impl NormalizePath { impl<S: 'static> FromRequest<S> for State<S>
/// Create new `NormalizePath` instance {
pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { type Config = ();
NormalizePath { type Result = FutureResult<Self, Error>;
append,
merge,
redirect,
re_merge: Regex::new("//+").unwrap(),
not_found: StatusCode::NOT_FOUND,
}
}
}
impl<S> Handler<S> for NormalizePath { #[inline]
type Result = Result<HttpResponse, HttpError>; fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
ok(State(req.clone()))
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
if let Some(router) = req.router() {
let query = req.query_string();
if self.merge {
// merge slashes
let p = self.re_merge.replace_all(req.path(), "/");
if p.len() != req.path().len() {
if router.has_route(p.as_ref()) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref())
.body(Body::Empty);
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.body(Body::Empty);
} }
} }
// try to remove trailing slash
if p.ends_with('/') {
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str())
} else {
req.header(header::LOCATION, p)
}
.body(Body::Empty);
}
}
} else if p.ends_with('/') {
// try to remove trailing slash
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str())
} else {
req.header(header::LOCATION, p)
}
.body(Body::Empty);
}
}
}
// append trailing slash
if self.append && !req.path().ends_with('/') {
let p = req.path().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.body(Body::Empty);
}
}
}
Ok(HttpResponse::new(self.not_found, Body::Empty))
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::{header, Method};
use test::TestRequest;
use application::Application;
fn index(_req: HttpRequest) -> HttpResponse {
HttpResponse::new(StatusCode::OK, Body::Empty)
}
#[test]
fn test_normalize_path_trailing_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params =
vec![("/resource1", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
("/resource2/", "", StatusCode::OK),
("/resource1?p1=1&p2=2", "", StatusCode::OK),
("/resource1/?p1=1&p2=2", "/resource1?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY),
("/resource2/?p1=1&p2=2", "", StatusCode::OK)
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
#[test]
fn test_normalize_path_trailing_slashes_disabled() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(
NormalizePath::new(false, true, StatusCode::MOVED_PERMANENTLY)))
.finish();
// trailing slashes
let params = vec![("/resource1", StatusCode::OK),
("/resource1/", StatusCode::MOVED_PERMANENTLY),
("/resource2", StatusCode::NOT_FOUND),
("/resource2/", StatusCode::OK),
("/resource1?p1=1&p2=2", StatusCode::OK),
("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND),
("/resource2/?p1=1&p2=2", StatusCode::OK)
];
for (path, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
}
}
#[test]
fn test_normalize_path_merge_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b//", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK),
("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
#[test]
fn test_normalize_path_merge_and_append_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.resource("/resource2/a/b/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("/resource1/a/b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b//", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b/", "", StatusCode::OK),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK),
("/resource1/a/b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target, r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
}

View File

@ -1,5 +1,6 @@
use mime::{self, Mime}; use mime::{self, Mime};
use header::{QualityItem, qitem, http}; use header::{QualityItem, qitem};
use http::header as http;
header! { header! {
/// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2)
@ -31,11 +32,11 @@ header! {
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// extern crate mime; /// extern crate mime;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::{Accept, qitem}; /// use actix_web::http::header::{Accept, qitem};
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// ///
/// builder.set( /// builder.set(
/// Accept(vec![ /// Accept(vec![
@ -48,11 +49,11 @@ header! {
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// extern crate mime; /// extern crate mime;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::{Accept, qitem}; /// use actix_web::http::header::{Accept, qitem};
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// ///
/// builder.set( /// builder.set(
/// Accept(vec![ /// Accept(vec![
@ -65,11 +66,11 @@ header! {
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// extern crate mime; /// extern crate mime;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::{Accept, QualityItem, q, qitem}; /// use actix_web::http::header::{Accept, QualityItem, q, qitem};
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// ///
/// builder.set( /// builder.set(
/// Accept(vec![ /// Accept(vec![
@ -128,7 +129,7 @@ header! {
#[test] #[test]
fn test_fuzzing1() { fn test_fuzzing1() {
use test::TestRequest; use test::TestRequest;
let req = TestRequest::with_header(http::ACCEPT, "chunk#;e").finish(); let req = TestRequest::with_header(super::http::ACCEPT, "chunk#;e").finish();
let header = Accept::parse(&req); let header = Accept::parse(&req);
assert!(header.is_ok()); assert!(header.is_ok());
} }

View File

@ -1,4 +1,4 @@
use header::{http, Charset, QualityItem}; use header::{ACCEPT_CHARSET, Charset, QualityItem};
header! { header! {
/// `Accept-Charset` header, defined in /// `Accept-Charset` header, defined in
@ -23,11 +23,11 @@ header! {
/// # Examples /// # Examples
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::{AcceptCharset, Charset, qitem}; /// use actix_web::http::header::{AcceptCharset, Charset, qitem};
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) /// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
/// ); /// );
@ -35,11 +35,11 @@ header! {
/// ``` /// ```
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::{AcceptCharset, Charset, q, QualityItem}; /// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem};
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// AcceptCharset(vec![ /// AcceptCharset(vec![
/// QualityItem::new(Charset::Us_Ascii, q(900)), /// QualityItem::new(Charset::Us_Ascii, q(900)),
@ -50,17 +50,17 @@ header! {
/// ``` /// ```
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::{AcceptCharset, Charset, qitem}; /// use actix_web::http::header::{AcceptCharset, Charset, qitem};
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
/// ); /// );
/// # } /// # }
/// ``` /// ```
(AcceptCharset, http::ACCEPT_CHARSET) => (QualityItem<Charset>)+ (AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+
test_accept_charset { test_accept_charset {
/// Test case from RFC /// Test case from RFC

View File

@ -1,6 +1,5 @@
use language_tags::LanguageTag; use language_tags::LanguageTag;
use header::{http, QualityItem}; use header::{ACCEPT_LANGUAGE, QualityItem};
header! { header! {
/// `Accept-Language` header, defined in /// `Accept-Language` header, defined in
@ -26,11 +25,11 @@ header! {
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate language_tags; /// # extern crate language_tags;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::{AcceptLanguage, LanguageTag, qitem}; /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem};
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// let mut langtag: LanguageTag = Default::default(); /// let mut langtag: LanguageTag = Default::default();
/// langtag.language = Some("en".to_owned()); /// langtag.language = Some("en".to_owned());
/// langtag.region = Some("US".to_owned()); /// langtag.region = Some("US".to_owned());
@ -45,11 +44,11 @@ header! {
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # #[macro_use] extern crate language_tags; /// # #[macro_use] extern crate language_tags;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::{AcceptLanguage, QualityItem, q, qitem}; /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem};
/// # /// #
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// AcceptLanguage(vec![ /// AcceptLanguage(vec![
/// qitem(langtag!(da)), /// qitem(langtag!(da)),
@ -59,7 +58,7 @@ header! {
/// ); /// );
/// # } /// # }
/// ``` /// ```
(AcceptLanguage, http::ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+ (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+
test_accept_language { test_accept_language {
// From the RFC // From the RFC

View File

@ -1,5 +1,5 @@
use http::Method; use http::Method;
use header::http; use http::header;
header! { header! {
/// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1)
@ -25,12 +25,12 @@ header! {
/// ```rust /// ```rust
/// # extern crate http; /// # extern crate http;
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::Allow; /// use actix_web::http::header::Allow;
/// use http::Method; /// use http::Method;
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// Allow(vec![Method::GET]) /// Allow(vec![Method::GET])
/// ); /// );
@ -40,12 +40,12 @@ header! {
/// ```rust /// ```rust
/// # extern crate http; /// # extern crate http;
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::Allow; /// use actix_web::http::header::Allow;
/// use http::Method; /// use http::Method;
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// Allow(vec![ /// Allow(vec![
/// Method::GET, /// Method::GET,
@ -55,7 +55,7 @@ header! {
/// ); /// );
/// # } /// # }
/// ``` /// ```
(Allow, http::ALLOW) => (Method)* (Allow, header::ALLOW) => (Method)*
test_allow { test_allow {
// From the RFC // From the RFC

View File

@ -1,7 +1,8 @@
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use std::str::FromStr; use std::str::FromStr;
use http::header;
use header::{Header, IntoHeaderValue, Writer}; use header::{Header, IntoHeaderValue, Writer};
use header::{http, from_comma_delimited, fmt_comma_delimited}; use header::{from_comma_delimited, fmt_comma_delimited};
/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
/// ///
@ -25,20 +26,20 @@ use header::{http, from_comma_delimited, fmt_comma_delimited};
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::{CacheControl, CacheDirective}; /// use actix_web::http::header::{CacheControl, CacheDirective};
/// ///
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// CacheControl(vec![CacheDirective::MaxAge(86400u32)]) /// CacheControl(vec![CacheDirective::MaxAge(86400u32)])
/// ); /// );
/// ``` /// ```
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::{CacheControl, CacheDirective}; /// use actix_web::http::header::{CacheControl, CacheDirective};
/// ///
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// CacheControl(vec![ /// CacheControl(vec![
/// CacheDirective::NoCache, /// CacheDirective::NoCache,
@ -56,8 +57,8 @@ __hyper__deref!(CacheControl => Vec<CacheDirective>);
//TODO: this could just be the header! macro //TODO: this could just be the header! macro
impl Header for CacheControl { impl Header for CacheControl {
fn name() -> http::HeaderName { fn name() -> header::HeaderName {
http::CACHE_CONTROL header::CACHE_CONTROL
} }
#[inline] #[inline]
@ -80,12 +81,12 @@ impl fmt::Display for CacheControl {
} }
impl IntoHeaderValue for CacheControl { impl IntoHeaderValue for CacheControl {
type Error = http::InvalidHeaderValueBytes; type Error = header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
http::HeaderValue::from_shared(writer.take()) header::HeaderValue::from_shared(writer.take())
} }
} }
@ -189,7 +190,7 @@ mod tests {
#[test] #[test]
fn test_parse_multiple_headers() { fn test_parse_multiple_headers() {
let req = TestRequest::with_header( let req = TestRequest::with_header(
http::CACHE_CONTROL, "no-cache, private").finish(); header::CACHE_CONTROL, "no-cache, private").finish();
let cache = Header::parse(&req); let cache = Header::parse(&req);
assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::NoCache, assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::NoCache,
CacheDirective::Private]))) CacheDirective::Private])))
@ -198,7 +199,7 @@ mod tests {
#[test] #[test]
fn test_parse_argument() { fn test_parse_argument() {
let req = TestRequest::with_header( let req = TestRequest::with_header(
http::CACHE_CONTROL, "max-age=100, private").finish(); header::CACHE_CONTROL, "max-age=100, private").finish();
let cache = Header::parse(&req); let cache = Header::parse(&req);
assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(100), assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(100),
CacheDirective::Private]))) CacheDirective::Private])))
@ -207,7 +208,7 @@ mod tests {
#[test] #[test]
fn test_parse_quote_form() { fn test_parse_quote_form() {
let req = TestRequest::with_header( let req = TestRequest::with_header(
http::CACHE_CONTROL, "max-age=\"200\"").finish(); header::CACHE_CONTROL, "max-age=\"200\"").finish();
let cache = Header::parse(&req); let cache = Header::parse(&req);
assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(200)]))) assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(200)])))
} }
@ -215,7 +216,7 @@ mod tests {
#[test] #[test]
fn test_parse_extension() { fn test_parse_extension() {
let req = TestRequest::with_header( let req = TestRequest::with_header(
http::CACHE_CONTROL, "foo, bar=baz").finish(); header::CACHE_CONTROL, "foo, bar=baz").finish();
let cache = Header::parse(&req); let cache = Header::parse(&req);
assert_eq!(cache.ok(), Some(CacheControl(vec![ assert_eq!(cache.ok(), Some(CacheControl(vec![
CacheDirective::Extension("foo".to_owned(), None), CacheDirective::Extension("foo".to_owned(), None),
@ -224,7 +225,7 @@ mod tests {
#[test] #[test]
fn test_parse_bad_syntax() { fn test_parse_bad_syntax() {
let req = TestRequest::with_header(http::CACHE_CONTROL, "foo=").finish(); let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish();
let cache: Result<CacheControl, _> = Header::parse(&req); let cache: Result<CacheControl, _> = Header::parse(&req);
assert_eq!(cache.ok(), None) assert_eq!(cache.ok(), None)
} }

View File

@ -1,6 +1,5 @@
use language_tags::LanguageTag; use language_tags::LanguageTag;
use header::{http, QualityItem}; use header::{CONTENT_LANGUAGE, QualityItem};
header! { header! {
/// `Content-Language` header, defined in /// `Content-Language` header, defined in
@ -27,11 +26,11 @@ header! {
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # #[macro_use] extern crate language_tags; /// # #[macro_use] extern crate language_tags;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// # use actix_web::header::{ContentLanguage, qitem}; /// # use actix_web::http::header::{ContentLanguage, qitem};
/// # /// #
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// ContentLanguage(vec![ /// ContentLanguage(vec![
/// qitem(langtag!(en)), /// qitem(langtag!(en)),
@ -43,12 +42,12 @@ header! {
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # #[macro_use] extern crate language_tags; /// # #[macro_use] extern crate language_tags;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// # use actix_web::header::{ContentLanguage, qitem}; /// # use actix_web::http::header::{ContentLanguage, qitem};
/// # /// #
/// # fn main() { /// # fn main() {
/// ///
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// ContentLanguage(vec![ /// ContentLanguage(vec![
/// qitem(langtag!(da)), /// qitem(langtag!(da)),
@ -57,7 +56,7 @@ header! {
/// ); /// );
/// # } /// # }
/// ``` /// ```
(ContentLanguage, http::CONTENT_LANGUAGE) => (QualityItem<LanguageTag>)+ (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem<LanguageTag>)+
test_content_language { test_content_language {
test_header!(test1, vec![b"da"]); test_header!(test1, vec![b"da"]);

View File

@ -1,13 +1,13 @@
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
use std::str::FromStr; use std::str::FromStr;
use header::{http, IntoHeaderValue, Writer};
use error::ParseError; use error::ParseError;
use header::{IntoHeaderValue, Writer,
HeaderValue, InvalidHeaderValueBytes, CONTENT_RANGE};
header! { header! {
/// `Content-Range` header, defined in /// `Content-Range` header, defined in
/// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2)
(ContentRange, http::CONTENT_RANGE) => [ContentRangeSpec] (ContentRange, CONTENT_RANGE) => [ContentRangeSpec]
test_content_range { test_content_range {
test_header!(test_bytes, test_header!(test_bytes,
@ -195,11 +195,11 @@ impl Display for ContentRangeSpec {
} }
impl IntoHeaderValue for ContentRangeSpec { impl IntoHeaderValue for ContentRangeSpec {
type Error = http::InvalidHeaderValueBytes; type Error = InvalidHeaderValueBytes;
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
http::HeaderValue::from_shared(writer.take()) HeaderValue::from_shared(writer.take())
} }
} }

View File

@ -1,5 +1,5 @@
use mime::{self, Mime}; use mime::{self, Mime};
use header::http; use header::CONTENT_TYPE;
header! { header! {
@ -32,11 +32,11 @@ header! {
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::ContentType; /// use actix_web::http::header::ContentType;
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// ContentType::json() /// ContentType::json()
/// ); /// );
@ -47,17 +47,17 @@ header! {
/// # extern crate mime; /// # extern crate mime;
/// # extern crate actix_web; /// # extern crate actix_web;
/// use mime::TEXT_HTML; /// use mime::TEXT_HTML;
/// use actix_web::httpcodes::HttpOk; /// use actix_web::HttpResponse;
/// use actix_web::header::ContentType; /// use actix_web::http::header::ContentType;
/// ///
/// # fn main() { /// # fn main() {
/// let mut builder = HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// ContentType(TEXT_HTML) /// ContentType(TEXT_HTML)
/// ); /// );
/// # } /// # }
/// ``` /// ```
(ContentType, http::CONTENT_TYPE) => [Mime] (ContentType, CONTENT_TYPE) => [Mime]
test_content_type { test_content_type {
test_header!( test_header!(

View File

@ -1,5 +1,5 @@
use std::time::SystemTime; use std::time::SystemTime;
use header::{http, HttpDate}; use header::{DATE, HttpDate};
header! { header! {
@ -21,14 +21,14 @@ header! {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::Date; /// use actix_web::http::header::Date;
/// use std::time::SystemTime; /// use std::time::SystemTime;
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set(Date(SystemTime::now().into())); /// builder.set(Date(SystemTime::now().into()));
/// ``` /// ```
(Date, http::DATE) => [HttpDate] (Date, DATE) => [HttpDate]
test_date { test_date {
test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]);

View File

@ -1,4 +1,4 @@
use header::{http, EntityTag}; use header::{ETAG, EntityTag};
header! { header! {
/// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3)
@ -28,21 +28,21 @@ header! {
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::{ETag, EntityTag}; /// use actix_web::http::header::{ETag, EntityTag};
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned())));
/// ``` /// ```
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::{ETag, EntityTag}; /// use actix_web::http::header::{ETag, EntityTag};
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned())));
/// ``` /// ```
(ETag, http::ETAG) => [EntityTag] (ETag, ETAG) => [EntityTag]
test_etag { test_etag {
// From the RFC // From the RFC

View File

@ -1,4 +1,4 @@
use header::{http, HttpDate}; use header::{EXPIRES, HttpDate};
header! { header! {
/// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3)
@ -22,15 +22,15 @@ header! {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::Expires; /// use actix_web::http::header::Expires;
/// use std::time::{SystemTime, Duration}; /// use std::time::{SystemTime, Duration};
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24);
/// builder.set(Expires(expiration.into())); /// builder.set(Expires(expiration.into()));
/// ``` /// ```
(Expires, http::EXPIRES) => [HttpDate] (Expires, EXPIRES) => [HttpDate]
test_expires { test_expires {
// Test case from RFC // Test case from RFC

View File

@ -1,4 +1,4 @@
use header::{http, EntityTag}; use header::{IF_MATCH, EntityTag};
header! { header! {
/// `If-Match` header, defined in /// `If-Match` header, defined in
@ -30,18 +30,18 @@ header! {
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::IfMatch; /// use actix_web::http::header::IfMatch;
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set(IfMatch::Any); /// builder.set(IfMatch::Any);
/// ``` /// ```
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::{IfMatch, EntityTag}; /// use actix_web::http::header::{IfMatch, EntityTag};
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// IfMatch::Items(vec![ /// IfMatch::Items(vec![
/// EntityTag::new(false, "xyzzy".to_owned()), /// EntityTag::new(false, "xyzzy".to_owned()),
@ -50,7 +50,7 @@ header! {
/// ]) /// ])
/// ); /// );
/// ``` /// ```
(IfMatch, http::IF_MATCH) => {Any / (EntityTag)+} (IfMatch, IF_MATCH) => {Any / (EntityTag)+}
test_if_match { test_if_match {
test_header!( test_header!(

View File

@ -1,4 +1,4 @@
use header::{http, HttpDate}; use header::{IF_MODIFIED_SINCE, HttpDate};
header! { header! {
/// `If-Modified-Since` header, defined in /// `If-Modified-Since` header, defined in
@ -22,15 +22,15 @@ header! {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::IfModifiedSince; /// use actix_web::http::header::IfModifiedSince;
/// use std::time::{SystemTime, Duration}; /// use std::time::{SystemTime, Duration};
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(IfModifiedSince(modified.into())); /// builder.set(IfModifiedSince(modified.into()));
/// ``` /// ```
(IfModifiedSince, http::IF_MODIFIED_SINCE) => [HttpDate] (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate]
test_if_modified_since { test_if_modified_since {
// Test case from RFC // Test case from RFC

View File

@ -1,4 +1,4 @@
use header::{http, EntityTag}; use header::{IF_NONE_MATCH, EntityTag};
header! { header! {
/// `If-None-Match` header, defined in /// `If-None-Match` header, defined in
@ -32,18 +32,18 @@ header! {
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::IfNoneMatch; /// use actix_web::http::header::IfNoneMatch;
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set(IfNoneMatch::Any); /// builder.set(IfNoneMatch::Any);
/// ``` /// ```
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::{IfNoneMatch, EntityTag}; /// use actix_web::http::header::{IfNoneMatch, EntityTag};
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set( /// builder.set(
/// IfNoneMatch::Items(vec![ /// IfNoneMatch::Items(vec![
/// EntityTag::new(false, "xyzzy".to_owned()), /// EntityTag::new(false, "xyzzy".to_owned()),
@ -52,7 +52,7 @@ header! {
/// ]) /// ])
/// ); /// );
/// ``` /// ```
(IfNoneMatch, http::IF_NONE_MATCH) => {Any / (EntityTag)+} (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+}
test_if_none_match { test_if_none_match {
test_header!(test1, vec![b"\"xyzzy\""]); test_header!(test1, vec![b"\"xyzzy\""]);
@ -67,18 +67,18 @@ header! {
mod tests { mod tests {
use super::IfNoneMatch; use super::IfNoneMatch;
use test::TestRequest; use test::TestRequest;
use header::{http, Header, EntityTag}; use header::{IF_NONE_MATCH, Header, EntityTag};
#[test] #[test]
fn test_if_none_match() { fn test_if_none_match() {
let mut if_none_match: Result<IfNoneMatch, _>; let mut if_none_match: Result<IfNoneMatch, _>;
let req = TestRequest::with_header(http::IF_NONE_MATCH, "*").finish(); let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish();
if_none_match = Header::parse(&req); if_none_match = Header::parse(&req);
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any));
let req = TestRequest::with_header( let req = TestRequest::with_header(
http::IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]).finish(); IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]).finish();
if_none_match = Header::parse(&req); if_none_match = Header::parse(&req);
let mut entities: Vec<EntityTag> = Vec::new(); let mut entities: Vec<EntityTag> = Vec::new();

View File

@ -1,8 +1,10 @@
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
use error::ParseError; use error::ParseError;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use header::{http, from_one_raw_str}; use http::header;
use header::{IntoHeaderValue, Header, EntityTag, HttpDate, Writer}; use header::from_one_raw_str;
use header::{IntoHeaderValue, Header, HeaderName, HeaderValue,
EntityTag, HttpDate, Writer, InvalidHeaderValueBytes};
/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) /// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2)
/// ///
@ -33,19 +35,19 @@ use header::{IntoHeaderValue, Header, EntityTag, HttpDate, Writer};
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::{IfRange, EntityTag}; /// use actix_web::http::header::{IfRange, EntityTag};
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// builder.set(IfRange::EntityTag(EntityTag::new(false, "xyzzy".to_owned()))); /// builder.set(IfRange::EntityTag(EntityTag::new(false, "xyzzy".to_owned())));
/// ``` /// ```
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::IfRange; /// use actix_web::http::header::IfRange;
/// use std::time::{SystemTime, Duration}; /// use std::time::{SystemTime, Duration};
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(IfRange::Date(fetched.into())); /// builder.set(IfRange::Date(fetched.into()));
/// ``` /// ```
@ -58,17 +60,19 @@ pub enum IfRange {
} }
impl Header for IfRange { impl Header for IfRange {
fn name() -> http::HeaderName { fn name() -> HeaderName {
http::IF_RANGE header::IF_RANGE
} }
#[inline] #[inline]
fn parse<T>(msg: &T) -> Result<Self, ParseError> where T: HttpMessage fn parse<T>(msg: &T) -> Result<Self, ParseError> where T: HttpMessage
{ {
let etag: Result<EntityTag, _> = from_one_raw_str(msg.headers().get(http::IF_RANGE)); let etag: Result<EntityTag, _> =
from_one_raw_str(msg.headers().get(header::IF_RANGE));
if let Ok(etag) = etag { if let Ok(etag) = etag {
return Ok(IfRange::EntityTag(etag)); return Ok(IfRange::EntityTag(etag));
} }
let date: Result<HttpDate, _> = from_one_raw_str(msg.headers().get(http::IF_RANGE)); let date: Result<HttpDate, _> =
from_one_raw_str(msg.headers().get(header::IF_RANGE));
if let Ok(date) = date { if let Ok(date) = date {
return Ok(IfRange::Date(date)); return Ok(IfRange::Date(date));
} }
@ -86,12 +90,12 @@ impl Display for IfRange {
} }
impl IntoHeaderValue for IfRange { impl IntoHeaderValue for IfRange {
type Error = http::InvalidHeaderValueBytes; type Error = InvalidHeaderValueBytes;
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
http::HeaderValue::from_shared(writer.take()) HeaderValue::from_shared(writer.take())
} }
} }

View File

@ -1,4 +1,4 @@
use header::{http, HttpDate}; use header::{IF_UNMODIFIED_SINCE, HttpDate};
header! { header! {
/// `If-Unmodified-Since` header, defined in /// `If-Unmodified-Since` header, defined in
@ -23,15 +23,15 @@ header! {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::IfUnmodifiedSince; /// use actix_web::http::header::IfUnmodifiedSince;
/// use std::time::{SystemTime, Duration}; /// use std::time::{SystemTime, Duration};
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(IfUnmodifiedSince(modified.into())); /// builder.set(IfUnmodifiedSince(modified.into()));
/// ``` /// ```
(IfUnmodifiedSince, http::IF_UNMODIFIED_SINCE) => [HttpDate] (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate]
test_if_unmodified_since { test_if_unmodified_since {
// Test case from RFC // Test case from RFC

View File

@ -1,4 +1,4 @@
use header::{http, HttpDate}; use header::{LAST_MODIFIED, HttpDate};
header! { header! {
/// `Last-Modified` header, defined in /// `Last-Modified` header, defined in
@ -22,15 +22,15 @@ header! {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use actix_web::httpcodes; /// use actix_web::HttpResponse;
/// use actix_web::header::LastModified; /// use actix_web::http::header::LastModified;
/// use std::time::{SystemTime, Duration}; /// use std::time::{SystemTime, Duration};
/// ///
/// let mut builder = httpcodes::HttpOk.build(); /// let mut builder = HttpResponse::Ok();
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// builder.set(LastModified(modified.into())); /// builder.set(LastModified(modified.into()));
/// ``` /// ```
(LastModified, http::LAST_MODIFIED) => [HttpDate] (LastModified, LAST_MODIFIED) => [HttpDate]
test_last_modified { test_last_modified {
// Test case from RFC // Test case from RFC

View File

@ -73,7 +73,7 @@ macro_rules! test_header {
($id:ident, $raw:expr) => { ($id:ident, $raw:expr) => {
#[test] #[test]
fn $id() { fn $id() {
#[allow(unused)] #[allow(unused, deprecated)]
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use test; use test;
let raw = $raw; let raw = $raw;
@ -144,7 +144,7 @@ macro_rules! header {
__hyper__deref!($id => Vec<$item>); __hyper__deref!($id => Vec<$item>);
impl $crate::header::Header for $id { impl $crate::header::Header for $id {
#[inline] #[inline]
fn name() -> $crate::header::http::HeaderName { fn name() -> $crate::header::HeaderName {
$name $name
} }
#[inline] #[inline]
@ -162,13 +162,13 @@ macro_rules! header {
} }
} }
impl $crate::header::IntoHeaderValue for $id { impl $crate::header::IntoHeaderValue for $id {
type Error = $crate::header::http::InvalidHeaderValueBytes; type Error = $crate::header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<$crate::header::http::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
use std::fmt::Write; use std::fmt::Write;
let mut writer = $crate::header::Writer::new(); let mut writer = $crate::header::Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
$crate::header::http::HeaderValue::from_shared(writer.take()) $crate::header::HeaderValue::from_shared(writer.take())
} }
} }
}; };
@ -180,7 +180,7 @@ macro_rules! header {
__hyper__deref!($id => Vec<$item>); __hyper__deref!($id => Vec<$item>);
impl $crate::header::Header for $id { impl $crate::header::Header for $id {
#[inline] #[inline]
fn name() -> $crate::header::http::HeaderName { fn name() -> $crate::header::HeaderName {
$name $name
} }
#[inline] #[inline]
@ -198,13 +198,13 @@ macro_rules! header {
} }
} }
impl $crate::header::IntoHeaderValue for $id { impl $crate::header::IntoHeaderValue for $id {
type Error = $crate::header::http::InvalidHeaderValueBytes; type Error = $crate::header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<$crate::header::http::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
use std::fmt::Write; use std::fmt::Write;
let mut writer = $crate::header::Writer::new(); let mut writer = $crate::header::Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
$crate::header::http::HeaderValue::from_shared(writer.take()) $crate::header::HeaderValue::from_shared(writer.take())
} }
} }
}; };
@ -216,7 +216,7 @@ macro_rules! header {
__hyper__deref!($id => $value); __hyper__deref!($id => $value);
impl $crate::header::Header for $id { impl $crate::header::Header for $id {
#[inline] #[inline]
fn name() -> $crate::header::http::HeaderName { fn name() -> $crate::header::HeaderName {
$name $name
} }
#[inline] #[inline]
@ -234,9 +234,9 @@ macro_rules! header {
} }
} }
impl $crate::header::IntoHeaderValue for $id { impl $crate::header::IntoHeaderValue for $id {
type Error = $crate::header::http::InvalidHeaderValueBytes; type Error = $crate::header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<$crate::header::http::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
self.0.try_into() self.0.try_into()
} }
} }
@ -253,12 +253,12 @@ macro_rules! header {
} }
impl $crate::header::Header for $id { impl $crate::header::Header for $id {
#[inline] #[inline]
fn name() -> $crate::header::http::HeaderName { fn name() -> $crate::header::HeaderName {
$name $name
} }
#[inline] #[inline]
fn parse<T>(msg: &T) -> Result<Self, $crate::error::ParseError> fn parse<T>(msg: &T) -> Result<Self, $crate::error::ParseError>
where T: $crate::header::HttpMessage where T: $crate::HttpMessage
{ {
let any = msg.headers().get(Self::name()).and_then(|hdr| { let any = msg.headers().get(Self::name()).and_then(|hdr| {
hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))});
@ -283,13 +283,13 @@ macro_rules! header {
} }
} }
impl $crate::header::IntoHeaderValue for $id { impl $crate::header::IntoHeaderValue for $id {
type Error = $crate::header::http::InvalidHeaderValueBytes; type Error = $crate::header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<$crate::header::http::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> {
use std::fmt::Write; use std::fmt::Write;
let mut writer = $crate::header::Writer::new(); let mut writer = $crate::header::Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
$crate::header::http::HeaderValue::from_shared(writer.take()) $crate::header::HeaderValue::from_shared(writer.take())
} }
} }
}; };

View File

@ -5,21 +5,14 @@ use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use http::{Error as HttpError}; use modhttp::{Error as HttpError};
use http::header::GetAll; use modhttp::header::GetAll;
use mime::Mime; use mime::Mime;
pub use cookie::{Cookie, CookieBuilder}; pub use modhttp::header::*;
pub use http_range::HttpRange;
#[doc(hidden)]
pub mod http {
pub use http::header::*;
}
use error::ParseError; use error::ParseError;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
pub use httpresponse::ConnectionType;
mod common; mod common;
mod shared; mod shared;
@ -34,7 +27,7 @@ pub use self::shared::*;
pub trait Header where Self: IntoHeaderValue { pub trait Header where Self: IntoHeaderValue {
/// Returns the name of the header field /// Returns the name of the header field
fn name() -> http::HeaderName; fn name() -> HeaderName;
/// Parse a header /// Parse a header
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>; fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
@ -47,69 +40,69 @@ pub trait IntoHeaderValue: Sized {
type Error: Into<HttpError>; type Error: Into<HttpError>;
/// Cast from PyObject to a concrete Python object type. /// Cast from PyObject to a concrete Python object type.
fn try_into(self) -> Result<http::HeaderValue, Self::Error>; fn try_into(self) -> Result<HeaderValue, Self::Error>;
} }
impl IntoHeaderValue for http::HeaderValue { impl IntoHeaderValue for HeaderValue {
type Error = http::InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
Ok(self) Ok(self)
} }
} }
impl<'a> IntoHeaderValue for &'a str { impl<'a> IntoHeaderValue for &'a str {
type Error = http::InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
self.parse() self.parse()
} }
} }
impl<'a> IntoHeaderValue for &'a [u8] { impl<'a> IntoHeaderValue for &'a [u8] {
type Error = http::InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
http::HeaderValue::from_bytes(self) HeaderValue::from_bytes(self)
} }
} }
impl IntoHeaderValue for Bytes { impl IntoHeaderValue for Bytes {
type Error = http::InvalidHeaderValueBytes; type Error = InvalidHeaderValueBytes;
#[inline] #[inline]
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
http::HeaderValue::from_shared(self) HeaderValue::from_shared(self)
} }
} }
impl IntoHeaderValue for Vec<u8> { impl IntoHeaderValue for Vec<u8> {
type Error = http::InvalidHeaderValueBytes; type Error = InvalidHeaderValueBytes;
#[inline] #[inline]
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
http::HeaderValue::from_shared(Bytes::from(self)) HeaderValue::from_shared(Bytes::from(self))
} }
} }
impl IntoHeaderValue for String { impl IntoHeaderValue for String {
type Error = http::InvalidHeaderValueBytes; type Error = InvalidHeaderValueBytes;
#[inline] #[inline]
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
http::HeaderValue::from_shared(Bytes::from(self)) HeaderValue::from_shared(Bytes::from(self))
} }
} }
impl IntoHeaderValue for Mime { impl IntoHeaderValue for Mime {
type Error = http::InvalidHeaderValueBytes; type Error = InvalidHeaderValueBytes;
#[inline] #[inline]
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
http::HeaderValue::from_shared(Bytes::from(format!("{}", self))) HeaderValue::from_shared(Bytes::from(format!("{}", self)))
} }
} }
@ -119,6 +112,7 @@ pub enum ContentEncoding {
/// Automatically select encoding based on encoding negotiation /// Automatically select encoding based on encoding negotiation
Auto, Auto,
/// A format using the Brotli algorithm /// A format using the Brotli algorithm
#[cfg(feature="brotli")]
Br, Br,
/// A format using the zlib structure with deflate algorithm /// A format using the zlib structure with deflate algorithm
Deflate, Deflate,
@ -141,15 +135,19 @@ impl ContentEncoding {
#[inline] #[inline]
pub fn as_str(&self) -> &'static str { pub fn as_str(&self) -> &'static str {
match *self { match *self {
#[cfg(feature="brotli")]
ContentEncoding::Br => "br", ContentEncoding::Br => "br",
ContentEncoding::Gzip => "gzip", ContentEncoding::Gzip => "gzip",
ContentEncoding::Deflate => "deflate", ContentEncoding::Deflate => "deflate",
ContentEncoding::Identity | ContentEncoding::Auto => "identity", ContentEncoding::Identity | ContentEncoding::Auto => "identity",
} }
} }
#[inline]
/// default quality value /// default quality value
pub fn quality(&self) -> f64 { pub fn quality(&self) -> f64 {
match *self { match *self {
#[cfg(feature="brotli")]
ContentEncoding::Br => 1.1, ContentEncoding::Br => 1.1,
ContentEncoding::Gzip => 1.0, ContentEncoding::Gzip => 1.0,
ContentEncoding::Deflate => 0.9, ContentEncoding::Deflate => 0.9,
@ -162,6 +160,7 @@ impl ContentEncoding {
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 s.trim().to_lowercase().as_ref() {
#[cfg(feature="brotli")]
"br" => ContentEncoding::Br, "br" => ContentEncoding::Br,
"gzip" => ContentEncoding::Gzip, "gzip" => ContentEncoding::Gzip,
"deflate" => ContentEncoding::Deflate, "deflate" => ContentEncoding::Deflate,
@ -200,7 +199,7 @@ impl fmt::Write for Writer {
#[inline] #[inline]
#[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>(all: GetAll<http::HeaderValue>) pub fn from_comma_delimited<T: FromStr>(all: GetAll<HeaderValue>)
-> Result<Vec<T>, ParseError> -> Result<Vec<T>, ParseError>
{ {
let mut result = Vec::new(); let mut result = Vec::new();
@ -219,7 +218,7 @@ pub fn from_comma_delimited<T: FromStr>(all: GetAll<http::HeaderValue>)
#[inline] #[inline]
#[doc(hidden)] #[doc(hidden)]
/// Reads a single string when parsing a header. /// Reads a single string when parsing a header.
pub fn from_one_raw_str<T: FromStr>(val: Option<&http::HeaderValue>) pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>)
-> Result<T, ParseError> -> Result<T, ParseError>
{ {
if let Some(line) = val { if let Some(line) = val {

View File

@ -1,4 +1,4 @@
#![allow(unused)] #![allow(unused, deprecated)]
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::str::FromStr; use std::str::FromStr;
use std::ascii::AsciiExt; use std::ascii::AsciiExt;

View File

@ -1,6 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
use header::{http, Writer, IntoHeaderValue}; use header::{HeaderValue, Writer, IntoHeaderValue, InvalidHeaderValueBytes};
/// check that each char in the slice is either: /// check that each char in the slice is either:
/// 1. `%x21`, or /// 1. `%x21`, or
@ -144,12 +144,12 @@ impl FromStr for EntityTag {
} }
impl IntoHeaderValue for EntityTag { impl IntoHeaderValue for EntityTag {
type Error = http::InvalidHeaderValueBytes; type Error = InvalidHeaderValueBytes;
fn try_into(self) -> Result<http::HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = Writer::new(); let mut wrt = Writer::new();
write!(wrt, "{}", self).unwrap(); write!(wrt, "{}", self).unwrap();
unsafe{Ok(http::HeaderValue::from_shared_unchecked(wrt.take()))} unsafe{Ok(HeaderValue::from_shared_unchecked(wrt.take()))}
} }
} }

View File

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

View File

@ -1,340 +1,321 @@
use std::{str, mem, ptr, slice}; //! Various helpers
use std::cell::RefCell;
use std::fmt::{self, Write};
use std::rc::Rc;
use std::ops::{Deref, DerefMut};
use std::collections::VecDeque;
use time;
use bytes::{BufMut, BytesMut};
use http::Version;
use httprequest::HttpInnerMessage; use regex::Regex;
use http::{header, StatusCode};
// "Sun, 06 Nov 1994 08:49:37 GMT".len() use handler::Handler;
pub(crate) const DATE_VALUE_LENGTH: usize = 29; use httprequest::HttpRequest;
use httpresponse::HttpResponse;
pub(crate) fn date(dst: &mut BytesMut) { /// Path normalization helper
CACHED.with(|cache| { ///
let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; /// By normalizing it means:
buf[..6].copy_from_slice(b"date: "); ///
buf[6..35].copy_from_slice(cache.borrow().buffer()); /// - Add a trailing slash to the path.
buf[35..].copy_from_slice(b"\r\n\r\n"); /// - Remove a trailing slash from the path.
dst.extend_from_slice(&buf); /// - Double slashes are replaced by one.
}) ///
/// The handler returns as soon as it finds a path that resolves
/// correctly. The order if all enable is 1) merge, 3) both merge and append
/// and 3) append. If the path resolves with
/// at least one of those conditions, it will redirect to the new path.
///
/// If *append* is *true* append slash when needed. If a resource is
/// defined with trailing slash and the request comes without it, it will
/// append it automatically.
///
/// If *merge* is *true*, merge multiple consecutive slashes in the path into one.
///
/// This handler designed to be use as a handler for application's *default resource*.
///
/// ```rust
/// # extern crate actix_web;
/// # #[macro_use] extern crate serde_derive;
/// # use actix_web::*;
/// use actix_web::http::NormalizePath;
///
/// # fn index(req: HttpRequest) -> HttpResponse {
/// # HttpResponse::Ok().into()
/// # }
/// fn main() {
/// let app = App::new()
/// .resource("/test/", |r| r.f(index))
/// .default_resource(|r| r.h(NormalizePath::default()))
/// .finish();
/// }
/// ```
/// In this example `/test`, `/test///` will be redirected to `/test/` url.
pub struct NormalizePath {
append: bool,
merge: bool,
re_merge: Regex,
redirect: StatusCode,
not_found: StatusCode,
} }
pub(crate) fn date_value(dst: &mut BytesMut) { impl Default for NormalizePath {
CACHED.with(|cache| { /// Create default `NormalizePath` instance, *append* is set to *true*,
dst.extend_from_slice(cache.borrow().buffer()); /// *merge* is set to *true* and *redirect* is set to `StatusCode::MOVED_PERMANENTLY`
}) fn default() -> NormalizePath {
NormalizePath {
append: true,
merge: true,
re_merge: Regex::new("//+").unwrap(),
redirect: StatusCode::MOVED_PERMANENTLY,
not_found: StatusCode::NOT_FOUND,
} }
pub(crate) fn update_date() {
CACHED.with(|cache| {
cache.borrow_mut().update();
});
}
struct CachedDate {
bytes: [u8; DATE_VALUE_LENGTH],
pos: usize,
}
thread_local!(static CACHED: RefCell<CachedDate> = RefCell::new(CachedDate {
bytes: [0; DATE_VALUE_LENGTH],
pos: 0,
}));
impl CachedDate {
fn buffer(&self) -> &[u8] {
&self.bytes[..]
}
fn update(&mut self) {
self.pos = 0;
write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap();
assert_eq!(self.pos, DATE_VALUE_LENGTH);
} }
} }
impl fmt::Write for CachedDate { impl NormalizePath {
fn write_str(&mut self, s: &str) -> fmt::Result { /// Create new `NormalizePath` instance
let len = s.len(); pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath {
self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); NormalizePath {
self.pos += len; append,
Ok(()) merge,
redirect,
re_merge: Regex::new("//+").unwrap(),
not_found: StatusCode::NOT_FOUND,
}
} }
} }
/// Internal use only! unsafe impl<S> Handler<S> for NormalizePath {
pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpInnerMessage>>>); type Result = HttpResponse;
impl SharedMessagePool { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
pub fn new() -> SharedMessagePool { if let Some(router) = req.router() {
SharedMessagePool(RefCell::new(VecDeque::with_capacity(128))) let query = req.query_string();
if self.merge {
// merge slashes
let p = self.re_merge.replace_all(req.path(), "/");
if p.len() != req.path().len() {
if router.has_route(p.as_ref()) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref())
.finish();
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish()
}
} }
#[inline] // try to remove trailing slash
pub fn get(&self) -> Rc<HttpInnerMessage> { if p.ends_with('/') {
if let Some(msg) = self.0.borrow_mut().pop_front() { let p = p.as_ref().trim_right_matches('/');
msg if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str())
} else { } else {
Rc::new(HttpInnerMessage::default()) req.header(header::LOCATION, p)
}
.finish();
} }
} }
} else if p.ends_with('/') {
#[inline] // try to remove trailing slash
pub fn release(&self, mut msg: Rc<HttpInnerMessage>) { let p = p.as_ref().trim_right_matches('/');
let v = &mut self.0.borrow_mut(); if router.has_route(p) {
if v.len() < 128 { let mut req = HttpResponse::build(self.redirect);
Rc::get_mut(&mut msg).unwrap().reset(); return if !query.is_empty() {
v.push_front(msg); req.header(header::LOCATION,
} (p.to_owned() + "?" + query).as_str())
}
}
pub(crate) struct SharedHttpInnerMessage(
Option<Rc<HttpInnerMessage>>, Option<Rc<SharedMessagePool>>);
impl Drop for SharedHttpInnerMessage {
fn drop(&mut self) {
if let Some(ref pool) = self.1 {
if let Some(msg) = self.0.take() {
if Rc::strong_count(&msg) == 1 {
pool.release(msg);
}
}
}
}
}
impl Deref for SharedHttpInnerMessage {
type Target = HttpInnerMessage;
fn deref(&self) -> &HttpInnerMessage {
self.get_ref()
}
}
impl DerefMut for SharedHttpInnerMessage {
fn deref_mut(&mut self) -> &mut HttpInnerMessage {
self.get_mut()
}
}
impl Clone for SharedHttpInnerMessage {
fn clone(&self) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(self.0.clone(), self.1.clone())
}
}
impl Default for SharedHttpInnerMessage {
fn default() -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(Rc::new(HttpInnerMessage::default())), None)
}
}
impl SharedHttpInnerMessage {
pub fn from_message(msg: HttpInnerMessage) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(Rc::new(msg)), None)
}
pub fn new(msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(msg), Some(pool))
}
#[inline(always)]
#[allow(mutable_transmutes)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub fn get_mut(&self) -> &mut HttpInnerMessage {
let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref();
unsafe{mem::transmute(r)}
}
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
pub fn get_ref(&self) -> &HttpInnerMessage {
self.0.as_ref().unwrap()
}
}
const DEC_DIGITS_LUT: &[u8] =
b"0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
let mut buf: [u8; 13] = [b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1',
b' ', b' ', b' ', b' ', b' '];
match version {
Version::HTTP_2 => buf[5] = b'2',
Version::HTTP_10 => buf[7] = b'0',
Version::HTTP_09 => {buf[5] = b'0'; buf[7] = b'9';},
_ => (),
}
let mut curr: isize = 12;
let buf_ptr = buf.as_mut_ptr();
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
let four = n > 999;
unsafe {
// decode 2 more chars, if > 2 chars
let d1 = (n % 100) << 1;
n /= 100;
curr -= 2;
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
// decode last 1 or 2 chars
if n < 10 {
curr -= 1;
*buf_ptr.offset(curr) = (n as u8) + b'0';
} else { } else {
let d1 = n << 1; req.header(header::LOCATION, p)
curr -= 2; }
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2); .finish();
} }
} }
}
bytes.extend_from_slice(&buf); // append trailing slash
if four { if self.append && !req.path().ends_with('/') {
bytes.put(b' '); let p = req.path().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
} }
} }
pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
if n < 10 {
let mut buf: [u8; 21] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e',
b'n',b't',b'-',b'l',b'e',b'n',b'g',
b't',b'h',b':',b' ',b'0',b'\r',b'\n'];
buf[18] = (n as u8) + b'0';
bytes.extend_from_slice(&buf);
} else if n < 100 {
let mut buf: [u8; 22] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e',
b'n',b't',b'-',b'l',b'e',b'n',b'g',
b't',b'h',b':',b' ',b'0',b'0',b'\r',b'\n'];
let d1 = n << 1;
unsafe {
ptr::copy_nonoverlapping(
DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(18), 2);
} }
bytes.extend_from_slice(&buf); HttpResponse::new(self.not_found)
} else if n < 1000 {
let mut buf: [u8; 23] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e',
b'n',b't',b'-',b'l',b'e',b'n',b'g',
b't',b'h',b':',b' ',b'0',b'0',b'0',b'\r',b'\n'];
// decode 2 more chars, if > 2 chars
let d1 = (n % 100) << 1;
n /= 100;
unsafe {ptr::copy_nonoverlapping(
DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(19), 2)};
// decode last 1
buf[18] = (n as u8) + b'0';
bytes.extend_from_slice(&buf);
} else {
bytes.extend_from_slice(b"\r\ncontent-length: ");
convert_usize(n, bytes);
} }
} }
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
let mut curr: isize = 39;
let mut buf: [u8; 41] = unsafe { mem::uninitialized() };
buf[39] = b'\r';
buf[40] = b'\n';
let buf_ptr = buf.as_mut_ptr();
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
unsafe {
// eagerly decode 4 characters at a time
while n >= 10_000 {
let rem = (n % 10_000) as isize;
n /= 10_000;
let d1 = (rem / 100) << 1;
let d2 = (rem % 100) << 1;
curr -= 4;
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2);
}
// if we reach here numbers are <= 9999, so at most 4 chars long
let mut n = n as isize; // possibly reduce 64bit math
// decode 2 more chars, if > 2 chars
if n >= 100 {
let d1 = (n % 100) << 1;
n /= 100;
curr -= 2;
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
// decode last 1 or 2 chars
if n < 10 {
curr -= 1;
*buf_ptr.offset(curr) = (n as u8) + b'0';
} else {
let d1 = n << 1;
curr -= 2;
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
}
unsafe {
bytes.extend_from_slice(
slice::from_raw_parts(buf_ptr.offset(curr), 41 - curr as usize));
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use http::{header, Method};
use test::TestRequest;
use application::App;
#[test] fn index(_req: HttpRequest) -> HttpResponse {
fn test_date_len() { HttpResponse::new(StatusCode::OK)
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
} }
#[test] #[test]
fn test_date() { fn test_normalize_path_trailing_slashes() {
let mut buf1 = BytesMut::new(); let mut app = App::new()
date(&mut buf1); .resource("/resource1", |r| r.method(Method::GET).f(index))
let mut buf2 = BytesMut::new(); .resource("/resource2/", |r| r.method(Method::GET).f(index))
date(&mut buf2); .default_resource(|r| r.h(NormalizePath::default()))
assert_eq!(buf1, buf2); .finish();
// trailing slashes
let params =
vec![("/resource1", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
("/resource2/", "", StatusCode::OK),
("/resource1?p1=1&p2=2", "", StatusCode::OK),
("/resource1/?p1=1&p2=2", "/resource1?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY),
("/resource2/?p1=1&p2=2", "", StatusCode::OK)
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
} }
#[test] #[test]
fn test_write_content_length() { fn test_normalize_path_trailing_slashes_disabled() {
let mut bytes = BytesMut::new(); let mut app = App::new()
write_content_length(0, &mut bytes); .resource("/resource1", |r| r.method(Method::GET).f(index))
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]); .resource("/resource2/", |r| r.method(Method::GET).f(index))
write_content_length(9, &mut bytes); .default_resource(|r| r.h(
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]); NormalizePath::new(false, true, StatusCode::MOVED_PERMANENTLY)))
write_content_length(10, &mut bytes); .finish();
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
write_content_length(99, &mut bytes); // trailing slashes
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]); let params = vec![("/resource1", StatusCode::OK),
write_content_length(100, &mut bytes); ("/resource1/", StatusCode::MOVED_PERMANENTLY),
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]); ("/resource2", StatusCode::NOT_FOUND),
write_content_length(101, &mut bytes); ("/resource2/", StatusCode::OK),
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]); ("/resource1?p1=1&p2=2", StatusCode::OK),
write_content_length(998, &mut bytes); ("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]); ("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND),
write_content_length(1000, &mut bytes); ("/resource2/?p1=1&p2=2", StatusCode::OK)
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); ];
write_content_length(1001, &mut bytes); for (path, code) in params {
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); let req = app.prepare_request(TestRequest::with_uri(path).finish());
write_content_length(5909, &mut bytes); let resp = app.run(req);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
}
}
#[test]
fn test_normalize_path_merge_slashes() {
let mut app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b//", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK),
("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
#[test]
fn test_normalize_path_merge_and_append_slashes() {
let mut app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.resource("/resource2/a/b/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("/resource1/a/b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b//", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b/", "", StatusCode::OK),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK),
("/resource1/a/b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target, r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
} }
} }

View File

@ -1,183 +1,130 @@
//! Basic http responses //! Basic http responses
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals, deprecated)]
use http::{StatusCode, Error as HttpError}; use http::StatusCode;
use body::Body; use body::Body;
use error::Error;
use handler::{Reply, Handler, RouteHandler, Responder}; use handler::{Reply, Handler, RouteHandler, Responder};
use httprequest::HttpRequest; 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); 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); 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); pub const HttpAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED);
#[deprecated(since="0.5.0",
note="please use `HttpResponse::pNonAuthoritativeInformation()` instead")]
pub const HttpNonAuthoritativeInformation: StaticResponse = pub const HttpNonAuthoritativeInformation: StaticResponse =
StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION); StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION);
#[deprecated(since="0.5.0", note="please use `HttpResponse::NoContent()` instead")]
pub const HttpNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); pub const HttpUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY);
#[deprecated(since="0.5.0", note="please use `HttpResponse::TemporaryRedirect()` instead")]
pub const HttpTemporaryRedirect: StaticResponse = pub const HttpTemporaryRedirect: StaticResponse =
StaticResponse(StatusCode::TEMPORARY_REDIRECT); StaticResponse(StatusCode::TEMPORARY_REDIRECT);
#[deprecated(since="0.5.0", note="please use `HttpResponse::PermanentRedirect()` instead")]
pub const HttpPermanentRedirect: StaticResponse = pub const HttpPermanentRedirect: StaticResponse =
StaticResponse(StatusCode::PERMANENT_REDIRECT); StaticResponse(StatusCode::PERMANENT_REDIRECT);
#[deprecated(since="0.5.0", note="please use `HttpResponse::BadRequest()` instead")]
pub const HttpBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST); 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); 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); 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); 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); pub const HttpNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND);
#[deprecated(since="0.5.0", note="please use `HttpResponse::MethodNotAllowed()` instead")]
pub const HttpMethodNotAllowed: StaticResponse = pub const HttpMethodNotAllowed: StaticResponse =
StaticResponse(StatusCode::METHOD_NOT_ALLOWED); StaticResponse(StatusCode::METHOD_NOT_ALLOWED);
#[deprecated(since="0.5.0", note="please use `HttpResponse::NotAcceptable()` instead")]
pub const HttpNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE); pub const HttpNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE);
#[deprecated(since="0.5.0",
note="please use `HttpResponse::ProxyAuthenticationRequired()` instead")]
pub const HttpProxyAuthenticationRequired: StaticResponse = pub const HttpProxyAuthenticationRequired: StaticResponse =
StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED); StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED);
#[deprecated(since="0.5.0", note="please use `HttpResponse::RequestTimeout()` instead")]
pub const HttpRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT); 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); 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); 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); pub const HttpLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED);
#[deprecated(since="0.5.0", note="please use `HttpResponse::PreconditionFailed()` instead")]
pub const HttpPreconditionFailed: StaticResponse = pub const HttpPreconditionFailed: StaticResponse =
StaticResponse(StatusCode::PRECONDITION_FAILED); StaticResponse(StatusCode::PRECONDITION_FAILED);
#[deprecated(since="0.5.0", note="please use `HttpResponse::PayloadTooLarge()` instead")]
pub const HttpPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE); 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); pub const HttpUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG);
#[deprecated(since="0.5.0",
note="please use `HttpResponse::UnsupportedMediaType()` instead")]
pub const HttpUnsupportedMediaType: StaticResponse = pub const HttpUnsupportedMediaType: StaticResponse =
StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE); StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE);
#[deprecated(since="0.5.0",
note="please use `HttpResponse::RangeNotSatisfiable()` instead")]
pub const HttpRangeNotSatisfiable: StaticResponse = pub const HttpRangeNotSatisfiable: StaticResponse =
StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE); StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE);
#[deprecated(since="0.5.0", note="please use `HttpResponse::ExpectationFailed()` instead")]
pub const HttpExpectationFailed: StaticResponse = pub const HttpExpectationFailed: StaticResponse =
StaticResponse(StatusCode::EXPECTATION_FAILED); StaticResponse(StatusCode::EXPECTATION_FAILED);
#[deprecated(since="0.5.0",
note="please use `HttpResponse::InternalServerError()` instead")]
pub const HttpInternalServerError: StaticResponse = pub const HttpInternalServerError: StaticResponse =
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR); StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
#[deprecated(since="0.5.0", note="please use `HttpResponse::NotImplemented()` instead")]
pub const HttpNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED); 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); pub const HttpBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY);
#[deprecated(since="0.5.0", note="please use `HttpResponse::ServiceUnavailable()` instead")]
pub const HttpServiceUnavailable: StaticResponse = pub const HttpServiceUnavailable: StaticResponse =
StaticResponse(StatusCode::SERVICE_UNAVAILABLE); StaticResponse(StatusCode::SERVICE_UNAVAILABLE);
#[deprecated(since="0.5.0", note="please use `HttpResponse::GatewayTimeout()` instead")]
pub const HttpGatewayTimeout: StaticResponse = pub const HttpGatewayTimeout: StaticResponse =
StaticResponse(StatusCode::GATEWAY_TIMEOUT); StaticResponse(StatusCode::GATEWAY_TIMEOUT);
#[deprecated(since="0.5.0",
note="please use `HttpResponse::VersionNotSupported()` instead")]
pub const HttpVersionNotSupported: StaticResponse = pub const HttpVersionNotSupported: StaticResponse =
StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED); StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED);
#[deprecated(since="0.5.0",
note="please use `HttpResponse::VariantAlsoNegotiates()` instead")]
pub const HttpVariantAlsoNegotiates: StaticResponse = pub const HttpVariantAlsoNegotiates: StaticResponse =
StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES); StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES);
#[deprecated(since="0.5.0",
note="please use `HttpResponse::InsufficientStorage()` instead")]
pub const HttpInsufficientStorage: StaticResponse = pub const HttpInsufficientStorage: StaticResponse =
StaticResponse(StatusCode::INSUFFICIENT_STORAGE); StaticResponse(StatusCode::INSUFFICIENT_STORAGE);
#[deprecated(since="0.5.0", note="please use `HttpResponse::LoopDetected()` instead")]
pub const HttpLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED); pub const HttpLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED);
#[doc(hidden)]
pub const HTTPOk: StaticResponse = StaticResponse(StatusCode::OK);
#[doc(hidden)]
pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED);
#[doc(hidden)]
pub const HTTPAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED);
#[doc(hidden)]
pub const HTTPNonAuthoritativeInformation: StaticResponse =
StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION);
#[doc(hidden)]
pub const HTTPNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT);
#[doc(hidden)]
pub const HTTPResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT);
#[doc(hidden)]
pub const HTTPPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT);
#[doc(hidden)]
pub const HTTPMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS);
#[doc(hidden)]
pub const HTTPAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED);
#[doc(hidden)]
pub const HTTPMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES);
#[doc(hidden)]
pub const HTTPMovedPermanenty: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY);
#[doc(hidden)]
pub const HTTPFound: StaticResponse = StaticResponse(StatusCode::FOUND);
#[doc(hidden)]
pub const HTTPSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER);
#[doc(hidden)]
pub const HTTPNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED);
#[doc(hidden)]
pub const HTTPUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY);
#[doc(hidden)]
pub const HTTPTemporaryRedirect: StaticResponse =
StaticResponse(StatusCode::TEMPORARY_REDIRECT);
#[doc(hidden)]
pub const HTTPPermanentRedirect: StaticResponse =
StaticResponse(StatusCode::PERMANENT_REDIRECT);
#[doc(hidden)]
pub const HTTPBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST);
#[doc(hidden)]
pub const HTTPUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED);
#[doc(hidden)]
pub const HTTPPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED);
#[doc(hidden)]
pub const HTTPForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN);
#[doc(hidden)]
pub const HTTPNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND);
#[doc(hidden)]
pub const HTTPMethodNotAllowed: StaticResponse =
StaticResponse(StatusCode::METHOD_NOT_ALLOWED);
#[doc(hidden)]
pub const HTTPNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE);
#[doc(hidden)]
pub const HTTPProxyAuthenticationRequired: StaticResponse =
StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED);
#[doc(hidden)]
pub const HTTPRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT);
#[doc(hidden)]
pub const HTTPConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT);
#[doc(hidden)]
pub const HTTPGone: StaticResponse = StaticResponse(StatusCode::GONE);
#[doc(hidden)]
pub const HTTPLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED);
#[doc(hidden)]
pub const HTTPPreconditionFailed: StaticResponse =
StaticResponse(StatusCode::PRECONDITION_FAILED);
#[doc(hidden)]
pub const HTTPPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE);
#[doc(hidden)]
pub const HTTPUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG);
#[doc(hidden)]
pub const HTTPUnsupportedMediaType: StaticResponse =
StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE);
#[doc(hidden)]
pub const HTTPRangeNotSatisfiable: StaticResponse =
StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE);
#[doc(hidden)]
pub const HTTPExpectationFailed: StaticResponse =
StaticResponse(StatusCode::EXPECTATION_FAILED);
#[doc(hidden)]
pub const HTTPInternalServerError: StaticResponse =
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
#[doc(hidden)]
pub const HTTPNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED);
#[doc(hidden)]
pub const HTTPBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY);
#[doc(hidden)]
pub const HTTPServiceUnavailable: StaticResponse =
StaticResponse(StatusCode::SERVICE_UNAVAILABLE);
#[doc(hidden)]
pub const HTTPGatewayTimeout: StaticResponse =
StaticResponse(StatusCode::GATEWAY_TIMEOUT);
#[doc(hidden)]
pub const HTTPVersionNotSupported: StaticResponse =
StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED);
#[doc(hidden)]
pub const HTTPVariantAlsoNegotiates: StaticResponse =
StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES);
#[doc(hidden)]
pub const HTTPInsufficientStorage: StaticResponse =
StaticResponse(StatusCode::INSUFFICIENT_STORAGE);
#[doc(hidden)]
pub const HTTPLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED);
#[deprecated(since="0.5.0", note="please use `HttpResponse` instead")]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct StaticResponse(StatusCode); pub struct StaticResponse(StatusCode);
@ -185,13 +132,16 @@ impl StaticResponse {
pub fn build(&self) -> HttpResponseBuilder { pub fn build(&self) -> HttpResponseBuilder {
HttpResponse::build(self.0) 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 { pub fn with_reason(self, reason: &'static str) -> HttpResponse {
let mut resp = HttpResponse::new(self.0, Body::Empty); let mut resp = HttpResponse::new(self.0);
resp.set_reason(reason); resp.set_reason(reason);
resp resp
} }
pub fn with_body<B: Into<Body>>(self, body: B) -> HttpResponse { pub fn with_body<B: Into<Body>>(self, body: B) -> HttpResponse {
HttpResponse::new(self.0, body.into()) HttpResponse::with_body(self.0, body.into())
} }
} }
@ -199,34 +149,34 @@ impl<S> Handler<S> for StaticResponse {
type Result = HttpResponse; type Result = HttpResponse;
fn handle(&mut self, _: HttpRequest<S>) -> HttpResponse { fn handle(&mut self, _: HttpRequest<S>) -> HttpResponse {
HttpResponse::new(self.0, Body::Empty) HttpResponse::new(self.0)
} }
} }
impl<S> RouteHandler<S> for StaticResponse { impl<S> RouteHandler<S> for StaticResponse {
fn handle(&mut self, _: HttpRequest<S>) -> Reply { fn handle(&mut self, _: HttpRequest<S>) -> Reply {
Reply::response(HttpResponse::new(self.0, Body::Empty)) Reply::response(HttpResponse::new(self.0))
} }
} }
impl Responder for StaticResponse { impl Responder for StaticResponse {
type Item = HttpResponse; type Item = HttpResponse;
type Error = HttpError; type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> { fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
self.build().body(Body::Empty) Ok(self.build().finish())
} }
} }
impl From<StaticResponse> for HttpResponse { impl From<StaticResponse> for HttpResponse {
fn from(st: StaticResponse) -> Self { fn from(st: StaticResponse) -> Self {
HttpResponse::new(st.0, Body::Empty) HttpResponse::new(st.0)
} }
} }
impl From<StaticResponse> for Reply { impl From<StaticResponse> for Reply {
fn from(st: StaticResponse) -> Self { fn from(st: StaticResponse) -> Self {
HttpResponse::new(st.0, Body::Empty).into() HttpResponse::new(st.0).into()
} }
} }
@ -242,7 +192,14 @@ macro_rules! STATIC_RESP {
impl HttpResponse { impl HttpResponse {
STATIC_RESP!(Ok, StatusCode::OK); STATIC_RESP!(Ok, StatusCode::OK);
STATIC_RESP!(Created, StatusCode::CREATED); STATIC_RESP!(Created, StatusCode::CREATED);
STATIC_RESP!(Accepted, StatusCode::ACCEPTED);
STATIC_RESP!(NonAuthoritativeInformation, StatusCode::NON_AUTHORITATIVE_INFORMATION);
STATIC_RESP!(NoContent, StatusCode::NO_CONTENT); STATIC_RESP!(NoContent, StatusCode::NO_CONTENT);
STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT);
STATIC_RESP!(PartialContent, StatusCode::PARTIAL_CONTENT);
STATIC_RESP!(MultiStatus, StatusCode::MULTI_STATUS);
STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED);
STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY); STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY);
@ -258,7 +215,6 @@ impl HttpResponse {
STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED); STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED);
STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED);
STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN); STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN);
STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED);
STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE);
STATIC_RESP!(ProxyAuthenticationRequired, StatusCode::PROXY_AUTHENTICATION_REQUIRED); STATIC_RESP!(ProxyAuthenticationRequired, StatusCode::PROXY_AUTHENTICATION_REQUIRED);
@ -269,40 +225,50 @@ 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!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED);
STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY);
STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED);
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use http::StatusCode; use http::StatusCode;
use super::{HTTPOk, HTTPBadRequest, Body, HttpResponse}; use super::{HttpOk, HttpBadRequest, Body, HttpResponse};
#[test] #[test]
fn test_build() { fn test_build() {
let resp = HTTPOk.build().body(Body::Empty).unwrap(); let resp = HttpOk.build().body(Body::Empty);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test] #[test]
fn test_response() { fn test_response() {
let resp: HttpResponse = HTTPOk.into(); let resp: HttpResponse = HttpOk.into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test] #[test]
fn test_from() { fn test_from() {
let resp: HttpResponse = HTTPOk.into(); let resp: HttpResponse = HttpOk.into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test] #[test]
fn test_with_reason() { fn test_with_reason() {
let resp: HttpResponse = HTTPOk.into(); let resp: HttpResponse = HttpOk.into();
assert_eq!(resp.reason(), "OK"); assert_eq!(resp.reason(), "OK");
let resp = HTTPBadRequest.with_reason("test"); let resp = HttpBadRequest.with_reason("test");
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
assert_eq!(resp.reason(), "test"); assert_eq!(resp.reason(), "test");
} }

View File

@ -1,13 +1,13 @@
use std::str; use std::str;
use std::collections::HashMap;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{Future, Stream, Poll}; use futures::{Future, Stream, Poll};
use http_range::HttpRange; use http_range::HttpRange;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use mime::Mime; use mime::Mime;
use url::form_urlencoded; use serde_urlencoded;
use encoding::all::UTF_8; use encoding::all::UTF_8;
use encoding::EncodingRef; use encoding::EncodingRef;
use encoding::types::{Encoding, DecoderTrap};
use encoding::label::encoding_from_whatwg_label; use encoding::label::encoding_from_whatwg_label;
use http::{header, HeaderMap}; use http::{header, HeaderMap};
@ -125,7 +125,7 @@ pub trait HttpMessage {
/// .from_err() /// .from_err()
/// .and_then(|bytes: Bytes| { // <- complete body /// .and_then(|bytes: Bytes| { // <- complete body
/// println!("==== BODY ==== {:?}", bytes); /// println!("==== BODY ==== {:?}", bytes);
/// Ok(httpcodes::HttpOk.into()) /// Ok(HttpResponse::Ok().into())
/// }).responder() /// }).responder()
/// } /// }
/// # fn main() {} /// # fn main() {}
@ -136,9 +136,9 @@ pub trait HttpMessage {
MessageBody::new(self) MessageBody::new(self)
} }
/// Parse `application/x-www-form-urlencoded` encoded body. /// Parse `application/x-www-form-urlencoded` encoded request's body.
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>` which /// Return `UrlEncoded` future. Form can be deserialized to any type that implements
/// contains decoded parameters. /// `Deserialize` trait from *serde*.
/// ///
/// Returns error: /// Returns error:
/// ///
@ -151,21 +151,22 @@ pub trait HttpMessage {
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// use actix_web::*; /// # use futures::Future;
/// use futures::future::{Future, ok}; /// # use std::collections::HashMap;
/// use actix_web::{HttpMessage, HttpRequest, HttpResponse, FutureResponse};
/// ///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { /// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
/// req.urlencoded() // <- get UrlEncoded future /// Box::new(
/// req.urlencoded::<HashMap<String, String>>() // <- get UrlEncoded future
/// .from_err() /// .from_err()
/// .and_then(|params| { // <- url encoded parameters /// .and_then(|params| { // <- url encoded parameters
/// println!("==== BODY ==== {:?}", params); /// println!("==== BODY ==== {:?}", params);
/// ok(httpcodes::HttpOk.into()) /// Ok(HttpResponse::Ok().into())
/// }) /// }))
/// .responder()
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
fn urlencoded(self) -> UrlEncoded<Self> fn urlencoded<T: DeserializeOwned>(self) -> UrlEncoded<Self, T>
where Self: Stream<Item=Bytes, Error=PayloadError> + Sized where Self: Stream<Item=Bytes, Error=PayloadError> + Sized
{ {
UrlEncoded::new(self) UrlEncoded::new(self)
@ -198,7 +199,7 @@ pub trait HttpMessage {
/// .from_err() /// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value /// .and_then(|val: MyObj| { // <- deserialized value
/// println!("==== BODY ==== {:?}", val); /// println!("==== BODY ==== {:?}", val);
/// Ok(httpcodes::HttpOk.into()) /// Ok(HttpResponse::Ok().into())
/// }).responder() /// }).responder()
/// } /// }
/// # fn main() {} /// # fn main() {}
@ -240,7 +241,7 @@ pub trait HttpMessage {
/// } /// }
/// }) /// })
/// .finish() // <- Stream::finish() combinator from actix /// .finish() // <- Stream::finish() combinator from actix
/// .map(|_| httpcodes::HTTPOk.into()) /// .map(|_| HttpResponse::Ok().into())
/// .responder() /// .responder()
/// } /// }
/// # fn main() {} /// # fn main() {}
@ -321,14 +322,14 @@ impl<T> Future for MessageBody<T>
} }
/// Future that resolves to a parsed urlencoded values. /// Future that resolves to a parsed urlencoded values.
pub struct UrlEncoded<T> { pub struct UrlEncoded<T, U> {
req: Option<T>, req: Option<T>,
limit: usize, limit: usize,
fut: Option<Box<Future<Item=HashMap<String, String>, Error=UrlencodedError>>>, fut: Option<Box<Future<Item=U, Error=UrlencodedError>>>,
} }
impl<T> UrlEncoded<T> { impl<T, U> UrlEncoded<T, U> {
pub fn new(req: T) -> UrlEncoded<T> { pub fn new(req: T) -> UrlEncoded<T, U> {
UrlEncoded { UrlEncoded {
req: Some(req), req: Some(req),
limit: 262_144, limit: 262_144,
@ -343,10 +344,11 @@ impl<T> UrlEncoded<T> {
} }
} }
impl<T> Future for UrlEncoded<T> impl<T, U> Future for UrlEncoded<T, U>
where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static,
U: DeserializeOwned + 'static
{ {
type Item = HashMap<String, String>; type Item = U;
type Error = UrlencodedError; type Error = UrlencodedError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@ -385,13 +387,16 @@ impl<T> Future for UrlEncoded<T>
} }
}) })
.and_then(move |body| { .and_then(move |body| {
let mut m = HashMap::new(); let enc: *const Encoding = encoding as *const Encoding;
let parsed = form_urlencoded::parse_with_encoding( if enc == UTF_8 {
&body, Some(encoding), false).map_err(|_| UrlencodedError::Parse)?; serde_urlencoded::from_bytes::<U>(&body)
for (k, v) in parsed { .map_err(|_| UrlencodedError::Parse)
m.insert(k.into(), v.into()); } else {
let body = encoding.decode(&body, DecoderTrap::Strict)
.map_err(|_| UrlencodedError::Parse)?;
serde_urlencoded::from_str::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
} }
Ok(m)
}); });
self.fut = Some(Box::new(fut)); self.fut = Some(Box::new(fut));
} }
@ -410,7 +415,6 @@ mod tests {
use http::{Method, Version, Uri}; use http::{Method, Version, Uri};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use std::str::FromStr; use std::str::FromStr;
use std::iter::FromIterator;
use test::TestRequest; use test::TestRequest;
#[test] #[test]
@ -529,28 +533,37 @@ mod tests {
} }
} }
#[derive(Deserialize, Debug, PartialEq)]
struct Info {
hello: String,
}
#[test] #[test]
fn test_urlencoded_error() { fn test_urlencoded_error() {
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked); assert_eq!(req.urlencoded::<Info>()
.poll().err().unwrap(), UrlencodedError::Chunked);
let req = TestRequest::with_header( let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded") header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "xxxx") .header(header::CONTENT_LENGTH, "xxxx")
.finish(); .finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength); assert_eq!(req.urlencoded::<Info>()
.poll().err().unwrap(), UrlencodedError::UnknownLength);
let req = TestRequest::with_header( let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded") header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "1000000") .header(header::CONTENT_LENGTH, "1000000")
.finish(); .finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow); assert_eq!(req.urlencoded::<Info>()
.poll().err().unwrap(), UrlencodedError::Overflow);
let req = TestRequest::with_header( let req = TestRequest::with_header(
header::CONTENT_TYPE, "text/plain") header::CONTENT_TYPE, "text/plain")
.header(header::CONTENT_LENGTH, "10") .header(header::CONTENT_LENGTH, "10")
.finish(); .finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType); assert_eq!(req.urlencoded::<Info>()
.poll().err().unwrap(), UrlencodedError::ContentType);
} }
#[test] #[test]
@ -561,9 +574,8 @@ mod tests {
.finish(); .finish();
req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
let result = req.urlencoded().poll().ok().unwrap(); let result = req.urlencoded::<Info>().poll().ok().unwrap();
assert_eq!(result, Async::Ready( assert_eq!(result, Async::Ready(Info{hello: "world".to_owned()}));
HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())])));
let mut req = TestRequest::with_header( let mut req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8") header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8")
@ -572,8 +584,7 @@ mod tests {
req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
let result = req.urlencoded().poll().ok().unwrap(); let result = req.urlencoded().poll().ok().unwrap();
assert_eq!(result, Async::Ready( assert_eq!(result, Async::Ready(Info{hello: "world".to_owned()}));
HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())])));
} }
#[test] #[test]
@ -581,27 +592,27 @@ mod tests {
let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish();
match req.body().poll().err().unwrap() { match req.body().poll().err().unwrap() {
PayloadError::UnknownLength => (), PayloadError::UnknownLength => (),
_ => panic!("error"), _ => unreachable!("error"),
} }
let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish();
match req.body().poll().err().unwrap() { match req.body().poll().err().unwrap() {
PayloadError::Overflow => (), PayloadError::Overflow => (),
_ => panic!("error"), _ => unreachable!("error"),
} }
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(b"test")); req.payload_mut().unread_data(Bytes::from_static(b"test"));
match req.body().poll().ok().unwrap() { 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")),
_ => panic!("error"), _ => unreachable!("error"),
} }
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(b"11111111111111")); req.payload_mut().unread_data(Bytes::from_static(b"11111111111111"));
match req.body().limit(5).poll().err().unwrap() { match req.body().limit(5).poll().err().unwrap() {
PayloadError::Overflow => (), PayloadError::Overflow => (),
_ => panic!("error"), _ => unreachable!("error"),
} }
} }
} }

View File

@ -2,22 +2,28 @@
use std::{io, cmp, str, fmt, mem}; use std::{io, cmp, str, fmt, mem};
use std::rc::Rc; use std::rc::Rc;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::borrow::Cow;
use bytes::Bytes; use bytes::Bytes;
use cookie::Cookie; use cookie::Cookie;
use futures::{Async, Stream, Poll}; use futures::{Async, Stream, Poll};
use futures::future::{FutureResult, result};
use futures_cpupool::CpuPool; use futures_cpupool::CpuPool;
use failure; use failure;
use url::{Url, form_urlencoded}; use url::{Url, form_urlencoded};
use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use http::{header, Uri, Method, Version, HeaderMap, Extensions, StatusCode};
use tokio_io::AsyncRead; use tokio_io::AsyncRead;
use percent_encoding::percent_decode;
use body::Body;
use info::ConnectionInfo; use info::ConnectionInfo;
use param::Params; use param::Params;
use router::Router; use router::{Router, Resource};
use payload::Payload; use payload::Payload;
use handler::FromRequest;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use helpers::SharedHttpInnerMessage; use httpresponse::{HttpResponse, HttpResponseBuilder};
use error::{UrlGenerationError, CookieParseError, PayloadError}; use server::helpers::SharedHttpInnerMessage;
use error::{Error, UrlGenerationError, CookieParseError, PayloadError};
pub struct HttpInnerMessage { pub struct HttpInnerMessage {
@ -33,6 +39,13 @@ pub struct HttpInnerMessage {
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>>,
resource: RouterResource,
}
#[derive(Debug, Copy, Clone,PartialEq)]
enum RouterResource {
Notset,
Normal(u16),
} }
impl Default for HttpInnerMessage { impl Default for HttpInnerMessage {
@ -51,6 +64,7 @@ impl Default for HttpInnerMessage {
payload: None, payload: None,
extensions: Extensions::new(), extensions: Extensions::new(),
info: None, info: None,
resource: RouterResource::Notset,
} }
} }
} }
@ -87,9 +101,15 @@ impl HttpInnerMessage {
self.addr = None; self.addr = None;
self.info = None; self.info = None;
self.payload = None; self.payload = None;
self.resource = RouterResource::Notset;
} }
} }
lazy_static!{
static ref RESOURCE: Resource = Resource::unset();
}
/// An HTTP Request /// An HTTP Request
pub struct HttpRequest<S=()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>); pub struct HttpRequest<S=()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>);
@ -114,6 +134,7 @@ impl HttpRequest<()> {
addr: None, addr: None,
extensions: Extensions::new(), extensions: Extensions::new(),
info: None, info: None,
resource: RouterResource::Notset,
}), }),
None, None,
None, None,
@ -194,6 +215,24 @@ impl<S> HttpRequest<S> {
.server_settings().cpu_pool() .server_settings().cpu_pool()
} }
/// Create http response
pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse {
if let Some(router) = self.router() {
router.server_settings().get_response(status, body)
} else {
HttpResponse::with_body(status, body)
}
}
/// Create http response builder
pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder {
if let Some(router) = self.router() {
router.server_settings().get_response_builder(status)
} else {
HttpResponse::build(status)
}
}
#[doc(hidden)] #[doc(hidden)]
pub fn prefix_len(&self) -> usize { pub fn prefix_len(&self) -> usize {
if let Some(router) = self.router() { router.prefix().len() } else { 0 } if let Some(router) = self.router() { router.prefix().len() } else { 0 }
@ -203,11 +242,10 @@ impl<S> HttpRequest<S> {
#[inline] #[inline]
pub fn uri(&self) -> &Uri { &self.as_ref().uri } pub fn uri(&self) -> &Uri { &self.as_ref().uri }
#[doc(hidden)] /// Returns mutable the Request Uri.
#[inline]
/// Modify the Request Uri.
/// ///
/// This might be useful for middlewares, i.e. path normalization /// This might be useful for middlewares, e.g. path normalization.
#[inline]
pub fn uri_mut(&mut self) -> &mut Uri { pub fn uri_mut(&mut self) -> &mut Uri {
&mut self.as_mut().uri &mut self.as_mut().uri
} }
@ -222,7 +260,9 @@ impl<S> HttpRequest<S> {
self.as_ref().version self.as_ref().version
} }
#[doc(hidden)] ///Returns mutable Request's headers.
///
///This is intended to be used by middleware.
#[inline] #[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap { pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.as_mut().headers &mut self.as_mut().headers
@ -234,6 +274,12 @@ impl<S> HttpRequest<S> {
self.uri().path() self.uri().path()
} }
/// Percent decoded path of this Request.
#[inline]
pub fn path_decoded(&self) -> Cow<str> {
percent_decode(self.uri().path().as_bytes()).decode_utf8().unwrap()
}
/// Get *ConnectionInfo* for correct request. /// Get *ConnectionInfo* for correct request.
pub fn connection_info(&self) -> &ConnectionInfo { pub fn connection_info(&self) -> &ConnectionInfo {
if self.as_ref().info.is_none() { if self.as_ref().info.is_none() {
@ -248,19 +294,18 @@ impl<S> HttpRequest<S> {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::*; /// # use actix_web::{App, HttpRequest, HttpResponse, http};
/// # use actix_web::httpcodes::*;
/// # /// #
/// fn index(req: HttpRequest) -> HttpResponse { /// fn index(req: HttpRequest) -> HttpResponse {
/// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
/// HttpOk.into() /// HttpResponse::Ok().into()
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = App::new()
/// .resource("/test/{one}/{two}/{three}", |r| { /// .resource("/test/{one}/{two}/{three}", |r| {
/// r.name("foo"); // <- set resource name, then it could be used in `url_for` /// r.name("foo"); // <- set resource name, then it could be used in `url_for`
/// r.method(Method::GET).f(|_| httpcodes::HttpOk); /// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
@ -288,6 +333,21 @@ impl<S> HttpRequest<S> {
self.2.as_ref() self.2.as_ref()
} }
/// This method returns reference to matched `Resource` object.
#[inline]
pub fn resource(&self) -> &Resource {
if let Some(ref router) = self.2 {
if let RouterResource::Normal(idx) = self.as_ref().resource {
return router.get_resource(idx as usize)
}
}
&*RESOURCE
}
pub(crate) fn set_resource(&mut self, res: usize) {
self.as_mut().resource = RouterResource::Normal(res as u16);
}
/// Peer socket address /// Peer socket address
/// ///
/// Peer address is actual socket address, if proxy is used in front of /// Peer address is actual socket address, if proxy is used in front of
@ -360,6 +420,7 @@ impl<S> HttpRequest<S> {
} }
/// 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.
/// Route supports glob patterns: * for a single wildcard segment and :param /// Route supports glob patterns: * for a single wildcard segment and :param
/// for matching storing that segment of the request url in the Params object. /// for matching storing that segment of the request url in the Params object.
@ -431,6 +492,17 @@ impl<S> Clone for HttpRequest<S> {
} }
} }
impl<S: 'static> FromRequest<S> for HttpRequest<S>
{
type Config = ();
type Result = FutureResult<Self, Error>;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
result(Ok(req.clone()))
}
}
impl<S> Stream for HttpRequest<S> { impl<S> Stream for HttpRequest<S> {
type Item = Bytes; type Item = Bytes;
type Error = PayloadError; type Error = PayloadError;
@ -483,17 +555,17 @@ impl<S> AsyncRead for HttpRequest<S> {}
impl<S> fmt::Debug for HttpRequest<S> { impl<S> fmt::Debug for HttpRequest<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nHttpRequest {:?} {}:{}\n", let res = writeln!(f, "\nHttpRequest {:?} {}:{}",
self.as_ref().version, self.as_ref().method, self.as_ref().uri); self.as_ref().version, self.as_ref().method, self.path_decoded());
if !self.query_string().is_empty() { if !self.query_string().is_empty() {
let _ = write!(f, " query: ?{:?}\n", self.query_string()); let _ = writeln!(f, " query: ?{:?}", self.query_string());
} }
if !self.match_info().is_empty() { if !self.match_info().is_empty() {
let _ = write!(f, " params: {:?}\n", self.as_ref().params); let _ = writeln!(f, " params: {:?}", self.as_ref().params);
} }
let _ = write!(f, " headers:\n"); let _ = writeln!(f, " headers:");
for (key, val) in self.as_ref().headers.iter() { for (key, val) in self.as_ref().headers.iter() {
let _ = write!(f, " {:?}: {:?}\n", key, val); let _ = writeln!(f, " {:?}: {:?}", key, val);
} }
res res
} }
@ -503,8 +575,8 @@ impl<S> fmt::Debug for HttpRequest<S> {
mod tests { mod tests {
use super::*; use super::*;
use http::{Uri, HttpTryFrom}; use http::{Uri, HttpTryFrom};
use router::Pattern; use router::Resource;
use resource::Resource; use resource::ResourceHandler;
use test::TestRequest; use test::TestRequest;
use server::ServerSettings; use server::ServerSettings;
@ -566,10 +638,10 @@ mod tests {
fn test_request_match_info() { fn test_request_match_info() {
let mut req = TestRequest::with_uri("/value/?id=test").finish(); let mut req = TestRequest::with_uri("/value/?id=test").finish();
let mut resource = Resource::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let mut routes = Vec::new(); let mut routes = Vec::new();
routes.push((Pattern::new("index", "/{key}/"), Some(resource))); routes.push((Resource::new("index", "/{key}/"), 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());
@ -582,9 +654,9 @@ mod tests {
assert_eq!(req2.url_for("unknown", &["test"]), assert_eq!(req2.url_for("unknown", &["test"]),
Err(UrlGenerationError::RouterNotAvailable)); Err(UrlGenerationError::RouterNotAvailable));
let mut resource = Resource::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let routes = vec!((Pattern::new("index", "/user/{name}.{ext}"), Some(resource))); let routes = vec!((Resource::new("index", "/user/{name}.{ext}"), Some(resource)));
let (router, _) = Router::new("/", ServerSettings::default(), routes); 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"));
@ -604,26 +676,27 @@ mod tests {
fn test_url_for_with_prefix() { fn test_url_for_with_prefix() {
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish(); let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
let mut resource = Resource::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let routes = vec![(Pattern::new("index", "/user/{name}.{ext}"), Some(resource))]; let routes = vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); 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", "html"]);
assert_eq!(url.ok().unwrap().as_str(), "http://www.rust-lang.org/prefix/user/test.html"); assert_eq!(url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/user/test.html");
} }
#[test] #[test]
fn test_url_for_external() { fn test_url_for_external() {
let req = HttpRequest::default(); let req = HttpRequest::default();
let mut resource = Resource::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let routes = vec![ let routes = vec![
(Pattern::new("youtube", "https://youtube.com/watch/{video_id}"), None)]; (Resource::external("youtube", "https://youtube.com/watch/{video_id}"), 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

@ -1,7 +1,8 @@
//! Http response //! Http response
use std::{mem, str, fmt}; use std::{mem, str, fmt};
use std::rc::Rc;
use std::io::Write; use std::io::Write;
use std::cell::RefCell; use std::cell::UnsafeCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use cookie::{Cookie, CookieJar}; use cookie::{Cookie, CookieJar};
@ -17,6 +18,8 @@ use error::Error;
use handler::Responder; use handler::Responder;
use header::{Header, IntoHeaderValue, ContentEncoding}; use header::{Header, IntoHeaderValue, ContentEncoding};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpmessage::HttpMessage;
use client::ClientResponse;
/// max write buffer size 64k /// max write buffer size 64k
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
@ -34,12 +37,12 @@ pub enum ConnectionType {
} }
/// An HTTP Response /// An HTTP Response
pub struct HttpResponse(Option<Box<InnerHttpResponse>>); pub struct HttpResponse(Option<Box<InnerHttpResponse>>, Rc<UnsafeCell<HttpResponsePool>>);
impl Drop for HttpResponse { impl Drop for HttpResponse {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(inner) = self.0.take() { if let Some(inner) = self.0.take() {
Pool::release(inner) HttpResponsePool::release(&self.1, inner)
} }
} }
} }
@ -61,17 +64,25 @@ impl HttpResponse {
/// Create http response builder with specific status. /// Create http response builder with specific status.
#[inline] #[inline]
pub fn build(status: StatusCode) -> HttpResponseBuilder { pub fn build(status: StatusCode) -> HttpResponseBuilder {
HttpResponseBuilder { HttpResponsePool::get(status)
response: Some(Pool::get(status)),
err: None,
cookies: None,
} }
/// Create http response builder
#[inline]
pub fn build_from<T: Into<HttpResponseBuilder>>(source: T) -> HttpResponseBuilder {
source.into()
} }
/// Constructs a response /// Constructs a response
#[inline] #[inline]
pub fn new(status: StatusCode, body: Body) -> HttpResponse { pub fn new(status: StatusCode) -> HttpResponse {
HttpResponse(Some(Pool::with_body(status, body))) HttpResponsePool::with_body(status, Body::Empty)
}
/// Constructs a response with body
#[inline]
pub fn with_body<B: Into<Body>>(status: StatusCode, body: B) -> HttpResponse {
HttpResponsePool::with_body(status, body.into())
} }
/// Constructs a error response /// Constructs a error response
@ -82,6 +93,20 @@ impl HttpResponse {
resp resp
} }
/// Convert `HttpResponse` to a `HttpResponseBuilder`
#[inline]
pub fn into_builder(mut self) -> HttpResponseBuilder {
let response = self.0.take();
let pool = Some(Rc::clone(&self.1));
HttpResponseBuilder {
response,
pool,
err: None,
cookies: None, // TODO: convert set-cookie headers
}
}
/// The source `error` for this response /// The source `error` for this response
#[inline] #[inline]
pub fn error(&self) -> Option<&Error> { pub fn error(&self) -> Option<&Error> {
@ -216,13 +241,13 @@ impl HttpResponse {
impl fmt::Debug for HttpResponse { impl fmt::Debug for HttpResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nHttpResponse {:?} {}{}\n", let res = writeln!(f, "\nHttpResponse {:?} {}{}",
self.get_ref().version, self.get_ref().status, self.get_ref().version, self.get_ref().status,
self.get_ref().reason.unwrap_or("")); self.get_ref().reason.unwrap_or(""));
let _ = write!(f, " encoding: {:?}\n", self.get_ref().encoding); let _ = writeln!(f, " encoding: {:?}", self.get_ref().encoding);
let _ = write!(f, " headers:\n"); let _ = writeln!(f, " headers:");
for (key, val) in self.get_ref().headers.iter() { for (key, val) in self.get_ref().headers.iter() {
let _ = write!(f, " {:?}: {:?}\n", key, val); let _ = writeln!(f, " {:?}: {:?}", key, val);
} }
res res
} }
@ -232,9 +257,9 @@ impl fmt::Debug for HttpResponse {
/// ///
/// This type can be used to construct an instance of `HttpResponse` through a /// This type can be used to construct an instance of `HttpResponse` through a
/// builder-like pattern. /// builder-like pattern.
#[derive(Debug)]
pub struct HttpResponseBuilder { pub struct HttpResponseBuilder {
response: Option<Box<InnerHttpResponse>>, response: Option<Box<InnerHttpResponse>>,
pool: Option<Rc<UnsafeCell<HttpResponsePool>>>,
err: Option<HttpError>, err: Option<HttpError>,
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
} }
@ -264,15 +289,12 @@ impl HttpResponseBuilder {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::*; /// use actix_web::{HttpRequest, HttpResponse, Result, http};
/// # use actix_web::httpcodes::*;
/// #
/// use actix_web::header;
/// ///
/// fn index(req: HttpRequest) -> Result<HttpResponse> { /// fn index(req: HttpRequest) -> Result<HttpResponse> {
/// Ok(HttpOk.build() /// Ok(HttpResponse::Ok()
/// .set(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() {}
/// ``` /// ```
@ -291,18 +313,14 @@ impl HttpResponseBuilder {
/// Set a header. /// Set a header.
/// ///
/// ```rust /// ```rust
/// # extern crate http;
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::*; /// use actix_web::{http, HttpRequest, HttpResponse};
/// # use actix_web::httpcodes::*;
/// #
/// use http::header;
/// ///
/// fn index(req: HttpRequest) -> Result<HttpResponse> { /// fn index(req: HttpRequest) -> HttpResponse {
/// Ok(HttpOk.build() /// HttpResponse::Ok()
/// .header("X-TEST", "value") /// .header("X-TEST", "value")
/// .header(header::CONTENT_TYPE, "application/json") /// .header(http::header::CONTENT_TYPE, "application/json")
/// .finish()?) /// .finish()
/// } /// }
/// fn main() {} /// fn main() {}
/// ``` /// ```
@ -413,21 +431,18 @@ impl HttpResponseBuilder {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::*; /// use actix_web::{http, HttpRequest, HttpResponse, Result};
/// # use actix_web::httpcodes::*;
/// #
/// use actix_web::headers::Cookie;
/// ///
/// fn index(req: HttpRequest) -> Result<HttpResponse> { /// fn index(req: HttpRequest) -> HttpResponse {
/// Ok(HttpOk.build() /// HttpResponse::Ok()
/// .cookie( /// .cookie(
/// Cookie::build("name", "value") /// http::Cookie::build("name", "value")
/// .domain("www.rust-lang.org") /// .domain("www.rust-lang.org")
/// .path("/") /// .path("/")
/// .secure(true) /// .secure(true)
/// .http_only(true) /// .http_only(true)
/// .finish()) /// .finish())
/// .finish()?) /// .finish()
/// } /// }
/// fn main() {} /// fn main() {}
/// ``` /// ```
@ -493,38 +508,42 @@ impl HttpResponseBuilder {
/// Set a body and generate `HttpResponse`. /// Set a body and generate `HttpResponse`.
/// ///
/// `HttpResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
pub fn body<B: Into<Body>>(&mut self, body: B) -> Result<HttpResponse, HttpError> { pub fn body<B: Into<Body>>(&mut self, body: B) -> HttpResponse {
if let Some(e) = self.err.take() { if let Some(e) = self.err.take() {
return Err(e) return Error::from(e).into()
} }
let mut response = self.response.take().expect("cannot reuse response builder"); let mut response = self.response.take().expect("cannot reuse response builder");
if let Some(ref jar) = self.cookies { if let Some(ref jar) = self.cookies {
for cookie in jar.delta() { for cookie in jar.delta() {
response.headers.append( match HeaderValue::from_str(&cookie.to_string()) {
header::SET_COOKIE, Ok(val) => response.headers.append(header::SET_COOKIE, val),
HeaderValue::from_str(&cookie.to_string())?); Err(e) => return Error::from(e).into(),
};
} }
} }
response.body = body.into(); response.body = body.into();
Ok(HttpResponse(Some(response))) HttpResponse(Some(response), self.pool.take().unwrap())
} }
#[inline]
/// Set a streaming body and generate `HttpResponse`. /// Set a streaming body and generate `HttpResponse`.
/// ///
/// `HttpResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
pub fn streaming<S>(&mut self, stream: S) -> Result<HttpResponse, HttpError> pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
where S: Stream<Item=Bytes, Error=Error> + 'static, where S: Stream<Item=Bytes, Error=E> + 'static,
E: Into<Error>,
{ {
self.body(Body::Streaming(Box::new(stream))) self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
} }
/// Set a json body and generate `HttpResponse` /// Set a json body and generate `HttpResponse`
/// ///
/// `HttpResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
pub fn json<T: Serialize>(&mut self, value: T) -> Result<HttpResponse, Error> { pub fn json<T: Serialize>(&mut self, value: T) -> HttpResponse {
let body = serde_json::to_string(&value)?; match serde_json::to_string(&value) {
Ok(body) => {
let contains = if let Some(parts) = parts(&mut self.response, &self.err) { let contains =
if let Some(parts) = parts(&mut self.response, &self.err) {
parts.headers.contains_key(header::CONTENT_TYPE) parts.headers.contains_key(header::CONTENT_TYPE)
} else { } else {
true true
@ -533,13 +552,17 @@ impl HttpResponseBuilder {
self.header(header::CONTENT_TYPE, "application/json"); self.header(header::CONTENT_TYPE, "application/json");
} }
Ok(self.body(body)?) self.body(body)
},
Err(e) => Error::from(e).into()
}
} }
#[inline]
/// Set an empty body and generate `HttpResponse` /// Set an empty body and generate `HttpResponse`
/// ///
/// `HttpResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
pub fn finish(&mut self) -> Result<HttpResponse, HttpError> { pub fn finish(&mut self) -> HttpResponse {
self.body(Body::Empty) self.body(Body::Empty)
} }
@ -547,6 +570,7 @@ impl HttpResponseBuilder {
pub fn take(&mut self) -> HttpResponseBuilder { pub fn take(&mut self) -> HttpResponseBuilder {
HttpResponseBuilder { HttpResponseBuilder {
response: self.response.take(), response: self.response.take(),
pool: self.pool.take(),
err: self.err.take(), err: self.err.take(),
cookies: self.cookies.take(), cookies: self.cookies.take(),
} }
@ -576,77 +600,74 @@ impl<I: Into<HttpResponse>, E: Into<Error>> From<Result<I, E>> for HttpResponse
impl From<HttpResponseBuilder> for HttpResponse { impl From<HttpResponseBuilder> for HttpResponse {
fn from(mut builder: HttpResponseBuilder) -> Self { fn from(mut builder: HttpResponseBuilder) -> Self {
builder.finish().into() builder.finish()
} }
} }
impl Responder for HttpResponseBuilder { impl Responder for HttpResponseBuilder {
type Item = HttpResponse; type Item = HttpResponse;
type Error = HttpError; type Error = Error;
#[inline] #[inline]
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, HttpError> { fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, Error> {
self.finish() Ok(self.finish())
} }
} }
impl From<&'static str> for HttpResponse { impl From<&'static str> for HttpResponse {
fn from(val: &'static str) -> Self { fn from(val: &'static str) -> Self {
HttpResponse::build(StatusCode::OK) HttpResponse::Ok()
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(val) .body(val)
.into()
} }
} }
impl Responder for &'static str { impl Responder for &'static str {
type Item = HttpResponse; type Item = HttpResponse;
type Error = HttpError; type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> { fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
HttpResponse::build(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))
} }
} }
impl From<&'static [u8]> for HttpResponse { impl From<&'static [u8]> for HttpResponse {
fn from(val: &'static [u8]) -> Self { fn from(val: &'static [u8]) -> Self {
HttpResponse::build(StatusCode::OK) HttpResponse::Ok()
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(val) .body(val)
.into()
} }
} }
impl Responder for &'static [u8] { impl Responder for &'static [u8] {
type Item = HttpResponse; type Item = HttpResponse;
type Error = HttpError; type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> { fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
HttpResponse::build(StatusCode::OK) Ok(req.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self) .body(self))
} }
} }
impl From<String> for HttpResponse { impl From<String> for HttpResponse {
fn from(val: String) -> Self { fn from(val: String) -> Self {
HttpResponse::build(StatusCode::OK) HttpResponse::Ok()
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(val) .body(val)
.into()
} }
} }
impl Responder for String { impl Responder for String {
type Item = HttpResponse; type Item = HttpResponse;
type Error = HttpError; type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> { fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
HttpResponse::build(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))
} }
} }
@ -655,58 +676,79 @@ impl<'a> From<&'a String> for HttpResponse {
HttpResponse::build(StatusCode::OK) HttpResponse::build(StatusCode::OK)
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(val) .body(val)
.into()
} }
} }
impl<'a> Responder for &'a String { impl<'a> Responder for &'a String {
type Item = HttpResponse; type Item = HttpResponse;
type Error = HttpError; type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> { fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
HttpResponse::build(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))
} }
} }
impl From<Bytes> for HttpResponse { impl From<Bytes> for HttpResponse {
fn from(val: Bytes) -> Self { fn from(val: Bytes) -> Self {
HttpResponse::build(StatusCode::OK) HttpResponse::Ok()
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(val) .body(val)
.into()
} }
} }
impl Responder for Bytes { impl Responder for Bytes {
type Item = HttpResponse; type Item = HttpResponse;
type Error = HttpError; type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> { fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
HttpResponse::build(StatusCode::OK) Ok(req.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self) .body(self))
} }
} }
impl From<BytesMut> for HttpResponse { impl From<BytesMut> for HttpResponse {
fn from(val: BytesMut) -> Self { fn from(val: BytesMut) -> Self {
HttpResponse::build(StatusCode::OK) HttpResponse::Ok()
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(val) .body(val)
.into()
} }
} }
impl Responder for BytesMut { impl Responder for BytesMut {
type Item = HttpResponse; type Item = HttpResponse;
type Error = HttpError; type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> { fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
HttpResponse::build(StatusCode::OK) Ok(req.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self) .body(self))
}
}
/// Create `HttpResponseBuilder` from `ClientResponse`
///
/// It is useful for proxy response. This implementation
/// copies all responses's headers and status.
impl<'a> From<&'a ClientResponse> for HttpResponseBuilder {
fn from(resp: &'a ClientResponse) -> HttpResponseBuilder {
let mut builder = HttpResponse::build(resp.status());
for (key, value) in resp.headers() {
builder.header(key.clone(), value.clone());
}
builder
}
}
impl<'a, S> From<&'a HttpRequest<S>> for HttpResponseBuilder {
fn from(req: &'a HttpRequest<S>) -> HttpResponseBuilder {
if let Some(router) = req.router() {
router.server_settings().get_response_builder(StatusCode::OK)
} else {
HttpResponse::Ok()
}
} }
} }
@ -746,45 +788,69 @@ impl InnerHttpResponse {
} }
/// Internal use only! unsafe /// Internal use only! unsafe
struct Pool(VecDeque<Box<InnerHttpResponse>>); pub(crate) struct HttpResponsePool(VecDeque<Box<InnerHttpResponse>>);
thread_local!(static POOL: RefCell<Pool> = thread_local!(static POOL: Rc<UnsafeCell<HttpResponsePool>> = HttpResponsePool::pool());
RefCell::new(Pool(VecDeque::with_capacity(128))));
impl Pool { impl HttpResponsePool {
#[inline] pub fn pool() -> Rc<UnsafeCell<HttpResponsePool>> {
fn get(status: StatusCode) -> Box<InnerHttpResponse> { Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity(128))))
POOL.with(|pool| {
if let Some(mut resp) = pool.borrow_mut().0.pop_front() {
resp.body = Body::Empty;
resp.status = status;
resp
} else {
Box::new(InnerHttpResponse::new(status, Body::Empty))
}
})
} }
#[inline] #[inline]
fn with_body(status: StatusCode, body: Body) -> Box<InnerHttpResponse> { pub fn get_builder(pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode)
POOL.with(|pool| { -> HttpResponseBuilder
if let Some(mut resp) = pool.borrow_mut().0.pop_front() { {
resp.status = status; let p = unsafe{&mut *pool.as_ref().get()};
resp.body = body; if let Some(mut msg) = p.0.pop_front() {
resp msg.status = status;
HttpResponseBuilder {
response: Some(msg),
pool: Some(Rc::clone(pool)),
err: None,
cookies: None }
} else { } else {
Box::new(InnerHttpResponse::new(status, body)) let msg = Box::new(InnerHttpResponse::new(status, Body::Empty));
HttpResponseBuilder {
response: Some(msg),
pool: Some(Rc::clone(pool)),
err: None,
cookies: None }
} }
}) }
#[inline]
pub fn get_response(pool: &Rc<UnsafeCell<HttpResponsePool>>,
status: StatusCode, body: Body) -> HttpResponse
{
let p = unsafe{&mut *pool.as_ref().get()};
if let Some(mut msg) = p.0.pop_front() {
msg.status = status;
msg.body = body;
HttpResponse(Some(msg), Rc::clone(pool))
} else {
let msg = Box::new(InnerHttpResponse::new(status, body));
HttpResponse(Some(msg), Rc::clone(pool))
}
}
#[inline]
fn get(status: StatusCode) -> HttpResponseBuilder {
POOL.with(|pool| HttpResponsePool::get_builder(pool, status))
}
#[inline]
fn with_body(status: StatusCode, body: Body) -> HttpResponse {
POOL.with(|pool| HttpResponsePool::get_response(pool, status, body))
} }
#[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(mut inner: Box<InnerHttpResponse>) { fn release(pool: &Rc<UnsafeCell<HttpResponsePool>>, mut inner: Box<InnerHttpResponse>)
POOL.with(|pool| { {
let v = &mut pool.borrow_mut().0; let pool = unsafe{&mut *pool.as_ref().get()};
if v.len() < 128 { if pool.0.len() < 128 {
inner.headers.clear(); inner.headers.clear();
inner.version = None; inner.version = None;
inner.chunked = None; inner.chunked = None;
@ -794,9 +860,8 @@ impl Pool {
inner.response_size = 0; inner.response_size = 0;
inner.error = None; inner.error = None;
inner.write_capacity = MAX_WRITE_BUFFER_SIZE; inner.write_capacity = MAX_WRITE_BUFFER_SIZE;
v.push_front(inner); pool.0.push_front(inner);
} }
})
} }
} }
@ -808,14 +873,14 @@ mod tests {
use http::{Method, Uri}; use http::{Method, Uri};
use http::header::{COOKIE, CONTENT_TYPE, HeaderValue}; use http::header::{COOKIE, CONTENT_TYPE, HeaderValue};
use body::Binary; use body::Binary;
use {header, httpcodes}; use http;
#[test] #[test]
fn test_debug() { fn test_debug() {
let resp = HttpResponse::Ok() let resp = HttpResponse::Ok()
.header(COOKIE, HeaderValue::from_static("cookie1=value1; ")) .header(COOKIE, HeaderValue::from_static("cookie1=value1; "))
.header(COOKIE, HeaderValue::from_static("cookie2=value2; ")) .header(COOKIE, HeaderValue::from_static("cookie2=value2; "))
.finish().unwrap(); .finish();
let dbg = format!("{:?}", resp); let dbg = format!("{:?}", resp);
assert!(dbg.contains("HttpResponse")); assert!(dbg.contains("HttpResponse"));
} }
@ -830,19 +895,15 @@ mod tests {
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
let cookies = req.cookies().unwrap(); let cookies = req.cookies().unwrap();
let resp = httpcodes::HttpOk let resp = HttpResponse::Ok()
.build() .cookie(http::Cookie::build("name", "value")
.cookie(header::Cookie::build("name", "value")
.domain("www.rust-lang.org") .domain("www.rust-lang.org")
.path("/test") .path("/test")
.http_only(true) .http_only(true)
.max_age(Duration::days(1)) .max_age(Duration::days(1))
.finish()) .finish())
.del_cookie(&cookies[0]) .del_cookie(&cookies[0])
.body(Body::Empty); .finish();
assert!(resp.is_ok());
let resp = resp.unwrap();
let mut val: Vec<_> = resp.headers().get_all("Set-Cookie") let mut val: Vec<_> = resp.headers().get_all("Set-Cookie")
.iter().map(|v| v.to_str().unwrap().to_owned()).collect(); .iter().map(|v| v.to_str().unwrap().to_owned()).collect();
@ -857,46 +918,51 @@ mod tests {
let resp = HttpResponse::Ok() let resp = HttpResponse::Ok()
.header("X-TEST", "value") .header("X-TEST", "value")
.version(Version::HTTP_10) .version(Version::HTTP_10)
.finish().unwrap(); .finish();
assert_eq!(resp.version(), Some(Version::HTTP_10)); assert_eq!(resp.version(), Some(Version::HTTP_10));
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test] #[test]
fn test_upgrade() { fn test_upgrade() {
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK).upgrade().finish();
.upgrade().body(Body::Empty).unwrap();
assert!(resp.upgrade()) assert!(resp.upgrade())
} }
#[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().body(Body::Empty).unwrap();
assert!(!resp.keep_alive().unwrap()) assert!(!resp.keep_alive().unwrap())
} }
#[test] #[test]
fn test_content_type() { fn test_content_type() {
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK)
.content_type("text/plain").body(Body::Empty).unwrap(); .content_type("text/plain").body(Body::Empty);
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
} }
#[test] #[test]
fn test_content_encoding() { fn test_content_encoding() {
let resp = HttpResponse::build(StatusCode::OK).finish().unwrap(); let resp = HttpResponse::build(StatusCode::OK).finish();
assert_eq!(resp.content_encoding(), None); assert_eq!(resp.content_encoding(), None);
#[cfg(feature="brotli")]
{
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK)
.content_encoding(ContentEncoding::Br).finish().unwrap(); .content_encoding(ContentEncoding::Br).finish();
assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br));
} }
let resp = HttpResponse::build(StatusCode::OK)
.content_encoding(ContentEncoding::Gzip).finish();
assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip));
}
#[test] #[test]
fn test_json() { fn test_json() {
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK)
.json(vec!["v1", "v2", "v3"]).unwrap(); .json(vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap(); let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!(ct, HeaderValue::from_static("application/json"));
assert_eq!(*resp.body(), Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))); assert_eq!(*resp.body(), Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")));
@ -906,7 +972,7 @@ mod tests {
fn test_json_ct() { fn test_json_ct() {
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK)
.header(CONTENT_TYPE, "text/json") .header(CONTENT_TYPE, "text/json")
.json(vec!["v1", "v2", "v3"]).unwrap(); .json(vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap(); let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("text/json")); assert_eq!(ct, HeaderValue::from_static("text/json"));
assert_eq!(*resp.body(), Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))); assert_eq!(*resp.body(), Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")));
@ -1013,4 +1079,14 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test"))); assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test")));
} }
#[test]
fn test_into_builder() {
let resp: HttpResponse = "test".into();
assert_eq!(resp.status(), StatusCode::OK);
let mut builder = resp.into_builder();
let resp = builder.status(StatusCode::BAD_REQUEST).finish();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
} }

View File

@ -1,3 +1,5 @@
use std::fmt;
use std::ops::{Deref, DerefMut};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{Poll, Future, Stream}; use futures::{Poll, Future, Stream};
use http::header::CONTENT_LENGTH; use http::header::CONTENT_LENGTH;
@ -8,16 +10,48 @@ use serde::Serialize;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use error::{Error, JsonPayloadError, PayloadError}; use error::{Error, JsonPayloadError, PayloadError};
use handler::Responder; use handler::{Responder, FromRequest};
use http::StatusCode;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
/// Json response helper /// Json helper
/// ///
/// The `Json` type allows you to respond with well-formed JSON data: simply return a value of /// Json can be used for two different purpose. First is for json response generation
/// type Json<T> where T is the type of a structure to serialize into *JSON*. The /// and second is for extracting typed information from request's payload.
/// type `T` must implement the `Serialize` trait from *serde*. pub struct Json<T>(pub T);
impl<T> Deref for Json<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for Json<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T> fmt::Debug for Json<T> where T: fmt::Debug {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Json: {:?}", self.0)
}
}
impl<T> fmt::Display for Json<T> where T: fmt::Display {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
/// The `Json` type allows you to respond with well-formed JSON data: simply
/// return a value of type Json<T> where T is the type of a structure
/// to serialize into *JSON*. The type `T` must implement the `Serialize`
/// trait from *serde*.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
@ -34,18 +68,105 @@ use httpresponse::HttpResponse;
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
pub struct Json<T: Serialize> (pub T);
impl<T: Serialize> Responder for Json<T> { impl<T: Serialize> Responder for Json<T> {
type Item = HttpResponse; type Item = HttpResponse;
type Error = Error; type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> { fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
let body = serde_json::to_string(&self.0)?; let body = serde_json::to_string(&self.0)?;
Ok(HttpResponse::Ok() Ok(req.build_response(StatusCode::OK)
.content_type("application/json") .content_type("application/json")
.body(body)?) .body(body))
}
}
/// To extract typed information from request's body, the type `T` must implement the
/// `Deserialize` trait from *serde*.
///
/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction process.
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Json, Result, http};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// deserialize `Info` from request's body
/// fn index(info: Json<Info>) -> Result<String> {
/// Ok(format!("Welcome {}!", info.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html",
/// |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor
/// }
/// ```
impl<T, S> FromRequest<S> for Json<T>
where T: DeserializeOwned + 'static, S: 'static
{
type Config = JsonConfig;
type Result = Box<Future<Item=Self, Error=Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new(
JsonBody::new(req.clone())
.limit(cfg.limit)
.from_err()
.map(Json))
}
}
/// Json extractor configuration
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Json, Result, http};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// deserialize `Info` from request's body, max payload size is 4kb
/// fn index(info: Json<Info>) -> Result<String> {
/// Ok(format!("Welcome {}!", info.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::POST)
/// .with(index)
/// .limit(4096);} // <- change json extractor configuration
/// );
/// }
/// ```
pub struct JsonConfig {
limit: usize,
}
impl JsonConfig {
/// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self {
self.limit = limit;
self
}
}
impl Default for JsonConfig {
fn default() -> Self {
JsonConfig{limit: 262_144}
} }
} }
@ -56,15 +177,14 @@ impl<T: Serialize> Responder for Json<T> {
/// * content type is not `application/json` /// * content type is not `application/json`
/// * content length is greater than 256k /// * content length is greater than 256k
/// ///
///
/// # Server example /// # Server example
/// ///
/// ```rust /// ```rust
/// # 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::*;
/// use futures::future::Future; /// use futures::future::Future;
/// use actix_web::{AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error};
/// ///
/// #[derive(Deserialize, Debug)] /// #[derive(Deserialize, Debug)]
/// struct MyObj { /// struct MyObj {
@ -76,7 +196,7 @@ impl<T: Serialize> Responder for Json<T> {
/// .from_err() /// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value /// .and_then(|val: MyObj| { // <- deserialized value
/// println!("==== BODY ==== {:?}", val); /// println!("==== BODY ==== {:?}", val);
/// Ok(httpcodes::HttpOk.into()) /// Ok(HttpResponse::Ok().into())
/// }).responder() /// }).responder()
/// } /// }
/// # fn main() {} /// # fn main() {}
@ -160,6 +280,9 @@ mod tests {
use http::header; use http::header;
use futures::Async; use futures::Async;
use with::{With, ExtractorConfig};
use handler::Handler;
impl PartialEq for JsonPayloadError { impl PartialEq for JsonPayloadError {
fn eq(&self, other: &JsonPayloadError) -> bool { fn eq(&self, other: &JsonPayloadError) -> bool {
match *self { match *self {
@ -215,6 +338,27 @@ mod tests {
header::HeaderValue::from_static("16")); header::HeaderValue::from_static("16"));
req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!(json.poll().ok().unwrap(), Async::Ready(MyObject{name: "test".to_owned()})); assert_eq!(json.poll().ok().unwrap(),
Async::Ready(MyObject{name: "test".to_owned()}));
}
#[test]
fn test_with_json() {
let mut cfg = ExtractorConfig::<_, Json<MyObject>>::default();
cfg.limit(4096);
let mut handler = With::new(|data: Json<MyObject>| {data}, cfg);
let req = HttpRequest::default();
let err = handler.handle(req).as_response().unwrap().error().is_some();
assert!(err);
let mut req = HttpRequest::default();
req.headers_mut().insert(header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"));
req.headers_mut().insert(header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"));
req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
let ok = handler.handle(req).as_response().unwrap().error().is_none();
assert!(ok)
} }
} }

View File

@ -1,31 +1,50 @@
//! Actix web is a small, pragmatic, extremely fast, web framework for Rust. //! Actix web is a small, pragmatic, and extremely fast web framework
//! for Rust.
//! //!
//! ```rust //! ```rust
//! use actix_web::*; //! use actix_web::{server, App, Path};
//! # use std::thread; //! # use std::thread;
//! //!
//! fn index(req: HttpRequest) -> String { //! fn index(info: Path<(String, u32)>) -> String {
//! format!("Hello {}!", &req.match_info()["name"]) //! format!("Hello {}! id:{}", info.0, info.1)
//! } //! }
//! //!
//! fn main() { //! fn main() {
//! # thread::spawn(|| { //! # thread::spawn(|| {
//! HttpServer::new( //! server::new(
//! || Application::new() //! || App::new()
//! .resource("/{name}", |r| r.f(index))) //! .resource("/{name}/{id}/index.html", |r| r.with(index)))
//! .bind("127.0.0.1:8080").unwrap() //! .bind("127.0.0.1:8080").unwrap()
//! .run(); //! .run();
//! # }); //! # });
//! } //! }
//! ``` //! ```
//! //!
//! ## Documentation //! ## Documentation & community resources
//!
//! Besides the API documentation (which you are currently looking
//! at!), several other resources are available:
//! //!
//! * [User Guide](http://actix.github.io/actix-web/guide/) //! * [User Guide](http://actix.github.io/actix-web/guide/)
//! * [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)
//! * Supported Rust version: 1.21 or later //!
//! To get started navigating the API documentation you may want to
//! consider looking at the following pages:
//!
//! * [App](struct.App.html): This struct represents an actix-web
//! application and is used to configure routes and other common
//! settings.
//!
//! * [HttpServer](server/struct.HttpServer.html): This struct
//! represents an HTTP server instance and is used to instantiate and
//! configure servers.
//!
//! * [HttpRequest](struct.HttpRequest.html) and
//! [HttpResponse](struct.HttpResponse.html): These structs
//! represent HTTP requests and responses and expose various methods
//! for inspecting, creating and otherwise utilising them.
//! //!
//! ## Features //! ## Features
//! //!
@ -37,9 +56,10 @@
//! * Configurable request routing //! * Configurable request routing
//! * Graceful server shutdown //! * Graceful server shutdown
//! * Multipart streams //! * Multipart streams
//! * 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
#![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
@ -60,6 +80,8 @@ extern crate bitflags;
#[macro_use] #[macro_use]
extern crate failure; extern crate failure;
#[macro_use] #[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate futures; extern crate futures;
extern crate futures_cpupool; extern crate futures_cpupool;
extern crate tokio_io; extern crate tokio_io;
@ -67,7 +89,7 @@ extern crate tokio_core;
extern crate mio; extern crate mio;
extern crate net2; extern crate net2;
extern crate cookie; extern crate cookie;
extern crate http; extern crate http as modhttp;
extern crate httparse; extern crate httparse;
extern crate http_range; extern crate http_range;
extern crate mime; extern crate mime;
@ -76,9 +98,11 @@ extern crate language_tags;
extern crate rand; extern crate rand;
extern crate url; extern crate url;
extern crate libc; extern crate libc;
extern crate serde; #[macro_use] extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate serde_urlencoded;
extern crate flate2; extern crate flate2;
#[cfg(feature="brotli")]
extern crate brotli2; extern crate brotli2;
extern crate encoding; extern crate encoding;
extern crate percent_encoding; extern crate percent_encoding;
@ -104,7 +128,10 @@ extern crate tokio_openssl;
mod application; mod application;
mod body; mod body;
mod context; mod context;
mod de;
mod extractor;
mod handler; mod handler;
mod header;
mod helpers; mod helpers;
mod httpmessage; mod httpmessage;
mod httprequest; mod httprequest;
@ -117,33 +144,34 @@ mod resource;
mod param; mod param;
mod payload; mod payload;
mod pipeline; mod pipeline;
mod with;
pub mod client; pub mod client;
pub mod fs; pub mod fs;
pub mod ws; pub mod ws;
pub mod error; pub mod error;
pub mod header;
pub mod httpcodes;
pub mod multipart; pub mod multipart;
pub mod middleware; pub mod middleware;
pub mod pred; pub mod pred;
pub mod test; pub mod test;
pub mod server; pub mod server;
pub use extractor::{Path, Form, Query};
pub use error::{Error, Result, ResponseError}; pub use error::{Error, Result, ResponseError};
pub use body::{Body, Binary}; pub use body::{Body, Binary};
pub use json::Json; pub use json::Json;
pub use application::Application; pub use application::App;
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 handler::{Either, Reply, Responder, NormalizePath, AsyncResponder}; pub use handler::{Either, Responder, AsyncResponder, FromRequest, FutureResponse, State};
pub use route::Route;
pub use resource::Resource;
pub use context::HttpContext; pub use context::HttpContext;
pub use server::HttpServer;
// re-exports #[doc(hidden)]
pub use http::{Method, StatusCode, Version}; pub mod httpcodes;
#[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;
@ -155,17 +183,6 @@ pub(crate) const HAS_TLS: bool = true;
#[cfg(not(feature="tls"))] #[cfg(not(feature="tls"))]
pub(crate) const HAS_TLS: bool = false; pub(crate) const HAS_TLS: bool = false;
#[doc(hidden)]
#[deprecated(since="0.4.4", note="please use `actix::header` module")]
pub mod headers {
//! Headers implementation
pub use httpresponse::ConnectionType;
pub use cookie::{Cookie, CookieBuilder};
pub use http_range::HttpRange;
pub use header::ContentEncoding;
}
pub mod dev { pub mod dev {
//! The `actix-web` prelude for library developers //! The `actix-web` prelude for library developers
//! //!
@ -179,11 +196,35 @@ pub mod dev {
pub use body::BodyStream; pub use body::BodyStream;
pub use context::Drain; pub use context::Drain;
pub use json::{JsonBody, JsonConfig};
pub use info::ConnectionInfo; pub use info::ConnectionInfo;
pub use handler::Handler; pub use handler::{Handler, Reply};
pub use json::JsonBody; pub use extractor::{FormConfig, PayloadConfig};
pub use router::{Router, Pattern}; pub use route::Route;
pub use router::{Router, Resource, ResourceType};
pub use resource::ResourceHandler;
pub use param::{FromParam, Params}; pub use param::{FromParam, Params};
pub use httpmessage::{UrlEncoded, MessageBody}; pub use httpmessage::{UrlEncoded, MessageBody};
pub use httpresponse::HttpResponseBuilder; pub use httpresponse::HttpResponseBuilder;
} }
pub mod http {
//! Various HTTP related types
// re-exports
pub use modhttp::{Method, StatusCode, Version};
#[doc(hidden)]
pub use modhttp::{uri, Uri, Error, Extensions, HeaderMap, HttpTryFrom};
pub use http_range::HttpRange;
pub use cookie::{Cookie, CookieBuilder};
pub use helpers::NormalizePath;
pub mod header {
pub use ::header::*;
}
pub use header::ContentEncoding;
pub use httpresponse::ConnectionType;
}

View File

@ -9,39 +9,35 @@
//! 2. Use any of the builder methods to set fields in the backend. //! 2. Use any of the builder methods to set fields in the backend.
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend. //! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend.
//! //!
//! Cors middleware could be used as parameter for `Application::middleware()` or //! Cors middleware could be used as parameter for `App::middleware()` or
//! `Resource::middleware()` methods. But you have to use `Cors::register()` method to //! `ResourceHandler::middleware()` methods. But you have to use
//! support *preflight* OPTIONS request. //! `Cors::for_app()` method to support *preflight* OPTIONS request.
//! //!
//! //!
//! # Example //! # Example
//! //!
//! ```rust //! ```rust
//! # extern crate http;
//! # extern crate actix_web; //! # extern crate actix_web;
//! # use actix_web::*; //! use actix_web::{http, App, HttpRequest, HttpResponse};
//! use http::header; //! use actix_web::middleware::cors::Cors;
//! use actix_web::middleware::cors;
//! //!
//! fn index(mut req: HttpRequest) -> &'static str { //! fn index(mut req: HttpRequest) -> &'static str {
//! "Hello world" //! "Hello world"
//! } //! }
//! //!
//! fn main() { //! fn main() {
//! let app = Application::new() //! let app = App::new()
//! .resource("/index.html", |r| { //! .configure(|app| Cors::for_app(app) // <- Construct CORS middleware builder
//! cors::Cors::build() // <- Construct CORS middleware
//! .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![header::AUTHORIZATION, header::ACCEPT]) //! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
//! .allowed_header(header::CONTENT_TYPE) //! .allowed_header(http::header::CONTENT_TYPE)
//! .max_age(3600) //! .max_age(3600)
//! .finish().expect("Can not create CORS middleware") //! .resource("/index.html", |r| {
//! .register(r); // <- Register CORS middleware //! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(Method::GET).f(|_| httpcodes::HttpOk); //! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
//! r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
//! }) //! })
//! .finish(); //! .register());
//! } //! }
//! ``` //! ```
//! In this example custom *CORS* middleware get registered for "/index.html" endpoint. //! In this example custom *CORS* middleware get registered for "/index.html" endpoint.
@ -49,16 +45,17 @@
//! Cors middleware automatically handle *OPTIONS* preflight request. //! Cors middleware automatically handle *OPTIONS* preflight request.
use std::collections::HashSet; use std::collections::HashSet;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::rc::Rc;
use http::{self, Method, HttpTryFrom, Uri}; use http::{self, Method, HttpTryFrom, Uri, StatusCode};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use application::App;
use error::{Result, ResponseError}; use error::{Result, ResponseError};
use resource::Resource; use resource::ResourceHandler;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use httpcodes::{HttpOk, HttpBadRequest};
use middleware::{Middleware, Response, Started}; use middleware::{Middleware, Response, Started};
/// A set of errors that can occur during processing CORS /// A set of errors that can occur during processing CORS
@ -94,23 +91,10 @@ pub enum CorsError {
HeadersNotAllowed, HeadersNotAllowed,
} }
/// A set of errors that can occur during building CORS middleware
#[derive(Debug, Fail)]
pub enum CorsBuilderError {
#[fail(display="Parse error: {}", _0)]
ParseError(http::Error),
/// Credentials are allowed, but the Origin is set to "*". This is not allowed by W3C
///
/// This is a misconfiguration. Check the documentation for `Cors`.
#[fail(display="Credentials are allowed, but the Origin is set to \"*\"")]
CredentialsWithWildcardOrigin,
}
impl ResponseError for CorsError { impl ResponseError for CorsError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpBadRequest.build().body(format!("{}", self)).unwrap() HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self))
} }
} }
@ -158,7 +142,12 @@ impl<T> AllOrSome<T> {
/// ///
/// The Cors struct contains the settings for CORS requests to be validated and /// The Cors struct contains the settings for CORS requests to be validated and
/// for responses to be generated. /// for responses to be generated.
#[derive(Clone)]
pub struct Cors { pub struct Cors {
inner: Rc<Inner>,
}
struct Inner {
methods: HashSet<Method>, methods: HashSet<Method>,
origins: AllOrSome<HashSet<String>>, origins: AllOrSome<HashSet<String>>,
origins_str: Option<HeaderValue>, origins_str: Option<HeaderValue>,
@ -173,7 +162,7 @@ pub struct Cors {
impl Default for Cors { impl Default for Cors {
fn default() -> Cors { fn default() -> Cors {
Cors { let inner = Inner {
origins: AllOrSome::default(), origins: AllOrSome::default(),
origins_str: None, origins_str: None,
methods: HashSet::from_iter( methods: HashSet::from_iter(
@ -187,14 +176,15 @@ impl Default for Cors {
send_wildcard: false, send_wildcard: false,
supports_credentials: false, supports_credentials: false,
vary_header: true, vary_header: true,
} };
Cors{inner: Rc::new(inner)}
} }
} }
impl Cors { impl Cors {
pub fn build() -> CorsBuilder { pub fn build() -> CorsBuilder<()> {
CorsBuilder { CorsBuilder {
cors: Some(Cors { cors: Some(Inner {
origins: AllOrSome::All, origins: AllOrSome::All,
origins_str: None, origins_str: None,
methods: HashSet::new(), methods: HashSet::new(),
@ -209,24 +199,66 @@ impl Cors {
methods: false, methods: false,
error: None, error: None,
expose_hdrs: HashSet::new(), expose_hdrs: HashSet::new(),
resources: Vec::new(),
app: None,
}
}
/// Create CorsBuilder for a specified application.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors;
///
/// fn main() {
/// let app = App::new()
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/")
/// .resource("/resource", |r| { // register resource
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// })
/// .register() // construct CORS and return application instance
/// );
/// }
/// ```
pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> {
CorsBuilder {
cors: Some(Inner {
origins: AllOrSome::All,
origins_str: None,
methods: HashSet::new(),
headers: AllOrSome::All,
expose_hdrs: None,
max_age: None,
preflight: true,
send_wildcard: false,
supports_credentials: false,
vary_header: true,
}),
methods: false,
error: None,
expose_hdrs: HashSet::new(),
resources: Vec::new(),
app: Some(app),
} }
} }
/// This method register cors middleware with resource and /// This method register cors middleware with resource and
/// adds route for *OPTIONS* preflight requests. /// adds route for *OPTIONS* preflight requests.
/// ///
/// It is possible to register *Cors* middleware with `Resource::middleware()` /// It is possible to register *Cors* middleware with `ResourceHandler::middleware()`
/// method, but in that case *Cors* middleware wont be able to handle *OPTIONS* /// method, but in that case *Cors* middleware wont be able to handle *OPTIONS*
/// requests. /// requests.
pub fn register<S: 'static>(self, resource: &mut Resource<S>) { pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) {
resource.method(Method::OPTIONS).h(HttpOk); resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok());
resource.middleware(self); resource.middleware(self);
} }
fn validate_origin<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> { fn validate_origin<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> {
if let Some(hdr) = req.headers().get(header::ORIGIN) { if let Some(hdr) = req.headers().get(header::ORIGIN) {
if let Ok(origin) = hdr.to_str() { if let Ok(origin) = hdr.to_str() {
return match self.origins { return match self.inner.origins {
AllOrSome::All => Ok(()), AllOrSome::All => Ok(()),
AllOrSome::Some(ref allowed_origins) => { AllOrSome::Some(ref allowed_origins) => {
allowed_origins allowed_origins
@ -238,7 +270,7 @@ impl Cors {
} }
Err(CorsError::BadOrigin) Err(CorsError::BadOrigin)
} else { } else {
return match self.origins { return match self.inner.origins {
AllOrSome::All => Ok(()), AllOrSome::All => Ok(()),
_ => Err(CorsError::MissingOrigin) _ => Err(CorsError::MissingOrigin)
} }
@ -249,7 +281,7 @@ impl Cors {
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
if let Ok(meth) = hdr.to_str() { if let Ok(meth) = hdr.to_str() {
if let Ok(method) = Method::try_from(meth) { if let Ok(method) = Method::try_from(meth) {
return self.methods.get(&method) return self.inner.methods.get(&method)
.and_then(|_| Some(())) .and_then(|_| Some(()))
.ok_or_else(|| CorsError::MethodNotAllowed); .ok_or_else(|| CorsError::MethodNotAllowed);
} }
@ -261,7 +293,7 @@ impl Cors {
} }
fn validate_allowed_headers<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> { fn validate_allowed_headers<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> {
match self.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().get(header::ACCESS_CONTROL_REQUEST_HEADERS) { if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) {
@ -291,13 +323,13 @@ impl Cors {
impl<S> Middleware<S> for Cors { impl<S> Middleware<S> for Cors {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> { fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
if self.preflight && Method::OPTIONS == *req.method() { if self.inner.preflight && Method::OPTIONS == *req.method() {
self.validate_origin(req)?; self.validate_origin(req)?;
self.validate_allowed_method(req)?; self.validate_allowed_method(req)?;
self.validate_allowed_headers(req)?; self.validate_allowed_headers(req)?;
// allowed headers // allowed headers
let headers = if let Some(headers) = self.headers.as_ref() { let headers = if let Some(headers) = self.inner.headers.as_ref() {
Some(HeaderValue::try_from(&headers.iter().fold( Some(HeaderValue::try_from(&headers.iter().fold(
String::new(), |s, v| s + "," + v.as_str()).as_str()[1..]).unwrap()) String::new(), |s, v| s + "," + v.as_str()).as_str()[1..]).unwrap())
} else if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) { } else if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) {
@ -307,14 +339,14 @@ impl<S> Middleware<S> for Cors {
}; };
Ok(Started::Response( Ok(Started::Response(
HttpOk.build() HttpResponse::Ok()
.if_some(self.max_age.as_ref(), |max_age, resp| { .if_some(self.inner.max_age.as_ref(), |max_age, resp| {
let _ = resp.header( let _ = resp.header(
header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());}) header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());})
.if_some(headers, |headers, resp| { .if_some(headers, |headers, resp| {
let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); }) let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); })
.if_true(self.origins.is_all(), |resp| { .if_true(self.inner.origins.is_all(), |resp| {
if self.send_wildcard { if self.inner.send_wildcard {
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
} else { } else {
let origin = req.headers().get(header::ORIGIN).unwrap(); let origin = req.headers().get(header::ORIGIN).unwrap();
@ -322,20 +354,19 @@ impl<S> Middleware<S> for Cors {
header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
} }
}) })
.if_true(self.origins.is_some(), |resp| { .if_true(self.inner.origins.is_some(), |resp| {
resp.header( resp.header(
header::ACCESS_CONTROL_ALLOW_ORIGIN, header::ACCESS_CONTROL_ALLOW_ORIGIN,
self.origins_str.as_ref().unwrap().clone()); self.inner.origins_str.as_ref().unwrap().clone());
}) })
.if_true(self.supports_credentials, |resp| { .if_true(self.inner.supports_credentials, |resp| {
resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
}) })
.header( .header(
header::ACCESS_CONTROL_ALLOW_METHODS, header::ACCESS_CONTROL_ALLOW_METHODS,
&self.methods.iter().fold( &self.inner.methods.iter().fold(
String::new(), |s, v| s + "," + v.as_str()).as_str()[1..]) String::new(), |s, v| s + "," + v.as_str()).as_str()[1..])
.finish() .finish()))
.unwrap()))
} else { } else {
self.validate_origin(req)?; self.validate_origin(req)?;
@ -344,9 +375,9 @@ impl<S> Middleware<S> for Cors {
} }
fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> { fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
match self.origins { match self.inner.origins {
AllOrSome::All => { AllOrSome::All => {
if self.send_wildcard { if self.inner.send_wildcard {
resp.headers_mut().insert( resp.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*")); header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"));
} else if let Some(origin) = req.headers().get(header::ORIGIN) { } else if let Some(origin) = req.headers().get(header::ORIGIN) {
@ -357,20 +388,20 @@ impl<S> Middleware<S> for Cors {
AllOrSome::Some(_) => { AllOrSome::Some(_) => {
resp.headers_mut().insert( resp.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_ORIGIN, header::ACCESS_CONTROL_ALLOW_ORIGIN,
self.origins_str.as_ref().unwrap().clone()); self.inner.origins_str.as_ref().unwrap().clone());
} }
} }
if let Some(ref expose) = self.expose_hdrs { if let Some(ref expose) = self.inner.expose_hdrs {
resp.headers_mut().insert( resp.headers_mut().insert(
header::ACCESS_CONTROL_EXPOSE_HEADERS, header::ACCESS_CONTROL_EXPOSE_HEADERS,
HeaderValue::try_from(expose.as_str()).unwrap()); HeaderValue::try_from(expose.as_str()).unwrap());
} }
if self.supports_credentials { if self.inner.supports_credentials {
resp.headers_mut().insert( resp.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_CREDENTIALS, HeaderValue::from_static("true")); header::ACCESS_CONTROL_ALLOW_CREDENTIALS, HeaderValue::from_static("true"));
} }
if self.vary_header { if self.inner.vary_header {
let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) { let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) {
let mut val: Vec<u8> = Vec::with_capacity(hdr.as_bytes().len() + 8); let mut val: Vec<u8> = Vec::with_capacity(hdr.as_bytes().len() + 8);
val.extend(hdr.as_bytes()); val.extend(hdr.as_bytes());
@ -408,24 +439,28 @@ impl<S> Middleware<S> for Cors {
/// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) /// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
/// .allowed_header(header::CONTENT_TYPE) /// .allowed_header(header::CONTENT_TYPE)
/// .max_age(3600) /// .max_age(3600)
/// .finish().unwrap(); /// .finish();
/// # } /// # }
/// ``` /// ```
pub struct CorsBuilder { pub struct CorsBuilder<S=()> {
cors: Option<Cors>, cors: Option<Inner>,
methods: bool, methods: bool,
error: Option<http::Error>, error: Option<http::Error>,
expose_hdrs: HashSet<HeaderName>, expose_hdrs: HashSet<HeaderName>,
resources: Vec<(String, ResourceHandler<S>)>,
app: Option<App<S>>,
} }
fn cors<'a>(parts: &'a mut Option<Cors>, err: &Option<http::Error>) -> Option<&'a mut Cors> { fn cors<'a>(parts: &'a mut Option<Inner>, err: &Option<http::Error>)
-> Option<&'a mut Inner>
{
if err.is_some() { if err.is_some() {
return None return None
} }
parts.as_mut() parts.as_mut()
} }
impl CorsBuilder { impl<S: 'static> CorsBuilder<S> {
/// Add an origin that are allowed to make requests. /// Add an origin that are allowed to make requests.
/// Will be verified against the `Origin` request header. /// Will be verified against the `Origin` request header.
@ -441,7 +476,9 @@ impl CorsBuilder {
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
/// ///
/// Defaults to `All`. /// Defaults to `All`.
pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder { ///
/// Builder panics if supplied origin is not valid uri.
pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder<S> {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
match Uri::try_from(origin) { match Uri::try_from(origin) {
Ok(_) => { Ok(_) => {
@ -467,7 +504,7 @@ impl CorsBuilder {
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
/// ///
/// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]` /// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]`
pub fn allowed_methods<U, M>(&mut self, methods: U) -> &mut CorsBuilder pub fn allowed_methods<U, M>(&mut self, methods: U) -> &mut CorsBuilder<S>
where U: IntoIterator<Item=M>, Method: HttpTryFrom<M> where U: IntoIterator<Item=M>, Method: HttpTryFrom<M>
{ {
self.methods = true; self.methods = true;
@ -488,7 +525,7 @@ impl CorsBuilder {
} }
/// Set an allowed header /// Set an allowed header
pub fn allowed_header<H>(&mut self, header: H) -> &mut CorsBuilder pub fn allowed_header<H>(&mut self, header: H) -> &mut CorsBuilder<S>
where HeaderName: HttpTryFrom<H> where HeaderName: HttpTryFrom<H>
{ {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
@ -517,7 +554,7 @@ impl CorsBuilder {
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
/// ///
/// Defaults to `All`. /// Defaults to `All`.
pub fn allowed_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder pub fn allowed_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder<S>
where U: IntoIterator<Item=H>, HeaderName: HttpTryFrom<H> where U: IntoIterator<Item=H>, HeaderName: HttpTryFrom<H>
{ {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
@ -548,7 +585,7 @@ impl CorsBuilder {
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
/// ///
/// This defaults to an empty set. /// This defaults to an empty set.
pub fn expose_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder pub fn expose_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder<S>
where U: IntoIterator<Item=H>, HeaderName: HttpTryFrom<H> where U: IntoIterator<Item=H>, HeaderName: HttpTryFrom<H>
{ {
for h in headers { for h in headers {
@ -569,7 +606,7 @@ impl CorsBuilder {
/// This value is set as the `Access-Control-Max-Age` header. /// This value is set as the `Access-Control-Max-Age` header.
/// ///
/// This defaults to `None` (unset). /// This defaults to `None` (unset).
pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder { pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder<S> {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
cors.max_age = Some(max_age) cors.max_age = Some(max_age)
} }
@ -590,7 +627,7 @@ impl CorsBuilder {
/// in an `Error::CredentialsWithWildcardOrigin` error during actix launch or runtime. /// in an `Error::CredentialsWithWildcardOrigin` error during actix launch or runtime.
/// ///
/// Defaults to `false`. /// Defaults to `false`.
pub fn send_wildcard(&mut self) -> &mut CorsBuilder { pub fn send_wildcard(&mut self) -> &mut CorsBuilder<S> {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
cors.send_wildcard = true cors.send_wildcard = true
} }
@ -606,7 +643,10 @@ impl CorsBuilder {
/// and `send_wildcards` set to `true`. /// and `send_wildcards` set to `true`.
/// ///
/// Defaults to `false`. /// Defaults to `false`.
pub fn supports_credentials(&mut self) -> &mut CorsBuilder { ///
/// Builder panics if credentials are allowed, but the Origin is set to "*".
/// This is not allowed by W3C
pub fn supports_credentials(&mut self) -> &mut CorsBuilder<S> {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
cors.supports_credentials = true cors.supports_credentials = true
} }
@ -624,7 +664,7 @@ impl CorsBuilder {
/// caches that the CORS headers are dynamic, and cannot be cached. /// caches that the CORS headers are dynamic, and cannot be cached.
/// ///
/// By default `vary` header support is enabled. /// By default `vary` header support is enabled.
pub fn disable_vary_header(&mut self) -> &mut CorsBuilder { pub fn disable_vary_header(&mut self) -> &mut CorsBuilder<S> {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
cors.vary_header = false cors.vary_header = false
} }
@ -637,15 +677,53 @@ impl CorsBuilder {
/// This is useful application level middleware. /// This is useful application level middleware.
/// ///
/// By default *preflight* support is enabled. /// By default *preflight* support is enabled.
pub fn disable_preflight(&mut self) -> &mut CorsBuilder { pub fn disable_preflight(&mut self) -> &mut CorsBuilder<S> {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
cors.preflight = false cors.preflight = false
} }
self self
} }
/// Finishes building and returns the built `Cors` instance. /// Configure resource for a specific path.
pub fn finish(&mut self) -> Result<Cors, CorsBuilderError> { ///
/// This is similar to a `App::resource()` method. Except, cors middleware
/// get registered for the resource.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors;
///
/// fn main() {
/// let app = App::new()
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/")
/// .allowed_methods(vec!["GET", "POST"])
/// .allowed_header(http::header::CONTENT_TYPE)
/// .max_age(3600)
/// .resource("/resource1", |r| { // register resource
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// })
/// .resource("/resource2", |r| { // register another resource
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .register() // construct CORS and return application instance
/// );
/// }
/// ```
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S>
where F: FnOnce(&mut ResourceHandler<S>) -> R + 'static
{
// add resource handler
let mut handler = ResourceHandler::default();
f(&mut handler);
self.resources.push((path.to_owned(), handler));
self
}
fn construct(&mut self) -> Cors {
if !self.methods { if !self.methods {
self.allowed_methods(vec![Method::GET, Method::HEAD, self.allowed_methods(vec![Method::GET, Method::HEAD,
Method::POST, Method::OPTIONS, Method::PUT, Method::POST, Method::OPTIONS, Method::PUT,
@ -653,13 +731,13 @@ impl CorsBuilder {
} }
if let Some(e) = self.error.take() { if let Some(e) = self.error.take() {
return Err(CorsBuilderError::ParseError(e)) panic!("{}", e);
} }
let mut cors = self.cors.take().expect("cannot reuse CorsBuilder"); let mut cors = self.cors.take().expect("cannot reuse CorsBuilder");
if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() { if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() {
return Err(CorsBuilderError::CredentialsWithWildcardOrigin) panic!("Credentials are allowed, but the Origin is set to \"*\"");
} }
if let AllOrSome::Some(ref origins) = cors.origins { if let AllOrSome::Some(ref origins) = cors.origins {
@ -672,7 +750,40 @@ impl CorsBuilder {
self.expose_hdrs.iter().fold( self.expose_hdrs.iter().fold(
String::new(), |s, v| s + v.as_str())[1..].to_owned()); String::new(), |s, v| s + v.as_str())[1..].to_owned());
} }
Ok(cors) Cors{inner: Rc::new(cors)}
}
/// Finishes building and returns the built `Cors` instance.
///
/// This method panics in case of any configuration error.
pub fn finish(&mut self) -> Cors {
if !self.resources.is_empty() {
panic!("CorsBuilder::resource() was used,
to construct CORS `.register(app)` method should be used");
}
self.construct()
}
/// Finishes building Cors middleware and register middleware for application
///
/// This method panics in case of any configuration error or if non of
/// resources are registered.
pub fn register(&mut self) -> App<S> {
if self.resources.is_empty() {
panic!("No resources are registered.");
}
let cors = self.construct();
let mut app = self.app.take().expect(
"CorsBuilder has to be constructed with Cors::for_app(app)");
// register resources
for (path, mut resource) in self.resources.drain(..) {
cors.clone().register(&mut resource);
app.register_resource(&path, resource);
}
app
} }
} }
@ -680,7 +791,7 @@ impl CorsBuilder {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use test::TestRequest; use test::{self, TestRequest};
impl Started { impl Started {
fn is_done(&self) -> bool { fn is_done(&self) -> bool {
@ -706,13 +817,29 @@ mod tests {
} }
#[test] #[test]
#[should_panic(expected = "CredentialsWithWildcardOrigin")] #[should_panic(expected = "Credentials are allowed, but the Origin is set to")]
fn cors_validates_illegal_allow_credentials() { fn cors_validates_illegal_allow_credentials() {
Cors::build() Cors::build()
.supports_credentials() .supports_credentials()
.send_wildcard() .send_wildcard()
.finish() .finish();
.unwrap(); }
#[test]
#[should_panic(expected = "No resources are registered")]
fn no_resource() {
Cors::build()
.supports_credentials()
.send_wildcard()
.register();
}
#[test]
#[should_panic(expected = "Cors::for_app(app)")]
fn no_resource2() {
Cors::build()
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.register();
} }
#[test] #[test]
@ -732,7 +859,7 @@ mod tests {
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.allowed_header(header::CONTENT_TYPE) .allowed_header(header::CONTENT_TYPE)
.finish().unwrap(); .finish();
let mut req = TestRequest::with_header( let mut req = TestRequest::with_header(
"Origin", "https://www.example.com") "Origin", "https://www.example.com")
@ -768,7 +895,7 @@ mod tests {
// &b"POST,GET,OPTIONS"[..], // &b"POST,GET,OPTIONS"[..],
// resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap().as_bytes()); // resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap().as_bytes());
cors.preflight = false; Rc::get_mut(&mut cors.inner).unwrap().preflight = false;
assert!(cors.start(&mut req).unwrap().is_done()); assert!(cors.start(&mut req).unwrap().is_done());
} }
@ -776,7 +903,7 @@ mod tests {
#[should_panic(expected = "MissingOrigin")] #[should_panic(expected = "MissingOrigin")]
fn test_validate_missing_origin() { fn test_validate_missing_origin() {
let cors = Cors::build() let cors = Cors::build()
.allowed_origin("https://www.example.com").finish().unwrap(); .allowed_origin("https://www.example.com").finish();
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
cors.start(&mut req).unwrap(); cors.start(&mut req).unwrap();
@ -786,7 +913,7 @@ mod tests {
#[should_panic(expected = "OriginNotAllowed")] #[should_panic(expected = "OriginNotAllowed")]
fn test_validate_not_allowed_origin() { fn test_validate_not_allowed_origin() {
let cors = Cors::build() let cors = Cors::build()
.allowed_origin("https://www.example.com").finish().unwrap(); .allowed_origin("https://www.example.com").finish();
let mut req = TestRequest::with_header("Origin", "https://www.unknown.com") let mut req = TestRequest::with_header("Origin", "https://www.unknown.com")
.method(Method::GET) .method(Method::GET)
@ -797,7 +924,7 @@ mod tests {
#[test] #[test]
fn test_validate_origin() { fn test_validate_origin() {
let cors = Cors::build() let cors = Cors::build()
.allowed_origin("https://www.example.com").finish().unwrap(); .allowed_origin("https://www.example.com").finish();
let mut req = TestRequest::with_header("Origin", "https://www.example.com") let mut req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::GET) .method(Method::GET)
@ -808,10 +935,10 @@ mod tests {
#[test] #[test]
fn test_no_origin_response() { fn test_no_origin_response() {
let cors = Cors::build().finish().unwrap(); let cors = Cors::build().finish();
let mut req = TestRequest::default().method(Method::GET).finish(); let mut req = TestRequest::default().method(Method::GET).finish();
let resp: HttpResponse = HttpOk.into(); let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
assert!(resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).is_none()); assert!(resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).is_none());
@ -834,14 +961,14 @@ mod tests {
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.allowed_header(header::CONTENT_TYPE) .allowed_header(header::CONTENT_TYPE)
.finish().unwrap(); .finish();
let mut req = TestRequest::with_header( let mut req = TestRequest::with_header(
"Origin", "https://www.example.com") "Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.finish(); .finish();
let resp: HttpResponse = HttpOk.into(); let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
@ -850,9 +977,9 @@ mod tests {
&b"Origin"[..], &b"Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes()); resp.headers().get(header::VARY).unwrap().as_bytes());
let resp: HttpResponse = HttpOk.build() let resp: HttpResponse = HttpResponse::Ok()
.header(header::VARY, "Accept") .header(header::VARY, "Accept")
.finish().unwrap(); .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"[..],
@ -861,11 +988,31 @@ mod tests {
let cors = Cors::build() let cors = Cors::build()
.disable_vary_header() .disable_vary_header()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.finish().unwrap(); .finish();
let resp: HttpResponse = HttpOk.into(); let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"https://www.example.com"[..], &b"https://www.example.com"[..],
resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()); resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes());
} }
#[test]
fn cors_resource() {
let mut srv = test::TestServer::with_factory(
|| App::new()
.configure(
|app| Cors::for_app(app)
.allowed_origin("https://www.example.com")
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.register()));
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv.get().uri(srv.url("/test"))
.header("ORIGIN", "https://www.example.com").finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
} }

View File

@ -14,7 +14,7 @@
//! * There is no `Origin` header but the `Referer` header matches one of //! * There is no `Origin` header but the `Referer` header matches one of
//! the allowed origins. //! the allowed origins.
//! //!
//! Use [`CsrfFilterBuilder::allow_xhr()`](struct.CsrfFilterBuilder.html#method.allow_xhr) //! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr)
//! if you want to allow requests with unsafe methods via //! if you want to allow requests with unsafe methods via
//! [CORS](../cors/struct.Cors.html). //! [CORS](../cors/struct.Cors.html).
//! //!
@ -22,23 +22,21 @@
//! //!
//! ``` //! ```
//! # extern crate actix_web; //! # extern crate actix_web;
//! # use actix_web::*; //! use actix_web::{http, App, HttpRequest, HttpResponse};
//!
//! use actix_web::middleware::csrf; //! use actix_web::middleware::csrf;
//! //!
//! fn handle_post(_req: HttpRequest) -> &'static str { //! fn handle_post(_: HttpRequest) -> &'static str {
//! "This action should only be triggered with requests from the same site" //! "This action should only be triggered with requests from the same site"
//! } //! }
//! //!
//! fn main() { //! fn main() {
//! let app = Application::new() //! let app = App::new()
//! .middleware( //! .middleware(
//! csrf::CsrfFilter::build() //! csrf::CsrfFilter::new()
//! .allowed_origin("https://www.example.com") //! .allowed_origin("https://www.example.com"))
//! .finish())
//! .resource("/", |r| { //! .resource("/", |r| {
//! r.method(Method::GET).f(|_| httpcodes::HttpOk); //! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(Method::POST).f(handle_post); //! r.method(http::Method::POST).f(handle_post);
//! }) //! })
//! .finish(); //! .finish();
//! } //! }
@ -52,10 +50,9 @@ use std::collections::HashSet;
use bytes::Bytes; use bytes::Bytes;
use error::{Result, ResponseError}; use error::{Result, ResponseError};
use http::{HeaderMap, HttpTryFrom, Uri, header}; use http::{HeaderMap, HttpTryFrom, Uri, header};
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use httpmessage::HttpMessage;
use httpcodes::HttpForbidden;
use middleware::{Middleware, Started}; use middleware::{Middleware, Started};
/// Potential cross-site request forgery detected. /// Potential cross-site request forgery detected.
@ -74,7 +71,7 @@ pub enum CsrfError {
impl ResponseError for CsrfError { impl ResponseError for CsrfError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpForbidden.build().body(self.to_string()).unwrap() HttpResponse::Forbidden().body(self.to_string())
} }
} }
@ -112,6 +109,29 @@ fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
} }
/// A middleware that filters cross-site requests. /// A middleware that filters cross-site requests.
///
/// To construct a CSRF filter:
///
/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to
/// start building.
/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed
/// origins.
/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve
/// the constructed filter.
///
/// # Example
///
/// ```
/// use actix_web::App;
/// use actix_web::middleware::csrf;
///
/// # fn main() {
/// let app = App::new().middleware(
/// csrf::CsrfFilter::new()
/// .allowed_origin("https://www.example.com"));
/// # }
/// ```
#[derive(Default)]
pub struct CsrfFilter { pub struct CsrfFilter {
origins: HashSet<String>, origins: HashSet<String>,
allow_xhr: bool, allow_xhr: bool,
@ -121,15 +141,53 @@ pub struct CsrfFilter {
impl CsrfFilter { impl CsrfFilter {
/// Start building a `CsrfFilter`. /// Start building a `CsrfFilter`.
pub fn build() -> CsrfFilterBuilder { pub fn new() -> CsrfFilter {
CsrfFilterBuilder { CsrfFilter {
csrf: CsrfFilter {
origins: HashSet::new(), origins: HashSet::new(),
allow_xhr: false, allow_xhr: false,
allow_missing_origin: false, allow_missing_origin: false,
allow_upgrade: false, allow_upgrade: false,
} }
} }
/// Add an origin that is allowed to make requests. Will be verified
/// against the `Origin` request header.
pub fn allowed_origin(mut self, origin: &str) -> CsrfFilter {
self.origins.insert(origin.to_owned());
self
}
/// Allow all requests with an `X-Requested-With` header.
///
/// A cross-site attacker should not be able to send requests with custom
/// headers unless a CORS policy whitelists them. Therefore it should be
/// safe to allow requests with an `X-Requested-With` header (added
/// automatically by many JavaScript libraries).
///
/// This is disabled by default, because in Safari it is possible to
/// circumvent this using redirects and Flash.
///
/// Use this method to enable more lax filtering.
pub fn allow_xhr(mut self) -> CsrfFilter {
self.allow_xhr = true;
self
}
/// Allow requests if the expected `Origin` header is missing (and
/// there is no `Referer` to fall back on).
///
/// The filter is conservative by default, but it should be safe to allow
/// missing `Origin` headers because a cross-site attacker cannot prevent
/// the browser from sending `Origin` on unsafe requests.
pub fn allow_missing_origin(mut self) -> CsrfFilter {
self.allow_missing_origin = true;
self
}
/// Allow cross-site upgrade requests (for example to open a WebSocket).
pub fn allow_upgrade(mut self) -> CsrfFilter {
self.allow_upgrade = true;
self
} }
fn validate<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CsrfError> { fn validate<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CsrfError> {
@ -159,77 +217,6 @@ impl<S> Middleware<S> for CsrfFilter {
} }
} }
/// Used to build a `CsrfFilter`.
///
/// To construct a CSRF filter:
///
/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to
/// start building.
/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed
/// origins.
/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve
/// the constructed filter.
///
/// # Example
///
/// ```
/// use actix_web::middleware::csrf;
///
/// let csrf = csrf::CsrfFilter::build()
/// .allowed_origin("https://www.example.com")
/// .finish();
/// ```
pub struct CsrfFilterBuilder {
csrf: CsrfFilter,
}
impl CsrfFilterBuilder {
/// Add an origin that is allowed to make requests. Will be verified
/// against the `Origin` request header.
pub fn allowed_origin(mut self, origin: &str) -> CsrfFilterBuilder {
self.csrf.origins.insert(origin.to_owned());
self
}
/// Allow all requests with an `X-Requested-With` header.
///
/// A cross-site attacker should not be able to send requests with custom
/// headers unless a CORS policy whitelists them. Therefore it should be
/// safe to allow requests with an `X-Requested-With` header (added
/// automatically by many JavaScript libraries).
///
/// This is disabled by default, because in Safari it is possible to
/// circumvent this using redirects and Flash.
///
/// Use this method to enable more lax filtering.
pub fn allow_xhr(mut self) -> CsrfFilterBuilder {
self.csrf.allow_xhr = true;
self
}
/// Allow requests if the expected `Origin` header is missing (and
/// there is no `Referer` to fall back on).
///
/// The filter is conservative by default, but it should be safe to allow
/// missing `Origin` headers because a cross-site attacker cannot prevent
/// the browser from sending `Origin` on unsafe requests.
pub fn allow_missing_origin(mut self) -> CsrfFilterBuilder {
self.csrf.allow_missing_origin = true;
self
}
/// Allow cross-site upgrade requests (for example to open a WebSocket).
pub fn allow_upgrade(mut self) -> CsrfFilterBuilder {
self.csrf.allow_upgrade = true;
self
}
/// Finishes building the `CsrfFilter` instance.
pub fn finish(self) -> CsrfFilter {
self.csrf
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -238,9 +225,8 @@ mod tests {
#[test] #[test]
fn test_safe() { fn test_safe() {
let csrf = CsrfFilter::build() let csrf = CsrfFilter::new()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com");
.finish();
let mut req = TestRequest::with_header("Origin", "https://www.w3.org") let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::HEAD) .method(Method::HEAD)
@ -251,9 +237,8 @@ mod tests {
#[test] #[test]
fn test_csrf() { fn test_csrf() {
let csrf = CsrfFilter::build() let csrf = CsrfFilter::new()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com");
.finish();
let mut req = TestRequest::with_header("Origin", "https://www.w3.org") let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::POST) .method(Method::POST)
@ -264,9 +249,8 @@ mod tests {
#[test] #[test]
fn test_referer() { fn test_referer() {
let csrf = CsrfFilter::build() let csrf = CsrfFilter::new()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com");
.finish();
let mut req = TestRequest::with_header("Referer", "https://www.example.com/some/path?query=param") let mut req = TestRequest::with_header("Referer", "https://www.example.com/some/path?query=param")
.method(Method::POST) .method(Method::POST)
@ -277,14 +261,12 @@ mod tests {
#[test] #[test]
fn test_upgrade() { fn test_upgrade() {
let strict_csrf = CsrfFilter::build() let strict_csrf = CsrfFilter::new()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com");
.finish();
let lax_csrf = CsrfFilter::build() let lax_csrf = CsrfFilter::new()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.allow_upgrade() .allow_upgrade();
.finish();
let mut req = TestRequest::with_header("Origin", "https://cswsh.com") let mut req = TestRequest::with_header("Origin", "https://cswsh.com")
.header("Connection", "Upgrade") .header("Connection", "Upgrade")

View File

@ -13,17 +13,16 @@ use middleware::{Response, Middleware};
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::*; /// use actix_web::{http, middleware, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = App::new()
/// .middleware( /// .middleware(
/// middleware::DefaultHeaders::build() /// middleware::DefaultHeaders::new()
/// .header("X-Version", "0.2") /// .header("X-Version", "0.2"))
/// .finish())
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.method(Method::GET).f(|_| httpcodes::HttpOk); /// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed); /// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
@ -33,9 +32,41 @@ pub struct DefaultHeaders{
headers: HeaderMap, headers: HeaderMap,
} }
impl Default for DefaultHeaders {
fn default() -> Self {
DefaultHeaders{ct: false, headers: HeaderMap::new()}
}
}
impl DefaultHeaders { impl DefaultHeaders {
pub fn build() -> DefaultHeadersBuilder { /// Construct `DefaultHeaders` middleware.
DefaultHeadersBuilder{ct: false, headers: Some(HeaderMap::new())} pub fn new() -> DefaultHeaders {
DefaultHeaders::default()
}
/// Set a header.
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))]
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where HeaderName: HttpTryFrom<K>,
HeaderValue: HttpTryFrom<V>
{
match HeaderName::try_from(key) {
Ok(key) => {
match HeaderValue::try_from(value) {
Ok(value) => { self.headers.append(key, value); }
Err(_) => panic!("Can not create header value"),
}
},
Err(_) => panic!("Can not create header name"),
}
self
}
/// Set *CONTENT-TYPE* header if response does not contain this header.
pub fn content_type(mut self) -> Self {
self.ct = true;
self
} }
} }
@ -56,49 +87,6 @@ impl<S> Middleware<S> for DefaultHeaders {
} }
} }
/// Structure that follows the builder pattern for building `DefaultHeaders` middleware.
#[derive(Debug)]
pub struct DefaultHeadersBuilder {
ct: bool,
headers: Option<HeaderMap>,
}
impl DefaultHeadersBuilder {
/// Set a header.
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))]
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where HeaderName: HttpTryFrom<K>,
HeaderValue: HttpTryFrom<V>
{
if let Some(ref mut headers) = self.headers {
match HeaderName::try_from(key) {
Ok(key) => {
match HeaderValue::try_from(value) {
Ok(value) => { headers.append(key, value); }
Err(_) => panic!("Can not create header value"),
}
},
Err(_) => panic!("Can not create header name"),
};
}
self
}
/// Set *CONTENT-TYPE* header if response does not contain this header.
pub fn content_type(&mut self) -> &mut Self {
self.ct = true;
self
}
/// Finishes building and returns the built `DefaultHeaders` middleware.
pub fn finish(&mut self) -> DefaultHeaders {
let headers = self.headers.take().expect("cannot reuse middleware builder");
DefaultHeaders{ ct: self.ct, headers }
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -106,20 +94,19 @@ mod tests {
#[test] #[test]
fn test_default_headers() { fn test_default_headers() {
let mw = DefaultHeaders::build() let mw = DefaultHeaders::new()
.header(CONTENT_TYPE, "0001") .header(CONTENT_TYPE, "0001");
.finish();
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
let resp = HttpResponse::Ok().finish().unwrap(); let resp = HttpResponse::Ok().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!(),
}; };
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish().unwrap(); let resp = HttpResponse::Ok().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

@ -0,0 +1,114 @@
use std::collections::HashMap;
use error::Result;
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response};
type ErrorHandler<S> = Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response>;
/// `Middleware` for allowing custom handlers for responses.
///
/// You can use `ErrorHandlers::handler()` method to register a custom error handler
/// for specific status code. You can modify existing response or create completly new
/// one.
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpRequest, HttpResponse, Result};
/// use actix_web::middleware::{Response, ErrorHandlers};
///
/// fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
/// let mut builder = resp.into_builder();
/// builder.header(http::header::CONTENT_TYPE, "application/json");
/// Ok(Response::Done(builder.into()))
/// }
///
/// fn main() {
/// let app = App::new()
/// .middleware(
/// ErrorHandlers::new()
/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500))
/// .resource("/test", |r| {
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .finish();
/// }
/// ```
pub struct ErrorHandlers<S> {
handlers: HashMap<StatusCode, Box<ErrorHandler<S>>>,
}
impl<S> Default for ErrorHandlers<S> {
fn default() -> Self {
ErrorHandlers {
handlers: HashMap::new(),
}
}
}
impl<S> ErrorHandlers<S> {
/// Construct new `ErrorHandlers` instance
pub fn new() -> Self {
ErrorHandlers::default()
}
/// Register error handler for specified status code
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
where F: Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response> + 'static
{
self.handlers.insert(status, Box::new(handler));
self
}
}
impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
fn response(&self, req: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(handler) = self.handlers.get(&resp.status()) {
handler(req, resp)
} else {
Ok(Response::Done(resp))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::StatusCode;
use http::header::CONTENT_TYPE;
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
let mut builder = resp.into_builder();
builder.header(CONTENT_TYPE, "0001");
Ok(Response::Done(builder.into()))
}
#[test]
fn test_handler() {
let mw = ErrorHandlers::new()
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mut req = HttpRequest::default();
let resp = HttpResponse::InternalServerError().finish();
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let resp = HttpResponse::Ok().finish();
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert!(!resp.headers().contains_key(CONTENT_TYPE));
}
}

View File

@ -29,14 +29,14 @@ use middleware::{Middleware, Started, Finished};
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// extern crate env_logger; /// extern crate env_logger;
/// use actix_web::Application; /// use actix_web::App;
/// use actix_web::middleware::Logger; /// use actix_web::middleware::Logger;
/// ///
/// fn main() { /// fn main() {
/// std::env::set_var("RUST_LOG", "actix_web=info"); /// std::env::set_var("RUST_LOG", "actix_web=info");
/// env_logger::init(); /// env_logger::init();
/// ///
/// let app = Application::new() /// let app = App::new()
/// .middleware(Logger::default()) /// .middleware(Logger::default())
/// .middleware(Logger::new("%a %{User-Agent}i")) /// .middleware(Logger::new("%a %{User-Agent}i"))
/// .finish(); /// .finish();
@ -231,18 +231,14 @@ impl FormatText {
FormatText::ResponseSize => resp.response_size().fmt(fmt), FormatText::ResponseSize => resp.response_size().fmt(fmt),
FormatText::Pid => unsafe{libc::getpid().fmt(fmt)}, FormatText::Pid => unsafe{libc::getpid().fmt(fmt)},
FormatText::Time => { FormatText::Time => {
let response_time = time::now() - entry_time; let rt = time::now() - entry_time;
let response_time = response_time.num_seconds() as f64 + let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0;
(response_time.num_nanoseconds().unwrap_or(0) as f64) / 1000000000.0; fmt.write_fmt(format_args!("{:.6}", rt))
fmt.write_fmt(format_args!("{:.6}", response_time))
}, },
FormatText::TimeMillis => { FormatText::TimeMillis => {
let response_time = time::now() - entry_time; let rt = time::now() - entry_time;
let response_time_ms = (response_time.num_seconds() * 1000) as f64 + let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0;
(response_time.num_nanoseconds().unwrap_or(0) as f64) / 1000000.0; fmt.write_fmt(format_args!("{:.6}", rt))
fmt.write_fmt(format_args!("{:.6}", response_time_ms))
}, },
FormatText::RemoteAddr => { FormatText::RemoteAddr => {
if let Some(remote) = req.connection_info().remote() { if let Some(remote) = req.connection_info().remote() {
@ -294,7 +290,6 @@ impl<'a> fmt::Display for FormatDisplay<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use Body;
use super::*; use super::*;
use std::str::FromStr; use std::str::FromStr;
use time; use time;
@ -311,7 +306,8 @@ mod tests {
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt") .header("X-Test", "ttt")
.force_close().body(Body::Empty).unwrap(); .force_close()
.finish();
match logger.start(&mut req) { match logger.start(&mut req) {
Ok(Started::Done) => (), Ok(Started::Done) => (),
@ -323,7 +319,7 @@ mod tests {
} }
let entry_time = time::now(); let entry_time = time::now();
let render = |fmt: &mut Formatter| { let render = |fmt: &mut Formatter| {
for unit in logger.format.0.iter() { for unit in &logger.format.0 {
unit.render(fmt, &req, &resp, entry_time)?; unit.render(fmt, &req, &resp, entry_time)?;
} }
Ok(()) Ok(())
@ -340,12 +336,11 @@ mod tests {
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB")); headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"));
let req = HttpRequest::new( let req = HttpRequest::new(
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
.force_close().body(Body::Empty).unwrap();
let entry_time = time::now(); let entry_time = time::now();
let render = |fmt: &mut Formatter| { let render = |fmt: &mut Formatter| {
for unit in format.0.iter() { for unit in &format.0 {
unit.render(fmt, &req, &resp, entry_time)?; unit.render(fmt, &req, &resp, entry_time)?;
} }
Ok(()) Ok(())
@ -358,12 +353,11 @@ mod tests {
let req = HttpRequest::new( let req = HttpRequest::new(
Method::GET, Uri::from_str("/?test").unwrap(), Method::GET, Uri::from_str("/?test").unwrap(),
Version::HTTP_11, HeaderMap::new(), None); Version::HTTP_11, HeaderMap::new(), None);
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
.force_close().body(Body::Empty).unwrap();
let entry_time = time::now(); let entry_time = time::now();
let render = |fmt: &mut Formatter| { let render = |fmt: &mut Formatter| {
for unit in format.0.iter() { for unit in &format.0 {
unit.render(fmt, &req, &resp, entry_time)?; unit.render(fmt, &req, &resp, entry_time)?;
} }
Ok(()) Ok(())

View File

@ -10,14 +10,16 @@ mod logger;
#[cfg(feature = "session")] #[cfg(feature = "session")]
mod session; mod session;
mod defaultheaders; mod defaultheaders;
mod errhandlers;
pub mod cors; pub mod cors;
pub mod csrf; pub mod csrf;
pub use self::logger::Logger; pub use self::logger::Logger;
pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder}; pub use self::errhandlers::ErrorHandlers;
pub use self::defaultheaders::DefaultHeaders;
#[cfg(feature = "session")] #[cfg(feature = "session")]
pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage, pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage,
CookieSessionError, CookieSessionBackend, CookieSessionBackendBuilder}; CookieSessionError, CookieSessionBackend};
/// Middleware start result /// Middleware start result
pub enum Started { pub enum Started {

View File

@ -7,6 +7,7 @@ use serde_json;
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use http::header::{self, HeaderValue}; use http::header::{self, HeaderValue};
use time::Duration;
use cookie::{CookieJar, Cookie, Key}; use cookie::{CookieJar, Cookie, Key};
use futures::Future; use futures::Future;
use futures::future::{FutureResult, ok as FutOk, err as FutErr}; use futures::future::{FutureResult, ok as FutOk, err as FutErr};
@ -114,15 +115,14 @@ unsafe impl Sync for SessionImplBox {}
/// ```rust /// ```rust
/// # extern crate actix; /// # extern crate actix;
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::middleware::{SessionStorage, CookieSessionBackend}; /// use actix_web::App;
/// use actix_web::*; /// use actix_web::middleware::{SessionStorage, CookieSessionBackend};
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new().middleware( /// let app = App::new().middleware(
/// SessionStorage::new( // <- create session middleware /// SessionStorage::new( // <- create session middleware
/// CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend /// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend
/// .secure(false) /// .secure(false))
/// .finish())
/// ); /// );
/// } /// }
/// ``` /// ```
@ -257,23 +257,33 @@ impl SessionImpl for CookieSession {
} }
} }
enum CookieSecurity {
Signed,
Private
}
struct CookieSessionInner { struct CookieSessionInner {
key: Key, key: Key,
security: CookieSecurity,
name: String, name: String,
path: String, path: String,
domain: Option<String>, domain: Option<String>,
secure: bool, secure: bool,
max_age: Option<Duration>,
} }
impl CookieSessionInner { impl CookieSessionInner {
fn new(key: &[u8]) -> CookieSessionInner { fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner {
CookieSessionInner { CookieSessionInner {
security,
key: Key::from_master(key), key: Key::from_master(key),
name: "actix-session".to_owned(), name: "actix-session".to_owned(),
path: "/".to_owned(), path: "/".to_owned(),
domain: None, domain: None,
secure: true } secure: true,
max_age: None,
}
} }
fn set_cookie(&self, resp: &mut HttpResponse, state: &HashMap<String, String>) -> Result<()> { fn set_cookie(&self, resp: &mut HttpResponse, state: &HashMap<String, String>) -> Result<()> {
@ -292,8 +302,16 @@ impl CookieSessionInner {
cookie.set_domain(domain.clone()); cookie.set_domain(domain.clone());
} }
if let Some(max_age) = self.max_age {
cookie.set_max_age(max_age);
}
let mut jar = CookieJar::new(); let mut jar = CookieJar::new();
jar.signed(&self.key).add(cookie);
match self.security {
CookieSecurity::Signed => jar.signed(&self.key).add(cookie),
CookieSecurity::Private => jar.private(&self.key).add(cookie),
}
for cookie in jar.delta() { for cookie in jar.delta() {
let val = HeaderValue::from_str(&cookie.to_string())?; let val = HeaderValue::from_str(&cookie.to_string())?;
@ -309,7 +327,12 @@ impl CookieSessionInner {
if cookie.name() == self.name { if cookie.name() == self.name {
let mut jar = CookieJar::new(); let mut jar = CookieJar::new();
jar.add_original(cookie.clone()); jar.add_original(cookie.clone());
if let Some(cookie) = jar.signed(&self.key).get(&self.name) {
let cookie_opt = match self.security {
CookieSecurity::Signed => jar.signed(&self.key).get(&self.name),
CookieSecurity::Private => jar.private(&self.key).get(&self.name),
};
if let Some(cookie) = cookie_opt {
if let Ok(val) = serde_json::from_str(cookie.value()) { if let Ok(val) = serde_json::from_str(cookie.value()) {
return val; return val;
} }
@ -321,43 +344,90 @@ impl CookieSessionInner {
} }
} }
/// Use signed cookies as session storage. /// Use cookies for session storage.
/// ///
/// `CookieSessionBackend` creates sessions which are limited to storing /// `CookieSessionBackend` creates sessions which are limited to storing
/// fewer than 4000 bytes of data (as the payload must fit into a single cookie). /// fewer than 4000 bytes of data (as the payload must fit into a single cookie).
/// Internal server error get generated if session contains more than 4000 bytes. /// An Internal Server Error is generated if the session contains more than 4000 bytes.
/// ///
/// You need to pass a random value to the constructor of `CookieSessionBackend`. /// A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor.
/// This is private key for cookie session, When this value is changed, all session data is lost.
/// ///
/// Note that whatever you write into your session is visible by the user (but not modifiable). /// A *signed* cookie is stored on the client as plaintext alongside
/// a signature such that the cookie may be viewed but not modified by the client.
/// ///
/// Constructor panics if key length is less than 32 bytes. /// A *private* cookie is stored on the client as encrypted text
/// such that it may neither be viewed nor modified by the client.
///
/// The constructors take a key as an argument.
/// This is the private key for cookie session - when this value is changed, all session data is lost.
/// The constructors will panic if the key is less than 32 bytes in length.
///
///
/// # Example
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::CookieSessionBackend;
///
/// # fn main() {
/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32])
/// .domain("www.rust-lang.org")
/// .name("actix_session")
/// .path("/")
/// .secure(true);
/// # }
/// ```
pub struct CookieSessionBackend(Rc<CookieSessionInner>); pub struct CookieSessionBackend(Rc<CookieSessionInner>);
impl CookieSessionBackend { impl CookieSessionBackend {
/// Construct new `CookieSessionBackend` instance. /// Construct new *signed* `CookieSessionBackend` instance.
/// ///
/// Panics if key length is less than 32 bytes. /// Panics if key length is less than 32 bytes.
pub fn new(key: &[u8]) -> CookieSessionBackend { pub fn signed(key: &[u8]) -> CookieSessionBackend {
CookieSessionBackend( CookieSessionBackend(
Rc::new(CookieSessionInner::new(key))) Rc::new(CookieSessionInner::new(key, CookieSecurity::Signed)))
} }
/// Creates a new `CookieSessionBackendBuilder` instance from the given key. /// Construct new *private* `CookieSessionBackend` instance.
/// ///
/// Panics if key length is less than 32 bytes. /// Panics if key length is less than 32 bytes.
pub fn private(key: &[u8]) -> CookieSessionBackend {
CookieSessionBackend(
Rc::new(CookieSessionInner::new(key, CookieSecurity::Private)))
}
/// Sets the `path` field in the session cookie being built.
pub fn path<S: Into<String>>(mut self, value: S) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().path = value.into();
self
}
/// Sets the `name` field in the session cookie being built.
pub fn name<S: Into<String>>(mut self, value: S) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().name = value.into();
self
}
/// Sets the `domain` field in the session cookie being built.
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
self
}
/// Sets the `secure` field in the session cookie being built.
/// ///
/// # Example /// If the `secure` field is set, a cookie will only be transmitted when the
/// /// connection is secure - i.e. `https`
/// ``` pub fn secure(mut self, value: bool) -> CookieSessionBackend {
/// use actix_web::middleware::CookieSessionBackend; Rc::get_mut(&mut self.0).unwrap().secure = value;
/// self
/// let backend = CookieSessionBackend::build(&[0; 32]).finish(); }
/// ```
pub fn build(key: &[u8]) -> CookieSessionBackendBuilder { /// Sets the `max-age` field in the session cookie being built.
CookieSessionBackendBuilder::new(key) pub fn max_age(mut self, value: Duration) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self
} }
} }
@ -376,64 +446,3 @@ impl<S> SessionBackend<S> for CookieSessionBackend {
}) })
} }
} }
/// Structure that follows the builder pattern for building `CookieSessionBackend` structs.
///
/// To construct a backend:
///
/// 1. Call [`CookieSessionBackend::build`](struct.CookieSessionBackend.html#method.build) to start building.
/// 2. Use any of the builder methods to set fields in the backend.
/// 3. Call [finish](#method.finish) to retrieve the constructed backend.
///
/// # Example
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::CookieSessionBackend;
///
/// # fn main() {
/// let backend: CookieSessionBackend = CookieSessionBackend::build(&[0; 32])
/// .domain("www.rust-lang.org")
/// .name("actix_session")
/// .path("/")
/// .secure(true)
/// .finish();
/// # }
/// ```
pub struct CookieSessionBackendBuilder(CookieSessionInner);
impl CookieSessionBackendBuilder {
pub fn new(key: &[u8]) -> CookieSessionBackendBuilder {
CookieSessionBackendBuilder(
CookieSessionInner::new(key))
}
/// Sets the `path` field in the session cookie being built.
pub fn path<S: Into<String>>(mut self, value: S) -> CookieSessionBackendBuilder {
self.0.path = value.into();
self
}
/// Sets the `name` field in the session cookie being built.
pub fn name<S: Into<String>>(mut self, value: S) -> CookieSessionBackendBuilder {
self.0.name = value.into();
self
}
/// Sets the `domain` field in the session cookie being built.
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieSessionBackendBuilder {
self.0.domain = Some(value.into());
self
}
/// Sets the `secure` field in the session cookie being built.
pub fn secure(mut self, value: bool) -> CookieSessionBackendBuilder {
self.0.secure = value;
self
}
/// Finishes building and returns the built `CookieSessionBackend`.
pub fn finish(self) -> CookieSessionBackend {
CookieSessionBackend(Rc::new(self.0))
}
}

View File

@ -396,11 +396,11 @@ impl<S> Stream for Field<S> where S: Stream<Item=Bytes, Error=PayloadError> {
impl<S> fmt::Debug for Field<S> { impl<S> fmt::Debug for Field<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nMultipartField: {}\n", self.ct); let res = writeln!(f, "\nMultipartField: {}", self.ct);
let _ = write!(f, " boundary: {}\n", self.inner.borrow().boundary); let _ = writeln!(f, " boundary: {}", self.inner.borrow().boundary);
let _ = write!(f, " headers:\n"); let _ = writeln!(f, " headers:");
for (key, val) in self.headers.iter() { for (key, val) in self.headers.iter() {
let _ = write!(f, " {:?}: {:?}\n", key, val); let _ = writeln!(f, " {:?}: {:?}", key, val);
} }
res res
} }
@ -632,7 +632,7 @@ mod tests {
let headers = HeaderMap::new(); let headers = HeaderMap::new();
match Multipart::boundary(&headers) { match Multipart::boundary(&headers) {
Err(MultipartError::NoContentType) => (), Err(MultipartError::NoContentType) => (),
_ => panic!("should not happen"), _ => unreachable!("should not happen"),
} }
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
@ -641,7 +641,7 @@ mod tests {
match Multipart::boundary(&headers) { match Multipart::boundary(&headers) {
Err(MultipartError::ParseContentType) => (), Err(MultipartError::ParseContentType) => (),
_ => panic!("should not happen"), _ => unreachable!("should not happen"),
} }
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
@ -650,7 +650,7 @@ mod tests {
header::HeaderValue::from_static("multipart/mixed")); header::HeaderValue::from_static("multipart/mixed"));
match Multipart::boundary(&headers) { match Multipart::boundary(&headers) {
Err(MultipartError::Boundary) => (), Err(MultipartError::Boundary) => (),
_ => panic!("should not happen"), _ => unreachable!("should not happen"),
} }
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();

View File

@ -4,9 +4,10 @@ use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::slice::Iter; use std::slice::Iter;
use std::borrow::Cow; use std::borrow::Cow;
use http::StatusCode;
use smallvec::SmallVec; use smallvec::SmallVec;
use error::{ResponseError, UriSegmentError, InternalError, ErrorBadRequest}; use error::{ResponseError, UriSegmentError, InternalError};
/// A trait to abstract the idea of creating a new instance of a type from a path parameter. /// A trait to abstract the idea of creating a new instance of a type from a path parameter.
@ -45,6 +46,11 @@ impl<'a> Params<'a> {
self.0.is_empty() self.0.is_empty()
} }
/// Check number of extracted parameters
pub fn len(&self) -> usize {
self.0.len()
}
/// Get matched parameter by name without type conversion /// Get matched parameter by name without type conversion
pub fn get(&'a self, key: &str) -> Option<&'a str> { pub fn get(&'a self, key: &str) -> Option<&'a str> {
for item in self.0.iter() { for item in self.0.iter() {
@ -91,6 +97,14 @@ impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> {
} }
} }
impl<'a, 'c: 'a> Index<usize> for &'c Params<'a> {
type Output = str;
fn index(&self, idx: usize) -> &str {
self.0[idx].1.as_ref()
}
}
/// Creates a `PathBuf` from a path parameter. The returned `PathBuf` is /// Creates a `PathBuf` from a path parameter. The returned `PathBuf` is
/// percent-decoded. If a segment is equal to "..", the previous segment (if /// percent-decoded. If a segment is equal to "..", the previous segment (if
/// any) is skipped. /// any) is skipped.
@ -144,7 +158,8 @@ macro_rules! FROM_STR {
type Err = InternalError<<$type as FromStr>::Err>; type Err = InternalError<<$type as FromStr>::Err>;
fn from_param(val: &str) -> Result<Self, Self::Err> { fn from_param(val: &str) -> Result<Self, Self::Err> {
<$type as FromStr>::from_str(val).map_err(ErrorBadRequest) <$type as FromStr>::from_str(val)
.map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST))
} }
} }
} }

View File

@ -5,6 +5,7 @@ use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{Async, Poll, Stream}; use futures::{Async, Poll, Stream};
use futures::task::{Task, current as current_task};
use error::PayloadError; use error::PayloadError;
@ -158,6 +159,12 @@ impl PayloadWriter for PayloadSender {
if shared.borrow().need_read { if shared.borrow().need_read {
PayloadStatus::Read PayloadStatus::Read
} else { } else {
#[cfg(not(test))]
{
if shared.borrow_mut().io_task.is_none() {
shared.borrow_mut().io_task = Some(current_task());
}
}
PayloadStatus::Pause PayloadStatus::Pause
} }
} else { } else {
@ -174,6 +181,8 @@ struct Inner {
need_read: bool, need_read: bool,
items: VecDeque<Bytes>, items: VecDeque<Bytes>,
capacity: usize, capacity: usize,
task: Option<Task>,
io_task: Option<Task>,
} }
impl Inner { impl Inner {
@ -186,6 +195,8 @@ impl Inner {
items: VecDeque::new(), items: VecDeque::new(),
need_read: true, need_read: true,
capacity: MAX_BUFFER_SIZE, capacity: MAX_BUFFER_SIZE,
task: None,
io_task: None,
} }
} }
@ -204,6 +215,9 @@ impl Inner {
self.len += data.len(); self.len += data.len();
self.items.push_back(data); self.items.push_back(data);
self.need_read = self.len < self.capacity; self.need_read = self.len < self.capacity;
if let Some(task) = self.task.take() {
task.notify()
}
} }
#[inline] #[inline]
@ -237,6 +251,15 @@ impl Inner {
if let Some(data) = self.items.pop_front() { if let Some(data) = self.items.pop_front() {
self.len -= data.len(); self.len -= data.len();
self.need_read = self.len < self.capacity; self.need_read = self.len < self.capacity;
#[cfg(not(test))]
{
if self.need_read && self.task.is_none() {
self.task = Some(current_task());
}
if let Some(task) = self.io_task.take() {
task.notify()
}
}
Ok(Async::Ready(Some(data))) Ok(Async::Ready(Some(data)))
} else if let Some(err) = self.err.take() { } else if let Some(err) = self.err.take() {
Err(err) Err(err)
@ -244,6 +267,15 @@ impl Inner {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
self.need_read = true; self.need_read = true;
#[cfg(not(test))]
{
if self.task.is_none() {
self.task = Some(current_task());
}
if let Some(task) = self.io_task.take() {
task.notify()
}
}
Ok(Async::NotReady) Ok(Async::NotReady)
} }
} }
@ -270,6 +302,11 @@ impl<S> PayloadHelper<S> where S: Stream<Item=Bytes, Error=PayloadError> {
} }
} }
/// Get mutable reference to an inner stream.
pub fn get_mut(&mut self) -> &mut S {
&mut self.stream
}
#[inline] #[inline]
fn poll_stream(&mut self) -> Poll<bool, PayloadError> { fn poll_stream(&mut self) -> Poll<bool, PayloadError> {
self.stream.poll().map(|res| { self.stream.poll().map(|res| {

View File

@ -1,6 +1,6 @@
use std::{io, mem}; use std::{io, mem};
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::UnsafeCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use log::Level::Debug; use log::Level::Debug;
@ -10,7 +10,7 @@ use futures::unsync::oneshot;
use body::{Body, BodyStream}; use body::{Body, BodyStream};
use context::{Frame, ActorHttpContext}; use context::{Frame, ActorHttpContext};
use error::Error; use error::Error;
use headers::ContentEncoding; use header::ContentEncoding;
use handler::{Reply, ReplyItem}; use handler::{Reply, ReplyItem};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -18,11 +18,18 @@ use middleware::{Middleware, Finished, Started, Response};
use application::Inner; use application::Inner;
use server::{Writer, WriterState, HttpHandlerTask}; use server::{Writer, WriterState, HttpHandlerTask};
#[derive(Debug, Clone, Copy)]
pub(crate) enum HandlerType {
Normal(usize),
Handler(usize),
Default,
}
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>) -> Reply; fn handle(&mut self, req: HttpRequest<S>, htype: HandlerType) -> Reply;
} }
pub(crate) struct Pipeline<S, H>(PipelineInfo<S>, PipelineState<S, H>); pub(crate) struct Pipeline<S, H>(PipelineInfo<S>, PipelineState<S, H>);
@ -105,7 +112,7 @@ impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
pub fn new(req: HttpRequest<S>, pub fn new(req: HttpRequest<S>,
mws: Rc<Vec<Box<Middleware<S>>>>, mws: Rc<Vec<Box<Middleware<S>>>>,
handler: Rc<RefCell<H>>) -> Pipeline<S, H> handler: Rc<UnsafeCell<H>>, htype: HandlerType) -> Pipeline<S, H>
{ {
let mut info = PipelineInfo { let mut info = PipelineInfo {
req, mws, req, mws,
@ -113,9 +120,9 @@ impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
error: None, error: None,
context: None, context: None,
disconnected: None, disconnected: None,
encoding: handler.borrow().encoding(), encoding: unsafe{&*handler.get()}.encoding(),
}; };
let state = StartMiddlewares::init(&mut info, handler); let state = StartMiddlewares::init(&mut info, handler, htype);
Pipeline(info, state) Pipeline(info, state)
} }
@ -209,20 +216,23 @@ type Fut = Box<Future<Item=Option<HttpResponse>, Error=Error>>;
/// Middlewares start executor /// Middlewares start executor
struct StartMiddlewares<S, H> { struct StartMiddlewares<S, H> {
hnd: Rc<RefCell<H>>, hnd: Rc<UnsafeCell<H>>,
htype: HandlerType,
fut: Option<Fut>, fut: Option<Fut>,
_s: PhantomData<S>, _s: PhantomData<S>,
} }
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> { impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
fn init(info: &mut PipelineInfo<S>, handler: Rc<RefCell<H>>) -> PipelineState<S, H> { fn init(info: &mut PipelineInfo<S>, hnd: Rc<UnsafeCell<H>>, htype: HandlerType)
-> PipelineState<S, H>
{
// execute middlewares, we need this stage because middlewares could be non-async // execute middlewares, we need this stage because middlewares could be non-async
// and we can move to next state immediately // 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 = handler.borrow_mut().handle(info.req.clone()); 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(&mut info.req) {
@ -234,7 +244,7 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
match fut.poll() { match fut.poll() {
Ok(Async::NotReady) => Ok(Async::NotReady) =>
return PipelineState::Starting(StartMiddlewares { return PipelineState::Starting(StartMiddlewares {
hnd: handler, hnd, htype,
fut: Some(fut), fut: Some(fut),
_s: PhantomData}), _s: PhantomData}),
Ok(Async::Ready(resp)) => { Ok(Async::Ready(resp)) => {
@ -264,7 +274,8 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
return Some(RunMiddlewares::init(info, resp)); return Some(RunMiddlewares::init(info, resp));
} }
if info.count == len { if info.count == len {
let reply = (*self.hnd.borrow_mut()).handle(info.req.clone()); let reply = unsafe{
&mut *self.hnd.get()}.handle(info.req.clone(), self.htype);
return Some(WaitingResponse::init(info, reply)); return Some(WaitingResponse::init(info, reply));
} else { } else {
loop { loop {
@ -453,9 +464,10 @@ impl<S: 'static, H> ProcessResponse<S, H> {
fn poll_io(mut self, io: &mut Writer, info: &mut PipelineInfo<S>) fn poll_io(mut self, io: &mut Writer, info: &mut PipelineInfo<S>)
-> Result<PipelineState<S, H>, PipelineState<S, H>> -> Result<PipelineState<S, H>, PipelineState<S, H>>
{ {
loop {
if self.drain.is_none() && self.running != RunningState::Paused { if self.drain.is_none() && self.running != RunningState::Paused {
// if task is paused, write buffer is probably full // if task is paused, write buffer is probably full
'outter: loop { 'inner: loop {
let result = match mem::replace(&mut self.iostate, IOState::Done) { let result = match mem::replace(&mut self.iostate, IOState::Done) {
IOState::Response => { IOState::Response => {
let encoding = self.resp.content_encoding().unwrap_or(info.encoding); let encoding = self.resp.content_encoding().unwrap_or(info.encoding);
@ -481,11 +493,16 @@ impl<S: 'static, H> ProcessResponse<S, H> {
} }
} }
// always poll stream or actor for the first time
match self.resp.replace_body(Body::Empty) { match self.resp.replace_body(Body::Empty) {
Body::Streaming(stream) => Body::Streaming(stream) => {
self.iostate = IOState::Payload(stream), self.iostate = IOState::Payload(stream);
Body::Actor(ctx) => continue 'inner
self.iostate = IOState::Actor(ctx), },
Body::Actor(ctx) => {
self.iostate = IOState::Actor(ctx);
continue 'inner
},
_ => (), _ => (),
} }
@ -540,7 +557,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
return Ok( return Ok(
FinishingMiddlewares::init(info, self.resp)) FinishingMiddlewares::init(info, self.resp))
} }
break 'outter break 'inner
}, },
Frame::Chunk(Some(chunk)) => { Frame::Chunk(Some(chunk)) => {
match io.write(chunk) { match io.write(chunk) {
@ -552,14 +569,13 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Ok(result) => res = Some(result), Ok(result) => res = Some(result),
} }
}, },
Frame::Drain(fut) => Frame::Drain(fut) => self.drain = Some(fut),
self.drain = Some(fut),
} }
} }
self.iostate = IOState::Actor(ctx); self.iostate = IOState::Actor(ctx);
if self.drain.is_some() { if self.drain.is_some() {
self.running.resume(); self.running.resume();
break 'outter break 'inner
} }
res.unwrap() res.unwrap()
}, },
@ -602,15 +618,18 @@ impl<S: 'static, H> ProcessResponse<S, H> {
let _ = tx.send(()); let _ = tx.send(());
} }
// restart io processing // restart io processing
return self.poll_io(io, info); continue
}, },
Ok(Async::NotReady) => return Err(PipelineState::Response(self)), Ok(Async::NotReady) =>
return Err(PipelineState::Response(self)),
Err(err) => { Err(err) => {
info.error = Some(err.into()); info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(info, self.resp)) return Ok(FinishingMiddlewares::init(info, self.resp))
} }
} }
} }
break
}
// response is completed // response is completed
match self.iostate { match self.iostate {

View File

@ -8,7 +8,8 @@ use httprequest::HttpRequest;
/// Trait defines resource route predicate. /// Trait defines resource route predicate.
/// Predicate can modify request object. It is also possible to /// Predicate can modify request object. It is also possible to
/// to store extra attributes on request by using `.extensions()` method. /// to store extra attributes on request by using `Extensions` container,
/// Extensions container available via `HttpRequest::extensions()` method.
pub trait Predicate<S> { pub trait Predicate<S> {
/// Check if request matches predicate /// Check if request matches predicate
@ -20,16 +21,13 @@ pub trait Predicate<S> {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate http; /// use actix_web::{pred, App, HttpResponse};
/// # use actix_web::*;
/// # use actix_web::httpcodes::*;
/// use actix_web::pred;
/// ///
/// fn main() { /// fn main() {
/// Application::new() /// App::new()
/// .resource("/index.html", |r| r.route() /// .resource("/index.html", |r| r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Post())) /// .filter(pred::Any(pred::Get()).or(pred::Post()))
/// .h(HttpMethodNotAllowed)); /// .f(|r| HttpResponse::MethodNotAllowed()));
/// } /// }
/// ``` /// ```
pub fn Any<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AnyPredicate<S> pub fn Any<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AnyPredicate<S>
@ -63,17 +61,14 @@ impl<S: 'static> Predicate<S> for AnyPredicate<S> {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate http; /// use actix_web::{pred, App, HttpResponse};
/// # use actix_web::*;
/// # use actix_web::httpcodes::*;
/// use actix_web::pred;
/// ///
/// fn main() { /// fn main() {
/// Application::new() /// App::new()
/// .resource("/index.html", |r| r.route() /// .resource("/index.html", |r| r.route()
/// .filter(pred::All(pred::Get()) /// .filter(pred::All(pred::Get())
/// .and(pred::Header("content-type", "plain/text"))) /// .and(pred::Header("content-type", "plain/text")))
/// .h(HttpMethodNotAllowed)); /// .f(|_| HttpResponse::MethodNotAllowed()));
/// } /// }
/// ``` /// ```
pub fn All<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AllPredicate<S> { pub fn All<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AllPredicate<S> {

View File

@ -5,9 +5,8 @@ use smallvec::SmallVec;
use http::{Method, StatusCode}; use http::{Method, StatusCode};
use pred; use pred;
use body::Body;
use route::Route; use route::Route;
use handler::{Reply, Handler, Responder}; use handler::{Reply, Handler, Responder, FromRequest};
use middleware::Middleware; use middleware::Middleware;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -24,24 +23,24 @@ use httpresponse::HttpResponse;
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::*; /// use actix_web::{App, HttpResponse, http};
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = App::new()
/// .resource( /// .resource(
/// "/", |r| r.method(Method::GET).f(|r| HttpResponse::Ok())) /// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok()))
/// .finish(); /// .finish();
/// } /// }
pub struct Resource<S=()> { pub struct ResourceHandler<S=()> {
name: String, name: String,
state: PhantomData<S>, state: PhantomData<S>,
routes: SmallVec<[Route<S>; 3]>, routes: SmallVec<[Route<S>; 3]>,
middlewares: Rc<Vec<Box<Middleware<S>>>>, middlewares: Rc<Vec<Box<Middleware<S>>>>,
} }
impl<S> Default for Resource<S> { impl<S> Default for ResourceHandler<S> {
fn default() -> Self { fn default() -> Self {
Resource { ResourceHandler {
name: String::new(), name: String::new(),
state: PhantomData, state: PhantomData,
routes: SmallVec::new(), routes: SmallVec::new(),
@ -49,10 +48,10 @@ impl<S> Default for Resource<S> {
} }
} }
impl<S> Resource<S> { impl<S> ResourceHandler<S> {
pub(crate) fn default_not_found() -> Self { pub(crate) fn default_not_found() -> Self {
Resource { ResourceHandler {
name: String::new(), name: String::new(),
state: PhantomData, state: PhantomData,
routes: SmallVec::new(), routes: SmallVec::new(),
@ -69,7 +68,7 @@ impl<S> Resource<S> {
} }
} }
impl<S: 'static> Resource<S> { impl<S: 'static> ResourceHandler<S> {
/// Register a new route and return mutable reference to *Route* object. /// Register a new route and return mutable reference to *Route* object.
/// *Route* is used for route configuration, i.e. adding predicates, setting up handler. /// *Route* is used for route configuration, i.e. adding predicates, setting up handler.
@ -79,7 +78,7 @@ impl<S: 'static> Resource<S> {
/// use actix_web::*; /// use actix_web::*;
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = App::new()
/// .resource( /// .resource(
/// "/", |r| r.route() /// "/", |r| r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Put())) /// .filter(pred::Any(pred::Get()).or(pred::Put()))
@ -93,12 +92,42 @@ impl<S: 'static> Resource<S> {
self.routes.last_mut().unwrap() self.routes.last_mut().unwrap()
} }
/// Register a new `GET` route.
pub fn get(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Get())
}
/// Register a new `POST` route.
pub fn post(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Post())
}
/// Register a new `PUT` route.
pub fn put(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Put())
}
/// Register a new `DELETE` route.
pub fn delete(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Delete())
}
/// Register a new `HEAD` route.
pub fn head(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Head())
}
/// Register a new route and add method check to route. /// Register a new route and add method check to route.
/// ///
/// This is shortcut for: /// This is shortcut for:
/// ///
/// ```rust,ignore /// ```rust,ignore
/// Resource::resource("/", |r| r.route().filter(pred::Get()).f(index) /// Application::resource("/", |r| r.route().filter(pred::Get()).f(index)
/// ``` /// ```
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());
@ -110,7 +139,7 @@ impl<S: 'static> Resource<S> {
/// This is shortcut for: /// This is shortcut for:
/// ///
/// ```rust,ignore /// ```rust,ignore
/// Resource::resource("/", |r| r.route().h(handler) /// Application::resource("/", |r| r.route().h(handler)
/// ``` /// ```
pub fn h<H: Handler<S>>(&mut self, handler: H) { pub fn h<H: Handler<S>>(&mut self, handler: H) {
self.routes.push(Route::default()); self.routes.push(Route::default());
@ -122,7 +151,7 @@ impl<S: 'static> Resource<S> {
/// This is shortcut for: /// This is shortcut for:
/// ///
/// ```rust,ignore /// ```rust,ignore
/// Resource::resource("/", |r| r.route().f(index) /// Application::resource("/", |r| r.route().f(index)
/// ``` /// ```
pub fn f<F, R>(&mut self, handler: F) pub fn f<F, R>(&mut self, handler: F)
where F: Fn(HttpRequest<S>) -> R + 'static, where F: Fn(HttpRequest<S>) -> R + 'static,
@ -132,9 +161,25 @@ impl<S: 'static> Resource<S> {
self.routes.last_mut().unwrap().f(handler) self.routes.last_mut().unwrap().f(handler)
} }
/// Register a middleware /// Register a new route and add handler.
/// ///
/// This is similar to `Application's` middlewares, but /// This is shortcut for:
///
/// ```rust,ignore
/// Application::resource("/", |r| r.route().with(index)
/// ```
pub fn with<T, F, R>(&mut self, handler: F)
where F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
self.routes.push(Route::default());
self.routes.last_mut().unwrap().with(handler);
}
/// Register a resource middleware
///
/// This is similar to `App's` middlewares, but
/// middlewares get invoked on resource level. /// middlewares get invoked on resource level.
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).unwrap().push(Box::new(mw)); Rc::get_mut(&mut self.middlewares).unwrap().push(Box::new(mw));
@ -142,7 +187,7 @@ impl<S: 'static> Resource<S> {
pub(crate) fn handle(&mut self, pub(crate) fn handle(&mut self,
mut req: HttpRequest<S>, mut req: HttpRequest<S>,
default: Option<&mut Resource<S>>) -> Reply default: Option<&mut ResourceHandler<S>>) -> Reply
{ {
for route in &mut self.routes { for route in &mut self.routes {
if route.check(&mut req) { if route.check(&mut req) {
@ -156,7 +201,7 @@ impl<S: 'static> Resource<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, Body::Empty)) Reply::response(HttpResponse::new(StatusCode::NOT_FOUND))
} }
} }
} }

View File

@ -5,12 +5,13 @@ use futures::{Async, Future, Poll};
use error::Error; use error::Error;
use pred::Predicate; use pred::Predicate;
use handler::{Reply, ReplyItem, Handler, use http::StatusCode;
use handler::{Reply, ReplyItem, Handler, FromRequest,
Responder, RouteHandler, AsyncHandler, WrapHandler}; Responder, RouteHandler, AsyncHandler, WrapHandler};
use middleware::{Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted}; use middleware::{Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted};
use httpcodes::HttpNotFound;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use with::{With, With2, With3, ExtractorConfig};
/// Resource route definition /// Resource route definition
/// ///
@ -26,7 +27,7 @@ impl<S: 'static> Default for Route<S> {
fn default() -> Route<S> { fn default() -> Route<S> {
Route { Route {
preds: Vec::new(), preds: Vec::new(),
handler: InnerHandler::new(|_| HttpNotFound), handler: InnerHandler::new(|_| HttpResponse::new(StatusCode::NOT_FOUND)),
} }
} }
} }
@ -60,14 +61,13 @@ impl<S: 'static> Route<S> {
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::*; /// # use actix_web::*;
/// # use actix_web::httpcodes::*;
/// # fn main() { /// # fn main() {
/// Application::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| HttpOk) /// .f(|req| HttpResponse::Ok())
/// ) /// )
/// # .finish(); /// # .finish();
/// # } /// # }
@ -77,12 +77,6 @@ impl<S: 'static> Route<S> {
self self
} }
#[doc(hidden)]
#[deprecated(since="0.4.1", note="please use `.filter()` instead")]
pub fn p<T: Predicate<S> + 'static>(&mut self, p: T) -> &mut Self {
self.filter(p)
}
/// Set handler object. Usually call to this method is last call /// Set handler object. Usually call to this method is last call
/// during route configuration, so it does not return reference to self. /// during route configuration, so it does not return reference to self.
pub fn h<H: Handler<S>>(&mut self, handler: H) { pub fn h<H: Handler<S>>(&mut self, handler: H) {
@ -107,6 +101,101 @@ impl<S: 'static> Route<S> {
{ {
self.handler = InnerHandler::async(handler); self.handler = InnerHandler::async(handler);
} }
/// Set handler function, use request extractor for paramters.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Result, http};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Path<Info>) -> Result<String> {
/// Ok(format!("Welcome {}!", info.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// }
/// ```
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
where F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
let cfg = ExtractorConfig::default();
self.h(With::new(handler, Clone::clone(&cfg)));
cfg
}
/// Set handler function, use request extractor for both paramters.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Query, Path, Result, http};
///
/// #[derive(Deserialize)]
/// struct PParam {
/// username: String,
/// }
///
/// #[derive(Deserialize)]
/// struct QParam {
/// count: u32,
/// }
///
/// /// extract path and query information using serde
/// fn index(p: Path<PParam>, q: Query<QParam>) -> Result<String> {
/// Ok(format!("Welcome {}!", p.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor
/// }
/// ```
pub fn with2<T1, T2, F, R>(&mut self, handler: F)
-> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>)
where F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
{
let cfg1 = ExtractorConfig::default();
let cfg2 = ExtractorConfig::default();
self.h(With2::new(handler, Clone::clone(&cfg1), Clone::clone(&cfg2)));
(cfg1, cfg2)
}
/// Set handler function, use request extractor for all paramters.
pub fn with3<T1, T2, T3, F, R>(&mut self, handler: F)
-> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>, ExtractorConfig<S, T3>)
where F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
{
let cfg1 = ExtractorConfig::default();
let cfg2 = ExtractorConfig::default();
let cfg3 = ExtractorConfig::default();
self.h(With3::new(
handler, Clone::clone(&cfg1), Clone::clone(&cfg2), Clone::clone(&cfg3)));
(cfg1, cfg2, cfg3)
}
} }
/// `RouteHandler` wrapper. This struct is required because it needs to be shared /// `RouteHandler` wrapper. This struct is required because it needs to be shared

Some files were not shown because too many files have changed in this diff Show More