1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-13 21:53:59 +02:00

Compare commits

...

368 Commits

Author SHA1 Message Date
b4ed564e5d update changes 2018-07-26 09:11:50 -07:00
80fbc2e9ec Fix stream draining for http/2 connections #290 2018-07-25 15:38:02 -07:00
f58065082e fix missing content-encoding header for h2 connections #421 2018-07-25 10:30:55 -07:00
6048817ba7 Correct flate feature names in documentation 2018-07-25 20:22:18 +03:00
e408b68744 Update cookie dependency (#422) 2018-07-25 18:01:22 +03:00
b878613e10 fix warning 2018-07-24 15:49:46 -07:00
85b275bb2b fix warnings 2018-07-24 15:09:30 -07:00
d6abd2fe22 allow to handle empty path for application with prefix 2018-07-24 14:51:48 -07:00
b79a9aaec7 fix changelog 2018-07-24 14:18:04 -07:00
b9586b3f71 Merge pull request #412 from gdamjan/master
remove the timestamp from the default logger middleware
2018-07-24 14:07:10 -07:00
d3b12d885e Merge branch 'master' into master 2018-07-24 14:07:03 -07:00
f21386708a Merge pull request #416 from axos88/master
Add FromRequest<S> implementation for Option<T> and Result<T> where T: FromRequest<S>
2018-07-24 14:06:08 -07:00
b48a2d4d7b add changes to CHANGES.md 2018-07-24 22:25:48 +02:00
35b754a3ab pr fixes 2018-07-24 09:42:46 +02:00
1079c5c562 Add FromRequest<S> implementation for Result<T> and Option<T> where T:FromRequest<S> 2018-07-24 09:42:46 +02:00
f4bb7efa89 add partialeq, eq, partialord and ord dervie to Path, Form and Query 2018-07-24 09:42:46 +02:00
0099091e96 remove unnecessary use 2018-07-24 09:42:46 +02:00
c352a69d54 fix dead links 2018-07-23 13:22:16 -07:00
f5347ec897 Merge pull request #415 from DenisKolodin/cookie-http-only
Add http_only flag to CookieSessionBackend
2018-07-23 02:54:23 -07:00
b367f07d56 Add http_only flag to CookieSessionBackend 2018-07-23 12:49:59 +03:00
6a75a3d683 document the change in the default logger 2018-07-21 16:01:42 +02:00
56b924e155 remove the timestamp from the default logger middleware
env_logger and other logging systems will (or should) already add their
own timestamp.
2018-07-21 15:15:28 +02:00
4862227df9 fix not implemented panic #410 2018-07-21 05:58:08 -07:00
f6499d9ba5 publish stable docs on actix.rs site 2018-07-21 04:19:02 -07:00
7138bb2f29 update migration 2018-07-21 01:00:50 -07:00
8cb510293d update changes 2018-07-20 14:10:41 -07:00
040d9d2755 Merge branch 'master' of github.com:actix/actix-web 2018-07-20 12:43:44 -07:00
2043bb5ece do not reallocate waiters 2018-07-20 10:20:41 -07:00
a751df2589 Initial config for static files (#405) 2018-07-20 07:49:25 +03:00
f6e35a04f0 Just a bit of sanity check for short paths (#409) 2018-07-20 07:48:57 +03:00
0925a7691a ws/context: Increase write() visibility to public (#402)
This type is introduced to avoid confusion between the `.binary()` and `.write_raw()` methods on WebSocket contexts
2018-07-19 20:04:13 +03:00
2988a84e5f Expose leaked private ContentDisposition (#406) 2018-07-19 20:03:45 +03:00
6b10e1eff6 rename PayloadHelper 2018-07-18 10:01:28 +06:00
85672d1379 fix client connector wait queue 2018-07-18 01:23:56 +06:00
373f2e5028 add release stat 2018-07-17 17:38:16 +06:00
f9f259e718 Merge branch 'master' of github.com:actix/actix-web 2018-07-17 17:23:23 +06:00
d43902ee7c proper handling for client connection release 2018-07-17 17:23:03 +06:00
a7ca5fa5d8 Add few missing entries to changelog 2018-07-17 11:10:04 +03:00
29a275b0f5 Session should write percent encoded cookies and add cookie middleware test (#393)
* Should write percent encoded cookies to HTTP response

* Add cookie middleware test
2018-07-17 08:38:18 +03:00
1af5aa3a3e calculate client request timeout 2018-07-17 02:30:21 +06:00
bccd7c7671 add wait queue size stat to client connector 2018-07-17 01:57:57 +06:00
2a8c2fb55e export Payload 2018-07-16 12:14:24 +06:00
2dd57a48d6 checks nested scopes in has_resource() 2018-07-16 11:33:29 +06:00
22385505a3 clippy warnings and fmt 2018-07-16 11:17:45 +06:00
5888f01317 use has_prefixed_route for NormalizePath helper 2018-07-16 11:13:41 +06:00
b7a3fce17b simplify has_prefixed_route() 2018-07-16 11:10:51 +06:00
bce05e4fcb Merge pull request #381 from OtaK/fix/has_route_prefixes
Add prefix aware RouteInfo::has_prefixed_route()
2018-07-16 10:58:50 +06:00
3373847a14 allocate buffer for request payload extractors 2018-07-16 00:40:22 +06:00
8f64508887 Added RouteInfo::has_prefixed_route() method for route matching with prefix awareness 2018-07-15 19:37:20 +02:00
30c84786b7 Merge pull request #399 from actix/router-refactor
Router refactoring
2018-07-15 19:16:07 +06:00
2e5f627050 do not force install tarpaulin 2018-07-15 19:15:36 +06:00
2214492792 use assert and restore test case 2018-07-15 18:53:02 +06:00
c43b6e3577 cargo tarpaulin 2018-07-15 16:39:15 +06:00
42d3e86941 calculate prefix dynamicly 2018-07-15 16:25:56 +06:00
b759dddf5a simplify application prefix impl 2018-07-15 16:25:56 +06:00
9570c1cccd rename RouteInfo 2018-07-15 16:25:56 +06:00
da915972c0 refactor router 2018-07-15 16:25:56 +06:00
cf976d296f Merge pull request #397 from actix/Turbo87-patch-1
error: Fix documentation typo
2018-07-14 09:38:43 +06:00
9012cf43fe error: Fix documentation typo 2018-07-14 00:05:07 +02:00
7d753eeb8c Private serde fork (#390)
* Fork serde_urlencoded

* Apply enum PR https://github.com/nox/serde_urlencoded/pull/30

* Add test to verify enum in query

* Docs are updated to show example of how to use enum.
2018-07-13 09:59:09 +03:00
4395add1c7 update travis config 2018-07-13 00:05:01 +06:00
35911b832a Merge branch 'master' of github.com:actix/actix-web 2018-07-12 23:59:10 +06:00
b8b90d9ec9 rename ResourceHandler to Resource 2018-07-12 15:30:01 +06:00
422a870cd7 Merge pull request #387 from actix/fix-missing-content-length
fix missing content length
2018-07-12 16:18:55 +10:00
db005af1af clippy warnings 2018-07-12 10:41:49 +06:00
8e462c5944 use write instead format 2018-07-12 10:35:09 +06:00
86e44de787 pin failure crate 2018-07-12 10:29:37 +06:00
d9988f3ab6 fix missing content length
fix missing content length when no compression is used
2018-07-11 21:21:32 +10:00
696152f763 Merge pull request #377 from Diggsey/apply-mask
Refactor `apply_mask` implementation, removing dead code paths and re…
2018-07-11 13:36:08 +06:00
f38a370b94 update changes 2018-07-11 13:34:40 +06:00
28b36c650a fix h2 compatibility 2018-07-11 13:25:07 +06:00
b22132d3d6 Merge branch 'master' into apply-mask 2018-07-11 13:15:35 +06:00
19ae5e9489 Merge branch 'master' of github.com:actix/actix-web 2018-07-11 12:56:53 +06:00
9aef34e768 remove & to &mut transmute #385 2018-07-11 12:56:35 +06:00
bed961fe35 Lessen numbers of jobs for AppVeyor 2018-07-11 09:23:17 +03:00
87824a9cf6 Refactor apply_mask implementation, removing dead code paths and reducing scope of unsafety 2018-07-08 13:56:43 +01:00
82920e1ac1 Do not override user settings on signals and stop handling (#375) 2018-07-08 09:01:44 +03:00
110605f50b stop actor context on error #311 2018-07-08 09:41:55 +06:00
00c97504b6 Merge pull request #368 from Diggsey/master
Remove reimplementation of `LazyCell`
2018-07-07 09:46:44 +06:00
85012f947a Remove reimplementation of LazyCell 2018-07-06 22:28:08 +01:00
62ba01fc15 update changes 2018-07-06 15:00:14 +06:00
5b7aed101a remove unsafe 2018-07-06 13:54:43 +06:00
1c3b32169e remove stream from WebsocketsContext::with_factory 2018-07-06 12:11:40 +06:00
cfa470db50 close conneciton for head requests 2018-07-06 09:21:24 +06:00
a5f7a67b4d clippy warnings 2018-07-06 08:24:44 +06:00
185e710dc8 do not drop content-encoding header in case of identity #363 2018-07-06 08:24:36 +06:00
9070d59ea8 do not read head payload 2018-07-06 08:11:36 +06:00
2a25caf2c5 Merge branch 'master' of github.com:actix/actix-web 2018-07-06 07:49:50 +06:00
7d96b92aa3 add check for usize cast 2018-07-06 07:46:47 +06:00
67e4cad281 Introduce method to set header if it is missing only (#364)
Also let default headers use it.

Closes #320
2018-07-05 19:27:18 +03:00
080f232a0f Use StaticFile default handler when file is inaccessible (#357)
* Use Staticfile default handler on all error paths

* Return an error from StaticFiles::new() if directory doesn't exist
2018-07-05 12:34:13 +03:00
ac3a76cd32 update httparse version 2018-07-05 13:21:33 +06:00
8058d15624 clippy warnings 2018-07-05 13:16:16 +06:00
05a43a855e remove unsafe 2018-07-05 13:00:46 +06:00
80339147b9 call disconnect on write error 2018-07-05 12:50:54 +06:00
6af2f5d642 re-enable start_incoming support 2018-07-05 12:14:10 +06:00
d7762297da update actix dependency 2018-07-05 12:02:32 +06:00
d5606625a2 remove public Clone for Request 2018-07-04 22:57:40 +06:00
5d79114239 optimize Request handling 2018-07-04 22:52:49 +06:00
f559f23e1c Merge branch 'master' of github.com:actix/actix-web 2018-07-04 21:02:40 +06:00
6fd686ef98 cleanup warnings 2018-07-04 21:01:27 +06:00
4c5a63965e use new actix context api 2018-07-04 17:04:23 +06:00
09aabc7b3b plain/text -> text/plain in comment (#362) 2018-07-04 11:17:44 +03:00
b6d26c9faf Merge pull request #348 from actix/request-mutability
Request mutability
2018-07-02 23:52:42 +06:00
fec6047ddc refactor HttpRequest mutability 2018-07-02 23:35:32 +06:00
445ea043dd remove unsafes 2018-07-02 23:32:29 +06:00
0be5448597 Properly escape special characters in fs/directory_listing. (#355) 2018-06-30 15:01:48 +03:00
0f27389e72 set length of vector to max_bytes (closes #345) (#346) 2018-06-26 08:09:12 +03:00
a9425a866b Fix duplicate tail of StaticFiles with index_file
Map from 0.6 to master
2018-06-25 19:59:55 +03:00
800c404c72 explicit response release 2018-06-25 10:10:02 +06:00
32212bad1f simplify http response pool 2018-06-25 09:08:28 +06:00
d1b73e30e0 update comments 2018-06-24 22:27:30 +06:00
c0cdc39ba9 do not store cookies on client response 2018-06-24 22:21:04 +06:00
8e8a68f90b add empty output stream 2018-06-24 22:05:44 +06:00
989cd61236 handle empty te 2018-06-24 10:59:01 +06:00
33260c7b35 split encoding module 2018-06-24 10:42:20 +06:00
40ca9ba9c5 simplify write buffer 2018-06-24 10:30:58 +06:00
45682c04a8 refactor content encoder 2018-06-24 08:54:01 +06:00
348491b18c fix alpn connector 2018-06-23 17:59:45 +06:00
3d2226aa9e Merge branch 'master' of github.com:actix/actix-web 2018-06-23 12:40:45 +06:00
cf38183dcb refactor client connector waiters maintenance 2018-06-23 12:40:21 +06:00
e3dc6f0ca8 refactor h1decoder 2018-06-23 12:28:55 +06:00
a5369aed8b Changes a leaked box into an Rc<String> and makes resource() return an Option (#343) 2018-06-23 08:16:52 +02:00
ff0ab733e4 remove unsafe from mask 2018-06-23 11:51:02 +06:00
d1318a35a0 remove unnecessary unsafes 2018-06-23 10:29:23 +06:00
756227896b update set_date impl 2018-06-23 10:13:09 +06:00
4fadff63f4 Use Box::leak for dynamic param names 2018-06-23 09:57:03 +06:00
7bc7b4839b Switch from fnv to a identity hasher in extensions (#342) 2018-06-22 11:32:32 +02:00
dda6ee95df Changes the router to use atoms internally (#341) 2018-06-22 09:33:32 +02:00
765c38e7b9 remove libc dependency 2018-06-22 11:47:33 +06:00
6c44575923 transmute names once 2018-06-22 11:44:38 +06:00
fc7238baee refactor read_from_io 2018-06-22 11:30:40 +06:00
edd22bb279 refactor read_from_io 2018-06-22 09:01:20 +06:00
17c033030b Revert "remove unnecessary use of unsafe in read_from_io"
This reverts commit da237611cb.
2018-06-22 08:55:19 +06:00
3afdf3fa7e Merge pull request #335 from gnzlbg/fix_unsafe
remove unnecessary use of unsafe in read_from_io
2018-06-22 07:23:14 +06:00
50fbef88fc cleanup srver pipeline 2018-06-21 23:51:25 +06:00
c9069e9a3c remove unneeded UnsafeCell 2018-06-21 23:21:28 +06:00
65ca563579 use read only self for Middleware 2018-06-21 23:06:23 +06:00
3de9284592 Handler::handle uses &self instead of mutabble reference 2018-06-21 17:07:54 +06:00
5a9992736f Merge pull request #339 from joshleeb/propogate-scope-default-resource
Propagate scope default resource
2018-06-21 15:40:02 +06:00
0338767264 Update CHANGES for default scope propagation 2018-06-21 19:37:34 +10:00
c5e8c1b710 Propagate default resources to underlying scopes 2018-06-21 18:17:27 +10:00
b5594ae2a5 Fix doc api example 2018-06-21 14:11:00 +06:00
58d1f4a4aa switch to actix master 2018-06-21 13:34:36 +06:00
b7d813eeba update tests 2018-06-21 12:04:00 +06:00
8e160ebda7 clippy warning 2018-06-21 11:49:36 +06:00
0093b7ea5a refactor extractor configuration #331 2018-06-21 11:47:01 +06:00
75eec8bd4f fix condition 2018-06-21 11:23:21 +06:00
ebc59cf7b9 add unsafe checks #331 2018-06-21 11:20:21 +06:00
c2c4a5ba3f fix failure Send+Sync compatibility 2018-06-21 10:45:24 +06:00
dbd093075d Merge pull request #338 from tbroadley/fix-typos
Fix typos
2018-06-21 10:13:30 +06:00
1be27e17f8 convert timer error to io error 2018-06-21 10:05:20 +06:00
8b0fbb85d1 SendRequest execution fails with the entered unreachable code #329 2018-06-21 09:52:18 +06:00
cfe6725eb4 Allow to disable masking for websockets client 2018-06-21 09:49:33 +06:00
f815c1c096 Add test for default_resource scope propagation 2018-06-21 13:10:40 +10:00
280eae4335 Merge pull request #334 from Vurich/master
Fix some unsoundness
2018-06-21 07:15:33 +06:00
bd8cbfff35 docs: fix typos 2018-06-20 21:05:26 -04:00
da237611cb remove unnecessary use of unsafe in read_from_io 2018-06-20 13:14:53 +02:00
Jef
234c60d473 Fix some unsoundness
This improves the sound implementation of `fn route`.
Previously this function would iterate twice but we
can reduce the overhead without using `unsafe`.
2018-06-20 10:53:18 +02:00
2f917f3700 various cleanups and comments 2018-06-20 01:27:41 +06:00
311f0b23a9 cleanup more code 2018-06-20 00:36:32 +06:00
a69c1e3de5 remove unsafe from scope impl 2018-06-19 23:46:58 +06:00
c427fd1241 Merge pull request #328 from xfix/remove-some-uses-of-unsafe-from-frame-message
Remove some uses of unsafe from Frame::message
2018-06-19 21:52:41 +06:00
adcb4e1492 Merge pull request #327 from xfix/remove-use-of-unsafe-from-pipeline-poll
Remove use of unsafe from Pipeline#poll
2018-06-19 19:58:15 +06:00
3b1124c56c Merge branch 'master' into remove-some-uses-of-unsafe-from-frame-message 2018-06-19 19:20:40 +06:00
cafde76361 Merge branch 'master' into remove-use-of-unsafe-from-pipeline-poll 2018-06-19 19:20:25 +06:00
bfb93cae66 Update connector.rs 2018-06-19 19:19:31 +06:00
b5c1e42feb Merge branch 'master' into remove-use-of-unsafe-from-pipeline-poll 2018-06-19 18:30:37 +06:00
e884e7e84e Remove some uses of unsafe from Frame::message 2018-06-19 14:11:53 +02:00
877e177b60 Remove use of unsafe from Pipeline#poll 2018-06-19 13:42:44 +02:00
27b6af2800 refactor route matching 2018-06-19 16:45:26 +06:00
5c42b0902f better doc api examples 2018-06-19 12:07:07 +06:00
247e8727cb ClientBody is not needed 2018-06-19 10:15:16 +06:00
362b14c2f7 remove unsafe cell from ws client 2018-06-19 09:36:17 +06:00
261ad31b9a remove some unsafe code 2018-06-19 07:44:01 +06:00
68cd5bdf68 use actix 0.6 2018-06-18 09:18:03 +06:00
26f37ec2e3 refactor HttpHandlerTask trait 2018-06-18 05:45:54 +06:00
ef15646bd7 refactor edfault cpu pool 2018-06-18 04:56:18 +06:00
a5bbc455c0 cleanup mut transform 2018-06-18 04:41:41 +06:00
6ec8352612 method only for tests 2018-06-18 01:05:02 +06:00
f0f19c14d2 remove wsclient 2018-06-18 01:03:47 +06:00
daed502ee5 make mut api private 2018-06-18 01:03:07 +06:00
9d114d785e remove Clone from ExtractorConfig 2018-06-18 00:19:07 +06:00
ea118edf56 do not use references in ConnectionInfo 2018-06-18 00:01:41 +06:00
e1db47d550 refactor server settings 2018-06-17 23:51:20 +06:00
38fe8bebec fix doc string 2018-06-17 08:57:51 +06:00
c3f295182f use HashMap for HttpRequest::query() 2018-06-17 08:54:30 +06:00
b6ed778775 remove HttpMessage::range() 2018-06-17 08:48:50 +06:00
0f2aac1a27 remove unneed Send and Sync 2018-06-17 08:32:22 +06:00
70244c29e0 update doc api examples 2018-06-17 04:09:07 +06:00
a7a062fb68 clippy warnings 2018-06-17 03:26:34 +06:00
f3a73d7dde update changelog 2018-06-17 03:24:08 +06:00
879b2b5bde port Extensions from http crate #315 2018-06-17 03:22:08 +06:00
33050f55a3 remove Context::actor() method 2018-06-17 03:10:44 +06:00
e4443226f6 update actix usage 2018-06-17 02:58:56 +06:00
342a194605 fix handling ServerCommand #316 2018-06-16 22:56:27 +06:00
566b16c1f7 Merge branch 'master' of github.com:actix/actix-web 2018-06-14 11:42:27 +02:00
8261cf437d update actix api 2018-06-13 23:37:19 -07:00
8a8e6add08 Merge pull request #314 from DJMcNab/app-cleanup
remove duplication of `App::with_state` in `App::new`
2018-06-14 01:19:56 +03:00
b79307cab1 Merge branch 'master' into app-cleanup 2018-06-14 01:01:11 +03:00
4c646962a9 Merge pull request #312 from eddomuke/master
Add HttpMessage::readlines()
2018-06-14 00:40:29 +03:00
cb77f7e688 Add HttpMessage::readlines() 2018-06-14 00:19:48 +03:00
1bee528018 move ReadlinesError to error module 2018-06-13 22:59:36 +03:00
ad9aacf521 change poll method of Readlines 2018-06-13 22:41:35 +03:00
f8854f951c remove duplication of App::with_state in App::new 2018-06-13 20:31:20 +01:00
6d95e34552 add HttpMessage::readlines() 2018-06-13 20:45:31 +03:00
6c765739d0 add HttpMessage::readlines() 2018-06-13 20:43:03 +03:00
c8528e8920 Merge pull request #308 from eddomuke/master
Allow to override Form extractor error
2018-06-13 01:53:32 +03:00
0a080d9fb4 add test for form extractor 2018-06-13 01:33:28 +03:00
45b408526c Merge branch 'master' into master 2018-06-13 00:53:46 +03:00
1a91854270 Merge branch 'master' of github.com:actix/actix-web 2018-06-12 14:50:41 -07:00
99092fdf06 http/2 end-of-frame is not set if body is empty bytes #307 2018-06-12 14:50:21 -07:00
748ff389e4 Allow to override Form extractor error 2018-06-13 00:47:47 +03:00
b679b4cabc Merge pull request #306 from eddomuke/master
add ClientRequestBuilder::form()
2018-06-12 13:33:16 -07:00
ed7cbaa772 fix form_extractor test 2018-06-12 23:04:54 +03:00
e6bbda0efc add serialize 2018-06-12 22:42:15 +03:00
94283a73c2 make into_string, to_string 2018-06-12 22:31:33 +03:00
ffca416463 add test for ClientRequestBuilder::form() 2018-06-12 22:16:20 +03:00
9cc7651c22 add change to CHANGES.md 2018-06-12 20:32:16 +03:00
8af082d873 remove FormPayloadError 2018-06-12 20:26:09 +03:00
d4d3add17d add ClientRequestBuilder::form() 2018-06-12 19:30:00 +03:00
ce6f9e848b Merge pull request #305 from axon-q/response-cookies
Add HttpResponse methods to retrieve, add, and delete cookies
2018-06-12 14:39:06 +00:00
d8e1fd102d add cookie methods to HttpResponse 2018-06-12 13:56:53 +00:00
e414a52b51 content_disposition: remove unnecessary allocations 2018-06-12 13:48:23 +00:00
4d69e6d0b4 fs: minor cleanups to content_disposition 2018-06-12 13:47:49 +00:00
6f38d769a8 Merge pull request #304 from kazcw/master
fix url in example
2018-06-12 03:58:48 -07:00
48f77578ea fix url in example 2018-06-11 21:55:05 -07:00
9b012b3304 do not allow stream or actor responses for internal error #301 2018-06-11 19:45:17 -07:00
a0344eebeb InternalError can trigger memory unsafety #301 2018-06-11 18:54:36 -07:00
b9f6c313d4 Merge branch 'master' of github.com:actix/actix-web 2018-06-11 12:56:33 -07:00
ef420a8bdf fix docs.rs 2018-06-11 12:21:09 -07:00
0d54b6f38e Implement Responder for Option #294 (#297) 2018-06-11 14:05:41 +03:00
9afc3b6737 api docs link 2018-06-10 10:31:19 -07:00
ef88fc78d0 Merge branch 'master' of github.com:actix/actix-web 2018-06-10 10:25:05 -07:00
9dd66dfc22 better name for error 2018-06-10 10:24:34 -07:00
87a822e093 fix deprecated warnings 2018-06-10 10:14:13 -07:00
3788887c92 Merge pull request #293 from axon-q/static-file-updates
Better Content-Type and Content-Disposition handling for static files
2018-06-09 08:51:42 -07:00
785d0e24f0 Merge branch 'master' into static-file-updates 2018-06-09 08:21:34 -07:00
818d0bc187 new StreamHandler impl 2018-06-09 07:53:46 -07:00
aee24d4af0 minor syntax changes 2018-06-09 14:47:06 +00:00
fee203b402 update changelog 2018-06-09 14:02:05 +00:00
8681a346c6 fs: refactor Content-Type and Content-Disposition handling 2018-06-09 13:56:01 +00:00
1fdf6d13be content_disposition: add doc example 2018-06-09 13:38:21 +00:00
3751656722 expose fs::file_extension_to_mime() function 2018-06-09 11:20:06 +00:00
9151d61eda allow to use custom resolver for ClientConnector 2018-06-08 16:33:57 -07:00
4fe2f6b763 Merge pull request #284 from axon-q/multipart-content-disposition
multipart: parse and validate Content-Disposition
2018-06-07 21:20:18 -07:00
5a7902ff9a Merge branch 'master' into multipart-content-disposition 2018-06-07 21:20:11 -07:00
172b514fef Merge pull request #288 from memoryruins/patch-1
Update TechEmpower benchmarks to round 16
2018-06-07 21:09:49 -07:00
efb5d13280 readme: link to TechEmpower r16 benchmarks 2018-06-07 23:55:08 -04:00
f9f2ed04ab fix doc test 2018-06-07 20:22:23 -07:00
ce40ab307b update changes 2018-06-07 20:09:08 -07:00
f7ef8ae5a5 add Host predicate 2018-06-07 20:00:54 -07:00
60d40df545 fix clippy warning 2018-06-07 19:46:46 -07:00
f7bd6eeedc add application filters 2018-06-07 19:46:38 -07:00
a11f3c112f fix doc test 2018-06-07 21:18:51 +00:00
e9f59bc7d6 Merge branch 'master' into multipart-content-disposition 2018-06-07 11:02:53 -07:00
e970846167 update changelog 2018-06-07 17:59:35 +00:00
56e0dc06c1 defer parsing until user method call 2018-06-07 17:29:46 +00:00
789af0bbf2 Added improved failure interoperability with downcasting (#285)
Deprecates Error::cause and introduces failure interoperability functions and downcasting.
2018-06-07 18:53:27 +02:00
97b5410aad remove Option from ContentDisposition::from_raw() argument 2018-06-07 12:55:35 +00:00
a6e07c06b6 move CD parsing to Content-Type parsing location 2018-06-07 12:35:10 +00:00
31a301c9a6 fix multipart test 2018-06-07 11:38:35 +00:00
5a37a8b813 restore hyper tests 2018-06-07 10:55:36 +00:00
c0c1817b5c remove unicase dependency 2018-06-07 10:33:00 +00:00
82c888df22 fix test 2018-06-07 09:10:46 +00:00
936ba2a368 multipart: parse and validate Content-Disposition 2018-06-06 14:06:01 +00:00
2d0b609c68 travis config 2018-06-05 10:08:42 -07:00
6467d34a32 update release date 2018-06-05 09:45:07 -07:00
2b616808c7 metadata for docs.rs 2018-06-05 09:00:21 -07:00
e5f7e4e481 update changelog 2018-06-05 08:55:28 -07:00
d1da227ac5 fix multipart boundary parsing #282 2018-06-05 08:53:51 -07:00
960a8c425d update changelog 2018-06-05 07:40:11 -07:00
f94fd9ebee CORS: Do not validate Origin header on non-OPTION requests #271 2018-06-05 07:39:47 -07:00
67ee24f9a0 Merge pull request #274 from mockersf/user-agent
add default value for header User-Agent in requests
2018-06-04 14:04:52 -07:00
5004821cda Merge branch 'master' into user-agent 2018-06-04 14:04:45 -07:00
ae7a0e993d update changelog 2018-06-04 13:43:52 -07:00
984791187a Middleware::response is not invoked if error result was returned by another Middleware::start #255 2018-06-04 13:42:47 -07:00
b07c50860a update changelog 2018-06-04 22:34:07 +02:00
eb0909b3a8 Merge branch 'master' into user-agent 2018-06-04 10:20:53 -07:00
ca3fb11f8b add actix-web version in header 2018-06-04 08:15:04 +02:00
47eb4e3d3d Merge pull request #278 from mbrobbel/patch-2
Fix typo
2018-06-03 16:28:51 -07:00
268c5d9238 Fix typo 2018-06-03 20:28:08 +02:00
86be54df71 add default value for header User-Agent in requests 2018-06-03 15:48:00 +02:00
ea018e0ad6 better examle in doc string 2018-06-02 16:03:23 -07:00
b799677532 better error messages for overflow errors 2018-06-02 15:10:48 -07:00
8c7182f6e6 Merge pull request #270 from DoumanAsh/payload_err
Specialize ResponseError for PayloadError
2018-06-02 15:06:55 -07:00
7298c7aabf Merge branch 'master' into payload_err 2018-06-02 15:04:22 -07:00
7e0706a942 implement Debug for Form, Query, Path extractors 2018-06-02 15:00:11 -07:00
698f0a1849 update changelog 2018-06-02 15:00:11 -07:00
8b8a3ac01d Support chunked encoding for UrlEncoded body #262 2018-06-02 15:00:06 -07:00
7ab23d082d fix doc test 2018-06-02 13:45:29 -07:00
913dce0a72 Merge branch 'master' into payload_err 2018-06-02 23:10:06 +03:00
2a9b57f489 Correct docstring 2018-06-02 22:27:43 +03:00
fce8dd275a Specialize ResponseError for PayloadError
Closes #257
2018-06-02 22:20:22 +03:00
3c472a2f66 remove debug prints 2018-06-02 11:57:49 -07:00
dcb561584d remove debug print 2018-06-02 11:55:50 -07:00
593a66324f update changelog 2018-06-02 11:45:37 -07:00
4a39216aa7 fixed HttpRequest::url_for for a named route with no variables #265 2018-06-02 11:44:09 -07:00
8d905c8504 add links to migration 2018-06-02 09:28:32 -07:00
33326ea41b fix layout 2018-06-02 09:25:11 -07:00
0457fe4d61 add System changes to migration guide 2018-06-02 09:19:13 -07:00
cede817915 update changelog 2018-06-02 09:15:44 -07:00
3bfed36fcc do not re-export actix_inner 2018-06-02 09:14:47 -07:00
0ff5f5f448 update migration 2018-06-02 09:01:51 -07:00
2f476021d8 Merge pull request #267 from joshleeb/trait-middleware-mut-self
Update Middleware Trait to Use `&mut self`
2018-06-02 08:54:30 -07:00
a61a1b0efe Merge branch 'master' into trait-middleware-mut-self 2018-06-02 08:54:00 -07:00
e041e9d3b7 Merge pull request #268 from killercup/docs/no-more-missing-docs
No more missing docs
2018-06-02 08:52:14 -07:00
890a7e70d6 Add missing API docs
These were written without much knowledge of the actix-web internals!
Please review carefully!
2018-06-02 15:52:50 +02:00
47b7be4fd3 Add warning for missing API docs 2018-06-02 15:50:45 +02:00
9c9eb62031 Update Middleware trait to use &mut self 2018-06-02 16:47:18 +10:00
8d73c30dae Merge pull request #266 from killercup/docs/fix-typos-and-run-more-code
Fix some ResourceHandler docs
2018-06-01 16:37:34 -07:00
d912bf8771 Add more docs to ResourceHandler API 2018-06-02 00:57:24 +02:00
f414a491dd Fix some ResourceHandler docs
Re-enables code blocks as doc tests to prevent them failing in the
future.
2018-06-02 00:57:07 +02:00
8f42fec9b2 stable compat 2018-06-01 12:17:13 -07:00
8452c7a044 fix doc api example 2018-06-01 11:22:40 -07:00
009ee4b3db update changelog 2018-06-01 10:55:54 -07:00
3e0a71101c drop with2 and with3 2018-06-01 10:54:23 -07:00
c8930b7b6b fix rustfmt formatting 2018-06-01 10:27:23 -07:00
3f5a39a5b7 cargo fmt 2018-06-01 09:37:14 -07:00
154cd3c5de better actix mod re-exports 2018-06-01 09:36:16 -07:00
80965d7a9a Re-export actix dependency. Closes #260 (#264)
- Re-export actix's prelude into actix namespace
- Removing implicit dependency on root's actix module
2018-05-31 20:43:14 +03:00
77becb9bc0 fix doc string 2018-05-29 18:48:39 -07:00
dde266b9ef fix doc string 2018-05-29 18:31:39 -07:00
34fd9f8148 travis config 2018-05-29 18:18:05 -07:00
a64205e502 refactor TransferEncoding; allow to use client api with threaded tokio runtime 2018-05-29 16:32:39 -07:00
844be8d9dd fix ssl test server 2018-05-29 10:59:24 -07:00
dffb7936fb Merge branch 'master' of github.com:actix/actix-web 2018-05-29 10:31:43 -07:00
ecd05662c0 use new actix system api 2018-05-29 10:31:37 -07:00
6eee3d1083 Merge pull request #258 from mbrobbel/patch-1
Fix typo in httpresponse.rs
2018-05-29 09:15:39 -07:00
6b43fc7068 Fix typo in httpresponse.rs 2018-05-29 18:11:10 +02:00
fb582a6bca fix connector 2018-05-27 05:18:37 -07:00
be2ceb7c66 update actix Addr; make ClientConnector thread safe 2018-05-27 05:02:49 -07:00
7c71171602 Merge pull request #248 from bbigras/same-site
Add same-site to CookieSessionBackend
2018-05-26 08:02:12 -07:00
4dcecd907b Add same-site to CookieSessionBackend
closes #247
2018-05-25 19:18:16 -04:00
255cd4917d fix doc test 2018-05-24 22:04:14 -07:00
f48702042b min rustc version 2018-05-24 21:09:20 -07:00
690169db89 migrate to tokio 2018-05-24 21:03:16 -07:00
565bcfb561 Merge pull request #245 from svartalf/response-builder-cookies-doc
Updating docs for HttpResponseBuilder::del_cookie
2018-05-24 12:42:08 -07:00
36f933ce1d Updating docs for HttpResponseBuilder::del_cookie 2018-05-24 21:53:35 +03:00
111b6835fa fix comment 2018-05-24 11:06:15 -07:00
bf63be3bcd bump version 2018-05-24 09:24:04 -07:00
9f9e0b98ad change homepage link 2018-05-24 08:55:10 -07:00
556646aaec update changelog 2018-05-24 07:56:51 -07:00
174fb0b5f4 Merge pull request #239 from max-frai/master
Add ability to set encoding for exact NamedFile.
2018-05-24 07:46:53 -07:00
836706653b Merge branch 'master' into master 2018-05-24 07:46:46 -07:00
17f1a2b92a more scope tests 2018-05-23 14:11:01 -07:00
3b08b16c11 bump version 2018-05-23 13:21:54 -07:00
68eb2f26c9 Allow to use path without traling slashes for scope registration #241 2018-05-23 13:21:29 -07:00
72757887c9 update doc links 2018-05-23 11:20:12 -07:00
eb5dbd43ae update changelog 2018-05-23 10:37:17 -07:00
1f1dfac3f9 Merge pull request #240 from ivanovaleksey/patch-2
Fix TestServer::post
2018-05-23 09:50:40 -07:00
2479b14aba Fix TestServer::post 2018-05-23 19:07:42 +03:00
ac24703512 Add ability to set encoding for exact NamedFile. 2018-05-23 09:12:23 +03:00
db0091ba6f disable server test for windows 2018-05-21 21:01:52 -07:00
2159158c30 Fix streaming response with body compression 2018-05-21 20:50:10 -07:00
76d790425f bump version 2018-05-21 19:07:56 -07:00
90968d4333 Drop connection if request's payload is not fulle consumed #236 2018-05-21 18:54:17 -07:00
577a509875 increase delay 2018-05-21 16:12:33 -07:00
a9728abfc8 run coverage report on 1.24 2018-05-20 21:10:50 -07:00
14d1b8e2b6 prepare release 2018-05-20 21:09:54 -07:00
285c73e95e Re-use tcp listener on pause/resume 2018-05-20 20:47:20 -07:00
483db7028c expose low level data 2018-05-20 20:37:19 -07:00
082ff46041 Fix scope resource path extractor #234 2018-05-20 17:04:23 -07:00
f32e8f22c8 Merge pull request #231 from qrvaelet/ranges
NamedFile: range upgrade
2018-05-20 09:18:46 -07:00
766dde7c42 Merge branch 'master' into ranges 2018-05-20 08:51:07 -07:00
b68687044e range header syntax fix, change range to content-range in responses, enabled accept ranges, tests for content-range, content-length, and range status code 2018-05-20 17:40:36 +02:00
c9e84e9dd3 Merge pull request #233 from sindreij/patch-1
Fix some typos in server/srv.rs
2018-05-20 06:19:53 -07:00
0126ac46fc Fix some typos in server/srv.rs
Hello! This looks like a great library, thanks for creating it! While reading through the documentation I found a few typos.
2018-05-20 14:43:26 +02:00
97 changed files with 11479 additions and 7075 deletions

View File

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

View File

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

View File

@ -1,5 +1,192 @@
# Changes # Changes
## [0.7.2] - 2018-07-26
### Added
* Add implementation of `FromRequest<S>` for `Option<T>` and `Result<T, Error>`
* Allow to handle application prefix, i.e. allow to handle `/app` path
for application with `/app` prefix.
Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix)
api doc.
* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies
### Changed
* Upgrade to cookie 0.11
* Removed the timestamp from the default logger middleware
### Fixed
* Missing response header "content-encoding" #421
* Fix stream draining for http/2 connections #290
## [0.7.1] - 2018-07-21
### Fixed
* Fixed default_resource 'not yet implemented' panic #410
## [0.7.0] - 2018-07-21
### Added
* Add `fs::StaticFileConfig` to provide means of customizing static
file services. It allows to map `mime` to `Content-Disposition`,
specify whether to use `ETag` and `Last-Modified` and allowed methods.
* Add `.has_prefixed_resource()` method to `router::ResourceInfo`
for route matching with prefix awareness
* Add `HttpMessage::readlines()` for reading line by line.
* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests.
* Add method to configure custom error handler to `Form` extractor.
* Add methods to `HttpResponse` to retrieve, add, and delete cookies
* Add `.set_content_type()` and `.set_content_disposition()` methods
to `fs::NamedFile` to allow overriding the values inferred by default
* Add `fs::file_extension_to_mime()` helper function to get the MIME
type for a file extension
* Add `.content_disposition()` method to parse Content-Disposition of
multipart fields
* Re-export `actix::prelude::*` as `actix_web::actix` module.
* `HttpRequest::url_for_static()` for a named route with no variables segments
* Propagation of the application's default resource to scopes that haven't set a default resource.
### Changed
* Min rustc version is 1.26
* Use tokio instead of tokio-core
* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages.
* Became possible to use enums with query extractor.
Issue [#371](https://github.com/actix/actix-web/issues/371).
[Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134)
* `HttpResponse::into_builder()` now moves cookies into the builder
instead of dropping them
* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest`
* Added header `User-Agent: Actix-web/<current_version>` to default headers when building a request
* port `Extensions` type from http create, we don't need `Send + Sync`
* `HttpRequest::query()` returns `Ref<HashMap<String, String>>`
* `HttpRequest::cookies()` returns `Ref<Vec<Cookie<'static>>>`
* `StaticFiles::new()` returns `Result<StaticFiles<S>, Error>` instead of `StaticFiles<S>`
* `StaticFiles` uses the default handler if the file does not exist
### Removed
* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead.
* Remove `HttpMessage::range()`
## [0.6.15] - 2018-07-11
### Fixed
* Fix h2 compatibility #352
* Fix duplicate tail of StaticFiles with index_file. #344
## [0.6.14] - 2018-06-21
### Added
* Allow to disable masking for websockets client
### Fixed
* SendRequest execution fails with the "internal error: entered unreachable code" #329
## [0.6.13] - 2018-06-11
* http/2 end-of-frame is not set if body is empty bytes #307
* InternalError can trigger memory unsafety #301
## [0.6.12] - 2018-06-08
### Added
* Add `Host` filter #287
* Allow to filter applications
* Improved failure interoperability with downcasting #285
* Allow to use custom resolver for `ClientConnector`
## [0.6.11] - 2018-06-05
* Support chunked encoding for UrlEncoded body #262
* `HttpRequest::url_for()` for a named route with no variables segments #265
* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255
* CORS: Do not validate Origin header on non-OPTION requests #271
* Fix multipart upload "Incomplete" error #282
## [0.6.10] - 2018-05-24
### Added
* Allow to use path without trailing slashes for scope registration #241
* Allow to set encoding for exact NamedFile #239
### Fixed
* `TestServer::post()` actually sends `GET` request #240
## 0.6.9 (2018-05-22)
* Drop connection if request's payload is not fully consumed #236
* Fix streaming response with body compression
## 0.6.8 (2018-05-20)
* Fix scope resource path extractor #234
* Re-use tcp listener on pause/resume
## 0.6.7 (2018-05-17) ## 0.6.7 (2018-05-17)
* Fix compilation with --no-default-features * Fix compilation with --no-default-features
@ -338,7 +525,7 @@
* Server multi-threading * Server multi-threading
* Gracefull shutdown support * Graceful shutdown support
## 0.2.1 (2017-11-03) ## 0.2.1 (2017-11-03)

View File

@ -1,13 +1,13 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "0.6.7" version = "0.7.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://github.com/actix/actix-web" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/" documentation = "https://actix.rs/api/actix-web/stable/actix_web/"
categories = ["network-programming", "asynchronous", categories = ["network-programming", "asynchronous",
"web-programming::http-server", "web-programming::http-server",
"web-programming::http-client", "web-programming::http-client",
@ -16,6 +16,9 @@ license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs" build = "build.rs"
[package.metadata.docs.rs]
features = ["tls", "alpn", "session", "brotli", "flate2-c"]
[badges] [badges]
travis-ci = { repository = "actix/actix-web", branch = "master" } travis-ci = { repository = "actix/actix-web", branch = "master" }
appveyor = { repository = "fafhrd91/actix-web-hdy9d" } appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
@ -47,47 +50,51 @@ flate2-c = ["flate2/miniz-sys"]
flate2-rust = ["flate2/rust_backend"] flate2-rust = ["flate2/rust_backend"]
[dependencies] [dependencies]
actix = "^0.5.5" actix = "0.7.0"
base64 = "0.9" base64 = "0.9"
bitflags = "1.0" bitflags = "1.0"
failure = "0.1.1"
h2 = "0.1" h2 = "0.1"
http = "^0.1.5" htmlescape = "0.3"
httparse = "1.2" http = "^0.1.8"
http-range = "0.1" httparse = "1.3"
libc = "0.2"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
mime_guess = "2.0.0-alpha" mime_guess = "2.0.0-alpha"
num_cpus = "1.0" num_cpus = "1.0"
percent-encoding = "1.0" percent-encoding = "1.0"
rand = "0.4" rand = "0.5"
regex = "1.0" regex = "1.0"
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" lazy_static = "1.0"
lazycell = "1.0.0"
parking_lot = "0.6"
url = { version="1.7", features=["query_encoding"] } url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode"] } cookie = { version="0.11", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true } brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="1.0", optional = true, default-features = false } flate2 = { version="1.0", optional = true, default-features = false }
failure = "=0.1.1"
# io # io
mio = "^0.6.13" mio = "^0.6.13"
net2 = "0.2" net2 = "0.2"
bytes = "0.4" bytes = "0.4"
byteorder = "1" byteorder = "1.2"
futures = "0.1" futures = "0.1"
futures-cpupool = "0.1" futures-cpupool = "0.1"
slab = "0.4" slab = "0.4"
tokio = "0.1"
tokio-io = "0.1" tokio-io = "0.1"
tokio-core = "0.1" tokio-tcp = "0.1"
tokio-timer = "0.2"
tokio-reactor = "0.1"
# native-tls # native-tls
native-tls = { version="0.1", optional = true } native-tls = { version="0.1", optional = true }
@ -97,6 +104,10 @@ tokio-tls = { version="0.1", optional = true }
openssl = { version="0.10", optional = true } openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true } tokio-openssl = { version="0.2", optional = true }
# forked url_encoded
itoa = "0.4"
dtoa = "0.4"
[dev-dependencies] [dev-dependencies]
env_logger = "0.5" env_logger = "0.5"
serde_derive = "1.0" serde_derive = "1.0"
@ -112,5 +123,4 @@ codegen-units = 1
[workspace] [workspace]
members = [ members = [
"./", "./",
"tools/wsload/",
] ]

View File

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

View File

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

View File

@ -2,41 +2,36 @@
Actix web is a simple, pragmatic and extremely fast web framework for Rust. Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols * Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols
* Streaming and pipelining * Streaming and pipelining
* Keep-alive and slow requests handling * Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support * Client/server [WebSockets](https://actix.rs/docs/websockets/) support
* Transparent content compression/decompression (br, gzip, deflate) * Transparent content compression/decompression (br, gzip, deflate)
* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html) * Configurable [request routing](https://actix.rs/docs/url-dispatch/)
* Graceful server shutdown * Graceful server shutdown
* Multipart streams * Multipart streams
* Static assets * Static assets
* SSL support with OpenSSL or `native-tls` * SSL support with OpenSSL or `native-tls`
* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging), * Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/))
[Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions), * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
[Redis sessions](https://github.com/actix/actix-redis),
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
* Includes an asynchronous [HTTP client](https://github.com/actix/actix-web/blob/master/src/client/mod.rs)
* Built on top of [Actix actor framework](https://github.com/actix/actix) * Built on top of [Actix actor framework](https://github.com/actix/actix)
## Documentation & community resources ## Documentation & community resources
* [User Guide](https://actix.rs/book/actix-web/) * [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
* [API Documentation (Releases)](https://docs.rs/actix-web/) * [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/)
* [Chat on gitter](https://gitter.im/actix/actix) * [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web) * Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.24 or later * Minimum supported Rust version: 1.26 or later
## Example ## Example
```rust ```rust
extern crate actix_web; extern crate actix_web;
use actix_web::{http, server, App, Path}; use actix_web::{http, server, App, Path, Responder};
fn index(info: Path<(u32, String)>) -> String { fn index(info: Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0) format!("Hello {}! id:{}", info.1, info.0)
} }
@ -69,7 +64,7 @@ You may consider checking out
## Benchmarks ## Benchmarks
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext) * [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext)
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks). * Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).

View File

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

View File

@ -1,163 +1,91 @@
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use handler::{AsyncResult, FromRequest, Handler, Responder, RouteHandler, WrapHandler}; use handler::{AsyncResult, FromRequest, Handler, Responder, WrapHandler};
use header::ContentEncoding; use header::ContentEncoding;
use http::Method; use http::Method;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::Middleware; use middleware::Middleware;
use pipeline::{HandlerType, Pipeline, PipelineHandler}; use pipeline::{Pipeline, PipelineHandler};
use pred::Predicate; use pred::Predicate;
use resource::ResourceHandler; use resource::Resource;
use router::{Resource, Router}; use router::{ResourceDef, Router};
use scope::Scope; use scope::Scope;
use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, ServerSettings}; use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request};
/// Application /// Application
pub struct HttpApplication<S = ()> { pub struct HttpApplication<S = ()> {
state: Rc<S>, state: Rc<S>,
prefix: String, prefix: String,
prefix_len: usize, prefix_len: usize,
router: Router, inner: Rc<Inner<S>>,
inner: Rc<UnsafeCell<Inner<S>>>, filters: Option<Vec<Box<Predicate<S>>>>,
middlewares: Rc<Vec<Box<Middleware<S>>>>, middlewares: Rc<Vec<Box<Middleware<S>>>>,
} }
pub(crate) struct Inner<S> { #[doc(hidden)]
prefix: usize, pub struct Inner<S> {
default: ResourceHandler<S>, router: Router<S>,
encoding: ContentEncoding, encoding: ContentEncoding,
resources: Vec<ResourceHandler<S>>,
handlers: Vec<PrefixHandlerType<S>>,
}
enum PrefixHandlerType<S> {
Handler(String, Box<RouteHandler<S>>),
Scope(Resource, Box<RouteHandler<S>>, Vec<Box<Predicate<S>>>),
} }
impl<S: 'static> PipelineHandler<S> for Inner<S> { impl<S: 'static> PipelineHandler<S> for Inner<S> {
#[inline]
fn encoding(&self) -> ContentEncoding { fn encoding(&self) -> ContentEncoding {
self.encoding self.encoding
} }
fn handle( fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
&mut self, req: HttpRequest<S>, htype: HandlerType, self.router.handle(req)
) -> AsyncResult<HttpResponse> {
match htype {
HandlerType::Normal(idx) => {
self.resources[idx].handle(req, Some(&mut self.default))
}
HandlerType::Handler(idx) => match self.handlers[idx] {
PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req),
PrefixHandlerType::Scope(_, ref mut hnd, _) => hnd.handle(req),
},
HandlerType::Default => self.default.handle(req, None),
}
} }
} }
impl<S: 'static> HttpApplication<S> { impl<S: 'static> HttpApplication<S> {
#[inline]
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();
let path: &'static str =
unsafe { &*(&req.path()[inner.prefix..] as *const _) };
let path_len = path.len();
'outer: for idx in 0..inner.handlers.len() {
match inner.handlers[idx] {
PrefixHandlerType::Handler(ref prefix, _) => {
let m = {
path.starts_with(prefix)
&& (path_len == prefix.len()
|| path.split_at(prefix.len()).1.starts_with('/'))
};
if m {
let prefix_len = inner.prefix + prefix.len();
let path: &'static str =
unsafe { &*(&req.path()[prefix_len..] as *const _) };
req.set_prefix_len(prefix_len as u16);
if path.is_empty() {
req.match_info_mut().add("tail", "/");
} else {
req.match_info_mut().add("tail", path);
}
return HandlerType::Handler(idx);
}
}
PrefixHandlerType::Scope(ref pattern, _, ref filters) => {
if let Some(prefix_len) =
pattern.match_prefix_with_params(path, req.match_info_mut())
{
for filter in filters {
if !filter.check(req) {
continue 'outer;
}
}
let prefix_len = inner.prefix + prefix_len - 1;
let path: &'static str =
unsafe { &*(&req.path()[prefix_len..] as *const _) };
req.set_prefix_len(prefix_len as u16);
if path.is_empty() {
req.match_info_mut().set("tail", "/");
} else {
req.match_info_mut().set("tail", path);
}
return HandlerType::Handler(idx);
}
}
}
}
HandlerType::Default
}
}
#[cfg(test)] #[cfg(test)]
pub(crate) fn run(&mut self, mut req: HttpRequest<S>) -> AsyncResult<HttpResponse> { pub(crate) fn run(&self, req: Request) -> AsyncResult<HttpResponse> {
let tp = self.get_handler(&mut req); let info = self
unsafe { &mut *self.inner.get() }.handle(req, tp) .inner
} .router
.recognize(&req, &self.state, self.prefix_len);
let req = HttpRequest::new(req, Rc::clone(&self.state), info);
#[cfg(test)] self.inner.handle(&req)
pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest<S> {
req.with_state(Rc::clone(&self.state), self.router.clone())
} }
} }
impl<S: 'static> HttpHandler for HttpApplication<S> { impl<S: 'static> HttpHandler for HttpApplication<S> {
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest> { type Task = Pipeline<S, Inner<S>>;
fn handle(&self, msg: Request) -> Result<Pipeline<S, Inner<S>>, Request> {
let m = { let m = {
let path = req.path(); if self.prefix_len == 0 {
true
} else {
let path = msg.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()); if let Some(ref filters) = self.filters {
let tp = self.get_handler(&mut req); for filter in filters {
if !filter.check(&msg, &self.state) {
return Err(msg);
}
}
}
let info = self
.inner
.router
.recognize(&msg, &self.state, self.prefix_len);
let inner = Rc::clone(&self.inner); let inner = Rc::clone(&self.inner);
Ok(Box::new(Pipeline::new( let req = HttpRequest::new(msg, Rc::clone(&self.state), info);
req, Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner))
Rc::clone(&self.middlewares),
inner,
tp,
)))
} else { } else {
Err(req) Err(msg)
} }
} }
} }
@ -165,13 +93,10 @@ impl<S: 'static> HttpHandler for HttpApplication<S> {
struct ApplicationParts<S> { struct ApplicationParts<S> {
state: S, state: S,
prefix: String, prefix: String,
settings: ServerSettings, router: Router<S>,
default: ResourceHandler<S>,
resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
handlers: Vec<PrefixHandlerType<S>>,
external: HashMap<String, Resource>,
encoding: ContentEncoding, encoding: ContentEncoding,
middlewares: Vec<Box<Middleware<S>>>, middlewares: Vec<Box<Middleware<S>>>,
filters: Vec<Box<Predicate<S>>>,
} }
/// Structure that follows the builder pattern for building application /// Structure that follows the builder pattern for building application
@ -184,19 +109,7 @@ impl App<()> {
/// Create application with empty state. Application can /// Create application with empty state. Application can
/// be configured with a builder-like pattern. /// be configured with a builder-like pattern.
pub fn new() -> App<()> { pub fn new() -> App<()> {
App { App::with_state(())
parts: Some(ApplicationParts {
state: (),
prefix: "/".to_owned(),
settings: ServerSettings::default(),
default: ResourceHandler::default_not_found(),
resources: Vec::new(),
handlers: Vec::new(),
external: HashMap::new(),
encoding: ContentEncoding::Auto,
middlewares: Vec::new(),
}),
}
} }
} }
@ -226,13 +139,10 @@ where
App { App {
parts: Some(ApplicationParts { parts: Some(ApplicationParts {
state, state,
prefix: "/".to_owned(), prefix: "".to_owned(),
settings: ServerSettings::default(), router: Router::new(),
default: ResourceHandler::default_not_found(),
resources: Vec::new(),
handlers: Vec::new(),
external: HashMap::new(),
middlewares: Vec::new(), middlewares: Vec::new(),
filters: Vec::new(),
encoding: ContentEncoding::Auto, encoding: ContentEncoding::Auto,
}), }),
} }
@ -261,7 +171,9 @@ where
/// In the following example only requests with an `/app/` path /// In the following example only requests with an `/app/` path
/// prefix get handled. Requests with path `/app/test/` would be /// prefix get handled. Requests with path `/app/test/` would be
/// handled, while requests with the paths `/application` or /// handled, while requests with the paths `/application` or
/// `/other/...` would return `NOT FOUND`. /// `/other/...` would return `NOT FOUND`. It is also possible to
/// handle `/app` path, to do this you can register resource for
/// empty string `""`
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
@ -270,6 +182,8 @@ where
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new()
/// .prefix("/app") /// .prefix("/app")
/// .resource("", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app` path
/// .resource("/", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app/` path
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok()); /// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
@ -289,6 +203,26 @@ where
self self
} }
/// Add match predicate to application.
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn main() {
/// App::new()
/// .filter(pred::Host("www.rust-lang.org"))
/// .resource("/path", |r| r.f(|_| HttpResponse::Ok()))
/// # .finish();
/// # }
/// ```
pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> App<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.filters.push(Box::new(p));
}
self
}
/// Configure route for a specific path. /// Configure route for a specific path.
/// ///
/// This is a simplified version of the `App::resource()` method. /// This is a simplified version of the `App::resource()` method.
@ -304,10 +238,12 @@ where
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new()
/// .route("/test", http::Method::GET, /// .route("/test", http::Method::GET, |_: HttpRequest| {
/// |_: HttpRequest| HttpResponse::Ok()) /// HttpResponse::Ok()
/// .route("/test", http::Method::POST, /// })
/// |_: HttpRequest| HttpResponse::MethodNotAllowed()); /// .route("/test", http::Method::POST, |_: HttpRequest| {
/// HttpResponse::MethodNotAllowed()
/// });
/// } /// }
/// ``` /// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S> pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S>
@ -316,26 +252,12 @@ where
R: Responder + 'static, R: Responder + 'static,
T: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
{ {
{ self.parts
let parts: &mut ApplicationParts<S> = unsafe { .as_mut()
&mut *(self.parts.as_mut().expect("Use after finish") as *mut _) .expect("Use after finish")
}; .router
.register_route(path, method, f);
// get resource handler
for &mut (ref pattern, ref mut handler) in &mut parts.resources {
if let Some(ref mut handler) = *handler {
if pattern.pattern() == path {
handler.method(method).with(f);
return self;
}
}
}
let mut handler = ResourceHandler::default();
handler.method(method).with(f);
let pattern = Resource::new(handler.get_name(), path);
parts.resources.push((pattern, Some(handler)));
}
self self
} }
@ -349,9 +271,9 @@ where
/// use actix_web::{http, App, HttpRequest, HttpResponse}; /// use actix_web::{http, App, HttpRequest, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/{project_id}", |scope| {
/// .scope("/{project_id}", |scope| { /// scope
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) /// .resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
/// }); /// });
@ -367,25 +289,12 @@ where
where where
F: FnOnce(Scope<S>) -> Scope<S>, F: FnOnce(Scope<S>) -> Scope<S>,
{ {
{ let scope = f(Scope::new(path));
let mut scope = Box::new(f(Scope::new())); self.parts
.as_mut()
let mut path = path.trim().trim_right_matches('/').to_owned(); .expect("Use after finish")
if !path.is_empty() && !path.starts_with('/') { .router
path.insert(0, '/') .register_scope(scope);
}
if !path.ends_with('/') {
path.push('/');
}
let parts = self.parts.as_mut().expect("Use after finish");
let filters = scope.take_filters();
parts.handlers.push(PrefixHandlerType::Scope(
Resource::prefix("", &path),
scope,
filters,
));
}
self self
} }
@ -414,8 +323,7 @@ where
/// use actix_web::{http, App, HttpResponse}; /// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().resource("/users/{userid}/{friend}", |r| {
/// .resource("/users/{userid}/{friend}", |r| {
/// r.get().f(|_| HttpResponse::Ok()); /// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// }); /// });
@ -423,41 +331,47 @@ where
/// ``` /// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> App<S> pub fn resource<F, R>(mut self, path: &str, f: F) -> App<S>
where where
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static, F: FnOnce(&mut Resource<S>) -> R + 'static,
{ {
{ {
let parts = self.parts.as_mut().expect("Use after finish"); let parts = self.parts.as_mut().expect("Use after finish");
// add resource handler // create resource
let mut handler = ResourceHandler::default(); let mut resource = Resource::new(ResourceDef::new(path));
f(&mut handler);
let pattern = Resource::new(handler.get_name(), path); // configure
parts.resources.push((pattern, Some(handler))); f(&mut resource);
parts.router.register_resource(resource);
} }
self self
} }
/// Configure resource for a specific path. /// Configure resource for a specific path.
#[doc(hidden)] #[doc(hidden)]
pub fn register_resource(&mut self, path: &str, resource: ResourceHandler<S>) { pub fn register_resource(&mut self, resource: Resource<S>) {
let pattern = Resource::new(resource.get_name(), path);
self.parts self.parts
.as_mut() .as_mut()
.expect("Use after finish") .expect("Use after finish")
.resources .router
.push((pattern, Some(resource))); .register_resource(resource);
} }
/// Default resource to be used if no matching route could be found. /// Default resource to be used if no matching route could be found.
pub fn default_resource<F, R>(mut self, f: F) -> App<S> pub fn default_resource<F, R>(mut self, f: F) -> App<S>
where where
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static, F: FnOnce(&mut Resource<S>) -> R + 'static,
{ {
{ // create and configure default resource
let parts = self.parts.as_mut().expect("Use after finish"); let mut resource = Resource::new(ResourceDef::new(""));
f(&mut parts.default); f(&mut resource);
}
self.parts
.as_mut()
.expect("Use after finish")
.router
.register_default_resource(resource.into());
self self
} }
@ -480,7 +394,7 @@ where
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{App, HttpRequest, HttpResponse, Result}; /// use actix_web::{App, HttpRequest, HttpResponse, Result};
/// ///
/// fn index(mut req: HttpRequest) -> Result<HttpResponse> { /// fn index(req: &HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
/// Ok(HttpResponse::Ok().into()) /// Ok(HttpResponse::Ok().into())
@ -498,17 +412,11 @@ where
T: AsRef<str>, T: AsRef<str>,
U: AsRef<str>, U: AsRef<str>,
{ {
{ self.parts
let parts = self.parts.as_mut().expect("Use after finish"); .as_mut()
.expect("Use after finish")
if parts.external.contains_key(name.as_ref()) { .router
panic!("External resource {:?} is registered.", name.as_ref()); .register_external(name.as_ref(), ResourceDef::external(url.as_ref()));
}
parts.external.insert(
String::from(name.as_ref()),
Resource::external(name.as_ref(), url.as_ref()),
);
}
self self
} }
@ -526,13 +434,11 @@ where
/// use actix_web::{http, App, HttpRequest, HttpResponse}; /// use actix_web::{http, App, HttpRequest, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().handler("/app", |req: &HttpRequest| match *req.method() {
/// .handler("/app", |req: HttpRequest| {
/// match *req.method() {
/// http::Method::GET => HttpResponse::Ok(), /// http::Method::GET => HttpResponse::Ok(),
/// http::Method::POST => HttpResponse::MethodNotAllowed(), /// http::Method::POST => HttpResponse::MethodNotAllowed(),
/// _ => HttpResponse::NotFound(), /// _ => HttpResponse::NotFound(),
/// }}); /// });
/// } /// }
/// ``` /// ```
pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> App<S> { pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> App<S> {
@ -544,12 +450,11 @@ where
if path.len() > 1 && path.ends_with('/') { if path.len() > 1 && path.ends_with('/') {
path.pop(); path.pop();
} }
let parts = self.parts.as_mut().expect("Use after finish"); self.parts
.as_mut()
parts.handlers.push(PrefixHandlerType::Handler( .expect("Use after finish")
path, .router
Box::new(WrapHandler::new(handler)), .register_handler(&path, Box::new(WrapHandler::new(handler)), None);
));
} }
self self
} }
@ -573,12 +478,11 @@ where
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{App, HttpResponse, fs, middleware}; /// use actix_web::{fs, middleware, App, HttpResponse};
/// ///
/// // this function could be located in different module /// // this function could be located in different module
/// fn config(app: App) -> App { /// fn config(app: App) -> App {
/// app /// app.resource("/test", |r| {
/// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok()); /// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// }) /// })
@ -588,7 +492,7 @@ where
/// let app = App::new() /// let app = App::new()
/// .middleware(middleware::Logger::default()) /// .middleware(middleware::Logger::default())
/// .configure(config) // <- register resources /// .configure(config) // <- register resources
/// .handler("/static", fs::StaticFiles::new(".")); /// .handler("/static", fs::StaticFiles::new(".").unwrap());
/// } /// }
/// ``` /// ```
pub fn configure<F>(self, cfg: F) -> App<S> pub fn configure<F>(self, cfg: F) -> App<S>
@ -600,36 +504,27 @@ where
/// Finish application configuration and create `HttpHandler` object. /// 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 mut 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() { parts.router.finish();
("/".to_owned(), 0)
let inner = Rc::new(Inner {
router: parts.router,
encoding: parts.encoding,
});
let filters = if parts.filters.is_empty() {
None
} else { } else {
(prefix.to_owned(), prefix.len()) Some(parts.filters)
}; };
let mut resources = parts.resources;
for (_, pattern) in parts.external {
resources.push((pattern, None));
}
let (router, resources) = Router::new(&prefix, parts.settings, resources);
let inner = Rc::new(UnsafeCell::new(Inner {
prefix: prefix_len,
default: parts.default,
encoding: parts.encoding,
handlers: parts.handlers,
resources,
}));
HttpApplication { HttpApplication {
state: Rc::new(parts.state),
router: router.clone(),
middlewares: Rc::new(parts.middlewares),
prefix,
prefix_len,
inner, inner,
filters,
state: Rc::new(parts.state),
middlewares: Rc::new(parts.middlewares),
prefix: prefix.to_owned(),
prefix_len: prefix.len(),
} }
} }
@ -649,7 +544,8 @@ where
/// ///
/// fn main() { /// fn main() {
/// # thread::spawn(|| { /// # thread::spawn(|| {
/// server::new(|| { vec![ /// server::new(|| {
/// vec![
/// App::with_state(State1) /// App::with_state(State1)
/// .prefix("/app1") /// .prefix("/app1")
/// .resource("/", |r| r.f(|r| HttpResponse::Ok())) /// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
@ -657,25 +553,38 @@ where
/// App::with_state(State2) /// App::with_state(State2)
/// .prefix("/app2") /// .prefix("/app2")
/// .resource("/", |r| r.f(|r| HttpResponse::Ok())) /// .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()
/// # }); /// # });
/// } /// }
/// ``` /// ```
pub fn boxed(mut self) -> Box<HttpHandler> { pub fn boxed(mut self) -> Box<HttpHandler<Task = Box<HttpHandlerTask>>> {
Box::new(self.finish()) Box::new(BoxedApplication { app: self.finish() })
}
}
struct BoxedApplication<S> {
app: HttpApplication<S>,
}
impl<S: 'static> HttpHandler for BoxedApplication<S> {
type Task = Box<HttpHandlerTask>;
fn handle(&self, req: Request) -> Result<Self::Task, Request> {
self.app.handle(req).map(|t| {
let task: Self::Task = Box::new(t);
task
})
} }
} }
impl<S: 'static> IntoHttpHandler for App<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) -> HttpApplication<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.settings = settings;
}
self.finish() self.finish()
} }
} }
@ -683,11 +592,7 @@ impl<S: 'static> IntoHttpHandler for App<S> {
impl<'a, S: 'static> IntoHttpHandler for &'a mut App<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) -> HttpApplication<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.settings = settings;
}
self.finish() self.finish()
} }
} }
@ -708,206 +613,269 @@ impl<S: 'static> Iterator for App<S> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use body::{Binary, Body};
use http::StatusCode; use http::StatusCode;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use test::TestRequest; use pred;
use test::{TestRequest, TestServer};
#[test] #[test]
fn test_default_resource() { fn test_default_resource() {
let mut app = App::new() let app = App::new()
.resource("/test", |r| r.f(|_| HttpResponse::Ok())) .resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/blah").finish(); let req = TestRequest::with_uri("/blah").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let mut app = App::new() let app = App::new()
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed()))
.finish(); .finish();
let req = TestRequest::with_uri("/blah").finish(); let req = TestRequest::with_uri("/blah").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
} }
#[test] #[test]
fn test_unhandled_prefix() { fn test_unhandled_prefix() {
let mut app = App::new() let app = App::new()
.prefix("/test") .prefix("/test")
.resource("/test", |r| r.f(|_| HttpResponse::Ok())) .resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.finish(); .finish();
assert!(app.handle(HttpRequest::default()).is_err()); let ctx = TestRequest::default().request();
assert!(app.handle(ctx).is_err());
} }
#[test] #[test]
fn test_state() { fn test_state() {
let mut app = App::with_state(10) let app = App::with_state(10)
.resource("/", |r| r.f(|_| HttpResponse::Ok())) .resource("/", |r| r.f(|_| HttpResponse::Ok()))
.finish(); .finish();
let req = let req = TestRequest::with_state(10).request();
HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone());
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
} }
#[test] #[test]
fn test_prefix() { fn test_prefix() {
let mut app = App::new() let app = App::new()
.prefix("/test") .prefix("/test")
.resource("/blah", |r| r.f(|_| HttpResponse::Ok())) .resource("/blah", |r| r.f(|_| HttpResponse::Ok()))
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").request();
let resp = app.handle(req); let resp = app.handle(req);
assert!(resp.is_ok()); assert!(resp.is_ok());
let req = TestRequest::with_uri("/test/").finish(); let req = TestRequest::with_uri("/test/").request();
let resp = app.handle(req); let resp = app.handle(req);
assert!(resp.is_ok()); assert!(resp.is_ok());
let req = TestRequest::with_uri("/test/blah").finish(); let req = TestRequest::with_uri("/test/blah").request();
let resp = app.handle(req); let resp = app.handle(req);
assert!(resp.is_ok()); assert!(resp.is_ok());
let req = TestRequest::with_uri("/testing").finish(); let req = TestRequest::with_uri("/testing").request();
let resp = app.handle(req); let resp = app.handle(req);
assert!(resp.is_err()); assert!(resp.is_err());
} }
#[test] #[test]
fn test_handler() { fn test_handler() {
let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish(); let app = App::new()
.handler("/test", |_: &_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/").finish(); let req = TestRequest::with_uri("/test/").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/app").finish(); let req = TestRequest::with_uri("/test/app").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/testapp").finish(); let req = TestRequest::with_uri("/testapp").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/blah").finish(); let req = TestRequest::with_uri("/blah").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
#[test] #[test]
fn test_handler2() { fn test_handler2() {
let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish(); let app = App::new()
.handler("test", |_: &_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/").finish(); let req = TestRequest::with_uri("/test/").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test/app").finish(); let req = TestRequest::with_uri("/test/app").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/testapp").finish(); let req = TestRequest::with_uri("/testapp").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/blah").finish(); let req = TestRequest::with_uri("/blah").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
#[test] #[test]
fn test_handler_with_prefix() { fn test_handler_with_prefix() {
let mut app = App::new() let app = App::new()
.prefix("prefix") .prefix("prefix")
.handler("/test", |_| HttpResponse::Ok()) .handler("/test", |_: &_| HttpResponse::Ok())
.finish(); .finish();
let req = TestRequest::with_uri("/prefix/test").finish(); let req = TestRequest::with_uri("/prefix/test").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/test/").finish(); let req = TestRequest::with_uri("/prefix/test/").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/test/app").finish(); let req = TestRequest::with_uri("/prefix/test/app").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/prefix/testapp").finish(); let req = TestRequest::with_uri("/prefix/testapp").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/prefix/blah").finish(); let req = TestRequest::with_uri("/prefix/blah").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
#[test] #[test]
fn test_route() { fn test_route() {
let mut app = App::new() let app = App::new()
.route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok())
.route("/test", Method::POST, |_: HttpRequest| { .route("/test", Method::POST, |_: HttpRequest| {
HttpResponse::Created() HttpResponse::Created()
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/test").method(Method::GET).finish(); let req = TestRequest::with_uri("/test").method(Method::GET).request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test").method(Method::POST).finish(); let req = TestRequest::with_uri("/test")
.method(Method::POST)
.request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test").method(Method::HEAD).finish(); let req = TestRequest::with_uri("/test")
.method(Method::HEAD)
.request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
#[test] #[test]
fn test_handler_prefix() { fn test_handler_prefix() {
let mut app = App::new() let app = App::new()
.prefix("/app") .prefix("/app")
.handler("/test", |_| HttpResponse::Ok()) .handler("/test", |_: &_| HttpResponse::Ok())
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/test").finish(); let req = TestRequest::with_uri("/app/test").request();
let resp = app.run(req.clone());
assert_eq!(resp.as_msg().status(), StatusCode::OK);
assert_eq!(req.prefix_len(), 9);
let req = TestRequest::with_uri("/app/test/").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/test/app").finish(); let req = TestRequest::with_uri("/app/test/").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/testapp").finish(); let req = TestRequest::with_uri("/app/test/app").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/testapp").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/blah").finish(); let req = TestRequest::with_uri("/app/blah").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
#[test]
fn test_option_responder() {
let app = App::new()
.resource("/none", |r| r.f(|_| -> Option<&'static str> { None }))
.resource("/some", |r| r.f(|_| Some("some")))
.finish();
let req = TestRequest::with_uri("/none").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/some").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some")));
}
#[test]
fn test_filter() {
let mut srv = TestServer::with_factory(|| {
App::new()
.filter(pred::Get())
.handler("/test", |_: &_| HttpResponse::Ok())
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv.post().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_prefix_root() {
let mut srv = TestServer::with_factory(|| {
App::new()
.prefix("/test")
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
.resource("", |r| r.f(|_| HttpResponse::Created()))
});
let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
}
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
//! Error and Result module //! Error and Result module
use std::cell::RefCell;
use std::io::Error as IoError; use std::io::Error as IoError;
use std::str::Utf8Error; use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use std::sync::Mutex;
use std::{fmt, io, result}; use std::{fmt, io, result};
use actix::MailboxError; use actix::MailboxError;
@ -12,10 +12,11 @@ use futures::Canceled;
use http::uri::InvalidUri; use http::uri::InvalidUri;
use http::{header, Error as HttpError, StatusCode}; use http::{header, Error as HttpError, StatusCode};
use http2::Error as Http2Error; use http2::Error as Http2Error;
use http_range::HttpRangeParseError;
use httparse; use httparse;
use serde::de::value::Error as DeError; use serde::de::value::Error as DeError;
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use serde_urlencoded::ser::Error as FormError;
use tokio_timer::Error as TimerError;
pub use url::ParseError as UrlParseError; pub use url::ParseError as UrlParseError;
// re-exports // re-exports
@ -23,7 +24,7 @@ pub use cookie::ParseError as CookieParseError;
use handler::Responder; use handler::Responder;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::{HttpResponse, HttpResponseParts};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations /// for actix web operations
@ -33,21 +34,44 @@ use httpresponse::HttpResponse;
/// `Result`. /// `Result`.
pub type Result<T, E = Error> = result::Result<T, E>; pub type Result<T, E = Error> = result::Result<T, E>;
/// General purpose actix web error /// General purpose actix web error.
///
/// An actix web error is used to carry errors from `failure` or `std::error`
/// through actix in a convenient way. It can be created through
/// converting errors with `into()`.
///
/// Whenever it is created from an external object a response error is created
/// for it that can be used to create an http response from it this means that
/// if you have access to an actix `Error` you can always get a
/// `ResponseError` reference from it.
pub struct Error { pub struct Error {
cause: Box<ResponseError>, cause: Box<ResponseError>,
backtrace: Option<Backtrace>, backtrace: Option<Backtrace>,
} }
impl Error { impl Error {
/// Returns a reference to the underlying cause of this Error. /// Deprecated way to reference the underlying response error.
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665 #[deprecated(
since = "0.6.0", note = "please use `Error::as_response_error()` instead"
)]
pub fn cause(&self) -> &ResponseError { pub fn cause(&self) -> &ResponseError {
self.cause.as_ref() self.cause.as_ref()
} }
/// Returns a reference to the underlying cause of this `Error` as `Fail`
pub fn as_fail(&self) -> &Fail {
self.cause.as_fail()
}
/// Returns the reference to the underlying `ResponseError`.
pub fn as_response_error(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Returns a reference to the Backtrace carried by this error, if it /// Returns a reference to the Backtrace carried by this error, if it
/// carries one. /// carries one.
///
/// This uses the same `Backtrace` type that `failure` uses.
pub fn backtrace(&self) -> &Backtrace { pub fn backtrace(&self) -> &Backtrace {
if let Some(bt) = self.cause.backtrace() { if let Some(bt) = self.cause.backtrace() {
bt bt
@ -55,10 +79,66 @@ impl Error {
self.backtrace.as_ref().unwrap() self.backtrace.as_ref().unwrap()
} }
} }
/// Attempts to downcast this `Error` to a particular `Fail` type by
/// reference.
///
/// If the underlying error is not of type `T`, this will return `None`.
pub fn downcast_ref<T: Fail>(&self) -> Option<&T> {
// in the most trivial way the cause is directly of the requested type.
if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) {
return Some(rv);
}
// in the more complex case the error has been constructed from a failure
// error. This happens because we implement From<failure::Error> by
// calling compat() and then storing it here. In failure this is
// represented by a failure::Error being wrapped in a failure::Compat.
//
// So we first downcast into that compat, to then further downcast through
// the failure's Error downcasting system into the original failure.
//
// This currently requires a transmute. This could be avoided if failure
// provides a deref: https://github.com/rust-lang-nursery/failure/pull/213
let compat: Option<&failure::Compat<failure::Error>> =
Fail::downcast_ref(self.cause.as_fail());
if let Some(compat) = compat {
pub struct CompatWrappedError {
error: failure::Error,
}
let compat: &CompatWrappedError =
unsafe { &*(compat as *const _ as *const CompatWrappedError) };
compat.error.downcast_ref()
} else {
None
}
}
}
/// Helper trait to downcast a response error into a fail.
///
/// This is currently not exposed because it's unclear if this is the best way
/// to achieve the downcasting on `Error` for which this is needed.
#[doc(hidden)]
pub trait InternalResponseErrorAsFail {
#[doc(hidden)]
fn as_fail(&self) -> &Fail;
#[doc(hidden)]
fn as_mut_fail(&mut self) -> &mut Fail;
}
#[doc(hidden)]
impl<T: ResponseError> InternalResponseErrorAsFail for T {
fn as_fail(&self) -> &Fail {
self
}
fn as_mut_fail(&mut self) -> &mut Fail {
self
}
} }
/// Error that can be converted to `HttpResponse` /// Error that can be converted to `HttpResponse`
pub trait ResponseError: Fail { pub trait ResponseError: Fail + InternalResponseErrorAsFail {
/// Create response for error /// Create response for error
/// ///
/// Internal server error is generated by default. /// Internal server error is generated by default.
@ -111,11 +191,9 @@ impl<T: ResponseError> From<T> for Error {
} }
/// Compatibility for `failure::Error` /// Compatibility for `failure::Error`
impl<T> ResponseError for failure::Compat<T> impl<T> ResponseError for failure::Compat<T> where
where T: fmt::Display + fmt::Debug + Sync + Send + 'static
T: fmt::Display + fmt::Debug + Sync + Send + 'static, {}
{
}
impl From<failure::Error> for Error { impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error { fn from(err: failure::Error) -> Error {
@ -126,6 +204,12 @@ impl From<failure::Error> for Error {
/// `InternalServerError` for `JsonError` /// `InternalServerError` for `JsonError`
impl ResponseError for JsonError {} impl ResponseError for JsonError {}
/// `InternalServerError` for `FormError`
impl ResponseError for FormError {}
/// `InternalServerError` for `TimerError`
impl ResponseError for TimerError {}
/// `InternalServerError` for `UrlParseError` /// `InternalServerError` for `UrlParseError`
impl ResponseError for UrlParseError {} impl ResponseError for UrlParseError {}
@ -289,8 +373,18 @@ impl From<IoError> for PayloadError {
} }
} }
/// `InternalServerError` for `PayloadError` /// `PayloadError` returns two possible results:
impl ResponseError for PayloadError {} ///
/// - `Overflow` returns `PayloadTooLarge`
/// - Other errors returns `BadRequest`
impl ResponseError for PayloadError {
fn error_response(&self) -> HttpResponse {
match *self {
PayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE),
_ => HttpResponse::new(StatusCode::BAD_REQUEST),
}
}
}
/// Return `BadRequest` for `cookie::ParseError` /// Return `BadRequest` for `cookie::ParseError`
impl ResponseError for cookie::ParseError { impl ResponseError for cookie::ParseError {
@ -299,37 +393,6 @@ impl ResponseError for cookie::ParseError {
} }
} }
/// Http range header parsing error
#[derive(Fail, PartialEq, Debug)]
pub enum HttpRangeError {
/// Returned if range is invalid.
#[fail(display = "Range header is invalid")]
InvalidRange,
/// Returned if first-byte-pos of all of the byte-range-spec
/// values is greater than the content size.
/// See `https://github.com/golang/go/commit/aa9b3d7`
#[fail(
display = "First-byte-pos of all of the byte-range-spec values is greater than the content size"
)]
NoOverlap,
}
/// Return `BadRequest` for `HttpRangeError`
impl ResponseError for HttpRangeError {
fn error_response(&self) -> HttpResponse {
HttpResponse::with_body(StatusCode::BAD_REQUEST, "Invalid Range header provided")
}
}
impl From<HttpRangeParseError> for HttpRangeError {
fn from(err: HttpRangeParseError) -> HttpRangeError {
match err {
HttpRangeParseError::InvalidRange => HttpRangeError::InvalidRange,
HttpRangeParseError::NoOverlap => HttpRangeError::NoOverlap,
}
}
}
/// A set of errors that can occur during parsing multipart streams /// A set of errors that can occur during parsing multipart streams
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
pub enum MultipartError { pub enum MultipartError {
@ -413,8 +476,10 @@ pub enum UrlencodedError {
/// Can not decode chunked transfer encoding /// Can not decode chunked transfer encoding
#[fail(display = "Can not decode chunked transfer encoding")] #[fail(display = "Can not decode chunked transfer encoding")]
Chunked, Chunked,
/// Payload size is bigger than 256k /// Payload size is bigger than allowed. (default: 256kB)
#[fail(display = "Payload size is bigger than 256k")] #[fail(
display = "Urlencoded payload size is bigger than allowed. (default: 256kB)"
)]
Overflow, Overflow,
/// Payload size is now known /// Payload size is now known
#[fail(display = "Payload size is now known")] #[fail(display = "Payload size is now known")]
@ -454,8 +519,8 @@ impl From<PayloadError> for UrlencodedError {
/// A set of errors that can occur during parsing json payloads /// A set of errors that can occur during parsing json payloads
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
pub enum JsonPayloadError { pub enum JsonPayloadError {
/// Payload size is bigger than 256k /// Payload size is bigger than allowed. (default: 256kB)
#[fail(display = "Payload size is bigger than 256k")] #[fail(display = "Json payload size is bigger than allowed. (default: 256kB)")]
Overflow, Overflow,
/// Content type error /// Content type error
#[fail(display = "Content type error")] #[fail(display = "Content type error")]
@ -492,6 +557,30 @@ impl From<JsonError> for JsonPayloadError {
} }
} }
/// Error type returned when reading body as lines.
pub enum ReadlinesError {
/// Error when decoding a line.
EncodingError,
/// Payload error.
PayloadError(PayloadError),
/// Line limit exceeded.
LimitOverflow,
/// ContentType error.
ContentTypeError(ContentTypeError),
}
impl From<PayloadError> for ReadlinesError {
fn from(err: PayloadError) -> Self {
ReadlinesError::PayloadError(err)
}
}
impl From<ContentTypeError> for ReadlinesError {
fn from(err: ContentTypeError) -> Self {
ReadlinesError::ContentTypeError(err)
}
}
/// Errors which can occur when attempting to interpret a segment string as a /// Errors which can occur when attempting to interpret a segment string as a
/// valid path segment. /// valid path segment.
#[derive(Fail, Debug, PartialEq)] #[derive(Fail, Debug, PartialEq)]
@ -517,12 +606,13 @@ impl ResponseError for UriSegmentError {
/// Errors which can occur when attempting to generate resource uri. /// Errors which can occur when attempting to generate resource uri.
#[derive(Fail, Debug, PartialEq)] #[derive(Fail, Debug, PartialEq)]
pub enum UrlGenerationError { pub enum UrlGenerationError {
/// Resource not found
#[fail(display = "Resource not found")] #[fail(display = "Resource not found")]
ResourceNotFound, ResourceNotFound,
/// Not all path pattern covered
#[fail(display = "Not all path pattern covered")] #[fail(display = "Not all path pattern covered")]
NotEnoughElements, NotEnoughElements,
#[fail(display = "Router is not available")] /// URL parse error
RouterNotAvailable,
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
ParseError(#[cause] UrlParseError), ParseError(#[cause] UrlParseError),
} }
@ -536,6 +626,24 @@ impl From<UrlParseError> for UrlGenerationError {
} }
} }
/// Errors which can occur when serving static files.
#[derive(Fail, Debug, PartialEq)]
pub enum StaticFileError {
/// Path is not a directory
#[fail(display = "Path is not a directory. Unable to serve static files")]
IsNotDirectory,
/// Cannot render directory
#[fail(display = "Unable to render directory without index file")]
IsDirectory,
}
/// Return `NotFound` for `StaticFileError`
impl ResponseError for StaticFileError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::NOT_FOUND)
}
}
/// Helper type that can wrap any error and generate custom response. /// Helper type that can wrap any error and generate custom response.
/// ///
/// In following example any `io::Error` will be converted into "BAD REQUEST" /// In following example any `io::Error` will be converted into "BAD REQUEST"
@ -559,12 +667,9 @@ pub struct InternalError<T> {
backtrace: Backtrace, backtrace: Backtrace,
} }
unsafe impl<T> Sync for InternalError<T> {}
unsafe impl<T> Send for InternalError<T> {}
enum InternalErrorType { enum InternalErrorType {
Status(StatusCode), Status(StatusCode),
Response(RefCell<Option<HttpResponse>>), Response(Box<Mutex<Option<HttpResponseParts>>>),
} }
impl<T> InternalError<T> { impl<T> InternalError<T> {
@ -579,9 +684,10 @@ impl<T> InternalError<T> {
/// Create `InternalError` with predefined `HttpResponse`. /// Create `InternalError` with predefined `HttpResponse`.
pub fn from_response(cause: T, response: HttpResponse) -> Self { pub fn from_response(cause: T, response: HttpResponse) -> Self {
let resp = response.into_parts();
InternalError { InternalError {
cause, cause,
status: InternalErrorType::Response(RefCell::new(Some(response))), status: InternalErrorType::Response(Box::new(Mutex::new(Some(resp)))),
backtrace: Backtrace::new(), backtrace: Backtrace::new(),
} }
} }
@ -622,8 +728,8 @@ where
match self.status { match self.status {
InternalErrorType::Status(st) => HttpResponse::new(st), InternalErrorType::Status(st) => HttpResponse::new(st),
InternalErrorType::Response(ref resp) => { InternalErrorType::Response(ref resp) => {
if let Some(resp) = resp.borrow_mut().take() { if let Some(resp) = resp.lock().unwrap().take() {
resp HttpResponse::from_parts(resp)
} else { } else {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
} }
@ -818,9 +924,6 @@ mod tests {
let resp: HttpResponse = ParseError::Incomplete.error_response(); let resp: HttpResponse = ParseError::Incomplete.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = HttpRangeError::InvalidRange.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = CookieParseError::EmptyName.error_response(); let resp: HttpResponse = CookieParseError::EmptyName.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
@ -833,7 +936,7 @@ mod tests {
} }
#[test] #[test]
fn test_cause() { fn test_as_fail() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.description().to_owned();
let e = ParseError::Io(orig); let e = ParseError::Io(orig);
@ -851,7 +954,7 @@ mod tests {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.description().to_owned();
let e = Error::from(orig); let e = Error::from(orig);
assert_eq!(format!("{}", e.cause()), desc); assert_eq!(format!("{}", e.as_fail()), desc);
} }
#[test] #[test]
@ -870,14 +973,6 @@ mod tests {
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
#[test]
fn test_range_error() {
let e: HttpRangeError = HttpRangeParseError::InvalidRange.into();
assert_eq!(e, HttpRangeError::InvalidRange);
let e: HttpRangeError = HttpRangeParseError::NoOverlap.into();
assert_eq!(e, HttpRangeError::NoOverlap);
}
#[test] #[test]
fn test_expect_error() { fn test_expect_error() {
let resp: HttpResponse = ExpectError::Encoding.error_response(); let resp: HttpResponse = ExpectError::Encoding.error_response();
@ -950,6 +1045,32 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test]
fn test_error_downcasting_direct() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = DemoError.into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_downcasting_compat() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = failure::Error::from(DemoError).into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test] #[test]
fn test_error_helpers() { fn test_error_helpers() {
let r: HttpResponse = ErrorBadRequest("err").into(); let r: HttpResponse = ErrorBadRequest("err").into();

113
src/extensions.rs Normal file
View File

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

View File

@ -1,21 +1,23 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::str; use std::rc::Rc;
use std::{fmt, str};
use bytes::Bytes; use bytes::Bytes;
use encoding::all::UTF_8; use encoding::all::UTF_8;
use encoding::types::{DecoderTrap, Encoding}; use encoding::types::{DecoderTrap, Encoding};
use futures::{Async, Future, Poll}; use futures::{future, Async, Future, Poll};
use mime::Mime; use mime::Mime;
use serde::de::{self, DeserializeOwned}; use serde::de::{self, DeserializeOwned};
use serde_urlencoded; use serde_urlencoded;
use de::PathDeserializer; use de::PathDeserializer;
use error::{Error, ErrorBadRequest, ErrorNotFound}; use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError};
use handler::{AsyncResult, FromRequest}; use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest; use httprequest::HttpRequest;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's path. /// Extract typed information from the request's path.
/// ///
/// ## Example /// ## Example
@ -24,7 +26,7 @@ use httprequest::HttpRequest;
/// # extern crate bytes; /// # extern crate bytes;
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// use actix_web::{App, Path, Result, http}; /// use actix_web::{http, App, Path, Result};
/// ///
/// /// extract path info from "/{username}/{count}/index.html" url /// /// extract path info from "/{username}/{count}/index.html" url
/// /// {username} - deserializes to a String /// /// {username} - deserializes to a String
@ -36,7 +38,8 @@ use httprequest::HttpRequest;
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().resource(
/// "/{username}/{count}/index.html", // <- define path parameters /// "/{username}/{count}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// } /// }
/// ``` /// ```
/// ///
@ -48,7 +51,7 @@ use httprequest::HttpRequest;
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Result, http}; /// use actix_web::{http, App, Path, Result};
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// struct Info {
@ -63,7 +66,8 @@ use httprequest::HttpRequest;
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters /// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// } /// }
/// ``` /// ```
pub struct Path<T> { pub struct Path<T> {
@ -113,6 +117,19 @@ where
} }
} }
impl<T: fmt::Debug> fmt::Debug for Path<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Path<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from from the request's query. /// Extract typed information from from the request's query.
/// ///
/// ## Example /// ## Example
@ -124,15 +141,24 @@ where
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Query, http}; /// use actix_web::{App, Query, http};
/// ///
/// #[derive(Deserialize)] ///
/// struct Info { ///#[derive(Debug, Deserialize)]
/// username: String, ///pub enum ResponseType {
/// } /// Token,
/// Code
///}
///
///#[derive(Deserialize)]
///pub struct AuthRequest {
/// id: u64,
/// response_type: ResponseType,
///}
/// ///
/// // use `with` extractor for query info /// // use `with` extractor for query info
/// // this handler get called only if request's query contains `username` field /// // this handler get called only if request's query contains `username` field
/// fn index(info: Query<Info>) -> String { /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
/// format!("Welcome {}!", info.username) /// fn index(info: Query<AuthRequest>) -> String {
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
/// } /// }
/// ///
/// fn main() { /// fn main() {
@ -173,13 +199,25 @@ where
#[inline] #[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone();
serde_urlencoded::from_str::<T>(req.query_string()) serde_urlencoded::from_str::<T>(req.query_string())
.map_err(|e| e.into()) .map_err(|e| e.into())
.map(Query) .map(Query)
} }
} }
impl<T: fmt::Debug> fmt::Debug for Query<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Query<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's body. /// Extract typed information from the request's body.
/// ///
/// To extract typed information from request's body, the type `T` must /// To extract typed information from request's body, the type `T` must
@ -236,26 +274,40 @@ where
T: DeserializeOwned + 'static, T: DeserializeOwned + 'static,
S: 'static, S: 'static,
{ {
type Config = FormConfig; type Config = FormConfig<S>;
type Result = Box<Future<Item = Self, Error = Error>>; type Result = Box<Future<Item = Self, Error = Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let req2 = req.clone();
let err = Rc::clone(&cfg.ehandler);
Box::new( Box::new(
UrlEncoded::new(req.clone()) UrlEncoded::new(req)
.limit(cfg.limit) .limit(cfg.limit)
.from_err() .map_err(move |e| (*err)(e, &req2))
.map(Form), .map(Form),
) )
} }
} }
impl<T: fmt::Debug> fmt::Debug for Form<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Form<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
/// Form extractor configuration /// Form extractor configuration
/// ///
/// ```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::{App, Form, Result, http}; /// use actix_web::{http, App, Form, Result};
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct FormData { /// struct FormData {
@ -270,28 +322,43 @@ where
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().resource(
/// "/index.html", |r| { /// "/index.html",
/// |r| {
/// r.method(http::Method::GET) /// r.method(http::Method::GET)
/// .with(index) /// // register form handler and change form extractor configuration
/// .limit(4096);} // <- change form extractor configuration /// .with_config(index, |cfg| {cfg.limit(4096);})
/// },
/// ); /// );
/// } /// }
/// ``` /// ```
pub struct FormConfig { pub struct FormConfig<S> {
limit: usize, limit: usize,
ehandler: Rc<Fn(UrlencodedError, &HttpRequest<S>) -> Error>,
} }
impl FormConfig { impl<S> FormConfig<S> {
/// Change max size of payload. By default max size is 256Kb /// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self { pub fn limit(&mut self, limit: usize) -> &mut Self {
self.limit = limit; self.limit = limit;
self self
} }
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
where
F: Fn(UrlencodedError, &HttpRequest<S>) -> Error + 'static,
{
self.ehandler = Rc::new(f);
self
}
} }
impl Default for FormConfig { impl<S> Default for FormConfig<S> {
fn default() -> Self { fn default() -> Self {
FormConfig { limit: 262_144 } FormConfig {
limit: 262_144,
ehandler: Rc::new(|e, _| e.into()),
}
} }
} }
@ -315,9 +382,8 @@ impl Default for FormConfig {
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new()
/// "/index.html", |r| /// .resource("/index.html", |r| r.method(http::Method::GET).with(index));
/// r.method(http::Method::GET).with(index));
/// } /// }
/// ``` /// ```
impl<S: 'static> FromRequest<S> for Bytes { impl<S: 'static> FromRequest<S> for Bytes {
@ -329,9 +395,7 @@ impl<S: 'static> FromRequest<S> for Bytes {
// check content-type // check content-type
cfg.check_mimetype(req)?; cfg.check_mimetype(req)?;
Ok(Box::new( Ok(Box::new(MessageBody::new(req).limit(cfg.limit).from_err()))
MessageBody::new(req.clone()).limit(cfg.limit).from_err(),
))
} }
} }
@ -354,11 +418,11 @@ impl<S: 'static> FromRequest<S> for Bytes {
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().resource("/index.html", |r| {
/// "/index.html", |r| {
/// r.method(http::Method::GET) /// r.method(http::Method::GET)
/// .with(index) // <- register handler with extractor params /// .with_config(index, |cfg| { // <- register handler with extractor params
/// .limit(4096); // <- limit size of the payload /// cfg.limit(4096); // <- limit size of the payload
/// })
/// }); /// });
/// } /// }
/// ``` /// ```
@ -375,7 +439,7 @@ impl<S: 'static> FromRequest<S> for String {
let encoding = req.encoding()?; let encoding = req.encoding()?;
Ok(Box::new( Ok(Box::new(
MessageBody::new(req.clone()) MessageBody::new(req)
.limit(cfg.limit) .limit(cfg.limit)
.from_err() .from_err()
.and_then(move |body| { .and_then(move |body| {
@ -394,6 +458,126 @@ impl<S: 'static> FromRequest<S> for String {
} }
} }
/// Optionally extract a field from the request
///
/// If the FromRequest for T fails, return None rather than returning an error response
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// extern crate rand;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing { name: String }
///
/// impl<S> FromRequest<S> for Thing {
/// type Config = ();
/// type Result = Result<Thing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
///
/// }
/// }
///
/// /// extract text data from request
/// fn index(supplied_thing: Option<Thing>) -> Result<String> {
/// match supplied_thing {
/// // Puns not intended
/// Some(thing) => Ok(format!("Got something: {:?}", thing)),
/// None => Ok(format!("No thing!"))
/// }
/// }
///
/// fn main() {
/// let app = App::new().resource("/users/:first", |r| {
/// r.method(http::Method::POST).with(index)
/// });
/// }
/// ```
impl<T: 'static, S: 'static> FromRequest<S> for Option<T>
where
T: FromRequest<S>,
{
type Config = T::Config;
type Result = Box<Future<Item = Option<T>, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new(T::from_request(req, cfg).into().then(|r| match r {
Ok(v) => future::ok(Some(v)),
Err(_) => future::ok(None),
}))
}
}
/// Optionally extract a field from the request or extract the Error if unsuccessful
///
/// If the FromRequest for T fails, inject Err into handler rather than returning an error response
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// extern crate rand;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing { name: String }
///
/// impl<S> FromRequest<S> for Thing {
/// type Config = ();
/// type Result = Result<Thing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
///
/// }
/// }
///
/// /// extract text data from request
/// fn index(supplied_thing: Result<Thing>) -> Result<String> {
/// match supplied_thing {
/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)),
/// Err(e) => Ok(format!("Error extracting thing: {}", e))
/// }
/// }
///
/// fn main() {
/// let app = App::new().resource("/users/:first", |r| {
/// r.method(http::Method::POST).with(index)
/// });
/// }
/// ```
impl<T: 'static, S: 'static> FromRequest<S> for Result<T, Error>
where
T: FromRequest<S>,
{
type Config = T::Config;
type Result = Box<Future<Item = Result<T, Error>, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new(T::from_request(req, cfg).into().then(future::ok))
}
}
/// Payload configuration for request's payload. /// Payload configuration for request's payload.
pub struct PayloadConfig { pub struct PayloadConfig {
limit: usize, limit: usize,
@ -561,9 +745,8 @@ mod tests {
use futures::{Async, Future}; use futures::{Async, Future};
use http::header; use http::header;
use mime; use mime;
use resource::ResourceHandler; use resource::Resource;
use router::{Resource, Router}; use router::{ResourceDef, Router};
use server::ServerSettings;
use test::TestRequest; use test::TestRequest;
#[derive(Deserialize, Debug, PartialEq)] #[derive(Deserialize, Debug, PartialEq)]
@ -574,9 +757,9 @@ mod tests {
#[test] #[test]
fn test_bytes() { fn test_bytes() {
let cfg = PayloadConfig::default(); let cfg = PayloadConfig::default();
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); let req = TestRequest::with_header(header::CONTENT_LENGTH, "11")
req.payload_mut() .set_payload(Bytes::from_static(b"hello=world"))
.unread_data(Bytes::from_static(b"hello=world")); .finish();
match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() { match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() {
Async::Ready(s) => { Async::Ready(s) => {
@ -589,9 +772,9 @@ mod tests {
#[test] #[test]
fn test_string() { fn test_string() {
let cfg = PayloadConfig::default(); let cfg = PayloadConfig::default();
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); let req = TestRequest::with_header(header::CONTENT_LENGTH, "11")
req.payload_mut() .set_payload(Bytes::from_static(b"hello=world"))
.unread_data(Bytes::from_static(b"hello=world")); .finish();
match String::from_request(&req, &cfg).unwrap().poll().unwrap() { match String::from_request(&req, &cfg).unwrap().poll().unwrap() {
Async::Ready(s) => { Async::Ready(s) => {
@ -603,13 +786,12 @@ mod tests {
#[test] #[test]
fn test_form() { fn test_form() {
let mut req = TestRequest::with_header( let req = TestRequest::with_header(
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "11") ).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish(); .finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
let mut cfg = FormConfig::default(); let mut cfg = FormConfig::default();
cfg.limit(4096); cfg.limit(4096);
@ -621,9 +803,101 @@ mod tests {
} }
} }
#[test]
fn test_option() {
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).finish();
let mut cfg = FormConfig::default();
cfg.limit(4096);
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(r, None),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(
r,
Some(Form(Info {
hello: "world".into()
}))
),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world"))
.finish();
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(r, None),
_ => unreachable!(),
}
}
#[test]
fn test_result() {
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default())
.poll()
.unwrap()
{
Async::Ready(Ok(r)) => assert_eq!(
r,
Form(Info {
hello: "world".into()
})
),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world"))
.finish();
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default())
.poll()
.unwrap()
{
Async::Ready(r) => assert!(r.is_err()),
_ => unreachable!(),
}
}
#[test] #[test]
fn test_payload_config() { fn test_payload_config() {
let req = HttpRequest::default(); let req = TestRequest::default().finish();
let mut cfg = PayloadConfig::default(); let mut cfg = PayloadConfig::default();
cfg.mimetype(mime::APPLICATION_JSON); cfg.mimetype(mime::APPLICATION_JSON);
assert!(cfg.check_mimetype(&req).is_err()); assert!(cfg.check_mimetype(&req).is_err());
@ -658,14 +932,12 @@ mod tests {
#[test] #[test]
fn test_request_extract() { fn test_request_extract() {
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish(); let req = TestRequest::with_uri("/name/user1/?id=test").finish();
let mut resource = ResourceHandler::<()>::default(); let mut router = Router::<()>::new();
resource.name("index"); router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let mut routes = Vec::new(); let info = router.recognize(&req, &(), 0);
routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); let req = req.with_route_info(info);
let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some());
let s = Path::<MyStruct>::from_request(&req, &()).unwrap(); let s = Path::<MyStruct>::from_request(&req, &()).unwrap();
assert_eq!(s.key, "name"); assert_eq!(s.key, "name");
@ -678,8 +950,11 @@ mod tests {
let s = Query::<Id>::from_request(&req, &()).unwrap(); let s = Query::<Id>::from_request(&req, &()).unwrap();
assert_eq!(s.id, "test"); assert_eq!(s.id, "test");
let mut req = TestRequest::with_uri("/name/32/").finish(); let mut router = Router::<()>::new();
assert!(router.recognize(&mut req).is_some()); router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let req = TestRequest::with_uri("/name/32/").finish();
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
let s = Path::<Test2>::from_request(&req, &()).unwrap(); let s = Path::<Test2>::from_request(&req, &()).unwrap();
assert_eq!(s.as_ref().key, "name"); assert_eq!(s.as_ref().key, "name");
@ -696,28 +971,23 @@ mod tests {
#[test] #[test]
fn test_extract_path_single() { fn test_extract_path_single() {
let mut resource = ResourceHandler::<()>::default(); let mut router = Router::<()>::new();
resource.name("index"); router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
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(); let req = TestRequest::with_uri("/32/").finish();
assert!(router.recognize(&mut req).is_some()); let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
assert_eq!(*Path::<i8>::from_request(&mut req, &()).unwrap(), 32); assert_eq!(*Path::<i8>::from_request(&req, &()).unwrap(), 32);
} }
#[test] #[test]
fn test_tuple_extract() { fn test_tuple_extract() {
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish(); let mut router = Router::<()>::new();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let mut resource = ResourceHandler::<()>::default(); let req = TestRequest::with_uri("/name/user1/?id=test").finish();
resource.name("index"); let info = router.recognize(&req, &(), 0);
let mut routes = Vec::new(); let req = req.with_route_info(info);
routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some());
let res = match <(Path<(String, String)>,)>::extract(&req).poll() { let res = match <(Path<(String, String)>,)>::extract(&req).poll() {
Ok(Async::Ready(res)) => res, Ok(Async::Ready(res)) => res,

1204
src/fs.rs

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -161,7 +161,7 @@ impl IntoHeaderValue for EntityTag {
fn try_into(self) -> Result<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(HeaderValue::from_shared_unchecked(wrt.take())) } HeaderValue::from_shared(wrt.take())
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,19 +2,19 @@
//! for Rust. //! for Rust.
//! //!
//! ```rust //! ```rust
//! use actix_web::{server, App, Path}; //! use actix_web::{server, App, Path, Responder};
//! # use std::thread; //! # use std::thread;
//! //!
//! fn index(info: Path<(String, u32)>) -> String { //! fn index(info: Path<(String, u32)>) -> impl Responder {
//! format!("Hello {}! id:{}", info.0, info.1) //! format!("Hello {}! id:{}", info.0, info.1)
//! } //! }
//! //!
//! fn main() { //! fn main() {
//! # thread::spawn(|| { //! # thread::spawn(|| {
//! server::new( //! server::new(|| {
//! || App::new() //! App::new().resource("/{name}/{id}/index.html", |r| r.with(index))
//! .resource("/{name}/{id}/index.html", |r| r.with(index))) //! }).bind("127.0.0.1:8080")
//! .bind("127.0.0.1:8080").unwrap() //! .unwrap()
//! .run(); //! .run();
//! # }); //! # });
//! } //! }
@ -25,7 +25,7 @@
//! Besides the API documentation (which you are currently looking //! Besides the API documentation (which you are currently looking
//! at!), several other resources are available: //! at!), several other resources are available:
//! //!
//! * [User Guide](https://actix.rs/book/actix-web/) //! * [User Guide](https://actix.rs/docs/)
//! * [Chat on gitter](https://gitter.im/actix/actix) //! * [Chat on gitter](https://gitter.im/actix/actix)
//! * [GitHub repository](https://github.com/actix/actix-web) //! * [GitHub repository](https://github.com/actix/actix-web)
//! * [Cargo package](https://crates.io/crates/actix-web) //! * [Cargo package](https://crates.io/crates/actix-web)
@ -59,7 +59,7 @@
//! * 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.24 or later //! * Supported Rust version: 1.26 or later
//! //!
//! ## Package feature //! ## Package feature
//! //!
@ -70,18 +70,20 @@
//! dependency //! dependency
//! * `brotli` - enables `brotli` compression support, requires `c` //! * `brotli` - enables `brotli` compression support, requires `c`
//! compiler //! compiler
//! * `flate-c` - enables `gzip`, `deflate` compression support, requires //! * `flate2-c` - enables `gzip`, `deflate` compression support, requires
//! `c` compiler //! `c` compiler
//! * `flate-rust` - experimental rust based implementation for //! * `flate2-rust` - experimental rust based implementation for
//! `gzip`, `deflate` compression. //! `gzip`, `deflate` compression.
//! //!
#![cfg_attr(actix_nightly, feature( #![cfg_attr(actix_nightly, feature(
specialization, // for impl ErrorResponse for std::error::Error specialization, // for impl ErrorResponse for std::error::Error
extern_prelude,
))] ))]
#![cfg_attr( #![cfg_attr(
feature = "cargo-clippy", feature = "cargo-clippy",
allow(decimal_literal_representation, suspicious_arithmetic_impl) allow(decimal_literal_representation, suspicious_arithmetic_impl)
)] )]
#![warn(missing_docs)]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
@ -101,19 +103,23 @@ extern crate lazy_static;
extern crate futures; extern crate futures;
extern crate cookie; extern crate cookie;
extern crate futures_cpupool; extern crate futures_cpupool;
extern crate htmlescape;
extern crate http as modhttp; extern crate http as modhttp;
extern crate http_range;
extern crate httparse; extern crate httparse;
extern crate language_tags; extern crate language_tags;
extern crate libc; extern crate lazycell;
extern crate mime; extern crate mime;
extern crate mime_guess; extern crate mime_guess;
extern crate mio; extern crate mio;
extern crate net2; extern crate net2;
extern crate parking_lot;
extern crate rand; extern crate rand;
extern crate slab; extern crate slab;
extern crate tokio_core; extern crate tokio;
extern crate tokio_io; extern crate tokio_io;
extern crate tokio_reactor;
extern crate tokio_tcp;
extern crate tokio_timer;
extern crate url; extern crate url;
#[macro_use] #[macro_use]
extern crate serde; extern crate serde;
@ -124,12 +130,12 @@ extern crate encoding;
extern crate flate2; extern crate flate2;
extern crate h2 as http2; extern crate h2 as http2;
extern crate num_cpus; extern crate num_cpus;
#[macro_use]
extern crate percent_encoding; extern crate percent_encoding;
extern crate serde_json; extern crate serde_json;
extern crate serde_urlencoded;
extern crate smallvec; extern crate smallvec;
#[macro_use] #[macro_use]
extern crate actix; extern crate actix as actix_inner;
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
@ -149,6 +155,7 @@ mod application;
mod body; mod body;
mod context; mod context;
mod de; mod de;
mod extensions;
mod extractor; mod extractor;
mod handler; mod handler;
mod header; mod header;
@ -166,6 +173,7 @@ mod resource;
mod route; mod route;
mod router; mod router;
mod scope; mod scope;
mod serde_urlencoded;
mod uri; mod uri;
mod with; mod with;
@ -182,6 +190,7 @@ pub use application::App;
pub use body::{Binary, Body}; pub use body::{Binary, Body};
pub use context::HttpContext; pub use context::HttpContext;
pub use error::{Error, ResponseError, Result}; pub use error::{Error, ResponseError, Result};
pub use extensions::Extensions;
pub use extractor::{Form, Path, Query}; pub use extractor::{Form, Path, Query};
pub use handler::{ pub use handler::{
AsyncResponder, Either, FromRequest, FutureResponse, Responder, State, AsyncResponder, Either, FromRequest, FutureResponse, Responder, State,
@ -191,10 +200,19 @@ pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse; pub use httpresponse::HttpResponse;
pub use json::Json; pub use json::Json;
pub use scope::Scope; pub use scope::Scope;
pub use server::Request;
#[doc(hidden)] pub mod actix {
#[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")] //! Re-exports [actix's](https://docs.rs/actix/) prelude
pub use ws::WsWriter;
extern crate actix;
pub use self::actix::actors::resolver;
pub use self::actix::actors::signal;
pub use self::actix::fut;
pub use self::actix::msgs;
pub use self::actix::prelude::*;
pub use self::actix::{run, spawn};
}
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
pub(crate) const HAS_OPENSSL: bool = true; pub(crate) const HAS_OPENSSL: bool = true;
@ -226,10 +244,10 @@ pub mod dev {
pub use info::ConnectionInfo; pub use info::ConnectionInfo;
pub use json::{JsonBody, JsonConfig}; pub use json::{JsonBody, JsonConfig};
pub use param::{FromParam, Params}; pub use param::{FromParam, Params};
pub use resource::ResourceHandler; pub use payload::{Payload, PayloadBuffer};
pub use resource::Resource;
pub use route::Route; pub use route::Route;
pub use router::{Resource, ResourceType, Router}; pub use router::{ResourceDef, ResourceInfo, ResourceType, Router};
pub use with::ExtractorConfig;
} }
pub mod http { pub mod http {
@ -242,12 +260,13 @@ pub mod http {
pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri}; pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri};
pub use cookie::{Cookie, CookieBuilder}; pub use cookie::{Cookie, CookieBuilder};
pub use http_range::HttpRange;
pub use helpers::NormalizePath; pub use helpers::NormalizePath;
/// Various http headers
pub mod header { pub mod header {
pub use header::*; pub use header::*;
pub use header::{ContentDisposition, DispositionType, DispositionParam, Charset, LanguageTag};
} }
pub use header::ContentEncoding; pub use header::ContentEncoding;
pub use httpresponse::ConnectionType; pub use httpresponse::ConnectionType;

View File

@ -11,7 +11,7 @@
//! constructed backend. //! constructed backend.
//! //!
//! Cors middleware could be used as parameter for `App::middleware()` or //! Cors middleware could be used as parameter for `App::middleware()` or
//! `ResourceHandler::middleware()` methods. But you have to use //! `Resource::middleware()` methods. But you have to use
//! `Cors::for_app()` method to support *preflight* OPTIONS request. //! `Cors::for_app()` method to support *preflight* OPTIONS request.
//! //!
//! //!
@ -19,16 +19,16 @@
//! //!
//! ```rust //! ```rust
//! # extern crate actix_web; //! # extern crate actix_web;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//! use actix_web::middleware::cors::Cors; //! use actix_web::middleware::cors::Cors;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//! //!
//! fn index(mut req: HttpRequest) -> &'static str { //! fn index(mut req: HttpRequest) -> &'static str {
//! "Hello world" //! "Hello world"
//! } //! }
//! //!
//! fn main() { //! fn main() {
//! let app = App::new() //! let app = App::new().configure(|app| {
//! .configure(|app| Cors::for_app(app) // <- Construct CORS middleware builder //! Cors::for_app(app) // <- Construct CORS middleware builder
//! .allowed_origin("https://www.rust-lang.org/") //! .allowed_origin("https://www.rust-lang.org/")
//! .allowed_methods(vec!["GET", "POST"]) //! .allowed_methods(vec!["GET", "POST"])
//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) //! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
@ -38,7 +38,8 @@
//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); //! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); //! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
//! }) //! })
//! .register()); //! .register()
//! });
//! } //! }
//! ``` //! ```
//! In this example custom *CORS* middleware get registered for "/index.html" //! In this example custom *CORS* middleware get registered for "/index.html"
@ -58,7 +59,9 @@ use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started}; use middleware::{Middleware, Response, Started};
use resource::ResourceHandler; use resource::Resource;
use router::ResourceDef;
use server::Request;
/// A set of errors that can occur during processing CORS /// A set of errors that can occur during processing CORS
#[derive(Debug, Fail)] #[derive(Debug, Fail)]
@ -206,6 +209,7 @@ impl Default for Cors {
} }
impl Cors { impl Cors {
/// Build a new CORS middleware instance
pub fn build() -> CorsBuilder<()> { pub fn build() -> CorsBuilder<()> {
CorsBuilder { CorsBuilder {
cors: Some(Inner { cors: Some(Inner {
@ -232,17 +236,19 @@ impl Cors {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors; /// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().configure(
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder /// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/") /// .allowed_origin("https://www.rust-lang.org/")
/// .resource("/resource", |r| { // register resource /// .resource("/resource", |r| { // register resource
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); /// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// }) /// })
/// .register() // construct CORS and return application instance /// .register()
/// }, // construct CORS and return application instance
/// ); /// );
/// } /// }
/// ``` /// ```
@ -272,14 +278,16 @@ impl Cors {
/// adds route for *OPTIONS* preflight requests. /// adds route for *OPTIONS* preflight requests.
/// ///
/// It is possible to register *Cors* middleware with /// It is possible to register *Cors* middleware with
/// `ResourceHandler::middleware()` method, but in that case *Cors* /// `Resource::middleware()` method, but in that case *Cors*
/// middleware wont be able to handle *OPTIONS* requests. /// middleware wont be able to handle *OPTIONS* requests.
pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) { pub fn register<S: 'static>(self, resource: &mut Resource<S>) {
resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok()); 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(&self, req: &Request) -> 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.inner.origins { return match self.inner.origins {
@ -299,9 +307,7 @@ impl Cors {
} }
} }
fn validate_allowed_method<S>( fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> {
&self, req: &mut HttpRequest<S>,
) -> Result<(), CorsError> {
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) {
@ -319,9 +325,7 @@ impl Cors {
} }
} }
fn validate_allowed_headers<S>( fn validate_allowed_headers(&self, req: &Request) -> Result<(), CorsError> {
&self, req: &mut HttpRequest<S>,
) -> Result<(), CorsError> {
match self.inner.headers { match self.inner.headers {
AllOrSome::All => Ok(()), AllOrSome::All => Ok(()),
AllOrSome::Some(ref allowed_headers) => { AllOrSome::Some(ref allowed_headers) => {
@ -352,11 +356,11 @@ 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: &HttpRequest<S>) -> Result<Started> {
if self.inner.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.inner.headers.as_ref() { let headers = if let Some(headers) = self.inner.headers.as_ref() {
@ -420,14 +424,17 @@ impl<S> Middleware<S> for Cors {
.finish(), .finish(),
)) ))
} else { } else {
// Only check requests with a origin header.
if req.headers().contains_key(header::ORIGIN) {
self.validate_origin(req)?; self.validate_origin(req)?;
}
Ok(Started::Done) Ok(Started::Done)
} }
} }
fn response( fn response(
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse, &self, req: &HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> { ) -> Result<Response> {
match self.inner.origins { match self.inner.origins {
AllOrSome::All => { AllOrSome::All => {
@ -491,8 +498,8 @@ impl<S> Middleware<S> for Cors {
/// ```rust /// ```rust
/// # extern crate http; /// # extern crate http;
/// # extern crate actix_web; /// # extern crate actix_web;
/// use http::header;
/// use actix_web::middleware::cors; /// use actix_web::middleware::cors;
/// use http::header;
/// ///
/// # fn main() { /// # fn main() {
/// let cors = cors::Cors::build() /// let cors = cors::Cors::build()
@ -509,7 +516,7 @@ pub struct CorsBuilder<S = ()> {
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>)>, resources: Vec<Resource<S>>,
app: Option<App<S>>, app: Option<App<S>>,
} }
@ -764,12 +771,13 @@ impl<S: 'static> CorsBuilder<S> {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors; /// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().configure(
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder /// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/") /// .allowed_origin("https://www.rust-lang.org/")
/// .allowed_methods(vec!["GET", "POST"]) /// .allowed_methods(vec!["GET", "POST"])
/// .allowed_header(http::header::CONTENT_TYPE) /// .allowed_header(http::header::CONTENT_TYPE)
@ -781,19 +789,20 @@ impl<S: 'static> CorsBuilder<S> {
/// r.method(http::Method::HEAD) /// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed()); /// .f(|_| HttpResponse::MethodNotAllowed());
/// }) /// })
/// .register() // construct CORS and return application instance /// .register()
/// }, // construct CORS and return application instance
/// ); /// );
/// } /// }
/// ``` /// ```
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S> pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S>
where where
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static, F: FnOnce(&mut Resource<S>) -> R + 'static,
{ {
// add resource handler // add resource handler
let mut handler = ResourceHandler::default(); let mut resource = Resource::new(ResourceDef::new(path));
f(&mut handler); f(&mut resource);
self.resources.push((path.to_owned(), handler)); self.resources.push(resource);
self self
} }
@ -870,9 +879,9 @@ impl<S: 'static> CorsBuilder<S> {
.expect("CorsBuilder has to be constructed with Cors::for_app(app)"); .expect("CorsBuilder has to be constructed with Cors::for_app(app)");
// register resources // register resources
for (path, mut resource) in self.resources.drain(..) { for mut resource in self.resources.drain(..) {
cors.clone().register(&mut resource); cors.clone().register(&mut resource);
app.register_resource(&path, resource); app.register_resource(resource);
} }
app app
@ -936,10 +945,9 @@ mod tests {
#[test] #[test]
fn validate_origin_allows_all_origins() { fn validate_origin_allows_all_origins() {
let cors = Cors::default(); let cors = Cors::default();
let mut req = let req = TestRequest::with_header("Origin", "https://www.example.com").finish();
TestRequest::with_header("Origin", "https://www.example.com").finish();
assert!(cors.start(&mut req).ok().unwrap().is_done()) assert!(cors.start(&req).ok().unwrap().is_done())
} }
#[test] #[test]
@ -952,20 +960,20 @@ mod tests {
.allowed_header(header::CONTENT_TYPE) .allowed_header(header::CONTENT_TYPE)
.finish(); .finish();
let mut req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.finish(); .finish();
assert!(cors.start(&mut req).is_err()); assert!(cors.start(&req).is_err());
let mut req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.finish(); .finish();
assert!(cors.start(&mut req).is_err()); assert!(cors.start(&req).is_err());
let mut req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.header( .header(
header::ACCESS_CONTROL_REQUEST_HEADERS, header::ACCESS_CONTROL_REQUEST_HEADERS,
@ -974,7 +982,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.finish(); .finish();
let resp = cors.start(&mut req).unwrap().response(); let resp = cors.start(&req).unwrap().response();
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
resp.headers() resp.headers()
@ -998,19 +1006,18 @@ mod tests {
// as_bytes()); // as_bytes());
Rc::get_mut(&mut cors.inner).unwrap().preflight = false; Rc::get_mut(&mut cors.inner).unwrap().preflight = false;
assert!(cors.start(&mut req).unwrap().is_done()); assert!(cors.start(&req).unwrap().is_done());
} }
#[test] // #[test]
#[should_panic(expected = "MissingOrigin")] // #[should_panic(expected = "MissingOrigin")]
fn test_validate_missing_origin() { // fn test_validate_missing_origin() {
let cors = Cors::build() // let cors = Cors::build()
.allowed_origin("https://www.example.com") // .allowed_origin("https://www.example.com")
.finish(); // .finish();
// let mut req = HttpRequest::default();
let mut req = HttpRequest::default(); // cors.start(&req).unwrap();
cors.start(&mut req).unwrap(); // }
}
#[test] #[test]
#[should_panic(expected = "OriginNotAllowed")] #[should_panic(expected = "OriginNotAllowed")]
@ -1019,10 +1026,10 @@ mod tests {
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.finish(); .finish();
let mut req = TestRequest::with_header("Origin", "https://www.unknown.com") let req = TestRequest::with_header("Origin", "https://www.unknown.com")
.method(Method::GET) .method(Method::GET)
.finish(); .finish();
cors.start(&mut req).unwrap(); cors.start(&req).unwrap();
} }
#[test] #[test]
@ -1031,30 +1038,30 @@ mod tests {
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.finish(); .finish();
let mut req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::GET) .method(Method::GET)
.finish(); .finish();
assert!(cors.start(&mut req).unwrap().is_done()); assert!(cors.start(&req).unwrap().is_done());
} }
#[test] #[test]
fn test_no_origin_response() { fn test_no_origin_response() {
let cors = Cors::build().finish(); let cors = Cors::build().finish();
let mut req = TestRequest::default().method(Method::GET).finish(); let req = TestRequest::default().method(Method::GET).finish();
let resp: HttpResponse = HttpResponse::Ok().into(); let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&req, resp).unwrap().response();
assert!( assert!(
resp.headers() resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.is_none() .is_none()
); );
let mut req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.finish(); .finish();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"https://www.example.com"[..], &b"https://www.example.com"[..],
resp.headers() resp.headers()
@ -1075,12 +1082,12 @@ mod tests {
.allowed_header(header::CONTENT_TYPE) .allowed_header(header::CONTENT_TYPE)
.finish(); .finish();
let mut req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.finish(); .finish();
let resp: HttpResponse = HttpResponse::Ok().into(); let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
resp.headers() resp.headers()
@ -1095,7 +1102,7 @@ mod tests {
let resp: HttpResponse = let resp: HttpResponse =
HttpResponse::Ok().header(header::VARY, "Accept").finish(); HttpResponse::Ok().header(header::VARY, "Accept").finish();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"Accept, Origin"[..], &b"Accept, Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes() resp.headers().get(header::VARY).unwrap().as_bytes()
@ -1106,7 +1113,7 @@ mod tests {
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.finish(); .finish();
let resp: HttpResponse = HttpResponse::Ok().into(); let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"https://www.example.com"[..], &b"https://www.example.com"[..],
resp.headers() resp.headers()
@ -1127,10 +1134,19 @@ mod tests {
}) })
}); });
let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let request = srv
.get()
.uri(srv.url("/test"))
.header("ORIGIN", "https://www.example2.com")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv let request = srv
.get() .get()
.uri(srv.url("/test")) .uri(srv.url("/test"))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
use std::cell::UnsafeCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem; use std::mem;
use std::rc::Rc; use std::rc::Rc;
@ -15,12 +14,9 @@ use middleware::{
Started as MiddlewareStarted, Started as MiddlewareStarted,
}; };
use pred::Predicate; use pred::Predicate;
use resource::ResourceHandler; use resource::{DefaultResource, Resource};
use router::Resource; use router::{ResourceDef, Router};
use server::Request;
type Route<S> = UnsafeCell<Box<RouteHandler<S>>>;
type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>;
type NestedInfo<S> = (Resource, Route<S>, Vec<Box<Predicate<S>>>);
/// Resources scope /// Resources scope
/// ///
@ -38,9 +34,9 @@ type NestedInfo<S> = (Resource, Route<S>, Vec<Box<Predicate<S>>>);
/// use actix_web::{http, App, HttpRequest, HttpResponse}; /// use actix_web::{http, App, HttpRequest, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/{project_id}/", |scope| {
/// .scope("/{project_id}/", |scope| { /// scope
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) /// .resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
/// }); /// });
@ -52,27 +48,35 @@ type NestedInfo<S> = (Resource, Route<S>, Vec<Box<Predicate<S>>>);
/// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path2 - `GET` requests
/// * /{project_id}/path3 - `HEAD` requests /// * /{project_id}/path3 - `HEAD` requests
/// ///
#[derive(Default)] pub struct Scope<S> {
pub struct Scope<S: 'static> { rdef: ResourceDef,
router: Rc<Router<S>>,
filters: Vec<Box<Predicate<S>>>, filters: Vec<Box<Predicate<S>>>,
nested: Vec<NestedInfo<S>>,
middlewares: Rc<Vec<Box<Middleware<S>>>>, middlewares: Rc<Vec<Box<Middleware<S>>>>,
default: Rc<UnsafeCell<ResourceHandler<S>>>,
resources: ScopeResources<S>,
} }
#[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] #[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))]
impl<S: 'static> Scope<S> { impl<S: 'static> Scope<S> {
pub fn new() -> Scope<S> { /// Create a new scope
// TODO: Why is this not exactly the default impl?
pub fn new(path: &str) -> Scope<S> {
Scope { Scope {
rdef: ResourceDef::prefix(path),
router: Rc::new(Router::new()),
filters: Vec::new(), filters: Vec::new(),
nested: Vec::new(),
resources: Rc::new(Vec::new()),
middlewares: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()),
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
} }
} }
#[inline]
pub(crate) fn rdef(&self) -> &ResourceDef {
&self.rdef
}
pub(crate) fn router(&self) -> &Router<S> {
self.router.as_ref()
}
#[inline] #[inline]
pub(crate) fn take_filters(&mut self) -> Vec<Box<Predicate<S>>> { pub(crate) fn take_filters(&mut self) -> Vec<Box<Predicate<S>>> {
mem::replace(&mut self.filters, Vec::new()) mem::replace(&mut self.filters, Vec::new())
@ -89,12 +93,13 @@ impl<S: 'static> Scope<S> {
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/app", |scope| {
/// .scope("/app", |scope| { /// scope
/// scope.filter(pred::Header("content-type", "text/plain")) /// .filter(pred::Header("content-type", "text/plain"))
/// .route("/test1", http::Method::GET, index) /// .route("/test1", http::Method::GET, index)
/// .route("/test2", http::Method::POST, /// .route("/test2", http::Method::POST, |_: HttpRequest| {
/// |_: HttpRequest| HttpResponse::MethodNotAllowed()) /// HttpResponse::MethodNotAllowed()
/// })
/// }); /// });
/// } /// }
/// ``` /// ```
@ -111,13 +116,12 @@ impl<S: 'static> Scope<S> {
/// ///
/// struct AppState; /// struct AppState;
/// ///
/// fn index(req: HttpRequest<AppState>) -> &'static str { /// fn index(req: &HttpRequest<AppState>) -> &'static str {
/// "Welcome!" /// "Welcome!"
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/app", |scope| {
/// .scope("/app", |scope| {
/// scope.with_state("/state2", AppState, |scope| { /// scope.with_state("/state2", AppState, |scope| {
/// scope.resource("/test1", |r| r.f(index)) /// scope.resource("/test1", |r| r.f(index))
/// }) /// })
@ -129,30 +133,25 @@ impl<S: 'static> Scope<S> {
F: FnOnce(Scope<T>) -> Scope<T>, F: FnOnce(Scope<T>) -> Scope<T>,
{ {
let scope = Scope { let scope = Scope {
rdef: ResourceDef::prefix(path),
filters: Vec::new(), filters: Vec::new(),
nested: Vec::new(), router: Rc::new(Router::new()),
resources: Rc::new(Vec::new()),
middlewares: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()),
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
}; };
let mut scope = f(scope); let mut scope = f(scope);
let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/')
}
if !path.ends_with('/') {
path.push('/');
}
let state = Rc::new(state); let state = Rc::new(state);
let filters: Vec<Box<Predicate<S>>> = vec![Box::new(FiltersWrapper { let filters: Vec<Box<Predicate<S>>> = vec![Box::new(FiltersWrapper {
state: Rc::clone(&state), state: Rc::clone(&state),
filters: scope.take_filters(), filters: scope.take_filters(),
})]; })];
let handler = UnsafeCell::new(Box::new(Wrapper { scope, state })); let handler = Box::new(Wrapper { scope, state });
self.nested
.push((Resource::prefix("", &path), handler, filters)); Rc::get_mut(&mut self.router).unwrap().register_handler(
path,
handler,
Some(filters),
);
self self
} }
@ -165,16 +164,13 @@ impl<S: 'static> Scope<S> {
/// ///
/// struct AppState; /// struct AppState;
/// ///
/// fn index(req: HttpRequest<AppState>) -> &'static str { /// fn index(req: &HttpRequest<AppState>) -> &'static str {
/// "Welcome!" /// "Welcome!"
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::with_state(AppState) /// let app = App::with_state(AppState).scope("/app", |scope| {
/// .scope("/app", |scope| { /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.f(index)))
/// scope.nested("/v1", |scope| {
/// scope.resource("/test1", |r| r.f(index))
/// })
/// }); /// });
/// } /// }
/// ``` /// ```
@ -183,28 +179,14 @@ impl<S: 'static> Scope<S> {
F: FnOnce(Scope<S>) -> Scope<S>, F: FnOnce(Scope<S>) -> Scope<S>,
{ {
let scope = Scope { let scope = Scope {
rdef: ResourceDef::prefix(&path),
filters: Vec::new(), filters: Vec::new(),
nested: Vec::new(), router: Rc::new(Router::new()),
resources: Rc::new(Vec::new()),
middlewares: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()),
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
}; };
let mut scope = f(scope); Rc::get_mut(&mut self.router)
.unwrap()
let mut path = path.trim().trim_right_matches('/').to_owned(); .register_scope(f(scope));
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/')
}
if !path.ends_with('/') {
path.push('/');
}
let filters = scope.take_filters();
self.nested.push((
Resource::prefix("", &path),
UnsafeCell::new(Box::new(scope)),
filters,
));
self self
} }
@ -227,11 +209,12 @@ impl<S: 'static> Scope<S> {
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/app", |scope| {
/// .scope("/app", |scope| { /// scope.route("/test1", http::Method::GET, index).route(
/// scope.route("/test1", http::Method::GET, index) /// "/test2",
/// .route("/test2", http::Method::POST, /// http::Method::POST,
/// |_: HttpRequest| HttpResponse::MethodNotAllowed()) /// |_: HttpRequest| HttpResponse::MethodNotAllowed(),
/// )
/// }); /// });
/// } /// }
/// ``` /// ```
@ -241,23 +224,9 @@ impl<S: 'static> Scope<S> {
R: Responder + 'static, R: Responder + 'static,
T: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
{ {
// get resource handler Rc::get_mut(&mut self.router)
let slf: &Scope<S> = unsafe { &*(&self as *const _) }; .unwrap()
for &(ref pattern, ref resource) in slf.resources.iter() { .register_route(path, method, f);
if pattern.pattern() == path {
let resource = unsafe { &mut *resource.get() };
resource.method(method).with(f);
return self;
}
}
let mut handler = ResourceHandler::default();
handler.method(method).with(f);
let pattern = Resource::new(handler.get_name(), path);
Rc::get_mut(&mut self.resources)
.expect("Can not use after configuration")
.push((pattern, Rc::new(UnsafeCell::new(handler))));
self self
} }
@ -272,8 +241,7 @@ impl<S: 'static> Scope<S> {
/// use actix_web::*; /// use actix_web::*;
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().scope("/api", |scope| {
/// .scope("/api", |scope| {
/// scope.resource("/users/{userid}/{friend}", |r| { /// scope.resource("/users/{userid}/{friend}", |r| {
/// r.get().f(|_| HttpResponse::Ok()); /// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// r.head().f(|_| HttpResponse::MethodNotAllowed());
@ -287,27 +255,36 @@ impl<S: 'static> Scope<S> {
/// ``` /// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> Scope<S> pub fn resource<F, R>(mut self, path: &str, f: F) -> Scope<S>
where where
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static, F: FnOnce(&mut Resource<S>) -> R + 'static,
{ {
// add resource handler // add resource
let mut handler = ResourceHandler::default(); let pattern = ResourceDef::with_prefix(
f(&mut handler); path,
if path.is_empty() { "" } else { "/" },
let pattern = Resource::new(handler.get_name(), path); false,
Rc::get_mut(&mut self.resources) );
.expect("Can not use after configuration") let mut resource = Resource::new(pattern);
.push((pattern, Rc::new(UnsafeCell::new(handler)))); f(&mut resource);
Rc::get_mut(&mut self.router)
.unwrap()
.register_resource(resource);
self self
} }
/// Default resource to be used if no matching route could be found. /// Default resource to be used if no matching route could be found.
pub fn default_resource<F, R>(self, f: F) -> Scope<S> pub fn default_resource<F, R>(mut self, f: F) -> Scope<S>
where where
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static, F: FnOnce(&mut Resource<S>) -> R + 'static,
{ {
let default = unsafe { &mut *self.default.as_ref().get() }; // create and configure default resource
f(default); let mut resource = Resource::new(ResourceDef::new(""));
f(&mut resource);
Rc::get_mut(&mut self.router)
.expect("Multiple copies of scope router")
.register_default_resource(resource.into());
self self
} }
@ -327,72 +304,38 @@ impl<S: 'static> Scope<S> {
} }
impl<S: 'static> RouteHandler<S> for Scope<S> { impl<S: 'static> RouteHandler<S> for Scope<S> {
fn handle(&mut self, mut req: HttpRequest<S>) -> AsyncResult<HttpResponse> { fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
let path = unsafe { &*(&req.match_info()["tail"] as *const _) }; let tail = req.match_info().tail as usize;
let path = if path == "" { "/" } else { path };
// recognize resources // recognize resources
for &(ref pattern, ref resource) in self.resources.iter() { let info = self.router.recognize(req, req.state(), tail);
if pattern.match_with_params(path, req.match_info_mut()) { let req2 = req.with_route_info(info);
let default = unsafe { &mut *self.default.as_ref().get() };
if self.middlewares.is_empty() { if self.middlewares.is_empty() {
let resource = unsafe { &mut *resource.get() }; self.router.handle(&req2)
return resource.handle(req, Some(default));
} else {
return AsyncResult::async(Box::new(Compose::new(
req,
Rc::clone(&self.middlewares),
Rc::clone(&resource),
Some(Rc::clone(&self.default)),
)));
}
}
}
// nested scopes
let len = req.prefix_len() as usize;
let path: &'static str = unsafe { &*(&req.path()[len..] as *const _) };
'outer: for &(ref prefix, ref handler, ref filters) in &self.nested {
if let Some(prefix_len) =
prefix.match_prefix_with_params(path, req.match_info_mut())
{
for filter in filters {
if !filter.check(&mut req) {
continue 'outer;
}
}
let prefix_len = len + prefix_len - 1;
let path: &'static str =
unsafe { &*(&req.path()[prefix_len..] as *const _) };
req.set_prefix_len(prefix_len as u16);
if path.is_empty() {
req.match_info_mut().set("tail", "/");
} else {
req.match_info_mut().set("tail", path);
}
let hnd: &mut RouteHandler<_> =
unsafe { (&mut *(handler.get())).as_mut() };
return hnd.handle(req);
}
}
// default handler
let default = unsafe { &mut *self.default.as_ref().get() };
if self.middlewares.is_empty() {
default.handle(req, None)
} else { } else {
AsyncResult::async(Box::new(Compose::new( AsyncResult::async(Box::new(Compose::new(
req, req2,
Rc::clone(&self.router),
Rc::clone(&self.middlewares), Rc::clone(&self.middlewares),
Rc::clone(&self.default),
None,
))) )))
} }
} }
fn has_default_resource(&self) -> bool {
self.router.has_default_resource()
}
fn default_resource(&mut self, default: DefaultResource<S>) {
Rc::get_mut(&mut self.router)
.expect("Can not use after configuration")
.register_default_resource(default);
}
fn finish(&mut self) {
Rc::get_mut(&mut self.router)
.expect("Can not use after configuration")
.finish();
}
} }
struct Wrapper<S: 'static> { struct Wrapper<S: 'static> {
@ -401,8 +344,9 @@ struct Wrapper<S: 'static> {
} }
impl<S: 'static, S2: 'static> RouteHandler<S2> for Wrapper<S> { impl<S: 'static, S2: 'static> RouteHandler<S2> for Wrapper<S> {
fn handle(&mut self, req: HttpRequest<S2>) -> AsyncResult<HttpResponse> { fn handle(&self, req: &HttpRequest<S2>) -> AsyncResult<HttpResponse> {
self.scope.handle(req.change_state(Rc::clone(&self.state))) let req = req.with_state(Rc::clone(&self.state));
self.scope.handle(&req)
} }
} }
@ -412,10 +356,9 @@ struct FiltersWrapper<S: 'static> {
} }
impl<S: 'static, S2: 'static> Predicate<S2> for FiltersWrapper<S> { impl<S: 'static, S2: 'static> Predicate<S2> for FiltersWrapper<S> {
fn check(&self, req: &mut HttpRequest<S2>) -> bool { fn check(&self, req: &Request, _: &S2) -> bool {
let mut req = req.change_state(Rc::clone(&self.state));
for filter in &self.filters { for filter in &self.filters {
if !filter.check(&mut req) { if !filter.check(&req, &self.state) {
return false; return false;
} }
} }
@ -432,9 +375,8 @@ struct Compose<S: 'static> {
struct ComposeInfo<S: 'static> { struct ComposeInfo<S: 'static> {
count: usize, count: usize,
req: HttpRequest<S>, req: HttpRequest<S>,
router: Rc<Router<S>>,
mws: Rc<Vec<Box<Middleware<S>>>>, mws: Rc<Vec<Box<Middleware<S>>>>,
default: Option<Rc<UnsafeCell<ResourceHandler<S>>>>,
resource: Rc<UnsafeCell<ResourceHandler<S>>>,
} }
enum ComposeState<S: 'static> { enum ComposeState<S: 'static> {
@ -459,16 +401,13 @@ impl<S: 'static> ComposeState<S> {
impl<S: 'static> Compose<S> { impl<S: 'static> Compose<S> {
fn new( fn new(
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, req: HttpRequest<S>, router: Rc<Router<S>>, mws: Rc<Vec<Box<Middleware<S>>>>,
resource: Rc<UnsafeCell<ResourceHandler<S>>>,
default: Option<Rc<UnsafeCell<ResourceHandler<S>>>>,
) -> Self { ) -> Self {
let mut info = ComposeInfo { let mut info = ComposeInfo {
count: 0,
req,
mws, mws,
resource, req,
default, router,
count: 0,
}; };
let state = StartMiddlewares::init(&mut info); let state = StartMiddlewares::init(&mut info);
@ -506,29 +445,27 @@ type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
impl<S: 'static> StartMiddlewares<S> { impl<S: 'static> StartMiddlewares<S> {
fn init(info: &mut ComposeInfo<S>) -> ComposeState<S> { fn init(info: &mut ComposeInfo<S>) -> ComposeState<S> {
let len = info.mws.len(); let len = info.mws.len();
loop { loop {
if info.count == len { if info.count == len {
let resource = unsafe { &mut *info.resource.get() }; let reply = info.router.handle(&info.req);
let reply = if let Some(ref default) = info.default {
let d = unsafe { &mut *default.as_ref().get() };
resource.handle(info.req.clone(), Some(d))
} else {
resource.handle(info.req.clone(), None)
};
return WaitingResponse::init(info, reply); return WaitingResponse::init(info, reply);
} else { } else {
match info.mws[info.count].start(&mut info.req) { let result = info.mws[info.count].start(&info.req);
match result {
Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => { Ok(MiddlewareStarted::Response(resp)) => {
return RunMiddlewares::init(info, resp) return RunMiddlewares::init(info, resp);
} }
Ok(MiddlewareStarted::Future(fut)) => { Ok(MiddlewareStarted::Future(fut)) => {
return ComposeState::Starting(StartMiddlewares { return ComposeState::Starting(StartMiddlewares {
fut: Some(fut), fut: Some(fut),
_s: PhantomData, _s: PhantomData,
}) });
}
Err(err) => {
return RunMiddlewares::init(info, err.into());
} }
Err(err) => return Response::init(err.into()),
} }
} }
} }
@ -536,26 +473,25 @@ impl<S: 'static> StartMiddlewares<S> {
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> { fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
let len = info.mws.len(); let len = info.mws.len();
'outer: loop { 'outer: loop {
match self.fut.as_mut().unwrap().poll() { match self.fut.as_mut().unwrap().poll() {
Ok(Async::NotReady) => return None, Ok(Async::NotReady) => {
return None;
}
Ok(Async::Ready(resp)) => { Ok(Async::Ready(resp)) => {
info.count += 1; info.count += 1;
if let Some(resp) = resp { if let Some(resp) = resp {
return Some(RunMiddlewares::init(info, resp)); return Some(RunMiddlewares::init(info, resp));
} }
loop { loop {
if info.count == len { if info.count == len {
let resource = unsafe { &mut *info.resource.get() }; let reply = info.router.handle(&info.req);
let reply = if let Some(ref default) = info.default {
let d = unsafe { &mut *default.as_ref().get() };
resource.handle(info.req.clone(), Some(d))
} else {
resource.handle(info.req.clone(), None)
};
return Some(WaitingResponse::init(info, reply)); return Some(WaitingResponse::init(info, reply));
} else { } else {
match info.mws[info.count].start(&mut info.req) { let result = info.mws[info.count].start(&info.req);
match result {
Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => { Ok(MiddlewareStarted::Response(resp)) => {
return Some(RunMiddlewares::init(info, resp)); return Some(RunMiddlewares::init(info, resp));
@ -564,12 +500,16 @@ impl<S: 'static> StartMiddlewares<S> {
self.fut = Some(fut); self.fut = Some(fut);
continue 'outer; continue 'outer;
} }
Err(err) => return Some(Response::init(err.into())), Err(err) => {
return Some(RunMiddlewares::init(info, err.into()));
} }
} }
} }
} }
Err(err) => return Some(Response::init(err.into())), }
Err(err) => {
return Some(RunMiddlewares::init(info, err.into()));
}
} }
} }
} }
@ -599,7 +539,7 @@ impl<S: 'static> WaitingResponse<S> {
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> { fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
match self.fut.poll() { match self.fut.poll() {
Ok(Async::NotReady) => None, Ok(Async::NotReady) => None,
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)), Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)),
Err(err) => Some(RunMiddlewares::init(info, err.into())), Err(err) => Some(RunMiddlewares::init(info, err.into())),
} }
} }
@ -618,7 +558,8 @@ impl<S: 'static> RunMiddlewares<S> {
let len = info.mws.len(); let len = info.mws.len();
loop { loop {
resp = match info.mws[curr].response(&mut info.req, resp) { let state = info.mws[curr].response(&info.req, resp);
resp = match state {
Err(err) => { Err(err) => {
info.count = curr + 1; info.count = curr + 1;
return FinishingMiddlewares::init(info, err.into()); return FinishingMiddlewares::init(info, err.into());
@ -636,7 +577,7 @@ impl<S: 'static> RunMiddlewares<S> {
curr, curr,
fut: Some(fut), fut: Some(fut),
_s: PhantomData, _s: PhantomData,
}) });
} }
}; };
} }
@ -660,7 +601,8 @@ impl<S: 'static> RunMiddlewares<S> {
if self.curr == len { if self.curr == len {
return Some(FinishingMiddlewares::init(info, resp)); return Some(FinishingMiddlewares::init(info, resp));
} else { } else {
match info.mws[self.curr].response(&mut info.req, resp) { let state = info.mws[self.curr].response(&info.req, resp);
match state {
Err(err) => { Err(err) => {
return Some(FinishingMiddlewares::init(info, err.into())) return Some(FinishingMiddlewares::init(info, err.into()))
} }
@ -728,9 +670,9 @@ impl<S: 'static> FinishingMiddlewares<S> {
} }
info.count -= 1; info.count -= 1;
match info.mws[info.count as usize] let state = info.mws[info.count as usize]
.finish(&mut info.req, self.resp.as_ref().unwrap()) .finish(&info.req, self.resp.as_ref().unwrap());
{ match state {
MiddlewareFinished::Done => { MiddlewareFinished::Done => {
if info.count == 0 { if info.count == 0 {
return Some(Response::init(self.resp.take().unwrap())); return Some(Response::init(self.resp.take().unwrap()));
@ -772,20 +714,73 @@ mod tests {
#[test] #[test]
fn test_scope() { fn test_scope() {
let mut app = App::new() let app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/app/path1").finish(); let req = TestRequest::with_uri("/app/path1").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
} }
#[test]
fn test_scope_root() {
let app = App::new()
.scope("/app", |scope| {
scope
.resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Created()))
})
.finish();
let req = TestRequest::with_uri("/app").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
}
#[test]
fn test_scope_root2() {
let app = App::new()
.scope("/app/", |scope| {
scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
})
.finish();
let req = TestRequest::with_uri("/app").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
fn test_scope_root3() {
let app = App::new()
.scope("/app/", |scope| {
scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
})
.finish();
let req = TestRequest::with_uri("/app").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test] #[test]
fn test_scope_route() { fn test_scope_route() {
let mut app = App::new() let app = App::new()
.scope("app", |scope| { .scope("app", |scope| {
scope scope
.route("/path1", Method::GET, |_: HttpRequest<_>| { .route("/path1", Method::GET, |_: HttpRequest<_>| {
@ -797,26 +792,26 @@ mod tests {
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/app/path1").finish(); let req = TestRequest::with_uri("/app/path1").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/path1") let req = TestRequest::with_uri("/app/path1")
.method(Method::DELETE) .method(Method::DELETE)
.finish(); .request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/path1") let req = TestRequest::with_uri("/app/path1")
.method(Method::POST) .method(Method::POST)
.finish(); .request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
#[test] #[test]
fn test_scope_filter() { fn test_scope_filter() {
let mut app = App::new() let app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope scope
.filter(pred::Get()) .filter(pred::Get())
@ -826,20 +821,20 @@ mod tests {
let req = TestRequest::with_uri("/app/path1") let req = TestRequest::with_uri("/app/path1")
.method(Method::POST) .method(Method::POST)
.finish(); .request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/path1") let req = TestRequest::with_uri("/app/path1")
.method(Method::GET) .method(Method::GET)
.finish(); .request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
} }
#[test] #[test]
fn test_scope_variable_segment() { fn test_scope_variable_segment() {
let mut app = App::new() let app = App::new()
.scope("/ab-{project}", |scope| { .scope("/ab-{project}", |scope| {
scope.resource("/path1", |r| { scope.resource("/path1", |r| {
r.f(|r| { r.f(|r| {
@ -850,7 +845,7 @@ mod tests {
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/ab-project1/path1").finish(); let req = TestRequest::with_uri("/ab-project1/path1").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
@ -862,7 +857,7 @@ mod tests {
_ => panic!(), _ => panic!(),
} }
let req = TestRequest::with_uri("/aa-project1/path1").finish(); let req = TestRequest::with_uri("/aa-project1/path1").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
@ -871,7 +866,7 @@ mod tests {
fn test_scope_with_state() { fn test_scope_with_state() {
struct State; struct State;
let mut app = App::new() let app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope.with_state("/t1", State, |scope| { scope.with_state("/t1", State, |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
@ -879,16 +874,81 @@ mod tests {
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/app/t1/path1").finish(); let req = TestRequest::with_uri("/app/t1/path1").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
} }
#[test]
fn test_scope_with_state_root() {
struct State;
let app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1", State, |scope| {
scope
.resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Created()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/t1/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
}
#[test]
fn test_scope_with_state_root2() {
struct State;
let app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1/", State, |scope| {
scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/t1/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test]
fn test_scope_with_state_root3() {
struct State;
let app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1/", State, |scope| {
scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/t1/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test] #[test]
fn test_scope_with_state_filter() { fn test_scope_with_state_filter() {
struct State; struct State;
let mut app = App::new() let app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope.with_state("/t1", State, |scope| { scope.with_state("/t1", State, |scope| {
scope scope
@ -900,20 +960,20 @@ mod tests {
let req = TestRequest::with_uri("/app/t1/path1") let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::POST) .method(Method::POST)
.finish(); .request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/t1/path1") let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::GET) .method(Method::GET)
.finish(); .request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
} }
#[test] #[test]
fn test_nested_scope() { fn test_nested_scope() {
let mut app = App::new() let app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope.nested("/t1", |scope| { scope.nested("/t1", |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
@ -921,14 +981,35 @@ mod tests {
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/app/t1/path1").finish(); let req = TestRequest::with_uri("/app/t1/path1").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
}
#[test]
fn test_nested_scope_root() {
let app = App::new()
.scope("/app", |scope| {
scope.nested("/t1", |scope| {
scope
.resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Created()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/t1/").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
} }
#[test] #[test]
fn test_nested_scope_filter() { fn test_nested_scope_filter() {
let mut app = App::new() let app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope.nested("/t1", |scope| { scope.nested("/t1", |scope| {
scope scope
@ -940,20 +1021,20 @@ mod tests {
let req = TestRequest::with_uri("/app/t1/path1") let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::POST) .method(Method::POST)
.finish(); .request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/t1/path1") let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::GET) .method(Method::GET)
.finish(); .request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
} }
#[test] #[test]
fn test_nested_scope_with_variable_segment() { fn test_nested_scope_with_variable_segment() {
let mut app = App::new() let app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope.nested("/{project_id}", |scope| { scope.nested("/{project_id}", |scope| {
scope.resource("/path1", |r| { scope.resource("/path1", |r| {
@ -968,7 +1049,7 @@ mod tests {
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/app/project_1/path1").finish(); let req = TestRequest::with_uri("/app/project_1/path1").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
@ -983,7 +1064,7 @@ mod tests {
#[test] #[test]
fn test_nested2_scope_with_variable_segment() { fn test_nested2_scope_with_variable_segment() {
let mut app = App::new() let app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope.nested("/{project}", |scope| { scope.nested("/{project}", |scope| {
scope.nested("/{id}", |scope| { scope.nested("/{id}", |scope| {
@ -1001,7 +1082,7 @@ mod tests {
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/app/test/1/path1").finish(); let req = TestRequest::with_uri("/app/test/1/path1").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
@ -1013,14 +1094,14 @@ mod tests {
_ => panic!(), _ => panic!(),
} }
let req = TestRequest::with_uri("/app/test/1/path2").finish(); let req = TestRequest::with_uri("/app/test/1/path2").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
#[test] #[test]
fn test_default_resource() { fn test_default_resource() {
let mut app = App::new() let app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope scope
.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) .resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
@ -1028,12 +1109,35 @@ mod tests {
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/app/path2").finish(); let req = TestRequest::with_uri("/app/path2").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/path2").finish(); let req = TestRequest::with_uri("/path2").request();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
#[test]
fn test_default_resource_propagation() {
let app = App::new()
.scope("/app1", |scope| {
scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest()))
})
.scope("/app2", |scope| scope)
.default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed()))
.finish();
let req = TestRequest::with_uri("/non-exist").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
let req = TestRequest::with_uri("/app1/non-exist").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/app2/non-exist").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
}
} }

305
src/serde_urlencoded/de.rs Normal file
View File

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

121
src/serde_urlencoded/mod.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

24
src/server/error.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,17 +8,18 @@ use modhttp::Response;
use std::rc::Rc; use std::rc::Rc;
use std::{cmp, io}; use std::{cmp, io};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{HttpTryFrom, Method, Version};
use http::{HttpTryFrom, Version};
use super::encoding::ContentEncoder;
use super::helpers; use super::helpers;
use super::message::Request;
use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::WorkerSettings; use super::settings::WorkerSettings;
use super::shared::SharedBytes;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
use body::{Binary, Body}; use body::{Binary, Body};
use header::ContentEncoding; use header::ContentEncoding;
use httprequest::HttpInnerMessage; use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
const CHUNK_SIZE: usize = 16_384; const CHUNK_SIZE: usize = 16_384;
@ -35,27 +36,25 @@ bitflags! {
pub(crate) struct H2Writer<H: 'static> { pub(crate) struct H2Writer<H: 'static> {
respond: SendResponse<Bytes>, respond: SendResponse<Bytes>,
stream: Option<SendStream<Bytes>>, stream: Option<SendStream<Bytes>>,
encoder: ContentEncoder,
flags: Flags, flags: Flags,
written: u64, written: u64,
buffer: SharedBytes, buffer: Output,
buffer_capacity: usize, buffer_capacity: usize,
settings: Rc<WorkerSettings<H>>, settings: Rc<WorkerSettings<H>>,
} }
impl<H: 'static> H2Writer<H> { impl<H: 'static> H2Writer<H> {
pub fn new( pub fn new(
respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<H>>, respond: SendResponse<Bytes>, settings: Rc<WorkerSettings<H>>,
) -> H2Writer<H> { ) -> H2Writer<H> {
H2Writer { H2Writer {
respond,
settings,
stream: None, stream: None,
encoder: ContentEncoder::empty(buf.clone()),
flags: Flags::empty(), flags: Flags::empty(),
written: 0, written: 0,
buffer: buf, buffer: Output::Buffer(settings.get_bytes()),
buffer_capacity: 0, buffer_capacity: 0,
respond,
settings,
} }
} }
@ -66,60 +65,92 @@ impl<H: 'static> H2Writer<H> {
} }
} }
impl<H: 'static> Drop for H2Writer<H> {
fn drop(&mut self) {
self.settings.release_bytes(self.buffer.take());
}
}
impl<H: 'static> Writer for H2Writer<H> { impl<H: 'static> Writer for H2Writer<H> {
fn written(&self) -> u64 { fn written(&self) -> u64 {
self.written self.written
} }
#[inline]
fn set_date(&mut self) {
self.settings.set_date(self.buffer.as_mut(), true)
}
#[inline]
fn buffer(&mut self) -> &mut BytesMut {
self.buffer.as_mut()
}
fn start( fn start(
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, &mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding,
encoding: ContentEncoding,
) -> io::Result<WriterState> { ) -> io::Result<WriterState> {
// prepare response // prepare response
self.flags.insert(Flags::STARTED); self.flags.insert(Flags::STARTED);
self.encoder = let mut info = ResponseInfo::new(req.inner.method == Method::HEAD);
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); self.buffer.for_server(&mut info, &req.inner, msg, encoding);
if let Body::Empty = *msg.body() {
self.flags.insert(Flags::EOF);
}
// http2 specific
msg.headers_mut().remove(CONNECTION);
msg.headers_mut().remove(TRANSFER_ENCODING);
// using helpers::date is quite a lot faster
if !msg.headers().contains_key(DATE) {
let mut bytes = BytesMut::with_capacity(29);
self.settings.set_date_simple(&mut bytes);
msg.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
}
let body = msg.replace_body(Body::Empty);
match body {
Body::Binary(ref bytes) => {
let mut val = BytesMut::new();
helpers::convert_usize(bytes.len(), &mut val);
let l = val.len();
msg.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
);
}
Body::Empty => {
msg.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
}
_ => (),
}
let mut has_date = false;
let mut resp = Response::new(()); let mut resp = Response::new(());
*resp.status_mut() = msg.status(); *resp.status_mut() = msg.status();
*resp.version_mut() = Version::HTTP_2; *resp.version_mut() = Version::HTTP_2;
for (key, value) in msg.headers().iter() { for (key, value) in msg.headers().iter() {
match *key {
// http2 specific
CONNECTION | TRANSFER_ENCODING => continue,
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
continue;
},
CONTENT_LENGTH => match info.length {
ResponseLength::None => (),
_ => continue,
},
DATE => has_date = true,
_ => (),
}
resp.headers_mut().insert(key, value.clone()); resp.headers_mut().insert(key, value.clone());
} }
// set date header
if !has_date {
let mut bytes = BytesMut::with_capacity(29);
self.settings.set_date(&mut bytes, false);
resp.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
}
// content length
match info.length {
ResponseLength::Zero => {
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
self.flags.insert(Flags::EOF);
}
ResponseLength::Length(len) => {
let mut val = BytesMut::new();
helpers::convert_usize(len, &mut val);
let l = val.len();
resp.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
);
}
ResponseLength::Length64(len) => {
let l = format!("{}", len);
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap());
}
_ => (),
}
if let Some(ce) = info.content_encoding {
resp.headers_mut()
.insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap());
}
match self match self
.respond .respond
.send_response(resp, self.flags.contains(Flags::EOF)) .send_response(resp, self.flags.contains(Flags::EOF))
@ -130,15 +161,20 @@ impl<H: 'static> Writer for H2Writer<H> {
trace!("Response: {:?}", msg); trace!("Response: {:?}", msg);
let body = msg.replace_body(Body::Empty);
if let Body::Binary(bytes) = body { if let Body::Binary(bytes) = body {
if bytes.is_empty() {
Ok(WriterState::Done)
} else {
self.flags.insert(Flags::EOF); self.flags.insert(Flags::EOF);
self.written = bytes.len() as u64; self.written = bytes.len() as u64;
self.encoder.write(bytes)?; self.buffer.write(bytes.as_ref())?;
if let Some(ref mut stream) = self.stream { if let Some(ref mut stream) = self.stream {
self.flags.insert(Flags::RESERVED); self.flags.insert(Flags::RESERVED);
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
} }
Ok(WriterState::Pause) Ok(WriterState::Pause)
}
} else { } else {
msg.replace_body(body); msg.replace_body(body);
self.buffer_capacity = msg.write_buffer_capacity(); self.buffer_capacity = msg.write_buffer_capacity();
@ -146,16 +182,16 @@ impl<H: 'static> Writer for H2Writer<H> {
} }
} }
fn write(&mut self, payload: Binary) -> io::Result<WriterState> { fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
self.written = payload.len() as u64; self.written = payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) { if !self.flags.contains(Flags::DISCONNECTED) {
if self.flags.contains(Flags::STARTED) { if self.flags.contains(Flags::STARTED) {
// TODO: add warning, write after EOF // TODO: add warning, write after EOF
self.encoder.write(payload)?; self.buffer.write(payload.as_ref())?;
} else { } else {
// might be response for EXCEPT // might be response for EXCEPT
self.buffer.extend_from_slice(payload.as_ref()) error!("Not supported");
} }
} }
@ -167,10 +203,8 @@ impl<H: 'static> Writer for H2Writer<H> {
} }
fn write_eof(&mut self) -> io::Result<WriterState> { fn write_eof(&mut self) -> io::Result<WriterState> {
self.encoder.write_eof()?;
self.flags.insert(Flags::EOF); self.flags.insert(Flags::EOF);
if !self.encoder.is_eof() { if !self.buffer.write_eof()? {
Err(io::Error::new( Err(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"Last payload item, but eof is not reached", "Last payload item, but eof is not reached",
@ -211,14 +245,18 @@ impl<H: 'static> Writer for H2Writer<H> {
let cap = cmp::min(self.buffer.len(), CHUNK_SIZE); let cap = cmp::min(self.buffer.len(), CHUNK_SIZE);
stream.reserve_capacity(cap); stream.reserve_capacity(cap);
} else { } else {
if eof {
stream.reserve_capacity(0);
continue;
}
self.flags.remove(Flags::RESERVED); self.flags.remove(Flags::RESERVED);
return Ok(Async::NotReady); return Ok(Async::Ready(()));
} }
} }
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
} }
} }
} }
Ok(Async::NotReady) Ok(Async::Ready(()))
} }
} }

View File

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

357
src/server/input.rs Normal file
View File

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

255
src/server/message.rs Normal file
View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,5 @@
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use std::cell::UnsafeCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::rc::Rc; use std::rc::Rc;
use error::Error; use error::Error;
@ -9,110 +7,14 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
/// Extractor configuration pub(crate) struct With<T, S, F, R>
///
/// `Route::with()` and `Route::with_async()` returns instance
/// of the `ExtractorConfig` type. It could be used for extractor configuration.
///
/// In this example `Form<FormData>` configured.
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Form, Result, http};
///
/// #[derive(Deserialize)]
/// struct FormData {
/// username: String,
/// }
///
/// fn index(form: Form<FormData>) -> Result<String> {
/// Ok(format!("Welcome {}!", form.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::GET)
/// .with(index)
/// .limit(4096);} // <- change form extractor configuration
/// );
/// }
/// ```
///
/// Same could be donce with multiple extractors
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Form, Path, Result, http};
///
/// #[derive(Deserialize)]
/// struct FormData {
/// username: String,
/// }
///
/// fn index(data: (Path<(String,)>, Form<FormData>)) -> Result<String> {
/// Ok(format!("Welcome {}!", data.1.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::GET)
/// .with(index)
/// .1.limit(4096);} // <- change form extractor configuration
/// );
/// }
/// ```
pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> {
cfg: Rc<UnsafeCell<T::Config>>,
}
impl<S: 'static, T: FromRequest<S>> Default for ExtractorConfig<S, T> {
fn default() -> Self {
ExtractorConfig {
cfg: Rc::new(UnsafeCell::new(T::Config::default())),
}
}
}
impl<S: 'static, T: FromRequest<S>> Clone for ExtractorConfig<S, T> {
fn clone(&self) -> Self {
ExtractorConfig {
cfg: Rc::clone(&self.cfg),
}
}
}
impl<S: 'static, T: FromRequest<S>> AsRef<T::Config> for ExtractorConfig<S, T> {
fn as_ref(&self) -> &T::Config {
unsafe { &*self.cfg.get() }
}
}
impl<S: 'static, T: FromRequest<S>> Deref for ExtractorConfig<S, T> {
type Target = T::Config;
fn deref(&self) -> &T::Config {
unsafe { &*self.cfg.get() }
}
}
impl<S: 'static, T: FromRequest<S>> DerefMut for ExtractorConfig<S, T> {
fn deref_mut(&mut self) -> &mut T::Config {
unsafe { &mut *self.cfg.get() }
}
}
pub struct With<T, S, F, R>
where where
F: Fn(T) -> R, F: Fn(T) -> R,
T: FromRequest<S>, T: FromRequest<S>,
S: 'static, S: 'static,
{ {
hnd: Rc<UnsafeCell<F>>, hnd: Rc<F>,
cfg: ExtractorConfig<S, T>, cfg: Rc<T::Config>,
_s: PhantomData<S>, _s: PhantomData<S>,
} }
@ -122,10 +24,10 @@ where
T: FromRequest<S>, T: FromRequest<S>,
S: 'static, S: 'static,
{ {
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self { pub fn new(f: F, cfg: T::Config) -> Self {
With { With {
cfg, cfg: Rc::new(cfg),
hnd: Rc::new(UnsafeCell::new(f)), hnd: Rc::new(f),
_s: PhantomData, _s: PhantomData,
} }
} }
@ -140,9 +42,9 @@ where
{ {
type Result = AsyncResult<HttpResponse>; type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut { let mut fut = WithHandlerFut {
req, req: req.clone(),
started: false, started: false,
hnd: Rc::clone(&self.hnd), hnd: Rc::clone(&self.hnd),
cfg: self.cfg.clone(), cfg: self.cfg.clone(),
@ -166,8 +68,8 @@ where
S: 'static, S: 'static,
{ {
started: bool, started: bool,
hnd: Rc<UnsafeCell<F>>, hnd: Rc<F>,
cfg: ExtractorConfig<S, T>, cfg: Rc<T::Config>,
req: HttpRequest<S>, req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>, fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<Box<Future<Item = HttpResponse, Error = Error>>>, fut2: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
@ -206,8 +108,7 @@ where
} }
}; };
let hnd: &mut F = unsafe { &mut *self.hnd.get() }; let item = match (*self.hnd)(item).respond_to(&self.req) {
let item = match (*hnd)(item).respond_to(&self.req) {
Ok(item) => item.into(), Ok(item) => item.into(),
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
@ -223,7 +124,7 @@ where
} }
} }
pub struct WithAsync<T, S, F, R, I, E> pub(crate) struct WithAsync<T, S, F, R, I, E>
where where
F: Fn(T) -> R, F: Fn(T) -> R,
R: Future<Item = I, Error = E>, R: Future<Item = I, Error = E>,
@ -232,8 +133,8 @@ where
T: FromRequest<S>, T: FromRequest<S>,
S: 'static, S: 'static,
{ {
hnd: Rc<UnsafeCell<F>>, hnd: Rc<F>,
cfg: ExtractorConfig<S, T>, cfg: Rc<T::Config>,
_s: PhantomData<S>, _s: PhantomData<S>,
} }
@ -246,10 +147,10 @@ where
T: FromRequest<S>, T: FromRequest<S>,
S: 'static, S: 'static,
{ {
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self { pub fn new(f: F, cfg: T::Config) -> Self {
WithAsync { WithAsync {
cfg, cfg: Rc::new(cfg),
hnd: Rc::new(UnsafeCell::new(f)), hnd: Rc::new(f),
_s: PhantomData, _s: PhantomData,
} }
} }
@ -266,12 +167,12 @@ where
{ {
type Result = AsyncResult<HttpResponse>; type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
let mut fut = WithAsyncHandlerFut { let mut fut = WithAsyncHandlerFut {
req, req: req.clone(),
started: false, started: false,
hnd: Rc::clone(&self.hnd), hnd: Rc::clone(&self.hnd),
cfg: self.cfg.clone(), cfg: Rc::clone(&self.cfg),
fut1: None, fut1: None,
fut2: None, fut2: None,
fut3: None, fut3: None,
@ -295,8 +196,8 @@ where
S: 'static, S: 'static,
{ {
started: bool, started: bool,
hnd: Rc<UnsafeCell<F>>, hnd: Rc<F>,
cfg: ExtractorConfig<S, T>, cfg: Rc<T::Config>,
req: HttpRequest<S>, req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>, fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<R>, fut2: Option<R>,
@ -356,458 +257,7 @@ where
} }
}; };
let hnd: &mut F = unsafe { &mut *self.hnd.get() }; self.fut2 = Some((*self.hnd)(item));
self.fut2 = Some((*hnd)(item));
self.poll()
}
}
pub struct With2<T1, T2, S, F, R>
where
F: Fn(T1, T2) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
_s: PhantomData<S>,
}
impl<T1, T2, S, F, R> With2<T1, T2, S, F, R>
where
F: Fn(T1, T2) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
S: 'static,
{
pub fn new(
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
) -> Self {
With2 {
hnd: Rc::new(UnsafeCell::new(f)),
cfg1,
cfg2,
_s: PhantomData,
}
}
}
impl<T1, T2, S, F, R> Handler<S> for With2<T1, T2, S, F, R>
where
F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut2 {
req,
started: false,
hnd: Rc::clone(&self.hnd),
cfg1: self.cfg1.clone(),
cfg2: self.cfg2.clone(),
item: None,
fut1: None,
fut2: None,
fut3: None,
};
match fut.poll() {
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::ok(e),
}
}
}
struct WithHandlerFut2<T1, T2, S, F, R>
where
F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
S: 'static,
{
started: bool,
hnd: Rc<UnsafeCell<F>>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
req: HttpRequest<S>,
item: Option<T1>,
fut1: Option<Box<Future<Item = T1, Error = Error>>>,
fut2: Option<Box<Future<Item = T2, Error = Error>>>,
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T1, T2, S, F, R> Future for WithHandlerFut2<T1, T2, S, F, R>
where
F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
S: 'static,
{
type Item = HttpResponse;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut3 {
return fut.poll();
}
if !self.started {
self.started = true;
let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
let item1 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
};
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item = Some(item1);
self.fut2 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item1, item2).respond_to(&self.req) {
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut3 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
if self.fut1.is_some() {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => {
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item = Some(item);
self.fut2 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item, item2).respond_to(&self.req) {
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut3 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
Async::NotReady => return Ok(Async::NotReady),
}
}
let item = match self.fut2.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)(self.item.take().unwrap(), item).respond_to(&self.req) {
Ok(item) => item.into(),
Err(err) => return Err(err.into()),
};
match item.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => self.fut3 = Some(fut),
}
self.poll()
}
}
pub struct With3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
cfg3: ExtractorConfig<S, T3>,
_s: PhantomData<S>,
}
impl<T1, T2, T3, S, F, R> With3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
pub fn new(
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
cfg3: ExtractorConfig<S, T3>,
) -> Self {
With3 {
hnd: Rc::new(UnsafeCell::new(f)),
cfg1,
cfg2,
cfg3,
_s: PhantomData,
}
}
}
impl<T1, T2, T3, S, F, R> Handler<S> for With3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S>,
T2: FromRequest<S>,
T3: FromRequest<S>,
T1: 'static,
T2: 'static,
T3: 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut3 {
req,
hnd: Rc::clone(&self.hnd),
cfg1: self.cfg1.clone(),
cfg2: self.cfg2.clone(),
cfg3: self.cfg3.clone(),
started: false,
item1: None,
item2: None,
fut1: None,
fut2: None,
fut3: None,
fut4: None,
};
match fut.poll() {
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::err(e),
}
}
}
struct WithHandlerFut3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
req: HttpRequest<S>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
cfg3: ExtractorConfig<S, T3>,
started: bool,
item1: Option<T1>,
item2: Option<T2>,
fut1: Option<Box<Future<Item = T1, Error = Error>>>,
fut2: Option<Box<Future<Item = T2, Error = Error>>>,
fut3: Option<Box<Future<Item = T3, Error = Error>>>,
fut4: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T1, T2, T3, S, F, R> Future for WithHandlerFut3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
type Item = HttpResponse;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut4 {
return fut.poll();
}
if !self.started {
self.started = true;
let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
let item1 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
};
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item1 = Some(item1);
self.fut2 = Some(fut);
return self.poll();
}
};
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
let item3 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item1 = Some(item1);
self.item2 = Some(item2);
self.fut3 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item1, item2, item3).respond_to(&self.req) {
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut4 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
if self.fut1.is_some() {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => {
self.item1 = Some(item);
self.fut1.take();
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut2 = Some(fut);
return self.poll();
}
};
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
let item3 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item2 = Some(item2);
self.fut3 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(self.item1.take().unwrap(), item2, item3)
.respond_to(&self.req)
{
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut4 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
Async::NotReady => return Ok(Async::NotReady),
}
}
if self.fut2.is_some() {
match self.fut2.as_mut().unwrap().poll()? {
Async::Ready(item) => {
self.fut2.take();
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
let item3 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item2 = Some(item);
self.fut3 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(self.item1.take().unwrap(), item, item3)
.respond_to(&self.req)
{
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut4 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
Async::NotReady => return Ok(Async::NotReady),
}
}
let item = match self.fut3.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item =
match (*hnd)(self.item1.take().unwrap(), self.item2.take().unwrap(), item)
.respond_to(&self.req)
{
Ok(item) => item.into(),
Err(err) => return Err(err.into()),
};
match item.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => self.fut4 = Some(fut),
}
self.poll() self.poll()
} }
} }

View File

@ -1,5 +1,5 @@
//! Http client request //! Http client request
use std::cell::UnsafeCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use std::{fmt, io, str}; use std::{fmt, io, str};
@ -7,59 +7,73 @@ use std::{fmt, io, str};
use base64; use base64;
use bytes::Bytes; use bytes::Bytes;
use cookie::Cookie; use cookie::Cookie;
use futures::unsync::mpsc::{unbounded, UnboundedSender}; use futures::sync::mpsc::{unbounded, UnboundedSender};
use futures::{Async, Future, Poll, Stream}; use futures::{Async, Future, Poll, Stream};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use http::{Error as HttpError, HttpTryFrom, StatusCode}; use http::{Error as HttpError, HttpTryFrom, StatusCode};
use rand; use rand;
use sha1::Sha1; use sha1::Sha1;
use actix::prelude::*; use actix::{Addr, SystemService};
use body::{Binary, Body}; use body::{Binary, Body};
use error::{Error, UrlParseError}; use error::{Error, UrlParseError};
use header::IntoHeaderValue; use header::IntoHeaderValue;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use payload::PayloadHelper; use payload::PayloadBuffer;
use client::{ use client::{
ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, ClientConnector, ClientRequest, ClientRequestBuilder, HttpResponseParserError,
HttpResponseParserError, SendRequest, SendRequestError, Pipeline, SendRequest, SendRequestError,
}; };
use super::frame::Frame; use super::frame::{Frame, FramedMessage};
use super::proto::{CloseReason, OpCode}; use super::proto::{CloseReason, OpCode};
use super::{Message, ProtocolError, WsWriter}; use super::{Message, ProtocolError, WsWriter};
/// Websocket client error /// Websocket client error
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
pub enum ClientError { pub enum ClientError {
/// Invalid url
#[fail(display = "Invalid url")] #[fail(display = "Invalid url")]
InvalidUrl, InvalidUrl,
/// Invalid response status
#[fail(display = "Invalid response status")] #[fail(display = "Invalid response status")]
InvalidResponseStatus(StatusCode), InvalidResponseStatus(StatusCode),
/// Invalid upgrade header
#[fail(display = "Invalid upgrade header")] #[fail(display = "Invalid upgrade header")]
InvalidUpgradeHeader, InvalidUpgradeHeader,
/// Invalid connection header
#[fail(display = "Invalid connection header")] #[fail(display = "Invalid connection header")]
InvalidConnectionHeader(HeaderValue), InvalidConnectionHeader(HeaderValue),
/// Missing CONNECTION header
#[fail(display = "Missing CONNECTION header")] #[fail(display = "Missing CONNECTION header")]
MissingConnectionHeader, MissingConnectionHeader,
/// Missing SEC-WEBSOCKET-ACCEPT header
#[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")] #[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")]
MissingWebSocketAcceptHeader, MissingWebSocketAcceptHeader,
/// Invalid challenge response
#[fail(display = "Invalid challenge response")] #[fail(display = "Invalid challenge response")]
InvalidChallengeResponse(String, HeaderValue), InvalidChallengeResponse(String, HeaderValue),
/// Http parsing error
#[fail(display = "Http parsing error")] #[fail(display = "Http parsing error")]
Http(Error), Http(Error),
/// Url parsing error
#[fail(display = "Url parsing error")] #[fail(display = "Url parsing error")]
Url(UrlParseError), Url(UrlParseError),
/// Response parsing error
#[fail(display = "Response parsing error")] #[fail(display = "Response parsing error")]
ResponseParseError(HttpResponseParserError), ResponseParseError(HttpResponseParserError),
/// Send request error
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
SendRequest(SendRequestError), SendRequest(SendRequestError),
/// Protocol error
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
Protocol(#[cause] ProtocolError), Protocol(#[cause] ProtocolError),
/// IO Error
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
Io(io::Error), Io(io::Error),
/// "Disconnected"
#[fail(display = "Disconnected")] #[fail(display = "Disconnected")]
Disconnected, Disconnected,
} }
@ -104,15 +118,16 @@ impl From<HttpResponseParserError> for ClientError {
/// ///
/// Example of `WebSocket` client usage is available in /// Example of `WebSocket` client usage is available in
/// [websocket example]( /// [websocket example](
/// https://github.com/actix/actix-web/blob/master/examples/websocket/src/client.rs#L24) /// https://github.com/actix/examples/blob/master/websocket/src/client.rs#L24)
pub struct Client { pub struct Client {
request: ClientRequestBuilder, request: ClientRequestBuilder,
err: Option<ClientError>, err: Option<ClientError>,
http_err: Option<HttpError>, http_err: Option<HttpError>,
origin: Option<HeaderValue>, origin: Option<HeaderValue>,
protocols: Option<String>, protocols: Option<String>,
conn: Addr<Unsync, ClientConnector>, conn: Addr<ClientConnector>,
max_size: usize, max_size: usize,
no_masking: bool,
} }
impl Client { impl Client {
@ -122,9 +137,7 @@ impl Client {
} }
/// Create new websocket connection with custom `ClientConnector` /// Create new websocket connection with custom `ClientConnector`
pub fn with_connector<S: AsRef<str>>( pub fn with_connector<S: AsRef<str>>(uri: S, conn: Addr<ClientConnector>) -> Client {
uri: S, conn: Addr<Unsync, ClientConnector>,
) -> Client {
let mut cl = Client { let mut cl = Client {
request: ClientRequest::build(), request: ClientRequest::build(),
err: None, err: None,
@ -132,6 +145,7 @@ impl Client {
origin: None, origin: None,
protocols: None, protocols: None,
max_size: 65_536, max_size: 65_536,
no_masking: false,
conn, conn,
}; };
cl.request.uri(uri.as_ref()); cl.request.uri(uri.as_ref());
@ -186,6 +200,12 @@ impl Client {
self self
} }
/// Disable payload masking. By default ws client masks frame payload.
pub fn no_masking(mut self) -> Self {
self.no_masking = true;
self
}
/// Set request header /// Set request header
pub fn header<K, V>(mut self, key: K, value: V) -> Self pub fn header<K, V>(mut self, key: K, value: V) -> Self
where where
@ -248,14 +268,14 @@ impl Client {
} }
// start handshake // start handshake
ClientHandshake::new(request, self.max_size) ClientHandshake::new(request, self.max_size, self.no_masking)
} }
} }
} }
struct Inner { struct Inner {
tx: UnboundedSender<Bytes>, tx: UnboundedSender<Bytes>,
rx: PayloadHelper<ClientResponse>, rx: PayloadBuffer<Box<Pipeline>>,
closed: bool, closed: bool,
} }
@ -269,10 +289,13 @@ pub struct ClientHandshake {
key: String, key: String,
error: Option<ClientError>, error: Option<ClientError>,
max_size: usize, max_size: usize,
no_masking: bool,
} }
impl ClientHandshake { impl ClientHandshake {
fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake { fn new(
mut request: ClientRequest, max_size: usize, no_masking: bool,
) -> ClientHandshake {
// Generate a random key for the `Sec-WebSocket-Key` header. // Generate a random key for the `Sec-WebSocket-Key` header.
// a base64-encoded (see Section 4 of [RFC4648]) value that, // a base64-encoded (see Section 4 of [RFC4648]) value that,
// when decoded, is 16 bytes in length (RFC 6455) // when decoded, is 16 bytes in length (RFC 6455)
@ -292,6 +315,7 @@ impl ClientHandshake {
ClientHandshake { ClientHandshake {
key, key,
max_size, max_size,
no_masking,
request: Some(request.send()), request: Some(request.send()),
tx: Some(tx), tx: Some(tx),
error: None, error: None,
@ -305,6 +329,7 @@ impl ClientHandshake {
tx: None, tx: None,
error: Some(err), error: Some(err),
max_size: 0, max_size: 0,
no_masking: false,
} }
} }
@ -406,24 +431,27 @@ impl Future for ClientHandshake {
let inner = Inner { let inner = Inner {
tx: self.tx.take().unwrap(), tx: self.tx.take().unwrap(),
rx: PayloadHelper::new(resp), rx: PayloadBuffer::new(resp.payload()),
closed: false, closed: false,
}; };
let inner = Rc::new(UnsafeCell::new(inner)); let inner = Rc::new(RefCell::new(inner));
Ok(Async::Ready(( Ok(Async::Ready((
ClientReader { ClientReader {
inner: Rc::clone(&inner), inner: Rc::clone(&inner),
max_size: self.max_size, max_size: self.max_size,
no_masking: self.no_masking,
}, },
ClientWriter { inner }, ClientWriter { inner },
))) )))
} }
} }
/// Websocket reader client
pub struct ClientReader { pub struct ClientReader {
inner: Rc<UnsafeCell<Inner>>, inner: Rc<RefCell<Inner>>,
max_size: usize, max_size: usize,
no_masking: bool,
} }
impl fmt::Debug for ClientReader { impl fmt::Debug for ClientReader {
@ -432,26 +460,20 @@ impl fmt::Debug for ClientReader {
} }
} }
impl ClientReader {
#[inline]
fn as_mut(&mut self) -> &mut Inner {
unsafe { &mut *self.inner.get() }
}
}
impl Stream for ClientReader { impl Stream for ClientReader {
type Item = Message; type Item = Message;
type Error = ProtocolError; type Error = ProtocolError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
let max_size = self.max_size; let max_size = self.max_size;
let inner = self.as_mut(); let no_masking = self.no_masking;
let mut inner = self.inner.borrow_mut();
if inner.closed { if inner.closed {
return Ok(Async::Ready(None)); return Ok(Async::Ready(None));
} }
// read // read
match Frame::parse(&mut inner.rx, false, max_size) { match Frame::parse(&mut inner.rx, no_masking, max_size) {
Ok(Async::Ready(Some(frame))) => { Ok(Async::Ready(Some(frame))) => {
let (_finished, opcode, payload) = frame.unpack(); let (_finished, opcode, payload) = frame.unpack();
@ -499,26 +521,23 @@ impl Stream for ClientReader {
} }
} }
/// Websocket writer client
pub struct ClientWriter { pub struct ClientWriter {
inner: Rc<UnsafeCell<Inner>>, inner: Rc<RefCell<Inner>>,
} }
impl ClientWriter { impl ClientWriter {
/// Write payload /// Write payload
#[inline] #[inline]
fn write(&mut self, mut data: Binary) { fn write(&mut self, mut data: FramedMessage) {
if !self.as_mut().closed { let inner = self.inner.borrow_mut();
let _ = self.as_mut().tx.unbounded_send(data.take()); if !inner.closed {
let _ = inner.tx.unbounded_send(data.0.take());
} else { } else {
warn!("Trying to write to disconnected response"); warn!("Trying to write to disconnected response");
} }
} }
#[inline]
fn as_mut(&mut self) -> &mut Inner {
unsafe { &mut *self.inner.get() }
}
/// Send text frame /// Send text frame
#[inline] #[inline]
pub fn text<T: Into<Binary>>(&mut self, text: T) { pub fn text<T: Into<Binary>>(&mut self, text: T) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,20 @@
extern crate actix; extern crate actix;
extern crate actix_web; extern crate actix_web;
#[cfg(feature = "brotli")]
extern crate brotli2;
extern crate bytes; extern crate bytes;
extern crate flate2; extern crate flate2;
extern crate futures; extern crate futures;
extern crate h2; extern crate h2;
extern crate http as modhttp; extern crate http as modhttp;
extern crate rand; extern crate rand;
extern crate tokio_core; extern crate tokio;
extern crate tokio_reactor;
extern crate tokio_tcp;
#[cfg(feature = "brotli")] use std::io::{Read, Write};
extern crate brotli2; use std::sync::{mpsc, Arc};
use std::{net, thread, time};
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder}; use brotli2::write::{BrotliDecoder, BrotliEncoder};
@ -21,12 +26,11 @@ use futures::stream::once;
use futures::{Future, Stream}; use futures::{Future, Stream};
use h2::client as h2client; use h2::client as h2client;
use modhttp::Request; use modhttp::Request;
use rand::distributions::Alphanumeric;
use rand::Rng; use rand::Rng;
use std::io::{Read, Write}; use tokio::executor::current_thread;
use std::sync::{mpsc, Arc}; use tokio::runtime::current_thread::Runtime;
use std::{net, thread, time}; use tokio_tcp::TcpStream;
use tokio_core::net::TcpStream;
use tokio_core::reactor::Core;
use actix::System; use actix::System;
use actix_web::*; use actix_web::*;
@ -54,12 +58,13 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World"; Hello World Hello World Hello World Hello World Hello World";
#[test] #[test]
#[cfg(unix)]
fn test_start() { fn test_start() {
let _ = test::TestServer::unused_addr(); let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
thread::spawn(move || { thread::spawn(|| {
let sys = System::new("test"); System::run(move || {
let srv = server::new(|| { let srv = server::new(|| {
vec![App::new().resource("/", |r| { vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok()) r.method(http::Method::GET).f(|_| HttpResponse::Ok())
@ -69,36 +74,44 @@ fn test_start() {
let srv = srv.bind("127.0.0.1:0").unwrap(); let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0]; let addr = srv.addrs()[0];
let srv_addr = srv.start(); let srv_addr = srv.start();
let _ = tx.send((addr, srv_addr)); let _ = tx.send((addr, srv_addr, System::current()));
sys.run();
}); });
let (addr, srv_addr) = rx.recv().unwrap(); });
let (addr, srv_addr, sys) = rx.recv().unwrap();
System::set_current(sys.clone());
let mut sys = System::new("test-server"); let mut rt = Runtime::new().unwrap();
{ {
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish() .finish()
.unwrap(); .unwrap();
let response = sys.run_until_complete(req.send()).unwrap(); let response = rt.block_on(req.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
} }
// pause // pause
let _ = srv_addr.send(server::PauseServer).wait(); let _ = srv_addr.send(server::PauseServer).wait();
thread::sleep(time::Duration::from_millis(200)); thread::sleep(time::Duration::from_millis(200));
assert!(net::TcpStream::connect(addr).is_err()); {
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.timeout(time::Duration::from_millis(200))
.finish()
.unwrap();
assert!(rt.block_on(req.send()).is_err());
}
// resume // resume
let _ = srv_addr.send(server::ResumeServer).wait(); let _ = srv_addr.send(server::ResumeServer).wait();
thread::sleep(time::Duration::from_millis(200)); thread::sleep(time::Duration::from_millis(200));
{ {
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish() .finish()
.unwrap(); .unwrap();
let response = sys.run_until_complete(req.send()).unwrap(); let response = rt.block_on(req.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
} }
let _ = sys.stop();
} }
#[test] #[test]
@ -107,8 +120,8 @@ fn test_shutdown() {
let _ = test::TestServer::unused_addr(); let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
thread::spawn(move || { thread::spawn(|| {
let sys = System::new("test"); System::run(move || {
let srv = server::new(|| { let srv = server::new(|| {
vec![App::new().resource("/", |r| { vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok()) r.method(http::Method::GET).f(|_| HttpResponse::Ok())
@ -118,24 +131,26 @@ fn test_shutdown() {
let srv = srv.bind("127.0.0.1:0").unwrap(); let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0]; let addr = srv.addrs()[0];
let srv_addr = srv.shutdown_timeout(1).start(); let srv_addr = srv.shutdown_timeout(1).start();
let _ = tx.send((addr, srv_addr)); let _ = tx.send((addr, srv_addr, System::current()));
sys.run();
}); });
let (addr, srv_addr) = rx.recv().unwrap(); });
let (addr, srv_addr, sys) = rx.recv().unwrap();
let mut sys = System::new("test-server"); System::set_current(sys.clone());
let mut rt = Runtime::new().unwrap();
{ {
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish() .finish()
.unwrap(); .unwrap();
let response = sys.run_until_complete(req.send()).unwrap(); let response = rt.block_on(req.send()).unwrap();
srv_addr.do_send(server::StopServer { graceful: true }); srv_addr.do_send(server::StopServer { graceful: true });
assert!(response.status().is_success()); assert!(response.status().is_success());
} }
thread::sleep(time::Duration::from_millis(1000)); thread::sleep(time::Duration::from_millis(1000));
assert!(net::TcpStream::connect(addr).is_err()); assert!(net::TcpStream::connect(addr).is_err());
let _ = sys.stop();
} }
#[test] #[test]
@ -254,7 +269,7 @@ fn test_body_gzip_large() {
#[test] #[test]
fn test_body_gzip_large_random() { fn test_body_gzip_large_random() {
let data = rand::thread_rng() let data = rand::thread_rng()
.gen_ascii_chars() .sample_iter(&Alphanumeric)
.take(70_000) .take(70_000)
.collect::<String>(); .collect::<String>();
let srv_data = Arc::new(data.clone()); let srv_data = Arc::new(data.clone());
@ -353,8 +368,8 @@ fn test_head_empty() {
} }
// read response // read response
//let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
//assert!(bytes.is_empty()); assert!(bytes.is_empty());
} }
#[test] #[test]
@ -381,8 +396,8 @@ fn test_head_binary() {
} }
// read response // read response
//let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
//assert!(bytes.is_empty()); assert!(bytes.is_empty());
} }
#[test] #[test]
@ -455,6 +470,39 @@ fn test_body_chunked_explicit() {
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
} }
#[test]
fn test_body_identity() {
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let enc = e.finish().unwrap();
let enc2 = enc.clone();
let mut srv = test::TestServer::new(move |app| {
let enc3 = enc2.clone();
app.handler(move |_| {
HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.header(http::header::CONTENT_ENCODING, "deflate")
.body(enc3.clone())
})
});
// client request
let request = srv
.get()
.header("accept-encoding", "deflate")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
// decode deflate
assert_eq!(bytes, Bytes::from(STR));
}
#[test] #[test]
fn test_body_deflate() { fn test_body_deflate() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
@ -509,7 +557,7 @@ fn test_body_brotli() {
#[test] #[test]
fn test_gzip_encoding() { fn test_gzip_encoding() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| { app.handler(|req: &HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
@ -542,7 +590,7 @@ fn test_gzip_encoding() {
fn test_gzip_encoding_large() { fn test_gzip_encoding_large() {
let data = STR.repeat(10); let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| { app.handler(|req: &HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
@ -574,12 +622,12 @@ fn test_gzip_encoding_large() {
#[test] #[test]
fn test_reading_gzip_encoding_large_random() { fn test_reading_gzip_encoding_large_random() {
let data = rand::thread_rng() let data = rand::thread_rng()
.gen_ascii_chars() .sample_iter(&Alphanumeric)
.take(60_000) .take(60_000)
.collect::<String>(); .collect::<String>();
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| { app.handler(|req: &HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
@ -612,7 +660,7 @@ fn test_reading_gzip_encoding_large_random() {
#[test] #[test]
fn test_reading_deflate_encoding() { fn test_reading_deflate_encoding() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| { app.handler(|req: &HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
@ -645,7 +693,7 @@ fn test_reading_deflate_encoding() {
fn test_reading_deflate_encoding_large() { fn test_reading_deflate_encoding_large() {
let data = STR.repeat(10); let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| { app.handler(|req: &HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
@ -677,12 +725,12 @@ fn test_reading_deflate_encoding_large() {
#[test] #[test]
fn test_reading_deflate_encoding_large_random() { fn test_reading_deflate_encoding_large_random() {
let data = rand::thread_rng() let data = rand::thread_rng()
.gen_ascii_chars() .sample_iter(&Alphanumeric)
.take(160_000) .take(160_000)
.collect::<String>(); .collect::<String>();
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| { app.handler(|req: &HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
@ -716,7 +764,7 @@ fn test_reading_deflate_encoding_large_random() {
#[test] #[test]
fn test_brotli_encoding() { fn test_brotli_encoding() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| { app.handler(|req: &HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
@ -750,7 +798,7 @@ fn test_brotli_encoding() {
fn test_brotli_encoding_large() { fn test_brotli_encoding_large() {
let data = STR.repeat(10); let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| { app.handler(|req: &HttpRequest| {
req.body() req.body()
.and_then(|bytes: Bytes| { .and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
@ -784,9 +832,8 @@ fn test_h2() {
let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let addr = srv.addr(); let addr = srv.addr();
let mut core = Core::new().unwrap(); let mut core = Runtime::new().unwrap();
let handle = core.handle(); let tcp = TcpStream::connect(&addr);
let tcp = TcpStream::connect(&addr, &handle);
let tcp = tcp let tcp = tcp
.then(|res| h2client::handshake(res.unwrap())) .then(|res| h2client::handshake(res.unwrap()))
@ -800,7 +847,7 @@ fn test_h2() {
let (response, _) = client.send_request(request, false).unwrap(); let (response, _) = client.send_request(request, false).unwrap();
// Spawn a task to run the conn... // Spawn a task to run the conn...
handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); current_thread::spawn(h2.map_err(|e| println!("GOT ERR={:?}", e)));
response.and_then(|response| { response.and_then(|response| {
assert_eq!(response.status(), http::StatusCode::OK); assert_eq!(response.status(), http::StatusCode::OK);
@ -813,7 +860,7 @@ fn test_h2() {
}) })
}) })
}); });
let _res = core.run(tcp); let _res = core.block_on(tcp);
// assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref())); // assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref()));
} }

View File

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

View File

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

View File

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