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

Compare commits

...

1333 Commits

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

* changes.md is updated

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

* Include canonical reason test for HTTP error responses.

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

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

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

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

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

* Add test to verify enum in query

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

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

* Return an error from StaticFiles::new() if directory doesn't exist
2018-07-05 12:34:13 +03:00
ac3a76cd32 update httparse version 2018-07-05 13:21:33 +06:00
8058d15624 clippy warnings 2018-07-05 13:16:16 +06:00
05a43a855e remove unsafe 2018-07-05 13:00:46 +06:00
80339147b9 call disconnect on write error 2018-07-05 12:50:54 +06:00
6af2f5d642 re-enable start_incoming support 2018-07-05 12:14:10 +06:00
d7762297da update actix dependency 2018-07-05 12:02:32 +06:00
d5606625a2 remove public Clone for Request 2018-07-04 22:57:40 +06:00
5d79114239 optimize Request handling 2018-07-04 22:52:49 +06:00
f559f23e1c Merge branch 'master' of github.com:actix/actix-web 2018-07-04 21:02:40 +06:00
6fd686ef98 cleanup warnings 2018-07-04 21:01:27 +06:00
4c5a63965e use new actix context api 2018-07-04 17:04:23 +06:00
09aabc7b3b plain/text -> text/plain in comment (#362) 2018-07-04 11:17:44 +03:00
b6d26c9faf Merge pull request #348 from actix/request-mutability
Request mutability
2018-07-02 23:52:42 +06:00
fec6047ddc refactor HttpRequest mutability 2018-07-02 23:35:32 +06:00
445ea043dd remove unsafes 2018-07-02 23:32:29 +06:00
0be5448597 Properly escape special characters in fs/directory_listing. (#355) 2018-06-30 15:01:48 +03:00
0f27389e72 set length of vector to max_bytes (closes #345) (#346) 2018-06-26 08:09:12 +03:00
a9425a866b Fix duplicate tail of StaticFiles with index_file
Map from 0.6 to master
2018-06-25 19:59:55 +03:00
800c404c72 explicit response release 2018-06-25 10:10:02 +06:00
32212bad1f simplify http response pool 2018-06-25 09:08:28 +06:00
d1b73e30e0 update comments 2018-06-24 22:27:30 +06:00
c0cdc39ba9 do not store cookies on client response 2018-06-24 22:21:04 +06:00
8e8a68f90b add empty output stream 2018-06-24 22:05:44 +06:00
989cd61236 handle empty te 2018-06-24 10:59:01 +06:00
33260c7b35 split encoding module 2018-06-24 10:42:20 +06:00
40ca9ba9c5 simplify write buffer 2018-06-24 10:30:58 +06:00
45682c04a8 refactor content encoder 2018-06-24 08:54:01 +06:00
348491b18c fix alpn connector 2018-06-23 17:59:45 +06:00
3d2226aa9e Merge branch 'master' of github.com:actix/actix-web 2018-06-23 12:40:45 +06:00
cf38183dcb refactor client connector waiters maintenance 2018-06-23 12:40:21 +06:00
e3dc6f0ca8 refactor h1decoder 2018-06-23 12:28:55 +06:00
a5369aed8b Changes a leaked box into an Rc<String> and makes resource() return an Option (#343) 2018-06-23 08:16:52 +02:00
ff0ab733e4 remove unsafe from mask 2018-06-23 11:51:02 +06:00
d1318a35a0 remove unnecessary unsafes 2018-06-23 10:29:23 +06:00
756227896b update set_date impl 2018-06-23 10:13:09 +06:00
4fadff63f4 Use Box::leak for dynamic param names 2018-06-23 09:57:03 +06:00
7bc7b4839b Switch from fnv to a identity hasher in extensions (#342) 2018-06-22 11:32:32 +02:00
dda6ee95df Changes the router to use atoms internally (#341) 2018-06-22 09:33:32 +02:00
765c38e7b9 remove libc dependency 2018-06-22 11:47:33 +06:00
6c44575923 transmute names once 2018-06-22 11:44:38 +06:00
fc7238baee refactor read_from_io 2018-06-22 11:30:40 +06:00
edd22bb279 refactor read_from_io 2018-06-22 09:01:20 +06:00
17c033030b Revert "remove unnecessary use of unsafe in read_from_io"
This reverts commit da237611cb.
2018-06-22 08:55:19 +06:00
3afdf3fa7e Merge pull request #335 from gnzlbg/fix_unsafe
remove unnecessary use of unsafe in read_from_io
2018-06-22 07:23:14 +06:00
50fbef88fc cleanup srver pipeline 2018-06-21 23:51:25 +06:00
c9069e9a3c remove unneeded UnsafeCell 2018-06-21 23:21:28 +06:00
65ca563579 use read only self for Middleware 2018-06-21 23:06:23 +06:00
3de9284592 Handler::handle uses &self instead of mutabble reference 2018-06-21 17:07:54 +06:00
5a9992736f Merge pull request #339 from joshleeb/propogate-scope-default-resource
Propagate scope default resource
2018-06-21 15:40:02 +06:00
0338767264 Update CHANGES for default scope propagation 2018-06-21 19:37:34 +10:00
c5e8c1b710 Propagate default resources to underlying scopes 2018-06-21 18:17:27 +10:00
b5594ae2a5 Fix doc api example 2018-06-21 14:11:00 +06:00
58d1f4a4aa switch to actix master 2018-06-21 13:34:36 +06:00
b7d813eeba update tests 2018-06-21 12:04:00 +06:00
8e160ebda7 clippy warning 2018-06-21 11:49:36 +06:00
0093b7ea5a refactor extractor configuration #331 2018-06-21 11:47:01 +06:00
75eec8bd4f fix condition 2018-06-21 11:23:21 +06:00
ebc59cf7b9 add unsafe checks #331 2018-06-21 11:20:21 +06:00
c2c4a5ba3f fix failure Send+Sync compatibility 2018-06-21 10:45:24 +06:00
dbd093075d Merge pull request #338 from tbroadley/fix-typos
Fix typos
2018-06-21 10:13:30 +06:00
1be27e17f8 convert timer error to io error 2018-06-21 10:05:20 +06:00
8b0fbb85d1 SendRequest execution fails with the entered unreachable code #329 2018-06-21 09:52:18 +06:00
cfe6725eb4 Allow to disable masking for websockets client 2018-06-21 09:49:33 +06:00
f815c1c096 Add test for default_resource scope propagation 2018-06-21 13:10:40 +10:00
280eae4335 Merge pull request #334 from Vurich/master
Fix some unsoundness
2018-06-21 07:15:33 +06:00
bd8cbfff35 docs: fix typos 2018-06-20 21:05:26 -04:00
da237611cb remove unnecessary use of unsafe in read_from_io 2018-06-20 13:14:53 +02:00
Jef
234c60d473 Fix some unsoundness
This improves the sound implementation of `fn route`.
Previously this function would iterate twice but we
can reduce the overhead without using `unsafe`.
2018-06-20 10:53:18 +02:00
2f917f3700 various cleanups and comments 2018-06-20 01:27:41 +06:00
311f0b23a9 cleanup more code 2018-06-20 00:36:32 +06:00
a69c1e3de5 remove unsafe from scope impl 2018-06-19 23:46:58 +06:00
c427fd1241 Merge pull request #328 from xfix/remove-some-uses-of-unsafe-from-frame-message
Remove some uses of unsafe from Frame::message
2018-06-19 21:52:41 +06:00
adcb4e1492 Merge pull request #327 from xfix/remove-use-of-unsafe-from-pipeline-poll
Remove use of unsafe from Pipeline#poll
2018-06-19 19:58:15 +06:00
3b1124c56c Merge branch 'master' into remove-some-uses-of-unsafe-from-frame-message 2018-06-19 19:20:40 +06:00
cafde76361 Merge branch 'master' into remove-use-of-unsafe-from-pipeline-poll 2018-06-19 19:20:25 +06:00
bfb93cae66 Update connector.rs 2018-06-19 19:19:31 +06:00
b5c1e42feb Merge branch 'master' into remove-use-of-unsafe-from-pipeline-poll 2018-06-19 18:30:37 +06:00
e884e7e84e Remove some uses of unsafe from Frame::message 2018-06-19 14:11:53 +02:00
877e177b60 Remove use of unsafe from Pipeline#poll 2018-06-19 13:42:44 +02:00
27b6af2800 refactor route matching 2018-06-19 16:45:26 +06:00
5c42b0902f better doc api examples 2018-06-19 12:07:07 +06:00
247e8727cb ClientBody is not needed 2018-06-19 10:15:16 +06:00
362b14c2f7 remove unsafe cell from ws client 2018-06-19 09:36:17 +06:00
261ad31b9a remove some unsafe code 2018-06-19 07:44:01 +06:00
68cd5bdf68 use actix 0.6 2018-06-18 09:18:03 +06:00
26f37ec2e3 refactor HttpHandlerTask trait 2018-06-18 05:45:54 +06:00
ef15646bd7 refactor edfault cpu pool 2018-06-18 04:56:18 +06:00
a5bbc455c0 cleanup mut transform 2018-06-18 04:41:41 +06:00
6ec8352612 method only for tests 2018-06-18 01:05:02 +06:00
f0f19c14d2 remove wsclient 2018-06-18 01:03:47 +06:00
daed502ee5 make mut api private 2018-06-18 01:03:07 +06:00
9d114d785e remove Clone from ExtractorConfig 2018-06-18 00:19:07 +06:00
ea118edf56 do not use references in ConnectionInfo 2018-06-18 00:01:41 +06:00
e1db47d550 refactor server settings 2018-06-17 23:51:20 +06:00
38fe8bebec fix doc string 2018-06-17 08:57:51 +06:00
c3f295182f use HashMap for HttpRequest::query() 2018-06-17 08:54:30 +06:00
b6ed778775 remove HttpMessage::range() 2018-06-17 08:48:50 +06:00
0f2aac1a27 remove unneed Send and Sync 2018-06-17 08:32:22 +06:00
70244c29e0 update doc api examples 2018-06-17 04:09:07 +06:00
a7a062fb68 clippy warnings 2018-06-17 03:26:34 +06:00
f3a73d7dde update changelog 2018-06-17 03:24:08 +06:00
879b2b5bde port Extensions from http crate #315 2018-06-17 03:22:08 +06:00
33050f55a3 remove Context::actor() method 2018-06-17 03:10:44 +06:00
e4443226f6 update actix usage 2018-06-17 02:58:56 +06:00
342a194605 fix handling ServerCommand #316 2018-06-16 22:56:27 +06:00
566b16c1f7 Merge branch 'master' of github.com:actix/actix-web 2018-06-14 11:42:27 +02:00
8261cf437d update actix api 2018-06-13 23:37:19 -07:00
8a8e6add08 Merge pull request #314 from DJMcNab/app-cleanup
remove duplication of `App::with_state` in `App::new`
2018-06-14 01:19:56 +03:00
b79307cab1 Merge branch 'master' into app-cleanup 2018-06-14 01:01:11 +03:00
4c646962a9 Merge pull request #312 from eddomuke/master
Add HttpMessage::readlines()
2018-06-14 00:40:29 +03:00
cb77f7e688 Add HttpMessage::readlines() 2018-06-14 00:19:48 +03:00
1bee528018 move ReadlinesError to error module 2018-06-13 22:59:36 +03:00
ad9aacf521 change poll method of Readlines 2018-06-13 22:41:35 +03:00
f8854f951c remove duplication of App::with_state in App::new 2018-06-13 20:31:20 +01:00
6d95e34552 add HttpMessage::readlines() 2018-06-13 20:45:31 +03:00
6c765739d0 add HttpMessage::readlines() 2018-06-13 20:43:03 +03:00
c8528e8920 Merge pull request #308 from eddomuke/master
Allow to override Form extractor error
2018-06-13 01:53:32 +03:00
0a080d9fb4 add test for form extractor 2018-06-13 01:33:28 +03:00
45b408526c Merge branch 'master' into master 2018-06-13 00:53:46 +03:00
1a91854270 Merge branch 'master' of github.com:actix/actix-web 2018-06-12 14:50:41 -07:00
99092fdf06 http/2 end-of-frame is not set if body is empty bytes #307 2018-06-12 14:50:21 -07:00
748ff389e4 Allow to override Form extractor error 2018-06-13 00:47:47 +03:00
b679b4cabc Merge pull request #306 from eddomuke/master
add ClientRequestBuilder::form()
2018-06-12 13:33:16 -07:00
ed7cbaa772 fix form_extractor test 2018-06-12 23:04:54 +03:00
e6bbda0efc add serialize 2018-06-12 22:42:15 +03:00
94283a73c2 make into_string, to_string 2018-06-12 22:31:33 +03:00
ffca416463 add test for ClientRequestBuilder::form() 2018-06-12 22:16:20 +03:00
9cc7651c22 add change to CHANGES.md 2018-06-12 20:32:16 +03:00
8af082d873 remove FormPayloadError 2018-06-12 20:26:09 +03:00
d4d3add17d add ClientRequestBuilder::form() 2018-06-12 19:30:00 +03:00
ce6f9e848b Merge pull request #305 from axon-q/response-cookies
Add HttpResponse methods to retrieve, add, and delete cookies
2018-06-12 14:39:06 +00:00
d8e1fd102d add cookie methods to HttpResponse 2018-06-12 13:56:53 +00:00
e414a52b51 content_disposition: remove unnecessary allocations 2018-06-12 13:48:23 +00:00
4d69e6d0b4 fs: minor cleanups to content_disposition 2018-06-12 13:47:49 +00:00
6f38d769a8 Merge pull request #304 from kazcw/master
fix url in example
2018-06-12 03:58:48 -07:00
48f77578ea fix url in example 2018-06-11 21:55:05 -07:00
9b012b3304 do not allow stream or actor responses for internal error #301 2018-06-11 19:45:17 -07:00
a0344eebeb InternalError can trigger memory unsafety #301 2018-06-11 18:54:36 -07:00
b9f6c313d4 Merge branch 'master' of github.com:actix/actix-web 2018-06-11 12:56:33 -07:00
ef420a8bdf fix docs.rs 2018-06-11 12:21:09 -07:00
0d54b6f38e Implement Responder for Option #294 (#297) 2018-06-11 14:05:41 +03:00
9afc3b6737 api docs link 2018-06-10 10:31:19 -07:00
ef88fc78d0 Merge branch 'master' of github.com:actix/actix-web 2018-06-10 10:25:05 -07:00
9dd66dfc22 better name for error 2018-06-10 10:24:34 -07:00
87a822e093 fix deprecated warnings 2018-06-10 10:14:13 -07:00
3788887c92 Merge pull request #293 from axon-q/static-file-updates
Better Content-Type and Content-Disposition handling for static files
2018-06-09 08:51:42 -07:00
785d0e24f0 Merge branch 'master' into static-file-updates 2018-06-09 08:21:34 -07:00
818d0bc187 new StreamHandler impl 2018-06-09 07:53:46 -07:00
aee24d4af0 minor syntax changes 2018-06-09 14:47:06 +00:00
fee203b402 update changelog 2018-06-09 14:02:05 +00:00
8681a346c6 fs: refactor Content-Type and Content-Disposition handling 2018-06-09 13:56:01 +00:00
1fdf6d13be content_disposition: add doc example 2018-06-09 13:38:21 +00:00
3751656722 expose fs::file_extension_to_mime() function 2018-06-09 11:20:06 +00:00
9151d61eda allow to use custom resolver for ClientConnector 2018-06-08 16:33:57 -07:00
4fe2f6b763 Merge pull request #284 from axon-q/multipart-content-disposition
multipart: parse and validate Content-Disposition
2018-06-07 21:20:18 -07:00
5a7902ff9a Merge branch 'master' into multipart-content-disposition 2018-06-07 21:20:11 -07:00
172b514fef Merge pull request #288 from memoryruins/patch-1
Update TechEmpower benchmarks to round 16
2018-06-07 21:09:49 -07:00
efb5d13280 readme: link to TechEmpower r16 benchmarks 2018-06-07 23:55:08 -04:00
f9f2ed04ab fix doc test 2018-06-07 20:22:23 -07:00
ce40ab307b update changes 2018-06-07 20:09:08 -07:00
f7ef8ae5a5 add Host predicate 2018-06-07 20:00:54 -07:00
60d40df545 fix clippy warning 2018-06-07 19:46:46 -07:00
f7bd6eeedc add application filters 2018-06-07 19:46:38 -07:00
a11f3c112f fix doc test 2018-06-07 21:18:51 +00:00
e9f59bc7d6 Merge branch 'master' into multipart-content-disposition 2018-06-07 11:02:53 -07:00
e970846167 update changelog 2018-06-07 17:59:35 +00:00
56e0dc06c1 defer parsing until user method call 2018-06-07 17:29:46 +00:00
789af0bbf2 Added improved failure interoperability with downcasting (#285)
Deprecates Error::cause and introduces failure interoperability functions and downcasting.
2018-06-07 18:53:27 +02:00
97b5410aad remove Option from ContentDisposition::from_raw() argument 2018-06-07 12:55:35 +00:00
a6e07c06b6 move CD parsing to Content-Type parsing location 2018-06-07 12:35:10 +00:00
31a301c9a6 fix multipart test 2018-06-07 11:38:35 +00:00
5a37a8b813 restore hyper tests 2018-06-07 10:55:36 +00:00
c0c1817b5c remove unicase dependency 2018-06-07 10:33:00 +00:00
82c888df22 fix test 2018-06-07 09:10:46 +00:00
936ba2a368 multipart: parse and validate Content-Disposition 2018-06-06 14:06:01 +00:00
2d0b609c68 travis config 2018-06-05 10:08:42 -07:00
6467d34a32 update release date 2018-06-05 09:45:07 -07:00
2b616808c7 metadata for docs.rs 2018-06-05 09:00:21 -07:00
e5f7e4e481 update changelog 2018-06-05 08:55:28 -07:00
d1da227ac5 fix multipart boundary parsing #282 2018-06-05 08:53:51 -07:00
960a8c425d update changelog 2018-06-05 07:40:11 -07:00
f94fd9ebee CORS: Do not validate Origin header on non-OPTION requests #271 2018-06-05 07:39:47 -07:00
67ee24f9a0 Merge pull request #274 from mockersf/user-agent
add default value for header User-Agent in requests
2018-06-04 14:04:52 -07:00
5004821cda Merge branch 'master' into user-agent 2018-06-04 14:04:45 -07:00
ae7a0e993d update changelog 2018-06-04 13:43:52 -07:00
984791187a Middleware::response is not invoked if error result was returned by another Middleware::start #255 2018-06-04 13:42:47 -07:00
b07c50860a update changelog 2018-06-04 22:34:07 +02:00
eb0909b3a8 Merge branch 'master' into user-agent 2018-06-04 10:20:53 -07:00
ca3fb11f8b add actix-web version in header 2018-06-04 08:15:04 +02:00
47eb4e3d3d Merge pull request #278 from mbrobbel/patch-2
Fix typo
2018-06-03 16:28:51 -07:00
268c5d9238 Fix typo 2018-06-03 20:28:08 +02:00
86be54df71 add default value for header User-Agent in requests 2018-06-03 15:48:00 +02:00
ea018e0ad6 better examle in doc string 2018-06-02 16:03:23 -07:00
b799677532 better error messages for overflow errors 2018-06-02 15:10:48 -07:00
8c7182f6e6 Merge pull request #270 from DoumanAsh/payload_err
Specialize ResponseError for PayloadError
2018-06-02 15:06:55 -07:00
7298c7aabf Merge branch 'master' into payload_err 2018-06-02 15:04:22 -07:00
7e0706a942 implement Debug for Form, Query, Path extractors 2018-06-02 15:00:11 -07:00
698f0a1849 update changelog 2018-06-02 15:00:11 -07:00
8b8a3ac01d Support chunked encoding for UrlEncoded body #262 2018-06-02 15:00:06 -07:00
7ab23d082d fix doc test 2018-06-02 13:45:29 -07:00
913dce0a72 Merge branch 'master' into payload_err 2018-06-02 23:10:06 +03:00
2a9b57f489 Correct docstring 2018-06-02 22:27:43 +03:00
fce8dd275a Specialize ResponseError for PayloadError
Closes #257
2018-06-02 22:20:22 +03:00
3c472a2f66 remove debug prints 2018-06-02 11:57:49 -07:00
dcb561584d remove debug print 2018-06-02 11:55:50 -07:00
593a66324f update changelog 2018-06-02 11:45:37 -07:00
4a39216aa7 fixed HttpRequest::url_for for a named route with no variables #265 2018-06-02 11:44:09 -07:00
8d905c8504 add links to migration 2018-06-02 09:28:32 -07:00
33326ea41b fix layout 2018-06-02 09:25:11 -07:00
0457fe4d61 add System changes to migration guide 2018-06-02 09:19:13 -07:00
cede817915 update changelog 2018-06-02 09:15:44 -07:00
3bfed36fcc do not re-export actix_inner 2018-06-02 09:14:47 -07:00
0ff5f5f448 update migration 2018-06-02 09:01:51 -07:00
2f476021d8 Merge pull request #267 from joshleeb/trait-middleware-mut-self
Update Middleware Trait to Use `&mut self`
2018-06-02 08:54:30 -07:00
a61a1b0efe Merge branch 'master' into trait-middleware-mut-self 2018-06-02 08:54:00 -07:00
e041e9d3b7 Merge pull request #268 from killercup/docs/no-more-missing-docs
No more missing docs
2018-06-02 08:52:14 -07:00
890a7e70d6 Add missing API docs
These were written without much knowledge of the actix-web internals!
Please review carefully!
2018-06-02 15:52:50 +02:00
47b7be4fd3 Add warning for missing API docs 2018-06-02 15:50:45 +02:00
9c9eb62031 Update Middleware trait to use &mut self 2018-06-02 16:47:18 +10:00
8d73c30dae Merge pull request #266 from killercup/docs/fix-typos-and-run-more-code
Fix some ResourceHandler docs
2018-06-01 16:37:34 -07:00
d912bf8771 Add more docs to ResourceHandler API 2018-06-02 00:57:24 +02:00
f414a491dd Fix some ResourceHandler docs
Re-enables code blocks as doc tests to prevent them failing in the
future.
2018-06-02 00:57:07 +02:00
8f42fec9b2 stable compat 2018-06-01 12:17:13 -07:00
8452c7a044 fix doc api example 2018-06-01 11:22:40 -07:00
009ee4b3db update changelog 2018-06-01 10:55:54 -07:00
3e0a71101c drop with2 and with3 2018-06-01 10:54:23 -07:00
c8930b7b6b fix rustfmt formatting 2018-06-01 10:27:23 -07:00
3f5a39a5b7 cargo fmt 2018-06-01 09:37:14 -07:00
154cd3c5de better actix mod re-exports 2018-06-01 09:36:16 -07:00
80965d7a9a Re-export actix dependency. Closes #260 (#264)
- Re-export actix's prelude into actix namespace
- Removing implicit dependency on root's actix module
2018-05-31 20:43:14 +03:00
77becb9bc0 fix doc string 2018-05-29 18:48:39 -07:00
dde266b9ef fix doc string 2018-05-29 18:31:39 -07:00
34fd9f8148 travis config 2018-05-29 18:18:05 -07:00
a64205e502 refactor TransferEncoding; allow to use client api with threaded tokio runtime 2018-05-29 16:32:39 -07:00
844be8d9dd fix ssl test server 2018-05-29 10:59:24 -07:00
dffb7936fb Merge branch 'master' of github.com:actix/actix-web 2018-05-29 10:31:43 -07:00
ecd05662c0 use new actix system api 2018-05-29 10:31:37 -07:00
6eee3d1083 Merge pull request #258 from mbrobbel/patch-1
Fix typo in httpresponse.rs
2018-05-29 09:15:39 -07:00
6b43fc7068 Fix typo in httpresponse.rs 2018-05-29 18:11:10 +02:00
fb582a6bca fix connector 2018-05-27 05:18:37 -07:00
be2ceb7c66 update actix Addr; make ClientConnector thread safe 2018-05-27 05:02:49 -07:00
7c71171602 Merge pull request #248 from bbigras/same-site
Add same-site to CookieSessionBackend
2018-05-26 08:02:12 -07:00
4dcecd907b Add same-site to CookieSessionBackend
closes #247
2018-05-25 19:18:16 -04:00
255cd4917d fix doc test 2018-05-24 22:04:14 -07:00
f48702042b min rustc version 2018-05-24 21:09:20 -07:00
690169db89 migrate to tokio 2018-05-24 21:03:16 -07:00
565bcfb561 Merge pull request #245 from svartalf/response-builder-cookies-doc
Updating docs for HttpResponseBuilder::del_cookie
2018-05-24 12:42:08 -07:00
36f933ce1d Updating docs for HttpResponseBuilder::del_cookie 2018-05-24 21:53:35 +03:00
111b6835fa fix comment 2018-05-24 11:06:15 -07:00
bf63be3bcd bump version 2018-05-24 09:24:04 -07:00
9f9e0b98ad change homepage link 2018-05-24 08:55:10 -07:00
556646aaec update changelog 2018-05-24 07:56:51 -07:00
174fb0b5f4 Merge pull request #239 from max-frai/master
Add ability to set encoding for exact NamedFile.
2018-05-24 07:46:53 -07:00
836706653b Merge branch 'master' into master 2018-05-24 07:46:46 -07:00
17f1a2b92a more scope tests 2018-05-23 14:11:01 -07:00
3b08b16c11 bump version 2018-05-23 13:21:54 -07:00
68eb2f26c9 Allow to use path without traling slashes for scope registration #241 2018-05-23 13:21:29 -07:00
72757887c9 update doc links 2018-05-23 11:20:12 -07:00
eb5dbd43ae update changelog 2018-05-23 10:37:17 -07:00
1f1dfac3f9 Merge pull request #240 from ivanovaleksey/patch-2
Fix TestServer::post
2018-05-23 09:50:40 -07:00
2479b14aba Fix TestServer::post 2018-05-23 19:07:42 +03:00
ac24703512 Add ability to set encoding for exact NamedFile. 2018-05-23 09:12:23 +03:00
db0091ba6f disable server test for windows 2018-05-21 21:01:52 -07:00
2159158c30 Fix streaming response with body compression 2018-05-21 20:50:10 -07:00
76d790425f bump version 2018-05-21 19:07:56 -07:00
90968d4333 Drop connection if request's payload is not fulle consumed #236 2018-05-21 18:54:17 -07:00
577a509875 increase delay 2018-05-21 16:12:33 -07:00
a9728abfc8 run coverage report on 1.24 2018-05-20 21:10:50 -07:00
14d1b8e2b6 prepare release 2018-05-20 21:09:54 -07:00
285c73e95e Re-use tcp listener on pause/resume 2018-05-20 20:47:20 -07:00
483db7028c expose low level data 2018-05-20 20:37:19 -07:00
082ff46041 Fix scope resource path extractor #234 2018-05-20 17:04:23 -07:00
f32e8f22c8 Merge pull request #231 from qrvaelet/ranges
NamedFile: range upgrade
2018-05-20 09:18:46 -07:00
766dde7c42 Merge branch 'master' into ranges 2018-05-20 08:51:07 -07:00
b68687044e range header syntax fix, change range to content-range in responses, enabled accept ranges, tests for content-range, content-length, and range status code 2018-05-20 17:40:36 +02:00
c9e84e9dd3 Merge pull request #233 from sindreij/patch-1
Fix some typos in server/srv.rs
2018-05-20 06:19:53 -07:00
0126ac46fc Fix some typos in server/srv.rs
Hello! This looks like a great library, thanks for creating it! While reading through the documentation I found a few typos.
2018-05-20 14:43:26 +02:00
9b7ea836d0 bump version 2018-05-17 18:34:09 -07:00
537b420d35 Fix compilation with --no-default-features 2018-05-17 18:33:48 -07:00
16906c5951 clippy warnings 2018-05-17 12:23:37 -07:00
45e9aaa462 rustfmt 0.7 2018-05-17 12:20:20 -07:00
564cc15c04 update changes 2018-05-17 12:20:04 -07:00
8fd18d56a5 Merge pull request #227 from qrvaelet/ranges
NamedFile: added basic ranges header support, added content-length support
2018-05-17 12:18:10 -07:00
a5692d4ecf Merge branch 'master' into ranges 2018-05-17 11:16:08 -07:00
2d83f79433 NamedFile: added ranges support, content-length support 2018-05-17 20:09:41 +02:00
f3ece74406 better error handling 2018-05-17 10:58:08 -07:00
8de1f60347 add session extractor doc api 2018-05-16 21:05:59 -07:00
b4252f8fd1 implement extractor for Session 2018-05-16 21:02:51 -07:00
fe2b50a9ef update changelog 2018-05-16 11:02:50 -07:00
d8ae8c3821 Merge pull request #225 from mitsuhiko/feature/addrs-with-scheme
Support returning addresses plus scheme from the server
2018-05-16 11:02:15 -07:00
c9a026fabb Merge branch 'master' into feature/addrs-with-scheme 2018-05-16 11:01:45 -07:00
64eca1546e Merge pull request #224 from mitsuhiko/feature/listen-tls
Add support for listen_tls/listen_ssl
2018-05-16 11:01:28 -07:00
b19fe98ff4 Merge branch 'master' into feature/listen-tls 2018-05-16 11:01:21 -07:00
b393ddf879 fix panic during middleware execution #226 2018-05-16 11:00:29 -07:00
7bb7d85c1d Added support for returning addresses plus scheme from the server 2018-05-16 16:17:27 +02:00
6e976153e7 Add support for listen_tls/listen_ssl 2018-05-16 15:20:47 +02:00
03e758cee4 bump version 2018-05-15 19:08:34 -07:00
0d36b8f826 fix 1.24 compatibility 2018-05-15 19:07:43 -07:00
f82fa08d72 various optimizations 2018-05-15 16:49:03 -07:00
d6787e6c56 prepare release 2018-05-15 10:20:32 -07:00
b9d870645f store cookies in extensions 2018-05-15 10:09:48 -07:00
ef89430f9b undeprecate query() and store query in extensions 2018-05-15 09:53:58 -07:00
953a0d4e4a add test case for #222 2018-05-15 09:29:59 -07:00
5ea2d68438 h1 decoder blocks on error #222 2018-05-15 07:55:36 -07:00
d65a03f6ac use latest nightly for appveyor 2018-05-13 08:43:09 -07:00
b588b2bf5c Merge pull request #221 from skorgu/patch-1
Include mention of http client in README.md
2018-05-11 21:56:46 -07:00
d455e2cd13 Merge branch 'master' into patch-1 2018-05-11 21:56:35 -07:00
9306631d6e Fix segfault in ServerSettings::get_response_builder() 2018-05-11 21:19:48 -07:00
f735da504b Include mention of http client in README.md 2018-05-11 20:36:54 -04:00
487a713ca0 update doc string 2018-05-11 15:01:15 -07:00
095ad328ee prepare release 2018-05-10 15:45:06 -07:00
a38afa0cec --no-count for tarpaulin 2018-05-10 13:05:56 -07:00
9619698543 doc string 2018-05-10 13:04:56 -07:00
4b1a471b35 add more examples for extractor config 2018-05-10 13:03:43 -07:00
b6039b0bff add doc string 2018-05-10 11:04:03 -07:00
d8fa43034f export ExtractorConfig type 2018-05-10 11:00:22 -07:00
92f993e054 Fix client request timeout handling 2018-05-10 09:37:38 -07:00
c172deb0f3 Merge pull request #219 from benjamingroeber/improve-readme
correct order of format arguments in readme example
2018-05-10 09:15:50 -07:00
dee6aed010 Merge branch 'master' into improve-readme 2018-05-10 09:15:44 -07:00
76f021a6e3 add tests for ErrorXXX helpers 2018-05-10 09:13:26 -07:00
2f244ea028 fix order of name and id in readme example 2018-05-10 18:12:59 +02:00
5f5ddc8f01 Merge pull request #218 from Dowwie/master
added error response functions for 501,502,503,504
2018-05-10 08:59:23 -07:00
8b473745cb added error response functions for 501,502,503,504 2018-05-10 11:26:38 -04:00
18575ee1ee Add Router::with_async() method for async handler registration 2018-05-09 16:27:31 -07:00
e58b38fd13 deprecate WsWrite from top level mod 2018-05-09 06:12:16 -07:00
b043c34632 bump version 2018-05-09 06:05:44 -07:00
b748bf3b0d make api public 2018-05-09 06:05:16 -07:00
be12d5e6fc make WsWriter trait optional 2018-05-09 05:48:06 -07:00
7c4941f868 update migration doc 2018-05-08 18:48:09 -07:00
d1f5c457c4 Merge branch 'master' of github.com:actix/actix-web 2018-05-08 18:35:52 -07:00
c26c5fd9a4 prep release 2018-05-08 18:34:36 -07:00
4a73d1c8c1 Merge pull request #216 from lcowell/lcowell-scoupe
replace typo `scoupe` with `scope`
2018-05-08 17:47:20 -07:00
7c395fcc83 replace typo scoupe with scope 2018-05-08 17:40:18 -07:00
54c33a7aff Allow to exclude certain endpoints from logging #211 2018-05-08 16:30:34 -07:00
47d80382b2 Fix http/2 payload streaming #215 2018-05-08 15:44:50 -07:00
ba816a8562 Merge pull request #214 from niklasf/de-path-404
let Path::from_request() fail with ErrorNotFound
2018-05-08 14:41:05 -07:00
6f75b0e95e let Path::from_request() fail with ErrorNotFound 2018-05-08 22:59:46 +02:00
b3cc43bb9b Fix connector's default keep-alive and lifetime settings #212 2018-05-08 13:41:04 -07:00
ecda97aadd update doc string 2018-05-08 05:54:06 -07:00
8cda362866 simplify pipeline 2018-05-07 16:09:41 -07:00
3c6c1268c9 travis cover report 2018-05-07 15:54:29 -07:00
72908d974c test for Scope::route(); prep release 2018-05-07 15:19:03 -07:00
c755d71a8b add filters support to scopes 2018-05-07 14:40:04 -07:00
a817ddb57b add variable segments support for scope prefix 2018-05-07 13:50:43 -07:00
44c36e93d1 Merge pull request #210 from andreevlex/feature/spelling-check-06-05
spelling check
2018-05-07 11:30:46 -07:00
c92ebc22d7 Merge branch 'master' into feature/spelling-check-06-05 2018-05-07 11:30:39 -07:00
599fd6af93 fix formatting 2018-05-07 20:53:45 +03:00
fa81d97004 more handler tests 2018-05-06 20:05:31 -07:00
c54f045b39 more handler tests 2018-05-06 15:11:36 -07:00
cd11293c1f spelling check 2018-05-06 19:07:30 +03:00
45325a5f75 more middleware tests 2018-05-06 08:33:41 -07:00
a7c40024ce async handle middleware test 2018-05-05 18:40:16 -07:00
0af4d01fe4 move middleware tests to seprate module 2018-05-05 12:18:43 -07:00
bd6e18b7fe update migration doc 2018-05-04 13:38:17 -07:00
f66cf16823 upgrade regex 2018-05-04 12:25:06 -07:00
03d6b04eef update tests 2018-05-04 12:11:38 -07:00
f37880d89c refactor Responder trait 2018-05-04 11:44:22 -07:00
8b43574bd5 Merge branch 'master' of github.com:actix/actix-web 2018-05-03 16:27:12 -07:00
b07d0e712f always provide backtrace for error 2018-05-03 16:26:42 -07:00
acd7380865 rename Reply to a AsyncResult 2018-05-03 16:22:08 -07:00
0208dfb6b2 Merge pull request #208 from DenisKolodin/ws-trait
Add WsWriter trait
2018-05-03 10:43:44 -07:00
bb61dd41af Merge branch 'master' into ws-trait 2018-05-03 08:57:45 -07:00
58079b5bbe add session test 2018-05-02 19:11:44 -07:00
3623383e83 fix tests 2018-05-02 16:48:42 -07:00
7036656ae4 make Reply generic over error too 2018-05-02 16:33:29 -07:00
32a2866449 Allow to override files listing renderer for #203 2018-05-02 15:53:07 -07:00
35a4078434 update changelog 2018-05-02 13:43:51 -07:00
4ca5d8bcfc add FromRequest impl for tuples of various length 2018-05-02 13:38:25 -07:00
a38acb41e5 better query example 2018-05-02 06:30:06 -07:00
31e23d4ab1 add query deprecation info 2018-05-02 06:28:38 -07:00
1aadfee6f7 rename from_default to extract 2018-05-02 06:09:50 -07:00
76b644365f use read only ref for FromRequest; remove unnecessary static 2018-05-02 06:07:30 -07:00
80f385e703 Add WsWriter trait
`WsWriter` trait is a common interface for writing to a websocket and
it's implemented for both: `WebScoketContext` and `ClientWriter`.
2018-05-02 08:35:50 +03:00
a1958deaae add impl Future for Reply 2018-05-01 17:30:06 -07:00
8d65468c58 refactor FromRequest trait 2018-05-01 17:19:15 -07:00
195246573e rename threads to workers 2018-05-01 13:15:35 -07:00
e01102bda2 no need for mut 2018-05-01 11:45:46 -07:00
9b6343d54b refactor session impl 2018-05-01 09:40:23 -07:00
d9a4fadaae make HttpRequest::extensions() readonly 2018-05-01 09:05:50 -07:00
48e05a2d87 add nested scope support 2018-04-30 22:04:24 -07:00
70d0c5c700 update changes 2018-04-30 19:56:17 -07:00
d43ca96c5c Allow to use ssl and non-ssl connections with the same HttpServer #206 2018-04-30 19:51:55 -07:00
bfd46e6a71 update doc string 2018-04-29 22:28:16 -07:00
25b245ac72 allow to use custom state for scope 2018-04-29 22:19:52 -07:00
eefbe19651 remove deprecated types and methods 2018-04-29 21:05:10 -07:00
ab4e889f96 add middleware finished handler for route middleware 2018-04-29 20:50:38 -07:00
91235ac816 fix reading from socket 2018-04-29 20:34:59 -07:00
9c1bda3eca fix stable compiler compatibility 2018-04-29 19:49:26 -07:00
4a29f12876 update doc string; missing file 2018-04-29 19:39:28 -07:00
368730f5f1 Add route scopes #202 2018-04-29 19:35:50 -07:00
aa757a5be8 Allow to access Error's backtrace object 2018-04-29 14:21:50 -07:00
03ded62337 bump minimum supported rustc version because of minor version change of parking_lot crate 2018-04-29 14:13:46 -07:00
c72d1381a6 clippy warnings 2018-04-29 09:09:08 -07:00
d98d723f97 bump rustc version requirements 2018-04-29 08:24:19 -07:00
eb6e618812 Merge pull request #204 from svenstaro/master
Add Content-Disposition to NamedFile (fixes #172)
2018-04-29 08:18:48 -07:00
de222fe33b Merge and fix PR comments 2018-04-29 14:02:50 +02:00
de49796fd1 clippy warnings; fmt 2018-04-28 22:55:47 -07:00
a38c3985f6 refactor http1 parser 2018-04-28 22:20:32 -07:00
492c072564 Add Content-Disposition to NamedFile (fixes #172) 2018-04-27 09:49:55 +02:00
fd876efa68 allow to access application state during configuration stage 2018-04-26 09:05:07 -07:00
c5b9bed478 update changes 2018-04-26 08:01:08 -07:00
3eba383cdc Merge pull request #196 from fuchsnj/websocket_close_reason
Websocket close reason
2018-04-26 07:55:32 -07:00
927f2e594e Merge branch 'master' into websocket_close_reason 2018-04-25 20:17:19 -07:00
fa9edf2180 prep release 2018-04-24 12:34:10 -07:00
5ca904d1db make flate crate optional 2018-04-24 12:24:04 -07:00
2e7d323e1a add r2d2 example link 2018-04-24 09:34:38 -07:00
b66566f610 comments 2018-04-24 09:32:19 -07:00
2477afcf30 Allow to use rust backend for flate2 crate #199 2018-04-24 09:29:15 -07:00
bcd03a9c62 link to askama example 2018-04-24 09:16:46 -07:00
f8af3ef7f4 refactor keep-alive 2018-04-22 15:28:04 -07:00
f8b75c157f fix style 2018-04-22 11:43:47 -04:00
b7b61afacc add ws close description parse test 2018-04-21 17:20:23 -04:00
507361c1df Merge branch 'master' into websocket_close_reason 2018-04-21 17:05:43 -04:00
f6fd9e70f9 code cleanup 2018-04-21 16:53:55 -04:00
de8a09254d use Optional with websocket close reason 2018-04-21 16:50:27 -04:00
f89b7a9bb8 Merge pull request #194 from actix/brandur-allowed-origin-into
Let CSRF's `allowed_origin()` be specified as a type supporting `Into<String>`
2018-04-21 10:37:18 -07:00
59244b203c Let CSRF's allowed_origin() be specified as a type supporting Into<String>
A very minor addition: I'm using this middleware on specific resources,
and given a non-static string, I often have to `clone()` already to get
a string into a closure. Take this code for example:

``` rust
let server = actix_web::server::new(move || {
    let csrf_origin_graphql = csrf_origin.clone();

    ...

    .resource("/graphql", move |r| {
	r.middleware(
	    csrf::CsrfFilter::new().allowed_origin(csrf_origin_graphql.as_str()),
	);

	r.method(Method::POST).a(graphql::handlers::graphql_post);
    })
```

Letting `allowed_origin()` take an `Into<String>` instead of `&str` would
prevent a second `clone()` in the code above, and also make the code a little
nicer to read (you eliminate the `.as_str()` above). This is a pattern that
seems to be common throughout actix-web already anyway, so it should also be
fine to have here.
2018-04-21 08:41:06 -07:00
2adf8a3a48 add changelog entry 2018-04-21 07:56:11 -07:00
805dbea8e7 Merge pull request #192 from fuchsnj/check_if_close_code_exists
check if close code exists before reading it
2018-04-21 07:54:25 -07:00
dc9a24a189 add websocket empty close status test 2018-04-20 21:55:07 -04:00
5528cf62f0 check if close code exists before reading it 2018-04-20 21:30:18 -04:00
9880a95603 Merge pull request #189 from drklee3/patch-1
Update README links to use new guide
2018-04-19 19:24:40 -07:00
2579c49865 Update README links to use new guide 2018-04-19 18:51:01 -07:00
01a0f3f5a0 remove unused dependency 2018-04-19 09:54:22 -07:00
2c8d987241 Use Display formatting for InternalError Display implementation #188 2018-04-19 07:55:09 -07:00
813d1d6e66 doc strings layout 2018-04-18 20:41:03 -07:00
48b02abee7 fmt 2018-04-18 20:16:29 -07:00
ce1081432b export session module 2018-04-18 20:11:49 -07:00
e9bdba57a0 Add identity service middleware 2018-04-18 19:05:24 -07:00
f907be585e Middleware response() is not invoked if there was an error in async handler #187 2018-04-18 14:15:53 -07:00
022f9800ed formatting 2018-04-18 10:49:03 -07:00
a9a54ac4c6 prep release 2018-04-18 10:45:59 -07:00
50b9fee3a7 Merge branch 'master' of github.com:actix/actix-web 2018-04-17 16:24:02 -07:00
bf9a90293f fix doc strings 2018-04-17 16:22:25 -07:00
17ec3a3a26 Merge pull request #185 from kornelski/master
Replace use of try!() with ?
2018-04-17 15:57:09 -07:00
5b4b885fd6 Replace use of try!() with ? 2018-04-17 23:20:47 +01:00
65b8197876 better doc string for Application::with_state() 2018-04-17 13:59:55 -07:00
a826d113ee add custom request path quoter #182 2018-04-17 12:55:13 -07:00
3a79505a44 update doc string 2018-04-17 07:51:06 -07:00
5f3a7a6a52 Merge pull request #184 from ivanovaleksey/patch-1
Fix route in App::resource example
2018-04-17 07:49:09 -07:00
6a7b097bcf Fix route in App::resource example 2018-04-17 16:01:34 +03:00
30a36bed9d fix doc example 2018-04-16 09:50:37 -07:00
79818560b2 cleanup doc strings; prepare release 2018-04-16 09:30:59 -07:00
58cc0dfbc5 Fix Client Request with custom Body Stream halting on certain size requests #176 2018-04-15 10:22:09 -07:00
a9ea649348 Allow to configure StaticFiles CpuPool, via static method or env variable 2018-04-13 19:46:14 -07:00
634c5723a0 update changelog 2018-04-13 19:19:30 -07:00
a5b5ff0894 update doc strings 2018-04-13 19:14:14 -07:00
5140fea8d1 allow to use castom error handler for json extractor 2018-04-13 19:10:42 -07:00
333b4f57d3 use different directory for tests 2018-04-13 17:00:18 -07:00
827ca5eada remove skeptic tests 2018-04-13 16:36:39 -07:00
ebc1f6eff9 drop skeptic 2018-04-13 16:21:57 -07:00
a8567da3e2 move guide to separate repo; update links 2018-04-13 16:20:23 -07:00
113f5ad1a8 add rustfmt config 2018-04-13 16:02:01 -07:00
95f6277007 fix typo 2018-04-13 14:36:07 -07:00
22c776f46e Fix StaticFiles does not support percent encoded paths #177 2018-04-13 10:13:12 -07:00
c0976bfa17 fix test 2018-04-12 21:28:17 -07:00
5e9ec4299c fix workspace links 2018-04-12 20:52:30 -07:00
e05aba65de examples moved to separate repo 2018-04-12 20:31:58 -07:00
c5b18c6d30 prepare release 2018-04-12 16:03:22 -07:00
94c5bb5cdd add helper method for returning inner value 2018-04-12 15:55:15 -07:00
2ca0ea70c4 use one default cpu pool for StaticFiles #174 2018-04-12 15:50:20 -07:00
0b01884fca add timeouts stats to client connector 2018-04-12 13:08:13 -07:00
83168731fc update user guide content compression section 2018-04-12 09:54:35 -07:00
7295846426 Merge pull request #173 from jannic/pr
fix end-of-stream handling in parse_payload
2018-04-12 09:30:26 -07:00
72bc1546c4 fix end-of-stream handling in parse_payload
parse_payload can be called with a pre-filled buf.

In this case, it's totaly fine for read_from_io to return
sync::Ready(0) while buf is not empty. This is not an
PayloadError::Incomplete.

So, move the check for PayloadError::Incomplete down to the
decoding code: If the decoder is not ready, but the input stream
is finished, PayloadError::Incomplete will be returned.
2018-04-12 09:47:32 +02:00
d39b531537 Merge branch 'master' of github.com:actix/actix-web 2018-04-11 19:05:34 -07:00
35e68723df use older mdbook 2018-04-11 19:05:14 -07:00
0624f9b9d9 Update MIGRATION-0.4-0.5.md 2018-04-11 16:53:27 -07:00
0e3820afdf Update MIGRATION-0.4-0.5.md 2018-04-11 16:49:45 -07:00
839d67ac6a migration to 0.5 2018-04-11 16:46:21 -07:00
b517957761 fix stats for tls and alpn features 2018-04-11 16:34:01 -07:00
d18f9c5905 add clinet connector stats 2018-04-11 16:11:11 -07:00
76fcdc13a3 Merge pull request #171 from DoumanAsh/without_state_public
Make HttpRequest::without_state public
2018-04-11 23:48:19 +03:00
62a9b4c53c Rename HttpRequest::without_state into drop_state and make it public 2018-04-11 22:41:06 +03:00
c570229351 Update README.md 2018-04-11 10:49:34 -07:00
d041df6c4b update links 2018-04-10 19:27:09 -07:00
bc28e54976 add homepage link 2018-04-10 19:20:21 -07:00
26ab5cbd01 forgot to include 2018-04-10 15:14:46 -07:00
50c2a5ceb0 update basic example 2018-04-10 14:45:03 -07:00
8dbbb0ee07 update guide 2018-04-10 13:31:10 -07:00
ca76dff5a7 update redis example 2018-04-10 13:21:54 -07:00
88f66d49d0 openssl features 2018-04-10 11:07:54 -07:00
be288fa00a for NamedFile process etag and last modified only if status code is 200 2018-04-10 10:57:53 -07:00
5e6a0aa3df simplier example in readme 2018-04-10 10:39:16 -07:00
fd87eb59f8 remove reference to master 2018-04-10 10:29:10 -07:00
81ac905c7b fix prefix and static file serving #168 2018-04-10 10:16:00 -07:00
bb11fb3d24 update client mod doc string 2018-04-09 21:57:40 -07:00
23eea54776 update cors doc string 2018-04-09 21:39:32 -07:00
2881859400 proper test for CorsBuilder::resource 2018-04-09 21:29:57 -07:00
1686682c19 extend CorsBuilder api to make it more user friendly 2018-04-09 21:11:15 -07:00
d04ff13955 update version 2018-04-09 14:27:13 -07:00
e757dc5a71 clippy warnings 2018-04-09 14:25:30 -07:00
be358db422 CorsBuilder::finish() panics on any configuration error 2018-04-09 14:20:12 -07:00
7df2d6b12a clippy warnings; extend url_for example in user guide 2018-04-09 13:30:38 -07:00
458e6bdcc2 Merge pull request #170 from adwhit/private-cookies
Public, signed and private cookies
2018-04-09 12:54:15 -07:00
0b0bbd6bd9 Merge branch 'master' into private-cookies 2018-04-09 12:54:08 -07:00
5617896780 cleanup doc tests 2018-04-09 10:40:12 -07:00
2b803f30c9 remove CookieSessionBackend::new 2018-04-09 18:33:29 +01:00
9b152acc32 add signed and private cookies 2018-04-09 17:59:28 +01:00
eb66685d1a simplify csrf middleware 2018-04-09 09:49:07 -07:00
b505e682d4 fix session doc test 2018-04-09 09:31:11 -07:00
48e7013997 update guide examples 2018-04-09 07:57:50 -07:00
ff14633b3d simplify CookieSessionBackend; expose max_age cookie setting 2018-04-08 11:05:37 -07:00
37db7d8168 allow to override status code for NamedFile 2018-04-08 10:53:58 -07:00
89bf12605d Merge pull request #165 from tazjin/docs/various-doc-fixes
Various minor documentation fixes
2018-04-07 09:53:19 -07:00
9fb0498437 docs(lib): Add a note about getting started with the API docs
Adds some initial pointers for newcomers to the documentation that
direct them at some of the most commonly used API types.

I based these links on what *I* usually end up looking at when I open
the actix_web docs.
2018-04-07 17:27:53 +02:00
b2a43a3c8d docs(application): Formatting & spelling fixes in module docs 2018-04-07 17:19:11 +02:00
38063b9873 docs(client): Minor formatting and spelling fixes in module docs 2018-04-07 17:00:57 +02:00
1045a6c6f0 docs(README): Minor formatting and spelling fixes 2018-04-07 17:00:39 +02:00
7243c58fce stable rust compatibility 2018-04-06 21:57:45 -07:00
fffaf2bb2d App::route method 2018-04-06 21:18:42 -07:00
7becb95a97 fix guide example 2018-04-06 20:24:49 -07:00
a4b837a1c1 flaky test 2018-04-06 19:45:14 -07:00
542315ce7f simplify StaticFiles 2018-04-06 19:34:55 -07:00
602d78b76c Merge pull request #163 from memoryruins/guide
Guide: edits to the second half
2018-04-06 19:11:12 -07:00
18b706d4fb Guide: tweak to websocket and testing. 2018-04-06 19:44:52 -04:00
94b41fd484 Guide: tweak to database integration. 2018-04-06 19:42:18 -04:00
3a80cb7bf3 Guide: tweak to http/2. 2018-04-06 19:37:14 -04:00
e4a85a53f4 Guide: additional tweaks to Middleware. 2018-04-06 19:35:11 -04:00
1a45dbd768 Guide: additional tweak to testing chapter. 2018-04-06 19:26:07 -04:00
7cff5d9ade Guide: additional tweaks to request and response chapter. 2018-04-06 19:17:03 -04:00
c04e0fdec4 Merge branch 'master' into guide 2018-04-06 19:04:50 -04:00
0f0fe5f148 Guide: updates to the Database integration chapter. 2018-04-06 19:02:11 -04:00
e7f9f5b46d Guide: updates to HTTP/2 chapter. 2018-04-06 18:46:56 -04:00
c3fbba2678 Guide: updates to static file handling chapter. 2018-04-06 18:40:57 -04:00
a88e97edba Guide: updates to Middleware chapter. 2018-04-06 18:29:18 -04:00
1f08100f6f Guide: updates to the WebSockets chapter. 2018-04-06 18:04:42 -04:00
ab60ec6e1d Guide: updates to the Testing chapter. 2018-04-06 18:03:30 -04:00
0fbd05009d Guide: tweaks to the request and response chapter. 2018-04-06 17:31:18 -04:00
fdb7419e24 use actix-web from master 2018-04-06 14:11:04 -07:00
191b53bd7c pin futures 0.1 2018-04-06 13:22:27 -07:00
2d4ee0ee01 make Pause::new public 2018-04-06 12:34:24 -07:00
5bd5f67d79 add Pause message constructors 2018-04-06 12:31:31 -07:00
5d8cbccfe9 Remove article. 2018-04-06 15:12:06 -04:00
8d5fa6ee71 added Pause/Resume for client connector 2018-04-06 11:08:41 -07:00
084104d058 update doc strings for extractors 2018-04-06 10:24:57 -07:00
2c411a04a9 no need for export in doc example 2018-04-06 10:15:06 -07:00
af0c8d893d add shortcut method for client requests 2018-04-06 10:09:31 -07:00
691457fbfe update tests 2018-04-06 09:45:10 -07:00
2dafd9c681 do not re-export HttpServer from server module 2018-04-06 08:40:11 -07:00
12586db15c Merge pull request #160 from memoryruins/guide
Guide: edits to first half
2018-04-05 19:44:42 -07:00
b847bda8ca Merge branch 'master' into guide 2018-04-05 19:44:34 -07:00
2a543001e0 Tweaks to the URL Dispatch chapter. 2018-04-05 22:12:20 -04:00
0f86c596fa Tweaks to Errors chapter. 2018-04-05 21:54:39 -04:00
6c55501252 client connector wait timeout 2018-04-05 18:33:58 -07:00
961edfd21a Tweaks to the Handler chapter. 2018-04-05 21:30:52 -04:00
7f0de705a3 Tweaks to Server chapter. 2018-04-05 20:55:19 -04:00
c2ad65a61d Various tweaks to Application chapter. 2018-04-05 19:43:17 -04:00
3c93e0c654 Add newline for reading source. 2018-04-05 19:25:41 -04:00
a0f1ff7eb3 Add src directory to main.rs and list on first codeblock. 2018-04-05 19:21:29 -04:00
9f45cfe492 Expand note about actix. 2018-04-05 19:12:23 -04:00
46e6641528 Add repository hyperlink and trim repeat. 2018-04-05 18:46:36 -04:00
a3f124685a Remove redundant quickstart paragraph. 2018-04-05 18:32:04 -04:00
800f711cc1 add PayloadConfig 2018-04-04 21:13:48 -07:00
7be4b1f399 clippy warns 2018-04-04 20:24:09 -07:00
eeae0ddab4 start client timeout for response only 2018-04-04 20:15:47 -07:00
c1af59c618 update juniper example 2018-04-04 17:57:02 -07:00
d8a9606162 add connection limits to pool 2018-04-04 16:39:01 -07:00
8038a52287 run test coverage on beta 2018-04-04 08:12:33 -07:00
c273b7ac3f update json example 2018-04-04 08:08:31 -07:00
df21892b5b added extractor configuration 2018-04-03 22:06:18 -07:00
a255a6fb69 use build_response method 2018-04-03 17:37:17 -07:00
b693d5491b Merge pull request #157 from krircc/master
only use diesel::r2d2 feature. no need r2d2_diesel create
2018-04-03 08:18:16 -07:00
56a31ea0ee only use diesel::r2d2 feature. no need r2d2_diesel create 2018-04-03 22:37:53 +08:00
2a269f1111 update changes 2018-04-02 22:08:04 -07:00
fee30d6f47 fix doc test compatibility 2018-04-02 22:01:20 -07:00
476b1fb36a simplify DefaultHeaders middleware 2018-04-02 21:43:50 -07:00
3b93bff602 add ErrorHandlers middleware 2018-04-02 21:37:00 -07:00
d292c5023f add String and Bytes extractor 2018-04-02 16:19:18 -07:00
ef6f310060 update urlencoded example in guide 2018-04-02 15:08:49 -07:00
a6cbdde43f add extractor for Binary type; move all extractors to separate module 2018-04-02 14:55:42 -07:00
cbf4c61eb5 add urlencoded body extractor 2018-04-02 14:00:18 -07:00
280c8d87f8 expose ResourceType 2018-04-02 11:18:31 -07:00
83bf852192 Fix logger request duration calculation 2018-04-02 11:09:24 -07:00
9d39f441e9 Merge pull request #153 from rofrol/add-header-for-juniper-example
Add header for juniper example
2018-04-02 10:51:42 -07:00
03d851680b Merge branch 'master' into add-header-for-juniper-example 2018-04-02 10:51:35 -07:00
0ddd018214 Merge pull request #154 from dholbert/patch-1
Use https (not http) url for meritbadge
2018-04-02 10:50:17 -07:00
8219a7aebe Use https (not http) url for meritbadge
Right now this readme file uses an HTTP url to reference a meritbadge image, which ends up producing "broken https" UI on the crates.io page https://crates.io/crates/actix-web. This patch just upgrades this to an HTTPS url (which still works), to avoid that problem. (Literally a 1-character change, changing "http" to "https" in "http://meritbadge.herokuapp.com/actix-web")
2018-04-02 10:44:46 -07:00
6c906b08e1 match resource path before executing middlewares 2018-04-02 10:27:37 -07:00
220cbe40e5 Add header for juniper example 2018-04-02 19:10:33 +02:00
74d0656d27 update diesel example 2018-04-01 18:24:07 -07:00
17c27ef42d HttpRequest::resource() returns current matched resource 2018-04-01 17:37:22 -07:00
b2e771df2c use r2d2 for diesel example 2018-04-01 08:20:15 -07:00
a5a36ff194 update readme example 2018-03-31 17:15:44 -07:00
97e2bcd055 allow primitive types for Path extractor 2018-03-31 17:12:08 -07:00
23cfa649f4 update tests 2018-03-31 10:21:54 -07:00
8791c0f880 simplify With handlers 2018-03-31 09:58:33 -07:00
16c212f853 add extractors info to guide 2018-03-31 09:18:25 -07:00
3ee228005d rename Application 2018-03-31 00:16:55 -07:00
7a743fa6b5 update examples 2018-03-30 23:37:15 -07:00
44e3df82f6 simplify http response construction; deprecate httpcodes 2018-03-30 23:07:33 -07:00
8d8f6bedad update examples 2018-03-30 18:54:38 -07:00
9e751de707 re-arrange modules and exports 2018-03-30 17:31:18 -07:00
b16419348e add from HttpRequest to a HttpRequestBuilder 2018-03-30 14:30:24 -07:00
3ccaa04575 unhide AsyncResponder; remove unused code 2018-03-30 09:34:03 -07:00
d80b84c915 add test builder guide information 2018-03-29 19:23:45 -07:00
145010a2b0 use unreachable instead of panic 2018-03-29 15:55:27 -07:00
3e98177fad added State extractor 2018-03-29 15:41:13 -07:00
d24752d9bc update example in readme 2018-03-29 15:07:12 -07:00
92fe2e96de update doc strings 2018-03-29 15:00:18 -07:00
3cf54bc0fd proper serde deserializer implementation for path 2018-03-29 14:30:45 -07:00
86dd732704 use FromRequest instead of HttpRequestExtractor 2018-03-29 13:12:28 -07:00
dfd8f1058e move NormalizePath type to separate module 2018-03-29 11:39:21 -07:00
f5636f321b drop deprecated code 2018-03-29 11:06:44 -07:00
ae6c9cb7fa re-arrange exports, some doc string updates 2018-03-29 10:44:26 -07:00
32052c2750 update guide 2018-03-29 10:01:07 -07:00
7d6deab9fb drop request's extract_xxx methods 2018-03-29 09:26:01 -07:00
9e61c67128 do not re-export Version 2018-03-28 22:00:36 -07:00
13bb5f20d2 fix export name 2018-03-28 21:58:08 -07:00
d14991ec96 update doc strings 2018-03-28 21:49:50 -07:00
45dec8d0c0 optimize with and with2 method impls and tests 2018-03-28 21:33:40 -07:00
90e3aaaf8a fix router cannot parse Non-ASCII characters in URL #137 2018-03-28 16:10:58 -07:00
4f7d45ee9c remove unneeded import 2018-03-28 14:38:01 -07:00
e1d2536d85 remove unused code 2018-03-28 14:34:17 -07:00
65700281e8 add support for multiple extractors 2018-03-28 14:24:32 -07:00
80f6b93714 Merge pull request #138 from bwasty/guide
Guide: improve wording & style
2018-03-28 13:40:05 -07:00
5585465859 Merge branch 'master' into guide 2018-03-28 13:39:59 -07:00
368103dd09 guide: improve wording & style 2018-03-28 22:16:01 +02:00
df7ffe14f2 add PathAndQuery extractor 2018-03-28 11:20:06 -07:00
36161aba99 update Path and Query doc strings 2018-03-28 07:27:06 -07:00
9f5a91ae3c export types 2018-03-27 21:59:55 -07:00
4e61e0db34 mdbook 2018-03-27 21:33:35 -07:00
2dfccdd924 allow to fail nightly 2018-03-27 20:57:02 -07:00
4358da9926 refactor WithHandler trait 2018-03-27 20:33:24 -07:00
62fb75ff95 add Application::configure method, it simplifies configuration process 2018-03-27 11:16:02 -07:00
29a0feb415 fix guide example 2018-03-27 07:47:29 -07:00
dcc5eb7ace pass request as value 2018-03-26 23:34:31 -07:00
81f4e12a27 fix doc string test 2018-03-26 23:29:53 -07:00
2f60a4b89d add handler with exatractor 2018-03-26 23:10:31 -07:00
b03c7051ff add extractor info to the guide 2018-03-26 18:35:08 -07:00
8fff2c7595 remove Path and Query from public api 2018-03-26 18:18:38 -07:00
052d5f0bc5 silence AsciiExt deprecation warn 2018-03-26 16:12:25 -07:00
68cf32e848 add path and query extractors 2018-03-26 15:58:30 -07:00
a56e5113ee process transfer-encoding before content-length, fix tests on 32bit platform 2018-03-24 09:22:34 -07:00
5127b85672 Merge pull request #132 from andreevlex/spell-check-24-03
spelling check
2018-03-24 11:47:11 +03:00
2d80c5053d spelling check 2018-03-24 09:35:52 +03:00
d46854b315 bump version 2018-03-22 21:16:42 -07:00
47f836cd1b add helper method for response creation 2018-03-22 21:14:57 -07:00
449709dd7e add 0.5 sec deley before exit 2018-03-22 18:41:02 -07:00
5a25fd95f5 Fix panic on invalid URL characters #130 2018-03-22 18:08:12 -07:00
b942bcc4a6 Fix long client urls #129 2018-03-22 07:44:16 -07:00
1107fdec9d fix guide 2018-03-21 21:02:57 -07:00
04515e4697 update guide 2018-03-21 21:02:04 -07:00
93d99b5a49 Use more ergonomic actix_web::Error instead of http::Error for ClientRequestBuilder::body() 2018-03-21 20:19:31 -07:00
e49910cdab Use more ergonomic actix_web::Error instead of http::Error for HttpResponseBuilder::body() 2018-03-21 20:15:52 -07:00
e8a1850c79 add helper conversion from ClientResponse for HttpResponseBuilder 2018-03-21 20:04:35 -07:00
afb81b6b8f add convinience ClientRequest::build_from() from HttpRequest 2018-03-21 19:54:21 -07:00
4866a26578 make streaming method more ergonomic 2018-03-21 19:14:18 -07:00
2d75ced4ed fix client connection pooling 2018-03-21 11:51:08 -07:00
7bcc258b09 Use fast compression setting 2018-03-21 08:56:21 -07:00
d5fa0a9418 disable brotli if feature is not enabled, faster compression 2018-03-21 08:03:21 -07:00
ce6d237cc1 prepare 0.4.10 release 2018-03-20 15:53:39 -07:00
70caa2552b simplify httpresponse release 2018-03-20 15:51:19 -07:00
ee7d58dd7f disable h2 2018-03-20 12:35:44 -07:00
c4f4cadb43 Fix http/2 date header generation 2018-03-20 11:40:05 -07:00
978091cedb wake up io task when next chunk of data is needed 2018-03-20 11:37:13 -07:00
8198f5e10a Refactor TestServer configuration 2018-03-20 11:23:35 -07:00
6cd40df387 Fix server websockets big payloads support 2018-03-19 17:27:03 -07:00
35ee5d36d8 actix 0.5.5, ws test 2018-03-19 13:12:36 -07:00
e7ec0f9fd7 ws tests and proper write payload ref 2018-03-19 09:30:58 -07:00
f4a47ef71e allow set client request/ws timeout 2018-03-18 19:27:51 -07:00
6b1a79fab8 update example 2018-03-18 16:27:34 -07:00
ab73da4a1a use Error instead of InternalError for helper methods error::ErrorXXX 2018-03-18 14:18:47 -07:00
e0c8da567c various optimizations 2018-03-18 11:05:44 -07:00
c10dedf7e4 Merge pull request #124 from DoumanAsh/show_hidden
Show Request's hidden methods
2018-03-17 18:39:21 +03:00
ec192e0ab1 Show Request's hidden methods 2018-03-17 18:10:22 +03:00
6d792d9948 simplify h1 parse 2018-03-16 20:56:23 -07:00
1fe4315c94 use actix 0.5.4 2018-03-16 13:37:47 -07:00
381b90e9a1 bump version 2018-03-16 12:31:29 -07:00
2d18dba40a fix compilation 2018-03-16 12:28:08 -07:00
d2693d58a8 clippy warnings 2018-03-16 12:12:55 -07:00
84bf282c17 add basic client connection pooling 2018-03-16 12:04:01 -07:00
b15b5e5246 check number of available workers 2018-03-16 11:17:27 -07:00
52b3b0c362 Merge pull request #119 from DoumanAsh/default_static_files
Add default resource for StaticFiles
2018-03-16 20:12:07 +03:00
64c4cefa8f Merge branch 'master' into default_static_files 2018-03-16 09:31:36 -07:00
7e8b231f57 disable test 2018-03-16 09:13:36 -07:00
8a344d0c94 Add default resource for StaticFiles 2018-03-16 19:04:36 +03:00
4096089a3f allow to disable http/2 support 2018-03-16 08:48:44 -07:00
b16f2d5f05 proper check for actor context poll 2018-03-16 08:04:26 -07:00
5baf15822a always start actors 2018-03-16 07:46:27 -07:00
5368ce823e Merge pull request #123 from h416/patch-1
fix typo
2018-03-16 05:31:10 -07:00
4effdf065b fix typo 2018-03-16 19:03:16 +09:00
61970ab190 always poll stream or actor for the first time 2018-03-15 17:11:49 -07:00
484b00a0f9 Merge branch 'master' of github.com:actix/actix-web 2018-03-15 16:55:33 -07:00
73bf2068aa allow to use NamedFile with any request method 2018-03-15 16:55:22 -07:00
1cda949204 Merge pull request #122 from mockersf/test_qp
test for query parameters in client
2018-03-14 16:10:31 -07:00
ad6b823255 test for query parameters in client 2018-03-14 21:45:49 +01:00
0f064db31d Move brotli encoding to a feature 2018-03-13 17:21:22 -07:00
fd0bb54469 add debug formatter for ClientRequestBuilder 2018-03-13 15:09:05 -07:00
e27bbaa55c Update CHANGES.md 2018-03-13 13:15:21 -07:00
8a50eae1e2 Merge pull request #121 from glademiller/master
Send Query Parameters in client requests
2018-03-13 13:14:51 -07:00
38080f67b3 If no path is available from the URI request / 2018-03-13 13:35:11 -06:00
08504e0892 Move path call inline into write 2018-03-13 13:26:13 -06:00
401c0ad809 https://github.com/actix/actix-web/issues/120 - Send Query Parameters in client requests 2018-03-13 13:17:55 -06:00
b4b0deb7fa Wake payload reading task when data is available 2018-03-12 16:29:13 -07:00
05ff35d383 Fix server keep-alive handling 2018-03-12 16:16:17 -07:00
29c3e8f7ea update test 2018-03-12 10:19:09 -07:00
6657446433 Allow to set read buffer capacity for server request 2018-03-12 10:01:56 -07:00
46b9a9c887 update readme 2018-03-12 09:13:04 -07:00
b3cdb472d0 remove reserved state for h2 write if buffer is empty 2018-03-12 09:04:54 -07:00
31e1aab9a4 do not log WouldBlock error from socket accept 2018-03-12 09:02:15 -07:00
67f383f346 typo 2018-03-11 16:53:46 -07:00
49f5c335f6 better sleep on error 2018-03-11 16:52:20 -07:00
692e11a584 bump version 2018-03-11 16:40:25 -07:00
208117ca6f Merge pull request #118 from messense/feature/sockets-vec
Use Vec instead of HashMap to store sockets in HttpServer
2018-03-11 16:38:23 -07:00
3e276ac921 Merge branch 'master' into feature/sockets-vec 2018-03-11 16:38:17 -07:00
4af115a19c Fix steraming response handling for http/2 2018-03-11 16:37:44 -07:00
051703eb2c Fix connection get closed too early 2018-03-11 15:37:33 -07:00
31fbbd3168 Fix panic on unknown content encoding 2018-03-11 14:50:13 -07:00
fee1e255ac add comments 2018-03-11 10:10:30 -07:00
a4c933e56e update doc string 2018-03-11 09:36:54 -07:00
9ddf5a3550 better doc string for Either 2018-03-11 09:28:22 -07:00
9ab0fa604d Use Vec instead of HashMap to store sockets in HttpServer 2018-03-11 17:29:44 +08:00
6c709b33cc return error on write zero bytes 2018-03-10 10:42:46 -08:00
71b4c07ea4 Fix json content type detection 2018-03-10 10:27:29 -08:00
ac9eba8261 add api doc for Either 2018-03-10 10:12:44 -08:00
cad55f9c80 add Either responder 2018-03-10 09:39:43 -08:00
4263574a58 fix panic in cors if request does not contain origin header and send_wildcard is not set 2018-03-10 08:31:20 -08:00
84ef5ee410 Merge pull request #116 from messense/feature/from-usize-to-keepalive
Impl From<usize> and From<Option<usize>> for KeepAlive
2018-03-10 22:55:55 +08:00
598fb9190d rerun build if USE_SKEPTIC env var changed 2018-03-10 17:53:11 +08:00
9a404a0c03 Impl From<usize> and From<Option<usize>> for KeepAlive 2018-03-10 17:52:50 +08:00
3dd8fdf450 fix guide 2018-03-09 21:40:51 -08:00
05f5ba0084 refactor keep-alive; fixed write to socket for upgraded connection 2018-03-09 16:21:14 -08:00
8169149554 update wstool 2018-03-09 13:12:25 -08:00
8d1de6c497 ws client timeouts 2018-03-09 13:12:14 -08:00
caaace82e3 export symbols 2018-03-09 13:03:15 -08:00
02dd5375a9 aling mask to 8 bytes 2018-03-09 10:25:47 -08:00
717602472a clippy warnings 2018-03-09 10:11:38 -08:00
b56be8e571 write buffer capacity for client 2018-03-09 10:09:13 -08:00
2853086463 add write buffer capacity config 2018-03-09 10:00:15 -08:00
e2107ec6f4 use small vec on hot path 2018-03-09 08:00:44 -08:00
c33caddf57 update tests 2018-03-09 05:50:47 -08:00
db1e04e418 prepare release 2018-03-09 05:42:42 -08:00
f8b8fe3865 add space to cookie header 2018-03-09 05:38:07 -08:00
1c6ddfd34c naming 2018-03-09 05:36:40 -08:00
49e007ff2a move protobuf support to the example 2018-03-09 05:29:06 -08:00
2068eee669 update readme 2018-03-08 20:58:05 -08:00
f3c63e631a add protobuf feature 2018-03-08 20:56:18 -08:00
3f0803a7d3 Merge branch 'master' of github.com:actix/actix-web 2018-03-08 20:39:10 -08:00
f12b613211 more ws optimizations 2018-03-08 20:39:05 -08:00
695c052c58 Merge pull request #115 from kingxsp/master
Add protobuf support
2018-03-08 18:59:18 -08:00
63634be542 Merge branch 'master' into master 2018-03-09 10:22:15 +08:00
f88f1c65b6 update tests 2018-03-08 18:19:46 -08:00
a0b589eb96 Add protobuf support 2018-03-09 10:05:13 +08:00
ebdc983dfe optimize websocket stream 2018-03-08 17:19:50 -08:00
395243a539 another attempt to fix cookie handling 2018-03-08 11:16:54 -08:00
1ab676d7eb bump version and add some tests 2018-03-07 22:40:46 -08:00
47f01e5b7e update doc string 2018-03-07 21:39:20 -08:00
ffb89935b6 update all features 2018-03-07 21:37:42 -08:00
77a111b95c prepare release 2018-03-07 21:28:54 -08:00
6c0fb3a7d2 handle panics in worker threads 2018-03-07 21:10:53 -08:00
824244622f update test 2018-03-07 17:42:57 -08:00
42d2a29b1d non-blocking processing for NamedFile 2018-03-07 17:40:13 -08:00
af8875f6ab sleep on accept socket error 2018-03-07 15:52:05 -08:00
1db1ce1ca3 one more cookie handling fix 2018-03-07 15:41:46 -08:00
f55ef3a059 create default CpuPool 2018-03-07 14:56:53 -08:00
67bf0ae79f fix HttpServer::listen method 2018-03-07 14:46:12 -08:00
b06cf32329 Merge pull request #114 from DancingBard/master
BoyScoutRule: Fixed typo
2018-03-07 13:07:10 -08:00
7cce29b633 BoyScoutRule: Fixed typo 2018-03-07 21:54:25 +01:00
c26d9545a5 map connector timeout error 2018-03-07 12:09:53 -08:00
b950d6997d add csrf link to readme 2018-03-07 11:31:02 -08:00
0bf29a522b Allow to use std::net::TcpListener for HttpServer 2018-03-07 11:28:44 -08:00
24342fb745 Merge pull request #113 from niklasf/csrf-upgrade
Let CSRF filter catch cross-site upgrades
2018-03-07 09:58:30 -08:00
0278e364ec add tests for csrf upgrade filter 2018-03-07 18:42:21 +01:00
b9d6bbd357 filter cross-site upgrades in csrf middleware 2018-03-07 17:49:30 +01:00
5816ecd1bc fix variable name: cors -> csrf 2018-03-07 17:44:19 +01:00
2ff55ee1c5 Update CHANGES.md 2018-03-07 06:14:44 -08:00
b42de6c41f Merge pull request #111 from adwhit/cookie-handling
Fix client cookie handling
2018-03-07 06:13:02 -08:00
9e0e081c90 Merge branch 'master' into cookie-handling 2018-03-07 06:12:37 -08:00
178f5a104e Merge pull request #110 from messense/feature/tools-actix
Use actix from crates.io in tools/wsload
2018-03-07 06:10:18 -08:00
1e42f9575a Merge branch 'master' into feature/tools-actix 2018-03-07 06:09:09 -08:00
24dfcf1303 Merge pull request #109 from kingoflolz/master
make session an optional feature
2018-03-07 06:04:55 -08:00
6cc3aaef1b add client cookie handling test 2018-03-07 11:43:55 +00:00
436a16a2c8 Use actix from crates.io in tools/wsload 2018-03-07 19:26:23 +08:00
9afad5885b fix client cookie handling 2018-03-07 09:48:34 +00:00
04d0abb3c7 make session an optional feature 2018-03-07 15:38:58 +08:00
1e5daa1de8 update changes 2018-03-06 23:04:18 -08:00
d3c859f9f3 bump version 2018-03-06 22:44:06 -08:00
c1419413aa Fix client cookie support 2018-03-06 22:36:34 -08:00
acd33cccbb add tls 2018-03-06 17:34:46 -08:00
57a1d68f89 add client response timeout 2018-03-06 17:04:48 -08:00
5c88441cd7 Merge pull request #108 from glademiller/feature/allow_connection_timeout_to_be_set
Allow connection timeout to be set
2018-03-06 15:18:31 -08:00
6a3c5c4ce0 Merge branch 'master' into feature/allow_connection_timeout_to_be_set 2018-03-06 15:18:25 -08:00
14a511bdad use IntoHeaderValue and Header for client request 2018-03-06 15:18:04 -08:00
e4ed53d691 Merge branch 'feature/allow_connection_timeout_to_be_set' of https://github.com/glademiller/actix-web into feature/allow_connection_timeout_to_be_set 2018-03-06 15:44:18 -07:00
5bf4f3be8b Actix dependency needs to be updated to master 2018-03-06 15:43:56 -07:00
6b9e51740b Merge branch 'master' into feature/allow_connection_timeout_to_be_set 2018-03-06 15:28:31 -07:00
be7e8d159b Allow connection timeout to be set 2018-03-06 15:26:09 -07:00
ceb97cd6b9 Merge pull request #106 from niklasf/starting-url
give a url in the log when starting
2018-03-06 11:41:42 -08:00
85b650048d give a url in the log when starting 2018-03-06 20:37:18 +01:00
a0e6313d56 Fix compression #103 and #104 2018-03-06 11:02:03 -08:00
9cc6f6b1e4 Merge pull request #102 from mockersf/gzip
add tests with large random bodies for gzip
2018-03-06 08:48:06 -08:00
526753ee88 update tests for stable compiler 2018-03-06 07:56:43 -08:00
779e773185 add tests with large random bodies for gzip 2018-03-06 14:26:48 +01:00
7eb310f8ce fix guide 2018-03-06 00:44:45 -08:00
d573cf2d97 Merge branch 'master' of github.com:actix/actix-web 2018-03-06 00:43:34 -08:00
32b5544ad9 port hyper header 2018-03-06 00:43:25 -08:00
e182ed33b1 add Header trait 2018-03-05 19:28:42 -08:00
6f1836f80e Merge pull request #98 from flip111/patch-2
Update qs_14.md
2018-03-05 16:48:47 -08:00
5b530f11b5 Update qs_14.md
fix missing semicolon
2018-03-06 01:46:16 +01:00
0c30057c8c move headers to separate module; allow custom HeaderValue conversion 2018-03-05 16:45:54 -08:00
6078344ecc Merge pull request #97 from flip111/patch-1
Update qs_14.md
2018-03-05 16:36:32 -08:00
67f5a949a4 Update qs_14.md
fix syntax error on use statement
2018-03-06 01:35:41 +01:00
05e49e893e allow only GET and HEAD for NamedFile 2018-03-05 14:04:30 -08:00
c8844425ad Enable compression support for NamedFile 2018-03-05 13:31:30 -08:00
b282ec106e Add ResponseError impl for SendRequestError 2018-03-05 13:02:31 -08:00
ea2a8f6908 add http proxy example 2018-03-05 11:12:19 -08:00
2b942ec5f2 add uds example readme 2018-03-05 09:47:17 -08:00
bf77be0337 Merge pull request #95 from messense/feature/unix-socket-example
Add unix domain socket example
2018-03-05 09:37:00 -08:00
c2741054bb Add unix domain socket example 2018-03-05 22:14:25 +08:00
e708f51156 prep release 2018-03-04 20:28:06 -08:00
cbb821148b explicitly set tcp nodelay 2018-03-04 20:14:58 -08:00
d6b021e185 Merge pull request #94 from messense/feature/str-repeat
Use str::repeat
2018-03-04 19:57:49 -08:00
0adb7e8553 Use str::repeat 2018-03-05 09:54:58 +08:00
dbfa1f0ac8 fix example 2018-03-04 10:44:41 -08:00
11347e3c7d Allow to use Arc<Vec<u8>> as response/request body 2018-03-04 10:33:18 -08:00
631fe72a46 websockets text() is more generic 2018-03-04 10:18:42 -08:00
f673dba759 Fix handling of requests with an encoded body with a length > 8192 #93 2018-03-04 09:48:34 -08:00
ab978a18ff unix only test 2018-03-03 18:50:00 -08:00
327df159c6 prepare release 2018-03-03 18:46:22 -08:00
2ccbd5fa18 fix socket polling 2018-03-03 12:17:26 -08:00
058630d041 simplify channels list management 2018-03-03 11:16:55 -08:00
f456be0309 simplify linked nodes 2018-03-03 10:06:13 -08:00
9bd6cb03ac Merge branch 'master' of github.com:actix/actix-web 2018-03-03 09:29:46 -08:00
16afeda79c update changes 2018-03-03 09:29:36 -08:00
83fcdfd91f fix potential bug in payload processing 2018-03-03 09:27:54 -08:00
8f94ae41cc Merge pull request #90 from rvlzzr/master
move reuse_address before bind
2018-03-02 23:08:33 -08:00
4e41347de8 move reuse_address before bind 2018-03-02 22:57:11 -08:00
6acb6dd4e7 set release date 2018-03-02 22:31:58 -08:00
791a980e2d update tests 2018-03-02 22:08:56 -08:00
c2d8abcee7 Fix disconnect on idle connections 2018-03-02 20:47:23 -08:00
16c05f07ba make HttpRequest::match_info_mut() public 2018-03-02 20:40:08 -08:00
2158ad29ee add Pattern::with_prefix, make it usable outside of actix 2018-03-02 20:39:22 -08:00
feba5aeffd bump version 2018-03-02 14:31:23 -08:00
343888017e Update CHANGES.md 2018-03-02 12:26:31 -08:00
3a5d445b2f Merge pull request #89 from niklasf/csrf-middleware
add csrf filter middleware
2018-03-02 12:25:23 -08:00
e60acb7607 Merge branch 'master' into csrf-middleware 2018-03-02 12:25:05 -08:00
bebfc6c9b5 sleep for test 2018-03-02 11:32:37 -08:00
3b2928a391 Better naming for websockets implementation 2018-03-02 11:29:55 -08:00
10f57dac31 add csrf filter middleware 2018-03-02 20:13:43 +01:00
b640b49b05 adjust low buf size 2018-03-01 20:13:50 -08:00
1fea4bd9a6 prepare release 2018-03-01 20:01:25 -08:00
206c4e581a rename httpcodes 2018-03-01 19:12:59 -08:00
4e13505b92 rename .p to a .filter 2018-03-01 18:42:50 -08:00
5b6d7cddbf Fix payload parse in situation when socket data is not ready 2018-03-01 18:27:04 -08:00
4aaf9f08f8 update readme 2018-02-28 22:31:54 -08:00
b0ba23ff55 Merge pull request #88 from rofrol/patch-2
be consistent with host - had CORS preflight once
2018-02-28 17:07:57 -08:00
42b19b1819 Merge branch 'master' into patch-2 2018-02-28 17:07:44 -08:00
0335fde3f9 Update README.md 2018-02-28 16:58:05 -08:00
f27edbff89 be consistent with host - had CORS preflight once 2018-03-01 01:01:27 +01:00
d62d6e68e0 use new version of http crate 2018-02-28 14:16:55 -08:00
1284264511 Update CHANGES.md 2018-02-28 12:35:16 -08:00
d977fe563b Merge pull request #87 from adwhit/fix-session-set
fix session mut borrow lifetime
2018-02-28 12:34:46 -08:00
bb68f9dd90 add session borrow fix to changes 2018-02-28 19:52:53 +00:00
313396d9b5 fix session mut borrow lifetime 2018-02-28 19:35:26 +00:00
171a23561e export Drain 2018-02-28 11:10:54 -08:00
67f33a4760 add redis session example 2018-02-28 10:26:40 -08:00
764421fe44 update categories 2018-02-27 23:51:57 -08:00
b339ea0a3a update versions in guide 2018-02-27 23:31:43 -08:00
8994732227 doc strings 2018-02-27 23:30:26 -08:00
7591592279 fix handle big data chunkd for parsing 2018-02-27 23:04:57 -08:00
4a48b43927 big value 2018-02-27 21:49:08 -08:00
b1ad4763a2 check juniper example 2018-02-27 21:23:41 -08:00
2f3a2115c0 Merge pull request #86 from pyros2097/master
add juniper example
2018-02-27 21:21:52 -08:00
1283c00583 add juniper example 2018-02-28 10:41:24 +05:30
9f81eae215 build docs on nightly 2018-02-27 21:04:22 -08:00
ccb6ebb259 headers test 2018-02-27 20:49:53 -08:00
da76de76f0 upgrade sha crate 2018-02-27 20:32:51 -08:00
c316a99746 stop server test 2018-02-27 20:04:01 -08:00
1e2aa4fc90 mark context as modified after writing data 2018-02-27 18:05:06 -08:00
e2c8f17c2c drop connection if handler get dropped without consuming payload 2018-02-27 16:08:57 -08:00
9b06eac720 Merge branch 'master' of github.com:actix/actix-web 2018-02-27 15:41:53 -08:00
4f99cd1580 add ws error tracing 2018-02-27 15:38:57 -08:00
f56fa49a9b Merge pull request #84 from mpaltun/patch-1
Fix typos in README
2018-02-27 15:18:16 -08:00
1f063e4136 move with_connector method to ClientRequestBuilder 2018-02-27 15:14:33 -08:00
33c935dccc Fix typos in README 2018-02-28 01:13:59 +02:00
a7bf635158 unify headers and body processing for client response and server request 2018-02-27 15:03:28 -08:00
aac9b5a97c update readme 2018-02-27 12:49:11 -08:00
6c480fae90 added HttpRequest::encoding() method; fix urlencoded parsing with charset 2018-02-27 11:31:54 -08:00
5dcb558f50 refactor websockets handling 2018-02-27 10:09:24 -08:00
a344c3a02e remove read buffer management api 2018-02-26 20:07:22 -08:00
0ab8bc11f3 fix guide example 2018-02-26 16:41:57 -08:00
abae65a49e remove unused code 2018-02-26 16:11:00 -08:00
d6fd4a3524 use buffer capacity; remove unused imports 2018-02-26 15:34:25 -08:00
72aa2d9eae clippy warnings 2018-02-26 14:33:56 -08:00
644f1a9518 refactor ws frame parser 2018-02-26 13:58:23 -08:00
56ae565688 fix guide examples 2018-02-26 08:02:58 -08:00
0a3b776aa7 refactor multipart stream 2018-02-26 06:00:54 +03:00
6ef9c60361 add Read and AsyncRead impl to HttpRequest 2018-02-25 21:26:58 +03:00
a2b98b31e8 refactor payload related futures for HttpRequest 2018-02-25 20:34:26 +03:00
ab5ed27bf1 refactor and simplify content encoding 2018-02-25 11:43:00 +03:00
141b992450 Make payload and httprequest a stream 2018-02-25 11:21:45 +03:00
4e41e13baf refactor client payload processing 2018-02-25 11:18:17 +03:00
ea8e8e75a2 fix websocket example 2018-02-24 08:41:58 +03:00
a855c8b2c9 better ergonomics for WsClient::client() 2018-02-24 08:14:21 +03:00
fd31eb74c5 better ergonomics for ws client 2018-02-24 07:36:50 +03:00
3b22b1b168 Merge pull request #78 from pyros2097/master
Fix websocket example path
2018-02-23 01:47:40 -08:00
7a7df7f8fb Merge branch 'master' into master 2018-02-23 01:47:30 -08:00
25aabfb3e2 fix big ws frames 2018-02-23 10:45:33 +01:00
3a3657cfaf Update qs_9.md 2018-02-23 12:39:19 +05:30
aff43cc8b8 fix routes registration order 2018-02-22 05:48:18 -08:00
4a9c1ae894 allow to use Connection for sending client request 2018-02-21 22:53:23 -08:00
4a07430e8e remove RegexSet mention 2018-02-21 22:04:59 -08:00
9a076c69d1 update route matching guide section 2018-02-21 22:00:22 -08:00
8f2d3a0a76 fix NormalizePath helper 2018-02-21 14:53:42 -08:00
d4611f8bb9 Merge branch 'master' of github.com:actix/actix-web 2018-02-21 14:31:31 -08:00
fd56e5dc82 do not use regset for route recognition 2018-02-21 14:31:22 -08:00
7c74259453 Merge pull request #77 from rofrol/patch-1
could used -> could be used, latest actix sync
2018-02-21 10:00:08 -08:00
6a01af32bc could used -> could be used, latest actix sync 2018-02-21 18:59:00 +01:00
5634e5794f more tests for NormalizePath helper 2018-02-20 13:03:21 -08:00
187644e178 update logger doc string 2018-02-20 12:53:51 -08:00
7198dde465 add logger info 2018-02-20 12:49:42 -08:00
2374aa42ed set date header for client requests 2018-02-19 23:18:18 -08:00
03912d2089 support client request's async body 2018-02-19 22:48:27 -08:00
3f95cce9e8 allow to pass different binary data 2018-02-19 20:03:57 -08:00
979cea03ac added TestRequest::set_payload() 2018-02-19 20:01:38 -08:00
6424defee6 code coverage on 1.21 2018-02-19 17:32:22 -08:00
6ee14efbe2 optimize http message serialization 2018-02-19 17:21:04 -08:00
4d81186059 escape router pattern re 2018-02-19 14:57:57 -08:00
ddc82395e8 try to remove trailing slash for normalize path handler 2018-02-19 14:27:36 -08:00
360ffbba68 clone router with httprequest 2018-02-19 14:26:51 -08:00
f2f1798215 allow to send request using custom connector 2018-02-19 13:41:21 -08:00
548f4e4d62 replace reqwest with actix::client 2018-02-19 13:18:18 -08:00
cb70d5ec3d refactor http client 2018-02-19 03:11:11 -08:00
edd114f6e4 allow to set default content encoding on application level 2018-02-18 22:23:17 -08:00
816c6fb0e0 log 5xx responses as error 2018-02-18 09:57:57 -08:00
0da382a7a4 use actix 0.5 release 2018-02-17 13:33:38 -08:00
3e3d3279b8 deregister server socket on shutdown 2018-02-16 09:42:15 -08:00
3c95823e53 add r2d2 example 2018-02-15 23:05:10 -08:00
8607c51bcf do not stop accept thread on error 2018-02-15 22:02:03 -08:00
080bb3e5ae disable dead code link 2018-02-15 16:25:43 -08:00
d31e71a169 update examples 2018-02-15 13:59:25 -08:00
7b0e1642b6 add techempower benchmark link 2018-02-15 09:53:09 -08:00
096dee519c Merge pull request #71 from rbtcollins/patch-1
Wait for spawned thread
2018-02-13 14:58:11 -08:00
8bce3b9d10 Merge branch 'master' into patch-1 2018-02-13 14:57:59 -08:00
b28ecbcf0c Update qs_2.md 2018-02-14 10:37:12 +13:00
8f9ec5c23c fix doc test 2018-02-13 07:50:49 -08:00
96b87761d1 update examples 2018-02-12 23:13:06 -08:00
b1eec3131f use newer api 2018-02-12 22:56:47 -08:00
a544034c06 use Recipient 2018-02-12 22:09:31 -08:00
4b8181476c consistently use #[cause] and display causing errors (#73) 2018-02-12 23:55:44 -06:00
eb041de36d update examples 2018-02-12 19:15:39 -08:00
80285f2a32 fix doc test 2018-02-12 18:38:13 -08:00
de869ed879 Merge pull request #72 from rbtcollins/patch-2
Use AtomicUsize properly
2018-02-12 17:46:03 -08:00
7ccacb92ce update websocket-chat example 2018-02-12 17:42:10 -08:00
57655d8153 Use AtomicUsize properly
doing a read+write on an atomic int will lose updates from other threads.
2018-02-13 13:47:59 +13:00
335ca8ff33 use new actix api 2018-02-12 16:08:04 -08:00
720d8c36c1 update names 2018-02-12 12:45:08 -08:00
8c1b5fa945 sync with latest actix 2018-02-12 12:17:30 -08:00
232aba2080 Wait for spawned thread
A spawned thread doesn't block the main thread exiting unless explicitly joined.
The demo as written in the guide simply exits immediately at the moment.
2018-02-12 23:52:03 +13:00
30bdf9cb5e update actix api 2018-02-12 01:13:06 -08:00
285c66e7d8 build docs for apln and tls features 2018-02-10 11:39:12 -08:00
856055c6ca simplify HttpServer::start_tls() method 2018-02-10 11:34:54 -08:00
e3081306da update doc string 2018-02-10 11:29:40 -08:00
94c4053cb5 more HttpServer type simplification 2018-02-10 11:01:54 -08:00
762961b0f4 simplify HttpServer type definition 2018-02-10 10:22:03 -08:00
3109f9be62 special handling for upgraded pipeline 2018-02-10 00:05:20 -08:00
2d049e4a9f update example 2018-02-09 22:46:34 -08:00
0c98775b51 refactor h1 stream polling 2018-02-09 22:26:48 -08:00
b4b5c78b51 optimize ws frame generation 2018-02-09 20:43:14 -08:00
78da98a16d add wsload tool; optimize ws frame parser 2018-02-09 17:20:28 -08:00
74377ef73d fix back pressure for h1 import stream 2018-02-09 16:20:10 -08:00
728377a447 fix example 2018-02-08 20:55:34 -08:00
73ed1342eb more actix compatibility 2018-02-08 17:13:56 -08:00
bc6300be34 actix compatibility 2018-02-08 17:08:57 -08:00
2faf3a5eb6 fix deprecation warnings, update actix 2018-02-08 17:00:22 -08:00
6181a84d7b update websocket-chat example 2018-02-08 14:03:41 -08:00
f8f99ec0c7 Disable signals in HttpServers started by the tests. (#69)
Something is wrong with signals on windows.
This change causes the unit tests to pass on Windows.
2018-02-08 14:55:47 -06:00
bd03ba1192 Update most examples to use actix from git (#68) 2018-02-08 00:08:36 -06:00
d0cbf7cd25 upgrade trust-dns-resolver 2018-02-07 14:58:08 -08:00
93aa220e8d remove default impl for std error, it prevents use of Fail 2018-02-07 13:57:58 -08:00
81e4fb9353 Avoid using Path to calculate URIs, because it doesn't do the right thing on Windows (#67)
Redirecting to index files now always uses `/` instead of backslash on windows.
2018-02-07 15:31:09 -06:00
884ea02f5c Allow returning failure::Error from handlers (#65)
This implements From<failure::Error> for Error (by way of `failure::Compat`)
and ResponseError for failure::Compat<T>.
2018-02-06 10:26:50 -06:00
b6d5516e3a remove rust_backtrace for appveyor 2018-02-04 10:48:16 -08:00
46841cc87e update config for appveyor 2018-02-04 10:31:39 -08:00
7ad66956b2 add HttpRequest::uri_mut(), allows to modify request uri 2018-02-03 08:31:32 -08:00
d568161852 update websocket-chat example 2018-02-03 08:25:31 -08:00
671ab35cf6 re enable 1.21 2018-02-02 21:32:43 -08:00
c63ad4b6f1 appveyor cfg 2018-02-02 21:31:16 -08:00
eb713bd60e update actix version 2018-02-01 01:08:08 -08:00
2b74fbf586 fix websocket example 2018-01-31 13:18:30 -08:00
58f85658bd update actix 2018-01-31 12:57:02 -08:00
7e9fbfca72 missing http codes 2018-01-31 12:34:58 -08:00
5115384501 Merge pull request #64 from andreevlex/fix-2
spelling check
2018-01-31 10:54:05 -08:00
a1b96b1cf4 return "chnked" value 2018-01-31 21:37:12 +03:00
a565e71018 spelling check 2018-01-31 20:28:53 +03:00
e41b175e3d Update README.md 2018-01-31 06:40:00 -08:00
db39f122be Update README.md 2018-01-31 06:37:37 -08:00
afd2dc4666 Update README.md 2018-01-31 06:36:15 -08:00
cba7e426a5 Update README.md 2018-01-31 06:35:47 -08:00
01e7cc9620 Update README.md 2018-01-31 06:34:50 -08:00
5a5497b745 add close ws test 2018-01-30 16:04:04 -08:00
b698e3546b link to websocket example 2018-01-30 15:26:58 -08:00
e99a5e8144 drop local actix ref 2018-01-30 15:19:30 -08:00
577f91206c added support for websocket testing 2018-01-30 15:13:33 -08:00
76f9542df7 rename module 2018-01-30 13:04:52 -08:00
9739168d48 fix limit usage for Request/Response Body future 2018-01-30 12:44:14 -08:00
5cbaf3a1b8 add client ssl support 2018-01-30 11:17:17 -08:00
a02e0dfab6 initial work on client connector 2018-01-29 23:01:20 -08:00
5cc3bba5cc change ws client names 2018-01-29 15:45:37 -08:00
6e51573975 app veyor config 2018-01-29 14:51:34 -08:00
b686f39d0b complete impl for client request and response 2018-01-29 14:44:25 -08:00
6416a796c3 add ClientRequest and ClientRequestBuilder 2018-01-29 11:45:33 -08:00
b6a394a113 added StaticFiles::inex_file config 2018-01-29 03:23:45 -08:00
456fd1364a add handle method to contexts 2018-01-28 09:47:46 -08:00
f3cce6a04c update websocket example 2018-01-28 09:07:12 -08:00
9835a4537a update websocket example 2018-01-28 08:58:18 -08:00
715ec4ae2f update actix 2018-01-28 08:26:36 -08:00
55b2fb7f77 update example 2018-01-28 01:04:58 -08:00
7c7743c145 use right path 2018-01-27 22:52:17 -08:00
826fc62299 disable websocket-chat example 2018-01-27 22:44:50 -08:00
5dd2e7523d basic websocket client 2018-01-27 22:03:03 -08:00
4821d51167 fix actix compatibility 2018-01-27 11:15:03 -08:00
c446be48e3 min rust version 1.21 2018-01-27 10:58:09 -08:00
042f8391bb Merge branch 'master' of github.com:actix/actix-web 2018-01-27 10:05:07 -08:00
d4bc3294a3 actix compatibility 2018-01-27 10:04:56 -08:00
04d53d6f57 Merge pull request #59 from andreevlex/fix-cors
spelling check cors example
2018-01-26 21:42:36 -08:00
881e0e0346 spelling check 2018-01-27 08:38:17 +03:00
b9f8a00ba3 update cors example readme 2018-01-26 19:56:34 -08:00
99bed67bec rename cors example 2018-01-26 19:52:20 -08:00
52a454800f cleanup cors example 2018-01-26 19:51:13 -08:00
c09c8e4980 Merge pull request #58 from krircc/master
add actix-web-cors example
2018-01-26 19:43:40 -08:00
b931dda1fe Merge branch 'master' into master 2018-01-26 19:42:06 -08:00
74166b4834 add actix-web-cors example 2018-01-27 11:00:26 +08:00
4abb769ee5 fix request json loader; mime_type() method 2018-01-25 21:50:28 -08:00
e8e2ca1526 refactor alpn support; upgrade openssl to 0.10 2018-01-25 10:24:04 -08:00
78967dea13 stop http context immediately 2018-01-24 20:17:14 -08:00
58a5d493b7 re-eanble write backpressure for h1 connections 2018-01-24 20:12:49 -08:00
c5341017cd fix typo 2018-01-23 15:39:53 -08:00
f4873fcdee stop websocket context 2018-01-23 15:35:39 -08:00
35efd017bb impl waiting for HttpContext 2018-01-23 09:42:04 -08:00
fb76c490c6 mention tokio handle in guide 2018-01-22 20:10:05 -08:00
3653c78e92 check example on stable 2018-01-22 19:49:19 -08:00
1053c44326 pin new actix version 2018-01-22 17:01:54 -08:00
e6ea177181 impl WebsocketContext::waiting() method 2018-01-22 16:55:50 -08:00
1957469061 code of conduct 2018-01-21 15:29:02 -08:00
196 changed files with 37358 additions and 15425 deletions

View File

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

View File

@ -1,5 +1,5 @@
language: rust
sudo: false
sudo: required
dist: trusty
cache:
@ -8,83 +8,50 @@ cache:
matrix:
include:
- rust: 1.20.0
- rust: stable
- rust: beta
- rust: nightly
allow_failures:
- rust: nightly
- rust: beta
#rust:
# - 1.20.0
# - stable
# - beta
# - nightly-2018-01-03
env:
global:
- RUSTFLAGS="-C link-dead-code"
# - RUSTFLAGS="-C link-dead-code"
- OPENSSL_VERSION=openssl-1.0.2
before_install:
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
- sudo apt-get update -qq
- sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
# Add clippy
before_script:
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
( ( cargo install clippy && export CLIPPY=true ) || export CLIPPY=false );
fi
- export PATH=$PATH:~/.cargo/bin
script:
- |
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then
cargo clean
USE_SKEPTIC=1 cargo test --features=alpn
else
cargo clean
cargo test
# --features=alpn
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then
cd examples/basics && cargo check && cd ../..
cd examples/hello-world && cargo check && cd ../..
cd examples/multipart && cargo check && cd ../..
cd examples/json && cargo check && cd ../..
cd examples/template_tera && cargo check && cd ../..
cd examples/diesel && cargo check && cd ../..
cd examples/tls && cargo check && cd ../..
cd examples/websocket-chat && cargo check && cd ../..
cd examples/websocket && cargo check && cd ../..
cargo check --features rust-tls
cargo check --features ssl
cargo check --features tls
cargo test --features="ssl,tls,rust-tls" -- --nocapture
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
cargo clippy
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin
RUST_BACKTRACE=1 cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi
# Upload docs
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then
cargo doc --features alpn --no-deps &&
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
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 &&
cargo install mdbook &&
cd guide && mdbook build -d ../target/doc/guide && cd .. &&
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 &&
echo "Uploaded documentation"
fi
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi

View File

@ -1,5 +1,676 @@
# Changes
## [0.7.13] - 2018-10-14
### Fixed
* Fixed rustls support
* HttpServer not sending streamed request body on HTTP/2 requests #544
## [0.7.12] - 2018-10-10
### Changed
* Set min version for actix
* Set min version for actix-net
## [0.7.11] - 2018-10-09
### Fixed
* Fixed 204 responses for http/2
## [0.7.10] - 2018-10-09
### Fixed
* Fixed panic during graceful shutdown
## [0.7.9] - 2018-10-09
### Added
* Added client shutdown timeout setting
* Added slow request timeout setting
* Respond with 408 response on slow request timeout #523
### Fixed
* HTTP1 decoding errors are reported to the client. #512
* Correctly compose multiple allowed origins in CORS. #517
* Websocket server finished() isn't called if client disconnects #511
* Responses with the following codes: 100, 101, 102, 204 -- are sent without Content-Length header. #521
* Correct usage of `no_http2` flag in `bind_*` methods. #519
## [0.7.8] - 2018-09-17
### Added
* Use server `Keep-Alive` setting as slow request timeout #439
### Changed
* Use 5 seconds keep-alive timer by default.
### Fixed
* Fixed wrong error message for i16 type #510
## [0.7.7] - 2018-09-11
### Fixed
* Fix linked list of HttpChannels #504
* Fix requests to TestServer fail #508
## [0.7.6] - 2018-09-07
### Fixed
* Fix system_exit in HttpServer #501
* Fix parsing of route param containin regexes with repetition #500
### Changes
* Unhide `SessionBackend` and `SessionImpl` traits #455
## [0.7.5] - 2018-09-04
### Added
* Added the ability to pass a custom `TlsConnector`.
* Allow to register handlers on scope level #465
### Fixed
* Handle socket read disconnect
* Handling scoped paths without leading slashes #460
### Changed
* Read client response until eof if connection header set to close #464
## [0.7.4] - 2018-08-23
### Added
* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`,
accept backpressure #250
* Allow to customize connection handshake process via `HttpServer::listen_with()`
and `HttpServer::bind_with()` methods
* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472
### Changed
* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`.
`Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
* native-tls - 0.2
* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461
### Fixed
* Use zlib instead of raw deflate for decoding and encoding payloads with
`Content-Encoding: deflate`.
* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436
* Fix adding multiple response headers #446
* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448
* Panic during access without routing being set #452
* Fixed http/2 error handling
### Deprecated
* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or
`RustlsAcceptor::with_flags()` instead
* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been
deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`.
* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been
deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`.
## [0.7.3] - 2018-08-01
### Added
* Support HTTP/2 with rustls #36
* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433
### Fixed
* Fixed failure 0.1.2 compatibility
* Do not override HOST header for client request #428
* Gz streaming, use `flate2::write::GzDecoder` #228
* HttpRequest::url_for is not working with scopes #429
* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436
## [0.7.2] - 2018-07-26
### Added
* Add implementation of `FromRequest<S>` for `Option<T>` and `Result<T, Error>`
* Allow to handle application prefix, i.e. allow to handle `/app` path
for application with `/app` prefix.
Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix)
api doc.
* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies
### Changed
* Upgrade to cookie 0.11
* Removed the timestamp from the default logger middleware
### Fixed
* Missing response header "content-encoding" #421
* Fix stream draining for http/2 connections #290
## [0.7.1] - 2018-07-21
### Fixed
* Fixed default_resource 'not yet implemented' panic #410
## [0.7.0] - 2018-07-21
### Added
* Add `fs::StaticFileConfig` to provide means of customizing static
file services. It allows to map `mime` to `Content-Disposition`,
specify whether to use `ETag` and `Last-Modified` and allowed methods.
* Add `.has_prefixed_resource()` method to `router::ResourceInfo`
for route matching with prefix awareness
* Add `HttpMessage::readlines()` for reading line by line.
* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests.
* Add method to configure custom error handler to `Form` extractor.
* Add methods to `HttpResponse` to retrieve, add, and delete cookies
* Add `.set_content_type()` and `.set_content_disposition()` methods
to `fs::NamedFile` to allow overriding the values inferred by default
* Add `fs::file_extension_to_mime()` helper function to get the MIME
type for a file extension
* Add `.content_disposition()` method to parse Content-Disposition of
multipart fields
* Re-export `actix::prelude::*` as `actix_web::actix` module.
* `HttpRequest::url_for_static()` for a named route with no variables segments
* Propagation of the application's default resource to scopes that haven't set a default resource.
### Changed
* Min rustc version is 1.26
* Use tokio instead of tokio-core
* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages.
* Became possible to use enums with query extractor.
Issue [#371](https://github.com/actix/actix-web/issues/371).
[Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134)
* `HttpResponse::into_builder()` now moves cookies into the builder
instead of dropping them
* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest`
* Added header `User-Agent: Actix-web/<current_version>` to default headers when building a request
* port `Extensions` type from http create, we don't need `Send + Sync`
* `HttpRequest::query()` returns `Ref<HashMap<String, String>>`
* `HttpRequest::cookies()` returns `Ref<Vec<Cookie<'static>>>`
* `StaticFiles::new()` returns `Result<StaticFiles<S>, Error>` instead of `StaticFiles<S>`
* `StaticFiles` uses the default handler if the file does not exist
### Removed
* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead.
* Remove `HttpMessage::range()`
## [0.6.15] - 2018-07-11
### Fixed
* Fix h2 compatibility #352
* Fix duplicate tail of StaticFiles with index_file. #344
## [0.6.14] - 2018-06-21
### Added
* Allow to disable masking for websockets client
### Fixed
* SendRequest execution fails with the "internal error: entered unreachable code" #329
## [0.6.13] - 2018-06-11
* http/2 end-of-frame is not set if body is empty bytes #307
* InternalError can trigger memory unsafety #301
## [0.6.12] - 2018-06-08
### Added
* Add `Host` filter #287
* Allow to filter applications
* Improved failure interoperability with downcasting #285
* Allow to use custom resolver for `ClientConnector`
## [0.6.11] - 2018-06-05
* Support chunked encoding for UrlEncoded body #262
* `HttpRequest::url_for()` for a named route with no variables segments #265
* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255
* CORS: Do not validate Origin header on non-OPTION requests #271
* Fix multipart upload "Incomplete" error #282
## [0.6.10] - 2018-05-24
### Added
* Allow to use path without trailing slashes for scope registration #241
* Allow to set encoding for exact NamedFile #239
### Fixed
* `TestServer::post()` actually sends `GET` request #240
## 0.6.9 (2018-05-22)
* Drop connection if request's payload is not fully consumed #236
* Fix streaming response with body compression
## 0.6.8 (2018-05-20)
* Fix scope resource path extractor #234
* Re-use tcp listener on pause/resume
## 0.6.7 (2018-05-17)
* Fix compilation with --no-default-features
## 0.6.6 (2018-05-17)
* Panic during middleware execution #226
* Add support for listen_tls/listen_ssl #224
* Implement extractor for `Session`
* Ranges header support for NamedFile #60
## 0.6.5 (2018-05-15)
* Fix error handling during request decoding #222
## 0.6.4 (2018-05-11)
* Fix segfault in ServerSettings::get_response_builder()
## 0.6.3 (2018-05-10)
* Add `Router::with_async()` method for async handler registration.
* Added error response functions for 501,502,503,504
* Fix client request timeout handling
## 0.6.2 (2018-05-09)
* WsWriter trait is optional.
## 0.6.1 (2018-05-08)
* Fix http/2 payload streaming #215
* Fix connector's default `keep-alive` and `lifetime` settings #212
* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214
* Allow to exclude certain endpoints from logging #211
## 0.6.0 (2018-05-08)
* Add route scopes #202
* Allow to use ssl and non-ssl connections at the same time #206
* Websocket CloseCode Empty/Status is ambiguous #193
* Add Content-Disposition to NamedFile #204
* Allow to access Error's backtrace object
* Allow to override files listing renderer for `StaticFiles` #203
* Various extractor usability improvements #207
## 0.5.6 (2018-04-24)
* Make flate2 crate optional #200
## 0.5.5 (2018-04-24)
* Fix panic when Websocket is closed with no error code #191
* Allow to use rust backend for flate2 crate #199
## 0.5.4 (2018-04-19)
* Add identity service middleware
* Middleware response() is not invoked if there was an error in async handler #187
* Use Display formatting for InternalError Display implementation #188
## 0.5.3 (2018-04-18)
* Impossible to quote slashes in path parameters #182
## 0.5.2 (2018-04-16)
* Allow to configure StaticFiles's CpuPool, via static method or env variable
* Add support for custom handling of Json extractor errors #181
* Fix StaticFiles does not support percent encoded paths #177
* Fix Client Request with custom Body Stream halting on certain size requests #176
## 0.5.1 (2018-04-12)
* Client connector provides stats, `ClientConnector::stats()`
* Fix end-of-stream handling in parse_payload #173
* Fix StaticFiles generate a lot of threads #174
## 0.5.0 (2018-04-10)
* Type-safe path/query/form parameter handling, using serde #70
* HttpResponse builder's methods `.body()`, `.finish()`, `.json()`
return `HttpResponse` instead of `Result`
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
* Added `signed` and `private` `CookieSessionBackend`s
* Added `HttpRequest::resource()`, returns current matched resource
* Added `ErrorHandlers` middleware
* Fix router cannot parse Non-ASCII characters in URL #137
* Fix client connection pooling
* Fix long client urls #129
* Fix panic on invalid URL characters #130
* Fix logger request duration calculation #152
* Fix prefix and static file serving #168
## 0.4.10 (2018-03-20)
* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods
* Allow to set client request timeout
* Allow to set client websocket handshake timeout
* Refactor `TestServer` configuration
* Fix server websockets big payloads support
* Fix http/2 date header generation
## 0.4.9 (2018-03-16)
* Allow to disable http/2 support
* Wake payload reading task when data is available
* Fix server keep-alive handling
* Send Query Parameters in client requests #120
* Move brotli encoding to a feature
* Add option of default handler for `StaticFiles` handler #57
* Add basic client connection pooling
## 0.4.8 (2018-03-12)
* Allow to set read buffer capacity for server request
* Handle WouldBlock error for socket accept call
## 0.4.7 (2018-03-11)
* Fix panic on unknown content encoding
* Fix connection get closed too early
* Fix streaming response handling for http/2
* Better sleep on error support
## 0.4.6 (2018-03-10)
* Fix client cookie handling
* Fix json content type detection
* Fix CORS middleware #117
* Optimize websockets stream support
## 0.4.5 (2018-03-07)
* Fix compression #103 and #104
* Fix client cookie handling #111
* Non-blocking processing of a `NamedFile`
* Enable compression support for `NamedFile`
* Better support for `NamedFile` type
* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client.
* Add native-tls support for client
* Allow client connection timeout to be set #108
* Allow to use std::net::TcpListener for HttpServer
* Handle panics in worker threads
## 0.4.4 (2018-03-04)
* Allow to use Arc<Vec<u8>> as response/request body
* Fix handling of requests with an encoded body with a length > 8192 #93
## 0.4.3 (2018-03-03)
* Fix request body read bug
* Fix segmentation fault #79
* Set reuse address before bind #90
## 0.4.2 (2018-03-02)
* Better naming for websockets implementation
* Add `Pattern::with_prefix()`, make it more usable outside of actix
* Add csrf middleware for filter for cross-site request forgery #89
* Fix disconnect on idle connections
## 0.4.1 (2018-03-01)
* Rename `Route::p()` to `Route::filter()`
* Better naming for http codes
* Fix payload parse in situation when socket data is not ready.
* Fix Session mutable borrow lifetime #87
## 0.4.0 (2018-02-28)
* Actix 0.5 compatibility
* Fix request json/urlencoded loaders
* Simplify HttpServer type definition
* Added HttpRequest::encoding() method
* Added HttpRequest::mime_type() method
* Added HttpRequest::uri_mut(), allows to modify request uri
* Added StaticFiles::index_file()
* Added http client
* Added websocket client
* Added TestServer::ws(), test websockets client
* Added TestServer http client support
* Allow to override content encoding on application level
## 0.3.3 (2018-01-25)
* Stop processing any events after context stop
* Re-enable write back-pressure for h1 connections
* Refactor HttpServer::start_ssl() method
* Upgrade openssl to 0.10
## 0.3.2 (2018-01-21)
* Fix HEAD requests handling
@ -38,7 +709,7 @@
* Server multi-threading
* Gracefull shutdown support
* Graceful shutdown support
## 0.2.1 (2017-11-03)

46
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@ -1,20 +1,24 @@
[package]
name = "actix-web"
version = "0.3.2"
version = "0.7.13"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web framework"
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://github.com/actix/actix-web"
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/"
documentation = "https://actix.rs/api/actix-web/stable/actix_web/"
categories = ["network-programming", "asynchronous",
"web-programming::http-server", "web-programming::websocket"]
"web-programming::http-server",
"web-programming::http-client",
"web-programming::websocket"]
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config",
"appveyor.yml", "/examples/**"]
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs"
[package.metadata.docs.rs]
features = ["tls", "ssl", "rust-tls", "session", "brotli", "flate2-c"]
[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
@ -25,87 +29,111 @@ name = "actix_web"
path = "src/lib.rs"
[features]
default = []
default = ["session", "brotli", "flate2-c", "cell"]
# tls
tls = ["native-tls", "tokio-tls"]
tls = ["native-tls", "tokio-tls", "actix-net/tls"]
# openssl
alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
ssl = ["openssl", "tokio-openssl", "actix-net/ssl"]
# deprecated, use "ssl"
alpn = ["openssl", "tokio-openssl", "actix-net/ssl"]
# rustls
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"]
# unix sockets
uds = ["tokio-uds"]
# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]
# brotli encoding, requires c compiler
brotli = ["brotli2"]
# miniz-sys backend for flate2 crate
flate2-c = ["flate2/miniz-sys"]
# rust backend for flate2 crate
flate2-rust = ["flate2/rust_backend"]
cell = ["actix-net/cell"]
[dependencies]
log = "0.4"
failure = "0.1"
failure_derive = "0.1"
actix = "^0.7.5"
actix-net = "^0.1.1"
base64 = "0.9"
bitflags = "1.0"
failure = "^0.1.2"
h2 = "0.1"
http = "^0.1.2"
httparse = "1.2"
http-range = "0.1"
time = "0.1"
htmlescape = "0.3"
http = "^0.1.8"
httparse = "1.3"
log = "0.4"
mime = "0.3"
mime_guess = "1.8"
regex = "0.2"
sha1 = "0.4"
url = "1.6"
libc = "0.2"
mime_guess = "2.0.0-alpha"
num_cpus = "1.0"
percent-encoding = "1.0"
rand = "0.5"
regex = "1.0"
serde = "1.0"
serde_json = "1.0"
brotli2 = "^0.3.2"
percent-encoding = "1.0"
sha1 = "0.6"
smallvec = "0.6"
bitflags = "1.0"
num_cpus = "1.0"
flate2 = "1.0"
cookie = { version="0.10", features=["percent-encode", "secure"] }
time = "0.1"
encoding = "0.2"
language-tags = "0.2"
lazy_static = "1.0"
lazycell = "1.0.0"
parking_lot = "0.6"
serde_urlencoded = "^0.5.3"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.11", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="^1.0.2", optional = true, default-features = false }
# io
mio = "0.6"
mio = "^0.6.13"
net2 = "0.2"
bytes = "0.4"
byteorder = "1.2"
futures = "0.1"
futures-cpupool = "0.1"
slab = "0.4"
tokio = "0.1"
tokio-io = "0.1"
tokio-core = "0.1"
tokio-tcp = "0.1"
tokio-timer = "0.2"
tokio-reactor = "0.1"
tokio-current-thread = "0.1"
# native-tls
native-tls = { version="0.1", optional = true }
tokio-tls = { version="0.1", optional = true }
native-tls = { version="0.2", optional = true }
tokio-tls = { version="0.2", optional = true }
# openssl
tokio-openssl = { version="0.1", optional = true }
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }
[dependencies.actix]
version = "^0.4.2"
#rustls
rustls = { version = "0.14", optional = true }
tokio-rustls = { version = "0.8", optional = true }
webpki = { version = "0.18", optional = true }
webpki-roots = { version = "0.15", optional = true }
[dependencies.openssl]
version = "0.9"
optional = true
# unix sockets
tokio-uds = { version="0.2", optional = true }
[dev-dependencies]
env_logger = "0.5"
reqwest = "0.8"
skeptic = "0.13"
serde_derive = "1.0"
[build-dependencies]
skeptic = "0.13"
version_check = "0.1"
[profile.release]
lto = true
opt-level = 3
# debug = true
[workspace]
members = [
"./",
"examples/basics",
"examples/diesel",
"examples/json",
"examples/hello-world",
"examples/multipart",
"examples/state",
"examples/template_tera",
"examples/tls",
"examples/websocket",
"examples/websocket-chat",
]
codegen-units = 1

View File

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

176
MIGRATION.md Normal file
View File

@ -0,0 +1,176 @@
## 0.7.4
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
## 0.7
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
use `HttpMessage::payload()` method.
instead of
```rust
fn index(req: HttpRequest) -> impl Responder {
req
.from_err()
.fold(...)
....
}
```
use `.payload()`
```rust
fn index(req: HttpRequest) -> impl Responder {
req
.payload() // <- get request payload stream
.from_err()
.fold(...)
....
}
```
* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
instead of
```rust
fn index(query: Query<..>, info: Json<MyStruct) -> impl Responder {}
```
use tuple of extractors and use `.with()` for registration:
```rust
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
```
* `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
* Removed deprecated `HttpServer::threads()`, use
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
* Renamed `client::ClientConnectorError::Connector` to
`client::ClientConnectorError::Resolver`
* `Route::with()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_config()`
instead of
```rust
fn main() {
let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET)
.with(index)
.limit(4096); // <- limit size of the payload
});
}
```
use
```rust
fn main() {
let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET)
.with_config(index, |cfg| { // <- register handler
cfg.limit(4096); // <- limit size of the payload
})
});
}
```
* `Route::with_async()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_async_config()`
## 0.6
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
* `ws::Message::Close` now includes optional close reason.
`ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed.
* `HttpServer::threads()` renamed to `HttpServer::workers()`.
* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated.
Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead.
* `HttpRequest::extensions()` returns read only reference to the request's Extension
`HttpRequest::extensions_mut()` returns mutable reference.
* Instead of
`use actix_web::middleware::{
CookieSessionBackend, CookieSessionError, RequestSession,
Session, SessionBackend, SessionImpl, SessionStorage};`
use `actix_web::middleware::session`
`use actix_web::middleware::session{CookieSessionBackend, CookieSessionError,
RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};`
* `FromRequest::from_request()` accepts mutable reference to a request
* `FromRequest::Result` has to implement `Into<Reply<Self>>`
* [`Responder::respond_to()`](
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
is generic over `S`
* Use `Query` extractor instead of HttpRequest::query()`.
```rust
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
...
}
```
or
```rust
let q = Query::<HashMap<String, String>>::extract(req);
```
* Websocket operations are implemented as `WsWriter` trait.
you need to use `use actix_web::ws::WsWriter`
## 0.5
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
methods return `HttpResponse` instead of `Result<HttpResponse>`
* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version`
moved to `actix_web::http` module
* `actix_web::header` moved to `actix_web::http::header`
* `NormalizePath` moved to `actix_web::http` module
* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function,
shortcut for `actix_web::server::HttpServer::new()`
* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself
* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead.
* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type
* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()`
functions should be used instead
* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>`
instead of `Result<_, http::Error>`
* `Application` renamed to a `App`
* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev`

View File

@ -1,6 +1,6 @@
.PHONY: default build test doc book clean
CARGO_FLAGS := --features "$(FEATURES) alpn"
CARGO_FLAGS := --features "$(FEATURES) alpn tls"
default: test
@ -10,17 +10,5 @@ build:
test: build clippy
cargo test $(CARGO_FLAGS)
skeptic:
USE_SKEPTIC=1 cargo test $(CARGO_FLAGS)
# cd examples/word-count && python setup.py install && pytest -v tests
clippy:
if $$CLIPPY; then cargo clippy $(CARGO_FLAGS); fi
doc: build
cargo doc --no-deps $(CARGO_FLAGS)
cd guide; mdbook build -d ../target/doc/guide/; cd ..
book:
cd guide; mdbook build -d ../target/doc/guide/; cd ..

100
README.md
View File

@ -1,65 +1,69 @@
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Actix web is a small, fast, pragmatic, open source rust web framework.
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
```rust,ignore
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.rs/docs/websockets/) support
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable [request routing](https://actix.rs/docs/url-dispatch/)
* Multipart streams
* Static assets
* SSL support with OpenSSL or `native-tls`
* 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)
* Built on top of [Actix actor framework](https://github.com/actix/actix)
## Documentation & community resources
* [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.26 or later
## Example
```rust
extern crate actix_web;
use actix_web::*;
use actix_web::{http, server, App, Path, Responder};
fn index(req: HttpRequest) -> String {
format!("Hello {}!", &req.match_info()["name"])
fn index(info: Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0)
}
fn main() {
HttpServer::new(
|| Application::new()
.resource("/{name}", |r| r.f(index)))
server::new(
|| App::new()
.route("/{id}/{name}/index.html", http::Method::GET, index))
.bind("127.0.0.1:8080").unwrap()
.run();
}
```
## Documentation
### More examples
* [User Guide](http://actix.github.io/actix-web/guide/)
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
* [API Documentation (Releases)](https://docs.rs/actix-web/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.20 or later
* [Basics](https://github.com/actix/examples/tree/master/basics/)
* [Stateful](https://github.com/actix/examples/tree/master/state/)
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/)
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
* [Json](https://github.com/actix/examples/tree/master/json/)
## Features
* Supported *HTTP/1.x* and *HTTP/2.0* protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* [WebSockets](https://actix.github.io/actix-web/actix_web/ws/index.html)
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable request routing
* Graceful server shutdown
* Multipart streams
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers),
[CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html))
* Built on top of [Actix](https://github.com/actix/actix).
You may consider checking out
[this directory](https://github.com/actix/examples/tree/master/) for more examples.
## Benchmarks
Some basic benchmarks could be found in this [respository](https://github.com/fafhrd91/benchmarks).
## Examples
* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/)
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/)
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/)
* [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/)
* [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/)
* [SSL / HTTP/2.0](https://github.com/actix/actix-web/tree/master/examples/tls/)
* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
* [SockJS Server](https://github.com/actix/actix-sockjs)
* [Json](https://github.com/actix/actix-web/tree/master/examples/json/)
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext)
## License
@ -69,3 +73,9 @@ This project is licensed under either of
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
at your option.
## Code of Conduct
Contribution to the actix-web crate is organized under the terms of the
Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to
intervene to uphold that code of conduct.

View File

@ -1,46 +1,15 @@
extern crate skeptic;
extern crate version_check;
use std::{env, fs};
#[cfg(unix)]
fn main() {
let f = env::var("OUT_DIR").unwrap() + "/skeptic-tests.rs";
if env::var("USE_SKEPTIC").is_ok() {
let _ = fs::remove_file(f);
// generates doc tests for `README.md`.
skeptic::generate_doc_tests(
&["README.md",
"guide/src/qs_1.md",
"guide/src/qs_2.md",
"guide/src/qs_3.md",
"guide/src/qs_3_5.md",
"guide/src/qs_4.md",
"guide/src/qs_4_5.md",
"guide/src/qs_5.md",
"guide/src/qs_7.md",
"guide/src/qs_8.md",
"guide/src/qs_9.md",
"guide/src/qs_10.md",
"guide/src/qs_12.md",
"guide/src/qs_13.md",
]);
} else {
let _ = fs::File::create(f);
match version_check::is_min_version("1.26.0") {
Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"),
_ => (),
};
match version_check::is_nightly() {
Some(true) => {
println!("cargo:rustc-cfg=actix_nightly");
println!("cargo:rustc-cfg=actix_impl_trait");
}
match version_check::is_nightly() {
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
Some(false) => (),
None => (),
};
}
#[cfg(not(unix))]
fn main() {
match version_check::is_nightly() {
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
Some(false) => (),
None => (),
};

View File

@ -1,11 +0,0 @@
[package]
name = "basics"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
futures = "*"
env_logger = "0.5"
actix = "0.4"
actix-web = { path="../.." }

View File

@ -1,20 +0,0 @@
# basics
## Usage
### server
```bash
cd actix-web/examples/basics
cargo run
# Started http server: 127.0.0.1:8080
```
### web client
- [http://localhost:8080/index.html](http://localhost:8080/index.html)
- [http://localhost:8080/async/bob](http://localhost:8080/async/bob)
- [http://localhost:8080/user/bob/](http://localhost:8080/user/bob/) plain/text download
- [http://localhost:8080/test](http://localhost:8080/test) (return status switch GET or POST or other)
- [http://localhost:8080/static/index.html](http://localhost:8080/static/index.html)
- [http://localhost:8080/static/notexit](http://localhost:8080/static/notexit) display 404 page

View File

@ -1,151 +0,0 @@
#![allow(unused_variables)]
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
use futures::Stream;
use std::{io, env};
use actix_web::*;
use actix_web::middleware::RequestSession;
use futures::future::{FutureResult, result};
/// favicon handler
fn favicon(req: HttpRequest) -> Result<fs::NamedFile> {
Ok(fs::NamedFile::open("../static/favicon.ico")?)
}
/// simple index handler
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req);
// example of ...
if let Ok(ch) = req.payload_mut().readany().poll() {
if let futures::Async::Ready(Some(d)) = ch {
println!("{}", String::from_utf8_lossy(d.as_ref()));
}
}
// session
let mut counter = 1;
if let Some(count) = req.session().get::<i32>("counter")? {
println!("SESSION value: {}", count);
counter = count + 1;
req.session().set("counter", counter)?;
} else {
req.session().set("counter", counter)?;
}
// html
let html = format!(r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
<body>
<h1>Welcome <img width="30px" height="30px" src="/static/actixLogo.png" /></h1>
session counter = {}
</body>
</html>"#, counter);
// response
Ok(HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8")
.body(&html).unwrap())
}
/// 404 handler
fn p404(req: HttpRequest) -> Result<HttpResponse> {
// html
let html = r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
<body>
<a href="index.html">back to home</a>
<h1>404</h1>
</body>
</html>"#;
// response
Ok(HttpResponse::build(StatusCode::NOT_FOUND)
.content_type("text/html; charset=utf-8")
.body(html).unwrap())
}
/// async handler
fn index_async(req: HttpRequest) -> FutureResult<HttpResponse, Error>
{
println!("{:?}", req);
result(HttpResponse::Ok()
.content_type("text/html")
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))
.map_err(|e| e.into()))
}
/// handler with path parameters like `/user/{name}/`
fn with_param(req: HttpRequest) -> Result<HttpResponse>
{
println!("{:?}", req);
Ok(HttpResponse::Ok()
.content_type("test/plain")
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))?)
}
fn main() {
env::set_var("RUST_LOG", "actix_web=debug");
env::set_var("RUST_BACKTRACE", "1");
env_logger::init();
let sys = actix::System::new("basic-example");
let addr = HttpServer::new(
|| Application::new()
// enable logger
.middleware(middleware::Logger::default())
// cookie session middleware
.middleware(middleware::SessionStorage::new(
middleware::CookieSessionBackend::build(&[0; 32])
.secure(false)
.finish()
))
// register favicon
.resource("/favicon.ico", |r| r.f(favicon))
// register simple route, handle all methods
.resource("/index.html", |r| r.f(index))
// with path parameters
.resource("/user/{name}/", |r| r.method(Method::GET).f(with_param))
// async handler
.resource("/async/{name}", |r| r.method(Method::GET).a(index_async))
.resource("/test", |r| r.f(|req| {
match *req.method() {
Method::GET => httpcodes::HTTPOk,
Method::POST => httpcodes::HTTPMethodNotAllowed,
_ => httpcodes::HTTPNotFound,
}
}))
.resource("/error.html", |r| r.f(|req| {
error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "test"))
}))
// static files
.handler("/static/", fs::StaticFiles::new("../static/", true))
// redirect
.resource("/", |r| r.method(Method::GET).f(|req| {
println!("{:?}", req);
HttpResponse::Found()
.header("LOCATION", "/index.html")
.finish()
}))
// default
.default_resource(|r| {
r.method(Method::GET).f(p404);
r.route().p(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed);
}))
.bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080")
.shutdown_timeout(0) // <- Set shutdown timeout to 0 seconds (default 60s)
.start();
println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -1 +0,0 @@
DATABASE_URL=file:test.db

View File

@ -1,19 +0,0 @@
[package]
name = "diesel-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
env_logger = "0.5"
actix = "0.4"
actix-web = { path = "../../" }
futures = "0.1"
uuid = { version = "0.5", features = ["serde", "v4"] }
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
diesel = { version = "1.0.0-beta1", features = ["sqlite"] }
dotenv = "0.10"

View File

@ -1,43 +0,0 @@
# diesel
Diesel's `Getting Started` guide using SQLite for Actix web
## Usage
### init database sqlite
```bash
cargo install diesel_cli --no-default-features --features sqlite
cd actix-web/examples/diesel
echo "DATABASE_URL=file:test.db" > .env
diesel migration run
```
### server
```bash
# if ubuntu : sudo apt-get install libsqlite3-dev
# if fedora : sudo dnf install libsqlite3x-devel
cd actix-web/examples/diesel
cargo run (or ``cargo watch -x run``)
# Started http server: 127.0.0.1:8080
```
### web client
[http://127.0.0.1:8080/NAME](http://127.0.0.1:8080/NAME)
### sqlite client
```bash
# if ubuntu : sudo apt-get install sqlite3
# if fedora : sudo dnf install sqlite3x
sqlite3 test.db
sqlite> .tables
sqlite> select * from users;
```
## Postgresql
You will also find another complete example of diesel+postgresql on [https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix)

View File

@ -1 +0,0 @@
DROP TABLE users

View File

@ -1,4 +0,0 @@
CREATE TABLE users (
id VARCHAR NOT NULL PRIMARY KEY,
name VARCHAR NOT NULL
)

View File

@ -1,53 +0,0 @@
//! Db executor actor
use uuid;
use diesel;
use actix_web::*;
use actix::prelude::*;
use diesel::prelude::*;
use models;
use schema;
/// This is db executor actor. We are going to run 3 of them in parallel.
pub struct DbExecutor(pub SqliteConnection);
/// This is only message that this actor can handle, but it is easy to extend number of
/// messages.
pub struct CreateUser {
pub name: String,
}
impl ResponseType for CreateUser {
type Item = models::User;
type Error = Error;
}
impl Actor for DbExecutor {
type Context = SyncContext<Self>;
}
impl Handler<CreateUser> for DbExecutor {
type Result = MessageResult<CreateUser>;
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
use self::schema::users::dsl::*;
let uuid = format!("{}", uuid::Uuid::new_v4());
let new_user = models::NewUser {
id: &uuid,
name: &msg.name,
};
diesel::insert_into(users)
.values(&new_user)
.execute(&self.0)
.expect("Error inserting person");
let mut items = users
.filter(id.eq(&uuid))
.load::<models::User>(&self.0)
.expect("Error loading person");
Ok(items.pop().unwrap())
}
}

View File

@ -1,73 +0,0 @@
//! Actix web diesel example
//!
//! Diesel does not support tokio, so we have to run it in separate threads.
//! Actix supports sync actors by default, so we going to create sync actor that will
//! use diesel. Technically sync actors are worker style actors, multiple of them
//! can run in parallel and process messages from same queue.
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate diesel;
extern crate uuid;
extern crate futures;
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix::*;
use actix_web::*;
use diesel::prelude::*;
use futures::future::Future;
mod db;
mod models;
mod schema;
use db::{CreateUser, DbExecutor};
/// State with DbExecutor address
struct State {
db: SyncAddress<DbExecutor>,
}
/// Async request handler
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
let name = &req.match_info()["name"];
req.state().db.call_fut(CreateUser{name: name.to_owned()})
.from_err()
.and_then(|res| {
match res {
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?),
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
}
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("diesel-example");
// Start db executor actors
let addr = SyncArbiter::start(3, || {
DbExecutor(SqliteConnection::establish("test.db").unwrap())
});
// Start http server
let _addr = HttpServer::new(move || {
Application::with_state(State{db: addr.clone()})
// enable logger
.middleware(middleware::Logger::default())
.resource("/{name}", |r| r.method(Method::GET).a(index))})
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -1,14 +0,0 @@
use super::schema::users;
#[derive(Serialize, Queryable)]
pub struct User {
pub id: String,
pub name: String,
}
#[derive(Insertable)]
#[table_name = "users"]
pub struct NewUser<'a> {
pub id: &'a str,
pub name: &'a str,
}

View File

@ -1,6 +0,0 @@
table! {
users (id) {
id -> Text,
name -> Text,
}
}

Binary file not shown.

View File

@ -1,10 +0,0 @@
[package]
name = "hello-world"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
env_logger = "0.5"
actix = "0.4"
actix-web = { path = "../../" }

View File

@ -1,28 +0,0 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix_web::*;
fn index(_req: HttpRequest) -> &'static str {
"Hello world!"
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("ws-example");
let _addr = HttpServer::new(
|| Application::new()
// enable logger
.middleware(middleware::Logger::default())
.resource("/index.html", |r| r.f(|_| "Hello world!"))
.resource("/", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -1,18 +0,0 @@
[package]
name = "json-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
bytes = "0.4"
futures = "0.1"
env_logger = "*"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
json = "*"
actix = "0.4"
actix-web = { path="../../" }

View File

@ -1,48 +0,0 @@
# json
Json's `Getting Started` guide using json (serde-json or json-rust) for Actix web
## Usage
### server
```bash
cd actix-web/examples/json
cargo run
# Started http server: 127.0.0.1:8080
```
### web client
With [Postman](https://www.getpostman.com/) or [Rested](moz-extension://60daeb1c-5b1b-4afd-9842-0579ed34dfcb/dist/index.html)
- POST / (embed serde-json):
- method : ``POST``
- url : ``http://127.0.0.1:8080/``
- header : ``Content-Type`` = ``application/json``
- body (raw) : ``{"name": "Test user", "number": 100}``
- POST /manual (manual serde-json):
- method : ``POST``
- url : ``http://127.0.0.1:8080/manual``
- header : ``Content-Type`` = ``application/json``
- body (raw) : ``{"name": "Test user", "number": 100}``
- POST /mjsonrust (manual json-rust):
- method : ``POST``
- url : ``http://127.0.0.1:8080/mjsonrust``
- header : ``Content-Type`` = ``application/json``
- body (raw) : ``{"name": "Test user", "number": 100}`` (you can also test ``{notjson}``)
### python client
- ``pip install aiohttp``
- ``python client.py``
if ubuntu :
- ``pip3 install aiohttp``
- ``python3 client.py``

View File

@ -1,18 +0,0 @@
# This script could be used for actix-web multipart example test
# just start server and run client.py
import json
import asyncio
import aiohttp
async def req():
resp = await aiohttp.ClientSession().request(
"post", 'http://localhost:8080/',
data=json.dumps({"name": "Test user", "number": 100}),
headers={"content-type": "application/json"})
print(str(resp))
print(await resp.text())
assert 200 == resp.status
asyncio.get_event_loop().run_until_complete(req())

View File

@ -1,99 +0,0 @@
extern crate actix;
extern crate actix_web;
extern crate bytes;
extern crate futures;
extern crate env_logger;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate json;
use actix_web::*;
use bytes::BytesMut;
use futures::{Future, Stream};
use json::JsonValue;
#[derive(Debug, Serialize, Deserialize)]
struct MyObj {
name: String,
number: i32,
}
/// This handler uses `HttpRequest::json()` for loading serde json object.
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.json()
.from_err() // convert all errors into `Error`
.and_then(|val: MyObj| {
println!("model: {:?}", val);
Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response
})
.responder()
}
const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// This handler manually load request payload and parse serde json
fn index_manual(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// readany() returns asynchronous stream of Bytes objects
req.payload_mut().readany()
// `Future::from_err` acts like `?` in that it coerces the error type from
// the future into the final error type
.from_err()
// `fold` will asynchronously read each chunk of the request body and
// call supplied closure, then it resolves to result of closure
.fold(BytesMut::new(), move |mut body, chunk| {
// limit max size of in-memory payload
if (body.len() + chunk.len()) > MAX_SIZE {
Err(error::ErrorBadRequest("overflow"))
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
// `Future::and_then` can be used to merge an asynchronous workflow with a
// synchronous workflow
.and_then(|body| {
// body is loaded, now we can deserialize serde-json
let obj = serde_json::from_slice::<MyObj>(&body)?;
Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response
})
.responder()
}
/// This handler manually load request payload and parse json-rust
fn index_mjsonrust(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.payload_mut().readany().concat2()
.from_err()
.and_then(|body| {
// body is loaded, now we can deserialize json-rust
let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result
let injson: JsonValue = match result { Ok(v) => v, Err(e) => object!{"err" => e.to_string() } };
Ok(HttpResponse::build(StatusCode::OK)
.content_type("application/json")
.body(injson.dump()).unwrap())
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("json-example");
let addr = HttpServer::new(|| {
Application::new()
// enable logger
.middleware(middleware::Logger::default())
.resource("/manual", |r| r.method(Method::POST).f(index_manual))
.resource("/mjsonrust", |r| r.method(Method::POST).f(index_mjsonrust))
.resource("/", |r| r.method(Method::POST).f(index))})
.bind("127.0.0.1:8080").unwrap()
.shutdown_timeout(1)
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -1,15 +0,0 @@
[package]
name = "multipart-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "multipart"
path = "src/main.rs"
[dependencies]
env_logger = "*"
futures = "0.1"
actix = "0.4"
actix-web = { path="../../" }

View File

@ -1,24 +0,0 @@
# multipart
Multipart's `Getting Started` guide for Actix web
## Usage
### server
```bash
cd actix-web/examples/multipart
cargo run (or ``cargo watch -x run``)
# Started http server: 127.0.0.1:8080
```
### client
- ``pip install aiohttp``
- ``python client.py``
- you must see in server console multipart fields
if ubuntu :
- ``pip3 install aiohttp``
- ``python3 client.py``

View File

@ -1,34 +0,0 @@
# This script could be used for actix-web multipart example test
# just start server and run client.py
import asyncio
import aiohttp
async def req1():
with aiohttp.MultipartWriter() as writer:
writer.append('test')
writer.append_json({'passed': True})
resp = await aiohttp.ClientSession().request(
"post", 'http://localhost:8080/multipart',
data=writer, headers=writer.headers)
print(resp)
assert 200 == resp.status
async def req2():
with aiohttp.MultipartWriter() as writer:
writer.append('test')
writer.append_json({'passed': True})
writer.append(open('src/main.rs'))
resp = await aiohttp.ClientSession().request(
"post", 'http://localhost:8080/multipart',
data=writer, headers=writer.headers)
print(resp)
assert 200 == resp.status
loop = asyncio.get_event_loop()
loop.run_until_complete(req1())
loop.run_until_complete(req2())

View File

@ -1,59 +0,0 @@
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
use actix::*;
use actix_web::*;
use futures::{Future, Stream};
use futures::future::{result, Either};
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
{
println!("{:?}", req);
req.multipart() // <- get multipart stream for current request
.from_err() // <- convert multipart errors
.and_then(|item| { // <- iterate over multipart items
match item {
// Handle multipart Field
multipart::MultipartItem::Field(field) => {
println!("==== FIELD ==== {:?}", field);
// Field in turn is stream of *Bytes* object
Either::A(
field.map_err(Error::from)
.map(|chunk| {
println!("-- CHUNK: \n{}",
std::str::from_utf8(&chunk).unwrap());})
.finish())
},
multipart::MultipartItem::Nested(mp) => {
// Or item could be nested Multipart stream
Either::B(result(Ok(())))
}
}
})
.finish() // <- Stream::finish() combinator from actix
.map(|_| httpcodes::HTTPOk.into())
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("multipart-example");
let addr = HttpServer::new(
|| Application::new()
.middleware(middleware::Logger::default()) // <- logger
.resource("/multipart", |r| r.method(Method::POST).a(index)))
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -1,11 +0,0 @@
[package]
name = "state"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
futures = "*"
env_logger = "0.5"
actix = "0.4"
actix-web = { path = "../../" }

View File

@ -1,15 +0,0 @@
# state
## Usage
### server
```bash
cd actix-web/examples/state
cargo run
# Started http server: 127.0.0.1:8080
```
### web client
- [http://localhost:8080/](http://localhost:8080/)

View File

@ -1,77 +0,0 @@
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
//! There are two level of statefulness in actix-web. Application has state
//! that is shared across all handlers within same Application.
//! And individual handler can have state.
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use std::cell::Cell;
use actix::*;
use actix_web::*;
/// Application state
struct AppState {
counter: Cell<usize>,
}
/// somple handle
fn index(req: HttpRequest<AppState>) -> HttpResponse {
println!("{:?}", req);
req.state().counter.set(req.state().counter.get() + 1);
httpcodes::HTTPOk.with_body(
format!("Num of requests: {}", req.state().counter.get()))
}
/// `MyWebSocket` counts how many messages it receives from peer,
/// websocket-client.py could be used for tests
struct MyWebSocket {
counter: usize,
}
impl Actor for MyWebSocket {
type Context = ws::WebsocketContext<Self, AppState>;
}
impl Handler<ws::Message> for MyWebSocket {
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
self.counter += 1;
println!("WS({}): {:?}", self.counter, msg);
match msg {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(&text),
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Closed | ws::Message::Error => {
ctx.stop();
}
_ => (),
}
}
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("ws-example");
let addr = HttpServer::new(
|| Application::with_state(AppState{counter: Cell::new(0)})
// enable logger
.middleware(middleware::Logger::default())
// websocket route
.resource(
"/ws/", |r|
r.method(Method::GET).f(|req| ws::start(req, MyWebSocket{counter: 0})))
// register simple handler, handle all methods
.resource("/", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,90 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
</script>
<script language="javascript" type="text/javascript">
$(function() {
var conn = null;
function log(msg) {
var control = $('#log');
control.html(control.html() + msg + '<br/>');
control.scrollTop(control.scrollTop() + 1000);
}
function connect() {
disconnect();
var wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://')+window.location.host + '/ws/';
conn = new WebSocket(wsUri);
log('Connecting...');
conn.onopen = function() {
log('Connected.');
update_ui();
};
conn.onmessage = function(e) {
log('Received: ' + e.data);
};
conn.onclose = function() {
log('Disconnected.');
conn = null;
update_ui();
};
}
function disconnect() {
if (conn != null) {
log('Disconnecting...');
conn.close();
conn = null;
update_ui();
}
}
function update_ui() {
var msg = '';
if (conn == null) {
$('#status').text('disconnected');
$('#connect').html('Connect');
} else {
$('#status').text('connected (' + conn.protocol + ')');
$('#connect').html('Disconnect');
}
}
$('#connect').click(function() {
if (conn == null) {
connect();
} else {
disconnect();
}
update_ui();
return false;
});
$('#send').click(function() {
var text = $('#text').val();
log('Sending: ' + text);
conn.send(text);
$('#text').val('').focus();
return false;
});
$('#text').keyup(function(e) {
if (e.keyCode === 13) {
$('#send').click();
return false;
}
});
});
</script>
</head>
<body>
<h3>Chat!</h3>
<div>
<button id="connect">Connect</button>&nbsp;|&nbsp;Status:
<span id="status">disconnected</span>
</div>
<div id="log"
style="width:20em;height:15em;overflow:auto;border:1px solid black">
</div>
<form id="chatform" onsubmit="return false;">
<input id="text" type="text" />
<input id="send" type="button" value="Send" />
</form>
</body>
</html>

View File

@ -1,11 +0,0 @@
[package]
name = "template-tera"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
env_logger = "0.5"
actix = "0.4"
actix-web = { path = "../../" }
tera = "*"

View File

@ -1,17 +0,0 @@
# template_tera
Minimal example of using the template [tera](https://github.com/Keats/tera) that displays a form.
## Usage
### server
```bash
cd actix-web/examples/template_tera
cargo run (or ``cargo watch -x run``)
# Started http server: 127.0.0.1:8080
```
### web client
- [http://localhost:8080](http://localhost:8080)

View File

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

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Actix web</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
<h3>What is your name?</h3>
<form>
<input type="text" name="name" /><br/>
<p><input type="submit"></p>
</form>
</p>
</body>
</html>

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Actix web</title>
</head>
<body>
<h1>Hi, {{ name }}!</h1>
<p>
{{ text }}
</p>
</body>
</html>

View File

@ -1,14 +0,0 @@
[package]
name = "tls-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "server"
path = "src/main.rs"
[dependencies]
env_logger = "0.5"
actix = "^0.4.2"
actix-web = { path = "../../", features=["alpn"] }

View File

@ -1,16 +0,0 @@
# tls example
## Usage
### server
```bash
cd actix-web/examples/tls
cargo run (or ``cargo watch -x run``)
# Started http server: 127.0.0.1:8443
```
### web client
- curl: ``curl -v https://127.0.0.1:8443/index.html --compress -k``
- browser: [https://127.0.0.1:8443/index.html](https://127.0.0.1:8080/index.html)

Binary file not shown.

View File

@ -1,51 +0,0 @@
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use std::fs::File;
use std::io::Read;
use actix_web::*;
/// simple handle
fn index(req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req);
Ok(httpcodes::HTTPOk
.build()
.content_type("text/plain")
.body("Welcome!")?)
}
fn main() {
if ::std::env::var("RUST_LOG").is_err() {
::std::env::set_var("RUST_LOG", "actix_web=trace");
}
let _ = env_logger::init();
let sys = actix::System::new("ws-example");
let mut file = File::open("identity.pfx").unwrap();
let mut pkcs12 = vec![];
file.read_to_end(&mut pkcs12).unwrap();
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
let addr = HttpServer::new(
|| Application::new()
// enable logger
.middleware(middleware::Logger::default())
// register simple handler, handle all methods
.resource("/index.html", |r| r.f(index))
// with path parameters
.resource("/", |r| r.method(Method::GET).f(|req| {
httpcodes::HTTPFound
.build()
.header("LOCATION", "/index.html")
.body(Body::Empty)
})))
.bind("127.0.0.1:8443").unwrap()
.start_ssl(&pkcs12).unwrap();
println!("Started http server: 127.0.0.1:8443");
let _ = sys.run();
}

View File

@ -1,29 +0,0 @@
[package]
name = "websocket-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "server"
path = "src/main.rs"
[[bin]]
name = "client"
path = "src/client.rs"
[dependencies]
rand = "*"
bytes = "0.4"
byteorder = "1.1"
futures = "0.1"
tokio-io = "0.1"
tokio-core = "0.1"
env_logger = "*"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
actix = "^0.4.2"
actix-web = { path="../../" }

View File

@ -1,32 +0,0 @@
# Websocket chat example
This is extension of the
[actix chat example](https://github.com/actix/actix/tree/master/examples/chat)
Added features:
* Browser WebSocket client
* Chat server runs in separate thread
* Tcp listener runs in separate thread
## Server
Chat server listens for incoming tcp connections. Server can access several types of message:
* `\list` - list all available rooms
* `\join name` - join room, if room does not exist, create new one
* `\name name` - set session name
* `some message` - just string, send message to all peers in same room
* client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets dropped
To start server use command: `cargo run --bin server`
## Client
Client connects to server. Reads input from stdin and sends to server.
To run client use command: `cargo run --bin client`
## WebSocket Browser Client
Open url: [http://localhost:8080/](http://localhost:8080/)

View File

@ -1,72 +0,0 @@
#!/usr/bin/env python3
"""websocket cmd client for wssrv.py example."""
import argparse
import asyncio
import signal
import sys
import aiohttp
def start_client(loop, url):
name = input('Please enter your name: ')
# send request
ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False)
# input reader
def stdin_callback():
line = sys.stdin.buffer.readline().decode('utf-8')
if not line:
loop.stop()
else:
ws.send_str(name + ': ' + line)
loop.add_reader(sys.stdin.fileno(), stdin_callback)
@asyncio.coroutine
def dispatch():
while True:
msg = yield from ws.receive()
if msg.type == aiohttp.WSMsgType.TEXT:
print('Text: ', msg.data.strip())
elif msg.type == aiohttp.WSMsgType.BINARY:
print('Binary: ', msg.data)
elif msg.type == aiohttp.WSMsgType.PING:
ws.pong()
elif msg.type == aiohttp.WSMsgType.PONG:
print('Pong received')
else:
if msg.type == aiohttp.WSMsgType.CLOSE:
yield from ws.close()
elif msg.type == aiohttp.WSMsgType.ERROR:
print('Error during receive %s' % ws.exception())
elif msg.type == aiohttp.WSMsgType.CLOSED:
pass
break
yield from dispatch()
ARGS = argparse.ArgumentParser(
description="websocket console client for wssrv.py example.")
ARGS.add_argument(
'--host', action="store", dest='host',
default='127.0.0.1', help='Host name')
ARGS.add_argument(
'--port', action="store", dest='port',
default=8080, type=int, help='Port number')
if __name__ == '__main__':
args = ARGS.parse_args()
if ':' in args.host:
args.host, port = args.host.split(':', 1)
args.port = int(port)
url = 'http://{}:{}/ws/'.format(args.host, args.port)
loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, loop.stop)
asyncio.Task(start_client(loop, url))
loop.run_forever()

View File

@ -1,149 +0,0 @@
#[macro_use] extern crate actix;
extern crate bytes;
extern crate byteorder;
extern crate futures;
extern crate tokio_io;
extern crate tokio_core;
extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
use std::{io, net, process, thread};
use std::str::FromStr;
use std::time::Duration;
use futures::Future;
use tokio_core::net::TcpStream;
use actix::prelude::*;
mod codec;
fn main() {
let sys = actix::System::new("chat-client");
// Connect to server
let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap();
Arbiter::handle().spawn(
TcpStream::connect(&addr, Arbiter::handle())
.and_then(|stream| {
let addr: SyncAddress<_> = ChatClient.framed(stream, codec::ClientChatCodec);
// start console loop
thread::spawn(move|| {
loop {
let mut cmd = String::new();
if io::stdin().read_line(&mut cmd).is_err() {
println!("error");
return
}
addr.send(ClientCommand(cmd));
}
});
futures::future::ok(())
})
.map_err(|e| {
println!("Can not connect to server: {}", e);
process::exit(1)
})
);
println!("Running chat client");
sys.run();
}
struct ChatClient;
#[derive(Message)]
struct ClientCommand(String);
impl Actor for ChatClient {
type Context = FramedContext<Self>;
fn started(&mut self, ctx: &mut FramedContext<Self>) {
// start heartbeats otherwise server will disconnect after 10 seconds
self.hb(ctx)
}
fn stopping(&mut self, _: &mut FramedContext<Self>) -> bool {
println!("Disconnected");
// Stop application on disconnect
Arbiter::system().send(actix::msgs::SystemExit(0));
true
}
}
impl ChatClient {
fn hb(&self, ctx: &mut FramedContext<Self>) {
ctx.run_later(Duration::new(1, 0), |act, ctx| {
if ctx.send(codec::ChatRequest::Ping).is_ok() {
act.hb(ctx);
}
});
}
}
/// Handle stdin commands
impl Handler<ClientCommand> for ChatClient {
type Result = ();
fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext<Self>) {
let m = msg.0.trim();
if m.is_empty() {
return
}
// we check for /sss type of messages
if m.starts_with('/') {
let v: Vec<&str> = m.splitn(2, ' ').collect();
match v[0] {
"/list" => {
let _ = ctx.send(codec::ChatRequest::List);
},
"/join" => {
if v.len() == 2 {
let _ = ctx.send(codec::ChatRequest::Join(v[1].to_owned()));
} else {
println!("!!! room name is required");
}
},
_ => println!("!!! unknown command"),
}
} else {
let _ = ctx.send(codec::ChatRequest::Message(m.to_owned()));
}
}
}
/// Server communication
impl FramedActor for ChatClient {
type Io = TcpStream;
type Codec = codec::ClientChatCodec;
fn handle(&mut self, msg: io::Result<codec::ChatResponse>, ctx: &mut FramedContext<Self>) {
match msg {
Err(_) => ctx.stop(),
Ok(msg) => match msg {
codec::ChatResponse::Message(ref msg) => {
println!("message: {}", msg);
}
codec::ChatResponse::Joined(ref msg) => {
println!("!!! joined: {}", msg);
}
codec::ChatResponse::Rooms(rooms) => {
println!("\n!!! Available rooms:");
for room in rooms {
println!("{}", room);
}
println!("");
}
_ => (),
}
}
}
}

View File

@ -1,134 +0,0 @@
#![allow(dead_code)]
use std::io;
use serde_json as json;
use byteorder::{BigEndian , ByteOrder};
use bytes::{BytesMut, BufMut};
use tokio_io::codec::{Encoder, Decoder};
use actix::ResponseType;
/// Client request
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag="cmd", content="data")]
pub enum ChatRequest {
/// List rooms
List,
/// Join rooms
Join(String),
/// Send message
Message(String),
/// Ping
Ping
}
impl ResponseType for ChatRequest {
type Item = ();
type Error = ();
}
/// Server response
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag="cmd", content="data")]
pub enum ChatResponse {
Ping,
/// List of rooms
Rooms(Vec<String>),
/// Joined
Joined(String),
/// Message
Message(String),
}
impl ResponseType for ChatResponse {
type Item = ();
type Error = ();
}
/// Codec for Client -> Server transport
pub struct ChatCodec;
impl Decoder for ChatCodec
{
type Item = ChatRequest;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
let size = {
if src.len() < 2 {
return Ok(None)
}
BigEndian::read_u16(src.as_ref()) as usize
};
if src.len() >= size + 2 {
src.split_to(2);
let buf = src.split_to(size);
Ok(Some(json::from_slice::<ChatRequest>(&buf)?))
} else {
Ok(None)
}
}
}
impl Encoder for ChatCodec
{
type Item = ChatResponse;
type Error = io::Error;
fn encode(&mut self, msg: ChatResponse, dst: &mut BytesMut) -> Result<(), Self::Error> {
let msg = json::to_string(&msg).unwrap();
let msg_ref: &[u8] = msg.as_ref();
dst.reserve(msg_ref.len() + 2);
dst.put_u16::<BigEndian>(msg_ref.len() as u16);
dst.put(msg_ref);
Ok(())
}
}
/// Codec for Server -> Client transport
pub struct ClientChatCodec;
impl Decoder for ClientChatCodec
{
type Item = ChatResponse;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
let size = {
if src.len() < 2 {
return Ok(None)
}
BigEndian::read_u16(src.as_ref()) as usize
};
if src.len() >= size + 2 {
src.split_to(2);
let buf = src.split_to(size);
Ok(Some(json::from_slice::<ChatResponse>(&buf)?))
} else {
Ok(None)
}
}
}
impl Encoder for ClientChatCodec
{
type Item = ChatRequest;
type Error = io::Error;
fn encode(&mut self, msg: ChatRequest, dst: &mut BytesMut) -> Result<(), Self::Error> {
let msg = json::to_string(&msg).unwrap();
let msg_ref: &[u8] = msg.as_ref();
dst.reserve(msg_ref.len() + 2);
dst.put_u16::<BigEndian>(msg_ref.len() as u16);
dst.put(msg_ref);
Ok(())
}
}

View File

@ -1,210 +0,0 @@
#![allow(unused_variables)]
extern crate rand;
extern crate bytes;
extern crate byteorder;
extern crate futures;
extern crate tokio_io;
extern crate tokio_core;
extern crate env_logger;
extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
#[macro_use]
extern crate actix;
extern crate actix_web;
use std::time::Instant;
use actix::*;
use actix_web::*;
mod codec;
mod server;
mod session;
/// This is our websocket route state, this state is shared with all route instances
/// via `HttpContext::state()`
struct WsChatSessionState {
addr: SyncAddress<server::ChatServer>,
}
/// Entry point for our route
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse> {
ws::start(
req,
WsChatSession {
id: 0,
hb: Instant::now(),
room: "Main".to_owned(),
name: None})
}
struct WsChatSession {
/// unique session id
id: usize,
/// Client must send ping at least once per 10 seconds, otherwise we drop connection.
hb: Instant,
/// joined room
room: String,
/// peer name
name: Option<String>,
}
impl Actor for WsChatSession {
type Context = ws::WebsocketContext<Self, WsChatSessionState>;
/// Method is called on actor start.
/// We register ws session with ChatServer
fn started(&mut self, ctx: &mut Self::Context) {
// register self in chat server. `AsyncContext::wait` register
// future within context, but context waits until this future resolves
// before processing any other events.
// HttpContext::state() is instance of WsChatSessionState, state is shared across all
// routes within application
let subs = ctx.sync_subscriber();
ctx.state().addr.call(
self, server::Connect{addr: subs}).then(
|res, act, ctx| {
match res {
Ok(Ok(res)) => act.id = res,
// something is wrong with chat server
_ => ctx.stop(),
}
fut::ok(())
}).wait(ctx);
}
fn stopping(&mut self, ctx: &mut Self::Context) -> bool {
// notify chat server
ctx.state().addr.send(server::Disconnect{id: self.id});
true
}
}
/// Handle messages from chat server, we simply send it to peer websocket
impl Handler<session::Message> for WsChatSession {
type Result = ();
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
ctx.text(&msg.0);
}
}
/// WebSocket message handler
impl Handler<ws::Message> for WsChatSession {
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
println!("WEBSOCKET MESSAGE: {:?}", msg);
match msg {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Pong(msg) => self.hb = Instant::now(),
ws::Message::Text(text) => {
let m = text.trim();
// we check for /sss type of messages
if m.starts_with('/') {
let v: Vec<&str> = m.splitn(2, ' ').collect();
match v[0] {
"/list" => {
// Send ListRooms message to chat server and wait for response
println!("List rooms");
ctx.state().addr.call(self, server::ListRooms).then(|res, _, ctx| {
match res {
Ok(Ok(rooms)) => {
for room in rooms {
ctx.text(&room);
}
},
_ => println!("Something is wrong"),
}
fut::ok(())
}).wait(ctx)
// .wait(ctx) pauses all events in context,
// so actor wont receive any new messages until it get list
// of rooms back
},
"/join" => {
if v.len() == 2 {
self.room = v[1].to_owned();
ctx.state().addr.send(
server::Join{id: self.id, name: self.room.clone()});
ctx.text("joined");
} else {
ctx.text("!!! room name is required");
}
},
"/name" => {
if v.len() == 2 {
self.name = Some(v[1].to_owned());
} else {
ctx.text("!!! name is required");
}
},
_ => ctx.text(&format!("!!! unknown command: {:?}", m)),
}
} else {
let msg = if let Some(ref name) = self.name {
format!("{}: {}", name, m)
} else {
m.to_owned()
};
// send message to chat server
ctx.state().addr.send(
server::Message{id: self.id,
msg: msg,
room: self.room.clone()})
}
},
ws::Message::Binary(bin) =>
println!("Unexpected binary"),
ws::Message::Closed | ws::Message::Error => {
ctx.stop();
}
_ => (),
}
}
}
fn main() {
let _ = env_logger::init();
let sys = actix::System::new("websocket-example");
// Start chat server actor in separate thread
let server: SyncAddress<_> =
Arbiter::start(|_| server::ChatServer::default());
// Start tcp server in separate thread
let srv = server.clone();
Arbiter::new("tcp-server").send::<msgs::Execute>(
msgs::Execute::new(move || {
session::TcpServer::new("127.0.0.1:12345", srv);
Ok(())
}));
// Create Http server with websocket support
let addr = HttpServer::new(
move || {
// Websocket sessions state
let state = WsChatSessionState { addr: server.clone() };
Application::with_state(state)
// redirect to websocket.html
.resource("/", |r| r.method(Method::GET).f(|_| {
httpcodes::HTTPFound
.build()
.header("LOCATION", "/static/websocket.html")
.finish()
}))
// websocket
.resource("/ws/", |r| r.route().f(chat_route))
// static resources
.handler("/static/", fs::StaticFiles::new("static/", true))
})
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -1,204 +0,0 @@
//! `ChatServer` is an actor. It maintains list of connection client session.
//! And manages available rooms. Peers send messages to other peers in same
//! room through `ChatServer`.
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use rand::{self, Rng, ThreadRng};
use actix::prelude::*;
use session;
/// Message for chat server communications
/// New chat session is created
pub struct Connect {
pub addr: Box<actix::Subscriber<session::Message> + Send>,
}
/// Response type for Connect message
///
/// Chat server returns unique session id
impl ResponseType for Connect {
type Item = usize;
type Error = ();
}
/// Session is disconnected
#[derive(Message)]
pub struct Disconnect {
pub id: usize,
}
/// Send message to specific room
#[derive(Message)]
pub struct Message {
/// Id of the client session
pub id: usize,
/// Peer message
pub msg: String,
/// Room name
pub room: String,
}
/// List of available rooms
pub struct ListRooms;
impl ResponseType for ListRooms {
type Item = Vec<String>;
type Error = ();
}
/// Join room, if room does not exists create new one.
#[derive(Message)]
pub struct Join {
/// Client id
pub id: usize,
/// Room name
pub name: String,
}
/// `ChatServer` manages chat rooms and responsible for coordinating chat session.
/// implementation is super primitive
pub struct ChatServer {
sessions: HashMap<usize, Box<actix::Subscriber<session::Message> + Send>>,
rooms: HashMap<String, HashSet<usize>>,
rng: RefCell<ThreadRng>,
}
impl Default for ChatServer {
fn default() -> ChatServer {
// default room
let mut rooms = HashMap::new();
rooms.insert("Main".to_owned(), HashSet::new());
ChatServer {
sessions: HashMap::new(),
rooms: rooms,
rng: RefCell::new(rand::thread_rng()),
}
}
}
impl ChatServer {
/// Send message to all users in the room
fn send_message(&self, room: &str, message: &str, skip_id: usize) {
if let Some(sessions) = self.rooms.get(room) {
for id in sessions {
if *id != skip_id {
if let Some(addr) = self.sessions.get(id) {
let _ = addr.send(session::Message(message.to_owned()));
}
}
}
}
}
}
/// Make actor from `ChatServer`
impl Actor for ChatServer {
/// We are going to use simple Context, we just need ability to communicate
/// with other actors.
type Context = Context<Self>;
}
/// Handler for Connect message.
///
/// Register new session and assign unique id to this session
impl Handler<Connect> for ChatServer {
type Result = MessageResult<Connect>;
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Self::Result {
println!("Someone joined");
// notify all users in same room
self.send_message(&"Main".to_owned(), "Someone joined", 0);
// register session with random id
let id = self.rng.borrow_mut().gen::<usize>();
self.sessions.insert(id, msg.addr);
// auto join session to Main room
self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
// send id back
Ok(id)
}
}
/// Handler for Disconnect message.
impl Handler<Disconnect> for ChatServer {
type Result = ();
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
println!("Someone disconnected");
let mut rooms: Vec<String> = Vec::new();
// remove address
if self.sessions.remove(&msg.id).is_some() {
// remove session from all rooms
for (name, sessions) in &mut self.rooms {
if sessions.remove(&msg.id) {
rooms.push(name.to_owned());
}
}
}
// send message to other users
for room in rooms {
self.send_message(&room, "Someone disconnected", 0);
}
}
}
/// Handler for Message message.
impl Handler<Message> for ChatServer {
type Result = ();
fn handle(&mut self, msg: Message, _: &mut Context<Self>) {
self.send_message(&msg.room, msg.msg.as_str(), msg.id);
}
}
/// Handler for `ListRooms` message.
impl Handler<ListRooms> for ChatServer {
type Result = MessageResult<ListRooms>;
fn handle(&mut self, _: ListRooms, _: &mut Context<Self>) -> Self::Result {
let mut rooms = Vec::new();
for key in self.rooms.keys() {
rooms.push(key.to_owned())
}
Ok(rooms)
}
}
/// Join room, send disconnect message to old room
/// send join message to new room
impl Handler<Join> for ChatServer {
type Result = ();
fn handle(&mut self, msg: Join, _: &mut Context<Self>) {
let Join {id, name} = msg;
let mut rooms = Vec::new();
// remove session from all rooms
for (n, sessions) in &mut self.rooms {
if sessions.remove(&id) {
rooms.push(n.to_owned());
}
}
// send message to other users
for room in rooms {
self.send_message(&room, "Someone disconnected", 0);
}
if self.rooms.get_mut(&name).is_none() {
self.rooms.insert(name.clone(), HashSet::new());
}
self.send_message(&name, "Someone connected", id);
self.rooms.get_mut(&name).unwrap().insert(id);
}
}

View File

@ -1,197 +0,0 @@
//! `ClientSession` is an actor, it manages peer tcp connection and
//! proxies commands from peer to `ChatServer`.
use std::{io, net};
use std::str::FromStr;
use std::time::{Instant, Duration};
use futures::Stream;
use tokio_core::net::{TcpStream, TcpListener};
use actix::prelude::*;
use server::{self, ChatServer};
use codec::{ChatRequest, ChatResponse, ChatCodec};
/// Chat server sends this messages to session
#[derive(Message)]
pub struct Message(pub String);
/// `ChatSession` actor is responsible for tcp peer communications.
pub struct ChatSession {
/// unique session id
id: usize,
/// this is address of chat server
addr: SyncAddress<ChatServer>,
/// Client must send ping at least once per 10 seconds, otherwise we drop connection.
hb: Instant,
/// joined room
room: String,
}
impl Actor for ChatSession {
/// For tcp communication we are going to use `FramedContext`.
/// It is convenient wrapper around `Framed` object from `tokio_io`
type Context = FramedContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
// we'll start heartbeat process on session start.
self.hb(ctx);
// register self in chat server. `AsyncContext::wait` register
// future within context, but context waits until this future resolves
// before processing any other events.
let addr: SyncAddress<_> = ctx.address();
self.addr.call(self, server::Connect{addr: addr.subscriber()})
.then(|res, act, ctx| {
match res {
Ok(Ok(res)) => act.id = res,
// something is wrong with chat server
_ => ctx.stop(),
}
actix::fut::ok(())
}).wait(ctx);
}
fn stopping(&mut self, ctx: &mut Self::Context) -> bool {
// notify chat server
self.addr.send(server::Disconnect{id: self.id});
true
}
}
/// To use `FramedContext` we have to define Io type and Codec
impl FramedActor for ChatSession {
type Io = TcpStream;
type Codec= ChatCodec;
/// This is main event loop for client requests
fn handle(&mut self, msg: io::Result<ChatRequest>, ctx: &mut FramedContext<Self>) {
match msg {
Err(_) => ctx.stop(),
Ok(msg) => match msg {
ChatRequest::List => {
// Send ListRooms message to chat server and wait for response
println!("List rooms");
self.addr.call(self, server::ListRooms).then(|res, _, ctx| {
match res {
Ok(Ok(rooms)) => {
let _ = ctx.send(ChatResponse::Rooms(rooms));
},
_ => println!("Something is wrong"),
}
actix::fut::ok(())
}).wait(ctx)
// .wait(ctx) pauses all events in context,
// so actor wont receive any new messages until it get list of rooms back
},
ChatRequest::Join(name) => {
println!("Join to room: {}", name);
self.room = name.clone();
self.addr.send(server::Join{id: self.id, name: name.clone()});
let _ = ctx.send(ChatResponse::Joined(name));
},
ChatRequest::Message(message) => {
// send message to chat server
println!("Peer message: {}", message);
self.addr.send(
server::Message{id: self.id,
msg: message, room:
self.room.clone()})
}
// we update heartbeat time on ping from peer
ChatRequest::Ping =>
self.hb = Instant::now(),
}
}
}
}
/// Handler for Message, chat server sends this message, we just send string to peer
impl Handler<Message> for ChatSession {
type Result = ();
fn handle(&mut self, msg: Message, ctx: &mut FramedContext<Self>) {
// send message to peer
let _ = ctx.send(ChatResponse::Message(msg.0));
}
}
/// Helper methods
impl ChatSession {
pub fn new(addr: SyncAddress<ChatServer>) -> ChatSession {
ChatSession {id: 0, addr: addr, hb: Instant::now(), room: "Main".to_owned()}
}
/// helper method that sends ping to client every second.
///
/// also this method check heartbeats from client
fn hb(&self, ctx: &mut FramedContext<Self>) {
ctx.run_later(Duration::new(1, 0), |act, ctx| {
// check client heartbeats
if Instant::now().duration_since(act.hb) > Duration::new(10, 0) {
// heartbeat timed out
println!("Client heartbeat failed, disconnecting!");
// notify chat server
act.addr.send(server::Disconnect{id: act.id});
// stop actor
ctx.stop();
}
if ctx.send(ChatResponse::Ping).is_ok() {
// if we can not send message to sink, sink is closed (disconnected)
act.hb(ctx);
}
});
}
}
/// Define tcp server that will accept incoming tcp connection and create
/// chat actors.
pub struct TcpServer {
chat: SyncAddress<ChatServer>,
}
impl TcpServer {
pub fn new(s: &str, chat: SyncAddress<ChatServer>) {
// Create server listener
let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap();
let listener = TcpListener::bind(&addr, Arbiter::handle()).unwrap();
// Our chat server `Server` is an actor, first we need to start it
// and then add stream on incoming tcp connections to it.
// TcpListener::incoming() returns stream of the (TcpStream, net::SocketAddr) items
// So to be able to handle this events `Server` actor has to implement
// stream handler `StreamHandler<(TcpStream, net::SocketAddr), io::Error>`
let _: () = TcpServer::create(|ctx| {
ctx.add_message_stream(listener.incoming()
.map_err(|_| ())
.map(|(t, a)| TcpConnect(t, a)));
TcpServer{chat: chat}
});
}
}
/// Make actor from `Server`
impl Actor for TcpServer {
/// Every actor has to provide execution `Context` in which it can run.
type Context = Context<Self>;
}
#[derive(Message)]
struct TcpConnect(TcpStream, net::SocketAddr);
/// Handle stream of TcpStream's
impl Handler<TcpConnect> for TcpServer {
type Result = ();
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) {
// For each incoming connection we create `ChatSession` actor
// with out chat server address.
let server = self.chat.clone();
let _: () = ChatSession::new(server).framed(msg.0, ChatCodec);
}
}

View File

@ -1,90 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
</script>
<script language="javascript" type="text/javascript">
$(function() {
var conn = null;
function log(msg) {
var control = $('#log');
control.html(control.html() + msg + '<br/>');
control.scrollTop(control.scrollTop() + 1000);
}
function connect() {
disconnect();
var wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://')+window.location.host + '/ws/';
conn = new WebSocket(wsUri);
log('Connecting...');
conn.onopen = function() {
log('Connected.');
update_ui();
};
conn.onmessage = function(e) {
log('Received: ' + e.data);
};
conn.onclose = function() {
log('Disconnected.');
conn = null;
update_ui();
};
}
function disconnect() {
if (conn != null) {
log('Disconnecting...');
conn.close();
conn = null;
update_ui();
}
}
function update_ui() {
var msg = '';
if (conn == null) {
$('#status').text('disconnected');
$('#connect').html('Connect');
} else {
$('#status').text('connected (' + conn.protocol + ')');
$('#connect').html('Disconnect');
}
}
$('#connect').click(function() {
if (conn == null) {
connect();
} else {
disconnect();
}
update_ui();
return false;
});
$('#send').click(function() {
var text = $('#text').val();
log('Sending: ' + text);
conn.send(text);
$('#text').val('').focus();
return false;
});
$('#text').keyup(function(e) {
if (e.keyCode === 13) {
$('#send').click();
return false;
}
});
});
</script>
</head>
<body>
<h3>Chat!</h3>
<div>
<button id="connect">Connect</button>&nbsp;|&nbsp;Status:
<span id="status">disconnected</span>
</div>
<div id="log"
style="width:20em;height:15em;overflow:auto;border:1px solid black">
</div>
<form id="chatform" onsubmit="return false;">
<input id="text" type="text" />
<input id="send" type="button" value="Send" />
</form>
</body>
</html>

View File

@ -1,14 +0,0 @@
[package]
name = "websocket"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
[[bin]]
name = "server"
path = "src/main.rs"
[dependencies]
env_logger = "*"
futures = "0.1"
actix = "^0.4.2"
actix-web = { git = "https://github.com/actix/actix-web.git" }

View File

@ -1,27 +0,0 @@
# websockect
Simple echo websocket server.
## Usage
### server
```bash
cd actix-web/examples/websocket
cargo run
# Started http server: 127.0.0.1:8080
```
### web client
- [http://localhost:8080/ws/index.html](http://localhost:8080/ws/index.html)
### python client
- ``pip install aiohttp``
- ``python websocket-client.py``
if ubuntu :
- ``pip3 install aiohttp``
- ``python3 websocket-client.py``

View File

@ -1,65 +0,0 @@
//! Simple echo websocket server.
//! Open `http://localhost:8080/ws/index.html` in browser
//! or [python console client](https://github.com/actix/actix-web/blob/master/examples/websocket-client.py)
//! could be used for testing.
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix::*;
use actix_web::*;
/// do websocket handshake and start `MyWebSocket` actor
fn ws_index(r: HttpRequest) -> Result<HttpResponse> {
ws::start(r, MyWebSocket)
}
/// websocket connection is long running connection, it easier
/// to handle with an actor
struct MyWebSocket;
impl Actor for MyWebSocket {
type Context = ws::WebsocketContext<Self>;
}
/// Handler for `ws::Message`
impl Handler<ws::Message> for MyWebSocket {
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
// process websocket messages
println!("WS: {:?}", msg);
match msg {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(&text),
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Closed | ws::Message::Error => {
ctx.stop();
}
_ => (),
}
}
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=trace");
let _ = env_logger::init();
let sys = actix::System::new("ws-example");
let _addr = HttpServer::new(
|| Application::new()
// enable logger
.middleware(middleware::Logger::default())
// websocket route
.resource("/ws/", |r| r.method(Method::GET).f(ws_index))
// static files
.handler("/", fs::StaticFiles::new("../static/", true)))
// start http server on 127.0.0.1:8080
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -1,72 +0,0 @@
#!/usr/bin/env python3
"""websocket cmd client for wssrv.py example."""
import argparse
import asyncio
import signal
import sys
import aiohttp
def start_client(loop, url):
name = input('Please enter your name: ')
# send request
ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False)
# input reader
def stdin_callback():
line = sys.stdin.buffer.readline().decode('utf-8')
if not line:
loop.stop()
else:
ws.send_str(name + ': ' + line)
loop.add_reader(sys.stdin.fileno(), stdin_callback)
@asyncio.coroutine
def dispatch():
while True:
msg = yield from ws.receive()
if msg.type == aiohttp.WSMsgType.TEXT:
print('Text: ', msg.data.strip())
elif msg.type == aiohttp.WSMsgType.BINARY:
print('Binary: ', msg.data)
elif msg.type == aiohttp.WSMsgType.PING:
ws.pong()
elif msg.type == aiohttp.WSMsgType.PONG:
print('Pong received')
else:
if msg.type == aiohttp.WSMsgType.CLOSE:
yield from ws.close()
elif msg.type == aiohttp.WSMsgType.ERROR:
print('Error during receive %s' % ws.exception())
elif msg.type == aiohttp.WSMsgType.CLOSED:
pass
break
yield from dispatch()
ARGS = argparse.ArgumentParser(
description="websocket console client for wssrv.py example.")
ARGS.add_argument(
'--host', action="store", dest='host',
default='127.0.0.1', help='Host name')
ARGS.add_argument(
'--port', action="store", dest='port',
default=8080, type=int, help='Port number')
if __name__ == '__main__':
args = ARGS.parse_args()
if ':' in args.host:
args.host, port = args.host.split(':', 1)
args.port = int(port)
url = 'http://{}:{}/ws/'.format(args.host, args.port)
loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, loop.stop)
asyncio.Task(start_client(loop, url))
loop.run_forever()

View File

@ -1,6 +0,0 @@
title = "Actix web"
description = "Actix web framework guide"
author = "Actix Project and Contributors"
[output.html]
google-analytics = "UA-110322332-1"

View File

@ -1,16 +0,0 @@
# Summary
[Quickstart](./qs_1.md)
- [Getting Started](./qs_2.md)
- [Application](./qs_3.md)
- [Server](./qs_3_5.md)
- [Handler](./qs_4.md)
- [Errors](./qs_4_5.md)
- [URL Dispatch](./qs_5.md)
- [Request & Response](./qs_7.md)
- [Testing](./qs_8.md)
- [Middlewares](./qs_10.md)
- [Static file handling](./qs_12.md)
- [WebSockets](./qs_9.md)
- [HTTP/2](./qs_13.md)
- [Database integration](./qs_14.md)

View File

@ -1,34 +0,0 @@
# Quick start
Before you can start writing a actix web application, youll need a version of Rust installed.
We recommend you use rustup to install or configure such a version.
## Install Rust
Before we begin, we need to install Rust using the [rustup](https://www.rustup.rs/) installer:
```bash
curl https://sh.rustup.rs -sSf | sh
```
If you already have rustup installed, run this command to ensure you have the latest version of Rust:
```bash
rustup update
```
Actix web framework requires rust version 1.20 and up.
## Running Examples
The fastest way to start experimenting with actix web is to clone the actix web repository
and run the included examples in the examples/ directory. The following set of
commands runs the `basics` example:
```bash
git clone https://github.com/actix/actix-web
cd actix-web/examples/basics
cargo run
```
Check [examples/](https://github.com/actix/actix-web/tree/master/examples) directory for more examples.

View File

@ -1,206 +0,0 @@
# Middlewares
Actix middlewares system allows to add additional behavior to request/response processing.
Middleware can hook into incoming request process and modify request or halt request
processing and return response early. Also it can hook into response processing.
Typically middlewares involves in following actions:
* Pre-process the Request
* Post-process a Response
* Modify application state
* Access external services (redis, logging, sessions)
Middlewares are registered for each application and get executed in same order as
registration order. In general, *middleware* is a type that implements
[*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method
in this trait has default implementation. Each method can return result immediately
or *future* object.
Here is example of simple middleware that adds request and response headers:
```rust
# extern crate http;
# extern crate actix_web;
use http::{header, HttpTryFrom};
use actix_web::*;
use actix_web::middleware::{Middleware, Started, Response};
struct Headers; // <- Our middleware
/// Middleware implementation, middlewares are generic over application state,
/// so you can access state with `HttpRequest::state()` method.
impl<S> Middleware<S> for Headers {
/// Method is called when request is ready. It may return
/// future, which should resolve before next middleware get called.
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
req.headers_mut().insert(
header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain"));
Ok(Started::Done)
}
/// Method is called when handler returns response,
/// but before sending http message to peer.
fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
resp.headers_mut().insert(
header::HeaderName::try_from("X-VERSION").unwrap(),
header::HeaderValue::from_static("0.2"));
Ok(Response::Done(resp))
}
}
fn main() {
Application::new()
.middleware(Headers) // <- Register middleware, this method could be called multiple times
.resource("/", |r| r.h(httpcodes::HTTPOk));
}
```
Active provides several useful middlewares, like *logging*, *user sessions*, etc.
## Logging
Logging is implemented as middleware.
It is common to register logging middleware as first middleware for application.
Logging middleware has to be registered for each application.
### Usage
Create `Logger` middleware with the specified `format`.
Default `Logger` could be created with `default` method, it uses the default format:
```ignore
%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
```
```rust
# extern crate actix_web;
use actix_web::Application;
use actix_web::middleware::Logger;
fn main() {
Application::new()
.middleware(Logger::default())
.middleware(Logger::new("%a %{User-Agent}i"))
.finish();
}
```
Here is example of default logging format:
```
INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397
INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646
```
### Format
`%%` The percent sign
`%a` Remote IP-address (IP-address of proxy if using reverse proxy)
`%t` Time when the request was started to process
`%P` The process ID of the child that serviced the request
`%r` First line of request
`%s` Response status code
`%b` Size of response in bytes, including HTTP headers
`%T` Time taken to serve the request, in seconds with floating fraction in .06f format
`%D` Time taken to serve the request, in milliseconds
`%{FOO}i` request.headers['FOO']
`%{FOO}o` response.headers['FOO']
`%{FOO}e` os.environ['FOO']
## Default headers
To set default response headers `DefaultHeaders` middleware could be used.
*DefaultHeaders* middleware does not set header if response headers already contains
specified header.
```rust
# extern crate actix_web;
use actix_web::*;
fn main() {
let app = Application::new()
.middleware(
middleware::DefaultHeaders::build()
.header("X-Version", "0.2")
.finish())
.resource("/test", |r| {
r.method(Method::GET).f(|req| httpcodes::HTTPOk);
r.method(Method::HEAD).f(|req| httpcodes::HTTPMethodNotAllowed);
})
.finish();
}
```
## User sessions
Actix provides general solution for session management.
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware can be
use with different backend types to store session data in different backends.
By default only cookie session backend is implemented. Other backend implementations
could be added later.
[*Cookie session backend*](../actix_web/middleware/struct.CookieSessionBackend.html)
uses signed cookies as session storage. *Cookie session backend* creates sessions which
are limited to storing fewer than 4000 bytes of data (as the payload must fit into a
single cookie). Internal server error get generated if session contains more than 4000 bytes.
You need to pass a random value to the constructor of *CookieSessionBackend*.
This is private key for cookie session. When this value is changed, all session data is lost.
Note that whatever you write into your session is visible by the user (but not modifiable).
In general case, you create
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware
and initializes it with specific backend implementation, like *CookieSessionBackend*.
To access session data
[*HttpRequest::session()*](../actix_web/middleware/trait.RequestSession.html#tymethod.session)
method has to be used. This method returns
[*Session*](../actix_web/middleware/struct.Session.html) object, which allows to get or set
session data.
```rust
# extern crate actix;
# extern crate actix_web;
use actix_web::*;
use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend};
fn index(mut req: HttpRequest) -> Result<&'static str> {
// access session data
if let Some(count) = req.session().get::<i32>("counter")? {
println!("SESSION value: {}", count);
req.session().set("counter", count+1)?;
} else {
req.session().set("counter", 1)?;
}
Ok("Welcome!")
}
fn main() {
# let sys = actix::System::new("basic-example");
HttpServer::new(
|| Application::new()
.middleware(SessionStorage::new( // <- create session middleware
CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend
.secure(false)
.finish()
)))
.bind("127.0.0.1:59880").unwrap()
.start();
# actix::Arbiter::system().send(actix::msgs::SystemExit(0));
# let _ = sys.run();
}
```

View File

@ -1,44 +0,0 @@
# Static file handling
## Individual file
It is possible to serve static files with custom path pattern and `NamedFile`. To
match path tail we can use `[.*]` regex.
```rust
# extern crate actix_web;
use actix_web::*;
use std::path::PathBuf;
fn index(req: HttpRequest) -> Result<fs::NamedFile> {
let path: PathBuf = req.match_info().query("tail")?;
Ok(fs::NamedFile::open(path)?)
}
fn main() {
Application::new()
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
.finish();
}
```
## Directory
To serve files from specific directory and sub-directories `StaticFiles` could be used.
`StaticFiles` must be registered with `Application::handler()` method otherwise
it won't be able to server sub-paths.
```rust
# extern crate actix_web;
use actix_web::*;
fn main() {
Application::new()
.handler("/static", fs::StaticFiles::new(".", true))
.finish();
}
```
First parameter is a base directory. Second parameter is *show_index*, if it is set to *true*
directory listing would be returned for directories, if it is set to *false*
then *404 Not Found* would be returned instead of directory listing.

View File

@ -1,42 +0,0 @@
# HTTP/2.0
Actix web automatically upgrades connection to *HTTP/2.0* if possible.
## Negotiation
*HTTP/2.0* protocol over tls without prior knowledge requires
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
`rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation.
With enable `alpn` feature `HttpServer` provides
[serve_tls](../actix_web/struct.HttpServer.html#method.serve_tls) method.
```toml
[dependencies]
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] }
```
```rust,ignore
use std::fs::File;
use actix_web::*;
fn main() {
let mut file = File::open("identity.pfx").unwrap();
let mut pkcs12 = vec![];
file.read_to_end(&mut pkcs12).unwrap();
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
HttpServer::new(
|| Application::new()
.resource("/index.html", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap();
.serve_ssl(pkcs12).unwrap();
}
```
Upgrade to *HTTP/2.0* schema described in
[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported.
Starting *HTTP/2* with prior knowledge is supported for both clear text connection
and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4)
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls)
for concrete example.

View File

@ -1,125 +0,0 @@
# Database integration
## Diesel
At the moment of 1.0 release Diesel does not support asynchronous operations.
But it possible to use `actix` synchronous actor system as a db interface api.
Technically sync actors are worker style actors, multiple of them
can be run in parallel and process messages from same queue (sync actors work in mpmc mode).
Let's create simple db api that can insert new user row into sqlite table.
We have to define sync actor and connection that this actor will use. Same approach
could used for other databases.
```rust,ignore
use actix::prelude::*;*
struct DbExecutor(SqliteConnection);
impl Actor for DbExecutor {
type Context = SyncContext<Self>;
}
```
This is definition of our actor. Now we need to define *create user* message and response.
```rust,ignore
#[derive(Message)]
#[rtype(User, Error)]
struct CreateUser {
name: String,
}
```
We can send `CreateUser` message to `DbExecutor` actor, and as result we get
`User` model. Now we need to define actual handler implementation for this message.
```rust,ignore
impl Handler<CreateUser> for DbExecutor {
type Result = Result<User, Error>
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result
{
use self::schema::users::dsl::*;
// Create insertion model
let uuid = format!("{}", uuid::Uuid::new_v4());
let new_user = models::NewUser {
id: &uuid,
name: &msg.name,
};
// normal diesl operations
diesel::insert_into(users)
.values(&new_user)
.execute(&self.0)
.expect("Error inserting person");
let mut items = users
.filter(id.eq(&uuid))
.load::<models::User>(&self.0)
.expect("Error loading person");
Ok(items.pop().unwrap())
}
}
```
That is it. Now we can use *DbExecutor* actor from any http handler or middleware.
All we need is to start *DbExecutor* actors and store address in a state where http handler
can access it.
```rust,ignore
/// This is state where we will store *DbExecutor* address.
struct State {
db: SyncAddress<DbExecutor>,
}
fn main() {
let sys = actix::System::new("diesel-example");
// Start 3 parallel db executors
let addr = SyncArbiter::start(3, || {
DbExecutor(SqliteConnection::establish("test.db").unwrap())
});
// Start http server
HttpServer::new(move || {
Application::with_state(State{db: addr.clone()})
.resource("/{name}", |r| r.method(Method::GET).a(index))})
.bind("127.0.0.1:8080").unwrap()
.start().unwrap();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}
```
And finally we can use address in a request handler. We get message response
asynchronously, so handler needs to return future object, also `Route::a()` needs to be
used for async handler registration.
```rust,ignore
/// Async handler
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
let name = &req.match_info()["name"];
// Send message to `DbExecutor` actor
req.state().db.call_fut(CreateUser{name: name.to_owned()})
.from_err()
.and_then(|res| {
match res {
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?),
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
}
})
.responder()
}
```
Full example is available in
[examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/).
More information on sync actors could be found in
[actix documentation](https://docs.rs/actix/0.3.3/actix/sync/index.html).

View File

@ -1,95 +0,0 @@
# Getting Started
Lets create and run our first actix web application. Well create a new Cargo project
that depends on actix web and then run the application.
In previous section we already installed required rust version. Now let's create new cargo projects.
## Hello, world!
Lets write our first actix web application! Start by creating a new binary-based
Cargo project and changing into the new directory:
```bash
cargo new hello-world --bin
cd hello-world
```
Now, add actix and actix web as dependencies of your project by ensuring your Cargo.toml
contains the following:
```toml
[dependencies]
actix = "0.4"
actix-web = "0.3"
```
In order to implement a web server, first we need to create a request handler.
A request handler is a function that accepts a `HttpRequest` instance as its only parameter
and returns a type that can be converted into `HttpResponse`:
```rust
# extern crate actix_web;
# use actix_web::*;
fn index(req: HttpRequest) -> &'static str {
"Hello world!"
}
# fn main() {}
```
Next, create an `Application` instance and register the
request handler with the application's `resource` on a particular *HTTP method* and *path*::
```rust
# extern crate actix_web;
# use actix_web::*;
# fn index(req: HttpRequest) -> &'static str {
# "Hello world!"
# }
# fn main() {
Application::new()
.resource("/", |r| r.f(index));
# }
```
After that, application instance can be used with `HttpServer` to listen for incoming
connections. Server accepts function that should return `HttpHandler` instance:
```rust,ignore
HttpServer::new(
|| Application::new()
.resource("/", |r| r.f(index)))
.bind("127.0.0.1:8088")?
.run();
```
That's it. Now, compile and run the program with cargo run.
Head over to ``http://localhost:8088/`` to see the results.
Here is full source of main.rs file:
```rust
# use std::thread;
# extern crate actix_web;
use actix_web::*;
fn index(req: HttpRequest) -> &'static str {
"Hello world!"
}
fn main() {
# thread::spawn(|| {
HttpServer::new(
|| Application::new()
.resource("/", |r| r.f(index)))
.bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088")
.run();
# });
}
```
Note on `actix` crate. Actix web framework is built on top of actix actor library.
`actix::System` initializes actor system, `HttpServer` is an actor and must run within
properly configured actix system. For more information please check
[actix documentation](https://actix.github.io/actix/actix/)

View File

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

View File

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

View File

@ -1,237 +0,0 @@
# Handler
A request handler can by any object that implements
[*Handler trait*](../actix_web/dev/trait.Handler.html).
Request handling happen in two stages. First handler object get called.
Handle can return any object that implements
[*Responder trait*](../actix_web/trait.Responder.html#foreign-impls).
Then `respond_to()` get called on returned object. And finally
result of the `respond_to()` call get converted to `Reply` object.
By default actix provides `Responder` implementations for some standard types,
like `&'static str`, `String`, etc.
For complete list of implementations check
[*Responder documentation*](../actix_web/trait.Responder.html#foreign-impls).
Examples of valid handlers:
```rust,ignore
fn index(req: HttpRequest) -> &'static str {
"Hello world!"
}
```
```rust,ignore
fn index(req: HttpRequest) -> String {
"Hello world!".to_owned()
}
```
```rust,ignore
fn index(req: HttpRequest) -> Bytes {
Bytes::from_static("Hello world!")
}
```
```rust,ignore
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
...
}
```
Some notes on shared application state and handler state. If you noticed
*Handler* trait is generic over *S*, which defines application state type. So
application state is accessible from handler with `HttpRequest::state()` method.
But state is accessible as a read-only reference, if you need mutable access to state
you have to implement it yourself. On other hand handler can mutable access it's own state
as `handle` method takes mutable reference to *self*. Beware, actix creates multiple copies
of application state and handlers, unique for each thread, so if you run your
application in several threads actix will create same amount as number of threads
of application state objects and handler objects.
Here is example of handler that stores number of processed requests:
```rust
# extern crate actix;
# extern crate actix_web;
use actix_web::*;
use actix_web::dev::Handler;
struct MyHandler(usize);
impl<S> Handler<S> for MyHandler {
type Result = HttpResponse;
/// Handle request
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
self.0 += 1;
httpcodes::HTTPOk.into()
}
}
# fn main() {}
```
This handler will work, but `self.0` value will be different depends on number of threads and
number of requests processed per thread. Proper implementation would use `Arc` and `AtomicUsize`
```rust
# extern crate actix;
# extern crate actix_web;
use actix_web::*;
use actix_web::dev::Handler;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
struct MyHandler(Arc<AtomicUsize>);
impl<S> Handler<S> for MyHandler {
type Result = HttpResponse;
/// Handle request
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let num = self.0.load(Ordering::Relaxed) + 1;
self.0.store(num, Ordering::Relaxed);
httpcodes::HTTPOk.into()
}
}
fn main() {
let sys = actix::System::new("example");
let inc = Arc::new(AtomicUsize::new(0));
HttpServer::new(
move || {
let cloned = inc.clone();
Application::new()
.resource("/", move |r| r.h(MyHandler(cloned)))
})
.bind("127.0.0.1:8088").unwrap()
.start();
println!("Started http server: 127.0.0.1:8088");
# actix::Arbiter::system().send(actix::msgs::SystemExit(0));
let _ = sys.run();
}
```
Be careful with synchronization primitives like *Mutex* or *RwLock*. Actix web framework
handles request asynchronously, by blocking thread execution all concurrent
request handling processes would block. If you need to share or update some state
from multiple threads consider using [actix](https://actix.github.io/actix/actix/) actor system.
## Response with custom type
To return custom type directly from handler function, type needs to implement `Responder` trait.
Let's create response for custom type that serializes to `application/json` response:
```rust
# extern crate actix;
# extern crate actix_web;
extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
use actix_web::*;
#[derive(Serialize)]
struct MyObj {
name: &'static str,
}
/// Responder
impl Responder for MyObj {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse> {
let body = serde_json::to_string(&self)?;
// Create response and set content type
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body)?)
}
}
/// Because `MyObj` implements `Responder`, it is possible to return it directly
fn index(req: HttpRequest) -> MyObj {
MyObj{name: "user"}
}
fn main() {
let sys = actix::System::new("example");
HttpServer::new(
|| Application::new()
.resource("/", |r| r.method(Method::GET).f(index)))
.bind("127.0.0.1:8088").unwrap()
.start();
println!("Started http server: 127.0.0.1:8088");
# actix::Arbiter::system().send(actix::msgs::SystemExit(0));
let _ = sys.run();
}
```
## Async handlers
There are two different types of async handlers.
Response object could be generated asynchronously or more precisely, any type
that implements [*Responder*](../actix_web/trait.Responder.html) trait. In this case handle must
return `Future` object that resolves to *Responder* type, i.e:
```rust
# extern crate actix_web;
# extern crate futures;
# extern crate bytes;
# use actix_web::*;
# use bytes::Bytes;
# use futures::stream::once;
# use futures::future::{FutureResult, result};
fn index(req: HttpRequest) -> FutureResult<HttpResponse, Error> {
result(HttpResponse::Ok()
.content_type("text/html")
.body(format!("Hello!"))
.map_err(|e| e.into()))
}
fn index2(req: HttpRequest) -> FutureResult<&'static str, Error> {
result(Ok("Welcome!"))
}
fn main() {
Application::new()
.resource("/async", |r| r.route().a(index))
.resource("/", |r| r.route().a(index2))
.finish();
}
```
Or response body can be generated asynchronously. In this case body
must implement stream trait `Stream<Item=Bytes, Error=Error>`, i.e:
```rust
# extern crate actix_web;
# extern crate futures;
# extern crate bytes;
# use actix_web::*;
# use bytes::Bytes;
# use futures::stream::once;
fn index(req: HttpRequest) -> HttpResponse {
let body = once(Ok(Bytes::from_static(b"test")));
HttpResponse::Ok()
.content_type("application/json")
.body(Body::Streaming(Box::new(body))).unwrap()
}
fn main() {
Application::new()
.resource("/async", |r| r.f(index))
.finish();
}
```
Both methods could be combined. (i.e Async response with streaming body)

View File

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

View File

@ -1,578 +0,0 @@
# URL Dispatch
URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching
language. *Regex* crate and it's
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is being used for
pattern matching. If one of the patterns matches the path information associated with a request,
a particular handler object is invoked. A handler is a specific object that implements
`Handler` trait, defined in your application, that receives the request and returns
a response object. More information is available in [handler section](../qs_4.html).
## Resource configuration
Resource configuration is the act of adding a new resource to an application.
A resource has a name, which acts as an identifier to be used for URL generation.
The name also allows developers to add routes to existing resources.
A resource also has a pattern, meant to match against the *PATH* portion of a *URL*,
it does not match against *QUERY* portion (the portion following the scheme and
port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*).
The [Application::resource](../actix_web/struct.Application.html#method.resource) methods
add a single resource to application routing table. This method accepts *path pattern*
and resource configuration function.
```rust
# extern crate actix_web;
# use actix_web::*;
# use actix_web::httpcodes::*;
#
# fn index(req: HttpRequest) -> HttpResponse {
# unimplemented!()
# }
#
fn main() {
Application::new()
.resource("/prefix", |r| r.f(index))
.resource("/user/{name}",
|r| r.method(Method::GET).f(|req| HTTPOk))
.finish();
}
```
*Configuration function* has following type:
```rust,ignore
FnOnce(&mut Resource<_>) -> ()
```
*Configuration function* can set name and register specific routes.
If resource does not contain any route or does not have any matching routes it
returns *NOT FOUND* http resources.
## Configuring a Route
Resource contains set of routes. Each route in turn has set of predicates and handler.
New route could be created with `Resource::route()` method which returns reference
to new *Route* instance. By default *route* does not contain any predicates, so matches
all requests and default handler is `HTTPNotFound`.
Application routes incoming requests based on route criteria which is defined during
resource registration and route registration. Resource matches all routes it contains in
the order that the routes were registered via `Resource::route()`. *Route* can contain
any number of *predicates* but only one handler.
```rust
# extern crate actix_web;
# use actix_web::*;
# use actix_web::httpcodes::*;
fn main() {
Application::new()
.resource("/path", |resource|
resource.route()
.p(pred::Get())
.p(pred::Header("content-type", "text/plain"))
.f(|req| HTTPOk)
)
.finish();
}
```
In this example `index` get called for *GET* request,
if request contains `Content-Type` header and value of this header is *text/plain*
and path equals to `/test`. Resource calls handle of the first matches route.
If resource can not match any route "NOT FOUND" response get returned.
[*Resource::route()*](../actix_web/struct.Resource.html#method.route) method returns
[*Route*](../actix_web/struct.Route.html) object. Route can be configured with
builder-like pattern. Following configuration methods are available:
* [*Route::p()*](../actix_web/struct.Route.html#method.p) method registers new predicate,
any number of predicates could be registered for each route.
* [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function
for this route. Only one handler could be registered. Usually handler registration
is the last config operation. Handler function could be function or closure and has type
`Fn(HttpRequest<S>) -> R + 'static`
* [*Route::h()*](../actix_web/struct.Route.html#method.h) method registers handler object
that implements `Handler` trait. This is similar to `f()` method, only one handler could
be registered. Handler registration is the last config operation.
* [*Route::a()*](../actix_web/struct.Route.html#method.a) method registers async handler
function for this route. Only one handler could be registered. Handler registration
is the last config operation. Handler function could be function or closure and has type
`Fn(HttpRequest<S>) -> Future<Item = HttpResponse, Error = Error> + 'static`
## Route matching
The main purpose of route configuration is to match (or not match) the request's `path`
against a URL path pattern. `path` represents the path portion of the URL that was requested.
The way that *actix* does this is very simple. When a request enters the system,
for each resource configuration registration present in the system, actix checks
the request's path against the pattern declared. *Regex* crate and it's
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is being used for
pattern matching. If resource could not be found, *default resource* get used as matched
resource.
When a route configuration is declared, it may contain route predicate arguments. All route
predicates associated with a route declaration must be `true` for the route configuration to
be used for a given request during a check. If any predicate in the set of route predicate
arguments provided to a route configuration returns `false` during a check, that route is
skipped and route matching continues through the ordered set of routes.
If any route matches, the route matching process stops and the handler associated with
route get invoked.
If no route matches after all route patterns are exhausted, *NOT FOUND* response get returned.
## Resource pattern syntax
The syntax of the pattern matching language used by the actix in the pattern
argument is straightforward.
The pattern used in route configuration may start with a slash character. If the pattern
does not start with a slash character, an implicit slash will be prepended
to it at matching time. For example, the following patterns are equivalent:
```
{foo}/bar/baz
```
and:
```
/{foo}/bar/baz
```
A *variable part* (replacement marker) is specified in the form *{identifier}*,
where this means "accept any characters up to the next slash character and use this
as the name in the `HttpRequest.match_info()` object".
A replacement marker in a pattern matches the regular expression `[^{}/]+`.
A match_info is the `Params` object representing the dynamic parts extracted from a
*URL* based on the routing pattern. It is available as *request.match_info*. For example, the
following pattern defines one literal segment (foo) and two replacement markers (baz, and bar):
```
foo/{baz}/{bar}
```
The above pattern will match these URLs, generating the following match information:
```
foo/1/2 -> Params {'baz':'1', 'bar':'2'}
foo/abc/def -> Params {'baz':'abc', 'bar':'def'}
```
It will not match the following patterns however:
```
foo/1/2/ -> No match (trailing slash)
bar/abc/def -> First segment literal mismatch
```
The match for a segment replacement marker in a segment will be done only up to
the first non-alphanumeric character in the segment in the pattern. So, for instance,
if this route pattern was used:
```
foo/{name}.html
```
The literal path */foo/biz.html* will match the above route pattern, and the match result
will be `Params{'name': 'biz'}`. However, the literal path */foo/biz* will not match,
because it does not contain a literal *.html* at the end of the segment represented
by *{name}.html* (it only contains biz, not biz.html).
To capture both segments, two replacement markers can be used:
```
foo/{name}.{ext}
```
The literal path */foo/biz.html* will match the above route pattern, and the match
result will be *Params{'name': 'biz', 'ext': 'html'}*. This occurs because there is a
literal part of *.* (period) between the two replacement markers *{name}* and *{ext}*.
Replacement markers can optionally specify a regular expression which will be used to decide
whether a path segment should match the marker. To specify that a replacement marker should
match only a specific set of characters as defined by a regular expression, you must use a
slightly extended form of replacement marker syntax. Within braces, the replacement marker
name must be followed by a colon, then directly thereafter, the regular expression. The default
regular expression associated with a replacement marker *[^/]+* matches one or more characters
which are not a slash. For example, under the hood, the replacement marker *{foo}* can more
verbosely be spelled as *{foo:[^/]+}*. You can change this to be an arbitrary regular expression
to match an arbitrary sequence of characters, such as *{foo:\d+}* to match only digits.
Segments must contain at least one character in order to match a segment replacement marker.
For example, for the URL */abc/*:
* */abc/{foo}* will not match.
* */{foo}/* will match.
Note that path will be URL-unquoted and decoded into valid unicode string before
matching pattern and values representing matched path segments will be URL-unquoted too.
So for instance, the following pattern:
```
foo/{bar}
```
When matching the following URL:
```
http://example.com/foo/La%20Pe%C3%B1a
```
The matchdict will look like so (the value is URL-decoded):
```
Params{'bar': 'La Pe\xf1a'}
```
Literal strings in the path segment should represent the decoded value of the
path provided to actix. You don't want to use a URL-encoded value in the pattern.
For example, rather than this:
```
/Foo%20Bar/{baz}
```
You'll want to use something like this:
```
/Foo Bar/{baz}
```
It is possible to get "tail match". For this purpose custom regex has to be used.
```
foo/{bar}/{tail:.*}
```
The above pattern will match these URLs, generating the following match information:
```
foo/1/2/ -> Params{'bar':'1', 'tail': '2/'}
foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'}
```
## Match information
All values representing matched path segments are available in
[`HttpRequest::match_info`](../actix_web/struct.HttpRequest.html#method.match_info).
Specific value can be received with
[`Params::get()`](../actix_web/dev/struct.Params.html#method.get) method.
Any matched parameter can be deserialized into specific type if this type
implements `FromParam` trait. For example most of standard integer types
implements `FromParam` trait. i.e.:
```rust
# extern crate actix_web;
use actix_web::*;
fn index(req: HttpRequest) -> Result<String> {
let v1: u8 = req.match_info().query("v1")?;
let v2: u8 = req.match_info().query("v2")?;
Ok(format!("Values {} {}", v1, v2))
}
fn main() {
Application::new()
.resource(r"/a/{v1}/{v2}/", |r| r.f(index))
.finish();
}
```
For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2".
It is possible to create a `PathBuf` from a tail path parameter. The returned `PathBuf` is
percent-decoded. If a segment is equal to "..", the previous segment (if
any) is skipped.
For security purposes, if a segment meets any of the following conditions,
an `Err` is returned indicating the condition met:
* Decoded segment starts with any of: `.` (except `..`), `*`
* Decoded segment ends with any of: `:`, `>`, `<`
* Decoded segment contains any of: `/`
* On Windows, decoded segment contains any of: '\'
* Percent-encoding results in invalid UTF8.
As a result of these conditions, a `PathBuf` parsed from request path parameter is
safe to interpolate within, or use as a suffix of, a path without additional checks.
```rust
# extern crate actix_web;
use actix_web::*;
use std::path::PathBuf;
fn index(req: HttpRequest) -> Result<String> {
let path: PathBuf = req.match_info().query("tail")?;
Ok(format!("Path {:?}", path))
}
fn main() {
Application::new()
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
.finish();
}
```
List of `FromParam` implementation could be found in
[api docs](../actix_web/dev/trait.FromParam.html#foreign-impls)
## Generating resource URLs
Use the [HttpRequest.url_for()](../actix_web/struct.HttpRequest.html#method.url_for)
method to generate URLs based on resource patterns. For example, if you've configured a
resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this.
```rust
# extern crate actix_web;
# use actix_web::*;
# use actix_web::httpcodes::*;
#
fn index(req: HttpRequest) -> HttpResponse {
let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
HTTPOk.into()
}
fn main() {
let app = Application::new()
.resource("/test/{a}/{b}/{c}", |r| {
r.name("foo"); // <- set resource name, then it could be used in `url_for`
r.method(Method::GET).f(|_| httpcodes::HTTPOk);
})
.finish();
}
```
This would return something like the string *http://example.com/test/1/2/3* (at least if
the current protocol and hostname implied http://example.com).
`url_for()` method return [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you
can modify this url (add query parameters, anchor, etc).
`url_for()` could be called only for *named* resources otherwise error get returned.
## External resources
Resources that are valid URLs, could be registered as external resources. They are useful
for URL generation purposes only and are never considered for matching at request time.
```rust
# extern crate actix_web;
use actix_web::*;
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
Ok(httpcodes::HTTPOk.into())
}
fn main() {
let app = Application::new()
.resource("/index.html", |r| r.f(index))
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
.finish();
}
```
## Path normalization and redirecting to slash-appended routes
By normalizing it means:
- Add a trailing slash to the path.
- Double slashes are replaced by one.
The handler returns as soon as it finds a path that resolves
correctly. The order if all enable is 1) merge, 3) both merge and append
and 3) append. If the path resolves with
at least one of those conditions, it will redirect to the new path.
If *append* is *true* append slash when needed. If a resource is
defined with trailing slash and the request comes without it, it will
append it automatically.
If *merge* is *true*, merge multiple consecutive slashes in the path into one.
This handler designed to be use as a handler for application's *default resource*.
```rust
# extern crate actix_web;
# #[macro_use] extern crate serde_derive;
# use actix_web::*;
#
# fn index(req: HttpRequest) -> httpcodes::StaticResponse {
# httpcodes::HTTPOk
# }
fn main() {
let app = Application::new()
.resource("/resource/", |r| r.f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
}
```
In this example `/resource`, `//resource///` will be redirected to `/resource/` url.
In this example path normalization handler get registered for all method,
but you should not rely on this mechanism to redirect *POST* requests. The redirect of the
slash-appending *Not Found* will turn a *POST* request into a GET, losing any
*POST* data in the original request.
It is possible to register path normalization only for *GET* requests only
```rust
# extern crate actix_web;
# #[macro_use] extern crate serde_derive;
# use actix_web::*;
#
# fn index(req: HttpRequest) -> httpcodes::StaticResponse {
# httpcodes::HTTPOk
# }
fn main() {
let app = Application::new()
.resource("/resource/", |r| r.f(index))
.default_resource(|r| r.method(Method::GET).h(NormalizePath::default()))
.finish();
}
```
## Using a Application Prefix to Compose Applications
The `Application::prefix()`" method allows to set specific application prefix.
This prefix represents a resource prefix that will be prepended to all resource patterns added
by the resource configuration. This can be used to help mount a set of routes at a different
location than the included callable's author intended while still maintaining the same
resource names.
For example:
```rust
# extern crate actix_web;
# use actix_web::*;
#
fn show_users(req: HttpRequest) -> HttpResponse {
unimplemented!()
}
fn main() {
Application::new()
.prefix("/users")
.resource("/show", |r| r.f(show_users))
.finish();
}
```
In the above example, the *show_users* route will have an effective route pattern of
*/users/show* instead of */show* because the application's prefix argument will be prepended
to the pattern. The route will then only match if the URL path is */users/show*,
and when the `HttpRequest.url_for()` function is called with the route name show_users,
it will generate a URL with that same path.
## Custom route predicates
You can think of predicate as simple function that accept *request* object reference
and returns *true* or *false*. Formally predicate is any object that implements
[`Predicate`](../actix_web/pred/trait.Predicate.html) trait. Actix provides
several predicates, you can check [functions section](../actix_web/pred/index.html#functions)
of api docs.
Here is simple predicates that check that request contains specific *header*:
```rust
# extern crate actix_web;
# extern crate http;
# use actix_web::*;
# use actix_web::httpcodes::*;
use http::header::CONTENT_TYPE;
use actix_web::pred::Predicate;
struct ContentTypeHeader;
impl<S: 'static> Predicate<S> for ContentTypeHeader {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
req.headers().contains_key(CONTENT_TYPE)
}
}
fn main() {
Application::new()
.resource("/index.html", |r|
r.route()
.p(ContentTypeHeader)
.h(HTTPOk));
}
```
In this example *index* handler will be called only if request contains *CONTENT-TYPE* header.
Predicates can have access to application's state via `HttpRequest::state()` method.
Also predicates can store extra information in
[requests`s extensions](../actix_web/struct.HttpRequest.html#method.extensions).
### Modifying predicate values
You can invert the meaning of any predicate value by wrapping it in a `Not` predicate.
For example if you want to return "METHOD NOT ALLOWED" response for all methods
except "GET":
```rust
# extern crate actix_web;
# extern crate http;
# use actix_web::*;
# use actix_web::httpcodes::*;
use actix_web::pred;
fn main() {
Application::new()
.resource("/index.html", |r|
r.route()
.p(pred::Not(pred::Get()))
.f(|req| HTTPMethodNotAllowed))
.finish();
}
```
`Any` predicate accept list of predicates and matches if any of the supplied
predicates match. i.e:
```rust,ignore
pred::Any(pred::Get()).or(pred::Post())
```
`All` predicate accept list of predicates and matches if all of the supplied
predicates match. i.e:
```rust,ignore
pred::All(pred::Get()).and(pred::Header("content-type", "plain/text"))
```
## Changing the default Not Found response
If path pattern can not be found in routing table or resource can not find matching
route, default resource is used. Default response is *NOT FOUND* response.
It is possible to override *NOT FOUND* response with `Application::default_resource()` method.
This method accepts *configuration function* same as normal resource configuration
with `Application::resource()` method.
```rust
# extern crate actix_web;
# extern crate http;
use actix_web::*;
use actix_web::httpcodes::*;
fn main() {
Application::new()
.default_resource(|r| {
r.method(Method::GET).f(|req| HTTPNotFound);
r.route().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed);
})
# .finish();
}
```

View File

@ -1,297 +0,0 @@
# Request & Response
## Response
Builder-like patter is used to construct an instance of `HttpResponse`.
`HttpResponse` provides several method that returns `HttpResponseBuilder` instance,
which is implements various convenience methods that helps build response.
Check [documentation](../actix_web/dev/struct.HttpResponseBuilder.html)
for type description. Methods `.body`, `.finish`, `.json` finalizes response creation and
returns constructed *HttpResponse* instance. if this methods get called for the same
builder instance multiple times, builder will panic.
```rust
# extern crate actix_web;
use actix_web::*;
use actix_web::headers::ContentEncoding;
fn index(req: HttpRequest) -> HttpResponse {
HttpResponse::Ok()
.content_encoding(ContentEncoding::Br)
.content_type("plain/text")
.header("X-Hdr", "sample")
.body("data").unwrap()
}
# fn main() {}
```
## Content encoding
Actix automatically *compress*/*decompress* payload. Following codecs are supported:
* Brotli
* Gzip
* Deflate
* Identity
If request headers contains `Content-Encoding` header, request payload get decompressed
according to header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`.
Response payload get compressed based on *content_encoding* parameter.
By default `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected
then compression depends on request's `Accept-Encoding` header.
`ContentEncoding::Identity` could be used to disable compression.
If other content encoding is selected the compression is enforced for this codec. For example,
to enable `brotli` response's body compression use `ContentEncoding::Br`:
```rust
# extern crate actix_web;
use actix_web::*;
use actix_web::headers::ContentEncoding;
fn index(req: HttpRequest) -> HttpResponse {
HttpResponse::Ok()
.content_encoding(ContentEncoding::Br)
.body("data").unwrap()
}
# fn main() {}
```
## JSON Request
There are two options of json body deserialization.
First option is to use *HttpResponse::json()* method. This method returns
[*JsonBody*](../actix_web/dev/struct.JsonBody.html) object which resolves into
deserialized value.
```rust
# extern crate actix;
# extern crate actix_web;
# extern crate futures;
# extern crate serde_json;
# #[macro_use] extern crate serde_derive;
# use actix_web::*;
# use futures::Future;
#[derive(Debug, Serialize, Deserialize)]
struct MyObj {
name: String,
number: i32,
}
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.json().from_err()
.and_then(|val: MyObj| {
println!("model: {:?}", val);
Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response
})
.responder()
}
# fn main() {}
```
Or you can manually load payload into memory and then deserialize it.
Here is simple example. We will deserialize *MyObj* struct. We need to load request
body first and then deserialize json into object.
```rust
# extern crate actix_web;
# extern crate futures;
# use actix_web::*;
# #[macro_use] extern crate serde_derive;
extern crate serde_json;
use futures::{Future, Stream};
#[derive(Serialize, Deserialize)]
struct MyObj {name: String, number: i32}
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// `concat2` will asynchronously read each chunk of the request body and
// return a single, concatenated, chunk
req.payload_mut().readany().concat2()
// `Future::from_err` acts like `?` in that it coerces the error type from
// the future into the final error type
.from_err()
// `Future::and_then` can be used to merge an asynchronous workflow with a
// synchronous workflow
.and_then(|body| { // <- body is loaded, now we can deserialize json
let obj = serde_json::from_slice::<MyObj>(&body)?;
Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response
})
.responder()
}
# fn main() {}
```
Complete example for both options is available in
[examples directory](https://github.com/actix/actix-web/tree/master/examples/json/).
## JSON Response
The `Json` type allows you to respond with well-formed JSON data: simply return a value of
type Json<T> where T is the type of a structure to serialize into *JSON*. The
type `T` must implement the `Serialize` trait from *serde*.
```rust
# extern crate actix_web;
#[macro_use] extern crate serde_derive;
use actix_web::*;
#[derive(Serialize)]
struct MyObj {
name: String,
}
fn index(req: HttpRequest) -> Result<Json<MyObj>> {
Ok(Json(MyObj{name: req.match_info().query("name")?}))
}
fn main() {
Application::new()
.resource(r"/a/{name}", |r| r.method(Method::GET).f(index))
.finish();
}
```
## Chunked transfer encoding
Actix automatically decode *chunked* encoding. `HttpRequest::payload()` already contains
decoded bytes stream. If request payload compressed with one of supported
compression codecs (br, gzip, deflate) bytes stream get decompressed.
Chunked encoding on response could be enabled with `HttpResponseBuilder::chunked()` method.
But this takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies.
Also if response payload compression is enabled and streaming body is used, chunked encoding
get enabled automatically.
Enabling chunked encoding for *HTTP/2.0* responses is forbidden.
```rust
# extern crate actix_web;
use actix_web::*;
fn index(req: HttpRequest) -> HttpResponse {
HttpResponse::Ok()
.chunked()
.body(Body::Streaming(payload::Payload::empty().stream())).unwrap()
}
# fn main() {}
```
## Multipart body
Actix provides multipart stream support.
[*Multipart*](../actix_web/multipart/struct.Multipart.html) is implemented as
a stream of multipart items, each item could be
[*Field*](../actix_web/multipart/struct.Field.html) or nested *Multipart* stream.
`HttpResponse::multipart()` method returns *Multipart* stream for current request.
In simple form multipart stream handling could be implemented similar to this example
```rust,ignore
# extern crate actix_web;
use actix_web::*;
fn index(req: HttpRequest) -> Box<Future<...>> {
req.multipart() // <- get multipart stream for current request
.and_then(|item| { // <- iterate over multipart items
match item {
// Handle multipart Field
multipart::MultipartItem::Field(field) => {
println!("==== FIELD ==== {:?} {:?}", field.headers(), field.content_type());
Either::A(
// Field in turn is a stream of *Bytes* objects
field.map(|chunk| {
println!("-- CHUNK: \n{}",
std::str::from_utf8(&chunk).unwrap());})
.fold((), |_, _| result(Ok(()))))
},
multipart::MultipartItem::Nested(mp) => {
// Or item could be nested Multipart stream
Either::B(result(Ok(())))
}
}
})
}
```
Full example is available in
[examples directory](https://github.com/actix/actix-web/tree/master/examples/multipart/).
## Urlencoded body
Actix provides support for *application/x-www-form-urlencoded* encoded body.
`HttpResponse::urlencoded()` method returns
[*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, it resolves
into `HashMap<String, String>` which contains decoded parameters.
*UrlEncoded* future can resolve into a error in several cases:
* content type is not `application/x-www-form-urlencoded`
* transfer encoding is `chunked`.
* content-length is greater than 256k
* payload terminates with error.
```rust
# extern crate actix_web;
# extern crate futures;
use actix_web::*;
use futures::future::{Future, ok};
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.urlencoded() // <- get UrlEncoded future
.from_err()
.and_then(|params| { // <- url encoded parameters
println!("==== BODY ==== {:?}", params);
ok(httpcodes::HTTPOk.into())
})
.responder()
}
# fn main() {}
```
## Streaming request
Actix uses [*Payload*](../actix_web/payload/struct.Payload.html) object as request payload stream.
*HttpRequest* provides several methods, which can be used for payload access.
At the same time *Payload* implements *Stream* trait, so it could be used with various
stream combinators. Also *Payload* provides several convenience methods that return
future object that resolve to Bytes object.
* *readany()* method returns *Stream* of *Bytes* objects.
* *readexactly()* method returns *Future* that resolves when specified number of bytes
get received.
* *readline()* method returns *Future* that resolves when `\n` get received.
* *readuntil()* method returns *Future* that resolves when specified bytes string
matches in input bytes stream
In this example handle reads request payload chunk by chunk and prints every chunk.
```rust
# extern crate actix_web;
# extern crate futures;
# use futures::future::result;
use actix_web::*;
use futures::{Future, Stream};
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.payload()
.readany()
.from_err()
.fold((), |_, chunk| {
println!("Chunk: {:?}", chunk);
result::<_, error::PayloadError>(Ok(()))
})
.map(|_| HttpResponse::Ok().finish().unwrap())
.responder()
}
# fn main() {}
```

View File

@ -1,97 +0,0 @@
# Testing
Every application should be well tested and. Actix provides the tools to perform unit and
integration tests.
## Unit tests
For unit testing actix provides request builder type and simple handler runner.
[*TestRequest*](../actix_web/test/struct.TestRequest.html) implements builder-like pattern.
You can generate `HttpRequest` instance with `finish()` method or you can
run your handler with `run()` or `run_async()` methods.
```rust
# extern crate http;
# extern crate actix_web;
use http::{header, StatusCode};
use actix_web::*;
use actix_web::test::TestRequest;
fn index(req: HttpRequest) -> HttpResponse {
if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
if let Ok(s) = hdr.to_str() {
return httpcodes::HTTPOk.into()
}
}
httpcodes::HTTPBadRequest.into()
}
fn main() {
let resp = TestRequest::with_header("content-type", "text/plain")
.run(index)
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let resp = TestRequest::default()
.run(index)
.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
```
## Integration tests
There are several methods how you can test your application. Actix provides
[*TestServer*](../actix_web/test/struct.TestServer.html)
server that could be used to run whole application of just specific handlers
in real http server. At the moment it is required to use third-party libraries
to make actual requests, libraries like [reqwest](https://crates.io/crates/reqwest).
In simple form *TestServer* could be configured to use handler. *TestServer::new* method
accepts configuration function, only argument for this function is *test application*
instance. You can check [api documentation](../actix_web/test/struct.TestApp.html)
for more information.
```rust
# extern crate actix_web;
extern crate reqwest;
use actix_web::*;
use actix_web::test::TestServer;
fn index(req: HttpRequest) -> HttpResponse {
httpcodes::HTTPOk.into()
}
fn main() {
let srv = TestServer::new(|app| app.handler(index)); // <- Start new test server
let url = srv.url("/"); // <- get handler url
assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request
}
```
Other option is to use application factory. In this case you need to pass factory function
same as you use for real http server configuration.
```rust
# extern crate actix_web;
extern crate reqwest;
use actix_web::*;
use actix_web::test::TestServer;
fn index(req: HttpRequest) -> HttpResponse {
httpcodes::HTTPOk.into()
}
/// This function get called by http server.
fn create_app() -> Application {
Application::new()
.resource("/test", |r| r.h(index))
}
fn main() {
let srv = TestServer::with_factory(create_app); // <- Start new test server
let url = srv.url("/test"); // <- get handler url
assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request
}
```

View File

@ -1,49 +0,0 @@
# WebSockets
Actix supports WebSockets out-of-the-box. It is possible to convert request's `Payload`
to a stream of [*ws::Message*](../actix_web/ws/enum.Message.html) with
a [*ws::WsStream*](../actix_web/ws/struct.WsStream.html) and then use stream
combinators to handle actual messages. But it is simpler to handle websocket communications
with http actor.
This is example of simple websocket echo server:
```rust
# extern crate actix;
# extern crate actix_web;
use actix::*;
use actix_web::*;
/// Define http actor
struct Ws;
impl Actor for Ws {
type Context = ws::WebsocketContext<Self>;
}
/// Define Handler for ws::Message message
impl Handler<ws::Message> for Ws {
type Result=();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(&text),
ws::Message::Binary(bin) => ctx.binary(bin),
_ => (),
}
}
}
fn main() {
Application::new()
.resource("/ws/", |r| r.f(|req| ws::start(req, Ws))) // <- register websocket route
.finish();
}
```
Simple websocket echo server example is available in
[examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket.rs).
Example chat server with ability to chat over websocket connection or tcp connection
is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)

5
rustfmt.toml Normal file
View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,16 @@
use std::{fmt, mem};
use std::rc::Rc;
use std::sync::Arc;
use bytes::{Bytes, BytesMut};
use futures::Stream;
use std::sync::Arc;
use std::{fmt, mem};
use error::Error;
use context::ActorHttpContext;
use error::Error;
use handler::Responder;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Type represent streaming body
pub type BodyStream = Box<Stream<Item=Bytes, Error=Error>>;
pub type BodyStream = Box<Stream<Item = Bytes, Error = Error>>;
/// Represents various types of http message body.
pub enum Body {
@ -32,10 +34,10 @@ pub enum Binary {
/// Static slice
Slice(&'static [u8]),
/// Shared string body
SharedString(Rc<String>),
/// Shared string body
#[doc(hidden)]
ArcSharedString(Arc<String>),
SharedString(Arc<String>),
/// Shared vec body
SharedVec(Arc<Vec<u8>>),
}
impl Body {
@ -44,7 +46,7 @@ impl Body {
pub fn is_streaming(&self) -> bool {
match *self {
Body::Streaming(_) | Body::Actor(_) => true,
_ => false
_ => false,
}
}
@ -53,7 +55,16 @@ impl Body {
pub fn is_binary(&self) -> bool {
match *self {
Body::Binary(_) => true,
_ => false
_ => false,
}
}
/// Is this binary empy.
#[inline]
pub fn is_empty(&self) -> bool {
match *self {
Body::Empty => true,
_ => false,
}
}
@ -61,6 +72,15 @@ impl Body {
pub fn from_slice(s: &[u8]) -> Body {
Body::Binary(Binary::Bytes(Bytes::from(s)))
}
/// Is this binary body.
#[inline]
pub(crate) fn binary(self) -> Binary {
match self {
Body::Binary(b) => b,
_ => panic!(),
}
}
}
impl PartialEq for Body {
@ -90,7 +110,10 @@ impl fmt::Debug for Body {
}
}
impl<T> From<T> for Body where T: Into<Binary>{
impl<T> From<T> for Body
where
T: Into<Binary>,
{
fn from(b: T) -> Body {
Body::Binary(b.into())
}
@ -104,17 +127,19 @@ impl From<Box<ActorHttpContext>> for Body {
impl Binary {
#[inline]
/// Returns `true` if body is empty
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
/// Length of body in bytes
pub fn len(&self) -> usize {
match *self {
Binary::Bytes(ref bytes) => bytes.len(),
Binary::Slice(slice) => slice.len(),
Binary::SharedString(ref s) => s.len(),
Binary::ArcSharedString(ref s) => s.len(),
Binary::SharedVec(ref s) => s.len(),
}
}
@ -134,8 +159,8 @@ impl Clone for Binary {
match *self {
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
Binary::SharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())),
Binary::ArcSharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())),
Binary::SharedString(ref s) => Binary::SharedString(s.clone()),
Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()),
}
}
}
@ -146,7 +171,7 @@ impl Into<Bytes> for Binary {
Binary::Bytes(bytes) => bytes,
Binary::Slice(slice) => Bytes::from(slice),
Binary::SharedString(s) => Bytes::from(s.as_str()),
Binary::ArcSharedString(s) => Bytes::from(s.as_str()),
Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())),
}
}
}
@ -193,41 +218,53 @@ impl From<BytesMut> for Binary {
}
}
impl From<Rc<String>> for Binary {
fn from(body: Rc<String>) -> Binary {
Binary::SharedString(body)
}
}
impl<'a> From<&'a Rc<String>> for Binary {
fn from(body: &'a Rc<String>) -> Binary {
Binary::SharedString(Rc::clone(body))
}
}
impl From<Arc<String>> for Binary {
fn from(body: Arc<String>) -> Binary {
Binary::ArcSharedString(body)
Binary::SharedString(body)
}
}
impl<'a> From<&'a Arc<String>> for Binary {
fn from(body: &'a Arc<String>) -> Binary {
Binary::ArcSharedString(Arc::clone(body))
Binary::SharedString(Arc::clone(body))
}
}
impl From<Arc<Vec<u8>>> for Binary {
fn from(body: Arc<Vec<u8>>) -> Binary {
Binary::SharedVec(body)
}
}
impl<'a> From<&'a Arc<Vec<u8>>> for Binary {
fn from(body: &'a Arc<Vec<u8>>) -> Binary {
Binary::SharedVec(Arc::clone(body))
}
}
impl AsRef<[u8]> for Binary {
#[inline]
fn as_ref(&self) -> &[u8] {
match *self {
Binary::Bytes(ref bytes) => bytes.as_ref(),
Binary::Slice(slice) => slice,
Binary::SharedString(ref s) => s.as_bytes(),
Binary::ArcSharedString(ref s) => s.as_bytes(),
Binary::SharedVec(ref s) => s.as_ref().as_ref(),
}
}
}
impl Responder for Binary {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(HttpResponse::build_from(req)
.content_type("application/octet-stream")
.body(self))
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -247,68 +284,61 @@ mod tests {
#[test]
fn test_static_str() {
assert_eq!(Binary::from("test").len(), 4);
assert_eq!(Binary::from("test").as_ref(), "test".as_bytes());
assert_eq!(Binary::from("test").as_ref(), b"test");
}
#[test]
fn test_static_bytes() {
assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
assert_eq!(Binary::from(b"test".as_ref()).as_ref(), "test".as_bytes());
assert_eq!(Binary::from(b"test".as_ref()).as_ref(), b"test");
assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4);
assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), "test".as_bytes());
assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), b"test");
}
#[test]
fn test_vec() {
assert_eq!(Binary::from(Vec::from("test")).len(), 4);
assert_eq!(Binary::from(Vec::from("test")).as_ref(), "test".as_bytes());
assert_eq!(Binary::from(Vec::from("test")).as_ref(), b"test");
}
#[test]
fn test_bytes() {
assert_eq!(Binary::from(Bytes::from("test")).len(), 4);
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), "test".as_bytes());
}
#[test]
fn test_ref_string() {
let b = Rc::new("test".to_owned());
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
}
#[test]
fn test_rc_string() {
let b = Rc::new("test".to_owned());
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes());
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
}
#[test]
fn test_arc_string() {
let b = Arc::new("test".to_owned());
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes());
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
assert_eq!(Binary::from(&b).as_ref(), b"test");
}
#[test]
fn test_string() {
let b = "test".to_owned();
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes());
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
assert_eq!(Binary::from(&b).as_ref(), b"test");
}
#[test]
fn test_shared_vec() {
let b = Arc::new(Vec::from(&b"test"[..]));
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), &b"test"[..]);
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), &b"test"[..]);
}
#[test]
fn test_bytes_mut() {
let b = BytesMut::from("test");
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b).as_ref(), "test".as_bytes());
assert_eq!(Binary::from(b).as_ref(), b"test");
}
#[test]

1338
src/client/connector.rs Normal file

File diff suppressed because it is too large Load Diff

118
src/client/mod.rs Normal file
View File

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

238
src/client/parser.rs Normal file
View File

@ -0,0 +1,238 @@
use std::mem;
use bytes::{Bytes, BytesMut};
use futures::{Async, Poll};
use http::header::{self, HeaderName, HeaderValue};
use http::{HeaderMap, StatusCode, Version};
use httparse;
use error::{ParseError, PayloadError};
use server::h1decoder::{EncodingDecoder, HeaderIndex};
use server::IoStream;
use super::response::ClientMessage;
use super::ClientResponse;
const MAX_BUFFER_SIZE: usize = 131_072;
const MAX_HEADERS: usize = 96;
#[derive(Default)]
pub struct HttpResponseParser {
decoder: Option<EncodingDecoder>,
eof: bool, // indicate that we read payload until stream eof
}
#[derive(Debug, Fail)]
pub enum HttpResponseParserError {
/// Server disconnected
#[fail(display = "Server disconnected")]
Disconnect,
#[fail(display = "{}", _0)]
Error(#[cause] ParseError),
}
impl HttpResponseParser {
pub fn parse<T>(
&mut self, io: &mut T, buf: &mut BytesMut,
) -> Poll<ClientResponse, HttpResponseParserError>
where
T: IoStream,
{
loop {
// 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.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(
ParseError::TooLarge,
));
}
// Parser needs more data.
}
}
}
// Read some more data into the buffer for the parser.
match io.read_available(buf) {
Ok(Async::Ready((false, true))) => {
return Err(HttpResponseParserError::Disconnect)
}
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => return Err(HttpResponseParserError::Error(err.into())),
}
}
}
pub fn parse_payload<T>(
&mut self, io: &mut T, buf: &mut BytesMut,
) -> Poll<Option<Bytes>, PayloadError>
where
T: IoStream,
{
if self.decoder.is_some() {
loop {
// read payload
let (not_ready, stream_finished) = match io.read_available(buf) {
Ok(Async::Ready((_, true))) => (false, true),
Ok(Async::Ready((_, false))) => (false, false),
Ok(Async::NotReady) => (true, false),
Err(err) => return Err(err.into()),
};
match self.decoder.as_mut().unwrap().decode(buf) {
Ok(Async::Ready(Some(b))) => return Ok(Async::Ready(Some(b))),
Ok(Async::Ready(None)) => {
self.decoder.take();
return Ok(Async::Ready(None));
}
Ok(Async::NotReady) => {
if not_ready {
return Ok(Async::NotReady);
}
if stream_finished {
// read untile eof?
if self.eof {
return Ok(Async::Ready(None));
} else {
return Err(PayloadError::Incomplete);
}
}
}
Err(err) => return Err(err.into()),
}
}
} else {
Ok(Async::Ready(None))
}
}
fn parse_message(
buf: &mut BytesMut,
) -> Poll<(ClientResponse, Option<(EncodingDecoder, bool)>), ParseError> {
// Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() };
let (len, version, status, headers_len) = {
let mut parsed: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
let mut resp = httparse::Response::new(&mut parsed);
match resp.parse(buf)? {
httparse::Status::Complete(len) => {
let version = if resp.version.unwrap_or(1) == 1 {
Version::HTTP_11
} else {
Version::HTTP_10
};
HeaderIndex::record(buf, resp.headers, &mut headers);
let status = StatusCode::from_u16(resp.code.unwrap())
.map_err(|_| ParseError::Status)?;
(len, version, status, resp.headers.len())
}
httparse::Status::Partial => return Ok(Async::NotReady),
}
};
let slice = buf.split_to(len).freeze();
// convert headers
let mut hdrs = HeaderMap::new();
for idx in headers[..headers_len].iter() {
if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) {
// Unsafe: httparse check header value for valid utf-8
let value = unsafe {
HeaderValue::from_shared_unchecked(
slice.slice(idx.value.0, idx.value.1),
)
};
hdrs.append(name, value);
} else {
return Err(ParseError::Header);
}
}
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
Some((EncodingDecoder::eof(), true))
} else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
Some((EncodingDecoder::length(len), false))
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header);
}
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header);
}
} else if chunked(&hdrs)? {
// Chunked encoding
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 {
None
};
if let Some(decoder) = decoder {
Ok(Async::Ready((
ClientResponse::new(ClientMessage {
status,
version,
headers: hdrs,
cookies: None,
}),
Some(decoder),
)))
} else {
Ok(Async::Ready((
ClientResponse::new(ClientMessage {
status,
version,
headers: hdrs,
cookies: None,
}),
None,
)))
}
}
}
/// Check if request has chunked transfer encoding
pub fn chunked(headers: &HeaderMap) -> Result<bool, ParseError> {
if let Some(encodings) = headers.get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() {
Ok(s.to_lowercase().contains("chunked"))
} else {
Err(ParseError::Header)
}
} else {
Ok(false)
}
}

552
src/client/pipeline.rs Normal file
View File

@ -0,0 +1,552 @@
use bytes::{Bytes, BytesMut};
use futures::sync::oneshot;
use futures::{Async, Future, Poll, Stream};
use http::header::CONTENT_ENCODING;
use std::time::{Duration, Instant};
use std::{io, mem};
use tokio_timer::Delay;
use actix::{Addr, Request, SystemService};
use super::{
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,
Connection, HttpClientWriter, HttpResponseParser, HttpResponseParserError,
};
use body::{Body, BodyStream};
use context::{ActorHttpContext, Frame};
use error::Error;
use error::PayloadError;
use header::ContentEncoding;
use http::{Method, Uri};
use httpmessage::HttpMessage;
use server::input::PayloadStream;
use server::WriterState;
/// A set of errors that can occur during request sending and response reading
#[derive(Fail, Debug)]
pub enum SendRequestError {
/// Response took too long
#[fail(display = "Timeout while waiting for response")]
Timeout,
/// Failed to connect to host
#[fail(display = "Failed to connect to host: {}", _0)]
Connector(#[cause] ClientConnectorError),
/// Error parsing response
#[fail(display = "{}", _0)]
ParseError(#[cause] HttpResponseParserError),
/// Error reading response payload
#[fail(display = "Error reading response payload: {}", _0)]
Io(#[cause] io::Error),
}
impl From<io::Error> for SendRequestError {
fn from(err: io::Error) -> SendRequestError {
SendRequestError::Io(err)
}
}
impl From<ClientConnectorError> for SendRequestError {
fn from(err: ClientConnectorError) -> SendRequestError {
match err {
ClientConnectorError::Timeout => SendRequestError::Timeout,
_ => SendRequestError::Connector(err),
}
}
}
enum State {
New,
Connect(Request<ClientConnector, Connect>),
Connection(Connection),
Send(Box<Pipeline>),
None,
}
/// `SendRequest` is a `Future` which represents an asynchronous
/// request sending process.
#[must_use = "SendRequest does nothing unless polled"]
pub struct SendRequest {
req: ClientRequest,
state: State,
conn: Option<Addr<ClientConnector>>,
conn_timeout: Duration,
wait_timeout: Duration,
timeout: Option<Duration>,
}
impl SendRequest {
pub(crate) fn new(req: ClientRequest) -> SendRequest {
SendRequest {
req,
conn: None,
state: State::New,
timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
}
}
pub(crate) fn with_connector(
req: ClientRequest, conn: Addr<ClientConnector>,
) -> SendRequest {
SendRequest {
req,
conn: Some(conn),
state: State::New,
timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
}
}
pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest {
SendRequest {
req,
state: State::Connection(conn),
conn: None,
timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
}
}
/// Set request timeout
///
/// Request timeout is the total time before a response must be received.
/// Default value is 5 seconds.
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
/// Set connection timeout
///
/// Connection timeout includes resolving hostname and actual connection to
/// the host.
/// Default value is 1 second.
pub fn conn_timeout(mut self, timeout: Duration) -> Self {
self.conn_timeout = timeout;
self
}
/// Set wait timeout
///
/// If connections pool limits are enabled, wait time indicates max time
/// to wait for available connection. Default value is 5 seconds.
pub fn wait_timeout(mut self, timeout: Duration) -> Self {
self.wait_timeout = timeout;
self
}
}
impl Future for SendRequest {
type Item = ClientResponse;
type Error = SendRequestError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
let state = mem::replace(&mut self.state, State::None);
match state {
State::New => {
let conn = if let Some(conn) = self.conn.take() {
conn
} else {
ClientConnector::from_registry()
};
self.state = State::Connect(conn.send(Connect {
uri: self.req.uri().clone(),
wait_timeout: self.wait_timeout,
conn_timeout: self.conn_timeout,
}))
}
State::Connect(mut conn) => match conn.poll() {
Ok(Async::NotReady) => {
self.state = State::Connect(conn);
return Ok(Async::NotReady);
}
Ok(Async::Ready(result)) => match result {
Ok(stream) => self.state = State::Connection(stream),
Err(err) => return Err(err.into()),
},
Err(_) => {
return Err(SendRequestError::Connector(
ClientConnectorError::Disconnected,
));
}
},
State::Connection(conn) => {
let mut writer = HttpClientWriter::new();
writer.start(&mut self.req)?;
let body = match self.req.replace_body(Body::Empty) {
Body::Streaming(stream) => IoBody::Payload(stream),
Body::Actor(ctx) => IoBody::Actor(ctx),
_ => IoBody::Done,
};
let timeout = self
.timeout
.take()
.unwrap_or_else(|| Duration::from_secs(5));
let pl = Box::new(Pipeline {
body,
writer,
conn: Some(conn),
parser: Some(HttpResponseParser::default()),
parser_buf: BytesMut::new(),
disconnected: false,
body_completed: false,
drain: None,
decompress: None,
should_decompress: self.req.response_decompress(),
write_state: RunningState::Running,
timeout: Some(Delay::new(Instant::now() + timeout)),
meth: self.req.method().clone(),
path: self.req.uri().clone(),
});
self.state = State::Send(pl);
}
State::Send(mut pl) => {
pl.poll_timeout()?;
pl.poll_write().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str())
})?;
match pl.parse() {
Ok(Async::Ready(mut resp)) => {
if self.req.method() == Method::HEAD {
pl.parser.take();
}
resp.set_pipeline(pl);
return Ok(Async::Ready(resp));
}
Ok(Async::NotReady) => {
self.state = State::Send(pl);
return Ok(Async::NotReady);
}
Err(err) => {
return Err(SendRequestError::ParseError(err));
}
}
}
State::None => unreachable!(),
}
}
}
}
pub struct Pipeline {
body: IoBody,
body_completed: bool,
conn: Option<Connection>,
writer: HttpClientWriter,
parser: Option<HttpResponseParser>,
parser_buf: BytesMut,
disconnected: bool,
drain: Option<oneshot::Sender<()>>,
decompress: Option<PayloadStream>,
should_decompress: bool,
write_state: RunningState,
timeout: Option<Delay>,
meth: Method,
path: Uri,
}
enum IoBody {
Payload(BodyStream),
Actor(Box<ActorHttpContext>),
Done,
}
#[derive(Debug, PartialEq)]
enum RunningState {
Running,
Paused,
Done,
}
impl RunningState {
#[inline]
fn pause(&mut self) {
if *self != RunningState::Done {
*self = RunningState::Paused
}
}
#[inline]
fn resume(&mut self) {
if *self != RunningState::Done {
*self = RunningState::Running
}
}
}
impl Pipeline {
fn release_conn(&mut self) {
if let Some(conn) = self.conn.take() {
if self.meth == Method::HEAD {
conn.close()
} else {
conn.release()
}
}
}
#[inline]
fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
if let Some(ref mut conn) = self.conn {
match self
.parser
.as_mut()
.unwrap()
.parse(conn, &mut self.parser_buf)
{
Ok(Async::Ready(resp)) => {
// check content-encoding
if self.should_decompress {
if let Some(enc) = resp.headers().get(CONTENT_ENCODING) {
if let Ok(enc) = enc.to_str() {
match ContentEncoding::from(enc) {
ContentEncoding::Auto
| ContentEncoding::Identity => (),
enc => {
self.decompress = Some(PayloadStream::new(enc))
}
}
}
}
}
Ok(Async::Ready(resp))
}
val => val,
}
} else {
Ok(Async::NotReady)
}
}
#[inline]
pub(crate) fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if self.conn.is_none() {
return Ok(Async::Ready(None));
}
let mut need_run = false;
// need write?
match self
.poll_write()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?
{
Async::NotReady => need_run = true,
Async::Ready(_) => {
self.poll_timeout().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e))
})?;
}
}
// need read?
if self.parser.is_some() {
let conn: &mut Connection = self.conn.as_mut().unwrap();
loop {
match self
.parser
.as_mut()
.unwrap()
.parse_payload(conn, &mut self.parser_buf)?
{
Async::Ready(Some(b)) => {
if let Some(ref mut decompress) = self.decompress {
match decompress.feed_data(b) {
Ok(Some(b)) => return Ok(Async::Ready(Some(b))),
Ok(None) => return Ok(Async::NotReady),
Err(ref err)
if err.kind() == io::ErrorKind::WouldBlock =>
{
continue
}
Err(err) => return Err(err.into()),
}
} else {
return Ok(Async::Ready(Some(b)));
}
}
Async::Ready(None) => {
let _ = self.parser.take();
break;
}
Async::NotReady => return Ok(Async::NotReady),
}
}
}
// eof
if let Some(mut decompress) = self.decompress.take() {
let res = decompress.feed_eof();
if let Some(b) = res? {
self.release_conn();
return Ok(Async::Ready(Some(b)));
}
}
if need_run {
Ok(Async::NotReady)
} else {
self.release_conn();
Ok(Async::Ready(None))
}
}
fn poll_timeout(&mut self) -> Result<(), SendRequestError> {
if self.timeout.is_some() {
match self.timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
Ok(Async::NotReady) => (),
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e).into()),
}
}
Ok(())
}
#[inline]
fn poll_write(&mut self) -> Poll<(), Error> {
if self.write_state == RunningState::Done || self.conn.is_none() {
return Ok(Async::Ready(()));
}
let mut done = false;
if self.drain.is_none() && self.write_state != RunningState::Paused {
'outter: loop {
let result = match mem::replace(&mut self.body, IoBody::Done) {
IoBody::Payload(mut body) => match body.poll()? {
Async::Ready(None) => {
self.writer.write_eof()?;
self.body_completed = true;
break;
}
Async::Ready(Some(chunk)) => {
self.body = IoBody::Payload(body);
self.writer.write(chunk.as_ref())?
}
Async::NotReady => {
done = true;
self.body = IoBody::Payload(body);
break;
}
},
IoBody::Actor(mut ctx) => {
if self.disconnected {
ctx.disconnected();
}
match ctx.poll()? {
Async::Ready(Some(vec)) => {
if vec.is_empty() {
self.body = IoBody::Actor(ctx);
break;
}
let mut res = None;
for frame in vec {
match frame {
Frame::Chunk(None) => {
self.body_completed = true;
self.writer.write_eof()?;
break 'outter;
}
Frame::Chunk(Some(chunk)) => {
res =
Some(self.writer.write(chunk.as_ref())?)
}
Frame::Drain(fut) => self.drain = Some(fut),
}
}
self.body = IoBody::Actor(ctx);
if self.drain.is_some() {
self.write_state.resume();
break;
}
res.unwrap()
}
Async::Ready(None) => {
done = true;
break;
}
Async::NotReady => {
done = true;
self.body = IoBody::Actor(ctx);
break;
}
}
}
IoBody::Done => {
self.body_completed = true;
done = true;
break;
}
};
match result {
WriterState::Pause => {
self.write_state.pause();
break;
}
WriterState::Done => self.write_state.resume(),
}
}
}
// flush io but only if we need to
match self
.writer
.poll_completed(self.conn.as_mut().unwrap(), false)
{
Ok(Async::Ready(_)) => {
if self.disconnected
|| (self.body_completed && self.writer.is_completed())
{
self.write_state = RunningState::Done;
} else {
self.write_state.resume();
}
// resolve drain futures
if let Some(tx) = self.drain.take() {
let _ = tx.send(());
}
// restart io processing
if !done || self.write_state == RunningState::Done {
self.poll_write()
} else {
Ok(Async::NotReady)
}
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => Err(err.into()),
}
}
}
impl Drop for Pipeline {
fn drop(&mut self) {
if let Some(conn) = self.conn.take() {
debug!(
"Client http transaction is not completed, dropping connection: {:?} {:?}",
self.meth,
self.path,
);
conn.close()
}
}
}
/// Future that resolves to a complete request body.
impl Stream for Box<Pipeline> {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
Pipeline::poll(self)
}
}

782
src/client/request.rs Normal file
View File

@ -0,0 +1,782 @@
use std::fmt::Write as FmtWrite;
use std::io::Write;
use std::time::Duration;
use std::{fmt, mem};
use actix::Addr;
use bytes::{BufMut, Bytes, BytesMut};
use cookie::{Cookie, CookieJar};
use futures::Stream;
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use serde::Serialize;
use serde_json;
use serde_urlencoded;
use url::Url;
use super::connector::{ClientConnector, Connection};
use super::pipeline::SendRequest;
use body::Body;
use error::Error;
use header::{ContentEncoding, Header, IntoHeaderValue};
use http::header::{self, HeaderName, HeaderValue};
use http::{uri, Error as HttpError, HeaderMap, HttpTryFrom, Method, Uri, Version};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
/// An HTTP Client Request
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # extern crate tokio;
/// # use futures::Future;
/// # use std::process;
/// use actix_web::{actix, client};
///
/// fn main() {
/// actix::run(
/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .finish().unwrap()
/// .send() // <- Send http request
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
/// println!("Response: {:?}", response);
/// # actix::System::current().stop();
/// Ok(())
/// }),
/// );
/// }
/// ```
pub struct ClientRequest {
uri: Uri,
method: Method,
version: Version,
headers: HeaderMap,
body: Body,
chunked: bool,
upgrade: bool,
timeout: Option<Duration>,
encoding: ContentEncoding,
response_decompress: bool,
buffer_capacity: usize,
conn: ConnectionType,
}
enum ConnectionType {
Default,
Connector(Addr<ClientConnector>),
Connection(Connection),
}
impl Default for ClientRequest {
fn default() -> ClientRequest {
ClientRequest {
uri: Uri::default(),
method: Method::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
body: Body::Empty,
chunked: false,
upgrade: false,
timeout: None,
encoding: ContentEncoding::Auto,
response_decompress: true,
buffer_capacity: 32_768,
conn: ConnectionType::Default,
}
}
}
impl ClientRequest {
/// Create request builder for `GET` request
pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::GET).uri(uri);
builder
}
/// Create request builder for `HEAD` request
pub fn head<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::HEAD).uri(uri);
builder
}
/// Create request builder for `POST` request
pub fn post<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::POST).uri(uri);
builder
}
/// Create request builder for `PUT` request
pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::PUT).uri(uri);
builder
}
/// Create request builder for `DELETE` request
pub fn delete<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
builder.method(Method::DELETE).uri(uri);
builder
}
}
impl ClientRequest {
/// Create client request builder
pub fn build() -> ClientRequestBuilder {
ClientRequestBuilder {
request: Some(ClientRequest::default()),
err: None,
cookies: None,
default_headers: true,
}
}
/// Create client request builder
pub fn build_from<T: Into<ClientRequestBuilder>>(source: T) -> ClientRequestBuilder {
source.into()
}
/// Get the request URI
#[inline]
pub fn uri(&self) -> &Uri {
&self.uri
}
/// Set client request URI
#[inline]
pub fn set_uri(&mut self, uri: Uri) {
self.uri = uri
}
/// Get the request method
#[inline]
pub fn method(&self) -> &Method {
&self.method
}
/// Set HTTP `Method` for the request
#[inline]
pub fn set_method(&mut self, method: Method) {
self.method = method
}
/// Get HTTP version for the request
#[inline]
pub fn version(&self) -> Version {
self.version
}
/// Set http `Version` for the request
#[inline]
pub fn set_version(&mut self, version: Version) {
self.version = version
}
/// Get the headers from the request
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Get a mutable reference to the headers
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
/// is chunked encoding enabled
#[inline]
pub fn chunked(&self) -> bool {
self.chunked
}
/// is upgrade request
#[inline]
pub fn upgrade(&self) -> bool {
self.upgrade
}
/// Content encoding
#[inline]
pub fn content_encoding(&self) -> ContentEncoding {
self.encoding
}
/// Decompress response payload
#[inline]
pub fn response_decompress(&self) -> bool {
self.response_decompress
}
/// Requested write buffer capacity
pub fn write_buffer_capacity(&self) -> usize {
self.buffer_capacity
}
/// Get body of this response
#[inline]
pub fn body(&self) -> &Body {
&self.body
}
/// Set a body
pub fn set_body<B: Into<Body>>(&mut self, body: B) {
self.body = body.into();
}
/// Extract body, replace it with `Empty`
pub(crate) fn replace_body(&mut self, body: Body) -> Body {
mem::replace(&mut self.body, body)
}
/// Send request
///
/// This method returns a future that resolves to a ClientResponse
pub fn send(mut self) -> SendRequest {
let timeout = self.timeout.take();
let send = match mem::replace(&mut self.conn, ConnectionType::Default) {
ConnectionType::Default => SendRequest::new(self),
ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn),
ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn),
};
if let Some(timeout) = timeout {
send.timeout(timeout)
} else {
send
}
}
}
impl fmt::Debug for ClientRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"\nClientRequest {:?} {}:{}",
self.version, self.method, self.uri
)?;
writeln!(f, " headers:")?;
for (key, val) in self.headers.iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
/// An HTTP Client request builder
///
/// This type can be used to construct an instance of `ClientRequest` through a
/// builder-like pattern.
pub struct ClientRequestBuilder {
request: Option<ClientRequest>,
err: Option<HttpError>,
cookies: Option<CookieJar>,
default_headers: bool,
}
impl ClientRequestBuilder {
/// Set HTTP URI of request.
#[inline]
pub fn uri<U: AsRef<str>>(&mut self, uri: U) -> &mut Self {
match Url::parse(uri.as_ref()) {
Ok(url) => self._uri(url.as_str()),
Err(_) => self._uri(uri.as_ref()),
}
}
fn _uri(&mut self, url: &str) -> &mut Self {
match Uri::try_from(url) {
Ok(uri) => {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.uri = uri;
}
}
Err(e) => self.err = Some(e.into()),
}
self
}
/// Set HTTP method of this request.
#[inline]
pub fn method(&mut self, method: Method) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.method = method;
}
self
}
/// Set HTTP method of this request.
#[inline]
pub fn get_method(&mut self) -> &Method {
let parts = self.request.as_ref().expect("cannot reuse request builder");
&parts.method
}
/// Set HTTP version of this request.
///
/// By default requests's HTTP version depends on network stream
#[inline]
pub fn version(&mut self, version: Version) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.version = version;
}
self
}
/// Set a header.
///
/// ```rust
/// # extern crate mime;
/// # extern crate actix_web;
/// # use actix_web::client::*;
/// #
/// use actix_web::{client, http};
///
/// fn main() {
/// let req = client::ClientRequest::build()
/// .set(http::header::Date::now())
/// .set(http::header::ContentType(mime::TEXT_HTML))
/// .finish()
/// .unwrap();
/// }
/// ```
#[doc(hidden)]
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
match hdr.try_into() {
Ok(value) => {
parts.headers.insert(H::name(), value);
}
Err(e) => self.err = Some(e.into()),
}
}
self
}
/// Append a header.
///
/// Header gets appended to existing header.
/// To override header use `set_header()` method.
///
/// ```rust
/// # extern crate http;
/// # extern crate actix_web;
/// # use actix_web::client::*;
/// #
/// use http::header;
///
/// fn main() {
/// let req = ClientRequest::build()
/// .header("X-TEST", "value")
/// .header(header::CONTENT_TYPE, "application/json")
/// .finish()
/// .unwrap();
/// }
/// ```
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
parts.headers.append(key, value);
}
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set a header.
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
parts.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set a header only if it is not yet set.
pub fn set_header_if_none<K, V>(&mut self, key: K, value: V) -> &mut Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => if !parts.headers.contains_key(&key) {
match value.try_into() {
Ok(value) => {
parts.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
}
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set content encoding.
///
/// By default `ContentEncoding::Identity` is used.
#[inline]
pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.encoding = enc;
}
self
}
/// Enables automatic chunked transfer encoding
#[inline]
pub fn chunked(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.chunked = true;
}
self
}
/// Enable connection upgrade
#[inline]
pub fn upgrade(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.upgrade = true;
}
self
}
/// Set request's content type
#[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self
where
HeaderValue: HttpTryFrom<V>,
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderValue::try_from(value) {
Ok(value) => {
parts.headers.insert(header::CONTENT_TYPE, value);
}
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set content length
#[inline]
pub fn content_length(&mut self, len: u64) -> &mut Self {
let mut wrt = BytesMut::new().writer();
let _ = write!(wrt, "{}", len);
self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze())
}
/// Set a cookie
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{client, http};
///
/// fn main() {
/// let req = client::ClientRequest::build()
/// .cookie(
/// http::Cookie::build("name", "value")
/// .domain("www.rust-lang.org")
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .finish(),
/// )
/// .finish()
/// .unwrap();
/// }
/// ```
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
if self.cookies.is_none() {
let mut jar = CookieJar::new();
jar.add(cookie.into_owned());
self.cookies = Some(jar)
} else {
self.cookies.as_mut().unwrap().add(cookie.into_owned());
}
self
}
/// Do not add default request headers.
/// By default `Accept-Encoding` and `User-Agent` headers are set.
pub fn no_default_headers(&mut self) -> &mut Self {
self.default_headers = false;
self
}
/// Disable automatic decompress response body
pub fn disable_decompress(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.response_decompress = false;
}
self
}
/// Set write buffer capacity
///
/// Default buffer capacity is 32kb
pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.buffer_capacity = cap;
}
self
}
/// Set request timeout
///
/// Request timeout is a total time before response should be received.
/// Default value is 5 seconds.
pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.timeout = Some(timeout);
}
self
}
/// Send request using custom connector
pub fn with_connector(&mut self, conn: Addr<ClientConnector>) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connector(conn);
}
self
}
/// Send request using existing `Connection`
pub fn with_connection(&mut self, conn: Connection) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connection(conn);
}
self
}
/// This method calls provided closure with builder reference if
/// value is `true`.
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
where
F: FnOnce(&mut ClientRequestBuilder),
{
if value {
f(self);
}
self
}
/// This method calls provided closure with builder reference if
/// value is `Some`.
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
where
F: FnOnce(T, &mut ClientRequestBuilder),
{
if let Some(val) = value {
f(val, self);
}
self
}
/// Set a body and generate `ClientRequest`.
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn body<B: Into<Body>>(&mut self, body: B) -> Result<ClientRequest, Error> {
if let Some(e) = self.err.take() {
return Err(e.into());
}
if self.default_headers {
// enable br only for https
let https = if let Some(parts) = parts(&mut self.request, &self.err) {
parts
.uri
.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS)
.unwrap_or(true)
} else {
true
};
if https {
self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate");
} else {
self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate");
}
// set request host header
if let Some(parts) = parts(&mut self.request, &self.err) {
if let Some(host) = parts.uri.host() {
if !parts.headers.contains_key(header::HOST) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match parts.uri.port() {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
parts.headers.insert(header::HOST, value);
}
Err(e) => self.err = Some(e.into()),
}
}
}
}
// user agent
self.set_header_if_none(
header::USER_AGENT,
concat!("actix-web/", env!("CARGO_PKG_VERSION")),
);
}
let mut request = self.request.take().expect("cannot reuse request builder");
// set cookies
if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new();
for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value);
}
request.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
request.body = body.into();
Ok(request)
}
/// Set a JSON body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn json<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
let body = serde_json::to_string(&value)?;
let contains = if let Some(parts) = parts(&mut self.request, &self.err) {
parts.headers.contains_key(header::CONTENT_TYPE)
} else {
true
};
if !contains {
self.header(header::CONTENT_TYPE, "application/json");
}
self.body(body)
}
/// Set a urlencoded body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn form<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
let body = serde_urlencoded::to_string(&value)?;
let contains = if let Some(parts) = parts(&mut self.request, &self.err) {
parts.headers.contains_key(header::CONTENT_TYPE)
} else {
true
};
if !contains {
self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded");
}
self.body(body)
}
/// Set a streaming body and generate `ClientRequest`.
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn streaming<S, E>(&mut self, stream: S) -> Result<ClientRequest, Error>
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error>,
{
self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
}
/// Set an empty body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn finish(&mut self) -> Result<ClientRequest, Error> {
self.body(Body::Empty)
}
/// This method construct new `ClientRequestBuilder`
pub fn take(&mut self) -> ClientRequestBuilder {
ClientRequestBuilder {
request: self.request.take(),
err: self.err.take(),
cookies: self.cookies.take(),
default_headers: self.default_headers,
}
}
}
#[inline]
fn parts<'a>(
parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>,
) -> Option<&'a mut ClientRequest> {
if err.is_some() {
return None;
}
parts.as_mut()
}
impl fmt::Debug for ClientRequestBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref parts) = self.request {
writeln!(
f,
"\nClientRequestBuilder {:?} {}:{}",
parts.version, parts.method, parts.uri
)?;
writeln!(f, " headers:")?;
for (key, val) in parts.headers.iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
} else {
write!(f, "ClientRequestBuilder(Consumed)")
}
}
}
/// Create `ClientRequestBuilder` from `HttpRequest`
///
/// It is useful for proxy requests. This implementation
/// copies all request headers and the method.
impl<'a, S: 'static> From<&'a HttpRequest<S>> for ClientRequestBuilder {
fn from(req: &'a HttpRequest<S>) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
for (key, value) in req.headers() {
builder.header(key.clone(), value.clone());
}
builder.method(req.method().clone());
builder
}
}

124
src/client/response.rs Normal file
View File

@ -0,0 +1,124 @@
use std::cell::RefCell;
use std::{fmt, str};
use cookie::Cookie;
use http::header::{self, HeaderValue};
use http::{HeaderMap, StatusCode, Version};
use error::CookieParseError;
use httpmessage::HttpMessage;
use super::pipeline::Pipeline;
pub(crate) struct ClientMessage {
pub status: StatusCode,
pub version: Version,
pub headers: HeaderMap<HeaderValue>,
pub cookies: Option<Vec<Cookie<'static>>>,
}
impl Default for ClientMessage {
fn default() -> ClientMessage {
ClientMessage {
status: StatusCode::OK,
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
cookies: None,
}
}
}
/// An HTTP Client response
pub struct ClientResponse(ClientMessage, RefCell<Option<Box<Pipeline>>>);
impl HttpMessage for ClientResponse {
type Stream = Box<Pipeline>;
/// Get the headers from the response.
#[inline]
fn headers(&self) -> &HeaderMap {
&self.0.headers
}
#[inline]
fn payload(&self) -> Box<Pipeline> {
self.1
.borrow_mut()
.take()
.expect("Payload is already consumed.")
}
}
impl ClientResponse {
pub(crate) fn new(msg: ClientMessage) -> ClientResponse {
ClientResponse(msg, RefCell::new(None))
}
pub(crate) fn set_pipeline(&mut self, pl: Box<Pipeline>) {
*self.1.borrow_mut() = Some(pl);
}
/// Get the HTTP version of this response.
#[inline]
pub fn version(&self) -> Version {
self.0.version
}
/// Get the status from the server.
#[inline]
pub fn status(&self) -> StatusCode {
self.0.status
}
/// Load response cookies.
pub fn cookies(&self) -> Result<Vec<Cookie<'static>>, CookieParseError> {
let mut cookies = Vec::new();
for val in self.0.headers.get_all(header::SET_COOKIE).iter() {
let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
}
Ok(cookies)
}
/// Return request cookie.
pub fn cookie(&self, name: &str) -> Option<Cookie> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies {
if cookie.name() == name {
return Some(cookie);
}
}
}
None
}
}
impl fmt::Debug for ClientResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status())?;
writeln!(f, " headers:")?;
for (key, val) in self.headers().iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debug() {
let mut resp = ClientResponse::new(ClientMessage::default());
resp.0
.headers
.insert(header::COOKIE, HeaderValue::from_static("cookie1=value1"));
resp.0
.headers
.insert(header::COOKIE, HeaderValue::from_static("cookie2=value2"));
let dbg = format!("{:?}", resp);
assert!(dbg.contains("ClientResponse"));
}
}

412
src/client/writer.rs Normal file
View File

@ -0,0 +1,412 @@
#![cfg_attr(
feature = "cargo-clippy",
allow(clippy::redundant_field_names)
)]
use std::cell::RefCell;
use std::fmt::Write as FmtWrite;
use std::io::{self, Write};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliEncoder;
use bytes::{BufMut, BytesMut};
#[cfg(feature = "flate2")]
use flate2::write::{GzEncoder, ZlibEncoder};
#[cfg(feature = "flate2")]
use flate2::Compression;
use futures::{Async, Poll};
use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::{HttpTryFrom, Version};
use time::{self, Duration};
use tokio_io::AsyncWrite;
use body::{Binary, Body};
use header::ContentEncoding;
use server::output::{ContentEncoder, Output, TransferEncoding};
use server::WriterState;
use client::ClientRequest;
const AVERAGE_HEADER_SIZE: usize = 30;
bitflags! {
struct Flags: u8 {
const STARTED = 0b0000_0001;
const UPGRADE = 0b0000_0010;
const KEEPALIVE = 0b0000_0100;
const DISCONNECTED = 0b0000_1000;
}
}
pub(crate) struct HttpClientWriter {
flags: Flags,
written: u64,
headers_size: u32,
buffer: Output,
buffer_capacity: usize,
}
impl HttpClientWriter {
pub fn new() -> HttpClientWriter {
HttpClientWriter {
flags: Flags::empty(),
written: 0,
headers_size: 0,
buffer_capacity: 0,
buffer: Output::Buffer(BytesMut::new()),
}
}
pub fn disconnected(&mut self) {
self.buffer.take();
}
pub fn is_completed(&self) -> bool {
self.buffer.is_empty()
}
// pub fn keepalive(&self) -> bool {
// self.flags.contains(Flags::KEEPALIVE) &&
// !self.flags.contains(Flags::UPGRADE) }
fn write_to_stream<T: AsyncWrite>(
&mut self, stream: &mut T,
) -> io::Result<WriterState> {
while !self.buffer.is_empty() {
match stream.write(self.buffer.as_ref().as_ref()) {
Ok(0) => {
self.disconnected();
return Ok(WriterState::Done);
}
Ok(n) => {
let _ = self.buffer.split_to(n);
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
if self.buffer.len() > self.buffer_capacity {
return Ok(WriterState::Pause);
} else {
return Ok(WriterState::Done);
}
}
Err(err) => return Err(err),
}
}
Ok(WriterState::Done)
}
}
pub struct Writer<'a>(pub &'a mut BytesMut);
impl<'a> io::Write for Writer<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl HttpClientWriter {
pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> {
// prepare task
self.buffer = content_encoder(self.buffer.take(), msg);
self.flags.insert(Flags::STARTED);
if msg.upgrade() {
self.flags.insert(Flags::UPGRADE);
}
// render message
{
// output buffer
let buffer = self.buffer.as_mut();
// status line
writeln!(
Writer(buffer),
"{} {} {:?}\r",
msg.method(),
msg.uri()
.path_and_query()
.map(|u| u.as_str())
.unwrap_or("/"),
msg.version()
).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
// write headers
if let Body::Binary(ref bytes) = *msg.body() {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
} else {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE);
}
for (key, value) in msg.headers() {
let v = value.as_ref();
let k = key.as_str().as_bytes();
buffer.reserve(k.len() + v.len() + 4);
buffer.put_slice(k);
buffer.put_slice(b": ");
buffer.put_slice(v);
buffer.put_slice(b"\r\n");
}
// set date header
if !msg.headers().contains_key(DATE) {
buffer.extend_from_slice(b"date: ");
set_date(buffer);
buffer.extend_from_slice(b"\r\n\r\n");
} else {
buffer.extend_from_slice(b"\r\n");
}
}
self.headers_size = self.buffer.len() as u32;
if msg.body().is_binary() {
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
self.written += bytes.len() as u64;
self.buffer.write(bytes.as_ref())?;
}
} else {
self.buffer_capacity = msg.write_buffer_capacity();
}
Ok(())
}
pub fn write(&mut self, payload: &[u8]) -> io::Result<WriterState> {
self.written += payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) {
self.buffer.write(payload)?;
}
if self.buffer.len() > self.buffer_capacity {
Ok(WriterState::Pause)
} else {
Ok(WriterState::Done)
}
}
pub fn write_eof(&mut self) -> io::Result<()> {
if self.buffer.write_eof()? {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Last payload item, but eof is not reached",
))
}
}
#[inline]
pub fn poll_completed<T: AsyncWrite>(
&mut self, stream: &mut T, shutdown: bool,
) -> Poll<(), io::Error> {
match self.write_to_stream(stream) {
Ok(WriterState::Done) => {
if shutdown {
stream.shutdown()
} else {
Ok(Async::Ready(()))
}
}
Ok(WriterState::Pause) => Ok(Async::NotReady),
Err(err) => Err(err),
}
}
}
fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
let version = req.version();
let mut body = req.replace_body(Body::Empty);
let mut encoding = req.content_encoding();
let transfer = match body {
Body::Empty => {
req.headers_mut().remove(CONTENT_LENGTH);
return Output::Empty(buf);
}
Body::Binary(ref mut bytes) => {
#[cfg(any(feature = "flate2", feature = "brotli"))]
{
if encoding.is_compression() {
let mut tmp = BytesMut::new();
let mut transfer = TransferEncoding::eof(tmp);
let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
ZlibEncoder::new(transfer, Compression::default()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
transfer,
Compression::default(),
)),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
ContentEncoder::Br(BrotliEncoder::new(transfer, 5))
}
ContentEncoding::Auto | ContentEncoding::Identity => {
unreachable!()
}
};
// TODO return error!
let _ = enc.write(bytes.as_ref());
let _ = enc.write_eof();
*bytes = Binary::from(enc.buf_mut().take());
req.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
encoding = ContentEncoding::Identity;
}
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
}
#[cfg(not(any(feature = "flate2", feature = "brotli")))]
{
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
}
}
Body::Streaming(_) | Body::Actor(_) => {
if req.upgrade() {
if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2");
} else {
req.headers_mut()
.insert(CONNECTION, HeaderValue::from_static("upgrade"));
}
if encoding != ContentEncoding::Identity {
encoding = ContentEncoding::Identity;
req.headers_mut().remove(CONTENT_ENCODING);
}
TransferEncoding::eof(buf)
} else {
streaming_encoding(buf, version, req)
}
}
};
if encoding.is_compression() {
req.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
}
req.replace_body(body);
let enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => {
ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::default()))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default()))
}
#[cfg(feature = "brotli")]
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 5)),
ContentEncoding::Identity | ContentEncoding::Auto => return Output::TE(transfer),
};
Output::Encoder(enc)
}
fn streaming_encoding(
buf: BytesMut, version: Version, req: &mut ClientRequest,
) -> TransferEncoding {
if req.chunked() {
// Enable transfer encoding
req.headers_mut().remove(CONTENT_LENGTH);
if version == Version::HTTP_2 {
req.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
} else {
req.headers_mut()
.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
TransferEncoding::chunked(buf)
}
} else {
// if Content-Length is specified, then use it as length hint
let (len, chunked) = if let Some(len) = req.headers().get(CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
(Some(len), false)
} else {
error!("illegal Content-Length: {:?}", len);
(None, false)
}
} else {
error!("illegal Content-Length: {:?}", len);
(None, false)
}
} else {
(None, true)
};
if !chunked {
if let Some(len) = len {
TransferEncoding::length(len, buf)
} else {
TransferEncoding::eof(buf)
}
} else {
// Enable transfer encoding
match version {
Version::HTTP_11 => {
req.headers_mut()
.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
TransferEncoding::chunked(buf)
}
_ => {
req.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
}
}
}
}
}
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
pub const DATE_VALUE_LENGTH: usize = 29;
fn set_date(dst: &mut BytesMut) {
CACHED.with(|cache| {
let mut cache = cache.borrow_mut();
let now = time::get_time();
if now > cache.next_update {
cache.update(now);
}
dst.extend_from_slice(cache.buffer());
})
}
struct CachedDate {
bytes: [u8; DATE_VALUE_LENGTH],
next_update: time::Timespec,
}
thread_local!(static CACHED: RefCell<CachedDate> = RefCell::new(CachedDate {
bytes: [0; DATE_VALUE_LENGTH],
next_update: time::Timespec::new(0, 0),
}));
impl CachedDate {
fn buffer(&self) -> &[u8] {
&self.bytes[..]
}
fn update(&mut self, now: time::Timespec) {
write!(&mut self.bytes[..], "{}", time::at_utc(now).rfc822()).unwrap();
self.next_update = now + Duration::seconds(1);
self.next_update.nsec = 0;
}
}

View File

@ -1,24 +1,26 @@
use std;
use std::marker::PhantomData;
use futures::{Async, Future, Poll};
extern crate actix;
use futures::sync::oneshot;
use futures::sync::oneshot::Sender;
use futures::unsync::oneshot;
use futures::{Async, Future, Poll};
use smallvec::SmallVec;
use std::marker::PhantomData;
use actix::{Actor, ActorState, ActorContext, AsyncContext,
Address, SyncAddress, Handler, Subscriber, ResponseType, SpawnHandle};
use actix::fut::ActorFuture;
use actix::dev::{queue, AsyncContextApi,
ContextImpl, ContextProtocol, Envelope, ToEnvelope, RemoteEnvelope};
use self::actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope,
};
use self::actix::fut::ActorFuture;
use self::actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
};
use body::{Body, Binary};
use error::{Error, Result, ErrorInternalServerError};
use body::{Binary, Body};
use error::{Error, ErrorInternalServerError};
use httprequest::HttpRequest;
pub trait ActorHttpContext: 'static {
fn disconnected(&mut self);
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 2]>>, Error>;
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error>;
}
#[derive(Debug)]
@ -27,16 +29,29 @@ pub enum Frame {
Drain(oneshot::Sender<()>),
}
/// Http actor execution context
pub struct HttpContext<A, S=()> where A: Actor<Context=HttpContext<A, S>>,
impl Frame {
pub fn len(&self) -> usize {
match *self {
Frame::Chunk(Some(ref bin)) => bin.len(),
_ => 0,
}
}
}
/// Execution context for http actors
pub struct HttpContext<A, S = ()>
where
A: Actor<Context = HttpContext<A, S>>,
{
inner: ContextImpl<A>,
stream: Option<SmallVec<[Frame; 2]>>,
inner: ContextParts<A>,
stream: Option<SmallVec<[Frame; 4]>>,
request: HttpRequest<S>,
disconnected: bool,
}
impl<A, S> ActorContext for HttpContext<A, S> where A: Actor<Context=Self>
impl<A, S> ActorContext for HttpContext<A, S>
where
A: Actor<Context = Self>,
{
fn stop(&mut self) {
self.inner.stop();
@ -49,62 +64,80 @@ impl<A, S> ActorContext for HttpContext<A, S> where A: Actor<Context=Self>
}
}
impl<A, S> AsyncContext<A> for HttpContext<A, S> where A: Actor<Context=Self>
impl<A, S> AsyncContext<A> for HttpContext<A, S>
where
A: Actor<Context = Self>,
{
#[inline]
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
where
F: ActorFuture<Item = (), Error = (), Actor = A> + 'static,
{
self.inner.spawn(fut)
}
#[inline]
fn wait<F>(&mut self, fut: F)
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
where
F: ActorFuture<Item = (), Error = (), Actor = A> + 'static,
{
self.inner.wait(fut)
}
#[doc(hidden)]
#[inline]
fn waiting(&self) -> bool {
self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped
}
#[inline]
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
self.inner.cancel_future(handle)
}
}
#[doc(hidden)]
impl<A, S> AsyncContextApi<A> for HttpContext<A, S> where A: Actor<Context=Self> {
#[inline]
fn unsync_sender(&mut self) -> queue::unsync::UnboundedSender<ContextProtocol<A>> {
self.inner.unsync_sender()
}
#[inline]
fn unsync_address(&mut self) -> Address<A> {
self.inner.unsync_address()
}
#[inline]
fn sync_address(&mut self) -> SyncAddress<A> {
self.inner.sync_address()
fn address(&self) -> Addr<A> {
self.inner.address()
}
}
impl<A, S: 'static> HttpContext<A, S> where A: Actor<Context=Self> {
impl<A, S: 'static> HttpContext<A, S>
where
A: Actor<Context = Self>,
{
#[inline]
pub fn new(req: HttpRequest<S>, actor: A) -> HttpContext<A, S> {
HttpContext::from_request(req).actor(actor)
}
pub fn from_request(req: HttpRequest<S>) -> HttpContext<A, S> {
HttpContext {
inner: ContextImpl::new(None),
/// Create a new HTTP Context from a request and an actor
pub fn create(req: HttpRequest<S>, actor: A) -> Body {
let mb = Mailbox::default();
let ctx = HttpContext {
inner: ContextParts::new(mb.sender_producer()),
stream: None,
request: req,
disconnected: false,
};
Body::Actor(Box::new(HttpContextFut::new(ctx, actor, mb)))
}
}
#[inline]
pub fn actor(mut self, actor: A) -> HttpContext<A, S> {
self.inner.set_actor(actor);
self
/// Create a new HTTP Context
pub fn with_factory<F>(req: HttpRequest<S>, f: F) -> Body
where
F: FnOnce(&mut Self) -> A + 'static,
{
let mb = Mailbox::default();
let mut ctx = HttpContext {
inner: ContextParts::new(mb.sender_producer()),
stream: None,
request: req,
disconnected: false,
};
let act = f(&mut ctx);
Body::Actor(Box::new(HttpContextFut::new(ctx, act, mb)))
}
}
impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
impl<A, S> HttpContext<A, S>
where
A: Actor<Context = Self>,
{
/// Shared application state
#[inline]
pub fn state(&self) -> &S {
@ -136,7 +169,6 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
/// Returns drain future
pub fn drain(&mut self) -> Drain<A> {
let (tx, rx) = oneshot::channel();
self.inner.modify();
self.add_frame(Frame::Drain(tx));
Drain::new(rx)
}
@ -152,54 +184,68 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
if self.stream.is_none() {
self.stream = Some(SmallVec::new());
}
self.stream.as_mut().map(|s| s.push(frame));
if let Some(s) = self.stream.as_mut() {
s.push(frame)
}
}
/// Handle of the running future
///
/// SpawnHandle is the handle returned by `AsyncContext::spawn()` method.
pub fn handle(&self) -> SpawnHandle {
self.inner.curr_handle()
}
}
impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
#[inline]
#[doc(hidden)]
pub fn subscriber<M>(&mut self) -> Box<Subscriber<M>>
where A: Handler<M>, M: ResponseType + 'static
{
self.inner.subscriber()
}
#[inline]
#[doc(hidden)]
pub fn sync_subscriber<M>(&mut self) -> Box<Subscriber<M> + Send>
where A: Handler<M>,
M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send,
{
self.inner.sync_subscriber()
impl<A, S> AsyncContextParts<A> for HttpContext<A, S>
where
A: Actor<Context = Self>,
{
fn parts(&mut self) -> &mut ContextParts<A> {
&mut self.inner
}
}
impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>, S: 'static {
struct HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
{
fut: ContextFut<A, HttpContext<A, S>>,
}
impl<A, S> HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
{
fn new(ctx: HttpContext<A, S>, act: A, mailbox: Mailbox<A>) -> Self {
let fut = ContextFut::new(ctx, act, mailbox);
HttpContextFut { fut }
}
}
impl<A, S> ActorHttpContext for HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
S: 'static,
{
#[inline]
fn disconnected(&mut self) {
self.disconnected = true;
self.stop();
self.fut.ctx().disconnected = true;
self.fut.ctx().stop();
}
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 2]>>, Error> {
let ctx: &mut HttpContext<A, S> = unsafe {
std::mem::transmute(self as &mut HttpContext<A, S>)
};
if self.inner.alive() {
match self.inner.poll(ctx) {
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
if self.fut.alive() {
match self.fut.poll() {
Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
Err(_) => return Err(ErrorInternalServerError("error").into()),
Err(_) => return Err(ErrorInternalServerError("error")),
}
}
// frames
if let Some(data) = self.stream.take() {
if let Some(data) = self.fut.ctx().stream.take() {
Ok(Async::Ready(Some(data)))
} else if self.inner.alive() {
} else if self.fut.alive() {
Ok(Async::NotReady)
} else {
Ok(Async::Ready(None))
@ -207,38 +253,29 @@ impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>,
}
}
impl<A, S> ToEnvelope<A> for HttpContext<A, S>
where A: Actor<Context=HttpContext<A, S>>,
impl<A, M, S> ToEnvelope<A, M> for HttpContext<A, S>
where
A: Actor<Context = HttpContext<A, S>> + Handler<M>,
M: Message + Send + 'static,
M::Result: Send,
{
#[inline]
fn pack<M>(msg: M, tx: Option<Sender<Result<M::Item, M::Error>>>,
channel_on_drop: bool) -> Envelope<A>
where A: Handler<M>,
M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send
{
RemoteEnvelope::new(msg, tx, channel_on_drop).into()
}
}
impl<A, S> From<HttpContext<A, S>> for Body
where A: Actor<Context=HttpContext<A, S>>,
S: 'static
{
fn from(ctx: HttpContext<A, S>) -> Body {
Body::Actor(Box::new(ctx))
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
Envelope::new(msg, tx)
}
}
/// Consume a future
pub struct Drain<A> {
fut: oneshot::Receiver<()>,
_a: PhantomData<A>,
}
impl<A> Drain<A> {
/// Create a drain from a future
pub fn new(fut: oneshot::Receiver<()>) -> Self {
Drain {
fut: fut,
_a: PhantomData
fut,
_a: PhantomData,
}
}
}
@ -249,10 +286,9 @@ impl<A: Actor> ActorFuture for Drain<A> {
type Actor = A;
#[inline]
fn poll(&mut self,
_: &mut A,
_: &mut <Self::Actor as Actor>::Context) -> Poll<Self::Item, Self::Error>
{
fn poll(
&mut self, _: &mut A, _: &mut <Self::Actor as Actor>::Context,
) -> Poll<Self::Item, Self::Error> {
self.fut.poll().map_err(|_| ())
}
}

443
src/de.rs Normal file
View File

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

File diff suppressed because it is too large Load Diff

114
src/extensions.rs Normal file
View File

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

1024
src/extractor.rs Normal file

File diff suppressed because it is too large Load Diff

1763
src/fs.rs

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,23 @@
use std::marker::PhantomData;
use std::ops::Deref;
use regex::Regex;
use futures::future::{Future, ok, err};
use http::{header, StatusCode, Error as HttpError};
use futures::future::{err, ok, Future};
use futures::{Async, Poll};
use body::Body;
use error::Error;
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use resource::DefaultResource;
/// Trait defines object that could be registered as route handler
#[allow(unused_variables)]
pub trait Handler<S>: 'static {
/// The type of value that handler will return.
type Result: Responder;
/// Handle request
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result;
fn handle(&self, req: &HttpRequest<S>) -> Self::Result;
}
/// Trait implemented by types that generate responses for clients.
@ -25,114 +25,304 @@ pub trait Handler<S>: 'static {
/// Types that implement this trait can be used as the return type of a handler.
pub trait Responder {
/// The associated item which can be returned.
type Item: Into<Reply>;
type Item: Into<AsyncResult<HttpResponse>>;
/// The associated error which can be returned.
type Error: Into<Error>;
/// Convert itself to `Reply` or `Error`.
fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error>;
/// Convert itself to `AsyncResult` or `Error`.
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<Self::Item, Self::Error>;
}
#[doc(hidden)]
/// Convenience trait that convert `Future` object into `Boxed` future
/// Trait implemented by types that can be extracted from request.
///
/// Types that implement this trait can be used with `Route::with()` method.
pub trait FromRequest<S>: Sized {
/// Configuration for conversion process
type Config: Default;
/// Future that resolves to a Self
type Result: Into<AsyncResult<Self>>;
/// Convert request to a Self
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result;
/// Convert request to a Self
///
/// This method uses default extractor configuration
fn extract(req: &HttpRequest<S>) -> Self::Result {
Self::from_request(req, &Self::Config::default())
}
}
/// Combines two different responder types into a single type
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::future::Future;
/// use actix_web::{AsyncResponder, Either, Error, HttpRequest, HttpResponse};
/// use futures::future::result;
///
/// type RegisterResult =
/// Either<HttpResponse, Box<Future<Item = HttpResponse, Error = Error>>>;
///
/// fn index(req: HttpRequest) -> RegisterResult {
/// if is_a_variant() {
/// // <- choose variant A
/// Either::A(HttpResponse::BadRequest().body("Bad data"))
/// } else {
/// Either::B(
/// // <- variant B
/// result(Ok(HttpResponse::Ok()
/// .content_type("text/html")
/// .body("Hello!")))
/// .responder(),
/// )
/// }
/// }
/// # fn is_a_variant() -> bool { true }
/// # fn main() {}
/// ```
#[derive(Debug)]
pub enum Either<A, B> {
/// First branch of the type
A(A),
/// Second branch of the type
B(B),
}
impl<A, B> Responder for Either<A, B>
where
A: Responder,
B: Responder,
{
type Item = AsyncResult<HttpResponse>;
type Error = Error;
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
match self {
Either::A(a) => match a.respond_to(req) {
Ok(val) => Ok(val.into()),
Err(err) => Err(err.into()),
},
Either::B(b) => match b.respond_to(req) {
Ok(val) => Ok(val.into()),
Err(err) => Err(err.into()),
},
}
}
}
impl<A, B, I, E> Future for Either<A, B>
where
A: Future<Item = I, Error = E>,
B: Future<Item = I, Error = E>,
{
type Item = I;
type Error = E;
fn poll(&mut self) -> Poll<I, E> {
match *self {
Either::A(ref mut fut) => fut.poll(),
Either::B(ref mut fut) => fut.poll(),
}
}
}
impl<T> Responder for Option<T>
where
T: Responder,
{
type Item = AsyncResult<HttpResponse>;
type Error = Error;
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
match self {
Some(t) => match t.respond_to(req) {
Ok(val) => Ok(val.into()),
Err(err) => Err(err.into()),
},
None => Ok(req.build_response(StatusCode::NOT_FOUND).finish().into()),
}
}
}
/// Convenience trait that converts `Future` object to a `Boxed` future
///
/// For example loading json from request's body is async operation.
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{
/// App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse,
/// };
/// use futures::future::Future;
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
/// Ok(HttpResponse::Ok().into())
/// })
/// // Construct boxed future by using `AsyncResponder::responder()` method
/// .responder()
/// }
/// # fn main() {}
/// ```
pub trait AsyncResponder<I, E>: Sized {
fn responder(self) -> Box<Future<Item=I, Error=E>>;
/// Convert to a boxed future
fn responder(self) -> Box<Future<Item = I, Error = E>>;
}
impl<F, I, E> AsyncResponder<I, E> for F
where F: Future<Item=I, Error=E> + 'static,
where
F: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
{
fn responder(self) -> Box<Future<Item=I, Error=E>> {
fn responder(self) -> Box<Future<Item = I, Error = E>> {
Box::new(self)
}
}
/// Handler<S> for Fn()
impl<F, R, S> Handler<S> for F
where F: Fn(HttpRequest<S>) -> R + 'static,
R: Responder + 'static
where
F: Fn(&HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
type Result = R;
fn handle(&mut self, req: HttpRequest<S>) -> R {
fn handle(&self, req: &HttpRequest<S>) -> R {
(self)(req)
}
}
/// Represents response process.
pub struct Reply(ReplyItem);
/// Represents async result
///
/// Result could be in tree different forms.
/// * Ok(T) - ready item
/// * Err(E) - error happen during reply process
/// * Future<T, E> - reply process completes in the future
pub struct AsyncResult<I, E = Error>(Option<AsyncResultItem<I, E>>);
pub(crate) enum ReplyItem {
Message(HttpResponse),
Future(Box<Future<Item=HttpResponse, Error=Error>>),
impl<I, E> Future for AsyncResult<I, E> {
type Item = I;
type Error = E;
fn poll(&mut self) -> Poll<I, E> {
let res = self.0.take().expect("use after resolve");
match res {
AsyncResultItem::Ok(msg) => Ok(Async::Ready(msg)),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(mut fut) => match fut.poll() {
Ok(Async::NotReady) => {
self.0 = Some(AsyncResultItem::Future(fut));
Ok(Async::NotReady)
}
Ok(Async::Ready(msg)) => Ok(Async::Ready(msg)),
Err(err) => Err(err),
},
}
}
}
impl Reply {
pub(crate) enum AsyncResultItem<I, E> {
Ok(I),
Err(E),
Future(Box<Future<Item = I, Error = E>>),
}
impl<I, E> AsyncResult<I, E> {
/// Create async response
#[inline]
pub fn async<F>(fut: F) -> Reply
where F: Future<Item=HttpResponse, Error=Error> + 'static
{
Reply(ReplyItem::Future(Box::new(fut)))
pub fn async(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
AsyncResult(Some(AsyncResultItem::Future(fut)))
}
/// Send response
#[inline]
pub fn response<R: Into<HttpResponse>>(response: R) -> Reply {
Reply(ReplyItem::Message(response.into()))
pub fn ok<R: Into<I>>(ok: R) -> AsyncResult<I, E> {
AsyncResult(Some(AsyncResultItem::Ok(ok.into())))
}
/// Send error
#[inline]
pub fn err<R: Into<E>>(err: R) -> AsyncResult<I, E> {
AsyncResult(Some(AsyncResultItem::Err(err.into())))
}
#[inline]
pub(crate) fn into(self) -> ReplyItem {
self.0
pub(crate) fn into(self) -> AsyncResultItem<I, E> {
self.0.expect("use after resolve")
}
#[cfg(test)]
pub(crate) fn as_response(&self) -> Option<&HttpResponse> {
match self.0 {
ReplyItem::Message(ref resp) => Some(resp),
pub(crate) fn as_msg(&self) -> &I {
match self.0.as_ref().unwrap() {
&AsyncResultItem::Ok(ref resp) => resp,
_ => panic!(),
}
}
#[cfg(test)]
pub(crate) fn as_err(&self) -> Option<&E> {
match self.0.as_ref().unwrap() {
&AsyncResultItem::Err(ref err) => Some(err),
_ => None,
}
}
}
impl Responder for Reply {
type Item = Reply;
impl Responder for AsyncResult<HttpResponse> {
type Item = AsyncResult<HttpResponse>;
type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<Reply, Error> {
fn respond_to<S>(
self, _: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
Ok(self)
}
}
impl Responder for HttpResponse {
type Item = Reply;
type Item = AsyncResult<HttpResponse>;
type Error = Error;
#[inline]
fn respond_to(self, _: HttpRequest) -> Result<Reply, Error> {
Ok(Reply(ReplyItem::Message(self)))
fn respond_to<S>(
self, _: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
Ok(AsyncResult(Some(AsyncResultItem::Ok(self))))
}
}
impl From<HttpResponse> for Reply {
impl<T> From<T> for AsyncResult<T> {
#[inline]
fn from(resp: HttpResponse) -> Reply {
Reply(ReplyItem::Message(resp))
fn from(resp: T) -> AsyncResult<T> {
AsyncResult(Some(AsyncResultItem::Ok(resp)))
}
}
impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
{
impl<T: Responder, E: Into<Error>> Responder for Result<T, E> {
type Item = <T as Responder>::Item;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error> {
fn respond_to<S: 'static>(self, req: &HttpRequest<S>) -> Result<Self::Item, Error> {
match self {
Ok(val) => match val.respond_to(req) {
Ok(val) => Ok(val),
@ -143,65 +333,94 @@ impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
}
}
impl<E: Into<Error>> From<Result<Reply, E>> for Reply {
impl<T, E: Into<Error>> From<Result<AsyncResult<T>, E>> for AsyncResult<T> {
#[inline]
fn from(res: Result<Reply, E>) -> Self {
fn from(res: Result<AsyncResult<T>, E>) -> Self {
match res {
Ok(val) => val,
Err(err) => Reply(ReplyItem::Message(err.into().into())),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
}
impl<E: Into<Error>> From<Result<HttpResponse, E>> for Reply {
impl<T, E: Into<Error>> From<Result<T, E>> for AsyncResult<T> {
#[inline]
fn from(res: Result<HttpResponse, E>) -> Self {
fn from(res: Result<T, E>) -> Self {
match res {
Ok(val) => Reply(ReplyItem::Message(val)),
Err(err) => Reply(ReplyItem::Message(err.into().into())),
Ok(val) => AsyncResult(Some(AsyncResultItem::Ok(val))),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
}
impl From<Box<Future<Item=HttpResponse, Error=Error>>> for Reply {
#[inline]
fn from(fut: Box<Future<Item=HttpResponse, Error=Error>>) -> Reply {
Reply(ReplyItem::Future(fut))
}
}
impl<I, E> Responder for Box<Future<Item=I, Error=E>>
where I: Responder + 'static,
E: Into<Error> + 'static
impl<T, E> From<Result<Box<Future<Item = T, Error = E>>, E>> for AsyncResult<T>
where
T: 'static,
E: Into<Error> + 'static,
{
type Item = Reply;
#[inline]
fn from(res: Result<Box<Future<Item = T, Error = E>>, E>) -> Self {
match res {
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(Box::new(
fut.map_err(|e| e.into()),
)))),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
}
impl<T> From<Box<Future<Item = T, Error = Error>>> for AsyncResult<T> {
#[inline]
fn from(fut: Box<Future<Item = T, Error = Error>>) -> AsyncResult<T> {
AsyncResult(Some(AsyncResultItem::Future(fut)))
}
}
/// Convenience type alias
pub type FutureResponse<I, E = Error> = Box<Future<Item = I, Error = E>>;
impl<I, E> Responder for Box<Future<Item = I, Error = E>>
where
I: Responder + 'static,
E: Into<Error> + 'static,
{
type Item = AsyncResult<HttpResponse>;
type Error = Error;
#[inline]
fn respond_to(self, req: HttpRequest) -> Result<Reply, Error> {
let fut = self.map_err(|e| e.into())
.then(move |r| {
match r.respond_to(req) {
Ok(reply) => match reply.into().0 {
ReplyItem::Message(resp) => ok(resp),
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
let req = req.clone();
let fut = self
.map_err(|e| e.into())
.then(move |r| match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => ok(resp),
_ => panic!("Nested async replies are not supported"),
},
Err(e) => err(e),
}
});
Ok(Reply::async(fut))
Ok(AsyncResult::async(Box::new(fut)))
}
}
/// Trait defines object that could be registered as resource route
pub(crate) trait RouteHandler<S>: 'static {
fn handle(&mut self, req: HttpRequest<S>) -> Reply;
fn handle(&self, &HttpRequest<S>) -> AsyncResult<HttpResponse>;
fn has_default_resource(&self) -> bool {
false
}
fn default_resource(&mut self, _: DefaultResource<S>) {}
fn finish(&mut self) {}
}
/// Route handler wrapper for Handler
pub(crate)
struct WrapHandler<S, H, R>
where H: Handler<S, Result=R>,
pub(crate) struct WrapHandler<S, H, R>
where
H: Handler<S, Result = R>,
R: Responder,
S: 'static,
{
@ -210,34 +429,35 @@ struct WrapHandler<S, H, R>
}
impl<S, H, R> WrapHandler<S, H, R>
where H: Handler<S, Result=R>,
where
H: Handler<S, Result = R>,
R: Responder,
S: 'static,
{
pub fn new(h: H) -> Self {
WrapHandler{h: h, s: PhantomData}
WrapHandler { h, s: PhantomData }
}
}
impl<S, H, R> RouteHandler<S> for WrapHandler<S, H, R>
where H: Handler<S, Result=R>,
where
H: Handler<S, Result = R>,
R: Responder + 'static,
S: 'static,
{
fn handle(&mut self, req: HttpRequest<S>) -> Reply {
let req2 = req.clone_without_state();
match self.h.handle(req).respond_to(req2) {
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
match self.h.handle(req).respond_to(req) {
Ok(reply) => reply.into(),
Err(err) => Reply::response(err.into()),
Err(err) => AsyncResult::err(err.into()),
}
}
}
/// Async route handler
pub(crate)
struct AsyncHandler<S, H, F, R, E>
where H: Fn(HttpRequest<S>) -> F + 'static,
F: Future<Item=R, Error=E> + 'static,
pub(crate) struct AsyncHandler<S, H, F, R, E>
where
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
S: 'static,
@ -247,316 +467,96 @@ struct AsyncHandler<S, H, F, R, E>
}
impl<S, H, F, R, E> AsyncHandler<S, H, F, R, E>
where H: Fn(HttpRequest<S>) -> F + 'static,
F: Future<Item=R, Error=E> + 'static,
where
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
S: 'static,
{
pub fn new(h: H) -> Self {
AsyncHandler{h: Box::new(h), s: PhantomData}
AsyncHandler {
h: Box::new(h),
s: PhantomData,
}
}
}
impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
where H: Fn(HttpRequest<S>) -> F + 'static,
F: Future<Item=R, Error=E> + 'static,
where
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
S: 'static,
{
fn handle(&mut self, req: HttpRequest<S>) -> Reply {
let req2 = req.clone_without_state();
let fut = (self.h)(req)
.map_err(|e| e.into())
.then(move |r| {
match r.respond_to(req2) {
Ok(reply) => match reply.into().0 {
ReplyItem::Message(resp) => ok(resp),
_ => panic!("Nested async replies are not supported"),
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
let req = req.clone();
let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| {
match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => Either::A(ok(resp)),
AsyncResultItem::Err(e) => Either::A(err(e)),
AsyncResultItem::Future(fut) => Either::B(fut),
},
Err(e) => err(e),
Err(e) => Either::A(err(e)),
}
});
Reply::async(fut)
AsyncResult::async(Box::new(fut))
}
}
/// Path normalization helper
/// Access an application state
///
/// By normalizing it means:
/// `S` - application state type
///
/// - Add a trailing slash to the path.
/// - Double slashes are replaced by one.
///
/// The handler returns as soon as it finds a path that resolves
/// correctly. The order if all enable is 1) merge, 3) both merge and append
/// and 3) append. If the path resolves with
/// at least one of those conditions, it will redirect to the new path.
///
/// If *append* is *true* append slash when needed. If a resource is
/// defined with trailing slash and the request comes without it, it will
/// append it automatically.
///
/// If *merge* is *true*, merge multiple consecutive slashes in the path into one.
///
/// This handler designed to be use as a handler for application's *default resource*.
/// ## Example
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # #[macro_use] extern crate serde_derive;
/// # use actix_web::*;
/// #
/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
/// # httpcodes::HTTPOk
/// # }
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Path, State};
///
/// /// Application state
/// struct MyApp {
/// msg: &'static str,
/// }
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(state: State<MyApp>, path: Path<Info>) -> String {
/// format!("{} {}!", state.msg, path.username)
/// }
///
/// fn main() {
/// let app = Application::new()
/// .resource("/test/", |r| r.f(index))
/// .default_resource(|r| r.h(NormalizePath::default()))
/// .finish();
/// let app = App::with_state(MyApp { msg: "Welcome" }).resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
/// In this example `/test`, `/test///` will be redirected to `/test/` url.
pub struct NormalizePath {
append: bool,
merge: bool,
re_merge: Regex,
redirect: StatusCode,
not_found: StatusCode,
}
pub struct State<S>(HttpRequest<S>);
impl Default for NormalizePath {
/// Create default `NormalizePath` instance, *append* is set to *true*,
/// *merge* is set to *true* and *redirect* is set to `StatusCode::MOVED_PERMANENTLY`
fn default() -> NormalizePath {
NormalizePath {
append: true,
merge: true,
re_merge: Regex::new("//+").unwrap(),
redirect: StatusCode::MOVED_PERMANENTLY,
not_found: StatusCode::NOT_FOUND,
}
impl<S> Deref for State<S> {
type Target = S;
fn deref(&self) -> &S {
self.0.state()
}
}
impl NormalizePath {
/// Create new `NormalizePath` instance
pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath {
NormalizePath {
append: append,
merge: merge,
re_merge: Regex::new("//+").unwrap(),
redirect: redirect,
not_found: StatusCode::NOT_FOUND,
}
impl<S> FromRequest<S> for State<S> {
type Config = ();
type Result = State<S>;
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
State(req.clone())
}
}
impl<S> Handler<S> for NormalizePath {
type Result = Result<HttpResponse, HttpError>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
if let Some(router) = req.router() {
let query = req.query_string();
if self.merge {
// merge slashes
let p = self.re_merge.replace_all(req.path(), "/");
if p.len() != req.path().len() {
if router.has_route(p.as_ref()) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref())
.body(Body::Empty);
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.body(Body::Empty);
}
}
}
}
// append trailing slash
if self.append && !req.path().ends_with('/') {
let p = req.path().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.body(Body::Empty);
}
}
}
Ok(HttpResponse::new(self.not_found, Body::Empty))
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::{header, Method};
use test::TestRequest;
use application::Application;
fn index(_req: HttpRequest) -> HttpResponse {
HttpResponse::new(StatusCode::OK, Body::Empty)
}
#[test]
fn test_normalize_path_trailing_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![("/resource1", "", StatusCode::OK),
("/resource1/", "", StatusCode::NOT_FOUND),
("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
("/resource2/", "", StatusCode::OK),
("/resource1?p1=1&p2=2", "", StatusCode::OK),
("/resource1/?p1=1&p2=2", "", StatusCode::NOT_FOUND),
("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY),
("/resource2/?p1=1&p2=2", "", StatusCode::OK)
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
#[test]
fn test_normalize_path_trailing_slashes_disabled() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(
NormalizePath::new(false, true, StatusCode::MOVED_PERMANENTLY)))
.finish();
// trailing slashes
let params = vec![("/resource1", StatusCode::OK),
("/resource1/", StatusCode::NOT_FOUND),
("/resource2", StatusCode::NOT_FOUND),
("/resource2/", StatusCode::OK),
("/resource1?p1=1&p2=2", StatusCode::OK),
("/resource1/?p1=1&p2=2", StatusCode::NOT_FOUND),
("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND),
("/resource2/?p1=1&p2=2", StatusCode::OK)
];
for (path, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
}
}
#[test]
fn test_normalize_path_merge_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/", "", StatusCode::NOT_FOUND),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/", "", StatusCode::NOT_FOUND),
("/resource1/a/b?p=1", "", StatusCode::OK),
("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/?p=1", "", StatusCode::NOT_FOUND),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/?p=1", "", StatusCode::NOT_FOUND),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
#[test]
fn test_normalize_path_merge_and_append_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.resource("/resource2/a/b/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("/resource1/a/b/", "", StatusCode::NOT_FOUND),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/", "", StatusCode::NOT_FOUND),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/", "", StatusCode::NOT_FOUND),
("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b/", "", StatusCode::OK),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK),
("/resource1/a/b/?p=1", "", StatusCode::NOT_FOUND),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/?p=1", "", StatusCode::NOT_FOUND),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/?p=1", "", StatusCode::NOT_FOUND),
("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target, r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
}

159
src/header/common/accept.rs Normal file
View File

@ -0,0 +1,159 @@
use header::{qitem, QualityItem};
use http::header as http;
use mime::{self, Mime};
header! {
/// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2)
///
/// The `Accept` header field can be used by user agents to specify
/// response media types that are acceptable. Accept header fields can
/// be used to indicate that the request is specifically limited to a
/// small set of desired types, as in the case of a request for an
/// in-line image
///
/// # ABNF
///
/// ```text
/// Accept = #( media-range [ accept-params ] )
///
/// media-range = ( "*/*"
/// / ( type "/" "*" )
/// / ( type "/" subtype )
/// ) *( OWS ";" OWS parameter )
/// accept-params = weight *( accept-ext )
/// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ]
/// ```
///
/// # Example values
/// * `audio/*; q=0.2, audio/basic`
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
///
/// # Examples
/// ```rust
/// # extern crate actix_web;
/// extern crate mime;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{Accept, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
///
/// builder.set(
/// Accept(vec![
/// qitem(mime::TEXT_HTML),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_web;
/// extern crate mime;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{Accept, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
///
/// builder.set(
/// Accept(vec![
/// qitem(mime::APPLICATION_JSON),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_web;
/// extern crate mime;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{Accept, QualityItem, q, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
///
/// builder.set(
/// Accept(vec![
/// qitem(mime::TEXT_HTML),
/// qitem("application/xhtml+xml".parse().unwrap()),
/// QualityItem::new(
/// mime::TEXT_XML,
/// q(900)
/// ),
/// qitem("image/webp".parse().unwrap()),
/// QualityItem::new(
/// mime::STAR_STAR,
/// q(800)
/// ),
/// ])
/// );
/// # }
/// ```
(Accept, http::ACCEPT) => (QualityItem<Mime>)+
test_accept {
// Tests from the RFC
test_header!(
test1,
vec![b"audio/*; q=0.2, audio/basic"],
Some(HeaderField(vec![
QualityItem::new("audio/*".parse().unwrap(), q(200)),
qitem("audio/basic".parse().unwrap()),
])));
test_header!(
test2,
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
Some(HeaderField(vec![
QualityItem::new(TEXT_PLAIN, q(500)),
qitem(TEXT_HTML),
QualityItem::new(
"text/x-dvi".parse().unwrap(),
q(800)),
qitem("text/x-c".parse().unwrap()),
])));
// Custom tests
test_header!(
test3,
vec![b"text/plain; charset=utf-8"],
Some(Accept(vec![
qitem(TEXT_PLAIN_UTF_8),
])));
test_header!(
test4,
vec![b"text/plain; charset=utf-8; q=0.5"],
Some(Accept(vec![
QualityItem::new(TEXT_PLAIN_UTF_8,
q(500)),
])));
#[test]
fn test_fuzzing1() {
use test::TestRequest;
let req = TestRequest::with_header(super::http::ACCEPT, "chunk#;e").finish();
let header = Accept::parse(&req);
assert!(header.is_ok());
}
}
}
impl Accept {
/// A constructor to easily create `Accept: */*`.
pub fn star() -> Accept {
Accept(vec![qitem(mime::STAR_STAR)])
}
/// A constructor to easily create `Accept: application/json`.
pub fn json() -> Accept {
Accept(vec![qitem(mime::APPLICATION_JSON)])
}
/// A constructor to easily create `Accept: text/*`.
pub fn text() -> Accept {
Accept(vec![qitem(mime::TEXT_STAR)])
}
/// A constructor to easily create `Accept: image/*`.
pub fn image() -> Accept {
Accept(vec![qitem(mime::IMAGE_STAR)])
}
}

View File

@ -0,0 +1,69 @@
use header::{Charset, QualityItem, ACCEPT_CHARSET};
header! {
/// `Accept-Charset` header, defined in
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3)
///
/// The `Accept-Charset` header field can be sent by a user agent to
/// indicate what charsets are acceptable in textual response content.
/// This field allows user agents capable of understanding more
/// comprehensive or special-purpose charsets to signal that capability
/// to an origin server that is capable of representing information in
/// those charsets.
///
/// # ABNF
///
/// ```text
/// Accept-Charset = 1#( ( charset / "*" ) [ weight ] )
/// ```
///
/// # Example values
/// * `iso-8859-5, unicode-1-1;q=0.8`
///
/// # Examples
/// ```rust
/// # extern crate actix_web;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptCharset, Charset, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// AcceptCharset(vec![qitem(Charset::Us_Ascii)])
/// );
/// # }
/// ```
/// ```rust
/// # extern crate actix_web;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// AcceptCharset(vec![
/// QualityItem::new(Charset::Us_Ascii, q(900)),
/// QualityItem::new(Charset::Iso_8859_10, q(200)),
/// ])
/// );
/// # }
/// ```
/// ```rust
/// # extern crate actix_web;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptCharset, Charset, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))])
/// );
/// # }
/// ```
(AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+
test_accept_charset {
/// Test case from RFC
test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]);
}
}

View File

@ -0,0 +1,72 @@
use header::{Encoding, QualityItem};
header! {
/// `Accept-Encoding` header, defined in
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4)
///
/// The `Accept-Encoding` header field can be used by user agents to
/// indicate what response content-codings are
/// acceptable in the response. An `identity` token is used as a synonym
/// for "no encoding" in order to communicate when no encoding is
/// preferred.
///
/// # ABNF
///
/// ```text
/// Accept-Encoding = #( codings [ weight ] )
/// codings = content-coding / "identity" / "*"
/// ```
///
/// # Example values
/// * `compress, gzip`
/// * ``
/// * `*`
/// * `compress;q=0.5, gzip;q=1`
/// * `gzip;q=1.0, identity; q=0.5, *;q=0`
///
/// # Examples
/// ```
/// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem};
///
/// let mut headers = Headers::new();
/// headers.set(
/// AcceptEncoding(vec![qitem(Encoding::Chunked)])
/// );
/// ```
/// ```
/// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem};
///
/// let mut headers = Headers::new();
/// headers.set(
/// AcceptEncoding(vec![
/// qitem(Encoding::Chunked),
/// qitem(Encoding::Gzip),
/// qitem(Encoding::Deflate),
/// ])
/// );
/// ```
/// ```
/// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem};
///
/// let mut headers = Headers::new();
/// headers.set(
/// AcceptEncoding(vec![
/// qitem(Encoding::Chunked),
/// QualityItem::new(Encoding::Gzip, q(600)),
/// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)),
/// ])
/// );
/// ```
(AcceptEncoding, "Accept-Encoding") => (QualityItem<Encoding>)*
test_accept_encoding {
// From the RFC
test_header!(test1, vec![b"compress, gzip"]);
test_header!(test2, vec![b""], Some(AcceptEncoding(vec![])));
test_header!(test3, vec![b"*"]);
// Note: Removed quality 1 from gzip
test_header!(test4, vec![b"compress;q=0.5, gzip"]);
// Note: Removed quality 1 from gzip
test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]);
}
}

View File

@ -0,0 +1,75 @@
use header::{QualityItem, ACCEPT_LANGUAGE};
use language_tags::LanguageTag;
header! {
/// `Accept-Language` header, defined in
/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5)
///
/// The `Accept-Language` header field can be used by user agents to
/// indicate the set of natural languages that are preferred in the
/// response.
///
/// # ABNF
///
/// ```text
/// Accept-Language = 1#( language-range [ weight ] )
/// language-range = <language-range, see [RFC4647], Section 2.1>
/// ```
///
/// # Example values
/// * `da, en-gb;q=0.8, en;q=0.7`
/// * `en-us;q=1.0, en;q=0.5, fr`
///
/// # Examples
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate language_tags;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem};
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// let mut langtag: LanguageTag = Default::default();
/// langtag.language = Some("en".to_owned());
/// langtag.region = Some("US".to_owned());
/// builder.set(
/// AcceptLanguage(vec![
/// qitem(langtag),
/// ])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate actix_web;
/// # #[macro_use] extern crate language_tags;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem};
/// #
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// AcceptLanguage(vec![
/// qitem(langtag!(da)),
/// QualityItem::new(langtag!(en;;;GB), q(800)),
/// QualityItem::new(langtag!(en), q(700)),
/// ])
/// );
/// # }
/// ```
(AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+
test_accept_language {
// From the RFC
test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]);
// Own test
test_header!(
test2, vec![b"en-US, en; q=0.5, fr"],
Some(AcceptLanguage(vec![
qitem("en-US".parse().unwrap()),
QualityItem::new("en".parse().unwrap(), q(500)),
qitem("fr".parse().unwrap()),
])));
}
}

View File

@ -0,0 +1,85 @@
use http::Method;
use http::header;
header! {
/// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1)
///
/// The `Allow` header field lists the set of methods advertised as
/// supported by the target resource. The purpose of this field is
/// strictly to inform the recipient of valid request methods associated
/// with the resource.
///
/// # ABNF
///
/// ```text
/// Allow = #method
/// ```
///
/// # Example values
/// * `GET, HEAD, PUT`
/// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr`
/// * ``
///
/// # Examples
///
/// ```rust
/// # extern crate http;
/// # extern crate actix_web;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::Allow;
/// use http::Method;
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// Allow(vec![Method::GET])
/// );
/// # }
/// ```
///
/// ```rust
/// # extern crate http;
/// # extern crate actix_web;
/// use actix_web::HttpResponse;
/// use actix_web::http::header::Allow;
/// use http::Method;
///
/// # fn main() {
/// let mut builder = HttpResponse::Ok();
/// builder.set(
/// Allow(vec![
/// Method::GET,
/// Method::POST,
/// Method::PATCH,
/// ])
/// );
/// # }
/// ```
(Allow, header::ALLOW) => (Method)*
test_allow {
// From the RFC
test_header!(
test1,
vec![b"GET, HEAD, PUT"],
Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT])));
// Own tests
test_header!(
test2,
vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"],
Some(HeaderField(vec![
Method::OPTIONS,
Method::GET,
Method::PUT,
Method::POST,
Method::DELETE,
Method::HEAD,
Method::TRACE,
Method::CONNECT,
Method::PATCH])));
test_header!(
test3,
vec![b""],
Some(HeaderField(Vec::<Method>::new())));
}
}

View File

@ -0,0 +1,254 @@
use header::{Header, IntoHeaderValue, Writer};
use header::{fmt_comma_delimited, from_comma_delimited};
use http::header;
use std::fmt::{self, Write};
use std::str::FromStr;
/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2)
///
/// The `Cache-Control` header field is used to specify directives for
/// caches along the request/response chain. Such cache directives are
/// unidirectional in that the presence of a directive in a request does
/// not imply that the same directive is to be given in the response.
///
/// # ABNF
///
/// ```text
/// Cache-Control = 1#cache-directive
/// cache-directive = token [ "=" ( token / quoted-string ) ]
/// ```
///
/// # Example values
///
/// * `no-cache`
/// * `private, community="UCI"`
/// * `max-age=30`
///
/// # Examples
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{CacheControl, CacheDirective};
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)]));
/// ```
///
/// ```rust
/// use actix_web::HttpResponse;
/// use actix_web::http::header::{CacheControl, CacheDirective};
///
/// let mut builder = HttpResponse::Ok();
/// builder.set(CacheControl(vec![
/// CacheDirective::NoCache,
/// CacheDirective::Private,
/// CacheDirective::MaxAge(360u32),
/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())),
/// ]));
/// ```
#[derive(PartialEq, Clone, Debug)]
pub struct CacheControl(pub Vec<CacheDirective>);
__hyper__deref!(CacheControl => Vec<CacheDirective>);
//TODO: this could just be the header! macro
impl Header for CacheControl {
fn name() -> header::HeaderName {
header::CACHE_CONTROL
}
#[inline]
fn parse<T>(msg: &T) -> Result<Self, ::error::ParseError>
where
T: ::HttpMessage,
{
let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?;
if !directives.is_empty() {
Ok(CacheControl(directives))
} else {
Err(::error::ParseError::Header)
}
}
}
impl fmt::Display for CacheControl {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_comma_delimited(f, &self[..])
}
}
impl IntoHeaderValue for CacheControl {
type Error = header::InvalidHeaderValueBytes;
fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self);
header::HeaderValue::from_shared(writer.take())
}
}
/// `CacheControl` contains a list of these directives.
#[derive(PartialEq, Clone, Debug)]
pub enum CacheDirective {
/// "no-cache"
NoCache,
/// "no-store"
NoStore,
/// "no-transform"
NoTransform,
/// "only-if-cached"
OnlyIfCached,
// request directives
/// "max-age=delta"
MaxAge(u32),
/// "max-stale=delta"
MaxStale(u32),
/// "min-fresh=delta"
MinFresh(u32),
// response directives
/// "must-revalidate"
MustRevalidate,
/// "public"
Public,
/// "private"
Private,
/// "proxy-revalidate"
ProxyRevalidate,
/// "s-maxage=delta"
SMaxAge(u32),
/// Extension directives. Optionally include an argument.
Extension(String, Option<String>),
}
impl fmt::Display for CacheDirective {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::CacheDirective::*;
fmt::Display::fmt(
match *self {
NoCache => "no-cache",
NoStore => "no-store",
NoTransform => "no-transform",
OnlyIfCached => "only-if-cached",
MaxAge(secs) => return write!(f, "max-age={}", secs),
MaxStale(secs) => return write!(f, "max-stale={}", secs),
MinFresh(secs) => return write!(f, "min-fresh={}", secs),
MustRevalidate => "must-revalidate",
Public => "public",
Private => "private",
ProxyRevalidate => "proxy-revalidate",
SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
Extension(ref name, None) => &name[..],
Extension(ref name, Some(ref arg)) => {
return write!(f, "{}={}", name, arg)
}
},
f,
)
}
}
impl FromStr for CacheDirective {
type Err = Option<<u32 as FromStr>::Err>;
fn from_str(s: &str) -> Result<CacheDirective, Option<<u32 as FromStr>::Err>> {
use self::CacheDirective::*;
match s {
"no-cache" => Ok(NoCache),
"no-store" => Ok(NoStore),
"no-transform" => Ok(NoTransform),
"only-if-cached" => Ok(OnlyIfCached),
"must-revalidate" => Ok(MustRevalidate),
"public" => Ok(Public),
"private" => Ok(Private),
"proxy-revalidate" => Ok(ProxyRevalidate),
"" => Err(None),
_ => match s.find('=') {
Some(idx) if idx + 1 < s.len() => {
match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) {
("max-age", secs) => secs.parse().map(MaxAge).map_err(Some),
("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some),
("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some),
("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some),
(left, right) => {
Ok(Extension(left.to_owned(), Some(right.to_owned())))
}
}
}
Some(_) => Err(None),
None => Ok(Extension(s.to_owned(), None)),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use header::Header;
use test::TestRequest;
#[test]
fn test_parse_multiple_headers() {
let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private")
.finish();
let cache = Header::parse(&req);
assert_eq!(
cache.ok(),
Some(CacheControl(vec![
CacheDirective::NoCache,
CacheDirective::Private,
]))
)
}
#[test]
fn test_parse_argument() {
let req =
TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private")
.finish();
let cache = Header::parse(&req);
assert_eq!(
cache.ok(),
Some(CacheControl(vec![
CacheDirective::MaxAge(100),
CacheDirective::Private,
]))
)
}
#[test]
fn test_parse_quote_form() {
let req =
TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish();
let cache = Header::parse(&req);
assert_eq!(
cache.ok(),
Some(CacheControl(vec![CacheDirective::MaxAge(200)]))
)
}
#[test]
fn test_parse_extension() {
let req =
TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish();
let cache = Header::parse(&req);
assert_eq!(
cache.ok(),
Some(CacheControl(vec![
CacheDirective::Extension("foo".to_owned(), None),
CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())),
]))
)
}
#[test]
fn test_parse_bad_syntax() {
let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish();
let cache: Result<CacheControl, _> = Header::parse(&req);
assert_eq!(cache.ok(), None)
}
}

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