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

Compare commits

...

582 Commits

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

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

View File

@ -4,13 +4,13 @@ environment:
matrix:
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: 1.20.0
CHANNEL: 1.21.0
- TARGET: i686-pc-windows-msvc
CHANNEL: 1.20.0
CHANNEL: 1.21.0
- TARGET: x86_64-pc-windows-gnu
CHANNEL: 1.20.0
CHANNEL: 1.21.0
- TARGET: x86_64-pc-windows-msvc
CHANNEL: 1.20.0
CHANNEL: 1.21.0
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: stable
@ -59,6 +59,4 @@ build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo build --no-default-features
- cargo clean
- cargo test --no-default-features

View File

@ -8,23 +8,22 @@ cache:
matrix:
include:
- rust: 1.20.0
- rust: 1.21.0
- rust: stable
- rust: beta
- rust: nightly
allow_failures:
- rust: nightly
- rust: beta
#rust:
# - 1.20.0
# - 1.21.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:
@ -46,20 +45,28 @@ script:
cargo clean
USE_SKEPTIC=1 cargo test --features=alpn
else
cargo test --features=alpn
cargo clean
cargo test -- --nocapture
# --features=alpn
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cd examples/basics && cargo check && cd ../..
cd examples/hello-world && cargo check && cd ../..
cd examples/http-proxy && cargo check && cd ../..
cd examples/multipart && cargo check && cd ../..
cd examples/json && cargo check && cd ../..
cd examples/juniper && cargo check && cd ../..
cd examples/protobuf && cargo check && cd ../..
cd examples/state && cargo check && cd ../..
cd examples/template_tera && cargo check && cd ../..
cd examples/diesel && cargo check && cd ../..
cd examples/r2d2 && cargo check && cd ../..
cd examples/tls && cargo check && cd ../..
cd examples/websocket-chat && cargo check && cd ../..
cd examples/websocket && cargo check && cd ../..
cd examples/unix-socket && cargo check && cd ../..
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
@ -69,10 +76,10 @@ script:
# 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" == "beta" ]]; then
cargo doc --features "alpn, tls, session" --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
cargo install mdbook &&
curl -sL https://github.com/rust-lang-nursery/mdBook/releases/download/v0.1.5/mdbook-v0.1.5-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $HOME/.cargo/bin &&
cd guide && mdbook build -d ../target/doc/guide && cd .. &&
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 &&
@ -80,7 +87,7 @@ after_success:
fi
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; 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)

View File

@ -1,5 +1,205 @@
# Changes
## 0.5.0
* Type-safe path/query/form parameter handling, using serde #70
* HttpResponse builder's methods `.body()`, `.finish()`, `.json()`
return `HttpResponse` instead of `Result`
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
* Added `HttpRequest::resource()`, returns current matched resource
* Added `ErrorHandlers` middleware
* Fix router cannot parse Non-ASCII characters in URL #137
* Fix client connection pooling
* Fix long client urls #129
* Fix panic on invalid URL characters #130
* Fix logger request duration calculation #152
* Fix prefix and static file serving #168
* Add `signed` and `private` `CookieSessionBackend`s
## 0.4.10 (2018-03-20)
* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods
* Allow to set client request timeout
* Allow to set client websocket handshake timeout
* Refactor `TestServer` configuration
* Fix server websockets big payloads support
* Fix http/2 date header generation
## 0.4.9 (2018-03-16)
* Allow to disable http/2 support
* Wake payload reading task when data is available
* Fix server keep-alive handling
* Send Query Parameters in client requests #120
* Move brotli encoding to a feature
* Add option of default handler for `StaticFiles` handler #57
* Add basic client connection pooling
## 0.4.8 (2018-03-12)
* 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
* Log request processing errors
* Always enable content encoding if encoding explicitly selected
* Allow multiple Applications on a single server with different state #49
* CORS middleware: allowed_headers is defaulting to None #50
## 0.3.1 (2018-01-13)
* Fix directory entry path #47

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,18 +1,20 @@
[package]
name = "actix-web"
version = "0.3.1"
version = "0.5.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web framework"
description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust."
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://github.com/actix/actix-web"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/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/static/*"]
"appveyor.yml", "/examples/**"]
build = "build.rs"
[badges]
@ -25,64 +27,73 @@ name = "actix_web"
path = "src/lib.rs"
[features]
default = []
default = ["session", "brotli"]
# tls
tls = ["native-tls", "tokio-tls"]
# openssl
alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
alpn = ["openssl", "tokio-openssl"]
# sessions
session = ["cookie/secure"]
# brotli encoding
brotli = ["brotli2"]
[dependencies]
log = "0.4"
failure = "0.1"
failure_derive = "0.1"
actix = "^0.5.5"
base64 = "0.9"
bitflags = "1.0"
failure = "0.1.1"
flate2 = "1.0"
h2 = "0.1"
http = "^0.1.2"
http = "^0.1.5"
httparse = "1.2"
http-range = "0.1"
time = "0.1"
mime = "0.3"
mime_guess = "1.8"
regex = "0.2"
sha1 = "0.4"
url = "1.6"
libc = "0.2"
log = "0.4"
mime = "0.3"
mime_guess = "2.0.0-alpha"
num_cpus = "1.0"
percent-encoding = "1.0"
rand = "0.4"
regex = "0.2"
serde = "1.0"
serde_json = "1.0"
brotli2 = "^0.3.2"
percent-encoding = "1.0"
serde_urlencoded = "0.5"
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"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true }
# io
mio = "0.6"
mio = "^0.6.13"
net2 = "0.2"
bytes = "0.4"
byteorder = "1"
futures = "0.1"
futures-cpupool = "0.1"
tokio-io = "0.1"
tokio-core = "0.1"
trust-dns-resolver = "0.8"
# native-tls
native-tls = { version="0.1", optional = true }
tokio-tls = { version="0.1", optional = true }
# openssl
tokio-openssl = { version="0.1", optional = true }
[dependencies.actix]
version = "^0.4.2"
[dependencies.openssl]
version = "0.9"
optional = true
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }
[dev-dependencies]
env_logger = "0.4"
reqwest = "0.8"
env_logger = "0.5"
skeptic = "0.13"
serde_derive = "1.0"
@ -93,19 +104,27 @@ version_check = "0.1"
[profile.release]
lto = true
opt-level = 3
# debug = true
codegen-units = 1
[workspace]
members = [
"./",
"examples/basics",
"examples/juniper",
"examples/diesel",
"examples/r2d2",
"examples/json",
"examples/protobuf",
"examples/hello-world",
"examples/http-proxy",
"examples/multipart",
"examples/state",
"examples/redis-session",
"examples/template_tera",
"examples/tls",
"examples/websocket",
"examples/websocket-chat",
"examples/web-cors/backend",
"examples/unix-socket",
"tools/wsload/",
]

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

View File

@ -1,66 +1,75 @@
# 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
extern crate actix_web;
use actix_web::*;
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.github.io/actix-web/guide/qs_13.html) protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.github.io/actix-web/guide/qs_9.html) support
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable [request routing](https://actix.github.io/actix-web/guide/qs_5.html)
* Graceful server shutdown
* Multipart streams
* Static assets
* SSL support with OpenSSL or `native-tls`
* 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),
[Redis sessions](https://github.com/actix/actix-redis),
[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),
[CSRF](https://actix.github.io/actix-web/actix_web/middleware/csrf/index.html))
* Built on top of [Actix actor framework](https://github.com/actix/actix)
fn index(req: HttpRequest) -> String {
format!("Hello {}!", &req.match_info()["name"])
}
fn main() {
HttpServer::new(
|| Application::new()
.resource("/{name}", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap()
.run();
}
```
## Documentation
## Documentation & community resources
* [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
* Minimum supported Rust version: 1.21 or later
## Features
## Example
* 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).
```rust
extern crate actix_web;
use actix_web::{http, server, App, Path};
## Benchmarks
fn index(info: Path<(u32, String)>) -> String {
format!("Hello {}! id:{}", info.0, info.1)
}
Some basic benchmarks could be found in this [respository](https://github.com/fafhrd91/benchmarks).
fn main() {
server::new(
|| App::new()
.route("/{id}/{name}/index.html", http::Method::GET, index))
.bind("127.0.0.1:8080").unwrap()
.run();
}
```
## Examples
### More 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/)
* [Protobuf support](https://github.com/actix/actix-web/tree/master/examples/protobuf/)
* [Multipart 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/)
You may consider checking out
[this directory](https://github.com/actix/actix-web/tree/master/examples) for more examples.
## Benchmarks
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext)
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).
## License
This project is licensed under either of
@ -69,3 +78,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

@ -6,12 +6,13 @@ use std::{env, fs};
#[cfg(unix)]
fn main() {
println!("cargo:rerun-if-env-changed=USE_SKEPTIC");
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",
&[// "README.md",
"guide/src/qs_1.md",
"guide/src/qs_2.md",
"guide/src/qs_3.md",
@ -25,6 +26,7 @@ fn main() {
"guide/src/qs_10.md",
"guide/src/qs_12.md",
"guide/src/qs_13.md",
"guide/src/qs_14.md",
]);
} else {
let _ = fs::File::create(f);

View File

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

View File

@ -7,8 +7,11 @@ extern crate env_logger;
extern crate futures;
use futures::Stream;
use actix_web::*;
use actix_web::middleware::RequestSession;
use std::{io, env};
use actix_web::{error, fs, pred, server,
App, HttpRequest, HttpResponse, Result, Error};
use actix_web::http::{Method, StatusCode};
use actix_web::middleware::{self, RequestSession};
use futures::future::{FutureResult, result};
/// favicon handler
@ -21,7 +24,7 @@ fn index(mut req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req);
// example of ...
if let Ok(ch) = req.payload_mut().readany().poll() {
if let Ok(ch) = req.poll() {
if let futures::Async::Ready(Some(d)) = ch {
println!("{}", String::from_utf8_lossy(d.as_ref()));
}
@ -48,7 +51,7 @@ fn index(mut req: HttpRequest) -> Result<HttpResponse> {
// response
Ok(HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8")
.body(&html).unwrap())
.body(&html))
}
@ -56,17 +59,17 @@ fn index(mut req: HttpRequest) -> Result<HttpResponse> {
fn p404(req: HttpRequest) -> Result<HttpResponse> {
// 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>
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>"#);
</html>"#;
// response
Ok(HttpResponse::build(StatusCode::NOT_FOUND)
.content_type("text/html; charset=utf-8")
.body(&html).unwrap())
.body(html))
}
@ -75,36 +78,34 @@ 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()))
result(Ok(HttpResponse::Ok()
.content_type("text/html")
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))))
}
/// handler with path parameters like `/user/{name}/`
fn with_param(req: HttpRequest) -> Result<HttpResponse>
fn with_param(req: HttpRequest) -> HttpResponse
{
println!("{:?}", req);
Ok(HttpResponse::Ok()
.content_type("test/plain")
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))?)
HttpResponse::Ok()
.content_type("test/plain")
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
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()
let addr = server::new(
|| App::new()
// enable logger
.middleware(middleware::Logger::default())
// cookie session middleware
.middleware(middleware::SessionStorage::new(
middleware::CookieSessionBackend::build(&[0; 32])
.secure(false)
.finish()
middleware::CookieSessionBackend::signed(&[0; 32]).secure(false)
))
// register favicon
.resource("/favicon.ico", |r| r.f(favicon))
@ -116,13 +117,17 @@ fn main() {
.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,
Method::GET => HttpResponse::Ok(),
Method::POST => HttpResponse::MethodNotAllowed(),
_ => HttpResponse::NotFound(),
}
}))
.resource("/error.html", |r| r.f(|req| {
error::InternalError::new(
io::Error::new(io::ErrorKind::Other, "test"), StatusCode::OK)
}))
// static files
.handler("/static/", fs::StaticFiles::new("../static/", true))
.handler("/static/", fs::StaticFiles::new("../static/"))
// redirect
.resource("/", |r| r.method(Method::GET).f(|req| {
println!("{:?}", req);
@ -134,7 +139,8 @@ fn main() {
// default
.default_resource(|r| {
r.method(Method::GET).f(p404);
r.route().p(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed);
r.route().filter(pred::Not(pred::Get())).f(
|req| HttpResponse::MethodNotAllowed());
}))
.bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080")

View File

@ -5,9 +5,9 @@ authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
env_logger = "0.4"
actix = "0.4"
actix-web = { git = "https://github.com/actix/actix-web" }
env_logger = "0.5"
actix = "0.5"
actix-web = { path = "../../" }
futures = "0.1"
uuid = { version = "0.5", features = ["serde", "v4"] }
@ -15,5 +15,6 @@ serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
diesel = { version = "1.0.0-beta1", features = ["sqlite"] }
diesel = { version = "^1.1.0", features = ["sqlite", "r2d2"] }
r2d2 = "0.8"
dotenv = "0.10"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,58 @@
extern crate actix;
extern crate actix_web;
extern crate futures;
extern crate env_logger;
use futures::{Future, Stream};
use actix_web::{
client, server, middleware,
App, AsyncResponder, Body, HttpRequest, HttpResponse, HttpMessage, Error};
/// Stream client request response and then send body to a server response
fn index(_req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
client::ClientRequest::get("https://www.rust-lang.org/en-US/")
.finish().unwrap()
.send()
.map_err(Error::from) // <- convert SendRequestError to an Error
.and_then(
|resp| resp.body() // <- this is MessageBody type, resolves to complete body
.from_err() // <- convert PayloadError to a Error
.and_then(|body| { // <- we got complete body, now send as server response
Ok(HttpResponse::Ok().body(body))
}))
.responder()
}
/// streaming client request to a streaming server response
fn streaming(_req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// send client request
client::ClientRequest::get("https://www.rust-lang.org/en-US/")
.finish().unwrap()
.send() // <- connect to host and send request
.map_err(Error::from) // <- convert SendRequestError to an Error
.and_then(|resp| { // <- we received client response
Ok(HttpResponse::Ok()
// read one chunk from client response and send this chunk to a server response
// .from_err() converts PayloadError to a Error
.body(Body::Streaming(Box::new(resp.from_err()))))
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("http-proxy");
server::new(
|| App::new()
.middleware(middleware::Logger::default())
.resource("/streaming", |r| r.f(streaming))
.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

@ -14,5 +14,5 @@ serde_json = "1.0"
serde_derive = "1.0"
json = "*"
actix = "0.4"
actix-web = { git = "https://github.com/actix/actix-web" }
actix = "0.5"
actix-web = { path="../../" }

View File

@ -7,7 +7,9 @@ extern crate serde_json;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate json;
use actix_web::*;
use actix_web::{
middleware, http, error, server,
App, AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error, Json};
use bytes::BytesMut;
use futures::{Future, Stream};
@ -19,24 +21,29 @@ struct MyObj {
number: i32,
}
/// This handler uses `HttpRequest::json()` for loading serde json object.
/// This handler uses `HttpRequest::json()` for loading json object.
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.json()
.from_err() // convert all errors into `Error`
.and_then(|val: MyObj| {
println!("model: {:?}", val);
Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response
Ok(HttpResponse::Ok().json(val)) // <- send response
})
.responder()
}
/// This handler uses json extractor
fn extract_item(item: Json<MyObj>) -> HttpResponse {
println!("model: {:?}", &item);
HttpResponse::Ok().json(item.0) // <- send response
}
const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// 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()
/// This handler manually load request payload and parse json object
fn index_manual(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// HttpRequest is stream of Bytes objects
req
// `Future::from_err` acts like `?` in that it coerces the error type from
// the future into the final error type
.from_err()
@ -57,39 +64,43 @@ fn index_manual(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Err
.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
Ok(HttpResponse::Ok().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()
fn index_mjsonrust(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.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)
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(injson.dump()).unwrap())
.body(injson.dump()))
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
env_logger::init();
let sys = actix::System::new("json-example");
let addr = HttpServer::new(|| {
Application::new()
server::new(|| {
App::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))})
.resource("/extractor", |r| {
r.method(http::Method::POST)
.with(extract_item)
.limit(4096); // <- limit size of the payload
})
.resource("/manual", |r| r.method(http::Method::POST).f(index_manual))
.resource("/mjsonrust", |r| r.method(http::Method::POST).f(index_mjsonrust))
.resource("/", |r| r.method(http::Method::POST).f(index))})
.bind("127.0.0.1:8080").unwrap()
.shutdown_timeout(1)
.start();

View File

@ -0,0 +1,17 @@
[package]
name = "juniper-example"
version = "0.1.0"
authors = ["pyros2097 <pyros2097@gmail.com>"]
workspace = "../.."
[dependencies]
env_logger = "0.5"
actix = "0.5"
actix-web = { path = "../../" }
futures = "0.1"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
juniper = "0.9.2"

View File

@ -0,0 +1,15 @@
# juniper
Juniper integration for Actix web
### server
```bash
cd actix-web/examples/juniper
cargo run (or ``cargo watch -x run``)
# Started http server: 127.0.0.1:8080
```
### web client
[http://127.0.0.1:8080/graphiql](http://127.0.0.1:8080/graphiql)

View File

@ -0,0 +1,108 @@
//! Actix web juniper example
//!
//! A simple example integrating juniper in actix-web
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate juniper;
extern crate futures;
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix::prelude::*;
use actix_web::{
middleware, http, server,
App, AsyncResponder, HttpRequest, HttpResponse, FutureResponse, Error, State, Json};
use juniper::http::graphiql::graphiql_source;
use juniper::http::GraphQLRequest;
use futures::future::Future;
mod schema;
use schema::Schema;
use schema::create_schema;
struct AppState {
executor: Addr<Syn, GraphQLExecutor>,
}
#[derive(Serialize, Deserialize)]
pub struct GraphQLData(GraphQLRequest);
impl Message for GraphQLData {
type Result = Result<String, Error>;
}
pub struct GraphQLExecutor {
schema: std::sync::Arc<Schema>
}
impl GraphQLExecutor {
fn new(schema: std::sync::Arc<Schema>) -> GraphQLExecutor {
GraphQLExecutor {
schema: schema,
}
}
}
impl Actor for GraphQLExecutor {
type Context = SyncContext<Self>;
}
impl Handler<GraphQLData> for GraphQLExecutor {
type Result = Result<String, Error>;
fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result {
let res = msg.0.execute(&self.schema, &());
let res_text = serde_json::to_string(&res)?;
Ok(res_text)
}
}
fn graphiql(_req: HttpRequest<AppState>) -> Result<HttpResponse, Error> {
let html = graphiql_source("http://127.0.0.1:8080/graphql");
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(html))
}
fn graphql(st: State<AppState>, data: Json<GraphQLData>) -> FutureResponse<HttpResponse> {
st.executor.send(data.0)
.from_err()
.and_then(|res| {
match res {
Ok(user) => Ok(HttpResponse::Ok()
.content_type("application/json")
.body(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into())
}
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("juniper-example");
let schema = std::sync::Arc::new(create_schema());
let addr = SyncArbiter::start(3, move || {
GraphQLExecutor::new(schema.clone())
});
// Start http server
server::new(move || {
App::with_state(AppState{executor: addr.clone()})
// enable logger
.middleware(middleware::Logger::default())
.resource("/graphql", |r| r.method(http::Method::POST).with2(graphql))
.resource("/graphiql", |r| r.method(http::Method::GET).h(graphiql))})
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -0,0 +1,58 @@
use juniper::FieldResult;
use juniper::RootNode;
#[derive(GraphQLEnum)]
enum Episode {
NewHope,
Empire,
Jedi,
}
#[derive(GraphQLObject)]
#[graphql(description = "A humanoid creature in the Star Wars universe")]
struct Human {
id: String,
name: String,
appears_in: Vec<Episode>,
home_planet: String,
}
#[derive(GraphQLInputObject)]
#[graphql(description = "A humanoid creature in the Star Wars universe")]
struct NewHuman {
name: String,
appears_in: Vec<Episode>,
home_planet: String,
}
pub struct QueryRoot;
graphql_object!(QueryRoot: () |&self| {
field human(&executor, id: String) -> FieldResult<Human> {
Ok(Human{
id: "1234".to_owned(),
name: "Luke".to_owned(),
appears_in: vec![Episode::NewHope],
home_planet: "Mars".to_owned(),
})
}
});
pub struct MutationRoot;
graphql_object!(MutationRoot: () |&self| {
field createHuman(&executor, new_human: NewHuman) -> FieldResult<Human> {
Ok(Human{
id: "1234".to_owned(),
name: new_human.name,
appears_in: new_human.appears_in,
home_planet: new_human.home_planet,
})
}
});
pub type Schema = RootNode<'static, QueryRoot, MutationRoot>;
pub fn create_schema() -> Schema {
Schema::new(QueryRoot {}, MutationRoot {})
}

View File

@ -11,5 +11,5 @@ path = "src/main.rs"
[dependencies]
env_logger = "*"
futures = "0.1"
actix = "0.4"
actix-web = { git = "https://github.com/actix/actix-web" }
actix = "0.5"
actix-web = { path="../../" }

View File

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

View File

@ -0,0 +1,16 @@
[package]
name = "protobuf-example"
version = "0.1.0"
authors = ["kingxsp <jin_hb_zh@126.com>"]
[dependencies]
bytes = "0.4"
futures = "0.1"
failure = "0.1"
env_logger = "*"
prost = "0.2.0"
prost-derive = "0.2.0"
actix = "0.5"
actix-web = { path="../../" }

View File

@ -0,0 +1,66 @@
# just start server and run client.py
# wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-python-3.5.1.zip
# unzip protobuf-python-3.5.1.zip.1
# cd protobuf-3.5.1/python/
# python3.6 setup.py install
# pip3.6 install --upgrade pip
# pip3.6 install aiohttp
#!/usr/bin/env python
import test_pb2
import traceback
import sys
import asyncio
import aiohttp
def op():
try:
obj = test_pb2.MyObj()
obj.number = 9
obj.name = 'USB'
#Serialize
sendDataStr = obj.SerializeToString()
#print serialized string value
print('serialized string:', sendDataStr)
#------------------------#
# message transmission #
#------------------------#
receiveDataStr = sendDataStr
receiveData = test_pb2.MyObj()
#Deserialize
receiveData.ParseFromString(receiveDataStr)
print('pares serialize string, return: devId = ', receiveData.number, ', name = ', receiveData.name)
except(Exception, e):
print(Exception, ':', e)
print(traceback.print_exc())
errInfo = sys.exc_info()
print(errInfo[0], ':', errInfo[1])
async def fetch(session):
obj = test_pb2.MyObj()
obj.number = 9
obj.name = 'USB'
async with session.post('http://localhost:8080/', data=obj.SerializeToString(),
headers={"content-type": "application/protobuf"}) as resp:
print(resp.status)
data = await resp.read()
receiveObj = test_pb2.MyObj()
receiveObj.ParseFromString(data)
print(receiveObj)
async def go(loop):
obj = test_pb2.MyObj()
obj.number = 9
obj.name = 'USB'
async with aiohttp.ClientSession(loop=loop) as session:
await fetch(session)
loop = asyncio.get_event_loop()
loop.run_until_complete(go(loop))
loop.close()

View File

@ -0,0 +1,57 @@
extern crate actix;
extern crate actix_web;
extern crate bytes;
extern crate futures;
#[macro_use]
extern crate failure;
extern crate env_logger;
extern crate prost;
#[macro_use]
extern crate prost_derive;
use futures::Future;
use actix_web::{
http, middleware, server,
App, AsyncResponder, HttpRequest, HttpResponse, Error};
mod protobuf;
use protobuf::ProtoBufResponseBuilder;
#[derive(Clone, Debug, PartialEq, Message)]
pub struct MyObj {
#[prost(int32, tag="1")]
pub number: i32,
#[prost(string, tag="2")]
pub name: String,
}
/// This handler uses `ProtoBufMessage` for loading protobuf object.
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
protobuf::ProtoBufMessage::new(req)
.from_err() // convert all errors into `Error`
.and_then(|val: MyObj| {
println!("model: {:?}", val);
Ok(HttpResponse::Ok().protobuf(val)?) // <- send response
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("protobuf-example");
server::new(|| {
App::new()
.middleware(middleware::Logger::default())
.resource("/", |r| r.method(http::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

@ -0,0 +1,168 @@
use bytes::{Bytes, BytesMut};
use futures::{Poll, Future, Stream};
use bytes::IntoBuf;
use prost::Message;
use prost::DecodeError as ProtoBufDecodeError;
use prost::EncodeError as ProtoBufEncodeError;
use actix_web::http::header::{CONTENT_TYPE, CONTENT_LENGTH};
use actix_web::{Responder, HttpMessage, HttpRequest, HttpResponse};
use actix_web::dev::HttpResponseBuilder;
use actix_web::error::{Error, PayloadError, ResponseError};
#[derive(Fail, Debug)]
pub enum ProtoBufPayloadError {
/// Payload size is bigger than 256k
#[fail(display="Payload size is bigger than 256k")]
Overflow,
/// Content type error
#[fail(display="Content type error")]
ContentType,
/// Serialize error
#[fail(display="ProtoBud serialize error: {}", _0)]
Serialize(#[cause] ProtoBufEncodeError),
/// Deserialize error
#[fail(display="ProtoBud deserialize error: {}", _0)]
Deserialize(#[cause] ProtoBufDecodeError),
/// Payload error
#[fail(display="Error that occur during reading payload: {}", _0)]
Payload(#[cause] PayloadError),
}
impl ResponseError for ProtoBufPayloadError {
fn error_response(&self) -> HttpResponse {
match *self {
ProtoBufPayloadError::Overflow => HttpResponse::PayloadTooLarge().into(),
_ => HttpResponse::BadRequest().into(),
}
}
}
impl From<PayloadError> for ProtoBufPayloadError {
fn from(err: PayloadError) -> ProtoBufPayloadError {
ProtoBufPayloadError::Payload(err)
}
}
impl From<ProtoBufDecodeError> for ProtoBufPayloadError {
fn from(err: ProtoBufDecodeError) -> ProtoBufPayloadError {
ProtoBufPayloadError::Deserialize(err)
}
}
#[derive(Debug)]
pub struct ProtoBuf<T: Message>(pub T);
impl<T: Message> Responder for ProtoBuf<T> {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
let mut buf = Vec::new();
self.0.encode(&mut buf)
.map_err(|e| Error::from(ProtoBufPayloadError::Serialize(e)))
.and_then(|()| {
Ok(HttpResponse::Ok()
.content_type("application/protobuf")
.body(buf)
.into())
})
}
}
pub struct ProtoBufMessage<T, U: Message + Default>{
limit: usize,
ct: &'static str,
req: Option<T>,
fut: Option<Box<Future<Item=U, Error=ProtoBufPayloadError>>>,
}
impl<T, U: Message + Default> ProtoBufMessage<T, U> {
/// Create `ProtoBufMessage` for request.
pub fn new(req: T) -> Self {
ProtoBufMessage{
limit: 262_144,
req: Some(req),
fut: None,
ct: "application/protobuf",
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
/// Set allowed content type.
///
/// By default *application/protobuf* content type is used. Set content type
/// to empty string if you want to disable content type check.
pub fn content_type(mut self, ct: &'static str) -> Self {
self.ct = ct;
self
}
}
impl<T, U: Message + Default + 'static> Future for ProtoBufMessage<T, U>
where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static
{
type Item = U;
type Error = ProtoBufPayloadError;
fn poll(&mut self) -> Poll<U, ProtoBufPayloadError> {
if let Some(req) = self.req.take() {
if let Some(len) = req.headers().get(CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<usize>() {
if len > self.limit {
return Err(ProtoBufPayloadError::Overflow);
}
} else {
return Err(ProtoBufPayloadError::Overflow);
}
}
}
// check content-type
if !self.ct.is_empty() && req.content_type() != self.ct {
return Err(ProtoBufPayloadError::ContentType)
}
let limit = self.limit;
let fut = req.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(ProtoBufPayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(|body| Ok(<U>::decode(&mut body.into_buf())?));
self.fut = Some(Box::new(fut));
}
self.fut.as_mut().expect("ProtoBufBody could not be used second time").poll()
}
}
pub trait ProtoBufResponseBuilder {
fn protobuf<T: Message>(&mut self, value: T) -> Result<HttpResponse, Error>;
}
impl ProtoBufResponseBuilder for HttpResponseBuilder {
fn protobuf<T: Message>(&mut self, value: T) -> Result<HttpResponse, Error> {
self.header(CONTENT_TYPE, "application/protobuf");
let mut body = Vec::new();
value.encode(&mut body).map_err(|e| ProtoBufPayloadError::Serialize(e))?;
Ok(self.body(body))
}
}

View File

@ -0,0 +1,6 @@
syntax = "proto3";
message MyObj {
int32 number = 1;
string name = 2;
}

View File

@ -0,0 +1,76 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='test.proto',
package='',
syntax='proto3',
serialized_pb=_b('\n\ntest.proto\"%\n\x05MyObj\x12\x0e\n\x06number\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\tb\x06proto3')
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_MYOBJ = _descriptor.Descriptor(
name='MyObj',
full_name='MyObj',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='number', full_name='MyObj.number', index=0,
number=1, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='name', full_name='MyObj.name', index=1,
number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=14,
serialized_end=51,
)
DESCRIPTOR.message_types_by_name['MyObj'] = _MYOBJ
MyObj = _reflection.GeneratedProtocolMessageType('MyObj', (_message.Message,), dict(
DESCRIPTOR = _MYOBJ,
__module__ = 'test_pb2'
# @@protoc_insertion_point(class_scope:MyObj)
))
_sym_db.RegisterMessage(MyObj)
# @@protoc_insertion_point(module_scope)

20
examples/r2d2/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "r2d2-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
env_logger = "0.5"
actix = "0.5"
actix-web = { path = "../../" }
futures = "0.1"
uuid = { version = "0.5", features = ["serde", "v4"] }
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
r2d2 = "*"
r2d2_sqlite = "*"
rusqlite = "*"

41
examples/r2d2/src/db.rs Normal file
View File

@ -0,0 +1,41 @@
//! Db executor actor
use std::io;
use uuid;
use actix_web::*;
use actix::prelude::*;
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
/// This is db executor actor. We are going to run 3 of them in parallel.
pub struct DbExecutor(pub Pool<SqliteConnectionManager>);
/// 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 Message for CreateUser {
type Result = Result<String, io::Error>;
}
impl Actor for DbExecutor {
type Context = SyncContext<Self>;
}
impl Handler<CreateUser> for DbExecutor {
type Result = Result<String, io::Error>;
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
let conn = self.0.get().unwrap();
let uuid = format!("{}", uuid::Uuid::new_v4());
conn.execute("INSERT INTO users (id, name) VALUES ($1, $2)",
&[&uuid, &msg.name]).unwrap();
Ok(conn.query_row("SELECT name FROM users WHERE id=$1", &[&uuid], |row| {
row.get(0)
}).map_err(|_| io::Error::new(io::ErrorKind::Other, "db error"))?)
}
}

65
examples/r2d2/src/main.rs Normal file
View File

@ -0,0 +1,65 @@
//! Actix web r2d2 example
extern crate serde;
extern crate serde_json;
extern crate uuid;
extern crate futures;
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate r2d2;
extern crate r2d2_sqlite;
extern crate rusqlite;
use actix::prelude::*;
use actix_web::{
middleware, http, server, App, AsyncResponder, HttpRequest, HttpResponse, Error};
use futures::future::Future;
use r2d2_sqlite::SqliteConnectionManager;
mod db;
use db::{CreateUser, DbExecutor};
/// State with DbExecutor address
struct State {
db: Addr<Syn, DbExecutor>,
}
/// Async request handler
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
let name = &req.match_info()["name"];
req.state().db.send(CreateUser{name: name.to_owned()})
.from_err()
.and_then(|res| {
match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into())
}
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init();
let sys = actix::System::new("r2d2-example");
// r2d2 pool
let manager = SqliteConnectionManager::file("test.db");
let pool = r2d2::Pool::new(manager).unwrap();
// Start db executor actors
let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone()));
// Start http server
server::new(move || {
App::with_state(State{db: addr.clone()})
// enable logger
.middleware(middleware::Logger::default())
.resource("/{name}", |r| r.method(http::Method::GET).a(index))})
.bind("127.0.0.1:8080").unwrap()
.start();
let _ = sys.run();
}

BIN
examples/r2d2/test.db Normal file

Binary file not shown.

View File

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

View File

@ -0,0 +1,48 @@
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate actix_redis;
extern crate env_logger;
use actix_web::*;
use actix_web::middleware::RequestSession;
use actix_redis::RedisSessionBackend;
/// simple handler
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req);
// session
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!".into())
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
env_logger::init();
let sys = actix::System::new("basic-example");
HttpServer::new(
|| Application::new()
// enable logger
.middleware(middleware::Logger::default())
// cookie session middleware
.middleware(middleware::SessionStorage::new(
RedisSessionBackend::new("127.0.0.1:6379", &[0; 32])
))
// register simple route, handle all methods
.resource("/", |r| r.f(index)))
.bind("0.0.0.0:8080").unwrap()
.threads(1)
.start();
let _ = sys.run();
}

View File

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

View File

@ -1,5 +1,5 @@
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
//! There are two level of statfulness in actix-web. Application has state
//! 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.
@ -9,21 +9,21 @@ extern crate env_logger;
use std::cell::Cell;
use actix::*;
use actix_web::*;
use actix::prelude::*;
use actix_web::{
http, server, ws, middleware, App, HttpRequest, HttpResponse};
/// Application state
struct AppState {
counter: Cell<usize>,
}
/// somple handle
/// simple 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()))
HttpResponse::Ok().body(format!("Num of requests: {}", req.state().counter.get()))
}
/// `MyWebSocket` counts how many messages it receives from peer,
@ -33,20 +33,19 @@ struct MyWebSocket {
}
impl Actor for MyWebSocket {
type Context = HttpContext<Self, AppState>;
type Context = ws::WebsocketContext<Self, AppState>;
}
impl Handler<ws::Message> for MyWebSocket {
type Result = ();
impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
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) => ws::WsWriter::pong(ctx, &msg),
ws::Message::Text(text) => ws::WsWriter::text(ctx, &text),
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
ws::Message::Closed | ws::Message::Error => {
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Close(_) => {
ctx.stop();
}
_ => (),
@ -56,17 +55,18 @@ impl Handler<ws::Message> for MyWebSocket {
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
env_logger::init();
let sys = actix::System::new("ws-example");
let addr = HttpServer::new(
|| Application::with_state(AppState{counter: Cell::new(0)})
server::new(
|| App::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})))
r.method(http::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()

View File

@ -5,7 +5,7 @@ authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[dependencies]
env_logger = "0.4"
actix = "0.4"
actix-web = { git = "https://github.com/actix/actix-web" }
env_logger = "0.5"
actix = "0.5"
actix-web = { path = "../../" }
tera = "*"

View File

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

View File

@ -9,6 +9,7 @@ name = "server"
path = "src/main.rs"
[dependencies]
env_logger = "0.4"
actix = "^0.4.2"
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] }
env_logger = "0.5"
actix = "0.5"
actix-web = { path = "../../", features=["alpn"] }
openssl = { version="0.10" }

31
examples/tls/cert.pem Normal file
View File

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww
CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx
NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY
MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1
sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U
NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy
voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr
odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND
xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA
CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI
yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U
UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO
vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un
CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN
BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk
3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI
JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD
JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL
d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu
ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC
CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur
y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7
YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh
g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt
tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y
1QU=
-----END CERTIFICATE-----

Binary file not shown.

51
examples/tls/key.pem Normal file
View File

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP
n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M
IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5
4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ
WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk
oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli
JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6
/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD
YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP
wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA
69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA
AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/
9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm
YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR
6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM
ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI
7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab
L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+
vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ
b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz
0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL
OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI
6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC
71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g
9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu
bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb
IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga
/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc
KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2
iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP
tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD
jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY
l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj
gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh
Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q
1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW
t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI
fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9
5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt
+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc
3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf
cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T
qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU
DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K
5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc
fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc
Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ
4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6
I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c=
-----END RSA PRIVATE KEY-----

View File

@ -2,49 +2,47 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate openssl;
use std::fs::File;
use std::io::Read;
use actix_web::*;
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
use actix_web::{
http, middleware, server, App, HttpRequest, HttpResponse, Error};
/// somple handle
fn index(req: HttpRequest) -> Result<HttpResponse> {
/// simple handle
fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
println!("{:?}", req);
Ok(httpcodes::HTTPOk
.build()
Ok(HttpResponse::Ok()
.content_type("text/plain")
.body("Welcome!")?)
.body("Welcome!"))
}
fn main() {
if ::std::env::var("RUST_LOG").is_err() {
::std::env::set_var("RUST_LOG", "actix_web=trace");
::std::env::set_var("RUST_LOG", "actix_web=info");
}
let _ = env_logger::init();
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();
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
builder.set_certificate_chain_file("cert.pem").unwrap();
let addr = HttpServer::new(
|| Application::new()
server::new(
|| App::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()
.resource("/", |r| r.method(http::Method::GET).f(|req| {
HttpResponse::Found()
.header("LOCATION", "/index.html")
.body(Body::Empty)
.finish()
})))
.bind("127.0.0.1:8443").unwrap()
.start_ssl(&pkcs12).unwrap();
.start_ssl(builder).unwrap();
println!("Started http server: 127.0.0.1:8443");
let _ = sys.run();

View File

@ -0,0 +1,10 @@
[package]
name = "unix-socket"
version = "0.1.0"
authors = ["Messense Lv <messense@icloud.com>"]
[dependencies]
env_logger = "0.5"
actix = "0.5"
actix-web = { path = "../../" }
tokio-uds = "0.1"

View File

@ -0,0 +1,14 @@
## Unix domain socket example
```bash
$ curl --unix-socket /tmp/actix-uds.socket http://localhost/
Hello world!
```
Although this will only one thread for handling incoming connections
according to the
[documentation](https://actix.github.io/actix-web/actix_web/struct.HttpServer.html#method.start_incoming).
And it does not delete the socket file (`/tmp/actix-uds.socket`) when stopping
the server so it will fail to start next time you run it unless you delete
the socket file manually.

View File

@ -0,0 +1,32 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate tokio_uds;
use actix::*;
use actix_web::{middleware, server, App, HttpRequest};
use tokio_uds::UnixListener;
fn index(_req: HttpRequest) -> &'static str {
"Hello world!"
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("unix-socket");
let listener = UnixListener::bind(
"/tmp/actix-uds.socket", Arbiter::handle()).expect("bind failed");
server::new(
|| App::new()
// enable logger
.middleware(middleware::Logger::default())
.resource("/index.html", |r| r.f(|_| "Hello world!"))
.resource("/", |r| r.f(index)))
.start_incoming(listener.incoming(), false);
println!("Started http server: /tmp/actix-uds.socket");
let _ = sys.run();
}

View File

@ -0,0 +1,15 @@
# Actix Web CORS example
## start
1 - backend server
```bash
$ cd web-cors/backend
$ cargo run
```
2 - frontend server
```bash
$ cd web-cors/frontend
$ npm install
$ npm run dev
```
then open browser 'http://localhost:1234/'

4
examples/web-cors/backend/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target/
**/*.rs.bk
Cargo.lock

View File

@ -0,0 +1,17 @@
[package]
name = "actix-web-cors"
version = "0.1.0"
authors = ["krircc <krircc@aliyun.com>"]
workspace = "../../../"
[dependencies]
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
http = "0.1"
actix = "0.5"
actix-web = { path = "../../../" }
dotenv = "0.10"
env_logger = "0.5"
futures = "0.1"

View File

@ -0,0 +1,43 @@
#[macro_use] extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate futures;
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use std::env;
use actix_web::{http, middleware, server, App};
mod user;
use user::info;
fn main() {
env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("Actix-web-CORS");
server::new(
|| App::new()
.middleware(middleware::Logger::default())
.resource("/user/info", |r| {
middleware::cors::Cors::build()
.allowed_origin("http://localhost:1234")
.allowed_methods(vec!["GET", "POST"])
.allowed_headers(
vec![http::header::AUTHORIZATION,
http::header::ACCEPT,
http::header::CONTENT_TYPE])
.max_age(3600)
.finish().expect("Can not create CORS middleware")
.register(r);
r.method(http::Method::POST).a(info);
}))
.bind("127.0.0.1:8000").unwrap()
.shutdown_timeout(200)
.start();
let _ = sys.run();
}

View File

@ -0,0 +1,19 @@
use actix_web::{AsyncResponder, Error, HttpMessage, HttpResponse, HttpRequest};
use futures::Future;
#[derive(Deserialize,Serialize, Debug)]
struct Info {
username: String,
email: String,
password: String,
confirm_password: String,
}
pub fn info(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.json()
.from_err()
.and_then(|res: Info| {
Ok(HttpResponse::Ok().json(res))
}).responder()
}

View File

@ -0,0 +1,3 @@
{
"presets": ["env"]
}

14
examples/web-cors/frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
.DS_Store
node_modules/
/dist/
.cache
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>webapp</title>
</head>
<body>
<div id="app"></div>
<script src="./src/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,22 @@
{
"name": "actix-web-cors",
"version": "0.1.0",
"description": "webapp",
"main": "main.js",
"scripts": {
"dev": "rm -rf dist/ && NODE_ENV=development parcel index.html",
"build": "NODE_ENV=production parcel build index.html",
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "ISC",
"dependencies": {
"vue": "^2.5.13",
"vue-router": "^3.0.1",
"axios": "^0.17.1"
},
"devDependencies": {
"babel-preset-env": "^1.6.1",
"parcel-bundler": "^1.4.1",
"parcel-plugin-vue": "^1.5.0"
}
}

View File

@ -0,0 +1,145 @@
<template>
<div id="app">
<div id="content">
<div id="title">
<a to="#">SignUp</a>
</div>
<input type="text" name="username" placeholder="Username" v-model="Username" required />
<input type="text" name="email" placeholder="E-mail" v-model="Email" required />
<input type="password" name="password" placeholder="Password" v-model="Password" required/>
<input type="password" name="confirm_password" placeholder="Confirm password" v-model="ConfirmPassword" required/><br/>
<button id="submit" @click="signup">Sign up</button>
<div id="user-info">
<p>Click Above 'Sign up' Button <br> Then Get Your Signup Info!</p>
<p>email : {{ email }}</p>
<p>username {{ username }}</p>
<p>password : {{ password }}</p>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'app',
data () {
return {
Username: '',
Email: '',
Password: '',
ConfirmPassword: '',
email: '',
username: '',
password: ''
}
},
methods: {
signup () {
var username = this.Username
var email = this.Email
var password = this.Password
var confirm_password = this.ConfirmPassword
console.log(email)
axios.post('http://localhost:8000/user/info', {
username: username,
email: email,
password: password,
confirm_password: confirm_password
})
.then(response => {
console.log(response.data)
this.email = response.data.email
this.username = response.data.username
this.password = response.data.password
})
.catch(e => {
console.log(e)
})
}
}
}
</script>
<style scoped>
#content {
width: 250px;
margin: 0 auto;
padding-top: 33px;
}
#title {
padding: 0.5rem 0;
font-size: 22px;
font-weight: bold;
background-color:bisque;
text-align: center;
}
input[type="text"],
input[type="password"] {
margin: 6px auto auto;
width: 250px;
height: 36px;
border: none;
border-bottom: 1px solid #AAA;
font-size: 16px;
}
#submit {
margin: 10px 0 20px 0;
width: 250px;
height: 33px;
background-color:bisque;
border: none;
border-radius: 2px;
font-family: 'Roboto', sans-serif;
font-weight: bold;
text-transform: uppercase;
transition: 0.1s ease;
cursor: pointer;
}
input[type="checkbox"] {
margin-top: 11px;
}
dialog {
top: 50%;
width: 80%;
border: 5px solid rgba(0, 0, 0, 0.3);
}
dialog::backdrop{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
}
#closeDialog {
display: inline-block;
border-radius: 3px;
border: none;
font-size: 1rem;
padding: 0.4rem 0.8em;
background: #eb9816;
border-bottom: 1px solid #f1b75c;
color: white;
font-weight: bold;
text-align: center;
}
#closeDialog:hover, #closeDialog:focus {
opacity: 0.92;
cursor: pointer;
}
#user-info {
width: 250px;
margin: 0 auto;
padding-top: 44px;
}
@media only screen and (min-width: 600px) {
#content {
margin: 0 auto;
padding-top: 100px;
}
}
</style>

View File

@ -0,0 +1,11 @@
import Vue from 'vue'
import App from './app'
new Vue({
el: '#app',
render: h => h(App)
})
if (module.hot) {
module.hot.accept();
}

View File

@ -25,5 +25,5 @@ serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
actix = "^0.4.2"
actix-web = { git = "https://github.com/actix/actix-web" }
actix = "0.5"
actix-web = { path="../../" }

View File

@ -16,8 +16,8 @@ Chat server listens for incoming tcp connections. Server can access several type
* `\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 messsage 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 droppped
* `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`

View File

@ -12,6 +12,9 @@ use std::{io, net, process, thread};
use std::str::FromStr;
use std::time::Duration;
use futures::Future;
use tokio_io::AsyncRead;
use tokio_io::io::WriteHalf;
use tokio_io::codec::FramedRead;
use tokio_core::net::TcpStream;
use actix::prelude::*;
@ -26,7 +29,12 @@ fn main() {
Arbiter::handle().spawn(
TcpStream::connect(&addr, Arbiter::handle())
.and_then(|stream| {
let addr: SyncAddress<_> = ChatClient.framed(stream, codec::ClientChatCodec);
let addr: Addr<Syn, _> = ChatClient::create(|ctx| {
let (r, w) = stream.split();
ChatClient::add_stream(FramedRead::new(r, codec::ClientChatCodec), ctx);
ChatClient{
framed: actix::io::FramedWrite::new(
w, codec::ClientChatCodec, ctx)}});
// start console loop
thread::spawn(move|| {
@ -37,7 +45,7 @@ fn main() {
return
}
addr.send(ClientCommand(cmd));
addr.do_send(ClientCommand(cmd));
}
});
@ -54,44 +62,45 @@ fn main() {
}
struct ChatClient;
struct ChatClient {
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, codec::ClientChatCodec>,
}
#[derive(Message)]
struct ClientCommand(String);
impl Actor for ChatClient {
type Context = FramedContext<Self>;
type Context = Context<Self>;
fn started(&mut self, ctx: &mut FramedContext<Self>) {
fn started(&mut self, ctx: &mut Context<Self>) {
// start heartbeats otherwise server will disconnect after 10 seconds
self.hb(ctx)
}
fn stopping(&mut self, _: &mut FramedContext<Self>) -> bool {
fn stopped(&mut self, _: &mut Context<Self>) {
println!("Disconnected");
// Stop application on disconnect
Arbiter::system().send(actix::msgs::SystemExit(0));
true
Arbiter::system().do_send(actix::msgs::SystemExit(0));
}
}
impl ChatClient {
fn hb(&self, ctx: &mut FramedContext<Self>) {
fn hb(&self, ctx: &mut Context<Self>) {
ctx.run_later(Duration::new(1, 0), |act, ctx| {
if ctx.send(codec::ChatRequest::Ping).is_ok() {
act.hb(ctx);
}
act.framed.write(codec::ChatRequest::Ping);
act.hb(ctx);
});
}
}
impl actix::io::WriteHandler<io::Error> for ChatClient {}
/// Handle stdin commands
impl Handler<ClientCommand> for ChatClient {
type Result = ();
fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext<Self>) {
fn handle(&mut self, msg: ClientCommand, _: &mut Context<Self>) {
let m = msg.0.trim();
if m.is_empty() {
return
@ -102,11 +111,11 @@ impl Handler<ClientCommand> for ChatClient {
let v: Vec<&str> = m.splitn(2, ' ').collect();
match v[0] {
"/list" => {
let _ = ctx.send(codec::ChatRequest::List);
self.framed.write(codec::ChatRequest::List);
},
"/join" => {
if v.len() == 2 {
let _ = ctx.send(codec::ChatRequest::Join(v[1].to_owned()));
self.framed.write(codec::ChatRequest::Join(v[1].to_owned()));
} else {
println!("!!! room name is required");
}
@ -114,36 +123,31 @@ impl Handler<ClientCommand> for ChatClient {
_ => println!("!!! unknown command"),
}
} else {
let _ = ctx.send(codec::ChatRequest::Message(m.to_owned()));
self.framed.write(codec::ChatRequest::Message(m.to_owned()));
}
}
}
/// Server communication
impl FramedActor for ChatClient {
type Io = TcpStream;
type Codec = codec::ClientChatCodec;
impl StreamHandler<codec::ChatResponse, io::Error> for ChatClient {
fn handle(&mut self, msg: io::Result<codec::ChatResponse>, ctx: &mut FramedContext<Self>) {
fn handle(&mut self, msg: codec::ChatResponse, _: &mut Context<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!("");
}
_ => (),
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

@ -4,10 +4,9 @@ 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)]
#[derive(Serialize, Deserialize, Debug, Message)]
#[serde(tag="cmd", content="data")]
pub enum ChatRequest {
/// List rooms
@ -20,13 +19,8 @@ pub enum ChatRequest {
Ping
}
impl ResponseType for ChatRequest {
type Item = ();
type Error = ();
}
/// Server response
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Message)]
#[serde(tag="cmd", content="data")]
pub enum ChatResponse {
Ping,
@ -41,11 +35,6 @@ pub enum ChatResponse {
Message(String),
}
impl ResponseType for ChatResponse {
type Item = ();
type Error = ();
}
/// Codec for Client -> Server transport
pub struct ChatCodec;

View File

@ -17,7 +17,8 @@ extern crate actix_web;
use std::time::Instant;
use actix::*;
use actix_web::*;
use actix_web::server::HttpServer;
use actix_web::{http, fs, ws, App, HttpRequest, HttpResponse, Error};
mod codec;
mod server;
@ -26,11 +27,11 @@ 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>,
addr: Addr<Syn, server::ChatServer>,
}
/// Entry point for our route
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse> {
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse, Error> {
ws::start(
req,
WsChatSession {
@ -62,12 +63,12 @@ impl Actor for WsChatSession {
// 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| {
let addr: Addr<Syn, _> = ctx.address();
ctx.state().addr.send(server::Connect{addr: addr.recipient()})
.into_actor(self)
.then(|res, act, ctx| {
match res {
Ok(Ok(res)) => act.id = res,
Ok(res) => act.id = res,
// something is wrong with chat server
_ => ctx.stop(),
}
@ -75,10 +76,10 @@ impl Actor for WsChatSession {
}).wait(ctx);
}
fn stopping(&mut self, ctx: &mut Self::Context) -> bool {
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
// notify chat server
ctx.state().addr.send(server::Disconnect{id: self.id});
true
ctx.state().addr.do_send(server::Disconnect{id: self.id});
Running::Stop
}
}
@ -87,13 +88,12 @@ impl Handler<session::Message> for WsChatSession {
type Result = ();
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
ctx.text(&msg.0);
ctx.text(msg.0);
}
}
/// WebSocket message handler
impl Handler<ws::Message> for WsChatSession {
type Result = ();
impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
println!("WEBSOCKET MESSAGE: {:?}", msg);
@ -109,17 +109,19 @@ impl Handler<ws::Message> for WsChatSession {
"/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)
ctx.state().addr.send(server::ListRooms)
.into_actor(self)
.then(|res, _, ctx| {
match res {
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
@ -127,7 +129,7 @@ impl Handler<ws::Message> for WsChatSession {
"/join" => {
if v.len() == 2 {
self.room = v[1].to_owned();
ctx.state().addr.send(
ctx.state().addr.do_send(
server::Join{id: self.id, name: self.room.clone()});
ctx.text("joined");
@ -142,7 +144,7 @@ impl Handler<ws::Message> for WsChatSession {
ctx.text("!!! name is required");
}
},
_ => ctx.text(&format!("!!! unknown command: {:?}", m)),
_ => ctx.text(format!("!!! unknown command: {:?}", m)),
}
} else {
let msg = if let Some(ref name) = self.name {
@ -151,7 +153,7 @@ impl Handler<ws::Message> for WsChatSession {
m.to_owned()
};
// send message to chat server
ctx.state().addr.send(
ctx.state().addr.do_send(
server::Message{id: self.id,
msg: msg,
room: self.room.clone()})
@ -159,10 +161,9 @@ impl Handler<ws::Message> for WsChatSession {
},
ws::Message::Binary(bin) =>
println!("Unexpected binary"),
ws::Message::Closed | ws::Message::Error => {
ws::Message::Close(_) => {
ctx.stop();
}
_ => (),
}
}
}
@ -172,35 +173,33 @@ fn main() {
let sys = actix::System::new("websocket-example");
// Start chat server actor in separate thread
let server: SyncAddress<_> =
Arbiter::start(|_| server::ChatServer::default());
let server: Addr<Syn, _> = Arbiter::start(|_| server::ChatServer::default());
// Start tcp server in separate thread
let srv = server.clone();
Arbiter::new("tcp-server").send::<msgs::Execute>(
Arbiter::new("tcp-server").do_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(
HttpServer::new(
move || {
// Websocket sessions state
let state = WsChatSessionState { addr: server.clone() };
Application::with_state(state)
App::with_state(state)
// redirect to websocket.html
.resource("/", |r| r.method(Method::GET).f(|_| {
httpcodes::HTTPFound
.build()
.resource("/", |r| r.method(http::Method::GET).f(|_| {
HttpResponse::Found()
.header("LOCATION", "/static/websocket.html")
.finish()
}))
// websocket
.resource("/ws/", |r| r.route().f(chat_route))
// static resources
.handler("/static/", fs::StaticFiles::new("static/", true))
.handler("/static/", fs::StaticFiles::new("static/"))
})
.bind("127.0.0.1:8080").unwrap()
.start();

View File

@ -12,16 +12,10 @@ use session;
/// Message for chat server communications
/// New chat session is created
#[derive(Message)]
#[rtype(usize)]
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 = ();
pub addr: Recipient<Syn, session::Message>,
}
/// Session is disconnected
@ -44,9 +38,8 @@ pub struct Message {
/// List of available rooms
pub struct ListRooms;
impl ResponseType for ListRooms {
type Item = Vec<String>;
type Error = ();
impl actix::Message for ListRooms {
type Result = Vec<String>;
}
/// Join room, if room does not exists create new one.
@ -61,7 +54,7 @@ pub struct Join {
/// `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>>,
sessions: HashMap<usize, Recipient<Syn, session::Message>>,
rooms: HashMap<String, HashSet<usize>>,
rng: RefCell<ThreadRng>,
}
@ -87,7 +80,7 @@ impl ChatServer {
for id in sessions {
if *id != skip_id {
if let Some(addr) = self.sessions.get(id) {
let _ = addr.send(session::Message(message.to_owned()));
let _ = addr.do_send(session::Message(message.to_owned()));
}
}
}
@ -106,7 +99,7 @@ impl Actor for ChatServer {
///
/// Register new session and assign unique id to this session
impl Handler<Connect> for ChatServer {
type Result = MessageResult<Connect>;
type Result = usize;
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Self::Result {
println!("Someone joined");
@ -122,7 +115,7 @@ impl Handler<Connect> for ChatServer {
self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
// send id back
Ok(id)
id
}
}
@ -171,7 +164,7 @@ impl Handler<ListRooms> for ChatServer {
rooms.push(key.to_owned())
}
Ok(rooms)
MessageResult(rooms)
}
}

View File

@ -4,6 +4,9 @@ use std::{io, net};
use std::str::FromStr;
use std::time::{Instant, Duration};
use futures::Stream;
use tokio_io::AsyncRead;
use tokio_io::io::WriteHalf;
use tokio_io::codec::FramedRead;
use tokio_core::net::{TcpStream, TcpListener};
use actix::prelude::*;
@ -16,22 +19,24 @@ use codec::{ChatRequest, ChatResponse, ChatCodec};
#[derive(Message)]
pub struct Message(pub String);
/// `ChatSession` actor is responsible for tcp peer communitions.
/// `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>,
addr: Addr<Syn, ChatServer>,
/// Client must send ping at least once per 10 seconds, otherwise we drop connection.
hb: Instant,
/// joined room
room: String,
/// Framed wrapper
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, ChatCodec>,
}
impl Actor for ChatSession {
/// For tcp communication we are going to use `FramedContext`.
/// It is convinient wrapper around `Framed` object from `tokio_io`
type Context = FramedContext<Self>;
/// It is convenient wrapper around `Framed` object from `tokio_io`
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
// we'll start heartbeat process on session start.
@ -40,11 +45,12 @@ impl Actor for ChatSession {
// 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()})
let addr: Addr<Syn, _> = ctx.address();
self.addr.send(server::Connect{addr: addr.recipient()})
.into_actor(self)
.then(|res, act, ctx| {
match res {
Ok(Ok(res)) => act.id = res,
Ok(res) => act.id = res,
// something is wrong with chat server
_ => ctx.stop(),
}
@ -52,56 +58,55 @@ impl Actor for ChatSession {
}).wait(ctx);
}
fn stopping(&mut self, ctx: &mut Self::Context) -> bool {
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
// notify chat server
self.addr.send(server::Disconnect{id: self.id});
true
self.addr.do_send(server::Disconnect{id: self.id});
Running::Stop
}
}
/// To use `FramedContext` we have to define Io type and Codec
impl FramedActor for ChatSession {
type Io = TcpStream;
type Codec= ChatCodec;
impl actix::io::WriteHandler<io::Error> for ChatSession {}
/// To use `Framed` we have to define Io type and Codec
impl StreamHandler<ChatRequest, io::Error> for ChatSession {
/// This is main event loop for client requests
fn handle(&mut self, msg: io::Result<ChatRequest>, ctx: &mut FramedContext<Self>) {
fn handle(&mut self, msg: ChatRequest, ctx: &mut Context<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| {
ChatRequest::List => {
// Send ListRooms message to chat server and wait for response
println!("List rooms");
self.addr.send(server::ListRooms)
.into_actor(self)
.then(|res, act, ctx| {
match res {
Ok(Ok(rooms)) => {
let _ = ctx.send(ChatResponse::Rooms(rooms));
Ok(rooms) => {
act.framed.write(ChatResponse::Rooms(rooms));
},
_ => println!("Something is wrong"),
_ => 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(),
// .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.do_send(server::Join{id: self.id, name: name.clone()});
self.framed.write(ChatResponse::Joined(name));
},
ChatRequest::Message(message) => {
// send message to chat server
println!("Peer message: {}", message);
self.addr.do_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(),
}
}
}
@ -110,23 +115,25 @@ impl FramedActor for ChatSession {
impl Handler<Message> for ChatSession {
type Result = ();
fn handle(&mut self, msg: Message, ctx: &mut FramedContext<Self>) {
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
// send message to peer
let _ = ctx.send(ChatResponse::Message(msg.0));
self.framed.write(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()}
pub fn new(addr: Addr<Syn,ChatServer>,
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, ChatCodec>) -> ChatSession {
ChatSession {id: 0, addr: addr, hb: Instant::now(),
room: "Main".to_owned(), framed: framed}
}
/// helper method that sends ping to client every second.
///
/// also this method check heartbeats from client
fn hb(&self, ctx: &mut FramedContext<Self>) {
fn hb(&self, ctx: &mut Context<Self>) {
ctx.run_later(Duration::new(1, 0), |act, ctx| {
// check client heartbeats
if Instant::now().duration_since(act.hb) > Duration::new(10, 0) {
@ -134,29 +141,28 @@ impl ChatSession {
println!("Client heartbeat failed, disconnecting!");
// notify chat server
act.addr.send(server::Disconnect{id: act.id});
act.addr.do_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);
}
act.framed.write(ChatResponse::Ping);
// if we can not send message to sink, sink is closed (disconnected)
act.hb(ctx);
});
}
}
/// Define tcp server that will accept incomint tcp connection and create
/// Define tcp server that will accept incoming tcp connection and create
/// chat actors.
pub struct TcpServer {
chat: SyncAddress<ChatServer>,
chat: Addr<Syn, ChatServer>,
}
impl TcpServer {
pub fn new(s: &str, chat: SyncAddress<ChatServer>) {
pub fn new(s: &str, chat: Addr<Syn, ChatServer>) {
// Create server listener
let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap();
let listener = TcpListener::bind(&addr, Arbiter::handle()).unwrap();
@ -192,6 +198,10 @@ impl Handler<TcpConnect> for TcpServer {
// 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);
let _: () = ChatSession::create(|ctx| {
let (r, w) = msg.0.split();
ChatSession::add_stream(FramedRead::new(r, ChatCodec), ctx);
ChatSession::new(server, actix::io::FramedWrite::new(w, ChatCodec, ctx))
});
}
}

View File

@ -2,13 +2,19 @@
name = "websocket"
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]
env_logger = "*"
futures = "0.1"
actix = "^0.4.2"
actix-web = { git = "https://github.com/actix/actix-web.git" }
tokio-core = "0.1"
actix = "0.5"
actix-web = { path="../../" }

View File

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

View File

@ -0,0 +1,113 @@
//! Simple websocket client.
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
extern crate tokio_core;
use std::{io, thread};
use std::time::Duration;
use actix::*;
use futures::Future;
use actix_web::ws::{Message, ProtocolError, Client, ClientWriter};
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("ws-example");
Arbiter::handle().spawn(
Client::new("http://127.0.0.1:8080/ws/")
.connect()
.map_err(|e| {
println!("Error: {}", e);
()
})
.map(|(reader, writer)| {
let addr: Addr<Syn, _> = ChatClient::create(|ctx| {
ChatClient::add_stream(reader, ctx);
ChatClient(writer)
});
// 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.do_send(ClientCommand(cmd));
}
});
()
})
);
let _ = sys.run();
}
struct ChatClient(ClientWriter);
#[derive(Message)]
struct ClientCommand(String);
impl Actor for ChatClient {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Context<Self>) {
// start heartbeats otherwise server will disconnect after 10 seconds
self.hb(ctx)
}
fn stopped(&mut self, _: &mut Context<Self>) {
println!("Disconnected");
// Stop application on disconnect
Arbiter::system().do_send(actix::msgs::SystemExit(0));
}
}
impl ChatClient {
fn hb(&self, ctx: &mut Context<Self>) {
ctx.run_later(Duration::new(1, 0), |act, ctx| {
act.0.ping("");
act.hb(ctx);
});
}
}
/// Handle stdin commands
impl Handler<ClientCommand> for ChatClient {
type Result = ();
fn handle(&mut self, msg: ClientCommand, ctx: &mut Context<Self>) {
self.0.text(msg.0)
}
}
/// Handle server websocket messages
impl StreamHandler<Message, ProtocolError> for ChatClient {
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
match msg {
Message::Text(txt) => println!("Server: {:?}", txt),
_ => ()
}
}
fn started(&mut self, ctx: &mut Context<Self>) {
println!("Connected");
}
fn finished(&mut self, ctx: &mut Context<Self>) {
println!("Server disconnected");
ctx.stop()
}
}

View File

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

View File

@ -1,3 +1,4 @@
[book]
title = "Actix web"
description = "Actix web framework guide"
author = "Actix Project and Contributors"

View File

@ -1,11 +1,8 @@
# 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:
Before we begin, we need to install Rust using [rustup](https://www.rustup.rs/):
```bash
curl https://sh.rustup.rs -sSf | sh
@ -17,13 +14,14 @@ If you already have rustup installed, run this command to ensure you have the la
rustup update
```
Actix web framework requires rust version 1.20 and up.
Actix web framework requires rust version 1.21 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:
The fastest way to start experimenting with actix web is to clone the
[repository](https://github.com/actix/actix-web) and run the included examples.
The following set of commands runs the `basics` example:
```bash
git clone https://github.com/actix/actix-web

View File

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

View File

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

View File

@ -1,42 +1,46 @@
# HTTP/2.0
Actix web automatically upgrades connection to *HTTP/2.0* if possible.
Actix web automatically upgrades connections to *HTTP/2.0* if possible.
## Negotiation
*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
[tls alpn](https://tools.ietf.org/html/rfc7301).
> Currently, only `rust-openssl` has support.
`alpn` negotiation requires enabling the feature. When enabled, `HttpServer` provides the
[serve_tls](../actix_web/struct.HttpServer.html#method.serve_tls) method.
```toml
[dependencies]
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] }
actix-web = { version = "0.3.3", features=["alpn"] }
openssl = { version="0.10", features = ["v110"] }
```
```rust,ignore
use std::fs::File;
use actix_web::*;
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
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();
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
builder.set_certificate_chain_file("cert.pem").unwrap();
HttpServer::new(
|| Application::new()
|| App::new()
.resource("/index.html", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap();
.serve_ssl(pkcs12).unwrap();
.serve_ssl(builder).unwrap();
}
```
Upgrade to *HTTP/2.0* schema described in
Upgrades to *HTTP/2.0* schema described in
[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported.
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.
> Check out [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls)
> for a concrete example.

View File

@ -2,17 +2,18 @@
## 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).
At the moment, Diesel 1.0 does not support asynchronous operations,
but it possible to use the `actix` synchronous actor system as a database interface api.
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.
Technically, sync actors are worker style actors. Multiple sync actors
can be run in parallel and process messages from same queue. Sync actors work in mpsc mode.
Let's create a simple database api that can insert a new user row into a SQLite table.
We must define a sync actor and a connection that this actor will use. The same approach
can be used for other databases.
```rust,ignore
use actix::prelude::*;*
use actix::prelude::*;
struct DbExecutor(SqliteConnection);
@ -21,22 +22,24 @@ impl Actor for DbExecutor {
}
```
This is definition of our actor. Now we need to define *create user* message and response.
This is the definition of our actor. Now, we must define the *create user* message and response.
```rust,ignore
#[derive(Message)]
#[rtype(User, Error)]
struct CreateUser {
name: String,
}
impl Message for CreateUser {
type Result = Result<User, Error>;
}
```
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.
We can send a `CreateUser` message to the `DbExecutor` actor, and as a result, we will receive a
`User` model instance. Next, we must define the handler implementation for this message.
```rust,ignore
impl Handler<CreateUser> for DbExecutor {
type Result = Result<User, Error>
type Result = Result<User, Error>;
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result
{
@ -49,7 +52,7 @@ impl Handler<CreateUser> for DbExecutor {
name: &msg.name,
};
// normal diesl operations
// normal diesel operations
diesel::insert_into(users)
.values(&new_user)
.execute(&self.0)
@ -65,14 +68,14 @@ impl Handler<CreateUser> for DbExecutor {
}
```
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
That's it! Now, we can use the *DbExecutor* actor from any http handler or middleware.
All we need is to start *DbExecutor* actors and store the 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>,
db: Addr<Syn, DbExecutor>,
}
fn main() {
@ -85,7 +88,7 @@ fn main() {
// Start http server
HttpServer::new(move || {
Application::with_state(State{db: addr.clone()})
App::with_state(State{db: addr.clone()})
.resource("/{name}", |r| r.method(Method::GET).a(index))})
.bind("127.0.0.1:8080").unwrap()
.start().unwrap();
@ -95,9 +98,9 @@ fn main() {
}
```
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.
We will use the address in a request handler. The handle returns a future object;
thus, we receive the message response asynchronously.
`Route::a()` must be used for async handler registration.
```rust,ignore
@ -106,20 +109,20 @@ 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()})
req.state().db.send(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())
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into())
}
})
.responder()
}
```
Full example is available in
[examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/).
> A full example is available in the
> [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).
> More information on sync actors can be found in the
> [actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html).

View File

@ -1,14 +1,10 @@
# 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.
Lets write our first actix web application!
## Hello, world!
Lets write our first actix web application! Start by creating a new binary-based
Cargo project and changing into the new directory:
Start by creating a new binary-based Cargo project and changing into the new directory:
```bash
cargo new hello-world --bin
@ -20,15 +16,16 @@ contains the following:
```toml
[dependencies]
actix = "0.4"
actix-web = "0.3"
actix = "0.5"
actix-web = "0.5"
```
In order to implement a web server, first we need to create a request handler.
In order to implement a web server, we first need to create a request handler.
A request handler is a function that accepts a `HttpRequest` instance as its only parameter
A request handler is a function that accepts an `HttpRequest` instance as its only parameter
and returns a type that can be converted into `HttpResponse`:
Filename: src/main.rs
```rust
# extern crate actix_web;
# use actix_web::*;
@ -48,48 +45,55 @@ request handler with the application's `resource` on a particular *HTTP method*
# "Hello world!"
# }
# fn main() {
Application::new()
App::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:
After that, the application instance can be used with `HttpServer` to listen for incoming
connections. The server accepts a function that should return an `HttpHandler` instance:
```rust,ignore
HttpServer::new(
|| Application::new()
|| App::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.
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:
The full source of src/main.rs is listed below:
```rust
# use std::thread;
# extern crate actix_web;
use actix_web::*;
extern crate actix_web;
use actix_web::{server, App, HttpRequest, HttpResponse};
fn index(req: HttpRequest) -> &'static str {
"Hello world!"
}
fn main() {
# thread::spawn(|| {
HttpServer::new(
|| Application::new()
# // In the doctest suite we can't run blocking code - deliberately leak a thread
# // If copying this example in show-all mode, make sure you skip the thread spawn
# // call.
# thread::spawn(|| {
server::HttpServer::new(
|| App::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/)
> **Note**: actix web is built upon [actix](https://github.com/actix/actix),
> an [actor model](https://en.wikipedia.org/wiki/Actor_model) framework in Rust.
`actix::System` initializes actor system, `HttpServer` is an actor and must run within a
properly configured actix system.
> For more information, check out the [actix documentation](https://actix.github.io/actix/actix/)
> and [actix guide](https://actix.github.io/actix/guide/).

View File

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

View File

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

View File

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

View File

@ -1,20 +1,22 @@
# Errors
Actix uses [`Error` type](../actix_web/error/struct.Error.html)
Actix uses the [`Error` type](../actix_web/error/struct.Error.html)
and [`ResponseError` trait](../actix_web/error/trait.ResponseError.html)
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
Any error that implements the `ResponseError` trait can be returned as an error value.
`Handler` can return an `Result` object. By default, actix provides a
`Responder` implementation for compatible result types. Here is the 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
Any error that implements `ResponseError` can be converted into an `Error` object.
For example, if the *handler* function returns `io::Error`, it would be converted
into an `HttpInternalServerError` response. Implementation for `io::Error` is provided
by default.
```rust
@ -27,7 +29,7 @@ fn index(req: HttpRequest) -> io::Result<fs::NamedFile> {
}
#
# fn main() {
# Application::new()
# App::new()
# .resource(r"/a/index.html", |r| r.f(index))
# .finish();
# }
@ -35,9 +37,9 @@ fn index(req: HttpRequest) -> io::Result<fs::NamedFile> {
## 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.
To add support for custom errors, all we need to do is implement the `ResponseError` trait
for the custom error type. The `ResponseError` trait has a default implementation
for the `error_response()` method: it generates a *500* response.
```rust
# extern crate actix_web;
@ -58,19 +60,19 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> {
}
#
# fn main() {
# Application::new()
# App::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.
In this example the *index* handler always returns a *500* response. But it is easy
to return different responses for different types of errors.
```rust
# extern crate actix_web;
#[macro_use] extern crate failure;
use actix_web::*;
use actix_web::{App, HttpRequest, HttpResponse, http, error};
#[derive(Fail, Debug)]
enum MyError {
@ -86,11 +88,11 @@ impl error::ResponseError for MyError {
fn error_response(&self) -> HttpResponse {
match *self {
MyError::InternalError => HttpResponse::new(
StatusCode::INTERNAL_SERVER_ERROR, Body::Empty),
http::StatusCode::INTERNAL_SERVER_ERROR),
MyError::BadClientData => HttpResponse::new(
StatusCode::BAD_REQUEST, Body::Empty),
http::StatusCode::BAD_REQUEST),
MyError::Timeout => HttpResponse::new(
StatusCode::GATEWAY_TIMEOUT, Body::Empty),
http::StatusCode::GATEWAY_TIMEOUT),
}
}
}
@ -100,7 +102,7 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> {
}
#
# fn main() {
# Application::new()
# App::new()
# .resource(r"/a/index.html", |r| r.f(index))
# .finish();
# }
@ -108,8 +110,8 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> {
## 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.
Actix provides a set of error helper types. It is possible to use them for generating
specific error responses. We can use the helper types for the first example with a custom error.
```rust
# extern crate actix_web;
@ -124,13 +126,28 @@ struct MyError {
fn index(req: HttpRequest) -> Result<&'static str> {
let result: Result<&'static str, MyError> = Err(MyError{name: "test"});
Ok(result.map_err(error::ErrorBadRequest)?)
Ok(result.map_err(|e| error::ErrorBadRequest(e))?)
}
# fn main() {
# Application::new()
# App::new()
# .resource(r"/a/index.html", |r| r.f(index))
# .finish();
# }
```
In this example *BAD REQUEST* response get generated for `MyError` error.
In this example, a *BAD REQUEST* response is generated for the `MyError` error.
## Error logging
Actix logs all errors with the log level `WARN`. If log level set to `DEBUG`
and `RUST_BACKTRACE` is enabled, the backtrace gets logged. The Error type uses
the cause's error backtrace if available. If the underlying failure does not provide
a backtrace, a new backtrace is constructed pointing to that conversion point
(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,107 +1,131 @@
# 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).
URL dispatch provides a simple way for mapping URLs to `Handler` code using a simple pattern
matching language. If one of the patterns matches the path information associated with a request,
a particular handler object is invoked.
> A handler is a specific object that implements the
> `Handler` trait, defined in your application, that receives the request and returns
> a response object. More information is available in the [handler section](../qs_4.html).
## Resource configuration
Resource configuration is the act of adding a new resource to an application.
Resource configuration is the act of adding a new resources to an application.
A resource has a name, which acts as an identifier to be used for URL generation.
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
A resource also has a pattern, meant to match against the *PATH* portion of a *URL*.
It does not match against the *QUERY* portion (the portion following the scheme and
port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*).
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.
The [App::route](../actix_web/struct.App.html#method.route) method provides
simple way of registering routes. This method adds a single route to application
routing table. This method accepts a *path pattern*,
*http method* and a handler function. `route()` method could be called multiple times
for the same path, in that case, multiple routes register for the same resource path.
```rust
# extern crate actix_web;
# use actix_web::*;
# use actix_web::httpcodes::*;
#
# fn index(req: HttpRequest) -> HttpResponse {
# unimplemented!()
# }
#
use actix_web::{App, HttpRequest, HttpResponse, http::Method};
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))
App::new()
.route("/user/{name}", Method::GET, index)
.route("/user/{name}", Method::POST, index)
.finish();
}
```
*Configuration function* has following type:
While *App::route()* provides simple way of registering routes, to access
complete resource configuration, different method has to be used.
The [App::resource](../actix_web/struct.App.html#method.resource) method
adds a single resource to application routing table. This method accepts a *path pattern*
and a resource configuration function.
```rust
# extern crate actix_web;
use actix_web::{App, HttpRequest, HttpResponse, http::Method};
fn index(req: HttpRequest) -> HttpResponse {
unimplemented!()
}
fn main() {
App::new()
.resource("/prefix", |r| r.f(index))
.resource("/user/{name}",
|r| r.method(Method::GET).f(|req| HttpResponse::Ok()))
.finish();
}
```
The *Configuration function* has the following type:
```rust,ignore
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.
The *Configuration function* can set a name and register specific routes.
If a resource does not contain any route or does not have any matching routes, it
returns *NOT FOUND* http response.
## 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`.
Resource contains a set of routes. Each route in turn has a set of predicates and a handler.
New routes can be created with `Resource::route()` method which returns a reference
to new *Route* instance. By default the *route* does not contain any predicates, so matches
all requests and the default handler is `HttpNotFound`.
Application routes incoming requests based on route criteria which is defined during
The application routes incoming requests based on route criteria which are defined during
resource registration and route registration. Resource matches all routes it contains in
the order that the routes were registered via `Resource::route()`. *Route* can contain
any number of *predicates* but only one handler.
the order the routes were registered via `Resource::route()`.
> A *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()
App::new()
.resource("/path", |resource|
resource.route()
.p(pred::Get())
.p(pred::Header("content-type", "text/plain"))
.f(|req| HTTPOk)
.filter(pred::Get())
.filter(pred::Header("content-type", "text/plain"))
.f(|req| HttpResponse::Ok())
)
.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.
In this example, `HttpResponse::Ok()` is returned for *GET* requests.
If a request contains `Content-Type` header, the value of this header is *text/plain*,
and path equals to `/path`, Resource calls handle of the first matching route.
[*Resource::route()*](../actix_web/struct.Resource.html#method.route) method returns
[*Route*](../actix_web/struct.Route.html) object. Route can be configured with
If a resource can not match any route, a "NOT FOUND" response is returned.
[*Resource::route()*](../actix_web/struct.Resource.html#method.route) returns a
[*Route*](../actix_web/struct.Route.html) object. Route can be configured with a
builder-like pattern. Following configuration methods are available:
* [*Route::p()*](../actix_web/struct.Route.html#method.p) method registers new predicate,
any number of predicates could be registered for each route.
* [*Route::filter()*](../actix_web/struct.Route.html#method.filter) registers a new predicate.
Any number of predicates can 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
* [*Route::f()*](../actix_web/struct.Route.html#method.f) registers handler function
for this route. Only one handler can be registered. Usually handler registration
is the last config operation. Handler function can be a function or closure and has the 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
* [*Route::h()*](../actix_web/struct.Route.html#method.h) registers a handler object
that implements the `Handler` trait. This is similar to `f()` method - only one handler can
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
* [*Route::a()*](../actix_web/struct.Route.html#method.a) registers an async handler
function for this route. Only one handler can be registered. Handler registration
is the last config operation. Handler function can be a function or closure and has the type
`Fn(HttpRequest<S>) -> Future<Item = HttpResponse, Error = Error> + 'static`
## Route matching
@ -110,11 +134,10 @@ The main purpose of route configuration is to match (or not match) the request's
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.
for each resource configuration declaration present in the system, actix checks
the request's path against the pattern declared. This checking happens in the order that
the routes were declared via `App::resource()` method. If resource can not be found,
the *default resource* is used as the 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
@ -123,13 +146,11 @@ arguments provided to a route configuration returns `false` during a check, that
skipped and route matching continues through the ordered set of routes.
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.
the route is invoked. If no route matches after all route patterns are exhausted, a *NOT FOUND* response get returned.
## Resource pattern syntax
The syntax of the pattern matching language used by the actix in the pattern
The syntax of the pattern matching language used by actix in the pattern
argument is straightforward.
The pattern used in route configuration may start with a slash character. If the pattern
@ -213,8 +234,9 @@ 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.
> **Note**: path will be URL-unquoted and decoded into valid unicode string before
> matching pattern and values representing matched path segments will be URL-unquoted too.
So for instance, the following pattern:
```
@ -264,12 +286,12 @@ foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'}
All values representing matched path segments are available in
[`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.
Specific values can be retrieved with
[`Params::get()`](../actix_web/dev/struct.Params.html#method.get).
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.:
Any matched parameter can be deserialized into a specific type if the type
implements the `FromParam` trait. For example most standard integer types
the trait, i.e.:
```rust
# extern crate actix_web;
@ -282,7 +304,7 @@ fn index(req: HttpRequest) -> Result<String> {
}
fn main() {
Application::new()
App::new()
.resource(r"/a/{v1}/{v2}/", |r| r.f(index))
.finish();
}
@ -297,19 +319,19 @@ 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.
* 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;
use actix_web::{App, HttpRequest, Result, http::Method};
fn index(req: HttpRequest) -> Result<String> {
let path: PathBuf = req.match_info().query("tail")?;
@ -317,64 +339,122 @@ fn index(req: HttpRequest) -> Result<String> {
}
fn main() {
Application::new()
App::new()
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
.finish();
}
```
List of `FromParam` implementation could be found in
List of `FromParam` implementations can be found in
[api docs](../actix_web/dev/trait.FromParam.html#foreign-impls)
## Path information extractor
Actix provides functionality for type safe path information extraction.
[Path](../actix_web/struct.Path.html) extracts information, destination type
could be defined in several different forms. Simplest approach is to use
`tuple` type. Each element in tuple must correpond to one element from
path pattern. i.e. you can match path pattern `/{id}/{username}/` against
`Pyth<(u32, String)>` type, but `Path<(String, String, String)>` type will
always fail.
```rust
# extern crate actix_web;
use actix_web::{App, Path, Result, http::Method};
// extract path info using serde
fn index(info: Path<(String, u32)>) -> Result<String> {
Ok(format!("Welcome {}! id: {}", info.0, info.1))
}
fn main() {
let app = App::new()
.resource("/{username}/{id}/index.html", // <- define path parameters
|r| r.method(Method::GET).with(index));
}
```
It also possible to extract path pattern information to a struct. In this case,
this struct must implement *serde's *`Deserialize` trait.
```rust
# extern crate actix_web;
#[macro_use] extern crate serde_derive;
use actix_web::{App, Path, Result, http::Method};
#[derive(Deserialize)]
struct Info {
username: String,
}
// extract path info using serde
fn index(info: Path<Info>) -> Result<String> {
Ok(format!("Welcome {}!", info.username))
}
fn main() {
let app = App::new()
.resource("/{username}/index.html", // <- define path parameters
|r| r.method(Method::GET).with(index));
}
```
[Query](../actix_web/struct.Query.html) provides similar functionality for
request query parameters.
## Generating resource URLs
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.
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::*;
# use actix_web::{App, Result, HttpRequest, HttpResponse, http::Method, http::header};
#
fn index(req: HttpRequest) -> HttpResponse {
let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
HTTPOk.into()
fn index(req: HttpRequest) -> Result<HttpResponse> {
let url = req.url_for("foo", &["1", "2", "3"])?; // <- generate url for "foo" resource
Ok(HttpResponse::Found()
.header(header::LOCATION, url.as_str())
.finish())
}
fn main() {
let app = Application::new()
let app = App::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);
r.method(Method::GET).f(|_| HttpResponse::Ok());
})
.route("/test/", Method::GET, index)
.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
`url_for()` method returns [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you
can modify this url (add query parameters, anchor, etc).
`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
Resources that are valid URLs, can be registered as external resources. They are useful
for URL generation purposes only and are never considered for matching at request time.
```rust
# extern crate actix_web;
use actix_web::*;
use actix_web::{App, HttpRequest, HttpResponse, Error};
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
fn index(mut req: HttpRequest) -> Result<HttpResponse, Error> {
let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
Ok(httpcodes::HTTPOk.into())
Ok(HttpResponse::Ok().into())
}
fn main() {
let app = Application::new()
let app = App::new()
.resource("/index.html", |r| r.f(index))
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
.finish();
@ -385,66 +465,67 @@ fn main() {
By normalizing it means:
- Add a trailing slash to the path.
- Double slashes are replaced by one.
* 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 *append* is *true*, append slash when needed. If a resource is
defined with trailing slash and the request doesn't have one, it will
be appended 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*.
This handler designed to be used as a handler for application's *default resource*.
```rust
# extern crate actix_web;
# #[macro_use] extern crate serde_derive;
# use actix_web::*;
use actix_web::http::NormalizePath;
#
# fn index(req: HttpRequest) -> httpcodes::StaticResponse {
# httpcodes::HTTPOk
# fn index(req: HttpRequest) -> HttpResponse {
# HttpResponse::Ok().into()
# }
fn main() {
let app = Application::new()
let app = App::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 `/resource`, `//resource///` will be redirected to `/resource/`.
In this example path normalization handler get registered for all method,
In this example, the path normalization handler is registered for all methods,
but you should not rely on this mechanism to redirect *POST* requests. The redirect of the
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
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::*;
use actix_web::{App, HttpRequest, http::Method, http::NormalizePath};
#
# fn index(req: HttpRequest) -> httpcodes::StaticResponse {
# httpcodes::HTTPOk
# fn index(req: HttpRequest) -> &'static str {
# "test"
# }
fn main() {
let app = Application::new()
let app = App::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
## Using an Application Prefix to Compose Applications
The `Application::prefix()`" method allows to set specific application prefix.
The `App::prefix()` method allows to set a specific application prefix.
This prefix represents a resource prefix that will be prepended to all resource patterns added
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
@ -461,7 +542,7 @@ fn show_users(req: HttpRequest) -> HttpResponse {
}
fn main() {
Application::new()
App::new()
.prefix("/users")
.resource("/show", |r| r.f(show_users))
.finish();
@ -476,77 +557,73 @@ 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
You can think of a predicate as a simple function that accepts a *request* object reference
and returns *true* or *false*. Formally, a predicate is any object that implements the
[`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*:
Here is a simple predicate that check that a request contains a 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;
use actix_web::{http, pred::Predicate, App, HttpRequest};
struct ContentTypeHeader;
impl<S: 'static> Predicate<S> for ContentTypeHeader {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
req.headers().contains_key(CONTENT_TYPE)
req.headers().contains_key(http::header::CONTENT_TYPE)
}
}
fn main() {
Application::new()
App::new()
.resource("/index.html", |r|
r.route()
.p(ContentTypeHeader)
.h(HTTPOk));
.filter(ContentTypeHeader)
.f(|_| HttpResponse::Ok()));
}
```
In this example *index* handler will be called only if request contains *CONTENT-TYPE* header.
In this example, *index* handler will be called only if request contains *CONTENT-TYPE* header.
Predicates can have access to application's state via `HttpRequest::state()` method.
Predicates have access to the application's state via `HttpRequest::state()`.
Also predicates can store extra information in
[requests`s extensions](../actix_web/struct.HttpRequest.html#method.extensions).
[request 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
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;
use actix_web::{pred, App, HttpResponse};
fn main() {
Application::new()
App::new()
.resource("/index.html", |r|
r.route()
.p(pred::Not(pred::Get()))
.f(|req| HTTPMethodNotAllowed))
.filter(pred::Not(pred::Get()))
.f(|req| HttpResponse::MethodNotAllowed()))
.finish();
}
```
`Any` predicate accept list of predicates and matches if any of the supplied
The `Any` predicate accepts a list of predicates and matches if any of the supplied
predicates match. i.e:
```rust,ignore
pred::Any(pred::Get()).or(pred::Post())
```
`All` predicate accept list of predicates and matches if all of the supplied
The `All` predicate accepts a list of predicates and matches if all of the supplied
predicates match. i.e:
```rust,ignore
@ -555,23 +632,22 @@ predicates match. i.e:
## 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.
If the path pattern can not be found in the routing table or a resource can not find matching
route, the default resource is used. The default response is *NOT FOUND*.
It is possible to override the *NOT FOUND* response with `App::default_resource()`.
This method accepts a *configuration function* same as normal resource configuration
with `App::resource()` method.
```rust
# extern crate actix_web;
# extern crate http;
use actix_web::*;
use actix_web::httpcodes::*;
use actix_web::{App, HttpResponse, http::Method, pred};
fn main() {
Application::new()
App::new()
.default_resource(|r| {
r.method(Method::GET).f(|req| HTTPNotFound);
r.route().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed);
r.method(Method::GET).f(|req| HttpResponse::NotFound());
r.route().filter(pred::Not(pred::Get()))
.f(|req| HttpResponse::MethodNotAllowed());
})
# .finish();
}

View File

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

View File

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

View File

@ -1,12 +1,12 @@
# WebSockets
Actix supports WebSockets out-of-the-box. It is possible to convert request's `Payload`
Actix supports WebSockets out-of-the-box. It is possible to convert a request's `Payload`
to a stream of [*ws::Message*](../actix_web/ws/enum.Message.html) with
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.
combinators to handle actual messages, but it is simpler to handle websocket communications
with an http actor.
This is example of simple websocket echo server:
The following is an example of a simple websocket echo server:
```rust
# extern crate actix;
@ -21,14 +21,13 @@ impl Actor for Ws {
type Context = ws::WebsocketContext<Self>;
}
/// Define Handler for ws::Message message
impl Handler<ws::Message> for Ws {
type Result=();
/// Handler for ws::Message message
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
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::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin),
_ => (),
}
@ -36,14 +35,14 @@ impl Handler<ws::Message> for Ws {
}
fn main() {
Application::new()
App::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).
> A simple websocket echo server example is available in the
> [examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket).
Example chat server with ability to chat over websocket connection or tcp connection
is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
> An example chat server with the ability to chat over a websocket or tcp connection
> is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)

View File

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

View File

@ -1,4 +1,4 @@
use std::fmt;
use std::{fmt, mem};
use std::rc::Rc;
use std::sync::Arc;
use bytes::{Bytes, BytesMut};
@ -6,6 +6,10 @@ use futures::Stream;
use error::Error;
use context::ActorHttpContext;
use handler::Responder;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Type represent streaming body
pub type BodyStream = Box<Stream<Item=Bytes, Error=Error>>;
@ -31,11 +35,13 @@ pub enum Binary {
Bytes(Bytes),
/// Static slice
Slice(&'static [u8]),
/// Shared stirng body
/// Shared string body
SharedString(Rc<String>),
/// Shared string body
#[doc(hidden)]
ArcSharedString(Arc<String>),
/// Shared vec body
SharedVec(Arc<Vec<u8>>),
}
impl Body {
@ -115,6 +121,7 @@ impl Binary {
Binary::Slice(slice) => slice.len(),
Binary::SharedString(ref s) => s.len(),
Binary::ArcSharedString(ref s) => s.len(),
Binary::SharedVec(ref s) => s.len(),
}
}
@ -122,6 +129,23 @@ impl Binary {
pub fn from_slice(s: &[u8]) -> Binary {
Binary::Bytes(Bytes::from(s))
}
/// Convert Binary to a Bytes instance
pub fn take(&mut self) -> Bytes {
mem::replace(self, Binary::Slice(b"")).into()
}
}
impl Clone for Binary {
fn clone(&self) -> Binary {
match *self {
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
Binary::SharedString(ref s) => Binary::SharedString(s.clone()),
Binary::ArcSharedString(ref s) => Binary::ArcSharedString(s.clone()),
Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()),
}
}
}
impl Into<Bytes> for Binary {
@ -131,6 +155,7 @@ impl Into<Bytes> for Binary {
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())),
}
}
}
@ -201,17 +226,42 @@ impl<'a> From<&'a Arc<String>> for Binary {
}
}
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(self, _: HttpRequest) -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok()
.content_type("application/octet-stream")
.body(self))
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -231,68 +281,77 @@ 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());
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
}
#[test]
fn test_ref_string() {
let b = Rc::new("test".to_owned());
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes());
assert_eq!(Binary::from(&b).as_ref(), b"test");
}
#[test]
fn test_rc_string() {
let b = Rc::new("test".to_owned());
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), "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_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]

1095
src/client/connector.rs Normal file

File diff suppressed because it is too large Load Diff

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

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

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

@ -0,0 +1,193 @@
use std::mem;
use httparse;
use http::{Version, HttpTryFrom, HeaderMap, StatusCode};
use http::header::{self, HeaderName, HeaderValue};
use bytes::{Bytes, BytesMut};
use futures::{Poll, Async};
use error::{ParseError, PayloadError};
use server::{utils, IoStream};
use server::h1::{Decoder, chunked};
use super::ClientResponse;
use super::response::ClientMessage;
const MAX_BUFFER_SIZE: usize = 131_072;
const MAX_HEADERS: usize = 96;
#[derive(Default)]
pub struct HttpResponseParser {
decoder: Option<Decoder>,
}
#[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
{
// if buf is empty parse_message will always return NotReady, let's avoid that
if buf.is_empty() {
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) =>
return Err(HttpResponseParserError::Disconnect),
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) =>
return Ok(Async::NotReady),
Err(err) =>
return Err(HttpResponseParserError::Error(err.into()))
}
}
loop {
match HttpResponseParser::parse_message(buf)
.map_err(HttpResponseParserError::Error)?
{
Async::Ready((msg, decoder)) => {
self.decoder = decoder;
return Ok(Async::Ready(msg));
},
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(ParseError::TooLarge));
}
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) =>
return Err(HttpResponseParserError::Disconnect),
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) =>
return Err(HttpResponseParserError::Error(err.into())),
}
},
}
}
}
pub fn parse_payload<T>(&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 = match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => {
if buf.is_empty() {
return Err(PayloadError::Incomplete)
}
true
}
Err(err) => return Err(err.into()),
Ok(Async::NotReady) => true,
_ => false,
};
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)
}
}
Err(err) => return Err(err.into()),
}
}
} else {
Ok(Async::Ready(None))
}
}
fn parse_message(buf: &mut BytesMut)
-> Poll<(ClientResponse, Option<Decoder>), ParseError>
{
// Parse http message
let bytes_ptr = buf.as_ref().as_ptr() as usize;
let mut headers: [httparse::Header; MAX_HEADERS] =
unsafe{mem::uninitialized()};
let (len, version, status, headers_len) = {
let b = unsafe{ let b: &[u8] = buf; mem::transmute(b) };
let mut resp = httparse::Response::new(&mut headers);
match resp.parse(b)? {
httparse::Status::Complete(len) => {
let version = if resp.version.unwrap_or(1) == 1 {
Version::HTTP_11
} else {
Version::HTTP_10
};
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 header in headers[..headers_len].iter() {
if let Ok(name) = HeaderName::try_from(header.name) {
let v_start = header.value.as_ptr() as usize - bytes_ptr;
let v_end = v_start + header.value.len();
let value = unsafe {
HeaderValue::from_shared_unchecked(slice.slice(v_start, v_end)) };
hdrs.append(name, value);
} else {
return Err(ParseError::Header)
}
}
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
Some(Decoder::eof())
} 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(Decoder::length(len))
} 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(Decoder::chunked())
} 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)))
}
}
}

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

@ -0,0 +1,488 @@
use std::{io, mem};
use std::time::Duration;
use bytes::{Bytes, BytesMut};
use http::header::CONTENT_ENCODING;
use futures::{Async, Future, Poll};
use futures::unsync::oneshot;
use tokio_core::reactor::Timeout;
use actix::prelude::*;
use error::Error;
use body::{Body, BodyStream};
use context::{Frame, ActorHttpContext};
use header::ContentEncoding;
use httpmessage::HttpMessage;
use error::PayloadError;
use server::WriterState;
use server::shared::SharedBytes;
use server::encoding::PayloadStream;
use super::{ClientRequest, ClientResponse};
use super::{Connect, Connection, ClientConnector, ClientConnectorError};
use super::HttpClientWriter;
use super::{HttpResponseParser, HttpResponseParserError};
/// 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(actix::dev::Request<Unsync, 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: Addr<Unsync, ClientConnector>,
conn_timeout: Duration,
wait_timeout: Duration,
timeout: Option<Timeout>,
}
impl SendRequest {
pub(crate) fn new(req: ClientRequest) -> SendRequest {
SendRequest::with_connector(req, ClientConnector::from_registry())
}
pub(crate) fn with_connector(req: ClientRequest, conn: Addr<Unsync, ClientConnector>)
-> SendRequest
{
SendRequest{req, 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: ClientConnector::from_registry(),
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::new(timeout, Arbiter::handle()).unwrap());
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 =>
self.state = State::Connect(self.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(SharedBytes::default());
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(||
Timeout::new(
Duration::from_secs(5), Arbiter::handle()).unwrap());
let pl = Box::new(Pipeline {
body, writer,
conn: Some(conn),
parser: Some(HttpResponseParser::default()),
parser_buf: BytesMut::new(),
disconnected: false,
drain: None,
decompress: None,
should_decompress: self.req.response_decompress(),
write_state: RunningState::Running,
timeout: Some(timeout),
});
self.state = State::Send(pl);
},
State::Send(mut pl) => {
pl.poll_write()
.map_err(|e| io::Error::new(
io::ErrorKind::Other, format!("{}", e).as_str()))?;
match pl.parse() {
Ok(Async::Ready(mut resp)) => {
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(crate) struct Pipeline {
body: IoBody,
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<Timeout>,
}
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() {
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 fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if self.conn.is_none() {
return Ok(Async::Ready(None))
}
let conn: &mut Connection = unsafe{ mem::transmute(self.conn.as_mut().unwrap())};
let mut need_run = false;
// need write?
match self.poll_write()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?
{
Async::NotReady => need_run = true,
Async::Ready(_) => {
let _ = self.poll_timeout()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?;
}
}
// need read?
if self.parser.is_some() {
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) -> Poll<(), SendRequestError> {
if self.timeout.is_some() {
match self.timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(())) => Err(SendRequestError::Timeout),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(_) => unreachable!()
}
} else {
Ok(Async::NotReady)
}
}
#[inline]
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.disconnected = true;
break
},
Async::Ready(Some(chunk)) => {
self.body = IoBody::Payload(body);
self.writer.write(chunk.into())?
}
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) => {
// info.context = Some(ctx);
self.disconnected = true;
self.writer.write_eof()?;
break 'outter
},
Frame::Chunk(Some(chunk)) =>
res = Some(self.writer.write(chunk)?),
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.disconnected = 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.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() {
conn.close()
}
}
}

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

@ -0,0 +1,700 @@
use std::{fmt, mem};
use std::fmt::Write as FmtWrite;
use std::io::Write;
use std::time::Duration;
use actix::{Addr, Unsync};
use cookie::{Cookie, CookieJar};
use bytes::{Bytes, BytesMut, BufMut};
use futures::Stream;
use serde_json;
use serde::Serialize;
use url::Url;
use percent_encoding::{USERINFO_ENCODE_SET, percent_encode};
use body::Body;
use error::Error;
use header::{ContentEncoding, Header, IntoHeaderValue};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use http::{uri, HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError};
use http::header::{self, HeaderName, HeaderValue};
use super::pipeline::SendRequest;
use super::connector::{Connection, ClientConnector};
/// An HTTP Client Request
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::Future;
/// use actix_web::client::ClientRequest;
///
/// fn main() {
/// let sys = actix::System::new("test");
///
/// actix::Arbiter::handle().spawn({
/// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .finish().unwrap()
/// .send() // <- Send http request
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
/// println!("Response: {:?}", response);
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
/// Ok(())
/// })
/// });
///
/// sys.run();
/// }
/// ```
pub struct ClientRequest {
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<Unsync, 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 {
let res = writeln!(f, "\nClientRequest {:?} {}:{}",
self.version, self.method, self.uri);
let _ = writeln!(f, " headers:");
for (key, val) in self.headers.iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val);
}
res
}
}
/// 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) => {
// set request host header
if let Some(host) = uri.host() {
self.set_header(header::HOST, host);
}
if let Some(parts) = parts(&mut self.request, &self.err) {
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 = parts(&mut self.request, &self.err)
.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 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` header is 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<Unsync, 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.header(header::ACCEPT_ENCODING, "br, gzip, deflate");
} else {
self.header(header::ACCEPT_ENCODING, "gzip, deflate");
}
}
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 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 {
let res = writeln!(f, "\nClientRequestBuilder {:?} {}:{}",
parts.version, parts.method, parts.uri);
let _ = writeln!(f, " headers:");
for (key, val) in parts.headers.iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val);
}
res
} else {
write!(f, "ClientRequestBuilder(Consumed)")
}
}
}
/// Create `ClientRequestBuilder` from `HttpRequest`
///
/// It is useful for proxy requests. This implementation
/// copies all request headers and the method.
impl<'a, S: 'static> From<&'a HttpRequest<S>> for ClientRequestBuilder {
fn from(req: &'a HttpRequest<S>) -> ClientRequestBuilder {
let mut builder = ClientRequest::build();
for (key, value) in req.headers() {
builder.header(key.clone(), value.clone());
}
builder.method(req.method().clone());
builder
}
}

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

@ -0,0 +1,148 @@
use std::{fmt, str};
use std::rc::Rc;
use std::cell::UnsafeCell;
use bytes::Bytes;
use cookie::Cookie;
use futures::{Async, Poll, Stream};
use http::{HeaderMap, StatusCode, Version};
use http::header::{self, HeaderValue};
use httpmessage::HttpMessage;
use error::{CookieParseError, PayloadError};
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(Rc<UnsafeCell<ClientMessage>>, Option<Box<Pipeline>>);
impl HttpMessage for ClientResponse {
/// Get the headers from the response.
#[inline]
fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
}
}
impl ClientResponse {
pub(crate) fn new(msg: ClientMessage) -> ClientResponse {
ClientResponse(Rc::new(UnsafeCell::new(msg)), None)
}
pub(crate) fn set_pipeline(&mut self, pl: Box<Pipeline>) {
self.1 = Some(pl);
}
#[inline]
fn as_ref(&self) -> &ClientMessage {
unsafe{ &*self.0.get() }
}
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
fn as_mut(&self) -> &mut ClientMessage {
unsafe{ &mut *self.0.get() }
}
/// Get the HTTP version of this response.
#[inline]
pub fn version(&self) -> Version {
self.as_ref().version
}
/// Get the status from the server.
#[inline]
pub fn status(&self) -> StatusCode {
self.as_ref().status
}
/// Load response cookies.
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
if self.as_ref().cookies.is_none() {
let msg = self.as_mut();
let mut cookies = Vec::new();
for val in msg.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());
}
msg.cookies = Some(cookies)
}
Ok(self.as_ref().cookies.as_ref().unwrap())
}
/// 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 {
let res = writeln!(
f, "\nClientResponse {:?} {}", self.version(), self.status());
let _ = writeln!(f, " headers:");
for (key, val) in self.headers().iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val);
}
res
}
}
/// Future that resolves to a complete request body.
impl Stream for ClientResponse {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(ref mut pl) = self.1 {
pl.poll()
} else {
Ok(Async::Ready(None))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debug() {
let resp = ClientResponse::new(ClientMessage::default());
resp.as_mut().headers.insert(
header::COOKIE, HeaderValue::from_static("cookie1=value1"));
resp.as_mut().headers.insert(
header::COOKIE, HeaderValue::from_static("cookie2=value2"));
let dbg = format!("{:?}", resp);
assert!(dbg.contains("ClientResponse"));
}
}

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

@ -0,0 +1,367 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::io::{self, Write};
use std::cell::RefCell;
use std::fmt::Write as FmtWrite;
use time::{self, Duration};
use bytes::{BytesMut, BufMut};
use futures::{Async, Poll};
use tokio_io::AsyncWrite;
use http::{Version, HttpTryFrom};
use http::header::{HeaderValue, DATE,
CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
use flate2::Compression;
use flate2::write::{GzEncoder, DeflateEncoder};
#[cfg(feature="brotli")]
use brotli2::write::BrotliEncoder;
use body::{Body, Binary};
use header::ContentEncoding;
use server::WriterState;
use server::shared::SharedBytes;
use server::encoding::{ContentEncoder, TransferEncoding};
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: SharedBytes,
buffer_capacity: usize,
encoder: ContentEncoder,
}
impl HttpClientWriter {
pub fn new(buffer: SharedBytes) -> HttpClientWriter {
let encoder = ContentEncoder::Identity(TransferEncoding::eof(buffer.clone()));
HttpClientWriter {
flags: Flags::empty(),
written: 0,
headers_size: 0,
buffer_capacity: 0,
buffer,
encoder,
}
}
pub fn disconnected(&mut self) {
self.buffer.take();
}
// 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()) {
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)
}
}
impl HttpClientWriter {
pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> {
// prepare task
self.flags.insert(Flags::STARTED);
self.encoder = content_encoder(self.buffer.clone(), msg);
if msg.upgrade() {
self.flags.insert(Flags::UPGRADE);
}
// render message
{
// status line
writeln!(self.buffer, "{} {} {:?}\r",
msg.method(),
msg.uri().path_and_query().map(|u| u.as_str()).unwrap_or("/"),
msg.version())?;
// write headers
let mut buffer = self.buffer.get_mut();
if let Body::Binary(ref bytes) = *msg.body() {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
} else {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE);
}
for (key, value) in msg.headers() {
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(&mut buffer);
buffer.extend_from_slice(b"\r\n\r\n");
} else {
buffer.extend_from_slice(b"\r\n");
}
self.headers_size = 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.encoder.write(bytes)?;
}
} else {
self.buffer_capacity = msg.write_buffer_capacity();
}
}
Ok(())
}
pub fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
self.written += payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) {
if self.flags.contains(Flags::UPGRADE) {
self.buffer.extend(payload);
} else {
self.encoder.write(payload)?;
}
}
if self.buffer.len() > self.buffer_capacity {
Ok(WriterState::Pause)
} else {
Ok(WriterState::Done)
}
}
pub fn write_eof(&mut self) -> io::Result<()> {
self.encoder.write_eof()?;
if self.encoder.is_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: SharedBytes, req: &mut ClientRequest) -> ContentEncoder {
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);
TransferEncoding::length(0, buf)
},
Body::Binary(ref mut bytes) => {
if encoding.is_compression() {
let tmp = SharedBytes::default();
let transfer = TransferEncoding::eof(tmp.clone());
let mut enc = match encoding {
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::default())),
ContentEncoding::Gzip => ContentEncoder::Gzip(
GzEncoder::new(transfer, Compression::default())),
#[cfg(feature="brotli")]
ContentEncoding::Br => ContentEncoder::Br(
BrotliEncoder::new(transfer, 5)),
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
ContentEncoding::Auto => unreachable!()
};
// TODO return error!
let _ = enc.write(bytes.clone());
let _ = enc.write_eof();
*bytes = Binary::from(tmp.take());
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)
},
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);
match encoding {
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::default())),
ContentEncoding::Gzip => ContentEncoder::Gzip(
GzEncoder::new(transfer, Compression::default())),
#[cfg(feature="brotli")]
ContentEncoding::Br => ContentEncoder::Br(
BrotliEncoder::new(transfer, 5)),
ContentEncoding::Identity | ContentEncoding::Auto => ContentEncoder::Identity(transfer),
}
}
fn streaming_encoding(buf: SharedBytes, 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,4 +1,4 @@
use std;
use std::mem;
use std::marker::PhantomData;
use futures::{Async, Future, Poll};
use futures::sync::oneshot::Sender;
@ -6,19 +6,18 @@ use futures::unsync::oneshot;
use smallvec::SmallVec;
use actix::{Actor, ActorState, ActorContext, AsyncContext,
Address, SyncAddress, Handler, Subscriber, ResponseType, SpawnHandle};
Addr, Handler, Message, SpawnHandle, Syn, Unsync};
use actix::fut::ActorFuture;
use actix::dev::{queue, AsyncContextApi,
ContextImpl, ContextProtocol, Envelope, ToEnvelope, RemoteEnvelope};
use actix::dev::{ContextImpl, ToEnvelope, SyncEnvelope};
use body::{Body, Binary};
use error::{Error, Result, ErrorInternalServerError};
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,11 +26,20 @@ pub enum Frame {
Drain(oneshot::Sender<()>),
}
/// Http actor execution context
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]>>,
stream: Option<SmallVec<[Frame; 4]>>,
request: HttpRequest<S>,
disconnected: bool,
}
@ -51,33 +59,36 @@ 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>
{
#[inline]
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
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
{
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> {
#[doc(hidden)]
#[inline]
fn unsync_sender(&mut self) -> queue::unsync::UnboundedSender<ContextProtocol<A>> {
self.inner.unsync_sender()
}
#[inline]
fn unsync_address(&mut self) -> Address<A> {
fn unsync_address(&mut self) -> Addr<Unsync, A> {
self.inner.unsync_address()
}
#[doc(hidden)]
#[inline]
fn sync_address(&mut self) -> SyncAddress<A> {
fn sync_address(&mut self) -> Addr<Syn, A> {
self.inner.sync_address()
}
}
@ -127,7 +138,7 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
}
}
/// Indicate end of streamimng payload. Also this method calls `Self::close`.
/// Indicate end of streaming payload. Also this method calls `Self::close`.
#[inline]
pub fn write_eof(&mut self) {
self.add_frame(Frame::Chunk(None));
@ -153,26 +164,14 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
self.stream = Some(SmallVec::new());
}
self.stream.as_mut().map(|s| s.push(frame));
}
}
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()
self.inner.modify();
}
#[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()
/// Handle of the running future
///
/// SpawnHandle is the handle returned by `AsyncContext::spawn()` method.
pub fn handle(&self) -> SpawnHandle {
self.inner.curr_handle()
}
}
@ -184,15 +183,15 @@ impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>,
self.stop();
}
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 2]>>, Error> {
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
let ctx: &mut HttpContext<A, S> = unsafe {
std::mem::transmute(self as &mut HttpContext<A, S>)
mem::transmute(self as &mut HttpContext<A, S>)
};
if self.inner.alive() {
match self.inner.poll(ctx) {
Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
Err(_) => return Err(ErrorInternalServerError("error").into()),
Err(_) => return Err(ErrorInternalServerError("error")),
}
}
@ -207,16 +206,12 @@ 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<Syn, 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()
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> SyncEnvelope<A> {
SyncEnvelope::new(msg, tx)
}
}
@ -236,10 +231,7 @@ pub struct Drain<A> {
impl<A> Drain<A> {
pub fn new(fut: oneshot::Receiver<()>) -> Self {
Drain {
fut: fut,
_a: PhantomData
}
Drain { fut, _a: PhantomData }
}
}

390
src/de.rs Normal file
View File

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

View File

@ -4,26 +4,25 @@ use std::str::Utf8Error;
use std::string::FromUtf8Error;
use std::io::Error as IoError;
#[cfg(actix_nightly)]
use std::error::Error as StdError;
use cookie;
use httparse;
use failure::Fail;
use actix::MailboxError;
use futures::Canceled;
use failure::{self, Fail, Backtrace};
use http2::Error as Http2Error;
use http::{header, StatusCode, Error as HttpError};
use http::uri::InvalidUriBytes;
use http::uri::InvalidUri;
use http_range::HttpRangeParseError;
use serde::de::value::Error as DeError;
use serde_json::error::Error as JsonError;
use url::ParseError as UrlParseError;
pub use url::ParseError as UrlParseError;
// re-exports
pub use cookie::{ParseError as CookieParseError};
use body::Body;
use handler::Responder;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations
@ -33,9 +32,9 @@ use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed};
pub type Result<T, E=Error> = result::Result<T, E>;
/// General purpose actix web error
#[derive(Fail, Debug)]
pub struct Error {
cause: Box<ResponseError>,
backtrace: Option<Backtrace>,
}
impl Error {
@ -54,7 +53,7 @@ pub trait ResponseError: Fail {
///
/// Internal server error is generated by default.
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty)
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
}
}
@ -64,6 +63,16 @@ impl fmt::Display for Error {
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(bt) = self.cause.backtrace() {
write!(f, "{:?}\n\n{:?}", &self.cause, bt)
} else {
write!(f, "{:?}\n\n{:?}", &self.cause, self.backtrace.as_ref().unwrap())
}
}
}
/// `HttpResponse` for `Error`
impl From<Error> for HttpResponse {
fn from(err: Error) -> Self {
@ -74,21 +83,45 @@ impl From<Error> for HttpResponse {
/// `Error` for any error that implements `ResponseError`
impl<T: ResponseError> From<T> for Error {
fn from(err: T) -> Error {
Error { cause: Box::new(err) }
let backtrace = if err.backtrace().is_none() {
Some(Backtrace::new())
} else {
None
};
Error { cause: Box::new(err), backtrace }
}
}
/// Default error is `InternalServerError`
#[cfg(actix_nightly)]
default impl<T: StdError + Sync + Send + 'static> ResponseError for T {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty)
/// Compatibility for `failure::Error`
impl<T> ResponseError for failure::Compat<T>
where T: fmt::Display + fmt::Debug + Sync + Send + 'static { }
impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error {
err.compat().into()
}
}
/// `InternalServerError` for `JsonError`
impl ResponseError for JsonError {}
/// `InternalServerError` for `UrlParseError`
impl ResponseError for UrlParseError {}
/// Return `BAD_REQUEST` for `de::value::Error`
impl ResponseError for DeError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
/// Return `BAD_REQUEST` for `Utf8Error`
impl ResponseError for Utf8Error {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
/// Return `InternalServerError` for `HttpError`,
/// Response generation can return `HttpError`, so it is internal error
impl ResponseError for HttpError {}
@ -99,21 +132,35 @@ impl ResponseError for io::Error {
fn error_response(&self) -> HttpResponse {
match self.kind() {
io::ErrorKind::NotFound =>
HttpResponse::new(StatusCode::NOT_FOUND, Body::Empty),
HttpResponse::new(StatusCode::NOT_FOUND),
io::ErrorKind::PermissionDenied =>
HttpResponse::new(StatusCode::FORBIDDEN, Body::Empty),
HttpResponse::new(StatusCode::FORBIDDEN),
_ =>
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty)
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
}
}
}
/// `InternalServerError` for `InvalidHeaderValue`
impl ResponseError for header::InvalidHeaderValue {}
/// `BadRequest` for `InvalidHeaderValue`
impl ResponseError for header::InvalidHeaderValue {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
/// `BadRequest` for `InvalidHeaderValue`
impl ResponseError for header::InvalidHeaderValueBytes {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
/// `InternalServerError` for `futures::Canceled`
impl ResponseError for Canceled {}
/// `InternalServerError` for `actix::MailboxError`
impl ResponseError for MailboxError {}
/// A set of errors that can occur during parsing HTTP streams
#[derive(Fail, Debug)]
pub enum ParseError {
@ -122,7 +169,7 @@ pub enum ParseError {
Method,
/// An invalid `Uri`, such as `exam ple.domain`.
#[fail(display="Uri error: {}", _0)]
Uri(InvalidUriBytes),
Uri(InvalidUri),
/// An invalid `HttpVersion`, such as `HTP/1.1`
#[fail(display="Invalid HTTP version specified")]
Version,
@ -153,7 +200,7 @@ pub enum ParseError {
/// Return `BadRequest` for `ParseError`
impl ResponseError for ParseError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
@ -163,6 +210,12 @@ impl From<IoError> for ParseError {
}
}
impl From<InvalidUri> for ParseError {
fn from(err: InvalidUri) -> ParseError {
ParseError::Uri(err)
}
}
impl From<Utf8Error> for ParseError {
fn from(err: Utf8Error) -> ParseError {
ParseError::Utf8(err)
@ -202,9 +255,9 @@ pub enum PayloadError {
/// A payload length is unknown.
#[fail(display="A payload length is unknown.")]
UnknownLength,
/// Parse error
/// Io error
#[fail(display="{}", _0)]
ParseError(#[cause] IoError),
Io(#[cause] IoError),
/// Http2 error
#[fail(display="{}", _0)]
Http2(#[cause] Http2Error),
@ -212,7 +265,7 @@ pub enum PayloadError {
impl From<IoError> for PayloadError {
fn from(err: IoError) -> PayloadError {
PayloadError::ParseError(err)
PayloadError::Io(err)
}
}
@ -222,7 +275,7 @@ impl ResponseError for PayloadError {}
/// Return `BadRequest` for `cookie::ParseError`
impl ResponseError for cookie::ParseError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
@ -242,8 +295,8 @@ pub enum HttpRangeError {
/// Return `BadRequest` for `HttpRangeError`
impl ResponseError for HttpRangeError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(
StatusCode::BAD_REQUEST, Body::from("Invalid Range header provided"))
HttpResponse::with_body(
StatusCode::BAD_REQUEST, "Invalid Range header provided")
}
}
@ -268,6 +321,9 @@ pub enum MultipartError {
/// Multipart boundary is not found
#[fail(display="Multipart boundary is not found")]
Boundary,
/// Multipart stream is incomplete
#[fail(display="Multipart stream is incomplete")]
Incomplete,
/// Error during field parsing
#[fail(display="{}", _0)]
Parse(#[cause] ParseError),
@ -292,7 +348,7 @@ impl From<PayloadError> for MultipartError {
impl ResponseError for MultipartError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
@ -308,57 +364,26 @@ pub enum ExpectError {
}
impl ResponseError for ExpectError {
fn error_response(&self) -> HttpResponse {
HTTPExpectationFailed.with_body("Unknown Expect")
HttpResponse::with_body(StatusCode::EXPECTATION_FAILED, "Unknown Expect")
}
}
/// Websocket handshake errors
/// A set of error that can occure during parsing content type
#[derive(Fail, PartialEq, Debug)]
pub enum WsHandshakeError {
/// Only get method is allowed
#[fail(display="Method not allowed")]
GetMethodRequired,
/// Ugrade header if not set to websocket
#[fail(display="Websocket upgrade is expected")]
NoWebsocketUpgrade,
/// Connection header is not set to upgrade
#[fail(display="Connection upgrade is expected")]
NoConnectionUpgrade,
/// Websocket version header is not set
#[fail(display="Websocket version header is required")]
NoVersionHeader,
/// Unsupported websockt version
#[fail(display="Unsupported version")]
UnsupportedVersion,
/// Websocket key is not set or wrong
#[fail(display="Unknown websocket key")]
BadWebsocketKey,
pub enum ContentTypeError {
/// Can not parse content type
#[fail(display="Can not parse content type")]
ParseError,
/// Unknown content encoding
#[fail(display="Unknown content encoding")]
UnknownEncoding,
}
impl ResponseError for WsHandshakeError {
/// Return `BadRequest` for `ContentTypeError`
impl ResponseError for ContentTypeError {
fn error_response(&self) -> HttpResponse {
match *self {
WsHandshakeError::GetMethodRequired => {
HTTPMethodNotAllowed
.build()
.header(header::ALLOW, "GET")
.finish()
.unwrap()
}
WsHandshakeError::NoWebsocketUpgrade =>
HTTPBadRequest.with_reason("No WebSocket UPGRADE header found"),
WsHandshakeError::NoConnectionUpgrade =>
HTTPBadRequest.with_reason("No CONNECTION upgrade"),
WsHandshakeError::NoVersionHeader =>
HTTPBadRequest.with_reason("Websocket version header is required"),
WsHandshakeError::UnsupportedVersion =>
HTTPBadRequest.with_reason("Unsupported version"),
WsHandshakeError::BadWebsocketKey =>
HTTPBadRequest.with_reason("Handshake error"),
}
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
@ -377,16 +402,26 @@ pub enum UrlencodedError {
/// Content type error
#[fail(display="Content type error")]
ContentType,
/// Parse error
#[fail(display="Parse error")]
Parse,
/// Payload error
#[fail(display="Error that occur during reading payload")]
Payload(PayloadError),
#[fail(display="Error that occur during reading payload: {}", _0)]
Payload(#[cause] PayloadError),
}
/// Return `BadRequest` for `UrlencodedError`
impl ResponseError for UrlencodedError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
match *self {
UrlencodedError::Overflow =>
HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE),
UrlencodedError::UnknownLength =>
HttpResponse::new(StatusCode::LENGTH_REQUIRED),
_ =>
HttpResponse::new(StatusCode::BAD_REQUEST),
}
}
}
@ -406,18 +441,23 @@ pub enum JsonPayloadError {
#[fail(display="Content type error")]
ContentType,
/// Deserialize error
#[fail(display="Json deserialize error")]
Deserialize(JsonError),
#[fail(display="Json deserialize error: {}", _0)]
Deserialize(#[cause] JsonError),
/// Payload error
#[fail(display="Error that occur during reading payload")]
Payload(PayloadError),
#[fail(display="Error that occur during reading payload: {}", _0)]
Payload(#[cause] PayloadError),
}
/// Return `BadRequest` for `UrlencodedError`
impl ResponseError for JsonPayloadError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
match *self {
JsonPayloadError::Overflow =>
HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE),
_ =>
HttpResponse::new(StatusCode::BAD_REQUEST),
}
}
}
@ -452,7 +492,7 @@ pub enum UriSegmentError {
impl ResponseError for UriSegmentError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
HttpResponse::new(StatusCode::BAD_REQUEST)
}
}
@ -478,39 +518,10 @@ impl From<UrlParseError> for UrlGenerationError {
}
}
macro_rules! ERROR_WRAP {
($type:ty, $status:expr) => {
unsafe impl<T> Sync for $type {}
unsafe impl<T> Send for $type {}
impl<T> $type {
pub fn cause(&self) -> &T {
&self.0
}
}
impl<T: fmt::Debug + 'static> Fail for $type {}
impl<T: fmt::Debug + 'static> fmt::Display for $type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl<T> ResponseError for $type
where T: Send + Sync + fmt::Debug + 'static,
{
fn error_response(&self) -> HttpResponse {
HttpResponse::new($status, Body::Empty)
}
}
}
}
/// Helper type that can wrap any error and generate *BAD REQUEST* response.
/// Helper type that can wrap any error and generate custom response.
///
/// In following example any `io::Error` will be converted into "BAD REQUEST" response
/// as oposite to *INNTERNAL SERVER ERROR* which is defined by default.
/// as opposite to *INNTERNAL SERVER ERROR* which is defined by default.
///
/// ```rust
/// # extern crate actix_web;
@ -523,67 +534,166 @@ macro_rules! ERROR_WRAP {
/// }
/// # fn main() {}
/// ```
#[derive(Debug)]
pub struct ErrorBadRequest<T>(pub T);
ERROR_WRAP!(ErrorBadRequest<T>, StatusCode::BAD_REQUEST);
pub struct InternalError<T> {
cause: T,
status: StatusCode,
backtrace: Backtrace,
}
#[derive(Debug)]
/// Helper type that can wrap any error and generate *UNAUTHORIZED* response.
pub struct ErrorUnauthorized<T>(pub T);
ERROR_WRAP!(ErrorUnauthorized<T>, StatusCode::UNAUTHORIZED);
unsafe impl<T> Sync for InternalError<T> {}
unsafe impl<T> Send for InternalError<T> {}
#[derive(Debug)]
/// Helper type that can wrap any error and generate *FORBIDDEN* response.
pub struct ErrorForbidden<T>(pub T);
ERROR_WRAP!(ErrorForbidden<T>, StatusCode::FORBIDDEN);
impl<T> InternalError<T> {
pub fn new(cause: T, status: StatusCode) -> Self {
InternalError {
cause,
status,
backtrace: Backtrace::new(),
}
}
}
#[derive(Debug)]
/// Helper type that can wrap any error and generate *NOT FOUND* response.
pub struct ErrorNotFound<T>(pub T);
ERROR_WRAP!(ErrorNotFound<T>, StatusCode::NOT_FOUND);
impl<T> Fail for InternalError<T>
where T: Send + Sync + fmt::Debug + 'static
{
fn backtrace(&self) -> Option<&Backtrace> {
Some(&self.backtrace)
}
}
#[derive(Debug)]
/// Helper type that can wrap any error and generate *METHOD NOT ALLOWED* response.
pub struct ErrorMethodNotAllowed<T>(pub T);
ERROR_WRAP!(ErrorMethodNotAllowed<T>, StatusCode::METHOD_NOT_ALLOWED);
impl<T> fmt::Debug for InternalError<T>
where T: Send + Sync + fmt::Debug + 'static
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.cause, f)
}
}
#[derive(Debug)]
/// Helper type that can wrap any error and generate *REQUEST TIMEOUT* response.
pub struct ErrorRequestTimeout<T>(pub T);
ERROR_WRAP!(ErrorRequestTimeout<T>, StatusCode::REQUEST_TIMEOUT);
impl<T> fmt::Display for InternalError<T>
where T: Send + Sync + fmt::Debug + 'static
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.cause, f)
}
}
#[derive(Debug)]
/// Helper type that can wrap any error and generate *CONFLICT* response.
pub struct ErrorConflict<T>(pub T);
ERROR_WRAP!(ErrorConflict<T>, StatusCode::CONFLICT);
impl<T> ResponseError for InternalError<T>
where T: Send + Sync + fmt::Debug + 'static
{
fn error_response(&self) -> HttpResponse {
HttpResponse::new(self.status)
}
}
#[derive(Debug)]
/// Helper type that can wrap any error and generate *GONE* response.
pub struct ErrorGone<T>(pub T);
ERROR_WRAP!(ErrorGone<T>, StatusCode::GONE);
impl<T> Responder for InternalError<T>
where T: Send + Sync + fmt::Debug + 'static
{
type Item = HttpResponse;
type Error = Error;
#[derive(Debug)]
/// Helper type that can wrap any error and generate *PRECONDITION FAILED* response.
pub struct ErrorPreconditionFailed<T>(pub T);
ERROR_WRAP!(ErrorPreconditionFailed<T>, StatusCode::PRECONDITION_FAILED);
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
Err(self.into())
}
}
#[derive(Debug)]
/// Helper type that can wrap any error and generate *EXPECTATION FAILED* response.
pub struct ErrorExpectationFailed<T>(pub T);
ERROR_WRAP!(ErrorExpectationFailed<T>, StatusCode::EXPECTATION_FAILED);
/// Helper function that creates wrapper of any error and generate *BAD REQUEST* response.
#[allow(non_snake_case)]
pub fn ErrorBadRequest<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::BAD_REQUEST).into()
}
#[derive(Debug)]
/// Helper type that can wrap any error and generate *INTERNAL SERVER ERROR* response.
pub struct ErrorInternalServerError<T>(pub T);
ERROR_WRAP!(ErrorInternalServerError<T>, StatusCode::INTERNAL_SERVER_ERROR);
/// Helper function that creates wrapper of any error and generate *UNAUTHORIZED* response.
#[allow(non_snake_case)]
pub fn ErrorUnauthorized<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
}
/// Helper function that creates wrapper of any error and generate *FORBIDDEN* response.
#[allow(non_snake_case)]
pub fn ErrorForbidden<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::FORBIDDEN).into()
}
/// Helper function that creates wrapper of any error and generate *NOT FOUND* response.
#[allow(non_snake_case)]
pub fn ErrorNotFound<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::NOT_FOUND).into()
}
/// Helper function that creates wrapper of any error and generate *METHOD NOT ALLOWED* response.
#[allow(non_snake_case)]
pub fn ErrorMethodNotAllowed<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
}
/// Helper function that creates wrapper of any error and generate *REQUEST TIMEOUT* response.
#[allow(non_snake_case)]
pub fn ErrorRequestTimeout<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into()
}
/// Helper function that creates wrapper of any error and generate *CONFLICT* response.
#[allow(non_snake_case)]
pub fn ErrorConflict<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::CONFLICT).into()
}
/// Helper function that creates wrapper of any error and generate *GONE* response.
#[allow(non_snake_case)]
pub fn ErrorGone<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::GONE).into()
}
/// Helper function that creates wrapper of any error and generate *PRECONDITION FAILED* response.
#[allow(non_snake_case)]
pub fn ErrorPreconditionFailed<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
}
/// Helper function that creates wrapper of any error and generate *EXPECTATION FAILED* response.
#[allow(non_snake_case)]
pub fn ErrorExpectationFailed<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *INTERNAL SERVER ERROR* response.
#[allow(non_snake_case)]
pub fn ErrorInternalServerError<T>(err: T) -> Error
where T: Send + Sync + fmt::Debug + 'static
{
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
}
#[cfg(test)]
mod tests {
use std::env;
use std::error::Error as StdError;
use std::io;
use httparse;
use http::{StatusCode, Error as HttpError};
use cookie::ParseError as CookieParseError;
use failure;
use super::*;
#[test]
@ -660,29 +770,13 @@ mod tests {
assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED);
}
#[test]
fn test_wserror_http_response() {
let resp: HttpResponse = WsHandshakeError::GetMethodRequired.error_response();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let resp: HttpResponse = WsHandshakeError::NoWebsocketUpgrade.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::NoConnectionUpgrade.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::NoVersionHeader.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::UnsupportedVersion.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::BadWebsocketKey.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
macro_rules! from {
($from:expr => $error:pat) => {
match ParseError::from($from) {
e @ $error => {
assert!(format!("{}", e).len() >= 5);
} ,
e => panic!("{:?}", e)
e => unreachable!("{:?}", e)
}
}
}
@ -694,7 +788,7 @@ mod tests {
let desc = format!("{}", e.cause().unwrap());
assert_eq!(desc, $from.description().to_owned());
},
_ => panic!("{:?}", $from)
_ => unreachable!("{:?}", $from)
}
}
}
@ -712,4 +806,18 @@ mod tests {
from!(httparse::Error::TooManyHeaders => ParseError::TooLarge);
from!(httparse::Error::Version => ParseError::Version);
}
#[test]
fn failure_error() {
const NAME: &str = "RUST_BACKTRACE";
let old_tb = env::var(NAME);
env::set_var(NAME, "0");
let error = failure::err_msg("Hello!");
let resp: Error = error.into();
assert_eq!(format!("{:?}", resp), "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n");
match old_tb {
Ok(x) => env::set_var(NAME, x),
_ => env::remove_var(NAME),
}
}
}

606
src/extractor.rs Normal file
View File

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

509
src/fs.rs
View File

@ -1,26 +1,41 @@
//! Static files support.
// //! TODO: needs to re-implement actual files handling, current impl blocks
use std::io;
use std::io::Read;
//! Static files support
use std::{io, cmp};
use std::io::{Read, Seek};
use std::fmt::Write;
use std::fs::{File, DirEntry};
use std::fs::{File, DirEntry, Metadata};
use std::path::{Path, PathBuf};
use std::ops::{Deref, DerefMut};
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use bytes::{Bytes, BytesMut, BufMut};
use futures::{Async, Poll, Future, Stream};
use futures_cpupool::{CpuPool, CpuFuture};
use mime_guess::get_mime_type;
use header;
use error::Error;
use param::FromParam;
use handler::{Handler, Responder};
use headers::ContentEncoding;
use handler::{Handler, RouteHandler, WrapHandler, Responder, Reply};
use http::{Method, StatusCode};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use httpcodes::HTTPOk;
/// A file with an associated name; responds with the Content-Type based on the
/// file extension.
#[derive(Debug)]
pub struct NamedFile(PathBuf, File);
pub struct NamedFile {
path: PathBuf,
file: File,
md: Metadata,
modified: Option<SystemTime>,
cpu_pool: Option<CpuPool>,
only_get: bool,
status_code: StatusCode,
}
impl NamedFile {
/// Attempts to open a file in read-only mode.
@ -30,18 +45,30 @@ impl NamedFile {
/// ```rust
/// use actix_web::fs::NamedFile;
///
/// # #[allow(unused_variables)]
/// let file = NamedFile::open("foo.txt");
/// ```
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
let file = File::open(path.as_ref())?;
Ok(NamedFile(path.as_ref().to_path_buf(), file))
let md = file.metadata()?;
let path = path.as_ref().to_path_buf();
let modified = md.modified().ok();
let cpu_pool = None;
Ok(NamedFile{path, file, md, modified, cpu_pool,
only_get: false,
status_code: StatusCode::OK})
}
/// Allow only GET and HEAD methods
#[inline]
pub fn only_get(mut self) -> Self {
self.only_get = true;
self
}
/// Returns reference to the underlying `File` object.
#[inline]
pub fn file(&self) -> &File {
&self.1
&self.file
}
/// Retrieve the path of this file.
@ -52,7 +79,6 @@ impl NamedFile {
/// # use std::io;
/// use actix_web::fs::NamedFile;
///
/// # #[allow(dead_code)]
/// # fn path() -> io::Result<()> {
/// let file = NamedFile::open("test.txt")?;
/// assert_eq!(file.path().as_os_str(), "foo.txt");
@ -61,7 +87,43 @@ impl NamedFile {
/// ```
#[inline]
pub fn path(&self) -> &Path {
self.0.as_path()
self.path.as_path()
}
/// Set `CpuPool` to use
#[inline]
pub fn set_cpu_pool(mut self, cpu_pool: CpuPool) -> Self {
self.cpu_pool = Some(cpu_pool);
self
}
/// Set response **Status Code**
pub fn set_status_code(mut self, status: StatusCode) -> Self {
self.status_code = status;
self
}
fn etag(&self) -> Option<header::EntityTag> {
// This etag format is similar to Apache's.
self.modified.as_ref().map(|mtime| {
let ino = {
#[cfg(unix)]
{ self.md.ino() }
#[cfg(not(unix))]
{ 0 }
};
let dur = mtime.duration_since(UNIX_EPOCH)
.expect("modification time must be after epoch");
header::EntityTag::strong(
format!("{:x}:{:x}:{:x}:{:x}",
ino, self.md.len(), dur.as_secs(),
dur.subsec_nanos()))
})
}
fn last_modified(&self) -> Option<header::HttpDate> {
self.modified.map(|mtime| mtime.into())
}
}
@ -69,30 +131,181 @@ impl Deref for NamedFile {
type Target = File;
fn deref(&self) -> &File {
&self.1
&self.file
}
}
impl DerefMut for NamedFile {
fn deref_mut(&mut self) -> &mut File {
&mut self.1
&mut self.file
}
}
/// Returns true if `req` has no `If-Match` header or one which matches `etag`.
fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfMatch>() {
None | Some(header::IfMatch::Any) => true,
Some(header::IfMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.strong_eq(some_etag) {
return true;
}
}
}
false
}
}
}
/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`.
fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfNoneMatch>() {
Some(header::IfNoneMatch::Any) => false,
Some(header::IfNoneMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.weak_eq(some_etag) {
return false;
}
}
}
true
}
None => true,
}
}
impl Responder for NamedFile {
type Item = HttpResponse;
type Error = io::Error;
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> {
let mut resp = HTTPOk.build();
resp.content_encoding(ContentEncoding::Identity);
if let Some(ext) = self.path().extension() {
let mime = get_mime_type(&ext.to_string_lossy());
resp.content_type(format!("{}", mime).as_str());
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
if self.status_code != StatusCode::OK {
let mut resp = HttpResponse::build(self.status_code);
resp.if_some(self.path().extension(), |ext, resp| {
resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
});
let reader = ChunkedReadFile {
size: self.md.len(),
offset: 0,
cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()),
file: Some(self.file),
fut: None,
};
return Ok(resp.streaming(reader))
}
if self.only_get && *req.method() != Method::GET && *req.method() != Method::HEAD {
return Ok(HttpResponse::MethodNotAllowed()
.header(header::CONTENT_TYPE, "text/plain")
.header(header::ALLOW, "GET, HEAD")
.body("This resource only supports GET and HEAD."))
}
let etag = self.etag();
let last_modified = self.last_modified();
// check preconditions
let precondition_failed = if !any_match(etag.as_ref(), &req) {
true
} else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) =
(last_modified, req.get_header())
{
m > since
} else {
false
};
// check last modified
let not_modified = if !none_match(etag.as_ref(), &req) {
true
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
(last_modified, req.get_header())
{
m <= since
} else {
false
};
let mut resp = HttpResponse::build(self.status_code);
resp
.if_some(self.path().extension(), |ext, resp| {
resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
})
.if_some(last_modified, |lm, resp| {resp.set(header::LastModified(lm));})
.if_some(etag, |etag, resp| {resp.set(header::ETag(etag));});
if precondition_failed {
return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish())
} else if not_modified {
return Ok(resp.status(StatusCode::NOT_MODIFIED).finish())
}
if *req.method() == Method::HEAD {
Ok(resp.finish())
} else {
let reader = ChunkedReadFile {
size: self.md.len(),
offset: 0,
cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()),
file: Some(self.file),
fut: None,
};
Ok(resp.streaming(reader))
}
}
}
/// A helper created from a `std::fs::File` which reads the file
/// chunk-by-chunk on a `CpuPool`.
pub struct ChunkedReadFile {
size: u64,
offset: u64,
cpu_pool: CpuPool,
file: Option<File>,
fut: Option<CpuFuture<(File, Bytes), io::Error>>,
}
impl Stream for ChunkedReadFile {
type Item = Bytes;
type Error= Error;
fn poll(&mut self) -> Poll<Option<Bytes>, Error> {
if self.fut.is_some() {
return match self.fut.as_mut().unwrap().poll()? {
Async::Ready((file, bytes)) => {
self.fut.take();
self.file = Some(file);
self.offset += bytes.len() as u64;
Ok(Async::Ready(Some(bytes)))
},
Async::NotReady => Ok(Async::NotReady),
};
}
let size = self.size;
let offset = self.offset;
if size == offset {
Ok(Async::Ready(None))
} else {
let mut file = self.file.take().expect("Use after completion");
self.fut = Some(self.cpu_pool.spawn_fn(move || {
let max_bytes = cmp::min(size.saturating_sub(offset), 65_536) as usize;
let mut buf = BytesMut::with_capacity(max_bytes);
file.seek(io::SeekFrom::Start(offset))?;
let nbytes = file.read(unsafe{buf.bytes_mut()})?;
if nbytes == 0 {
return Err(io::ErrorKind::UnexpectedEof.into())
}
unsafe{buf.advance_mut(nbytes)};
Ok((file, buf.freeze()))
}));
self.poll()
}
let mut data = Vec::new();
let _ = self.1.read_to_end(&mut data);
Ok(resp.body(data).unwrap())
}
}
@ -105,10 +318,7 @@ pub struct Directory{
impl Directory {
pub fn new(base: PathBuf, path: PathBuf) -> Directory {
Directory {
base: base,
path: path
}
Directory { base, path }
}
fn can_list(&self, entry: &io::Result<DirEntry>) -> bool {
@ -167,61 +377,42 @@ impl Responder for Directory {
<ul>\
{}\
</ul></body>\n</html>", index_of, index_of, body);
Ok(HTTPOk.build()
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(html).unwrap())
.body(html))
}
}
/// This enum represents all filesystem elements.
pub enum FilesystemElement {
File(NamedFile),
Directory(Directory),
}
impl Responder for FilesystemElement {
type Item = HttpResponse;
type Error = io::Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
match self {
FilesystemElement::File(file) => file.respond_to(req),
FilesystemElement::Directory(dir) => dir.respond_to(req),
}
}
}
/// Static files handling
///
/// `StaticFile` handler must be registered with `Application::handler()` method,
/// `StaticFile` handler must be registered with `App::handler()` method,
/// because `StaticFile` handler requires access sub-path information.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{fs, Application};
/// use actix_web::{fs, App};
///
/// fn main() {
/// let app = Application::new()
/// .handler("/static", fs::StaticFiles::new(".", true))
/// let app = App::new()
/// .handler("/static", fs::StaticFiles::new("."))
/// .finish();
/// }
/// ```
pub struct StaticFiles {
pub struct StaticFiles<S> {
directory: PathBuf,
accessible: bool,
index: Option<String>,
show_index: bool,
cpu_pool: CpuPool,
default: Box<RouteHandler<S>>,
_chunk_size: usize,
_follow_symlinks: bool,
}
impl StaticFiles {
/// Create new `StaticFiles` instance
///
/// `dir` - base directory
///
/// `index` - show index for directory
pub fn new<D: Into<PathBuf>>(dir: D, index: bool) -> StaticFiles {
impl<S: 'static> StaticFiles<S> {
/// Create new `StaticFiles` instance for specified base directory.
pub fn new<T: Into<PathBuf>>(dir: T) -> StaticFiles<S> {
let dir = dir.into();
let (dir, access) = match dir.canonicalize() {
@ -242,41 +433,83 @@ impl StaticFiles {
StaticFiles {
directory: dir,
accessible: access,
show_index: index,
index: None,
show_index: false,
cpu_pool: CpuPool::new(40),
default: Box::new(WrapHandler::new(
|_| HttpResponse::new(StatusCode::NOT_FOUND))),
_chunk_size: 0,
_follow_symlinks: false,
}
}
/// Show files listing for directories.
///
/// By default show files listing is disabled.
pub fn show_files_listing(mut self) -> Self {
self.show_index = true;
self
}
/// Set index file
///
/// Redirects to specific index file for directory "/" instead of
/// showing files listing.
pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles<S> {
self.index = Some(index.into());
self
}
/// Sets default handler which is used when no matched file could be found.
pub fn default_handler<H: Handler<S>>(mut self, handler: H) -> StaticFiles<S> {
self.default = Box::new(WrapHandler::new(handler));
self
}
}
impl<S> Handler<S> for StaticFiles {
type Result = Result<FilesystemElement, io::Error>;
impl<S: 'static> Handler<S> for StaticFiles<S> {
type Result = Result<Reply, Error>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
if !self.accessible {
Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
Ok(self.default.handle(req))
} else {
let path = if let Some(path) = req.match_info().get("tail") {
path
} else {
return Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
let relpath = match req.match_info().get("tail").map(PathBuf::from_param) {
Some(Ok(path)) => path,
_ => return Ok(self.default.handle(req))
};
let relpath = PathBuf::from_param(path)
.map_err(|_| io::Error::new(io::ErrorKind::NotFound, "not found"))?;
// full filepath
let path = self.directory.join(&relpath).canonicalize()?;
if path.is_dir() {
if self.show_index {
Ok(FilesystemElement::Directory(Directory::new(self.directory.clone(), path)))
if let Some(ref redir_index) = self.index {
// TODO: Don't redirect, just return the index content.
// TODO: It'd be nice if there were a good usable URL manipulation library
let mut new_path: String = req.path().to_owned();
for el in relpath.iter() {
new_path.push_str(&el.to_string_lossy());
new_path.push('/');
}
if !new_path.ends_with('/') {
new_path.push('/');
}
new_path.push_str(redir_index);
HttpResponse::Found()
.header(header::LOCATION, new_path.as_str())
.finish()
.respond_to(req.without_state())
} else if self.show_index {
Directory::new(self.directory.clone(), path)
.respond_to(req.without_state())?
.respond_to(req.without_state())
} else {
Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
Ok(self.default.handle(req))
}
} else {
Ok(FilesystemElement::File(NamedFile::open(path)?))
NamedFile::open(path)?.set_cpu_pool(self.cpu_pool.clone())
.respond_to(req.without_state())?
.respond_to(req.without_state())
}
}
}
@ -285,12 +518,15 @@ impl<S> Handler<S> for StaticFiles {
#[cfg(test)]
mod tests {
use super::*;
use http::header;
use application::App;
use test::{self, TestRequest};
use http::{header, Method, StatusCode};
#[test]
fn test_named_file() {
assert!(NamedFile::open("test--").is_err());
let mut file = NamedFile::open("Cargo.toml").unwrap();
let mut file = NamedFile::open("Cargo.toml").unwrap()
.set_cpu_pool(CpuPool::new(1));
{ file.file();
let _f: &File = &file; }
{ let _f: &mut File = &mut file; }
@ -299,23 +535,130 @@ mod tests {
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml")
}
#[test]
fn test_named_file_status_code() {
let mut file = NamedFile::open("Cargo.toml").unwrap()
.set_status_code(StatusCode::NOT_FOUND)
.set_cpu_pool(CpuPool::new(1));
{ file.file();
let _f: &File = &file; }
{ let _f: &mut File = &mut file; }
let resp = file.respond_to(HttpRequest::default()).unwrap();
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml");
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_named_file_not_allowed() {
let req = TestRequest::default().method(Method::POST).finish();
let file = NamedFile::open("Cargo.toml").unwrap();
let resp = file.only_get().respond_to(req).unwrap();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
}
#[test]
fn test_named_file_any_method() {
let req = TestRequest::default().method(Method::POST).finish();
let file = NamedFile::open("Cargo.toml").unwrap();
let resp = file.respond_to(req).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_static_files() {
let mut st = StaticFiles::new(".", true);
let mut st = StaticFiles::new(".").show_files_listing();
st.accessible = false;
assert!(st.handle(HttpRequest::default()).is_err());
let resp = st.handle(HttpRequest::default()).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
st.accessible = true;
st.show_index = false;
assert!(st.handle(HttpRequest::default()).is_err());
let resp = st.handle(HttpRequest::default()).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "");
st.show_index = true;
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8");
assert!(resp.body().is_binary());
assert!(format!("{:?}", resp.body()).contains("README.md"));
}
#[test]
fn test_redirect_to_index() {
let mut st = StaticFiles::new(".").index_file("index.html");
let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "guide");
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");
let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "guide/");
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");
}
#[test]
fn test_redirect_to_index_nested() {
let mut st = StaticFiles::new(".").index_file("Cargo.toml");
let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "examples/basics");
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
let resp = resp.as_response().expect("HTTP Response");
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/examples/basics/Cargo.toml");
}
#[test]
fn integration_redirect_to_index_with_prefix() {
let mut srv = test::TestServer::with_factory(
|| App::new()
.prefix("public")
.handler("/", StaticFiles::new(".").index_file("Cargo.toml")));
let request = srv.get().uri(srv.url("/public")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND);
let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap();
assert_eq!(loc, "/public/Cargo.toml");
let request = srv.get().uri(srv.url("/public/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND);
let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap();
assert_eq!(loc, "/public/Cargo.toml");
}
#[test]
fn integration_redirect_to_index() {
let mut srv = test::TestServer::with_factory(
|| App::new()
.handler("test", StaticFiles::new(".").index_file("Cargo.toml")));
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND);
let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap();
assert_eq!(loc, "/test/Cargo.toml");
let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::FOUND);
let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap();
assert_eq!(loc, "/test/Cargo.toml");
}
}

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