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

Compare commits

...

250 Commits
v0.7.3 ... 0.7

Author SHA1 Message Date
Zeyi Fan
6813ce789d add x509 to stream extension (#844) 2019-05-15 08:38:59 -07:00
Nikolay Kim
cc6e0c6d04 Fix client payload decompression #674 2019-03-28 20:40:25 -07:00
Zeyi Fan
d9496d46d1 [0.7] Fix never-ending HTTP2 empty response (#737)
* Fix never-ending HTTP2 empty response #737
2019-03-28 17:40:12 -07:00
Jannik Keye
bf8262196f feat: enable use of patch as request method (#718) 2019-03-14 11:36:10 +03:00
Luca Bruno
17ecdd63d2 httpresponse: add constructor for HttpResponseBuilder (#697) 2019-03-13 17:20:18 +03:00
David McGuire
cc7f6b5eef Fix preflight CORS header compliance; refactor previous patch. (#717) 2019-03-11 07:26:54 +03:00
Stephen Ellis
ceca96da28 Added HTTP Authentication for Client (#540) 2019-03-06 12:56:12 +03:00
Douman
42f030d3f4 Ensure that Content-Length zero is specified in empty request 2019-03-05 08:37:15 +03:00
Hugo Benício
6d11ee683f fixing little typo in docs (#711) 2019-03-01 11:34:58 +03:00
Douman
80d4cbe301 Add change notes for new HttpResponseBuilder 2019-02-27 21:37:20 +03:00
Kornel
69d710dbce Add insert and remove() to response builder (#707) 2019-02-27 15:52:42 +03:00
Michael Edwards
0059a55dfb Fix typo 2019-02-13 14:31:28 +03:00
cuebyte
c695358bcb Ignored the If-Modified-Since if If-None-Match is specified (#680) (#692) 2019-02-09 00:33:00 +03:00
Jason Hills
b018e4abaf Fixes TestRequest::with_cookie panic 2019-02-07 07:55:27 +03:00
Vladislav Stepanov
346d85a884 Serve static file directly instead of redirecting (#676) 2019-02-04 13:20:46 +03:00
wildarch
9968afe4a6 Use NamedFile with an existing File (#670) 2019-01-28 08:07:28 +03:00
Tomas Izquierdo Garcia-Faria
f5bec968c7 Bump v_htmlescape version to 0.4 2019-01-25 11:31:42 +03:00
Neil Jensen
a534fdd125 Add io handling for ECONNRESET when data has already been received 2019-01-20 08:45:33 +03:00
rishflab
3431fff4d7 Fixed example in client documentation. This closes #665. 2019-01-14 07:44:30 +03:00
Sameer Puri
d6df2e3399 Fix HttpResponse doc spelling "os" to "of" 2019-01-11 08:45:15 +03:00
Douman
1fbb52ad3b 0.7.18 Bump 2019-01-10 17:05:18 +03:00
Julian Tescher
e5cdd22720 Fix test server listener thread leak (#655) 2019-01-08 10:42:22 -08:00
Douman
4f2e970732 Tidy up CHANGES.md 2019-01-08 10:49:03 +03:00
Douman
4d45313f9d Decode special characters when handling static files 2019-01-08 10:46:58 +03:00
Juan Aguilar
55a2a59906 Improve change askama_escape in favor of v_htmlescape (#651) 2019-01-03 22:34:18 +03:00
Ji Qu
61883042c2 Add with-cookie init-method for TestRequest (#647) 2019-01-02 13:24:08 +03:00
Douman
799c6eb719 0.7.17 Bump 2018-12-25 16:28:36 +03:00
Douman
037a1c6a24 Bump min version of rustc
Due to actix & trust-dns requirement
2018-12-24 21:17:09 +03:00
BlueC0re
bfdf762062 Only return a single Origin value (#644)
Only return a single origin if matched.
2018-12-24 21:16:07 +03:00
Nikolay Kim
477bf0d8ae Send HTTP/1.1 100 Continue if request contains expect: continue header #634 2018-12-23 10:19:12 -08:00
Phil Booth
e9fe3879df Support custom content types in JsonConfig 2018-12-23 08:27:47 +03:00
Douman
1a940d4c18 H1 decoded should ignore header cases 2018-12-16 18:34:32 +03:00
Douman
e8bdcb1c08 Update min version of http
Closes #630
2018-12-15 09:26:56 +03:00
Douman
46db09428c Prepare release 0.7.16 2018-12-11 21:04:05 +03:00
ethanpailes
90eef31cc0 impl ResponseError for SendError when possible (#619) 2018-12-11 19:37:52 +03:00
Akos Vandra
86af02156b add impl FromRequest for Either<A,B> (#618) 2018-12-10 19:02:05 +03:00
Douman
ac9fc662c6 Bump version to 0.7.15 2018-12-05 18:27:06 +03:00
Douman
0745a1a9f8 Remove usage of upcoming keyword async
AsyncResult::async is replaced with AsyncResult::future
2018-12-05 18:23:04 +03:00
silwol
b1635bc0e6 Update some dependencies (#612)
* Update rand to 0.6

* Update parking_lot to 0.7

* Update env_logger to 0.6
2018-12-04 09:58:22 +03:00
Kelly Thomas Kline
08c7743bb8 Add set_mailbox_capacity() function 2018-12-02 08:40:09 +03:00
vemoo
68c5d6e6d6 impl From<Cow<'static, [u8]>> for Binary (#611)
impl `From` for `Cow<'static, [u8]>`  and `From<Cow<'static, str>>` for `Binary`
2018-12-02 08:32:55 +03:00
François
c386353337 decode reserved characters when extracting path with configuration (#577)
* decode reserved characters when extracting path with configuration

* remove useless clone

* add a method to get decoded parameter by name
2018-11-24 16:54:11 +03:00
Douman
9aab382ea8 Allow user to provide addr to custom resolver
We basically swaps Addr with Recipient to enable user to use custom resolver
2018-11-23 15:36:12 +03:00
Douman
389cb13cd6 Export PathConfig and QueryConfig
Closes #597
2018-11-20 23:06:38 +03:00
Huston Bokinsky
6a93178479 Complete error helper functions. 2018-11-20 08:07:46 +03:00
Nikolay Kim
cd9901c928 prepare release 2018-11-14 16:24:01 -08:00
Nikolay Kim
1ef0eed0bd do not stop on keep-alive timer if sink is not completly flushed 2018-11-08 20:46:13 -08:00
Nikolay Kim
61b1030882 Fix websockets connection drop if request contains content-length header #567 2018-11-08 20:35:47 -08:00
Nikolay Kim
7065c540e1 set nodelay on socket #560 2018-11-08 16:29:43 -08:00
Nikolay Kim
aed3933ae8 Merge branch 'master' of github.com:actix/actix-web 2018-11-08 16:15:45 -08:00
Nikolay Kim
5b7740dee3 hide ChunkedReadFile 2018-11-08 16:12:16 -08:00
imaperson
1a0bf32ec7 Fix unnecessary owned string and change htmlescape in favor of askama_escape (#584) 2018-11-08 16:08:06 -08:00
Nikolay Kim
9ab586e24e update actix-net dep 2018-11-08 16:06:23 -08:00
Nikolay Kim
62f1c90c8d update base64 dep 2018-11-07 21:18:40 -08:00
Nikolay Kim
2677d325a7 fix keep-alive timer reset 2018-11-07 21:09:33 -08:00
Julian Tescher
8e354021d4 Add SameSite option to identity middleware cookie (#581) 2018-11-07 23:24:06 +03:00
Stanislav Tkach
3b536ee96c Use old clippy attributes syntax (#562) 2018-11-01 11:14:48 +03:00
Nikolay Kim
cfd9a56ff7 Add async/await ref 2018-10-28 09:24:19 -07:00
Douman
5f91f5eda6 Correct IoStream::set_keepalive for UDS (#564)
Enable uds feature in tests
2018-10-26 10:59:06 +03:00
François
42d5d48e71 add a way to configure error treatment for Query and Path extractors (#550)
* add a way to configure error treatment for Query extractor

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

* changes.md is updated

* typo
2018-10-02 09:15:48 -07:00
Nikolay Kim
eed377e773 uneeded dep 2018-10-02 00:20:27 -07:00
Nikolay Kim
f3ce6574e4 fix client timer and add slow request tests 2018-10-02 00:19:28 -07:00
Nikolay Kim
f007860a16 cleanup warnings 2018-10-01 22:48:11 -07:00
Nikolay Kim
fdfadb52e1 fix doc test for State 2018-10-01 22:29:30 -07:00
Nikolay Kim
368f73513a set tcp-keepalive for test as well 2018-10-01 22:25:53 -07:00
Nikolay Kim
c674ea9126 add StreamConfiguration service 2018-10-01 22:23:02 -07:00
Nikolay Kim
7c78797d9b proper stop for test_ws_stopped test 2018-10-01 21:30:00 -07:00
Nikolay Kim
84edc57fd9 increase sleep time 2018-10-01 21:19:27 -07:00
Nikolay Kim
127af92541 clippy warnings 2018-10-01 21:16:56 -07:00
Nikolay Kim
e4686f6c8d set socket linger to 0 on timeout 2018-10-01 20:53:22 -07:00
Nikolay Kim
1bac65de4c add websocket stopped test 2018-10-01 20:15:26 -07:00
Nikolay Kim
16945a554a add client shutdown timeout 2018-10-01 20:04:16 -07:00
Nikolay Kim
91af3ca148 simplify h1 dispatcher 2018-10-01 19:18:24 -07:00
Nikolay Kim
2217a152cb expose app error by http service 2018-10-01 15:19:49 -07:00
Nikolay Kim
c1e0b4f322 expose internal http server types and allow to create custom http pipelines 2018-10-01 14:43:06 -07:00
Nikolay Kim
5966ee6192 add HttpServer::register() function, allows to register services in actix net server 2018-09-28 16:03:53 -07:00
Nikolay Kim
4aac3d6a92 refactor keep-alive timer 2018-09-28 15:04:59 -07:00
Nikolay Kim
e95babf8d3 log acctor init errors 2018-09-28 12:37:20 -07:00
Nikolay Kim
f2d42e5e77 refactor acceptor error handling 2018-09-28 11:50:47 -07:00
Nikolay Kim
0f1c80ccc6 deprecate start_incoming 2018-09-28 08:45:49 -07:00
Nikolay Kim
fc5088b55e fix tarpaulin args 2018-09-28 00:08:23 -07:00
Nikolay Kim
bec37fdbd5 update travis config 2018-09-27 22:23:29 -07:00
Nikolay Kim
4b59ae2476 fix ssl config for client connector 2018-09-27 22:15:38 -07:00
Nikolay Kim
d0fc9d7b99 simplify listen_ and bind_ methods 2018-09-27 21:55:44 -07:00
Nikolay Kim
1ff86e5ac4 restore rust-tls support 2018-09-27 21:24:21 -07:00
Nikolay Kim
ecfda64f6d add native-tls support 2018-09-27 20:40:34 -07:00
Nikolay Kim
0bca21ec6d fix ssl tests 2018-09-27 19:57:40 -07:00
Nikolay Kim
3173c9fa83 diesable client timeout for tcp stream acceptor 2018-09-27 19:34:07 -07:00
Nikolay Kim
85445ea809 rename and simplify ServiceFactory trait 2018-09-27 18:33:29 -07:00
Nikolay Kim
d57579d700 refactor acceptor pipeline add client timeout 2018-09-27 18:33:29 -07:00
Nikolay Kim
b6a1cfa6ad update openssl support 2018-09-27 18:33:29 -07:00
Nikolay Kim
9f1417af30 refactor http service builder 2018-09-27 18:33:29 -07:00
Nikolay Kim
0aa0f326f7 fix changes from master 2018-09-27 18:33:29 -07:00
Nikolay Kim
dbb4fab4f7 separate mod for HttpHandler; add HttpHandler impl for Vec<H> 2018-09-27 18:33:29 -07:00
Nikolay Kim
6f3e70a92a simplify application factory 2018-09-27 18:33:29 -07:00
Nikolay Kim
a63d3f9a7a cleanup ServerFactory trait 2018-09-27 18:33:29 -07:00
Nikolay Kim
a3cfc24232 refactor acceptor service 2018-09-27 18:33:29 -07:00
Nikolay Kim
6a61138bf8 enable ssl feature 2018-09-27 18:33:29 -07:00
Nikolay Kim
7cf9af9b55 disable ssl for travis 2018-09-27 18:33:29 -07:00
Nikolay Kim
c9a52e3197 refactor date generatioin 2018-09-27 18:33:29 -07:00
Nikolay Kim
1907102685 switch to actix-net server 2018-09-27 18:33:29 -07:00
Nikolay Kim
52195bbf16 update version 2018-09-27 18:17:58 -07:00
sapir
59deb4b40d Try to separate HTTP/1 read & write disconnect handling, to fix #511. (#514) 2018-09-27 18:15:02 -07:00
Ashley
782eeb5ded Reduced unsafe converage (#520) 2018-09-26 11:56:34 +03:00
Douman
1b298142e3 Correct composing of multiple origins in cors (#518) 2018-09-21 08:45:22 +03:00
Douman
0dc96658f2 Send response to inform client of error (#515) 2018-09-21 07:24:10 +03:00
Nikolay Kim
f40153fca4 fix node::insert() method, missing next element 2018-09-17 11:39:03 -07:00
Nikolay Kim
764103566d update changes 2018-09-17 10:48:37 -07:00
Nikolay Kim
bfb2f2e9e1 fix node.remove(), update next node pointer 2018-09-17 10:25:45 -07:00
Nikolay Kim
599e6b3385 refactor channel node remove operation 2018-09-17 05:29:07 -07:00
Nikolay Kim
03e318f446 update changes 2018-09-15 17:10:53 -07:00
Nikolay Kim
7449884ce3 fix wrong error message for path deserialize for i32 #510 2018-09-15 17:09:07 -07:00
Nikolay Kim
bbe69e5b8d update version 2018-09-15 10:00:54 -07:00
Nikolay Kim
9d1eefc38f use 5 seconds keep-alive timer by default 2018-09-15 09:57:54 -07:00
Nikolay Kim
d65c72b44d use server keep-alive timer as slow request timer 2018-09-15 09:55:38 -07:00
Nikolay Kim
c3f8b5cf22 clippy warnings 2018-09-11 11:25:32 -07:00
Nikolay Kim
70a3f317d3 fix failing requests to test server #508 2018-09-11 11:24:05 -07:00
Nikolay Kim
513c8ec1ce Merge pull request #505 from Neopallium/master
Fix issue with HttpChannel linked list.
2018-09-11 11:18:33 -07:00
Robert G. Jakabosky
04608b2ea6 Update changes. 2018-09-12 00:27:15 +08:00
Robert G. Jakabosky
70b45659e2 Make Node's traverse method take a closure instead of calling shutdown on each HttpChannel. 2018-09-12 00:27:15 +08:00
Robert G. Jakabosky
e0ae6b10cd Fix bug with HttpChannel linked list. 2018-09-12 00:27:15 +08:00
Maciej Piechotka
003b05b095 Don't ignore errors in std::fmt::Debug implementations (#506) 2018-09-11 14:57:55 +03:00
Nikolay Kim
cdb57b840e prepare release 2018-09-07 20:47:54 -07:00
Nikolay Kim
002bb24b26 unhide SessionBackend and SessionImpl traits and cleanup warnings 2018-09-07 20:46:43 -07:00
Nikolay Kim
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
Maciej Piechotka
4251b0bc10 Refactor resource route parsing to allow repetition in the regexes 2018-09-06 08:51:55 +02:00
Nikolay Kim
42f3773bec update changes 2018-09-05 09:03:58 -07:00
Jan Michael Auer
86fdbb47a5 Fix system_exit in HttpServer (#501) 2018-09-05 10:41:23 +02:00
Nikolay Kim
4ca9fd2ad1 remove debug print 2018-09-03 22:09:12 -07:00
Nikolay Kim
f0f67072ae Read client response until eof if connection header set to close #464 2018-09-03 21:35:59 -07:00
Nikolay Kim
24d1228943 simplify handler path processing 2018-09-03 11:28:47 -07:00
Nikolay Kim
b7a73e0a4f fix Scope::handler doc test 2018-09-02 08:51:26 -07:00
Nikolay Kim
968c81e267 Handling scoped paths without leading slashes #460 2018-09-02 08:14:54 -07:00
Nikolay Kim
d5957a8466 Merge branch 'master' of https://github.com/actix/actix-web 2018-09-02 07:47:45 -07:00
Nikolay Kim
f2f05e7715 allow to register handlers on scope level #465 2018-09-02 07:47:19 -07:00
Markus Unterwaditzer
3439f55288 doc: Add example for using custom nativetls connector (#497) 2018-09-01 18:13:52 +03:00
Robert Gabriel Jakabosky
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
Nikolay Kim
6464f96f8b Merge branch 'master' of https://github.com/actix/actix-web 2018-08-31 18:56:53 -07:00
Nikolay Kim
a2b170fec9 fmt 2018-08-31 18:56:21 -07:00
Nikolay Kim
0b42cae082 update tests 2018-08-31 18:54:19 -07:00
Nikolay Kim
c313c003a4 Fix typo 2018-08-31 17:45:29 -07:00
Nikolay Kim
3fa23f5e10 update version 2018-08-31 17:25:15 -07:00
Nikolay Kim
2d51831899 handle socket read disconnect 2018-08-31 17:24:13 -07:00
Nikolay Kim
e59abfd716 Merge pull request #496 from Neopallium/master
Fix issue with 'Connection: close' in ClientRequest
2018-08-31 17:17:39 -07:00
Robert G. Jakabosky
66881d7dd1 If buffer is empty, read more data before calling parser. 2018-09-01 02:25:05 +08:00
Robert G. Jakabosky
a42a8a2321 Add some comments to clarify logic. 2018-09-01 02:15:36 +08:00
Robert G. Jakabosky
2341656173 Simplify buffer reading logic. Remove duplicate code. 2018-09-01 01:41:38 +08:00
Robert G. Jakabosky
487519acec Add client test for 'Connection: close' as reported in issue #495 2018-09-01 00:34:19 +08:00
Robert Gabriel Jakabosky
af6caa92c8 Merge branch 'master' into master 2018-09-01 00:17:34 +08:00
Robert G. Jakabosky
3ccbce6bc8 Fix issue with 'Connection: close' in ClientRequest 2018-09-01 00:08:53 +08:00
Armin Ronacher
797b52ecbf Update CHANGES.md 2018-08-29 20:58:23 +02:00
Markus Unterwaditzer
4bab50c861 Add ability to pass a custom TlsConnector (#491) 2018-08-29 20:53:31 +02:00
Nikolay Kim
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
Robert G. Jakabosky
8393d09a0f Fix tests. 2018-08-27 00:31:31 +08:00
Robert G. Jakabosky
c3ae9997fc Fix bug with http1 client disconnects. 2018-08-26 22:21:05 +08:00
Nikolay Kim
d39dcc58cd Merge pull request #482 from 0x1793d1/master
Fix server startup log message
2018-08-24 20:53:45 -07:00
0x1793d1
471a3e9806 Fix server startup log message 2018-08-24 23:21:32 +02:00
Nikolay Kim
48ef18ffa9 update changes 2018-08-23 12:54:59 -07:00
Nikolay Kim
9ef7a9c182 hide AcceptorService 2018-08-23 11:30:49 -07:00
Nikolay Kim
3dafe6c251 hide token and server flags 2018-08-23 11:30:07 -07:00
Nikolay Kim
8dfc34e785 fix tokio-tls IoStream impl 2018-08-23 10:27:32 -07:00
Nikolay Kim
810995ade0 fix tokio-tls dependency #480 2018-08-23 10:10:13 -07:00
Nikolay Kim
1716380f08 clippy fmt 2018-08-23 09:48:01 -07:00
Nikolay Kim
e9c139bdea clippy warnings 2018-08-23 09:47:32 -07:00
Nikolay Kim
cf54be2f17 hide new server api 2018-08-23 09:39:11 -07:00
Nikolay Kim
f39b520a2d Merge pull request #478 from fzgregor/master
Made extensions constructor public
2018-08-23 09:34:47 -07:00
Nikolay Kim
89f414477c Merge branch 'master' into master 2018-08-23 09:34:34 -07:00
Douman
986f19af86 Revert back to serde_urlencoded dependecy (#479) 2018-08-21 22:23:17 +03:00
Franz Gregor
e680541e10 Made extensions constructor public 2018-08-18 19:32:28 +02:00
Douman
56bc900a82 Set minimum rustls version that fixes corruption (#474) 2018-08-17 19:53:16 +03:00
Kornel
bdc9a8bb07 Optionally support tokio-uds's UnixStream as IoStream (#472) 2018-08-17 19:04:15 +03:00
Nikolay Kim
8fe30a5b66 Merge pull request #473 from kornelski/usetest
Fix tests on Unix
2018-08-17 07:20:47 -07:00
Kornel Lesiński
a8405d0686 Fix tests on Unix 2018-08-17 13:13:48 +01:00
Nikolay Kim
eb1e9a785f allow to use fn with multiple arguments with .with()/.with_async() 2018-08-16 20:29:06 -07:00
Douman
248bd388ca Improve HTTP server docs (#470) 2018-08-16 16:11:15 +03:00
Douman
9f5641c85b Add mention of reworked Content-Disposition 2018-08-13 17:37:00 +03:00
Gowee
d9c7cd96a6 Rework Content-Disposition parsing totally (#461) 2018-08-13 17:34:05 +03:00
Nikolay Kim
bf7779a9a3 add TestRequest::run_async_result helper method 2018-08-09 18:58:14 -07:00
Nikolay Kim
cc3fbd27e0 better ergonomics 2018-08-09 17:25:23 -07:00
Nikolay Kim
26629aafa5 explicit use 2018-08-09 13:41:13 -07:00
Nikolay Kim
2ab7dbadce better ergonomics for Server::service() method 2018-08-09 13:38:10 -07:00
Nikolay Kim
2e8d67e2ae upgrade native-tls package 2018-08-09 13:08:59 -07:00
Nikolay Kim
43b6828ab5 Merge branch 'master' of https://github.com/actix/actix-web 2018-08-09 11:52:45 -07:00
Nikolay Kim
e4ce6dfbdf refactor workers management 2018-08-09 11:52:32 -07:00
Nikolay Kim
6b9fa2c3d9 Merge pull request #458 from davidMcneil/master
Add json2 HttpResponseBuilder method
2018-08-09 02:10:14 -07:00
Douman
5713d93158 Merge branch 'master' into master 2018-08-09 08:13:22 +03:00
Nikolay Kim
cfe4829a56 add TestRequest::execute() helper method 2018-08-08 16:13:45 -07:00
Nikolay Kim
b69774db61 fix attr name 2018-08-08 14:23:16 -07:00
Nikolay Kim
542782f28a add HttpRequest::drop_state() 2018-08-08 13:57:13 -07:00
David McNeil
7c8dc4c201 Add json2 tests 2018-08-08 12:17:19 -06:00
David McNeil
7a11c2eac1 Add json2 HttpResponseBuilder method 2018-08-08 11:11:15 -06:00
Nikolay Kim
8eb9eb4247 flush io on complete 2018-08-08 09:12:32 -07:00
Nikolay Kim
992f7a11b3 remove debug println 2018-08-07 22:40:09 -07:00
Nikolay Kim
30769e3072 fix http/2 error handling 2018-08-07 20:48:25 -07:00
Nikolay Kim
57f991280c fix protocol order for rustls acceptor 2018-08-07 13:53:24 -07:00
Nikolay Kim
85acc3f8df deprecate HttpServer::no_http2(), update changes 2018-08-07 12:49:40 -07:00
Nikolay Kim
5bd82d4f03 update changes 2018-08-07 12:00:51 -07:00
Nikolay Kim
58a079bd10 include content-length to error response 2018-08-07 11:56:39 -07:00
Nikolay Kim
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
Douman
86a5afb5ca Reserve enough space for ServerError task to write status line 2018-08-07 17:34:24 +03:00
Douman
9c80d3aa77 Write non-80 port in HOST of client's request (#451) 2018-08-07 10:01:29 +03:00
Erik Desjardins
954f1a0b0f impl FromRequest for () (#449) 2018-08-06 10:44:08 +03:00
Nikolay Kim
f4fba5f481 Merge pull request #447 from DoumanAsh/multiple_set_cookies
Correct setting cookies in HTTP2 writer
2018-08-04 08:58:12 -07:00
Nikolay Kim
995f819eae Merge branch 'master' into multiple_set_cookies 2018-08-04 08:58:00 -07:00
Nikolay Kim
85e7548088 fix adding multiple response headers for http/2 #446 2018-08-04 08:56:33 -07:00
Douman
900fd5a98e Correct settings headers for HTTP2
Add test to verify number of Set-Cookies
2018-08-04 18:05:41 +03:00
Nikolay Kim
84b27db218 fix no_http2 flag 2018-08-03 19:40:43 -07:00
Nikolay Kim
ac9180ac46 simplify channel impl 2018-08-03 19:32:46 -07:00
Nikolay Kim
e34b5c08ba allow to pass extra information from acceptor to application level 2018-08-03 19:24:53 -07:00
Nikolay Kim
f3f1e04853 refactor ssl support 2018-08-03 16:09:46 -07:00
Nikolay Kim
036cf5e867 update changes 2018-08-03 08:20:59 -07:00
Jan Michael Auer
e61ef7dee4 Use zlib instead of deflate for content encoding (#442) 2018-08-03 14:56:26 +02:00
Mathieu Amiot
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
Mathieu Amiot
f8e5d7c6c1 Fixed broken build on wrong variable usage (#440) 2018-08-03 14:11:51 +03:00
Nikolay Kim
8c89c90c50 add accept backpressure #250 2018-08-02 23:17:10 -07:00
Nikolay Kim
e9c1889df4 test timing 2018-08-01 16:41:24 -07:00
91 changed files with 8300 additions and 5508 deletions

View File

@@ -1,6 +1,6 @@
environment: environment:
global: global:
PROJECT_NAME: actix PROJECT_NAME: actix-web
matrix: matrix:
# Stable channel # Stable channel
- TARGET: i686-pc-windows-msvc - TARGET: i686-pc-windows-msvc
@@ -37,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

@@ -30,14 +30,17 @@ before_script:
script: script:
- | - |
if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then
cargo clean cargo clean
cargo test --features="alpn,tls,rust-tls" -- --nocapture cargo check --features rust-tls
cargo check --features ssl
cargo check --features tls
cargo test --features="ssl,tls,rust-tls,uds" -- --nocapture
fi fi
- | - |
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin
cargo tarpaulin --features="alpn,tls,rust-tls" --out Xml --no-count 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
@@ -45,8 +48,8 @@ script:
# Upload docs # Upload docs
after_success: after_success:
- | - |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --features "alpn, tls, rust-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,269 @@
# Changes # Changes
## [0.7.19] - 2019-03-29
### Added
* Add `from_file` and `from_file_with_config` to `NamedFile` to allow sending files without a known path. #670
* Add `insert` and `remove` methods to `HttpResponseBuilder`
* Add client HTTP Authentication methods `.basic_auth()` and `.bearer_auth()`. #540
* Add support for PATCH HTTP method
### Fixed
* Ignored the `If-Modified-Since` if `If-None-Match` is specified. #680
* Do not remove `Content-Length` on `Body::Empty` and insert zero value if it is missing for `POST` and `PUT` methods.
* Fix preflight CORS header compliance; refactor previous patch (#603). #717
* Fix never-ending HTTP2 request when response is empty (#709). #737
* Fix client payload decompression #674
## [0.7.18] - 2019-01-10
### Added
* Add `with_cookie` for `TestRequest` to allow users to customize request cookie. #647
* Add `cookie` method for `TestRequest` to allow users to add cookie dynamically.
### Fixed
* StaticFiles decode special characters in request's path
* Fix test server listener leak #654
## [0.7.17] - 2018-12-25
### Added
* Support for custom content types in `JsonConfig`. #637
* Send `HTTP/1.1 100 Continue` if request contains `expect: continue` header #634
### Fixed
* HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631
* Access-Control-Allow-Origin header should only a return a single, matching origin. #603
## [0.7.16] - 2018-12-11
### Added
* Implement `FromRequest` extractor for `Either<A,B>`
* Implement `ResponseError` for `SendError`
## [0.7.15] - 2018-12-05
### Changed
* `ClientConnector::resolver` now accepts `Into<Recipient>` instead of `Addr`. It enables user to implement own resolver.
* `QueryConfig` and `PathConfig` are made public.
* `AsyncResult::async` is changed to `AsyncResult::future` as `async` is reserved keyword in 2018 edition.
### Added
* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled
with `PathConfig::default().disable_decoding()`
## [0.7.14] - 2018-11-14
### Added
* Add method to configure custom error handler to `Query` and `Path` extractors.
* Add method to configure `SameSite` option in `CookieIdentityPolicy`.
* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled
with `PathConfig::default().disable_decoding()`
### Fixed
* Fix websockets connection drop if request contains "content-length" header #567
* Fix keep-alive timer reset
* HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549
* Set nodelay for socket #560
## [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 ## [0.7.3] - 2018-08-01
### Added ### Added
@@ -8,7 +272,6 @@
* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433 * Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433
### Fixed ### Fixed
* Fixed failure 0.1.2 compatibility * Fixed failure 0.1.2 compatibility
@@ -19,6 +282,8 @@
* HttpRequest::url_for is not working with scopes #429 * 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 ## [0.7.2] - 2018-07-26

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "0.7.3" version = "0.7.19"
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"
@@ -17,7 +17,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs" build = "build.rs"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"] 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" }
@@ -29,16 +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 # rustls
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"] 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"]
@@ -52,21 +58,25 @@ 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"]
[dependencies] cell = ["actix-net/cell"]
actix = "0.7.0"
base64 = "0.9" [dependencies]
actix = "0.7.9"
actix-net = "0.2.6"
v_htmlescape = "0.4"
base64 = "0.10"
bitflags = "1.0" bitflags = "1.0"
failure = "^0.1.2"
h2 = "0.1" h2 = "0.1"
htmlescape = "0.3" http = "^0.1.14"
http = "^0.1.8"
httparse = "1.3" httparse = "1.3"
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.5" rand = "0.6"
regex = "1.0" regex = "1.0"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
@@ -77,14 +87,13 @@ encoding = "0.2"
language-tags = "0.2" language-tags = "0.2"
lazy_static = "1.0" lazy_static = "1.0"
lazycell = "1.0.0" lazycell = "1.0.0"
parking_lot = "0.6" parking_lot = "0.7"
serde_urlencoded = "^0.5.3"
url = { version="1.7", features=["query_encoding"] } url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.11", 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.2", optional = true, default-features = false } flate2 = { version="^1.0.2", optional = true, default-features = false }
failure = "^0.1.2"
# io # io
mio = "^0.6.13" mio = "^0.6.13"
net2 = "0.2" net2 = "0.2"
@@ -96,29 +105,29 @@ slab = "0.4"
tokio = "0.1" tokio = "0.1"
tokio-io = "0.1" tokio-io = "0.1"
tokio-tcp = "0.1" tokio-tcp = "0.1"
tokio-timer = "0.2" tokio-timer = "0.2.8"
tokio-reactor = "0.1" 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
rustls = { version = "0.13", optional = true } rustls = { version = "0.14", optional = true }
tokio-rustls = { version = "0.7", optional = true } tokio-rustls = { version = "0.8", optional = true }
webpki = { version = "0.18", optional = true } webpki = { version = "0.18", optional = true }
webpki-roots = { version = "0.15", optional = true } webpki-roots = { version = "0.15", optional = true }
# forked url_encoded # unix sockets
itoa = "0.4" tokio-uds = { version="0.2", optional = true }
dtoa = "0.4"
[dev-dependencies] [dev-dependencies]
env_logger = "0.5" env_logger = "0.6"
serde_derive = "1.0" serde_derive = "1.0"
[build-dependencies] [build-dependencies]
@@ -128,8 +137,3 @@ version_check = "0.1"
lto = true lto = true
opt-level = 3 opt-level = 3
codegen-units = 1 codegen-units = 1
[workspace]
members = [
"./",
]

View File

@@ -1,3 +1,39 @@
## 0.7.15
* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
your routes, you should use `%20`.
instead of
```rust
fn main() {
let app = App::new().resource("/my index", |r| {
r.method(http::Method::GET)
.with(index);
});
}
```
use
```rust
fn main() {
let app = App::new().resource("/my%20index", |r| {
r.method(http::Method::GET)
.with(index);
});
}
```
* If you used `AsyncResult::async` you need to replace it with `AsyncResult::future`
## 0.7.4
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
## 0.7 ## 0.7
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload * `HttpRequest` does not implement `Stream` anymore. If you need to read request payload

View File

@@ -8,13 +8,13 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Client/server [WebSockets](https://actix.rs/docs/websockets/) 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/docs/url-dispatch/) * 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, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) * Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/))
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Built on top of [Actix actor framework](https://github.com/actix/actix) * Built on top of [Actix actor framework](https://github.com/actix/actix)
* Experimental [Async/Await](https://github.com/mehcode/actix-web-async-await) support.
## Documentation & community resources ## Documentation & community resources
@@ -23,7 +23,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/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.26 or later * Minimum supported Rust version: 1.31 or later
## Example ## Example
@@ -66,8 +66,6 @@ You may consider checking out
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&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
This project is licensed under either of This project is licensed under either of

View File

@@ -12,6 +12,7 @@ use resource::Resource;
use router::{ResourceDef, Router}; use router::{ResourceDef, Router};
use scope::Scope; use scope::Scope;
use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request};
use with::WithFactory;
/// Application /// Application
pub struct HttpApplication<S = ()> { pub struct HttpApplication<S = ()> {
@@ -134,7 +135,7 @@ where
/// instance for each thread, thus application state must be constructed /// instance for each thread, thus application state must be constructed
/// multiple times. If you want to share state between different /// multiple times. If you want to share state between different
/// threads, a shared object should be used, e.g. `Arc`. Application /// threads, a shared object should be used, e.g. `Arc`. Application
/// state does not need to be `Send` and `Sync`. /// state does not need to be `Send` or `Sync`.
pub fn with_state(state: S) -> App<S> { pub fn with_state(state: S) -> App<S> {
App { App {
parts: Some(ApplicationParts { parts: Some(ApplicationParts {
@@ -249,7 +250,7 @@ where
/// ``` /// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S> pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S>
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,
{ {
@@ -446,11 +447,8 @@ where
{ {
let mut path = path.trim().trim_right_matches('/').to_owned(); let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') { if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/') path.insert(0, '/');
} };
if path.len() > 1 && path.ends_with('/') {
path.pop();
}
self.parts self.parts
.as_mut() .as_mut()
.expect("Use after finish") .expect("Use after finish")
@@ -775,8 +773,7 @@ mod tests {
.route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok())
.route("/test", Method::POST, |_: HttpRequest| { .route("/test", Method::POST, |_: HttpRequest| {
HttpResponse::Created() HttpResponse::Created()
}) }).finish();
.finish();
let req = TestRequest::with_uri("/test").method(Method::GET).request(); let req = TestRequest::with_uri("/test").method(Method::GET).request();
let resp = app.run(req); let resp = app.run(req);

View File

@@ -1,5 +1,6 @@
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::Stream; use futures::Stream;
use std::borrow::Cow;
use std::sync::Arc; use std::sync::Arc;
use std::{fmt, mem}; use std::{fmt, mem};
@@ -194,12 +195,30 @@ impl From<Vec<u8>> for Binary {
} }
} }
impl From<Cow<'static, [u8]>> for Binary {
fn from(b: Cow<'static, [u8]>) -> Binary {
match b {
Cow::Borrowed(s) => Binary::Slice(s),
Cow::Owned(vec) => Binary::Bytes(Bytes::from(vec)),
}
}
}
impl From<String> for Binary { impl From<String> for Binary {
fn from(s: String) -> Binary { fn from(s: String) -> Binary {
Binary::Bytes(Bytes::from(s)) Binary::Bytes(Bytes::from(s))
} }
} }
impl From<Cow<'static, str>> for Binary {
fn from(s: Cow<'static, str>) -> Binary {
match s {
Cow::Borrowed(s) => Binary::Slice(s.as_ref()),
Cow::Owned(s) => Binary::Bytes(Bytes::from(s)),
}
}
}
impl<'a> From<&'a String> for Binary { impl<'a> From<&'a String> for Binary {
fn from(s: &'a String) -> Binary { fn from(s: &'a String) -> Binary {
Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s))) Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s)))
@@ -287,6 +306,16 @@ mod tests {
assert_eq!(Binary::from("test").as_ref(), b"test"); assert_eq!(Binary::from("test").as_ref(), b"test");
} }
#[test]
fn test_cow_str() {
let cow: Cow<'static, str> = Cow::Borrowed("test");
assert_eq!(Binary::from(cow.clone()).len(), 4);
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
let cow: Cow<'static, str> = Cow::Owned("test".to_owned());
assert_eq!(Binary::from(cow.clone()).len(), 4);
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
}
#[test] #[test]
fn test_static_bytes() { fn test_static_bytes() {
assert_eq!(Binary::from(b"test".as_ref()).len(), 4); assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
@@ -307,6 +336,16 @@ 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_cow_bytes() {
let cow: Cow<'static, [u8]> = Cow::Borrowed(b"test");
assert_eq!(Binary::from(cow.clone()).len(), 4);
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
let cow: Cow<'static, [u8]> = Cow::Owned(Vec::from("test"));
assert_eq!(Binary::from(cow.clone()).len(), 4);
assert_eq!(Binary::from(cow.clone()).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());

View File

@@ -3,9 +3,9 @@ use std::net::Shutdown;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::{fmt, io, mem, time}; use std::{fmt, io, mem, time};
use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; use actix_inner::actors::resolver::{Connect as ResolveConnect, Resolver, ResolverError};
use actix::{ use actix_inner::{
fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context, fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context,
ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised,
SystemService, WrapFuture, SystemService, WrapFuture,
}; };
@@ -16,58 +16,37 @@ use http::{Error as HttpError, HttpTryFrom, Uri};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::Delay; use tokio_timer::Delay;
#[cfg(feature = "alpn")] #[cfg(any(feature = "alpn", feature = "ssl"))]
use openssl::ssl::{Error as OpensslError, SslConnector, SslMethod}; use {
#[cfg(feature = "alpn")] openssl::ssl::{Error as SslError, SslConnector, SslMethod},
use tokio_openssl::SslConnectorExt; tokio_openssl::SslConnectorExt,
};
#[cfg(all(feature = "tls", not(feature = "alpn")))] #[cfg(all(
use native_tls::{Error as TlsError, TlsConnector, TlsStream}; feature = "tls",
#[cfg(all(feature = "tls", not(feature = "alpn")))] not(any(feature = "alpn", feature = "ssl", feature = "rust-tls"))
use tokio_tls::TlsConnectorExt; ))]
use {
native_tls::{Error as SslError, TlsConnector as NativeTlsConnector},
tokio_tls::TlsConnector as SslConnector,
};
#[cfg( #[cfg(all(
all( feature = "rust-tls",
feature = "rust-tls", not(any(feature = "alpn", feature = "tls", feature = "ssl"))
not(any(feature = "alpn", feature = "tls")) ))]
) use {
)] rustls::ClientConfig, std::io::Error as SslError, std::sync::Arc,
use rustls::ClientConfig; tokio_rustls::TlsConnector as SslConnector, webpki::DNSNameRef, webpki_roots,
#[cfg( };
all(
feature = "rust-tls", #[cfg(not(any(
not(any(feature = "alpn", feature = "tls")) feature = "alpn",
) feature = "ssl",
)] feature = "tls",
use std::io::Error as TLSError; feature = "rust-tls"
#[cfg( )))]
all( type SslConnector = ();
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
use std::sync::Arc;
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
use tokio_rustls::ClientConfigExt;
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
use webpki::DNSNameRef;
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
use webpki_roots;
use server::IoStream; use server::IoStream;
use {HAS_OPENSSL, HAS_RUSTLS, HAS_TLS}; use {HAS_OPENSSL, HAS_RUSTLS, HAS_TLS};
@@ -173,24 +152,14 @@ pub enum ClientConnectorError {
SslIsNotSupported, SslIsNotSupported,
/// SSL error /// SSL error
#[cfg(feature = "alpn")] #[cfg(any(
feature = "tls",
feature = "alpn",
feature = "ssl",
feature = "rust-tls",
))]
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
SslError(#[cause] OpensslError), SslError(#[cause] SslError),
/// SSL error
#[cfg(all(feature = "tls", not(feature = "alpn")))]
#[fail(display = "{}", _0)]
SslError(#[cause] TlsError),
/// SSL error
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
#[fail(display = "{}", _0)]
SslError(#[cause] TLSError),
/// Resolver error /// Resolver error
#[fail(display = "{}", _0)] #[fail(display = "{}", _0)]
@@ -242,17 +211,8 @@ impl Paused {
/// `ClientConnector` type is responsible for transport layer of a /// `ClientConnector` type is responsible for transport layer of a
/// client connection. /// client connection.
pub struct ClientConnector { pub struct ClientConnector {
#[cfg(all(feature = "alpn"))] #[allow(dead_code)]
connector: SslConnector, connector: SslConnector,
#[cfg(all(feature = "tls", not(feature = "alpn")))]
connector: TlsConnector,
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
connector: Arc<ClientConfig>,
stats: ClientConnectorStats, stats: ClientConnectorStats,
subscriber: Option<Recipient<ClientConnectorStats>>, subscriber: Option<Recipient<ClientConnectorStats>>,
@@ -260,7 +220,7 @@ pub struct ClientConnector {
acq_tx: mpsc::UnboundedSender<AcquiredConnOperation>, acq_tx: mpsc::UnboundedSender<AcquiredConnOperation>,
acq_rx: Option<mpsc::UnboundedReceiver<AcquiredConnOperation>>, acq_rx: Option<mpsc::UnboundedReceiver<AcquiredConnOperation>>,
resolver: Option<Addr<Resolver>>, resolver: Option<Recipient<ResolveConnect>>,
conn_lifetime: Duration, conn_lifetime: Duration,
conn_keep_alive: Duration, conn_keep_alive: Duration,
limit: usize, limit: usize,
@@ -279,7 +239,7 @@ impl Actor for ClientConnector {
fn started(&mut self, ctx: &mut Self::Context) { fn started(&mut self, ctx: &mut Self::Context) {
if self.resolver.is_none() { if self.resolver.is_none() {
self.resolver = Some(Resolver::from_registry()) self.resolver = Some(Resolver::from_registry().recipient())
} }
self.collect_periodic(ctx); self.collect_periodic(ctx);
ctx.add_stream(self.acq_rx.take().unwrap()); ctx.add_stream(self.acq_rx.take().unwrap());
@@ -293,76 +253,47 @@ impl SystemService for ClientConnector {}
impl Default for ClientConnector { impl Default for ClientConnector {
fn default() -> ClientConnector { fn default() -> ClientConnector {
#[cfg(all(feature = "alpn"))] let connector = {
{ #[cfg(all(any(feature = "alpn", feature = "ssl")))]
let builder = SslConnector::builder(SslMethod::tls()).unwrap(); {
ClientConnector::with_connector(builder.build()) SslConnector::builder(SslMethod::tls()).unwrap().build()
}
#[cfg(all(feature = "tls", not(feature = "alpn")))]
{
let (tx, rx) = mpsc::unbounded();
let builder = TlsConnector::builder().unwrap();
ClientConnector {
stats: ClientConnectorStats::default(),
subscriber: None,
acq_tx: tx,
acq_rx: Some(rx),
resolver: None,
connector: builder.build().unwrap(),
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100,
limit_per_host: 0,
acquired: 0,
acquired_per_host: HashMap::new(),
available: HashMap::new(),
to_close: Vec::new(),
waiters: Some(HashMap::new()),
wait_timeout: None,
paused: Paused::No,
} }
}
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
{
let mut config = ClientConfig::new();
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
ClientConnector::with_connector(config)
}
#[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] #[cfg(all(
{ feature = "tls",
let (tx, rx) = mpsc::unbounded(); not(any(feature = "alpn", feature = "ssl", feature = "rust-tls"))
ClientConnector { ))]
stats: ClientConnectorStats::default(), {
subscriber: None, NativeTlsConnector::builder().build().unwrap().into()
acq_tx: tx,
acq_rx: Some(rx),
resolver: None,
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100,
limit_per_host: 0,
acquired: 0,
acquired_per_host: HashMap::new(),
available: HashMap::new(),
to_close: Vec::new(),
waiters: Some(HashMap::new()),
wait_timeout: None,
paused: Paused::No,
} }
}
#[cfg(all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls", feature = "ssl"))
))]
{
let mut config = ClientConfig::new();
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
SslConnector::from(Arc::new(config))
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[cfg(not(any(
feature = "alpn", feature = "ssl", feature = "tls", feature = "rust-tls")))]
{
()
}
};
#[cfg_attr(feature = "cargo-clippy", allow(let_unit_value))]
ClientConnector::with_connector_impl(connector)
} }
} }
impl ClientConnector { impl ClientConnector {
#[cfg(feature = "alpn")] #[cfg(any(feature = "alpn", feature = "ssl"))]
/// Create `ClientConnector` actor with custom `SslConnector` instance. /// Create `ClientConnector` actor with custom `SslConnector` instance.
/// ///
/// By default `ClientConnector` uses very a simple SSL configuration. /// By default `ClientConnector` uses very a simple SSL configuration.
@@ -375,7 +306,6 @@ impl ClientConnector {
/// # extern crate futures; /// # extern crate futures;
/// # use futures::{future, Future}; /// # use futures::{future, Future};
/// # use std::io::Write; /// # use std::io::Write;
/// # use std::process;
/// # use actix_web::actix::Actor; /// # use actix_web::actix::Actor;
/// extern crate openssl; /// extern crate openssl;
/// use actix_web::{actix, client::ClientConnector, client::Connect}; /// use actix_web::{actix, client::ClientConnector, client::Connect};
@@ -402,35 +332,14 @@ impl ClientConnector {
/// } /// }
/// ``` /// ```
pub fn with_connector(connector: SslConnector) -> ClientConnector { pub fn with_connector(connector: SslConnector) -> ClientConnector {
let (tx, rx) = mpsc::unbounded(); // keep level of indirection for docstrings matching featureflags
Self::with_connector_impl(connector)
ClientConnector {
connector,
stats: ClientConnectorStats::default(),
subscriber: None,
acq_tx: tx,
acq_rx: Some(rx),
resolver: None,
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100,
limit_per_host: 0,
acquired: 0,
acquired_per_host: HashMap::new(),
available: HashMap::new(),
to_close: Vec::new(),
waiters: Some(HashMap::new()),
wait_timeout: None,
paused: Paused::No,
}
} }
#[cfg( #[cfg(all(
all( feature = "rust-tls",
feature = "rust-tls", not(any(feature = "alpn", feature = "ssl", feature = "tls"))
not(any(feature = "alpn", feature = "tls")) ))]
)
)]
/// Create `ClientConnector` actor with custom `SslConnector` instance. /// Create `ClientConnector` actor with custom `SslConnector` instance.
/// ///
/// By default `ClientConnector` uses very a simple SSL configuration. /// By default `ClientConnector` uses very a simple SSL configuration.
@@ -441,10 +350,8 @@ impl ClientConnector {
/// # #![cfg(feature = "rust-tls")] /// # #![cfg(feature = "rust-tls")]
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
/// # extern crate tokio;
/// # use futures::{future, Future}; /// # use futures::{future, Future};
/// # use std::io::Write; /// # use std::io::Write;
/// # use std::process;
/// # use actix_web::actix::Actor; /// # use actix_web::actix::Actor;
/// extern crate rustls; /// extern crate rustls;
/// extern crate webpki_roots; /// extern crate webpki_roots;
@@ -460,7 +367,7 @@ impl ClientConnector {
/// config /// config
/// .root_store /// .root_store
/// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); /// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
/// let conn = ClientConnector::with_connector(Arc::new(config)).start(); /// let conn = ClientConnector::with_connector(config).start();
/// ///
/// conn.send( /// conn.send(
/// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host
@@ -476,10 +383,61 @@ impl ClientConnector {
/// } /// }
/// ``` /// ```
pub fn with_connector(connector: ClientConfig) -> ClientConnector { pub fn with_connector(connector: ClientConfig) -> ClientConnector {
// keep level of indirection for docstrings matching featureflags
Self::with_connector_impl(SslConnector::from(Arc::new(connector)))
}
#[cfg(all(
feature = "tls",
not(any(feature = "ssl", feature = "alpn", feature = "rust-tls"))
))]
/// Create `ClientConnector` actor with custom `SslConnector` instance.
///
/// By default `ClientConnector` uses very a simple SSL configuration.
/// With `with_connector` method it is possible to use a custom
/// `SslConnector` object.
///
/// ```rust
/// # #![cfg(feature = "tls")]
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::{future, Future};
/// # use std::io::Write;
/// # use actix_web::actix::Actor;
/// extern crate native_tls;
/// extern crate webpki_roots;
/// use native_tls::TlsConnector;
/// use actix_web::{actix, client::ClientConnector, client::Connect};
///
/// fn main() {
/// actix::run(|| {
/// let connector = TlsConnector::new().unwrap();
/// let conn = ClientConnector::with_connector(connector.into()).start();
///
/// conn.send(
/// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host
/// .map_err(|_| ())
/// .and_then(|res| {
/// if let Ok(mut stream) = res {
/// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
/// }
/// # actix::System::current().stop();
/// Ok(())
/// })
/// });
/// }
/// ```
pub fn with_connector(connector: SslConnector) -> ClientConnector {
// keep level of indirection for docstrings matching featureflags
Self::with_connector_impl(connector)
}
#[inline]
fn with_connector_impl(connector: SslConnector) -> ClientConnector {
let (tx, rx) = mpsc::unbounded(); let (tx, rx) = mpsc::unbounded();
ClientConnector { ClientConnector {
connector: Arc::new(connector), connector,
stats: ClientConnectorStats::default(), stats: ClientConnectorStats::default(),
subscriber: None, subscriber: None,
acq_tx: tx, acq_tx: tx,
@@ -545,8 +503,10 @@ impl ClientConnector {
} }
/// Use custom resolver actor /// Use custom resolver actor
pub fn resolver(mut self, addr: Addr<Resolver>) -> Self { ///
self.resolver = Some(addr); /// By default actix's Resolver is used.
pub fn resolver<A: Into<Recipient<ResolveConnect>>>(mut self, addr: A) -> Self {
self.resolver = Some(addr.into());
self self
} }
@@ -768,168 +728,164 @@ impl ClientConnector {
).map_err(move |_, act, _| { ).map_err(move |_, act, _| {
act.release_key(&key2); act.release_key(&key2);
() ()
}) }).and_then(move |res, act, _| {
.and_then(move |res, act, _| { #[cfg(any(feature = "alpn", feature = "ssl"))]
#[cfg(feature = "alpn")] match res {
match res { Err(err) => {
Err(err) => { let _ = waiter.tx.send(Err(err.into()));
let _ = waiter.tx.send(Err(err.into())); fut::Either::B(fut::err(()))
fut::Either::B(fut::err(())) }
} Ok(stream) => {
Ok(stream) => { act.stats.opened += 1;
act.stats.opened += 1; if conn.0.ssl {
if conn.0.ssl { fut::Either::A(
fut::Either::A( act.connector
act.connector .connect_async(&key.host, stream)
.connect_async(&key.host, stream) .into_actor(act)
.into_actor(act) .then(move |res, _, _| {
.then(move |res, _, _| { match res {
match res { Err(e) => {
Err(e) => { let _ = waiter.tx.send(Err(
let _ = waiter.tx.send(Err( ClientConnectorError::SslError(e),
ClientConnectorError::SslError(e), ));
));
}
Ok(stream) => {
let _ =
waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
}
} }
fut::ok(()) Ok(stream) => {
}), let _ = waiter.tx.send(Ok(Connection::new(
) conn.0.clone(),
} else { Some(conn),
let _ = waiter.tx.send(Ok(Connection::new( Box::new(stream),
conn.0.clone(), )));
Some(conn),
Box::new(stream),
)));
fut::Either::B(fut::ok(()))
}
}
}
#[cfg(all(feature = "tls", not(feature = "alpn")))]
match res {
Err(err) => {
let _ = waiter.tx.send(Err(err.into()));
fut::Either::B(fut::err(()))
}
Ok(stream) => {
act.stats.opened += 1;
if conn.0.ssl {
fut::Either::A(
act.connector
.connect_async(&conn.0.host, stream)
.into_actor(act)
.then(move |res, _, _| {
match res {
Err(e) => {
let _ = waiter.tx.send(Err(
ClientConnectorError::SslError(e),
));
}
Ok(stream) => {
let _ =
waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
}
} }
fut::ok(()) }
}), fut::ok(())
) }),
} else { )
let _ = waiter.tx.send(Ok(Connection::new( } else {
conn.0.clone(), let _ = waiter.tx.send(Ok(Connection::new(
Some(conn), conn.0.clone(),
Box::new(stream), Some(conn),
))); Box::new(stream),
fut::Either::B(fut::ok(())) )));
} fut::Either::B(fut::ok(()))
} }
} }
}
#[cfg( #[cfg(all(feature = "tls", not(any(feature = "alpn", feature = "ssl"))))]
all( match res {
feature = "rust-tls", Err(err) => {
not(any(feature = "alpn", feature = "tls")) let _ = waiter.tx.send(Err(err.into()));
) fut::Either::B(fut::err(()))
)] }
match res { Ok(stream) => {
Err(err) => { act.stats.opened += 1;
let _ = waiter.tx.send(Err(err.into())); if conn.0.ssl {
fut::Either::B(fut::err(())) fut::Either::A(
} act.connector
Ok(stream) => { .connect(&conn.0.host, stream)
act.stats.opened += 1; .into_actor(act)
if conn.0.ssl { .then(move |res, _, _| {
let host = match res {
DNSNameRef::try_from_ascii_str(&key.host).unwrap(); Err(e) => {
fut::Either::A( let _ = waiter.tx.send(Err(
act.connector ClientConnectorError::SslError(e),
.connect_async(host, stream) ));
.into_actor(act)
.then(move |res, _, _| {
match res {
Err(e) => {
let _ = waiter.tx.send(Err(
ClientConnectorError::SslError(e),
));
}
Ok(stream) => {
let _ =
waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
}
} }
fut::ok(()) Ok(stream) => {
}), let _ = waiter.tx.send(Ok(Connection::new(
) conn.0.clone(),
} else { Some(conn),
let _ = waiter.tx.send(Ok(Connection::new( Box::new(stream),
conn.0.clone(), )));
Some(conn), }
Box::new(stream), }
))); fut::ok(())
fut::Either::B(fut::ok(())) }),
} )
} else {
let _ = waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
fut::Either::B(fut::ok(()))
} }
} }
}
#[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] #[cfg(all(
match res { feature = "rust-tls",
Err(err) => { not(any(feature = "alpn", feature = "ssl", feature = "tls"))
let _ = waiter.tx.send(Err(err.into())); ))]
fut::err(()) match res {
} Err(err) => {
Ok(stream) => { let _ = waiter.tx.send(Err(err.into()));
act.stats.opened += 1; fut::Either::B(fut::err(()))
if conn.0.ssl { }
let _ = waiter Ok(stream) => {
.tx act.stats.opened += 1;
.send(Err(ClientConnectorError::SslIsNotSupported)); if conn.0.ssl {
} else { let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap();
let _ = waiter.tx.send(Ok(Connection::new( fut::Either::A(
conn.0.clone(), act.connector
Some(conn), .connect(host, stream)
Box::new(stream), .into_actor(act)
))); .then(move |res, _, _| {
}; match res {
fut::ok(()) Err(e) => {
let _ = waiter.tx.send(Err(
ClientConnectorError::SslError(e),
));
}
Ok(stream) => {
let _ = waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
}
}
fut::ok(())
}),
)
} else {
let _ = waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
fut::Either::B(fut::ok(()))
} }
} }
}) }
.spawn(ctx);
#[cfg(not(any(
feature = "alpn",
feature = "ssl",
feature = "tls",
feature = "rust-tls"
)))]
match res {
Err(err) => {
let _ = waiter.tx.send(Err(err.into()));
fut::err(())
}
Ok(stream) => {
act.stats.opened += 1;
if conn.0.ssl {
let _ =
waiter.tx.send(Err(ClientConnectorError::SslIsNotSupported));
} else {
let _ = waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
};
fut::ok(())
}
}
}).spawn(ctx);
} }
} }
@@ -986,7 +942,7 @@ impl Handler<Connect> for ClientConnector {
} }
let host = uri.host().unwrap().to_owned(); let host = uri.host().unwrap().to_owned();
let port = uri.port().unwrap_or_else(|| proto.port()); let port = uri.port_part().map(|port| port.as_u16()).unwrap_or_else(|| proto.port());
let key = Key { let key = Key {
host, host,
port, port,
@@ -1287,6 +1243,10 @@ impl Connection {
} }
/// Create a new connection from an IO Stream /// Create a new connection from an IO Stream
///
/// The stream can be a `UnixStream` if the Unix-only "uds" feature is enabled.
///
/// See also `ClientRequestBuilder::with_connection()`.
pub fn from_stream<T: IoStream + Send>(io: T) -> Connection { pub fn from_stream<T: IoStream + Send>(io: T) -> Connection {
Connection::new(Key::empty(), None, Box::new(io)) Connection::new(Key::empty(), None, Box::new(io))
} }
@@ -1320,6 +1280,11 @@ impl IoStream for Connection {
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> { fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
IoStream::set_linger(&mut *self.stream, dur) IoStream::set_linger(&mut *self.stream, dur)
} }
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
IoStream::set_keepalive(&mut *self.stream, dur)
}
} }
impl io::Read for Connection { impl io::Read for Connection {
@@ -1345,3 +1310,31 @@ impl AsyncWrite for Connection {
self.stream.shutdown() self.stream.shutdown()
} }
} }
#[cfg(feature = "tls")]
use tokio_tls::TlsStream;
#[cfg(feature = "tls")]
/// This is temp solution untile actix-net migration
impl<Io: IoStream> IoStream for TlsStream<Io> {
#[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)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_keepalive(dur)
}
}

View File

@@ -2,11 +2,12 @@
//! //!
//! ```rust //! ```rust
//! # extern crate actix_web; //! # extern crate actix_web;
//! # extern crate actix;
//! # extern crate futures; //! # extern crate futures;
//! # extern crate tokio; //! # extern crate tokio;
//! # use futures::Future;
//! # use std::process; //! # use std::process;
//! use actix_web::{actix, client}; //! use actix_web::client;
//! use futures::Future;
//! //!
//! fn main() { //! fn main() {
//! actix::run( //! actix::run(
@@ -61,12 +62,13 @@ impl ResponseError for SendRequestError {
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate actix;
/// # extern crate futures; /// # extern crate futures;
/// # extern crate tokio; /// # extern crate tokio;
/// # extern crate env_logger; /// # extern crate env_logger;
/// # use futures::Future;
/// # use std::process; /// # use std::process;
/// use actix_web::{actix, client}; /// use actix_web::client;
/// use futures::Future;
/// ///
/// fn main() { /// fn main() {
/// actix::run( /// actix::run(
@@ -103,6 +105,13 @@ pub fn post<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
builder builder
} }
/// Create request builder for `PATCH` requests
pub fn patch<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::PATCH).uri(uri);
builder
}
/// Create request builder for `PUT` requests /// Create request builder for `PUT` requests
pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder { pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build(); let mut builder = ClientRequest::build();

View File

@@ -20,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)]
@@ -38,43 +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.
if !buf.is_empty() {
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.len() >= 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) { match io.read_available(buf) {
Ok(Async::Ready(true)) => { Ok(Async::Ready((false, true))) => {
return Err(HttpResponseParserError::Disconnect) return Err(HttpResponseParserError::Disconnect)
} }
Ok(Async::Ready(false)) => (), 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 io.read_available(buf) {
Ok(Async::Ready(true)) => {
return Err(HttpResponseParserError::Disconnect)
}
Ok(Async::Ready(false)) => (),
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>(
@@ -87,8 +87,8 @@ impl HttpResponseParser {
loop { loop {
// read payload // read payload
let (not_ready, stream_finished) = match io.read_available(buf) { let (not_ready, stream_finished) = match io.read_available(buf) {
Ok(Async::Ready(true)) => (false, true), Ok(Async::Ready((_, true))) => (false, true),
Ok(Async::Ready(false)) => (false, false), Ok(Async::Ready((_, false))) => (false, false),
Ok(Async::NotReady) => (true, false), Ok(Async::NotReady) => (true, false),
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };
@@ -104,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()),
@@ -117,7 +122,7 @@ 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> {
// Unsafe: we read only this data only after httparse parses headers into. // Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks. // performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() };
@@ -163,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);
@@ -179,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

@@ -6,7 +6,8 @@ use std::time::{Duration, Instant};
use std::{io, mem}; use std::{io, mem};
use tokio_timer::Delay; use tokio_timer::Delay;
use actix::{Addr, Request, SystemService}; use actix::{Addr, SystemService};
use actix_inner::dev::Request;
use super::{ use super::{
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect, ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,
@@ -87,7 +88,8 @@ impl SendRequest {
} }
pub(crate) fn with_connector( pub(crate) fn with_connector(
req: ClientRequest, conn: Addr<ClientConnector>, req: ClientRequest,
conn: Addr<ClientConnector>,
) -> SendRequest { ) -> SendRequest {
SendRequest { SendRequest {
req, req,
@@ -362,11 +364,11 @@ impl Pipeline {
if let Some(ref mut decompress) = self.decompress { if let Some(ref mut decompress) = self.decompress {
match decompress.feed_data(b) { match decompress.feed_data(b) {
Ok(Some(b)) => return Ok(Async::Ready(Some(b))), Ok(Some(b)) => return Ok(Async::Ready(Some(b))),
Ok(None) => return Ok(Async::NotReady), Ok(None) => continue,
Err(ref err) Err(ref err)
if err.kind() == io::ErrorKind::WouldBlock => if err.kind() == io::ErrorKind::WouldBlock =>
{ {
continue continue;
} }
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
} }

View File

@@ -12,6 +12,7 @@ use serde::Serialize;
use serde_json; use serde_json;
use serde_urlencoded; use serde_urlencoded;
use url::Url; use url::Url;
use base64::encode;
use super::connector::{ClientConnector, Connection}; use super::connector::{ClientConnector, Connection};
use super::pipeline::SendRequest; use super::pipeline::SendRequest;
@@ -27,11 +28,12 @@ use httprequest::HttpRequest;
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate actix;
/// # extern crate futures; /// # extern crate futures;
/// # extern crate tokio; /// # extern crate tokio;
/// # use futures::Future; /// # use futures::Future;
/// # use std::process; /// # use std::process;
/// use actix_web::{actix, client}; /// use actix_web::client;
/// ///
/// fn main() { /// fn main() {
/// actix::run( /// actix::run(
@@ -110,6 +112,13 @@ impl ClientRequest {
builder builder
} }
/// Create request builder for `PATCH` request
pub fn patch<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::PATCH).uri(uri);
builder
}
/// Create request builder for `PUT` request /// Create request builder for `PUT` request
pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder { pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build(); let mut builder = ClientRequest::build();
@@ -254,16 +263,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(())
} }
} }
@@ -484,6 +493,29 @@ impl ClientRequestBuilder {
self self
} }
/// Set HTTP basic authorization
pub fn basic_auth<U, P>(&mut self, username: U, password: Option<P>) -> &mut Self
where
U: fmt::Display,
P: fmt::Display,
{
let auth = match password {
Some(password) => format!("{}:{}", username, password),
None => format!("{}", username)
};
let header_value = format!("Basic {}", encode(&auth));
self.header(header::AUTHORIZATION, &*header_value)
}
/// Set HTTP bearer authentication
pub fn bearer_auth<T>( &mut self, token: T) -> &mut Self
where
T: fmt::Display,
{
let header_value = format!("Bearer {}", token);
self.header(header::AUTHORIZATION, &*header_value)
}
/// Set content length /// Set content length
#[inline] #[inline]
pub fn content_length(&mut self, len: u64) -> &mut Self { pub fn content_length(&mut self, len: u64) -> &mut Self {
@@ -629,7 +661,14 @@ impl ClientRequestBuilder {
if let Some(parts) = parts(&mut self.request, &self.err) { if let Some(parts) = parts(&mut self.request, &self.err) {
if let Some(host) = parts.uri.host() { if let Some(host) = parts.uri.host() {
if !parts.headers.contains_key(header::HOST) { if !parts.headers.contains_key(header::HOST) {
match host.try_into() { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match parts.uri.port_part().map(|port| port.as_u16()) {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => { Ok(value) => {
parts.headers.insert(header::HOST, value); parts.headers.insert(header::HOST, value);
} }
@@ -743,16 +782,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

@@ -95,12 +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!(f, "\nClientResponse {:?} {}", self.version(), self.status()); writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status())?;
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(())
} }
} }

View File

@@ -1,4 +1,7 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] #![cfg_attr(
feature = "cargo-clippy",
allow(redundant_field_names)
)]
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
@@ -8,14 +11,14 @@ 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::{ use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, self, HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
}; };
use http::{HttpTryFrom, Version}; use http::{Method, HttpTryFrom, Version};
use time::{self, Duration}; use time::{self, Duration};
use tokio_io::AsyncWrite; use tokio_io::AsyncWrite;
@@ -220,7 +223,19 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
let transfer = match body { let transfer = match body {
Body::Empty => { Body::Empty => {
req.headers_mut().remove(CONTENT_LENGTH); match req.method() {
//Insert zero content-length only if user hasn't added it.
//We don't really need it for other methods as they are not supposed to carry payload
&Method::POST | &Method::PUT | &Method::PATCH => {
req.headers_mut()
.entry(CONTENT_LENGTH)
.expect("CONTENT_LENGTH to be valid header name")
.or_insert(header::HeaderValue::from_static("0"));
},
_ => {
req.headers_mut().remove(CONTENT_LENGTH);
}
}
return Output::Empty(buf); return Output::Empty(buf);
} }
Body::Binary(ref mut bytes) => { Body::Binary(ref mut bytes) => {
@@ -232,7 +247,7 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
let mut enc = match encoding { let mut enc = match encoding {
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate( ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::default()), ZlibEncoder::new(transfer, Compression::default()),
), ),
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
@@ -302,10 +317,9 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
req.replace_body(body); req.replace_body(body);
let enc = 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()))
@@ -408,3 +422,76 @@ impl CachedDate {
self.next_update.nsec = 0; self.next_update.nsec = 0;
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_content_encoder_empty_body() {
let mut req = ClientRequest::post("http://google.com").finish().expect("Create request");
let result = content_encoder(BytesMut::new(), &mut req);
match result {
Output::Empty(buf) => {
assert_eq!(buf.len(), 0);
let content_len = req.headers().get(CONTENT_LENGTH).expect("To set Content-Length for empty POST");
assert_eq!(content_len, "0");
},
_ => panic!("Unexpected result, should be Output::Empty"),
}
req.set_method(Method::GET);
let result = content_encoder(BytesMut::new(), &mut req);
match result {
Output::Empty(buf) => {
assert_eq!(buf.len(), 0);
assert!(!req.headers().contains_key(CONTENT_LENGTH));
},
_ => panic!("Unexpected result, should be Output::Empty"),
}
req.set_method(Method::PUT);
let result = content_encoder(BytesMut::new(), &mut req);
match result {
Output::Empty(buf) => {
assert_eq!(buf.len(), 0);
let content_len = req.headers().get(CONTENT_LENGTH).expect("To set Content-Length for empty PUT");
assert_eq!(content_len, "0");
},
_ => panic!("Unexpected result, should be Output::Empty"),
}
req.set_method(Method::DELETE);
let result = content_encoder(BytesMut::new(), &mut req);
match result {
Output::Empty(buf) => {
assert_eq!(buf.len(), 0);
assert!(!req.headers().contains_key(CONTENT_LENGTH));
},
_ => panic!("Unexpected result, should be Output::Empty"),
}
req.set_method(Method::PATCH);
let result = content_encoder(BytesMut::new(), &mut req);
match result {
Output::Empty(buf) => {
assert_eq!(buf.len(), 0);
let content_len = req.headers().get(CONTENT_LENGTH).expect("To set Content-Length for empty PATCH");
assert_eq!(content_len, "0");
},
_ => panic!("Unexpected result, should be Output::Empty"),
}
}
}

View File

@@ -1,7 +1,10 @@
use std::rc::Rc;
use serde::de::{self, Deserializer, Error as DeError, Visitor}; use serde::de::{self, Deserializer, Error as DeError, Visitor};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use param::ParamsIter; use param::ParamsIter;
use uri::RESERVED_QUOTER;
macro_rules! unsupported_type { macro_rules! unsupported_type {
($trait_fn:ident, $name:expr) => { ($trait_fn:ident, $name:expr) => {
@@ -13,6 +16,20 @@ macro_rules! unsupported_type {
}; };
} }
macro_rules! percent_decode_if_needed {
($value:expr, $decode:expr) => {
if $decode {
if let Some(ref mut value) = RESERVED_QUOTER.requote($value.as_bytes()) {
Rc::make_mut(value).parse()
} else {
$value.parse()
}
} else {
$value.parse()
}
}
}
macro_rules! parse_single_value { macro_rules! parse_single_value {
($trait_fn:ident, $visit_fn:ident, $tp:tt) => { ($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
@@ -23,11 +40,11 @@ macro_rules! parse_single_value {
format!("wrong number of parameters: {} expected 1", format!("wrong number of parameters: {} expected 1",
self.req.match_info().len()).as_str())) self.req.match_info().len()).as_str()))
} else { } else {
let v = self.req.match_info()[0].parse().map_err( let v_parsed = percent_decode_if_needed!(&self.req.match_info()[0], self.decode)
|_| de::value::Error::custom( .map_err(|_| de::value::Error::custom(
format!("can not parse {:?} to a {}", format!("can not parse {:?} to a {}", &self.req.match_info()[0], $tp)
&self.req.match_info()[0], $tp)))?; ))?;
visitor.$visit_fn(v) visitor.$visit_fn(v_parsed)
} }
} }
} }
@@ -35,11 +52,12 @@ macro_rules! parse_single_value {
pub struct PathDeserializer<'de, S: 'de> { pub struct PathDeserializer<'de, S: 'de> {
req: &'de HttpRequest<S>, req: &'de HttpRequest<S>,
decode: bool,
} }
impl<'de, S: 'de> PathDeserializer<'de, S> { impl<'de, S: 'de> PathDeserializer<'de, S> {
pub fn new(req: &'de HttpRequest<S>) -> Self { pub fn new(req: &'de HttpRequest<S>, decode: bool) -> Self {
PathDeserializer { req } PathDeserializer { req, decode }
} }
} }
@@ -53,6 +71,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
visitor.visit_map(ParamsDeserializer { visitor.visit_map(ParamsDeserializer {
params: self.req.match_info().iter(), params: self.req.match_info().iter(),
current: None, current: None,
decode: self.decode,
}) })
} }
@@ -107,6 +126,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
} else { } else {
visitor.visit_seq(ParamsSeq { visitor.visit_seq(ParamsSeq {
params: self.req.match_info().iter(), params: self.req.match_info().iter(),
decode: self.decode,
}) })
} }
} }
@@ -128,6 +148,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
} else { } else {
visitor.visit_seq(ParamsSeq { visitor.visit_seq(ParamsSeq {
params: self.req.match_info().iter(), params: self.req.match_info().iter(),
decode: self.decode,
}) })
} }
} }
@@ -141,28 +162,13 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
Err(de::value::Error::custom("unsupported type: enum")) Err(de::value::Error::custom("unsupported type: enum"))
} }
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.req.match_info().len() != 1 {
Err(de::value::Error::custom(
format!(
"wrong number of parameters: {} expected 1",
self.req.match_info().len()
).as_str(),
))
} else {
visitor.visit_str(&self.req.match_info()[0])
}
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
visitor.visit_seq(ParamsSeq { visitor.visit_seq(ParamsSeq {
params: self.req.match_info().iter(), params: self.req.match_info().iter(),
decode: self.decode,
}) })
} }
@@ -175,7 +181,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");
@@ -184,13 +190,16 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
parse_single_value!(deserialize_f32, visit_f32, "f32"); parse_single_value!(deserialize_f32, visit_f32, "f32");
parse_single_value!(deserialize_f64, visit_f64, "f64"); parse_single_value!(deserialize_f64, visit_f64, "f64");
parse_single_value!(deserialize_string, visit_string, "String"); parse_single_value!(deserialize_string, visit_string, "String");
parse_single_value!(deserialize_str, visit_string, "String");
parse_single_value!(deserialize_byte_buf, visit_string, "String"); parse_single_value!(deserialize_byte_buf, visit_string, "String");
parse_single_value!(deserialize_char, visit_char, "char"); parse_single_value!(deserialize_char, visit_char, "char");
} }
struct ParamsDeserializer<'de> { struct ParamsDeserializer<'de> {
params: ParamsIter<'de>, params: ParamsIter<'de>,
current: Option<(&'de str, &'de str)>, current: Option<(&'de str, &'de str)>,
decode: bool,
} }
impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
@@ -212,7 +221,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
V: de::DeserializeSeed<'de>, V: de::DeserializeSeed<'de>,
{ {
if let Some((_, value)) = self.current.take() { if let Some((_, value)) = self.current.take() {
seed.deserialize(Value { value }) seed.deserialize(Value { value, decode: self.decode })
} else { } else {
Err(de::value::Error::custom("unexpected item")) Err(de::value::Error::custom("unexpected item"))
} }
@@ -252,16 +261,18 @@ macro_rules! parse_value {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de> where V: Visitor<'de>
{ {
let v = self.value.parse().map_err( let v_parsed = percent_decode_if_needed!(&self.value, self.decode)
|_| de::value::Error::custom( .map_err(|_| de::value::Error::custom(
format!("can not parse {:?} to a {}", self.value, $tp)))?; format!("can not parse {:?} to a {}", &self.value, $tp)
visitor.$visit_fn(v) ))?;
visitor.$visit_fn(v_parsed)
} }
} }
} }
struct Value<'de> { struct Value<'de> {
value: &'de str, value: &'de str,
decode: bool,
} }
impl<'de> Deserializer<'de> for Value<'de> { impl<'de> Deserializer<'de> for Value<'de> {
@@ -377,6 +388,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
struct ParamsSeq<'de> { struct ParamsSeq<'de> {
params: ParamsIter<'de>, params: ParamsIter<'de>,
decode: bool,
} }
impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> { impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
@@ -387,7 +399,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 { value: item.1 })?)), Some(item) => Ok(Some(seed.deserialize(Value { value: item.1, decode: self.decode })?)),
None => Ok(None), None => Ok(None),
} }
} }

View File

@@ -5,7 +5,7 @@ use std::string::FromUtf8Error;
use std::sync::Mutex; use std::sync::Mutex;
use std::{fmt, io, result}; use std::{fmt, io, result};
use actix::MailboxError; use actix::{MailboxError, SendError};
use cookie; use cookie;
use failure::{self, Backtrace, Fail}; use failure::{self, Backtrace, Fail};
use futures::Canceled; use futures::Canceled;
@@ -136,6 +136,10 @@ pub trait ResponseError: Fail + InternalResponseErrorAsFail {
} }
} }
impl<T> ResponseError for SendError<T>
where T: Send + Sync + 'static {
}
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.cause, f) fmt::Display::fmt(&self.cause, f)
@@ -759,6 +763,16 @@ where
InternalError::new(err, StatusCode::UNAUTHORIZED).into() InternalError::new(err, StatusCode::UNAUTHORIZED).into()
} }
/// Helper function that creates wrapper of any error and generate
/// *PAYMENT_REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorPaymentRequired<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into()
}
/// Helper function that creates wrapper of any error and generate *FORBIDDEN* /// Helper function that creates wrapper of any error and generate *FORBIDDEN*
/// response. /// response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
@@ -789,6 +803,26 @@ where
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
} }
/// Helper function that creates wrapper of any error and generate *NOT
/// ACCEPTABLE* response.
#[allow(non_snake_case)]
pub fn ErrorNotAcceptable<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into()
}
/// Helper function that creates wrapper of any error and generate *PROXY
/// AUTHENTICATION REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorProxyAuthenticationRequired<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into()
}
/// Helper function that creates wrapper of any error and generate *REQUEST /// Helper function that creates wrapper of any error and generate *REQUEST
/// TIMEOUT* response. /// TIMEOUT* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
@@ -819,6 +853,16 @@ where
InternalError::new(err, StatusCode::GONE).into() InternalError::new(err, StatusCode::GONE).into()
} }
/// Helper function that creates wrapper of any error and generate *LENGTH
/// REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorLengthRequired<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::LENGTH_REQUIRED).into()
}
/// Helper function that creates wrapper of any error and generate /// Helper function that creates wrapper of any error and generate
/// *PRECONDITION FAILED* response. /// *PRECONDITION FAILED* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
@@ -829,6 +873,46 @@ where
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
} }
/// Helper function that creates wrapper of any error and generate
/// *PAYLOAD TOO LARGE* response.
#[allow(non_snake_case)]
pub fn ErrorPayloadTooLarge<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into()
}
/// Helper function that creates wrapper of any error and generate
/// *URI TOO LONG* response.
#[allow(non_snake_case)]
pub fn ErrorUriTooLong<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::URI_TOO_LONG).into()
}
/// Helper function that creates wrapper of any error and generate
/// *UNSUPPORTED MEDIA TYPE* response.
#[allow(non_snake_case)]
pub fn ErrorUnsupportedMediaType<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into()
}
/// Helper function that creates wrapper of any error and generate
/// *RANGE NOT SATISFIABLE* response.
#[allow(non_snake_case)]
pub fn ErrorRangeNotSatisfiable<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into()
}
/// Helper function that creates wrapper of any error and generate /// Helper function that creates wrapper of any error and generate
/// *EXPECTATION FAILED* response. /// *EXPECTATION FAILED* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
@@ -839,6 +923,106 @@ where
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into() InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
} }
/// Helper function that creates wrapper of any error and generate
/// *IM A TEAPOT* response.
#[allow(non_snake_case)]
pub fn ErrorImATeapot<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::IM_A_TEAPOT).into()
}
/// Helper function that creates wrapper of any error and generate
/// *MISDIRECTED REQUEST* response.
#[allow(non_snake_case)]
pub fn ErrorMisdirectedRequest<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into()
}
/// Helper function that creates wrapper of any error and generate
/// *UNPROCESSABLE ENTITY* response.
#[allow(non_snake_case)]
pub fn ErrorUnprocessableEntity<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into()
}
/// Helper function that creates wrapper of any error and generate
/// *LOCKED* response.
#[allow(non_snake_case)]
pub fn ErrorLocked<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::LOCKED).into()
}
/// Helper function that creates wrapper of any error and generate
/// *FAILED DEPENDENCY* response.
#[allow(non_snake_case)]
pub fn ErrorFailedDependency<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into()
}
/// Helper function that creates wrapper of any error and generate
/// *UPGRADE REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorUpgradeRequired<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into()
}
/// Helper function that creates wrapper of any error and generate
/// *PRECONDITION REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorPreconditionRequired<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into()
}
/// Helper function that creates wrapper of any error and generate
/// *TOO MANY REQUESTS* response.
#[allow(non_snake_case)]
pub fn ErrorTooManyRequests<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into()
}
/// Helper function that creates wrapper of any error and generate
/// *REQUEST HEADER FIELDS TOO LARGE* response.
#[allow(non_snake_case)]
pub fn ErrorRequestHeaderFieldsTooLarge<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into()
}
/// Helper function that creates wrapper of any error and generate
/// *UNAVAILABLE FOR LEGAL REASONS* response.
#[allow(non_snake_case)]
pub fn ErrorUnavailableForLegalReasons<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into()
}
/// Helper function that creates wrapper of any error and /// Helper function that creates wrapper of any error and
/// generate *INTERNAL SERVER ERROR* response. /// generate *INTERNAL SERVER ERROR* response.
#[allow(non_snake_case)] #[allow(non_snake_case)]
@@ -889,6 +1073,66 @@ where
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
} }
/// Helper function that creates wrapper of any error and
/// generate *HTTP VERSION NOT SUPPORTED* response.
#[allow(non_snake_case)]
pub fn ErrorHttpVersionNotSupported<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *VARIANT ALSO NEGOTIATES* response.
#[allow(non_snake_case)]
pub fn ErrorVariantAlsoNegotiates<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into()
}
/// Helper function that creates wrapper of any error and
/// generate *INSUFFICIENT STORAGE* response.
#[allow(non_snake_case)]
pub fn ErrorInsufficientStorage<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into()
}
/// Helper function that creates wrapper of any error and
/// generate *LOOP DETECTED* response.
#[allow(non_snake_case)]
pub fn ErrorLoopDetected<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::LOOP_DETECTED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *NOT EXTENDED* response.
#[allow(non_snake_case)]
pub fn ErrorNotExtended<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_EXTENDED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *NETWORK AUTHENTICATION REQUIRED* response.
#[allow(non_snake_case)]
pub fn ErrorNetworkAuthenticationRequired<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -1068,6 +1312,9 @@ mod tests {
let r: HttpResponse = ErrorUnauthorized("err").into(); let r: HttpResponse = ErrorUnauthorized("err").into();
assert_eq!(r.status(), StatusCode::UNAUTHORIZED); assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
let r: HttpResponse = ErrorPaymentRequired("err").into();
assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED);
let r: HttpResponse = ErrorForbidden("err").into(); let r: HttpResponse = ErrorForbidden("err").into();
assert_eq!(r.status(), StatusCode::FORBIDDEN); assert_eq!(r.status(), StatusCode::FORBIDDEN);
@@ -1077,6 +1324,12 @@ mod tests {
let r: HttpResponse = ErrorMethodNotAllowed("err").into(); let r: HttpResponse = ErrorMethodNotAllowed("err").into();
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
let r: HttpResponse = ErrorNotAcceptable("err").into();
assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE);
let r: HttpResponse = ErrorProxyAuthenticationRequired("err").into();
assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED);
let r: HttpResponse = ErrorRequestTimeout("err").into(); let r: HttpResponse = ErrorRequestTimeout("err").into();
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
@@ -1086,12 +1339,57 @@ mod tests {
let r: HttpResponse = ErrorGone("err").into(); let r: HttpResponse = ErrorGone("err").into();
assert_eq!(r.status(), StatusCode::GONE); assert_eq!(r.status(), StatusCode::GONE);
let r: HttpResponse = ErrorLengthRequired("err").into();
assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED);
let r: HttpResponse = ErrorPreconditionFailed("err").into(); let r: HttpResponse = ErrorPreconditionFailed("err").into();
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
let r: HttpResponse = ErrorPayloadTooLarge("err").into();
assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE);
let r: HttpResponse = ErrorUriTooLong("err").into();
assert_eq!(r.status(), StatusCode::URI_TOO_LONG);
let r: HttpResponse = ErrorUnsupportedMediaType("err").into();
assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
let r: HttpResponse = ErrorRangeNotSatisfiable("err").into();
assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE);
let r: HttpResponse = ErrorExpectationFailed("err").into(); let r: HttpResponse = ErrorExpectationFailed("err").into();
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
let r: HttpResponse = ErrorImATeapot("err").into();
assert_eq!(r.status(), StatusCode::IM_A_TEAPOT);
let r: HttpResponse = ErrorMisdirectedRequest("err").into();
assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST);
let r: HttpResponse = ErrorUnprocessableEntity("err").into();
assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY);
let r: HttpResponse = ErrorLocked("err").into();
assert_eq!(r.status(), StatusCode::LOCKED);
let r: HttpResponse = ErrorFailedDependency("err").into();
assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY);
let r: HttpResponse = ErrorUpgradeRequired("err").into();
assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED);
let r: HttpResponse = ErrorPreconditionRequired("err").into();
assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED);
let r: HttpResponse = ErrorTooManyRequests("err").into();
assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS);
let r: HttpResponse = ErrorRequestHeaderFieldsTooLarge("err").into();
assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE);
let r: HttpResponse = ErrorUnavailableForLegalReasons("err").into();
assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
let r: HttpResponse = ErrorInternalServerError("err").into(); let r: HttpResponse = ErrorInternalServerError("err").into();
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
@@ -1106,5 +1404,23 @@ mod tests {
let r: HttpResponse = ErrorGatewayTimeout("err").into(); let r: HttpResponse = ErrorGatewayTimeout("err").into();
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
let r: HttpResponse = ErrorHttpVersionNotSupported("err").into();
assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED);
let r: HttpResponse = ErrorVariantAlsoNegotiates("err").into();
assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES);
let r: HttpResponse = ErrorInsufficientStorage("err").into();
assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE);
let r: HttpResponse = ErrorLoopDetected("err").into();
assert_eq!(r.status(), StatusCode::LOOP_DETECTED);
let r: HttpResponse = ErrorNotExtended("err").into();
assert_eq!(r.status(), StatusCode::NOT_EXTENDED);
let r: HttpResponse = ErrorNetworkAuthenticationRequired("err").into();
assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED);
} }
} }

View File

@@ -31,6 +31,7 @@ impl Hasher for IdHasher {
type AnyMap = HashMap<TypeId, Box<Any>, BuildHasherDefault<IdHasher>>; type AnyMap = HashMap<TypeId, Box<Any>, BuildHasherDefault<IdHasher>>;
#[derive(Default)]
/// A type map of request extensions. /// A type map of request extensions.
pub struct Extensions { pub struct Extensions {
map: AnyMap, map: AnyMap,
@@ -39,7 +40,7 @@ pub struct Extensions {
impl Extensions { impl Extensions {
/// Create an empty `Extensions`. /// Create an empty `Extensions`.
#[inline] #[inline]
pub(crate) fn new() -> Extensions { pub fn new() -> Extensions {
Extensions { Extensions {
map: HashMap::default(), map: HashMap::default(),
} }

View File

@@ -12,13 +12,15 @@ use serde::de::{self, DeserializeOwned};
use serde_urlencoded; use serde_urlencoded;
use de::PathDeserializer; use de::PathDeserializer;
use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError}; use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError, ErrorConflict};
use handler::{AsyncResult, FromRequest}; use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use Either;
#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's path. /// Extract typed information from the request's path. Information from the path is
/// URL decoded. Decoding of special characters can be disabled through `PathConfig`.
/// ///
/// ## Example /// ## Example
/// ///
@@ -101,22 +103,83 @@ 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,
{ {
type Config = (); type Config = PathConfig<S>;
type Result = Result<Self, Error>; type Result = Result<Self, Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let req = req.clone(); let req = req.clone();
de::Deserialize::deserialize(PathDeserializer::new(&req)) let req2 = req.clone();
.map_err(ErrorNotFound) let err = Rc::clone(&cfg.ehandler);
de::Deserialize::deserialize(PathDeserializer::new(&req, cfg.decode))
.map_err(move |e| (*err)(e, &req2))
.map(|inner| Path { inner }) .map(|inner| Path { inner })
} }
} }
/// Path extractor configuration
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{error, http, App, HttpResponse, Path, Result};
///
/// /// deserialize `Info` from request's body, max payload size is 4kb
/// fn index(info: Path<(u32, String)>) -> Result<String> {
/// Ok(format!("Welcome {}!", info.1))
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html/{id}/{name}", |r| {
/// r.method(http::Method::GET).with_config(index, |cfg| {
/// cfg.0.error_handler(|err, req| {
/// // <- create custom error response
/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
/// });
/// })
/// });
/// }
/// ```
pub struct PathConfig<S> {
ehandler: Rc<Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error>,
decode: bool,
}
impl<S> PathConfig<S> {
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
where
F: Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error + 'static,
{
self.ehandler = Rc::new(f);
self
}
/// Disable decoding of URL encoded special charaters from the path
pub fn disable_decoding(&mut self) -> &mut Self
{
self.decode = false;
self
}
}
impl<S> Default for PathConfig<S> {
fn default() -> Self {
PathConfig {
ehandler: Rc::new(|e, _| ErrorNotFound(e)),
decode: true,
}
}
}
impl<T: fmt::Debug> fmt::Debug for Path<T> { impl<T: fmt::Debug> fmt::Debug for Path<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f) self.inner.fmt(f)
@@ -130,7 +193,7 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
} }
#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from from the request's query. /// Extract typed information from the request's query.
/// ///
/// ## Example /// ## Example
/// ///
@@ -194,17 +257,69 @@ impl<T, S> FromRequest<S> for Query<T>
where where
T: de::DeserializeOwned, T: de::DeserializeOwned,
{ {
type Config = (); type Config = QueryConfig<S>;
type Result = Result<Self, Error>; type Result = Result<Self, Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result { fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let req2 = req.clone();
let err = Rc::clone(&cfg.ehandler);
serde_urlencoded::from_str::<T>(req.query_string()) serde_urlencoded::from_str::<T>(req.query_string())
.map_err(|e| e.into()) .map_err(move |e| (*err)(e, &req2))
.map(Query) .map(Query)
} }
} }
/// Query extractor configuration
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{error, http, App, HttpResponse, Query, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// deserialize `Info` from request's body, max payload size is 4kb
/// fn index(info: Query<Info>) -> Result<String> {
/// Ok(format!("Welcome {}!", info.username))
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET).with_config(index, |cfg| {
/// cfg.0.error_handler(|err, req| {
/// // <- create custom error response
/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
/// });
/// })
/// });
/// }
/// ```
pub struct QueryConfig<S> {
ehandler: Rc<Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error>,
}
impl<S> QueryConfig<S> {
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
where
F: Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error + 'static,
{
self.ehandler = Rc::new(f);
self
}
}
impl<S> Default for QueryConfig<S> {
fn default() -> Self {
QueryConfig {
ehandler: Rc::new(|e, _| e.into()),
}
}
}
impl<T: fmt::Debug> fmt::Debug for Query<T> { impl<T: fmt::Debug> fmt::Debug for Query<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f) self.0.fmt(f)
@@ -326,7 +441,7 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
/// |r| { /// |r| {
/// r.method(http::Method::GET) /// r.method(http::Method::GET)
/// // register form handler and change form extractor configuration /// // register form handler and change form extractor configuration
/// .with_config(index, |cfg| {cfg.limit(4096);}) /// .with_config(index, |cfg| {cfg.0.limit(4096);})
/// }, /// },
/// ); /// );
/// } /// }
@@ -421,7 +536,7 @@ impl<S: 'static> FromRequest<S> for Bytes {
/// let app = App::new().resource("/index.html", |r| { /// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET) /// r.method(http::Method::GET)
/// .with_config(index, |cfg| { // <- register handler with extractor params /// .with_config(index, |cfg| { // <- register handler with extractor params
/// cfg.limit(4096); // <- limit size of the payload /// cfg.0.limit(4096); // <- limit size of the payload
/// }) /// })
/// }); /// });
/// } /// }
@@ -520,6 +635,153 @@ where
} }
} }
/// Extract either one of two fields from the request.
///
/// If both or none of the fields can be extracted, the default behaviour is to prefer the first
/// successful, last that failed. The behaviour can be changed by setting the appropriate
/// ```EitherCollisionStrategy```.
///
/// CAVEAT: Most of the time both extractors will be run. Make sure that the extractors you specify
/// can be run one after another (or in parallel). This will always fail for extractors that modify
/// the request state (such as the `Form` extractors that read in the body stream).
/// So Either<Form<A>, Form<B>> will not work correctly - it will only succeed if it matches the first
/// option, but will always fail to match the second (since the body stream will be at the end, and
/// appear to be empty).
///
/// ## 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;
/// use actix_web::Either;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing { name: String }
///
/// #[derive(Debug, Deserialize)]
/// struct OtherThing { id: 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"))
/// }
/// }
/// }
///
/// impl<S> FromRequest<S> for OtherThing {
/// type Config = ();
/// type Result = Result<OtherThing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(OtherThing { id: "otherthingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
/// }
/// }
///
/// /// extract text data from request
/// fn index(supplied_thing: Either<Thing, OtherThing>) -> Result<String> {
/// match supplied_thing {
/// Either::A(thing) => Ok(format!("Got something: {:?}", thing)),
/// Either::B(other_thing) => Ok(format!("Got anotherthing: {:?}", other_thing))
/// }
/// }
///
/// fn main() {
/// let app = App::new().resource("/users/:first", |r| {
/// r.method(http::Method::POST).with(index)
/// });
/// }
/// ```
impl<A: 'static, B: 'static, S: 'static> FromRequest<S> for Either<A,B> where A: FromRequest<S>, B: FromRequest<S> {
type Config = EitherConfig<A,B,S>;
type Result = AsyncResult<Either<A,B>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let a = A::from_request(&req.clone(), &cfg.a).into().map(|a| Either::A(a));
let b = B::from_request(req, &cfg.b).into().map(|b| Either::B(b));
match &cfg.collision_strategy {
EitherCollisionStrategy::PreferA => AsyncResult::future(Box::new(a.or_else(|_| b))),
EitherCollisionStrategy::PreferB => AsyncResult::future(Box::new(b.or_else(|_| a))),
EitherCollisionStrategy::FastestSuccessful => AsyncResult::future(Box::new(a.select2(b).then( |r| match r {
Ok(future::Either::A((ares, _b))) => AsyncResult::ok(ares),
Ok(future::Either::B((bres, _a))) => AsyncResult::ok(bres),
Err(future::Either::A((_aerr, b))) => AsyncResult::future(Box::new(b)),
Err(future::Either::B((_berr, a))) => AsyncResult::future(Box::new(a))
}))),
EitherCollisionStrategy::ErrorA => AsyncResult::future(Box::new(b.then(|r| match r {
Err(_berr) => AsyncResult::future(Box::new(a)),
Ok(b) => AsyncResult::future(Box::new(a.then( |r| match r {
Ok(_a) => Err(ErrorConflict("Both wings of either extractor completed")),
Err(_arr) => Ok(b)
})))
}))),
EitherCollisionStrategy::ErrorB => AsyncResult::future(Box::new(a.then(|r| match r {
Err(_aerr) => AsyncResult::future(Box::new(b)),
Ok(a) => AsyncResult::future(Box::new(b.then( |r| match r {
Ok(_b) => Err(ErrorConflict("Both wings of either extractor completed")),
Err(_berr) => Ok(a)
})))
}))),
}
}
}
/// Defines the result if neither or both of the extractors supplied to an Either<A,B> extractor succeed.
#[derive(Debug)]
pub enum EitherCollisionStrategy {
/// If both are successful, return A, if both fail, return error of B
PreferA,
/// If both are successful, return B, if both fail, return error of A
PreferB,
/// Return result of the faster, error of the slower if both fail
FastestSuccessful,
/// Return error if both succeed, return error of A if both fail
ErrorA,
/// Return error if both succeed, return error of B if both fail
ErrorB
}
impl Default for EitherCollisionStrategy {
fn default() -> Self {
EitherCollisionStrategy::FastestSuccessful
}
}
///Determines Either extractor configuration
///
///By default `EitherCollisionStrategy::FastestSuccessful` is used.
pub struct EitherConfig<A,B,S> where A: FromRequest<S>, B: FromRequest<S> {
a: A::Config,
b: B::Config,
collision_strategy: EitherCollisionStrategy
}
impl<A,B,S> Default for EitherConfig<A,B,S> where A: FromRequest<S>, B: FromRequest<S> {
fn default() -> Self {
EitherConfig {
a: A::Config::default(),
b: B::Config::default(),
collision_strategy: EitherCollisionStrategy::default()
}
}
}
/// Optionally extract a field from the request or extract the Error if unsuccessful /// 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 /// If the FromRequest for T fails, inject Err into handler rather than returning an error response
@@ -690,6 +952,12 @@ 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));
@@ -754,6 +1022,11 @@ mod tests {
hello: String, hello: String,
} }
#[derive(Deserialize, Debug, PartialEq)]
struct OtherInfo {
bye: String,
}
#[test] #[test]
fn test_bytes() { fn test_bytes() {
let cfg = PayloadConfig::default(); let cfg = PayloadConfig::default();
@@ -790,8 +1063,8 @@ mod tests {
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "11") ).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world")) .set_payload(Bytes::from_static(b"hello=world"))
.finish(); .finish();
let mut cfg = FormConfig::default(); let mut cfg = FormConfig::default();
cfg.limit(4096); cfg.limit(4096);
@@ -825,8 +1098,8 @@ mod tests {
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9") ).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"hello=world")) .set_payload(Bytes::from_static(b"hello=world"))
.finish(); .finish();
match Option::<Form<Info>>::from_request(&req, &cfg) match Option::<Form<Info>>::from_request(&req, &cfg)
.poll() .poll()
@@ -845,8 +1118,8 @@ mod tests {
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9") ).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world")) .set_payload(Bytes::from_static(b"bye=world"))
.finish(); .finish();
match Option::<Form<Info>>::from_request(&req, &cfg) match Option::<Form<Info>>::from_request(&req, &cfg)
.poll() .poll()
@@ -857,14 +1130,56 @@ mod tests {
} }
} }
#[test]
fn test_either() {
let req = TestRequest::default().finish();
let mut cfg: EitherConfig<Query<Info>, Query<OtherInfo>, _> = EitherConfig::default();
assert!(Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().is_err());
let req = TestRequest::default().uri("/index?hello=world").finish();
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(r) => assert_eq!(r, Either::A(Query(Info { hello: "world".into() }))),
_ => unreachable!(),
}
let req = TestRequest::default().uri("/index?bye=world").finish();
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(r) => assert_eq!(r, Either::B(Query(OtherInfo { bye: "world".into() }))),
_ => unreachable!(),
}
let req = TestRequest::default().uri("/index?hello=world&bye=world").finish();
cfg.collision_strategy = EitherCollisionStrategy::PreferA;
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(r) => assert_eq!(r, Either::A(Query(Info { hello: "world".into() }))),
_ => unreachable!(),
}
cfg.collision_strategy = EitherCollisionStrategy::PreferB;
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
Async::Ready(r) => assert_eq!(r, Either::B(Query(OtherInfo { bye: "world".into() }))),
_ => unreachable!(),
}
cfg.collision_strategy = EitherCollisionStrategy::ErrorA;
assert!(Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().is_err());
cfg.collision_strategy = EitherCollisionStrategy::FastestSuccessful;
assert!(Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().is_ok());
}
#[test] #[test]
fn test_result() { fn test_result() {
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, "11") ).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world")) .set_payload(Bytes::from_static(b"hello=world"))
.finish(); .finish();
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default()) match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default())
.poll() .poll()
@@ -883,8 +1198,8 @@ mod tests {
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9") ).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world")) .set_payload(Bytes::from_static(b"bye=world"))
.finish(); .finish();
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default()) match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default())
.poll() .poll()
@@ -939,15 +1254,15 @@ mod tests {
let info = router.recognize(&req, &(), 0); let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info); let req = req.with_route_info(info);
let s = Path::<MyStruct>::from_request(&req, &()).unwrap(); let s = Path::<MyStruct>::from_request(&req, &PathConfig::default()).unwrap();
assert_eq!(s.key, "name"); assert_eq!(s.key, "name");
assert_eq!(s.value, "user1"); assert_eq!(s.value, "user1");
let s = Path::<(String, String)>::from_request(&req, &()).unwrap(); let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap();
assert_eq!(s.0, "name"); assert_eq!(s.0, "name");
assert_eq!(s.1, "user1"); assert_eq!(s.1, "user1");
let s = Query::<Id>::from_request(&req, &()).unwrap(); let s = Query::<Id>::from_request(&req, &QueryConfig::default()).unwrap();
assert_eq!(s.id, "test"); assert_eq!(s.id, "test");
let mut router = Router::<()>::default(); let mut router = Router::<()>::default();
@@ -956,11 +1271,11 @@ mod tests {
let info = router.recognize(&req, &(), 0); let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info); let req = req.with_route_info(info);
let s = Path::<Test2>::from_request(&req, &()).unwrap(); let s = Path::<Test2>::from_request(&req, &PathConfig::default()).unwrap();
assert_eq!(s.as_ref().key, "name"); assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32); assert_eq!(s.value, 32);
let s = Path::<(String, u8)>::from_request(&req, &()).unwrap(); let s = Path::<(String, u8)>::from_request(&req, &PathConfig::default()).unwrap();
assert_eq!(s.0, "name"); assert_eq!(s.0, "name");
assert_eq!(s.1, 32); assert_eq!(s.1, 32);
@@ -977,7 +1292,69 @@ mod tests {
let req = TestRequest::with_uri("/32/").finish(); let req = TestRequest::with_uri("/32/").finish();
let info = router.recognize(&req, &(), 0); let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info); let req = req.with_route_info(info);
assert_eq!(*Path::<i8>::from_request(&req, &()).unwrap(), 32); assert_eq!(*Path::<i8>::from_request(&req, &&PathConfig::default()).unwrap(), 32);
}
#[test]
fn test_extract_path_decode() {
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
macro_rules! test_single_value {
($value:expr, $expected:expr) => {
{
let req = TestRequest::with_uri($value).finish();
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
assert_eq!(*Path::<String>::from_request(&req, &PathConfig::default()).unwrap(), $expected);
}
}
}
test_single_value!("/%25/", "%");
test_single_value!("/%40%C2%A3%24%25%5E%26%2B%3D/", "@£$%^&+=");
test_single_value!("/%2B/", "+");
test_single_value!("/%252B/", "%2B");
test_single_value!("/%2F/", "/");
test_single_value!("/%252F/", "%2F");
test_single_value!("/http%3A%2F%2Flocalhost%3A80%2Ffoo/", "http://localhost:80/foo");
test_single_value!("/%2Fvar%2Flog%2Fsyslog/", "/var/log/syslog");
test_single_value!(
"/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%252Fvar%252Flog%252Fsyslog/",
"http://localhost:80/file/%2Fvar%2Flog%2Fsyslog"
);
let req = TestRequest::with_uri("/%25/7/?id=test").finish();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
let s = Path::<Test2>::from_request(&req, &PathConfig::default()).unwrap();
assert_eq!(s.key, "%");
assert_eq!(s.value, 7);
let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap();
assert_eq!(s.0, "%");
assert_eq!(s.1, "7");
}
#[test]
fn test_extract_path_no_decode() {
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
let req = TestRequest::with_uri("/%25/").finish();
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
assert_eq!(
*Path::<String>::from_request(
&req,
&&PathConfig::default().disable_decoding()
).unwrap(),
"%25"
);
} }
#[test] #[test]
@@ -1006,5 +1383,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);
} }
} }

321
src/fs.rs
View File

@@ -11,10 +11,10 @@ use std::{cmp, io};
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use v_htmlescape::escape as escape_html_entity;
use bytes::Bytes; use bytes::Bytes;
use futures::{Async, Future, Poll, Stream}; use futures::{Async, Future, Poll, Stream};
use futures_cpupool::{CpuFuture, CpuPool}; use futures_cpupool::{CpuFuture, CpuPool};
use htmlescape::encode_minimal as escape_html_entity;
use mime; use mime;
use mime_guess::{get_mime_type, guess_mime_type}; use mime_guess::{get_mime_type, guess_mime_type};
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
@@ -120,6 +120,32 @@ pub struct NamedFile<C = DefaultConfig> {
} }
impl NamedFile { impl NamedFile {
/// Creates an instance from a previously opened file.
///
/// The given `path` need not exist and is only used to determine the `ContentType` and
/// `ContentDisposition` headers.
///
/// # Examples
///
/// ```no_run
/// extern crate actix_web;
///
/// use actix_web::fs::NamedFile;
/// use std::io::{self, Write};
/// use std::env;
/// use std::fs::File;
///
/// fn main() -> io::Result<()> {
/// let mut file = File::create("foo.txt")?;
/// file.write_all(b"Hello, world!")?;
/// let named_file = NamedFile::from_file(file, "bar.txt")?;
/// Ok(())
/// }
/// ```
pub fn from_file<P: AsRef<Path>>(file: File, path: P) -> io::Result<NamedFile> {
Self::from_file_with_config(file, path, DefaultConfig)
}
/// Attempts to open a file in read-only mode. /// Attempts to open a file in read-only mode.
/// ///
/// # Examples /// # Examples
@@ -135,16 +161,29 @@ impl NamedFile {
} }
impl<C: StaticFileConfig> NamedFile<C> { impl<C: StaticFileConfig> NamedFile<C> {
/// Attempts to open a file in read-only mode using provided configiration. /// Creates an instance from a previously opened file using the provided configuration.
///
/// The given `path` need not exist and is only used to determine the `ContentType` and
/// `ContentDisposition` headers.
/// ///
/// # Examples /// # Examples
/// ///
/// ```rust /// ```no_run
/// use actix_web::fs::{DefaultConfig, NamedFile}; /// extern crate actix_web;
/// ///
/// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); /// use actix_web::fs::{DefaultConfig, NamedFile};
/// use std::io::{self, Write};
/// use std::env;
/// use std::fs::File;
///
/// fn main() -> io::Result<()> {
/// let mut file = File::create("foo.txt")?;
/// file.write_all(b"Hello, world!")?;
/// let named_file = NamedFile::from_file_with_config(file, "bar.txt", DefaultConfig)?;
/// Ok(())
/// }
/// ``` /// ```
pub fn open_with_config<P: AsRef<Path>>(path: P, _: C) -> io::Result<NamedFile<C>> { pub fn from_file_with_config<P: AsRef<Path>>(file: File, path: P, _: C) -> io::Result<NamedFile<C>> {
let path = path.as_ref().to_path_buf(); let path = path.as_ref().to_path_buf();
// Get the name of the file and use it to construct default Content-Type // Get the name of the file and use it to construct default Content-Type
@@ -164,16 +203,11 @@ impl<C: StaticFileConfig> NamedFile<C> {
let disposition_type = C::content_disposition_map(ct.type_()); let disposition_type = C::content_disposition_map(ct.type_());
let cd = ContentDisposition { let cd = ContentDisposition {
disposition: disposition_type, disposition: disposition_type,
parameters: vec![DispositionParam::Filename( parameters: vec![DispositionParam::Filename(filename.into_owned())],
header::Charset::Ext("UTF-8".to_owned()),
None,
filename.as_bytes().to_vec(),
)],
}; };
(ct, cd) (ct, cd)
}; };
let file = File::open(&path)?;
let md = file.metadata()?; let md = file.metadata()?;
let modified = md.modified().ok(); let modified = md.modified().ok();
let cpu_pool = None; let cpu_pool = None;
@@ -192,6 +226,19 @@ impl<C: StaticFileConfig> NamedFile<C> {
}) })
} }
/// Attempts to open a file in read-only mode using provided configuration.
///
/// # Examples
///
/// ```rust
/// use actix_web::fs::{DefaultConfig, NamedFile};
///
/// let file = NamedFile::open_with_config("foo.txt", DefaultConfig);
/// ```
pub fn open_with_config<P: AsRef<Path>>(path: P, config: C) -> io::Result<NamedFile<C>> {
Self::from_file_with_config(File::open(&path)?, path, config)
}
/// Returns reference to the underlying `File` object. /// Returns reference to the underlying `File` object.
#[inline] #[inline]
pub fn file(&self) -> &File { pub fn file(&self) -> &File {
@@ -373,11 +420,7 @@ impl<C: StaticFileConfig> Responder for NamedFile<C> {
.body("This resource only supports GET and HEAD.")); .body("This resource only supports GET and HEAD."));
} }
let etag = if C::is_use_etag() { let etag = if C::is_use_etag() { self.etag() } else { None };
self.etag()
} else {
None
};
let last_modified = if C::is_use_last_modifier() { let last_modified = if C::is_use_last_modifier() {
self.last_modified() self.last_modified()
} else { } else {
@@ -398,6 +441,8 @@ impl<C: StaticFileConfig> Responder for NamedFile<C> {
// check last modified // check last modified
let not_modified = if !none_match(etag.as_ref(), req) { let not_modified = if !none_match(etag.as_ref(), req) {
true true
} else if req.headers().contains_key(header::IF_NONE_MATCH) {
false
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
(last_modified, req.get_header()) (last_modified, req.get_header())
{ {
@@ -480,6 +525,7 @@ impl<C: StaticFileConfig> Responder for NamedFile<C> {
} }
} }
#[doc(hidden)]
/// A helper created from a `std::fs::File` which reads the file /// A helper created from a `std::fs::File` which reads the file
/// chunk-by-chunk on a `CpuPool`. /// chunk-by-chunk on a `CpuPool`.
pub struct ChunkedReadFile { pub struct ChunkedReadFile {
@@ -522,7 +568,8 @@ impl Stream for ChunkedReadFile {
max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
let mut buf = Vec::with_capacity(max_bytes); let mut buf = Vec::with_capacity(max_bytes);
file.seek(io::SeekFrom::Start(offset))?; file.seek(io::SeekFrom::Start(offset))?;
let nbytes = file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; let nbytes =
file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?;
if nbytes == 0 { if nbytes == 0 {
return Err(io::ErrorKind::UnexpectedEof.into()); return Err(io::ErrorKind::UnexpectedEof.into());
} }
@@ -568,8 +615,23 @@ impl Directory {
} }
} }
// show file url as relative to static path
macro_rules! encode_file_url {
($path:ident) => {
utf8_percent_encode(&$path.to_string_lossy(), DEFAULT_ENCODE_SET)
};
}
// " -- &quot; & -- &amp; ' -- &#x27; < -- &lt; > -- &gt; / -- &#x2f;
macro_rules! encode_file_name {
($entry:ident) => {
escape_html_entity(&$entry.file_name().to_string_lossy())
};
}
fn directory_listing<S>( fn directory_listing<S>(
dir: &Directory, req: &HttpRequest<S>, dir: &Directory,
req: &HttpRequest<S>,
) -> Result<HttpResponse, io::Error> { ) -> Result<HttpResponse, io::Error> {
let index_of = format!("Index of {}", req.path()); let index_of = format!("Index of {}", req.path());
let mut body = String::new(); let mut body = String::new();
@@ -582,11 +644,6 @@ fn directory_listing<S>(
Ok(p) => base.join(p), Ok(p) => base.join(p),
Err(_) => continue, Err(_) => continue,
}; };
// show file url as relative to static path
let file_url = utf8_percent_encode(&p.to_string_lossy(), DEFAULT_ENCODE_SET)
.to_string();
// " -- &quot; & -- &amp; ' -- &#x27; < -- &lt; > -- &gt;
let file_name = escape_html_entity(&entry.file_name().to_string_lossy());
// if file is a directory, add '/' to the end of the name // if file is a directory, add '/' to the end of the name
if let Ok(metadata) = entry.metadata() { if let Ok(metadata) = entry.metadata() {
@@ -594,13 +651,15 @@ fn directory_listing<S>(
let _ = write!( let _ = write!(
body, body,
"<li><a href=\"{}\">{}/</a></li>", "<li><a href=\"{}\">{}/</a></li>",
file_url, file_name encode_file_url!(p),
encode_file_name!(entry),
); );
} else { } else {
let _ = write!( let _ = write!(
body, body,
"<li><a href=\"{}\">{}</a></li>", "<li><a href=\"{}\">{}</a></li>",
file_url, file_name encode_file_url!(p),
encode_file_name!(entry),
); );
} }
} else { } else {
@@ -663,7 +722,8 @@ impl<S: 'static> StaticFiles<S> {
/// Create new `StaticFiles` instance for specified base directory and /// Create new `StaticFiles` instance for specified base directory and
/// `CpuPool`. /// `CpuPool`.
pub fn with_pool<T: Into<PathBuf>>( pub fn with_pool<T: Into<PathBuf>>(
dir: T, pool: CpuPool, dir: T,
pool: CpuPool,
) -> Result<StaticFiles<S>, Error> { ) -> Result<StaticFiles<S>, Error> {
Self::with_config_pool(dir, pool, DefaultConfig) Self::with_config_pool(dir, pool, DefaultConfig)
} }
@@ -674,7 +734,8 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
/// ///
/// Identical with `new` but allows to specify configiration to use. /// Identical with `new` but allows to specify configiration to use.
pub fn with_config<T: Into<PathBuf>>( pub fn with_config<T: Into<PathBuf>>(
dir: T, config: C, dir: T,
config: C,
) -> Result<StaticFiles<S, C>, Error> { ) -> Result<StaticFiles<S, C>, Error> {
// use default CpuPool // use default CpuPool
let pool = { DEFAULT_CPUPOOL.lock().clone() }; let pool = { DEFAULT_CPUPOOL.lock().clone() };
@@ -685,7 +746,9 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
/// Create new `StaticFiles` instance for specified base directory with config and /// Create new `StaticFiles` instance for specified base directory with config and
/// `CpuPool`. /// `CpuPool`.
pub fn with_config_pool<T: Into<PathBuf>>( pub fn with_config_pool<T: Into<PathBuf>>(
dir: T, pool: CpuPool, _: C, dir: T,
pool: CpuPool,
_: C,
) -> Result<StaticFiles<S, C>, Error> { ) -> Result<StaticFiles<S, C>, Error> {
let dir = dir.into().canonicalize()?; let dir = dir.into().canonicalize()?;
@@ -729,7 +792,7 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
/// Set index file /// Set index file
/// ///
/// Redirects to specific index file for directory "/" instead of /// Shows specific index file for directory "/" instead of
/// showing files listing. /// showing files listing.
pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles<S, C> { pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles<S, C> {
self.index = Some(index.into()); self.index = Some(index.into());
@@ -743,9 +806,10 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
} }
fn try_handle( fn try_handle(
&self, req: &HttpRequest<S>, &self,
req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> { ) -> Result<AsyncResult<HttpResponse>, Error> {
let tail: String = req.match_info().query("tail")?; let tail: String = req.match_info().get_decoded("tail").unwrap_or_else(|| "".to_string());
let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?; let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?;
// full filepath // full filepath
@@ -753,17 +817,11 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
if path.is_dir() { if path.is_dir() {
if let Some(ref redir_index) = self.index { if let Some(ref redir_index) = self.index {
// TODO: Don't redirect, just return the index content. let path = path.join(redir_index);
// TODO: It'd be nice if there were a good usable URL manipulation
// library NamedFile::open_with_config(path, C::default())?
let mut new_path: String = req.path().to_owned(); .set_cpu_pool(self.cpu_pool.clone())
if !new_path.ends_with('/') { .respond_to(&req)?
new_path.push('/');
}
new_path.push_str(redir_index);
HttpResponse::Found()
.header(header::LOCATION, new_path.as_str())
.finish()
.respond_to(&req) .respond_to(&req)
} else if self.show_index { } else if self.show_index {
let dir = Directory::new(self.directory.clone(), path); let dir = Directory::new(self.directory.clone(), path);
@@ -873,8 +931,7 @@ impl HttpRange {
length: length as u64, length: length as u64,
})) }))
} }
}) }).collect::<Result<_, _>>()?;
.collect::<Result<_, _>>()?;
let ranges: Vec<HttpRange> = all_ranges.into_iter().filter_map(|x| x).collect(); let ranges: Vec<HttpRange> = all_ranges.into_iter().filter_map(|x| x).collect();
@@ -889,6 +946,8 @@ impl HttpRange {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::fs; use std::fs;
use std::time::Duration;
use std::ops::Add;
use super::*; use super::*;
use application::App; use application::App;
@@ -908,6 +967,43 @@ mod tests {
assert_eq!(m, mime::APPLICATION_OCTET_STREAM); assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
} }
#[test]
fn test_if_modified_since_without_if_none_match() {
let mut file = NamedFile::open("Cargo.toml")
.unwrap()
.set_cpu_pool(CpuPool::new(1));
let since = header::HttpDate::from(
SystemTime::now().add(Duration::from_secs(60)));
let req = TestRequest::default()
.header(header::IF_MODIFIED_SINCE, since)
.finish();
let resp = file.respond_to(&req).unwrap();
assert_eq!(
resp.status(),
StatusCode::NOT_MODIFIED
);
}
#[test]
fn test_if_modified_since_with_if_none_match() {
let mut file = NamedFile::open("Cargo.toml")
.unwrap()
.set_cpu_pool(CpuPool::new(1));
let since = header::HttpDate::from(
SystemTime::now().add(Duration::from_secs(60)));
let req = TestRequest::default()
.header(header::IF_NONE_MATCH, "miss_etag")
.header(header::IF_MODIFIED_SINCE, since)
.finish();
let resp = file.respond_to(&req).unwrap();
assert_ne!(
resp.status(),
StatusCode::NOT_MODIFIED
);
}
#[test] #[test]
fn test_named_file_text() { fn test_named_file_text() {
assert!(NamedFile::open("test--").is_err()); assert!(NamedFile::open("test--").is_err());
@@ -990,11 +1086,7 @@ mod tests {
use header::{ContentDisposition, DispositionParam, DispositionType}; use header::{ContentDisposition, DispositionParam, DispositionType};
let cd = ContentDisposition { let cd = ContentDisposition {
disposition: DispositionType::Attachment, disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::Filename( parameters: vec![DispositionParam::Filename(String::from("test.png"))],
header::Charset::Ext("UTF-8".to_owned()),
None,
"test.png".as_bytes().to_vec(),
)],
}; };
let mut file = NamedFile::open("tests/test.png") let mut file = NamedFile::open("tests/test.png")
.unwrap() .unwrap()
@@ -1292,6 +1384,27 @@ mod tests {
assert_eq!(bytes, data); assert_eq!(bytes, data);
} }
#[test]
fn test_static_files_with_spaces() {
let mut srv = test::TestServer::with_factory(|| {
App::new().handler(
"/",
StaticFiles::new(".").unwrap().index_file("Cargo.toml"),
)
});
let request = srv
.get()
.uri(srv.url("/tests/test%20space.binary"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let bytes = srv.execute(response.body()).unwrap();
let data = Bytes::from(fs::read("tests/test space.binary").unwrap());
assert_eq!(bytes, data);
}
#[derive(Default)] #[derive(Default)]
pub struct OnlyMethodHeadConfig; pub struct OnlyMethodHeadConfig;
impl StaticFileConfig for OnlyMethodHeadConfig { impl StaticFileConfig for OnlyMethodHeadConfig {
@@ -1404,43 +1517,66 @@ mod tests {
} }
#[test] #[test]
fn test_redirect_to_index() { fn test_serve_index() {
let st = StaticFiles::new(".").unwrap().index_file("index.html"); let st = StaticFiles::new(".").unwrap().index_file("test.binary");
let req = TestRequest::default().uri("/tests").finish(); let req = TestRequest::default().uri("/tests").finish();
let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = st.handle(&req).respond_to(&req).unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::LOCATION).unwrap(), resp.headers().get(header::CONTENT_TYPE).expect("content type"),
"/tests/index.html" "application/octet-stream"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).expect("content disposition"),
"attachment; filename=\"test.binary\""
); );
let req = TestRequest::default().uri("/tests/").finish(); let req = TestRequest::default().uri("/tests/").finish();
let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = st.handle(&req).respond_to(&req).unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::LOCATION).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"/tests/index.html" "application/octet-stream"
); );
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"attachment; filename=\"test.binary\""
);
// nonexistent index file
let req = TestRequest::default().uri("/tests/unknown").finish();
let resp = st.handle(&req).respond_to(&req).unwrap();
let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let req = TestRequest::default().uri("/tests/unknown/").finish();
let resp = st.handle(&req).respond_to(&req).unwrap();
let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
} }
#[test] #[test]
fn test_redirect_to_index_nested() { fn test_serve_index_nested() {
let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); let st = StaticFiles::new(".").unwrap().index_file("mod.rs");
let req = TestRequest::default().uri("/src/client").finish(); let req = TestRequest::default().uri("/src/client").finish();
let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = st.handle(&req).respond_to(&req).unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::LOCATION).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"/src/client/mod.rs" "text/x-rust"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"mod.rs\""
); );
} }
#[test] #[test]
fn integration_redirect_to_index_with_prefix() { fn integration_serve_index_with_prefix() {
let mut srv = test::TestServer::with_factory(|| { let mut srv = test::TestServer::with_factory(|| {
App::new() App::new()
.prefix("public") .prefix("public")
@@ -1449,29 +1585,21 @@ mod tests {
let request = srv.get().uri(srv.url("/public")).finish().unwrap(); let request = srv.get().uri(srv.url("/public")).finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND); assert_eq!(response.status(), StatusCode::OK);
let loc = response let bytes = srv.execute(response.body()).unwrap();
.headers() let data = Bytes::from(fs::read("Cargo.toml").unwrap());
.get(header::LOCATION) assert_eq!(bytes, data);
.unwrap()
.to_str()
.unwrap();
assert_eq!(loc, "/public/Cargo.toml");
let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); let request = srv.get().uri(srv.url("/public/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND); assert_eq!(response.status(), StatusCode::OK);
let loc = response let bytes = srv.execute(response.body()).unwrap();
.headers() let data = Bytes::from(fs::read("Cargo.toml").unwrap());
.get(header::LOCATION) assert_eq!(bytes, data);
.unwrap()
.to_str()
.unwrap();
assert_eq!(loc, "/public/Cargo.toml");
} }
#[test] #[test]
fn integration_redirect_to_index() { fn integration_serve_index() {
let mut srv = test::TestServer::with_factory(|| { let mut srv = test::TestServer::with_factory(|| {
App::new().handler( App::new().handler(
"test", "test",
@@ -1481,25 +1609,26 @@ mod tests {
let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND); assert_eq!(response.status(), StatusCode::OK);
let loc = response let bytes = srv.execute(response.body()).unwrap();
.headers() let data = Bytes::from(fs::read("Cargo.toml").unwrap());
.get(header::LOCATION) assert_eq!(bytes, data);
.unwrap()
.to_str()
.unwrap();
assert_eq!(loc, "/test/Cargo.toml");
let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND); assert_eq!(response.status(), StatusCode::OK);
let loc = response let bytes = srv.execute(response.body()).unwrap();
.headers() let data = Bytes::from(fs::read("Cargo.toml").unwrap());
.get(header::LOCATION) assert_eq!(bytes, data);
.unwrap()
.to_str() // nonexistent index file
.unwrap(); let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap();
assert_eq!(loc, "/test/Cargo.toml"); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
} }
#[test] #[test]

View File

@@ -86,7 +86,7 @@ pub trait FromRequest<S>: Sized {
/// # fn is_a_variant() -> bool { true } /// # fn is_a_variant() -> bool { true }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum Either<A, B> { pub enum Either<A, B> {
/// First branch of the type /// First branch of the type
A(A), A(A),
@@ -250,7 +250,7 @@ pub(crate) enum AsyncResultItem<I, E> {
impl<I, E> AsyncResult<I, E> { impl<I, E> AsyncResult<I, E> {
/// Create async response /// Create async response
#[inline] #[inline]
pub fn async(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> { pub fn future(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
AsyncResult(Some(AsyncResultItem::Future(fut))) AsyncResult(Some(AsyncResultItem::Future(fut)))
} }
@@ -353,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()))),
} }
} }
@@ -397,7 +401,7 @@ where
}, },
Err(e) => err(e), Err(e) => err(e),
}); });
Ok(AsyncResult::async(Box::new(fut))) Ok(AsyncResult::future(Box::new(fut)))
} }
} }
@@ -498,7 +502,7 @@ where
Err(e) => Either::A(err(e)), Err(e) => Either::A(err(e)),
} }
}); });
AsyncResult::async(Box::new(fut)) AsyncResult::future(Box::new(fut))
} }
} }
@@ -526,8 +530,7 @@ where
/// } /// }
/// ///
/// /// extract path info using serde /// /// extract path info using serde
/// fn index(data: (State<MyApp>, Path<Info>)) -> String { /// fn index(state: State<MyApp>, path: Path<Info>) -> String {
/// let (state, path) = data;
/// format!("{} {}!", state.msg, path.username) /// format!("{} {}!", state.msg, path.username)
/// } /// }
/// ///

File diff suppressed because it is too large Load Diff

View File

@@ -223,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)
@@ -263,8 +262,10 @@ where
// From hyper v0.11.27 src/header/parsing.rs // From hyper v0.11.27 src/header/parsing.rs
/// An extended header parameter value (i.e., tagged with a character set and optionally, /// The value part of an extended parameter consisting of three parts:
/// a language), as defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). /// 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)] #[derive(Clone, Debug, PartialEq)]
pub struct ExtendedValue { pub struct ExtendedValue {
/// The character set that is used to encode the `value` to a string. /// The character set that is used to encode the `value` to a string.

View File

@@ -279,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![

View File

@@ -200,7 +200,7 @@ pub trait HttpMessage: Sized {
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
fn json<T: DeserializeOwned>(&self) -> JsonBody<Self, T> { fn json<T: DeserializeOwned>(&self) -> JsonBody<Self, T> {
JsonBody::new(self) JsonBody::new::<()>(self, None)
} }
/// Return stream to http payload processes as multipart. /// Return stream to http payload processes as multipart.
@@ -213,9 +213,10 @@ pub trait HttpMessage: Sized {
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate env_logger; /// # extern crate env_logger;
/// # extern crate futures; /// # extern crate futures;
/// # extern crate actix;
/// # use std::str; /// # use std::str;
/// # use actix_web::*; /// # use actix_web::*;
/// # use actix_web::actix::fut::FinishStream; /// # use actix::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>> {
@@ -479,8 +480,7 @@ where
body.extend_from_slice(&chunk); body.extend_from_slice(&chunk);
Ok(body) Ok(body)
} }
}) }).map(|body| body.freeze()),
.map(|body| body.freeze()),
)); ));
self.poll() self.poll()
} }
@@ -588,8 +588,7 @@ where
body.extend_from_slice(&chunk); body.extend_from_slice(&chunk);
Ok(body) Ok(body)
} }
}) }).and_then(move |body| {
.and_then(move |body| {
if (encoding as *const Encoding) == UTF_8 { if (encoding as *const Encoding) == UTF_8 {
serde_urlencoded::from_bytes::<U>(&body) serde_urlencoded::from_bytes::<U>(&body)
.map_err(|_| UrlencodedError::Parse) .map_err(|_| UrlencodedError::Parse)
@@ -694,8 +693,7 @@ mod tests {
.header( .header(
header::TRANSFER_ENCODING, header::TRANSFER_ENCODING,
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"), Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
) ).finish();
.finish();
assert!(req.chunked().is_err()); assert!(req.chunked().is_err());
} }
@@ -734,7 +732,7 @@ mod tests {
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
@@ -744,7 +742,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
@@ -765,8 +763,8 @@ mod tests {
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "11") ).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world")) .set_payload(Bytes::from_static(b"hello=world"))
.finish(); .finish();
let result = req.urlencoded::<Info>().poll().ok().unwrap(); let result = req.urlencoded::<Info>().poll().ok().unwrap();
assert_eq!( assert_eq!(
@@ -780,8 +778,8 @@ mod tests {
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded; charset=utf-8", "application/x-www-form-urlencoded; charset=utf-8",
).header(header::CONTENT_LENGTH, "11") ).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world")) .set_payload(Bytes::from_static(b"hello=world"))
.finish(); .finish();
let result = req.urlencoded().poll().ok().unwrap(); let result = req.urlencoded().poll().ok().unwrap();
assert_eq!( assert_eq!(
@@ -830,8 +828,7 @@ mod tests {
b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\
industry. Lorem Ipsum has been the industry's standard dummy\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\
Contrary to popular belief, Lorem Ipsum is not simply random text.", Contrary to popular belief, Lorem Ipsum is not simply random text.",
)) )).finish();
.finish();
let mut r = Readlines::new(&req); let mut r = Readlines::new(&req);
match r.poll().ok().unwrap() { match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!( Async::Ready(Some(s)) => assert_eq!(

View File

@@ -81,6 +81,15 @@ impl<S> HttpRequest<S> {
} }
} }
/// Construct new http request with empty state.
pub fn drop_state(&self) -> HttpRequest {
HttpRequest {
state: Rc::new(()),
req: self.req.as_ref().map(|r| r.clone()),
resource: self.resource.clone(),
}
}
#[inline] #[inline]
/// Construct new http request with new RouteInfo. /// Construct new http request with new RouteInfo.
pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest<S> { pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest<S> {
@@ -207,7 +216,7 @@ impl<S> HttpRequest<S> {
self.url_for(name, &NO_PARAMS) self.url_for(name, &NO_PARAMS)
} }
/// This method returns reference to current `RouteInfo` object. /// This method returns reference to current `ResourceInfo` object.
#[inline] #[inline]
pub fn resource(&self) -> &ResourceInfo { pub fn resource(&self) -> &ResourceInfo {
&self.resource &self.resource
@@ -255,7 +264,8 @@ impl<S> HttpRequest<S> {
if self.extensions().get::<Cookies>().is_none() { if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new(); let mut cookies = Vec::new();
for hdr in self.request().inner.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());
@@ -344,24 +354,24 @@ impl<S> FromRequest<S> 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.version(), self.version(),
self.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.match_info()); writeln!(f, " params: {:?}", self.match_info())?;
} }
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(())
} }
} }

View File

@@ -48,10 +48,10 @@ impl HttpResponse {
self.0.as_mut() self.0.as_mut()
} }
/// Create http response builder with specific status. /// Create a new HTTP response builder with specific status.
#[inline] #[inline]
pub fn build(status: StatusCode) -> HttpResponseBuilder { pub fn build(status: StatusCode) -> HttpResponseBuilder {
HttpResponsePool::get(status) HttpResponseBuilder::new(status)
} }
/// Create http response builder /// Create http response builder
@@ -142,8 +142,7 @@ impl HttpResponse {
HeaderValue::from_str(&cookie.to_string()) HeaderValue::from_str(&cookie.to_string())
.map(|c| { .map(|c| {
h.append(header::SET_COOKIE, c); h.append(header::SET_COOKIE, c);
}) }).map_err(|e| e.into())
.map_err(|e| e.into())
} }
/// Remove all cookies with the given name from this response. Returns /// Remove all cookies with the given name from this response. Returns
@@ -247,7 +246,7 @@ impl HttpResponse {
self self
} }
/// Get body os this response /// Get body of this response
#[inline] #[inline]
pub fn body(&self) -> &Body { pub fn body(&self) -> &Body {
&self.get_ref().body &self.get_ref().body
@@ -273,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
} }
@@ -347,6 +346,12 @@ pub struct HttpResponseBuilder {
} }
impl HttpResponseBuilder { impl HttpResponseBuilder {
/// Create a new HTTP response builder with specific status.
#[inline]
pub fn new(status: StatusCode) -> HttpResponseBuilder {
HttpResponsePool::get(status)
}
/// Set HTTP status code of this response. /// Set HTTP status code of this response.
#[inline] #[inline]
pub fn status(&mut self, status: StatusCode) -> &mut Self { pub fn status(&mut self, status: StatusCode) -> &mut Self {
@@ -367,7 +372,7 @@ impl HttpResponseBuilder {
self self
} }
/// Set a header. /// Append a header.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
@@ -395,7 +400,7 @@ impl HttpResponseBuilder {
self self
} }
/// Set a header. /// Append a header.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
@@ -427,6 +432,65 @@ impl HttpResponseBuilder {
} }
self self
} }
/// Set or replace a header with a single value.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, HttpRequest, HttpResponse};
///
/// fn index(req: HttpRequest) -> HttpResponse {
/// HttpResponse::Ok()
/// .insert("X-TEST", "value")
/// .insert(http::header::CONTENT_TYPE, "application/json")
/// .finish()
/// }
/// fn main() {}
/// ```
pub fn insert<K, V>(&mut self, key: K, value: V) -> &mut Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.response, &self.err) {
match HeaderName::try_from(key) {
Ok(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
}
/// Remove all instances of a header already set on this `HttpResponseBuilder`.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, HttpRequest, HttpResponse};
///
/// fn index(req: HttpRequest) -> HttpResponse {
/// HttpResponse::Ok()
/// .header(http::header::CONTENT_TYPE, "nevermind") // won't be used
/// .remove(http::header::CONTENT_TYPE)
/// .finish()
/// }
/// ```
pub fn remove<K>(&mut self, key: K) -> &mut Self
where HeaderName: HttpTryFrom<K>
{
if let Some(parts) = parts(&mut self.response, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => {
parts.headers.remove(key);
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set the custom reason for the response. /// Set the custom reason for the response.
#[inline] #[inline]
@@ -650,7 +714,14 @@ impl HttpResponseBuilder {
/// ///
/// `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)
{ {
@@ -1072,8 +1143,7 @@ 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 let mut val: Vec<_> = resp
@@ -1113,6 +1183,14 @@ mod tests {
assert_eq!((v.name(), v.value()), ("cookie3", "val300")); assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
} }
#[test]
fn test_builder_new() {
let resp = HttpResponseBuilder::new(StatusCode::BAD_REQUEST)
.finish();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test] #[test]
fn test_basic_builder() { fn test_basic_builder() {
let resp = HttpResponse::Ok() let resp = HttpResponse::Ok()
@@ -1123,6 +1201,40 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test]
fn test_insert() {
let resp = HttpResponse::Ok()
.insert("deleteme", "old value")
.insert("deleteme", "new value")
.finish();
assert_eq!("new value", resp.headers().get("deleteme").expect("new value"));
}
#[test]
fn test_remove() {
let resp = HttpResponse::Ok()
.header("deleteme", "value")
.remove("deleteme")
.finish();
assert!(resp.headers().get("deleteme").is_none())
}
#[test]
fn test_remove_replace() {
let resp = HttpResponse::Ok()
.header("some-header", "old_value1")
.header("some-header", "old_value2")
.remove("some-header")
.header("some-header", "new_value1")
.header("some-header", "new_value2")
.remove("unrelated-header")
.finish();
let mut v = resp.headers().get_all("some-header").into_iter();
assert_eq!("new_value1", v.next().unwrap());
assert_eq!("new_value2", v.next().unwrap());
assert_eq!(None, v.next());
}
#[test] #[test]
fn test_upgrade() { fn test_upgrade() {
let resp = HttpResponse::build(StatusCode::OK).upgrade().finish(); let resp = HttpResponse::build(StatusCode::OK).upgrade().finish();
@@ -1186,6 +1298,30 @@ 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 bin_ref(&self) -> &Binary { pub(crate) fn bin_ref(&self) -> &Binary {
match *self { match *self {

View File

@@ -16,7 +16,10 @@ pub struct ConnectionInfo {
impl ConnectionInfo { impl ConnectionInfo {
/// Create *ConnectionInfo* instance for a request. /// Create *ConnectionInfo* instance for a request.
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] #[cfg_attr(
feature = "cargo-clippy",
allow(cyclomatic_complexity)
)]
pub fn update(&mut self, req: &Request) { pub fn update(&mut self, req: &Request) {
let mut host = None; let mut host = None;
let mut scheme = None; let mut scheme = None;
@@ -174,8 +177,7 @@ mod tests {
.header( .header(
header::FORWARDED, header::FORWARDED,
"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();
.request();
let mut info = ConnectionInfo::default(); let mut info = ConnectionInfo::default();
info.update(&req); info.update(&req);

View File

@@ -143,7 +143,7 @@ where
let req2 = 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) JsonBody::new(req, Some(cfg))
.limit(cfg.limit) .limit(cfg.limit)
.map_err(move |e| (*err)(e, &req2)) .map_err(move |e| (*err)(e, &req2))
.map(Json), .map(Json),
@@ -155,6 +155,7 @@ where
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// extern crate mime;
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{error, http, App, HttpResponse, Json, Result}; /// use actix_web::{error, http, App, HttpResponse, Json, Result};
/// ///
@@ -172,7 +173,10 @@ where
/// let app = App::new().resource("/index.html", |r| { /// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::POST) /// r.method(http::Method::POST)
/// .with_config(index, |cfg| { /// .with_config(index, |cfg| {
/// cfg.limit(4096) // <- change json extractor configuration /// cfg.0.limit(4096) // <- change json extractor configuration
/// .content_type(|mime| { // <- accept text/plain content type
/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
/// })
/// .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()
@@ -184,6 +188,7 @@ where
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>,
content_type: Option<Box<Fn(mime::Mime) -> bool>>,
} }
impl<S> JsonConfig<S> { impl<S> JsonConfig<S> {
@@ -201,6 +206,15 @@ impl<S> JsonConfig<S> {
self.ehandler = Rc::new(f); self.ehandler = Rc::new(f);
self self
} }
/// Set predicate for allowed content types
pub fn content_type<F>(&mut self, predicate: F) -> &mut Self
where
F: Fn(mime::Mime) -> bool + 'static,
{
self.content_type = Some(Box::new(predicate));
self
}
} }
impl<S> Default for JsonConfig<S> { impl<S> Default for JsonConfig<S> {
@@ -208,6 +222,7 @@ impl<S> Default for JsonConfig<S> {
JsonConfig { JsonConfig {
limit: 262_144, limit: 262_144,
ehandler: Rc::new(|e, _| e.into()), ehandler: Rc::new(|e, _| e.into()),
content_type: None,
} }
} }
} }
@@ -217,6 +232,7 @@ impl<S> Default for JsonConfig<S> {
/// Returns error: /// Returns error:
/// ///
/// * content type is not `application/json` /// * content type is not `application/json`
/// (unless specified in [`JsonConfig`](struct.JsonConfig.html))
/// * content length is greater than 256k /// * content length is greater than 256k
/// ///
/// # Server example /// # Server example
@@ -253,10 +269,13 @@ pub struct JsonBody<T: HttpMessage, U: DeserializeOwned> {
impl<T: HttpMessage, 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<S>(req: &T, cfg: Option<&JsonConfig<S>>) -> Self {
// check content-type // check content-type
let json = if let Ok(Some(mime)) = req.mime_type() { let json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) ||
cfg.map_or(false, |cfg| {
cfg.content_type.as_ref().map_or(false, |predicate| predicate(mime))
})
} else { } else {
false false
}; };
@@ -327,8 +346,7 @@ impl<T: HttpMessage + 'static, U: DeserializeOwned + 'static> Future for JsonBod
body.extend_from_slice(&chunk); body.extend_from_slice(&chunk);
Ok(body) Ok(body)
} }
}) }).and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
.and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut)); self.fut = Some(Box::new(fut));
self.poll() self.poll()
} }
@@ -388,8 +406,7 @@ mod tests {
.header( .header(
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"), header::HeaderValue::from_static("application/text"),
) ).finish();
.finish();
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
@@ -397,12 +414,10 @@ mod tests {
.header( .header(
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"), header::HeaderValue::from_static("application/json"),
) ).header(
.header(
header::CONTENT_LENGTH, header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"), header::HeaderValue::from_static("10000"),
) ).finish();
.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);
@@ -410,12 +425,10 @@ mod tests {
.header( .header(
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"), header::HeaderValue::from_static("application/json"),
) ).header(
.header(
header::CONTENT_LENGTH, header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"), header::HeaderValue::from_static("16"),
) ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish(); .finish();
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
@@ -442,9 +455,65 @@ mod tests {
).header( ).header(
header::CONTENT_LENGTH, header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"), header::HeaderValue::from_static("16"),
) ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .finish();
.finish();
assert!(handler.handle(&req).as_err().is_none()) assert!(handler.handle(&req).as_err().is_none())
} }
#[test]
fn test_with_json_and_bad_content_type() {
let mut cfg = JsonConfig::default();
cfg.limit(4096);
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_some())
}
#[test]
fn test_with_json_and_good_custom_content_type() {
let mut cfg = JsonConfig::default();
cfg.limit(4096);
cfg.content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
});
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_none())
}
#[test]
fn test_with_json_and_bad_custom_content_type() {
let mut cfg = JsonConfig::default();
cfg.limit(4096);
cfg.content_type(|mime: mime::Mime| {
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
});
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/html"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_some())
}
} }

View File

@@ -64,8 +64,10 @@
//! ## Package feature //! ## Package feature
//! //!
//! * `tls` - enables ssl support via `native-tls` crate //! * `tls` - enables ssl support via `native-tls` crate
//! * `alpn` - enables ssl support via `openssl` crate, require for `http/2` //! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
//! support //! * `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 //! * `session` - enables session support, includes `ring` crate as
//! dependency //! dependency
//! * `brotli` - enables `brotli` compression support, requires `c` //! * `brotli` - enables `brotli` compression support, requires `c`
@@ -78,11 +80,8 @@
#![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, extern_prelude,
tool_lints,
))] ))]
#![cfg_attr(
feature = "cargo-clippy",
allow(decimal_literal_representation, suspicious_arithmetic_impl)
)]
#![warn(missing_docs)] #![warn(missing_docs)]
#[macro_use] #[macro_use]
@@ -103,7 +102,6 @@ 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 httparse; extern crate httparse;
extern crate language_tags; extern crate language_tags;
@@ -116,10 +114,13 @@ extern crate parking_lot;
extern crate rand; extern crate rand;
extern crate slab; extern crate slab;
extern crate tokio; extern crate tokio;
extern crate tokio_current_thread;
extern crate tokio_io; extern crate tokio_io;
extern crate tokio_reactor; extern crate tokio_reactor;
extern crate tokio_tcp; extern crate tokio_tcp;
extern crate tokio_timer; 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;
@@ -130,10 +131,14 @@ 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] #[macro_use]
extern crate percent_encoding; extern crate percent_encoding;
extern crate serde_json; extern crate serde_json;
extern crate smallvec; extern crate smallvec;
extern crate v_htmlescape;
extern crate actix_net;
#[macro_use] #[macro_use]
extern crate actix as actix_inner; extern crate actix as actix_inner;
@@ -182,7 +187,6 @@ mod resource;
mod route; mod route;
mod router; mod router;
mod scope; mod scope;
mod serde_urlencoded;
mod uri; mod uri;
mod with; mod with;
@@ -213,14 +217,12 @@ pub use server::Request;
pub mod actix { pub mod actix {
//! Re-exports [actix's](https://docs.rs/actix/) prelude //! Re-exports [actix's](https://docs.rs/actix/) prelude
pub use super::actix_inner::actors::resolver;
extern crate actix; pub use super::actix_inner::actors::signal;
pub use self::actix::actors::resolver; pub use super::actix_inner::fut;
pub use self::actix::actors::signal; pub use super::actix_inner::msgs;
pub use self::actix::fut; pub use super::actix_inner::prelude::*;
pub use self::actix::msgs; pub use super::actix_inner::{run, spawn};
pub use self::actix::prelude::*;
pub use self::actix::{run, spawn};
} }
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
@@ -251,14 +253,15 @@ pub mod dev {
pub use body::BodyStream; pub use body::BodyStream;
pub use context::Drain; pub use context::Drain;
pub use extractor::{FormConfig, PayloadConfig}; pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig, EitherConfig, EitherCollisionStrategy};
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 payload::{Payload, PayloadBuffer}; pub use payload::{Payload, PayloadBuffer};
pub use pipeline::Pipeline;
pub use resource::Resource; pub use resource::Resource;
pub use route::Route; pub use route::Route;
pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; pub use router::{ResourceDef, ResourceInfo, ResourceType, Router};
@@ -280,7 +283,9 @@ pub mod http {
/// Various http headers /// Various http headers
pub mod header { pub mod header {
pub use header::*; pub use header::*;
pub use header::{ContentDisposition, DispositionType, DispositionParam, Charset, LanguageTag}; pub use header::{
Charset, ContentDisposition, DispositionParam, DispositionType, LanguageTag,
};
} }
pub use header::ContentEncoding; pub use header::ContentEncoding;
pub use httpresponse::ConnectionType; pub use httpresponse::ConnectionType;

View File

@@ -307,6 +307,32 @@ impl Cors {
} }
} }
fn access_control_allow_origin(&self, req: &Request) -> Option<HeaderValue> {
match self.inner.origins {
AllOrSome::All => {
if self.inner.send_wildcard {
Some(HeaderValue::from_static("*"))
} else if let Some(origin) = req.headers().get(header::ORIGIN) {
Some(origin.clone())
} else {
None
}
}
AllOrSome::Some(ref origins) => {
if let Some(origin) = req.headers().get(header::ORIGIN).filter(|o| {
match o.to_str() {
Ok(os) => origins.contains(os),
_ => false
}
}) {
Some(origin.clone())
} else {
Some(self.inner.origins_str.as_ref().unwrap().clone())
}
}
}
}
fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> { fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> {
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
if let Ok(meth) = hdr.to_str() { if let Ok(meth) = hdr.to_str() {
@@ -387,32 +413,15 @@ 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_some(self.access_control_allow_origin(&req), |origin, resp| {
.if_true(self.inner.origins.is_all(), |resp| { let _ =
if self.inner.send_wildcard { resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); }).if_true(self.inner.supports_credentials, |resp| {
} else {
let origin = req.headers().get(header::ORIGIN).unwrap();
resp.header(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
origin.clone(),
);
}
})
.if_true(self.inner.origins.is_some(), |resp| {
resp.header(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
self.inner.origins_str.as_ref().unwrap().clone(),
);
})
.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 &self
.inner .inner
@@ -420,8 +429,7 @@ impl<S> Middleware<S> for Cors {
.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 {
// Only check requests with a origin header. // Only check requests with a origin header.
@@ -436,25 +444,11 @@ impl<S> Middleware<S> for Cors {
fn response( fn response(
&self, req: &HttpRequest<S>, mut resp: HttpResponse, &self, req: &HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> { ) -> Result<Response> {
match self.inner.origins {
AllOrSome::All => { if let Some(origin) = self.access_control_allow_origin(req) {
if self.inner.send_wildcard { resp.headers_mut()
resp.headers_mut().insert( .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
header::ACCESS_CONTROL_ALLOW_ORIGIN, };
HeaderValue::from_static("*"),
);
} else if let Some(origin) = req.headers().get(header::ORIGIN) {
resp.headers_mut()
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
}
}
AllOrSome::Some(_) => {
resp.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
self.inner.origins_str.as_ref().unwrap().clone(),
);
}
}
if let Some(ref expose) = self.inner.expose_hdrs { if let Some(ref expose) = self.inner.expose_hdrs {
resp.headers_mut().insert( resp.headers_mut().insert(
@@ -832,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(),
); );
} }
@@ -978,8 +972,7 @@ mod tests {
.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(&req).unwrap().response(); let resp = cors.start(&req).unwrap().response();
@@ -1073,12 +1066,14 @@ 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();
@@ -1100,6 +1095,22 @@ mod tests {
resp.headers().get(header::VARY).unwrap().as_bytes() resp.headers().get(header::VARY).unwrap().as_bytes()
); );
{
let headers = resp
.headers()
.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 = let resp: HttpResponse =
HttpResponse::Ok().header(header::VARY, "Accept").finish(); HttpResponse::Ok().header(header::VARY, "Accept").finish();
let resp = cors.response(&req, resp).unwrap().response(); let resp = cors.response(&req, resp).unwrap().response();
@@ -1111,15 +1122,21 @@ 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(&req, resp).unwrap().response(); let resp = cors.response(&req, resp).unwrap().response();
let origins_str = resp
.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.to_str()
.unwrap();
assert_eq!( assert_eq!(
&b"https://www.example.com"[..], "https://www.example.com",
resp.headers() origins_str
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
); );
} }
@@ -1156,4 +1173,80 @@ mod tests {
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
} }
#[test]
fn test_multiple_origins() {
let cors = Cors::build()
.allowed_origin("https://example.com")
.allowed_origin("https://example.org")
.allowed_methods(vec![Method::GET])
.finish();
let req = TestRequest::with_header("Origin", "https://example.com")
.method(Method::GET)
.finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&req, resp).unwrap().response();
assert_eq!(
&b"https://example.com"[..],
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
let req = TestRequest::with_header("Origin", "https://example.org")
.method(Method::GET)
.finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&req, resp).unwrap().response();
assert_eq!(
&b"https://example.org"[..],
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
}
#[test]
fn test_multiple_origins_preflight() {
let cors = Cors::build()
.allowed_origin("https://example.com")
.allowed_origin("https://example.org")
.allowed_methods(vec![Method::GET])
.finish();
let req = TestRequest::with_header("Origin", "https://example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
.method(Method::OPTIONS)
.finish();
let resp = cors.start(&req).ok().unwrap().response();
assert_eq!(
&b"https://example.com"[..],
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
let req = TestRequest::with_header("Origin", "https://example.org")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
.method(Method::OPTIONS)
.finish();
let resp = cors.start(&req).ok().unwrap().response();
assert_eq!(
&b"https://example.org"[..],
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.unwrap()
.as_bytes()
);
}
} }

View File

@@ -76,7 +76,7 @@ impl ResponseError for CsrfError {
} }
fn uri_origin(uri: &Uri) -> Option<String> { fn uri_origin(uri: &Uri) -> Option<String> {
match (uri.scheme_part(), uri.host(), uri.port()) { match (uri.scheme_part(), uri.host(), uri.port_part().map(|port| port.as_u16())) {
(Some(scheme), Some(host), Some(port)) => { (Some(scheme), Some(host), Some(port)) => {
Some(format!("{}://{}:{}", scheme, host, port)) Some(format!("{}://{}:{}", scheme, host, port))
} }
@@ -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()
@@ -251,7 +250,7 @@ mod tests {
"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(&req).is_ok()); assert!(csrf.start(&req).is_ok());
} }

View File

@@ -131,7 +131,7 @@ mod tests {
ErrorHandlers::new() ErrorHandlers::new()
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
).middleware(MiddlewareOne) ).middleware(MiddlewareOne)
.handler(|_| HttpResponse::Ok()) .handler(|_| HttpResponse::Ok())
}); });
let request = srv.get().finish().unwrap(); let request = srv.get().finish().unwrap();

View File

@@ -48,7 +48,7 @@
//! ``` //! ```
use std::rc::Rc; use std::rc::Rc;
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 time::Duration; use time::Duration;
@@ -237,6 +237,7 @@ struct CookieIdentityInner {
domain: Option<String>, domain: Option<String>,
secure: bool, secure: bool,
max_age: Option<Duration>, max_age: Option<Duration>,
same_site: Option<SameSite>,
} }
impl CookieIdentityInner { impl CookieIdentityInner {
@@ -248,6 +249,7 @@ impl CookieIdentityInner {
domain: None, domain: None,
secure: true, secure: true,
max_age: None, max_age: None,
same_site: None,
} }
} }
@@ -268,6 +270,10 @@ impl CookieIdentityInner {
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();
if some { if some {
jar.private(&self.key).add(cookie); jar.private(&self.key).add(cookie);
@@ -370,6 +376,12 @@ impl CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self self
} }
/// Sets the `same_site` field in the session cookie being built.
pub fn same_site(mut self, same_site: SameSite) -> Self {
Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site);
self
}
} }
impl<S> IdentityPolicy<S> for CookieIdentityPolicy { impl<S> IdentityPolicy<S> for CookieIdentityPolicy {

View File

@@ -33,7 +33,8 @@
//! //!
//! ```rust //! ```rust
//! # extern crate actix_web; //! # extern crate actix_web;
//! use actix_web::{actix, server, App, HttpRequest, Result}; //! # extern crate actix;
//! use actix_web::{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> {
@@ -270,14 +271,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.
@@ -285,9 +289,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.
@@ -579,8 +584,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"
@@ -599,8 +603,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.with(|ses: Session| { r.with(|ses: Session| {
let _ = ses.set("counter", 100); let _ = ses.set("counter", 100);
"test" "test"

View File

@@ -441,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(())
} }
} }
@@ -756,13 +756,10 @@ mod tests {
{ {
use http::header::{DispositionParam, DispositionType}; use http::header::{DispositionParam, DispositionType};
let cd = field.content_disposition().unwrap(); let cd = field.content_disposition().unwrap();
assert_eq!( assert_eq!(cd.disposition, DispositionType::FormData);
cd.disposition,
DispositionType::Ext("form-data".into())
);
assert_eq!( assert_eq!(
cd.parameters[0], cd.parameters[0],
DispositionParam::Ext("name".into(), "file".into()) DispositionParam::Name("file".into())
); );
} }
assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().type_(), mime::TEXT);
@@ -813,7 +810,6 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
} }

View File

@@ -8,7 +8,7 @@ use http::StatusCode;
use smallvec::SmallVec; use smallvec::SmallVec;
use error::{InternalError, ResponseError, UriSegmentError}; use error::{InternalError, ResponseError, UriSegmentError};
use uri::Url; use uri::{Url, RESERVED_QUOTER};
/// 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.
@@ -103,6 +103,17 @@ impl Params {
} }
} }
/// Get URL-decoded matched parameter by name without type conversion
pub fn get_decoded(&self, key: &str) -> Option<String> {
self.get(key).map(|value| {
if let Some(ref mut value) = RESERVED_QUOTER.requote(value.as_bytes()) {
Rc::make_mut(value).to_string()
} else {
value.to_string()
}
})
}
/// Get unprocessed part of path /// Get unprocessed part of path
pub fn unprocessed(&self) -> &str { pub fn unprocessed(&self) -> &str {
&self.url.path()[(self.tail as usize)..] &self.url.path()[(self.tail as usize)..]
@@ -236,7 +247,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))
@@ -301,4 +311,24 @@ mod tests {
Ok(PathBuf::from_iter(vec!["seg2"])) Ok(PathBuf::from_iter(vec!["seg2"]))
); );
} }
#[test]
fn test_get_param_by_name() {
let mut params = Params::new();
params.add_static("item1", "path");
params.add_static("item2", "http%3A%2F%2Flocalhost%3A80%2Ffoo");
assert_eq!(params.get("item0"), None);
assert_eq!(params.get_decoded("item0"), None);
assert_eq!(params.get("item1"), Some("path"));
assert_eq!(params.get_decoded("item1"), Some("path".to_string()));
assert_eq!(
params.get("item2"),
Some("http%3A%2F%2Flocalhost%3A80%2Ffoo")
);
assert_eq!(
params.get_decoded("item2"),
Some("http://localhost:80/foo".to_string())
);
}
} }

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;
@@ -513,8 +515,7 @@ where
.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()
} }
} }
@@ -553,8 +554,7 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[test] #[test]
@@ -578,8 +578,7 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[test] #[test]
@@ -596,8 +595,7 @@ 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]
@@ -625,8 +623,7 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[test] #[test]
@@ -659,8 +656,7 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[test] #[test]
@@ -693,8 +689,7 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
#[test] #[test]
@@ -715,7 +710,6 @@ mod tests {
let res: Result<(), ()> = Ok(()); let res: Result<(), ()> = Ok(());
result(res) result(res)
})) })).unwrap();
.unwrap();
} }
} }

View File

@@ -42,13 +42,6 @@ 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 {
match *self {
PipelineState::Response(_) => true,
_ => false,
}
}
fn poll( fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], &mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> { ) -> Option<PipelineState<S, H>> {
@@ -58,9 +51,8 @@ impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> {
PipelineState::RunMiddlewares(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), PipelineState::Finishing(ref mut state) => state.poll(info, mws),
PipelineState::Completed(ref mut state) => state.poll(info), PipelineState::Completed(ref mut state) => state.poll(info),
PipelineState::Response(_) | PipelineState::None | PipelineState::Error => { PipelineState::Response(ref mut state) => state.poll(info, mws),
None PipelineState::None | PipelineState::Error => None,
}
} }
} }
} }
@@ -89,7 +81,7 @@ impl<S: 'static> 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>>>>, handler: Rc<H>, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: Rc<H>,
) -> Pipeline<S, H> { ) -> Pipeline<S, H> {
let mut info = PipelineInfo { let mut info = PipelineInfo {
@@ -130,22 +122,20 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
let mut state = mem::replace(&mut self.1, PipelineState::None); let mut state = mem::replace(&mut self.1, PipelineState::None);
loop { loop {
if state.is_response() { if let PipelineState::Response(st) = state {
if let PipelineState::Response(st) = state { match st.poll_io(io, &mut self.0, &self.2) {
match st.poll_io(io, &mut self.0, &self.2) { Ok(state) => {
Ok(state) => { self.1 = state;
self.1 = state; if let Some(error) = self.0.error.take() {
if let Some(error) = self.0.error.take() { return Err(error);
return Err(error); } else {
} else { return Ok(Async::Ready(self.is_done()));
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 state { match state {
@@ -401,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<()>>,
@@ -442,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,
@@ -451,6 +441,79 @@ 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>>], mws: &[Box<Middleware<S>>],
@@ -461,29 +524,39 @@ 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 = let result = match io.start(
match io.start(&info.req, &mut self.resp, encoding) { &info.req,
Ok(res) => res, self.resp.as_mut().unwrap(),
Err(err) => { encoding,
info.error = Some(err.into()); ) {
return Ok(FinishingMiddlewares::init( Ok(res) => res,
info, mws, self.resp, Err(err) => {
)); info.error = Some(err.into());
} return Ok(FinishingMiddlewares::init(
}; 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, status: {} {}", "Error occurred during request handling, status: {} {}",
self.resp.status(), err self.resp.as_ref().unwrap().status(), err
); );
} else { } else {
warn!( warn!(
"Error occured during request handling: {}", "Error occurred during request handling: {}",
err err
); );
} }
@@ -493,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;
@@ -512,7 +585,9 @@ 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, mws, self.resp, info,
mws,
self.resp.take().unwrap(),
)); ));
} }
break; break;
@@ -523,7 +598,9 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Err(err) => { Err(err) => {
info.error = Some(err.into()); info.error = Some(err.into());
return Ok(FinishingMiddlewares::init( return Ok(FinishingMiddlewares::init(
info, mws, self.resp, info,
mws,
self.resp.take().unwrap(),
)); ));
} }
Ok(result) => result, Ok(result) => result,
@@ -536,7 +613,9 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Err(err) => { Err(err) => {
info.error = Some(err); info.error = Some(err);
return Ok(FinishingMiddlewares::init( return Ok(FinishingMiddlewares::init(
info, mws, self.resp, info,
mws,
self.resp.take().unwrap(),
)); ));
} }
}, },
@@ -559,26 +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, mws, 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.context = Some(ctx); Err(err) => {
info.error = Some(err.into()); info.context = Some(ctx);
return Ok( info.error = Some(err.into());
FinishingMiddlewares::init( return Ok(
info, mws, self.resp, FinishingMiddlewares::init(
), info,
); mws,
} self.resp.take().unwrap(),
Ok(result) => res = Some(result), ),
);
} }
} Ok(result) => res = Some(result),
},
Frame::Drain(fut) => self.drain = Some(fut), Frame::Drain(fut) => self.drain = Some(fut),
} }
} }
@@ -598,7 +681,9 @@ impl<S: 'static, H> ProcessResponse<S, H> {
info.context = Some(ctx); info.context = Some(ctx);
info.error = Some(err); info.error = Some(err);
return Ok(FinishingMiddlewares::init( return Ok(FinishingMiddlewares::init(
info, mws, self.resp, info,
mws,
self.resp.take().unwrap(),
)); ));
} }
} }
@@ -638,7 +723,11 @@ impl<S: 'static, H> ProcessResponse<S, H> {
info.context = Some(ctx); info.context = Some(ctx);
} }
info.error = Some(err.into()); info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(info, mws, self.resp)); return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
} }
} }
} }
@@ -652,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, mws, 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, mws, self.resp)) Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
))
} }
_ => Err(PipelineState::Response(self)), _ => Err(PipelineState::Response(self)),
} }

View File

@@ -264,8 +264,7 @@ mod tests {
.header( .header(
header::HOST, header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"), header::HeaderValue::from_static("www.rust-lang.org"),
) ).finish();
.finish();
let pred = Host("www.rust-lang.org"); let pred = Host("www.rust-lang.org");
assert!(pred.check(&req, req.state())); assert!(pred.check(&req, req.state()));

View File

@@ -13,6 +13,7 @@ use middleware::Middleware;
use pred; use pred;
use route::Route; use route::Route;
use router::ResourceDef; use router::ResourceDef;
use with::WithFactory;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub(crate) struct RouteId(usize); pub(crate) struct RouteId(usize);
@@ -106,6 +107,12 @@ impl<S: 'static> Resource<S> {
self.routes.last_mut().unwrap().filter(pred::Post()) self.routes.last_mut().unwrap().filter(pred::Post())
} }
/// Register a new `PATCH` route.
pub fn patch(&mut self) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes.last_mut().unwrap().filter(pred::Patch())
}
/// Register a new `PUT` route. /// Register a new `PUT` route.
pub fn put(&mut self) -> &mut Route<S> { pub fn put(&mut self) -> &mut Route<S> {
self.routes.push(Route::default()); self.routes.push(Route::default());
@@ -217,7 +224,7 @@ impl<S: 'static> Resource<S> {
/// ``` /// ```
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,
{ {

View File

@@ -16,7 +16,7 @@ use middleware::{
Started as MiddlewareStarted, Started as MiddlewareStarted,
}; };
use pred::Predicate; use pred::Predicate;
use with::{With, WithAsync}; use with::{WithAsyncFactory, WithFactory};
/// Resource route definition /// Resource route definition
/// ///
@@ -57,7 +57,7 @@ impl<S: 'static> Route<S> {
pub(crate) fn compose( pub(crate) fn compose(
&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::future(Box::new(Compose::new(req, mws, self.handler.clone())))
} }
/// Add match predicate to route. /// Add match predicate to route.
@@ -134,8 +134,7 @@ impl<S: 'static> Route<S> {
/// } /// }
/// ``` /// ```
/// ///
/// It is possible to use tuples for specifing multiple extractors for one /// It is possible to use multiple extractors for one handler function.
/// handler function.
/// ///
/// ```rust /// ```rust
/// # extern crate bytes; /// # extern crate bytes;
@@ -152,9 +151,9 @@ impl<S: 'static> Route<S> {
/// ///
/// /// extract path info using serde /// /// extract path info using serde
/// fn index( /// fn index(
/// info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>), /// path: Path<Info>, query: Query<HashMap<String, String>>, body: Json<Info>,
/// ) -> Result<String> { /// ) -> Result<String> {
/// Ok(format!("Welcome {}!", info.0.username)) /// Ok(format!("Welcome {}!", path.username))
/// } /// }
/// ///
/// fn main() { /// fn main() {
@@ -166,15 +165,15 @@ impl<S: 'static> Route<S> {
/// ``` /// ```
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> + 'static,
R: Responder + 'static, R: Responder + 'static,
T: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
{ {
self.h(With::new(handler, <T::Config as Default>::default())); self.h(handler.create());
} }
/// Set handler function. Same as `.with()` but it allows to configure /// Set handler function. Same as `.with()` but it allows to configure
/// extractor. /// extractor. Configuration closure accepts config objects as tuple.
/// ///
/// ```rust /// ```rust
/// # extern crate bytes; /// # extern crate bytes;
@@ -192,21 +191,21 @@ impl<S: 'static> Route<S> {
/// let app = App::new().resource("/index.html", |r| { /// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET) /// r.method(http::Method::GET)
/// .with_config(index, |cfg| { // <- register handler /// .with_config(index, |cfg| { // <- register handler
/// cfg.limit(4096); // <- limit size of the payload /// cfg.0.limit(4096); // <- limit size of the payload
/// }) /// })
/// }); /// });
/// } /// }
/// ``` /// ```
pub fn with_config<T, F, R, C>(&mut self, handler: F, cfg_f: C) pub fn with_config<T, F, R, C>(&mut self, handler: F, cfg_f: C)
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,
C: FnOnce(&mut T::Config), C: FnOnce(&mut T::Config),
{ {
let mut cfg = <T::Config as Default>::default(); let mut cfg = <T::Config as Default>::default();
cfg_f(&mut cfg); cfg_f(&mut cfg);
self.h(With::new(handler, cfg)); self.h(handler.create_with_config(cfg));
} }
/// Set async handler function, use request extractor for parameters. /// Set async handler function, use request extractor for parameters.
@@ -240,17 +239,18 @@ impl<S: 'static> Route<S> {
/// ``` /// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F) pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where where
F: Fn(T) -> R + 'static, F: WithAsyncFactory<T, S, R, I, E>,
R: Future<Item = I, Error = E> + 'static, R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static, I: Responder + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
T: FromRequest<S> + 'static, T: FromRequest<S> + 'static,
{ {
self.h(WithAsync::new(handler, <T::Config as Default>::default())); self.h(handler.create());
} }
/// Set async handler function, use request extractor for parameters. /// Set async handler function, use request extractor for parameters.
/// This method allows to configure extractor. /// This method allows to configure extractor. Configuration closure
/// accepts config objects as tuple.
/// ///
/// ```rust /// ```rust
/// # extern crate bytes; /// # extern crate bytes;
@@ -275,14 +275,14 @@ impl<S: 'static> Route<S> {
/// "/{username}/index.html", // <- define path parameters /// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET) /// |r| r.method(http::Method::GET)
/// .with_async_config(index, |cfg| { /// .with_async_config(index, |cfg| {
/// cfg.limit(4096); /// cfg.0.limit(4096);
/// }), /// }),
/// ); // <- use `with` extractor /// ); // <- use `with` extractor
/// } /// }
/// ``` /// ```
pub fn with_async_config<T, F, R, I, E, C>(&mut self, handler: F, cfg: C) pub fn with_async_config<T, F, R, I, E, C>(&mut self, handler: F, cfg: C)
where where
F: Fn(T) -> R + 'static, F: WithAsyncFactory<T, S, R, I, E>,
R: Future<Item = I, Error = E> + 'static, R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static, I: Responder + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
@@ -291,7 +291,7 @@ impl<S: 'static> Route<S> {
{ {
let mut extractor_cfg = <T::Config as Default>::default(); let mut extractor_cfg = <T::Config as Default>::default();
cfg(&mut extractor_cfg); cfg(&mut extractor_cfg);
self.h(WithAsync::new(handler, extractor_cfg)); self.h(handler.create_with_config(extractor_cfg));
} }
} }

View File

@@ -17,6 +17,7 @@ use pred::Predicate;
use resource::{DefaultResource, Resource}; use resource::{DefaultResource, Resource};
use scope::Scope; use scope::Scope;
use server::Request; use server::Request;
use with::WithFactory;
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) enum ResourceId { pub(crate) enum ResourceId {
@@ -290,19 +291,6 @@ impl<S: 'static> Router<S> {
} }
} }
#[cfg(test)]
pub(crate) fn route_info(&self, req: &Request, prefix: u16) -> ResourceInfo {
let mut params = Params::with_url(req.url());
params.set_tail(prefix);
ResourceInfo {
params,
prefix: 0,
rmap: self.rmap.clone(),
resource: ResourceId::Default,
}
}
#[cfg(test)] #[cfg(test)]
pub(crate) fn default_route_info(&self) -> ResourceInfo { pub(crate) fn default_route_info(&self) -> ResourceInfo {
ResourceInfo { ResourceInfo {
@@ -411,7 +399,7 @@ impl<S: 'static> Router<S> {
pub(crate) fn register_route<T, F, R>(&mut self, path: &str, method: Method, f: F) pub(crate) fn register_route<T, F, R>(&mut self, path: &str, method: Method, f: 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,
{ {
@@ -827,73 +815,70 @@ impl ResourceDef {
Ok(()) Ok(())
} }
fn parse( fn parse_param(pattern: &str) -> (PatternElement, String, &str) {
pattern: &str, for_prefix: bool,
) -> (String, Vec<PatternElement>, bool, usize) {
const DEFAULT_PATTERN: &str = "[^/]+"; const DEFAULT_PATTERN: &str = "[^/]+";
let mut params_nesting = 0usize;
let mut re1 = String::from("^"); let close_idx = pattern
let mut re2 = String::new(); .find(|c| match c {
let mut el = String::new(); '{' => {
let mut in_param = false; params_nesting += 1;
let mut in_param_pattern = false; false
let mut param_name = String::new();
let mut param_pattern = String::from(DEFAULT_PATTERN);
let mut is_dynamic = false;
let mut elems = Vec::new();
let mut len = 0;
for ch in pattern.chars() {
if in_param {
// In parameter segment: `{....}`
if ch == '}' {
elems.push(PatternElement::Var(param_name.clone()));
re1.push_str(&format!(r"(?P<{}>{})", &param_name, &param_pattern));
param_name.clear();
param_pattern = String::from(DEFAULT_PATTERN);
len = 0;
in_param_pattern = false;
in_param = false;
} else if ch == ':' {
// The parameter name has been determined; custom pattern land
in_param_pattern = true;
param_pattern.clear();
} else if in_param_pattern {
// Ignore leading whitespace for pattern
if !(ch == ' ' && param_pattern.is_empty()) {
param_pattern.push(ch);
}
} else {
param_name.push(ch);
} }
} else if ch == '{' { '}' => {
in_param = true; params_nesting -= 1;
is_dynamic = true; params_nesting == 0
elems.push(PatternElement::Str(el.clone())); }
el.clear(); _ => false,
} else { }).expect("malformed param");
re1.push_str(escape(&ch.to_string()).as_str()); let (mut param, rem) = pattern.split_at(close_idx + 1);
re2.push(ch); param = &param[1..param.len() - 1]; // Remove outer brackets
el.push(ch); let (name, pattern) = match param.find(':') {
len += 1; Some(idx) => {
let (name, pattern) = param.split_at(idx);
(name, &pattern[1..])
} }
} None => (param, DEFAULT_PATTERN),
if !el.is_empty() {
elems.push(PatternElement::Str(el.clone()));
}
let re = if is_dynamic {
if !for_prefix {
re1.push('$');
}
re1
} else {
re2
}; };
(re, elems, is_dynamic, len) (
PatternElement::Var(name.to_string()),
format!(r"(?P<{}>{})", &name, &pattern),
rem,
)
}
fn parse(
mut pattern: &str, for_prefix: bool,
) -> (String, Vec<PatternElement>, bool, usize) {
if pattern.find('{').is_none() {
return (
String::from(pattern),
vec![PatternElement::Str(String::from(pattern))],
false,
pattern.chars().count(),
);
};
let mut elems = Vec::new();
let mut re = String::from("^");
while let Some(idx) = pattern.find('{') {
let (prefix, rem) = pattern.split_at(idx);
elems.push(PatternElement::Str(String::from(prefix)));
re.push_str(&escape(prefix));
let (param_pattern, re_part, rem) = Self::parse_param(rem);
elems.push(param_pattern);
re.push_str(&re_part);
pattern = rem;
}
elems.push(PatternElement::Str(String::from(pattern)));
re.push_str(&escape(pattern));
if !for_prefix {
re.push_str("$");
}
(re, elems, true, pattern.chars().count())
} }
} }
@@ -1084,6 +1069,16 @@ mod tests {
let info = re.match_with_params(&req, 0).unwrap(); let info = re.match_with_params(&req, 0).unwrap();
assert_eq!(info.get("version").unwrap(), "151"); assert_eq!(info.get("version").unwrap(), "151");
assert_eq!(info.get("id").unwrap(), "adahg32"); assert_eq!(info.get("id").unwrap(), "adahg32");
let re = ResourceDef::new("/{id:[[:digit:]]{6}}");
assert!(re.is_match("/012345"));
assert!(!re.is_match("/012"));
assert!(!re.is_match("/01234567"));
assert!(!re.is_match("/XXXXXX"));
let req = TestRequest::with_uri("/012345").finish();
let info = re.match_with_params(&req, 0).unwrap();
assert_eq!(info.get("id").unwrap(), "012345");
} }
#[test] #[test]

View File

@@ -5,7 +5,10 @@ use std::rc::Rc;
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use error::Error; use error::Error;
use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler}; use handler::{
AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, RouteHandler,
WrapHandler,
};
use http::Method; use http::Method;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@@ -17,6 +20,7 @@ use pred::Predicate;
use resource::{DefaultResource, Resource}; use resource::{DefaultResource, Resource};
use router::{ResourceDef, Router}; use router::{ResourceDef, Router};
use server::Request; use server::Request;
use with::WithFactory;
/// Resources scope /// Resources scope
/// ///
@@ -55,7 +59,10 @@ pub struct Scope<S> {
middlewares: Rc<Vec<Box<Middleware<S>>>>, middlewares: Rc<Vec<Box<Middleware<S>>>>,
} }
#[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] #[cfg_attr(
feature = "cargo-clippy",
allow(new_without_default_derive)
)]
impl<S: 'static> Scope<S> { impl<S: 'static> Scope<S> {
/// Create a new scope /// Create a new scope
pub fn new(path: &str) -> Scope<S> { pub fn new(path: &str) -> Scope<S> {
@@ -179,7 +186,7 @@ impl<S: 'static> Scope<S> {
where where
F: FnOnce(Scope<S>) -> Scope<S>, F: FnOnce(Scope<S>) -> Scope<S>,
{ {
let rdef = ResourceDef::prefix(&path); let rdef = ResourceDef::prefix(&insert_slash(path));
let scope = Scope { let scope = Scope {
rdef: rdef.clone(), rdef: rdef.clone(),
filters: Vec::new(), filters: Vec::new(),
@@ -222,13 +229,15 @@ impl<S: 'static> Scope<S> {
/// ``` /// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> Scope<S> pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> Scope<S>
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,
{ {
Rc::get_mut(&mut self.router) Rc::get_mut(&mut self.router).unwrap().register_route(
.unwrap() &insert_slash(path),
.register_route(path, method, f); method,
f,
);
self self
} }
@@ -260,7 +269,7 @@ impl<S: 'static> Scope<S> {
F: FnOnce(&mut Resource<S>) -> R + 'static, F: FnOnce(&mut Resource<S>) -> R + 'static,
{ {
// add resource // add resource
let mut resource = Resource::new(ResourceDef::new(path)); let mut resource = Resource::new(ResourceDef::new(&insert_slash(path)));
f(&mut resource); f(&mut resource);
Rc::get_mut(&mut self.router) Rc::get_mut(&mut self.router)
@@ -285,6 +294,35 @@ impl<S: 'static> Scope<S> {
self self
} }
/// Configure handler for specific path prefix.
///
/// A path prefix consists of valid path segments, i.e for the
/// prefix `/app` any request with the paths `/app`, `/app/` or
/// `/app/test` would match, but the path `/application` would
/// not.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// let app = App::new().scope("/scope-prefix", |scope| {
/// scope.handler("/app", |req: &HttpRequest| match *req.method() {
/// http::Method::GET => HttpResponse::Ok(),
/// http::Method::POST => HttpResponse::MethodNotAllowed(),
/// _ => HttpResponse::NotFound(),
/// })
/// });
/// }
/// ```
pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> Scope<S> {
let path = insert_slash(path.trim().trim_right_matches('/'));
Rc::get_mut(&mut self.router)
.expect("Multiple copies of scope router")
.register_handler(&path, Box::new(WrapHandler::new(handler)), None);
self
}
/// Register a scope middleware /// Register a scope middleware
/// ///
/// This is similar to `App's` middlewares, but /// This is similar to `App's` middlewares, but
@@ -300,6 +338,14 @@ impl<S: 'static> Scope<S> {
} }
} }
fn insert_slash(path: &str) -> String {
let mut path = path.to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/');
};
path
}
impl<S: 'static> RouteHandler<S> for Scope<S> { impl<S: 'static> RouteHandler<S> for Scope<S> {
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> { fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
let tail = req.match_info().tail as usize; let tail = req.match_info().tail as usize;
@@ -310,7 +356,7 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
if self.middlewares.is_empty() { if self.middlewares.is_empty() {
self.router.handle(&req2) self.router.handle(&req2)
} else { } else {
AsyncResult::async(Box::new(Compose::new( AsyncResult::future(Box::new(Compose::new(
req2, req2,
Rc::clone(&self.router), Rc::clone(&self.router),
Rc::clone(&self.middlewares), Rc::clone(&self.middlewares),
@@ -714,8 +760,7 @@ mod tests {
let app = App::new() let app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/path1").request(); let req = TestRequest::with_uri("/app/path1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -729,8 +774,7 @@ mod tests {
scope scope
.resource("", |r| r.f(|_| HttpResponse::Ok())) .resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Created())) .resource("/", |r| r.f(|_| HttpResponse::Created()))
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app").request(); let req = TestRequest::with_uri("/app").request();
let resp = app.run(req); let resp = app.run(req);
@@ -746,8 +790,7 @@ mod tests {
let app = App::new() let app = App::new()
.scope("/app/", |scope| { .scope("/app/", |scope| {
scope.resource("", |r| r.f(|_| HttpResponse::Ok())) scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app").request(); let req = TestRequest::with_uri("/app").request();
let resp = app.run(req); let resp = app.run(req);
@@ -763,8 +806,7 @@ mod tests {
let app = App::new() let app = App::new()
.scope("/app/", |scope| { .scope("/app/", |scope| {
scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app").request(); let req = TestRequest::with_uri("/app").request();
let resp = app.run(req); let resp = app.run(req);
@@ -782,12 +824,38 @@ mod tests {
scope scope
.route("/path1", Method::GET, |_: HttpRequest<_>| { .route("/path1", Method::GET, |_: HttpRequest<_>| {
HttpResponse::Ok() HttpResponse::Ok()
}) }).route("/path1", Method::DELETE, |_: HttpRequest<_>| {
.route("/path1", Method::DELETE, |_: HttpRequest<_>| {
HttpResponse::Ok() HttpResponse::Ok()
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/path1").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/path1")
.method(Method::DELETE)
.request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/path1")
.method(Method::POST)
.request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_scope_route_without_leading_slash() {
let app = App::new()
.scope("app", |scope| {
scope
.route("path1", Method::GET, |_: HttpRequest<_>| HttpResponse::Ok())
.route("path1", Method::DELETE, |_: HttpRequest<_>| {
HttpResponse::Ok()
})
}).finish();
let req = TestRequest::with_uri("/app/path1").request(); let req = TestRequest::with_uri("/app/path1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -813,8 +881,7 @@ mod tests {
scope scope
.filter(pred::Get()) .filter(pred::Get())
.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) .resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/path1") let req = TestRequest::with_uri("/app/path1")
.method(Method::POST) .method(Method::POST)
@@ -839,8 +906,7 @@ mod tests {
.body(format!("project: {}", &r.match_info()["project"])) .body(format!("project: {}", &r.match_info()["project"]))
}) })
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/ab-project1/path1").request(); let req = TestRequest::with_uri("/ab-project1/path1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -868,8 +934,7 @@ mod tests {
scope.with_state("/t1", State, |scope| { scope.with_state("/t1", State, |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/t1/path1").request(); let req = TestRequest::with_uri("/app/t1/path1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -887,8 +952,7 @@ mod tests {
.resource("", |r| r.f(|_| HttpResponse::Ok())) .resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Created())) .resource("/", |r| r.f(|_| HttpResponse::Created()))
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/t1").request(); let req = TestRequest::with_uri("/app/t1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -908,8 +972,7 @@ mod tests {
scope.with_state("/t1/", State, |scope| { scope.with_state("/t1/", State, |scope| {
scope.resource("", |r| r.f(|_| HttpResponse::Ok())) scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/t1").request(); let req = TestRequest::with_uri("/app/t1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -929,8 +992,7 @@ mod tests {
scope.with_state("/t1/", State, |scope| { scope.with_state("/t1/", State, |scope| {
scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/t1").request(); let req = TestRequest::with_uri("/app/t1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -952,8 +1014,7 @@ mod tests {
.filter(pred::Get()) .filter(pred::Get())
.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) .resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/t1/path1") let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::POST) .method(Method::POST)
@@ -975,8 +1036,21 @@ mod tests {
scope.nested("/t1", |scope| { scope.nested("/t1", |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/t1/path1").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
}
#[test]
fn test_nested_scope_no_slash() {
let app = App::new()
.scope("/app", |scope| {
scope.nested("t1", |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
})
}).finish();
let req = TestRequest::with_uri("/app/t1/path1").request(); let req = TestRequest::with_uri("/app/t1/path1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -992,8 +1066,7 @@ mod tests {
.resource("", |r| r.f(|_| HttpResponse::Ok())) .resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Created())) .resource("/", |r| r.f(|_| HttpResponse::Created()))
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/t1").request(); let req = TestRequest::with_uri("/app/t1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -1013,8 +1086,7 @@ mod tests {
.filter(pred::Get()) .filter(pred::Get())
.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) .resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/t1/path1") let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::POST) .method(Method::POST)
@@ -1043,8 +1115,7 @@ mod tests {
}) })
}) })
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/project_1/path1").request(); let req = TestRequest::with_uri("/app/project_1/path1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -1076,8 +1147,7 @@ mod tests {
}) })
}) })
}) })
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/test/1/path1").request(); let req = TestRequest::with_uri("/app/test/1/path1").request();
let resp = app.run(req); let resp = app.run(req);
@@ -1103,8 +1173,7 @@ mod tests {
scope scope
.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) .resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
.default_resource(|r| r.f(|_| HttpResponse::BadRequest())) .default_resource(|r| r.f(|_| HttpResponse::BadRequest()))
}) }).finish();
.finish();
let req = TestRequest::with_uri("/app/path2").request(); let req = TestRequest::with_uri("/app/path2").request();
let resp = app.run(req); let resp = app.run(req);
@@ -1120,8 +1189,7 @@ mod tests {
let app = App::new() let app = App::new()
.scope("/app1", |scope| { .scope("/app1", |scope| {
scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest())) scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest()))
}) }).scope("/app2", |scope| scope)
.scope("/app2", |scope| scope)
.default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed()))
.finish(); .finish();
@@ -1137,4 +1205,32 @@ mod tests {
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
} }
#[test]
fn test_handler() {
let app = App::new()
.scope("/scope", |scope| {
scope.handler("/test", |_: &_| HttpResponse::Ok())
}).finish();
let req = TestRequest::with_uri("/scope/test").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/scope/test/").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/scope/test/app").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/scope/testapp").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/scope/blah").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,316 +0,0 @@
use std::sync::mpsc as sync_mpsc;
use std::time::{Duration, Instant};
use std::{io, net, thread};
use futures::{sync::mpsc, Future};
use mio;
use slab::Slab;
use tokio_timer::Delay;
use actix::{msgs::Execute, Arbiter, System};
use super::srv::{ServerCommand, Socket};
use super::worker::Conn;
pub(crate) enum Command {
Pause,
Resume,
Stop,
Worker(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>),
}
struct ServerSocketInfo {
addr: net::SocketAddr,
token: usize,
sock: mio::net::TcpListener,
timeout: Option<Instant>,
}
struct Accept {
poll: mio::Poll,
rx: sync_mpsc::Receiver<Command>,
sockets: Slab<ServerSocketInfo>,
workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
_reg: mio::Registration,
next: usize,
srv: mpsc::UnboundedSender<ServerCommand>,
timer: (mio::Registration, mio::SetReadiness),
}
const CMD: mio::Token = mio::Token(0);
const TIMER: mio::Token = mio::Token(1);
pub(crate) fn start_accept_thread(
socks: Vec<(usize, Socket)>, srv: mpsc::UnboundedSender<ServerCommand>,
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();
let sys = System::current();
// start accept thread
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
let _ = thread::Builder::new()
.name("actix-web accept loop".to_owned())
.spawn(move || {
System::set_current(sys);
Accept::new(reg, rx, socks, workers, srv).poll();
});
(readiness, tx)
}
/// 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
}
impl Accept {
fn new(
_reg: mio::Registration, rx: sync_mpsc::Receiver<Command>,
socks: Vec<(usize, Socket)>,
workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
srv: mpsc::UnboundedSender<ServerCommand>,
) -> Accept {
// 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 commands
if let Err(err) =
poll.register(&_reg, CMD, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register Registration: {}", err);
}
// Start accept
let mut sockets = Slab::new();
for (stoken, sock) in socks {
let server = mio::net::TcpListener::from_std(sock.lst)
.expect("Can not create mio::net::TcpListener");
let entry = sockets.vacant_entry();
let token = entry.key();
// Start listening for incoming connections
if let Err(err) = poll.register(
&server,
mio::Token(token + 1000),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register io: {}", err);
}
entry.insert(ServerSocketInfo {
token: stoken,
addr: sock.addr,
sock: server,
timeout: None,
});
}
// Timer
let (tm, tmr) = mio::Registration::new2();
if let Err(err) =
poll.register(&tm, TIMER, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register Registration: {}", err);
}
Accept {
poll,
rx,
_reg,
sockets,
workers,
srv,
next: 0,
timer: (tm, tmr),
}
}
fn poll(&mut self) {
// Create storage for events
let mut events = mio::Events::with_capacity(128);
loop {
if let Err(err) = self.poll.poll(&mut events, None) {
panic!("Poll error: {}", err);
}
for event in events.iter() {
let token = event.token();
match token {
CMD => if !self.process_cmd() {
return;
},
TIMER => self.process_timer(),
_ => self.accept(token),
}
}
}
}
fn process_timer(&mut self) {
let now = Instant::now();
for (token, info) in self.sockets.iter_mut() {
if let Some(inst) = info.timeout.take() {
if now > inst {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + 1000),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not register server socket {}", err);
} else {
info!("Resume accepting connections on {}", info.addr);
}
} else {
info.timeout = Some(inst);
}
}
}
}
fn process_cmd(&mut self) -> bool {
loop {
match self.rx.try_recv() {
Ok(cmd) => match cmd {
Command::Pause => {
for (_, info) in self.sockets.iter_mut() {
if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err);
} else {
info!("Paused accepting connections on {}", info.addr);
}
}
}
Command::Resume => {
for (token, info) in self.sockets.iter() {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + 1000),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err);
} else {
info!(
"Accepting connections on {} has been resumed",
info.addr
);
}
}
}
Command::Stop => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
Command::Worker(idx, addr) => {
self.workers.push((idx, addr));
}
},
Err(err) => match err {
sync_mpsc::TryRecvError::Empty => break,
sync_mpsc::TryRecvError::Disconnected => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
},
}
}
true
}
fn accept(&mut self, token: mio::Token) {
let token = usize::from(token);
if token < 1000 {
return;
}
if let Some(info) = self.sockets.get_mut(token - 1000) {
loop {
match info.sock.accept_std() {
Ok((io, addr)) => {
let mut msg = Conn {
io,
token: info.token,
peer: Some(addr),
http2: false,
};
while !self.workers.is_empty() {
match self.workers[self.next].1.unbounded_send(msg) {
Ok(_) => (),
Err(err) => {
let _ = self.srv.unbounded_send(
ServerCommand::WorkerDied(
self.workers[self.next].0,
),
);
msg = err.into_inner();
self.workers.swap_remove(self.next);
if self.workers.is_empty() {
error!("No workers");
thread::sleep(Duration::from_millis(100));
break;
} else if self.workers.len() <= self.next {
self.next = 0;
}
continue;
}
}
self.next = (self.next + 1) % self.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);
if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err);
}
// sleep after error
info.timeout = Some(Instant::now() + Duration::from_millis(500));
let r = self.timer.1.clone();
System::current().arbiter().do_send(Execute::new(
move || -> Result<(), ()> {
Arbiter::spawn(
Delay::new(
Instant::now() + Duration::from_millis(510),
).map_err(|_| ())
.and_then(move |_| {
let _ =
r.set_readiness(mio::Ready::readable());
Ok(())
}),
);
Ok(())
},
));
break;
}
}
}
}
}
}

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(())),
}
}
}

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

@@ -0,0 +1,134 @@
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, StreamConfiguration};
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(StreamConfiguration::new().nodelay(true))
.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(StreamConfiguration::new().nodelay(true))
.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::settings::ServiceConfig;
use super::{h1, h2, HttpHandler, IoStream}; 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: HttpHandler + '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(8192),
)),
}
}
}
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,70 +74,58 @@ 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)) => { trace!("Slow request timed out, close connection");
self.node.as_mut().map(|n| h1.settings().head().insert(n)) let proto = mem::replace(&mut self.proto, HttpProtocol::None);
if let HttpProtocol::Unknown(settings, io, buf) = proto {
self.proto = HttpProtocol::H1(h1::Http1Dispatcher::for_error(
settings,
io,
StatusCode::REQUEST_TIMEOUT,
self.ka_timeout.take(),
buf,
));
return self.poll();
}
return Ok(Async::Ready(()));
} }
Some(HttpProtocol::H2(ref mut h2)) => { Ok(Async::NotReady) => (),
self.node.as_mut().map(|n| h2.settings().head().insert(n)) Err(_) => panic!("Something is really wrong"),
} }
Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => {
self.node.as_mut().map(|n| settings.head().insert(n))
}
None => unreachable!(),
};
} }
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() {
n.remove()
};
}
_ => (),
}
return result;
}
Some(HttpProtocol::H2(ref mut h2)) => {
let result = h2.poll();
match result {
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 io.read_available(buf) { match io.read_available(buf) {
Ok(Async::Ready(true)) | Err(_) => { Ok(Async::Ready((read_some, stream_closed))) => {
debug!("Ignored premature client disconnection"); is_eof = stream_closed;
settings.remove_channel(); // Only disconnect if no data was read.
if let Some(n) = self.node.as_mut() { if is_eof && !read_some {
n.remove() disconnect = true;
}; }
return Err(()); }
Err(e) => {
err = Some(e.into());
} }
_ => (), _ => (),
} }
if disconnect {
debug!("Ignored premature client disconnection");
return Ok(Async::Ready(()));
} else if let Some(e) = err {
return Err(e);
}
if buf.len() >= 14 { if buf.len() >= 14 {
if buf[..14] == HTTP2_PREFACE[..] { if buf[..14] == HTTP2_PREFACE[..] {
@@ -161,24 +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 = self.proto = HttpProtocol::H1(h1::Http1Dispatcher::new(
Some(HttpProtocol::H1(h1::Http1::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();
} }
} }
@@ -187,79 +169,45 @@ where
} }
} }
pub(crate) struct Node<T> { #[doc(hidden)]
next: Option<*mut Node<T>>, pub struct H1Channel<T, H>
prev: Option<*mut Node<T>>, 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>(&mut self, next: &mut Node<I>) { io,
unsafe { BytesMut::with_capacity(8192),
let next: *mut Node<T> = next as *const _ as *mut _; false,
None,
if let Some(ref mut next2) = self.next { )),
let n = next2.as_mut().unwrap();
n.prev = Some(next);
}
self.next = Some(next);
let next: &mut Node<T> = &mut *next;
next.prev = Some(self 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;
}
} }
} }
} }
@@ -297,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,9 +1,84 @@
use std::io;
use futures::{Async, Poll}; use futures::{Async, Poll};
use http2;
use super::{helpers, HttpHandlerTask, Writer}; use super::{helpers, HttpHandlerTask, Writer};
use http::{StatusCode, Version}; use http::{StatusCode, Version};
use Error; 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); pub(crate) struct ServerError(Version, StatusCode);
impl ServerError { impl ServerError {
@@ -16,8 +91,17 @@ impl HttpHandlerTask for ServerError {
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> { fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
{ {
let bytes = io.buffer(); 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); 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(); io.set_date();
Ok(Async::Ready(true)) Ok(Async::Ready(true))
} }

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ use futures::{Async, Poll};
use httparse; use httparse;
use super::message::{MessageFlags, Request}; 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};
@@ -18,8 +18,13 @@ pub(crate) struct H1Decoder {
decoder: Option<EncodingDecoder>, decoder: Option<EncodingDecoder>,
} }
#[derive(Debug)]
pub(crate) enum Message { pub(crate) enum Message {
Message { msg: Request, payload: bool }, Message {
msg: Request,
payload: bool,
expect: bool,
},
Chunk(Bytes), Chunk(Bytes),
Eof, Eof,
} }
@@ -42,7 +47,9 @@ 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,10 +67,11 @@ impl H1Decoder {
.parse_message(src, settings) .parse_message(src, settings)
.map_err(DecoderError::Error)? .map_err(DecoderError::Error)?
{ {
Async::Ready((msg, decoder)) => { Async::Ready((msg, expect, decoder)) => {
self.decoder = decoder; self.decoder = decoder;
Ok(Some(Message::Message { Ok(Some(Message::Message {
msg, msg,
expect,
payload: self.decoder.is_some(), payload: self.decoder.is_some(),
})) }))
} }
@@ -79,12 +87,15 @@ impl H1Decoder {
} }
fn parse_message<H>( fn parse_message<H>(
&self, buf: &mut BytesMut, settings: &WorkerSettings<H>, &self,
) -> Poll<(Request, Option<EncodingDecoder>), ParseError> { buf: &mut BytesMut,
settings: &ServiceConfig<H>,
) -> Poll<(Request, bool, 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 mut expect_continue = false;
let msg = { let msg = {
// Unsafe: we read only this data only after httparse parses headers into. // Unsafe: we read only this data only after httparse parses headers into.
@@ -152,23 +163,25 @@ impl H1Decoder {
} }
// transfer-encoding // transfer-encoding
header::TRANSFER_ENCODING => { header::TRANSFER_ENCODING => {
if let Ok(s) = value.to_str() { if let Ok(s) = value.to_str().map(|s| s.trim()) {
chunked = s.to_lowercase().contains("chunked"); chunked = s.eq_ignore_ascii_case("chunked");
} else { } else {
return Err(ParseError::Header); return Err(ParseError::Header);
} }
} }
// connection keep-alive state // connection keep-alive state
header::CONNECTION => { header::CONNECTION => {
let ka = if let Ok(conn) = value.to_str() { let ka = if let Ok(conn) =
value.to_str().map(|conn| conn.trim())
{
if version == Version::HTTP_10 if version == Version::HTTP_10
&& conn.contains("keep-alive") && conn.eq_ignore_ascii_case("keep-alive")
{ {
true true
} else { } else {
version == Version::HTTP_11 version == Version::HTTP_11
&& !(conn.contains("close") && !(conn.eq_ignore_ascii_case("close")
|| conn.contains("upgrade")) || conn.eq_ignore_ascii_case("upgrade"))
} }
} else { } else {
false false
@@ -177,6 +190,18 @@ impl H1Decoder {
} }
header::UPGRADE => { header::UPGRADE => {
has_upgrade = true; has_upgrade = true;
// check content-length, some clients (dart)
// sends "content-length: 0" with websocket upgrade
if let Ok(val) = value.to_str().map(|val| val.trim()) {
if val.eq_ignore_ascii_case("websocket") {
content_length = None;
}
}
}
header::EXPECT => {
if value == "100-continue" {
expect_continue = true
}
} }
_ => (), _ => (),
} }
@@ -208,7 +233,7 @@ impl H1Decoder {
None None
}; };
Ok(Async::Ready((msg, decoder))) Ok(Async::Ready((msg, expect_continue, decoder)))
} }
} }
@@ -220,7 +245,9 @@ pub(crate) struct HeaderIndex {
impl HeaderIndex { impl HeaderIndex {
pub(crate) fn record( pub(crate) fn record(
bytes: &[u8], headers: &[httparse::Header], indices: &mut [HeaderIndex], bytes: &[u8],
headers: &[httparse::Header],
indices: &mut [HeaderIndex],
) { ) {
let bytes_ptr = bytes.as_ptr() as usize; let bytes_ptr = bytes.as_ptr() as usize;
for (header, indices) in headers.iter().zip(indices.iter_mut()) { for (header, indices) in headers.iter().zip(indices.iter_mut()) {
@@ -368,7 +395,10 @@ macro_rules! byte (
impl ChunkedState { impl ChunkedState {
fn step( fn step(
&self, body: &mut BytesMut, size: &mut u64, buf: &mut Option<Bytes>, &self,
body: &mut BytesMut,
size: &mut u64,
buf: &mut Option<Bytes>,
) -> Poll<ChunkedState, io::Error> { ) -> Poll<ChunkedState, io::Error> {
use self::ChunkedState::*; use self::ChunkedState::*;
match *self { match *self {
@@ -431,7 +461,8 @@ impl ChunkedState {
} }
} }
fn read_size_lf( fn read_size_lf(
rdr: &mut BytesMut, size: &mut u64, rdr: &mut BytesMut,
size: &mut u64,
) -> Poll<ChunkedState, io::Error> { ) -> Poll<ChunkedState, io::Error> {
match byte!(rdr) { match byte!(rdr) {
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)),
@@ -444,7 +475,9 @@ impl ChunkedState {
} }
fn read_body( fn read_body(
rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option<Bytes>, rdr: &mut BytesMut,
rem: &mut u64,
buf: &mut Option<Bytes>,
) -> Poll<ChunkedState, io::Error> { ) -> Poll<ChunkedState, io::Error> {
trace!("Chunked read, remaining={:?}", rem); trace!("Chunked read, remaining={:?}", rem);

View File

@@ -1,7 +1,6 @@
// #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] // #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::io::{self, Write}; use std::io::{self, Write};
use std::rc::Rc;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use futures::{Async, Poll}; use futures::{Async, Poll};
@@ -9,7 +8,7 @@ use tokio_io::AsyncWrite;
use super::helpers; use super::helpers;
use super::output::{Output, ResponseInfo, ResponseLength}; use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::WorkerSettings; use super::settings::ServiceConfig;
use super::Request; 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};
@@ -38,11 +37,11 @@ pub(crate) struct H1Writer<T: AsyncWrite, H: 'static> {
headers_size: u32, headers_size: u32,
buffer: Output, 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(stream: T, settings: Rc<WorkerSettings<H>>) -> H1Writer<T, H> { pub fn new(stream: T, settings: ServiceConfig<H>) -> H1Writer<T, H> {
H1Writer { H1Writer {
flags: Flags::KEEPALIVE, flags: Flags::KEEPALIVE,
written: 0, written: 0,
@@ -63,7 +62,17 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
self.flags = Flags::KEEPALIVE; self.flags = Flags::KEEPALIVE;
} }
pub fn disconnected(&mut self) {} pub fn flushed(&mut self) -> bool {
self.buffer.is_empty()
}
pub fn disconnected(&mut self) {
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)
@@ -152,8 +161,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
let reason = msg.reason().as_bytes(); let reason = msg.reason().as_bytes();
if let Body::Binary(ref bytes) = body { if let Body::Binary(ref bytes) = body {
buffer.reserve( buffer.reserve(
256 256 + msg.headers().len() * AVERAGE_HEADER_SIZE
+ msg.headers().len() * AVERAGE_HEADER_SIZE
+ bytes.len() + bytes.len()
+ reason.len(), + reason.len(),
); );
@@ -168,13 +176,11 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
buffer.extend_from_slice(reason); buffer.extend_from_slice(reason);
// content length // content length
let mut len_is_set = true;
match info.length { match info.length {
ResponseLength::Chunked => { ResponseLength::Chunked => {
buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n")
} }
ResponseLength::Zero => {
buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n")
}
ResponseLength::Length(len) => { ResponseLength::Length(len) => {
helpers::write_content_length(len, &mut buffer) helpers::write_content_length(len, &mut buffer)
} }
@@ -183,6 +189,10 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
write!(buffer.writer(), "{}", len)?; write!(buffer.writer(), "{}", len)?;
buffer.extend_from_slice(b"\r\n"); 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"), ResponseLength::None => buffer.extend_from_slice(b"\r\n"),
} }
if let Some(ce) = info.content_encoding { if let Some(ce) = info.content_encoding {
@@ -195,47 +205,57 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
let mut pos = 0; let mut pos = 0;
let mut has_date = false; let mut has_date = false;
let mut remaining = buffer.remaining_mut(); let mut remaining = buffer.remaining_mut();
unsafe { let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) };
let mut buf = &mut *(buffer.bytes_mut() as *mut [u8]); for (key, value) in msg.headers() {
for (key, value) in msg.headers() { match *key {
match *key { TRANSFER_ENCODING => continue,
TRANSFER_ENCODING => continue, CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
CONTENT_ENCODING => if encoding != ContentEncoding::Identity { continue;
continue; },
}, CONTENT_LENGTH => match info.length {
CONTENT_LENGTH => match info.length { ResponseLength::None => (),
ResponseLength::None => (), ResponseLength::Zero => {
_ => continue, len_is_set = true;
},
DATE => {
has_date = true;
} }
_ => (), _ => continue,
},
DATE => {
has_date = true;
} }
_ => (),
}
let v = value.as_ref(); let v = value.as_ref();
let k = key.as_str().as_bytes(); let k = key.as_str().as_bytes();
let len = k.len() + v.len() + 4; let len = k.len() + v.len() + 4;
if len > remaining { if len > remaining {
unsafe {
buffer.advance_mut(pos); buffer.advance_mut(pos);
pos = 0; }
buffer.reserve(len); pos = 0;
remaining = buffer.remaining_mut(); buffer.reserve(len);
remaining = buffer.remaining_mut();
unsafe {
buf = &mut *(buffer.bytes_mut() as *mut _); buf = &mut *(buffer.bytes_mut() as *mut _);
} }
buf[pos..pos + k.len()].copy_from_slice(k);
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
} }
buf[pos..pos + k.len()].copy_from_slice(k);
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
}
unsafe {
buffer.advance_mut(pos); 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 {
@@ -269,10 +289,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
let pl: &[u8] = payload.as_ref(); let pl: &[u8] = payload.as_ref();
let n = match Self::write_data(&mut self.stream, pl) { let n = match Self::write_data(&mut self.stream, pl) {
Err(err) => { Err(err) => {
if err.kind() == io::ErrorKind::WriteZero { self.disconnected();
self.disconnected();
}
return Err(err); return Err(err);
} }
Ok(val) => val, Ok(val) => val,
@@ -316,14 +333,15 @@ 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 written = { let written = {
match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) { match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) {
Err(err) => { Err(err) => {
if err.kind() == io::ErrorKind::WriteZero { self.disconnected();
self.disconnected();
}
return Err(err); return Err(err);
} }
Ok(val) => val, Ok(val) => val,
@@ -337,9 +355,10 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
} }
} }
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

@@ -2,7 +2,7 @@ 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, Instant}; use std::time::Instant;
use std::{cmp, io, mem}; use std::{cmp, io, mem};
use bytes::{Buf, Bytes}; use bytes::{Buf, Bytes};
@@ -14,19 +14,21 @@ use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::Delay; use tokio_timer::Delay;
use error::{Error, PayloadError}; use error::{Error, PayloadError};
use extensions::Extensions;
use http::{StatusCode, Version}; use http::{StatusCode, Version};
use payload::{Payload, PayloadStatus, PayloadWriter}; use payload::{Payload, PayloadStatus, PayloadWriter};
use uri::Url; use uri::Url;
use super::error::ServerError; use super::error::{HttpDispatchError, ServerError};
use super::h2writer::H2Writer; use super::h2writer::H2Writer;
use super::input::PayloadType; use super::input::PayloadType;
use super::settings::WorkerSettings; use super::settings::ServiceConfig;
use super::{HttpHandler, HttpHandlerTask, Writer}; 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;
} }
} }
@@ -37,11 +39,13 @@ where
H: HttpHandler + '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<Delay>, extensions: Option<Rc<Extensions>>,
ka_expire: Instant,
ka_timer: Option<Delay>,
} }
enum State<T: AsyncRead + AsyncWrite> { enum State<T: AsyncRead + AsyncWrite> {
@@ -52,12 +56,27 @@ 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(),
@@ -65,84 +84,84 @@ where
unread: if buf.is_empty() { None } else { 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) { }
if item.flags.contains(EntryFlags::EOF)
&& !item.flags.contains(EntryFlags::FINISHED)
{
match item.task.poll_completed() { 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(
@@ -157,6 +176,7 @@ where
if item.flags.contains(EntryFlags::FINISHED) if item.flags.contains(EntryFlags::FINISHED)
&& !item.flags.contains(EntryFlags::WRITE_DONE) && !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) => (),
@@ -164,7 +184,7 @@ where
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);
} }
} }
@@ -173,7 +193,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)
{ {
@@ -197,50 +217,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 = Delay::new(
Instant::now()
+ Duration::new(keep_alive, 0),
);
// 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,9 +248,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 return conn.poll_close().map_err(|e| e.into());
.poll_close()
.map_err(|e| error!("Error during connection close: {}", e));
} else { } else {
return Ok(Async::NotReady); return Ok(Async::NotReady);
} }
@@ -265,7 +263,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 {
@@ -274,6 +272,39 @@ 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);
let _ = timer.poll();
}
} else {
timer.reset(self.ka_expire);
let _ = timer.poll();
}
}
Ok(Async::NotReady) => (),
Err(e) => {
error!("Timer error {:?}", e);
return Err(HttpDispatchError::Unknown);
}
}
}
Ok(())
}
} }
bitflags! { bitflags! {
@@ -322,8 +353,12 @@ struct Entry<H: HttpHandler + 'static> {
impl<H: HttpHandler + 'static> Entry<H> { impl<H: HttpHandler + 'static> Entry<H> {
fn new( fn new(
parts: Parts, recv: RecvStream, resp: SendResponse<Bytes>, parts: Parts,
addr: Option<SocketAddr>, settings: &Rc<WorkerSettings<H>>, recv: RecvStream,
resp: SendResponse<Bytes>,
addr: Option<SocketAddr>,
settings: ServiceConfig<H>,
extensions: Option<Rc<Extensions>>,
) -> Entry<H> ) -> Entry<H>
where where
H: HttpHandler + 'static, H: HttpHandler + 'static,
@@ -338,6 +373,7 @@ impl<H: HttpHandler + 'static> Entry<H> {
inner.method = parts.method; inner.method = parts.method;
inner.version = parts.version; inner.version = parts.version;
inner.headers = parts.headers; inner.headers = parts.headers;
inner.stream_extensions = extensions;
*inner.payload.borrow_mut() = Some(payload); *inner.payload.borrow_mut() = Some(payload);
inner.addr = addr; inner.addr = addr;
} }
@@ -346,28 +382,20 @@ impl<H: HttpHandler + 'static> Entry<H> {
let psender = PayloadType::new(msg.headers(), psender); let psender = PayloadType::new(msg.headers(), psender);
// start request processing // start request processing
let mut task = None; let task = match settings.handler().handle(msg) {
for h in settings.handlers().iter_mut() { Ok(task) => EntryPipe::Task(task),
msg = match h.handle(msg) { Err(_) => EntryPipe::Error(ServerError::err(
Ok(t) => { Version::HTTP_2,
task = Some(t); StatusCode::NOT_FOUND,
break; )),
} };
Err(msg) => msg,
}
}
Entry { Entry {
task: task.map(EntryPipe::Task).unwrap_or_else(|| { task,
EntryPipe::Error(ServerError::err(
Version::HTTP_2,
StatusCode::NOT_FOUND,
))
}),
payload: psender,
stream: H2Writer::new(resp, Rc::clone(settings)),
flags: EntryFlags::empty(),
recv, recv,
payload: psender,
stream: H2Writer::new(resp, settings),
flags: EntryFlags::empty(),
} }
} }

View File

@@ -1,25 +1,27 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] #![cfg_attr(
feature = "cargo-clippy",
allow(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::{HttpTryFrom, Method, Version};
use super::helpers; use super::helpers;
use super::message::Request; use super::message::Request;
use super::output::{Output, ResponseInfo, ResponseLength}; use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::WorkerSettings; 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 http::header::{ use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, 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;
@@ -40,13 +42,11 @@ pub(crate) struct H2Writer<H: 'static> {
written: u64, written: u64,
buffer: Output, 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>, settings: Rc<WorkerSettings<H>>,
) -> H2Writer<H> {
H2Writer { H2Writer {
stream: None, stream: None,
flags: Flags::empty(), flags: Flags::empty(),
@@ -96,6 +96,7 @@ impl<H: 'static> Writer for H2Writer<H> {
let mut has_date = false; let mut has_date = false;
let mut resp = Response::new(()); let mut resp = Response::new(());
let mut len_is_set = false;
*resp.status_mut() = msg.status(); *resp.status_mut() = msg.status();
*resp.version_mut() = Version::HTTP_2; *resp.version_mut() = Version::HTTP_2;
for (key, value) in msg.headers().iter() { for (key, value) in msg.headers().iter() {
@@ -107,12 +108,15 @@ impl<H: 'static> Writer for H2Writer<H> {
}, },
CONTENT_LENGTH => match info.length { CONTENT_LENGTH => match info.length {
ResponseLength::None => (), ResponseLength::None => (),
ResponseLength::Zero => {
len_is_set = true;
}
_ => continue, _ => continue,
}, },
DATE => has_date = true, DATE => has_date = true,
_ => (), _ => (),
} }
resp.headers_mut().insert(key, value.clone()); resp.headers_mut().append(key, value.clone());
} }
// set date header // set date header
@@ -126,8 +130,10 @@ impl<H: 'static> Writer for H2Writer<H> {
// content length // content length
match info.length { match info.length {
ResponseLength::Zero => { ResponseLength::Zero => {
resp.headers_mut() if !len_is_set {
.insert(CONTENT_LENGTH, HeaderValue::from_static("0")); resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
}
self.flags.insert(Flags::EOF); self.flags.insert(Flags::EOF);
} }
ResponseLength::Length(len) => { ResponseLength::Length(len) => {
@@ -144,6 +150,9 @@ impl<H: 'static> Writer for H2Writer<H> {
resp.headers_mut() resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap()); .insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap());
} }
ResponseLength::None => {
self.flags.insert(Flags::EOF);
}
_ => (), _ => (),
} }
if let Some(ce) = info.content_encoding { if let Some(ce) = info.content_encoding {
@@ -151,6 +160,8 @@ impl<H: 'static> Writer for H2Writer<H> {
.insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap()); .insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap());
} }
trace!("Response: {:?}", resp);
match self match self
.respond .respond
.send_response(resp, self.flags.contains(Flags::EOF)) .send_response(resp, self.flags.contains(Flags::EOF))
@@ -159,15 +170,12 @@ impl<H: 'static> Writer for H2Writer<H> {
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); let body = msg.replace_body(Body::Empty);
if let Body::Binary(bytes) = body { if let Body::Binary(bytes) = body {
if bytes.is_empty() { if bytes.is_empty() {
Ok(WriterState::Done) Ok(WriterState::Done)
} else { } else {
self.flags.insert(Flags::EOF); self.flags.insert(Flags::EOF);
self.written = bytes.len() as u64;
self.buffer.write(bytes.as_ref())?; self.buffer.write(bytes.as_ref())?;
if let Some(ref mut stream) = self.stream { if let Some(ref mut stream) = self.stream {
self.flags.insert(Flags::RESERVED); self.flags.insert(Flags::RESERVED);
@@ -183,8 +191,6 @@ 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
@@ -228,6 +234,16 @@ impl<H: 'static> Writer for H2Writer<H> {
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
} }
if self.flags.contains(Flags::EOF)
&& !self.flags.contains(Flags::RESERVED)
&& self.buffer.is_empty()
{
if let Err(e) = stream.send_data(Bytes::new(), true) {
return Err(io::Error::new(io::ErrorKind::Other, e));
}
return Ok(Async::Ready(()));
}
loop { loop {
match stream.poll_capacity() { match stream.poll_capacity() {
Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::NotReady) => return Ok(Async::NotReady),

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

@@ -8,8 +8,10 @@ const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
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 {
@@ -27,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),
@@ -72,7 +78,7 @@ pub 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,
); );
@@ -88,7 +94,7 @@ pub 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,
) )
@@ -105,47 +111,55 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
} }
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
unsafe { let mut curr: isize = 39;
let mut curr: isize = 39; let mut buf: [u8; 41] = unsafe { mem::uninitialized() };
let mut buf: [u8; 41] = mem::uninitialized(); buf[39] = b'\r';
buf[39] = b'\r'; buf[40] = b'\n';
buf[40] = b'\n'; let buf_ptr = buf.as_mut_ptr();
let buf_ptr = buf.as_mut_ptr(); let lut_ptr = DEC_DIGITS_LUT.as_ptr();
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
// 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);
} }
}
unsafe {
bytes.extend_from_slice(slice::from_raw_parts( bytes.extend_from_slice(slice::from_raw_parts(
buf_ptr.offset(curr), buf_ptr.offset(curr),
41 - curr as usize, 41 - curr as usize,

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(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 address can be bound or an `Actix` system is not yet
/// configured.
///
/// ```rust
/// extern crate actix_web;
/// extern crate actix;
/// 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::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(|_| ()));
}
}

View File

@@ -5,7 +5,7 @@ use brotli2::write::BrotliDecoder;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use error::PayloadError; use error::PayloadError;
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
use flate2::write::{DeflateDecoder, GzDecoder}; use flate2::write::{GzDecoder, ZlibDecoder};
use header::ContentEncoding; use header::ContentEncoding;
use http::header::{HeaderMap, CONTENT_ENCODING}; use http::header::{HeaderMap, CONTENT_ENCODING};
use payload::{PayloadSender, PayloadStatus, PayloadWriter}; use payload::{PayloadSender, PayloadStatus, PayloadWriter};
@@ -139,7 +139,7 @@ impl PayloadWriter for EncodedPayload {
pub(crate) enum Decoder { pub(crate) enum Decoder {
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
Deflate(Box<DeflateDecoder<Writer>>), Deflate(Box<ZlibDecoder<Writer>>),
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
Gzip(Box<GzDecoder<Writer>>), Gzip(Box<GzDecoder<Writer>>),
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
@@ -186,7 +186,7 @@ impl PayloadStream {
} }
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
ContentEncoding::Deflate => { ContentEncoding::Deflate => {
Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new()))) Decoder::Deflate(Box::new(ZlibDecoder::new(Writer::new())))
} }
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
ContentEncoding::Gzip => { ContentEncoding::Gzip => {

View File

@@ -1,5 +1,6 @@
use std::cell::{Cell, Ref, RefCell, RefMut}; use std::cell::{Cell, Ref, RefCell, RefMut};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::rc::Rc; use std::rc::Rc;
@@ -35,6 +36,7 @@ pub(crate) struct InnerRequest {
pub(crate) info: RefCell<ConnectionInfo>, pub(crate) info: RefCell<ConnectionInfo>,
pub(crate) payload: RefCell<Option<Payload>>, pub(crate) payload: RefCell<Option<Payload>>,
pub(crate) settings: ServerSettings, pub(crate) settings: ServerSettings,
pub(crate) stream_extensions: Option<Rc<Extensions>>,
pool: &'static RequestPool, pool: &'static RequestPool,
} }
@@ -82,6 +84,7 @@ impl Request {
info: RefCell::new(ConnectionInfo::default()), info: RefCell::new(ConnectionInfo::default()),
payload: RefCell::new(None), payload: RefCell::new(None),
extensions: RefCell::new(Extensions::new()), extensions: RefCell::new(Extensions::new()),
stream_extensions: None,
}), }),
} }
} }
@@ -189,6 +192,12 @@ impl Request {
} }
} }
/// Io stream extensions
#[inline]
pub fn stream_extensions(&self) -> Option<&Extensions> {
self.inner().stream_extensions.as_ref().map(|e| e.as_ref())
}
/// Server settings /// Server settings
#[inline] #[inline]
pub fn server_settings(&self) -> &ServerSettings { pub fn server_settings(&self) -> &ServerSettings {
@@ -212,6 +221,26 @@ impl Request {
} }
} }
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( pub(crate) struct RequestPool(
RefCell<VecDeque<Rc<InnerRequest>>>, RefCell<VecDeque<Rc<InnerRequest>>>,
RefCell<ServerSettings>, RefCell<ServerSettings>,

View File

@@ -1,5 +1,105 @@
//! 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 bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
@@ -7,7 +107,10 @@ use futures::{Async, Poll};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tcp::TcpStream; use tokio_tcp::TcpStream;
pub(crate) mod accept; pub use actix_net::server::{PauseServer, ResumeServer, StopServer};
pub(crate) mod acceptor;
pub(crate) mod builder;
mod channel; mod channel;
mod error; mod error;
pub(crate) mod h1; pub(crate) mod h1;
@@ -15,24 +118,39 @@ 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 http;
pub(crate) mod incoming;
pub(crate) mod input; pub(crate) mod input;
pub(crate) mod message; pub(crate) mod message;
pub(crate) mod output; pub(crate) mod output;
pub(crate) mod service;
pub(crate) mod settings; pub(crate) mod settings;
mod srv; mod ssl;
mod worker;
pub use self::handler::*;
pub use self::http::HttpServer;
pub use self::message::Request; 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)] #[doc(hidden)]
pub use self::helpers::write_content_length; pub use self::helpers::write_content_length;
use actix::Message;
use body::Binary; use body::Binary;
use error::Error; use extensions::Extensions;
use header::ContentEncoding; use header::ContentEncoding;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@@ -48,7 +166,8 @@ const HW_BUFFER_SIZE: usize = 32_768;
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse}; /// # extern crate actix;
/// use actix_web::{server, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system /// let sys = actix::System::new("example"); // <- create Actix system
@@ -63,15 +182,25 @@ const HW_BUFFER_SIZE: usize = 32_768;
/// 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 {
@@ -101,84 +230,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 {
/// Whether to try and shut down gracefully
pub graceful: bool,
}
impl Message for StopServer {
type Result = Result<(), ()>;
}
/// 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)
}
}
/// 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
}
}
#[doc(hidden)] #[doc(hidden)]
#[derive(Debug)] #[derive(Debug)]
pub enum WriterState { pub enum WriterState {
@@ -214,41 +265,81 @@ 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 read_available(&mut self, buf: &mut BytesMut) -> Poll<bool, io::Error> { 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; let mut read_some = false;
loop { loop {
if buf.remaining_mut() < LW_BUFFER_SIZE { if buf.remaining_mut() < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE); buf.reserve(HW_BUFFER_SIZE);
} }
unsafe {
match self.read(buf.bytes_mut()) { let read = unsafe { self.read(buf.bytes_mut()) };
Ok(n) => { match read {
if n == 0 { Ok(n) => {
return Ok(Async::Ready(!read_some)); if n == 0 {
} else { return Ok(Async::Ready((read_some, true)));
read_some = true; } else {
read_some = true;
unsafe {
buf.advance_mut(n); buf.advance_mut(n);
} }
} }
Err(e) => { }
return if e.kind() == io::ErrorKind::WouldBlock { Err(e) => {
if read_some { return if e.kind() == io::ErrorKind::WouldBlock {
Ok(Async::Ready(false)) if read_some {
} else { Ok(Async::Ready((read_some, false)))
Ok(Async::NotReady)
}
} else { } else {
Err(e) Ok(Async::NotReady)
}; }
} } else if e.kind() == io::ErrorKind::ConnectionReset && read_some {
Ok(Async::Ready((read_some, true)))
} 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, _dur: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
} }
impl IoStream for TcpStream { impl IoStream for TcpStream {
@@ -257,6 +348,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)
@@ -266,91 +362,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)
}
}
#[cfg(feature = "rust-tls")]
use rustls::{ClientSession, ServerSession};
#[cfg(feature = "rust-tls")]
use tokio_rustls::TlsStream as RustlsStream;
#[cfg(feature = "rust-tls")]
impl IoStream for RustlsStream<TcpStream, 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)
}
}
#[cfg(feature = "rust-tls")]
impl IoStream for RustlsStream<TcpStream, ServerSession> {
#[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)
} }
} }

View File

@@ -7,11 +7,11 @@ use std::{cmp, fmt, io, mem};
use brotli2::write::BrotliEncoder; use brotli2::write::BrotliEncoder;
use bytes::BytesMut; use bytes::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 http::header::{ACCEPT_ENCODING, CONTENT_LENGTH}; use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH};
use http::Version; use http::{StatusCode, Version};
use super::message::InnerRequest; use super::message::InnerRequest;
use body::{Binary, Body}; use body::{Binary, Body};
@@ -151,10 +151,9 @@ impl Output {
let version = resp.version().unwrap_or_else(|| req.version); let version = resp.version().unwrap_or_else(|| req.version);
let mut len = 0; let mut len = 0;
#[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
let has_body = match resp.body() { let has_body = match resp.body() {
&Body::Empty => false, Body::Empty => false,
&Body::Binary(ref bin) => { Body::Binary(ref bin) => {
len = bin.len(); len = bin.len();
!(response_encoding == ContentEncoding::Auto && len < 96) !(response_encoding == ContentEncoding::Auto && len < 96)
} }
@@ -190,16 +189,19 @@ impl Output {
#[cfg(not(any(feature = "brotli", feature = "flate2")))] #[cfg(not(any(feature = "brotli", feature = "flate2")))]
let mut encoding = ContentEncoding::Identity; let mut encoding = ContentEncoding::Identity;
#[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
let transfer = match resp.body() { let transfer = match resp.body() {
&Body::Empty => { Body::Empty => {
if !info.head { info.length = match resp.status() {
info.length = ResponseLength::Zero; StatusCode::NO_CONTENT
} | StatusCode::CONTINUE
| StatusCode::SWITCHING_PROTOCOLS
| StatusCode::PROCESSING => ResponseLength::None,
_ => ResponseLength::Zero,
};
*self = Output::Empty(buf); *self = Output::Empty(buf);
return; return;
} }
&Body::Binary(_) => { Body::Binary(_) => {
#[cfg(any(feature = "brotli", feature = "flate2"))] #[cfg(any(feature = "brotli", feature = "flate2"))]
{ {
if !(encoding == ContentEncoding::Identity if !(encoding == ContentEncoding::Identity
@@ -210,7 +212,7 @@ impl Output {
let mut enc = match encoding { let mut enc = match encoding {
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate( ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::fast()), ZlibEncoder::new(transfer, Compression::fast()),
), ),
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip( ContentEncoding::Gzip => ContentEncoder::Gzip(
@@ -244,7 +246,7 @@ impl Output {
} }
return; return;
} }
&Body::Streaming(_) | &Body::Actor(_) => { Body::Streaming(_) | Body::Actor(_) => {
if resp.upgrade() { if resp.upgrade() {
if version == Version::HTTP_2 { if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2"); error!("Connection upgrade is forbidden for HTTP/2");
@@ -273,10 +275,9 @@ impl Output {
let enc = 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::fast()))
Compression::fast(), }
)),
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
ContentEncoding::Gzip => { ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast())) ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
@@ -298,11 +299,10 @@ impl Output {
match resp.chunked() { match resp.chunked() {
Some(true) => { Some(true) => {
// Enable transfer encoding // Enable transfer encoding
info.length = ResponseLength::Chunked;
if version == Version::HTTP_2 { if version == Version::HTTP_2 {
info.length = ResponseLength::None;
TransferEncoding::eof(buf) TransferEncoding::eof(buf)
} else { } else {
info.length = ResponseLength::Chunked;
TransferEncoding::chunked(buf) TransferEncoding::chunked(buf)
} }
} }
@@ -336,15 +336,11 @@ impl Output {
} }
} else { } else {
// Enable transfer encoding // Enable transfer encoding
match version { info.length = ResponseLength::Chunked;
Version::HTTP_11 => { if version == Version::HTTP_2 {
info.length = ResponseLength::Chunked; TransferEncoding::eof(buf)
TransferEncoding::chunked(buf) } else {
} TransferEncoding::chunked(buf)
_ => {
info.length = ResponseLength::None;
TransferEncoding::eof(buf)
}
} }
} }
} }
@@ -354,7 +350,7 @@ impl Output {
pub(crate) enum ContentEncoder { pub(crate) enum ContentEncoder {
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
Deflate(DeflateEncoder<TransferEncoding>), Deflate(ZlibEncoder<TransferEncoding>),
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
Gzip(GzEncoder<TransferEncoding>), Gzip(GzEncoder<TransferEncoding>),
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]

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,17 +1,20 @@
use std::cell::{Cell, RefCell, RefMut, UnsafeCell}; use std::cell::{Cell, RefCell};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt::Write; use std::fmt::Write;
use std::rc::Rc; use std::rc::Rc;
use std::time::{Duration, Instant};
use std::{env, fmt, net}; use std::{env, fmt, net};
use bytes::BytesMut; use bytes::BytesMut;
use futures::{future, Future};
use futures_cpupool::CpuPool; use futures_cpupool::CpuPool;
use http::StatusCode; use http::StatusCode;
use lazycell::LazyCell; use lazycell::LazyCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use time; use time;
use tokio_current_thread::spawn;
use tokio_timer::{sleep, Delay};
use super::channel::Node;
use super::message::{Request, RequestPool}; use super::message::{Request, RequestPool};
use super::KeepAlive; use super::KeepAlive;
use body::Body; use body::Body;
@@ -39,7 +42,7 @@ lazy_static! {
/// Various server settings /// Various server settings
pub struct ServerSettings { pub struct ServerSettings {
addr: Option<net::SocketAddr>, addr: net::SocketAddr,
secure: bool, secure: bool,
host: String, host: String,
cpu_pool: LazyCell<CpuPool>, cpu_pool: LazyCell<CpuPool>,
@@ -61,7 +64,7 @@ impl Clone for ServerSettings {
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::get_pool(), responses: HttpResponsePool::get_pool(),
@@ -73,15 +76,9 @@ 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()
} else if let Some(ref addr) = addr {
format!("{}", addr)
} else {
"localhost".to_owned()
};
let cpu_pool = LazyCell::new(); let cpu_pool = LazyCell::new();
let responses = HttpResponsePool::get_pool(); let responses = HttpResponsePool::get_pool();
ServerSettings { ServerSettings {
@@ -93,23 +90,8 @@ impl ServerSettings {
} }
} }
pub(crate) fn parts(&self) -> (Option<net::SocketAddr>, String, bool) {
(self.addr, self.host.clone(), self.secure)
}
pub(crate) fn from_parts(parts: (Option<net::SocketAddr>, String, bool)) -> Self {
let (addr, host, secure) = parts;
ServerSettings {
addr,
host,
secure,
cpu_pool: LazyCell::new(),
responses: HttpResponsePool::get_pool(),
}
}
/// Returns the socket address of the local half of this TCP connection /// Returns the socket address of the local half of this TCP connection
pub fn local_addr(&self) -> Option<net::SocketAddr> { pub fn local_addr(&self) -> net::SocketAddr {
self.addr self.addr
} }
@@ -144,105 +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: &'static RequestPool, messages: &'static RequestPool,
channels: Cell<usize>, date: Cell<Option<Date>>,
node: RefCell<Node<()>>,
date: UnsafeCell<Date>,
} }
impl<H> WorkerSettings<H> { impl<H> Clone for ServiceConfig<H> {
fn clone(&self) -> Self {
ServiceConfig(self.0.clone())
}
}
impl<H> ServiceConfig<H> {
/// Create instance of `ServiceConfig`
pub(crate) fn new( pub(crate) fn new(
h: Vec<H>, keep_alive: KeepAlive, settings: ServerSettings, handler: H, keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64,
) -> WorkerSettings<H> { 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 {
h: RefCell::new(h), handler,
bytes: Rc::new(SharedBytesPool::new()),
messages: RequestPool::pool(settings),
channels: Cell::new(0),
node: RefCell::new(Node::head()),
date: UnsafeCell::new(Date::new()),
keep_alive, keep_alive,
ka_enabled, ka_enabled,
} client_timeout,
client_shutdown,
bytes: Rc::new(SharedBytesPool::new()),
messages: RequestPool::pool(settings),
date: Cell::new(None),
}))
} }
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) -> RefMut<Node<()>> { pub(crate) fn handler(&self) -> &H {
self.node.borrow_mut() &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_bytes(&self) -> BytesMut { pub(crate) fn get_bytes(&self) -> BytesMut {
self.bytes.get_bytes() self.0.bytes.get_bytes()
} }
pub fn release_bytes(&self, bytes: BytesMut) { pub(crate) fn release_bytes(&self, bytes: BytesMut) {
self.bytes.release_bytes(bytes) self.0.bytes.release_bytes(bytes)
} }
pub fn get_request(&self) -> Request { pub(crate) fn get_request(&self) -> Request {
RequestPool::get(self.messages) RequestPool::get(self.0.messages)
}
pub fn add_channel(&self) {
self.channels.set(self.channels.get() + 1);
}
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: WorkerSetting is !Sync and !Send
unsafe { &mut *self.date.get() }.update();
}
pub fn set_date(&self, dst: &mut BytesMut, full: bool) {
// Unsafe: WorkerSetting is !Sync and !Send
let date_bytes = unsafe { &(*self.date.get()).bytes };
if full {
let mut buf: [u8; 39] = [0; 39];
buf[..6].copy_from_slice(b"date: ");
buf[6..35].copy_from_slice(date_bytes);
buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf);
} else {
dst.extend_from_slice(date_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,
} }
@@ -250,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,
}; };
@@ -258,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();
} }
} }
@@ -299,6 +472,8 @@ impl SharedBytesPool {
#[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() {
@@ -307,15 +482,22 @@ mod tests {
#[test] #[test]
fn test_date() { fn test_date() {
let settings = WorkerSettings::<()>::new( let mut rt = current_thread::Runtime::new().unwrap();
Vec::new(),
KeepAlive::Os, let _ = rt.block_on(future::lazy(|| {
ServerSettings::default(), let settings = ServiceConfig::<()>::new(
); (),
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); KeepAlive::Os,
settings.set_date(&mut buf1, true); 0,
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); 0,
settings.set_date(&mut buf2, true); ServerSettings::default(),
assert_eq!(buf1, buf2); );
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,822 +0,0 @@
use std::rc::Rc;
use std::sync::{mpsc as sync_mpsc, Arc};
use std::time::Duration;
use std::{io, net};
use actix::{
fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler,
Response, StreamHandler, System, WrapFuture,
};
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};
#[cfg(feature = "rust-tls")]
use rustls::ServerConfig;
use super::accept::{start_accept_thread, Command};
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};
#[cfg(feature = "alpn")]
fn configure_alpn(builder: &mut SslAcceptorBuilder) -> io::Result<()> {
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";
if protos.windows(3).any(|window| window == H2) {
Ok(b"h2")
} else {
Err(AlpnError::NOACK)
}
});
Ok(())
}
/// An HTTP Server
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<Worker<H::Handler>>)>,
sockets: Vec<Socket>,
accept: Option<(
mio::SetReadiness,
sync_mpsc::Sender<Command>,
Slab<SocketInfo>,
)>,
exit: bool,
shutdown_timeout: u16,
signals: Option<Addr<signal::ProcessSignals>>,
no_http2: bool,
no_signals: bool,
}
pub(crate) enum ServerCommand {
WorkerDied(usize),
}
impl<H> Actor for HttpServer<H>
where
H: IntoHttpHandler,
{
type Context = Context<Self>;
}
pub(crate) struct Socket {
pub lst: net::TcpListener,
pub addr: net::SocketAddr,
pub 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: None,
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
}
/// Stop actix system.
///
/// `SystemExit` message stops currently running system.
pub fn system_exit(mut self) -> Self {
self.exit = true;
self
}
/// Set alternative address for `ProcessSignals` actor.
pub fn signals(mut self, addr: Addr<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()
}
/// Get addresses of bound sockets and the scheme for it.
///
/// This is useful when the server is bound from different sources
/// with some sockets listening on http and some listening on https
/// and the user should be presented with an enumeration of which
/// socket requires which protocol.
pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> {
self.sockets
.iter()
.map(|s| (s.addr, s.tp.scheme()))
.collect()
}
/// Use listener for accepting incoming connection requests
///
/// 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
}
#[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(mut self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self {
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Tls(acceptor.clone()),
});
self
}
#[cfg(feature = "alpn")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_ssl(
mut self, lst: net::TcpListener, mut builder: SslAcceptorBuilder,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
configure_alpn(&mut builder)?;
}
let acceptor = builder.build();
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Alpn(acceptor.clone()),
});
Ok(self)
}
#[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(
mut self, lst: net::TcpListener, mut builder: ServerConfig,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
builder.set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]);
}
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Rustls(Arc::new(builder)),
});
Ok(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 bind multiple addresses this method can be called multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets);
Ok(self)
}
#[cfg(feature = "tls")]
/// The ssl socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind_tls<S: net::ToSocketAddrs>(
mut self, addr: S, acceptor: TlsAcceptor,
) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(|mut s| {
s.tp = StreamHandlerType::Tls(acceptor.clone());
s
}));
Ok(self)
}
#[cfg(feature = "alpn")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_ssl<S: net::ToSocketAddrs>(
mut self, addr: S, mut builder: SslAcceptorBuilder,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
configure_alpn(&mut builder)?;
}
let acceptor = builder.build();
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(|mut s| {
s.tp = StreamHandlerType::Alpn(acceptor.clone());
s
}));
Ok(self)
}
#[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>(
mut self, addr: S, mut builder: ServerConfig,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
builder.set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]);
}
let builder = Arc::new(builder);
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(move |mut s| {
s.tp = StreamHandlerType::Rustls(builder.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 (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>();
let ka = self.keep_alive;
let socks = sockets.clone();
let factory = Arc::clone(&self.factory);
let parts = settings.parts();
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
let s = ServerSettings::from_parts(parts);
let apps: Vec<_> =
(*factory)().into_iter().map(|h| h.into_handler()).collect();
ctx.add_message_stream(rx);
Worker::new(apps, socks, ka, s)
});
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<signal::ProcessSignals>> {
if !self.no_signals {
if let Some(ref signals) = self.signals {
Some(signals.clone())
} else {
Some(System::current().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_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<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 accept thread
for (_, sock) in &addrs {
info!("Starting server on http://{}", sock.addr);
}
let (r, cmd) = start_accept_thread(addrs, tx.clone(), workers.clone());
self.accept = Some((r, cmd, socks));
// start http server actor
let signals = self.subscribe_to_signals();
let addr = 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_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();
}
}
#[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<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<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, S>(mut self, stream: S, secure: bool) -> Addr<Self>
where
S: Stream<Item = T, Error = io::Error> + Send + 'static,
T: AsyncRead + AsyncWrite + Send + '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())
.collect();
self.h = Some(Rc::new(WorkerSettings::new(
apps,
self.keep_alive,
settings,
)));
// start server
let signals = self.subscribe_to_signals();
let addr = 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 stop actix system
/// 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) => {
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 host = self.host.clone();
let socks = self.accept.as_ref().unwrap().2.clone();
let addr = socks[0].addr;
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
let settings = ServerSettings::new(Some(addr), &host, false);
let apps: Vec<_> =
(*factory)().into_iter().map(|h| h.into_handler()).collect();
ctx.add_message_stream(rx);
Worker::new(apps, socks, ka, settings)
});
if let Some(ref item) = &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::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 = 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();
ctx.spawn(
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), |_, _| {
System::current().stop();
});
}
}
fut::ok(())
}),
);
}
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), |_, _| {
System::current().stop();
});
}
Response::reply(Ok(()))
}
}
}
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)?)
}

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)
}
}

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

@@ -0,0 +1,99 @@
use std::net::{Shutdown, SocketAddr};
use std::rc::Rc;
use std::{io, time};
use actix_net::ssl;
use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_openssl::SslStream;
use extensions::Extensions;
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)
}
fn extensions(&self) -> Option<Rc<Extensions>> {
if let Some(x509) = self.get_ref().ssl().peer_certificate() {
let mut extensions = Extensions::new();
extensions.insert(x509);
Some(Rc::new(extensions))
} else {
None
}
}
}

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,293 +0,0 @@
use futures::sync::oneshot;
use futures::Future;
use net2::TcpStreamExt;
use slab::Slab;
use std::rc::Rc;
use std::{net, time};
use tokio::executor::current_thread;
use tokio_reactor::Handle;
use tokio_tcp::TcpStream;
#[cfg(any(feature = "tls", feature = "alpn", feature = "rust-tls"))]
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;
#[cfg(feature = "rust-tls")]
use rustls::{ServerConfig, Session};
#[cfg(feature = "rust-tls")]
use std::sync::Arc;
#[cfg(feature = "rust-tls")]
use tokio_rustls::ServerConfigExt;
use actix::msgs::StopArbiter;
use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response};
use server::channel::HttpChannel;
use server::settings::{ServerSettings, 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>>,
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,
settings: ServerSettings,
) -> 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, settings)),
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::current().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::current().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), 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),
#[cfg(feature = "rust-tls")]
Rustls(Arc<ServerConfig>),
}
impl StreamHandlerType {
fn handle<H: HttpHandler>(
&mut self, h: Rc<WorkerSettings<H>>, msg: Conn<net::TcpStream>,
) {
match *self {
StreamHandlerType::Normal => {
let _ = msg.io.set_nodelay(true);
let io = TcpStream::from_std(msg.io, &Handle::default())
.expect("failed to associate TCP stream");
current_thread::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_std(io, &Handle::default())
.expect("failed to associate TCP stream");
current_thread::spawn(TlsAcceptorExt::accept_async(acceptor, io).then(
move |res| {
match res {
Ok(io) => current_thread::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_std(io, &Handle::default())
.expect("failed to associate TCP stream");
current_thread::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
};
current_thread::spawn(HttpChannel::new(
h, io, peer, http2,
));
}
Err(err) => {
trace!("Error during handling tls connection: {}", err)
}
};
future::result(Ok(()))
},
));
}
#[cfg(feature = "rust-tls")]
StreamHandlerType::Rustls(ref acceptor) => {
let Conn { io, peer, .. } = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_std(io, &Handle::default())
.expect("failed to associate TCP stream");
current_thread::spawn(ServerConfigExt::accept_async(acceptor, io).then(
move |res| {
match res {
Ok(io) => {
let http2 = if let Some(p) =
io.get_ref().1.get_alpn_protocol()
{
p.len() == 2 && &p == &"h2"
} else {
false
};
current_thread::spawn(HttpChannel::new(
h, io, peer, http2,
));
}
Err(err) => {
trace!("Error during handling tls connection: {}", err)
}
};
future::result(Ok(()))
},
));
}
}
}
pub(crate) fn scheme(&self) -> &'static str {
match *self {
StreamHandlerType::Normal => "http",
#[cfg(feature = "tls")]
StreamHandlerType::Tls(_) => "https",
#[cfg(feature = "alpn")]
StreamHandlerType::Alpn(_) => "https",
#[cfg(feature = "rust-tls")]
StreamHandlerType::Rustls(_) => "https",
}
}
}

View File

@@ -4,8 +4,10 @@ use std::str::FromStr;
use std::sync::mpsc; use std::sync::mpsc;
use std::{net, thread}; use std::{net, thread};
use actix_inner::{Actor, Addr, System}; use actix::{Actor, Addr, System};
use actix::actors::signal;
use actix_net::server::Server;
use cookie::Cookie; use cookie::Cookie;
use futures::Future; use futures::Future;
use http::header::HeaderName; use http::header::HeaderName;
@@ -13,16 +15,16 @@ use http::{HeaderMap, HttpTryFrom, Method, Uri, Version};
use net2::TcpBuilder; use net2::TcpBuilder;
use tokio::runtime::current_thread::Runtime; use tokio::runtime::current_thread::Runtime;
#[cfg(feature = "alpn")] #[cfg(any(feature = "alpn", feature = "ssl"))]
use openssl::ssl::SslAcceptorBuilder; use openssl::ssl::SslAcceptorBuilder;
#[cfg(all(feature = "rust-tls"))] #[cfg(feature = "rust-tls")]
use rustls::ServerConfig; 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;
@@ -66,6 +68,7 @@ pub struct TestServer {
ssl: bool, ssl: bool,
conn: Addr<ClientConnector>, conn: Addr<ClientConnector>,
rt: Runtime, rt: Runtime,
backend: Addr<Server>,
} }
impl TestServer { impl TestServer {
@@ -75,13 +78,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,19 +93,18 @@ 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();
@@ -113,28 +115,30 @@ impl TestServer {
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();
HttpServer::new(factory) let srv = HttpServer::new(factory)
.disable_signals() .disable_signals()
.listen(tcp) .listen(tcp)
.keep_alive(5)
.start(); .start();
tx.send((System::current(), local_addr, TestServer::get_conn())) tx.send((System::current(), local_addr, TestServer::get_conn(), srv))
.unwrap(); .unwrap();
sys.run(); sys.run();
}); });
let (system, addr, conn) = rx.recv().unwrap(); let (system, addr, conn, backend) = rx.recv().unwrap();
System::set_current(system); System::set_current(system);
TestServer { TestServer {
addr, addr,
conn, conn,
ssl: false, ssl: false,
rt: Runtime::new().unwrap(), rt: Runtime::new().unwrap(),
backend,
} }
} }
fn get_conn() -> Addr<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};
@@ -142,7 +146,10 @@ 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(all(feature = "rust-tls", not(feature = "alpn")))] #[cfg(all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "ssl"))
))]
{ {
use rustls::ClientConfig; use rustls::ClientConfig;
use std::fs::File; use std::fs::File;
@@ -152,7 +159,7 @@ impl TestServer {
config.root_store.add_pem_file(pem_file).unwrap(); config.root_store.add_pem_file(pem_file).unwrap();
ClientConnector::with_connector(config).start() ClientConnector::with_connector(config).start()
} }
#[cfg(not(any(feature = "alpn", feature = "rust-tls")))] #[cfg(not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")))]
{ {
ClientConnector::default().start() ClientConnector::default().start()
} }
@@ -194,6 +201,7 @@ impl TestServer {
/// Stop http server /// Stop http server
fn stop(&mut self) { fn stop(&mut self) {
let _ = self.backend.send(signal::Signal(signal::SignalType::Term)).wait();
System::current().stop(); System::current().stop();
} }
@@ -231,6 +239,11 @@ impl TestServer {
ClientRequest::post(self.url("/").as_str()) ClientRequest::post(self.url("/").as_str())
} }
/// Create `PATCH` request
pub fn patch(&self) -> ClientRequestBuilder {
ClientRequest::patch(self.url("/").as_str())
}
/// Create `HEAD` request /// Create `HEAD` request
pub fn head(&self) -> ClientRequestBuilder { pub fn head(&self) -> ClientRequestBuilder {
ClientRequest::head(self.url("/").as_str()) ClientRequest::head(self.url("/").as_str())
@@ -256,30 +269,33 @@ 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,
{
state: F,
#[cfg(any(feature = "alpn", feature = "ssl"))]
ssl: Option<SslAcceptorBuilder>, ssl: Option<SslAcceptorBuilder>,
#[cfg(feature = "rust-tls")] #[cfg(feature = "rust-tls")]
rust_ssl: Option<ServerConfig>, rust_ssl: Option<ServerConfig>,
} }
impl<S: 'static> TestServerBuilder<S> { impl<S: 'static, F> TestServerBuilder<S, F>
where
F: Fn() -> S + Send + Clone + 'static,
{
/// Create a new test server /// Create a new test server
pub fn new<F>(state: F) -> TestServerBuilder<S> pub fn new(state: F) -> TestServerBuilder<S, F> {
where
F: Fn() -> S + Sync + Send + 'static,
{
TestServerBuilder { TestServerBuilder {
state: Box::new(state), state,
#[cfg(feature = "alpn")] #[cfg(any(feature = "alpn", feature = "ssl"))]
ssl: None, ssl: None,
#[cfg(feature = "rust-tls")] #[cfg(feature = "rust-tls")]
rust_ssl: None, rust_ssl: None,
} }
} }
#[cfg(feature = "alpn")] #[cfg(any(feature = "alpn", feature = "ssl"))]
/// Create ssl server /// Create ssl server
pub fn ssl(mut self, ssl: SslAcceptorBuilder) -> Self { pub fn ssl(mut self, ssl: SslAcceptorBuilder) -> Self {
self.ssl = Some(ssl); self.ssl = Some(ssl);
@@ -295,15 +311,15 @@ impl<S: 'static> TestServerBuilder<S> {
#[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();
let mut has_ssl = false; let mut has_ssl = false;
#[cfg(feature = "alpn")] #[cfg(any(feature = "alpn", feature = "ssl"))]
{ {
has_ssl = has_ssl || self.ssl.is_some(); has_ssl = has_ssl || self.ssl.is_some();
} }
@@ -322,14 +338,14 @@ impl<S: 'static> TestServerBuilder<S> {
let mut srv = HttpServer::new(move || { let mut 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
}).workers(1) }).workers(1)
.disable_signals(); .keep_alive(5)
.disable_signals();
tx.send((System::current(), addr, TestServer::get_conn()))
.unwrap();
#[cfg(feature = "alpn")]
#[cfg(any(feature = "alpn", feature = "ssl"))]
{ {
let ssl = self.ssl.take(); let ssl = self.ssl.take();
if let Some(ssl) = ssl { if let Some(ssl) = ssl {
@@ -342,25 +358,29 @@ impl<S: 'static> TestServerBuilder<S> {
let ssl = self.rust_ssl.take(); let ssl = self.rust_ssl.take();
if let Some(ssl) = ssl { if let Some(ssl) = ssl {
let tcp = net::TcpListener::bind(addr).unwrap(); let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen_rustls(tcp, ssl).unwrap(); srv = srv.listen_rustls(tcp, ssl);
} }
} }
if !has_ssl { if !has_ssl {
let tcp = net::TcpListener::bind(addr).unwrap(); let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen(tcp); srv = srv.listen(tcp);
} }
srv.start(); let backend = srv.start();
tx.send((System::current(), addr, TestServer::get_conn(), backend))
.unwrap();
sys.run(); sys.run();
}); });
let (system, addr, conn) = rx.recv().unwrap(); let (system, addr, conn, backend) = rx.recv().unwrap();
System::set_current(system); System::set_current(system);
TestServer { TestServer {
addr, addr,
conn, conn,
ssl: has_ssl, ssl: has_ssl,
rt: Runtime::new().unwrap(), rt: Runtime::new().unwrap(),
backend,
} }
} }
} }
@@ -500,6 +520,11 @@ impl TestRequest<()> {
{ {
TestRequest::default().header(key, value) TestRequest::default().header(key, value)
} }
/// Create TestRequest and set request cookie
pub fn with_cookie(cookie: Cookie<'static>) -> TestRequest<()> {
TestRequest::default().cookie(cookie)
}
} }
impl<S: 'static> TestRequest<S> { impl<S: 'static> TestRequest<S> {
@@ -536,6 +561,25 @@ impl<S: 'static> TestRequest<S> {
self self
} }
/// set cookie of this request
pub fn cookie(mut self, cookie: Cookie<'static>) -> Self {
if self.cookies.is_some() {
let mut should_insert = true;
let old_cookies = self.cookies.as_mut().unwrap();
for old_cookie in old_cookies.iter() {
if old_cookie == &cookie {
should_insert = false
};
};
if should_insert {
old_cookies.push(cookie);
};
} else {
self.cookies = Some(vec![cookie]);
};
self
}
/// Set a header /// Set a header
pub fn set<H: Header>(mut self, hdr: H) -> Self { pub fn set<H: Header>(mut self, hdr: H) -> Self {
if let Ok(value) = hdr.try_into() { if let Ok(value) = hdr.try_into() {
@@ -674,8 +718,6 @@ impl<S: 'static> TestRequest<S> {
/// This method generates `HttpRequest` instance and runs handler /// This method generates `HttpRequest` instance and runs handler
/// with generated request. /// with generated request.
///
/// This method panics is handler returns actor or async result.
pub fn run<H: Handler<S>>(self, h: &H) -> Result<HttpResponse, Error> { pub fn run<H: Handler<S>>(self, h: &H) -> Result<HttpResponse, Error> {
let req = self.finish(); let req = self.finish();
let resp = h.handle(&req); let resp = h.handle(&req);
@@ -684,7 +726,10 @@ impl<S: 'static> TestRequest<S> {
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()),
} }
@@ -704,8 +749,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 = Runtime::new().unwrap(); let mut sys = System::new("test");
match core.block_on(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),
@@ -716,4 +761,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,25 +1,12 @@
use http::Uri; use http::Uri;
use std::rc::Rc; use std::rc::Rc;
#[allow(dead_code)] // https://tools.ietf.org/html/rfc3986#section-2.2
const GEN_DELIMS: &[u8] = b":/?#[]@"; const RESERVED_PLUS_EXTRA: &[u8] = b":/?#[]@!$&'()*,+?;=%^ <>\"\\`{}|";
#[allow(dead_code)]
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; // https://tools.ietf.org/html/rfc3986#section-2.3
#[allow(dead_code)] const UNRESERVED: &[u8] =
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-._~";
#[allow(dead_code)]
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
#[allow(dead_code)]
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
-._~";
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
-._~
!$'()*,";
const QS: &[u8] = b"+&=;b";
#[inline] #[inline]
fn bit_at(array: &[u8], ch: u8) -> bool { fn bit_at(array: &[u8], ch: u8) -> bool {
@@ -32,7 +19,8 @@ fn set_bit(array: &mut [u8], ch: u8) {
} }
lazy_static! { lazy_static! {
static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; static ref UNRESERVED_QUOTER: Quoter = { Quoter::new(UNRESERVED) };
pub(crate) static ref RESERVED_QUOTER: Quoter = { Quoter::new(RESERVED_PLUS_EXTRA) };
} }
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
@@ -43,7 +31,7 @@ pub(crate) struct Url {
impl Url { impl Url {
pub fn new(uri: Uri) -> Url { pub fn new(uri: Uri) -> Url {
let path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); let path = UNRESERVED_QUOTER.requote(uri.path().as_bytes());
Url { uri, path } Url { uri, path }
} }
@@ -63,36 +51,19 @@ impl Url {
pub(crate) struct Quoter { pub(crate) struct Quoter {
safe_table: [u8; 16], safe_table: [u8; 16],
protected_table: [u8; 16],
} }
impl Quoter { impl Quoter {
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { pub fn new(safe: &[u8]) -> Quoter {
let mut q = Quoter { let mut q = Quoter {
safe_table: [0; 16], safe_table: [0; 16],
protected_table: [0; 16],
}; };
// prepare safe table // prepare safe table
for i in 0..128 {
if ALLOWED.contains(&i) {
set_bit(&mut q.safe_table, i);
}
if QS.contains(&i) {
set_bit(&mut q.safe_table, i);
}
}
for ch in safe { for ch in safe {
set_bit(&mut q.safe_table, *ch) set_bit(&mut q.safe_table, *ch)
} }
// prepare protected table
for ch in protected {
set_bit(&mut q.safe_table, *ch);
set_bit(&mut q.protected_table, *ch);
}
q q
} }
@@ -115,19 +86,17 @@ impl Quoter {
if let Some(ch) = restore_ch(pct[1], pct[2]) { if let Some(ch) = restore_ch(pct[1], pct[2]) {
if ch < 128 { if ch < 128 {
if bit_at(&self.protected_table, ch) {
buf.extend_from_slice(&pct);
idx += 1;
continue;
}
if bit_at(&self.safe_table, ch) { if bit_at(&self.safe_table, ch) {
buf.push(ch); buf.push(ch);
idx += 1; idx += 1;
continue; continue;
} }
buf.extend_from_slice(&pct);
} else {
// Not ASCII, decode it
buf.push(ch);
} }
buf.push(ch);
} else { } else {
buf.extend_from_slice(&pct[..]); buf.extend_from_slice(&pct[..]);
} }
@@ -148,7 +117,7 @@ impl Quoter {
if let Some(data) = cloned { if let Some(data) = cloned {
// Unsafe: we get data from http::Uri, which does utf-8 checks already // Unsafe: we get data from http::Uri, which does utf-8 checks already
// this code only decodes valid pct encoded values // this code only decodes valid pct encoded values
Some(unsafe { Rc::new(String::from_utf8_unchecked(data)) }) Some(Rc::new(unsafe { String::from_utf8_unchecked(data) }))
} else { } else {
None None
} }
@@ -172,3 +141,37 @@ fn from_hex(v: u8) -> Option<u8> {
fn restore_ch(d1: u8, d2: u8) -> Option<u8> { fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2))) from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2)))
} }
#[cfg(test)]
mod tests {
use std::rc::Rc;
use super::*;
#[test]
fn decode_path() {
assert_eq!(UNRESERVED_QUOTER.requote(b"https://localhost:80/foo"), None);
assert_eq!(
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
b"https://localhost:80/foo%25"
).unwrap()).unwrap(),
"https://localhost:80/foo%25".to_string()
);
assert_eq!(
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
b"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo"
).unwrap()).unwrap(),
"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo".to_string()
);
assert_eq!(
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
b"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A"
).unwrap()).unwrap(),
"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A".to_string()
);
}
}

View File

@@ -7,24 +7,57 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
pub(crate) struct With<T, S, F, R> trait FnWith<T, R>: 'static {
fn call_with(self: &Self, T) -> R;
}
impl<T, R, F: Fn(T) -> R + 'static> FnWith<T, R> for F {
fn call_with(self: &Self, arg: T) -> R {
(*self)(arg)
}
}
#[doc(hidden)]
pub trait WithFactory<T, S, R>: 'static
where
T: FromRequest<S>,
R: Responder,
{
fn create(self) -> With<T, S, R>;
fn create_with_config(self, T::Config) -> With<T, S, R>;
}
#[doc(hidden)]
pub trait WithAsyncFactory<T, S, R, I, E>: 'static
where
T: FromRequest<S>,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<Error>,
{
fn create(self) -> WithAsync<T, S, R, I, E>;
fn create_with_config(self, T::Config) -> WithAsync<T, S, R, I, E>;
}
#[doc(hidden)]
pub struct With<T, S, R>
where where
F: Fn(T) -> R,
T: FromRequest<S>, T: FromRequest<S>,
S: 'static, S: 'static,
{ {
hnd: Rc<F>, hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>, 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: T::Config) -> Self { pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
With { With {
cfg: Rc::new(cfg), cfg: Rc::new(cfg),
hnd: Rc::new(f), hnd: Rc::new(f),
@@ -33,9 +66,8 @@ where
} }
} }
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,
@@ -54,30 +86,28 @@ where
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::future(Box::new(fut)),
Err(e) => AsyncResult::err(e), Err(e) => AsyncResult::err(e),
} }
} }
} }
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<F>, hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>, 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,
@@ -108,7 +138,7 @@ where
} }
}; };
let item = match (*self.hnd)(item).respond_to(&self.req) { let item = match self.hnd.as_ref().call_with(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()),
}; };
@@ -124,30 +154,29 @@ where
} }
} }
pub(crate) struct WithAsync<T, S, F, R, I, E> #[doc(hidden)]
pub struct WithAsync<T, S, R, I, E>
where where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>, R: Future<Item = I, Error = E>,
I: Responder, I: Responder,
E: Into<E>, E: Into<E>,
T: FromRequest<S>, T: FromRequest<S>,
S: 'static, S: 'static,
{ {
hnd: Rc<F>, hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>, cfg: Rc<T::Config>,
_s: PhantomData<S>, _s: PhantomData<S>,
} }
impl<T, S, F, R, I, E> WithAsync<T, S, F, R, I, E> impl<T, S, R, I, E> WithAsync<T, S, R, I, E>
where where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>, R: Future<Item = I, Error = E>,
I: Responder, I: Responder,
E: Into<Error>, E: Into<Error>,
T: FromRequest<S>, T: FromRequest<S>,
S: 'static, S: 'static,
{ {
pub fn new(f: F, cfg: T::Config) -> Self { pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
WithAsync { WithAsync {
cfg: Rc::new(cfg), cfg: Rc::new(cfg),
hnd: Rc::new(f), hnd: Rc::new(f),
@@ -156,9 +185,8 @@ where
} }
} }
impl<T, S, F, R, I, E> Handler<S> for WithAsync<T, S, F, R, I, E> impl<T, S, R, I, E> Handler<S> for WithAsync<T, S, R, I, E>
where where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static, R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static, I: Responder + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
@@ -180,15 +208,14 @@ where
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::future(Box::new(fut)),
Err(e) => AsyncResult::err(e), Err(e) => AsyncResult::err(e),
} }
} }
} }
struct WithAsyncHandlerFut<T, S, F, R, I, E> struct WithAsyncHandlerFut<T, S, R, I, E>
where where
F: Fn(T) -> R,
R: Future<Item = I, Error = E> + 'static, R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static, I: Responder + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
@@ -196,7 +223,7 @@ where
S: 'static, S: 'static,
{ {
started: bool, started: bool,
hnd: Rc<F>, hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>, 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>>>,
@@ -204,9 +231,8 @@ where
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>, fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
} }
impl<T, S, F, R, I, E> Future for WithAsyncHandlerFut<T, S, F, R, I, E> impl<T, S, R, I, E> Future for WithAsyncHandlerFut<T, S, R, I, E>
where where
F: Fn(T) -> R,
R: Future<Item = I, Error = E> + 'static, R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static, I: Responder + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
@@ -257,7 +283,101 @@ where
} }
}; };
self.fut2 = Some((*self.hnd)(item)); self.fut2 = Some(self.hnd.as_ref().call_with(item));
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

@@ -231,6 +231,13 @@ where
pub fn handle(&self) -> SpawnHandle { pub fn handle(&self) -> SpawnHandle {
self.inner.curr_handle() self.inner.curr_handle()
} }
/// Set mailbox capacity
///
/// By default mailbox capacity is 16 messages.
pub fn set_mailbox_capacity(&mut self, cap: usize) {
self.inner.set_mailbox_capacity(cap)
}
} }
impl<A, S> WsWriter for WebsocketContext<A, S> impl<A, S> WsWriter for WebsocketContext<A, S>

View File

@@ -50,7 +50,10 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
// 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 understand that // inefficient, it could be done better. The compiler does not understand that
// a `ShortSlice` must be smaller than a u64. // a `ShortSlice` must be smaller than a u64.
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] #[cfg_attr(
feature = "cargo-clippy",
allow(needless_pass_by_value)
)]
fn xor_short(buf: ShortSlice, mask: u64) { fn xor_short(buf: ShortSlice, mask: u64) {
// Unsafe: we know that a `ShortSlice` fits in a u64 // Unsafe: we know that a `ShortSlice` fits in a u64
unsafe { unsafe {

View File

@@ -8,7 +8,8 @@
//! //!
//! ```rust //! ```rust
//! # extern crate actix_web; //! # extern crate actix_web;
//! # use actix_web::actix::*; //! # extern crate actix;
//! # use actix::prelude::*;
//! # use actix_web::*; //! # use actix_web::*;
//! use actix_web::{ws, HttpRequest, HttpResponse}; //! use actix_web::{ws, HttpRequest, HttpResponse};
//! //!
@@ -387,8 +388,7 @@ mod tests {
.header( .header(
header::UPGRADE, header::UPGRADE,
header::HeaderValue::from_static("websocket"), header::HeaderValue::from_static("websocket"),
) ).finish();
.finish();
assert_eq!( assert_eq!(
HandshakeError::NoConnectionUpgrade, HandshakeError::NoConnectionUpgrade,
handshake(&req).err().unwrap() handshake(&req).err().unwrap()
@@ -398,12 +398,10 @@ mod tests {
.header( .header(
header::UPGRADE, header::UPGRADE,
header::HeaderValue::from_static("websocket"), header::HeaderValue::from_static("websocket"),
) ).header(
.header(
header::CONNECTION, header::CONNECTION,
header::HeaderValue::from_static("upgrade"), header::HeaderValue::from_static("upgrade"),
) ).finish();
.finish();
assert_eq!( assert_eq!(
HandshakeError::NoVersionHeader, HandshakeError::NoVersionHeader,
handshake(&req).err().unwrap() handshake(&req).err().unwrap()
@@ -413,16 +411,13 @@ mod tests {
.header( .header(
header::UPGRADE, header::UPGRADE,
header::HeaderValue::from_static("websocket"), header::HeaderValue::from_static("websocket"),
) ).header(
.header(
header::CONNECTION, header::CONNECTION,
header::HeaderValue::from_static("upgrade"), header::HeaderValue::from_static("upgrade"),
) ).header(
.header(
header::SEC_WEBSOCKET_VERSION, header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("5"), header::HeaderValue::from_static("5"),
) ).finish();
.finish();
assert_eq!( assert_eq!(
HandshakeError::UnsupportedVersion, HandshakeError::UnsupportedVersion,
handshake(&req).err().unwrap() handshake(&req).err().unwrap()
@@ -432,16 +427,13 @@ mod tests {
.header( .header(
header::UPGRADE, header::UPGRADE,
header::HeaderValue::from_static("websocket"), header::HeaderValue::from_static("websocket"),
) ).header(
.header(
header::CONNECTION, header::CONNECTION,
header::HeaderValue::from_static("upgrade"), header::HeaderValue::from_static("upgrade"),
) ).header(
.header(
header::SEC_WEBSOCKET_VERSION, header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"), header::HeaderValue::from_static("13"),
) ).finish();
.finish();
assert_eq!( assert_eq!(
HandshakeError::BadWebsocketKey, HandshakeError::BadWebsocketKey,
handshake(&req).err().unwrap() handshake(&req).err().unwrap()
@@ -451,20 +443,16 @@ mod tests {
.header( .header(
header::UPGRADE, header::UPGRADE,
header::HeaderValue::from_static("websocket"), header::HeaderValue::from_static("websocket"),
) ).header(
.header(
header::CONNECTION, header::CONNECTION,
header::HeaderValue::from_static("upgrade"), header::HeaderValue::from_static("upgrade"),
) ).header(
.header(
header::SEC_WEBSOCKET_VERSION, header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"), header::HeaderValue::from_static("13"),
) ).header(
.header(
header::SEC_WEBSOCKET_KEY, header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"), header::HeaderValue::from_static("13"),
) ).finish();
.finish();
assert_eq!( assert_eq!(
StatusCode::SWITCHING_PROTOCOLS, StatusCode::SWITCHING_PROTOCOLS,
handshake(&req).unwrap().finish().status() handshake(&req).unwrap().finish().status()

BIN
tests/identity.pfx Normal file

Binary file not shown.

1
tests/test space.binary Normal file
View File

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

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,6 +67,16 @@ 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| {
@@ -116,8 +129,7 @@ fn test_client_gzip_encoding() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -146,8 +158,7 @@ fn test_client_gzip_encoding_large() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -168,7 +179,7 @@ fn test_client_gzip_encoding_large() {
#[test] #[test]
fn test_client_gzip_encoding_large_random() { fn test_client_gzip_encoding_large_random() {
let data = rand::thread_rng() let data = rand::thread_rng()
.gen_ascii_chars() .sample_iter(&rand::distributions::Alphanumeric)
.take(100_000) .take(100_000)
.collect::<String>(); .collect::<String>();
@@ -179,8 +190,7 @@ fn test_client_gzip_encoding_large_random() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Deflate) .content_encoding(http::ContentEncoding::Deflate)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -198,6 +208,13 @@ 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() {
@@ -208,8 +225,7 @@ fn test_client_brotli_encoding() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -231,7 +247,7 @@ fn test_client_brotli_encoding() {
#[test] #[test]
fn test_client_brotli_encoding_large_random() { fn test_client_brotli_encoding_large_random() {
let data = rand::thread_rng() let data = rand::thread_rng()
.gen_ascii_chars() .sample_iter(&rand::distributions::Alphanumeric)
.take(70_000) .take(70_000)
.collect::<String>(); .collect::<String>();
@@ -242,8 +258,7 @@ fn test_client_brotli_encoding_large_random() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Gzip) .content_encoding(http::ContentEncoding::Gzip)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -272,8 +287,7 @@ fn test_client_deflate_encoding() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Br) .content_encoding(http::ContentEncoding::Br)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -295,7 +309,7 @@ fn test_client_deflate_encoding() {
#[test] #[test]
fn test_client_deflate_encoding_large_random() { fn test_client_deflate_encoding_large_random() {
let data = rand::thread_rng() let data = rand::thread_rng()
.gen_ascii_chars() .sample_iter(&rand::distributions::Alphanumeric)
.take(70_000) .take(70_000)
.collect::<String>(); .collect::<String>();
@@ -306,8 +320,7 @@ fn test_client_deflate_encoding_large_random() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Br) .content_encoding(http::ContentEncoding::Br)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -336,8 +349,7 @@ fn test_client_streaming_explicit() {
.chunked() .chunked()
.content_encoding(http::ContentEncoding::Identity) .content_encoding(http::ContentEncoding::Identity)
.body(body)) .body(body))
}) }).responder()
.responder()
}) })
}); });
@@ -395,24 +407,29 @@ fn test_client_cookie_handling() {
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()
})
}) })
}); });
@@ -459,3 +476,61 @@ fn test_default_headers() {
"\"" "\""
))); )));
} }
#[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!"));
}
#[test]
fn client_basic_auth() {
let mut srv =
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
/// set authorization header to Basic <base64 encoded username:password>
let request = srv
.get()
.basic_auth("username", Some("password"))
.finish()
.unwrap();
let repr = format!("{:?}", request);
assert!(repr.contains("Basic dXNlcm5hbWU6cGFzc3dvcmQ="));
}
#[test]
fn client_bearer_auth() {
let mut srv =
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
/// set authorization header to Bearer <token>
let request = srv
.get()
.bearer_auth("someS3cr3tAutht0k3n")
.finish()
.unwrap();
let repr = format!("{:?}", request);
assert!(repr.contains("Bearer someS3cr3tAutht0k3n"));
}

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());
}
}

View File

@@ -191,8 +191,7 @@ fn test_form_extractor() {
.uri(srv.url("/test1/index.html")) .uri(srv.url("/test1/index.html"))
.form(FormData { .form(FormData {
username: "test".to_string(), username: "test".to_string(),
}) }).unwrap();
.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());
@@ -208,7 +207,7 @@ fn test_form_extractor2() {
r.route().with_config( r.route().with_config(
|form: Form<FormData>| format!("{}", form.username), |form: Form<FormData>| format!("{}", form.username),
|cfg| { |cfg| {
cfg.error_handler(|err, _| { cfg.0.error_handler(|err, _| {
error::InternalError::from_response( error::InternalError::from_response(
err, err,
HttpResponse::Conflict().finish(), HttpResponse::Conflict().finish(),
@@ -306,8 +305,7 @@ fn test_path_and_query_extractor2_async() {
Delay::new(Instant::now() + Duration::from_millis(10)) Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| { .and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0)) Ok(format!("Welcome {} - {}!", p.username, data.0))
}) }).responder()
.responder()
}, },
) )
}); });
@@ -336,8 +334,7 @@ fn test_path_and_query_extractor3_async() {
Delay::new(Instant::now() + Duration::from_millis(10)) Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| { .and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0)) Ok(format!("Welcome {} - {}!", p.username, data.0))
}) }).responder()
.responder()
}) })
}); });
}); });
@@ -361,8 +358,7 @@ fn test_path_and_query_extractor4_async() {
Delay::new(Instant::now() + Duration::from_millis(10)) Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| { .and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0)) Ok(format!("Welcome {} - {}!", p.username, data.0))
}) }).responder()
.responder()
}) })
}); });
}); });
@@ -387,8 +383,7 @@ fn test_path_and_query_extractor2_async2() {
Delay::new(Instant::now() + Duration::from_millis(10)) Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| { .and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0)) Ok(format!("Welcome {} - {}!", p.username, data.0))
}) }).responder()
.responder()
}, },
) )
}); });
@@ -422,15 +417,13 @@ fn test_path_and_query_extractor2_async2() {
fn test_path_and_query_extractor2_async3() { fn test_path_and_query_extractor2_async3() {
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| { app.resource("/{username}/index.html", |r| {
r.route().with( r.route()
|(data, p, _q): (Json<Value>, Path<PParam>, Query<PParam>)| { .with(|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
Delay::new(Instant::now() + Duration::from_millis(10)) Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| { .and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0)) Ok(format!("Welcome {} - {}!", p.username, data.0))
}) }).responder()
.responder() })
},
)
}); });
}); });
@@ -467,8 +460,7 @@ fn test_path_and_query_extractor2_async4() {
Delay::new(Instant::now() + Duration::from_millis(10)) Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| { .and_then(move |_| {
Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)) Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))
}) }).responder()
.responder()
}) })
}); });
}); });
@@ -680,6 +672,6 @@ fn test_unsafe_path_route() {
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
assert_eq!( assert_eq!(
bytes, bytes,
Bytes::from_static(b"success: http:%2F%2Fexample.com") Bytes::from_static(b"success: http%3A%2F%2Fexample.com")
); );
} }

View File

@@ -84,11 +84,10 @@ fn test_middleware_multiple() {
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest { }).middleware(MiddlewareTest {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).handler(|_| HttpResponse::Ok())
.handler(|_| HttpResponse::Ok())
}); });
let request = srv.get().finish().unwrap(); let request = srv.get().finish().unwrap();
@@ -143,11 +142,10 @@ fn test_resource_middleware_multiple() {
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest { }).middleware(MiddlewareTest {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).handler(|_| HttpResponse::Ok())
.handler(|_| HttpResponse::Ok())
}); });
let request = srv.get().finish().unwrap(); let request = srv.get().finish().unwrap();
@@ -176,8 +174,7 @@ fn test_scope_middleware() {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}) })
}); });
@@ -207,13 +204,11 @@ fn test_scope_middleware_multiple() {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).middleware(MiddlewareTest {
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}) })
}); });
@@ -242,8 +237,7 @@ fn test_middleware_async_handler() {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).resource("/", |r| {
.resource("/", |r| {
r.route().a(|_| { r.route().a(|_| {
Delay::new(Instant::now() + Duration::from_millis(10)) Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(|_| Ok(HttpResponse::Ok())) .and_then(|_| Ok(HttpResponse::Ok()))
@@ -312,8 +306,7 @@ fn test_scope_middleware_async_handler() {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).resource("/test", |r| {
.resource("/test", |r| {
r.route().a(|_| { r.route().a(|_| {
Delay::new(Instant::now() + Duration::from_millis(10)) Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(|_| Ok(HttpResponse::Ok())) .and_then(|_| Ok(HttpResponse::Ok()))
@@ -379,8 +372,7 @@ fn test_scope_middleware_async_error() {
start: Arc::clone(&act_req), start: Arc::clone(&act_req),
response: Arc::clone(&act_resp), response: Arc::clone(&act_resp),
finish: Arc::clone(&act_fin), finish: Arc::clone(&act_fin),
}) }).resource("/test", |r| r.f(index_test_middleware_async_error))
.resource("/test", |r| r.f(index_test_middleware_async_error))
}) })
}); });
@@ -514,13 +506,11 @@ fn test_async_middleware_multiple() {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).middleware(MiddlewareAsyncTest {
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}); });
let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let request = srv.get().uri(srv.url("/test")).finish().unwrap();
@@ -550,13 +540,11 @@ fn test_async_sync_middleware_multiple() {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).middleware(MiddlewareTest {
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}); });
let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let request = srv.get().uri(srv.url("/test")).finish().unwrap();
@@ -587,8 +575,7 @@ fn test_async_scope_middleware() {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}) })
}); });
@@ -620,13 +607,11 @@ fn test_async_scope_middleware_multiple() {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).middleware(MiddlewareAsyncTest {
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}) })
}); });
@@ -658,13 +643,11 @@ fn test_async_async_scope_middleware_multiple() {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).middleware(MiddlewareTest {
.middleware(MiddlewareTest {
start: Arc::clone(&act_num1), start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2), response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3), finish: Arc::clone(&act_num3),
}) }).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}) })
}); });
@@ -1012,8 +995,7 @@ fn test_session_storage_middleware() {
App::new() App::new()
.middleware(SessionStorage::new( .middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false), CookieSessionBackend::signed(&[0; 32]).secure(false),
)) )).resource("/index", move |r| {
.resource("/index", move |r| {
r.f(|req| { r.f(|req| {
let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD); let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD);
assert!(res.is_ok()); assert!(res.is_ok());
@@ -1033,8 +1015,7 @@ fn test_session_storage_middleware() {
HttpResponse::Ok() HttpResponse::Ok()
}) })
}) }).resource("/expect_cookie", move |r| {
.resource("/expect_cookie", move |r| {
r.f(|req| { r.f(|req| {
let _cookies = req.cookies().expect("To get cookies"); let _cookies = req.cookies().expect("To get cookies");

View File

@@ -1,4 +1,5 @@
extern crate actix; extern crate actix;
extern crate actix_net;
extern crate actix_web; extern crate actix_web;
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
extern crate brotli2; extern crate brotli2;
@@ -9,18 +10,27 @@ extern crate h2;
extern crate http as modhttp; extern crate http as modhttp;
extern crate rand; extern crate rand;
extern crate tokio; extern crate tokio;
extern crate tokio_current_thread;
extern crate tokio_current_thread as current_thread;
extern crate tokio_reactor; extern crate tokio_reactor;
extern crate tokio_tcp; extern crate tokio_tcp;
#[cfg(feature = "tls")]
extern crate native_tls;
#[cfg(feature = "ssl")]
extern crate openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::sync::{mpsc, Arc}; use std::sync::Arc;
use std::{net, thread, time}; use std::{thread, time};
#[cfg(feature = "brotli")] #[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder}; use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder};
use flate2::Compression; use flate2::Compression;
use futures::stream::once; use futures::stream::once;
use futures::{Future, Stream}; use futures::{Future, Stream};
@@ -28,11 +38,10 @@ use h2::client as h2client;
use modhttp::Request; use modhttp::Request;
use rand::distributions::Alphanumeric; use rand::distributions::Alphanumeric;
use rand::Rng; use rand::Rng;
use tokio::executor::current_thread;
use tokio::runtime::current_thread::Runtime; use tokio::runtime::current_thread::Runtime;
use tokio_current_thread::spawn;
use tokio_tcp::TcpStream; use tokio_tcp::TcpStream;
use actix::System;
use actix_web::*; use actix_web::*;
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
@@ -60,6 +69,9 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
#[test] #[test]
#[cfg(unix)] #[cfg(unix)]
fn test_start() { fn test_start() {
use actix::System;
use std::sync::mpsc;
let _ = test::TestServer::unused_addr(); let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
@@ -117,6 +129,10 @@ fn test_start() {
#[test] #[test]
#[cfg(unix)] #[cfg(unix)]
fn test_shutdown() { fn test_shutdown() {
use actix::System;
use std::net;
use std::sync::mpsc;
let _ = test::TestServer::unused_addr(); let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
@@ -156,6 +172,9 @@ fn test_shutdown() {
#[test] #[test]
#[cfg(unix)] #[cfg(unix)]
fn test_panic() { fn test_panic() {
use actix::System;
use std::sync::mpsc;
let _ = test::TestServer::unused_addr(); let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
@@ -167,8 +186,7 @@ fn test_panic() {
r.method(http::Method::GET).f(|_| -> &'static str { r.method(http::Method::GET).f(|_| -> &'static str {
panic!("error"); panic!("error");
}); });
}) }).resource("/", |r| {
.resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok()) r.method(http::Method::GET).f(|_| HttpResponse::Ok())
}) })
}).workers(1); }).workers(1);
@@ -528,7 +546,7 @@ fn test_body_chunked_explicit() {
#[test] #[test]
fn test_body_identity() { fn test_body_identity() {
let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap(); e.write_all(STR.as_ref()).unwrap();
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
let enc2 = enc.clone(); let enc2 = enc.clone();
@@ -578,7 +596,7 @@ fn test_body_deflate() {
let bytes = srv.execute(response.body()).unwrap(); let bytes = srv.execute(response.body()).unwrap();
// decode deflate // decode deflate
let mut e = DeflateDecoder::new(Vec::new()); let mut e = ZlibDecoder::new(Vec::new());
e.write_all(bytes.as_ref()).unwrap(); e.write_all(bytes.as_ref()).unwrap();
let dec = e.finish().unwrap(); let dec = e.finish().unwrap();
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
@@ -619,8 +637,7 @@ fn test_gzip_encoding() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity) .content_encoding(http::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -652,8 +669,7 @@ fn test_gzip_encoding_large() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity) .content_encoding(http::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -689,8 +705,7 @@ fn test_reading_gzip_encoding_large_random() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity) .content_encoding(http::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -722,12 +737,11 @@ fn test_reading_deflate_encoding() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity) .content_encoding(http::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap(); e.write_all(STR.as_ref()).unwrap();
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
@@ -755,12 +769,11 @@ fn test_reading_deflate_encoding_large() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity) .content_encoding(http::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap(); e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
@@ -792,12 +805,11 @@ fn test_reading_deflate_encoding_large_random() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity) .content_encoding(http::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap(); e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap(); let enc = e.finish().unwrap();
@@ -826,8 +838,7 @@ fn test_brotli_encoding() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity) .content_encoding(http::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -860,8 +871,7 @@ fn test_brotli_encoding_large() {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity) .content_encoding(http::ContentEncoding::Identity)
.body(bytes)) .body(bytes))
}) }).responder()
.responder()
}) })
}); });
@@ -883,10 +893,214 @@ fn test_brotli_encoding_large() {
assert_eq!(bytes, Bytes::from(data)); assert_eq!(bytes, Bytes::from(data));
} }
#[cfg(all(feature = "brotli", feature = "ssl"))]
#[test]
fn test_brotli_encoding_large_ssl() {
use actix::{Actor, System};
use openssl::ssl::{
SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode,
};
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
.set_private_key_file("tests/key.pem", SslFiletype::PEM)
.unwrap();
builder
.set_certificate_chain_file("tests/cert.pem")
.unwrap();
let data = STR.repeat(10);
let srv = test::TestServer::build().ssl(builder).start(|app| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
});
let mut rt = System::new("test");
// client connector
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let conn = client::ClientConnector::with_connector(builder.build()).start();
// body
let mut e = BrotliEncoder::new(Vec::new(), 5);
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
// client request
let request = client::ClientRequest::build()
.uri(srv.url("/"))
.method(http::Method::POST)
.header(http::header::CONTENT_ENCODING, "br")
.with_connector(conn)
.body(enc)
.unwrap();
let response = rt.block_on(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = rt.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from(data));
}
#[cfg(all(feature = "rust-tls", feature = "ssl"))]
#[test]
fn test_reading_deflate_encoding_large_random_ssl() {
use actix::{Actor, System};
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
use rustls::internal::pemfile::{certs, rsa_private_keys};
use rustls::{NoClientAuth, ServerConfig};
use std::fs::File;
use std::io::BufReader;
// load ssl keys
let mut config = ServerConfig::new(NoClientAuth::new());
let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap());
let cert_chain = certs(cert_file).unwrap();
let mut keys = rsa_private_keys(key_file).unwrap();
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(160_000)
.collect::<String>();
let srv = test::TestServer::build().rustls(config).start(|app| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
});
let mut rt = System::new("test");
// client connector
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let conn = client::ClientConnector::with_connector(builder.build()).start();
// encode data
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
// client request
let request = client::ClientRequest::build()
.uri(srv.url("/"))
.method(http::Method::POST)
.header(http::header::CONTENT_ENCODING, "deflate")
.with_connector(conn)
.body(enc)
.unwrap();
let response = rt.block_on(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = rt.block_on(response.body()).unwrap();
assert_eq!(bytes.len(), data.len());
assert_eq!(bytes, Bytes::from(data));
}
#[cfg(all(feature = "tls", feature = "ssl"))]
#[test]
fn test_reading_deflate_encoding_large_random_tls() {
use native_tls::{Identity, TlsAcceptor};
use openssl::ssl::{
SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode,
};
use std::fs::File;
use std::sync::mpsc;
use actix::{Actor, System};
let (tx, rx) = mpsc::channel();
// load ssl keys
let mut file = File::open("tests/identity.pfx").unwrap();
let mut identity = vec![];
file.read_to_end(&mut identity).unwrap();
let identity = Identity::from_pkcs12(&identity, "1").unwrap();
let acceptor = TlsAcceptor::new(identity).unwrap();
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
.set_private_key_file("tests/key.pem", SslFiletype::PEM)
.unwrap();
builder
.set_certificate_chain_file("tests/cert.pem")
.unwrap();
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(160_000)
.collect::<String>();
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
System::run(move || {
server::new(|| {
App::new().handler("/", |req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
}).responder()
})
}).bind_tls(addr, acceptor)
.unwrap()
.start();
let _ = tx.send(System::current());
});
});
let sys = rx.recv().unwrap();
let mut rt = System::new("test");
// client connector
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let conn = client::ClientConnector::with_connector(builder.build()).start();
// encode data
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
// client request
let request = client::ClientRequest::build()
.uri(format!("https://{}/", addr))
.method(http::Method::POST)
.header(http::header::CONTENT_ENCODING, "deflate")
.with_connector(conn)
.body(enc)
.unwrap();
let response = rt.block_on(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = rt.block_on(response.body()).unwrap();
assert_eq!(bytes.len(), data.len());
assert_eq!(bytes, Bytes::from(data));
let _ = sys.stop();
}
#[test] #[test]
fn test_h2() { fn test_h2() {
let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let addr = srv.addr(); let addr = srv.addr();
thread::sleep(time::Duration::from_millis(500));
let mut core = Runtime::new().unwrap(); let mut core = Runtime::new().unwrap();
let tcp = TcpStream::connect(&addr); let tcp = TcpStream::connect(&addr);
@@ -903,7 +1117,7 @@ fn test_h2() {
let (response, _) = client.send_request(request, false).unwrap(); let (response, _) = client.send_request(request, false).unwrap();
// Spawn a task to run the conn... // Spawn a task to run the conn...
current_thread::spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); spawn(h2.map_err(|e| println!("GOT ERR={:?}", e)));
response.and_then(|response| { response.and_then(|response| {
assert_eq!(response.status(), http::StatusCode::OK); assert_eq!(response.status(), http::StatusCode::OK);
@@ -930,3 +1144,265 @@ fn test_application() {
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
} }
#[test]
fn test_default_404_handler_response() {
let mut srv = test::TestServer::with_factory(|| {
App::new()
.prefix("/app")
.resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
});
let addr = srv.addr();
let mut buf = [0; 24];
let request = TcpStream::connect(&addr)
.and_then(|sock| {
tokio::io::write_all(sock, "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n")
.and_then(|(sock, _)| tokio::io::read_exact(sock, &mut buf))
.and_then(|(_, buf)| Ok(buf))
}).map_err(|e| panic!("{:?}", e));
let response = srv.execute(request).unwrap();
let rep = String::from_utf8_lossy(&response[..]);
assert!(rep.contains("HTTP/1.1 404 Not Found"));
}
#[test]
fn test_server_cookies() {
use actix_web::http;
let mut srv = test::TestServer::with_factory(|| {
App::new().resource("/", |r| {
r.f(|_| {
HttpResponse::Ok()
.cookie(
http::CookieBuilder::new("first", "first_value")
.http_only(true)
.finish(),
).cookie(http::Cookie::new("second", "first_value"))
.cookie(http::Cookie::new("second", "second_value"))
.finish()
})
})
});
let first_cookie = http::CookieBuilder::new("first", "first_value")
.http_only(true)
.finish();
let second_cookie = http::Cookie::new("second", "second_value");
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
let cookies = response.cookies().expect("To have cookies");
assert_eq!(cookies.len(), 2);
if cookies[0] == first_cookie {
assert_eq!(cookies[1], second_cookie);
} else {
assert_eq!(cookies[0], second_cookie);
assert_eq!(cookies[1], first_cookie);
}
let first_cookie = first_cookie.to_string();
let second_cookie = second_cookie.to_string();
//Check that we have exactly two instances of raw cookie headers
let cookies = response
.headers()
.get_all(http::header::SET_COOKIE)
.iter()
.map(|header| header.to_str().expect("To str").to_string())
.collect::<Vec<_>>();
assert_eq!(cookies.len(), 2);
if cookies[0] == first_cookie {
assert_eq!(cookies[1], second_cookie);
} else {
assert_eq!(cookies[0], second_cookie);
assert_eq!(cookies[1], first_cookie);
}
}
#[test]
fn test_slow_request() {
use actix::System;
use std::net;
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
System::run(move || {
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
});
let srv = srv.bind(addr).unwrap();
srv.client_timeout(200).start();
let _ = tx.send(System::current());
});
});
let sys = rx.recv().unwrap();
thread::sleep(time::Duration::from_millis(200));
let mut stream = net::TcpStream::connect(addr).unwrap();
let mut data = String::new();
let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 408 Request Timeout"));
let mut stream = net::TcpStream::connect(addr).unwrap();
let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n");
let mut data = String::new();
let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 408 Request Timeout"));
sys.stop();
}
#[test]
fn test_malformed_request() {
use actix::System;
use std::net;
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
System::run(move || {
let srv = server::new(|| {
App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})
});
let _ = srv.bind(addr).unwrap().start();
let _ = tx.send(System::current());
});
});
let sys = rx.recv().unwrap();
thread::sleep(time::Duration::from_millis(200));
let mut stream = net::TcpStream::connect(addr).unwrap();
let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n");
let mut data = String::new();
let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 400 Bad Request"));
sys.stop();
}
#[test]
fn test_app_404() {
let mut srv = test::TestServer::with_factory(|| {
App::new().prefix("/prefix").resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})
});
let request = srv.client(http::Method::GET, "/prefix/").finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
let request = srv.client(http::Method::GET, "/").finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
}
#[test]
#[cfg(feature = "ssl")]
fn test_ssl_handshake_timeout() {
use actix::System;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
use std::net;
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
let addr = test::TestServer::unused_addr();
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
.set_private_key_file("tests/key.pem", SslFiletype::PEM)
.unwrap();
builder
.set_certificate_chain_file("tests/cert.pem")
.unwrap();
thread::spawn(move || {
System::run(move || {
let srv = server::new(|| {
App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})
});
srv.bind_ssl(addr, builder)
.unwrap()
.workers(1)
.client_timeout(200)
.start();
let _ = tx.send(System::current());
});
});
let sys = rx.recv().unwrap();
let mut stream = net::TcpStream::connect(addr).unwrap();
let mut data = String::new();
let _ = stream.read_to_string(&mut data);
assert!(data.is_empty());
let _ = sys.stop();
}
#[test]
fn test_content_length() {
use actix_web::http::header::{HeaderName, HeaderValue};
use http::StatusCode;
let mut srv = test::TestServer::new(move |app| {
app.resource("/{status}", |r| {
r.f(|req: &HttpRequest| {
let indx: usize =
req.match_info().get("status").unwrap().parse().unwrap();
let statuses = [
StatusCode::NO_CONTENT,
StatusCode::CONTINUE,
StatusCode::SWITCHING_PROTOCOLS,
StatusCode::PROCESSING,
StatusCode::OK,
StatusCode::NOT_FOUND,
];
HttpResponse::new(statuses[indx])
})
});
});
let addr = srv.addr();
let mut get_resp = |i| {
let url = format!("http://{}/{}", addr, i);
let req = srv.get().uri(url).finish().unwrap();
srv.execute(req.send()).unwrap()
};
let header = HeaderName::from_static("content-length");
let value = HeaderValue::from_static("0");
for i in 0..4 {
let response = get_resp(i);
assert_eq!(response.headers().get(&header), None);
}
for i in 4..6 {
let response = get_resp(i);
assert_eq!(response.headers().get(&header), Some(&value));
}
}
#[test]
fn test_patch_method() {
let mut srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok()));
let req = srv.patch().finish().unwrap();
let response = srv.execute(req.send()).unwrap();
assert!(response.status().is_success());
}

View File

@@ -5,12 +5,16 @@ extern crate futures;
extern crate http; extern crate http;
extern crate rand; extern crate rand;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::{thread, time};
use bytes::Bytes; use bytes::Bytes;
use futures::Stream; use futures::Stream;
use rand::distributions::Alphanumeric; use rand::distributions::Alphanumeric;
use rand::Rng; use rand::Rng;
#[cfg(feature = "alpn")] #[cfg(feature = "ssl")]
extern crate openssl; extern crate openssl;
#[cfg(feature = "rust-tls")] #[cfg(feature = "rust-tls")]
extern crate rustls; extern crate rustls;
@@ -71,7 +75,7 @@ fn start_ws_resource(req: &HttpRequest) -> Result<HttpResponse, Error> {
#[test] #[test]
fn test_simple_path() { fn test_simple_path() {
const PATH:&str = "/v1/ws/"; const PATH: &str = "/v1/ws/";
// Create a websocket at a specific path. // Create a websocket at a specific path.
let mut srv = test::TestServer::new(|app| { let mut srv = test::TestServer::new(|app| {
@@ -103,7 +107,6 @@ fn test_simple_path() {
); );
} }
#[test] #[test]
fn test_empty_close_code() { fn test_empty_close_code() {
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
@@ -214,8 +217,7 @@ impl Ws2 {
act.send(ctx); act.send(ctx);
} }
actix::fut::ok(()) actix::fut::ok(())
}) }).wait(ctx);
.wait(ctx);
} }
} }
@@ -280,9 +282,8 @@ fn test_server_send_bin() {
} }
#[test] #[test]
#[cfg(feature = "alpn")] #[cfg(feature = "ssl")]
fn test_ws_server_ssl() { fn test_ws_server_ssl() {
extern crate openssl;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
// load ssl keys // load ssl keys
@@ -318,7 +319,6 @@ fn test_ws_server_ssl() {
#[test] #[test]
#[cfg(feature = "rust-tls")] #[cfg(feature = "rust-tls")]
fn test_ws_server_rust_tls() { fn test_ws_server_rust_tls() {
extern crate rustls;
use rustls::internal::pemfile::{certs, rsa_private_keys}; use rustls::internal::pemfile::{certs, rsa_private_keys};
use rustls::{NoClientAuth, ServerConfig}; use rustls::{NoClientAuth, ServerConfig};
use std::fs::File; use std::fs::File;
@@ -353,3 +353,43 @@ fn test_ws_server_rust_tls() {
assert_eq!(item, data); assert_eq!(item, data);
} }
} }
struct WsStopped(Arc<AtomicUsize>);
impl Actor for WsStopped {
type Context = ws::WebsocketContext<Self>;
fn stopped(&mut self, _: &mut Self::Context) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
impl StreamHandler<ws::Message, ws::ProtocolError> for WsStopped {
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg {
ws::Message::Text(text) => ctx.text(text),
_ => (),
}
}
}
#[test]
fn test_ws_stopped() {
let num = Arc::new(AtomicUsize::new(0));
let num2 = num.clone();
let mut srv = test::TestServer::new(move |app| {
let num3 = num2.clone();
app.handler(move |req| ws::start(req, WsStopped(num3.clone())))
});
{
let (reader, mut writer) = srv.ws().unwrap();
writer.text("text");
writer.close(None);
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
}
thread::sleep(time::Duration::from_millis(100));
assert_eq!(num.load(Ordering::Relaxed), 1);
}