1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-12 21:43:41 +02:00

Compare commits

...

635 Commits

Author SHA1 Message Date
c04b4678f1 bump version 2018-10-14 08:10:41 -07:00
dd948f836e HttpServer not sending streamed request body on HTTP/2 requests #544 2018-10-14 08:08:12 -07:00
63a443fce0 Correct build script 2018-10-13 10:05:21 +03:00
d145136e56 Add individual check for TLS features 2018-10-13 09:54:03 +03:00
32145cf6c3 fix after update tokio-rustls (#542) 2018-10-11 11:05:07 +03:00
ec8aef6b43 update dep versions 2018-10-10 08:36:16 -07:00
f45038bbfe remove unused code 2018-10-09 13:23:37 -07:00
c63838bb71 fix 204 support for http/2 2018-10-09 13:12:49 -07:00
4d17a9afcc update version 2018-10-09 11:42:52 -07:00
65e9201b4d Fixed panic during graceful shutdown 2018-10-09 11:35:57 -07:00
c3ad516f56 disable shutdown atm 2018-10-09 09:45:24 -07:00
93b1c5fd46 update deps 2018-10-08 21:58:37 -07:00
4e7fac08b9 do not override content-length header 2018-10-08 15:30:59 -07:00
07f6ca4b71 Merge branch 'master' of github.com:actix/actix-web 2018-10-08 13:06:49 -07:00
03d988b898 refactor date rendering 2018-10-08 10:16:19 -07:00
cfad5bf1f3 enable slow request timeout for h2 dispatcher 2018-10-08 07:47:42 -07:00
10678a22af test content length (#532) 2018-10-06 08:17:20 +03:00
lzx
7ae5a43877 httpresponse.rs doc fix (#534) 2018-10-06 08:16:12 +03:00
1e1a4f846e use actix-net cell features 2018-10-02 22:23:51 -07:00
49eea3bf76 travis config 2018-10-02 20:22:51 -07:00
b0677aa029 fix stable compatibility 2018-10-02 19:42:24 -07:00
401ea574c0 make AcceptorTimeout::new public 2018-10-02 19:31:30 -07:00
bbcd618304 export AcceptorTimeout 2018-10-02 19:12:08 -07:00
1f68ce8541 fix tests 2018-10-02 19:05:58 -07:00
2710f70e39 add H1 transport 2018-10-02 17:30:29 -07:00
ae5c4dfb78 refactor http channels list; rename WorkerSettings 2018-10-02 15:25:32 -07:00
d7379bd10b update server ssl tests; upgrade rustls 2018-10-02 13:41:33 -07:00
b59712c439 add ssl handshake timeout tests 2018-10-02 11:32:43 -07:00
724668910b fix ssh handshake timeout 2018-10-02 11:18:59 -07:00
61c7534e03 fix stream flushing 2018-10-02 10:43:23 -07:00
f8b176de9e Fix no_http2 flag in HttpServer (#526) 2018-10-02 20:09:31 +03:00
c8505bb53f content-length bug fix (#525)
* content-length bug fix

* changes.md is updated

* typo
2018-10-02 09:15:48 -07:00
eed377e773 uneeded dep 2018-10-02 00:20:27 -07:00
f3ce6574e4 fix client timer and add slow request tests 2018-10-02 00:19:28 -07:00
f007860a16 cleanup warnings 2018-10-01 22:48:11 -07:00
fdfadb52e1 fix doc test for State 2018-10-01 22:29:30 -07:00
368f73513a set tcp-keepalive for test as well 2018-10-01 22:25:53 -07:00
c674ea9126 add StreamConfiguration service 2018-10-01 22:23:02 -07:00
7c78797d9b proper stop for test_ws_stopped test 2018-10-01 21:30:00 -07:00
84edc57fd9 increase sleep time 2018-10-01 21:19:27 -07:00
127af92541 clippy warnings 2018-10-01 21:16:56 -07:00
e4686f6c8d set socket linger to 0 on timeout 2018-10-01 20:53:22 -07:00
1bac65de4c add websocket stopped test 2018-10-01 20:15:26 -07:00
16945a554a add client shutdown timeout 2018-10-01 20:04:16 -07:00
91af3ca148 simplify h1 dispatcher 2018-10-01 19:18:24 -07:00
2217a152cb expose app error by http service 2018-10-01 15:19:49 -07:00
c1e0b4f322 expose internal http server types and allow to create custom http pipelines 2018-10-01 14:43:06 -07:00
5966ee6192 add HttpServer::register() function, allows to register services in actix net server 2018-09-28 16:03:53 -07:00
4aac3d6a92 refactor keep-alive timer 2018-09-28 15:04:59 -07:00
e95babf8d3 log acctor init errors 2018-09-28 12:37:20 -07:00
f2d42e5e77 refactor acceptor error handling 2018-09-28 11:50:47 -07:00
0f1c80ccc6 deprecate start_incoming 2018-09-28 08:45:49 -07:00
fc5088b55e fix tarpaulin args 2018-09-28 00:08:23 -07:00
bec37fdbd5 update travis config 2018-09-27 22:23:29 -07:00
4b59ae2476 fix ssl config for client connector 2018-09-27 22:15:38 -07:00
d0fc9d7b99 simplify listen_ and bind_ methods 2018-09-27 21:55:44 -07:00
1ff86e5ac4 restore rust-tls support 2018-09-27 21:24:21 -07:00
ecfda64f6d add native-tls support 2018-09-27 20:40:34 -07:00
0bca21ec6d fix ssl tests 2018-09-27 19:57:40 -07:00
3173c9fa83 diesable client timeout for tcp stream acceptor 2018-09-27 19:34:07 -07:00
85445ea809 rename and simplify ServiceFactory trait 2018-09-27 18:33:29 -07:00
d57579d700 refactor acceptor pipeline add client timeout 2018-09-27 18:33:29 -07:00
b6a1cfa6ad update openssl support 2018-09-27 18:33:29 -07:00
9f1417af30 refactor http service builder 2018-09-27 18:33:29 -07:00
0aa0f326f7 fix changes from master 2018-09-27 18:33:29 -07:00
dbb4fab4f7 separate mod for HttpHandler; add HttpHandler impl for Vec<H> 2018-09-27 18:33:29 -07:00
6f3e70a92a simplify application factory 2018-09-27 18:33:29 -07:00
a63d3f9a7a cleanup ServerFactory trait 2018-09-27 18:33:29 -07:00
a3cfc24232 refactor acceptor service 2018-09-27 18:33:29 -07:00
6a61138bf8 enable ssl feature 2018-09-27 18:33:29 -07:00
7cf9af9b55 disable ssl for travis 2018-09-27 18:33:29 -07:00
c9a52e3197 refactor date generatioin 2018-09-27 18:33:29 -07:00
1907102685 switch to actix-net server 2018-09-27 18:33:29 -07:00
52195bbf16 update version 2018-09-27 18:17:58 -07:00
59deb4b40d Try to separate HTTP/1 read & write disconnect handling, to fix #511. (#514) 2018-09-27 18:15:02 -07:00
782eeb5ded Reduced unsafe converage (#520) 2018-09-26 11:56:34 +03:00
1b298142e3 Correct composing of multiple origins in cors (#518) 2018-09-21 08:45:22 +03:00
0dc96658f2 Send response to inform client of error (#515) 2018-09-21 07:24:10 +03:00
f40153fca4 fix node::insert() method, missing next element 2018-09-17 11:39:03 -07:00
764103566d update changes 2018-09-17 10:48:37 -07:00
bfb2f2e9e1 fix node.remove(), update next node pointer 2018-09-17 10:25:45 -07:00
599e6b3385 refactor channel node remove operation 2018-09-17 05:29:07 -07:00
03e318f446 update changes 2018-09-15 17:10:53 -07:00
7449884ce3 fix wrong error message for path deserialize for i32 #510 2018-09-15 17:09:07 -07:00
bbe69e5b8d update version 2018-09-15 10:00:54 -07:00
9d1eefc38f use 5 seconds keep-alive timer by default 2018-09-15 09:57:54 -07:00
d65c72b44d use server keep-alive timer as slow request timer 2018-09-15 09:55:38 -07:00
c3f8b5cf22 clippy warnings 2018-09-11 11:25:32 -07:00
70a3f317d3 fix failing requests to test server #508 2018-09-11 11:24:05 -07:00
513c8ec1ce Merge pull request #505 from Neopallium/master
Fix issue with HttpChannel linked list.
2018-09-11 11:18:33 -07:00
04608b2ea6 Update changes. 2018-09-12 00:27:15 +08:00
70b45659e2 Make Node's traverse method take a closure instead of calling shutdown on each HttpChannel. 2018-09-12 00:27:15 +08:00
e0ae6b10cd Fix bug with HttpChannel linked list. 2018-09-12 00:27:15 +08:00
003b05b095 Don't ignore errors in std::fmt::Debug implementations (#506) 2018-09-11 14:57:55 +03:00
cdb57b840e prepare release 2018-09-07 20:47:54 -07:00
002bb24b26 unhide SessionBackend and SessionImpl traits and cleanup warnings 2018-09-07 20:46:43 -07:00
51982b3fec Merge pull request #503 from uzytkownik/route-regex
Refactor resource route parsing to allow repetition in the regexes
2018-09-07 20:19:31 -07:00
4251b0bc10 Refactor resource route parsing to allow repetition in the regexes 2018-09-06 08:51:55 +02:00
42f3773bec update changes 2018-09-05 09:03:58 -07:00
86fdbb47a5 Fix system_exit in HttpServer (#501) 2018-09-05 10:41:23 +02:00
4ca9fd2ad1 remove debug print 2018-09-03 22:09:12 -07:00
f0f67072ae Read client response until eof if connection header set to close #464 2018-09-03 21:35:59 -07:00
24d1228943 simplify handler path processing 2018-09-03 11:28:47 -07:00
b7a73e0a4f fix Scope::handler doc test 2018-09-02 08:51:26 -07:00
968c81e267 Handling scoped paths without leading slashes #460 2018-09-02 08:14:54 -07:00
d5957a8466 Merge branch 'master' of https://github.com/actix/actix-web 2018-09-02 07:47:45 -07:00
f2f05e7715 allow to register handlers on scope level #465 2018-09-02 07:47:19 -07:00
3439f55288 doc: Add example for using custom nativetls connector (#497) 2018-09-01 18:13:52 +03:00
0425e2776f Fix Issue #490 (#498)
* Add failing testcase for HTTP 404 response with no reason text.

* Include canonical reason test for HTTP error responses.

* Don't send a reason for unknown status codes.
2018-09-01 12:00:32 +03:00
6464f96f8b Merge branch 'master' of https://github.com/actix/actix-web 2018-08-31 18:56:53 -07:00
a2b170fec9 fmt 2018-08-31 18:56:21 -07:00
0b42cae082 update tests 2018-08-31 18:54:19 -07:00
c313c003a4 Fix typo 2018-08-31 17:45:29 -07:00
3fa23f5e10 update version 2018-08-31 17:25:15 -07:00
2d51831899 handle socket read disconnect 2018-08-31 17:24:13 -07:00
e59abfd716 Merge pull request #496 from Neopallium/master
Fix issue with 'Connection: close' in ClientRequest
2018-08-31 17:17:39 -07:00
66881d7dd1 If buffer is empty, read more data before calling parser. 2018-09-01 02:25:05 +08:00
a42a8a2321 Add some comments to clarify logic. 2018-09-01 02:15:36 +08:00
2341656173 Simplify buffer reading logic. Remove duplicate code. 2018-09-01 01:41:38 +08:00
487519acec Add client test for 'Connection: close' as reported in issue #495 2018-09-01 00:34:19 +08:00
af6caa92c8 Merge branch 'master' into master 2018-09-01 00:17:34 +08:00
3ccbce6bc8 Fix issue with 'Connection: close' in ClientRequest 2018-09-01 00:08:53 +08:00
797b52ecbf Update CHANGES.md 2018-08-29 20:58:23 +02:00
4bab50c861 Add ability to pass a custom TlsConnector (#491) 2018-08-29 20:53:31 +02:00
5906971b6d Merge pull request #483 from Neopallium/master
Fix bug with client disconnect immediately after receiving http request.
2018-08-26 10:15:25 -07:00
8393d09a0f Fix tests. 2018-08-27 00:31:31 +08:00
c3ae9997fc Fix bug with http1 client disconnects. 2018-08-26 22:21:05 +08:00
d39dcc58cd Merge pull request #482 from 0x1793d1/master
Fix server startup log message
2018-08-24 20:53:45 -07:00
471a3e9806 Fix server startup log message 2018-08-24 23:21:32 +02:00
48ef18ffa9 update changes 2018-08-23 12:54:59 -07:00
9ef7a9c182 hide AcceptorService 2018-08-23 11:30:49 -07:00
3dafe6c251 hide token and server flags 2018-08-23 11:30:07 -07:00
8dfc34e785 fix tokio-tls IoStream impl 2018-08-23 10:27:32 -07:00
810995ade0 fix tokio-tls dependency #480 2018-08-23 10:10:13 -07:00
1716380f08 clippy fmt 2018-08-23 09:48:01 -07:00
e9c139bdea clippy warnings 2018-08-23 09:47:32 -07:00
cf54be2f17 hide new server api 2018-08-23 09:39:11 -07:00
f39b520a2d Merge pull request #478 from fzgregor/master
Made extensions constructor public
2018-08-23 09:34:47 -07:00
89f414477c Merge branch 'master' into master 2018-08-23 09:34:34 -07:00
986f19af86 Revert back to serde_urlencoded dependecy (#479) 2018-08-21 22:23:17 +03:00
e680541e10 Made extensions constructor public 2018-08-18 19:32:28 +02:00
56bc900a82 Set minimum rustls version that fixes corruption (#474) 2018-08-17 19:53:16 +03:00
bdc9a8bb07 Optionally support tokio-uds's UnixStream as IoStream (#472) 2018-08-17 19:04:15 +03:00
8fe30a5b66 Merge pull request #473 from kornelski/usetest
Fix tests on Unix
2018-08-17 07:20:47 -07:00
a8405d0686 Fix tests on Unix 2018-08-17 13:13:48 +01:00
eb1e9a785f allow to use fn with multiple arguments with .with()/.with_async() 2018-08-16 20:29:06 -07:00
248bd388ca Improve HTTP server docs (#470) 2018-08-16 16:11:15 +03:00
9f5641c85b Add mention of reworked Content-Disposition 2018-08-13 17:37:00 +03:00
d9c7cd96a6 Rework Content-Disposition parsing totally (#461) 2018-08-13 17:34:05 +03:00
bf7779a9a3 add TestRequest::run_async_result helper method 2018-08-09 18:58:14 -07:00
cc3fbd27e0 better ergonomics 2018-08-09 17:25:23 -07:00
26629aafa5 explicit use 2018-08-09 13:41:13 -07:00
2ab7dbadce better ergonomics for Server::service() method 2018-08-09 13:38:10 -07:00
2e8d67e2ae upgrade native-tls package 2018-08-09 13:08:59 -07:00
43b6828ab5 Merge branch 'master' of https://github.com/actix/actix-web 2018-08-09 11:52:45 -07:00
e4ce6dfbdf refactor workers management 2018-08-09 11:52:32 -07:00
6b9fa2c3d9 Merge pull request #458 from davidMcneil/master
Add json2 HttpResponseBuilder method
2018-08-09 02:10:14 -07:00
5713d93158 Merge branch 'master' into master 2018-08-09 08:13:22 +03:00
cfe4829a56 add TestRequest::execute() helper method 2018-08-08 16:13:45 -07:00
b69774db61 fix attr name 2018-08-08 14:23:16 -07:00
542782f28a add HttpRequest::drop_state() 2018-08-08 13:57:13 -07:00
7c8dc4c201 Add json2 tests 2018-08-08 12:17:19 -06:00
7a11c2eac1 Add json2 HttpResponseBuilder method 2018-08-08 11:11:15 -06:00
8eb9eb4247 flush io on complete 2018-08-08 09:12:32 -07:00
992f7a11b3 remove debug println 2018-08-07 22:40:09 -07:00
30769e3072 fix http/2 error handling 2018-08-07 20:48:25 -07:00
57f991280c fix protocol order for rustls acceptor 2018-08-07 13:53:24 -07:00
85acc3f8df deprecate HttpServer::no_http2(), update changes 2018-08-07 12:49:40 -07:00
5bd82d4f03 update changes 2018-08-07 12:00:51 -07:00
58a079bd10 include content-length to error response 2018-08-07 11:56:39 -07:00
16546a707f Merge pull request #453 from DoumanAsh/reserve_status_line_for_server_error
Reserve enough space for ServerError task to write status line
2018-08-07 11:48:55 -07:00
86a5afb5ca Reserve enough space for ServerError task to write status line 2018-08-07 17:34:24 +03:00
9c80d3aa77 Write non-80 port in HOST of client's request (#451) 2018-08-07 10:01:29 +03:00
954f1a0b0f impl FromRequest for () (#449) 2018-08-06 10:44:08 +03:00
f4fba5f481 Merge pull request #447 from DoumanAsh/multiple_set_cookies
Correct setting cookies in HTTP2 writer
2018-08-04 08:58:12 -07:00
995f819eae Merge branch 'master' into multiple_set_cookies 2018-08-04 08:58:00 -07:00
85e7548088 fix adding multiple response headers for http/2 #446 2018-08-04 08:56:33 -07:00
900fd5a98e Correct settings headers for HTTP2
Add test to verify number of Set-Cookies
2018-08-04 18:05:41 +03:00
84b27db218 fix no_http2 flag 2018-08-03 19:40:43 -07:00
ac9180ac46 simplify channel impl 2018-08-03 19:32:46 -07:00
e34b5c08ba allow to pass extra information from acceptor to application level 2018-08-03 19:24:53 -07:00
f3f1e04853 refactor ssl support 2018-08-03 16:09:46 -07:00
036cf5e867 update changes 2018-08-03 08:20:59 -07:00
e61ef7dee4 Use zlib instead of deflate for content encoding (#442) 2018-08-03 14:56:26 +02:00
9a10d8aa7a Fixed headers' formating for CORS Middleware Access-Control-Expose-Headers header value to HTTP/1.1 & HTTP/2 spec-compliant format (#436) 2018-08-03 15:03:11 +03:00
f8e5d7c6c1 Fixed broken build on wrong variable usage (#440) 2018-08-03 14:11:51 +03:00
8c89c90c50 add accept backpressure #250 2018-08-02 23:17:10 -07:00
e9c1889df4 test timing 2018-08-01 16:41:24 -07:00
0da3fdcb09 do not use Arc for rustls config 2018-08-01 10:59:00 -07:00
a5f80a25ff update changes 2018-08-01 10:51:47 -07:00
6d9a1cadad Merge pull request #433 from jrconlin/feat/432
feature: allow TestServer to open a websocket on any URL
2018-08-01 10:45:55 -07:00
97ada3d3d0 Merge branch 'feat/432' of github.com:jrconlin/actix-web into feat/432 2018-08-01 10:27:48 -07:00
115f59dd14 Merge branch 'master' of https://github.com/actix/actix-web into feat/432 2018-08-01 09:59:36 -07:00
972b008a6e remove unsafe error transmute, upgrade failure to 0.1.2 #434 2018-08-01 09:42:12 -07:00
246eafb8d2 Merge branch 'master' of https://github.com/actix/actix-web into feat/432 2018-08-01 09:36:08 -07:00
dca4c110dd feature: allow TestServer to open a websocket on any URL
* added `TestServer::ws_at(uri_str)`
* modified `TestServer::ws()` to call `self.ws_at("/")` to preserve
behavior

Closes #432
2018-08-01 09:30:27 -07:00
58230b15b9 use one thread for accept loop; refactor rust-tls support 2018-07-31 19:51:26 -07:00
aa1e75f071 feature: allow TestServer to open a websocket on any URL
* added `TestServer::ws_at(uri_str)`
* modified `TestServer::ws()` to call `self.ws_at("/")` to preserve
behavior

Closes #432
2018-07-31 16:21:18 -07:00
2071ea0532 HttpRequest::url_for is not working with scopes #429 2018-07-31 15:40:52 -07:00
3bd43090fb use new gzdecoder, fixes gz streaming #228 2018-07-31 09:06:05 -07:00
4dba531bf9 do not override HOST header for client request #428 2018-07-31 08:51:24 -07:00
2072c933ba handle error during request creation 2018-07-30 15:04:52 -07:00
7bc0ace52d move server accept impl to seprate module 2018-07-30 13:42:42 -07:00
4c4d0d2745 update changes 2018-07-30 10:23:28 -07:00
28a855214b Merge pull request #427 from jeizsm/feature/rustls
add rustls
2018-07-30 10:21:37 -07:00
196da6d570 add rustls 2018-07-30 08:21:12 +03:00
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
9b7ea836d0 bump version 2018-05-17 18:34:09 -07:00
537b420d35 Fix compilation with --no-default-features 2018-05-17 18:33:48 -07:00
16906c5951 clippy warnings 2018-05-17 12:23:37 -07:00
45e9aaa462 rustfmt 0.7 2018-05-17 12:20:20 -07:00
564cc15c04 update changes 2018-05-17 12:20:04 -07:00
8fd18d56a5 Merge pull request #227 from qrvaelet/ranges
NamedFile: added basic ranges header support, added content-length support
2018-05-17 12:18:10 -07:00
a5692d4ecf Merge branch 'master' into ranges 2018-05-17 11:16:08 -07:00
2d83f79433 NamedFile: added ranges support, content-length support 2018-05-17 20:09:41 +02:00
f3ece74406 better error handling 2018-05-17 10:58:08 -07:00
8de1f60347 add session extractor doc api 2018-05-16 21:05:59 -07:00
b4252f8fd1 implement extractor for Session 2018-05-16 21:02:51 -07:00
fe2b50a9ef update changelog 2018-05-16 11:02:50 -07:00
d8ae8c3821 Merge pull request #225 from mitsuhiko/feature/addrs-with-scheme
Support returning addresses plus scheme from the server
2018-05-16 11:02:15 -07:00
c9a026fabb Merge branch 'master' into feature/addrs-with-scheme 2018-05-16 11:01:45 -07:00
64eca1546e Merge pull request #224 from mitsuhiko/feature/listen-tls
Add support for listen_tls/listen_ssl
2018-05-16 11:01:28 -07:00
b19fe98ff4 Merge branch 'master' into feature/listen-tls 2018-05-16 11:01:21 -07:00
b393ddf879 fix panic during middleware execution #226 2018-05-16 11:00:29 -07:00
7bb7d85c1d Added support for returning addresses plus scheme from the server 2018-05-16 16:17:27 +02:00
6e976153e7 Add support for listen_tls/listen_ssl 2018-05-16 15:20:47 +02:00
03e758cee4 bump version 2018-05-15 19:08:34 -07:00
0d36b8f826 fix 1.24 compatibility 2018-05-15 19:07:43 -07:00
f82fa08d72 various optimizations 2018-05-15 16:49:03 -07:00
d6787e6c56 prepare release 2018-05-15 10:20:32 -07:00
b9d870645f store cookies in extensions 2018-05-15 10:09:48 -07:00
ef89430f9b undeprecate query() and store query in extensions 2018-05-15 09:53:58 -07:00
953a0d4e4a add test case for #222 2018-05-15 09:29:59 -07:00
5ea2d68438 h1 decoder blocks on error #222 2018-05-15 07:55:36 -07:00
d65a03f6ac use latest nightly for appveyor 2018-05-13 08:43:09 -07:00
b588b2bf5c Merge pull request #221 from skorgu/patch-1
Include mention of http client in README.md
2018-05-11 21:56:46 -07:00
d455e2cd13 Merge branch 'master' into patch-1 2018-05-11 21:56:35 -07:00
9306631d6e Fix segfault in ServerSettings::get_response_builder() 2018-05-11 21:19:48 -07:00
f735da504b Include mention of http client in README.md 2018-05-11 20:36:54 -04:00
487a713ca0 update doc string 2018-05-11 15:01:15 -07:00
095ad328ee prepare release 2018-05-10 15:45:06 -07:00
a38afa0cec --no-count for tarpaulin 2018-05-10 13:05:56 -07:00
9619698543 doc string 2018-05-10 13:04:56 -07:00
4b1a471b35 add more examples for extractor config 2018-05-10 13:03:43 -07:00
b6039b0bff add doc string 2018-05-10 11:04:03 -07:00
d8fa43034f export ExtractorConfig type 2018-05-10 11:00:22 -07:00
92f993e054 Fix client request timeout handling 2018-05-10 09:37:38 -07:00
c172deb0f3 Merge pull request #219 from benjamingroeber/improve-readme
correct order of format arguments in readme example
2018-05-10 09:15:50 -07:00
dee6aed010 Merge branch 'master' into improve-readme 2018-05-10 09:15:44 -07:00
76f021a6e3 add tests for ErrorXXX helpers 2018-05-10 09:13:26 -07:00
2f244ea028 fix order of name and id in readme example 2018-05-10 18:12:59 +02:00
5f5ddc8f01 Merge pull request #218 from Dowwie/master
added error response functions for 501,502,503,504
2018-05-10 08:59:23 -07:00
8b473745cb added error response functions for 501,502,503,504 2018-05-10 11:26:38 -04:00
18575ee1ee Add Router::with_async() method for async handler registration 2018-05-09 16:27:31 -07:00
e58b38fd13 deprecate WsWrite from top level mod 2018-05-09 06:12:16 -07:00
b043c34632 bump version 2018-05-09 06:05:44 -07:00
b748bf3b0d make api public 2018-05-09 06:05:16 -07:00
be12d5e6fc make WsWriter trait optional 2018-05-09 05:48:06 -07:00
7c4941f868 update migration doc 2018-05-08 18:48:09 -07:00
d1f5c457c4 Merge branch 'master' of github.com:actix/actix-web 2018-05-08 18:35:52 -07:00
c26c5fd9a4 prep release 2018-05-08 18:34:36 -07:00
4a73d1c8c1 Merge pull request #216 from lcowell/lcowell-scoupe
replace typo `scoupe` with `scope`
2018-05-08 17:47:20 -07:00
7c395fcc83 replace typo scoupe with scope 2018-05-08 17:40:18 -07:00
54c33a7aff Allow to exclude certain endpoints from logging #211 2018-05-08 16:30:34 -07:00
47d80382b2 Fix http/2 payload streaming #215 2018-05-08 15:44:50 -07:00
ba816a8562 Merge pull request #214 from niklasf/de-path-404
let Path::from_request() fail with ErrorNotFound
2018-05-08 14:41:05 -07:00
6f75b0e95e let Path::from_request() fail with ErrorNotFound 2018-05-08 22:59:46 +02:00
b3cc43bb9b Fix connector's default keep-alive and lifetime settings #212 2018-05-08 13:41:04 -07:00
106 changed files with 17411 additions and 10295 deletions

View File

@ -1,43 +1,21 @@
environment: environment:
global: global:
PROJECT_NAME: actix PROJECT_NAME: actix-web
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-2017-12-21
- TARGET: i686-pc-windows-msvc - TARGET: i686-pc-windows-msvc
CHANNEL: nightly-2017-12-21 CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu - TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly-2017-12-21 CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc - TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly-2017-12-21 CHANNEL: nightly
# Install Rust and Cargo # Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml) # (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
@ -59,4 +37,5 @@ build: false
# Equivalent to Travis' `script` phase # Equivalent to Travis' `script` phase
test_script: test_script:
- cargo clean
- cargo test --no-default-features --features="flate2-rust" - cargo test --no-default-features --features="flate2-rust"

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,17 @@ before_script:
script: script:
- | - |
if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then
cargo clean cargo clean
cargo test --features="alpn,tls" -- --nocapture cargo check --features rust-tls
cargo check --features ssl
cargo check --features tls
cargo test --features="ssl,tls,rust-tls" -- --nocapture
fi fi
- | - |
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; 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 RUST_BACKTRACE=1 cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml
bash <(curl -s https://codecov.io/bash) bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage" echo "Uploaded code coverage"
fi fi
@ -47,7 +49,7 @@ script:
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" == "stable" ]]; then
cargo doc --features "alpn, tls, session" --no-deps && cargo doc --features "ssl,tls,rust-tls,session" --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html && echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import.git && git clone https://github.com/davisp/ghp-import.git &&
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&

View File

@ -1,5 +1,427 @@
# Changes # Changes
## [0.7.13] - 2018-10-14
### Fixed
* Fixed rustls support
* HttpServer not sending streamed request body on HTTP/2 requests #544
## [0.7.12] - 2018-10-10
### Changed
* Set min version for actix
* Set min version for actix-net
## [0.7.11] - 2018-10-09
### Fixed
* Fixed 204 responses for http/2
## [0.7.10] - 2018-10-09
### Fixed
* Fixed panic during graceful shutdown
## [0.7.9] - 2018-10-09
### Added
* Added client shutdown timeout setting
* Added slow request timeout setting
* Respond with 408 response on slow request timeout #523
### Fixed
* HTTP1 decoding errors are reported to the client. #512
* Correctly compose multiple allowed origins in CORS. #517
* Websocket server finished() isn't called if client disconnects #511
* Responses with the following codes: 100, 101, 102, 204 -- are sent without Content-Length header. #521
* Correct usage of `no_http2` flag in `bind_*` methods. #519
## [0.7.8] - 2018-09-17
### Added
* Use server `Keep-Alive` setting as slow request timeout #439
### Changed
* Use 5 seconds keep-alive timer by default.
### Fixed
* Fixed wrong error message for i16 type #510
## [0.7.7] - 2018-09-11
### Fixed
* Fix linked list of HttpChannels #504
* Fix requests to TestServer fail #508
## [0.7.6] - 2018-09-07
### Fixed
* Fix system_exit in HttpServer #501
* Fix parsing of route param containin regexes with repetition #500
### Changes
* Unhide `SessionBackend` and `SessionImpl` traits #455
## [0.7.5] - 2018-09-04
### Added
* Added the ability to pass a custom `TlsConnector`.
* Allow to register handlers on scope level #465
### Fixed
* Handle socket read disconnect
* Handling scoped paths without leading slashes #460
### Changed
* Read client response until eof if connection header set to close #464
## [0.7.4] - 2018-08-23
### Added
* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`,
accept backpressure #250
* Allow to customize connection handshake process via `HttpServer::listen_with()`
and `HttpServer::bind_with()` methods
* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472
### Changed
* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`.
`Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
* native-tls - 0.2
* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461
### Fixed
* Use zlib instead of raw deflate for decoding and encoding payloads with
`Content-Encoding: deflate`.
* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436
* Fix adding multiple response headers #446
* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448
* Panic during access without routing being set #452
* Fixed http/2 error handling
### Deprecated
* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or
`RustlsAcceptor::with_flags()` instead
* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been
deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`.
* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been
deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`.
## [0.7.3] - 2018-08-01
### Added
* Support HTTP/2 with rustls #36
* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433
### Fixed
* Fixed failure 0.1.2 compatibility
* Do not override HOST header for client request #428
* Gz streaming, use `flate2::write::GzDecoder` #228
* HttpRequest::url_for is not working with scopes #429
* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436
## [0.7.2] - 2018-07-26
### Added
* Add implementation of `FromRequest<S>` for `Option<T>` and `Result<T, Error>`
* Allow to handle application prefix, i.e. allow to handle `/app` path
for application with `/app` prefix.
Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix)
api doc.
* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies
### Changed
* Upgrade to cookie 0.11
* Removed the timestamp from the default logger middleware
### Fixed
* Missing response header "content-encoding" #421
* Fix stream draining for http/2 connections #290
## [0.7.1] - 2018-07-21
### Fixed
* Fixed default_resource 'not yet implemented' panic #410
## [0.7.0] - 2018-07-21
### Added
* Add `fs::StaticFileConfig` to provide means of customizing static
file services. It allows to map `mime` to `Content-Disposition`,
specify whether to use `ETag` and `Last-Modified` and allowed methods.
* Add `.has_prefixed_resource()` method to `router::ResourceInfo`
for route matching with prefix awareness
* Add `HttpMessage::readlines()` for reading line by line.
* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests.
* Add method to configure custom error handler to `Form` extractor.
* Add methods to `HttpResponse` to retrieve, add, and delete cookies
* Add `.set_content_type()` and `.set_content_disposition()` methods
to `fs::NamedFile` to allow overriding the values inferred by default
* Add `fs::file_extension_to_mime()` helper function to get the MIME
type for a file extension
* Add `.content_disposition()` method to parse Content-Disposition of
multipart fields
* Re-export `actix::prelude::*` as `actix_web::actix` module.
* `HttpRequest::url_for_static()` for a named route with no variables segments
* Propagation of the application's default resource to scopes that haven't set a default resource.
### Changed
* Min rustc version is 1.26
* Use tokio instead of tokio-core
* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages.
* Became possible to use enums with query extractor.
Issue [#371](https://github.com/actix/actix-web/issues/371).
[Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134)
* `HttpResponse::into_builder()` now moves cookies into the builder
instead of dropping them
* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest`
* Added header `User-Agent: Actix-web/<current_version>` to default headers when building a request
* port `Extensions` type from http create, we don't need `Send + Sync`
* `HttpRequest::query()` returns `Ref<HashMap<String, String>>`
* `HttpRequest::cookies()` returns `Ref<Vec<Cookie<'static>>>`
* `StaticFiles::new()` returns `Result<StaticFiles<S>, Error>` instead of `StaticFiles<S>`
* `StaticFiles` uses the default handler if the file does not exist
### Removed
* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead.
* Remove `HttpMessage::range()`
## [0.6.15] - 2018-07-11
### Fixed
* Fix h2 compatibility #352
* Fix duplicate tail of StaticFiles with index_file. #344
## [0.6.14] - 2018-06-21
### Added
* 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)
* Fix compilation with --no-default-features
## 0.6.6 (2018-05-17)
* Panic during middleware execution #226
* Add support for listen_tls/listen_ssl #224
* Implement extractor for `Session`
* Ranges header support for NamedFile #60
## 0.6.5 (2018-05-15)
* Fix error handling during request decoding #222
## 0.6.4 (2018-05-11)
* Fix segfault in ServerSettings::get_response_builder()
## 0.6.3 (2018-05-10)
* Add `Router::with_async()` method for async handler registration.
* Added error response functions for 501,502,503,504
* Fix client request timeout handling
## 0.6.2 (2018-05-09)
* WsWriter trait is optional.
## 0.6.1 (2018-05-08)
* Fix http/2 payload streaming #215
* Fix connector's default `keep-alive` and `lifetime` settings #212
* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214
* Allow to exclude certain endpoints from logging #211
## 0.6.0 (2018-05-08) ## 0.6.0 (2018-05-08)
* Add route scopes #202 * Add route scopes #202
@ -287,7 +709,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.0" version = "0.7.13"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://github.com/actix/actix-web" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/" documentation = "https://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", "ssl", "rust-tls", "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" }
@ -26,13 +29,22 @@ name = "actix_web"
path = "src/lib.rs" path = "src/lib.rs"
[features] [features]
default = ["session", "brotli", "flate2-c"] default = ["session", "brotli", "flate2-c", "cell"]
# tls # tls
tls = ["native-tls", "tokio-tls"] tls = ["native-tls", "tokio-tls", "actix-net/tls"]
# openssl # openssl
alpn = ["openssl", "tokio-openssl"] ssl = ["openssl", "tokio-openssl", "actix-net/ssl"]
# deprecated, use "ssl"
alpn = ["openssl", "tokio-openssl", "actix-net/ssl"]
# rustls
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"]
# unix sockets
uds = ["tokio-uds"]
# sessions feature, session require "ring" crate and c compiler # sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"] session = ["cookie/secure"]
@ -46,57 +58,74 @@ flate2-c = ["flate2/miniz-sys"]
# rust backend for flate2 crate # rust backend for flate2 crate
flate2-rust = ["flate2/rust_backend"] flate2-rust = ["flate2/rust_backend"]
cell = ["actix-net/cell"]
[dependencies] [dependencies]
actix = "^0.5.5" actix = "^0.7.5"
actix-net = "^0.1.1"
base64 = "0.9" base64 = "0.9"
bitflags = "1.0" bitflags = "1.0"
failure = "0.1.1" failure = "^0.1.2"
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"
serde_urlencoded = "^0.5.3"
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.2", optional = true, default-features = false }
# 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"
tokio-current-thread = "0.1"
# native-tls # native-tls
native-tls = { version="0.1", optional = true } native-tls = { version="0.2", optional = true }
tokio-tls = { version="0.1", optional = true } tokio-tls = { version="0.2", optional = true }
# openssl # openssl
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 }
#rustls
rustls = { version = "0.14", optional = true }
tokio-rustls = { version = "0.8", optional = true }
webpki = { version = "0.18", optional = true }
webpki-roots = { version = "0.15", optional = true }
# unix sockets
tokio-uds = { version="0.2", optional = true }
[dev-dependencies] [dev-dependencies]
env_logger = "0.5" env_logger = "0.5"
serde_derive = "1.0" serde_derive = "1.0"
@ -108,9 +137,3 @@ version_check = "0.1"
lto = true lto = true
opt-level = 3 opt-level = 3
codegen-units = 1 codegen-units = 1
[workspace]
members = [
"./",
"tools/wsload/",
]

View File

@ -1,4 +1,100 @@
## Migration from 0.5 to 0.6 ## 0.7.4
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
## 0.7
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
use `HttpMessage::payload()` method.
instead of
```rust
fn index(req: HttpRequest) -> impl Responder {
req
.from_err()
.fold(...)
....
}
```
use `.payload()`
```rust
fn index(req: HttpRequest) -> impl Responder {
req
.payload() // <- get request payload stream
.from_err()
.fold(...)
....
}
```
* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
instead of
```rust
fn index(query: Query<..>, info: Json<MyStruct) -> impl Responder {}
```
use tuple of extractors and use `.with()` for registration:
```rust
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
```
* `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
* Removed deprecated `HttpServer::threads()`, use
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
* Renamed `client::ClientConnectorError::Connector` to
`client::ClientConnectorError::Resolver`
* `Route::with()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_config()`
instead of
```rust
fn main() {
let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET)
.with(index)
.limit(4096); // <- limit size of the payload
});
}
```
use
```rust
fn main() {
let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET)
.with_config(index, |cfg| { // <- register handler
cfg.limit(4096); // <- limit size of the payload
})
});
}
```
* `Route::with_async()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_async_config()`
## 0.6
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
* `ws::Message::Close` now includes optional close reason. * `ws::Message::Close` now includes optional close reason.
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
@ -11,6 +107,17 @@
* `HttpRequest::extensions()` returns read only reference to the request's Extension * `HttpRequest::extensions()` returns read only reference to the request's Extension
`HttpRequest::extensions_mut()` returns mutable reference. `HttpRequest::extensions_mut()` returns mutable reference.
* Instead of
`use actix_web::middleware::{
CookieSessionBackend, CookieSessionError, RequestSession,
Session, SessionBackend, SessionImpl, SessionStorage};`
use `actix_web::middleware::session`
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
* `FromRequest::from_request()` accepts mutable reference to a request * `FromRequest::from_request()` accepts mutable reference to a request
* `FromRequest::Result` has to implement `Into<Reply<Self>>` * `FromRequest::Result` has to implement `Into<Reply<Self>>`
@ -19,7 +126,7 @@
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
is generic over `S` is generic over `S`
* `HttpRequest::query()` is deprecated. Use `Query` extractor. * Use `Query` extractor instead of HttpRequest::query()`.
```rust ```rust
fn index(q: Query<HashMap<String, String>>) -> Result<..> { fn index(q: Query<HashMap<String, String>>) -> Result<..> {
@ -33,8 +140,11 @@
let q = Query::<HashMap<String, String>>::extract(req); let q = Query::<HashMap<String, String>>::extract(req);
``` ```
* Websocket operations are implemented as `WsWriter` trait.
you need to use `use actix_web::ws::WsWriter`
## Migration from 0.4 to 0.5
## 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
* 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))
* 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.0, info.1) format!("Hello {}! id:{}", info.1, info.0)
} }
fn main() { fn main() {
@ -68,9 +63,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).
## License ## License

View File

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

View File

@ -1,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

File diff suppressed because it is too large Load Diff

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>>),
} }
@ -62,10 +59,28 @@ impl Body {
} }
} }
/// Is this binary empy.
#[inline]
pub fn is_empty(&self) -> bool {
match *self {
Body::Empty => true,
_ => false,
}
}
/// Create body from slice (copy) /// Create body from slice (copy)
pub fn from_slice(s: &[u8]) -> Body { pub fn from_slice(s: &[u8]) -> Body {
Body::Binary(Binary::Bytes(Bytes::from(s))) Body::Binary(Binary::Bytes(Bytes::from(s)))
} }
/// Is this binary body.
#[inline]
pub(crate) fn binary(self) -> Binary {
match self {
Body::Binary(b) => b,
_ => panic!(),
}
}
} }
impl PartialEq for Body { impl PartialEq for Body {
@ -112,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(),
} }
} }
@ -144,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()),
} }
} }
@ -156,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())),
} }
} }
@ -204,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))
} }
} }
@ -247,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(),
} }
} }
@ -306,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;
@ -33,9 +30,12 @@ mod request;
mod response; mod response;
mod writer; mod writer;
pub use self::connector::{ClientConnector, ClientConnectorError, ClientConnectorStats, pub use self::connector::{
Connect, Connection, Pause, Resume}; ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection,
Pause, Resume,
};
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
pub(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;
@ -49,6 +49,7 @@ use httpresponse::HttpResponse;
impl ResponseError for SendRequestError { impl ResponseError for SendRequestError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
SendRequestError::Timeout => HttpResponse::GatewayTimeout(),
SendRequestError::Connector(_) => HttpResponse::BadGateway(), SendRequestError::Connector(_) => HttpResponse::BadGateway(),
_ => HttpResponse::InternalServerError(), _ => HttpResponse::InternalServerError(),
}.into() }.into()
@ -57,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;
@ -19,6 +20,7 @@ const MAX_HEADERS: usize = 96;
#[derive(Default)] #[derive(Default)]
pub struct HttpResponseParser { pub struct HttpResponseParser {
decoder: Option<EncodingDecoder>, decoder: Option<EncodingDecoder>,
eof: bool, // indicate that we read payload until stream eof
} }
#[derive(Debug, Fail)] #[derive(Debug, Fail)]
@ -37,41 +39,42 @@ impl HttpResponseParser {
where where
T: IoStream, T: IoStream,
{ {
// if buf is empty parse_message will always return NotReady, let's avoid that loop {
if buf.is_empty() { // Don't call parser until we have data to parse.
match utils::read_from_io(io, buf) { if !buf.is_empty() {
Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect), match HttpResponseParser::parse_message(buf)
.map_err(HttpResponseParserError::Error)?
{
Async::Ready((msg, info)) => {
if let Some((decoder, eof)) = info {
self.eof = eof;
self.decoder = Some(decoder);
} else {
self.eof = false;
self.decoder = None;
}
return Ok(Async::Ready(msg));
}
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(
ParseError::TooLarge,
));
}
// Parser needs more data.
}
}
}
// Read some more data into the buffer for the parser.
match io.read_available(buf) {
Ok(Async::Ready((false, true))) => {
return Err(HttpResponseParserError::Disconnect)
}
Ok(Async::Ready(_)) => (), Ok(Async::Ready(_)) => (),
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())),
} }
} }
loop {
match HttpResponseParser::parse_message(buf)
.map_err(HttpResponseParserError::Error)?
{
Async::Ready((msg, decoder)) => {
self.decoder = decoder;
return Ok(Async::Ready(msg));
}
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(ParseError::TooLarge));
}
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => {
return Err(HttpResponseParserError::Disconnect)
}
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => {
return Err(HttpResponseParserError::Error(err.into()))
}
}
}
}
}
} }
pub fn parse_payload<T>( pub fn parse_payload<T>(
@ -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) {
@ -101,7 +104,12 @@ impl HttpResponseParser {
return Ok(Async::NotReady); return Ok(Async::NotReady);
} }
if stream_finished { if stream_finished {
return Err(PayloadError::Incomplete); // read untile eof?
if self.eof {
return Ok(Async::Ready(None));
} else {
return Err(PayloadError::Incomplete);
}
} }
} }
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
@ -114,25 +122,24 @@ impl HttpResponseParser {
fn parse_message( fn parse_message(
buf: &mut BytesMut, buf: &mut BytesMut,
) -> Poll<(ClientResponse, Option<EncodingDecoder>), ParseError> { ) -> Poll<(ClientResponse, Option<(EncodingDecoder, bool)>), 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 +153,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 {
@ -160,12 +168,12 @@ impl HttpResponseParser {
} }
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS { let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
Some(EncodingDecoder::eof()) Some((EncodingDecoder::eof(), true))
} else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { } else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) {
// Content-Length // Content-Length
if let Ok(s) = len.to_str() { if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() { if let Ok(len) = s.parse::<u64>() {
Some(EncodingDecoder::length(len)) Some((EncodingDecoder::length(len), false))
} else { } else {
debug!("illegal Content-Length: {:?}", len); debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header); return Err(ParseError::Header);
@ -176,7 +184,18 @@ impl HttpResponseParser {
} }
} else if chunked(&hdrs)? { } else if chunked(&hdrs)? {
// Chunked encoding // Chunked encoding
Some(EncodingDecoder::chunked()) Some((EncodingDecoder::chunked(), false))
} else if let Some(value) = hdrs.get(header::CONNECTION) {
let close = if let Ok(s) = value.to_str() {
s == "close"
} else {
false
};
if close {
Some((EncodingDecoder::eof(), true))
} else {
None
}
} else { } else {
None None
}; };

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,17 +202,23 @@ 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);
} }
State::Send(mut pl) => { State::Send(mut pl) => {
pl.poll_timeout()?;
pl.poll_write().map_err(|e| { pl.poll_write().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str()) io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str())
})?; })?;
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));
} }
@ -207,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!(),
@ -216,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>,
@ -228,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 {
@ -262,14 +285,19 @@ 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() {
conn.release() if self.meth == Method::HEAD {
conn.close()
} else {
conn.release()
}
} }
} }
#[inline] #[inline]
fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> { fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
if let Some(ref mut conn) = self.conn { if let Some(ref mut conn) = self.conn {
match self.parser match self
.parser
.as_mut() .as_mut()
.unwrap() .unwrap()
.parse(conn, &mut self.parser_buf) .parse(conn, &mut self.parser_buf)
@ -300,22 +328,20 @@ 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?
match self.poll_write() match self
.poll_write()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))? .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?
{ {
Async::NotReady => need_run = true, Async::NotReady => need_run = true,
Async::Ready(_) => { Async::Ready(_) => {
let _ = self.poll_timeout().map_err(|e| { self.poll_timeout().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e)) io::Error::new(io::ErrorKind::Other, format!("{}", e))
})?; })?;
} }
@ -323,8 +349,11 @@ 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.parser match self
.parser
.as_mut() .as_mut()
.unwrap() .unwrap()
.parse_payload(conn, &mut self.parser_buf)? .parse_payload(conn, &mut self.parser_buf)?
@ -371,16 +400,15 @@ impl Pipeline {
} }
} }
fn poll_timeout(&mut self) -> Poll<(), SendRequestError> { fn poll_timeout(&mut self) -> Result<(), SendRequestError> {
if self.timeout.is_some() { if self.timeout.is_some() {
match self.timeout.as_mut().unwrap().poll() { match self.timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(())) => Err(SendRequestError::Timeout), Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::NotReady) => (),
Err(_) => unreachable!(), Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e).into()),
} }
} else {
Ok(Async::NotReady)
} }
Ok(())
} }
#[inline] #[inline]
@ -401,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;
@ -428,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),
} }
@ -469,7 +498,8 @@ impl Pipeline {
} }
// flush io but only if we need to // flush io but only if we need to
match self.writer match self
.writer
.poll_completed(self.conn.as_mut().unwrap(), false) .poll_completed(self.conn.as_mut().unwrap(), false)
{ {
Ok(Async::Ready(_)) => { Ok(Async::Ready(_)) => {
@ -501,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),
} }
@ -256,16 +254,16 @@ impl ClientRequest {
impl fmt::Debug for ClientRequest { impl fmt::Debug for ClientRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = writeln!( writeln!(
f, f,
"\nClientRequest {:?} {}:{}", "\nClientRequest {:?} {}:{}",
self.version, self.method, self.uri self.version, self.method, self.uri
); )?;
let _ = writeln!(f, " headers:"); writeln!(f, " headers:")?;
for (key, val) in self.headers.iter() { for (key, val) in self.headers.iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val); writeln!(f, " {:?}: {:?}", key, val)?;
} }
res Ok(())
} }
} }
@ -293,10 +291,6 @@ impl ClientRequestBuilder {
fn _uri(&mut self, url: &str) -> &mut Self { fn _uri(&mut self, url: &str) -> &mut Self {
match Uri::try_from(url) { match Uri::try_from(url) {
Ok(uri) => { Ok(uri) => {
// set request host header
if let Some(host) = uri.host() {
self.set_header(header::HOST, host);
}
if let Some(parts) = parts(&mut self.request, &self.err) { if let Some(parts) = parts(&mut self.request, &self.err) {
parts.uri = uri; parts.uri = uri;
} }
@ -318,8 +312,7 @@ impl ClientRequestBuilder {
/// Set HTTP method of this request. /// Set HTTP method of this request.
#[inline] #[inline]
pub fn get_method(&mut self) -> &Method { pub fn get_method(&mut self) -> &Method {
let parts = let parts = self.request.as_ref().expect("cannot reuse request builder");
parts(&mut self.request, &self.err).expect("cannot reuse request builder");
&parts.method &parts.method
} }
@ -347,7 +340,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 +373,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 +416,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 +506,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 {
@ -499,16 +518,13 @@ impl ClientRequestBuilder {
jar.add(cookie.into_owned()); jar.add(cookie.into_owned());
self.cookies = Some(jar) self.cookies = Some(jar)
} else { } else {
self.cookies self.cookies.as_mut().unwrap().add(cookie.into_owned());
.as_mut()
.unwrap()
.add(cookie.into_owned());
} }
self self
} }
/// 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
@ -544,7 +560,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);
} }
@ -604,15 +620,40 @@ 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");
} }
// set request host header
if let Some(parts) = parts(&mut self.request, &self.err) {
if let Some(host) = parts.uri.host() {
if !parts.headers.contains_key(header::HOST) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match parts.uri.port() {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
parts.headers.insert(header::HOST, value);
}
Err(e) => self.err = Some(e.into()),
}
}
}
}
// user agent
self.set_header_if_none(
header::USER_AGENT,
concat!("actix-web/", env!("CARGO_PKG_VERSION")),
);
} }
let mut request = self.request let mut request = self.request.take().expect("cannot reuse request builder");
.take()
.expect("cannot reuse request builder");
// set cookies // set cookies
if let Some(ref mut jar) = self.cookies { if let Some(ref mut jar) = self.cookies {
@ -649,6 +690,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.
@ -657,9 +716,7 @@ impl ClientRequestBuilder {
S: Stream<Item = Bytes, Error = E> + 'static, S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error>, E: Into<Error>,
{ {
self.body(Body::Streaming(Box::new( self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
stream.map_err(|e| e.into()),
)))
} }
/// Set an empty body and generate `ClientRequest` /// Set an empty body and generate `ClientRequest`
@ -693,16 +750,16 @@ fn parts<'a>(
impl fmt::Debug for ClientRequestBuilder { impl fmt::Debug for ClientRequestBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref parts) = self.request { if let Some(ref parts) = self.request {
let res = writeln!( writeln!(
f, f,
"\nClientRequestBuilder {:?} {}:{}", "\nClientRequestBuilder {:?} {}:{}",
parts.version, parts.method, parts.uri parts.version, parts.method, parts.uri
); )?;
let _ = writeln!(f, " headers:"); writeln!(f, " headers:")?;
for (key, val) in parts.headers.iter() { for (key, val) in parts.headers.iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val); writeln!(f, " {:?}: {:?}", key, val)?;
} }
res Ok(())
} else { } else {
write!(f, "ClientRequestBuilder(Consumed)") write!(f, "ClientRequestBuilder(Consumed)")
} }

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 mut cookies = Vec::new();
let msg = self.as_mut(); for val in self.0.headers.get_all(header::SET_COOKIE).iter() {
let mut cookies = Vec::new(); let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?;
for val in msg.headers.get_all(header::SET_COOKIE).iter() { cookies.push(Cookie::parse_encoded(s)?.into_owned());
let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
}
msg.cookies = Some(cookies)
} }
Ok(self.as_ref().cookies.as_ref().unwrap()) Ok(cookies)
} }
/// 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 {
@ -103,31 +95,12 @@ impl ClientResponse {
impl fmt::Debug for ClientResponse { impl fmt::Debug for ClientResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = writeln!( writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status())?;
f, writeln!(f, " headers:")?;
"\nClientResponse {:?} {}",
self.version(),
self.status()
);
let _ = writeln!(f, " headers:");
for (key, val) in self.headers().iter() { for (key, val) in self.headers().iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val); writeln!(f, " {:?}: {:?}", key, val)?;
}
res
}
}
/// 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))
} }
Ok(())
} }
} }
@ -137,15 +110,13 @@ mod tests {
#[test] #[test]
fn test_debug() { fn test_debug() {
let resp = ClientResponse::new(ClientMessage::default()); let mut resp = ClientResponse::new(ClientMessage::default());
resp.as_mut().headers.insert( resp.0
header::COOKIE, .headers
HeaderValue::from_static("cookie1=value1"), .insert(header::COOKIE, HeaderValue::from_static("cookie1=value1"));
); resp.0
resp.as_mut().headers.insert( .headers
header::COOKIE, .insert(header::COOKIE, HeaderValue::from_static("cookie2=value2"));
HeaderValue::from_static("cookie2=value2"),
);
let dbg = format!("{:?}", resp); let dbg = format!("{:?}", resp);
assert!(dbg.contains("ClientResponse")); assert!(dbg.contains("ClientResponse"));

View File

@ -1,4 +1,7 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] #![cfg_attr(
feature = "cargo-clippy",
allow(clippy::redundant_field_names)
)]
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
@ -8,20 +11,20 @@ use std::io::{self, Write};
use brotli2::write::BrotliEncoder; use brotli2::write::BrotliEncoder;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
use flate2::write::{DeflateEncoder, GzEncoder}; use flate2::write::{GzEncoder, ZlibEncoder};
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
use flate2::Compression; use flate2::Compression;
use futures::{Async, Poll}; use futures::{Async, Poll};
use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, use http::header::{
TRANSFER_ENCODING}; HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::{HttpTryFrom, Version}; use http::{HttpTryFrom, Version};
use time::{self, Duration}; use time::{self, Duration};
use tokio_io::AsyncWrite; use tokio_io::AsyncWrite;
use body::{Binary, Body}; use body::{Binary, Body};
use header::ContentEncoding; use header::ContentEncoding;
use server::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;
@ -41,21 +44,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,
} }
} }
@ -75,7 +75,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);
@ -97,21 +97,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()
@ -119,10 +133,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 {
@ -142,33 +155,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 {
self.buffer_capacity = msg.write_buffer_capacity();
} }
} else {
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 {
@ -179,9 +188,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(
@ -209,7 +216,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();
@ -217,47 +224,57 @@ 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) => {
if encoding.is_compression() { #[cfg(any(feature = "flate2", feature = "brotli"))]
let tmp = SharedBytes::default(); {
let transfer = TransferEncoding::eof(tmp.clone()); if encoding.is_compression() {
let mut enc = match encoding { let mut tmp = BytesMut::new();
#[cfg(feature = "flate2")] let mut transfer = TransferEncoding::eof(tmp);
ContentEncoding::Deflate => ContentEncoder::Deflate( let mut enc = match encoding {
DeflateEncoder::new(transfer, Compression::default()), #[cfg(feature = "flate2")]
), ContentEncoding::Deflate => ContentEncoder::Deflate(
#[cfg(feature = "flate2")] ZlibEncoder::new(transfer, Compression::default()),
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( ),
transfer, #[cfg(feature = "flate2")]
Compression::default(), ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
)), transfer,
#[cfg(feature = "brotli")] Compression::default(),
ContentEncoding::Br => { )),
ContentEncoder::Br(BrotliEncoder::new(transfer, 5)) #[cfg(feature = "brotli")]
} ContentEncoding::Br => {
ContentEncoding::Identity => ContentEncoder::Identity(transfer), ContentEncoder::Br(BrotliEncoder::new(transfer, 5))
ContentEncoding::Auto => unreachable!(), }
}; ContentEncoding::Auto | ContentEncoding::Identity => {
// TODO return error! unreachable!()
let _ = enc.write(bytes.clone()); }
let _ = enc.write_eof(); };
*bytes = Binary::from(tmp.take()); // TODO return error!
let _ = enc.write(bytes.as_ref());
let _ = enc.write_eof();
*bytes = Binary::from(enc.buf_mut().take());
req.headers_mut().insert( req.headers_mut().insert(
CONTENT_ENCODING, CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()), HeaderValue::from_static(encoding.as_str()),
); );
encoding = ContentEncoding::Identity; encoding = ContentEncoding::Identity;
}
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
}
#[cfg(not(any(feature = "flate2", feature = "brotli")))]
{
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
} }
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() {
@ -286,26 +303,24 @@ 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 => {
transfer, ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::default()))
Compression::default(), }
)),
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
ContentEncoding::Gzip => { ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default())) ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default()))
} }
#[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,13 +1,18 @@
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::{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, SpawnHandle,
};
use body::{Binary, Body}; use body::{Binary, Body};
use error::{Error, ErrorInternalServerError}; use error::{Error, ErrorInternalServerError};
@ -38,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,
@ -80,22 +85,17 @@ where
#[doc(hidden)] #[doc(hidden)]
#[inline] #[inline]
fn waiting(&self) -> bool { fn waiting(&self) -> bool {
self.inner.waiting() || self.inner.state() == ActorState::Stopping self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped || self.inner.state() == ActorState::Stopped
} }
#[inline] #[inline]
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()
} }
} }
@ -104,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]
pub fn actor(mut self, actor: A) -> HttpContext<A, S> { /// Create a new HTTP Context
self.inner.set_actor(actor); pub fn with_factory<F>(req: HttpRequest<S>, f: F) -> Body
self where
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)))
} }
} }
@ -157,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)
} }
@ -176,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
@ -187,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))
@ -220,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) => {
@ -177,7 +175,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
parse_single_value!(deserialize_bool, visit_bool, "bool"); parse_single_value!(deserialize_bool, visit_bool, "bool");
parse_single_value!(deserialize_i8, visit_i8, "i8"); parse_single_value!(deserialize_i8, visit_i8, "i8");
parse_single_value!(deserialize_i16, visit_i16, "i16"); parse_single_value!(deserialize_i16, visit_i16, "i16");
parse_single_value!(deserialize_i32, visit_i32, "i16"); parse_single_value!(deserialize_i32, visit_i32, "i32");
parse_single_value!(deserialize_i64, visit_i64, "i64"); parse_single_value!(deserialize_i64, visit_i64, "i64");
parse_single_value!(deserialize_u8, visit_u8, "u8"); parse_single_value!(deserialize_u8, visit_u8, "u8");
parse_single_value!(deserialize_u16, visit_u16, "u16"); parse_single_value!(deserialize_u16, visit_u16, "u16");
@ -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,9 +200,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
where where
K: de::DeserializeSeed<'de>, K: de::DeserializeSeed<'de>,
{ {
self.current = self.params self.current = self.params.next().map(|ref item| (item.0, item.1));
.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),
@ -336,9 +332,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
visitor.visit_enum(ValueEnum { visitor.visit_enum(ValueEnum { value: self.value })
value: self.value,
})
} }
fn deserialize_newtype_struct<V>( fn deserialize_newtype_struct<V>(
@ -372,9 +366,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
Err(de::value::Error::custom( Err(de::value::Error::custom("unsupported type: tuple struct"))
"unsupported type: tuple struct",
))
} }
unsupported_type!(deserialize_any, "any"); unsupported_type!(deserialize_any, "any");
@ -384,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> {
@ -395,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),
} }
} }
@ -415,10 +405,7 @@ impl<'de> de::EnumAccess<'de> for ValueEnum<'de> {
where where
V: de::DeserializeSeed<'de>, V: de::DeserializeSeed<'de>,
{ {
Ok(( Ok((seed.deserialize(Key { key: self.value })?, UnitVariant))
seed.deserialize(Key { key: self.value })?,
UnitVariant,
))
} }
} }

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,45 @@ 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 +80,54 @@ 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.
let compat: Option<&failure::Compat<failure::Error>> =
Fail::downcast_ref(self.cause.as_fail());
compat.and_then(|e| e.get_ref().downcast_ref())
}
}
/// 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.
@ -88,7 +157,7 @@ impl fmt::Debug for Error {
} }
} }
/// `HttpResponse` for `Error` /// Convert `Error` to a `HttpResponse` instance
impl From<Error> for HttpResponse { impl From<Error> for HttpResponse {
fn from(err: Error) -> Self { fn from(err: Error) -> Self {
HttpResponse::from_error(err) HttpResponse::from_error(err)
@ -111,11 +180,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 +193,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 +362,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,40 +382,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 {
@ -416,8 +465,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")]
@ -457,8 +508,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")]
@ -495,6 +546,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)]
@ -520,12 +595,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),
} }
@ -539,6 +615,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"
@ -551,8 +645,8 @@ impl From<UrlParseError> for UrlGenerationError {
/// use actix_web::fs::NamedFile; /// use actix_web::fs::NamedFile;
/// ///
/// fn index(req: HttpRequest) -> Result<fs::NamedFile> { /// fn index(req: HttpRequest) -> Result<fs::NamedFile> {
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?; /// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
/// Ok(f) /// Ok(f)
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
@ -562,12 +656,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> {
@ -582,9 +673,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(),
} }
} }
@ -625,8 +717,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)
} }
@ -757,6 +849,46 @@ where
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
} }
/// Helper function that creates wrapper of any error and
/// generate *NOT IMPLEMENTED* response.
#[allow(non_snake_case)]
pub fn ErrorNotImplemented<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *BAD GATEWAY* response.
#[allow(non_snake_case)]
pub fn ErrorBadGateway<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::BAD_GATEWAY).into()
}
/// Helper function that creates wrapper of any error and
/// generate *SERVICE UNAVAILABLE* response.
#[allow(non_snake_case)]
pub fn ErrorServiceUnavailable<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into()
}
/// Helper function that creates wrapper of any error and
/// generate *GATEWAY TIMEOUT* response.
#[allow(non_snake_case)]
pub fn ErrorGatewayTimeout<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -781,9 +913,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);
@ -796,7 +925,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);
@ -814,7 +943,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]
@ -833,14 +962,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();
@ -912,4 +1033,78 @@ mod tests {
let resp: HttpResponse = err.error_response(); let resp: HttpResponse = err.error_response();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test]
fn test_error_downcasting_direct() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = DemoError.into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_downcasting_compat() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = failure::Error::from(DemoError).into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_helpers() {
let r: HttpResponse = ErrorBadRequest("err").into();
assert_eq!(r.status(), StatusCode::BAD_REQUEST);
let r: HttpResponse = ErrorUnauthorized("err").into();
assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
let r: HttpResponse = ErrorForbidden("err").into();
assert_eq!(r.status(), StatusCode::FORBIDDEN);
let r: HttpResponse = ErrorNotFound("err").into();
assert_eq!(r.status(), StatusCode::NOT_FOUND);
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
let r: HttpResponse = ErrorRequestTimeout("err").into();
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
let r: HttpResponse = ErrorConflict("err").into();
assert_eq!(r.status(), StatusCode::CONFLICT);
let r: HttpResponse = ErrorGone("err").into();
assert_eq!(r.status(), StatusCode::GONE);
let r: HttpResponse = ErrorPreconditionFailed("err").into();
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
let r: HttpResponse = ErrorExpectationFailed("err").into();
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
let r: HttpResponse = ErrorInternalServerError("err").into();
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
let r: HttpResponse = ErrorNotImplemented("err").into();
assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED);
let r: HttpResponse = ErrorBadGateway("err").into();
assert_eq!(r.status(), StatusCode::BAD_GATEWAY);
let r: HttpResponse = ErrorServiceUnavailable("err").into();
assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE);
let r: HttpResponse = ErrorGatewayTimeout("err").into();
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
}
} }

114
src/extensions.rs Normal file
View File

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

View File

@ -1,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}; 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
@ -35,8 +37,9 @@ 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 {
@ -62,8 +65,9 @@ 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> {
@ -97,6 +101,12 @@ impl<T> Path<T> {
} }
} }
impl<T> From<T> for Path<T> {
fn from(inner: T) -> Path<T> {
Path { inner }
}
}
impl<T, S> FromRequest<S> for Path<T> impl<T, S> FromRequest<S> for Path<T>
where where
T: DeserializeOwned, T: DeserializeOwned,
@ -108,11 +118,24 @@ where
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone(); let req = req.clone();
de::Deserialize::deserialize(PathDeserializer::new(&req)) de::Deserialize::deserialize(PathDeserializer::new(&req))
.map_err(|e| e.into()) .map_err(ErrorNotFound)
.map(|inner| Path { inner }) .map(|inner| Path { inner })
} }
} }
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 +147,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 +205,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 +280,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 +328,43 @@ where
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().resource( /// let app = App::new().resource(
/// "/index.html", |r| { /// "/index.html",
/// r.method(http::Method::GET) /// |r| {
/// .with(index) /// r.method(http::Method::GET)
/// .limit(4096);} // <- change form extractor configuration /// // register form handler and change form extractor configuration
/// .with_config(index, |cfg| {cfg.0.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 +388,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,11 +401,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(),
))
} }
} }
@ -356,12 +424,12 @@ 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_config(index, |cfg| { // <- register handler with extractor params
/// .with(index) // <- register handler with extractor params /// cfg.0.limit(4096); // <- limit size of the payload
/// .limit(4096); // <- limit size of the payload /// })
/// }); /// });
/// } /// }
/// ``` /// ```
impl<S: 'static> FromRequest<S> for String { impl<S: 'static> FromRequest<S> for String {
@ -377,7 +445,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| {
@ -396,6 +464,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,
@ -508,18 +696,17 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
} }
}); });
impl<S> FromRequest<S> for () {
type Config = ();
type Result = Self;
fn from_request(_req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {}
}
tuple_from_req!(TupleFromRequest1, (0, A)); tuple_from_req!(TupleFromRequest1, (0, A));
tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); tuple_from_req!(TupleFromRequest2, (0, A), (1, B));
tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C));
tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D));
tuple_from_req!( tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E));
TupleFromRequest5,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E)
);
tuple_from_req!( tuple_from_req!(
TupleFromRequest6, TupleFromRequest6,
(0, A), (0, A),
@ -570,9 +757,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)]
@ -583,15 +769,11 @@ 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) match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() {
.unwrap()
.poll()
.unwrap()
{
Async::Ready(s) => { Async::Ready(s) => {
assert_eq!(s, Bytes::from_static(b"hello=world")); assert_eq!(s, Bytes::from_static(b"hello=world"));
} }
@ -602,15 +784,11 @@ 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) match String::from_request(&req, &cfg).unwrap().poll().unwrap() {
.unwrap()
.poll()
.unwrap()
{
Async::Ready(s) => { Async::Ready(s) => {
assert_eq!(s, "hello=world"); assert_eq!(s, "hello=world");
} }
@ -620,13 +798,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")
.finish(); .set_payload(Bytes::from_static(b"hello=world"))
req.payload_mut() .finish();
.unread_data(Bytes::from_static(b"hello=world"));
let mut cfg = FormConfig::default(); let mut cfg = FormConfig::default();
cfg.limit(4096); cfg.limit(4096);
@ -638,9 +815,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());
@ -675,17 +944,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::<()>::default();
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(( let req = req.with_route_info(info);
Resource::new("index", "/{key}/{value}/"),
Some(resource),
));
let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some());
let s = Path::<MyStruct>::from_request(&req, &()).unwrap(); let s = Path::<MyStruct>::from_request(&req, &()).unwrap();
assert_eq!(s.key, "name"); assert_eq!(s.key, "name");
@ -698,8 +962,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::<()>::default();
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");
@ -716,31 +983,23 @@ mod tests {
#[test] #[test]
fn test_extract_path_single() { fn test_extract_path_single() {
let mut resource = ResourceHandler::<()>::default(); let mut router = Router::<()>::default();
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::<()>::default();
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,
@ -759,5 +1018,7 @@ mod tests {
assert_eq!((res.0).1, "user1"); assert_eq!((res.0).1, "user1");
assert_eq!((res.1).0, "name"); assert_eq!((res.1).0, "name");
assert_eq!((res.1).1, "user1"); assert_eq!((res.1).1, "user1");
let () = <()>::extract(&req);
} }
} }

1349
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)
} }
} }
@ -327,13 +353,17 @@ impl<T, E: Into<Error>> From<Result<T, E>> for AsyncResult<T> {
} }
} }
impl<T, E: Into<Error>> From<Result<Box<Future<Item = T, Error = Error>>, E>> impl<T, E> From<Result<Box<Future<Item = T, Error = E>>, E>> for AsyncResult<T>
for AsyncResult<T> where
T: 'static,
E: Into<Error> + 'static,
{ {
#[inline] #[inline]
fn from(res: Result<Box<Future<Item = T, Error = Error>>, E>) -> Self { fn from(res: Result<Box<Future<Item = T, Error = E>>, E>) -> Self {
match res { match res {
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(fut))), Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(Box::new(
fut.map_err(|e| e.into()),
)))),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
} }
} }
@ -362,7 +392,8 @@ where
self, req: &HttpRequest<S>, self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> { ) -> Result<AsyncResult<HttpResponse>, Error> {
let req = req.clone(); let req = req.clone();
let fut = self.map_err(|e| e.into()) let fut = self
.map_err(|e| e.into())
.then(move |r| match r.respond_to(&req) { .then(move |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),
@ -374,9 +405,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
@ -397,10 +435,7 @@ where
S: 'static, S: 'static,
{ {
pub fn new(h: H) -> Self { pub fn new(h: H) -> Self {
WrapHandler { WrapHandler { h, s: PhantomData }
h,
s: PhantomData,
}
} }
} }
@ -410,8 +445,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()),
} }
@ -421,7 +456,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,
@ -433,7 +468,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,
@ -449,23 +484,24 @@ 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()) let req = req.clone();
.map_err(|e| e.into()) let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| {
.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)),
AsyncResultItem::Err(e) => Either::A(err(e)), AsyncResultItem::Err(e) => Either::A(err(e)),
AsyncResultItem::Future(fut) => Either::B(fut), AsyncResultItem::Future(fut) => Either::B(fut),
}, },
Err(e) => Either::A(err(e)), Err(e) => Either::A(err(e)),
}); }
});
AsyncResult::async(Box::new(fut)) AsyncResult::async(Box::new(fut))
} }
} }
@ -481,10 +517,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 {
@ -492,14 +530,15 @@ where
/// } /// }
/// ///
/// /// extract path info using serde /// /// extract path info using serde
/// fn index(state: State<MyApp>, info: Path<Info>) -> String { /// fn index(state: State<MyApp>, path: Path<Info>) -> String {
/// format!("{} {}!", state.msg, info.username) /// format!("{} {}!", state.msg, path.username)
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = App::with_state(MyApp{msg: "Welcome"}).resource( /// let app = App::with_state(MyApp { msg: "Welcome" }).resource(
/// "/{username}/index.html", // <- define path parameters /// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor /// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// } /// }
/// ``` /// ```
pub struct State<S>(HttpRequest<S>); pub struct State<S>(HttpRequest<S>);

File diff suppressed because it is too large Load Diff

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")]
@ -220,8 +223,7 @@ pub fn from_comma_delimited<T: FromStr>(
.filter_map(|x| match x.trim() { .filter_map(|x| match x.trim() {
"" => None, "" => None,
y => Some(y), y => Some(y),
}) }).filter_map(|x| x.trim().parse().ok()),
.filter_map(|x| x.trim().parse().ok()),
) )
} }
Ok(result) Ok(result)
@ -257,3 +259,213 @@ where
} }
Ok(()) Ok(())
} }
// From hyper v0.11.27 src/header/parsing.rs
/// The value part of an extended parameter consisting of three parts:
/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
/// and a character sequence representing the actual value (`value`), separated by single quote
/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
#[derive(Clone, Debug, PartialEq)]
pub struct ExtendedValue {
/// The character set that is used to encode the `value` to a string.
pub charset: Charset,
/// The human language details of the `value`, if available.
pub language_tag: Option<LanguageTag>,
/// The parameter value, as expressed in octets.
pub value: Vec<u8>,
}
/// Parses extended header parameter values (`ext-value`), as defined in
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
///
/// Extended values are denoted by parameter names that end with `*`.
///
/// ## ABNF
///
/// ```text
/// ext-value = charset "'" [ language ] "'" value-chars
/// ; like RFC 2231's <extended-initial-value>
/// ; (see [RFC2231], Section 7)
///
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
///
/// mime-charset = 1*mime-charsetc
/// mime-charsetc = ALPHA / DIGIT
/// / "!" / "#" / "$" / "%" / "&"
/// / "+" / "-" / "^" / "_" / "`"
/// / "{" / "}" / "~"
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
/// ; except that the single quote is not included
/// ; SHOULD be registered in the IANA charset registry
///
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
///
/// value-chars = *( pct-encoded / attr-char )
///
/// pct-encoded = "%" HEXDIG HEXDIG
/// ; see [RFC3986], Section 2.1
///
/// attr-char = ALPHA / DIGIT
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
/// / "^" / "_" / "`" / "|" / "~"
/// ; token except ( "*" / "'" / "%" )
/// ```
pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, ::error::ParseError> {
// Break into three pieces separated by the single-quote character
let mut parts = val.splitn(3, '\'');
// Interpret the first piece as a Charset
let charset: Charset = match parts.next() {
None => return Err(::error::ParseError::Header),
Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?,
};
// Interpret the second piece as a language tag
let language_tag: Option<LanguageTag> = match parts.next() {
None => return Err(::error::ParseError::Header),
Some("") => None,
Some(s) => match s.parse() {
Ok(lt) => Some(lt),
Err(_) => return Err(::error::ParseError::Header),
},
};
// Interpret the third piece as a sequence of value characters
let value: Vec<u8> = match parts.next() {
None => return Err(::error::ParseError::Header),
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
};
Ok(ExtendedValue {
value,
charset,
language_tag,
})
}
impl fmt::Display for ExtendedValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let encoded_value = percent_encoding::percent_encode(
&self.value[..],
self::percent_encoding_http::HTTP_VALUE,
);
if let Some(ref lang) = self.language_tag {
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
} else {
write!(f, "{}''{}", self.charset, encoded_value)
}
}
}
/// Percent encode a sequence of bytes with a character set defined in
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
///
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result {
let encoded =
percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE);
fmt::Display::fmt(&encoded, f)
}
mod percent_encoding_http {
use percent_encoding;
// internal module because macro is hard-coded to make a public item
// but we don't want to public export this item
define_encode_set! {
// This encode set is used for HTTP header values and is defined at
// https://tools.ietf.org/html/rfc5987#section-3.2
pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | {
' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?',
'[', '\\', ']', '{', '}'
}
}
}
#[cfg(test)]
mod tests {
use super::{parse_extended_value, ExtendedValue};
use header::shared::Charset;
use language_tags::LanguageTag;
#[test]
fn test_parse_extended_value_with_encoding_and_language_tag() {
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
assert!(extended_value.language_tag.is_some());
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
assert_eq!(
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_with_encoding() {
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
// and U+20AC (EURO SIGN)
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
assert!(extended_value.language_tag.is_none());
assert_eq!(
vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_missing_language_tag_and_encoding() {
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
let result = parse_extended_value("foo%20bar.html");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted() {
let result = parse_extended_value("UTF-8'missing third part");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted_blank() {
let result = parse_extended_value("blank second part'");
assert!(result.is_err());
}
#[test]
fn test_fmt_extended_value_with_encoding_and_language_tag() {
let extended_value = ExtendedValue {
charset: Charset::Iso_8859_1,
language_tag: Some("en".parse().expect("Could not parse language tag")),
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
};
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
}
#[test]
fn test_fmt_extended_value_with_encoding() {
let extended_value = ExtendedValue {
charset: Charset::Ext("UTF-8".to_string()),
language_tag: None,
value: vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
};
assert_eq!(
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
format!("{}", extended_value)
);
}
}

View File

@ -1,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

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

View File

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

View File

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

View File

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

View File

@ -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,57 +86,41 @@ 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 req.resource().has_prefixed_resource(p.as_ref()) {
if router.has_route(p.as_ref()) { let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref())
.finish();
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if req.resource().has_prefixed_resource(&p) {
let p = if !query.is_empty() { let p = if !query.is_empty() {
p + "?" + query p + "?" + query
} else { } else {
p p
}; };
return HttpResponse::build(self.redirect) return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref()) .header(header::LOCATION, p.as_str())
.finish(); .finish();
} }
// merge slashes and append trailing slash }
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
}
}
// try to remove trailing slash // try to remove trailing slash
if p.ends_with('/') { if p.ends_with('/') {
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(
header::LOCATION,
(p.to_owned() + "?" + query).as_str(),
)
} else {
req.header(header::LOCATION, p)
}.finish();
}
}
} else if p.ends_with('/') {
// try to remove trailing slash
let p = p.as_ref().trim_right_matches('/'); 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(
@ -148,22 +132,36 @@ impl<S> Handler<S> for NormalizePath {
}.finish(); }.finish();
} }
} }
} } else if p.ends_with('/') {
// append trailing slash // try to remove trailing slash
if self.append && !req.path().ends_with('/') { let p = p.as_ref().trim_right_matches('/');
let p = req.path().to_owned() + "/"; if req.resource().has_prefixed_resource(p) {
if router.has_route(&p) { let mut req = HttpResponse::build(self.redirect);
let p = if !query.is_empty() { return if !query.is_empty() {
p + "?" + query req.header(
header::LOCATION,
(p.to_owned() + "?" + query).as_str(),
)
} else { } else {
p req.header(header::LOCATION, p)
}; }.finish();
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
} }
} }
} }
// append trailing slash
if self.append && !req.path().ends_with('/') {
let p = req.path().to_owned() + "/";
if req.resource().has_prefixed_resource(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
}
}
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()))
@ -190,16 +188,8 @@ mod tests {
// trailing slashes // trailing slashes
let params = vec![ let params = vec![
("/resource1", "", StatusCode::OK), ("/resource1", "", StatusCode::OK),
( ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
"/resource1/", ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource2",
"/resource2/",
StatusCode::MOVED_PERMANENTLY,
),
("/resource2/", "", StatusCode::OK), ("/resource2/", "", StatusCode::OK),
("/resource1?p1=1&p2=2", "", StatusCode::OK), ("/resource1?p1=1&p2=2", "", StatusCode::OK),
( (
@ -215,18 +205,64 @@ 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); assert_eq!(r.status(), code);
if !target.is_empty() { if !target.is_empty() {
assert_eq!( assert_eq!(
target, target,
r.headers() r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
.get(header::LOCATION) );
.unwrap() }
.to_str() }
.unwrap() }
#[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);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
); );
} }
} }
@ -234,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| {
@ -243,8 +279,7 @@ mod tests {
true, true,
StatusCode::MOVED_PERMANENTLY, StatusCode::MOVED_PERMANENTLY,
)) ))
}) }).finish();
.finish();
// trailing slashes // trailing slashes
let params = vec![ let params = vec![
@ -258,16 +293,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()))
@ -276,16 +311,8 @@ mod tests {
// trailing slashes // trailing slashes
let params = vec![ let params = vec![
("/resource1/a/b", "", StatusCode::OK), ("/resource1/a/b", "", StatusCode::OK),
( ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
"/resource1/", ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource1//",
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
( (
"//resource1//a//b", "//resource1//a//b",
"/resource1/a/b", "/resource1/a/b",
@ -349,18 +376,14 @@ 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!(
target, target,
r.headers() r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
); );
} }
} }
@ -368,7 +391,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))
@ -533,18 +556,14 @@ 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!(
target, target,
r.headers() r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
); );
} }
} }

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)
} }
@ -55,10 +55,7 @@ impl HttpResponse {
STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG);
STATIC_RESP!( STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
UnsupportedMediaType,
StatusCode::UNSUPPORTED_MEDIA_TYPE
);
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
@ -67,14 +64,8 @@ impl HttpResponse {
STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY);
STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
STATIC_RESP!( STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
VersionNotSupported, STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
StatusCode::HTTP_VERSION_NOT_SUPPORTED
);
STATIC_RESP!(
VariantAlsoNegotiates,
StatusCode::VARIANT_ALSO_NEGOTIATES
);
STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED);
} }

View File

@ -3,24 +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::{ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError}; use error::{
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>
@ -92,19 +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,67 +431,113 @@ 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 len > self.limit {
return Err(PayloadError::Overflow);
}
} else {
return Err(PayloadError::UnknownLength);
}
} else {
return Err(PayloadError::UnknownLength);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
req.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.map(|body| body.freeze()),
));
} }
self.fut if let Some(err) = self.err.take() {
.as_mut() return Err(err);
.expect("UrlEncoded could not be used second time") }
.poll()
if let Some(len) = self.length.take() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
self.stream
.take()
.expect("Can not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
}).map(|body| body.freeze()),
));
self.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,68 +550,57 @@ 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 Ok(len) = s.parse::<u64>() {
if len > 262_144 {
return Err(UrlencodedError::Overflow);
}
} else {
return Err(UrlencodedError::UnknownLength);
}
} else {
return Err(UrlencodedError::UnknownLength);
}
}
// check content type
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Err(UrlencodedError::ContentType);
}
let encoding = req.encoding()
.map_err(|_| UrlencodedError::ContentType)?;
// future
let limit = self.limit;
let fut = req.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(move |body| {
let enc: *const Encoding = encoding as *const Encoding;
if enc == UTF_8 {
serde_urlencoded::from_bytes::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
} else {
let body = encoding
.decode(&body, DecoderTrap::Strict)
.map_err(|_| UrlencodedError::Parse)?;
serde_urlencoded::from_str::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
}
});
self.fut = Some(Box::new(fut));
} }
self.fut if let Some(err) = self.err.take() {
.as_mut() return Err(err);
}
// payload size
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(UrlencodedError::Overflow);
}
}
// future
let encoding = self.encoding;
let fut = self
.stream
.take()
.expect("UrlEncoded could not be used second time") .expect("UrlEncoded could not be used second time")
.poll() .from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
}).and_then(move |body| {
if (encoding as *const Encoding) == UTF_8 {
serde_urlencoded::from_bytes::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
} else {
let body = encoding
.decode(&body, DecoderTrap::Strict)
.map_err(|_| UrlencodedError::Parse)?;
serde_urlencoded::from_str::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
}
});
self.fut = Some(Box::new(fut));
self.poll()
} }
} }
@ -428,10 +610,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 +621,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 +629,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 +651,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();
@ -488,10 +667,7 @@ mod tests {
#[test] #[test]
fn test_encoding_error() { fn test_encoding_error() {
let req = TestRequest::with_header("content-type", "applicatjson").finish(); let req = TestRequest::with_header("content-type", "applicatjson").finish();
assert_eq!( assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
Some(ContentTypeError::ParseError),
req.encoding().err()
);
let req = TestRequest::with_header( let req = TestRequest::with_header(
"content-type", "content-type",
@ -503,47 +679,20 @@ 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()) header::TRANSFER_ENCODING,
}; Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
).finish();
headers.insert(
header::TRANSFER_ENCODING,
header::HeaderValue::from_str(s).unwrap(),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
assert!(req.chunked().is_err()); assert!(req.chunked().is_err());
} }
@ -578,18 +727,11 @@ 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",
).header(header::CONTENT_LENGTH, "xxxx") ).header(header::CONTENT_LENGTH, "xxxx")
.finish(); .finish();
assert_eq!( assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(), req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::UnknownLength UrlencodedError::UnknownLength
@ -599,7 +741,7 @@ mod tests {
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "1000000") ).header(header::CONTENT_LENGTH, "1000000")
.finish(); .finish();
assert_eq!( assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(), req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::Overflow UrlencodedError::Overflow
@ -616,13 +758,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")
.finish(); .set_payload(Bytes::from_static(b"hello=world"))
req.payload_mut() .finish();
.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!(
@ -632,13 +773,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")
.finish(); .set_payload(Bytes::from_static(b"hello=world"))
req.payload_mut() .finish();
.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!(
@ -663,20 +803,52 @@ mod tests {
_ => unreachable!("error"), _ => unreachable!("error"),
} }
let mut req = HttpRequest::default(); let req = TestRequest::default()
req.payload_mut() .set_payload(Bytes::from_static(b"test"))
.unread_data(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,288 +1,180 @@
//! 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 { struct Cookies(Vec<Cookie<'static>>);
const QUERY = 0b0000_0001;
const KEEPALIVE = 0b0000_0010; /// An HTTP Request
pub struct HttpRequest<S = ()> {
req: Option<Request>,
state: Rc<S>,
resource: ResourceInfo,
}
impl<S> HttpMessage for HttpRequest<S> {
type Stream = Payload;
#[inline]
fn headers(&self) -> &HeaderMap {
self.request().headers()
} }
}
pub struct HttpInnerMessage { #[inline]
pub version: Version, fn payload(&self) -> Payload {
pub method: Method, if let Some(payload) = self.request().inner.payload.borrow_mut().take() {
pub(crate) url: InnerUrl, payload
pub(crate) flags: MessageFlags, } else {
pub headers: HeaderMap, Payload::empty()
pub extensions: Extensions,
pub params: Params<'static>,
pub cookies: Option<Vec<Cookie<'static>>>,
pub query: Params<'static>,
pub addr: Option<SocketAddr>,
pub payload: Option<Payload>,
pub info: Option<ConnectionInfo<'static>>,
pub prefix: u16,
resource: RouterResource,
}
#[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(),
query: Params::new(),
addr: None,
cookies: None,
payload: None,
extensions: Extensions::new(),
info: None,
prefix: 0,
resource: RouterResource::Notset,
} }
} }
} }
impl HttpInnerMessage { impl<S> Deref for HttpRequest<S> {
/// Checks if a connection should be kept alive. type Target = Request;
#[inline]
pub fn keep_alive(&self) -> bool {
self.flags.contains(MessageFlags::KEEPALIVE)
}
#[inline] fn deref(&self) -> &Request {
pub(crate) fn reset(&mut self) { self.request()
self.headers.clear();
self.extensions.clear();
self.params.clear();
self.addr = None;
self.info = None;
self.flags = MessageFlags::empty();
self.cookies = None;
self.payload = None;
self.prefix = 0;
self.resource = RouterResource::Notset;
}
}
lazy_static! {
static ref RESOURCE: Resource = Resource::unset();
}
/// An HTTP Request
pub struct HttpRequest<S = ()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>);
impl HttpRequest<()> {
/// Construct a new Request.
#[inline]
pub fn new(
method: Method, uri: Uri, version: Version, headers: HeaderMap,
payload: Option<Payload>,
) -> HttpRequest {
let url = InnerUrl::new(uri);
HttpRequest(
SharedHttpInnerMessage::from_message(HttpInnerMessage {
method,
url,
version,
headers,
payload,
params: Params::new(),
query: Params::new(),
extensions: Extensions::new(),
cookies: None,
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> {
#[inline]
fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
} }
} }
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] /// Construct new http request with empty state.
/// Construct new http request without state.
pub fn drop_state(&self) -> HttpRequest { pub fn drop_state(&self) -> HttpRequest {
HttpRequest(self.0.clone(), None, self.2.clone()) HttpRequest {
} state: Rc::new(()),
req: self.req.as_ref().map(|r| r.clone()),
/// get mutable reference for inner message resource: self.resource.clone(),
/// mutable reference should not be returned as result for request's method }
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub(crate) fn as_mut(&self) -> &mut HttpInnerMessage {
self.0.get_mut()
}
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
fn as_ref(&self) -> &HttpInnerMessage {
self.0.get_ref()
} }
#[inline] #[inline]
pub(crate) fn get_inner(&mut self) -> &mut HttpInnerMessage { /// Construct new http request with new RouteInfo.
self.as_mut() pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest<S> {
resource.merge(&self.resource);
HttpRequest {
resource,
req: self.req.as_ref().map(|r| r.clone()),
state: self.state.clone(),
}
} }
/// 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
@ -312,43 +204,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 `ResourceInfo` 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
@ -360,29 +231,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; if self.extensions().get::<Query>().is_none() {
} let mut query = HashMap::new();
#[doc(hidden)]
#[deprecated(since = "0.6.0", note = "please use `Query<T>` extractor")]
/// Get a reference to the Params object.
/// Params is a container for url query parameters.
pub fn query(&self) -> &Params {
if !self.as_ref().flags.contains(MessageFlags::QUERY) {
self.as_mut().flags.insert(MessageFlags::QUERY);
let params: &mut Params =
unsafe { mem::transmute(&mut self.as_mut().query) };
params.clear();
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());
} }
self.extensions_mut().insert(Query(query));
} }
unsafe { mem::transmute(&self.as_ref().query) } Ref::map(self.extensions(), |ext| &ext.get::<Query>().unwrap().0)
} }
/// The query string in the URL. /// The query string in the URL.
@ -398,35 +259,45 @@ impl<S> HttpRequest<S> {
} }
/// Load request cookies. /// Load request cookies.
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> { #[inline]
if self.as_ref().cookies.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() {
cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
} }
} }
} }
msg.cookies = Some(cookies); self.extensions_mut().insert(Cookies(cookies));
} }
Ok(&self.as_ref().cookies.as_ref().unwrap()) 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());
} }
} }
} }
None None
} }
pub(crate) fn set_cookies(&mut self, cookies: Option<Vec<Cookie<'static>>>) {
if let Some(cookies) = cookies {
self.extensions_mut().insert(Cookies(cookies));
}
}
/// Get a reference to the Params object. /// Get a reference to the Params object.
/// ///
/// Params is a container for url parameters. /// Params is a container for url parameters.
@ -435,68 +306,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(),
}
} }
} }
@ -510,88 +352,34 @@ 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!( 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()); 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); writeln!(f, " params: {:?}", self.match_info())?;
} }
let _ = writeln!(f, " headers:"); writeln!(f, " headers:")?;
for (key, val) in self.as_ref().headers.iter() { for (key, val) in self.headers().iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val); writeln!(f, " {:?}: {:?}", key, val)?;
} }
res Ok(())
} }
} }
#[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]
@ -603,7 +391,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());
} }
@ -642,35 +430,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::<()>::default();
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::<()>::default();
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 = vec![( router.register_resource(resource);
Resource::new("index", "/user/{name}.{ext}"),
Some(resource), let info = router.default_route_info();
)]; assert!(!info.has_prefixed_resource("/use/"));
let (router, _) = Router::new("/", ServerSettings::default(), routes); assert!(info.has_resource("/user/test.html"));
assert!(router.has_route("/user/test.html")); assert!(info.has_prefixed_resource("/user/test.html"));
assert!(!router.has_route("/test/unknown")); 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);
@ -692,20 +472,25 @@ 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 = vec![( let mut router = Router::<()>::default();
Resource::new("index", "/user/{name}.{ext}"), router.set_prefix("/prefix");
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"
@ -713,19 +498,44 @@ 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::<()>::default();
Resource::external("youtube", "https://youtube.com/watch/{video_id}"), router.set_prefix("/prefix");
None, router.register_resource(resource);
)];
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::<()>::default();
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,51 @@ 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 {
@ -232,7 +272,7 @@ impl HttpResponse {
self.get_mut().response_size = size; self.get_mut().response_size = size;
} }
/// Set write buffer capacity /// Get write buffer capacity
pub fn write_buffer_capacity(&self) -> usize { pub fn write_buffer_capacity(&self) -> usize {
self.get_ref().write_capacity self.get_ref().write_capacity
} }
@ -241,6 +281,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 +316,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 +370,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 +530,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() {
@ -466,16 +541,27 @@ impl HttpResponseBuilder {
jar.add(cookie.into_owned()); jar.add(cookie.into_owned());
self.cookies = Some(jar) self.cookies = Some(jar)
} else { } else {
self.cookies self.cookies.as_mut().unwrap().add(cookie.into_owned());
.as_mut()
.unwrap()
.add(cookie.into_owned());
} }
self self
} }
/// 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() {
@ -534,9 +620,7 @@ impl HttpResponseBuilder {
if let Some(e) = self.err.take() { if let Some(e) = self.err.take() {
return Error::from(e).into(); return Error::from(e).into();
} }
let mut response = self.response let mut response = self.response.take().expect("cannot reuse response builder");
.take()
.expect("cannot reuse response builder");
if let Some(ref jar) = self.cookies { if let Some(ref jar) = self.cookies {
for cookie in jar.delta() { for cookie in jar.delta() {
match HeaderValue::from_str(&cookie.to_string()) { match HeaderValue::from_str(&cookie.to_string()) {
@ -546,7 +630,7 @@ impl HttpResponseBuilder {
} }
} }
response.body = body.into(); response.body = body.into();
HttpResponse(Some(response), self.pool.take().unwrap()) HttpResponse(response, self.pool)
} }
#[inline] #[inline]
@ -558,16 +642,21 @@ impl HttpResponseBuilder {
S: Stream<Item = Bytes, Error = E> + 'static, S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error>, E: Into<Error>,
{ {
self.body(Body::Streaming(Box::new( self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
stream.map_err(|e| e.into()),
)))
} }
/// Set a json body and generate `HttpResponse` /// Set a json body and generate `HttpResponse`
/// ///
/// `HttpResponseBuilder` can not be used after this call. /// `HttpResponseBuilder` can not be used after this call.
pub fn json<T: Serialize>(&mut self, value: T) -> HttpResponse { pub fn json<T: Serialize>(&mut self, value: T) -> HttpResponse {
match serde_json::to_string(&value) { self.json2(&value)
}
/// Set a json body and generate `HttpResponse`
///
/// `HttpResponseBuilder` can not be used after this call.
pub fn json2<T: Serialize>(&mut self, value: &T) -> HttpResponse {
match serde_json::to_string(value) {
Ok(body) => { Ok(body) => {
let contains = if let Some(parts) = parts(&mut self.response, &self.err) let contains = if let Some(parts) = parts(&mut self.response, &self.err)
{ {
@ -596,8 +685,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(),
} }
@ -605,7 +694,7 @@ impl HttpResponseBuilder {
} }
#[inline] #[inline]
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] #[cfg_attr(feature = "cargo-clippy", allow(clippy::borrowed_box))]
fn parts<'a>( fn parts<'a>(
parts: &'a mut Option<Box<InnerHttpResponse>>, err: &Option<HttpError>, parts: &'a mut Option<Box<InnerHttpResponse>>, err: &Option<HttpError>,
) -> Option<&'a mut Box<InnerHttpResponse>> { ) -> Option<&'a mut Box<InnerHttpResponse>> {
@ -654,7 +743,8 @@ impl Responder for &'static str {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(self)) .body(self))
} }
@ -673,7 +763,8 @@ impl Responder for &'static [u8] {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self)) .body(self))
} }
@ -692,7 +783,8 @@ impl Responder for String {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(self)) .body(self))
} }
@ -711,7 +803,8 @@ impl<'a> Responder for &'a String {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8") .content_type("text/plain; charset=utf-8")
.body(self)) .body(self))
} }
@ -730,7 +823,8 @@ impl Responder for Bytes {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self)) .body(self))
} }
@ -749,7 +843,8 @@ impl Responder for BytesMut {
type Error = Error; type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream") .content_type("application/octet-stream")
.body(self)) .body(self))
} }
@ -771,13 +866,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()
}
} }
} }
@ -796,6 +887,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 {
@ -813,38 +915,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( let pool = HttpResponsePool(RefCell::new(VecDeque::with_capacity(128)));
VecDeque::with_capacity(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,
} }
@ -853,16 +1002,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)
} }
} }
@ -876,13 +1024,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;
@ -892,7 +1037,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);
} }
} }
} }
@ -903,10 +1048,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()
@ -919,17 +1064,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()
@ -940,23 +1078,46 @@ mod tests {
.http_only(true) .http_only(true)
.max_age(Duration::days(1)) .max_age(Duration::days(1))
.finish(), .finish(),
) ).del_cookie(&cookies[0])
.del_cookie(&cookies[0])
.finish(); .finish();
let mut val: Vec<_> = resp.headers() let mut val: Vec<_> = resp
.headers()
.get_all("Set-Cookie") .get_all("Set-Cookie")
.iter() .iter()
.map(|v| v.to_str().unwrap().to_owned()) .map(|v| v.to_str().unwrap().to_owned())
.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()
@ -975,9 +1136,7 @@ mod tests {
#[test] #[test]
fn test_force_close() { fn test_force_close() {
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
.force_close()
.finish();
assert!(!resp.keep_alive().unwrap()) assert!(!resp.keep_alive().unwrap())
} }
@ -986,10 +1145,7 @@ mod tests {
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK)
.content_type("text/plain") .content_type("text/plain")
.body(Body::Empty); .body(Body::Empty);
assert_eq!( assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
resp.headers().get(CONTENT_TYPE).unwrap(),
"text/plain"
)
} }
#[test] #[test]
@ -1035,18 +1191,42 @@ mod tests {
); );
} }
#[test]
fn test_json2() {
let resp = HttpResponse::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("application/json"));
assert_eq!(
*resp.body(),
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
);
}
#[test]
fn test_json2_ct() {
let resp = HttpResponse::build(StatusCode::OK)
.header(CONTENT_TYPE, "text/json")
.json2(&vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("text/json"));
assert_eq!(
*resp.body(),
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
);
}
impl Body { impl Body {
pub(crate) fn binary(&self) -> Option<&Binary> { pub(crate) fn bin_ref(&self) -> &Binary {
match *self { match *self {
Body::Binary(ref bin) => Some(bin), Body::Binary(ref bin) => bin,
_ => None, _ => panic!(),
} }
} }
} }
#[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);
@ -1055,7 +1235,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test")); assert_eq!(resp.body().bin_ref(), &Binary::from("test"));
let resp: HttpResponse = "test".respond_to(&req).ok().unwrap(); let resp: HttpResponse = "test".respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@ -1064,7 +1244,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test")); assert_eq!(resp.body().bin_ref(), &Binary::from("test"));
let resp: HttpResponse = b"test".as_ref().into(); let resp: HttpResponse = b"test".as_ref().into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@ -1073,10 +1253,7 @@ mod tests {
HeaderValue::from_static("application/octet-stream") HeaderValue::from_static("application/octet-stream")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref()));
resp.body().binary().unwrap(),
&Binary::from(b"test".as_ref())
);
let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap(); let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@ -1085,10 +1262,7 @@ mod tests {
HeaderValue::from_static("application/octet-stream") HeaderValue::from_static("application/octet-stream")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref()));
resp.body().binary().unwrap(),
&Binary::from(b"test".as_ref())
);
let resp: HttpResponse = "test".to_owned().into(); let resp: HttpResponse = "test".to_owned().into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@ -1097,10 +1271,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned()));
resp.body().binary().unwrap(),
&Binary::from("test".to_owned())
);
let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap(); let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@ -1109,10 +1280,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned()));
resp.body().binary().unwrap(),
&Binary::from("test".to_owned())
);
let resp: HttpResponse = (&"test".to_owned()).into(); let resp: HttpResponse = (&"test".to_owned()).into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@ -1121,10 +1289,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned()));
resp.body().binary().unwrap(),
&Binary::from(&"test".to_owned())
);
let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap(); let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@ -1133,10 +1298,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8") HeaderValue::from_static("text/plain; charset=utf-8")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned()));
resp.body().binary().unwrap(),
&Binary::from(&"test".to_owned())
);
let b = Bytes::from_static(b"test"); let b = Bytes::from_static(b"test");
let resp: HttpResponse = b.into(); let resp: HttpResponse = b.into();
@ -1147,7 +1309,7 @@ mod tests {
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.body().binary().unwrap(), resp.body().bin_ref(),
&Binary::from(Bytes::from_static(b"test")) &Binary::from(Bytes::from_static(b"test"))
); );
@ -1160,7 +1322,7 @@ mod tests {
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.body().binary().unwrap(), resp.body().bin_ref(),
&Binary::from(Bytes::from_static(b"test")) &Binary::from(Bytes::from_static(b"test"))
); );
@ -1172,10 +1334,7 @@ mod tests {
HeaderValue::from_static("application/octet-stream") HeaderValue::from_static("application/octet-stream")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test")));
resp.body().binary().unwrap(),
&Binary::from(BytesMut::from("test"))
);
let b = BytesMut::from("test"); let b = BytesMut::from("test");
let resp: HttpResponse = b.respond_to(&req).ok().unwrap(); let resp: HttpResponse = b.respond_to(&req).ok().unwrap();
@ -1185,19 +1344,22 @@ mod tests {
HeaderValue::from_static("application/octet-stream") HeaderValue::from_static("application/octet-stream")
); );
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test")));
resp.body().binary().unwrap(),
&Binary::from(BytesMut::from("test"))
);
} }
#[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,26 @@
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(
pub fn new<S>(req: &'a HttpRequest<S>) -> ConnectionInfo<'a> { feature = "cargo-clippy",
allow(clippy::cyclomatic_complexity)
)]
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;
@ -53,8 +55,9 @@ impl<'a> ConnectionInfo<'a> {
// scheme // scheme
if scheme.is_none() { if scheme.is_none() {
if let Some(h) = req.headers() if let Some(h) = req
.get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap()) .headers()
.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());
@ -62,20 +65,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() { scheme = Some("https")
if router.server_settings().secure() {
scheme = Some("https")
}
}
} }
} }
} }
// host // host
if host.is_none() { if host.is_none() {
if let Some(h) = req.headers() if let Some(h) = req
.get(HeaderName::from_str(X_FORWARDED_HOST).unwrap()) .headers()
.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());
@ -88,9 +88,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());
}
} }
} }
} }
@ -98,8 +96,9 @@ impl<'a> ConnectionInfo<'a> {
// remote addr // remote addr
if remote.is_none() { if remote.is_none() {
if let Some(h) = req.headers() if let Some(h) = req
.get(HeaderName::from_str(X_FORWARDED_FOR).unwrap()) .headers()
.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());
@ -111,12 +110,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.
@ -128,7 +125,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.
@ -141,7 +138,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.
@ -153,7 +150,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)
@ -166,62 +163,58 @@ 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().insert( .header(header::HOST, "rust-lang.org")
header::HOST, .request();
HeaderValue::from_static("rust-lang.org"),
);
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() {}
/// ``` /// ```
@ -121,7 +123,8 @@ impl<T: Serialize> Responder for Json<T> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
let body = serde_json::to_string(&self.0)?; let body = serde_json::to_string(&self.0)?;
Ok(req.build_response(StatusCode::OK) Ok(req
.build_response(StatusCode::OK)
.content_type("application/json") .content_type("application/json")
.body(body)) .body(body))
} }
@ -137,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),
) )
} }
@ -153,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 {
@ -166,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_config(index, |cfg| {
/// .with(index) /// cfg.0.limit(4096) // <- change json extractor configuration
/// .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> {
@ -193,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
@ -222,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
@ -240,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,
} }
} }
@ -263,55 +295,41 @@ 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() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
} else {
false
};
if !json {
return Err(JsonPayloadError::ContentType);
}
let limit = self.limit;
let fut = req.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut));
} }
self.fut if let Some(err) = self.err.take() {
.as_mut() return Err(err);
}
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(JsonPayloadError::Overflow);
}
}
let fut = self
.stream
.take()
.expect("JsonBody could not be used second time") .expect("JsonBody could not be used second time")
.poll() .from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
}).and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut));
self.poll()
} }
} }
@ -323,7 +341,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 {
@ -351,7 +370,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"
@ -360,47 +379,39 @@ 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!( assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
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!( assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
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"),
); ).header(
req.headers_mut().insert( 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"),
); ).header(
req.headers_mut().insert( header::CONTENT_LENGTH,
header::CONTENT_LENGTH, header::HeaderValue::from_static("16"),
header::HeaderValue::from_static("16"), ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
); .finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!( assert_eq!(
json.poll().ok().unwrap(), json.poll().ok().unwrap(),
@ -412,24 +423,21 @@ 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"),
); ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
req.payload_mut() .finish();
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); assert!(handler.handle(&req).as_err().is_none())
assert!(handler.handle(req).as_err().is_none())
} }
} }

View File

@ -2,21 +2,21 @@
//! 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,15 +59,30 @@
//! * SSL support with OpenSSL or `native-tls` //! * SSL support with OpenSSL or `native-tls`
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) //! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
//! * Built on top of [Actix actor framework](https://github.com/actix/actix) //! * Built on top of [Actix actor framework](https://github.com/actix/actix)
//! * Supported Rust version: 1.24 or later //! * Supported Rust version: 1.26 or later
//!
//! ## Package feature
//!
//! * `tls` - enables ssl support via `native-tls` crate
//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2`
//! * `uds` - enables support for making client requests via Unix Domain Sockets.
//! Unix only. Not necessary for *serving* requests.
//! * `session` - enables session support, includes `ring` crate as
//! dependency
//! * `brotli` - enables `brotli` compression support, requires `c`
//! compiler
//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires
//! `c` compiler
//! * `flate2-rust` - experimental rust based implementation for
//! `gzip`, `deflate` compression.
//!
#![cfg_attr(actix_nightly, feature( #![cfg_attr(actix_nightly, feature(
specialization, // for impl ErrorResponse for std::error::Error specialization, // for impl ErrorResponse for std::error::Error
extern_prelude,
tool_lints,
))] ))]
#![cfg_attr( #![warn(missing_docs)]
feature = "cargo-clippy",
allow(decimal_literal_representation, suspicious_arithmetic_impl)
)]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
@ -87,19 +102,26 @@ 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_current_thread;
extern crate tokio_io; extern crate tokio_io;
extern crate tokio_reactor;
extern crate tokio_tcp;
extern crate tokio_timer;
#[cfg(all(unix, feature = "uds"))]
extern crate tokio_uds;
extern crate url; extern crate url;
#[macro_use] #[macro_use]
extern crate serde; extern crate serde;
@ -110,12 +132,15 @@ 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;
extern crate serde_urlencoded;
#[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;
extern crate actix_net;
#[macro_use] #[macro_use]
extern crate actix; extern crate actix as actix_inner;
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
@ -131,10 +156,20 @@ extern crate openssl;
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
extern crate tokio_openssl; extern crate tokio_openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
#[cfg(feature = "rust-tls")]
extern crate tokio_rustls;
#[cfg(feature = "rust-tls")]
extern crate webpki;
#[cfg(feature = "rust-tls")]
extern crate webpki_roots;
mod application; mod 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;
@ -168,14 +203,29 @@ 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::{AsyncResponder, Either, FromRequest, FutureResponse, Responder, State}; pub use handler::{
AsyncResponder, Either, FromRequest, FutureResponse, Responder, State,
};
pub use httpmessage::HttpMessage; pub use httpmessage::HttpMessage;
pub use httprequest::HttpRequest; pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse; pub use httpresponse::HttpResponse;
pub use json::Json; pub use json::Json;
pub use scope::Scope; pub use scope::Scope;
pub use ws::WsWriter; pub use server::Request;
pub mod actix {
//! Re-exports [actix's](https://docs.rs/actix/) prelude
extern crate actix;
pub use self::actix::actors::resolver;
pub use self::actix::actors::signal;
pub use self::actix::fut;
pub use self::actix::msgs;
pub use self::actix::prelude::*;
pub use self::actix::{run, spawn};
}
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
pub(crate) const HAS_OPENSSL: bool = true; pub(crate) const HAS_OPENSSL: bool = true;
@ -187,6 +237,11 @@ pub(crate) const HAS_TLS: bool = true;
#[cfg(not(feature = "tls"))] #[cfg(not(feature = "tls"))]
pub(crate) const HAS_TLS: bool = false; pub(crate) const HAS_TLS: bool = false;
#[cfg(feature = "rust-tls")]
pub(crate) const HAS_RUSTLS: bool = true;
#[cfg(not(feature = "rust-tls"))]
pub(crate) const HAS_RUSTLS: bool = false;
pub mod dev { pub mod dev {
//! The `actix-web` prelude for library developers //! The `actix-web` prelude for library developers
//! //!
@ -202,14 +257,16 @@ pub mod dev {
pub use context::Drain; pub use context::Drain;
pub use extractor::{FormConfig, PayloadConfig}; pub use extractor::{FormConfig, PayloadConfig};
pub use handler::{AsyncResult, Handler}; pub use handler::{AsyncResult, Handler};
pub use httpmessage::{MessageBody, UrlEncoded}; pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
pub use httpresponse::HttpResponseBuilder; pub use httpresponse::HttpResponseBuilder;
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 pipeline::Pipeline;
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 mod http { pub mod http {
@ -222,12 +279,15 @@ 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::{
Charset, ContentDisposition, DispositionParam, DispositionType, 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,18 +236,20 @@ impl Cors {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors; /// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new().configure(
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder /// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/") /// .allowed_origin("https://www.rust-lang.org/")
/// .resource("/resource", |r| { // register resource /// .resource("/resource", |r| { // register resource
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); /// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// }) /// })
/// .register() // construct CORS and return application instance /// .register()
/// ); /// }, // construct CORS and return application instance
/// );
/// } /// }
/// ``` /// ```
pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> { pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> {
@ -272,16 +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 resource
.method(Method::OPTIONS) .method(Method::OPTIONS)
.h(|_| HttpResponse::Ok()); .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 {
@ -301,15 +307,12 @@ impl Cors {
} }
} }
fn validate_allowed_method<S>( fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> {
&self, req: &mut HttpRequest<S>, if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
) -> Result<(), CorsError> {
if let Some(hdr) = req.headers()
.get(header::ACCESS_CONTROL_REQUEST_METHOD)
{
if let Ok(meth) = hdr.to_str() { if let Ok(meth) = hdr.to_str() {
if let Ok(method) = Method::try_from(meth) { if let Ok(method) = Method::try_from(meth) {
return self.inner return self
.inner
.methods .methods
.get(&method) .get(&method)
.and_then(|_| Some(())) .and_then(|_| Some(()))
@ -322,14 +325,12 @@ 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) => {
if let Some(hdr) = req.headers() if let Some(hdr) =
.get(header::ACCESS_CONTROL_REQUEST_HEADERS) req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
{ {
if let Ok(headers) = hdr.to_str() { if let Ok(headers) = hdr.to_str() {
let mut hdrs = HashSet::new(); let mut hdrs = HashSet::new();
@ -355,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() {
@ -371,8 +372,8 @@ impl<S> Middleware<S> for Cors {
.as_str()[1..], .as_str()[1..],
).unwrap(), ).unwrap(),
) )
} else if let Some(hdr) = req.headers() } else if let Some(hdr) =
.get(header::ACCESS_CONTROL_REQUEST_HEADERS) req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
{ {
Some(hdr.clone()) Some(hdr.clone())
} else { } else {
@ -386,12 +387,10 @@ impl<S> Middleware<S> for Cors {
header::ACCESS_CONTROL_MAX_AGE, header::ACCESS_CONTROL_MAX_AGE,
format!("{}", max_age).as_str(), format!("{}", max_age).as_str(),
); );
}) }).if_some(headers, |headers, resp| {
.if_some(headers, |headers, resp| {
let _ = let _ =
resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
}) }).if_true(self.inner.origins.is_all(), |resp| {
.if_true(self.inner.origins.is_all(), |resp| {
if self.inner.send_wildcard { if self.inner.send_wildcard {
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
} else { } else {
@ -401,35 +400,35 @@ impl<S> Middleware<S> for Cors {
origin.clone(), origin.clone(),
); );
} }
}) }).if_true(self.inner.origins.is_some(), |resp| {
.if_true(self.inner.origins.is_some(), |resp| {
resp.header( resp.header(
header::ACCESS_CONTROL_ALLOW_ORIGIN, header::ACCESS_CONTROL_ALLOW_ORIGIN,
self.inner.origins_str.as_ref().unwrap().clone(), self.inner.origins_str.as_ref().unwrap().clone(),
); );
}) }).if_true(self.inner.supports_credentials, |resp| {
.if_true(self.inner.supports_credentials, |resp| {
resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
}) }).header(
.header(
header::ACCESS_CONTROL_ALLOW_METHODS, header::ACCESS_CONTROL_ALLOW_METHODS,
&self.inner &self
.inner
.methods .methods
.iter() .iter()
.fold(String::new(), |s, v| s + "," + v.as_str()) .fold(String::new(), |s, v| s + "," + v.as_str())
.as_str()[1..], .as_str()[1..],
) ).finish(),
.finish(),
)) ))
} else { } else {
self.validate_origin(req)?; // Only check requests with a origin header.
if req.headers().contains_key(header::ORIGIN) {
self.validate_origin(req)?;
}
Ok(Started::Done) Ok(Started::Done)
} }
} }
fn response( fn response(
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse, &self, req: &HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> { ) -> Result<Response> {
match self.inner.origins { match self.inner.origins {
AllOrSome::All => { AllOrSome::All => {
@ -493,8 +492,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()
@ -511,7 +510,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>>,
} }
@ -766,12 +765,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)
@ -783,19 +783,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
} }
@ -825,15 +826,15 @@ impl<S: 'static> CorsBuilder<S> {
if let AllOrSome::Some(ref origins) = cors.origins { if let AllOrSome::Some(ref origins) = cors.origins {
let s = origins let s = origins
.iter() .iter()
.fold(String::new(), |s, v| s + &v.to_string()); .fold(String::new(), |s, v| format!("{}, {}", s, v));
cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap()); cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap());
} }
if !self.expose_hdrs.is_empty() { if !self.expose_hdrs.is_empty() {
cors.expose_hdrs = Some( cors.expose_hdrs = Some(
self.expose_hdrs self.expose_hdrs
.iter() .iter()
.fold(String::new(), |s, v| s + v.as_str())[1..] .fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..]
.to_owned(), .to_owned(),
); );
} }
@ -866,14 +867,15 @@ impl<S: 'static> CorsBuilder<S> {
} }
let cors = self.construct(); let cors = self.construct();
let mut app = self.app let mut app = self
.app
.take() .take()
.expect("CorsBuilder has to be constructed with Cors::for_app(app)"); .expect("CorsBuilder has to be constructed with Cors::for_app(app)");
// 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
@ -937,10 +939,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]
@ -953,29 +954,28 @@ 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,
"AUTHORIZATION,ACCEPT", "AUTHORIZATION,ACCEPT",
) ).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()
@ -999,19 +999,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")]
@ -1020,10 +1019,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]
@ -1032,30 +1031,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()
@ -1067,21 +1066,23 @@ mod tests {
#[test] #[test]
fn test_response() { fn test_response() {
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
let cors = Cors::build() let cors = Cors::build()
.send_wildcard() .send_wildcard()
.disable_preflight() .disable_preflight()
.max_age(3600) .max_age(3600)
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone())
.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()
@ -1094,10 +1095,25 @@ mod tests {
resp.headers().get(header::VARY).unwrap().as_bytes() resp.headers().get(header::VARY).unwrap().as_bytes()
); );
let resp: HttpResponse = HttpResponse::Ok() {
.header(header::VARY, "Accept") let headers = resp
.finish(); .headers()
let resp = cors.response(&mut req, resp).unwrap().response(); .get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
.unwrap()
.to_str()
.unwrap()
.split(',')
.map(|s| s.trim())
.collect::<Vec<&str>>();
for h in exposed_headers {
assert!(headers.contains(&h.as_str()));
}
}
let resp: HttpResponse =
HttpResponse::Ok().header(header::VARY, "Accept").finish();
let resp = cors.response(&req, resp).unwrap().response();
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,16 +1122,29 @@ mod tests {
let cors = Cors::build() let cors = Cors::build()
.disable_vary_header() .disable_vary_header()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.allowed_origin("https://www.google.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!(
&b"https://www.example.com"[..], let origins_str = resp
resp.headers() .headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap() .unwrap()
.as_bytes() .to_str()
); .unwrap();
if origins_str.starts_with("https://www.example.com") {
assert_eq!(
"https://www.example.com, https://www.google.com",
origins_str
);
} else {
assert_eq!(
"https://www.google.com, https://www.example.com",
origins_str
);
}
} }
#[test] #[test]
@ -1129,11 +1158,21 @@ mod tests {
}) })
}); });
let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let request = srv
.get()
.uri(srv.url("/test"))
.header("ORIGIN", "https://www.example2.com")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv.get() let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv
.get()
.uri(srv.url("/test")) .uri(srv.url("/test"))
.header("ORIGIN", "https://www.example.com") .header("ORIGIN", "https://www.example.com")
.finish() .finish()

View File

@ -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)]
@ -93,8 +93,7 @@ fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
.to_str() .to_str()
.map_err(|_| CsrfError::BadOrigin) .map_err(|_| CsrfError::BadOrigin)
.map(|o| o.into()) .map(|o| o.into())
}) }).or_else(|| {
.or_else(|| {
headers.get(header::REFERER).map(|referer| { headers.get(header::REFERER).map(|referer| {
Uri::try_from(Bytes::from(referer.as_bytes())) Uri::try_from(Bytes::from(referer.as_bytes()))
.ok() .ok()
@ -120,13 +119,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 +174,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 +186,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 +208,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 +224,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 +263,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();
/// } /// }
@ -49,7 +48,7 @@ impl DefaultHeaders {
/// Set a header. /// Set a header.
#[inline] #[inline]
#[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))] #[cfg_attr(feature = "cargo-clippy", allow(clippy::match_wild_err_arm))]
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
HeaderName: HttpTryFrom<K>, HeaderName: HttpTryFrom<K>,
@ -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,24 +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() let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish();
.header(CONTENT_TYPE, "0002") let resp = match mw.response(&req, resp) {
.finish();
let resp = match mw.response(&mut 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,23 +18,25 @@ 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()))
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// 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
@ -80,7 +80,7 @@ use middleware::{Middleware, Response, Started};
/// } /// }
/// ///
/// fn logout(mut req: HttpRequest) -> HttpResponse { /// fn logout(mut req: HttpRequest) -> HttpResponse {
/// req.forget(); // <- remove identity /// req.forget(); // <- remove identity
/// HttpResponse::Ok().finish() /// HttpResponse::Ok().finish()
/// } /// }
/// # fn main() {} /// # fn main() {}
@ -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,32 +177,22 @@ 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 Ok(id) => {
.from_request(&mut req) req.extensions_mut().insert(IdentityBox(Box::new(id)));
.then(move |res| match res { FutOk(None)
Ok(id) => { }
req.extensions_mut().insert(IdentityBox(Box::new(id))); Err(err) => FutErr(err),
FutOk(None) });
}
Err(err) => FutErr(err),
});
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))
} }
@ -288,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());
@ -317,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>);
@ -378,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

@ -1,9 +1,8 @@
//! Request logging middleware //! Request logging middleware
use std::collections::HashSet;
use std::env; use std::env;
use std::fmt; use std::fmt::{self, Display, Formatter};
use std::fmt::{Display, Formatter};
use libc;
use 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
@ -74,6 +71,7 @@ use middleware::{Finished, Middleware, Started};
/// ///
pub struct Logger { pub struct Logger {
format: Format, format: Format,
exclude: HashSet<String>,
} }
impl Logger { impl Logger {
@ -81,19 +79,27 @@ impl Logger {
pub fn new(format: &str) -> Logger { pub fn new(format: &str) -> Logger {
Logger { Logger {
format: Format::new(format), format: Format::new(format),
exclude: HashSet::new(),
} }
} }
/// Ignore and do not log access info for specified path.
pub fn exclude<T: Into<String>>(mut self, path: T) -> Self {
self.exclude.insert(path.into());
self
}
} }
impl Default for Logger { impl Default for Logger {
/// 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 {
format: Format::default(), format: Format::default(),
exclude: HashSet::new(),
} }
} }
} }
@ -101,26 +107,28 @@ 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) {
let entry_time = req.extensions().get::<StartTime>().unwrap().0; if let Some(entry_time) = req.extensions().get::<StartTime>() {
let render = |fmt: &mut Formatter| {
let render = |fmt: &mut Formatter| { for unit in &self.format.0 {
for unit in &self.format.0 { unit.render(fmt, req, resp, entry_time.0)?;
unit.render(fmt, req, resp, entry_time)?; }
} Ok(())
Ok(()) };
}; info!("{}", FormatDisplay(&render));
info!("{}", FormatDisplay(&render)); }
} }
} }
impl<S> Middleware<S> for Logger { impl<S> Middleware<S> for Logger {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> { fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
req.extensions_mut().insert(StartTime(time::now())); if !self.exclude.contains(req.path()) {
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
} }
@ -135,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"#)
} }
} }
@ -170,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,
@ -194,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,
@ -236,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;
@ -303,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!(),
} }
@ -353,21 +350,11 @@ 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( let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let resp = HttpResponse::build(StatusCode::OK)
.force_close()
.finish();
let entry_time = time::now(); let entry_time = time::now();
let render = |fmt: &mut Formatter| { let render = |fmt: &mut Formatter| {
@ -381,16 +368,8 @@ 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, let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
Uri::from_str("/?test").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
let resp = HttpResponse::build(StatusCode::OK)
.force_close()
.finish();
let entry_time = time::now(); let entry_time = time::now();
let render = |fmt: &mut Formatter| { let render = |fmt: &mut Formatter| {

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,17 +49,17 @@
//! } //! }
//! //!
//! 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
//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend //! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
//! .secure(false) //! .secure(false)
//! ))) //! )))
//! .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};
@ -80,6 +79,7 @@ use serde_json::error::Error as JsonError;
use time::Duration; use time::Duration;
use error::{Error, ResponseError, Result}; use error::{Error, ResponseError, Result};
use handler::FromRequest;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started}; use middleware::{Middleware, Response, Started};
@ -87,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)?;
/// } /// }
@ -103,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;
} }
@ -122,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)?;
/// } /// }
@ -190,27 +191,49 @@ impl Session {
} }
} }
struct SessionImplCell(RefCell<Box<SessionImpl>>); /// Extractor implementation for Session type.
///
/// ```rust
/// # use actix_web::*;
/// use actix_web::middleware::session::Session;
///
/// fn index(session: Session) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = session.get::<i32>("counter")? {
/// session.set("counter", count + 1)?;
/// } else {
/// session.set("counter", 1)?;
/// }
///
/// Ok("Welcome!")
/// }
/// # fn main() {}
/// ```
impl<S> FromRequest<S> for Session {
type Config = ();
type Result = Session;
#[doc(hidden)] #[inline]
unsafe impl Send for SessionImplCell {} fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
#[doc(hidden)] req.session()
unsafe impl Sync for SessionImplCell {} }
}
struct SessionImplCell(RefCell<Box<SessionImpl>>);
/// 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>);
@ -223,27 +246,22 @@ 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 let fut = self.0.from_request(&mut req).then(move |res| match res {
.from_request(&mut req) Ok(sess) => {
.then(move |res| match res { req.extensions_mut()
Ok(sess) => { .insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess)))));
req.extensions_mut().insert(Arc::new(SessionImplCell( FutOk(None)
RefCell::new(Box::new(sess)), }
))); Err(err) => FutErr(err),
FutOk(None) });
}
Err(err) => FutErr(err),
});
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))
@ -252,14 +270,17 @@ impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
} }
/// A simple key-value storage interface that is internally used by `Session`. /// A simple key-value storage interface that is internally used by `Session`.
#[doc(hidden)]
pub trait SessionImpl: 'static { pub trait SessionImpl: 'static {
/// Get session value by key
fn get(&self, key: &str) -> Option<&str>; fn get(&self, key: &str) -> Option<&str>;
/// Set session value
fn set(&mut self, key: &str, value: String); fn set(&mut self, key: &str, value: String);
/// Remove specific key from session
fn remove(&mut self, key: &str); fn remove(&mut self, key: &str);
/// Remove all values from session
fn clear(&mut self); fn clear(&mut self);
/// Write session to storage backend. /// Write session to storage backend.
@ -267,9 +288,10 @@ pub trait SessionImpl: 'static {
} }
/// Session's storage backend trait definition. /// Session's storage backend trait definition.
#[doc(hidden)]
pub trait SessionBackend<S>: Sized + 'static { pub trait SessionBackend<S>: Sized + 'static {
/// Session item
type Session: SessionImpl; type Session: SessionImpl;
/// Future that reads session
type ReadFuture: Future<Item = Self::Session, Error = Error>; type ReadFuture: Future<Item = Self::Session, Error = Error>;
/// Parse the session from request and load data from a storage backend. /// Parse the session from request and load data from a storage backend.
@ -340,7 +362,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 {
@ -352,7 +376,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,
} }
} }
@ -368,7 +394,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());
@ -378,6 +404,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 {
@ -386,7 +416,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);
} }
@ -395,7 +425,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());
@ -440,6 +470,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
/// ///
@ -505,6 +538,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);
@ -538,8 +583,7 @@ mod tests {
App::new() App::new()
.middleware(SessionStorage::new( .middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false), CookieSessionBackend::signed(&[0; 32]).secure(false),
)) )).resource("/", |r| {
.resource("/", |r| {
r.f(|req| { r.f(|req| {
let _ = req.session().set("counter", 100); let _ = req.session().set("counter", 100);
"test" "test"
@ -551,4 +595,23 @@ mod tests {
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.cookie("actix-session").is_some()); assert!(response.cookie("actix-session").is_some());
} }
#[test]
fn cookie_session_extractor() {
let mut srv = test::TestServer::with_factory(|| {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
)).resource("/", |r| {
r.with(|ses: Session| {
let _ = ses.set("counter", 100);
"test"
})
})
});
let request = srv.get().uri(srv.url("/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.cookie("actix-session").is_some());
}
} }

View File

@ -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,
}))), }))),
@ -122,11 +122,7 @@ where
if let Some(err) = self.error.take() { if let Some(err) = self.error.take() {
Err(err) Err(err)
} else if self.safety.current() { } else if self.safety.current() {
self.inner self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety)
.as_mut()
.unwrap()
.borrow_mut()
.poll(&self.safety)
} else { } else {
Ok(Async::NotReady) Ok(Async::NotReady)
} }
@ -137,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),
@ -168,18 +164,20 @@ 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()? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete), Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => { Async::Ready(Some(chunk)) => {
if chunk.len() == boundary.len() + 4 && &chunk[..2] == b"--" if chunk.len() == boundary.len() + 4
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes() && &chunk[2..boundary.len() + 2] == boundary.as_bytes()
{ {
Ok(Async::Ready(false)) Ok(Async::Ready(false))
} else if chunk.len() == boundary.len() + 6 && &chunk[..2] == b"--" } else if chunk.len() == boundary.len() + 6
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes() && &chunk[2..boundary.len() + 2] == boundary.as_bytes()
&& &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
{ {
@ -192,7 +190,7 @@ where
} }
fn skip_until_boundary( fn skip_until_boundary(
payload: &mut PayloadHelper<S>, boundary: &str, payload: &mut PayloadBuffer<S>, boundary: &str,
) -> Poll<bool, MultipartError> { ) -> Poll<bool, MultipartError> {
let mut eof = false; let mut eof = false;
loop { loop {
@ -401,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>
@ -428,13 +441,13 @@ where
impl<S> fmt::Debug for Field<S> { impl<S> fmt::Debug for Field<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = writeln!(f, "\nMultipartField: {}", self.ct); writeln!(f, "\nMultipartField: {}", self.ct)?;
let _ = writeln!(f, " boundary: {}", self.inner.borrow().boundary); writeln!(f, " boundary: {}", self.inner.borrow().boundary)?;
let _ = writeln!(f, " headers:"); writeln!(f, " headers:")?;
for (key, val) in self.headers.iter() { for (key, val) in self.headers.iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val); writeln!(f, " {:?}: {:?}", key, val)?;
} }
res Ok(())
} }
} }
@ -477,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))
@ -490,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)))
} }
@ -502,32 +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" && &chunk[2..4] == b"--" if &chunk[..2] == b"\r\n"
&& &chunk[2..4] == b"--"
&& &chunk[4..] == boundary.as_bytes() && &chunk[4..] == boundary.as_bytes()
{ {
payload.unread_data(chunk); payload.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)))
} }
} }
@ -575,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
@ -664,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() {
@ -711,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\
@ -734,6 +753,15 @@ 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::FormData);
assert_eq!(
cd.parameters[0],
DispositionParam::Name("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);
@ -782,7 +810,6 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
} }

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,63 +20,92 @@ 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>>,
{ pub(crate) fn add(&mut self, name: Rc<String>, value: ParamItem) {
let name = name.into(); self.segments.push((name, value));
let value = value.into(); }
for item in &mut self.0 {
if item.0 == name { pub(crate) fn add_static(&mut self, name: &str, value: &'static str) {
item.1 = value; self.segments
return; .push((Rc::new(name.to_string()), ParamItem::Static(value)));
}
}
self.0.push((name, 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)])
}
};
} }
} }
None if key == "tail" {
Some(&self.url.path()[(self.tail as usize)..])
} else {
None
}
}
/// Get unprocessed part of path
pub fn unprocessed(&self) -> &str {
&self.url.path()[(self.tail as usize)..]
} }
/// Get matched `FromParam` compatible parameter by name. /// Get matched `FromParam` compatible parameter by name.
@ -87,12 +117,12 @@ impl<'a> Params<'a> {
/// # extern crate actix_web; /// # extern crate actix_web;
/// # use actix_web::*; /// # use actix_web::*;
/// fn index(req: HttpRequest) -> Result<String> { /// fn index(req: HttpRequest) -> Result<String> {
/// let ivalue: isize = req.match_info().query("val")?; /// let ivalue: isize = req.match_info().query("val")?;
/// Ok(format!("isuze value: {:?}", ivalue)) /// Ok(format!("isuze value: {:?}", ivalue))
/// } /// }
/// # 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)],
}
} }
} }
@ -174,7 +236,6 @@ macro_rules! FROM_STR {
($type:ty) => { ($type:ty) => {
impl FromParam for $type { impl FromParam for $type {
type Err = InternalError<<$type as FromStr>::Err>; type Err = InternalError<<$type as FromStr>::Err>;
fn from_param(val: &str) -> Result<Self, Self::Err> { fn from_param(val: &str) -> Result<Self, Self::Err> {
<$type as FromStr>::from_str(val) <$type as FromStr>::from_str(val)
.map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST)) .map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST))

View File

@ -1,6 +1,8 @@
//! Payload stream //! Payload stream
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::task::{current as current_task, Task}; #[cfg(not(test))]
use futures::task::current as current_task;
use futures::task::Task;
use futures::{Async, Poll, Stream}; use futures::{Async, Poll, Stream};
use std::cell::RefCell; use std::cell::RefCell;
use std::cmp; use std::cmp;
@ -59,20 +61,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 +221,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 +282,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 +320,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 +335,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 +349,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 +365,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 +400,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 +419,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 +441,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,24 +497,25 @@ 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()
.fold(BytesMut::new(), |mut b, c| { .fold(BytesMut::new(), |mut b, c| {
b.extend_from_slice(c); b.extend_from_slice(c);
b b
}) }).freeze()
.freeze()
} }
} }
@ -524,7 +525,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,28 +543,27 @@ 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());
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[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"));
@ -578,17 +578,16 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[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());
@ -596,17 +595,16 @@ mod tests {
payload.readany().err().unwrap(); payload.readany().err().unwrap();
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[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"));
@ -625,17 +623,16 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[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());
@ -659,22 +656,18 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[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!( assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap());
Async::NotReady,
payload.read_until(b"ne").ok().unwrap()
);
sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2")); sender.feed_data(Bytes::from("line2"));
@ -696,15 +689,14 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[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"));
@ -718,7 +710,6 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
} }

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,
@ -47,64 +42,31 @@ enum PipelineState<S, H> {
} }
impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> { impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> {
fn is_response(&self) -> bool { fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
match *self { match *self {
PipelineState::Response(_) => true, PipelineState::Starting(ref mut state) => state.poll(info, mws),
_ => false, PipelineState::Handler(ref mut state) => state.poll(info, mws),
} PipelineState::RunMiddlewares(ref mut state) => state.poll(info, mws),
} PipelineState::Finishing(ref mut state) => state.poll(info, mws),
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
match *self {
PipelineState::Starting(ref mut state) => state.poll(info),
PipelineState::Handler(ref mut state) => state.poll(info),
PipelineState::RunMiddlewares(ref mut state) => state.poll(info),
PipelineState::Finishing(ref mut state) => state.poll(info),
PipelineState::Completed(ref mut state) => state.poll(info), PipelineState::Completed(ref mut state) => state.poll(info),
PipelineState::Response(_) | PipelineState::None | PipelineState::Error => { PipelineState::Response(ref mut state) => state.poll(info, mws),
None PipelineState::None | PipelineState::Error => None,
}
} }
} }
} }
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() {
@ -119,35 +81,25 @@ 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(crate) 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()),
))
} }
} }
impl<S: 'static, H> Pipeline<S, H> { impl<S: 'static, H> Pipeline<S, H> {
#[inline]
fn is_done(&self) -> bool { fn is_done(&self) -> bool {
match self.1 { match self.1 {
PipelineState::None PipelineState::None
@ -167,57 +119,61 @@ 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 let PipelineState::Response(st) = state {
let state = mem::replace(&mut self.1, PipelineState::None); match st.poll_io(io, &mut self.0, &self.2) {
if let PipelineState::Response(st) = state { Ok(state) => {
match st.poll_io(io, info) { self.1 = state;
Ok(state) => { if let Some(error) = self.0.error.take() {
self.1 = state; return Err(error);
if let Some(error) = self.0.error.take() { } else {
return Err(error); return Ok(Async::Ready(self.is_done()));
} else {
return Ok(Async::Ready(self.is_done()));
}
}
Err(state) => {
self.1 = state;
return Ok(Async::NotReady);
} }
} }
Err(state) => {
self.1 = state;
return Ok(Async::NotReady);
}
} }
} }
match self.1 { match state {
PipelineState::None => return Ok(Async::Ready(true)), PipelineState::None => return Ok(Async::Ready(true)),
PipelineState::Error => { PipelineState::Error => {
return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into()) return Err(
io::Error::new(io::ErrorKind::Other, "Internal error").into()
)
} }
_ => (), _ => (),
} }
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);
} }
} }
@ -228,76 +184,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));
} }
if info.count == len { loop {
let reply = unsafe { &mut *self.hnd.get() } if info.count == len {
.handle(info.req().clone(), self.htype); let reply = self.hnd.handle(&info.req);
return Some(WaitingResponse::init(info, reply)); return Some(WaitingResponse::init(info, mws, reply));
} else { } else {
loop { let res = mws[info.count as usize].start(&info.req);
match info.mws[info.count as usize].start(info.req_mut()) { 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()));
}
} }
} }
} }
@ -313,11 +281,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,
@ -326,11 +295,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())),
} }
} }
} }
@ -344,15 +315,19 @@ struct RunMiddlewares<S, H> {
} }
impl<S: 'static, H> RunMiddlewares<S, H> { impl<S: 'static, H> RunMiddlewares<S, H> {
fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H> { #[inline]
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());
@ -371,14 +346,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
@ -395,7 +372,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;
@ -413,7 +391,7 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
} }
struct ProcessResponse<S, H> { struct ProcessResponse<S, H> {
resp: HttpResponse, resp: Option<HttpResponse>,
iostate: IOState, iostate: IOState,
running: RunningState, running: RunningState,
drain: Option<oneshot::Sender<()>>, drain: Option<oneshot::Sender<()>>,
@ -421,7 +399,7 @@ struct ProcessResponse<S, H> {
_h: PhantomData<H>, _h: PhantomData<H>,
} }
#[derive(PartialEq)] #[derive(PartialEq, Debug)]
enum RunningState { enum RunningState {
Running, Running,
Paused, Paused,
@ -454,7 +432,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
#[inline] #[inline]
fn init(resp: HttpResponse) -> PipelineState<S, H> { fn init(resp: HttpResponse) -> PipelineState<S, H> {
PipelineState::Response(ProcessResponse { PipelineState::Response(ProcessResponse {
resp, resp: Some(resp),
iostate: IOState::Response, iostate: IOState::Response,
running: RunningState::Running, running: RunningState::Running,
drain: None, drain: None,
@ -463,8 +441,82 @@ impl<S: 'static, H> ProcessResponse<S, H> {
}) })
} }
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
// connection is dead at this point
match mem::replace(&mut self.iostate, IOState::Done) {
IOState::Response => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
IOState::Payload(_) => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
IOState::Actor(mut ctx) => {
if info.disconnected.take().is_some() {
ctx.disconnected();
}
loop {
match ctx.poll() {
Ok(Async::Ready(Some(vec))) => {
if vec.is_empty() {
continue;
}
for frame in vec {
match frame {
Frame::Chunk(None) => {
info.context = Some(ctx);
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
Frame::Chunk(Some(_)) => (),
Frame::Drain(fut) => {
let _ = fut.send(());
}
}
}
}
Ok(Async::Ready(None)) => {
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
))
}
Ok(Async::NotReady) => {
self.iostate = IOState::Actor(ctx);
return None;
}
Err(err) => {
info.context = Some(ctx);
info.error = Some(err);
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
}
}
}
IOState::Done => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
}
}
fn poll_io( 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 {
@ -472,28 +524,35 @@ impl<S: 'static, H> ProcessResponse<S, H> {
'inner: loop { 'inner: loop {
let result = match mem::replace(&mut self.iostate, IOState::Done) { let result = match mem::replace(&mut self.iostate, IOState::Done) {
IOState::Response => { IOState::Response => {
let encoding = let encoding = self
self.resp.content_encoding().unwrap_or(info.encoding); .resp
.as_ref()
.unwrap()
.content_encoding()
.unwrap_or(info.encoding);
let result = match io.start( let result = match io.start(
info.req_mut().get_inner(), &info.req,
&mut self.resp, self.resp.as_mut().unwrap(),
encoding, 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.take().unwrap(),
)); ));
} }
}; };
if let Some(err) = self.resp.error() { if let Some(err) = self.resp.as_ref().unwrap().error() {
if self.resp.status().is_server_error() { if self.resp.as_ref().unwrap().status().is_server_error()
{
error!( error!(
"Error occured during request handling: {}", "Error occured during request handling, status: {} {}",
err self.resp.as_ref().unwrap().status(), err
); );
} else { } else {
warn!( warn!(
@ -507,7 +566,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
} }
// always poll stream or actor for the first time // always poll stream or actor for the first time
match self.resp.replace_body(Body::Empty) { match self.resp.as_mut().unwrap().replace_body(Body::Empty) {
Body::Streaming(stream) => { Body::Streaming(stream) => {
self.iostate = IOState::Payload(stream); self.iostate = IOState::Payload(stream);
continue 'inner; continue 'inner;
@ -526,18 +585,22 @@ 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.take().unwrap(),
)); ));
} }
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.take().unwrap(),
)); ));
} }
Ok(result) => result, Ok(result) => result,
@ -549,7 +612,11 @@ 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.take().unwrap(),
));
} }
}, },
IOState::Actor(mut ctx) => { IOState::Actor(mut ctx) => {
@ -571,25 +638,30 @@ 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.take().unwrap(),
), ),
); );
} }
break 'inner; break 'inner;
} }
Frame::Chunk(Some(chunk)) => { Frame::Chunk(Some(chunk)) => match io
match io.write(chunk) { .write(&chunk)
Err(err) => { {
info.error = Some(err.into()); Err(err) => {
return Ok( info.context = Some(ctx);
FinishingMiddlewares::init( info.error = Some(err.into());
info, self.resp, return Ok(
), FinishingMiddlewares::init(
); info,
} mws,
Ok(result) => res = Some(result), self.resp.take().unwrap(),
),
);
} }
} Ok(result) => res = Some(result),
},
Frame::Drain(fut) => self.drain = Some(fut), Frame::Drain(fut) => self.drain = Some(fut),
} }
} }
@ -606,9 +678,12 @@ 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.take().unwrap(),
)); ));
} }
} }
@ -641,8 +716,18 @@ 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.take().unwrap(),
));
} }
} }
} }
@ -656,11 +741,19 @@ 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.take().unwrap(),
));
} }
} }
self.resp.set_response_size(io.written()); self.resp.as_mut().unwrap().set_response_size(io.written());
Ok(FinishingMiddlewares::init(info, self.resp)) Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
))
} }
_ => Err(PipelineState::Response(self)), _ => Err(PipelineState::Response(self)),
} }
@ -669,24 +762,28 @@ 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>,
} }
impl<S: 'static, H> FinishingMiddlewares<S, H> { impl<S: 'static, H> FinishingMiddlewares<S, H> {
fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> { #[inline]
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)
@ -694,7 +791,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 {
@ -714,13 +813,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));
} }
} }
@ -745,7 +848,13 @@ impl<S, H> Completed<S, H> {
if info.context.is_none() { if info.context.is_none() {
PipelineState::None PipelineState::None
} else { } else {
PipelineState::Completed(Completed(PhantomData, PhantomData)) match info.poll_context() {
Ok(Async::NotReady) => {
PipelineState::Completed(Completed(PhantomData, PhantomData))
}
Ok(Async::Ready(())) => PipelineState::None,
Err(_) => PipelineState::Error,
}
} }
} }
@ -758,68 +867,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
} }
} }
@ -181,14 +185,10 @@ pub fn Header<S: 'static>(
} }
#[doc(hidden)] #[doc(hidden)]
pub struct HeaderPredicate<S>( pub struct HeaderPredicate<S>(header::HeaderName, header::HeaderValue, PhantomData<S>);
header::HeaderName,
header::HeaderValue,
PhantomData<S>,
);
impl<S: 'static> Predicate<S> for HeaderPredicate<S> { impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool { fn check(&self, req: &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;
} }
@ -196,148 +196,133 @@ 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,15 +1,22 @@
use std::marker::PhantomData; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use http::{Method, StatusCode}; use futures::Future;
use http::Method;
use smallvec::SmallVec; use smallvec::SmallVec;
use error::Error;
use handler::{AsyncResult, FromRequest, Handler, Responder}; use handler::{AsyncResult, FromRequest, Handler, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::Middleware; use middleware::Middleware;
use pred; use pred;
use route::Route; use route::Route;
use router::ResourceDef;
use with::WithFactory;
#[derive(Copy, Clone)]
pub(crate) struct RouteId(usize);
/// *Resource* is an entry in route table which corresponds to requested URL. /// *Resource* is an entry in route table which corresponds to requested URL.
/// ///
@ -31,45 +38,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.
@ -80,11 +81,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();
/// } /// }
/// ``` /// ```
@ -125,25 +127,44 @@ 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());
self.routes self.routes.last_mut().unwrap().filter(pred::Method(method))
.last_mut()
.unwrap()
.filter(pred::Method(method))
} }
/// Register a new route and add handler object. /// Register a new route and add handler object.
/// ///
/// ```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());
@ -152,14 +173,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());
@ -168,14 +200,25 @@ 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
F: Fn(T) -> R + 'static, F: WithFactory<T, S, R>,
R: Responder + 'static, R: Responder + 'static,
T: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
{ {
@ -183,6 +226,45 @@ impl<S: 'static> ResourceHandler<S> {
self.routes.last_mut().unwrap().with(handler); self.routes.last_mut().unwrap().with(handler);
} }
/// Register a new route and add async handler.
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// use actix_web::*;
/// use futures::future::Future;
///
/// fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// unimplemented!()
/// }
///
/// App::new().resource("/", |r| r.with_async(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # use actix_web::*;
/// # use futures::future::Future;
/// # fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// # unimplemented!()
/// # }
/// App::new().resource("/", |r| r.route().with_async(index));
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.routes.push(Route::default());
self.routes.last_mut().unwrap().with_async(handler);
}
/// Register a resource middleware /// Register a resource middleware
/// ///
/// This is similar to `App's` middlewares, but /// This is similar to `App's` middlewares, but
@ -196,22 +278,47 @@ impl<S: 'static> ResourceHandler<S> {
.push(Box::new(mw)); .push(Box::new(mw));
} }
pub(crate) fn handle( #[inline]
&mut self, mut req: HttpRequest<S>, default: Option<&mut ResourceHandler<S>>, pub(crate) fn get_route_id(&self, req: &HttpRequest<S>) -> Option<RouteId> {
) -> AsyncResult<HttpResponse> { for idx in 0..self.routes.len() {
for route in &mut self.routes { if (&self.routes[idx]).check(req) {
if route.check(&mut req) { return Some(RouteId(idx));
return if self.middlewares.is_empty() {
route.handle(req)
} else {
route.compose(req, Rc::clone(&self.middlewares))
};
} }
} }
if let Some(resource) = default { None
resource.handle(req, None) }
#[inline]
pub(crate) fn handle(
&self, id: RouteId, req: &HttpRequest<S>,
) -> AsyncResult<HttpResponse> {
if self.middlewares.is_empty() {
(&self.routes[id.0]).handle(req)
} else { } else {
AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) (&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares))
} }
} }
} }
/// 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,19 +1,22 @@
use std::cell::UnsafeCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use error::Error; use error::Error;
use handler::{AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, use handler::{
Responder, RouteHandler, WrapHandler}; AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder,
RouteHandler, WrapHandler,
};
use http::StatusCode; use http::StatusCode;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::{Finished as MiddlewareFinished, Middleware, use middleware::{
Response as MiddlewareResponse, Started as MiddlewareStarted}; Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse,
Started as MiddlewareStarted,
};
use pred::Predicate; use pred::Predicate;
use with::{ExtractorConfig, With, With2, With3}; use with::{WithAsyncFactory, WithFactory};
/// Resource route definition /// Resource route definition
/// ///
@ -28,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;
} }
} }
@ -45,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())))
} }
@ -62,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();
/// # } /// # }
/// ``` /// ```
@ -87,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);
@ -96,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,
@ -111,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 {
@ -125,128 +128,197 @@ 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
/// } /// }
/// ``` /// ```
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T> ///
where /// It is possible to use multiple extractors for one handler function.
F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
let cfg = ExtractorConfig::default();
self.h(With::new(handler, Clone::clone(&cfg)));
cfg
}
/// Set handler function, use request extractor for both parameters.
/// ///
/// ```rust /// ```rust
/// # extern crate bytes; /// # extern crate bytes;
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Query, Path, Result, http}; /// # use std::collections::HashMap;
/// use actix_web::{http, App, Json, Path, Query, Result};
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct PParam { /// struct Info {
/// username: String, /// username: String,
/// } /// }
/// ///
/// #[derive(Deserialize)] /// /// extract path info using serde
/// struct QParam { /// fn index(
/// count: u32, /// path: Path<Info>, query: Query<HashMap<String, String>>, body: Json<Info>,
/// } /// ) -> Result<String> {
/// /// Ok(format!("Welcome {}!", path.username))
/// /// 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(index),
/// ); // <- use `with` extractor
/// } /// }
/// ``` /// ```
pub fn with2<T1, T2, F, R>( pub fn with<T, F, R>(&mut self, handler: F)
&mut self, handler: F,
) -> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>)
where where
F: Fn(T1, T2) -> R + 'static, F: WithFactory<T, S, R> + 'static,
R: Responder + 'static, R: Responder + 'static,
T1: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
{ {
let cfg1 = ExtractorConfig::default(); self.h(handler.create());
let cfg2 = ExtractorConfig::default();
self.h(With2::new(
handler,
Clone::clone(&cfg1),
Clone::clone(&cfg2),
));
(cfg1, cfg2)
} }
/// Set handler function, use request extractor for all parameters. /// Set handler function. Same as `.with()` but it allows to configure
pub fn with3<T1, T2, T3, F, R>( /// extractor. Configuration closure accepts config objects as tuple.
&mut self, handler: F, ///
) -> ( /// ```rust
ExtractorConfig<S, T1>, /// # extern crate bytes;
ExtractorConfig<S, T2>, /// # extern crate actix_web;
ExtractorConfig<S, T3>, /// # extern crate futures;
) /// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Path, Result};
///
/// /// extract text data from request
/// fn index(body: String) -> Result<String> {
/// Ok(format!("Body {}!", body))
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET)
/// .with_config(index, |cfg| { // <- register handler
/// cfg.0.limit(4096); // <- limit size of the payload
/// })
/// });
/// }
/// ```
pub fn with_config<T, F, R, C>(&mut self, handler: F, cfg_f: C)
where where
F: Fn(T1, T2, T3) -> R + 'static, F: WithFactory<T, S, R>,
R: Responder + 'static, R: Responder + 'static,
T1: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static, C: FnOnce(&mut T::Config),
T3: FromRequest<S> + 'static,
{ {
let cfg1 = ExtractorConfig::default(); let mut cfg = <T::Config as Default>::default();
let cfg2 = ExtractorConfig::default(); cfg_f(&mut cfg);
let cfg3 = ExtractorConfig::default(); self.h(handler.create_with_config(cfg));
self.h(With3::new( }
handler,
Clone::clone(&cfg1), /// Set async handler function, use request extractor for parameters.
Clone::clone(&cfg2), /// Also this method needs to be used if your handler function returns
Clone::clone(&cfg3), /// `impl Future<>`
)); ///
(cfg1, cfg2, cfg3) /// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Error, Path};
/// use futures::Future;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Path<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
/// unimplemented!()
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with_async(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where
F: WithAsyncFactory<T, S, R, I, E>,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.h(handler.create());
}
/// Set async handler function, use request extractor for parameters.
/// This method allows to configure extractor. Configuration closure
/// accepts config objects as tuple.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Error, Form};
/// use futures::Future;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Form<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
/// unimplemented!()
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET)
/// .with_async_config(index, |cfg| {
/// cfg.0.limit(4096);
/// }),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async_config<T, F, R, I, E, C>(&mut self, handler: F, cfg: C)
where
F: WithAsyncFactory<T, S, R, I, E>,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
C: FnOnce(&mut T::Config),
{
let mut extractor_cfg = <T::Config as Default>::default();
cfg(&mut extractor_cfg);
self.h(handler.create_with_config(extractor_cfg));
} }
} }
/// `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( InnerHandler(Rc::new(Box::new(WrapHandler::new(h))))
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( InnerHandler(Rc::new(Box::new(AsyncHandler::new(h))))
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)
} }
} }
@ -336,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()),
} }
} }
} }
@ -360,20 +436,24 @@ 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));
} }
if info.count == len { loop {
let reply = info.handler.handle(info.req.clone()); if info.count == len {
return Some(WaitingResponse::init(info, reply)); let reply = info.handler.handle(&info.req);
} else { return Some(WaitingResponse::init(info, reply));
loop { } 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));
@ -383,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>,
} }
@ -410,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,
@ -422,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())),
} }
} }
@ -441,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());
@ -459,7 +541,7 @@ impl<S: 'static> RunMiddlewares<S> {
curr, curr,
fut: Some(fut), fut: Some(fut),
_s: PhantomData, _s: PhantomData,
}) });
} }
}; };
} }
@ -483,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()))
} }
@ -551,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

File diff suppressed because it is too large Load Diff

383
src/server/acceptor.rs Normal file
View File

@ -0,0 +1,383 @@
use std::time::Duration;
use std::{fmt, net};
use actix_net::server::ServerMessage;
use actix_net::service::{NewService, Service};
use futures::future::{err, ok, Either, FutureResult};
use futures::{Async, Future, Poll};
use tokio_reactor::Handle;
use tokio_tcp::TcpStream;
use tokio_timer::{sleep, Delay};
use super::error::AcceptorError;
use super::IoStream;
/// This trait indicates types that can create acceptor service for http server.
pub trait AcceptorServiceFactory: Send + Clone + 'static {
type Io: IoStream + Send;
type NewService: NewService<Request = TcpStream, Response = Self::Io>;
fn create(&self) -> Self::NewService;
}
impl<F, T> AcceptorServiceFactory for F
where
F: Fn() -> T + Send + Clone + 'static,
T::Response: IoStream + Send,
T: NewService<Request = TcpStream>,
T::InitError: fmt::Debug,
{
type Io = T::Response;
type NewService = T;
fn create(&self) -> T {
(self)()
}
}
#[derive(Clone)]
/// Default acceptor service convert `TcpStream` to a `tokio_tcp::TcpStream`
pub(crate) struct DefaultAcceptor;
impl AcceptorServiceFactory for DefaultAcceptor {
type Io = TcpStream;
type NewService = DefaultAcceptor;
fn create(&self) -> Self::NewService {
DefaultAcceptor
}
}
impl NewService for DefaultAcceptor {
type Request = TcpStream;
type Response = TcpStream;
type Error = ();
type InitError = ();
type Service = DefaultAcceptor;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self) -> Self::Future {
ok(DefaultAcceptor)
}
}
impl Service for DefaultAcceptor {
type Request = TcpStream;
type Response = TcpStream;
type Error = ();
type Future = FutureResult<Self::Response, Self::Error>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: Self::Request) -> Self::Future {
ok(req)
}
}
pub(crate) struct TcpAcceptor<T> {
inner: T,
}
impl<T, E> TcpAcceptor<T>
where
T: NewService<Request = TcpStream, Error = AcceptorError<E>>,
T::InitError: fmt::Debug,
{
pub(crate) fn new(inner: T) -> Self {
TcpAcceptor { inner }
}
}
impl<T, E> NewService for TcpAcceptor<T>
where
T: NewService<Request = TcpStream, Error = AcceptorError<E>>,
T::InitError: fmt::Debug,
{
type Request = net::TcpStream;
type Response = T::Response;
type Error = AcceptorError<E>;
type InitError = T::InitError;
type Service = TcpAcceptorService<T::Service>;
type Future = TcpAcceptorResponse<T>;
fn new_service(&self) -> Self::Future {
TcpAcceptorResponse {
fut: self.inner.new_service(),
}
}
}
pub(crate) struct TcpAcceptorResponse<T>
where
T: NewService<Request = TcpStream>,
T::InitError: fmt::Debug,
{
fut: T::Future,
}
impl<T> Future for TcpAcceptorResponse<T>
where
T: NewService<Request = TcpStream>,
T::InitError: fmt::Debug,
{
type Item = TcpAcceptorService<T::Service>;
type Error = T::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(service)) => {
Ok(Async::Ready(TcpAcceptorService { inner: service }))
}
Err(e) => {
error!("Can not create accetor service: {:?}", e);
Err(e)
}
}
}
}
pub(crate) struct TcpAcceptorService<T> {
inner: T,
}
impl<T, E> Service for TcpAcceptorService<T>
where
T: Service<Request = TcpStream, Error = AcceptorError<E>>,
{
type Request = net::TcpStream;
type Response = T::Response;
type Error = AcceptorError<E>;
type Future = Either<T::Future, FutureResult<Self::Response, Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.inner.poll_ready()
}
fn call(&mut self, req: Self::Request) -> Self::Future {
let stream = TcpStream::from_std(req, &Handle::default()).map_err(|e| {
error!("Can not convert to an async tcp stream: {}", e);
AcceptorError::Io(e)
});
match stream {
Ok(stream) => Either::A(self.inner.call(stream)),
Err(e) => Either::B(err(e)),
}
}
}
#[doc(hidden)]
/// Acceptor timeout middleware
///
/// Applies timeout to request prcoessing.
pub struct AcceptorTimeout<T> {
inner: T,
timeout: Duration,
}
impl<T: NewService> AcceptorTimeout<T> {
/// Create new `AcceptorTimeout` instance. timeout is in milliseconds.
pub fn new(timeout: u64, inner: T) -> Self {
Self {
inner,
timeout: Duration::from_millis(timeout),
}
}
}
impl<T: NewService> NewService for AcceptorTimeout<T> {
type Request = T::Request;
type Response = T::Response;
type Error = AcceptorError<T::Error>;
type InitError = T::InitError;
type Service = AcceptorTimeoutService<T::Service>;
type Future = AcceptorTimeoutFut<T>;
fn new_service(&self) -> Self::Future {
AcceptorTimeoutFut {
fut: self.inner.new_service(),
timeout: self.timeout,
}
}
}
#[doc(hidden)]
pub struct AcceptorTimeoutFut<T: NewService> {
fut: T::Future,
timeout: Duration,
}
impl<T: NewService> Future for AcceptorTimeoutFut<T> {
type Item = AcceptorTimeoutService<T::Service>;
type Error = T::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let inner = try_ready!(self.fut.poll());
Ok(Async::Ready(AcceptorTimeoutService {
inner,
timeout: self.timeout,
}))
}
}
#[doc(hidden)]
/// Acceptor timeout service
///
/// Applies timeout to request prcoessing.
pub struct AcceptorTimeoutService<T> {
inner: T,
timeout: Duration,
}
impl<T: Service> Service for AcceptorTimeoutService<T> {
type Request = T::Request;
type Response = T::Response;
type Error = AcceptorError<T::Error>;
type Future = AcceptorTimeoutResponse<T>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.inner.poll_ready().map_err(AcceptorError::Service)
}
fn call(&mut self, req: Self::Request) -> Self::Future {
AcceptorTimeoutResponse {
fut: self.inner.call(req),
sleep: sleep(self.timeout),
}
}
}
#[doc(hidden)]
pub struct AcceptorTimeoutResponse<T: Service> {
fut: T::Future,
sleep: Delay,
}
impl<T: Service> Future for AcceptorTimeoutResponse<T> {
type Item = T::Response;
type Error = AcceptorError<T::Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll().map_err(AcceptorError::Service)? {
Async::NotReady => match self.sleep.poll() {
Err(_) => Err(AcceptorError::Timeout),
Ok(Async::Ready(_)) => Err(AcceptorError::Timeout),
Ok(Async::NotReady) => Ok(Async::NotReady),
},
Async::Ready(resp) => Ok(Async::Ready(resp)),
}
}
}
pub(crate) struct ServerMessageAcceptor<T> {
inner: T,
}
impl<T> ServerMessageAcceptor<T>
where
T: NewService<Request = net::TcpStream>,
{
pub(crate) fn new(inner: T) -> Self {
ServerMessageAcceptor { inner }
}
}
impl<T> NewService for ServerMessageAcceptor<T>
where
T: NewService<Request = net::TcpStream>,
{
type Request = ServerMessage;
type Response = ();
type Error = T::Error;
type InitError = T::InitError;
type Service = ServerMessageAcceptorService<T::Service>;
type Future = ServerMessageAcceptorResponse<T>;
fn new_service(&self) -> Self::Future {
ServerMessageAcceptorResponse {
fut: self.inner.new_service(),
}
}
}
pub(crate) struct ServerMessageAcceptorResponse<T>
where
T: NewService<Request = net::TcpStream>,
{
fut: T::Future,
}
impl<T> Future for ServerMessageAcceptorResponse<T>
where
T: NewService<Request = net::TcpStream>,
{
type Item = ServerMessageAcceptorService<T::Service>;
type Error = T::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll()? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(service) => Ok(Async::Ready(ServerMessageAcceptorService {
inner: service,
})),
}
}
}
pub(crate) struct ServerMessageAcceptorService<T> {
inner: T,
}
impl<T> Service for ServerMessageAcceptorService<T>
where
T: Service<Request = net::TcpStream>,
{
type Request = ServerMessage;
type Response = ();
type Error = T::Error;
type Future =
Either<ServerMessageAcceptorServiceFut<T>, FutureResult<(), Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.inner.poll_ready()
}
fn call(&mut self, req: Self::Request) -> Self::Future {
match req {
ServerMessage::Connect(stream) => {
Either::A(ServerMessageAcceptorServiceFut {
fut: self.inner.call(stream),
})
}
ServerMessage::Shutdown(_) => Either::B(ok(())),
ServerMessage::ForceShutdown => {
// self.settings
// .head()
// .traverse(|proto: &mut HttpProtocol<TcpStream, H>| proto.shutdown());
Either::B(ok(()))
}
}
}
}
pub(crate) struct ServerMessageAcceptorServiceFut<T: Service> {
fut: T::Future,
}
impl<T> Future for ServerMessageAcceptorServiceFut<T>
where
T: Service,
{
type Item = ();
type Error = T::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll()? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(_) => Ok(Async::Ready(())),
}
}
}

115
src/server/builder.rs Normal file
View File

@ -0,0 +1,115 @@
use std::{fmt, net};
use actix_net::either::Either;
use actix_net::server::{Server, ServiceFactory};
use actix_net::service::{NewService, NewServiceExt};
use super::acceptor::{
AcceptorServiceFactory, AcceptorTimeout, ServerMessageAcceptor, TcpAcceptor,
};
use super::error::AcceptorError;
use super::handler::IntoHttpHandler;
use super::service::HttpService;
use super::settings::{ServerSettings, ServiceConfig};
use super::KeepAlive;
pub(crate) trait ServiceProvider {
fn register(
&self, server: Server, lst: net::TcpListener, host: String,
addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64,
client_shutdown: u64,
) -> Server;
}
/// Utility type that builds complete http pipeline
pub(crate) struct HttpServiceBuilder<F, H, A>
where
F: Fn() -> H + Send + Clone,
{
factory: F,
acceptor: A,
}
impl<F, H, A> HttpServiceBuilder<F, H, A>
where
F: Fn() -> H + Send + Clone + 'static,
H: IntoHttpHandler,
A: AcceptorServiceFactory,
<A::NewService as NewService>::InitError: fmt::Debug,
{
/// Create http service builder
pub fn new(factory: F, acceptor: A) -> Self {
Self { factory, acceptor }
}
fn finish(
&self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool,
client_timeout: u64, client_shutdown: u64,
) -> impl ServiceFactory {
let factory = self.factory.clone();
let acceptor = self.acceptor.clone();
move || {
let app = (factory)().into_handler();
let settings = ServiceConfig::new(
app,
keep_alive,
client_timeout,
client_shutdown,
ServerSettings::new(addr, &host, false),
);
if secure {
Either::B(ServerMessageAcceptor::new(
TcpAcceptor::new(AcceptorTimeout::new(
client_timeout,
acceptor.create(),
)).map_err(|_| ())
.map_init_err(|_| ())
.and_then(
HttpService::new(settings)
.map_init_err(|_| ())
.map_err(|_| ()),
),
))
} else {
Either::A(ServerMessageAcceptor::new(
TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service))
.map_err(|_| ())
.map_init_err(|_| ())
.and_then(
HttpService::new(settings)
.map_init_err(|_| ())
.map_err(|_| ()),
),
))
}
}
}
}
impl<F, H, A> ServiceProvider for HttpServiceBuilder<F, H, A>
where
F: Fn() -> H + Send + Clone + 'static,
A: AcceptorServiceFactory,
<A::NewService as NewService>::InitError: fmt::Debug,
H: IntoHttpHandler,
{
fn register(
&self, server: Server, lst: net::TcpListener, host: String,
addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64,
client_shutdown: u64,
) -> Server {
server.listen2(
"actix-web",
lst,
self.finish(
host,
addr,
keep_alive,
secure,
client_timeout,
client_shutdown,
),
)
}
}

View File

@ -1,22 +1,43 @@
use std::net::{Shutdown, SocketAddr}; use std::net::Shutdown;
use std::rc::Rc; use std::{io, mem, time};
use std::{io, ptr, time};
use bytes::{Buf, BufMut, Bytes, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::Delay;
use super::settings::WorkerSettings; use super::error::HttpDispatchError;
use super::{h1, h2, utils, HttpHandler, IoStream}; use super::settings::ServiceConfig;
use super::{h1, h2, HttpHandler, IoStream};
use http::StatusCode;
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> { pub(crate) enum HttpProtocol<T: IoStream, H: HttpHandler + 'static> {
H1(h1::Http1<T, H>), H1(h1::Http1Dispatcher<T, H>),
H2(h2::Http2<T, H>), H2(h2::Http2<T, H>),
Unknown(Rc<WorkerSettings<H>>, Option<SocketAddr>, T, BytesMut), Unknown(ServiceConfig<H>, T, BytesMut),
None,
} }
// impl<T: IoStream, H: HttpHandler + 'static> HttpProtocol<T, H> {
// fn shutdown_(&mut self) {
// match self {
// HttpProtocol::H1(ref mut h1) => {
// let io = h1.io();
// let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0)));
// let _ = IoStream::shutdown(io, Shutdown::Both);
// }
// HttpProtocol::H2(ref mut h2) => h2.shutdown(),
// HttpProtocol::Unknown(_, io, _) => {
// let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0)));
// let _ = IoStream::shutdown(io, Shutdown::Both);
// }
// HttpProtocol::None => (),
// }
// }
// }
enum ProtocolKind { enum ProtocolKind {
Http1, Http1,
Http2, Http2,
@ -28,8 +49,8 @@ where
T: IoStream, T: IoStream,
H: HttpHandler + 'static, H: HttpHandler + 'static,
{ {
proto: Option<HttpProtocol<T, H>>, proto: HttpProtocol<T, H>,
node: Option<Node<HttpChannel<T, H>>>, ka_timeout: Option<Delay>,
} }
impl<T, H> HttpChannel<T, H> impl<T, H> HttpChannel<T, H>
@ -37,45 +58,12 @@ where
T: IoStream, T: IoStream,
H: HttpHandler + 'static, H: HttpHandler + 'static,
{ {
pub(crate) fn new( pub(crate) fn new(settings: ServiceConfig<H>, io: T) -> HttpChannel<T, H> {
settings: Rc<WorkerSettings<H>>, mut io: T, peer: Option<SocketAddr>, let ka_timeout = settings.client_timer();
http2: bool,
) -> HttpChannel<T, H> {
settings.add_channel();
let _ = io.set_nodelay(true);
if http2 { HttpChannel {
HttpChannel { ka_timeout,
node: None, proto: HttpProtocol::Unknown(settings, io, BytesMut::with_capacity(8192)),
proto: Some(HttpProtocol::H2(h2::Http2::new(
settings,
io,
peer,
Bytes::new(),
))),
}
} else {
HttpChannel {
node: None,
proto: Some(HttpProtocol::Unknown(
settings,
peer,
io,
BytesMut::with_capacity(4096),
)),
}
}
}
fn shutdown(&mut self) {
match self.proto {
Some(HttpProtocol::H1(ref mut h1)) => {
let io = h1.io();
let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0)));
let _ = IoStream::shutdown(io, Shutdown::Both);
}
Some(HttpProtocol::H2(ref mut h2)) => h2.shutdown(),
_ => (),
} }
} }
} }
@ -86,69 +74,57 @@ where
H: HttpHandler + 'static, H: HttpHandler + 'static,
{ {
type Item = (); type Item = ();
type Error = (); type Error = HttpDispatchError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if self.node.is_some() { // keep-alive timer
let el = self as *mut _; if self.ka_timeout.is_some() {
self.node = Some(Node::new(el)); match self.ka_timeout.as_mut().unwrap().poll() {
let _ = match self.proto { Ok(Async::Ready(_)) => {
Some(HttpProtocol::H1(ref mut h1)) => self.node trace!("Slow request timed out, close connection");
.as_ref() let proto = mem::replace(&mut self.proto, HttpProtocol::None);
.map(|n| h1.settings().head().insert(n)), if let HttpProtocol::Unknown(settings, io, buf) = proto {
Some(HttpProtocol::H2(ref mut h2)) => self.node self.proto = HttpProtocol::H1(h1::Http1Dispatcher::for_error(
.as_ref() settings,
.map(|n| h2.settings().head().insert(n)), io,
Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => { StatusCode::REQUEST_TIMEOUT,
self.node.as_ref().map(|n| settings.head().insert(n)) self.ka_timeout.take(),
buf,
));
return self.poll();
}
return Ok(Async::Ready(()));
} }
None => unreachable!(), Ok(Async::NotReady) => (),
}; Err(_) => panic!("Something is really wrong"),
}
} }
let mut is_eof = false;
let kind = match self.proto { let kind = match self.proto {
Some(HttpProtocol::H1(ref mut h1)) => { HttpProtocol::H1(ref mut h1) => return h1.poll(),
let result = h1.poll(); HttpProtocol::H2(ref mut h2) => return h2.poll(),
match result { HttpProtocol::Unknown(_, ref mut io, ref mut buf) => {
Ok(Async::Ready(())) | Err(_) => { let mut err = None;
h1.settings().remove_channel(); let mut disconnect = false;
if let Some(n) = self.node.as_mut() { match io.read_available(buf) {
n.remove() Ok(Async::Ready((read_some, stream_closed))) => {
}; is_eof = stream_closed;
// Only disconnect if no data was read.
if is_eof && !read_some {
disconnect = true;
}
}
Err(e) => {
err = Some(e.into());
} }
_ => (), _ => (),
} }
return result; if disconnect {
} debug!("Ignored premature client disconnection");
Some(HttpProtocol::H2(ref mut h2)) => { return Ok(Async::Ready(()));
let result = h2.poll(); } else if let Some(e) = err {
match result { return Err(e);
Ok(Async::Ready(())) | Err(_) => {
h2.settings().remove_channel();
if let Some(n) = self.node.as_mut() {
n.remove()
};
}
_ => (),
}
return result;
}
Some(HttpProtocol::Unknown(
ref mut settings,
_,
ref mut io,
ref mut buf,
)) => {
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) | Err(_) => {
debug!("Ignored premature client disconnection");
settings.remove_channel();
if let Some(n) = self.node.as_mut() {
n.remove()
};
return Err(());
}
_ => (),
} }
if buf.len() >= 14 { if buf.len() >= 14 {
@ -161,25 +137,30 @@ where
return Ok(Async::NotReady); return Ok(Async::NotReady);
} }
} }
None => unreachable!(), HttpProtocol::None => unreachable!(),
}; };
// upgrade to specific http protocol // upgrade to specific http protocol
if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() { let proto = mem::replace(&mut self.proto, HttpProtocol::None);
if let HttpProtocol::Unknown(settings, io, buf) = proto {
match kind { match kind {
ProtocolKind::Http1 => { ProtocolKind::Http1 => {
self.proto = Some(HttpProtocol::H1(h1::Http1::new( self.proto = HttpProtocol::H1(h1::Http1Dispatcher::new(
settings, io, addr, buf, settings,
))); io,
buf,
is_eof,
self.ka_timeout.take(),
));
return self.poll(); return self.poll();
} }
ProtocolKind::Http2 => { ProtocolKind::Http2 => {
self.proto = Some(HttpProtocol::H2(h2::Http2::new( self.proto = HttpProtocol::H2(h2::Http2::new(
settings, settings,
io, io,
addr,
buf.freeze(), buf.freeze(),
))); self.ka_timeout.take(),
));
return self.poll(); return self.poll();
} }
} }
@ -188,81 +169,45 @@ where
} }
} }
pub(crate) struct Node<T> { #[doc(hidden)]
next: Option<*mut Node<()>>, pub struct H1Channel<T, H>
prev: Option<*mut Node<()>>, where
element: *mut T, T: IoStream,
H: HttpHandler + 'static,
{
proto: HttpProtocol<T, H>,
} }
impl<T> Node<T> { impl<T, H> H1Channel<T, H>
fn new(el: *mut T) -> Self { where
Node { T: IoStream,
next: None, H: HttpHandler + 'static,
prev: None, {
element: el, pub(crate) fn new(settings: ServiceConfig<H>, io: T) -> H1Channel<T, H> {
} H1Channel {
} proto: HttpProtocol::H1(h1::Http1Dispatcher::new(
settings,
fn insert<I>(&self, next: &Node<I>) { io,
#[allow(mutable_transmutes)] BytesMut::with_capacity(8192),
unsafe { false,
if let Some(ref next2) = self.next { None,
let n: &mut Node<()> = )),
&mut *(next2.as_ref().unwrap() as *const _ as *mut _);
n.prev = Some(next as *const _ as *mut _);
}
let slf: &mut Node<T> = &mut *(self as *const _ as *mut _);
slf.next = Some(next as *const _ as *mut _);
let next: &mut Node<T> = &mut *(next as *const _ as *mut _);
next.prev = Some(slf as *const _ as *mut _);
}
}
fn remove(&mut self) {
unsafe {
self.element = ptr::null_mut();
let next = self.next.take();
let mut prev = self.prev.take();
if let Some(ref mut prev) = prev {
prev.as_mut().unwrap().next = next;
}
} }
} }
} }
impl Node<()> { impl<T, H> Future for H1Channel<T, H>
pub(crate) fn head() -> Self { where
Node { T: IoStream,
next: None, H: HttpHandler + 'static,
prev: None, {
element: ptr::null_mut(), type Item = ();
} type Error = HttpDispatchError;
}
pub(crate) fn traverse<T, H>(&self) fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
where match self.proto {
T: IoStream, HttpProtocol::H1(ref mut h1) => h1.poll(),
H: HttpHandler + 'static, _ => unreachable!(),
{
let mut next = self.next.as_ref();
loop {
if let Some(n) = next {
unsafe {
let n: &Node<()> = &*(n.as_ref().unwrap() as *const _);
next = n.next.as_ref();
if !n.element.is_null() {
let ch: &mut HttpChannel<T, H> =
&mut *(&mut *(n.element as *mut _) as *mut () as *mut _);
ch.shutdown();
}
}
} else {
return;
}
} }
} }
} }
@ -300,6 +245,10 @@ where
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> { fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
Ok(()) Ok(())
} }
#[inline]
fn set_keepalive(&mut self, _: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
} }
impl<T> io::Read for WrapperStream<T> impl<T> io::Read for WrapperStream<T>

View File

@ -1,891 +0,0 @@
use std::fmt::Write as FmtWrite;
use std::io::{Read, Write};
use std::str::FromStr;
use std::{cmp, io, mem};
#[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::{BufMut, Bytes, BytesMut};
#[cfg(feature = "flate2")]
use flate2::read::GzDecoder;
#[cfg(feature = "flate2")]
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
#[cfg(feature = "flate2")]
use flate2::Compression;
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING,
CONTENT_LENGTH, TRANSFER_ENCODING};
use http::{HttpTryFrom, Method, Version};
use body::{Binary, Body};
use error::PayloadError;
use header::ContentEncoding;
use httprequest::HttpInnerMessage;
use httpresponse::HttpResponse;
use payload::{PayloadSender, PayloadStatus, PayloadWriter};
use super::shared::SharedBytes;
pub(crate) enum PayloadType {
Sender(PayloadSender),
Encoding(Box<EncodedPayload>),
}
impl PayloadType {
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))),
}
}
}
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)),
}
}
}
pub(crate) enum ContentEncoder {
#[cfg(feature = "flate2")]
Deflate(DeflateEncoder<TransferEncoding>),
#[cfg(feature = "flate2")]
Gzip(GzEncoder<TransferEncoding>),
#[cfg(feature = "brotli")]
Br(BrotliEncoder<TransferEncoding>),
Identity(TransferEncoding),
}
impl ContentEncoder {
pub fn empty(bytes: SharedBytes) -> ContentEncoder {
ContentEncoder::Identity(TransferEncoding::eof(bytes))
}
pub fn for_server(
buf: SharedBytes, req: &HttpInnerMessage, resp: &mut HttpResponse,
response_encoding: ContentEncoding,
) -> ContentEncoder {
let version = resp.version().unwrap_or_else(|| req.version);
let is_head = req.method == Method::HEAD;
let mut body = resp.replace_body(Body::Empty);
let has_body = match body {
Body::Empty => false,
Body::Binary(ref bin) => {
!(response_encoding == ContentEncoding::Auto && bin.len() < 96)
}
_ => true,
};
// Enable content encoding only if response does not contain Content-Encoding
// header
let mut encoding = if has_body {
let encoding = match response_encoding {
ContentEncoding::Auto => {
// negotiate content-encoding
if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
if let Ok(enc) = val.to_str() {
AcceptEncoding::parse(enc)
} else {
ContentEncoding::Identity
}
} else {
ContentEncoding::Identity
}
}
encoding => encoding,
};
if encoding.is_compression() {
resp.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
}
encoding
} else {
ContentEncoding::Identity
};
let mut transfer = match body {
Body::Empty => {
if req.method != Method::HEAD {
resp.headers_mut().remove(CONTENT_LENGTH);
}
TransferEncoding::length(0, buf)
}
Body::Binary(ref mut bytes) => {
if !(encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto)
{
let tmp = SharedBytes::default();
let transfer = TransferEncoding::eof(tmp.clone());
let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::fast()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
transfer,
Compression::fast(),
)),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
}
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
ContentEncoding::Auto => unreachable!(),
};
// TODO return error!
let _ = enc.write(bytes.clone());
let _ = enc.write_eof();
*bytes = Binary::from(tmp.take());
encoding = ContentEncoding::Identity;
}
if is_head {
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
resp.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(b.freeze()).unwrap(),
);
} else {
// resp.headers_mut().remove(CONTENT_LENGTH);
}
TransferEncoding::eof(buf)
}
Body::Streaming(_) | Body::Actor(_) => {
if resp.upgrade() {
if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2");
}
if encoding != ContentEncoding::Identity {
encoding = ContentEncoding::Identity;
resp.headers_mut().remove(CONTENT_ENCODING);
}
TransferEncoding::eof(buf)
} else {
ContentEncoder::streaming_encoding(buf, version, resp)
}
}
};
//
if is_head {
transfer.kind = TransferEncodingKind::Length(0);
} else {
resp.replace_body(body);
}
match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
transfer,
Compression::fast(),
)),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
}
#[cfg(feature = "brotli")]
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)),
ContentEncoding::Identity | ContentEncoding::Auto => {
ContentEncoder::Identity(transfer)
}
}
}
fn streaming_encoding(
buf: SharedBytes, version: Version, resp: &mut HttpResponse,
) -> TransferEncoding {
match resp.chunked() {
Some(true) => {
// Enable transfer encoding
resp.headers_mut().remove(CONTENT_LENGTH);
if version == Version::HTTP_2 {
resp.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
} else {
resp.headers_mut()
.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
TransferEncoding::chunked(buf)
}
}
Some(false) => TransferEncoding::eof(buf),
None => {
// if Content-Length is specified, then use it as length hint
let (len, chunked) =
if let Some(len) = resp.headers().get(CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
(Some(len), false)
} else {
error!("illegal Content-Length: {:?}", len);
(None, false)
}
} else {
error!("illegal Content-Length: {:?}", len);
(None, false)
}
} else {
(None, true)
};
if !chunked {
if let Some(len) = len {
TransferEncoding::length(len, buf)
} else {
TransferEncoding::eof(buf)
}
} else {
// Enable transfer encoding
match version {
Version::HTTP_11 => {
resp.headers_mut().insert(
TRANSFER_ENCODING,
HeaderValue::from_static("chunked"),
);
TransferEncoding::chunked(buf)
}
_ => {
resp.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
}
}
}
}
}
}
}
impl ContentEncoder {
#[inline]
pub fn is_eof(&self) -> bool {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(),
ContentEncoder::Identity(ref encoder) => encoder.is_eof(),
}
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write_eof(&mut self) -> Result<(), io::Error> {
let encoder = mem::replace(
self,
ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())),
);
match encoder {
#[cfg(feature = "brotli")]
ContentEncoder::Br(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(())
}
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(())
}
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(())
}
Err(err) => Err(err),
},
ContentEncoder::Identity(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(())
}
}
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write(&mut self, data: Binary) -> Result<(), io::Error> {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => {
match encoder.write_all(data.as_ref()) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding br encoding: {}", err);
Err(err)
}
}
}
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => {
match encoder.write_all(data.as_ref()) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding gzip encoding: {}", err);
Err(err)
}
}
}
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => {
match encoder.write_all(data.as_ref()) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding deflate encoding: {}", err);
Err(err)
}
}
}
ContentEncoder::Identity(ref mut encoder) => {
encoder.encode(data)?;
Ok(())
}
}
}
}
/// Encoders to handle different Transfer-Encodings.
#[derive(Debug, Clone)]
pub(crate) struct TransferEncoding {
kind: TransferEncodingKind,
buffer: SharedBytes,
}
#[derive(Debug, PartialEq, Clone)]
enum TransferEncodingKind {
/// An Encoder for when Transfer-Encoding includes `chunked`.
Chunked(bool),
/// An Encoder for when Content-Length is set.
///
/// Enforces that the body is not longer than the Content-Length header.
Length(u64),
/// An Encoder for when Content-Length is not known.
///
/// Application decides when to stop writing.
Eof,
}
impl TransferEncoding {
#[inline]
pub fn eof(bytes: SharedBytes) -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Eof,
buffer: bytes,
}
}
#[inline]
pub fn chunked(bytes: SharedBytes) -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Chunked(false),
buffer: bytes,
}
}
#[inline]
pub fn length(len: u64, bytes: SharedBytes) -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Length(len),
buffer: bytes,
}
}
#[inline]
pub fn is_eof(&self) -> bool {
match self.kind {
TransferEncodingKind::Eof => true,
TransferEncodingKind::Chunked(ref eof) => *eof,
TransferEncodingKind::Length(ref remaining) => *remaining == 0,
}
}
/// Encode message. Return `EOF` state of encoder
#[inline]
pub fn encode(&mut self, mut msg: Binary) -> io::Result<bool> {
match self.kind {
TransferEncodingKind::Eof => {
let eof = msg.is_empty();
self.buffer.extend(msg);
Ok(eof)
}
TransferEncodingKind::Chunked(ref mut eof) => {
if *eof {
return Ok(true);
}
if msg.is_empty() {
*eof = true;
self.buffer.extend_from_slice(b"0\r\n\r\n");
} else {
let mut buf = BytesMut::new();
writeln!(&mut buf, "{:X}\r", msg.len())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
self.buffer.reserve(buf.len() + msg.len() + 2);
self.buffer.extend(buf.into());
self.buffer.extend(msg);
self.buffer.extend_from_slice(b"\r\n");
}
Ok(*eof)
}
TransferEncodingKind::Length(ref mut remaining) => {
if *remaining > 0 {
if msg.is_empty() {
return Ok(*remaining == 0);
}
let len = cmp::min(*remaining, msg.len() as u64);
self.buffer
.extend(msg.take().split_to(len as usize).into());
*remaining -= len as u64;
Ok(*remaining == 0)
} else {
Ok(true)
}
}
}
}
/// Encode eof. Return `EOF` state of encoder
#[inline]
pub fn encode_eof(&mut self) {
match self.kind {
TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (),
TransferEncodingKind::Chunked(ref mut eof) => {
if !*eof {
*eof = true;
self.buffer.extend_from_slice(b"0\r\n\r\n");
}
}
}
}
}
impl io::Write for TransferEncoding {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.encode(Binary::from_slice(buf))?;
Ok(buf.len())
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
struct AcceptEncoding {
encoding: ContentEncoding,
quality: f64,
}
impl Eq for AcceptEncoding {}
impl Ord for AcceptEncoding {
fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering {
if self.quality > other.quality {
cmp::Ordering::Less
} else if self.quality < other.quality {
cmp::Ordering::Greater
} else {
cmp::Ordering::Equal
}
}
}
impl PartialOrd for AcceptEncoding {
fn partial_cmp(&self, other: &AcceptEncoding) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for AcceptEncoding {
fn eq(&self, other: &AcceptEncoding) -> bool {
self.quality == other.quality
}
}
impl AcceptEncoding {
fn new(tag: &str) -> Option<AcceptEncoding> {
let parts: Vec<&str> = tag.split(';').collect();
let encoding = match parts.len() {
0 => return None,
_ => ContentEncoding::from(parts[0]),
};
let quality = match parts.len() {
1 => encoding.quality(),
_ => match f64::from_str(parts[1]) {
Ok(q) => q,
Err(_) => 0.0,
},
};
Some(AcceptEncoding {
encoding,
quality,
})
}
/// Parse a raw Accept-Encoding header value into an ordered list.
pub fn parse(raw: &str) -> ContentEncoding {
let mut encodings: Vec<_> = raw.replace(' ', "")
.split(',')
.map(|l| AcceptEncoding::new(l))
.collect();
encodings.sort();
for enc in encodings {
if let Some(enc) = enc {
return enc.encoding;
}
}
ContentEncoding::Identity
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chunked_te() {
let bytes = SharedBytes::default();
let mut enc = TransferEncoding::chunked(bytes.clone());
assert!(!enc.encode(Binary::from(b"test".as_ref()))
.ok()
.unwrap());
assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap());
assert_eq!(
bytes.get_mut().take().freeze(),
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")
);
}
}

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

@ -0,0 +1,108 @@
use std::io;
use futures::{Async, Poll};
use http2;
use super::{helpers, HttpHandlerTask, Writer};
use http::{StatusCode, Version};
use Error;
/// Errors produced by `AcceptorError` service.
#[derive(Debug)]
pub enum AcceptorError<T> {
/// The inner service error
Service(T),
/// Io specific error
Io(io::Error),
/// The request did not complete within the specified timeout.
Timeout,
}
#[derive(Fail, Debug)]
/// A set of errors that can occur during dispatching http requests
pub enum HttpDispatchError {
/// Application error
#[fail(display = "Application specific error: {}", _0)]
App(Error),
/// An `io::Error` that occurred while trying to read or write to a network
/// stream.
#[fail(display = "IO error: {}", _0)]
Io(io::Error),
/// The first request did not complete within the specified timeout.
#[fail(display = "The first request did not complete within the specified timeout")]
SlowRequestTimeout,
/// Shutdown timeout
#[fail(display = "Connection shutdown timeout")]
ShutdownTimeout,
/// HTTP2 error
#[fail(display = "HTTP2 error: {}", _0)]
Http2(http2::Error),
/// Payload is not consumed
#[fail(display = "Task is completed but request's payload is not consumed")]
PayloadIsNotConsumed,
/// Malformed request
#[fail(display = "Malformed request")]
MalformedRequest,
/// Internal error
#[fail(display = "Internal error")]
InternalError,
/// Unknown error
#[fail(display = "Unknown error")]
Unknown,
}
impl From<Error> for HttpDispatchError {
fn from(err: Error) -> Self {
HttpDispatchError::App(err)
}
}
impl From<io::Error> for HttpDispatchError {
fn from(err: io::Error) -> Self {
HttpDispatchError::Io(err)
}
}
impl From<http2::Error> for HttpDispatchError {
fn from(err: http2::Error) -> Self {
HttpDispatchError::Http2(err)
}
}
pub(crate) struct ServerError(Version, StatusCode);
impl ServerError {
pub fn err(ver: Version, status: StatusCode) -> Box<HttpHandlerTask> {
Box::new(ServerError(ver, status))
}
}
impl HttpHandlerTask for ServerError {
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
{
let bytes = io.buffer();
// Buffer should have sufficient capacity for status line
// and extra space
bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1);
helpers::write_status_line(self.0, self.1.as_u16(), bytes);
}
// Convert Status Code to Reason.
let reason = self.1.canonical_reason().unwrap_or("");
io.buffer().extend_from_slice(reason.as_bytes());
// No response body.
io.buffer().extend_from_slice(b"\r\ncontent-length: 0\r\n");
// date header
io.set_date();
Ok(Async::Ready(true))
}
}

File diff suppressed because it is too large Load Diff

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::ServiceConfig;
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;
@ -19,11 +18,9 @@ pub(crate) struct H1Decoder {
decoder: Option<EncodingDecoder>, decoder: Option<EncodingDecoder>,
} }
#[derive(Debug)]
pub(crate) enum Message { pub(crate) enum Message {
Message { Message { msg: Request, payload: bool },
msg: SharedHttpInnerMessage,
payload: bool,
},
Chunk(Bytes), Chunk(Bytes),
Eof, Eof,
} }
@ -46,7 +43,7 @@ impl H1Decoder {
} }
pub fn decode<H>( pub fn decode<H>(
&mut self, src: &mut BytesMut, settings: &WorkerSettings<H>, &mut self, src: &mut BytesMut, settings: &ServiceConfig<H>,
) -> Result<Option<Message>, DecoderError> { ) -> Result<Option<Message>, DecoderError> {
// read payload // read payload
if self.decoder.is_some() { if self.decoder.is_some() {
@ -60,22 +57,16 @@ impl H1Decoder {
} }
} }
match self.parse_message(src, settings) match self
.parse_message(src, settings)
.map_err(DecoderError::Error)? .map_err(DecoderError::Error)?
{ {
Async::Ready((msg, decoder)) => { Async::Ready((msg, decoder)) => {
if let Some(decoder) = decoder { self.decoder = decoder;
self.decoder = Some(decoder); Ok(Some(Message::Message {
Ok(Some(Message::Message { msg,
msg, payload: self.decoder.is_some(),
payload: true, }))
}))
} else {
Ok(Some(Message::Message {
msg,
payload: false,
}))
}
} }
Async::NotReady => { Async::NotReady => {
if src.len() >= MAX_BUFFER_SIZE { if src.len() >= MAX_BUFFER_SIZE {
@ -89,25 +80,25 @@ impl H1Decoder {
} }
fn parse_message<H>( fn parse_message<H>(
&self, buf: &mut BytesMut, settings: &WorkerSettings<H>, &self, buf: &mut BytesMut, settings: &ServiceConfig<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)?;
@ -117,6 +108,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),
@ -126,29 +119,29 @@ 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 {
header::CONTENT_LENGTH => { header::CONTENT_LENGTH => {
if let Ok(s) = value.to_str() { if let Ok(s) = value.to_str() {
if let Ok(len) = s.parse::<u64>() { if let Ok(len) = s.parse::<u64>() {
content_length = Some(len) content_length = Some(len);
} else { } else {
debug!("illegal Content-Length: {:?}", len); debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header); return Err(ParseError::Header);
@ -174,27 +167,30 @@ impl H1Decoder {
{ {
true true
} else { } else {
version == Version::HTTP_11 version == Version::HTTP_11 && !(conn
&& !(conn.contains("close") .contains("close")
|| conn.contains("upgrade")) || conn.contains("upgrade"))
} }
} 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
}; };
@ -206,7 +202,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 {
@ -217,6 +213,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,22 @@
#![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 bytes::{BufMut, BytesMut};
use futures::{Async, Poll}; use futures::{Async, Poll};
use std::io;
use std::rc::Rc;
use tokio_io::AsyncWrite; use tokio_io::AsyncWrite;
use super::encoding::ContentEncoder;
use super::helpers; use super::helpers;
use super::settings::WorkerSettings; use super::output::{Output, ResponseInfo, ResponseLength};
use super::shared::SharedBytes; use super::settings::ServiceConfig;
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 +33,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: ServiceConfig<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: ServiceConfig<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 +59,30 @@ 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 flushed(&mut self) -> bool {
self.buffer.is_empty()
} }
pub fn disconnected(&mut self) { pub fn disconnected(&mut self) {
self.buffer.take(); self.flags.insert(Flags::DISCONNECTED);
}
pub fn upgrade(&self) -> bool {
self.flags.contains(Flags::UPGRADE)
} }
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 +98,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,35 +155,50 @@ 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 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len() 256 + msg.headers().len() * AVERAGE_HEADER_SIZE
+ 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 { let mut len_is_set = true;
SharedBytes::put_slice(buffer, b"\r\ncontent-length: 0\r\n"); match info.length {
} else { ResponseLength::Chunked => {
SharedBytes::put_slice(buffer, b"\r\n"); buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n")
},
Body::Binary(ref bytes) => {
helpers::write_content_length(bytes.len(), &mut buffer)
} }
_ => SharedBytes::put_slice(buffer, b"\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::Zero => {
len_is_set = false;
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
@ -171,20 +207,37 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
let mut remaining = buffer.remaining_mut(); let mut remaining = buffer.remaining_mut();
let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) };
for (key, value) in msg.headers() { for (key, value) in msg.headers() {
if is_bin && key == CONTENT_LENGTH { match *key {
is_bin = false; TRANSFER_ENCODING => continue,
continue; CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
continue;
},
CONTENT_LENGTH => match info.length {
ResponseLength::None => (),
ResponseLength::Zero => {
len_is_set = true;
}
_ => 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) }; unsafe {
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 _) }; unsafe {
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);
@ -197,21 +250,26 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
pos += 2; pos += 2;
remaining -= len; remaining -= len;
} }
unsafe { buffer.advance_mut(pos) }; unsafe {
buffer.advance_mut(pos);
}
if !len_is_set {
buffer.extend_from_slice(b"content-length: 0\r\n")
}
// 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();
@ -221,7 +279,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) {
@ -229,21 +287,27 @@ 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) => {
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())?;
} }
} }
@ -255,9 +319,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
} }
fn write_eof(&mut self) -> io::Result<WriterState> { fn write_eof(&mut self) -> io::Result<WriterState> {
self.encoder.write_eof()?; if !self.buffer.write_eof()? {
if !self.encoder.is_eof() {
Err(io::Error::new( Err(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"Last payload item, but eof is not reached", "Last payload item, but eof is not reached",
@ -271,19 +333,32 @@ 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.flags.contains(Flags::DISCONNECTED) {
return Err(io::Error::new(io::ErrorKind::Other, "disconnected"));
}
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) => {
self.disconnected();
return Err(err);
}
Ok(val) => val,
}
};
let _ = self.buffer.split_to(written); let _ = self.buffer.split_to(written);
if self.buffer.len() > self.buffer_capacity { if shutdown && !self.buffer.is_empty()
|| (self.buffer.len() > self.buffer_capacity)
{
return Ok(Async::NotReady); return Ok(Async::NotReady);
} }
} }
if shutdown { if shutdown {
self.stream.poll_flush()?;
self.stream.shutdown() self.stream.shutdown()
} else { } else {
Ok(Async::Ready(())) Ok(self.stream.poll_flush()?)
} }
} }
} }

View File

@ -1,37 +1,34 @@
#![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::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 extensions::Extensions;
use httprequest::HttpRequest; use http::{StatusCode, Version};
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::{HttpDispatchError, ServerError};
use super::h2writer::H2Writer; use super::h2writer::H2Writer;
use super::settings::WorkerSettings; use super::input::PayloadType;
use super::{HttpHandler, HttpHandlerTask, Writer}; use super::settings::ServiceConfig;
use super::{HttpHandler, HttpHandlerTask, IoStream, Writer};
bitflags! { bitflags! {
struct Flags: u8 { struct Flags: u8 {
const DISCONNECTED = 0b0000_0010; const DISCONNECTED = 0b0000_0001;
const SHUTDOWN = 0b0000_0010;
} }
} }
@ -39,14 +36,16 @@ 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: ServiceConfig<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>, extensions: Option<Rc<Extensions>>,
ka_expire: Instant,
ka_timer: Option<Delay>,
} }
enum State<T: AsyncRead + AsyncWrite> { enum State<T: AsyncRead + AsyncWrite> {
@ -57,100 +56,114 @@ enum State<T: AsyncRead + AsyncWrite> {
impl<T, H> Http2<T, H> impl<T, H> Http2<T, H>
where where
T: AsyncRead + AsyncWrite + 'static, T: IoStream + 'static,
H: HttpHandler + 'static, H: HttpHandler + 'static,
{ {
pub fn new( pub fn new(
settings: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes, settings: ServiceConfig<H>, io: T, buf: Bytes, keepalive_timer: Option<Delay>,
) -> Self { ) -> Self {
let addr = io.peer_addr();
let extensions = io.extensions();
// keep-alive timeout
let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer {
(delay.deadline(), Some(delay))
} else if let Some(delay) = settings.keep_alive_timer() {
(delay.deadline(), Some(delay))
} else {
(settings.now(), None)
};
Http2 { Http2 {
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,
addr, addr,
settings, settings,
extensions,
ka_expire,
ka_timer,
} }
} }
pub(crate) fn shutdown(&mut self) { pub fn poll(&mut self) -> Poll<(), HttpDispatchError> {
self.state = State::Empty; self.poll_keepalive()?;
self.tasks.clear();
self.keepalive_timer.take();
}
pub fn settings(&self) -> &WorkerSettings<H> {
self.settings.as_ref()
}
pub fn poll(&mut self) -> Poll<(), ()> {
// server // server
if let State::Connection(ref mut conn) = self.state { if let State::Connection(ref mut conn) = self.state {
// keep-alive timer
if let Some(ref mut timeout) = self.keepalive_timer {
match timeout.poll() {
Ok(Async::Ready(_)) => {
trace!("Keep-alive timeout, close connection");
return Ok(Async::Ready(()));
}
Ok(Async::NotReady) => (),
Err(_) => unreachable!(),
}
}
loop { loop {
// shutdown connection
if self.flags.contains(Flags::SHUTDOWN) {
return conn.poll_close().map_err(|e| e.into());
}
let mut not_ready = true; let mut not_ready = true;
let disconnected = self.flags.contains(Flags::DISCONNECTED);
// check in-flight connections // check in-flight connections
for item in &mut self.tasks { for item in &mut self.tasks {
// read payload // read payload
item.poll_payload(); if !disconnected {
item.poll_payload();
}
if !item.flags.contains(EntryFlags::EOF) { if !item.flags.contains(EntryFlags::EOF) {
let retry = item.payload.need_read() == PayloadStatus::Read; if disconnected {
loop { item.flags.insert(EntryFlags::EOF);
match item.task.poll_io(&mut item.stream) { } else {
Ok(Async::Ready(ready)) => { let retry = item.payload.need_read() == PayloadStatus::Read;
if ready { loop {
match item.task.poll_io(&mut item.stream) {
Ok(Async::Ready(ready)) => {
if ready {
item.flags.insert(
EntryFlags::EOF | EntryFlags::FINISHED,
);
} else {
item.flags.insert(EntryFlags::EOF);
}
not_ready = false;
}
Ok(Async::NotReady) => {
if item.payload.need_read()
== PayloadStatus::Read
&& !retry
{
continue;
}
}
Err(err) => {
error!("Unhandled error: {}", err);
item.flags.insert( item.flags.insert(
EntryFlags::EOF | EntryFlags::FINISHED, EntryFlags::EOF
| EntryFlags::ERROR
| EntryFlags::WRITE_DONE,
); );
} else { item.stream.reset(Reason::INTERNAL_ERROR);
item.flags.insert(EntryFlags::EOF);
}
not_ready = false;
}
Ok(Async::NotReady) => {
if item.payload.need_read() == PayloadStatus::Read
&& !retry
{
continue;
} }
} }
Err(err) => { break;
error!("Unhandled error: {}", err);
item.flags.insert(
EntryFlags::EOF | EntryFlags::ERROR
| EntryFlags::WRITE_DONE,
);
item.stream.reset(Reason::INTERNAL_ERROR);
}
} }
break;
} }
} else if !item.flags.contains(EntryFlags::FINISHED) { }
match item.task.poll() {
if item.flags.contains(EntryFlags::EOF)
&& !item.flags.contains(EntryFlags::FINISHED)
{
match item.task.poll_completed() {
Ok(Async::NotReady) => (), Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => { Ok(Async::Ready(_)) => {
not_ready = false; item.flags.insert(
item.flags.insert(EntryFlags::FINISHED); EntryFlags::FINISHED | EntryFlags::WRITE_DONE,
);
} }
Err(err) => { Err(err) => {
item.flags.insert( item.flags.insert(
EntryFlags::ERROR | EntryFlags::WRITE_DONE EntryFlags::ERROR
| EntryFlags::WRITE_DONE
| EntryFlags::FINISHED, | EntryFlags::FINISHED,
); );
error!("Unhandled error: {}", err); error!("Unhandled error: {}", err);
@ -158,14 +171,17 @@ where
} }
} }
if !item.flags.contains(EntryFlags::WRITE_DONE) { if item.flags.contains(EntryFlags::FINISHED)
&& !item.flags.contains(EntryFlags::WRITE_DONE)
&& !disconnected
{
match item.stream.poll_completed(false) { match item.stream.poll_completed(false) {
Ok(Async::NotReady) => (), Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => { Ok(Async::Ready(_)) => {
not_ready = false; not_ready = false;
item.flags.insert(EntryFlags::WRITE_DONE); item.flags.insert(EntryFlags::WRITE_DONE);
} }
Err(_err) => { Err(_) => {
item.flags.insert(EntryFlags::ERROR); item.flags.insert(EntryFlags::ERROR);
} }
} }
@ -174,7 +190,7 @@ where
// cleanup finished tasks // cleanup finished tasks
while !self.tasks.is_empty() { while !self.tasks.is_empty() {
if self.tasks[0].flags.contains(EntryFlags::EOF) if self.tasks[0].flags.contains(EntryFlags::FINISHED)
&& self.tasks[0].flags.contains(EntryFlags::WRITE_DONE) && self.tasks[0].flags.contains(EntryFlags::WRITE_DONE)
|| self.tasks[0].flags.contains(EntryFlags::ERROR) || self.tasks[0].flags.contains(EntryFlags::ERROR)
{ {
@ -198,49 +214,30 @@ where
not_ready = false; not_ready = false;
let (parts, body) = req.into_parts(); let (parts, body) = req.into_parts();
// stop keepalive timer // update keep-alive expire
self.keepalive_timer.take(); if self.ka_timer.is_some() {
if let Some(expire) = self.settings.keep_alive_expire() {
self.ka_expire = expire;
}
}
self.tasks.push_back(Entry::new( self.tasks.push_back(Entry::new(
parts, parts,
body, body,
resp, resp,
self.addr, self.addr,
&self.settings, self.settings.clone(),
self.extensions.clone(),
)); ));
} }
Ok(Async::NotReady) => { Ok(Async::NotReady) => return Ok(Async::NotReady),
// start keep-alive timer
if self.tasks.is_empty() {
if self.settings.keep_alive_enabled() {
let keep_alive = self.settings.keep_alive();
if keep_alive > 0 && self.keepalive_timer.is_none() {
trace!("Start keep-alive timer");
let mut timeout = Timeout::new(
Duration::new(keep_alive, 0),
Arbiter::handle()).unwrap();
// register timeout
let _ = timeout.poll();
self.keepalive_timer = Some(timeout);
}
} else {
// keep-alive disable, drop connection
return conn.poll_close().map_err(|e| {
error!("Error during connection close: {}", e)
});
}
} else {
// keep-alive unset, rely on operating system
return Ok(Async::NotReady);
}
}
Err(err) => { Err(err) => {
trace!("Connection error: {}", err); trace!("Connection error: {}", err);
self.flags.insert(Flags::DISCONNECTED); self.flags.insert(Flags::SHUTDOWN);
for entry in &mut self.tasks { for entry in &mut self.tasks {
entry.task.disconnected() entry.task.disconnected()
} }
self.keepalive_timer.take(); continue;
} }
} }
} }
@ -248,8 +245,7 @@ where
if not_ready { if not_ready {
if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED)
{ {
return conn.poll_close() return conn.poll_close().map_err(|e| e.into());
.map_err(|e| error!("Error during connection close: {}", e));
} else { } else {
return Ok(Async::NotReady); return Ok(Async::NotReady);
} }
@ -264,7 +260,7 @@ where
Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => { Err(err) => {
trace!("Error handling connection: {}", err); trace!("Error handling connection: {}", err);
return Err(()); return Err(err.into());
} }
} }
} else { } else {
@ -273,6 +269,37 @@ where
self.poll() self.poll()
} }
/// keep-alive timer. returns `true` is keep-alive, otherwise drop
fn poll_keepalive(&mut self) -> Result<(), HttpDispatchError> {
if let Some(ref mut timer) = self.ka_timer {
match timer.poll() {
Ok(Async::Ready(_)) => {
// if we get timer during shutdown, just drop connection
if self.flags.contains(Flags::SHUTDOWN) {
return Err(HttpDispatchError::ShutdownTimeout);
}
if timer.deadline() >= self.ka_expire {
// check for any outstanding request handling
if self.tasks.is_empty() {
return Err(HttpDispatchError::ShutdownTimeout);
} else if let Some(dl) = self.settings.keep_alive_expire() {
timer.reset(dl)
}
} else {
timer.reset(self.ka_expire)
}
}
Ok(Async::NotReady) => (),
Err(e) => {
error!("Timer error {:?}", e);
return Err(HttpDispatchError::Unknown);
}
}
}
Ok(())
}
} }
bitflags! { bitflags! {
@ -285,18 +312,45 @@ 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: ServiceConfig<H>,
extensions: Option<Rc<Extensions>>,
) -> Entry<H> ) -> Entry<H>
where where
H: HttpHandler + 'static, H: HttpHandler + 'static,
@ -304,63 +358,61 @@ 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.stream_extensions = extensions;
let mut req = HttpRequest::from_message(msg); *inner.payload.borrow_mut() = Some(payload);
inner.addr = addr;
// Payload sender
let psender = PayloadType::new(req.headers(), psender);
// start request processing
let mut task = None;
for h in settings.handlers().iter_mut() {
req = match h.handle(req) {
Ok(t) => {
task = Some(t);
break;
}
Err(req) => req,
}
} }
// Payload sender
let psender = PayloadType::new(msg.headers(), psender);
// start request processing
let task = match settings.handler().handle(msg) {
Ok(task) => EntryPipe::Task(task),
Err(_) => EntryPipe::Error(ServerError::err(
Version::HTTP_2,
StatusCode::NOT_FOUND,
)),
};
Entry { Entry {
task: task.unwrap_or_else(|| Pipeline::error(HttpResponse::NotFound())), task,
payload: psender,
stream: H2Writer::new(
resp,
settings.get_shared_bytes(),
Rc::clone(settings),
),
flags: EntryFlags::empty(),
recv, recv,
payload: psender,
stream: H2Writer::new(resp, settings),
flags: EntryFlags::empty(),
} }
} }
fn poll_payload(&mut self) { fn poll_payload(&mut self) {
if !self.flags.contains(EntryFlags::REOF) { while !self.flags.contains(EntryFlags::REOF)
if self.payload.need_read() == PayloadStatus::Read { && self.payload.need_read() == PayloadStatus::Read
if let Err(err) = self.recv.release_capacity().release_capacity(32_768) { {
self.payload.set_error(PayloadError::Http2(err))
}
} else if let Err(err) = self.recv.release_capacity().release_capacity(0) {
self.payload.set_error(PayloadError::Http2(err))
}
match self.recv.poll() { match self.recv.poll() {
Ok(Async::Ready(Some(chunk))) => { Ok(Async::Ready(Some(chunk))) => {
let l = chunk.len();
self.payload.feed_data(chunk); self.payload.feed_data(chunk);
if let Err(err) = self.recv.release_capacity().release_capacity(l) {
self.payload.set_error(PayloadError::Http2(err));
break;
}
} }
Ok(Async::Ready(None)) => { Ok(Async::Ready(None)) => {
self.flags.insert(EntryFlags::REOF); self.flags.insert(EntryFlags::REOF);
self.payload.feed_eof();
}
Ok(Async::NotReady) => break,
Err(err) => {
self.payload.set_error(PayloadError::Http2(err));
break;
} }
Ok(Async::NotReady) => (),
Err(err) => self.payload.set_error(PayloadError::Http2(err)),
} }
} }
} }

View File

@ -1,24 +1,27 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] #![cfg_attr(
feature = "cargo-clippy",
allow(clippy::redundant_field_names)
)]
use std::{cmp, io};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{Async, Poll}; use futures::{Async, Poll};
use http2::server::SendResponse; use http2::server::SendResponse;
use http2::{Reason, SendStream}; use http2::{Reason, SendStream};
use modhttp::Response; use modhttp::Response;
use std::rc::Rc;
use std::{cmp, io};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
use http::{HttpTryFrom, Version};
use super::encoding::ContentEncoder;
use super::helpers; use super::helpers;
use super::settings::WorkerSettings; use super::message::Request;
use super::shared::SharedBytes; use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::ServiceConfig;
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 http::{HttpTryFrom, Method, Version};
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
const CHUNK_SIZE: usize = 16_384; const CHUNK_SIZE: usize = 16_384;
@ -35,27 +38,23 @@ 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: ServiceConfig<H>,
} }
impl<H: 'static> H2Writer<H> { impl<H: 'static> H2Writer<H> {
pub fn new( pub fn new(respond: SendResponse<Bytes>, settings: ServiceConfig<H>) -> H2Writer<H> {
respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<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,78 +65,124 @@ 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); let mut has_date = false;
let mut resp = Response::new(());
let mut len_is_set = false;
*resp.status_mut() = msg.status();
*resp.version_mut() = Version::HTTP_2;
for (key, value) in msg.headers().iter() {
match *key {
// http2 specific
CONNECTION | TRANSFER_ENCODING => continue,
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
continue;
},
CONTENT_LENGTH => match info.length {
ResponseLength::None => (),
ResponseLength::Zero => {
len_is_set = true;
}
_ => continue,
},
DATE => has_date = true,
_ => (),
}
resp.headers_mut().append(key, value.clone());
} }
// http2 specific // set date header
msg.headers_mut().remove(CONNECTION); if !has_date {
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); let mut bytes = BytesMut::with_capacity(29);
self.settings.set_date_simple(&mut bytes); self.settings.set_date(&mut bytes, false);
msg.headers_mut() resp.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
} }
let body = msg.replace_body(Body::Empty); // content length
match body { match info.length {
Body::Binary(ref bytes) => { ResponseLength::Zero => {
if !len_is_set {
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
}
self.flags.insert(Flags::EOF);
}
ResponseLength::Length(len) => {
let mut val = BytesMut::new(); let mut val = BytesMut::new();
helpers::convert_usize(bytes.len(), &mut val); helpers::convert_usize(len, &mut val);
let l = val.len(); let l = val.len();
msg.headers_mut().insert( resp.headers_mut().insert(
CONTENT_LENGTH, CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(), HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
); );
} }
Body::Empty => { ResponseLength::Length64(len) => {
msg.headers_mut() let l = format!("{}", len);
.insert(CONTENT_LENGTH, HeaderValue::from_static("0")); resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap());
}
ResponseLength::None => {
self.flags.insert(Flags::EOF);
} }
_ => (), _ => (),
} }
if let Some(ce) = info.content_encoding {
let mut resp = Response::new(()); resp.headers_mut()
*resp.status_mut() = msg.status(); .insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap());
*resp.version_mut() = Version::HTTP_2;
for (key, value) in msg.headers().iter() {
resp.headers_mut().insert(key, value.clone());
} }
match self.respond trace!("Response: {:?}", resp);
match self
.respond
.send_response(resp, self.flags.contains(Flags::EOF)) .send_response(resp, self.flags.contains(Flags::EOF))
{ {
Ok(stream) => self.stream = Some(stream), Ok(stream) => self.stream = Some(stream),
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")), Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")),
} }
trace!("Response: {:?}", msg); let body = msg.replace_body(Body::Empty);
if let Body::Binary(bytes) = body { if let Body::Binary(bytes) = body {
self.flags.insert(Flags::EOF); if bytes.is_empty() {
self.written = bytes.len() as u64; Ok(WriterState::Done)
self.encoder.write(bytes)?; } else {
if let Some(ref mut stream) = self.stream { self.flags.insert(Flags::EOF);
self.flags.insert(Flags::RESERVED); self.buffer.write(bytes.as_ref())?;
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); if let Some(ref mut stream) = self.stream {
self.flags.insert(Flags::RESERVED);
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
}
Ok(WriterState::Pause)
} }
Ok(WriterState::Pause)
} else { } else {
msg.replace_body(body); msg.replace_body(body);
self.buffer_capacity = msg.write_buffer_capacity(); self.buffer_capacity = msg.write_buffer_capacity();
@ -145,16 +190,14 @@ impl<H: 'static> Writer for H2Writer<H> {
} }
} }
fn write(&mut self, payload: Binary) -> io::Result<WriterState> { fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
self.written = payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) { if !self.flags.contains(Flags::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");
} }
} }
@ -166,10 +209,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",
@ -210,14 +251,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(()))
} }
} }

208
src/server/handler.rs Normal file
View File

@ -0,0 +1,208 @@
use futures::{Async, Future, Poll};
use super::message::Request;
use super::Writer;
use error::Error;
/// Low level http request handler
#[allow(unused_variables)]
pub trait HttpHandler: 'static {
/// Request handling task
type Task: HttpHandlerTask;
/// Handle request
fn handle(&self, req: Request) -> Result<Self::Task, Request>;
}
impl HttpHandler for Box<HttpHandler<Task = Box<HttpHandlerTask>>> {
type Task = Box<HttpHandlerTask>;
fn handle(&self, req: Request) -> Result<Box<HttpHandlerTask>, Request> {
self.as_ref().handle(req)
}
}
/// Low level http request handler
pub trait HttpHandlerTask {
/// Poll task, this method is used before or after *io* object is available
fn poll_completed(&mut self) -> Poll<(), Error> {
Ok(Async::Ready(()))
}
/// Poll task when *io* object is available
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
/// Connection is disconnected
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)
}
}
pub(super) struct HttpHandlerTaskFut<T: HttpHandlerTask> {
task: T,
}
impl<T: HttpHandlerTask> HttpHandlerTaskFut<T> {
pub(crate) fn new(task: T) -> Self {
Self { task }
}
}
impl<T: HttpHandlerTask> Future for HttpHandlerTaskFut<T> {
type Item = ();
type Error = ();
fn poll(&mut self) -> Poll<(), ()> {
self.task.poll_completed().map_err(|_| ())
}
}
/// Conversion helper trait
pub trait IntoHttpHandler {
/// The associated type which is result of conversion.
type Handler: HttpHandler;
/// Convert into `HttpHandler` object.
fn into_handler(self) -> Self::Handler;
}
impl<T: HttpHandler> IntoHttpHandler for T {
type Handler = T;
fn into_handler(self) -> Self::Handler {
self
}
}
impl<T: IntoHttpHandler> IntoHttpHandler for Vec<T> {
type Handler = VecHttpHandler<T::Handler>;
fn into_handler(self) -> Self::Handler {
VecHttpHandler(self.into_iter().map(|item| item.into_handler()).collect())
}
}
#[doc(hidden)]
pub struct VecHttpHandler<H: HttpHandler>(Vec<H>);
impl<H: HttpHandler> HttpHandler for VecHttpHandler<H> {
type Task = H::Task;
fn handle(&self, mut req: Request) -> Result<Self::Task, Request> {
for h in &self.0 {
req = match h.handle(req) {
Ok(task) => return Ok(task),
Err(e) => e,
};
}
Err(req)
}
}
macro_rules! http_handler ({$EN:ident, $(($n:tt, $T:ident)),+} => {
impl<$($T: HttpHandler,)+> HttpHandler for ($($T,)+) {
type Task = $EN<$($T,)+>;
fn handle(&self, mut req: Request) -> Result<Self::Task, Request> {
$(
req = match self.$n.handle(req) {
Ok(task) => return Ok($EN::$T(task)),
Err(e) => e,
};
)+
Err(req)
}
}
#[doc(hidden)]
pub enum $EN<$($T: HttpHandler,)+> {
$($T ($T::Task),)+
}
impl<$($T: HttpHandler,)+> HttpHandlerTask for $EN<$($T,)+>
{
fn poll_completed(&mut self) -> Poll<(), Error> {
match self {
$($EN :: $T(ref mut task) => task.poll_completed(),)+
}
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
match self {
$($EN::$T(ref mut task) => task.poll_io(io),)+
}
}
/// Connection is disconnected
fn disconnected(&mut self) {
match self {
$($EN::$T(ref mut task) => task.disconnected(),)+
}
}
}
});
http_handler!(HttpHandlerTask1, (0, A));
http_handler!(HttpHandlerTask2, (0, A), (1, B));
http_handler!(HttpHandlerTask3, (0, A), (1, B), (2, C));
http_handler!(HttpHandlerTask4, (0, A), (1, B), (2, C), (3, D));
http_handler!(HttpHandlerTask5, (0, A), (1, B), (2, C), (3, D), (4, E));
http_handler!(
HttpHandlerTask6,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F)
);
http_handler!(
HttpHandlerTask7,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G)
);
http_handler!(
HttpHandlerTask8,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G),
(7, H)
);
http_handler!(
HttpHandlerTask9,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G),
(7, H),
(8, I)
);
http_handler!(
HttpHandlerTask10,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E),
(5, F),
(6, G),
(7, H),
(8, I),
(9, J)
);

View File

@ -1,102 +1,17 @@
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\
6061626364656667686970717273747576777879\ 6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899"; 8081828384858687888990919293949596979899";
pub(crate) const STATUS_LINE_BUF_SIZE: usize = 13;
pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
let mut buf: [u8; 13] = [ let mut buf: [u8; STATUS_LINE_BUF_SIZE] = [
b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ', b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ',
]; ];
match version { match version {
@ -114,20 +29,24 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM
let lut_ptr = DEC_DIGITS_LUT.as_ptr(); let lut_ptr = DEC_DIGITS_LUT.as_ptr();
let four = n > 999; let four = n > 999;
// decode 2 more chars, if > 2 chars
let d1 = (n % 100) << 1;
n /= 100;
curr -= 2;
unsafe { unsafe {
// decode 2 more chars, if > 2 chars
let d1 = (n % 100) << 1;
n /= 100;
curr -= 2;
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2); ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
}
// decode last 1 or 2 chars // decode last 1 or 2 chars
if n < 10 { if n < 10 {
curr -= 1; curr -= 1;
unsafe {
*buf_ptr.offset(curr) = (n as u8) + b'0'; *buf_ptr.offset(curr) = (n as u8) + b'0';
} else { }
let d1 = n << 1; } else {
curr -= 2; let d1 = n << 1;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping( ptr::copy_nonoverlapping(
lut_ptr.offset(d1 as isize), lut_ptr.offset(d1 as isize),
buf_ptr.offset(curr), buf_ptr.offset(curr),
@ -143,7 +62,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',
@ -159,7 +78,7 @@ pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
let d1 = n << 1; let d1 = n << 1;
unsafe { unsafe {
ptr::copy_nonoverlapping( ptr::copy_nonoverlapping(
DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), DEC_DIGITS_LUT.as_ptr().add(d1),
buf.as_mut_ptr().offset(18), buf.as_mut_ptr().offset(18),
2, 2,
); );
@ -175,7 +94,7 @@ pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
n /= 100; n /= 100;
unsafe { unsafe {
ptr::copy_nonoverlapping( ptr::copy_nonoverlapping(
DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), DEC_DIGITS_LUT.as_ptr().add(d1),
buf.as_mut_ptr().offset(19), buf.as_mut_ptr().offset(19),
2, 2,
) )
@ -199,37 +118,43 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
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; n /= 10_000;
n /= 10_000;
let d1 = (rem / 100) << 1; let d1 = (rem / 100) << 1;
let d2 = (rem % 100) << 1; let d2 = (rem % 100) << 1;
curr -= 4; curr -= 4;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2); ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2);
} }
}
// if we reach here numbers are <= 9999, so at most 4 chars long // if we reach here numbers are <= 9999, so at most 4 chars long
let mut n = n as isize; // possibly reduce 64bit math let mut n = n as isize; // possibly reduce 64bit math
// decode 2 more chars, if > 2 chars // decode 2 more chars, if > 2 chars
if n >= 100 { if n >= 100 {
let d1 = (n % 100) << 1; let d1 = (n % 100) << 1;
n /= 100; n /= 100;
curr -= 2; curr -= 2;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
} }
}
// decode last 1 or 2 chars // decode last 1 or 2 chars
if n < 10 { if n < 10 {
curr -= 1; curr -= 1;
unsafe {
*buf_ptr.offset(curr) = (n as u8) + b'0'; *buf_ptr.offset(curr) = (n as u8) + b'0';
} else { }
let d1 = n << 1; } else {
curr -= 2; let d1 = n << 1;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
} }
} }
@ -251,63 +176,33 @@ mod tests {
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
bytes.reserve(50); bytes.reserve(50);
write_content_length(0, &mut bytes); write_content_length(0, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 0\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(9, &mut bytes); write_content_length(9, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 9\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(10, &mut bytes); write_content_length(10, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 10\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(99, &mut bytes); write_content_length(99, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 99\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(100, &mut bytes); write_content_length(100, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 100\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(101, &mut bytes); write_content_length(101, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 101\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(998, &mut bytes); write_content_length(998, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 998\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(1000, &mut bytes); write_content_length(1000, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 1000\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(1001, &mut bytes); write_content_length(1001, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 1001\r\n"[..]
);
bytes.reserve(50); bytes.reserve(50);
write_content_length(5909, &mut bytes); write_content_length(5909, &mut bytes);
assert_eq!( assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
bytes.take().freeze(),
b"\r\ncontent-length: 5909\r\n"[..]
);
} }
} }

579
src/server/http.rs Normal file
View File

@ -0,0 +1,579 @@
use std::{fmt, io, mem, net};
use actix::{Addr, System};
use actix_net::server::Server;
use actix_net::service::NewService;
use actix_net::ssl;
use net2::TcpBuilder;
use num_cpus;
#[cfg(feature = "tls")]
use native_tls::TlsAcceptor;
#[cfg(any(feature = "alpn", feature = "ssl"))]
use openssl::ssl::SslAcceptorBuilder;
#[cfg(feature = "rust-tls")]
use rustls::ServerConfig;
use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor};
use super::builder::{HttpServiceBuilder, ServiceProvider};
use super::{IntoHttpHandler, KeepAlive};
struct Socket {
scheme: &'static str,
lst: net::TcpListener,
addr: net::SocketAddr,
handler: Box<ServiceProvider>,
}
/// An HTTP Server
///
/// By default it serves HTTP2 when HTTPs is enabled,
/// in order to change it, use `ServerFlags` that can be provided
/// to acceptor service.
pub struct HttpServer<H, F>
where
H: IntoHttpHandler + 'static,
F: Fn() -> H + Send + Clone,
{
pub(super) factory: F,
pub(super) host: Option<String>,
pub(super) keep_alive: KeepAlive,
pub(super) client_timeout: u64,
pub(super) client_shutdown: u64,
backlog: i32,
threads: usize,
exit: bool,
shutdown_timeout: u16,
no_http2: bool,
no_signals: bool,
maxconn: usize,
maxconnrate: usize,
sockets: Vec<Socket>,
}
impl<H, F> HttpServer<H, F>
where
H: IntoHttpHandler + 'static,
F: Fn() -> H + Send + Clone + 'static,
{
/// Create new http server with application factory
pub fn new(factory: F) -> HttpServer<H, F> {
HttpServer {
factory,
threads: num_cpus::get(),
host: None,
backlog: 2048,
keep_alive: KeepAlive::Timeout(5),
shutdown_timeout: 30,
exit: false,
no_http2: false,
no_signals: false,
maxconn: 25_600,
maxconnrate: 256,
client_timeout: 5000,
client_shutdown: 5000,
sockets: Vec::new(),
}
}
/// Set number of workers to start.
///
/// By default http server uses number of available logical cpu as threads
/// count.
pub fn workers(mut self, num: usize) -> Self {
self.threads = num;
self
}
/// Set the maximum number of pending connections.
///
/// This refers to the number of clients that can be waiting to be served.
/// Exceeding this number results in the client getting an error when
/// attempting to connect. It should only affect servers under significant
/// load.
///
/// Generally set in the 64-2048 range. Default value is 2048.
///
/// This method should be called before `bind()` method call.
pub fn backlog(mut self, num: i32) -> Self {
self.backlog = num;
self
}
/// Sets the maximum per-worker number of concurrent connections.
///
/// All socket listeners will stop accepting connections when this limit is reached
/// for each worker.
///
/// By default max connections is set to a 25k.
pub fn maxconn(mut self, num: usize) -> Self {
self.maxconn = num;
self
}
/// Sets the maximum per-worker concurrent connection establish process.
///
/// All listeners will stop accepting connections when this limit is reached. It
/// can be used to limit the global SSL CPU usage.
///
/// By default max connections is set to a 256.
pub fn maxconnrate(mut self, num: usize) -> Self {
self.maxconnrate = num;
self
}
/// Set server keep-alive setting.
///
/// By default keep alive is set to a 5 seconds.
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
self.keep_alive = val.into();
self
}
/// Set server client timeout in milliseconds for first request.
///
/// Defines a timeout for reading client request header. If a client does not transmit
/// the entire set headers within this time, the request is terminated with
/// the 408 (Request Time-out) error.
///
/// To disable timeout set value to 0.
///
/// By default client timeout is set to 5000 milliseconds.
pub fn client_timeout(mut self, val: u64) -> Self {
self.client_timeout = val;
self
}
/// Set server connection shutdown timeout in milliseconds.
///
/// Defines a timeout for shutdown connection. If a shutdown procedure does not complete
/// within this time, the request is dropped.
///
/// To disable timeout set value to 0.
///
/// By default client timeout is set to 5000 milliseconds.
pub fn client_shutdown(mut self, val: u64) -> Self {
self.client_shutdown = val;
self
}
/// Set server host name.
///
/// Host name is used by application router aa a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
pub fn server_hostname(mut self, val: String) -> Self {
self.host = Some(val);
self
}
/// Stop actix system.
///
/// `SystemExit` message stops currently running system.
pub fn system_exit(mut self) -> Self {
self.exit = true;
self
}
/// Disable signal handling
pub fn disable_signals(mut self) -> Self {
self.no_signals = true;
self
}
/// Timeout for graceful workers shutdown.
///
/// After receiving a stop signal, workers have this much time to finish
/// serving requests. Workers still alive after the timeout are force
/// dropped.
///
/// By default shutdown timeout sets to 30 seconds.
pub fn shutdown_timeout(mut self, sec: u16) -> Self {
self.shutdown_timeout = sec;
self
}
/// Disable `HTTP/2` support
pub fn no_http2(mut self) -> Self {
self.no_http2 = true;
self
}
/// Get addresses of bound sockets.
pub fn addrs(&self) -> Vec<net::SocketAddr> {
self.sockets.iter().map(|s| s.addr).collect()
}
/// Get addresses of bound sockets and the scheme for it.
///
/// This is useful when the server is bound from different sources
/// with some sockets listening on http and some listening on https
/// and the user should be presented with an enumeration of which
/// socket requires which protocol.
pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> {
self.sockets.iter().map(|s| (s.addr, s.scheme)).collect()
}
/// Use listener for accepting incoming connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen(mut self, lst: net::TcpListener) -> Self {
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
lst,
addr,
scheme: "http",
handler: Box::new(HttpServiceBuilder::new(
self.factory.clone(),
DefaultAcceptor,
)),
});
self
}
#[doc(hidden)]
/// Use listener for accepting incoming connection requests
pub fn listen_with<A>(mut self, lst: net::TcpListener, acceptor: A) -> Self
where
A: AcceptorServiceFactory,
<A::NewService as NewService>::InitError: fmt::Debug,
{
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
lst,
addr,
scheme: "https",
handler: Box::new(HttpServiceBuilder::new(self.factory.clone(), acceptor)),
});
self
}
#[cfg(feature = "tls")]
/// Use listener for accepting incoming tls connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self {
use actix_net::service::NewServiceExt;
self.listen_with(lst, move || {
ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ())
})
}
#[cfg(any(feature = "alpn", feature = "ssl"))]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_ssl(
self, lst: net::TcpListener, builder: SslAcceptorBuilder,
) -> io::Result<Self> {
use super::{openssl_acceptor_with_flags, ServerFlags};
use actix_net::service::NewServiceExt;
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
let acceptor = openssl_acceptor_with_flags(builder, flags)?;
Ok(self.listen_with(lst, move || {
ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ())
}))
}
#[cfg(feature = "rust-tls")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_rustls(self, lst: net::TcpListener, config: ServerConfig) -> Self {
use super::{RustlsAcceptor, ServerFlags};
use actix_net::service::NewServiceExt;
// alpn support
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
self.listen_with(lst, move || {
RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ())
})
}
/// The socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
for lst in sockets {
self = self.listen(lst);
}
Ok(self)
}
/// Start listening for incoming connections with supplied acceptor.
#[doc(hidden)]
#[cfg_attr(
feature = "cargo-clippy",
allow(clippy::needless_pass_by_value)
)]
pub fn bind_with<S, A>(mut self, addr: S, acceptor: A) -> io::Result<Self>
where
S: net::ToSocketAddrs,
A: AcceptorServiceFactory,
<A::NewService as NewService>::InitError: fmt::Debug,
{
let sockets = self.bind2(addr)?;
for lst in sockets {
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
lst,
addr,
scheme: "https",
handler: Box::new(HttpServiceBuilder::new(
self.factory.clone(),
acceptor.clone(),
)),
});
}
Ok(self)
}
fn bind2<S: net::ToSocketAddrs>(
&self, addr: S,
) -> io::Result<Vec<net::TcpListener>> {
let mut err = None;
let mut succ = false;
let mut sockets = Vec::new();
for addr in addr.to_socket_addrs()? {
match create_tcp_listener(addr, self.backlog) {
Ok(lst) => {
succ = true;
sockets.push(lst);
}
Err(e) => err = Some(e),
}
}
if !succ {
if let Some(e) = err.take() {
Err(e)
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Can not bind to address.",
))
}
} else {
Ok(sockets)
}
}
#[cfg(feature = "tls")]
/// The ssl socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind_tls<S: net::ToSocketAddrs>(
self, addr: S, acceptor: TlsAcceptor,
) -> io::Result<Self> {
use actix_net::service::NewServiceExt;
use actix_net::ssl::NativeTlsAcceptor;
self.bind_with(addr, move || {
NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ())
})
}
#[cfg(any(feature = "alpn", feature = "ssl"))]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_ssl<S>(self, addr: S, builder: SslAcceptorBuilder) -> io::Result<Self>
where
S: net::ToSocketAddrs,
{
use super::{openssl_acceptor_with_flags, ServerFlags};
use actix_net::service::NewServiceExt;
// alpn support
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
let acceptor = openssl_acceptor_with_flags(builder, flags)?;
self.bind_with(addr, move || {
ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ())
})
}
#[cfg(feature = "rust-tls")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_rustls<S: net::ToSocketAddrs>(
self, addr: S, builder: ServerConfig,
) -> io::Result<Self> {
use super::{RustlsAcceptor, ServerFlags};
use actix_net::service::NewServiceExt;
// alpn support
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
self.bind_with(addr, move || {
RustlsAcceptor::with_flags(builder.clone(), flags).map_err(|_| ())
})
}
}
impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
/// Start listening for incoming connections.
///
/// This method starts number of http workers in separate threads.
/// For each address this method starts separate thread which does
/// `accept()` in a loop.
///
/// This methods panics if no socket addresses get bound.
///
/// This method requires to run within properly configured `Actix` system.
///
/// ```rust
/// extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system
///
/// server::new(|| App::new().resource("/", |r| r.h(|_: &_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .expect("Can not bind to 127.0.0.1:0")
/// .start();
/// # actix::System::current().stop();
/// sys.run(); // <- Run actix system, this method starts all async processes
/// }
/// ```
pub fn start(mut self) -> Addr<Server> {
ssl::max_concurrent_ssl_connect(self.maxconnrate);
let mut srv = Server::new()
.workers(self.threads)
.maxconn(self.maxconn)
.shutdown_timeout(self.shutdown_timeout);
srv = if self.exit { srv.system_exit() } else { srv };
srv = if self.no_signals {
srv.disable_signals()
} else {
srv
};
let sockets = mem::replace(&mut self.sockets, Vec::new());
for socket in sockets {
let host = self
.host
.as_ref()
.map(|h| h.to_owned())
.unwrap_or_else(|| format!("{}", socket.addr));
let (secure, client_shutdown) = if socket.scheme == "https" {
(true, self.client_shutdown)
} else {
(false, 0)
};
srv = socket.handler.register(
srv,
socket.lst,
host,
socket.addr,
self.keep_alive,
secure,
self.client_timeout,
client_shutdown,
);
}
srv.start()
}
/// Spawn new thread and start listening for incoming connections.
///
/// This method spawns new thread and starts new actix system. Other than
/// that it is similar to `start()` method. This method blocks.
///
/// This methods panics if no socket addresses get bound.
///
/// ```rust,ignore
/// # extern crate futures;
/// # extern crate actix_web;
/// # use futures::Future;
/// use actix_web::*;
///
/// fn main() {
/// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .expect("Can not bind to 127.0.0.1:0")
/// .run();
/// }
/// ```
pub fn run(self) {
let sys = System::new("http-server");
self.start();
sys.run();
}
/// Register current http server as actix-net's server service
pub fn register(self, mut srv: Server) -> Server {
for socket in self.sockets {
let host = self
.host
.as_ref()
.map(|h| h.to_owned())
.unwrap_or_else(|| format!("{}", socket.addr));
let (secure, client_shutdown) = if socket.scheme == "https" {
(true, self.client_shutdown)
} else {
(false, 0)
};
srv = socket.handler.register(
srv,
socket.lst,
host,
socket.addr,
self.keep_alive,
secure,
self.client_timeout,
client_shutdown,
);
}
srv
}
}
fn create_tcp_listener(
addr: net::SocketAddr, backlog: i32,
) -> io::Result<net::TcpListener> {
let builder = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
};
builder.reuse_address(true)?;
builder.bind(addr)?;
Ok(builder.listen(backlog)?)
}

69
src/server/incoming.rs Normal file
View File

@ -0,0 +1,69 @@
//! Support for `Stream<Item=T::AsyncReady+AsyncWrite>`, deprecated!
use std::{io, net};
use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message};
use futures::{Future, Stream};
use tokio_io::{AsyncRead, AsyncWrite};
use super::channel::{HttpChannel, WrapperStream};
use super::handler::{HttpHandler, IntoHttpHandler};
use super::http::HttpServer;
use super::settings::{ServerSettings, ServiceConfig};
impl<T: AsyncRead + AsyncWrite + 'static> Message for WrapperStream<T> {
type Result = ();
}
impl<H, F> HttpServer<H, F>
where
H: IntoHttpHandler,
F: Fn() -> H + Send + Clone,
{
#[doc(hidden)]
#[deprecated(since = "0.7.8")]
/// Start listening for incoming connections from a stream.
///
/// This method uses only one thread for handling incoming connections.
pub fn start_incoming<T, S>(self, stream: S, secure: bool)
where
S: Stream<Item = T, Error = io::Error> + 'static,
T: AsyncRead + AsyncWrite + 'static,
{
// set server settings
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
let apps = (self.factory)().into_handler();
let settings = ServiceConfig::new(
apps,
self.keep_alive,
self.client_timeout,
self.client_shutdown,
ServerSettings::new(addr, "127.0.0.1:8080", secure),
);
// start server
HttpIncoming::create(move |ctx| {
ctx.add_message_stream(stream.map_err(|_| ()).map(WrapperStream::new));
HttpIncoming { settings }
});
}
}
struct HttpIncoming<H: HttpHandler> {
settings: ServiceConfig<H>,
}
impl<H: HttpHandler> Actor for HttpIncoming<H> {
type Context = Context<Self>;
}
impl<T, H> Handler<WrapperStream<T>> for HttpIncoming<H>
where
T: AsyncRead + AsyncWrite,
H: HttpHandler,
{
type Result = ();
fn handle(&mut self, msg: WrapperStream<T>, _: &mut Context<Self>) -> Self::Result {
Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg).map_err(|_| ()));
}
}

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

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

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

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

View File

@ -1,50 +1,175 @@
//! Http server //! Http server module
use std::net::Shutdown; //!
//! The module contains everything necessary to setup
//! HTTP server.
//!
//! In order to start HTTP server, first you need to create and configure it
//! using factory that can be supplied to [new](fn.new.html).
//!
//! ## Factory
//!
//! Factory is a function that returns Application, describing how
//! to serve incoming HTTP requests.
//!
//! As the server uses worker pool, the factory function is restricted to trait bounds
//! `Send + Clone + 'static` so that each worker would be able to accept Application
//! without a need for synchronization.
//!
//! If you wish to share part of state among all workers you should
//! wrap it in `Arc` and potentially synchronization primitive like
//! [RwLock](https://doc.rust-lang.org/std/sync/struct.RwLock.html)
//! If the wrapped type is not thread safe.
//!
//! Note though that locking is not advisable for asynchronous programming
//! and you should minimize all locks in your request handlers
//!
//! ## HTTPS Support
//!
//! Actix-web provides support for major crates that provides TLS.
//! Each TLS implementation is provided with [AcceptorService](trait.AcceptorService.html)
//! that describes how HTTP Server accepts connections.
//!
//! For `bind` and `listen` there are corresponding `bind_ssl|tls|rustls` and `listen_ssl|tls|rustls` that accepts
//! these services.
//!
//! **NOTE:** `native-tls` doesn't support `HTTP2` yet
//!
//! ## Signal handling and shutdown
//!
//! By default HTTP Server listens for system signals
//! and, gracefully shuts down at most after 30 seconds.
//!
//! Both signal handling and shutdown timeout can be controlled
//! using corresponding methods.
//!
//! If worker, for some reason, unable to shut down within timeout
//! it is forcibly dropped.
//!
//! ## Example
//!
//! ```rust,ignore
//!extern crate actix;
//!extern crate actix_web;
//!extern crate rustls;
//!
//!use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse, Responder};
//!use std::io::BufReader;
//!use rustls::internal::pemfile::{certs, rsa_private_keys};
//!use rustls::{NoClientAuth, ServerConfig};
//!
//!fn index(req: &HttpRequest) -> Result<HttpResponse, Error> {
//! Ok(HttpResponse::Ok().content_type("text/plain").body("Welcome!"))
//!}
//!
//!fn load_ssl() -> ServerConfig {
//! use std::io::BufReader;
//!
//! const CERT: &'static [u8] = include_bytes!("../cert.pem");
//! const KEY: &'static [u8] = include_bytes!("../key.pem");
//!
//! let mut cert = BufReader::new(CERT);
//! let mut key = BufReader::new(KEY);
//!
//! let mut config = ServerConfig::new(NoClientAuth::new());
//! let cert_chain = certs(&mut cert).unwrap();
//! let mut keys = rsa_private_keys(&mut key).unwrap();
//! config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
//!
//! config
//!}
//!
//!fn main() {
//! let sys = actix::System::new("http-server");
//! // load ssl keys
//! let config = load_ssl();
//!
//! // create and start server at once
//! server::new(|| {
//! App::new()
//! // register simple handler, handle all methods
//! .resource("/index.html", |r| r.f(index))
//! }))
//! }).bind_rustls("127.0.0.1:8443", config)
//! .unwrap()
//! .start();
//!
//! println!("Started http server: 127.0.0.1:8080");
//! //Run system so that server would start accepting connections
//! let _ = sys.run();
//!}
//! ```
use std::net::{Shutdown, SocketAddr};
use std::rc::Rc;
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;
pub use actix_net::server::{PauseServer, ResumeServer, StopServer};
pub(crate) mod acceptor;
pub(crate) mod builder;
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;
mod handler;
pub(crate) mod helpers; pub(crate) mod helpers;
mod settings; mod http;
pub(crate) mod shared; pub(crate) mod incoming;
mod srv; pub(crate) mod input;
pub(crate) mod utils; pub(crate) mod message;
mod worker; pub(crate) mod output;
pub(crate) mod service;
pub(crate) mod settings;
mod ssl;
pub use self::handler::*;
pub use self::http::HttpServer;
pub use self::message::Request;
pub use self::ssl::*;
pub use self::error::{AcceptorError, HttpDispatchError};
pub use self::settings::ServerSettings; pub use self::settings::ServerSettings;
pub use self::srv::HttpServer;
#[doc(hidden)]
pub use self::acceptor::AcceptorTimeout;
#[doc(hidden)]
pub use self::settings::{ServiceConfig, ServiceConfigBuilder};
#[doc(hidden)]
pub use self::service::{H1Service, HttpService, StreamConfiguration};
#[doc(hidden)]
pub use self::helpers::write_content_length;
use body::Binary; use body::Binary;
use error::Error; use extensions::Extensions;
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,19 +177,29 @@ 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, H>(factory: F) -> HttpServer<H, F>
where where
F: Fn() -> U + Sync + Send + 'static, F: Fn() -> H + Send + Clone + 'static,
U: IntoIterator<Item = H> + 'static,
H: IntoHttpHandler + 'static, H: IntoHttpHandler + 'static,
{ {
HttpServer::new(factory) HttpServer::new(factory)
} }
#[doc(hidden)]
bitflags! {
///Flags that can be used to configure HTTP Server.
pub struct ServerFlags: u8 {
///Use HTTP1 protocol
const HTTP1 = 0b0000_0001;
///Use HTTP2 protocol
const HTTP2 = 0b0000_0010;
}
}
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
/// Server keep-alive setting /// Server keep-alive setting
pub enum KeepAlive { pub enum KeepAlive {
@ -94,70 +229,6 @@ impl From<Option<usize>> for KeepAlive {
} }
} }
/// Pause accepting incoming connections
///
/// If socket contains some pending connection, they might be dropped.
/// All opened connection remains active.
#[derive(Message)]
pub struct PauseServer;
/// Resume accepting incoming connections
#[derive(Message)]
pub struct ResumeServer;
/// Stop incoming connection processing, stop all workers and exit.
///
/// If server starts with `spawn()` method, then spawned thread get terminated.
pub struct StopServer {
pub graceful: bool,
}
impl actix::Message for StopServer {
type Result = Result<(), ()>;
}
/// Low level http request handler
#[allow(unused_variables)]
pub trait HttpHandler: 'static {
/// Handle request
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest>;
}
impl HttpHandler for Box<HttpHandler> {
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest> {
self.as_mut().handle(req)
}
}
#[doc(hidden)]
pub trait HttpHandlerTask {
/// Poll task, this method is used before or after *io* object is available
fn poll(&mut self) -> Poll<(), Error>;
/// Poll task when *io* object is available
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
/// Connection is disconnected
fn disconnected(&mut self);
}
/// Conversion helper trait
pub trait IntoHttpHandler {
/// The associated type which is result of conversion.
type Handler: HttpHandler;
/// Convert into `HttpHandler` object.
fn into_handler(self, settings: ServerSettings) -> Self::Handler;
}
impl<T: HttpHandler> IntoHttpHandler for T {
type Handler = T;
fn into_handler(self, _: ServerSettings) -> Self::Handler {
self
}
}
#[doc(hidden)] #[doc(hidden)]
#[derive(Debug)] #[derive(Debug)]
pub enum WriterState { pub enum WriterState {
@ -168,14 +239,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>;
@ -187,9 +264,79 @@ pub trait Writer {
pub trait IoStream: AsyncRead + AsyncWrite + 'static { pub trait IoStream: AsyncRead + AsyncWrite + 'static {
fn shutdown(&mut self, how: Shutdown) -> io::Result<()>; fn shutdown(&mut self, how: Shutdown) -> io::Result<()>;
/// Returns the socket address of the remote peer of this TCP connection.
fn peer_addr(&self) -> Option<SocketAddr> {
None
}
/// Sets the value of the TCP_NODELAY option on this socket.
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 set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
fn read_available(&mut self, buf: &mut BytesMut) -> Poll<(bool, bool), io::Error> {
let mut read_some = false;
loop {
if buf.remaining_mut() < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE);
}
let read = unsafe { self.read(buf.bytes_mut()) };
match read {
Ok(n) => {
if n == 0 {
return Ok(Async::Ready((read_some, true)));
} else {
read_some = true;
unsafe {
buf.advance_mut(n);
}
}
}
Err(e) => {
return if e.kind() == io::ErrorKind::WouldBlock {
if read_some {
Ok(Async::Ready((read_some, false)))
} else {
Ok(Async::NotReady)
}
} else {
Err(e)
};
}
}
}
}
/// Extra io stream extensions
fn extensions(&self) -> Option<Rc<Extensions>> {
None
}
}
#[cfg(all(unix, feature = "uds"))]
impl IoStream for ::tokio_uds::UnixStream {
#[inline]
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
::tokio_uds::UnixStream::shutdown(self, how)
}
#[inline]
fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_linger(&mut self, _dur: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_keepalive(&mut self, _nodelay: bool) -> io::Result<()> {
Ok(())
}
} }
impl IoStream for TcpStream { impl IoStream for TcpStream {
@ -198,6 +345,11 @@ impl IoStream for TcpStream {
TcpStream::shutdown(self, how) TcpStream::shutdown(self, how)
} }
#[inline]
fn peer_addr(&self) -> Option<SocketAddr> {
TcpStream::peer_addr(self).ok()
}
#[inline] #[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
TcpStream::set_nodelay(self, nodelay) TcpStream::set_nodelay(self, nodelay)
@ -207,48 +359,9 @@ impl IoStream for TcpStream {
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> { fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
TcpStream::set_linger(self, dur) TcpStream::set_linger(self, dur)
} }
}
#[cfg(feature = "alpn")]
use tokio_openssl::SslStream;
#[cfg(feature = "alpn")]
impl IoStream for SslStream<TcpStream> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline] #[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay) TcpStream::set_keepalive(self, dur)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
}
#[cfg(feature = "tls")]
use tokio_tls::TlsStream;
#[cfg(feature = "tls")]
impl IoStream for TlsStream<TcpStream> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
} }
} }

760
src/server/output.rs Normal file
View File

@ -0,0 +1,760 @@
use std::fmt::Write as FmtWrite;
use std::io::Write;
use std::str::FromStr;
use std::{cmp, fmt, io, mem};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliEncoder;
use bytes::BytesMut;
#[cfg(feature = "flate2")]
use flate2::write::{GzEncoder, ZlibEncoder};
#[cfg(feature = "flate2")]
use flate2::Compression;
use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH};
use http::{StatusCode, Version};
use super::message::InnerRequest;
use body::{Binary, Body};
use header::ContentEncoding;
use httpresponse::HttpResponse;
#[derive(Debug)]
pub(crate) enum ResponseLength {
Chunked,
Zero,
Length(usize),
Length64(u64),
None,
}
#[derive(Debug)]
pub(crate) struct ResponseInfo {
head: bool,
pub length: ResponseLength,
pub content_encoding: Option<&'static str>,
}
impl ResponseInfo {
pub fn new(head: bool) -> Self {
ResponseInfo {
head,
length: ResponseLength::None,
content_encoding: None,
}
}
}
#[derive(Debug)]
pub(crate) enum Output {
Empty(BytesMut),
Buffer(BytesMut),
Encoder(ContentEncoder),
TE(TransferEncoding),
Done,
}
impl Output {
pub fn take(&mut self) -> BytesMut {
match mem::replace(self, Output::Done) {
Output::Empty(bytes) => bytes,
Output::Buffer(bytes) => bytes,
Output::Encoder(mut enc) => enc.take_buf(),
Output::TE(mut te) => te.take(),
Output::Done => panic!(),
}
}
pub fn take_option(&mut self) -> Option<BytesMut> {
match mem::replace(self, Output::Done) {
Output::Empty(bytes) => Some(bytes),
Output::Buffer(bytes) => Some(bytes),
Output::Encoder(mut enc) => Some(enc.take_buf()),
Output::TE(mut te) => Some(te.take()),
Output::Done => None,
}
}
pub fn as_ref(&mut self) -> &BytesMut {
match self {
Output::Empty(ref mut bytes) => bytes,
Output::Buffer(ref mut bytes) => bytes,
Output::Encoder(ref mut enc) => enc.buf_ref(),
Output::TE(ref mut te) => te.buf_ref(),
Output::Done => panic!(),
}
}
pub fn as_mut(&mut self) -> &mut BytesMut {
match self {
Output::Empty(ref mut bytes) => bytes,
Output::Buffer(ref mut bytes) => bytes,
Output::Encoder(ref mut enc) => enc.buf_mut(),
Output::TE(ref mut te) => te.buf_mut(),
Output::Done => panic!(),
}
}
pub fn split_to(&mut self, cap: usize) -> BytesMut {
match self {
Output::Empty(ref mut bytes) => bytes.split_to(cap),
Output::Buffer(ref mut bytes) => bytes.split_to(cap),
Output::Encoder(ref mut enc) => enc.buf_mut().split_to(cap),
Output::TE(ref mut te) => te.buf_mut().split_to(cap),
Output::Done => BytesMut::new(),
}
}
pub fn len(&self) -> usize {
match self {
Output::Empty(ref bytes) => bytes.len(),
Output::Buffer(ref bytes) => bytes.len(),
Output::Encoder(ref enc) => enc.len(),
Output::TE(ref te) => te.len(),
Output::Done => 0,
}
}
pub fn is_empty(&self) -> bool {
match self {
Output::Empty(ref bytes) => bytes.is_empty(),
Output::Buffer(ref bytes) => bytes.is_empty(),
Output::Encoder(ref enc) => enc.is_empty(),
Output::TE(ref te) => te.is_empty(),
Output::Done => true,
}
}
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
match self {
Output::Buffer(ref mut bytes) => {
bytes.extend_from_slice(data);
Ok(())
}
Output::Encoder(ref mut enc) => enc.write(data),
Output::TE(ref mut te) => te.encode(data).map(|_| ()),
Output::Empty(_) | Output::Done => Ok(()),
}
}
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
match self {
Output::Buffer(_) => Ok(true),
Output::Encoder(ref mut enc) => enc.write_eof(),
Output::TE(ref mut te) => Ok(te.encode_eof()),
Output::Empty(_) | Output::Done => Ok(true),
}
}
pub(crate) fn for_server(
&mut self, info: &mut ResponseInfo, req: &InnerRequest, resp: &mut HttpResponse,
response_encoding: ContentEncoding,
) {
let buf = self.take();
let version = resp.version().unwrap_or_else(|| req.version);
let mut len = 0;
let has_body = match resp.body() {
Body::Empty => false,
Body::Binary(ref bin) => {
len = bin.len();
!(response_encoding == ContentEncoding::Auto && len < 96)
}
_ => true,
};
// Enable content encoding only if response does not contain Content-Encoding
// header
#[cfg(any(feature = "brotli", feature = "flate2"))]
let mut encoding = if has_body {
let encoding = match response_encoding {
ContentEncoding::Auto => {
// negotiate content-encoding
if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
if let Ok(enc) = val.to_str() {
AcceptEncoding::parse(enc)
} else {
ContentEncoding::Identity
}
} else {
ContentEncoding::Identity
}
}
encoding => encoding,
};
if encoding.is_compression() {
info.content_encoding = Some(encoding.as_str());
}
encoding
} else {
ContentEncoding::Identity
};
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
let mut encoding = ContentEncoding::Identity;
let transfer = match resp.body() {
Body::Empty => {
info.length = match resp.status() {
StatusCode::NO_CONTENT
| StatusCode::CONTINUE
| StatusCode::SWITCHING_PROTOCOLS
| StatusCode::PROCESSING => ResponseLength::None,
_ => ResponseLength::Zero,
};
*self = Output::Empty(buf);
return;
}
Body::Binary(_) => {
#[cfg(any(feature = "brotli", feature = "flate2"))]
{
if !(encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto)
{
let mut tmp = BytesMut::new();
let mut transfer = TransferEncoding::eof(tmp);
let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
ZlibEncoder::new(transfer, Compression::fast()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(
GzEncoder::new(transfer, Compression::fast()),
),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
}
ContentEncoding::Identity | ContentEncoding::Auto => {
unreachable!()
}
};
let bin = resp.replace_body(Body::Empty).binary();
// TODO return error!
let _ = enc.write(bin.as_ref());
let _ = enc.write_eof();
let body = enc.buf_mut().take();
len = body.len();
resp.replace_body(Binary::from(body));
}
}
info.length = ResponseLength::Length(len);
if info.head {
*self = Output::Empty(buf);
} else {
*self = Output::Buffer(buf);
}
return;
}
Body::Streaming(_) | Body::Actor(_) => {
if resp.upgrade() {
if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2");
}
if encoding != ContentEncoding::Identity {
encoding = ContentEncoding::Identity;
info.content_encoding.take();
}
TransferEncoding::eof(buf)
} else {
if !(encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto)
{
resp.headers_mut().remove(CONTENT_LENGTH);
}
Output::streaming_encoding(info, buf, version, resp)
}
}
};
// check for head response
if info.head {
resp.set_body(Body::Empty);
*self = Output::Empty(transfer.buf.unwrap());
return;
}
let enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => {
ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::fast()))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
}
#[cfg(feature = "brotli")]
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)),
ContentEncoding::Identity | ContentEncoding::Auto => {
*self = Output::TE(transfer);
return;
}
};
*self = Output::Encoder(enc);
}
fn streaming_encoding(
info: &mut ResponseInfo, buf: BytesMut, version: Version,
resp: &mut HttpResponse,
) -> TransferEncoding {
match resp.chunked() {
Some(true) => {
// Enable transfer encoding
info.length = ResponseLength::Chunked;
if version == Version::HTTP_11 {
TransferEncoding::chunked(buf)
} else {
TransferEncoding::eof(buf)
}
}
Some(false) => TransferEncoding::eof(buf),
None => {
// if Content-Length is specified, then use it as length hint
let (len, chunked) =
if let Some(len) = resp.headers().get(CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
(Some(len), false)
} else {
error!("illegal Content-Length: {:?}", len);
(None, false)
}
} else {
error!("illegal Content-Length: {:?}", len);
(None, false)
}
} else {
(None, true)
};
if !chunked {
if let Some(len) = len {
info.length = ResponseLength::Length64(len);
TransferEncoding::length(len, buf)
} else {
TransferEncoding::eof(buf)
}
} else {
// Enable transfer encoding
info.length = ResponseLength::Chunked;
if version == Version::HTTP_11 {
TransferEncoding::chunked(buf)
} else {
TransferEncoding::eof(buf)
}
}
}
}
}
}
pub(crate) enum ContentEncoder {
#[cfg(feature = "flate2")]
Deflate(ZlibEncoder<TransferEncoding>),
#[cfg(feature = "flate2")]
Gzip(GzEncoder<TransferEncoding>),
#[cfg(feature = "brotli")]
Br(BrotliEncoder<TransferEncoding>),
Identity(TransferEncoding),
}
impl fmt::Debug for ContentEncoder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"),
ContentEncoder::Identity(_) => writeln!(f, "ContentEncoder(Identity)"),
}
}
}
impl ContentEncoder {
#[inline]
pub fn len(&self) -> usize {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref encoder) => encoder.get_ref().len(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().len(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().len(),
ContentEncoder::Identity(ref encoder) => encoder.len(),
}
}
#[inline]
pub fn is_empty(&self) -> bool {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_empty(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_empty(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_empty(),
ContentEncoder::Identity(ref encoder) => encoder.is_empty(),
}
}
#[inline]
pub(crate) fn take_buf(&mut self) -> BytesMut {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
ContentEncoder::Identity(ref mut encoder) => encoder.take(),
}
}
#[inline]
pub(crate) fn buf_mut(&mut self) -> &mut BytesMut {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_mut(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_mut(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_mut(),
ContentEncoder::Identity(ref mut encoder) => encoder.buf_mut(),
}
}
#[inline]
pub(crate) fn buf_ref(&mut self) -> &BytesMut {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_ref(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_ref(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_ref(),
ContentEncoder::Identity(ref mut encoder) => encoder.buf_ref(),
}
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))]
#[inline(always)]
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
let encoder =
mem::replace(self, ContentEncoder::Identity(TransferEncoding::empty()));
match encoder {
#[cfg(feature = "brotli")]
ContentEncoder::Br(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(true)
}
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(true)
}
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(true)
}
Err(err) => Err(err),
},
ContentEncoder::Identity(mut writer) => {
let res = writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(res)
}
}
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))]
#[inline(always)]
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding br encoding: {}", err);
Err(err)
}
},
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding gzip encoding: {}", err);
Err(err)
}
},
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding deflate encoding: {}", err);
Err(err)
}
},
ContentEncoder::Identity(ref mut encoder) => {
encoder.encode(data)?;
Ok(())
}
}
}
}
/// Encoders to handle different Transfer-Encodings.
#[derive(Debug)]
pub(crate) struct TransferEncoding {
buf: Option<BytesMut>,
kind: TransferEncodingKind,
}
#[derive(Debug, PartialEq, Clone)]
enum TransferEncodingKind {
/// An Encoder for when Transfer-Encoding includes `chunked`.
Chunked(bool),
/// An Encoder for when Content-Length is set.
///
/// Enforces that the body is not longer than the Content-Length header.
Length(u64),
/// An Encoder for when Content-Length is not known.
///
/// Application decides when to stop writing.
Eof,
}
impl TransferEncoding {
fn take(&mut self) -> BytesMut {
self.buf.take().unwrap()
}
fn buf_ref(&mut self) -> &BytesMut {
self.buf.as_ref().unwrap()
}
fn len(&self) -> usize {
self.buf.as_ref().unwrap().len()
}
fn is_empty(&self) -> bool {
self.buf.as_ref().unwrap().is_empty()
}
fn buf_mut(&mut self) -> &mut BytesMut {
self.buf.as_mut().unwrap()
}
#[inline]
pub fn empty() -> TransferEncoding {
TransferEncoding {
buf: None,
kind: TransferEncodingKind::Eof,
}
}
#[inline]
pub fn eof(buf: BytesMut) -> TransferEncoding {
TransferEncoding {
buf: Some(buf),
kind: TransferEncodingKind::Eof,
}
}
#[inline]
pub fn chunked(buf: BytesMut) -> TransferEncoding {
TransferEncoding {
buf: Some(buf),
kind: TransferEncodingKind::Chunked(false),
}
}
#[inline]
pub fn length(len: u64, buf: BytesMut) -> TransferEncoding {
TransferEncoding {
buf: Some(buf),
kind: TransferEncodingKind::Length(len),
}
}
/// Encode message. Return `EOF` state of encoder
#[inline]
pub fn encode(&mut self, msg: &[u8]) -> io::Result<bool> {
match self.kind {
TransferEncodingKind::Eof => {
let eof = msg.is_empty();
self.buf.as_mut().unwrap().extend_from_slice(msg);
Ok(eof)
}
TransferEncodingKind::Chunked(ref mut eof) => {
if *eof {
return Ok(true);
}
if msg.is_empty() {
*eof = true;
self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n");
} else {
let mut buf = BytesMut::new();
writeln!(&mut buf, "{:X}\r", msg.len())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let b = self.buf.as_mut().unwrap();
b.reserve(buf.len() + msg.len() + 2);
b.extend_from_slice(buf.as_ref());
b.extend_from_slice(msg);
b.extend_from_slice(b"\r\n");
}
Ok(*eof)
}
TransferEncodingKind::Length(ref mut remaining) => {
if *remaining > 0 {
if msg.is_empty() {
return Ok(*remaining == 0);
}
let len = cmp::min(*remaining, msg.len() as u64);
self.buf
.as_mut()
.unwrap()
.extend_from_slice(&msg[..len as usize]);
*remaining -= len as u64;
Ok(*remaining == 0)
} else {
Ok(true)
}
}
}
}
/// Encode eof. Return `EOF` state of encoder
#[inline]
pub fn encode_eof(&mut self) -> bool {
match self.kind {
TransferEncodingKind::Eof => true,
TransferEncodingKind::Length(rem) => rem == 0,
TransferEncodingKind::Chunked(ref mut eof) => {
if !*eof {
*eof = true;
self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n");
}
true
}
}
}
}
impl io::Write for TransferEncoding {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.buf.is_some() {
self.encode(buf)?;
}
Ok(buf.len())
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
struct AcceptEncoding {
encoding: ContentEncoding,
quality: f64,
}
impl Eq for AcceptEncoding {}
impl Ord for AcceptEncoding {
fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering {
if self.quality > other.quality {
cmp::Ordering::Less
} else if self.quality < other.quality {
cmp::Ordering::Greater
} else {
cmp::Ordering::Equal
}
}
}
impl PartialOrd for AcceptEncoding {
fn partial_cmp(&self, other: &AcceptEncoding) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for AcceptEncoding {
fn eq(&self, other: &AcceptEncoding) -> bool {
self.quality == other.quality
}
}
impl AcceptEncoding {
fn new(tag: &str) -> Option<AcceptEncoding> {
let parts: Vec<&str> = tag.split(';').collect();
let encoding = match parts.len() {
0 => return None,
_ => ContentEncoding::from(parts[0]),
};
let quality = match parts.len() {
1 => encoding.quality(),
_ => match f64::from_str(parts[1]) {
Ok(q) => q,
Err(_) => 0.0,
},
};
Some(AcceptEncoding { encoding, quality })
}
/// Parse a raw Accept-Encoding header value into an ordered list.
pub fn parse(raw: &str) -> ContentEncoding {
let mut encodings: Vec<_> = raw
.replace(' ', "")
.split(',')
.map(|l| AcceptEncoding::new(l))
.collect();
encodings.sort();
for enc in encodings {
if let Some(enc) = enc {
return enc.encoding;
}
}
ContentEncoding::Identity
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
#[test]
fn test_chunked_te() {
let bytes = BytesMut::new();
let mut enc = TransferEncoding::chunked(bytes);
{
assert!(!enc.encode(b"test").ok().unwrap());
assert!(enc.encode(b"").ok().unwrap());
}
assert_eq!(
enc.buf_mut().take().freeze(),
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")
);
}
}

272
src/server/service.rs Normal file
View File

@ -0,0 +1,272 @@
use std::marker::PhantomData;
use std::time::Duration;
use actix_net::service::{NewService, Service};
use futures::future::{ok, FutureResult};
use futures::{Async, Poll};
use super::channel::{H1Channel, HttpChannel};
use super::error::HttpDispatchError;
use super::handler::HttpHandler;
use super::settings::ServiceConfig;
use super::IoStream;
/// `NewService` implementation for HTTP1/HTTP2 transports
pub struct HttpService<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
settings: ServiceConfig<H>,
_t: PhantomData<Io>,
}
impl<H, Io> HttpService<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
/// Create new `HttpService` instance.
pub fn new(settings: ServiceConfig<H>) -> Self {
HttpService {
settings,
_t: PhantomData,
}
}
}
impl<H, Io> NewService for HttpService<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
type Request = Io;
type Response = ();
type Error = HttpDispatchError;
type InitError = ();
type Service = HttpServiceHandler<H, Io>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self) -> Self::Future {
ok(HttpServiceHandler::new(self.settings.clone()))
}
}
pub struct HttpServiceHandler<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
settings: ServiceConfig<H>,
_t: PhantomData<Io>,
}
impl<H, Io> HttpServiceHandler<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
fn new(settings: ServiceConfig<H>) -> HttpServiceHandler<H, Io> {
HttpServiceHandler {
settings,
_t: PhantomData,
}
}
}
impl<H, Io> Service for HttpServiceHandler<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
type Request = Io;
type Response = ();
type Error = HttpDispatchError;
type Future = HttpChannel<Io, H>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: Self::Request) -> Self::Future {
HttpChannel::new(self.settings.clone(), req)
}
}
/// `NewService` implementation for HTTP1 transport
pub struct H1Service<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
settings: ServiceConfig<H>,
_t: PhantomData<Io>,
}
impl<H, Io> H1Service<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
/// Create new `HttpService` instance.
pub fn new(settings: ServiceConfig<H>) -> Self {
H1Service {
settings,
_t: PhantomData,
}
}
}
impl<H, Io> NewService for H1Service<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
type Request = Io;
type Response = ();
type Error = HttpDispatchError;
type InitError = ();
type Service = H1ServiceHandler<H, Io>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self) -> Self::Future {
ok(H1ServiceHandler::new(self.settings.clone()))
}
}
/// `Service` implementation for HTTP1 transport
pub struct H1ServiceHandler<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
settings: ServiceConfig<H>,
_t: PhantomData<Io>,
}
impl<H, Io> H1ServiceHandler<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
fn new(settings: ServiceConfig<H>) -> H1ServiceHandler<H, Io> {
H1ServiceHandler {
settings,
_t: PhantomData,
}
}
}
impl<H, Io> Service for H1ServiceHandler<H, Io>
where
H: HttpHandler,
Io: IoStream,
{
type Request = Io;
type Response = ();
type Error = HttpDispatchError;
type Future = H1Channel<Io, H>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: Self::Request) -> Self::Future {
H1Channel::new(self.settings.clone(), req)
}
}
/// `NewService` implementation for stream configuration service
///
/// Stream configuration service allows to change some socket level
/// parameters. for example `tcp nodelay` or `tcp keep-alive`.
pub struct StreamConfiguration<T, E> {
no_delay: Option<bool>,
tcp_ka: Option<Option<Duration>>,
_t: PhantomData<(T, E)>,
}
impl<T, E> Default for StreamConfiguration<T, E> {
fn default() -> Self {
Self::new()
}
}
impl<T, E> StreamConfiguration<T, E> {
/// Create new `StreamConfigurationService` instance.
pub fn new() -> Self {
Self {
no_delay: None,
tcp_ka: None,
_t: PhantomData,
}
}
/// Sets the value of the `TCP_NODELAY` option on this socket.
pub fn nodelay(mut self, nodelay: bool) -> Self {
self.no_delay = Some(nodelay);
self
}
/// Sets whether keepalive messages are enabled to be sent on this socket.
pub fn tcp_keepalive(mut self, keepalive: Option<Duration>) -> Self {
self.tcp_ka = Some(keepalive);
self
}
}
impl<T: IoStream, E> NewService for StreamConfiguration<T, E> {
type Request = T;
type Response = T;
type Error = E;
type InitError = ();
type Service = StreamConfigurationService<T, E>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self) -> Self::Future {
ok(StreamConfigurationService {
no_delay: self.no_delay,
tcp_ka: self.tcp_ka,
_t: PhantomData,
})
}
}
/// Stream configuration service
///
/// Stream configuration service allows to change some socket level
/// parameters. for example `tcp nodelay` or `tcp keep-alive`.
pub struct StreamConfigurationService<T, E> {
no_delay: Option<bool>,
tcp_ka: Option<Option<Duration>>,
_t: PhantomData<(T, E)>,
}
impl<T, E> Service for StreamConfigurationService<T, E>
where
T: IoStream,
{
type Request = T;
type Response = T;
type Error = E;
type Future = FutureResult<T, E>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, mut req: Self::Request) -> Self::Future {
if let Some(no_delay) = self.no_delay {
if req.set_nodelay(no_delay).is_err() {
error!("Can not set socket no-delay option");
}
}
if let Some(keepalive) = self.tcp_ka {
if req.set_keepalive(keepalive).is_err() {
error!("Can not set socket keep-alive option");
}
}
ok(req)
}
}

View File

@ -1,70 +1,74 @@
use bytes::BytesMut; use std::cell::{Cell, RefCell};
use futures_cpupool::{Builder, CpuPool}; use std::collections::VecDeque;
use http::StatusCode;
use std::cell::{Cell, RefCell, RefMut, UnsafeCell};
use std::fmt::Write; use std::fmt::Write;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::time::{Duration, Instant};
use std::{fmt, mem, net}; use std::{env, fmt, net};
use time;
use super::channel::Node; use bytes::BytesMut;
use super::helpers; use futures::{future, Future};
use super::shared::{SharedBytes, SharedBytesPool}; use futures_cpupool::CpuPool;
use http::StatusCode;
use lazycell::LazyCell;
use parking_lot::Mutex;
use time;
use tokio_current_thread::spawn;
use tokio_timer::{sleep, Delay};
use super::message::{Request, RequestPool};
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
#[derive(Clone)]
pub struct ServerSettings { pub struct ServerSettings {
addr: Option<net::SocketAddr>, addr: 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 {} impl Clone for ServerSettings {
unsafe impl Send for ServerSettings {} fn clone(&self) -> Self {
ServerSettings {
struct InnerCpuPool { addr: self.addr,
cpu_pool: UnsafeCell<Option<CpuPool>>, secure: self.secure,
} host: self.host.clone(),
cpu_pool: LazyCell::new(),
impl fmt::Debug for InnerCpuPool { responses: HttpResponsePool::get_pool(),
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: "127.0.0.1:8080".parse().unwrap(),
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(),
} }
} }
} }
@ -72,17 +76,11 @@ impl Default for ServerSettings {
impl ServerSettings { impl ServerSettings {
/// Crate server settings instance /// Crate server settings instance
pub(crate) fn new( pub(crate) fn new(
addr: Option<net::SocketAddr>, host: &Option<String>, secure: bool, addr: net::SocketAddr, host: &str, secure: bool,
) -> ServerSettings { ) -> ServerSettings {
let host = if let Some(ref host) = *host { let host = host.to_owned();
host.clone() let cpu_pool = LazyCell::new();
} else if let Some(ref addr) = addr { let responses = HttpResponsePool::get_pool();
format!("{}", addr)
} else {
"localhost".to_owned()
};
let cpu_pool = Arc::new(InnerCpuPool::new());
let responses = HttpResponsePool::pool();
ServerSettings { ServerSettings {
addr, addr,
secure, secure,
@ -93,7 +91,7 @@ impl ServerSettings {
} }
/// 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) -> net::SocketAddr {
self.addr self.addr
} }
@ -109,7 +107,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]
@ -128,99 +126,294 @@ impl ServerSettings {
// "Sun, 06 Nov 1994 08:49:37 GMT".len() // "Sun, 06 Nov 1994 08:49:37 GMT".len()
const DATE_VALUE_LENGTH: usize = 29; const DATE_VALUE_LENGTH: usize = 29;
pub(crate) struct WorkerSettings<H> { /// Http service configuration
h: RefCell<Vec<H>>, pub struct ServiceConfig<H>(Rc<Inner<H>>);
keep_alive: u64,
struct Inner<H> {
handler: H,
keep_alive: Option<Duration>,
client_timeout: u64,
client_shutdown: u64,
ka_enabled: bool, ka_enabled: bool,
bytes: Rc<SharedBytesPool>, bytes: Rc<SharedBytesPool>,
messages: Rc<helpers::SharedMessagePool>, messages: &'static RequestPool,
channels: Cell<usize>, date: Cell<Option<Date>>,
node: Box<Node<()>>,
date: UnsafeCell<Date>,
} }
impl<H> WorkerSettings<H> { impl<H> Clone for ServiceConfig<H> {
pub(crate) fn new(h: Vec<H>, keep_alive: KeepAlive) -> WorkerSettings<H> { fn clone(&self) -> Self {
ServiceConfig(self.0.clone())
}
}
impl<H> ServiceConfig<H> {
/// Create instance of `ServiceConfig`
pub(crate) fn new(
handler: H, keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64,
settings: ServerSettings,
) -> ServiceConfig<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),
KeepAlive::Disabled => (0, false), KeepAlive::Disabled => (0, false),
}; };
let keep_alive = if ka_enabled && keep_alive > 0 {
Some(Duration::from_secs(keep_alive))
} else {
None
};
WorkerSettings { ServiceConfig(Rc::new(Inner {
handler,
keep_alive, keep_alive,
ka_enabled, ka_enabled,
h: RefCell::new(h), client_timeout,
client_shutdown,
bytes: Rc::new(SharedBytesPool::new()), bytes: Rc::new(SharedBytesPool::new()),
messages: Rc::new(helpers::SharedMessagePool::new()), messages: RequestPool::pool(settings),
channels: Cell::new(0), date: Cell::new(None),
node: Box::new(Node::head()), }))
date: UnsafeCell::new(Date::new()),
}
} }
pub fn num_channels(&self) -> usize { /// Create worker settings builder.
self.channels.get() pub fn build(handler: H) -> ServiceConfigBuilder<H> {
ServiceConfigBuilder::new(handler)
} }
pub fn head(&self) -> &Node<()> { pub(crate) fn handler(&self) -> &H {
&self.node &self.0.handler
} }
pub fn handlers(&self) -> RefMut<Vec<H>> { #[inline]
self.h.borrow_mut() /// Keep alive duration if configured.
} pub fn keep_alive(&self) -> Option<Duration> {
self.0.keep_alive
pub fn keep_alive(&self) -> u64 {
self.keep_alive
} }
#[inline]
/// Return state of connection keep-alive funcitonality
pub fn keep_alive_enabled(&self) -> bool { pub fn keep_alive_enabled(&self) -> bool {
self.ka_enabled self.0.ka_enabled
} }
pub fn get_shared_bytes(&self) -> SharedBytes { pub(crate) fn get_bytes(&self) -> BytesMut {
SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes)) self.0.bytes.get_bytes()
} }
pub fn get_http_message(&self) -> helpers::SharedHttpInnerMessage { pub(crate) fn release_bytes(&self, bytes: BytesMut) {
helpers::SharedHttpInnerMessage::new( self.0.bytes.release_bytes(bytes)
self.messages.get(),
Rc::clone(&self.messages),
)
} }
pub fn add_channel(&self) { pub(crate) fn get_request(&self) -> Request {
self.channels.set(self.channels.get() + 1); RequestPool::get(self.0.messages)
}
pub fn remove_channel(&self) {
let num = self.channels.get();
if num > 0 {
self.channels.set(num - 1);
} else {
error!("Number of removed channels is bigger than added channel. Bug in actix-web");
}
}
pub fn update_date(&self) {
unsafe { &mut *self.date.get() }.update();
}
pub fn set_date(&self, dst: &mut BytesMut) {
let mut buf: [u8; 39] = unsafe { mem::uninitialized() };
buf[..6].copy_from_slice(b"date: ");
buf[6..35].copy_from_slice(&(unsafe { &*self.date.get() }.bytes));
buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf);
}
pub fn set_date_simple(&self, dst: &mut BytesMut) {
dst.extend_from_slice(&(unsafe { &*self.date.get() }.bytes));
} }
} }
impl<H: 'static> ServiceConfig<H> {
#[inline]
/// Client timeout for first request.
pub fn client_timer(&self) -> Option<Delay> {
let delay = self.0.client_timeout;
if delay != 0 {
Some(Delay::new(self.now() + Duration::from_millis(delay)))
} else {
None
}
}
/// Client timeout for first request.
pub fn client_timer_expire(&self) -> Option<Instant> {
let delay = self.0.client_timeout;
if delay != 0 {
Some(self.now() + Duration::from_millis(delay))
} else {
None
}
}
/// Client shutdown timer
pub fn client_shutdown_timer(&self) -> Option<Instant> {
let delay = self.0.client_shutdown;
if delay != 0 {
Some(self.now() + Duration::from_millis(delay))
} else {
None
}
}
#[inline]
/// Return keep-alive timer delay is configured.
pub fn keep_alive_timer(&self) -> Option<Delay> {
if let Some(ka) = self.0.keep_alive {
Some(Delay::new(self.now() + ka))
} else {
None
}
}
/// Keep-alive expire time
pub fn keep_alive_expire(&self) -> Option<Instant> {
if let Some(ka) = self.0.keep_alive {
Some(self.now() + ka)
} else {
None
}
}
fn check_date(&self) {
if unsafe { &*self.0.date.as_ptr() }.is_none() {
self.0.date.set(Some(Date::new()));
// periodic date update
let s = self.clone();
spawn(sleep(Duration::from_millis(500)).then(move |_| {
s.0.date.set(None);
future::ok(())
}));
}
}
pub(crate) fn set_date(&self, dst: &mut BytesMut, full: bool) {
self.check_date();
let date = &unsafe { &*self.0.date.as_ptr() }.as_ref().unwrap().bytes;
if full {
let mut buf: [u8; 39] = [0; 39];
buf[..6].copy_from_slice(b"date: ");
buf[6..35].copy_from_slice(date);
buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf);
} else {
dst.extend_from_slice(date);
}
}
#[inline]
pub(crate) fn now(&self) -> Instant {
self.check_date();
unsafe { &*self.0.date.as_ptr() }.as_ref().unwrap().current
}
}
/// A service config builder
///
/// This type can be used to construct an instance of `ServiceConfig` through a
/// builder-like pattern.
pub struct ServiceConfigBuilder<H> {
handler: H,
keep_alive: KeepAlive,
client_timeout: u64,
client_shutdown: u64,
host: String,
addr: net::SocketAddr,
secure: bool,
}
impl<H> ServiceConfigBuilder<H> {
/// Create instance of `ServiceConfigBuilder`
pub fn new(handler: H) -> ServiceConfigBuilder<H> {
ServiceConfigBuilder {
handler,
keep_alive: KeepAlive::Timeout(5),
client_timeout: 5000,
client_shutdown: 5000,
secure: false,
host: "localhost".to_owned(),
addr: "127.0.0.1:8080".parse().unwrap(),
}
}
/// Enable secure flag for current server.
///
/// By default this flag is set to false.
pub fn secure(mut self) -> Self {
self.secure = true;
self
}
/// Set server keep-alive setting.
///
/// By default keep alive is set to a 5 seconds.
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
self.keep_alive = val.into();
self
}
/// Set server client timeout in milliseconds for first request.
///
/// Defines a timeout for reading client request header. If a client does not transmit
/// the entire set headers within this time, the request is terminated with
/// the 408 (Request Time-out) error.
///
/// To disable timeout set value to 0.
///
/// By default client timeout is set to 5000 milliseconds.
pub fn client_timeout(mut self, val: u64) -> Self {
self.client_timeout = val;
self
}
/// Set server connection shutdown timeout in milliseconds.
///
/// Defines a timeout for shutdown connection. If a shutdown procedure does not complete
/// within this time, the request is dropped. This timeout affects only secure connections.
///
/// To disable timeout set value to 0.
///
/// By default client timeout is set to 5000 milliseconds.
pub fn client_shutdown(mut self, val: u64) -> Self {
self.client_shutdown = val;
self
}
/// Set server host name.
///
/// Host name is used by application router aa a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
///
/// By default host name is set to a "localhost" value.
pub fn server_hostname(mut self, val: &str) -> Self {
self.host = val.to_owned();
self
}
/// Set server ip address.
///
/// Host name is used by application router aa a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
///
/// By default server address is set to a "127.0.0.1:8080"
pub fn server_address<S: net::ToSocketAddrs>(mut self, addr: S) -> Self {
match addr.to_socket_addrs() {
Err(err) => error!("Can not convert to SocketAddr: {}", err),
Ok(mut addrs) => if let Some(addr) = addrs.next() {
self.addr = addr;
},
}
self
}
/// Finish service configuration and create `ServiceConfig` object.
pub fn finish(self) -> ServiceConfig<H> {
let settings = ServerSettings::new(self.addr, &self.host, self.secure);
let client_shutdown = if self.secure { self.client_shutdown } else { 0 };
ServiceConfig::new(
self.handler,
self.keep_alive,
self.client_timeout,
client_shutdown,
settings,
)
}
}
#[derive(Copy, Clone)]
struct Date { struct Date {
current: Instant,
bytes: [u8; DATE_VALUE_LENGTH], bytes: [u8; DATE_VALUE_LENGTH],
pos: usize, pos: usize,
} }
@ -228,6 +421,7 @@ struct Date {
impl Date { impl Date {
fn new() -> Date { fn new() -> Date {
let mut date = Date { let mut date = Date {
current: Instant::now(),
bytes: [0; DATE_VALUE_LENGTH], bytes: [0; DATE_VALUE_LENGTH],
pos: 0, pos: 0,
}; };
@ -236,6 +430,7 @@ impl Date {
} }
fn update(&mut self) { fn update(&mut self) {
self.pos = 0; self.pos = 0;
self.current = Instant::now();
write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap(); write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap();
} }
} }
@ -249,25 +444,60 @@ 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::*;
use futures::future;
use tokio::runtime::current_thread;
#[test] #[test]
fn test_date_len() { fn test_date_len() {
assert_eq!( assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
DATE_VALUE_LENGTH,
"Sun, 06 Nov 1994 08:49:37 GMT".len()
);
} }
#[test] #[test]
fn test_date() { fn test_date() {
let settings = WorkerSettings::<()>::new(Vec::new(), KeepAlive::Os); let mut rt = current_thread::Runtime::new().unwrap();
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf1); let _ = rt.block_on(future::lazy(|| {
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); let settings = ServiceConfig::<()>::new(
settings.set_date(&mut buf2); (),
assert_eq!(buf1, buf2); KeepAlive::Os,
0,
0,
ServerSettings::default(),
);
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf1, true);
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf2, true);
assert_eq!(buf1, buf2);
future::ok::<_, ()>(())
}));
} }
} }

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

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

12
src/server/ssl/mod.rs Normal file
View File

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

View File

@ -0,0 +1,34 @@
use std::net::{Shutdown, SocketAddr};
use std::{io, time};
use actix_net::ssl::TlsStream;
use server::IoStream;
impl<Io: IoStream> IoStream for TlsStream<Io> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn peer_addr(&self) -> Option<SocketAddr> {
self.get_ref().get_ref().peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_keepalive(dur)
}
}

87
src/server/ssl/openssl.rs Normal file
View File

@ -0,0 +1,87 @@
use std::net::{Shutdown, SocketAddr};
use std::{io, time};
use actix_net::ssl;
use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_openssl::SslStream;
use server::{IoStream, ServerFlags};
/// Support `SSL` connections via openssl package
///
/// `ssl` feature enables `OpensslAcceptor` type
pub struct OpensslAcceptor<T> {
_t: ssl::OpensslAcceptor<T>,
}
impl<T: AsyncRead + AsyncWrite> OpensslAcceptor<T> {
/// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support.
pub fn new(builder: SslAcceptorBuilder) -> io::Result<ssl::OpensslAcceptor<T>> {
OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2)
}
/// Create `OpensslAcceptor` with custom server flags.
pub fn with_flags(
builder: SslAcceptorBuilder, flags: ServerFlags,
) -> io::Result<ssl::OpensslAcceptor<T>> {
let acceptor = openssl_acceptor_with_flags(builder, flags)?;
Ok(ssl::OpensslAcceptor::new(acceptor))
}
}
/// Configure `SslAcceptorBuilder` with custom server flags.
pub fn openssl_acceptor_with_flags(
mut builder: SslAcceptorBuilder, flags: ServerFlags,
) -> io::Result<SslAcceptor> {
let mut protos = Vec::new();
if flags.contains(ServerFlags::HTTP1) {
protos.extend(b"\x08http/1.1");
}
if flags.contains(ServerFlags::HTTP2) {
protos.extend(b"\x02h2");
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";
if protos.windows(3).any(|window| window == H2) {
Ok(b"h2")
} else {
Err(AlpnError::NOACK)
}
});
}
if !protos.is_empty() {
builder.set_alpn_protos(&protos)?;
}
Ok(builder.build())
}
impl<T: IoStream> IoStream for SslStream<T> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn peer_addr(&self) -> Option<SocketAddr> {
self.get_ref().get_ref().peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_keepalive(dur)
}
}

87
src/server/ssl/rustls.rs Normal file
View File

@ -0,0 +1,87 @@
use std::net::{Shutdown, SocketAddr};
use std::{io, time};
use actix_net::ssl; //::RustlsAcceptor;
use rustls::{ClientSession, ServerConfig, ServerSession};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_rustls::TlsStream;
use server::{IoStream, ServerFlags};
/// Support `SSL` connections via rustls package
///
/// `rust-tls` feature enables `RustlsAcceptor` type
pub struct RustlsAcceptor<T> {
_t: ssl::RustlsAcceptor<T>,
}
impl<T: AsyncRead + AsyncWrite> RustlsAcceptor<T> {
/// Create `RustlsAcceptor` with custom server flags.
pub fn with_flags(
mut config: ServerConfig, flags: ServerFlags,
) -> ssl::RustlsAcceptor<T> {
let mut protos = Vec::new();
if flags.contains(ServerFlags::HTTP2) {
protos.push("h2".to_string());
}
if flags.contains(ServerFlags::HTTP1) {
protos.push("http/1.1".to_string());
}
if !protos.is_empty() {
config.set_protocols(&protos);
}
ssl::RustlsAcceptor::new(config)
}
}
impl<Io: IoStream> IoStream for TlsStream<Io, ClientSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_keepalive(dur)
}
}
impl<Io: IoStream> IoStream for TlsStream<Io, ServerSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}
#[inline]
fn peer_addr(&self) -> Option<SocketAddr> {
self.get_ref().0.peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_keepalive(dur)
}
}

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,242 +0,0 @@
use futures::unsync::oneshot;
use futures::Future;
use net2::TcpStreamExt;
use slab::Slab;
use std::rc::Rc;
use std::{net, time};
use tokio_core::net::TcpStream;
use tokio_core::reactor::Handle;
#[cfg(any(feature = "tls", feature = "alpn"))]
use futures::future;
#[cfg(feature = "tls")]
use native_tls::TlsAcceptor;
#[cfg(feature = "tls")]
use tokio_tls::TlsAcceptorExt;
#[cfg(feature = "alpn")]
use openssl::ssl::SslAcceptor;
#[cfg(feature = "alpn")]
use tokio_openssl::SslAcceptorExt;
use actix::msgs::StopArbiter;
use actix::*;
use server::channel::HttpChannel;
use server::settings::WorkerSettings;
use server::{HttpHandler, KeepAlive};
#[derive(Message)]
pub(crate) struct Conn<T> {
pub io: T,
pub token: usize,
pub peer: Option<net::SocketAddr>,
pub http2: bool,
}
#[derive(Clone)]
pub(crate) struct SocketInfo {
pub addr: net::SocketAddr,
pub htype: StreamHandlerType,
}
/// Stop worker message. Returns `true` on successful shutdown
/// and `false` if some connections still alive.
pub(crate) struct StopWorker {
pub graceful: Option<time::Duration>,
}
impl Message for StopWorker {
type Result = Result<bool, ()>;
}
/// Http worker
///
/// Worker accepts Socket objects via unbounded channel and start requests
/// processing.
pub(crate) struct Worker<H>
where
H: HttpHandler + 'static,
{
settings: Rc<WorkerSettings<H>>,
hnd: Handle,
socks: Slab<SocketInfo>,
tcp_ka: Option<time::Duration>,
}
impl<H: HttpHandler + 'static> Worker<H> {
pub(crate) fn new(
h: Vec<H>, socks: Slab<SocketInfo>, keep_alive: KeepAlive,
) -> Worker<H> {
let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive {
Some(time::Duration::new(val as u64, 0))
} else {
None
};
Worker {
settings: Rc::new(WorkerSettings::new(h, keep_alive)),
hnd: Arbiter::handle().clone(),
socks,
tcp_ka,
}
}
fn update_time(&self, ctx: &mut Context<Self>) {
self.settings.update_date();
ctx.run_later(time::Duration::new(1, 0), |slf, ctx| {
slf.update_time(ctx)
});
}
fn shutdown_timeout(
&self, ctx: &mut Context<Self>, tx: oneshot::Sender<bool>, dur: time::Duration,
) {
// sleep for 1 second and then check again
ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| {
let num = slf.settings.num_channels();
if num == 0 {
let _ = tx.send(true);
Arbiter::arbiter().do_send(StopArbiter(0));
} else if let Some(d) = dur.checked_sub(time::Duration::new(1, 0)) {
slf.shutdown_timeout(ctx, tx, d);
} else {
info!("Force shutdown http worker, {} connections", num);
slf.settings.head().traverse::<TcpStream, H>();
let _ = tx.send(false);
Arbiter::arbiter().do_send(StopArbiter(0));
}
});
}
}
impl<H: 'static> Actor for Worker<H>
where
H: HttpHandler + 'static,
{
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
self.update_time(ctx);
}
}
impl<H> Handler<Conn<net::TcpStream>> for Worker<H>
where
H: HttpHandler + 'static,
{
type Result = ();
fn handle(&mut self, msg: Conn<net::TcpStream>, _: &mut Context<Self>) {
if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() {
error!("Can not set socket keep-alive option");
}
self.socks.get_mut(msg.token).unwrap().htype.handle(
Rc::clone(&self.settings),
&self.hnd,
msg,
);
}
}
/// `StopWorker` message handler
impl<H> Handler<StopWorker> for Worker<H>
where
H: HttpHandler + 'static,
{
type Result = Response<bool, ()>;
fn handle(&mut self, msg: StopWorker, ctx: &mut Context<Self>) -> Self::Result {
let num = self.settings.num_channels();
if num == 0 {
info!("Shutting down http worker, 0 connections");
Response::reply(Ok(true))
} else if let Some(dur) = msg.graceful {
info!("Graceful http worker shutdown, {} connections", num);
let (tx, rx) = oneshot::channel();
self.shutdown_timeout(ctx, tx, dur);
Response::async(rx.map_err(|_| ()))
} else {
info!("Force shutdown http worker, {} connections", num);
self.settings.head().traverse::<TcpStream, H>();
Response::reply(Ok(false))
}
}
}
#[derive(Clone)]
pub(crate) enum StreamHandlerType {
Normal,
#[cfg(feature = "tls")]
Tls(TlsAcceptor),
#[cfg(feature = "alpn")]
Alpn(SslAcceptor),
}
impl StreamHandlerType {
fn handle<H: HttpHandler>(
&mut self, h: Rc<WorkerSettings<H>>, hnd: &Handle, msg: Conn<net::TcpStream>,
) {
match *self {
StreamHandlerType::Normal => {
let _ = msg.io.set_nodelay(true);
let io = TcpStream::from_stream(msg.io, hnd)
.expect("failed to associate TCP stream");
hnd.spawn(HttpChannel::new(h, io, msg.peer, msg.http2));
}
#[cfg(feature = "tls")]
StreamHandlerType::Tls(ref acceptor) => {
let Conn {
io, peer, http2, ..
} = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream");
hnd.spawn(
TlsAcceptorExt::accept_async(acceptor, io).then(move |res| {
match res {
Ok(io) => Arbiter::handle()
.spawn(HttpChannel::new(h, io, peer, http2)),
Err(err) => {
trace!("Error during handling tls connection: {}", err)
}
};
future::result(Ok(()))
}),
);
}
#[cfg(feature = "alpn")]
StreamHandlerType::Alpn(ref acceptor) => {
let Conn { io, peer, .. } = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream");
hnd.spawn(
SslAcceptorExt::accept_async(acceptor, io).then(move |res| {
match res {
Ok(io) => {
let http2 = if let Some(p) =
io.get_ref().ssl().selected_alpn_protocol()
{
p.len() == 2 && &p == b"h2"
} else {
false
};
Arbiter::handle()
.spawn(HttpChannel::new(h, io, peer, http2));
}
Err(err) => {
trace!("Error during handling tls connection: {}", err)
}
};
future::result(Ok(()))
}),
);
}
}
}
}

View File

@ -1,36 +1,39 @@
//! 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(any(feature = "alpn", feature = "ssl"))]
use openssl::ssl::SslAcceptor; use openssl::ssl::SslAcceptorBuilder;
#[cfg(feature = "rust-tls")]
use rustls::ServerConfig;
use application::{App, HttpApplication}; use application::{App, HttpApplication};
use body::Binary; use body::Binary;
use client::{ClientConnector, ClientRequest, ClientRequestBuilder}; use client::{ClientConnector, ClientRequest, ClientRequestBuilder};
use error::Error; use error::Error;
use handler::{AsyncResultItem, Handler, Responder}; use handler::{AsyncResult, AsyncResultItem, Handler, Responder};
use header::{Header, IntoHeaderValue}; use header::{Header, IntoHeaderValue};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; 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 +44,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 +63,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 {
@ -75,13 +75,13 @@ impl TestServer {
/// middlewares or set handlers for test application. /// middlewares or set handlers for test application.
pub fn new<F>(config: F) -> Self pub fn new<F>(config: F) -> Self
where where
F: Sync + Send + 'static + Fn(&mut TestApp<()>), F: Clone + Send + 'static + Fn(&mut TestApp<()>),
{ {
TestServerBuilder::new(|| ()).start(config) TestServerBuilder::new(|| ()).start(config)
} }
/// Create test server builder /// Create test server builder
pub fn build() -> TestServerBuilder<()> { pub fn build() -> TestServerBuilder<(), impl Fn() -> () + Clone + Send + 'static> {
TestServerBuilder::new(|| ()) TestServerBuilder::new(|| ())
} }
@ -90,53 +90,51 @@ impl TestServer {
/// This method can be used for constructing application state. /// This method can be used for constructing application state.
/// Also it can be used for external dependency initialization, /// Also it can be used for external dependency initialization,
/// like creating sync actors for diesel integration. /// like creating sync actors for diesel integration.
pub fn build_with_state<F, S>(state: F) -> TestServerBuilder<S> pub fn build_with_state<S, F>(state: F) -> TestServerBuilder<S, F>
where where
F: Fn() -> S + Sync + Send + 'static, F: Fn() -> S + Clone + Send + 'static,
S: 'static, S: 'static,
{ {
TestServerBuilder::new(state) TestServerBuilder::new(state)
} }
/// Start new test server with application factory /// Start new test server with application factory
pub fn with_factory<F, U, H>(factory: F) -> Self pub fn with_factory<F, H>(factory: F) -> Self
where where
F: Fn() -> U + Sync + Send + 'static, F: Fn() -> H + Send + Clone + 'static,
U: IntoIterator<Item = H> + 'static,
H: IntoHttpHandler + 'static, H: IntoHttpHandler + 'static,
{ {
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) let _ = HttpServer::new(factory)
.disable_signals() .disable_signals()
.start_incoming(tcp.incoming(), false); .listen(tcp)
.keep_alive(5)
.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(any(feature = "alpn", feature = "ssl"))]
{ {
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
@ -144,7 +142,20 @@ impl TestServer {
builder.set_verify(SslVerifyMode::NONE); builder.set_verify(SslVerifyMode::NONE);
ClientConnector::with_connector(builder.build()).start() ClientConnector::with_connector(builder.build()).start()
} }
#[cfg(not(feature = "alpn"))] #[cfg(all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "ssl"))
))]
{
use rustls::ClientConfig;
use std::fs::File;
use std::io::BufReader;
let mut config = ClientConfig::new();
let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
config.root_store.add_pem_file(pem_file).unwrap();
ClientConnector::with_connector(config).start()
}
#[cfg(not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")))]
{ {
ClientConnector::default().start() ClientConnector::default().start()
} }
@ -169,16 +180,16 @@ impl TestServer {
pub fn url(&self, uri: &str) -> String { pub fn url(&self, uri: &str) -> String {
if uri.starts_with('/') { if uri.starts_with('/') {
format!( format!(
"{}://{}{}", "{}://localhost:{}{}",
if self.ssl { "https" } else { "http" }, if self.ssl { "https" } else { "http" },
self.addr, self.addr.port(),
uri uri
) )
} else { } else {
format!( format!(
"{}://{}/{}", "{}://localhost:{}/{}",
if self.ssl { "https" } else { "http" }, if self.ssl { "https" } else { "http" },
self.addr, self.addr.port(),
uri uri
) )
} }
@ -186,10 +197,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,17 +205,23 @@ 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 at a given path
pub fn ws_at(
&mut self, path: &str,
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
let url = self.url(path);
self.rt
.block_on(ws::Client::with_connector(url, self.conn.clone()).connect())
}
/// Connect to a websocket server
pub fn ws( pub fn ws(
&mut self, &mut self,
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
let url = self.url("/"); self.ws_at("/")
self.system.run_until_complete(
ws::Client::with_connector(url, self.conn.clone()).connect(),
)
} }
/// Create `GET` request /// Create `GET` request
@ -217,7 +231,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
@ -245,99 +259,115 @@ impl Drop for TestServer {
/// ///
/// This type can be used to construct an instance of `TestServer` through a /// This type can be used to construct an instance of `TestServer` through a
/// builder-like pattern. /// builder-like pattern.
pub struct TestServerBuilder<S> { pub struct TestServerBuilder<S, F>
state: Box<Fn() -> S + Sync + Send + 'static>, where
#[cfg(feature = "alpn")] F: Fn() -> S + Send + Clone + 'static,
ssl: Option<SslAcceptor>, {
state: F,
#[cfg(any(feature = "alpn", feature = "ssl"))]
ssl: Option<SslAcceptorBuilder>,
#[cfg(feature = "rust-tls")]
rust_ssl: Option<ServerConfig>,
} }
impl<S: 'static> TestServerBuilder<S> { impl<S: 'static, F> TestServerBuilder<S, F>
pub fn new<F>(state: F) -> TestServerBuilder<S> where
where F: Fn() -> S + Send + Clone + 'static,
F: Fn() -> S + Sync + Send + 'static, {
{ /// Create a new test server
pub fn new(state: F) -> TestServerBuilder<S, F> {
TestServerBuilder { TestServerBuilder {
state: Box::new(state), state,
#[cfg(feature = "alpn")] #[cfg(any(feature = "alpn", feature = "ssl"))]
ssl: None, ssl: None,
#[cfg(feature = "rust-tls")]
rust_ssl: None,
} }
} }
#[cfg(feature = "alpn")] #[cfg(any(feature = "alpn", feature = "ssl"))]
/// 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
} }
#[cfg(feature = "rust-tls")]
/// Create rust tls server
pub fn rustls(mut self, ssl: ServerConfig) -> Self {
self.rust_ssl = Some(ssl);
self
}
#[allow(unused_mut)] #[allow(unused_mut)]
/// Configure test application and run test server /// Configure test application and run test server
pub fn start<F>(mut self, config: F) -> TestServer pub fn start<C>(mut self, config: C) -> TestServer
where where
F: Sync + Send + 'static + Fn(&mut TestApp<S>), C: Fn(&mut TestApp<S>) + Clone + Send + 'static,
{ {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
#[cfg(feature = "alpn")] let mut has_ssl = false;
let ssl = self.ssl.is_some();
#[cfg(not(feature = "alpn"))] #[cfg(any(feature = "alpn", feature = "ssl"))]
let ssl = false; {
has_ssl = has_ssl || self.ssl.is_some();
}
#[cfg(feature = "rust-tls")]
{
has_ssl = has_ssl || self.rust_ssl.is_some();
}
// run server in separate thread // run server in separate thread
let join = thread::spawn(move || { thread::spawn(move || {
let addr = TestServer::unused_addr();
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 local_addr = tcp.local_addr().unwrap();
let tcp =
TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap();
let state = self.state; let state = self.state;
let mut 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] app
}).disable_signals(); }).workers(1)
.keep_alive(5)
.disable_signals();
#[cfg(feature = "alpn")] tx.send((System::current(), addr, TestServer::get_conn()))
.unwrap();
#[cfg(any(feature = "alpn", feature = "ssl"))]
{ {
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( let tcp = net::TcpListener::bind(addr).unwrap();
tcp.incoming().and_then(move |(sock, addr)| { srv = srv.listen_ssl(tcp, ssl).unwrap();
ssl.accept_async(sock)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
.map(move |s| (s, addr))
}),
false,
);
} else {
srv.start_incoming(tcp.incoming(), false);
} }
} }
#[cfg(not(feature = "alpn"))] #[cfg(feature = "rust-tls")]
{ {
srv.start_incoming(tcp.incoming(), false); let ssl = self.rust_ssl.take();
if let Some(ssl) = ssl {
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen_rustls(tcp, ssl);
}
} }
if !has_ssl {
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen(tcp);
}
srv.start();
tx.send((Arbiter::system(), local_addr)).unwrap(); sys.run();
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, conn,
ssl, ssl: has_ssl,
system, rt: Runtime::new().unwrap(),
conn: TestServer::get_conn(),
thread: Some(join),
} }
} }
} }
@ -354,13 +384,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( where
self.app F: Fn(&HttpRequest<S>) -> R + 'static,
.take() R: Responder + 'static,
.unwrap() {
.resource("/", |r| r.h(handler)), self.app = Some(self.app.take().unwrap().resource("/", |r| r.f(handler)));
);
} }
/// Register middleware /// Register middleware
@ -376,7 +405,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
@ -386,8 +415,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()
} }
} }
@ -413,7 +442,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 {
@ -423,11 +452,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);
/// } /// }
/// ``` /// ```
@ -437,9 +466,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<()> {
@ -453,6 +483,7 @@ impl Default for TestRequest<()> {
params: Params::new(), params: Params::new(),
cookies: None, cookies: None,
payload: None, payload: None,
prefix: 0,
} }
} }
} }
@ -490,6 +521,7 @@ impl<S: 'static> TestRequest<S> {
params: Params::new(), params: Params::new(),
cookies: None, cookies: None,
payload: None, payload: None,
prefix: 0,
} }
} }
@ -537,7 +569,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
} }
@ -550,6 +582,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 {
@ -558,50 +596,103 @@ impl<S: 'static> TestRequest<S> {
uri, uri,
version, version,
headers, headers,
params, mut params,
cookies, cookies,
payload, payload,
prefix,
} = self; } = self;
let req = HttpRequest::new(method, uri, version, headers, payload); let router = Router::<()>::default();
req.as_mut().cookies = cookies;
req.as_mut().params = params; let pool = RequestPool::pool(ServerSettings::default());
let (router, _) = Router::new::<S>("/", ServerSettings::default(), Vec::new()); let mut req = RequestPool::get(pool);
req.with_state(Rc::new(state), router) {
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
} }
#[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 req = HttpRequest::new(method, uri, version, headers, payload); let pool = RequestPool::pool(ServerSettings::default());
req.as_mut().cookies = cookies; let mut req = RequestPool::get(pool);
req.as_mut().params = params; {
req.with_state(Rc::new(state), router) 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
}
/// 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.
/// pub fn run<H: Handler<S>>(self, h: &H) -> Result<HttpResponse, Error> {
/// This method panics is handler returns actor or async result.
pub fn run<H: Handler<S>>(self, mut h: H) -> Result<HttpResponse, Error> {
let req = self.finish(); let 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() {
AsyncResultItem::Ok(resp) => Ok(resp), AsyncResultItem::Ok(resp) => Ok(resp),
AsyncResultItem::Err(err) => Err(err), AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(_) => panic!("Async handler is not supported."), AsyncResultItem::Future(fut) => {
let mut sys = System::new("test");
sys.block_on(fut)
}
}, },
Err(err) => Err(err.into()), Err(err) => Err(err.into()),
} }
@ -621,8 +712,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 sys = System::new("test");
match core.run(fut) { match sys.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),
@ -633,4 +724,45 @@ impl<S: 'static> TestRequest<S> {
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
/// This method generates `HttpRequest` instance and executes handler
pub fn run_async_result<F, R, I, E>(self, f: F) -> Result<I, E>
where
F: FnOnce(&HttpRequest<S>) -> R,
R: Into<AsyncResult<I, E>>,
{
let req = self.finish();
let res = f(&req);
match res.into().into() {
AsyncResultItem::Ok(resp) => Ok(resp),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(fut) => {
let mut sys = System::new("test");
sys.block_on(fut)
}
}
}
/// This method generates `HttpRequest` instance and executes handler
pub fn execute<F, R>(self, f: F) -> Result<HttpResponse, Error>
where
F: FnOnce(&HttpRequest<S>) -> R,
R: Responder + 'static,
{
let req = self.finish();
let resp = f(&req);
match resp.respond_to(&req) {
Ok(resp) => match resp.into().into() {
AsyncResultItem::Ok(resp) => Ok(resp),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(fut) => {
let mut sys = System::new("test");
sys.block_on(fut)
}
},
Err(err) => Err(err.into()),
}
}
} }

View File

@ -1,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(Rc::new(unsafe { 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,84 +7,76 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> { trait FnWith<T, R>: 'static {
cfg: Rc<UnsafeCell<T::Config>>, fn call_with(self: &Self, T) -> R;
} }
impl<S: 'static, T: FromRequest<S>> Default for ExtractorConfig<S, T> { impl<T, R, F: Fn(T) -> R + 'static> FnWith<T, R> for F {
fn default() -> Self { fn call_with(self: &Self, arg: T) -> R {
ExtractorConfig { (*self)(arg)
cfg: Rc::new(UnsafeCell::new(T::Config::default())),
}
} }
} }
impl<S: 'static, T: FromRequest<S>> Clone for ExtractorConfig<S, T> { #[doc(hidden)]
fn clone(&self) -> Self { pub trait WithFactory<T, S, R>: 'static
ExtractorConfig { where
cfg: Rc::clone(&self.cfg), T: FromRequest<S>,
} R: Responder,
} {
} fn create(self) -> With<T, S, R>;
impl<S: 'static, T: FromRequest<S>> AsRef<T::Config> for ExtractorConfig<S, T> { fn create_with_config(self, T::Config) -> With<T, S, R>;
fn as_ref(&self) -> &T::Config { }
unsafe { &*self.cfg.get() }
} #[doc(hidden)]
} pub trait WithAsyncFactory<T, S, R, I, E>: 'static
where
impl<S: 'static, T: FromRequest<S>> Deref for ExtractorConfig<S, T> { T: FromRequest<S>,
type Target = T::Config; R: Future<Item = I, Error = E>,
I: Responder,
fn deref(&self) -> &T::Config { E: Into<Error>,
unsafe { &*self.cfg.get() } {
} fn create(self) -> WithAsync<T, S, R, I, E>;
}
fn create_with_config(self, T::Config) -> WithAsync<T, S, R, I, E>;
impl<S: 'static, T: FromRequest<S>> DerefMut for ExtractorConfig<S, T> { }
fn deref_mut(&mut self) -> &mut T::Config {
unsafe { &mut *self.cfg.get() } #[doc(hidden)]
} pub struct With<T, S, R>
}
pub struct With<T, S, F, R>
where where
F: Fn(T) -> R,
T: FromRequest<S>, T: FromRequest<S>,
S: 'static, S: 'static,
{ {
hnd: Rc<UnsafeCell<F>>, hnd: Rc<FnWith<T, R>>,
cfg: ExtractorConfig<S, T>, cfg: Rc<T::Config>,
_s: PhantomData<S>, _s: PhantomData<S>,
} }
impl<T, S, F, R> With<T, S, F, R> impl<T, S, R> With<T, S, R>
where where
F: Fn(T) -> R,
T: FromRequest<S>, T: FromRequest<S>,
S: 'static, S: 'static,
{ {
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self { pub fn new<F: Fn(T) -> R + 'static>(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,
} }
} }
} }
impl<T, S, F, R> Handler<S> for With<T, S, F, R> impl<T, S, R> Handler<S> for With<T, S, R>
where where
F: Fn(T) -> R + 'static,
R: Responder + 'static, R: Responder + 'static,
T: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
S: 'static, S: 'static,
{ {
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(),
@ -102,24 +92,22 @@ where
} }
} }
struct WithHandlerFut<T, S, F, R> struct WithHandlerFut<T, S, R>
where where
F: Fn(T) -> R,
R: Responder, R: Responder,
T: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
S: 'static, S: 'static,
{ {
started: bool, started: bool,
hnd: Rc<UnsafeCell<F>>, hnd: Rc<FnWith<T, R>>,
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>>>,
} }
impl<T, S, F, R> Future for WithHandlerFut<T, S, F, R> impl<T, S, R> Future for WithHandlerFut<T, S, R>
where where
F: Fn(T) -> R,
R: Responder + 'static, R: Responder + 'static,
T: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
S: 'static, S: 'static,
@ -150,8 +138,7 @@ where
} }
}; };
let hnd: &mut F = unsafe { &mut *self.hnd.get() }; let item = match self.hnd.as_ref().call_with(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()),
}; };
@ -167,93 +154,89 @@ where
} }
} }
pub struct With2<T1, T2, S, F, R> #[doc(hidden)]
pub struct WithAsync<T, S, R, I, E>
where where
F: Fn(T1, T2) -> R, R: Future<Item = I, Error = E>,
T1: FromRequest<S> + 'static, I: Responder,
T2: FromRequest<S> + 'static, E: Into<E>,
T: FromRequest<S>,
S: 'static, S: 'static,
{ {
hnd: Rc<UnsafeCell<F>>, hnd: Rc<FnWith<T, R>>,
cfg1: ExtractorConfig<S, T1>, cfg: Rc<T::Config>,
cfg2: ExtractorConfig<S, T2>,
_s: PhantomData<S>, _s: PhantomData<S>,
} }
impl<T1, T2, S, F, R> With2<T1, T2, S, F, R> impl<T, S, R, I, E> WithAsync<T, S, R, I, E>
where where
F: Fn(T1, T2) -> R, R: Future<Item = I, Error = E>,
T1: FromRequest<S> + 'static, I: Responder,
T2: FromRequest<S> + 'static, E: Into<Error>,
T: FromRequest<S>,
S: 'static, S: 'static,
{ {
pub fn new( pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>, WithAsync {
) -> Self { cfg: Rc::new(cfg),
With2 { hnd: Rc::new(f),
hnd: Rc::new(UnsafeCell::new(f)),
cfg1,
cfg2,
_s: PhantomData, _s: PhantomData,
} }
} }
} }
impl<T1, T2, S, F, R> Handler<S> for With2<T1, T2, S, F, R> impl<T, S, R, I, E> Handler<S> for WithAsync<T, S, R, I, E>
where where
F: Fn(T1, T2) -> R + 'static, R: Future<Item = I, Error = E> + 'static,
R: Responder + 'static, I: Responder + 'static,
T1: FromRequest<S> + 'static, E: Into<Error> + 'static,
T2: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
S: 'static, S: 'static,
{ {
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 = WithHandlerFut2 { let mut fut = WithAsyncHandlerFut {
req, req: req.clone(),
started: false, started: false,
hnd: Rc::clone(&self.hnd), hnd: Rc::clone(&self.hnd),
cfg1: self.cfg1.clone(), cfg: Rc::clone(&self.cfg),
cfg2: self.cfg2.clone(),
item: None,
fut1: None, fut1: None,
fut2: None, fut2: None,
fut3: None, fut3: None,
}; };
match fut.poll() { match fut.poll() {
Ok(Async::Ready(resp)) => AsyncResult::ok(resp), Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::ok(e), Err(e) => AsyncResult::err(e),
} }
} }
} }
struct WithHandlerFut2<T1, T2, S, F, R> struct WithAsyncHandlerFut<T, S, R, I, E>
where where
F: Fn(T1, T2) -> R + 'static, R: Future<Item = I, Error = E> + 'static,
R: Responder + 'static, I: Responder + 'static,
T1: FromRequest<S> + 'static, E: Into<Error> + 'static,
T2: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
S: 'static, S: 'static,
{ {
started: bool, started: bool,
hnd: Rc<UnsafeCell<F>>, hnd: Rc<FnWith<T, R>>,
cfg1: ExtractorConfig<S, T1>, cfg: Rc<T::Config>,
cfg2: ExtractorConfig<S, T2>,
req: HttpRequest<S>, req: HttpRequest<S>,
item: Option<T1>, fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut1: Option<Box<Future<Item = T1, Error = Error>>>, fut2: Option<R>,
fut2: Option<Box<Future<Item = T2, Error = Error>>>,
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>, fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
} }
impl<T1, T2, S, F, R> Future for WithHandlerFut2<T1, T2, S, F, R> impl<T, S, R, I, E> Future for WithAsyncHandlerFut<T, S, R, I, E>
where where
F: Fn(T1, T2) -> R + 'static, R: Future<Item = I, Error = E> + 'static,
R: Responder + 'static, I: Responder + 'static,
T1: FromRequest<S> + 'static, E: Into<Error> + 'static,
T2: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
S: 'static, S: 'static,
{ {
type Item = HttpResponse; type Item = HttpResponse;
@ -264,357 +247,137 @@ where
return fut.poll(); 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() { if self.fut2.is_some() {
match self.fut2.as_mut().unwrap().poll()? { return match self.fut2.as_mut().unwrap().poll() {
Async::Ready(item) => { Ok(Async::NotReady) => Ok(Async::NotReady),
self.fut2.take(); Ok(Async::Ready(r)) => match r.respond_to(&self.req) {
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); Ok(r) => match r.into().into() {
let item3 = match reply.into() { AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Err(err) => return Err(err), AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => { AsyncResultItem::Future(fut) => {
self.item2 = Some(item);
self.fut3 = Some(fut); self.fut3 = Some(fut);
return self.poll(); self.poll()
} }
}; },
let hnd: &mut F = unsafe { &mut *self.hnd.get() }; Err(e) => Err(e.into()),
match (*hnd)(self.item1.take().unwrap(), item, item3) },
.respond_to(&self.req) Err(e) => Err(e.into()),
{ };
Ok(item) => match item.into().into() { }
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), let item = if !self.started {
AsyncResultItem::Future(fut) => { self.started = true;
self.fut4 = Some(fut); let reply = T::from_request(&self.req, self.cfg.as_ref()).into();
return self.poll(); match reply.into() {
} AsyncResultItem::Err(err) => return Err(err),
}, AsyncResultItem::Ok(msg) => msg,
Err(e) => return Err(e.into()), AsyncResultItem::Future(fut) => {
} self.fut1 = Some(fut);
return self.poll();
} }
}
} else {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady), 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() }; self.fut2 = Some(self.hnd.as_ref().call_with(item));
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()
} }
} }
macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => {
impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func
where Func: Fn($($T,)+) -> Res + 'static,
$($T: FromRequest<State> + 'static,)+
Res: Responder + 'static,
State: 'static,
{
fn create(self) -> With<($($T,)+), State, Res> {
With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
}
fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> {
With::new(move |($($n,)+)| (self)($($n,)+), cfg)
}
}
});
macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => {
impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func
where Func: Fn($($T,)+) -> Res + 'static,
$($T: FromRequest<State> + 'static,)+
Res: Future<Item=Item, Error=Err>,
Item: Responder + 'static,
Err: Into<Error>,
State: 'static,
{
fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> {
WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
}
fn create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> {
WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg)
}
}
});
with_factory_tuple!((a, A));
with_factory_tuple!((a, A), (b, B));
with_factory_tuple!((a, A), (b, B), (c, C));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
with_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H)
);
with_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H),
(i, I)
);
with_async_factory_tuple!((a, A));
with_async_factory_tuple!((a, A), (b, B));
with_async_factory_tuple!((a, A), (b, B), (c, C));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
with_async_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H)
);
with_async_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H),
(i, I)
);

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,57 +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::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, use client::{
HttpResponseParserError, SendRequest, SendRequestError}; ClientConnector, ClientRequest, ClientRequestBuilder, HttpResponseParserError,
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,
} }
@ -102,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 {
@ -120,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,
@ -130,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());
@ -184,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
@ -218,8 +240,7 @@ impl Client {
self.request.upgrade(); self.request.upgrade();
self.request.set_header(header::UPGRADE, "websocket"); self.request.set_header(header::UPGRADE, "websocket");
self.request.set_header(header::CONNECTION, "upgrade"); self.request.set_header(header::CONNECTION, "upgrade");
self.request self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13");
.set_header(header::SEC_WEBSOCKET_VERSION, "13");
self.request.with_connector(self.conn.clone()); self.request.with_connector(self.conn.clone());
if let Some(protocols) = self.protocols.take() { if let Some(protocols) = self.protocols.take() {
@ -235,7 +256,9 @@ impl Client {
return ClientHandshake::error(ClientError::InvalidUrl); return ClientHandshake::error(ClientError::InvalidUrl);
} }
if let Some(scheme) = request.uri().scheme_part() { if let Some(scheme) = request.uri().scheme_part() {
if scheme != "http" && scheme != "https" && scheme != "ws" if scheme != "http"
&& scheme != "https"
&& scheme != "ws"
&& scheme != "wss" && scheme != "wss"
{ {
return ClientHandshake::error(ClientError::InvalidUrl); return ClientHandshake::error(ClientError::InvalidUrl);
@ -245,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,
} }
@ -266,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)
@ -289,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,
@ -302,6 +329,7 @@ impl ClientHandshake {
tx: None, tx: None,
error: Some(err), error: Some(err),
max_size: 0, max_size: 0,
no_masking: false,
} }
} }
@ -394,10 +422,7 @@ impl Future for ClientHandshake {
encoded, encoded,
key key
); );
return Err(ClientError::InvalidChallengeResponse( return Err(ClientError::InvalidChallengeResponse(encoded, key.clone()));
encoded,
key.clone(),
));
} }
} else { } else {
trace!("Missing SEC-WEBSOCKET-ACCEPT header"); trace!("Missing SEC-WEBSOCKET-ACCEPT header");
@ -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,65 +521,82 @@ 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");
} }
} }
/// Send text frame
#[inline] #[inline]
fn as_mut(&mut self) -> &mut Inner { pub fn text<T: Into<Binary>>(&mut self, text: T) {
unsafe { &mut *self.inner.get() } self.write(Frame::message(text.into(), OpCode::Text, true, true));
}
/// Send binary frame
#[inline]
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
self.write(Frame::message(data, OpCode::Binary, true, true));
}
/// Send ping frame
#[inline]
pub fn ping(&mut self, message: &str) {
self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true));
}
/// Send pong frame
#[inline]
pub fn pong(&mut self, message: &str) {
self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true));
}
/// Send close frame
#[inline]
pub fn close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(reason, true));
} }
} }
impl WsWriter for ClientWriter { impl WsWriter for ClientWriter {
/// Send text frame /// Send text frame
#[inline] #[inline]
fn text<T: Into<Binary>>(&mut self, text: T) { fn send_text<T: Into<Binary>>(&mut self, text: T) {
self.write(Frame::message(text.into(), OpCode::Text, true, true)); self.text(text)
} }
/// Send binary frame /// Send binary frame
#[inline] #[inline]
fn binary<B: Into<Binary>>(&mut self, data: B) { fn send_binary<B: Into<Binary>>(&mut self, data: B) {
self.write(Frame::message(data, OpCode::Binary, true, true)); self.binary(data)
} }
/// Send ping frame /// Send ping frame
#[inline] #[inline]
fn ping(&mut self, message: &str) { fn send_ping(&mut self, message: &str) {
self.write(Frame::message( self.ping(message)
Vec::from(message),
OpCode::Ping,
true,
true,
));
} }
/// Send pong frame /// Send pong frame
#[inline] #[inline]
fn pong(&mut self, message: &str) { fn send_pong(&mut self, message: &str) {
self.write(Frame::message( self.pong(message)
Vec::from(message),
OpCode::Pong,
true,
true,
));
} }
/// Send close frame /// Send close frame
#[inline] #[inline]
fn close(&mut self, reason: Option<CloseReason>) { fn send_close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(reason, true)); self.close(reason);
} }
} }

View File

@ -1,28 +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::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, ToEnvelope,
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,
@ -64,7 +71,8 @@ where
#[doc(hidden)] #[doc(hidden)]
#[inline] #[inline]
fn waiting(&self) -> bool { fn waiting(&self) -> bool {
self.inner.waiting() || self.inner.state() == ActorState::Stopping self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped || self.inner.state() == ActorState::Stopped
} }
@ -72,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()
} }
} }
@ -90,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)))
} }
} }
@ -115,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");
} }
@ -144,11 +165,50 @@ 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)
} }
/// Send text frame
#[inline]
pub fn text<T: Into<Binary>>(&mut self, text: T) {
self.write_raw(Frame::message(text.into(), OpCode::Text, true, false));
}
/// Send binary frame
#[inline]
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
self.write_raw(Frame::message(data, OpCode::Binary, true, false));
}
/// Send ping frame
#[inline]
pub fn ping(&mut self, message: &str) {
self.write_raw(Frame::message(
Vec::from(message),
OpCode::Ping,
true,
false,
));
}
/// Send pong frame
#[inline]
pub fn pong(&mut self, message: &str) {
self.write_raw(Frame::message(
Vec::from(message),
OpCode::Pong,
true,
false,
));
}
/// Send close frame
#[inline]
pub fn close(&mut self, reason: Option<CloseReason>) {
self.write_raw(Frame::close(reason, false));
}
/// Check if connection still open /// Check if connection still open
#[inline] #[inline]
pub fn connected(&self) -> bool { pub fn connected(&self) -> bool {
@ -163,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
@ -181,67 +240,81 @@ where
{ {
/// Send text frame /// Send text frame
#[inline] #[inline]
fn text<T: Into<Binary>>(&mut self, text: T) { fn send_text<T: Into<Binary>>(&mut self, text: T) {
self.write(Frame::message(text.into(), OpCode::Text, true, false)); self.text(text)
} }
/// Send binary frame /// Send binary frame
#[inline] #[inline]
fn binary<B: Into<Binary>>(&mut self, data: B) { fn send_binary<B: Into<Binary>>(&mut self, data: B) {
self.write(Frame::message(data, OpCode::Binary, true, false)); self.binary(data)
} }
/// Send ping frame /// Send ping frame
#[inline] #[inline]
fn ping(&mut self, message: &str) { fn send_ping(&mut self, message: &str) {
self.write(Frame::message( self.ping(message)
Vec::from(message),
OpCode::Ping,
true,
false,
));
} }
/// Send pong frame /// Send pong frame
#[inline] #[inline]
fn pong(&mut self, message: &str) { fn send_pong(&mut self, message: &str) {
self.write(Frame::message( self.pong(message)
Vec::from(message),
OpCode::Pong,
true,
false,
));
} }
/// Send close frame /// Send close frame
#[inline] #[inline]
fn close(&mut self, reason: Option<CloseReason>) { fn send_close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(reason, false)); self.close(reason)
} }
} }
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))
@ -249,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) => {
@ -47,9 +46,9 @@ impl Frame {
Frame::message(payload, OpCode::Close, true, genmask) Frame::message(payload, OpCode::Close, true, genmask)
} }
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] #[cfg_attr(feature = "cargo-clippy", allow(clippy::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,17 +117,14 @@ 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 {
None None
}; };
Ok(Async::Ready(Some(( Ok(Async::Ready(Some((idx, finished, opcode, length, mask))))
idx, finished, opcode, length, mask,
))))
} }
fn read_chunk_md( fn read_chunk_md(
@ -169,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
}; };
@ -187,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 {
@ -200,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>,
@ -229,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 {
@ -259,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()
apply_mask(p, mask); } else {
} data
};
Ok(Async::Ready(Some(Frame { Ok(Async::Ready(Some(Frame {
finished, finished,
@ -277,17 +279,14 @@ 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())
} else { } else {
None None
}; };
Some(CloseReason { Some(CloseReason { code, description })
code,
description,
})
} else { } else {
None None
} }
@ -296,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)
@ -317,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)
} }
} }
@ -386,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::*;
@ -407,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);
@ -425,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);
@ -436,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);
@ -453,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);
@ -472,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());
@ -486,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());
@ -500,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());
@ -516,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]
@ -525,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]
@ -535,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,126 @@
//! 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(clippy::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(clippy::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);
if cfg!(target_endian = "big") {
let mask_u32 = if n > 0 { mask_u64 = mask_u64.rotate_left(8 * head_len as u32);
unsafe {
xor_mem(ptr, mask_u32, n);
ptr = ptr.offset(head as isize);
}
len -= n;
if cfg!(target_endian = "big") {
mask_u32.rotate_left(8 * n as u32)
} else {
mask_u32.rotate_right(8 * n as u32)
}
} else { } else {
mask_u32 mask_u64 = mask_u64.rotate_right(8 * head_len as u32);
};
if head > 4 {
unsafe {
*(ptr as *mut u32) ^= mask_u32;
ptr = ptr.offset(4);
len -= 4;
}
}
mask_u32
} else {
mask_u32
};
if len > 0 {
debug_assert_eq!(ptr as usize % 4, 0);
}
// Properly aligned middle of the data.
if len >= 8 {
let mut mask_u64 = mask_u32 as u64;
mask_u64 = mask_u64 << 32 | mask_u32 as u64;
while len >= 8 {
unsafe {
*(ptr as *mut u64) ^= mask_u64;
ptr = ptr.offset(8);
len -= 8;
}
} }
} }
// Aligned segment
while len >= 4 { for v in mid {
unsafe { *v ^= mask_u64;
*(ptr as *mut u32) ^= mask_u32;
ptr = ptr.offset(4);
len -= 4;
}
} }
// Final unaligned segment
// Possible last block. if tail.len() > 0 {
if len > 0 { xor_short(tail, mask_u64);
unsafe {
xor_mem(ptr, mask_u32, len);
}
} }
} }
#[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(
let mut b: u32 = uninitialized(); feature = "cargo-clippy",
#[allow(trivial_casts)] allow(clippy::needless_pass_by_value)
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len); )]
b ^= mask; fn xor_short(buf: ShortSlice, mask: u64) {
#[allow(trivial_casts)] // Unsafe: we know that a `ShortSlice` fits in a u64
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len); unsafe {
let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len());
let mut b: u64 = 0;
#[allow(trivial_casts)]
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
b ^= mask;
#[allow(trivial_casts)]
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len);
}
}
#[inline]
// Unsafe: caller must ensure the buffer has the correct size and alignment
unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
// Assert correct size and alignment in debug builds
debug_assert!(buf.len().trailing_zeros() >= 3);
debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3);
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
}
#[inline]
// Splits a slice into three parts: an unaligned short head and tail, plus an aligned
// u64 mid section.
fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) {
let start_ptr = buf.as_ptr() as usize;
let end_ptr = start_ptr + buf.len();
// Round *up* to next aligned boundary for start
let start_aligned = (start_ptr + 7) & !0x7;
// Round *down* to last aligned boundary for end
let end_aligned = end_ptr & !0x7;
if end_aligned >= start_aligned {
// We have our three segments (head, mid, tail)
let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr);
let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr);
// Unsafe: we know the middle section is correctly aligned, and the outer
// sections are smaller than 8 bytes
unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) }
} else {
// We didn't cross even one aligned boundary!
// Unsafe: The outer sections are smaller than 8 bytes
unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) }
}
} }
#[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 +133,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 +144,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;
@ -62,9 +60,11 @@ mod frame;
mod mask; mod mask;
mod proto; mod proto;
pub use self::client::{Client, ClientError, ClientHandshake, ClientReader, ClientWriter}; pub use self::client::{
Client, ClientError, ClientHandshake, ClientReader, ClientWriter,
};
pub use self::context::WebsocketContext; pub use self::context::WebsocketContext;
pub use self::frame::Frame; pub use self::frame::{Frame, FramedMessage};
pub use self::proto::{CloseCode, CloseReason, OpCode}; pub use self::proto::{CloseCode, CloseReason, OpCode};
/// Websocket protocol errors /// Websocket protocol errors
@ -158,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.
@ -216,9 +219,7 @@ pub fn handshake<S>(
} }
// check supported version // check supported version
if !req.headers() if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
.contains_key(header::SEC_WEBSOCKET_VERSION)
{
return Err(HandshakeError::NoVersionHeader); return Err(HandshakeError::NoVersionHeader);
} }
let supported_ver = { let supported_ver = {
@ -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,
} }
@ -343,178 +344,114 @@ where
/// Common writing methods for a websocket. /// Common writing methods for a websocket.
pub trait WsWriter { pub trait WsWriter {
/// Send a text /// Send a text
fn text<T: Into<Binary>>(&mut self, text: T); fn send_text<T: Into<Binary>>(&mut self, text: T);
/// Send a binary /// Send a binary
fn binary<B: Into<Binary>>(&mut self, data: B); fn send_binary<B: Into<Binary>>(&mut self, data: B);
/// Send a ping message /// Send a ping message
fn ping(&mut self, message: &str); fn send_ping(&mut self, message: &str);
/// Send a pong message /// Send a pong message
fn pong(&mut self, message: &str); fn send_pong(&mut self, message: &str);
/// Close the connection /// Close the connection
fn close(&mut self, reason: Option<CloseReason>); fn send_close(&mut self, reason: Option<CloseReason>);
} }
#[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(header::UPGRADE, header::HeaderValue::from_static("test"))
header::UPGRADE, .finish();
header::HeaderValue::from_static("test"),
);
let req = HttpRequest::new(
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"),
); ).finish();
let req = HttpRequest::new(
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"),
); ).header(
headers.insert( header::CONNECTION,
header::CONNECTION, header::HeaderValue::from_static("upgrade"),
header::HeaderValue::from_static("upgrade"), ).finish();
);
let req = HttpRequest::new(
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"),
); ).header(
headers.insert( header::CONNECTION,
header::CONNECTION, header::HeaderValue::from_static("upgrade"),
header::HeaderValue::from_static("upgrade"), ).header(
); header::SEC_WEBSOCKET_VERSION,
headers.insert( header::HeaderValue::from_static("5"),
header::SEC_WEBSOCKET_VERSION, ).finish();
header::HeaderValue::from_static("5"),
);
let req = HttpRequest::new(
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"),
); ).header(
headers.insert( header::CONNECTION,
header::CONNECTION, header::HeaderValue::from_static("upgrade"),
header::HeaderValue::from_static("upgrade"), ).header(
); header::SEC_WEBSOCKET_VERSION,
headers.insert( header::HeaderValue::from_static("13"),
header::SEC_WEBSOCKET_VERSION, ).finish();
header::HeaderValue::from_static("13"),
);
let req = HttpRequest::new(
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"),
); ).header(
headers.insert( header::CONNECTION,
header::CONNECTION, header::HeaderValue::from_static("upgrade"),
header::HeaderValue::from_static("upgrade"), ).header(
); header::SEC_WEBSOCKET_VERSION,
headers.insert( header::HeaderValue::from_static("13"),
header::SEC_WEBSOCKET_VERSION, ).header(
header::HeaderValue::from_static("13"), header::SEC_WEBSOCKET_KEY,
); header::HeaderValue::from_static("13"),
headers.insert( ).finish();
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
assert_eq!( 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

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

BIN
tests/identity.pfx Normal file

Binary file not shown.

View File

@ -5,8 +5,11 @@ extern crate bytes;
extern crate flate2; extern crate flate2;
extern crate futures; extern crate futures;
extern crate rand; extern crate rand;
#[cfg(all(unix, feature = "uds"))]
extern crate tokio_uds;
use std::io::Read; use std::io::{Read, Write};
use std::{net, thread};
use bytes::Bytes; use bytes::Bytes;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
@ -64,19 +67,26 @@ fn test_simple() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
} }
#[test]
fn test_connection_close() {
let mut srv =
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let request = srv.get().header("Connection", "close").finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
#[test] #[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(),
}) })
}); });
let request = srv.get() let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap();
.uri(srv.url("/?qp=5").as_str())
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@ -113,19 +123,19 @@ 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()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(STR) .body(STR)
.unwrap(); .unwrap();
@ -142,19 +152,19 @@ 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()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@ -174,19 +184,19 @@ 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()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@ -198,23 +208,30 @@ fn test_client_gzip_encoding_large_random() {
assert_eq!(bytes, Bytes::from(data)); assert_eq!(bytes, Bytes::from(data));
} }
#[cfg(all(unix, feature = "uds"))]
#[test]
fn test_compatible_with_unix_socket_stream() {
let (stream, _) = tokio_uds::UnixStream::pair().unwrap();
let _ = client::Connection::from_stream(stream);
}
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
#[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()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
// client request // client request
let request = srv.client(http::Method::POST, "/") let request = srv
.client(http::Method::POST, "/")
.content_encoding(http::ContentEncoding::Br) .content_encoding(http::ContentEncoding::Br)
.body(STR) .body(STR)
.unwrap(); .unwrap();
@ -235,19 +252,19 @@ 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()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
// client request // client request
let request = srv.client(http::Method::POST, "/") let request = srv
.client(http::Method::POST, "/")
.content_encoding(http::ContentEncoding::Br) .content_encoding(http::ContentEncoding::Br)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@ -264,19 +281,19 @@ 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()
.content_encoding(http::ContentEncoding::Br) .content_encoding(http::ContentEncoding::Br)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(STR) .body(STR)
.unwrap(); .unwrap();
@ -297,19 +314,19 @@ 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()
.content_encoding(http::ContentEncoding::Br) .content_encoding(http::ContentEncoding::Br)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
// client request // client request
let request = srv.post() let request = srv
.post()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(data.clone()) .body(data.clone())
.unwrap(); .unwrap();
@ -324,7 +341,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| {
@ -332,16 +349,13 @@ fn test_client_streaming_explicit() {
.chunked() .chunked()
.content_encoding(http::ContentEncoding::Identity) .content_encoding(http::ContentEncoding::Identity)
.body(body)) .body(body))
}) }).responder()
.responder()
}) })
}); });
let body = once(Ok(Bytes::from_static(STR.as_ref()))); let body = once(Ok(Bytes::from_static(STR.as_ref())));
let request = srv.get() let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
.body(Body::Streaming(Box::new(body)))
.unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
@ -391,30 +405,36 @@ 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")
.and_then(|c1| if c1.value() == "value1" { .ok_or_else(err)
.and_then(|c1| {
if c1.value() == "value1" {
Ok(()) Ok(())
} else { } else {
Err(err()) Err(err())
}) }
.and_then(|()| req.cookie("cookie2").ok_or_else(err)) }).and_then(|()| req.cookie("cookie2").ok_or_else(err))
.and_then(|c2| if c2.value() == "value2" { .and_then(|c2| {
if c2.value() == "value2" {
Ok(()) Ok(())
} else { } else {
Err(err()) Err(err())
}) }
// Send some cookies back })
.map(|_| HttpResponse::Ok() // Send some cookies back
.cookie(cookie1.clone()) .map(|_| {
.cookie(cookie2.clone()) HttpResponse::Ok()
.finish() .cookie(cookie1.clone())
) .cookie(cookie2.clone())
.finish()
})
}) })
}); });
let request = srv.get() let request = srv
.get()
.cookie(cookie1.clone()) .cookie(cookie1.clone())
.cookie(cookie2.clone()) .cookie(cookie2.clone())
.finish() .finish()
@ -422,7 +442,67 @@ 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"),
"\""
)));
}
#[test]
fn client_read_until_eof() {
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
let lst = net::TcpListener::bind(addr).unwrap();
for stream in lst.incoming() {
let mut stream = stream.unwrap();
let mut b = [0; 1000];
let _ = stream.read(&mut b).unwrap();
let _ = stream
.write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!");
}
});
let mut sys = actix::System::new("test");
// client request
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.block_on(req.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = sys.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"welcome!"));
} }

View File

@ -0,0 +1,81 @@
extern crate actix;
extern crate actix_net;
extern crate actix_web;
use std::{thread, time};
use actix::System;
use actix_net::server::Server;
use actix_net::service::NewServiceExt;
use actix_web::server::{HttpService, KeepAlive, ServiceConfig, StreamConfiguration};
use actix_web::{client, http, test, App, HttpRequest};
#[test]
fn test_custom_pipeline() {
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
Server::new()
.bind("test", addr, move || {
let app = App::new()
.route("/", http::Method::GET, |_: HttpRequest| "OK")
.finish();
let settings = ServiceConfig::build(app)
.keep_alive(KeepAlive::Disabled)
.client_timeout(1000)
.client_shutdown(1000)
.server_hostname("localhost")
.server_address(addr)
.finish();
StreamConfiguration::new()
.nodelay(true)
.tcp_keepalive(Some(time::Duration::from_secs(10)))
.and_then(HttpService::new(settings))
}).unwrap()
.run();
});
let mut sys = System::new("test");
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.block_on(req.send()).unwrap();
assert!(response.status().is_success());
}
}
#[test]
fn test_h1() {
use actix_web::server::H1Service;
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
Server::new()
.bind("test", addr, move || {
let app = App::new()
.route("/", http::Method::GET, |_: HttpRequest| "OK")
.finish();
let settings = ServiceConfig::build(app)
.keep_alive(KeepAlive::Disabled)
.client_timeout(1000)
.client_shutdown(1000)
.server_hostname("localhost")
.server_address(addr)
.finish();
H1Service::new(settings)
}).unwrap()
.run();
});
let mut sys = System::new("test");
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.block_on(req.send()).unwrap();
assert!(response.status().is_success());
}
}

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