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

Compare commits

...

678 Commits

Author SHA1 Message Date
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
2b0f3d2a9a prepare release 2018-01-13 16:57:01 -08:00
93fdb596d4 Allow to explicitly disable chunked encoding 2018-01-13 16:17:33 -08:00
305666067e Do not enable chunked encoding for HTTP/1.0 2018-01-13 12:46:43 -08:00
b805d87ee7 no need for custom cookie module 2018-01-13 11:33:42 -08:00
bc6bb9984f user guide spelling 2018-01-13 11:17:48 -08:00
c043fd7912 Merge pull request #47 from belltoy/master
fix directory entry path
2018-01-13 11:16:53 -08:00
781282897a fix directory entry path 2018-01-13 08:37:27 +00:00
a9c71b2894 add link to cors middleware 2018-01-12 13:10:12 -08:00
edd26837dd update dependency specs in user guide 2018-01-12 12:54:57 -08:00
3105bca13b use cookie-rs released create 2018-01-12 12:32:54 -08:00
c470e7a02b use flate2 released crate 2018-01-12 12:31:33 -08:00
8a96e8fdd0 disable compression for static files 2018-01-11 23:49:53 -08:00
e919ec485e cleanup http channel 2018-01-11 22:06:06 -08:00
e482b88741 refactor http protocol selection procedure 2018-01-11 21:48:36 -08:00
eb8052b936 fix cors tests 2018-01-11 20:20:50 -08:00
dab918261c fix cors allowed header validation 2018-01-11 20:11:34 -08:00
11342e4566 add link to gitter 2018-01-11 18:49:30 -08:00
f7b895b53a add link to github 2018-01-11 18:47:34 -08:00
ac89880c0a move encoding to server 2018-01-11 18:41:33 -08:00
8a058efb4e move server protocol impl to submodule 2018-01-11 18:35:05 -08:00
fa93701bee upgrade packages 2018-01-11 16:47:55 -08:00
0707dfe5bb flush stream on drain 2018-01-11 16:22:27 -08:00
0a41ecd01d disable test 2018-01-11 15:38:57 -08:00
0648ad6f33 fix implicit chunked encoding 2018-01-11 15:26:46 -08:00
728d4f1f57 clean cargo before running skeptic tests 2018-01-11 11:39:17 -08:00
d152860fa7 add Cors::register method 2018-01-11 11:14:18 -08:00
43f14224b1 properly enable encoding tests 2018-01-10 22:42:26 -08:00
f7d9b45e64 travis config 2018-01-10 21:49:23 -08:00
448b73a4b5 encoding tests 2018-01-10 21:47:30 -08:00
1a31554ee6 travis config 2018-01-10 21:02:28 -08:00
49cdddf479 upgrade flate package 2018-01-10 20:28:06 -08:00
aed90ed458 rename payload 2018-01-10 20:08:13 -08:00
e0faf3f69c refactor pipeline impl 2018-01-10 16:45:57 -08:00
f7807e43d8 cleanup Binary type; more cors tests 2018-01-10 15:28:33 -08:00
fee54d1de0 tests for cors response 2018-01-10 14:56:45 -08:00
1445cc7a2c test for cors 2018-01-10 14:21:48 -08:00
16e9512457 better names for cors errors 2018-01-10 14:20:00 -08:00
615db0d9d8 complete cors implementation 2018-01-10 13:41:33 -08:00
3f3dcf413b move websocket code to submodule 2018-01-10 11:13:29 -08:00
d85081b64e update websocket examples 2018-01-10 10:40:14 -08:00
4b72a1b325 create custom WebsocketContext for websocket connection 2018-01-10 10:12:34 -08:00
8aae2daafa update example 2018-01-10 07:55:25 -08:00
d7f59ce481 add cors preflight request support 2018-01-09 23:55:42 -08:00
ce78f17a79 refactor Middleware trait, use Result 2018-01-09 22:48:35 -08:00
16310a5ebd initial work on cors middleware 2018-01-09 22:33:51 -08:00
e8412672a2 add resource level middlewares support 2018-01-09 20:00:18 -08:00
6c7dda495b add very simple http/2 test 2018-01-09 12:49:46 -08:00
584d0c9e99 Merge branch 'master' of github.com:actix/actix-web 2018-01-09 10:08:14 -08:00
a159a9cd6e cleanup doc tests 2018-01-09 10:08:06 -08:00
9d4e926302 Merge pull request #45 from ami44/master
fix url
2018-01-08 11:33:36 -08:00
41c94a1220 fix url 2018-01-08 19:10:47 +01:00
c7798ef45d update examples 2018-01-07 19:40:42 -08:00
d696c1692e Merge branch 'master' of github.com:actix/actix-web 2018-01-07 19:10:55 -08:00
f90bc0caae do no stop on write_eof 2018-01-07 19:10:42 -08:00
f802fe09e6 fix context poll 2018-01-07 17:13:49 -08:00
513fcd4a47 Update README.md 2018-01-07 08:33:06 -08:00
c28d052d22 Merge pull request #44 from krircc/master
IT's not true, actix-web are more features than other rust web frameworks
2018-01-07 08:32:08 -08:00
550f68ca05 Merge branch 'master' into master 2018-01-07 08:31:57 -08:00
3074071d03 Merge branch 'master' into master 2018-01-07 08:31:31 -08:00
3ffefb8b29 Merge pull request #42 from ami44/master
upd examples basics
2018-01-07 08:30:42 -08:00
3cf2fbbb23 Merge branch 'master' into master 2018-01-07 12:00:24 +01:00
896981cdf8 update examples 2018-01-06 23:22:10 -08:00
71da72efdb use general context impl 2018-01-06 22:59:39 -08:00
5e9b94a6dd Update README.md 2018-01-07 11:07:51 +08:00
665f4edf6b upd examples basics 2018-01-06 16:43:59 +01:00
247c23c1ea no need for StreamHandler 2018-01-06 01:06:35 -08:00
3ed9e872ad subscriber to os signals automatically 2018-01-05 16:32:36 -08:00
473ec38439 use dev cookies package as temp solution for ring problem 2018-01-05 14:50:33 -08:00
524493e0b0 pin nightly 2018-01-05 14:29:40 -08:00
5ae646332e update example to use actix 0.4 2018-01-05 14:01:19 -08:00
5ff35f5b99 upgrade to actix 0.4 2018-01-05 13:30:21 -08:00
dea354d6d8 fix basic example in guide 2018-01-04 16:21:18 -08:00
20d5c61c11 set nodelay for streams 2018-01-04 09:32:47 -08:00
91230afc44 fix time calculations 2018-01-04 09:32:33 -08:00
afeffe4b19 encode returns result 2018-01-04 09:32:15 -08:00
fdf7726831 update changelog 2018-01-03 23:59:12 -08:00
9559f6a175 introduce IoStream trait for low level stream operations 2018-01-03 23:41:55 -08:00
1f7aee23df shutdown io streams before exit 2018-01-03 22:43:44 -08:00
bf11bfed8e use explicit actix:: mod 2018-01-03 19:11:40 -08:00
e439d0546b * fix force_close
* shutdown io before exit

* fix response creation with body from pool
2018-01-03 18:21:34 -08:00
8348c830e2 no need for mut ref 2018-01-03 10:57:57 -08:00
ae084d1146 added helper future for reading request body 2018-01-03 09:23:58 -08:00
88031b7fde remove debug prints 2018-01-03 09:00:22 -08:00
70ea43b3c0 fix drain support for actor; make pattern more reusable 2018-01-02 23:43:17 -08:00
9e6d090fd0 update readme example 2018-01-02 19:57:25 -08:00
7af3b3f956 update example 2018-01-02 19:43:59 -08:00
3a59344ffb update h2 lib 2018-01-02 19:37:33 -08:00
3768a2885d fix examples 2018-01-02 15:52:11 -08:00
f0fdcc9936 handle application prefix for handlers; use handler for StaticFiles 2018-01-02 15:23:31 -08:00
77ba1de305 flush encoder 2018-01-02 14:53:51 -08:00
fb2c78d9fc add hello-world example 2018-01-02 13:42:30 -08:00
b49eadf7e5 fix content length serialization #33 2018-01-02 13:39:32 -08:00
9040f588af allow to handle entire sub path 2018-01-02 13:09:02 -08:00
284b59722a update websocket example 2018-01-01 09:31:42 -08:00
e798af26a2 Merge pull request #27 from ami44/master
move examples/websocket.rs to examples/websocket
2018-01-01 12:26:07 +01:00
fc88bb294a Merge remote-tracking branch 'upstream/master' 2018-01-01 12:22:03 +01:00
f3a90a2829 add example to workspace 2017-12-31 22:22:56 -08:00
d2f54b7d19 use workspace 2017-12-31 21:55:25 -08:00
8e89ff1d1e fix examples 2017-12-31 20:08:35 -08:00
cc38b30f7b refactor http actor usage 2017-12-31 17:26:32 -08:00
967d3244d7 fix http/2 support 2017-12-31 13:22:11 -08:00
d8548ad83b update examples/diesel readme 2017-12-31 08:12:26 +01:00
5741b8b372 add examples/websocket 2017-12-30 22:43:10 +01:00
7962d28a6d move examples/websocket.rs to examples/websocket 2017-12-30 21:47:39 +01:00
e18f9f3f3a Merge pull request #1 from actix/master
Update from original
2017-12-30 21:27:48 +01:00
73e2773a10 minor fix guide/ 2017-12-30 21:13:23 +01:00
c998c75515 move examples/state.rs to examples/state 2017-12-30 21:08:54 +01:00
f1f5b23e77 fix readme examples/signal 2017-12-30 21:08:12 +01:00
76b03851e6 fix examples - disable signal if windows 2017-12-30 21:05:03 +01:00
87188e1505 minor fix examples/websocket-chat 2017-12-30 16:50:49 +01:00
df393df547 move example/basic.rs to examples/basic 2017-12-30 16:50:17 +01:00
a1dc5a6bd1 update examples/tls/README 2017-12-30 16:24:50 +01:00
8e580ef7b9 add README examples/template_tera 2017-12-30 16:10:29 +01:00
12345004dd add README examples/signals 2017-12-30 16:10:00 +01:00
a1a77600c6 add README example/multipart 2017-12-30 16:09:39 +01:00
d7d9e8c0e9 update json example 2017-12-30 16:08:18 +01:00
e93af57fa7 add json example link 2017-12-30 16:07:39 +01:00
a166fc82f4 add json-rust example 2017-12-30 15:24:12 +01:00
2d769f805a add diesel+postgresql link 2017-12-30 12:21:34 +01:00
6ea894547d better application handling, fix url_for method for routes with prefix 2017-12-29 14:04:13 -08:00
491d43aa8c update tests 2017-12-29 11:49:36 -08:00
1baead993a call poll_io recursevely aftre drain completion 2017-12-29 11:45:56 -08:00
3d3e4dae9a refactor IntoHttpHandler trait 2017-12-29 11:33:04 -08:00
1d195a2cf2 make Pipeline private 2017-12-29 09:16:50 -08:00
d87fafb563 fix and refactor middleware runner 2017-12-29 01:01:31 -08:00
308df19865 update readme 2017-12-28 16:27:08 -08:00
538fea8027 add graceful shutdown system 2017-12-28 16:25:47 -08:00
3f4898a6d1 add StopWorker message 2017-12-28 13:07:29 -08:00
6a2bb9a473 split worker code to separate module 2017-12-28 12:38:37 -08:00
d8b0ce88a5 fix guide example 2017-12-28 12:27:46 -08:00
783e19c1bf fix RequestSession impl for HttpRequest 2017-12-28 11:43:45 -08:00
d80a0c9f94 add support for unix signals 2017-12-28 11:36:20 -08:00
02b37570f4 flaky test 2017-12-28 09:11:25 -08:00
820404cdd2 Merge pull request #20 from ami44/master
set session name
2017-12-28 04:23:41 -08:00
27b0dfd761 set sessio name 2017-12-28 13:02:46 +01:00
b714e1f4ce fix example 2017-12-27 19:29:04 -08:00
093d0bae40 Param ctor is private 2017-12-27 19:19:28 -08:00
8941557da6 add parameter container iterator 2017-12-27 19:09:36 -08:00
6bb893deab use Params object for query 2017-12-27 19:02:29 -08:00
19e1c1b75b use Cow for Params type 2017-12-27 18:41:09 -08:00
556de72932 add server spawn method 2017-12-27 17:49:10 -08:00
4d741b4de5 Fix typos 2017-12-27 13:26:31 -08:00
0589f2ee49 add server management commands 2017-12-27 12:58:32 -08:00
da8aa8b988 use mio for accept loop 2017-12-27 11:22:27 -08:00
be1cd2936d check example in 1.20 2017-12-27 09:49:59 -08:00
e1fb32c6e5 Update .travis.yml 2017-12-27 07:36:24 -08:00
5df5cc7374 fix guide example 2017-12-26 21:33:23 -08:00
0d21c2da22 various typos 2017-12-26 21:07:51 -08:00
183bcd38f8 modify unused_addr method; update websockt guide section 2017-12-26 20:52:21 -08:00
3abd0db6b1 restore server start test 2017-12-26 20:07:31 -08:00
29adc20581 rename module 2017-12-26 19:59:41 -08:00
743235b8fd add unit test helper 2017-12-26 19:48:02 -08:00
7f77ba557d add testing section to guide 2017-12-26 17:14:37 -08:00
d3b7d2d6b3 allow to use application factory for test server 2017-12-26 16:47:55 -08:00
f6510161b5 add simple TestServer for integrational tests cases 2017-12-26 16:35:00 -08:00
e3b0f02794 fix type for disable feartures 2017-12-26 15:17:20 -08:00
9521de5746 HttpServer::addrs() return all bound socket addresses 2017-12-26 14:45:38 -08:00
dd3a2aa68a add HttpServer::server_hostname method 2017-12-26 14:36:03 -08:00
cce9c68a10 add doc string 2017-12-26 12:46:27 -08:00
5e17a846af add notes on sync primitives 2017-12-26 11:19:08 -08:00
030a70841a add link to gitter 2017-12-26 11:04:25 -08:00
e4bfef9d26 fix tests 2017-12-26 09:28:24 -08:00
cf8c2ca95e refactor Handler trait, use mut self 2017-12-26 09:00:45 -08:00
b61a07a320 more info for middleware guide 2017-12-26 07:58:21 -08:00
ffb5742b71 fix tests 2017-12-25 19:42:55 -08:00
465a87a7cf right version 2017-12-25 13:44:50 -08:00
5b65987f6a write response optimizations 2017-12-25 13:40:06 -08:00
89c9dfb5bc update getting started guide section 2017-12-25 08:19:33 -08:00
a578262f73 update json example and guide info 2017-12-25 08:12:13 -08:00
b0c8fa03f0 use specific nightly version for appveyor 2017-12-25 07:33:05 -08:00
98b0e023f3 optimize payload detection 2017-12-25 07:31:12 -08:00
012d55e424 Set nightly version 2017-12-25 05:49:56 -08:00
f1e82ebc1e better connect handling 2017-12-24 16:15:40 -08:00
ddd9c24bb2 optimize payload type detection 2017-12-24 14:29:19 -08:00
9f9c75d832 simplify drain feature 2017-12-24 11:58:09 -08:00
eaab28cd3b proper fix for compression 2017-12-21 12:57:59 -08:00
c35d294611 fix compression 2017-12-21 12:54:18 -08:00
18f3841783 update test 2017-12-20 23:36:52 -08:00
0567e6fb0a fix typos in guide 2017-12-20 23:27:30 -08:00
55534bff8c simplify guide examples 2017-12-20 23:21:26 -08:00
bca1dd4f9e update doc strings 2017-12-20 23:19:21 -08:00
0a68811dce cleanup more examples 2017-12-20 21:06:04 -08:00
406d2c41e9 add doc string 2017-12-20 20:56:17 -08:00
63ddc07ccb added JsonBody future 2017-12-20 20:30:54 -08:00
33b2be3281 move json responder to separate module 2017-12-20 17:51:28 -08:00
bf23aa5d4b move db code to separate module 2017-12-20 17:44:19 -08:00
3c5fd18e02 cleanup examples 2017-12-20 16:32:31 -08:00
4dd3382ac7 update example 2017-12-20 16:13:21 -08:00
50891986bc simplify json example 2017-12-20 16:05:07 -08:00
df2aa42dad cleanup example 2017-12-20 15:45:26 -08:00
c36ad06332 more general Responder implementaiton for response future 2017-12-20 15:26:28 -08:00
821c96c37c check json example during travis build 2017-12-20 15:20:28 -08:00
cbb81bc747 json request example 2017-12-20 15:12:43 -08:00
79f047f5be remove box from predicates 2017-12-20 13:23:50 -08:00
813b56ebe5 make async handler future more generic 2017-12-20 12:51:39 -08:00
c3a39e026d Merge branch 'master' of github.com:actix/actix-web 2017-12-20 11:37:36 -08:00
e05596b65d upgrade actix min version 2017-12-20 11:37:27 -08:00
65767558fb Update README.md 2017-12-20 08:00:28 -08:00
7fc7d6e17a update guide 2017-12-19 22:36:06 -08:00
c47e2ccfee update guide examples 2017-12-19 18:44:17 -08:00
d0c01c2cdd update guide example 2017-12-19 18:38:02 -08:00
50b2f62c80 update guide section about ssl 2017-12-19 18:36:29 -08:00
0a96b8c579 update readme 2017-12-19 16:17:27 -08:00
d41aade0b7 update readme 2017-12-19 16:14:47 -08:00
626999bcc9 update doc strings 2017-12-19 16:09:19 -08:00
64d867d9a1 update session guide section 2017-12-19 15:44:25 -08:00
1596f4db73 refactor url encoded body parsing 2017-12-19 14:03:01 -08:00
fa2a3bc55e make method private 2017-12-19 13:11:19 -08:00
52c9865716 split examples check 2017-12-19 12:22:11 -08:00
db7bd962cb fix some doc strings 2017-12-19 11:46:11 -08:00
f858fa7a32 Merge branch 'travis-test' 2017-12-19 11:35:16 -08:00
2bad99b645 better query() method impl; update doc strings 2017-12-19 11:34:51 -08:00
566066e855 check examples 2017-12-19 10:56:48 -08:00
009874125e add client.py comments 2017-12-19 10:25:23 -08:00
2e790dfcc6 add multipart guide section 2017-12-19 10:10:03 -08:00
e3f9345420 multipart field is stream of bytes 2017-12-19 09:55:49 -08:00
790793f8a1 refactor multipart stream creation 2017-12-19 09:51:28 -08:00
13cbfc877d simplify server start method 2017-12-19 09:08:36 -08:00
4f6145e5c7 fix typos 2017-12-19 00:29:25 -08:00
f3b853f224 refactor payload 2017-12-19 00:18:57 -08:00
0cab873066 make payload sender public 2017-12-18 21:58:38 -08:00
64dc6c5771 fix typos 2017-12-18 20:03:42 -08:00
669975df75 fix typos 2017-12-18 20:00:57 -08:00
56fd088163 added database integration guide section 2017-12-18 19:38:16 -08:00
2124730e0a guide update 2017-12-18 18:56:58 -08:00
e9a3845e26 update license in readme 2017-12-18 16:48:30 -08:00
3f0e7456c0 update examples links 2017-12-18 16:42:58 -08:00
1e1da5832f better name 2017-12-18 16:40:33 -08:00
625c4ad0db add more comments 2017-12-18 16:30:35 -08:00
fde94bfe95 added diesel example 2017-12-18 16:25:26 -08:00
3e8a6c3988 add tera example 2017-12-18 13:41:52 -08:00
26af6040ff update tests 2017-12-18 13:26:43 -08:00
9ed4159c0c update examples 2017-12-18 13:06:41 -08:00
27d92f3a23 refactor server bind and start process 2017-12-17 12:35:04 -08:00
4b421b44a2 add mit license 2017-12-17 10:08:44 -08:00
1a51f75ecc update readme 2017-12-17 10:03:37 -08:00
9821c6ea90 update readme 2017-12-16 11:39:56 -08:00
167717d20e update readme 2017-12-16 11:22:39 -08:00
91ffab8f6e update readme 2017-12-16 07:30:53 -08:00
b1f33e29ec simplify content-length calculation 2017-12-16 07:29:15 -08:00
ed8bd3d6a3 h1 cleanups 2017-12-15 22:49:48 -08:00
1daf50095a cleanup response 2017-12-15 20:00:12 -08:00
a8b2f1b821 update tests 2017-12-15 18:49:11 -08:00
1ddcce7b76 hide httpresponse box 2017-12-15 16:24:15 -08:00
c3d5e4301a cleanup h1 parse 2017-12-15 13:10:12 -08:00
71c37bde5a Update README.md 2017-12-15 05:44:10 -08:00
4913e7d3c2 cleanup 2017-12-14 22:22:27 -08:00
d77156c16c fix readme 2017-12-14 20:49:09 -08:00
106f43e874 better SharedBytes usage for h2 2017-12-14 20:48:31 -08:00
2b0994e448 update tests 2017-12-14 20:29:49 -08:00
a2dff8a0b9 update readme 2017-12-14 20:12:28 -08:00
c37565cc4a various server optimizations 2017-12-14 19:34:31 -08:00
b61c2a0cf0 handle keep-alive setting more efficient 2017-12-14 11:20:45 -08:00
c98d320f8c rename FromRequest trait to Responder 2017-12-14 09:43:42 -08:00
355f54efe2 update api docs 2017-12-13 23:35:21 -08:00
8c1487f7f2 update tests 2017-12-13 23:09:35 -08:00
4529efa948 rename module 2017-12-13 22:54:52 -08:00
9d0a64ac98 remove unused file 2017-12-13 22:43:16 -08:00
b7cde3f4a9 update guide 2017-12-13 22:36:28 -08:00
408ddf0be1 add ssl guide ref 2017-12-13 21:56:30 -08:00
406ef20262 add readme 2017-12-13 21:44:16 -08:00
c2751efa87 refactor keep-alive; update guide 2017-12-13 21:38:47 -08:00
653b431895 fix example 2017-12-13 17:28:16 -08:00
96f598f2c4 various optimizations 2017-12-13 16:44:35 -08:00
81f8da03ae refactor http workers 2017-12-13 12:47:07 -08:00
6b61041aec move tests 2017-12-13 11:16:26 -08:00
d4187f682b various cleanups 2017-12-13 11:10:03 -08:00
55204c829c update tests 2017-12-13 08:00:25 -08:00
2e83c5924d cleanup and optimize some code 2017-12-12 21:32:58 -08:00
ab6efd2421 handle http connections in different threads 2017-12-12 17:21:00 -08:00
55818028cb state does not need to be Send 2017-12-12 08:51:16 -08:00
e9aa67b75d http server accepts factory of HttpHandlers 2017-12-12 07:40:36 -08:00
b9da09ddf0 update readme 2017-12-11 19:17:37 -08:00
d7efbb516d fix guide tests 2017-12-11 19:16:45 -08:00
6e3f598c50 fix guide page 2017-12-11 16:50:51 -08:00
007b7ce62f unify route not found handling 2017-12-11 16:26:51 -08:00
b1ae7f95cc update readme example 2017-12-11 15:57:37 -08:00
96381f5d6a fix doc 2017-12-11 14:27:09 -08:00
0f75d066f2 simplify Application creation; update url dispatch guide section 2017-12-11 14:16:29 -08:00
caca907c23 update guide 2017-12-09 14:06:22 -08:00
c5490a851c add guid for path normalization 2017-12-09 13:58:24 -08:00
0388a464ba tests for NormalizePath 2017-12-09 13:25:06 -08:00
71bbe2a5dd update doc string for NormalizePath 2017-12-09 11:55:55 -08:00
7addd2800d add NormalizePath handler 2017-12-09 11:39:13 -08:00
273de2260d refactor pipeline 2017-12-09 05:54:04 -08:00
b98ab2eebe use trait instead of pipeline 2017-12-09 04:33:40 -08:00
4a40b026a4 more error wrappers 2017-12-08 15:52:46 -08:00
a44f71d8c2 make ErrorBadRequest type useful 2017-12-08 15:25:37 -08:00
9043e7286d tests for default predicates 2017-12-08 12:51:44 -08:00
3e91b06241 fix static files 2017-12-08 12:29:28 -08:00
774bfc0a86 use server settings for scheme and host values 2017-12-08 09:48:53 -08:00
1293619096 set server settings to HttpHandler 2017-12-08 09:24:05 -08:00
2192d14eff added ServerSettings 2017-12-07 22:54:44 -08:00
b71ddf7b4c pass local addr to channel; use bitflags 2017-12-07 21:52:46 -08:00
3f06439d3e update examples 2017-12-07 18:08:16 -08:00
d595dd850e load cookies automatically 2017-12-07 18:00:20 -08:00
0abb3863dc simplify api 2017-12-07 17:38:18 -08:00
dff7618f35 rearrange exports 2017-12-07 16:40:29 -08:00
968f5d39d6 added external resources; refactor route recognizer 2017-12-07 16:22:26 -08:00
9e3aa59155 ignore tests 2017-12-07 10:23:14 -08:00
2a0d5db41a more tests 2017-12-06 18:39:13 -08:00
4b03d03404 rearrange exports 2017-12-06 17:06:40 -08:00
9ea0781aba fix test 2017-12-06 16:58:49 -08:00
63502fa833 test for Router::has_route 2017-12-06 16:40:23 -08:00
a18bd5dac0 add doc ref 2017-12-06 16:34:54 -08:00
0dd27bd224 added HttpRequest::url_for 2017-12-06 16:26:27 -08:00
8d52e2bbd9 tests for default resource 2017-12-06 13:02:53 -08:00
c63f058647 simplify application creation 2017-12-06 11:00:39 -08:00
87c7441f7d remove Applicaiton::route, resource is enough 2017-12-06 08:03:08 -08:00
04ded5ba68 hide pkcs 2017-12-06 07:49:01 -08:00
903b391e0a move ConnectionInfo to dev 2017-12-06 07:47:42 -08:00
c2bfc091bd fix travis 2017-12-05 22:14:38 -08:00
20af8822fd cleanup 2017-12-05 21:53:00 -08:00
d7e65b6212 add ConnectionInfo tests 2017-12-05 21:41:30 -08:00
c3de32c3b3 added ConnectionInfo 2017-12-05 17:09:15 -08:00
d8b880e167 work on resource_path api 2017-12-05 13:31:06 -08:00
3de43c2a46 update readme 2017-12-05 12:25:57 -08:00
bd1e9abdd8 simple readme example 2017-12-05 11:50:09 -08:00
86d7290f9e update tests 2017-12-05 11:43:41 -08:00
a83d9b24ae extrat elements of path pattern 2017-12-05 11:31:35 -08:00
3c9b6ea619 update guide 2017-12-04 20:38:38 -08:00
fd6b243cd6 update examples 2017-12-04 16:32:31 -08:00
2950c90c77 doc fixes 2017-12-04 16:26:40 -08:00
f4e9fc7b6a rename async to a 2017-12-04 16:09:22 -08:00
e98972e93b exclude example from code coverage 2017-12-04 15:35:07 -08:00
e332c1242f use Route for Applicaiton handlers 2017-12-04 14:53:40 -08:00
f5d6179a34 renamed Route::handler to Route::f, added Route::h to register Handler 2017-12-04 14:07:53 -08:00
03f7d95d88 fix fmratting 2017-12-04 13:36:28 -08:00
a163e75318 drop tail path pattern 2017-12-04 13:34:55 -08:00
3bf3738e65 introduce route predicates 2017-12-04 13:32:05 -08:00
57fd35ffc1 added default headers middleware 2017-12-03 20:47:15 -08:00
d35be02587 cleanup 2017-12-03 20:09:46 -08:00
57c53bd2a0 add encoding section 2017-12-03 18:58:15 -08:00
319e9bbd05 added Json response support 2017-12-03 18:51:52 -08:00
5decff9154 added fs tests 2017-12-03 18:15:09 -08:00
69f0c098e3 check show_index 2017-12-03 16:58:31 -08:00
5abc46034a refactor static files 2017-12-03 16:57:25 -08:00
7c6faaa8e0 add Item and Error to FromRequest trait 2017-12-03 14:22:04 -08:00
6bc7d60f52 more default impls for FromRequest 2017-12-02 17:14:55 -08:00
fb3185de94 rename module 2017-12-02 16:47:02 -08:00
61744b68a1 introduce custom FromRequest traint for conversion into Reply 2017-12-02 16:37:21 -08:00
187948ddd1 error response for io::Error 2017-12-02 14:58:22 -08:00
29a26b3236 code cleanup 2017-12-02 12:14:16 -08:00
d0b9d9c1d6 content-encoding; try cargo tarpaulin 2017-12-02 11:41:20 -08:00
0fc01c48d1 return bad request for param parse error 2017-12-02 11:03:41 -08:00
ebfd3ac275 tests for PathBuf::from_param 2017-12-02 10:43:14 -08:00
d8f27e95a6 added FromParam trait for path segment conversions, FramParam impl for PathBuf 2017-12-02 10:18:54 -08:00
d03d1207a8 add http2 section 2017-12-02 00:36:50 -08:00
8f33dec026 add logging doc section 2017-12-02 00:24:26 -08:00
0dae109172 missing files 2017-12-01 23:42:21 -08:00
1a5df7192e add multiple apps example 2017-12-01 23:32:15 -08:00
3ffd36eee2 some more guide 2017-12-01 23:06:15 -08:00
c3a0a4457a add appl builder async method; add async handler section 2017-12-01 21:58:19 -08:00
e6feec62a8 simplier examples 2017-12-01 21:31:38 -08:00
f0c346f18c handler info 2017-12-01 21:29:22 -08:00
186726fbad tests for Completed state 2017-12-01 19:57:34 -08:00
97bed17fd2 test for completed pipeline state 2017-12-01 16:10:01 -08:00
47645626c4 refactor pipeline 2017-12-01 15:45:15 -08:00
9a1ba527c0 recognizer tests 2017-11-30 19:34:33 -08:00
3fcd5f6935 use http::Uri for uri parsing 2017-11-30 19:01:25 -08:00
7135c0163b simlify code 2017-11-30 18:27:27 -08:00
f53f35f364 added tail pattern 2017-11-30 15:48:09 -08:00
07cc017320 make Task private 2017-11-30 15:13:56 -08:00
271a292ea5 no need to store disconnected state on task 2017-11-30 14:44:58 -08:00
6e138bf373 refactor streaming responses 2017-11-30 14:42:20 -08:00
a0bca2d4cf fix typo 2017-11-30 07:42:02 -08:00
6c4fdf604b do not set content encoding header for upgraded connection 2017-11-29 19:40:27 -08:00
e4f8551cba do not com press upgrade connection 2017-11-29 19:36:55 -08:00
559b1c50a3 do not encode payload less that 1024 bytes 2017-11-29 19:18:37 -08:00
d2eae3d5b3 simplify Handler trait 2017-11-29 15:10:45 -08:00
27035c9454 fixes 2017-11-29 14:22:47 -08:00
991dd107b1 update ws doc 2017-11-29 14:12:27 -08:00
ffb2e3c0ab update examples 2017-11-29 14:03:18 -08:00
acc2fff655 export and simplify HttpHandler trait 2017-11-29 13:53:52 -08:00
427566b90d export Handler 2017-11-29 13:41:51 -08:00
16ceb741b8 refactor RouteHandler trait 2017-11-29 13:26:55 -08:00
6f833798c7 refactor http actor handling 2017-11-29 10:31:24 -08:00
6177d86d97 refactor handler rtype handling 2017-11-29 09:17:00 -08:00
e9bfab8012 add deref for payload item 2017-11-28 19:51:39 -08:00
afeecea05f refactor reply handling 2017-11-28 19:49:17 -08:00
6f5b58b691 more guide 2017-11-28 18:00:10 -08:00
987b275c3f add response test 2017-11-28 14:29:22 -08:00
932e751240 add status code helper method for http response 2017-11-28 14:23:42 -08:00
706e2a07de add helper converters into response 2017-11-28 13:52:53 -08:00
a3022e6d88 some overview text for guide 2017-11-28 12:44:59 -08:00
b55d69b4c2 better handler result handling 2017-11-28 12:42:53 -08:00
0bd8725426 make resource handler result more generic 2017-11-28 12:28:51 -08:00
ac3fe30d19 use git master for examples 2017-11-28 10:27:58 -08:00
88eb6bc6a2 build mdbook 2017-11-28 09:57:27 -08:00
91cefa8e2f use precompiled mdbook 2017-11-28 09:40:22 -08:00
26413d1d61 add link to guide 2017-11-27 19:56:14 -08:00
06f9b7b52f fix travis config 2017-11-27 16:49:25 -08:00
599f3c26e0 start working on guide 2017-11-27 16:41:37 -08:00
b5a4f6f855 hellper method for json body 2017-11-27 10:39:47 -08:00
42716d3252 update tests 2017-11-26 22:59:04 -08:00
170d3163f3 better export naming 2017-11-26 22:53:28 -08:00
0519056199 consistent naming 2017-11-26 22:31:29 -08:00
45433f71e5 impl Default trait for HttpRequest 2017-11-26 22:20:28 -08:00
b62b303fdb remove unneeded directives 2017-11-26 22:11:51 -08:00
8e80fed2af added urlencoded errors 2017-11-26 22:00:25 -08:00
fdafb0c848 simplify middleware api; fix examples 2017-11-26 21:47:33 -08:00
5a3b6638a7 move state to request object 2017-11-26 21:18:38 -08:00
8e0a7f44d4 pass request by value 2017-11-26 20:34:20 -08:00
eb7f48a1c6 include payload into request 2017-11-26 19:00:57 -08:00
32483735ba cookie session implementation 2017-11-26 17:34:11 -08:00
53ce186294 cleanup pipeline 2017-11-25 12:05:27 -08:00
37c1e78c7a added helper Task::error method 2017-11-25 10:52:43 -08:00
45ecb87eab allow middlware error result 2017-11-25 10:24:45 -08:00
54bbc98343 cookie session prototype 2017-11-25 09:52:32 -08:00
f4972150cc better middleware error handling 2017-11-25 09:40:57 -08:00
1fc64bc83d better pipeline error handling 2017-11-25 09:28:25 -08:00
64ade803f9 store error for error response 2017-11-25 09:03:44 -08:00
940bc08aba remove unused imports 2017-11-24 22:19:06 -08:00
7569036dd4 refactor request pipeline 2017-11-24 22:15:52 -08:00
59b8214685 better nightly detection 2017-11-24 10:28:43 -08:00
f33c489154 added default ErrorResponse for std::error::Error 2017-11-24 10:03:13 -08:00
5529ea0428 better logger format test 2017-11-23 16:53:02 -08:00
39a20fb95d use env_logger for logger tests 2017-11-23 15:48:59 -08:00
155a636487 more logger tests 2017-11-23 15:37:11 -08:00
e571587a8c refactor logger middleware 2017-11-23 15:17:16 -08:00
5945035fc3 better method name 2017-11-19 18:55:37 -10:00
766e243c63 more Body tests 2017-11-19 18:32:37 -10:00
83862dfbb4 update payload tests 2017-11-19 18:26:30 -10:00
c44e4ad100 expect error tests 2017-11-19 18:02:31 -10:00
1a0e87ac3c add tests for errors 2017-11-19 17:58:47 -10:00
72edd75eab add custom ExceptError 2017-11-19 17:51:14 -10:00
78d8d21196 cleanup error 2017-11-19 17:26:31 -10:00
fd3dcdf0f6 use failure from crates 2017-11-18 06:50:56 -10:00
a87784ba15 use Result intead of HandlerResult 2017-11-18 06:50:07 -10:00
c800bf55f5 update tests 2017-11-15 20:28:02 -10:00
0143e18fe9 fix extern crate 2017-11-15 20:09:37 -10:00
de71ad7de4 refactor error handling 2017-11-15 20:06:28 -10:00
c565965865 rename BinaryBody 2017-11-10 13:42:32 -08:00
f2520d2d79 update logger doc 2017-11-10 13:34:16 -08:00
be3a1ab770 use remote addr in logger if available 2017-11-10 13:26:12 -08:00
f369d9af0e make remote addr available to http request 2017-11-10 13:08:15 -08:00
265628750c refactor logger middleware 2017-11-10 12:29:54 -08:00
657efb8cce fix readme 2017-11-09 22:18:59 -08:00
40c1d3b711 refactor middlewares 2017-11-09 22:08:54 -08:00
51cd08ef57 store cookies load state 2017-11-08 21:01:56 -08:00
2a319d733f enable secure cookies 2017-11-08 20:57:59 -08:00
519a9e64f8 cleanup tls example 2017-11-08 20:29:48 -08:00
4d575c6269 update readme 2017-11-08 20:25:14 -08:00
e9fe2ba740 use bytes::Writer 2017-11-08 20:08:16 -08:00
7565ed8e06 use higher pripority for br 2017-11-08 19:42:13 -08:00
02fb424659 add custom Debug impl for HttpResponse 2017-11-08 19:31:25 -08:00
1392b2b171 Update README.md 2017-11-08 16:48:20 -08:00
e558414867 add response content encoding 2017-11-08 16:44:46 -08:00
76b8104f52 use version in readme 2017-11-07 16:09:37 -08:00
2eb3ad0de3 better name 2017-11-07 16:08:10 -08:00
6974213036 use new brotli2 version 2017-11-07 15:59:37 -08:00
72c8ad9fe1 fix appveyor config for gnu target 2017-11-07 09:43:39 -08:00
a65fd695e1 refactor content encoding 2017-11-06 16:23:58 -08:00
994d0afd80 allow to set/change responses content encoding 2017-11-06 14:56:38 -08:00
2379bcbf39 added content-encoding support to h2 2017-11-06 09:35:52 -08:00
bddd8e9c2e better deflate decoding 2017-11-06 09:24:19 -08:00
c2978a6eea add content encoding decompression 2017-11-06 01:27:46 -08:00
b467ddf970 test with alpn feature 2017-11-04 15:16:49 -07:00
3f649b8e07 fix name 2017-11-04 14:07:15 -07:00
e9e247217a update readme 2017-11-04 13:49:51 -07:00
f23974cfb5 update readme 2017-11-04 13:49:05 -07:00
53868a88fa add keep-alive for h2 connection 2017-11-04 13:24:57 -07:00
28652a3ba8 update readme 2017-11-04 12:36:37 -07:00
41be1db8bc fix link 2017-11-04 12:35:55 -07:00
67f3ad31ab update readme 2017-11-04 12:35:19 -07:00
d7d3d663e9 refactor server impl and add support for alpn http2 negotiation 2017-11-04 12:33:14 -07:00
32cefb8455 implement h2 writer 2017-11-04 09:07:44 -07:00
4add742aba refactor task impl, extract stream writer to separate struct 2017-11-03 13:48:00 -07:00
f010672885 rename modules 2017-11-03 13:48:00 -07:00
c14e6c9008 make possible to use async handler 2017-11-03 13:35:34 -07:00
ec3b139273 Allow to start tls server with HttpServer::serve_tls 2017-11-01 16:34:58 -07:00
a12e5e9cf5 bump version 2017-10-30 20:46:39 -07:00
171 changed files with 24863 additions and 6836 deletions

View File

@ -3,6 +3,15 @@ environment:
PROJECT_NAME: actix
matrix:
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: 1.21.0
- TARGET: i686-pc-windows-msvc
CHANNEL: 1.21.0
- TARGET: x86_64-pc-windows-gnu
CHANNEL: 1.21.0
- TARGET: x86_64-pc-windows-msvc
CHANNEL: 1.21.0
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
@ -22,17 +31,23 @@ environment:
CHANNEL: beta
# Nightly channel
- TARGET: i686-pc-windows-gnu
CHANNEL: nightly
CHANNEL: nightly-2017-12-21
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
CHANNEL: nightly-2017-12-21
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly
CHANNEL: nightly-2017-12-21
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
CHANNEL: nightly-2017-12-21
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
install:
- ps: >-
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
$Env:PATH += ';C:\msys64\mingw64\bin'
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
$Env:PATH += ';C:\MinGW\bin'
}
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
@ -44,6 +59,4 @@ build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo build --no-default-features
- cargo clean
- cargo test --no-default-features

5
.gitignore vendored
View File

@ -1,7 +1,7 @@
target/
Cargo.lock
target/
guide/build/
/gh-pages
__pycache__
*.so
*.out
@ -9,7 +9,6 @@ __pycache__
*.pid
*.sock
*~
*.egg-info/
# These are backup files generated by rustfmt
**/*.rs.bk

View File

@ -1,28 +1,36 @@
language: rust
rust:
- 1.20.0
- stable
- beta
- nightly
sudo: required
sudo: false
dist: trusty
cache:
cargo: true
apt: true
matrix:
include:
- rust: 1.21.0
- rust: stable
- rust: beta
- rust: nightly
allow_failures:
- rust: nightly
- rust: beta
#rust:
# - 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
addons:
apt:
packages:
- libcurl4-openssl-dev
- libelf-dev
- libdw-dev
- cmake
- gcc
- binutils-dev
- libiberty-dev
before_install:
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
- sudo apt-get update -qq
- sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
# Add clippy
before_script:
@ -33,7 +41,31 @@ before_script:
- export PATH=$PATH:~/.cargo/bin
script:
- cargo test --no-default-features
- |
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo clean
USE_SKEPTIC=1 cargo test --features=alpn
else
cargo clean
cargo test
# --features=alpn
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cd examples/basics && cargo check && cd ../..
cd examples/hello-world && cargo check && cd ../..
cd examples/multipart && cargo check && cd ../..
cd examples/json && cargo check && cd ../..
cd examples/juniper && 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 ../..
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
cargo clippy
@ -43,27 +75,19 @@ script:
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
cargo doc --no-deps &&
cargo doc --features "alpn, tls" --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
cargo install mdbook &&
cd guide && mdbook build -d ../target/doc/guide && cd .. &&
git clone https://github.com/davisp/ghp-import.git &&
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
echo "Uploaded documentation"
fi
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
tar xzf master.tar.gz &&
cd kcov-master &&
mkdir build &&
cd build &&
cmake .. &&
make &&
make install DESTDIR=../../kcov-build &&
cd ../.. &&
rm -rf kcov-master &&
for file in target/debug/actix_web-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done &&
for file in target/debug/test_*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done &&
bash <(curl -s https://codecov.io/bash) &&
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "1.21.0" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi

View File

@ -1,4 +1,91 @@
# CHANGES
# Changes
## 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
* Do not enable chunked encoding for HTTP/1.0
* Allow explicitly disable chunked encoding
## 0.3.0 (2018-01-12)
* HTTP/2 Support
* Refactor streaming responses
* Refactor error handling
* Asynchronous middlewares
* Refactor logger middleware
* Content compression/decompression (br, gzip, deflate)
* Server multi-threading
* Gracefull shutdown support
## 0.2.1 (2017-11-03)
* Allow to start tls server with `HttpServer::serve_tls`
* Export `Frame` enum
* Add conversion impl from `HttpResponse` and `BinaryBody` to a `Frame`
## 0.2.0 (2017-10-30)

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,16 +1,20 @@
[package]
name = "actix-web"
version = "0.2.0"
version = "0.4.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web framework"
readme = "README.md"
keywords = ["actor", "http", "web"]
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"]
license = "Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
categories = ["network-programming", "asynchronous",
"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/**"]
build = "build.rs"
[badges]
@ -25,46 +29,89 @@ path = "src/lib.rs"
[features]
default = []
# http/2
# http2 = ["h2"]
# tls
tls = ["native-tls", "tokio-tls"]
# openssl
alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
[dependencies]
log = "0.3"
time = "0.1"
http = "0.1"
httparse = "0.1"
base64 = "0.9"
bitflags = "1.0"
brotli2 = "^0.3.2"
failure = "0.1.1"
flate2 = "1.0"
h2 = "0.1"
http = "^0.1.2"
httparse = "1.2"
http-range = "0.1"
libc = "0.2"
log = "0.4"
mime = "0.3"
mime_guess = "1.8"
cookie = { version="0.10", features=["percent-encode"] }
regex = "0.2"
sha1 = "0.2"
url = "1.5"
num_cpus = "1.0"
percent-encoding = "1.0"
rand = "0.4"
regex = "0.2"
serde = "1.0"
serde_json = "1.0"
sha1 = "0.6"
smallvec = "0.6"
time = "0.1"
encoding = "0.2"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode", "secure"] }
# tokio
# io
mio = "^0.6.13"
net2 = "0.2"
bytes = "0.4"
byteorder = "1"
futures = "0.1"
tokio-io = "0.1"
tokio-core = "0.1"
# h2 = { git = 'https://github.com/carllerche/h2', optional = true }
trust-dns-resolver = "0.8"
# native-tls
native-tls = { version="0.1", optional = true }
tokio-tls = { version="0.1", optional = true }
# openssl
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }
[dependencies.actix]
version = ">=0.3.1"
#path = "../actix"
#git = "https://github.com/actix/actix.git"
default-features = false
features = []
version = "0.5"
[dev-dependencies]
env_logger = "0.4"
reqwest = "0.8"
env_logger = "0.5"
skeptic = "0.13"
serde_derive = "1.0"
[build-dependencies]
skeptic = "0.13"
version_check = "0.1"
[profile.release]
lto = true
opt-level = 3
debug = true
codegen-units = 1
[workspace]
members = [
"./",
"examples/basics",
"examples/juniper",
"examples/diesel",
"examples/r2d2",
"examples/json",
"examples/hello-world",
"examples/multipart",
"examples/state",
"examples/template_tera",
"examples/tls",
"examples/websocket",
"examples/websocket-chat",
"examples/web-cors/backend",
"tools/wsload/",
]

25
LICENSE-MIT Normal file
View File

@ -0,0 +1,25 @@
Copyright (c) 2017 Nikilay Kim
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,6 +1,6 @@
.PHONY: default build test doc clean
.PHONY: default build test doc book clean
CARGO_FLAGS := --features "$(FEATURES)"
CARGO_FLAGS := --features "$(FEATURES) alpn"
default: test
@ -20,3 +20,7 @@ clippy:
doc: build
cargo doc --no-deps $(CARGO_FLAGS)
cd guide; mdbook build -d ../target/doc/guide/; cd ..
book:
cd guide; mdbook build -d ../target/doc/guide/; cd ..

168
README.md
View File

@ -1,123 +1,79 @@
# 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)
# 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)
Asynchronous web framework for [Actix](https://github.com/actix/actix).
Actix web is a small, pragmatic, extremely fast, web framework for Rust.
* 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
* 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).
## Documentation
* [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
---
Actix web is licensed under the [Apache-2.0 license](http://opensource.org/licenses/APACHE-2.0).
## Features
* HTTP 1.1 and 1.0 support
* Streaming and pipelining support
* Keep-alive and slow requests support
* [WebSockets support](https://actix.github.io/actix-web/actix_web/ws/index.html)
* Configurable request routing
* Multipart streams
* Middlewares
## Usage
To use `actix-web`, add this to your `Cargo.toml`:
```toml
[dependencies]
actix-web = "0.2"
```
* Minimum supported Rust version: 1.21 or later
## Example
* [Basic](https://github.com/actix/actix-web/tree/master/examples/basic.rs)
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state.rs)
* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart)
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket.rs)
* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat)
* [SockJS Server](https://github.com/fafhrd91/actix-sockjs)
```rust
extern crate actix;
```rust,ignore
extern crate actix_web;
extern crate env_logger;
use actix::*;
use actix_web::*;
struct MyWebSocket;
/// Actor with http context
impl Actor for MyWebSocket {
type Context = HttpContext<Self>;
}
/// Http route handler
impl Route for MyWebSocket {
type State = ();
fn request(req: &mut HttpRequest,
payload: Payload, ctx: &mut HttpContext<Self>) -> RouteResult<Self>
{
// websocket handshake
let resp = ws::handshake(req)?;
// send HttpResponse back to peer
ctx.start(resp);
// convert bytes stream to a stream of `ws::Message` and handle stream
ctx.add_stream(ws::WsStream::new(payload));
Reply::async(MyWebSocket)
}
}
/// Standard actix's stream handler for a stream of `ws::Message`
impl StreamHandler<ws::Message> for MyWebSocket {
fn started(&mut self, ctx: &mut Self::Context) {
println!("WebSocket session openned");
}
fn finished(&mut self, ctx: &mut Self::Context) {
println!("WebSocket session closed");
}
}
impl Handler<ws::Message> for MyWebSocket {
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
-> Response<Self, ws::Message>
{
// process websocket messages
println!("WS: {:?}", 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 => {
ctx.stop();
}
_ => (),
}
Self::empty()
}
fn index(req: HttpRequest) -> String {
format!("Hello {}!", &req.match_info()["name"])
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("ws-example");
HttpServer::new(
Application::default("/")
// enable logger
.middleware(Logger::new(None))
// websocket route
.resource("/ws/", |r| r.get::<MyWebSocket>())
.route_handler("/", StaticFiles::new("examples/static/", true)))
.serve::<_, ()>("127.0.0.1:8080").unwrap();
Arbiter::system().send(msgs::SystemExit(0));
let _ = sys.run();
|| Application::new()
.resource("/{name}", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap()
.run();
}
```
### More examples
* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/)
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
* [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/)
## 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
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
at your option.
## Code of Conduct
Contribution to the actix-web crate is organized under the terms of the
Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to
intervene to uphold that code of conduct.

View File

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

4
cov.sh
View File

@ -1,4 +0,0 @@
#!/bin/bash
for file in target/debug/actix_web-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; /usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done &&
for file in target/debug/test_*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; /usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done

View File

@ -1,53 +0,0 @@
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix_web::*;
/// somple handle
fn index(req: &mut HttpRequest, _payload: Payload, state: &()) -> HttpResponse {
println!("{:?}", req);
httpcodes::HTTPOk.into()
}
/// handle with path parameters like `/name/{name}/`
fn with_param(req: &mut HttpRequest, _payload: Payload, state: &())
-> HandlerResult<HttpResponse>
{
println!("{:?}", req);
Ok(HttpResponse::builder(StatusCode::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();
let sys = actix::System::new("ws-example");
HttpServer::new(
Application::default("/")
// enable logger
.middleware(Logger::new(None))
// register simple handler, handle all methods
.handler("/index.html", index)
// with path parameters
.resource("/user/{name}/", |r| r.handler(Method::GET, with_param))
// redirect
.resource("/", |r| r.handler(Method::GET, |req, _, _| {
println!("{:?}", req);
Ok(httpcodes::HTTPFound
.builder()
.header("LOCATION", "/index.html")
.body(Body::Empty)?)
}))
// static files
.route_handler("/static", StaticFiles::new("examples/static/", true)))
.serve::<_, ()>("127.0.0.1:8080").unwrap();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

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

20
examples/basics/README.md Normal file
View File

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

151
examples/basics/src/main.rs Normal file
View File

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

1
examples/diesel/.env Normal file
View File

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

View File

@ -0,0 +1,19 @@
[package]
name = "diesel-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"
diesel = { version = "1.0.0-beta1", features = ["sqlite"] }
dotenv = "0.10"

43
examples/diesel/README.md Normal file
View File

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

View File

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

View File

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

52
examples/diesel/src/db.rs Normal file
View File

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

View File

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

View File

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

View File

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

BIN
examples/diesel/test.db Normal file

Binary file not shown.

View File

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

View File

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

18
examples/json/Cargo.toml Normal file
View File

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

48
examples/json/README.md Normal file
View File

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

18
examples/json/client.py Normal file
View File

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

99
examples/json/src/main.rs Normal file
View File

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

View File

@ -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,110 @@
//! 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::*;
use actix_web::*;
use juniper::http::graphiql::graphiql_source;
use juniper::http::GraphQLRequest;
use futures::future::Future;
mod schema;
use schema::Schema;
use schema::create_schema;
struct State {
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<State>) -> Result<HttpResponse> {
let html = graphiql_source("http://localhost:8080/graphql");
Ok(HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8")
.body(html).unwrap())
}
fn graphql(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
let executor = req.state().executor.clone();
req.json()
.from_err()
.and_then(move |val: GraphQLData| {
executor.send(val)
.from_err()
.and_then(|res| {
match res {
Ok(user) => Ok(httpcodes::HTTPOk.build().body(user)?),
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
}
})
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("juniper-example");
let schema = std::sync::Arc::new(create_schema());
let addr = SyncArbiter::start(3, move || {
GraphQLExecutor::new(schema.clone())
});
// Start http server
let _addr = HttpServer::new(move || {
Application::with_state(State{executor: addr.clone()})
// enable logger
.middleware(middleware::Logger::default())
.resource("/graphql", |r| r.method(Method::POST).a(graphql))
.resource("/graphiql", |r| r.method(Method::GET).f(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

@ -2,6 +2,7 @@
name = "multipart-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "multipart"
@ -9,6 +10,6 @@ path = "src/main.rs"
[dependencies]
env_logger = "*"
#actix = "0.3"
actix = { git = "https://github.com/actix/actix.git" }
actix-web = { path = "../../" }
futures = "0.1"
actix = "0.5"
actix-web = { path="../../" }

View File

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

View File

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

View File

@ -2,77 +2,58 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
use actix::*;
use actix_web::*;
struct MyRoute;
use futures::{Future, Stream};
use futures::future::{result, Either};
impl Actor for MyRoute {
type Context = HttpContext<Self>;
}
impl Route for MyRoute {
type State = ();
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
{
println!("{:?}", req);
fn request(req: &mut HttpRequest, payload: Payload,
ctx: &mut HttpContext<Self>) -> RouteResult<Self> {
println!("{:?}", req);
req.multipart() // <- get multipart stream for current request
.from_err() // <- convert multipart errors
.and_then(|item| { // <- iterate over multipart items
match item {
// Handle multipart Field
multipart::MultipartItem::Field(field) => {
println!("==== FIELD ==== {:?}", field);
let multipart = req.multipart(payload)?;
// get Multipart stream
WrapStream::<MyRoute>::actstream(multipart)
.and_then(|item, act, ctx| {
// Multipart stream is a stream of Fields and nested Multiparts
match item {
multipart::MultipartItem::Field(field) => {
println!("==== FIELD ==== {:?}", field);
// Read field's stream
fut::Either::A(
field.actstream()
.map(|chunk, act, ctx| {
println!(
"-- CHUNK: \n{}",
std::str::from_utf8(&chunk.0).unwrap());
})
.finish())
},
multipart::MultipartItem::Nested(mp) => {
// Do nothing for nested multipart stream
fut::Either::B(fut::ok(()))
}
// Field in turn is stream of *Bytes* object
Either::A(
field.map_err(Error::from)
.map(|chunk| {
println!("-- CHUNK: \n{}",
std::str::from_utf8(&chunk).unwrap());})
.finish())
},
multipart::MultipartItem::Nested(mp) => {
// Or item could be nested Multipart stream
Either::B(result(Ok(())))
}
})
// wait until stream finish
.finish()
.map_err(|e, act, ctx| {
ctx.start(httpcodes::HTTPBadRequest);
ctx.write_eof();
})
.map(|_, act, ctx| {
ctx.start(httpcodes::HTTPOk);
ctx.write_eof();
})
.spawn(ctx);
Reply::async(MyRoute)
}
}
})
.finish() // <- Stream::finish() combinator from actix
.map(|_| httpcodes::HTTPOk.into())
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("multipart-example");
HttpServer::new(
vec![
Application::default("/")
.resource("/multipart", |r| {
r.post::<MyRoute>();
}).finish()
])
.serve::<_, ()>("127.0.0.1:8080").unwrap();
let addr = HttpServer::new(
|| Application::new()
.middleware(middleware::Logger::default()) // <- logger
.resource("/multipart", |r| r.method(Method::POST).a(index)))
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run();
}

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

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

@ -0,0 +1,64 @@
//! 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::*;
use actix_web::*;
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(httpcodes::HTTPOk.build().json(user)?),
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
}
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=debug");
let _ = 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
let _addr = HttpServer::new(move || {
Application::with_state(State{db: addr.clone()})
// enable logger
.middleware(middleware::Logger::default())
.resource("/{name}", |r| r.method(Method::GET).a(index))})
.bind("127.0.0.1:8080").unwrap()
.start();
let _ = sys.run();
}

BIN
examples/r2d2/test.db Normal file

Binary file not shown.

View File

@ -1,86 +0,0 @@
//! There are two level of statfulness in actix-web. Application has state
//! that is shared across all handlers within same Application.
//! And individual handler can have state.
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix::*;
use actix_web::*;
use std::cell::Cell;
struct AppState {
counter: Cell<usize>,
}
/// somple handle
fn index(req: &mut HttpRequest, _: Payload, state: &AppState) -> HttpResponse {
println!("{:?}", req);
state.counter.set(state.counter.get() + 1);
httpcodes::HTTPOk.with_body(
format!("Num of requests: {}", state.counter.get()))
}
/// `MyWebSocket` counts how many messages it receives from peer,
/// websocket-client.py could be used for tests
struct MyWebSocket {
counter: usize,
}
impl Actor for MyWebSocket {
type Context = HttpContext<Self>;
}
impl Route for MyWebSocket {
/// Shared application state
type State = AppState;
fn request(req: &mut HttpRequest,
payload: Payload, ctx: &mut HttpContext<Self>) -> RouteResult<Self>
{
let resp = ws::handshake(req)?;
ctx.start(resp);
ctx.add_stream(ws::WsStream::new(payload));
Reply::async(MyWebSocket{counter: 0})
}
}
impl StreamHandler<ws::Message> for MyWebSocket {}
impl Handler<ws::Message> for MyWebSocket {
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
-> Response<Self, ws::Message>
{
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 => {
ctx.stop();
}
_ => (),
}
Self::empty()
}
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("ws-example");
HttpServer::new(
Application::builder("/", AppState{counter: Cell::new(0)})
// enable logger
.middleware(Logger::new(None))
// websocket route
.resource("/ws/", |r| r.get::<MyWebSocket>())
// register simple handler, handle all methods
.handler("/", index))
.serve::<_, ()>("127.0.0.1:8080").unwrap();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

11
examples/state/Cargo.toml Normal file
View File

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

15
examples/state/README.md Normal file
View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
examples/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

15
examples/tls/Cargo.toml Normal file
View File

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

16
examples/tls/README.md Normal file
View File

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

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

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

50
examples/tls/src/main.rs Normal file
View File

@ -0,0 +1,50 @@
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate openssl;
use actix_web::*;
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
/// simple handle
fn index(req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req);
Ok(httpcodes::HTTPOk
.build()
.content_type("text/plain")
.body("Welcome!")?)
}
fn main() {
if ::std::env::var("RUST_LOG").is_err() {
::std::env::set_var("RUST_LOG", "actix_web=info");
}
let _ = env_logger::init();
let sys = actix::System::new("ws-example");
// 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()
// enable logger
.middleware(middleware::Logger::default())
// register simple handler, handle all methods
.resource("/index.html", |r| r.f(index))
// with path parameters
.resource("/", |r| r.method(Method::GET).f(|req| {
httpcodes::HTTPFound
.build()
.header("LOCATION", "/index.html")
.body(Body::Empty)
})))
.bind("127.0.0.1:8443").unwrap()
.start_ssl(builder).unwrap();
println!("Started http server: 127.0.0.1:8443");
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,45 @@
#[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;
extern crate http;
use std::env;
use http::header;
use actix_web::*;
use actix_web::middleware::cors;
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");
HttpServer::new(
|| Application::new()
.middleware(middleware::Logger::default())
.resource("/user/info", |r| {
cors::Cors::build()
.allowed_origin("http://localhost:1234")
.allowed_methods(vec!["GET", "POST"])
.allowed_headers(
vec![header::AUTHORIZATION,
header::ACCEPT, header::CONTENT_TYPE])
.max_age(3600)
.finish().expect("Can not create CORS middleware")
.register(r);
r.method(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::*;
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(httpcodes::HTTPOk.build().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

@ -2,6 +2,7 @@
name = "websocket-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "server"
@ -24,6 +25,5 @@ serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
#actix = "0.3"
actix = { git = "https://github.com/actix/actix.git" }
actix-web = { path = "../../" }
actix = "0.5"
actix-web = { path="../../" }

View File

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

View File

@ -1,4 +1,4 @@
extern crate actix;
#[macro_use] extern crate actix;
extern crate bytes;
extern crate byteorder;
extern crate futures;
@ -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,43 +62,48 @@ fn main() {
}
struct ChatClient;
struct ClientCommand(String);
impl ResponseType for ClientCommand {
type Item = ();
type Error = ();
struct ChatClient {
framed: actix::io::FramedWrite<WriteHalf<TcpStream>, codec::ClientChatCodec>,
}
impl Actor for ChatClient {
type Context = FramedContext<Self>;
#[derive(Message)]
struct ClientCommand(String);
fn started(&mut self, ctx: &mut FramedContext<Self>) {
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 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
{
fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext<Self>)
-> Response<Self, ClientCommand>
{
impl Handler<ClientCommand> for ChatClient {
type Result = ();
fn handle(&mut self, msg: ClientCommand, _: &mut Context<Self>) {
let m = msg.0.trim();
if m.is_empty() {
return Self::empty()
return
}
// we check for /sss type of messages
@ -98,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");
}
@ -110,35 +123,16 @@ 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()));
}
Self::empty()
}
}
/// Server communication
impl FramedActor for ChatClient {
type Io = TcpStream;
type Codec = codec::ClientChatCodec;
}
impl StreamHandler<codec::ChatResponse, io::Error> for ChatClient {
fn finished(&mut self, _: &mut FramedContext<Self>) {
println!("Disconnected");
// Stop application on disconnect
Arbiter::system().send(msgs::SystemExit(0));
}
}
impl Handler<codec::ChatResponse, io::Error> for ChatClient {
fn handle(&mut self, msg: codec::ChatResponse, _: &mut FramedContext<Self>)
-> Response<Self, codec::ChatResponse>
{
fn handle(&mut self, msg: codec::ChatResponse, _: &mut Context<Self>) {
match msg {
codec::ChatResponse::Message(ref msg) => {
println!("message: {}", msg);
@ -155,7 +149,5 @@ impl Handler<codec::ChatResponse, io::Error> for ChatClient {
}
_ => (),
}
Self::empty()
}
}

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

@ -10,6 +10,7 @@ extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
#[macro_use]
extern crate actix;
extern crate actix_web;
@ -22,11 +23,21 @@ mod codec;
mod server;
mod session;
/// This is our websocket route state, this state is shared with all route instances
/// via `HttpContext::state()`
struct WsChatSessionState {
addr: SyncAddress<server::ChatServer>,
addr: Addr<Syn, server::ChatServer>,
}
/// Entry point for our route
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse> {
ws::start(
req,
WsChatSession {
id: 0,
hb: Instant::now(),
room: "Main".to_owned(),
name: None})
}
struct WsChatSession {
@ -41,50 +52,53 @@ struct WsChatSession {
}
impl Actor for WsChatSession {
type Context = HttpContext<Self>;
}
type Context = ws::WebsocketContext<Self, WsChatSessionState>;
/// Entry point for our route
impl Route for WsChatSession {
type State = WsChatSessionState;
/// Method is called on actor start.
/// We register ws session with ChatServer
fn started(&mut self, ctx: &mut Self::Context) {
// register self in chat server. `AsyncContext::wait` register
// future within context, but context waits until this future resolves
// before processing any other events.
// HttpContext::state() is instance of WsChatSessionState, state is shared across all
// routes within application
let addr: Addr<Syn, _> = ctx.address();
ctx.state().addr.send(server::Connect{addr: addr.recipient()})
.into_actor(self)
.then(|res, act, ctx| {
match res {
Ok(res) => act.id = res,
// something is wrong with chat server
_ => ctx.stop(),
}
fut::ok(())
}).wait(ctx);
}
fn request(req: &mut HttpRequest,
payload: Payload, ctx: &mut HttpContext<Self>) -> RouteResult<Self>
{
// websocket handshakre, it may fail if request is not websocket request
let resp = ws::handshake(&req)?;
ctx.start(resp);
ctx.add_stream(ws::WsStream::new(payload));
Reply::async(
WsChatSession {
id: 0,
hb: Instant::now(),
room: "Main".to_owned(),
name: None})
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
// notify chat server
ctx.state().addr.do_send(server::Disconnect{id: self.id});
Running::Stop
}
}
/// Handle messages from chat server, we simply send it to peer websocket
impl Handler<session::Message> for WsChatSession {
fn handle(&mut self, msg: session::Message, ctx: &mut HttpContext<Self>)
-> Response<Self, session::Message>
{
ws::WsWriter::text(ctx, &msg.0);
Self::empty()
type Result = ();
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
ctx.text(msg.0);
}
}
/// WebSocket message handler
impl Handler<ws::Message> for WsChatSession {
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
-> Response<Self, ws::Message>
{
impl StreamHandler<ws::Message, ws::WsError> for WsChatSession {
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
println!("WEBSOCKET MESSAGE: {:?}", msg);
match msg {
ws::Message::Ping(msg) =>
ws::WsWriter::pong(ctx, &msg),
ws::Message::Pong(msg) =>
self.hb = Instant::now(),
ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Pong(msg) => self.hb = Instant::now(),
ws::Message::Text(text) => {
let m = text.trim();
// we check for /sss type of messages
@ -94,17 +108,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 {
ws::WsWriter::text(ctx, &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
@ -112,23 +128,22 @@ 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()});
ws::WsWriter::text(ctx, "joined");
ctx.text("joined");
} else {
ws::WsWriter::text(ctx, "!!! room name is required");
ctx.text("!!! room name is required");
}
},
"/name" => {
if v.len() == 2 {
self.name = Some(v[1].to_owned());
} else {
ws::WsWriter::text(ctx, "!!! name is required");
ctx.text("!!! name is required");
}
},
_ => ws::WsWriter::text(
ctx, &format!("!!! unknown command: {:?}", m)),
_ => ctx.text(format!("!!! unknown command: {:?}", m)),
}
} else {
let msg = if let Some(ref name) = self.name {
@ -137,7 +152,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()})
@ -145,82 +160,50 @@ impl Handler<ws::Message> for WsChatSession {
},
ws::Message::Binary(bin) =>
println!("Unexpected binary"),
ws::Message::Closed | ws::Message::Error => {
ws::Message::Close(_) => {
ctx.stop();
}
_ => (),
}
Self::empty()
}
}
impl StreamHandler<ws::Message> for WsChatSession
{
/// Method is called when stream get polled first time.
/// We register ws session with ChatServer
fn started(&mut self, ctx: &mut Self::Context) {
// register self in chat server. `AsyncContext::wait` register
// future within context, but context waits until this future resolves
// before processing any other events.
// HttpContext::state() is instance of WsChatSessionState, state is shared across all
// routes within application
let subs = ctx.sync_subscriber();
ctx.state().addr.call(
self, server::Connect{addr: subs}).then(
|res, act, ctx| {
match res {
Ok(Ok(res)) => act.id = res,
// something is wrong with chat server
_ => ctx.stop(),
}
fut::ok(())
}).wait(ctx);
}
/// Method is called when stream finishes, even if stream finishes with error.
fn finished(&mut self, ctx: &mut Self::Context) {
// notify chat server
ctx.state().addr.send(server::Disconnect{id: self.id});
ctx.stop()
}
}
fn main() {
let _ = env_logger::init();
let sys = actix::System::new("websocket-example");
// Start chat server actor in separate thread
let server: SyncAddress<_> =
Arbiter::start(|_| server::ChatServer::default());
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(())
}));
// Websocket sessions state
let state = WsChatSessionState { addr: server };
// Create Http server with websocket support
HttpServer::new(
Application::builder("/", state)
// redirect to websocket.html
.resource("/", |r|
r.handler(Method::GET, |req, payload, state| {
Ok(httpcodes::HTTPFound
.builder()
.header("LOCATION", "/static/websocket.html")
.body(Body::Empty)?)
}))
// websocket
.resource("/ws/", |r| r.get::<WsChatSession>())
// static resources
.route_handler("/static", StaticFiles::new("static/", true)))
.serve::<_, ()>("127.0.0.1:8080").unwrap();
let addr = HttpServer::new(
move || {
// Websocket sessions state
let state = WsChatSessionState { addr: server.clone() };
Application::with_state(state)
// redirect to websocket.html
.resource("/", |r| r.method(Method::GET).f(|_| {
httpcodes::HTTPFound
.build()
.header("LOCATION", "/static/websocket.html")
.finish()
}))
// websocket
.resource("/ws/", |r| r.route().f(chat_route))
// static resources
.handler("/static/", fs::StaticFiles::new("static/", true))
})
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -12,29 +12,20 @@ use session;
/// Message for chat server communications
/// New chat session is created
#[derive(Message)]
#[rtype(usize)]
pub struct Connect {
pub addr: Box<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
#[derive(Message)]
pub struct Disconnect {
pub id: usize,
}
impl ResponseType for Disconnect {
type Item = ();
type Error = ();
}
/// Send message to specific room
#[derive(Message)]
pub struct Message {
/// Id of the client session
pub id: usize,
@ -44,20 +35,15 @@ pub struct Message {
pub room: String,
}
impl ResponseType for Message {
type Item = ();
type Error = ();
}
/// 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.
#[derive(Message)]
pub struct Join {
/// Client id
pub id: usize,
@ -65,15 +51,10 @@ pub struct Join {
pub name: String,
}
impl ResponseType for Join {
type Item = ();
type Error = ();
}
/// `ChatServer` manages chat rooms and responsible for coordinating chat session.
/// implementation is super primitive
pub struct ChatServer {
sessions: HashMap<usize, Box<Subscriber<session::Message> + Send>>,
sessions: HashMap<usize, Recipient<Syn, session::Message>>,
rooms: HashMap<String, HashSet<usize>>,
rng: RefCell<ThreadRng>,
}
@ -99,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()));
}
}
}
@ -118,8 +99,9 @@ impl Actor for ChatServer {
///
/// Register new session and assign unique id to this session
impl Handler<Connect> for ChatServer {
type Result = usize;
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Response<Self, Connect> {
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Self::Result {
println!("Someone joined");
// notify all users in same room
@ -133,14 +115,15 @@ impl Handler<Connect> for ChatServer {
self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
// send id back
Self::reply(id)
id
}
}
/// Handler for Disconnect message.
impl Handler<Disconnect> for ChatServer {
type Result = ();
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) -> Response<Self, Disconnect> {
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
println!("Someone disconnected");
let mut rooms: Vec<String> = Vec::new();
@ -158,40 +141,39 @@ impl Handler<Disconnect> for ChatServer {
for room in rooms {
self.send_message(&room, "Someone disconnected", 0);
}
Self::empty()
}
}
/// Handler for Message message.
impl Handler<Message> for ChatServer {
type Result = ();
fn handle(&mut self, msg: Message, _: &mut Context<Self>) -> Response<Self, Message> {
fn handle(&mut self, msg: Message, _: &mut Context<Self>) {
self.send_message(&msg.room, msg.msg.as_str(), msg.id);
Self::empty()
}
}
/// Handler for `ListRooms` message.
impl Handler<ListRooms> for ChatServer {
type Result = MessageResult<ListRooms>;
fn handle(&mut self, _: ListRooms, _: &mut Context<Self>) -> Response<Self, ListRooms> {
fn handle(&mut self, _: ListRooms, _: &mut Context<Self>) -> Self::Result {
let mut rooms = Vec::new();
for key in self.rooms.keys() {
rooms.push(key.to_owned())
}
Self::reply(rooms)
MessageResult(rooms)
}
}
/// Join room, send disconnect message to old room
/// send join message to new room
impl Handler<Join> for ChatServer {
type Result = ();
fn handle(&mut self, msg: Join, _: &mut Context<Self>) -> Response<Self, Join> {
fn handle(&mut self, msg: Join, _: &mut Context<Self>) {
let Join {id, name} = msg;
let mut rooms = Vec::new();
@ -211,7 +193,5 @@ impl Handler<Join> for ChatServer {
}
self.send_message(&name, "Someone connected", id);
self.rooms.get_mut(&name).unwrap().insert(id);
Self::empty()
}
}

View File

@ -4,113 +4,102 @@ 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::*;
use actix::prelude::*;
use server::{self, ChatServer};
use codec::{ChatRequest, ChatResponse, ChatCodec};
/// Chat server sends this messages to session
#[derive(Message)]
pub struct Message(pub String);
impl ResponseType for Message {
type Item = ();
type Error = ();
}
/// `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>;
/// To use `FramedContext` we have to define Io type and Codec
impl FramedActor for ChatSession {
type Io = TcpStream;
type Codec= ChatCodec;
}
/// Also `FramedContext` requires Actor which is able to handle stream
/// of `<Codec as Decoder>::Item` items.
impl StreamHandler<ChatRequest, io::Error> for ChatSession {
fn started(&mut self, ctx: &mut FramedContext<Self>) {
fn started(&mut self, ctx: &mut Self::Context) {
// we'll start heartbeat process on session start.
self.hb(ctx);
// register self in chat server. `AsyncContext::wait` register
// future within context, but context waits until this future resolves
// before processing any other events.
self.addr.call(self, server::Connect{addr: ctx.sync_subscriber()}).then(|res, act, ctx| {
match res {
Ok(Ok(res)) => act.id = res,
// something is wrong with chat server
_ => ctx.stop(),
}
fut::ok(())
}).wait(ctx);
let addr: Addr<Syn, _> = ctx.address();
self.addr.send(server::Connect{addr: addr.recipient()})
.into_actor(self)
.then(|res, act, ctx| {
match res {
Ok(res) => act.id = res,
// something is wrong with chat server
_ => ctx.stop(),
}
actix::fut::ok(())
}).wait(ctx);
}
fn finished(&mut self, ctx: &mut FramedContext<Self>) {
fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
// notify chat server
self.addr.send(server::Disconnect{id: self.id});
ctx.stop()
self.addr.do_send(server::Disconnect{id: self.id});
Running::Stop
}
}
impl Handler<ChatRequest, io::Error> for ChatSession {
impl actix::io::WriteHandler<io::Error> for ChatSession {}
/// We'll stop chat session actor on any error, high likely it is just
/// termination of the tcp stream.
fn error(&mut self, _: io::Error, ctx: &mut FramedContext<Self>) {
ctx.stop()
}
/// 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: ChatRequest, ctx: &mut FramedContext<Self>)
-> Response<Self, ChatRequest>
{
fn handle(&mut self, msg: ChatRequest, ctx: &mut Context<Self>) {
match msg {
ChatRequest::List => {
// Send ListRooms message to chat server and wait for response
println!("List rooms");
self.addr.call(self, server::ListRooms).then(|res, _, ctx| {
match res {
Ok(Ok(rooms)) => {
let _ = ctx.send(ChatResponse::Rooms(rooms));
},
_ => println!("Something is wrong"),
}
fut::ok(())
}).wait(ctx)
self.addr.send(server::ListRooms)
.into_actor(self)
.then(|res, act, ctx| {
match res {
Ok(rooms) => {
act.framed.write(ChatResponse::Rooms(rooms));
},
_ => println!("Something is wrong"),
}
actix::fut::ok(())
}).wait(ctx)
// .wait(ctx) pauses all events in context,
// so actor wont receive any new messages until it get list of rooms back
},
ChatRequest::Join(name) => {
println!("Join to room: {}", name);
self.room = name.clone();
self.addr.send(server::Join{id: self.id, name: name.clone()});
let _ = ctx.send(ChatResponse::Joined(name));
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.send(
self.addr.do_send(
server::Message{id: self.id,
msg: message, room:
self.room.clone()})
@ -119,35 +108,32 @@ impl Handler<ChatRequest, io::Error> for ChatSession {
ChatRequest::Ping =>
self.hb = Instant::now(),
}
Self::empty()
}
}
/// Handler for Message, chat server sends this message, we just send string to peer
impl Handler<Message> for ChatSession {
type Result = ();
fn handle(&mut self, msg: Message, ctx: &mut FramedContext<Self>)
-> Response<Self, Message>
{
fn handle(&mut self, msg: Message, ctx: &mut Context<Self>) {
// send message to peer
let _ = ctx.send(ChatResponse::Message(msg.0));
Self::empty()
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) {
@ -155,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();
@ -188,7 +173,9 @@ impl TcpServer {
// So to be able to handle this events `Server` actor has to implement
// stream handler `StreamHandler<(TcpStream, net::SocketAddr), io::Error>`
let _: () = TcpServer::create(|ctx| {
ctx.add_stream(listener.incoming().map(|(t, a)| TcpConnect(t, a)));
ctx.add_message_stream(listener.incoming()
.map_err(|_| ())
.map(|(t, a)| TcpConnect(t, a)));
TcpServer{chat: chat}
});
}
@ -200,27 +187,21 @@ impl Actor for TcpServer {
type Context = Context<Self>;
}
#[derive(Message)]
struct TcpConnect(TcpStream, net::SocketAddr);
impl ResponseType for TcpConnect {
type Item = ();
type Error = ();
}
/// Handle stream of TcpStream's
impl StreamHandler<TcpConnect, io::Error> for TcpServer {}
impl Handler<TcpConnect> for TcpServer {
type Result = ();
impl Handler<TcpConnect, io::Error> for TcpServer {
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) -> Response<Self, TcpConnect>
{
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) {
// For each incoming connection we create `ChatSession` actor
// with out chat server address.
let server = self.chat.clone();
let _: () = ChatSession::new(server).framed(msg.0, ChatCodec);
// this is response for message, which is defined by `ResponseType` trait
// in this case we just return unit.
Self::empty()
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

@ -1,85 +0,0 @@
//! Simple echo websocket server.
//! Open `http://localhost:8080/ws/index.html` in browser
//! or [python console client](https://github.com/actix/actix-web/blob/master/examples/websocket-client.py)
//! could be used for testing.
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix::*;
use actix_web::*;
struct MyWebSocket;
impl Actor for MyWebSocket {
type Context = HttpContext<Self>;
}
/// Http route handler
impl Route for MyWebSocket {
type State = ();
fn request(req: &mut HttpRequest,
payload: Payload, ctx: &mut HttpContext<Self>) -> RouteResult<Self>
{
// websocket handshake
let resp = ws::handshake(req)?;
// send HttpResponse back to peer
ctx.start(resp);
// convert bytes stream to a stream of `ws::Message` and register it
ctx.add_stream(ws::WsStream::new(payload));
Reply::async(MyWebSocket)
}
}
/// Standard actix's stream handler for a stream of `ws::Message`
impl StreamHandler<ws::Message> for MyWebSocket {
fn started(&mut self, ctx: &mut Self::Context) {
println!("WebSocket session openned");
}
fn finished(&mut self, ctx: &mut Self::Context) {
println!("WebSocket session closed");
}
}
impl Handler<ws::Message> for MyWebSocket {
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
-> Response<Self, ws::Message>
{
// process websocket messages
println!("WS: {:?}", 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 => {
ctx.stop();
}
_ => (),
}
Self::empty()
}
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("ws-example");
HttpServer::new(
Application::default("/")
// enable logger
.middleware(Logger::new(None))
// websocket route
.resource("/ws/", |r| r.get::<MyWebSocket>())
.route_handler("/", StaticFiles::new("examples/static/", true)))
// start http server on 127.0.0.1:8080
.serve::<_, ()>("127.0.0.1:8080").unwrap();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -0,0 +1,20 @@
[package]
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"
tokio-core = "0.1"
actix = "0.5"
actix-web = { path="../../" }

View File

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

View File

@ -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, WsError, WsClient, WsClientWriter};
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(
WsClient::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(WsClientWriter);
#[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.as_str())
}
}
/// Handle server websocket messages
impl StreamHandler<Message, WsError> 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

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

7
guide/book.toml Normal file
View File

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

16
guide/src/SUMMARY.md Normal file
View File

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

34
guide/src/qs_1.md Normal file
View File

@ -0,0 +1,34 @@
# Quick start
Before you can start writing a actix web application, youll need a version of Rust installed.
We recommend you use rustup to install or configure such a version.
## Install Rust
Before we begin, we need to install Rust using the [rustup](https://www.rustup.rs/) installer:
```bash
curl https://sh.rustup.rs -sSf | sh
```
If you already have rustup installed, run this command to ensure you have the latest version of Rust:
```bash
rustup update
```
Actix web framework requires rust version 1.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:
```bash
git clone https://github.com/actix/actix-web
cd actix-web/examples/basics
cargo run
```
Check [examples/](https://github.com/actix/actix-web/tree/master/examples) directory for more examples.

212
guide/src/qs_10.md Normal file
View File

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

49
guide/src/qs_12.md Normal file
View File

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

44
guide/src/qs_13.md Normal file
View File

@ -0,0 +1,44 @@
# HTTP/2.0
Actix web automatically upgrades connection to *HTTP/2.0* if possible.
## Negotiation
*HTTP/2.0* protocol over tls without prior knowledge requires
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
`rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation.
With enable `alpn` feature `HttpServer` provides
[serve_tls](../actix_web/struct.HttpServer.html#method.serve_tls) method.
```toml
[dependencies]
actix-web = { 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() {
// 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()
.resource("/index.html", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap();
.serve_ssl(builder).unwrap();
}
```
Upgrade to *HTTP/2.0* schema described in
[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported.
Starting *HTTP/2* with prior knowledge is supported for both clear text connection
and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4)
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls)
for concrete example.

125
guide/src/qs_14.md Normal file
View File

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

98
guide/src/qs_2.md Normal file
View File

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

109
guide/src/qs_3.md Normal file
View File

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

193
guide/src/qs_3_5.md Normal file
View File

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

245
guide/src/qs_4.md Normal file
View File

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

151
guide/src/qs_4_5.md Normal file
View File

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

575
guide/src/qs_5.md Normal file
View File

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

287
guide/src/qs_7.md Normal file
View File

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

154
guide/src/qs_8.md Normal file
View File

@ -0,0 +1,154 @@
# Testing
Every application should be well tested and. Actix provides the tools to perform unit and
integration tests.
## Unit tests
For unit testing actix provides request builder type and simple handler runner.
[*TestRequest*](../actix_web/test/struct.TestRequest.html) implements builder-like pattern.
You can generate `HttpRequest` instance with `finish()` method or you can
run your handler with `run()` or `run_async()` methods.
```rust
# extern crate http;
# extern crate actix_web;
use http::{header, StatusCode};
use actix_web::*;
use actix_web::test::TestRequest;
fn index(req: HttpRequest) -> HttpResponse {
if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
if let Ok(s) = hdr.to_str() {
return httpcodes::HTTPOk.into()
}
}
httpcodes::HTTPBadRequest.into()
}
fn main() {
let resp = TestRequest::with_header("content-type", "text/plain")
.run(index)
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let resp = TestRequest::default()
.run(index)
.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
```
## Integration tests
There are several methods how you can test your application. Actix provides
[*TestServer*](../actix_web/test/struct.TestServer.html)
server that could be used to run whole application of just specific handlers
in real http server. *TrstServer::get()*, *TrstServer::post()* or *TrstServer::client()*
methods could be used to send request to test 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.
```rust
# extern crate actix_web;
use actix_web::*;
use actix_web::test::TestServer;
fn index(req: HttpRequest) -> HttpResponse {
httpcodes::HTTPOk.into()
}
fn main() {
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.
```rust
# extern crate http;
# extern crate actix_web;
use http::Method;
use actix_web::*;
use actix_web::test::TestServer;
fn index(req: HttpRequest) -> HttpResponse {
httpcodes::HTTPOk.into()
}
/// This function get called by http server.
fn create_app() -> Application {
Application::new()
.resource("/test", |r| r.h(index))
}
fn main() {
let mut srv = TestServer::with_factory(create_app); // <- Start new test server
let request = srv.client(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
}
```
## WebSocket server tests
It is possible to register *handler* with `TestApp::handler()` method that
initiate web socket connection. *TestServer* provides `ws()` which connects to
websocket server and returns ws reader and writer objects. *TestServer* also
provides `execute()` method which runs future object to completion and returns
result of the future computation.
Here is simple example, that shows how to test server 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::WsError> 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())));
}
```

48
guide/src/qs_9.md Normal file
View File

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

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