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

Compare commits

...

235 Commits

Author SHA1 Message Date
6acb6dd4e7 set release date 2018-03-02 22:31:58 -08:00
791a980e2d update tests 2018-03-02 22:08:56 -08:00
c2d8abcee7 Fix disconnect on idle connections 2018-03-02 20:47:23 -08:00
16c05f07ba make HttpRequest::match_info_mut() public 2018-03-02 20:40:08 -08:00
2158ad29ee add Pattern::with_prefix, make it usable outside of actix 2018-03-02 20:39:22 -08:00
feba5aeffd bump version 2018-03-02 14:31:23 -08:00
343888017e Update CHANGES.md 2018-03-02 12:26:31 -08:00
3a5d445b2f Merge pull request #89 from niklasf/csrf-middleware
add csrf filter middleware
2018-03-02 12:25:23 -08:00
e60acb7607 Merge branch 'master' into csrf-middleware 2018-03-02 12:25:05 -08:00
bebfc6c9b5 sleep for test 2018-03-02 11:32:37 -08:00
3b2928a391 Better naming for websockets implementation 2018-03-02 11:29:55 -08:00
10f57dac31 add csrf filter middleware 2018-03-02 20:13:43 +01:00
b640b49b05 adjust low buf size 2018-03-01 20:13:50 -08:00
1fea4bd9a6 prepare release 2018-03-01 20:01:25 -08:00
206c4e581a rename httpcodes 2018-03-01 19:12:59 -08:00
4e13505b92 rename .p to a .filter 2018-03-01 18:42:50 -08:00
5b6d7cddbf Fix payload parse in situation when socket data is not ready 2018-03-01 18:27:04 -08:00
4aaf9f08f8 update readme 2018-02-28 22:31:54 -08:00
b0ba23ff55 Merge pull request #88 from rofrol/patch-2
be consistent with host - had CORS preflight once
2018-02-28 17:07:57 -08:00
42b19b1819 Merge branch 'master' into patch-2 2018-02-28 17:07:44 -08:00
0335fde3f9 Update README.md 2018-02-28 16:58:05 -08:00
f27edbff89 be consistent with host - had CORS preflight once 2018-03-01 01:01:27 +01:00
d62d6e68e0 use new version of http crate 2018-02-28 14:16:55 -08:00
1284264511 Update CHANGES.md 2018-02-28 12:35:16 -08:00
d977fe563b Merge pull request #87 from adwhit/fix-session-set
fix session mut borrow lifetime
2018-02-28 12:34:46 -08:00
bb68f9dd90 add session borrow fix to changes 2018-02-28 19:52:53 +00:00
313396d9b5 fix session mut borrow lifetime 2018-02-28 19:35:26 +00:00
171a23561e export Drain 2018-02-28 11:10:54 -08:00
67f33a4760 add redis session example 2018-02-28 10:26:40 -08:00
764421fe44 update categories 2018-02-27 23:51:57 -08:00
b339ea0a3a update versions in guide 2018-02-27 23:31:43 -08:00
8994732227 doc strings 2018-02-27 23:30:26 -08:00
7591592279 fix handle big data chunkd for parsing 2018-02-27 23:04:57 -08:00
4a48b43927 big value 2018-02-27 21:49:08 -08:00
b1ad4763a2 check juniper example 2018-02-27 21:23:41 -08:00
2f3a2115c0 Merge pull request #86 from pyros2097/master
add juniper example
2018-02-27 21:21:52 -08:00
1283c00583 add juniper example 2018-02-28 10:41:24 +05:30
9f81eae215 build docs on nightly 2018-02-27 21:04:22 -08:00
ccb6ebb259 headers test 2018-02-27 20:49:53 -08:00
da76de76f0 upgrade sha crate 2018-02-27 20:32:51 -08:00
c316a99746 stop server test 2018-02-27 20:04:01 -08:00
1e2aa4fc90 mark context as modified after writing data 2018-02-27 18:05:06 -08:00
e2c8f17c2c drop connection if handler get dropped without consuming payload 2018-02-27 16:08:57 -08:00
9b06eac720 Merge branch 'master' of github.com:actix/actix-web 2018-02-27 15:41:53 -08:00
4f99cd1580 add ws error tracing 2018-02-27 15:38:57 -08:00
f56fa49a9b Merge pull request #84 from mpaltun/patch-1
Fix typos in README
2018-02-27 15:18:16 -08:00
1f063e4136 move with_connector method to ClientRequestBuilder 2018-02-27 15:14:33 -08:00
33c935dccc Fix typos in README 2018-02-28 01:13:59 +02:00
a7bf635158 unify headers and body processing for client response and server request 2018-02-27 15:03:28 -08:00
aac9b5a97c update readme 2018-02-27 12:49:11 -08:00
6c480fae90 added HttpRequest::encoding() method; fix urlencoded parsing with charset 2018-02-27 11:31:54 -08:00
5dcb558f50 refactor websockets handling 2018-02-27 10:09:24 -08:00
a344c3a02e remove read buffer management api 2018-02-26 20:07:22 -08:00
0ab8bc11f3 fix guide example 2018-02-26 16:41:57 -08:00
abae65a49e remove unused code 2018-02-26 16:11:00 -08:00
d6fd4a3524 use buffer capacity; remove unused imports 2018-02-26 15:34:25 -08:00
72aa2d9eae clippy warnings 2018-02-26 14:33:56 -08:00
644f1a9518 refactor ws frame parser 2018-02-26 13:58:23 -08:00
56ae565688 fix guide examples 2018-02-26 08:02:58 -08:00
0a3b776aa7 refactor multipart stream 2018-02-26 06:00:54 +03:00
6ef9c60361 add Read and AsyncRead impl to HttpRequest 2018-02-25 21:26:58 +03:00
a2b98b31e8 refactor payload related futures for HttpRequest 2018-02-25 20:34:26 +03:00
ab5ed27bf1 refactor and simplify content encoding 2018-02-25 11:43:00 +03:00
141b992450 Make payload and httprequest a stream 2018-02-25 11:21:45 +03:00
4e41e13baf refactor client payload processing 2018-02-25 11:18:17 +03:00
ea8e8e75a2 fix websocket example 2018-02-24 08:41:58 +03:00
a855c8b2c9 better ergonomics for WsClient::client() 2018-02-24 08:14:21 +03:00
fd31eb74c5 better ergonomics for ws client 2018-02-24 07:36:50 +03:00
3b22b1b168 Merge pull request #78 from pyros2097/master
Fix websocket example path
2018-02-23 01:47:40 -08:00
7a7df7f8fb Merge branch 'master' into master 2018-02-23 01:47:30 -08:00
25aabfb3e2 fix big ws frames 2018-02-23 10:45:33 +01:00
3a3657cfaf Update qs_9.md 2018-02-23 12:39:19 +05:30
aff43cc8b8 fix routes registration order 2018-02-22 05:48:18 -08:00
4a9c1ae894 allow to use Connection for sending client request 2018-02-21 22:53:23 -08:00
4a07430e8e remove RegexSet mention 2018-02-21 22:04:59 -08:00
9a076c69d1 update route matching guide section 2018-02-21 22:00:22 -08:00
8f2d3a0a76 fix NormalizePath helper 2018-02-21 14:53:42 -08:00
d4611f8bb9 Merge branch 'master' of github.com:actix/actix-web 2018-02-21 14:31:31 -08:00
fd56e5dc82 do not use regset for route recognition 2018-02-21 14:31:22 -08:00
7c74259453 Merge pull request #77 from rofrol/patch-1
could used -> could be used, latest actix sync
2018-02-21 10:00:08 -08:00
6a01af32bc could used -> could be used, latest actix sync 2018-02-21 18:59:00 +01:00
5634e5794f more tests for NormalizePath helper 2018-02-20 13:03:21 -08:00
187644e178 update logger doc string 2018-02-20 12:53:51 -08:00
7198dde465 add logger info 2018-02-20 12:49:42 -08:00
2374aa42ed set date header for client requests 2018-02-19 23:18:18 -08:00
03912d2089 support client request's async body 2018-02-19 22:48:27 -08:00
3f95cce9e8 allow to pass different binary data 2018-02-19 20:03:57 -08:00
979cea03ac added TestRequest::set_payload() 2018-02-19 20:01:38 -08:00
6424defee6 code coverage on 1.21 2018-02-19 17:32:22 -08:00
6ee14efbe2 optimize http message serialization 2018-02-19 17:21:04 -08:00
4d81186059 escape router pattern re 2018-02-19 14:57:57 -08:00
ddc82395e8 try to remove trailing slash for normalize path handler 2018-02-19 14:27:36 -08:00
360ffbba68 clone router with httprequest 2018-02-19 14:26:51 -08:00
f2f1798215 allow to send request using custom connector 2018-02-19 13:41:21 -08:00
548f4e4d62 replace reqwest with actix::client 2018-02-19 13:18:18 -08:00
cb70d5ec3d refactor http client 2018-02-19 03:11:11 -08:00
edd114f6e4 allow to set default content encoding on application level 2018-02-18 22:23:17 -08:00
816c6fb0e0 log 5xx responses as error 2018-02-18 09:57:57 -08:00
0da382a7a4 use actix 0.5 release 2018-02-17 13:33:38 -08:00
3e3d3279b8 deregister server socket on shutdown 2018-02-16 09:42:15 -08:00
3c95823e53 add r2d2 example 2018-02-15 23:05:10 -08:00
8607c51bcf do not stop accept thread on error 2018-02-15 22:02:03 -08:00
080bb3e5ae disable dead code link 2018-02-15 16:25:43 -08:00
d31e71a169 update examples 2018-02-15 13:59:25 -08:00
7b0e1642b6 add techempower benchmark link 2018-02-15 09:53:09 -08:00
096dee519c Merge pull request #71 from rbtcollins/patch-1
Wait for spawned thread
2018-02-13 14:58:11 -08:00
8bce3b9d10 Merge branch 'master' into patch-1 2018-02-13 14:57:59 -08:00
b28ecbcf0c Update qs_2.md 2018-02-14 10:37:12 +13:00
8f9ec5c23c fix doc test 2018-02-13 07:50:49 -08:00
96b87761d1 update examples 2018-02-12 23:13:06 -08:00
b1eec3131f use newer api 2018-02-12 22:56:47 -08:00
a544034c06 use Recipient 2018-02-12 22:09:31 -08:00
4b8181476c consistently use #[cause] and display causing errors (#73) 2018-02-12 23:55:44 -06:00
eb041de36d update examples 2018-02-12 19:15:39 -08:00
80285f2a32 fix doc test 2018-02-12 18:38:13 -08:00
de869ed879 Merge pull request #72 from rbtcollins/patch-2
Use AtomicUsize properly
2018-02-12 17:46:03 -08:00
7ccacb92ce update websocket-chat example 2018-02-12 17:42:10 -08:00
57655d8153 Use AtomicUsize properly
doing a read+write on an atomic int will lose updates from other threads.
2018-02-13 13:47:59 +13:00
335ca8ff33 use new actix api 2018-02-12 16:08:04 -08:00
720d8c36c1 update names 2018-02-12 12:45:08 -08:00
8c1b5fa945 sync with latest actix 2018-02-12 12:17:30 -08:00
232aba2080 Wait for spawned thread
A spawned thread doesn't block the main thread exiting unless explicitly joined.
The demo as written in the guide simply exits immediately at the moment.
2018-02-12 23:52:03 +13:00
30bdf9cb5e update actix api 2018-02-12 01:13:06 -08:00
285c66e7d8 build docs for apln and tls features 2018-02-10 11:39:12 -08:00
856055c6ca simplify HttpServer::start_tls() method 2018-02-10 11:34:54 -08:00
e3081306da update doc string 2018-02-10 11:29:40 -08:00
94c4053cb5 more HttpServer type simplification 2018-02-10 11:01:54 -08:00
762961b0f4 simplify HttpServer type definition 2018-02-10 10:22:03 -08:00
3109f9be62 special handling for upgraded pipeline 2018-02-10 00:05:20 -08:00
2d049e4a9f update example 2018-02-09 22:46:34 -08:00
0c98775b51 refactor h1 stream polling 2018-02-09 22:26:48 -08:00
b4b5c78b51 optimize ws frame generation 2018-02-09 20:43:14 -08:00
78da98a16d add wsload tool; optimize ws frame parser 2018-02-09 17:20:28 -08:00
74377ef73d fix back pressure for h1 import stream 2018-02-09 16:20:10 -08:00
728377a447 fix example 2018-02-08 20:55:34 -08:00
73ed1342eb more actix compatibility 2018-02-08 17:13:56 -08:00
bc6300be34 actix compatibility 2018-02-08 17:08:57 -08:00
2faf3a5eb6 fix deprecation warnings, update actix 2018-02-08 17:00:22 -08:00
6181a84d7b update websocket-chat example 2018-02-08 14:03:41 -08:00
f8f99ec0c7 Disable signals in HttpServers started by the tests. (#69)
Something is wrong with signals on windows.
This change causes the unit tests to pass on Windows.
2018-02-08 14:55:47 -06:00
bd03ba1192 Update most examples to use actix from git (#68) 2018-02-08 00:08:36 -06:00
d0cbf7cd25 upgrade trust-dns-resolver 2018-02-07 14:58:08 -08:00
93aa220e8d remove default impl for std error, it prevents use of Fail 2018-02-07 13:57:58 -08:00
81e4fb9353 Avoid using Path to calculate URIs, because it doesn't do the right thing on Windows (#67)
Redirecting to index files now always uses `/` instead of backslash on windows.
2018-02-07 15:31:09 -06:00
884ea02f5c Allow returning failure::Error from handlers (#65)
This implements From<failure::Error> for Error (by way of `failure::Compat`)
and ResponseError for failure::Compat<T>.
2018-02-06 10:26:50 -06:00
b6d5516e3a remove rust_backtrace for appveyor 2018-02-04 10:48:16 -08:00
46841cc87e update config for appveyor 2018-02-04 10:31:39 -08:00
7ad66956b2 add HttpRequest::uri_mut(), allows to modify request uri 2018-02-03 08:31:32 -08:00
d568161852 update websocket-chat example 2018-02-03 08:25:31 -08:00
671ab35cf6 re enable 1.21 2018-02-02 21:32:43 -08:00
c63ad4b6f1 appveyor cfg 2018-02-02 21:31:16 -08:00
eb713bd60e update actix version 2018-02-01 01:08:08 -08:00
2b74fbf586 fix websocket example 2018-01-31 13:18:30 -08:00
58f85658bd update actix 2018-01-31 12:57:02 -08:00
7e9fbfca72 missing http codes 2018-01-31 12:34:58 -08:00
5115384501 Merge pull request #64 from andreevlex/fix-2
spelling check
2018-01-31 10:54:05 -08:00
a1b96b1cf4 return "chnked" value 2018-01-31 21:37:12 +03:00
a565e71018 spelling check 2018-01-31 20:28:53 +03:00
e41b175e3d Update README.md 2018-01-31 06:40:00 -08:00
db39f122be Update README.md 2018-01-31 06:37:37 -08:00
afd2dc4666 Update README.md 2018-01-31 06:36:15 -08:00
cba7e426a5 Update README.md 2018-01-31 06:35:47 -08:00
01e7cc9620 Update README.md 2018-01-31 06:34:50 -08:00
5a5497b745 add close ws test 2018-01-30 16:04:04 -08:00
b698e3546b link to websocket example 2018-01-30 15:26:58 -08:00
e99a5e8144 drop local actix ref 2018-01-30 15:19:30 -08:00
577f91206c added support for websocket testing 2018-01-30 15:13:33 -08:00
76f9542df7 rename module 2018-01-30 13:04:52 -08:00
9739168d48 fix limit usage for Request/Response Body future 2018-01-30 12:44:14 -08:00
5cbaf3a1b8 add client ssl support 2018-01-30 11:17:17 -08:00
a02e0dfab6 initial work on client connector 2018-01-29 23:01:20 -08:00
5cc3bba5cc change ws client names 2018-01-29 15:45:37 -08:00
6e51573975 app veyor config 2018-01-29 14:51:34 -08:00
b686f39d0b complete impl for client request and response 2018-01-29 14:44:25 -08:00
6416a796c3 add ClientRequest and ClientRequestBuilder 2018-01-29 11:45:33 -08:00
b6a394a113 added StaticFiles::inex_file config 2018-01-29 03:23:45 -08:00
456fd1364a add handle method to contexts 2018-01-28 09:47:46 -08:00
f3cce6a04c update websocket example 2018-01-28 09:07:12 -08:00
9835a4537a update websocket example 2018-01-28 08:58:18 -08:00
715ec4ae2f update actix 2018-01-28 08:26:36 -08:00
55b2fb7f77 update example 2018-01-28 01:04:58 -08:00
7c7743c145 use right path 2018-01-27 22:52:17 -08:00
826fc62299 disable websocket-chat example 2018-01-27 22:44:50 -08:00
5dd2e7523d basic websocket client 2018-01-27 22:03:03 -08:00
4821d51167 fix actix compatibility 2018-01-27 11:15:03 -08:00
c446be48e3 min rust version 1.21 2018-01-27 10:58:09 -08:00
042f8391bb Merge branch 'master' of github.com:actix/actix-web 2018-01-27 10:05:07 -08:00
d4bc3294a3 actix compatibility 2018-01-27 10:04:56 -08:00
04d53d6f57 Merge pull request #59 from andreevlex/fix-cors
spelling check cors example
2018-01-26 21:42:36 -08:00
881e0e0346 spelling check 2018-01-27 08:38:17 +03:00
b9f8a00ba3 update cors example readme 2018-01-26 19:56:34 -08:00
99bed67bec rename cors example 2018-01-26 19:52:20 -08:00
52a454800f cleanup cors example 2018-01-26 19:51:13 -08:00
c09c8e4980 Merge pull request #58 from krircc/master
add actix-web-cors example
2018-01-26 19:43:40 -08:00
b931dda1fe Merge branch 'master' into master 2018-01-26 19:42:06 -08:00
74166b4834 add actix-web-cors example 2018-01-27 11:00:26 +08:00
4abb769ee5 fix request json loader; mime_type() method 2018-01-25 21:50:28 -08:00
e8e2ca1526 refactor alpn support; upgrade openssl to 0.10 2018-01-25 10:24:04 -08:00
78967dea13 stop http context immediately 2018-01-24 20:17:14 -08:00
58a5d493b7 re-eanble write backpressure for h1 connections 2018-01-24 20:12:49 -08:00
c5341017cd fix typo 2018-01-23 15:39:53 -08:00
f4873fcdee stop websocket context 2018-01-23 15:35:39 -08:00
35efd017bb impl waiting for HttpContext 2018-01-23 09:42:04 -08:00
fb76c490c6 mention tokio handle in guide 2018-01-22 20:10:05 -08:00
3653c78e92 check example on stable 2018-01-22 19:49:19 -08:00
1053c44326 pin new actix version 2018-01-22 17:01:54 -08:00
e6ea177181 impl WebsocketContext::waiting() method 2018-01-22 16:55:50 -08:00
1957469061 code of conduct 2018-01-21 15:29:02 -08:00
2227120ae0 exclude examples 2018-01-21 09:09:19 -08:00
21c8c0371d travis config 2018-01-21 08:50:29 -08:00
1914a6a0d8 Always enable content encoding if encoding explicitly selected 2018-01-21 08:31:46 -08:00
1cff4619e7 reduce threshold for content encoding 2018-01-21 08:12:32 -08:00
7bb7adf89c relax InternalError constraints 2018-01-20 22:02:42 -08:00
f55ff24925 fix guide example 2018-01-20 21:40:18 -08:00
f5f78d79e6 update doc strings 2018-01-20 21:16:31 -08:00
9180625dfd refactor helper error types 2018-01-20 21:11:46 -08:00
552320bae2 add error logging guide section 2018-01-20 20:21:01 -08:00
7cf221f767 Log request processing errors 2018-01-20 20:12:24 -08:00
98931a8623 test case for broken transfer encoding 2018-01-20 16:51:18 -08:00
ae10a89014 use ws masking from tungstenite project 2018-01-20 16:47:34 -08:00
71d534dadb CORS middleware: allowed_headers is defaulting to None #50 2018-01-20 16:36:57 -08:00
867bb1d409 Merge branch 'master' of github.com:actix/actix-web 2018-01-20 16:12:51 -08:00
91c44a1cf1 Fix HEAD requests handling 2018-01-20 16:12:38 -08:00
3bc60a8d5d Merge pull request #53 from andreevlex/spelling-check-2
spelling check
2018-01-16 12:07:58 -08:00
58df8fa4b9 spelling check 2018-01-16 21:59:33 +03:00
81f92b43e5 Merge pull request #52 from andreevlex/spelling-check
spelling check
2018-01-15 14:16:54 -08:00
e1d9c3803b spelling check 2018-01-16 00:47:25 +03:00
a7c24aace1 flush is useless 2018-01-14 19:28:34 -08:00
89a89e7b18 refactor shared bytes api 2018-01-14 17:00:28 -08:00
3425f7be40 fix tests 2018-01-14 14:58:58 -08:00
09a6f8a34f disable alpn feature 2018-01-14 14:44:32 -08:00
7060f298b4 use more binary 2018-01-14 14:40:39 -08:00
33dbe15760 use Binary for writer trait 2018-01-14 13:50:38 -08:00
e95c7dfc29 use local actix-web for examples 2018-01-13 19:04:07 -08:00
927a92fcac impl HttpHandler for Box<HttpHandler> and add helper method Application::boxed() #49 2018-01-13 18:58:17 -08:00
130 changed files with 8814 additions and 3442 deletions

View File

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

View File

@ -8,7 +8,7 @@ cache:
matrix: matrix:
include: include:
- rust: 1.20.0 - rust: 1.21.0
- rust: stable - rust: stable
- rust: beta - rust: beta
- rust: nightly - rust: nightly
@ -17,14 +17,14 @@ matrix:
- rust: beta - rust: beta
#rust: #rust:
# - 1.20.0 # - 1.21.0
# - stable # - stable
# - beta # - beta
# - nightly-2018-01-03 # - nightly-2018-01-03
env: env:
global: global:
- RUSTFLAGS="-C link-dead-code" # - RUSTFLAGS="-C link-dead-code"
- OPENSSL_VERSION=openssl-1.0.2 - OPENSSL_VERSION=openssl-1.0.2
before_install: before_install:
@ -46,17 +46,22 @@ script:
cargo clean cargo clean
USE_SKEPTIC=1 cargo test --features=alpn USE_SKEPTIC=1 cargo test --features=alpn
else else
cargo test --features=alpn cargo clean
cargo test -- --nocapture
# --features=alpn
fi fi
- | - |
if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cd examples/basics && cargo check && cd ../.. cd examples/basics && cargo check && cd ../..
cd examples/hello-world && cargo check && cd ../.. cd examples/hello-world && cargo check && cd ../..
cd examples/multipart && cargo check && cd ../.. cd examples/multipart && cargo check && cd ../..
cd examples/json && 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/template_tera && cargo check && cd ../..
cd examples/diesel && cargo check && cd ../.. cd examples/diesel && cargo check && cd ../..
cd examples/r2d2 && cargo check && cd ../..
cd examples/tls && cargo check && cd ../.. cd examples/tls && cargo check && cd ../..
cd examples/websocket-chat && cargo check && cd ../.. cd examples/websocket-chat && cargo check && cd ../..
cd examples/websocket && cargo check && cd ../.. cd examples/websocket && cargo check && cd ../..
@ -69,8 +74,8 @@ script:
# Upload docs # Upload docs
after_success: after_success:
- | - |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
cargo doc --features alpn --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 && echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
cargo install mdbook && cargo install mdbook &&
cd guide && mdbook build -d ../target/doc/guide && cd .. && cd guide && mdbook build -d ../target/doc/guide && cd .. &&
@ -80,7 +85,7 @@ after_success:
fi fi
- | - |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "1.21.0" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml USE_SKEPTIC=1 cargo tarpaulin --out Xml
bash <(curl -s https://codecov.io/bash) bash <(curl -s https://codecov.io/bash)

View File

@ -1,5 +1,78 @@
# Changes # Changes
## 0.4.2 (2018-03-02)
* Better naming for websockets implementation
* Add `Pattern::with_prefix()`, make it more usable outside of actix
* Add csrf middleware for filter for cross-site request forgery #89
* Fix disconnect on idle connections
## 0.4.1 (2018-03-01)
* Rename `Route::p()` to `Route::filter()`
* Better naming for http codes
* Fix payload parse in situation when socket data is not ready.
* Fix Session mutable borrow lifetime #87
## 0.4.0 (2018-02-28)
* Actix 0.5 compatibility
* Fix request json/urlencoded loaders
* Simplify HttpServer type definition
* Added HttpRequest::encoding() method
* Added HttpRequest::mime_type() method
* Added HttpRequest::uri_mut(), allows to modify request uri
* Added StaticFiles::index_file()
* Added http client
* Added websocket client
* Added TestServer::ws(), test websockets client
* Added TestServer http client support
* Allow to override content encoding on application level
## 0.3.3 (2018-01-25)
* Stop processing any events after context stop
* Re-enable write back-pressure for h1 connections
* Refactor HttpServer::start_ssl() method
* Upgrade openssl to 0.10
## 0.3.2 (2018-01-21)
* Fix HEAD requests handling
* Log request processing errors
* Always enable content encoding if encoding explicitly selected
* Allow multiple Applications on a single server with different state #49
* CORS middleware: allowed_headers is defaulting to None #50
## 0.3.1 (2018-01-13) ## 0.3.1 (2018-01-13)
* Fix directory entry path #47 * Fix directory entry path #47

46
CODE_OF_CONDUCT.md Normal file
View File

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

View File

@ -1,18 +1,20 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "0.3.1" version = "0.4.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web framework" description = "Actix web is a small, pragmatic, extremely fast, web framework for Rust."
readme = "README.md" readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://github.com/actix/actix-web" homepage = "https://github.com/actix/actix-web"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/" documentation = "https://docs.rs/actix-web/"
categories = ["network-programming", "asynchronous", categories = ["network-programming", "asynchronous",
"web-programming::http-server", "web-programming::websocket"] "web-programming::http-server",
"web-programming::http-client",
"web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", exclude = [".gitignore", ".travis.yml", ".cargo/config",
"appveyor.yml", "./examples/static/*"] "appveyor.yml", "/examples/**"]
build = "build.rs" build = "build.rs"
[badges] [badges]
@ -34,55 +36,55 @@ tls = ["native-tls", "tokio-tls"]
alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"] alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
[dependencies] [dependencies]
log = "0.4" base64 = "0.9"
failure = "0.1" bitflags = "1.0"
failure_derive = "0.1" brotli2 = "^0.3.2"
failure = "0.1.1"
flate2 = "1.0"
h2 = "0.1" h2 = "0.1"
http = "^0.1.2" http = "^0.1.5"
httparse = "1.2" httparse = "1.2"
http-range = "0.1" http-range = "0.1"
time = "0.1" libc = "0.2"
log = "0.4"
mime = "0.3" mime = "0.3"
mime_guess = "1.8" mime_guess = "1.8"
num_cpus = "1.0"
percent-encoding = "1.0"
rand = "0.4"
regex = "0.2" regex = "0.2"
sha1 = "0.4"
url = "1.6"
libc = "0.2"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
brotli2 = "^0.3.2" sha1 = "0.6"
percent-encoding = "1.0"
smallvec = "0.6" smallvec = "0.6"
bitflags = "1.0" time = "0.1"
num_cpus = "1.0" encoding = "0.2"
flate2 = "1.0" url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode", "secure"] } cookie = { version="0.10", features=["percent-encode", "secure"] }
# io # io
mio = "0.6" mio = "^0.6.13"
net2 = "0.2" net2 = "0.2"
bytes = "0.4" bytes = "0.4"
byteorder = "1"
futures = "0.1" futures = "0.1"
tokio-io = "0.1" tokio-io = "0.1"
tokio-core = "0.1" tokio-core = "0.1"
trust-dns-resolver = "0.8"
# native-tls # native-tls
native-tls = { version="0.1", optional = true } native-tls = { version="0.1", optional = true }
tokio-tls = { version="0.1", optional = true } tokio-tls = { version="0.1", optional = true }
# openssl # openssl
tokio-openssl = { version="0.1", optional = true } openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }
[dependencies.actix] [dependencies.actix]
version = "^0.4.2" version = "^0.5.1"
[dependencies.openssl]
version = "0.9"
optional = true
[dev-dependencies] [dev-dependencies]
env_logger = "0.4" env_logger = "0.5"
reqwest = "0.8"
skeptic = "0.13" skeptic = "0.13"
serde_derive = "1.0" serde_derive = "1.0"
@ -93,19 +95,24 @@ version_check = "0.1"
[profile.release] [profile.release]
lto = true lto = true
opt-level = 3 opt-level = 3
# debug = true codegen-units = 1
[workspace] [workspace]
members = [ members = [
"./", "./",
"examples/basics", "examples/basics",
"examples/juniper",
"examples/diesel", "examples/diesel",
"examples/r2d2",
"examples/json", "examples/json",
"examples/hello-world", "examples/hello-world",
"examples/multipart", "examples/multipart",
"examples/state", "examples/state",
"examples/redis-session",
"examples/template_tera", "examples/template_tera",
"examples/tls", "examples/tls",
"examples/websocket", "examples/websocket",
"examples/websocket-chat", "examples/websocket-chat",
"examples/web-cors/backend",
"tools/wsload/",
] ]

View File

@ -1,6 +1,33 @@
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) # Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Actix web is a small, fast, pragmatic, open source rust web framework. 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
* SSL support with openssl or native-tls
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
[Redis sessions](https://github.com/actix/actix-redis),
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers),
[CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html))
* Built on top of [Actix actor framework](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.21 or later
## Example
```rust,ignore ```rust,ignore
extern crate actix_web; extern crate actix_web;
@ -19,40 +46,11 @@ fn main() {
} }
``` ```
## Documentation ### More examples
* [User Guide](http://actix.github.io/actix-web/guide/)
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
* [API Documentation (Releases)](https://docs.rs/actix-web/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.20 or later
## Features
* Supported *HTTP/1.x* and *HTTP/2.0* protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* [WebSockets](https://actix.github.io/actix-web/actix_web/ws/index.html)
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable request routing
* Graceful server shutdown
* Multipart streams
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers),
[CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html))
* Built on top of [Actix](https://github.com/actix/actix).
## Benchmarks
Some basic benchmarks could be found in this [respository](https://github.com/fafhrd91/benchmarks).
## Examples
* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/) * [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/)
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/) * [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/) * [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/) * [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/) * [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/) * [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/)
@ -61,6 +59,15 @@ Some basic benchmarks could be found in this [respository](https://github.com/fa
* [SockJS Server](https://github.com/actix/actix-sockjs) * [SockJS Server](https://github.com/actix/actix-sockjs)
* [Json](https://github.com/actix/actix-web/tree/master/examples/json/) * [Json](https://github.com/actix/actix-web/tree/master/examples/json/)
You may consider checking out
[this directory](https://github.com/actix/actix-web/tree/master/examples) for more examples.
## Benchmarks
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext)
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).
## License ## License
This project is licensed under either of This project is licensed under either of
@ -69,3 +76,9 @@ This project is licensed under either of
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) * MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
at your option. 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

@ -25,6 +25,7 @@ fn main() {
"guide/src/qs_10.md", "guide/src/qs_10.md",
"guide/src/qs_12.md", "guide/src/qs_12.md",
"guide/src/qs_13.md", "guide/src/qs_13.md",
"guide/src/qs_14.md",
]); ]);
} else { } else {
let _ = fs::File::create(f); let _ = fs::File::create(f);

View File

@ -6,6 +6,6 @@ workspace = "../.."
[dependencies] [dependencies]
futures = "*" futures = "*"
env_logger = "0.4" env_logger = "0.5"
actix = "0.4" actix = "0.5"
actix-web = { path = "../../" } actix-web = { path="../.." }

View File

@ -7,6 +7,7 @@ extern crate env_logger;
extern crate futures; extern crate futures;
use futures::Stream; use futures::Stream;
use std::{io, env};
use actix_web::*; use actix_web::*;
use actix_web::middleware::RequestSession; use actix_web::middleware::RequestSession;
use futures::future::{FutureResult, result}; use futures::future::{FutureResult, result};
@ -21,7 +22,7 @@ fn index(mut req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req); println!("{:?}", req);
// example of ... // example of ...
if let Ok(ch) = req.payload_mut().readany().poll() { if let Ok(ch) = req.poll() {
if let futures::Async::Ready(Some(d)) = ch { if let futures::Async::Ready(Some(d)) = ch {
println!("{}", String::from_utf8_lossy(d.as_ref())); println!("{}", String::from_utf8_lossy(d.as_ref()));
} }
@ -56,17 +57,17 @@ fn index(mut req: HttpRequest) -> Result<HttpResponse> {
fn p404(req: HttpRequest) -> Result<HttpResponse> { fn p404(req: HttpRequest) -> Result<HttpResponse> {
// html // html
let html = format!(r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head> let html = r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
<body> <body>
<a href="index.html">back to home</a> <a href="index.html">back to home</a>
<h1>404</h1> <h1>404</h1>
</body> </body>
</html>"#); </html>"#;
// response // response
Ok(HttpResponse::build(StatusCode::NOT_FOUND) Ok(HttpResponse::build(StatusCode::NOT_FOUND)
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(&html).unwrap()) .body(html).unwrap())
} }
@ -92,8 +93,9 @@ fn with_param(req: HttpRequest) -> Result<HttpResponse>
} }
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info"); env::set_var("RUST_LOG", "actix_web=debug");
let _ = env_logger::init(); env::set_var("RUST_BACKTRACE", "1");
env_logger::init();
let sys = actix::System::new("basic-example"); let sys = actix::System::new("basic-example");
let addr = HttpServer::new( let addr = HttpServer::new(
@ -121,6 +123,9 @@ fn main() {
_ => httpcodes::HTTPNotFound, _ => httpcodes::HTTPNotFound,
} }
})) }))
.resource("/error.html", |r| r.f(|req| {
error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "test"))
}))
// static files // static files
.handler("/static/", fs::StaticFiles::new("../static/", true)) .handler("/static/", fs::StaticFiles::new("../static/", true))
// redirect // redirect
@ -134,7 +139,7 @@ fn main() {
// default // default
.default_resource(|r| { .default_resource(|r| {
r.method(Method::GET).f(p404); r.method(Method::GET).f(p404);
r.route().p(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed); r.route().filter(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed);
})) }))
.bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080") .bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080")

View File

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

View File

@ -8,7 +8,7 @@ use diesel::prelude::*;
use models; use models;
use schema; use schema;
/// This is db executor actor. We are going to run 3 of them in parallele. /// This is db executor actor. We are going to run 3 of them in parallel.
pub struct DbExecutor(pub SqliteConnection); pub struct DbExecutor(pub SqliteConnection);
/// This is only message that this actor can handle, but it is easy to extend number of /// This is only message that this actor can handle, but it is easy to extend number of
@ -17,9 +17,8 @@ pub struct CreateUser {
pub name: String, pub name: String,
} }
impl ResponseType for CreateUser { impl Message for CreateUser {
type Item = models::User; type Result = Result<models::User, Error>;
type Error = Error;
} }
impl Actor for DbExecutor { impl Actor for DbExecutor {
@ -27,7 +26,7 @@ impl Actor for DbExecutor {
} }
impl Handler<CreateUser> for DbExecutor { impl Handler<CreateUser> for DbExecutor {
type Result = MessageResult<CreateUser>; type Result = Result<models::User, Error>;
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
use self::schema::users::dsl::*; use self::schema::users::dsl::*;

View File

@ -31,14 +31,14 @@ use db::{CreateUser, DbExecutor};
/// State with DbExecutor address /// State with DbExecutor address
struct State { struct State {
db: SyncAddress<DbExecutor>, db: Addr<Syn, DbExecutor>,
} }
/// Async request handler /// Async request handler
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
let name = &req.match_info()["name"]; let name = &req.match_info()["name"];
req.state().db.call_fut(CreateUser{name: name.to_owned()}) req.state().db.send(CreateUser{name: name.to_owned()})
.from_err() .from_err()
.and_then(|res| { .and_then(|res| {
match res { match res {

View File

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

View File

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

View File

@ -34,9 +34,9 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
const MAX_SIZE: usize = 262_144; // max payload size is 256k const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// This handler manually load request payload and parse serde json /// This handler manually load request payload and parse serde json
fn index_manual(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index_manual(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// readany() returns asynchronous stream of Bytes objects // HttpRequest is stream of Bytes objects
req.payload_mut().readany() req
// `Future::from_err` acts like `?` in that it coerces the error type from // `Future::from_err` acts like `?` in that it coerces the error type from
// the future into the final error type // the future into the final error type
.from_err() .from_err()
@ -63,8 +63,8 @@ fn index_manual(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Err
} }
/// This handler manually load request payload and parse json-rust /// This handler manually load request payload and parse json-rust
fn index_mjsonrust(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index_mjsonrust(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.payload_mut().readany().concat2() req.concat2()
.from_err() .from_err()
.and_then(|body| { .and_then(|body| {
// body is loaded, now we can deserialize json-rust // body is loaded, now we can deserialize json-rust

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://127.0.0.1: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

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

View File

@ -11,7 +11,7 @@ use futures::{Future, Stream};
use futures::future::{result, Either}; use futures::future::{result, Either};
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
{ {
println!("{:?}", req); println!("{:?}", req);

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

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

View File

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

View File

@ -6,6 +6,6 @@ workspace = "../.."
[dependencies] [dependencies]
futures = "*" futures = "*"
env_logger = "0.4" env_logger = "0.5"
actix = "0.4" actix = "0.5"
actix-web = { git = "https://github.com/actix/actix-web" } actix-web = { path = "../../" }

View File

@ -1,5 +1,5 @@
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))] #![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
//! There are two level of statfulness in actix-web. Application has state //! There are two level of statefulness in actix-web. Application has state
//! that is shared across all handlers within same Application. //! that is shared across all handlers within same Application.
//! And individual handler can have state. //! And individual handler can have state.
@ -33,20 +33,19 @@ struct MyWebSocket {
} }
impl Actor for MyWebSocket { impl Actor for MyWebSocket {
type Context = HttpContext<Self, AppState>; type Context = ws::WebsocketContext<Self, AppState>;
} }
impl Handler<ws::Message> for MyWebSocket { impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
self.counter += 1; self.counter += 1;
println!("WS({}): {:?}", self.counter, msg); println!("WS({}): {:?}", self.counter, msg);
match msg { match msg {
ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg), ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ws::WsWriter::text(ctx, &text), ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin), ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Closed | ws::Message::Error => { ws::Message::Close(_) => {
ctx.stop(); ctx.stop();
} }
_ => (), _ => (),

View File

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

View File

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

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

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

Binary file not shown.

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

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

View File

@ -2,14 +2,13 @@
extern crate actix; extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate env_logger; extern crate env_logger;
extern crate openssl;
use std::fs::File;
use std::io::Read;
use actix_web::*; use actix_web::*;
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
/// somple handle /// simple handle
fn index(req: HttpRequest) -> Result<HttpResponse> { fn index(req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req); println!("{:?}", req);
Ok(httpcodes::HTTPOk Ok(httpcodes::HTTPOk
@ -20,15 +19,15 @@ fn index(req: HttpRequest) -> Result<HttpResponse> {
fn main() { fn main() {
if ::std::env::var("RUST_LOG").is_err() { if ::std::env::var("RUST_LOG").is_err() {
::std::env::set_var("RUST_LOG", "actix_web=trace"); ::std::env::set_var("RUST_LOG", "actix_web=info");
} }
let _ = env_logger::init(); let _ = env_logger::init();
let sys = actix::System::new("ws-example"); let sys = actix::System::new("ws-example");
let mut file = File::open("identity.pfx").unwrap(); // load ssl keys
let mut pkcs12 = vec![]; let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
file.read_to_end(&mut pkcs12).unwrap(); builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap(); builder.set_certificate_chain_file("cert.pem").unwrap();
let addr = HttpServer::new( let addr = HttpServer::new(
|| Application::new() || Application::new()
@ -44,7 +43,7 @@ fn main() {
.body(Body::Empty) .body(Body::Empty)
}))) })))
.bind("127.0.0.1:8443").unwrap() .bind("127.0.0.1:8443").unwrap()
.start_ssl(&pkcs12).unwrap(); .start_ssl(builder).unwrap();
println!("Started http server: 127.0.0.1:8443"); println!("Started http server: 127.0.0.1:8443");
let _ = sys.run(); 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

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

View File

@ -16,8 +16,8 @@ Chat server listens for incoming tcp connections. Server can access several type
* `\list` - list all available rooms * `\list` - list all available rooms
* `\join name` - join room, if room does not exist, create new one * `\join name` - join room, if room does not exist, create new one
* `\name name` - set session name * `\name name` - set session name
* `some message` - just string, send messsage to all peers in same room * `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 droppped * 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` To start server use command: `cargo run --bin server`

View File

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

View File

@ -4,10 +4,9 @@ use serde_json as json;
use byteorder::{BigEndian , ByteOrder}; use byteorder::{BigEndian , ByteOrder};
use bytes::{BytesMut, BufMut}; use bytes::{BytesMut, BufMut};
use tokio_io::codec::{Encoder, Decoder}; use tokio_io::codec::{Encoder, Decoder};
use actix::ResponseType;
/// Client request /// Client request
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Message)]
#[serde(tag="cmd", content="data")] #[serde(tag="cmd", content="data")]
pub enum ChatRequest { pub enum ChatRequest {
/// List rooms /// List rooms
@ -20,13 +19,8 @@ pub enum ChatRequest {
Ping Ping
} }
impl ResponseType for ChatRequest {
type Item = ();
type Error = ();
}
/// Server response /// Server response
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Message)]
#[serde(tag="cmd", content="data")] #[serde(tag="cmd", content="data")]
pub enum ChatResponse { pub enum ChatResponse {
Ping, Ping,
@ -41,11 +35,6 @@ pub enum ChatResponse {
Message(String), Message(String),
} }
impl ResponseType for ChatResponse {
type Item = ();
type Error = ();
}
/// Codec for Client -> Server transport /// Codec for Client -> Server transport
pub struct ChatCodec; pub struct ChatCodec;

View File

@ -26,7 +26,7 @@ mod session;
/// This is our websocket route state, this state is shared with all route instances /// This is our websocket route state, this state is shared with all route instances
/// via `HttpContext::state()` /// via `HttpContext::state()`
struct WsChatSessionState { struct WsChatSessionState {
addr: SyncAddress<server::ChatServer>, addr: Addr<Syn, server::ChatServer>,
} }
/// Entry point for our route /// Entry point for our route
@ -62,12 +62,12 @@ impl Actor for WsChatSession {
// before processing any other events. // before processing any other events.
// HttpContext::state() is instance of WsChatSessionState, state is shared across all // HttpContext::state() is instance of WsChatSessionState, state is shared across all
// routes within application // routes within application
let subs = ctx.sync_subscriber(); let addr: Addr<Syn, _> = ctx.address();
ctx.state().addr.call( ctx.state().addr.send(server::Connect{addr: addr.recipient()})
self, server::Connect{addr: subs}).then( .into_actor(self)
|res, act, ctx| { .then(|res, act, ctx| {
match res { match res {
Ok(Ok(res)) => act.id = res, Ok(res) => act.id = res,
// something is wrong with chat server // something is wrong with chat server
_ => ctx.stop(), _ => ctx.stop(),
} }
@ -75,10 +75,10 @@ impl Actor for WsChatSession {
}).wait(ctx); }).wait(ctx);
} }
fn stopping(&mut self, ctx: &mut Self::Context) -> bool { fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
// notify chat server // notify chat server
ctx.state().addr.send(server::Disconnect{id: self.id}); ctx.state().addr.do_send(server::Disconnect{id: self.id});
true Running::Stop
} }
} }
@ -87,13 +87,12 @@ impl Handler<session::Message> for WsChatSession {
type Result = (); type Result = ();
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
ctx.text(&msg.0); ctx.text(msg.0);
} }
} }
/// WebSocket message handler /// WebSocket message handler
impl Handler<ws::Message> for WsChatSession { impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
println!("WEBSOCKET MESSAGE: {:?}", msg); println!("WEBSOCKET MESSAGE: {:?}", msg);
@ -109,17 +108,19 @@ impl Handler<ws::Message> for WsChatSession {
"/list" => { "/list" => {
// Send ListRooms message to chat server and wait for response // Send ListRooms message to chat server and wait for response
println!("List rooms"); println!("List rooms");
ctx.state().addr.call(self, server::ListRooms).then(|res, _, ctx| { ctx.state().addr.send(server::ListRooms)
match res { .into_actor(self)
Ok(Ok(rooms)) => { .then(|res, _, ctx| {
for room in rooms { match res {
ctx.text(&room); Ok(rooms) => {
} for room in rooms {
}, ctx.text(room);
_ => println!("Something is wrong"), }
} },
fut::ok(()) _ => println!("Something is wrong"),
}).wait(ctx) }
fut::ok(())
}).wait(ctx)
// .wait(ctx) pauses all events in context, // .wait(ctx) pauses all events in context,
// so actor wont receive any new messages until it get list // so actor wont receive any new messages until it get list
// of rooms back // of rooms back
@ -127,7 +128,7 @@ impl Handler<ws::Message> for WsChatSession {
"/join" => { "/join" => {
if v.len() == 2 { if v.len() == 2 {
self.room = v[1].to_owned(); self.room = v[1].to_owned();
ctx.state().addr.send( ctx.state().addr.do_send(
server::Join{id: self.id, name: self.room.clone()}); server::Join{id: self.id, name: self.room.clone()});
ctx.text("joined"); ctx.text("joined");
@ -142,7 +143,7 @@ impl Handler<ws::Message> for WsChatSession {
ctx.text("!!! name is required"); ctx.text("!!! name is required");
} }
}, },
_ => ctx.text(&format!("!!! unknown command: {:?}", m)), _ => ctx.text(format!("!!! unknown command: {:?}", m)),
} }
} else { } else {
let msg = if let Some(ref name) = self.name { let msg = if let Some(ref name) = self.name {
@ -151,7 +152,7 @@ impl Handler<ws::Message> for WsChatSession {
m.to_owned() m.to_owned()
}; };
// send message to chat server // send message to chat server
ctx.state().addr.send( ctx.state().addr.do_send(
server::Message{id: self.id, server::Message{id: self.id,
msg: msg, msg: msg,
room: self.room.clone()}) room: self.room.clone()})
@ -159,10 +160,9 @@ impl Handler<ws::Message> for WsChatSession {
}, },
ws::Message::Binary(bin) => ws::Message::Binary(bin) =>
println!("Unexpected binary"), println!("Unexpected binary"),
ws::Message::Closed | ws::Message::Error => { ws::Message::Close(_) => {
ctx.stop(); ctx.stop();
} }
_ => (),
} }
} }
} }
@ -172,12 +172,11 @@ fn main() {
let sys = actix::System::new("websocket-example"); let sys = actix::System::new("websocket-example");
// Start chat server actor in separate thread // Start chat server actor in separate thread
let server: SyncAddress<_> = let server: Addr<Syn, _> = Arbiter::start(|_| server::ChatServer::default());
Arbiter::start(|_| server::ChatServer::default());
// Start tcp server in separate thread // Start tcp server in separate thread
let srv = server.clone(); let srv = server.clone();
Arbiter::new("tcp-server").send::<msgs::Execute>( Arbiter::new("tcp-server").do_send::<msgs::Execute>(
msgs::Execute::new(move || { msgs::Execute::new(move || {
session::TcpServer::new("127.0.0.1:12345", srv); session::TcpServer::new("127.0.0.1:12345", srv);
Ok(()) Ok(())

View File

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

View File

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

View File

@ -2,13 +2,19 @@
name = "websocket" name = "websocket"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]] [[bin]]
name = "server" name = "server"
path = "src/main.rs" path = "src/main.rs"
[[bin]]
name = "client"
path = "src/client.rs"
[dependencies] [dependencies]
env_logger = "*" env_logger = "*"
futures = "0.1" futures = "0.1"
actix = "^0.4.2" tokio-core = "0.1"
actix-web = { git = "https://github.com/actix/actix-web.git" } actix = "0.5"
actix-web = { path="../../" }

View File

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

View File

@ -25,17 +25,16 @@ impl Actor for MyWebSocket {
} }
/// Handler for `ws::Message` /// Handler for `ws::Message`
impl Handler<ws::Message> for MyWebSocket { impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
// process websocket messages // process websocket messages
println!("WS: {:?}", msg); println!("WS: {:?}", msg);
match msg { match msg {
ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(&text), ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Closed | ws::Message::Error => { ws::Message::Close(_) => {
ctx.stop(); ctx.stop();
} }
_ => (), _ => (),
@ -44,7 +43,7 @@ impl Handler<ws::Message> for MyWebSocket {
} }
fn main() { fn main() {
::std::env::set_var("RUST_LOG", "actix_web=trace"); ::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init(); let _ = env_logger::init();
let sys = actix::System::new("ws-example"); let sys = actix::System::new("ws-example");
@ -55,7 +54,8 @@ fn main() {
// websocket route // websocket route
.resource("/ws/", |r| r.method(Method::GET).f(ws_index)) .resource("/ws/", |r| r.method(Method::GET).f(ws_index))
// static files // static files
.handler("/", fs::StaticFiles::new("../static/", true))) .handler("/", fs::StaticFiles::new("../static/", true)
.index_file("index.html")))
// start http server on 127.0.0.1:8080 // start http server on 127.0.0.1:8080
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.start(); .start();

View File

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

View File

@ -17,7 +17,7 @@ If you already have rustup installed, run this command to ensure you have the la
rustup update rustup update
``` ```
Actix web framework requires rust version 1.20 and up. Actix web framework requires rust version 1.21 and up.
## Running Examples ## Running Examples

View File

@ -53,7 +53,7 @@ impl<S> Middleware<S> for Headers {
fn main() { fn main() {
Application::new() Application::new()
.middleware(Headers) // <- Register middleware, this method could be called multiple times .middleware(Headers) // <- Register middleware, this method could be called multiple times
.resource("/", |r| r.h(httpcodes::HTTPOk)); .resource("/", |r| r.h(httpcodes::HttpOk));
} }
``` ```
@ -64,7 +64,9 @@ Active provides several useful middlewares, like *logging*, *user sessions*, etc
Logging is implemented as middleware. Logging is implemented as middleware.
It is common to register logging middleware as first middleware for application. It is common to register logging middleware as first middleware for application.
Logging middleware has to be registered for each 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 ### Usage
@ -76,10 +78,14 @@ Default `Logger` could be created with `default` method, it uses the default for
``` ```
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
extern crate env_logger;
use actix_web::Application; use actix_web::Application;
use actix_web::middleware::Logger; use actix_web::middleware::Logger;
fn main() { fn main() {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
Application::new() Application::new()
.middleware(Logger::default()) .middleware(Logger::default())
.middleware(Logger::new("%a %{User-Agent}i")) .middleware(Logger::new("%a %{User-Agent}i"))
@ -138,8 +144,8 @@ fn main() {
.header("X-Version", "0.2") .header("X-Version", "0.2")
.finish()) .finish())
.resource("/test", |r| { .resource("/test", |r| {
r.method(Method::GET).f(|req| httpcodes::HTTPOk); r.method(Method::GET).f(|req| httpcodes::HttpOk);
r.method(Method::HEAD).f(|req| httpcodes::HTTPMethodNotAllowed); r.method(Method::HEAD).f(|req| httpcodes::HttpMethodNotAllowed);
}) })
.finish(); .finish();
} }
@ -200,7 +206,7 @@ fn main() {
))) )))
.bind("127.0.0.1:59880").unwrap() .bind("127.0.0.1:59880").unwrap()
.start(); .start();
# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
# let _ = sys.run(); # let _ = sys.run();
} }
``` ```

View File

@ -42,3 +42,8 @@ fn main() {
First parameter is a base directory. Second parameter is *show_index*, if it is set to *true* 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* directory listing would be returned for directories, if it is set to *false*
then *404 Not Found* would be returned instead of directory listing. 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.

View File

@ -12,24 +12,26 @@ With enable `alpn` feature `HttpServer` provides
```toml ```toml
[dependencies] [dependencies]
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] } actix-web = { version = "0.3.3", features=["alpn"] }
openssl = { version="0.10", features = ["v110"] }
``` ```
```rust,ignore ```rust,ignore
use std::fs::File; use std::fs::File;
use actix_web::*; use actix_web::*;
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
fn main() { fn main() {
let mut file = File::open("identity.pfx").unwrap(); // load ssl keys
let mut pkcs12 = vec![]; let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
file.read_to_end(&mut pkcs12).unwrap(); builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap(); builder.set_certificate_chain_file("cert.pem").unwrap();
HttpServer::new( HttpServer::new(
|| Application::new() || Application::new()
.resource("/index.html", |r| r.f(index))) .resource("/index.html", |r| r.f(index)))
.bind("127.0.0.1:8080").unwrap(); .bind("127.0.0.1:8080").unwrap();
.serve_ssl(pkcs12).unwrap(); .serve_ssl(builder).unwrap();
} }
``` ```

View File

@ -9,7 +9,7 @@ can be run in parallel and process messages from same queue (sync actors work in
Let's create simple db api that can insert new user row into sqlite table. 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 We have to define sync actor and connection that this actor will use. Same approach
could used for other databases. could be used for other databases.
```rust,ignore ```rust,ignore
use actix::prelude::*;* use actix::prelude::*;*
@ -72,7 +72,7 @@ can access it.
```rust,ignore ```rust,ignore
/// This is state where we will store *DbExecutor* address. /// This is state where we will store *DbExecutor* address.
struct State { struct State {
db: SyncAddress<DbExecutor>, db: Addr<Syn, DbExecutor>,
} }
fn main() { fn main() {
@ -106,12 +106,12 @@ fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>>
let name = &req.match_info()["name"]; let name = &req.match_info()["name"];
// Send message to `DbExecutor` actor // Send message to `DbExecutor` actor
req.state().db.call_fut(CreateUser{name: name.to_owned()}) req.state().db.send(CreateUser{name: name.to_owned()})
.from_err() .from_err()
.and_then(|res| { .and_then(|res| {
match res { match res {
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?), Ok(user) => Ok(httpcodes::HttpOk.build().json(user)?),
Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) Err(_) => Ok(httpcodes::HttpInternalServerError.into())
} }
}) })
.responder() .responder()
@ -122,4 +122,4 @@ Full example is available in
[examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/). [examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/).
More information on sync actors could be found in More information on sync actors could be found in
[actix documentation](https://docs.rs/actix/0.3.3/actix/sync/index.html). [actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html).

View File

@ -20,8 +20,8 @@ contains the following:
```toml ```toml
[dependencies] [dependencies]
actix = "0.4" actix = "0.5"
actix-web = "0.3" actix-web = "0.4"
``` ```
In order to implement a web server, first we need to create a request handler. In order to implement a web server, first we need to create a request handler.
@ -71,7 +71,7 @@ Here is full source of main.rs file:
```rust ```rust
# use std::thread; # use std::thread;
# extern crate actix_web; extern crate actix_web;
use actix_web::*; use actix_web::*;
fn index(req: HttpRequest) -> &'static str { fn index(req: HttpRequest) -> &'static str {
@ -79,13 +79,16 @@ fn index(req: HttpRequest) -> &'static str {
} }
fn main() { fn main() {
# thread::spawn(|| { # // 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( HttpServer::new(
|| Application::new() || Application::new()
.resource("/", |r| r.f(index))) .resource("/", |r| r.f(index)))
.bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088") .bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088")
.run(); .run();
# }); # });
} }
``` ```

View File

@ -46,15 +46,15 @@ Multiple applications could be served with one server:
use actix_web::*; use actix_web::*;
fn main() { fn main() {
HttpServer::<TcpStream, SocketAddr, _, _>::new(|| vec![ HttpServer::new(|| vec![
Application::new() Application::new()
.prefix("/app1") .prefix("/app1")
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)), .resource("/", |r| r.f(|r| httpcodes::HttpOk)),
Application::new() Application::new()
.prefix("/app2") .prefix("/app2")
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)), .resource("/", |r| r.f(|r| httpcodes::HttpOk)),
Application::new() Application::new()
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)), .resource("/", |r| r.f(|r| httpcodes::HttpOk)),
]); ]);
} }
``` ```

View File

@ -20,11 +20,11 @@ fn main() {
HttpServer::new( HttpServer::new(
|| Application::new() || Application::new()
.resource("/", |r| r.h(httpcodes::HTTPOk))) .resource("/", |r| r.h(httpcodes::HttpOk)))
.bind("127.0.0.1:59080").unwrap() .bind("127.0.0.1:59080").unwrap()
.start(); .start();
# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
let _ = sys.run(); let _ = sys.run();
} }
``` ```
@ -52,12 +52,12 @@ use std::sync::mpsc;
fn main() { fn main() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
thread::spawn(move || { thread::spawn(move || {
let sys = actix::System::new("http-server"); let sys = actix::System::new("http-server");
let addr = HttpServer::new( let addr = HttpServer::new(
|| Application::new() || Application::new()
.resource("/", |r| r.h(httpcodes::HTTPOk))) .resource("/", |r| r.h(httpcodes::HttpOk)))
.bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") .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 .shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds
.start(); .start();
@ -66,7 +66,7 @@ fn main() {
}); });
let addr = rx.recv().unwrap(); let addr = rx.recv().unwrap();
let _ = addr.call_fut( let _ = addr.send(
server::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server. server::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server.
} }
``` ```
@ -80,14 +80,12 @@ could be overridden with `HttpServer::threads()` method.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# extern crate tokio_core; # extern crate tokio_core;
# use tokio_core::net::TcpStream;
# use std::net::SocketAddr;
use actix_web::*; use actix_web::*;
fn main() { fn main() {
HttpServer::<TcpStream, SocketAddr, _, _>::new( HttpServer::new(
|| Application::new() || Application::new()
.resource("/", |r| r.h(httpcodes::HTTPOk))) .resource("/", |r| r.h(httpcodes::HttpOk)))
.threads(4); // <- Start 4 workers .threads(4); // <- Start 4 workers
} }
``` ```
@ -143,14 +141,12 @@ connection behavior is defined by server settings.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
# extern crate tokio_core; # extern crate tokio_core;
# use tokio_core::net::TcpStream;
# use std::net::SocketAddr;
use actix_web::*; use actix_web::*;
fn main() { fn main() {
HttpServer::<TcpStream, SocketAddr, _, _>::new(|| HttpServer::new(||
Application::new() Application::new()
.resource("/", |r| r.h(httpcodes::HTTPOk))) .resource("/", |r| r.h(httpcodes::HttpOk)))
.keep_alive(None); // <- Use `SO_KEEPALIVE` socket option. .keep_alive(None); // <- Use `SO_KEEPALIVE` socket option.
} }
``` ```
@ -159,7 +155,7 @@ If first option is selected then *keep alive* state
calculated based on response's *connection-type*. By default calculated based on response's *connection-type*. By default
`HttpResponse::connection_type` is not defined in that case *keep alive* `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* 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". and is on for *HTTP/1.1* and *HTTP/2.0*.
*Connection type* could be change with `HttpResponseBuilder::connection_type()` method. *Connection type* could be change with `HttpResponseBuilder::connection_type()` method.
@ -169,7 +165,7 @@ and is on for *HTTP/1.1* and "HTTP/2.0".
use actix_web::*; use actix_web::*;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
HTTPOk.build() HttpOk.build()
.connection_type(headers::ConnectionType::Close) // <- Close connection .connection_type(headers::ConnectionType::Close) // <- Close connection
.force_close() // <- Alternative method .force_close() // <- Alternative method
.finish().unwrap() .finish().unwrap()

View File

@ -65,7 +65,7 @@ impl<S> Handler<S> for MyHandler {
/// Handle request /// Handle request
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
self.0 += 1; self.0 += 1;
httpcodes::HTTPOk.into() httpcodes::HttpOk.into()
} }
} }
# fn main() {} # fn main() {}
@ -89,9 +89,8 @@ impl<S> Handler<S> for MyHandler {
/// Handle request /// Handle request
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result { fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let num = self.0.load(Ordering::Relaxed) + 1; self.0.fetch_add(1, Ordering::Relaxed);
self.0.store(num, Ordering::Relaxed); httpcodes::HttpOk.into()
httpcodes::HTTPOk.into()
} }
} }
@ -110,7 +109,7 @@ fn main() {
.start(); .start();
println!("Started http server: 127.0.0.1:8088"); println!("Started http server: 127.0.0.1:8088");
# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
let _ = sys.run(); let _ = sys.run();
} }
``` ```
@ -168,7 +167,7 @@ fn main() {
.start(); .start();
println!("Started http server: 127.0.0.1:8088"); println!("Started http server: 127.0.0.1:8088");
# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
let _ = sys.run(); let _ = sys.run();
} }
``` ```
@ -235,3 +234,12 @@ fn main() {
``` ```
Both methods could be combined. (i.e Async response with streaming body) 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.

View File

@ -14,7 +14,7 @@ impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
And any error that implements `ResponseError` can be converted into `Error` object. And any error that implements `ResponseError` can be converted into `Error` object.
For example if *handler* function returns `io::Error`, it would be converted For example if *handler* function returns `io::Error`, it would be converted
into `HTTPInternalServerError` response. Implementation for `io::Error` is provided into `HttpInternalServerError` response. Implementation for `io::Error` is provided
by default. by default.
```rust ```rust
@ -134,3 +134,18 @@ fn index(req: HttpRequest) -> Result<&'static str> {
``` ```
In this example *BAD REQUEST* response get generated for `MyError` error. In this example *BAD REQUEST* response get generated for `MyError` error.
## Error logging
Actix logs all errors with `WARN` log level. If log level set to `DEBUG`
and `RUST_BACKTRACE` is enabled, backtrace get logged. The Error type uses
cause's error backtrace if available, if the underlying failure does not provide
a backtrace, a new backtrace is constructed pointing to that conversion point
(rather than the origin of the error). This construction only happens if there
is no underlying backtrace; if it does have a backtrace no new backtrace is constructed.
You can enable backtrace and debug logging with following command:
```
>> RUST_BACKTRACE=1 RUST_LOG=actix_web=debug cargo run
```

View File

@ -1,9 +1,7 @@
# URL Dispatch # URL Dispatch
URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching
language. *Regex* crate and it's language. If one of the patterns matches the path information associated with a request,
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is being used for
pattern matching. If one of the patterns matches the path information associated with a request,
a particular handler object is invoked. A handler is a specific object that implements 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 `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). a response object. More information is available in [handler section](../qs_4.html).
@ -34,7 +32,7 @@ fn main() {
Application::new() Application::new()
.resource("/prefix", |r| r.f(index)) .resource("/prefix", |r| r.f(index))
.resource("/user/{name}", .resource("/user/{name}",
|r| r.method(Method::GET).f(|req| HTTPOk)) |r| r.method(Method::GET).f(|req| HttpOk))
.finish(); .finish();
} }
``` ```
@ -54,7 +52,7 @@ returns *NOT FOUND* http resources.
Resource contains set of routes. Each route in turn has set of predicates and handler. 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 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 to new *Route* instance. By default *route* does not contain any predicates, so matches
all requests and default handler is `HTTPNotFound`. all requests and default handler is `HttpNotFound`.
Application routes incoming requests based on route criteria which is defined during Application routes incoming requests based on route criteria which is defined during
resource registration and route registration. Resource matches all routes it contains in resource registration and route registration. Resource matches all routes it contains in
@ -70,9 +68,9 @@ fn main() {
Application::new() Application::new()
.resource("/path", |resource| .resource("/path", |resource|
resource.route() resource.route()
.p(pred::Get()) .filter(pred::Get())
.p(pred::Header("content-type", "text/plain")) .filter(pred::Header("content-type", "text/plain"))
.f(|req| HTTPOk) .f(|req| HttpOk)
) )
.finish(); .finish();
} }
@ -87,7 +85,7 @@ If resource can not match any route "NOT FOUND" response get returned.
[*Route*](../actix_web/struct.Route.html) object. Route can be configured with [*Route*](../actix_web/struct.Route.html) object. Route can be configured with
builder-like pattern. Following configuration methods are available: builder-like pattern. Following configuration methods are available:
* [*Route::p()*](../actix_web/struct.Route.html#method.p) method registers new predicate, * [*Route::filter()*](../actix_web/struct.Route.html#method.filter) method registers new predicate,
any number of predicates could be registered for each route. any number of predicates could be registered for each route.
* [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function * [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function
@ -110,11 +108,10 @@ The main purpose of route configuration is to match (or not match) the request's
against a URL path pattern. `path` represents the path portion of the URL that was requested. 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, The way that *actix* does this is very simple. When a request enters the system,
for each resource configuration registration present in the system, actix checks for each resource configuration declaration present in the system, actix checks
the request's path against the pattern declared. *Regex* crate and it's the request's path against the pattern declared. This checking happens in the order that
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is being used for the routes were declared via `Application::resource()` method. If resource could not be found,
pattern matching. If resource could not be found, *default resource* get used as matched *default resource* get used as matched resource.
resource.
When a route configuration is declared, it may contain route predicate arguments. All route 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 predicates associated with a route declaration must be `true` for the route configuration to
@ -339,14 +336,14 @@ resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this.
# #
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
HTTPOk.into() HttpOk.into()
} }
fn main() { fn main() {
let app = Application::new() let app = Application::new()
.resource("/test/{a}/{b}/{c}", |r| { .resource("/test/{a}/{b}/{c}", |r| {
r.name("foo"); // <- set resource name, then it could be used in `url_for` r.name("foo"); // <- set resource name, then it could be used in `url_for`
r.method(Method::GET).f(|_| httpcodes::HTTPOk); r.method(Method::GET).f(|_| httpcodes::HttpOk);
}) })
.finish(); .finish();
} }
@ -370,7 +367,7 @@ use actix_web::*;
fn index(mut req: HttpRequest) -> Result<HttpResponse> { fn index(mut req: HttpRequest) -> Result<HttpResponse> {
let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
Ok(httpcodes::HTTPOk.into()) Ok(httpcodes::HttpOk.into())
} }
fn main() { fn main() {
@ -407,7 +404,7 @@ This handler designed to be use as a handler for application's *default resource
# use actix_web::*; # use actix_web::*;
# #
# fn index(req: HttpRequest) -> httpcodes::StaticResponse { # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
# httpcodes::HTTPOk # httpcodes::HttpOk
# } # }
fn main() { fn main() {
let app = Application::new() let app = Application::new()
@ -432,7 +429,7 @@ It is possible to register path normalization only for *GET* requests only
# use actix_web::*; # use actix_web::*;
# #
# fn index(req: HttpRequest) -> httpcodes::StaticResponse { # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
# httpcodes::HTTPOk # httpcodes::HttpOk
# } # }
fn main() { fn main() {
let app = Application::new() let app = Application::new()
@ -505,8 +502,8 @@ fn main() {
Application::new() Application::new()
.resource("/index.html", |r| .resource("/index.html", |r|
r.route() r.route()
.p(ContentTypeHeader) .filter(ContentTypeHeader)
.h(HTTPOk)); .h(HttpOk));
} }
``` ```
@ -533,8 +530,8 @@ fn main() {
Application::new() Application::new()
.resource("/index.html", |r| .resource("/index.html", |r|
r.route() r.route()
.p(pred::Not(pred::Get())) .filter(pred::Not(pred::Get()))
.f(|req| HTTPMethodNotAllowed)) .f(|req| HttpMethodNotAllowed))
.finish(); .finish();
} }
``` ```
@ -570,8 +567,8 @@ use actix_web::httpcodes::*;
fn main() { fn main() {
Application::new() Application::new()
.default_resource(|r| { .default_resource(|r| {
r.method(Method::GET).f(|req| HTTPNotFound); r.method(Method::GET).f(|req| HttpNotFound);
r.route().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed); r.route().filter(pred::Not(pred::Get())).f(|req| HttpMethodNotAllowed);
}) })
# .finish(); # .finish();
} }

View File

@ -84,7 +84,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.json().from_err() req.json().from_err()
.and_then(|val: MyObj| { .and_then(|val: MyObj| {
println!("model: {:?}", val); println!("model: {:?}", val);
Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response Ok(httpcodes::HttpOk.build().json(val)?) // <- send response
}) })
.responder() .responder()
} }
@ -106,10 +106,10 @@ use futures::{Future, Stream};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct MyObj {name: String, number: i32} struct MyObj {name: String, number: i32}
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// `concat2` will asynchronously read each chunk of the request body and // `concat2` will asynchronously read each chunk of the request body and
// return a single, concatenated, chunk // return a single, concatenated, chunk
req.payload_mut().readany().concat2() req.concat2()
// `Future::from_err` acts like `?` in that it coerces the error type from // `Future::from_err` acts like `?` in that it coerces the error type from
// the future into the final error type // the future into the final error type
.from_err() .from_err()
@ -117,7 +117,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
// synchronous workflow // synchronous workflow
.and_then(|body| { // <- body is loaded, now we can deserialize json .and_then(|body| { // <- body is loaded, now we can deserialize json
let obj = serde_json::from_slice::<MyObj>(&body)?; let obj = serde_json::from_slice::<MyObj>(&body)?;
Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response Ok(httpcodes::HttpOk.build().json(obj)?) // <- send response
}) })
.responder() .responder()
} }
@ -169,13 +169,18 @@ get enabled automatically.
Enabling chunked encoding for *HTTP/2.0* responses is forbidden. Enabling chunked encoding for *HTTP/2.0* responses is forbidden.
```rust ```rust
# extern crate bytes;
# extern crate actix_web; # extern crate actix_web;
# extern crate futures;
# use futures::Stream;
use actix_web::*; use actix_web::*;
use bytes::Bytes;
use futures::stream::once;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.chunked() .chunked()
.body(Body::Streaming(payload::Payload::empty().stream())).unwrap() .body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data")))))).unwrap()
} }
# fn main() {} # fn main() {}
``` ```
@ -246,7 +251,7 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
.from_err() .from_err()
.and_then(|params| { // <- url encoded parameters .and_then(|params| { // <- url encoded parameters
println!("==== BODY ==== {:?}", params); println!("==== BODY ==== {:?}", params);
ok(httpcodes::HTTPOk.into()) ok(httpcodes::HttpOk.into())
}) })
.responder() .responder()
} }
@ -256,21 +261,8 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
## Streaming request ## Streaming request
Actix uses [*Payload*](../actix_web/payload/struct.Payload.html) object as request payload stream. *HttpRequest* is a stream of `Bytes` objects. It could be used to read request
*HttpRequest* provides several methods, which can be used for payload access. body payload.
At the same time *Payload* implements *Stream* trait, so it could be used with various
stream combinators. Also *Payload* provides several convenience methods that return
future object that resolve to Bytes object.
* *readany()* method returns *Stream* of *Bytes* objects.
* *readexactly()* method returns *Future* that resolves when specified number of bytes
get received.
* *readline()* method returns *Future* that resolves when `\n` get received.
* *readuntil()* method returns *Future* that resolves when specified bytes string
matches in input bytes stream
In this example handle reads request payload chunk by chunk and prints every chunk. In this example handle reads request payload chunk by chunk and prints every chunk.
@ -283,9 +275,7 @@ use futures::{Future, Stream};
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
req.payload() req.from_err()
.readany()
.from_err()
.fold((), |_, chunk| { .fold((), |_, chunk| {
println!("Chunk: {:?}", chunk); println!("Chunk: {:?}", chunk);
result::<_, error::PayloadError>(Ok(())) result::<_, error::PayloadError>(Ok(()))

View File

@ -20,10 +20,10 @@ use actix_web::test::TestRequest;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
if let Ok(s) = hdr.to_str() { if let Ok(s) = hdr.to_str() {
return httpcodes::HTTPOk.into() return httpcodes::HttpOk.into()
} }
} }
httpcodes::HTTPBadRequest.into() httpcodes::HttpBadRequest.into()
} }
fn main() { fn main() {
@ -45,8 +45,8 @@ fn main() {
There are several methods how you can test your application. Actix provides There are several methods how you can test your application. Actix provides
[*TestServer*](../actix_web/test/struct.TestServer.html) [*TestServer*](../actix_web/test/struct.TestServer.html)
server that could be used to run whole application of just specific handlers server that could be used to run whole application of just specific handlers
in real http server. At the moment it is required to use third-party libraries in real http server. *TrstServer::get()*, *TrstServer::post()* or *TrstServer::client()*
to make actual requests, libraries like [reqwest](https://crates.io/crates/reqwest). methods could be used to send request to test server.
In simple form *TestServer* could be configured to use handler. *TestServer::new* method In simple form *TestServer* could be configured to use handler. *TestServer::new* method
accepts configuration function, only argument for this function is *test application* accepts configuration function, only argument for this function is *test application*
@ -55,18 +55,21 @@ for more information.
```rust ```rust
# extern crate actix_web; # extern crate actix_web;
extern crate reqwest;
use actix_web::*; use actix_web::*;
use actix_web::test::TestServer; use actix_web::test::TestServer;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
httpcodes::HTTPOk.into() httpcodes::HttpOk.into()
} }
fn main() { fn main() {
let srv = TestServer::new(|app| app.handler(index)); // <- Start new test server let mut srv = TestServer::new(|app| app.handler(index)); // <- Start new test server
let url = srv.url("/"); // <- get handler url
assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request let 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
} }
``` ```
@ -74,13 +77,14 @@ Other option is to use application factory. In this case you need to pass factor
same as you use for real http server configuration. same as you use for real http server configuration.
```rust ```rust
# extern crate http;
# extern crate actix_web; # extern crate actix_web;
extern crate reqwest; use http::Method;
use actix_web::*; use actix_web::*;
use actix_web::test::TestServer; use actix_web::test::TestServer;
fn index(req: HttpRequest) -> HttpResponse { fn index(req: HttpRequest) -> HttpResponse {
httpcodes::HTTPOk.into() httpcodes::HttpOk.into()
} }
/// This function get called by http server. /// This function get called by http server.
@ -90,8 +94,61 @@ fn create_app() -> Application {
} }
fn main() { fn main() {
let srv = TestServer::with_factory(create_app); // <- Start new test server let mut srv = TestServer::with_factory(create_app); // <- Start new test server
let url = srv.url("/test"); // <- get handler url
assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request let 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())));
} }
``` ```

View File

@ -21,14 +21,13 @@ impl Actor for Ws {
type Context = ws::WebsocketContext<Self>; type Context = ws::WebsocketContext<Self>;
} }
/// Define Handler for ws::Message message /// Handler for ws::Message message
impl Handler<ws::Message> for Ws { impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
type Result=();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
match msg { match msg {
ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Ping(msg) => ctx.pong(&msg),
ws::Message::Text(text) => ctx.text(&text), ws::Message::Text(text) => ctx.text(text),
ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Binary(bin) => ctx.binary(bin),
_ => (), _ => (),
} }
@ -43,7 +42,7 @@ fn main() {
``` ```
Simple websocket echo server example is available in Simple websocket echo server example is available in
[examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket.rs). [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 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/) is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)

View File

@ -6,6 +6,7 @@ use std::collections::HashMap;
use handler::Reply; use handler::Reply;
use router::{Router, Pattern}; use router::{Router, Pattern};
use resource::Resource; use resource::Resource;
use headers::ContentEncoding;
use handler::{Handler, RouteHandler, WrapHandler}; use handler::{Handler, RouteHandler, WrapHandler};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use pipeline::{Pipeline, PipelineHandler}; use pipeline::{Pipeline, PipelineHandler};
@ -24,6 +25,7 @@ pub struct HttpApplication<S=()> {
pub(crate) struct Inner<S> { pub(crate) struct Inner<S> {
prefix: usize, prefix: usize,
default: Resource<S>, default: Resource<S>,
encoding: ContentEncoding,
router: Router, router: Router,
resources: Vec<Resource<S>>, resources: Vec<Resource<S>>,
handlers: Vec<(String, Box<RouteHandler<S>>)>, handlers: Vec<(String, Box<RouteHandler<S>>)>,
@ -31,6 +33,10 @@ pub(crate) struct Inner<S> {
impl<S: 'static> PipelineHandler<S> for Inner<S> { impl<S: 'static> PipelineHandler<S> for Inner<S> {
fn encoding(&self) -> ContentEncoding {
self.encoding
}
fn handle(&mut self, mut req: HttpRequest<S>) -> Reply { fn handle(&mut self, mut req: HttpRequest<S>) -> Reply {
if let Some(idx) = self.router.recognize(&mut req) { if let Some(idx) = self.router.recognize(&mut req) {
self.resources[idx].handle(req.clone(), Some(&mut self.default)) self.resources[idx].handle(req.clone(), Some(&mut self.default))
@ -59,9 +65,11 @@ impl<S: 'static> PipelineHandler<S> for Inner<S> {
#[cfg(test)] #[cfg(test)]
impl<S: 'static> HttpApplication<S> { impl<S: 'static> HttpApplication<S> {
#[cfg(test)]
pub(crate) fn run(&mut self, req: HttpRequest<S>) -> Reply { pub(crate) fn run(&mut self, req: HttpRequest<S>) -> Reply {
self.inner.borrow_mut().handle(req) self.inner.borrow_mut().handle(req)
} }
#[cfg(test)]
pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest<S> { pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest<S> {
req.with_state(Rc::clone(&self.state), self.router.clone()) req.with_state(Rc::clone(&self.state), self.router.clone())
} }
@ -92,9 +100,10 @@ struct ApplicationParts<S> {
prefix: String, prefix: String,
settings: ServerSettings, settings: ServerSettings,
default: Resource<S>, default: Resource<S>,
resources: HashMap<Pattern, Option<Resource<S>>>, resources: Vec<(Pattern, Option<Resource<S>>)>,
handlers: Vec<(String, Box<RouteHandler<S>>)>, handlers: Vec<(String, Box<RouteHandler<S>>)>,
external: HashMap<String, Pattern>, external: HashMap<String, Pattern>,
encoding: ContentEncoding,
middlewares: Vec<Box<Middleware<S>>>, middlewares: Vec<Box<Middleware<S>>>,
} }
@ -114,9 +123,10 @@ impl Application<()> {
prefix: "/".to_owned(), prefix: "/".to_owned(),
settings: ServerSettings::default(), settings: ServerSettings::default(),
default: Resource::default_not_found(), default: Resource::default_not_found(),
resources: HashMap::new(), resources: Vec::new(),
handlers: Vec::new(), handlers: Vec::new(),
external: HashMap::new(), external: HashMap::new(),
encoding: ContentEncoding::Auto,
middlewares: Vec::new(), middlewares: Vec::new(),
}) })
} }
@ -134,19 +144,20 @@ impl<S> Application<S> where S: 'static {
/// Create application with specific state. Application can be /// Create application with specific state. Application can be
/// configured with builder-like pattern. /// configured with builder-like pattern.
/// ///
/// State is shared with all reousrces within same application and could be /// State is shared with all resources within same application and could be
/// accessed with `HttpRequest::state()` method. /// accessed with `HttpRequest::state()` method.
pub fn with_state(state: S) -> Application<S> { pub fn with_state(state: S) -> Application<S> {
Application { Application {
parts: Some(ApplicationParts { parts: Some(ApplicationParts {
state: state, state,
prefix: "/".to_owned(), prefix: "/".to_owned(),
settings: ServerSettings::default(), settings: ServerSettings::default(),
default: Resource::default_not_found(), default: Resource::default_not_found(),
resources: HashMap::new(), resources: Vec::new(),
handlers: Vec::new(), handlers: Vec::new(),
external: HashMap::new(), external: HashMap::new(),
middlewares: Vec::new(), middlewares: Vec::new(),
encoding: ContentEncoding::Auto,
}) })
} }
} }
@ -154,7 +165,7 @@ impl<S> Application<S> where S: 'static {
/// Set application prefix /// Set application prefix
/// ///
/// Only requests that matches application's prefix get processed by this application. /// Only requests that matches application's prefix get processed by this application.
/// Application prefix always contains laading "/" slash. If supplied prefix /// Application prefix always contains leading "/" slash. If supplied prefix
/// does not contain leading slash, it get inserted. Prefix should /// does not contain leading slash, it get inserted. Prefix should
/// consists valid path segments. i.e for application with /// consists valid path segments. i.e for application with
/// prefix `/app` any request with following paths `/app`, `/app/` or `/app/test` /// prefix `/app` any request with following paths `/app`, `/app/` or `/app/test`
@ -172,8 +183,8 @@ impl<S> Application<S> where S: 'static {
/// let app = Application::new() /// let app = Application::new()
/// .prefix("/app") /// .prefix("/app")
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::GET).f(|_| httpcodes::HttpOk);
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); /// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
@ -215,8 +226,8 @@ impl<S> Application<S> where S: 'static {
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = Application::new()
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::GET).f(|_| httpcodes::HttpOk);
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); /// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
/// }); /// });
/// } /// }
/// ``` /// ```
@ -230,12 +241,8 @@ impl<S> Application<S> where S: 'static {
let mut resource = Resource::default(); let mut resource = Resource::default();
f(&mut resource); f(&mut resource);
let pattern = Pattern::new(resource.get_name(), path, "^/"); let pattern = Pattern::new(resource.get_name(), path);
if parts.resources.contains_key(&pattern) { parts.resources.push((pattern, Some(resource)));
panic!("Resource {:?} is registered.", path);
}
parts.resources.insert(pattern, Some(resource));
} }
self self
} }
@ -251,6 +258,16 @@ impl<S> Application<S> where S: 'static {
self self
} }
/// Set default content encoding. `ContentEncoding::Auto` is set by default.
pub fn default_encoding<F>(mut self, encoding: ContentEncoding) -> Application<S>
{
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.encoding = encoding;
}
self
}
/// Register external resource. /// Register external resource.
/// ///
/// External resources are useful for URL generation purposes only and /// External resources are useful for URL generation purposes only and
@ -264,7 +281,7 @@ impl<S> Application<S> where S: 'static {
/// fn index(mut req: HttpRequest) -> Result<HttpResponse> { /// fn index(mut req: HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
/// Ok(httpcodes::HTTPOk.into()) /// Ok(httpcodes::HttpOk.into())
/// } /// }
/// ///
/// fn main() { /// fn main() {
@ -284,7 +301,7 @@ impl<S> Application<S> where S: 'static {
panic!("External resource {:?} is registered.", name.as_ref()); panic!("External resource {:?} is registered.", name.as_ref());
} }
parts.external.insert( parts.external.insert(
String::from(name.as_ref()), Pattern::new(name.as_ref(), url.as_ref(), "^/")); String::from(name.as_ref()), Pattern::new(name.as_ref(), url.as_ref()));
} }
self self
} }
@ -303,9 +320,9 @@ impl<S> Application<S> where S: 'static {
/// let app = Application::new() /// let app = Application::new()
/// .handler("/app", |req: HttpRequest| { /// .handler("/app", |req: HttpRequest| {
/// match *req.method() { /// match *req.method() {
/// Method::GET => httpcodes::HTTPOk, /// Method::GET => httpcodes::HttpOk,
/// Method::POST => httpcodes::HTTPMethodNotAllowed, /// Method::POST => httpcodes::HttpMethodNotAllowed,
/// _ => httpcodes::HTTPNotFound, /// _ => httpcodes::HttpNotFound,
/// }}); /// }});
/// } /// }
/// ``` /// ```
@ -333,7 +350,7 @@ impl<S> Application<S> where S: 'static {
let mut resources = parts.resources; let mut resources = parts.resources;
for (_, pattern) in parts.external { for (_, pattern) in parts.external {
resources.insert(pattern, None); resources.push((pattern, None));
} }
let (router, resources) = Router::new(prefix, parts.settings, resources); let (router, resources) = Router::new(prefix, parts.settings, resources);
@ -342,20 +359,55 @@ impl<S> Application<S> where S: 'static {
Inner { Inner {
prefix: prefix.len(), prefix: prefix.len(),
default: parts.default, default: parts.default,
encoding: parts.encoding,
router: router.clone(), router: router.clone(),
resources: resources,
handlers: parts.handlers, handlers: parts.handlers,
resources,
} }
)); ));
HttpApplication { HttpApplication {
state: Rc::new(parts.state), state: Rc::new(parts.state),
prefix: prefix.to_owned(), prefix: prefix.to_owned(),
inner: inner,
router: router.clone(), router: router.clone(),
middlewares: Rc::new(parts.middlewares), middlewares: Rc::new(parts.middlewares),
inner,
} }
} }
/// Convenience method for creating `Box<HttpHandler>` instance.
///
/// This method is useful if you need to register several application instances
/// with different state.
///
/// ```rust
/// # use std::thread;
/// # extern crate actix_web;
/// use actix_web::*;
///
/// struct State1;
///
/// struct State2;
///
/// fn main() {
/// # thread::spawn(|| {
/// HttpServer::new(|| { vec![
/// Application::with_state(State1)
/// .prefix("/app1")
/// .resource("/", |r| r.h(httpcodes::HttpOk))
/// .boxed(),
/// Application::with_state(State2)
/// .prefix("/app2")
/// .resource("/", |r| r.h(httpcodes::HttpOk))
/// .boxed() ]})
/// .bind("127.0.0.1:8080").unwrap()
/// .run()
/// # });
/// }
/// ```
pub fn boxed(mut self) -> Box<HttpHandler> {
Box::new(self.finish())
}
} }
impl<S: 'static> IntoHttpHandler for Application<S> { impl<S: 'static> IntoHttpHandler for Application<S> {
@ -407,7 +459,7 @@ mod tests {
#[test] #[test]
fn test_default_resource() { fn test_default_resource() {
let mut app = Application::new() let mut app = Application::new()
.resource("/test", |r| r.h(httpcodes::HTTPOk)) .resource("/test", |r| r.h(httpcodes::HttpOk))
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
@ -419,7 +471,7 @@ mod tests {
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
let mut app = Application::new() let mut app = Application::new()
.default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed)) .default_resource(|r| r.h(httpcodes::HttpMethodNotAllowed))
.finish(); .finish();
let req = TestRequest::with_uri("/blah").finish(); let req = TestRequest::with_uri("/blah").finish();
let resp = app.run(req); let resp = app.run(req);
@ -430,7 +482,7 @@ mod tests {
fn test_unhandled_prefix() { fn test_unhandled_prefix() {
let mut app = Application::new() let mut app = Application::new()
.prefix("/test") .prefix("/test")
.resource("/test", |r| r.h(httpcodes::HTTPOk)) .resource("/test", |r| r.h(httpcodes::HttpOk))
.finish(); .finish();
assert!(app.handle(HttpRequest::default()).is_err()); assert!(app.handle(HttpRequest::default()).is_err());
} }
@ -438,7 +490,7 @@ mod tests {
#[test] #[test]
fn test_state() { fn test_state() {
let mut app = Application::with_state(10) let mut app = Application::with_state(10)
.resource("/", |r| r.h(httpcodes::HTTPOk)) .resource("/", |r| r.h(httpcodes::HttpOk))
.finish(); .finish();
let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone());
let resp = app.run(req); let resp = app.run(req);
@ -449,7 +501,7 @@ mod tests {
fn test_prefix() { fn test_prefix() {
let mut app = Application::new() let mut app = Application::new()
.prefix("/test") .prefix("/test")
.resource("/blah", |r| r.h(httpcodes::HTTPOk)) .resource("/blah", |r| r.h(httpcodes::HttpOk))
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.handle(req); let resp = app.handle(req);
@ -471,7 +523,7 @@ mod tests {
#[test] #[test]
fn test_handler() { fn test_handler() {
let mut app = Application::new() let mut app = Application::new()
.handler("/test", httpcodes::HTTPOk) .handler("/test", httpcodes::HttpOk)
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
@ -499,7 +551,7 @@ mod tests {
fn test_handler_prefix() { fn test_handler_prefix() {
let mut app = Application::new() let mut app = Application::new()
.prefix("/app") .prefix("/app")
.handler("/test", httpcodes::HTTPOk) .handler("/test", httpcodes::HttpOk)
.finish(); .finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();

View File

@ -1,4 +1,4 @@
use std::fmt; use std::{fmt, mem};
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
@ -31,7 +31,7 @@ pub enum Binary {
Bytes(Bytes), Bytes(Bytes),
/// Static slice /// Static slice
Slice(&'static [u8]), Slice(&'static [u8]),
/// Shared stirng body /// Shared string body
SharedString(Rc<String>), SharedString(Rc<String>),
/// Shared string body /// Shared string body
#[doc(hidden)] #[doc(hidden)]
@ -122,6 +122,22 @@ impl Binary {
pub fn from_slice(s: &[u8]) -> Binary { pub fn from_slice(s: &[u8]) -> Binary {
Binary::Bytes(Bytes::from(s)) Binary::Bytes(Bytes::from(s))
} }
/// Convert Binary to a Bytes instance
pub fn take(&mut self) -> Bytes {
mem::replace(self, Binary::Slice(b"")).into()
}
}
impl Clone for Binary {
fn clone(&self) -> Binary {
match *self {
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
Binary::SharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())),
Binary::ArcSharedString(ref s) => Binary::Bytes(Bytes::from(s.as_str())),
}
}
} }
impl Into<Bytes> for Binary { impl Into<Bytes> for Binary {

310
src/client/connector.rs Normal file
View File

@ -0,0 +1,310 @@
use std::{io, time};
use std::net::Shutdown;
use actix::{fut, Actor, ActorFuture, Context,
Handler, Message, ActorResponse, Supervised};
use actix::registry::ArbiterService;
use actix::fut::WrapFuture;
use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect};
use http::{Uri, HttpTryFrom, Error as HttpError};
use futures::Poll;
use tokio_io::{AsyncRead, AsyncWrite};
#[cfg(feature="alpn")]
use openssl::ssl::{SslMethod, SslConnector, Error as OpensslError};
#[cfg(feature="alpn")]
use tokio_openssl::SslConnectorExt;
#[cfg(feature="alpn")]
use futures::Future;
use HAS_OPENSSL;
use server::IoStream;
#[derive(Debug)]
/// `Connect` type represents message that can be send to `ClientConnector`
/// with connection request.
pub struct Connect(pub Uri);
impl Connect {
/// Create `Connect` message for specified `Uri`
pub fn new<U>(uri: U) -> Result<Connect, HttpError> where Uri: HttpTryFrom<U> {
Ok(Connect(Uri::try_from(uri).map_err(|e| e.into())?))
}
}
impl Message for Connect {
type Result = Result<Connection, ClientConnectorError>;
}
/// A set of errors that can occur during connecting to a http host
#[derive(Fail, Debug)]
pub enum ClientConnectorError {
/// Invalid url
#[fail(display="Invalid url")]
InvalidUrl,
/// SSL feature is not enabled
#[fail(display="SSL is not supported")]
SslIsNotSupported,
/// SSL error
#[cfg(feature="alpn")]
#[fail(display="{}", _0)]
SslError(#[cause] OpensslError),
/// Connection error
#[fail(display = "{}", _0)]
Connector(#[cause] ConnectorError),
/// Connecting took too long
#[fail(display = "Timeout out while establishing connection")]
Timeout,
/// Connector has been disconnected
#[fail(display = "Internal error: connector has been disconnected")]
Disconnected,
/// Connection io error
#[fail(display = "{}", _0)]
IoError(#[cause] io::Error),
}
impl From<ConnectorError> for ClientConnectorError {
fn from(err: ConnectorError) -> ClientConnectorError {
ClientConnectorError::Connector(err)
}
}
pub struct ClientConnector {
#[cfg(feature="alpn")]
connector: SslConnector,
}
impl Actor for ClientConnector {
type Context = Context<ClientConnector>;
}
impl Supervised for ClientConnector {}
impl ArbiterService for ClientConnector {}
impl Default for ClientConnector {
fn default() -> ClientConnector {
#[cfg(feature="alpn")]
{
let builder = SslConnector::builder(SslMethod::tls()).unwrap();
ClientConnector {
connector: builder.build()
}
}
#[cfg(not(feature="alpn"))]
ClientConnector {}
}
}
impl ClientConnector {
#[cfg(feature="alpn")]
/// Create `ClientConnector` actor with custom `SslConnector` instance.
///
/// By default `ClientConnector` uses very simple ssl configuration.
/// With `with_connector` method it is possible to use custom `SslConnector`
/// object.
///
/// ```rust
/// # #![cfg(feature="alpn")]
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::Future;
/// # use std::io::Write;
/// extern crate openssl;
/// use actix::prelude::*;
/// use actix_web::client::{Connect, ClientConnector};
///
/// use openssl::ssl::{SslMethod, SslConnector};
///
/// fn main() {
/// let sys = System::new("test");
///
/// // Start `ClientConnector` with custom `SslConnector`
/// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build();
/// let conn: Address<_> = ClientConnector::with_connector(ssl_conn).start();
///
/// Arbiter::handle().spawn({
/// conn.send(
/// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host
/// .map_err(|_| ())
/// .and_then(|res| {
/// if let Ok(mut stream) = res {
/// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
/// }
/// # Arbiter::system().do_send(actix::msgs::SystemExit(0));
/// Ok(())
/// })
/// });
///
/// sys.run();
/// }
/// ```
pub fn with_connector(connector: SslConnector) -> ClientConnector {
ClientConnector { connector }
}
}
impl Handler<Connect> for ClientConnector {
type Result = ActorResponse<ClientConnector, Connection, ClientConnectorError>;
fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result {
let uri = &msg.0;
// host name is required
if uri.host().is_none() {
return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl))
}
// supported protocols
let proto = match uri.scheme_part() {
Some(scheme) => match Protocol::from(scheme.as_str()) {
Some(proto) => proto,
None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)),
},
None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)),
};
// check ssl availability
if proto.is_secure() && !HAS_OPENSSL { //&& !HAS_TLS {
return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported))
}
let host = uri.host().unwrap().to_owned();
let port = uri.port().unwrap_or_else(|| proto.port());
ActorResponse::async(
Connector::from_registry()
.send(ResolveConnect::host_and_port(&host, port))
.into_actor(self)
.map_err(|_, _, _| ClientConnectorError::Disconnected)
.and_then(move |res, _act, _| {
#[cfg(feature="alpn")]
match res {
Err(err) => fut::Either::B(fut::err(err.into())),
Ok(stream) => {
if proto.is_secure() {
fut::Either::A(
_act.connector.connect_async(&host, stream)
.map_err(ClientConnectorError::SslError)
.map(|stream| Connection{stream: Box::new(stream)})
.into_actor(_act))
} else {
fut::Either::B(fut::ok(Connection{stream: Box::new(stream)}))
}
}
}
#[cfg(not(feature="alpn"))]
match res {
Err(err) => fut::err(err.into()),
Ok(stream) => {
if proto.is_secure() {
fut::err(ClientConnectorError::SslIsNotSupported)
} else {
fut::ok(Connection{stream: Box::new(stream)})
}
}
}
}))
}
}
#[derive(PartialEq, Hash, Debug, Clone, Copy)]
enum Protocol {
Http,
Https,
Ws,
Wss,
}
impl Protocol {
fn from(s: &str) -> Option<Protocol> {
match s {
"http" => Some(Protocol::Http),
"https" => Some(Protocol::Https),
"ws" => Some(Protocol::Ws),
"wss" => Some(Protocol::Wss),
_ => None,
}
}
fn is_secure(&self) -> bool {
match *self {
Protocol::Https | Protocol::Wss => true,
_ => false,
}
}
fn port(&self) -> u16 {
match *self {
Protocol::Http | Protocol::Ws => 80,
Protocol::Https | Protocol::Wss => 443
}
}
}
pub struct Connection {
stream: Box<IoStream>,
}
impl Connection {
pub fn stream(&mut self) -> &mut IoStream {
&mut *self.stream
}
pub fn from_stream<T: IoStream>(io: T) -> Connection {
Connection{stream: Box::new(io)}
}
}
impl IoStream for Connection {
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
IoStream::shutdown(&mut *self.stream, how)
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
IoStream::set_nodelay(&mut *self.stream, nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
IoStream::set_linger(&mut *self.stream, dur)
}
}
impl io::Read for Connection {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.stream.read(buf)
}
}
impl AsyncRead for Connection {}
impl io::Write for Connection {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stream.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.stream.flush()
}
}
impl AsyncWrite for Connection {
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.stream.shutdown()
}
}

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

@ -0,0 +1,14 @@
//! Http client
mod connector;
mod parser;
mod request;
mod response;
mod pipeline;
mod writer;
pub use self::pipeline::{SendRequest, SendRequestError};
pub use self::request::{ClientRequest, ClientRequestBuilder};
pub use self::response::ClientResponse;
pub use self::connector::{Connect, Connection, ClientConnector, ClientConnectorError};
pub(crate) use self::writer::HttpClientWriter;
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};

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

@ -0,0 +1,189 @@
use std::mem;
use httparse;
use http::{Version, HttpTryFrom, HeaderMap, StatusCode};
use http::header::{self, HeaderName, HeaderValue};
use bytes::{Bytes, BytesMut, BufMut};
use futures::{Poll, Async};
use error::{ParseError, PayloadError};
use server::{utils, IoStream};
use server::h1::{Decoder, chunked};
use super::ClientResponse;
use super::response::ClientMessage;
const MAX_BUFFER_SIZE: usize = 131_072;
const MAX_HEADERS: usize = 96;
#[derive(Default)]
pub struct HttpResponseParser {
decoder: Option<Decoder>,
}
#[derive(Debug, Fail)]
pub enum HttpResponseParserError {
/// Server disconnected
#[fail(display="Server disconnected")]
Disconnect,
#[fail(display="{}", _0)]
Error(#[cause] ParseError),
}
impl HttpResponseParser {
pub fn parse<T>(&mut self, io: &mut T, buf: &mut BytesMut)
-> Poll<ClientResponse, HttpResponseParserError>
where T: IoStream
{
// if buf is empty parse_message will always return NotReady, let's avoid that
let read = if buf.is_empty() {
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) =>
return Err(HttpResponseParserError::Disconnect),
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) =>
return Ok(Async::NotReady),
Err(err) =>
return Err(HttpResponseParserError::Error(err.into()))
}
false
} else {
true
};
loop {
match HttpResponseParser::parse_message(buf).map_err(HttpResponseParserError::Error)? {
Async::Ready((msg, decoder)) => {
self.decoder = decoder;
return Ok(Async::Ready(msg));
},
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(ParseError::TooLarge));
}
if read || buf.remaining_mut() == 0 {
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) =>
return Err(HttpResponseParserError::Disconnect),
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) =>
return Err(HttpResponseParserError::Error(err.into())),
}
} else {
return Ok(Async::NotReady)
}
},
}
}
}
pub fn parse_payload<T>(&mut self, io: &mut T, buf: &mut BytesMut)
-> Poll<Option<Bytes>, PayloadError>
where T: IoStream
{
if self.decoder.is_some() {
// read payload
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => {
if buf.is_empty() {
return Err(PayloadError::Incomplete)
}
}
Err(err) => return Err(err.into()),
_ => (),
}
match self.decoder.as_mut().unwrap().decode(buf) {
Ok(Async::Ready(Some(b))) => Ok(Async::Ready(Some(b))),
Ok(Async::Ready(None)) => {
self.decoder.take();
Ok(Async::Ready(None))
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => Err(err.into()),
}
} else {
Ok(Async::Ready(None))
}
}
fn parse_message(buf: &mut BytesMut)
-> Poll<(ClientResponse, Option<Decoder>), ParseError>
{
// Parse http message
let bytes_ptr = buf.as_ref().as_ptr() as usize;
let mut headers: [httparse::Header; MAX_HEADERS] =
unsafe{mem::uninitialized()};
let (len, version, status, headers_len) = {
let b = unsafe{ let b: &[u8] = buf; mem::transmute(b) };
let mut resp = httparse::Response::new(&mut headers);
match resp.parse(b)? {
httparse::Status::Complete(len) => {
let version = if resp.version.unwrap() == 1 {
Version::HTTP_11
} else {
Version::HTTP_10
};
let status = StatusCode::from_u16(resp.code.unwrap())
.map_err(|_| ParseError::Status)?;
(len, version, status, resp.headers.len())
}
httparse::Status::Partial => return Ok(Async::NotReady),
}
};
let slice = buf.split_to(len).freeze();
// convert headers
let mut hdrs = HeaderMap::new();
for header in headers[..headers_len].iter() {
if let Ok(name) = HeaderName::try_from(header.name) {
let v_start = header.value.as_ptr() as usize - bytes_ptr;
let v_end = v_start + header.value.len();
let value = unsafe {
HeaderValue::from_shared_unchecked(slice.slice(v_start, v_end)) };
hdrs.append(name, value);
} else {
return Err(ParseError::Header)
}
}
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
Some(Decoder::eof())
} else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
Some(Decoder::length(len))
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header)
}
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header)
}
} else if chunked(&hdrs)? {
// Chunked encoding
Some(Decoder::chunked())
} else {
None
};
if let Some(decoder) = decoder {
Ok(Async::Ready(
(ClientResponse::new(
ClientMessage{status, version,
headers: hdrs, cookies: None}), Some(decoder))))
} else {
Ok(Async::Ready(
(ClientResponse::new(
ClientMessage{status, version,
headers: hdrs, cookies: None}), None)))
}
}
}

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

@ -0,0 +1,383 @@
use std::{io, mem};
use bytes::{Bytes, BytesMut};
use http::header::CONTENT_ENCODING;
use futures::{Async, Future, Poll};
use futures::unsync::oneshot;
use actix::prelude::*;
use error::Error;
use body::{Body, BodyStream};
use context::{Frame, ActorHttpContext};
use headers::ContentEncoding;
use httpmessage::HttpMessage;
use error::PayloadError;
use server::WriterState;
use server::shared::SharedBytes;
use server::encoding::PayloadStream;
use super::{ClientRequest, ClientResponse};
use super::{Connect, Connection, ClientConnector, ClientConnectorError};
use super::HttpClientWriter;
use super::{HttpResponseParser, HttpResponseParserError};
/// A set of errors that can occur during sending request and reading response
#[derive(Fail, Debug)]
pub enum SendRequestError {
/// Failed to connect to host
#[fail(display="Failed to connect to host: {}", _0)]
Connector(#[cause] ClientConnectorError),
/// Error parsing response
#[fail(display="{}", _0)]
ParseError(#[cause] HttpResponseParserError),
/// Error reading response payload
#[fail(display="Error reading response payload: {}", _0)]
Io(#[cause] io::Error),
}
impl From<io::Error> for SendRequestError {
fn from(err: io::Error) -> SendRequestError {
SendRequestError::Io(err)
}
}
enum State {
New,
Connect(actix::dev::Request<Unsync, ClientConnector, Connect>),
Connection(Connection),
Send(Box<Pipeline>),
None,
}
/// `SendRequest` is a `Future` which represents asynchronous request sending process.
#[must_use = "SendRequest does nothing unless polled"]
pub struct SendRequest {
req: ClientRequest,
state: State,
conn: Addr<Unsync, ClientConnector>,
}
impl SendRequest {
pub(crate) fn new(req: ClientRequest) -> SendRequest {
SendRequest::with_connector(req, ClientConnector::from_registry())
}
pub(crate) fn with_connector(req: ClientRequest, conn: Addr<Unsync, ClientConnector>)
-> SendRequest
{
SendRequest{req, conn, state: State::New}
}
pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest
{
SendRequest{
req,
state: State::Connection(conn),
conn: ClientConnector::from_registry()}
}
}
impl Future for SendRequest {
type Item = ClientResponse;
type Error = SendRequestError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
let state = mem::replace(&mut self.state, State::None);
match state {
State::New =>
self.state = State::Connect(self.conn.send(Connect(self.req.uri().clone()))),
State::Connect(mut conn) => match conn.poll() {
Ok(Async::NotReady) => {
self.state = State::Connect(conn);
return Ok(Async::NotReady);
},
Ok(Async::Ready(result)) => match result {
Ok(stream) => {
self.state = State::Connection(stream)
},
Err(err) => return Err(SendRequestError::Connector(err)),
},
Err(_) => return Err(SendRequestError::Connector(
ClientConnectorError::Disconnected))
},
State::Connection(conn) => {
let mut writer = HttpClientWriter::new(SharedBytes::default());
writer.start(&mut self.req)?;
let body = match self.req.replace_body(Body::Empty) {
Body::Streaming(stream) => IoBody::Payload(stream),
Body::Actor(ctx) => IoBody::Actor(ctx),
_ => IoBody::Done,
};
let pl = Box::new(Pipeline {
body, conn, writer,
parser: Some(HttpResponseParser::default()),
parser_buf: BytesMut::new(),
disconnected: false,
drain: None,
decompress: None,
should_decompress: self.req.response_decompress(),
write_state: RunningState::Running,
});
self.state = State::Send(pl);
},
State::Send(mut pl) => {
pl.poll_write()
.map_err(|e| io::Error::new(
io::ErrorKind::Other, format!("{}", e).as_str()))?;
match pl.parse() {
Ok(Async::Ready(mut resp)) => {
resp.set_pipeline(pl);
return Ok(Async::Ready(resp))
},
Ok(Async::NotReady) => {
self.state = State::Send(pl);
return Ok(Async::NotReady)
},
Err(err) => return Err(SendRequestError::ParseError(err))
}
}
State::None => unreachable!(),
}
}
}
}
pub(crate) struct Pipeline {
body: IoBody,
conn: Connection,
writer: HttpClientWriter,
parser: Option<HttpResponseParser>,
parser_buf: BytesMut,
disconnected: bool,
drain: Option<oneshot::Sender<()>>,
decompress: Option<PayloadStream>,
should_decompress: bool,
write_state: RunningState,
}
enum IoBody {
Payload(BodyStream),
Actor(Box<ActorHttpContext>),
Done,
}
#[derive(Debug, PartialEq)]
enum RunningState {
Running,
Paused,
Done,
}
impl RunningState {
#[inline]
fn pause(&mut self) {
if *self != RunningState::Done {
*self = RunningState::Paused
}
}
#[inline]
fn resume(&mut self) {
if *self != RunningState::Done {
*self = RunningState::Running
}
}
}
impl Pipeline {
#[inline]
pub fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
match self.parser.as_mut().unwrap().parse(&mut self.conn, &mut self.parser_buf) {
Ok(Async::Ready(resp)) => {
// check content-encoding
if self.should_decompress {
if let Some(enc) = resp.headers().get(CONTENT_ENCODING) {
if let Ok(enc) = enc.to_str() {
match ContentEncoding::from(enc) {
ContentEncoding::Auto | ContentEncoding::Identity => (),
enc => self.decompress = Some(PayloadStream::new(enc)),
}
}
}
}
Ok(Async::Ready(resp))
}
val => val,
}
}
#[inline]
pub fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
let mut need_run = false;
// need write?
if let Async::NotReady = self.poll_write()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?
{
need_run = true;
}
// need read?
if self.parser.is_some() {
loop {
match self.parser.as_mut().unwrap()
.parse_payload(&mut self.conn, &mut self.parser_buf)?
{
Async::Ready(Some(b)) => {
if let Some(ref mut decompress) = self.decompress {
match decompress.feed_data(b) {
Ok(Some(b)) => return Ok(Async::Ready(Some(b))),
Ok(None) => return Ok(Async::NotReady),
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock =>
continue,
Err(err) => return Err(err.into()),
}
} else {
return Ok(Async::Ready(Some(b)))
}
},
Async::Ready(None) => {
let _ = self.parser.take();
break
}
Async::NotReady => return Ok(Async::NotReady),
}
}
}
// eof
if let Some(mut decompress) = self.decompress.take() {
let res = decompress.feed_eof();
if let Some(b) = res? {
return Ok(Async::Ready(Some(b)))
}
}
if need_run {
Ok(Async::NotReady)
} else {
Ok(Async::Ready(None))
}
}
#[inline]
pub fn poll_write(&mut self) -> Poll<(), Error> {
if self.write_state == RunningState::Done {
return Ok(Async::Ready(()))
}
let mut done = false;
if self.drain.is_none() && self.write_state != RunningState::Paused {
'outter: loop {
let result = match mem::replace(&mut self.body, IoBody::Done) {
IoBody::Payload(mut body) => {
match body.poll()? {
Async::Ready(None) => {
self.writer.write_eof()?;
self.disconnected = true;
break
},
Async::Ready(Some(chunk)) => {
self.body = IoBody::Payload(body);
self.writer.write(chunk.into())?
}
Async::NotReady => {
done = true;
self.body = IoBody::Payload(body);
break
},
}
},
IoBody::Actor(mut ctx) => {
if self.disconnected {
ctx.disconnected();
}
match ctx.poll()? {
Async::Ready(Some(vec)) => {
if vec.is_empty() {
self.body = IoBody::Actor(ctx);
break
}
let mut res = None;
for frame in vec {
match frame {
Frame::Chunk(None) => {
// info.context = Some(ctx);
self.disconnected = true;
self.writer.write_eof()?;
break 'outter
},
Frame::Chunk(Some(chunk)) =>
res = Some(self.writer.write(chunk)?),
Frame::Drain(fut) => self.drain = Some(fut),
}
}
self.body = IoBody::Actor(ctx);
if self.drain.is_some() {
self.write_state.resume();
break
}
res.unwrap()
},
Async::Ready(None) => {
done = true;
break
}
Async::NotReady => {
done = true;
self.body = IoBody::Actor(ctx);
break
}
}
},
IoBody::Done => {
self.disconnected = true;
done = true;
break
}
};
match result {
WriterState::Pause => {
self.write_state.pause();
break
}
WriterState::Done => {
self.write_state.resume()
},
}
}
}
// flush io but only if we need to
match self.writer.poll_completed(&mut self.conn, false) {
Ok(Async::Ready(_)) => {
if self.disconnected {
self.write_state = RunningState::Done;
} else {
self.write_state.resume();
}
// resolve drain futures
if let Some(tx) = self.drain.take() {
let _ = tx.send(());
}
// restart io processing
if !done || self.write_state == RunningState::Done {
self.poll_write()
} else {
Ok(Async::NotReady)
}
},
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => Err(err.into()),
}
}
}

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

@ -0,0 +1,578 @@
use std::{fmt, mem};
use std::io::Write;
use actix::{Addr, Unsync};
use cookie::{Cookie, CookieJar};
use bytes::{BytesMut, BufMut};
use http::{uri, HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError};
use http::header::{self, HeaderName, HeaderValue};
use serde_json;
use serde::Serialize;
use body::Body;
use error::Error;
use headers::ContentEncoding;
use super::pipeline::SendRequest;
use super::connector::{Connection, ClientConnector};
/// An HTTP Client Request
pub struct ClientRequest {
uri: Uri,
method: Method,
version: Version,
headers: HeaderMap,
body: Body,
chunked: bool,
upgrade: bool,
encoding: ContentEncoding,
response_decompress: bool,
buffer_capacity: Option<(usize, usize)>,
conn: ConnectionType,
}
enum ConnectionType {
Default,
Connector(Addr<Unsync, ClientConnector>),
Connection(Connection),
}
impl Default for ClientRequest {
fn default() -> ClientRequest {
ClientRequest {
uri: Uri::default(),
method: Method::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
body: Body::Empty,
chunked: false,
upgrade: false,
encoding: ContentEncoding::Auto,
response_decompress: true,
buffer_capacity: None,
conn: ConnectionType::Default,
}
}
}
impl ClientRequest {
/// Create request builder for `GET` request
pub fn get<U>(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom<U> {
let mut builder = ClientRequest::build();
builder.method(Method::GET).uri(uri);
builder
}
/// Create request builder for `HEAD` request
pub fn head<U>(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom<U> {
let mut builder = ClientRequest::build();
builder.method(Method::HEAD).uri(uri);
builder
}
/// Create request builder for `POST` request
pub fn post<U>(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom<U> {
let mut builder = ClientRequest::build();
builder.method(Method::POST).uri(uri);
builder
}
/// Create request builder for `PUT` request
pub fn put<U>(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom<U> {
let mut builder = ClientRequest::build();
builder.method(Method::PUT).uri(uri);
builder
}
/// Create request builder for `DELETE` request
pub fn delete<U>(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom<U> {
let mut builder = ClientRequest::build();
builder.method(Method::DELETE).uri(uri);
builder
}
}
impl ClientRequest {
/// Create client request builder
pub fn build() -> ClientRequestBuilder {
ClientRequestBuilder {
request: Some(ClientRequest::default()),
err: None,
cookies: None,
default_headers: true,
}
}
/// Get the request uri
#[inline]
pub fn uri(&self) -> &Uri {
&self.uri
}
/// Set client request uri
#[inline]
pub fn set_uri(&mut self, uri: Uri) {
self.uri = uri
}
/// Get the request method
#[inline]
pub fn method(&self) -> &Method {
&self.method
}
/// Set http `Method` for the request
#[inline]
pub fn set_method(&mut self, method: Method) {
self.method = method
}
/// Get http version for the request
#[inline]
pub fn version(&self) -> Version {
self.version
}
/// Set http `Version` for the request
#[inline]
pub fn set_version(&mut self, version: Version) {
self.version = version
}
/// Get the headers from the request
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Get a mutable reference to the headers
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
/// is chunked encoding enabled
#[inline]
pub fn chunked(&self) -> bool {
self.chunked
}
/// is upgrade request
#[inline]
pub fn upgrade(&self) -> bool {
self.upgrade
}
/// Content encoding
#[inline]
pub fn content_encoding(&self) -> ContentEncoding {
self.encoding
}
/// Decompress response payload
#[inline]
pub fn response_decompress(&self) -> bool {
self.response_decompress
}
pub fn buffer_capacity(&self) -> Option<(usize, usize)> {
self.buffer_capacity
}
/// Get body os this response
#[inline]
pub fn body(&self) -> &Body {
&self.body
}
/// Set a body
pub fn set_body<B: Into<Body>>(&mut self, body: B) {
self.body = body.into();
}
/// Extract body, replace it with Empty
pub(crate) fn replace_body(&mut self, body: Body) -> Body {
mem::replace(&mut self.body, body)
}
/// Send request
///
/// This method returns future that resolves to a ClientResponse
pub fn send(mut self) -> SendRequest {
match mem::replace(&mut self.conn, ConnectionType::Default) {
ConnectionType::Default => SendRequest::new(self),
ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn),
ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn),
}
}
}
impl fmt::Debug for ClientRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nClientRequest {:?} {}:{}\n",
self.version, self.method, self.uri);
let _ = write!(f, " headers:\n");
for key in self.headers.keys() {
let vals: Vec<_> = self.headers.get_all(key).iter().collect();
if vals.len() > 1 {
let _ = write!(f, " {:?}: {:?}\n", key, vals);
} else {
let _ = write!(f, " {:?}: {:?}\n", key, vals[0]);
}
}
res
}
}
/// An HTTP Client request builder
///
/// This type can be used to construct an instance of `ClientRequest` through a
/// builder-like pattern.
pub struct ClientRequestBuilder {
request: Option<ClientRequest>,
err: Option<HttpError>,
cookies: Option<CookieJar>,
default_headers: bool,
}
impl ClientRequestBuilder {
/// Set HTTP uri of request.
#[inline]
pub fn uri<U>(&mut self, uri: U) -> &mut Self where Uri: HttpTryFrom<U> {
match Uri::try_from(uri) {
Ok(uri) => {
// set request host header
if let Some(host) = uri.host() {
self.set_header(header::HOST, host);
}
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.uri = uri;
}
},
Err(e) => self.err = Some(e.into(),),
}
self
}
/// Set HTTP method of this request.
#[inline]
pub fn method(&mut self, method: Method) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.method = method;
}
self
}
/// Set HTTP version of this request.
///
/// By default requests's http version depends on network stream
#[inline]
pub fn version(&mut self, version: Version) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.version = version;
}
self
}
/// Add a header.
///
/// Header get appended to existing header.
/// To override header use `set_header()` method.
///
/// ```rust
/// # extern crate http;
/// # extern crate actix_web;
/// # use actix_web::client::*;
/// #
/// use http::header;
///
/// fn main() {
/// let req = ClientRequest::build()
/// .header("X-TEST", "value")
/// .header(header::CONTENT_TYPE, "application/json")
/// .finish().unwrap();
/// }
/// ```
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where HeaderName: HttpTryFrom<K>, HeaderValue: HttpTryFrom<V>
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => {
match HeaderValue::try_from(value) {
Ok(value) => { parts.headers.append(key, value); }
Err(e) => self.err = Some(e.into()),
}
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Replace a header.
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
where HeaderName: HttpTryFrom<K>, HeaderValue: HttpTryFrom<V>
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => {
match HeaderValue::try_from(value) {
Ok(value) => { parts.headers.insert(key, value); }
Err(e) => self.err = Some(e.into()),
}
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set content encoding.
///
/// By default `ContentEncoding::Identity` is used.
#[inline]
pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.encoding = enc;
}
self
}
/// Enables automatic chunked transfer encoding
#[inline]
pub fn chunked(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.chunked = true;
}
self
}
/// Enable connection upgrade
#[inline]
pub fn upgrade(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.upgrade = true;
}
self
}
/// Set request's content type
#[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self
where HeaderValue: HttpTryFrom<V>
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderValue::try_from(value) {
Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); },
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set content length
#[inline]
pub fn content_length(&mut self, len: u64) -> &mut Self {
let mut wrt = BytesMut::new().writer();
let _ = write!(wrt, "{}", len);
self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze())
}
/// Set a cookie
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # use actix_web::httpcodes::*;
/// #
/// use actix_web::headers::Cookie;
/// use actix_web::client::ClientRequest;
///
/// fn main() {
/// let req = ClientRequest::build()
/// .cookie(
/// Cookie::build("name", "value")
/// .domain("www.rust-lang.org")
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .finish())
/// .finish().unwrap();
/// }
/// ```
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
if self.cookies.is_none() {
let mut jar = CookieJar::new();
jar.add(cookie.into_owned());
self.cookies = Some(jar)
} else {
self.cookies.as_mut().unwrap().add(cookie.into_owned());
}
self
}
/// Remove cookie, cookie has to be cookie from `HttpRequest::cookies()` method.
pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self {
{
if self.cookies.is_none() {
self.cookies = Some(CookieJar::new())
}
let jar = self.cookies.as_mut().unwrap();
let cookie = cookie.clone().into_owned();
jar.add_original(cookie.clone());
jar.remove(cookie);
}
self
}
/// Do not add default request headers.
/// By default `Accept-Encoding` header is set.
pub fn no_default_headers(&mut self) -> &mut Self {
self.default_headers = false;
self
}
/// Disable automatic decompress response body
pub fn disable_decompress(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.response_decompress = false;
}
self
}
/// Set write buffer capacity
pub fn buffer_capacity(&mut self,
low_watermark: usize,
high_watermark: usize) -> &mut Self
{
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.buffer_capacity = Some((low_watermark, high_watermark));
}
self
}
/// Send request using custom connector
pub fn with_connector(&mut self, conn: Addr<Unsync, ClientConnector>) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connector(conn);
}
self
}
/// Send request using existing Connection
pub fn with_connection(&mut self, conn: Connection) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connection(conn);
}
self
}
/// This method calls provided closure with builder reference if value is true.
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
where F: FnOnce(&mut ClientRequestBuilder)
{
if value {
f(self);
}
self
}
/// This method calls provided closure with builder reference if value is Some.
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
where F: FnOnce(T, &mut ClientRequestBuilder)
{
if let Some(val) = value {
f(val, self);
}
self
}
/// Set a body and generate `ClientRequest`.
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn body<B: Into<Body>>(&mut self, body: B) -> Result<ClientRequest, HttpError> {
if let Some(e) = self.err.take() {
return Err(e)
}
if self.default_headers {
// enable br only for https
let https =
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.uri.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS).unwrap_or(true)
} else {
true
};
if https {
self.header(header::ACCEPT_ENCODING, "br, gzip, deflate");
} else {
self.header(header::ACCEPT_ENCODING, "gzip, deflate");
}
}
let mut request = self.request.take().expect("cannot reuse request builder");
// set cookies
if let Some(ref jar) = self.cookies {
for cookie in jar.delta() {
request.headers.append(
header::SET_COOKIE,
HeaderValue::from_str(&cookie.to_string())?);
}
}
request.body = body.into();
Ok(request)
}
/// Set a json body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn json<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
let body = serde_json::to_string(&value)?;
let contains = if let Some(parts) = parts(&mut self.request, &self.err) {
parts.headers.contains_key(header::CONTENT_TYPE)
} else {
true
};
if !contains {
self.header(header::CONTENT_TYPE, "application/json");
}
Ok(self.body(body)?)
}
/// Set an empty body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn finish(&mut self) -> Result<ClientRequest, HttpError> {
self.body(Body::Empty)
}
/// This method construct new `ClientRequestBuilder`
pub fn take(&mut self) -> ClientRequestBuilder {
ClientRequestBuilder {
request: self.request.take(),
err: self.err.take(),
cookies: self.cookies.take(),
default_headers: self.default_headers,
}
}
}
#[inline]
fn parts<'a>(parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>)
-> Option<&'a mut ClientRequest>
{
if err.is_some() {
return None
}
parts.as_mut()
}

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

@ -0,0 +1,139 @@
use std::{fmt, str};
use std::rc::Rc;
use std::cell::UnsafeCell;
use bytes::Bytes;
use cookie::Cookie;
use futures::{Async, Poll, Stream};
use http::{HeaderMap, StatusCode, Version};
use http::header::{self, HeaderValue};
use httpmessage::HttpMessage;
use error::{CookieParseError, PayloadError};
use super::pipeline::Pipeline;
pub(crate) struct ClientMessage {
pub status: StatusCode,
pub version: Version,
pub headers: HeaderMap<HeaderValue>,
pub cookies: Option<Vec<Cookie<'static>>>,
}
impl Default for ClientMessage {
fn default() -> ClientMessage {
ClientMessage {
status: StatusCode::OK,
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
cookies: None,
}
}
}
/// An HTTP Client response
pub struct ClientResponse(Rc<UnsafeCell<ClientMessage>>, Option<Box<Pipeline>>);
impl HttpMessage for ClientResponse {
/// Get the headers from the response.
#[inline]
fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
}
}
impl ClientResponse {
pub(crate) fn new(msg: ClientMessage) -> ClientResponse {
ClientResponse(Rc::new(UnsafeCell::new(msg)), None)
}
pub(crate) fn set_pipeline(&mut self, pl: Box<Pipeline>) {
self.1 = Some(pl);
}
#[inline]
fn as_ref(&self) -> &ClientMessage {
unsafe{ &*self.0.get() }
}
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
fn as_mut(&self) -> &mut ClientMessage {
unsafe{ &mut *self.0.get() }
}
/// Get the HTTP version of this response.
#[inline]
pub fn version(&self) -> Version {
self.as_ref().version
}
/// Get the status from the server.
#[inline]
pub fn status(&self) -> StatusCode {
self.as_ref().status
}
/// Load request cookies.
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
if self.as_ref().cookies.is_none() {
let msg = self.as_mut();
let mut cookies = Vec::new();
if let Some(val) = msg.headers.get(header::COOKIE) {
let s = str::from_utf8(val.as_bytes())
.map_err(CookieParseError::from)?;
for cookie in s.split("; ") {
cookies.push(Cookie::parse_encoded(cookie)?.into_owned());
}
}
msg.cookies = Some(cookies)
}
Ok(self.as_ref().cookies.as_ref().unwrap())
}
/// Return request cookie.
pub fn cookie(&self, name: &str) -> Option<&Cookie> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies {
if cookie.name() == name {
return Some(cookie)
}
}
}
None
}
}
impl fmt::Debug for ClientResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(
f, "\nClientResponse {:?} {}\n", self.version(), self.status());
let _ = write!(f, " headers:\n");
for key in self.headers().keys() {
let vals: Vec<_> = self.headers().get_all(key).iter().collect();
if vals.len() > 1 {
let _ = write!(f, " {:?}: {:?}\n", key, vals);
} else {
let _ = write!(f, " {:?}: {:?}\n", key, vals[0]);
}
}
res
}
}
/// Future that resolves to a complete request body.
impl Stream for ClientResponse {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(ref mut pl) = self.1 {
pl.poll()
} else {
Ok(Async::Ready(None))
}
}
}

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

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

View File

@ -1,4 +1,4 @@
use std; use std::mem;
use std::marker::PhantomData; use std::marker::PhantomData;
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use futures::sync::oneshot::Sender; use futures::sync::oneshot::Sender;
@ -6,19 +6,18 @@ use futures::unsync::oneshot;
use smallvec::SmallVec; use smallvec::SmallVec;
use actix::{Actor, ActorState, ActorContext, AsyncContext, use actix::{Actor, ActorState, ActorContext, AsyncContext,
Address, SyncAddress, Handler, Subscriber, ResponseType, SpawnHandle}; Addr, Handler, Message, SpawnHandle, Syn, Unsync};
use actix::fut::ActorFuture; use actix::fut::ActorFuture;
use actix::dev::{queue, AsyncContextApi, use actix::dev::{ContextImpl, ToEnvelope, SyncEnvelope};
ContextImpl, ContextProtocol, Envelope, ToEnvelope, RemoteEnvelope};
use body::{Body, Binary}; use body::{Body, Binary};
use error::{Error, Result, ErrorInternalServerError}; use error::{Error, ErrorInternalServerError};
use httprequest::HttpRequest; use httprequest::HttpRequest;
pub trait ActorHttpContext: 'static { pub trait ActorHttpContext: 'static {
fn disconnected(&mut self); fn disconnected(&mut self);
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 2]>>, Error>; fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error>;
} }
#[derive(Debug)] #[derive(Debug)]
@ -27,11 +26,20 @@ pub enum Frame {
Drain(oneshot::Sender<()>), Drain(oneshot::Sender<()>),
} }
impl Frame {
pub fn len(&self) -> usize {
match *self {
Frame::Chunk(Some(ref bin)) => bin.len(),
_ => 0,
}
}
}
/// Http actor execution context /// Http actor execution context
pub struct HttpContext<A, S=()> where A: Actor<Context=HttpContext<A, S>>, pub struct HttpContext<A, S=()> where A: Actor<Context=HttpContext<A, S>>,
{ {
inner: ContextImpl<A>, inner: ContextImpl<A>,
stream: Option<SmallVec<[Frame; 2]>>, stream: Option<SmallVec<[Frame; 4]>>,
request: HttpRequest<S>, request: HttpRequest<S>,
disconnected: bool, disconnected: bool,
} }
@ -51,33 +59,36 @@ impl<A, S> ActorContext for HttpContext<A, S> where A: Actor<Context=Self>
impl<A, S> AsyncContext<A> for HttpContext<A, S> where A: Actor<Context=Self> impl<A, S> AsyncContext<A> for HttpContext<A, S> where A: Actor<Context=Self>
{ {
#[inline]
fn spawn<F>(&mut self, fut: F) -> SpawnHandle fn spawn<F>(&mut self, fut: F) -> SpawnHandle
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
{ {
self.inner.spawn(fut) self.inner.spawn(fut)
} }
#[inline]
fn wait<F>(&mut self, fut: F) fn wait<F>(&mut self, fut: F)
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
{ {
self.inner.wait(fut) self.inner.wait(fut)
} }
#[doc(hidden)]
#[inline]
fn waiting(&self) -> bool {
self.inner.waiting() || self.inner.state() == ActorState::Stopping ||
self.inner.state() == ActorState::Stopped
}
#[inline]
fn cancel_future(&mut self, handle: SpawnHandle) -> bool { fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
self.inner.cancel_future(handle) self.inner.cancel_future(handle)
} }
} #[doc(hidden)]
#[doc(hidden)]
impl<A, S> AsyncContextApi<A> for HttpContext<A, S> where A: Actor<Context=Self> {
#[inline] #[inline]
fn unsync_sender(&mut self) -> queue::unsync::UnboundedSender<ContextProtocol<A>> { fn unsync_address(&mut self) -> Addr<Unsync, A> {
self.inner.unsync_sender()
}
#[inline]
fn unsync_address(&mut self) -> Address<A> {
self.inner.unsync_address() self.inner.unsync_address()
} }
#[doc(hidden)]
#[inline] #[inline]
fn sync_address(&mut self) -> SyncAddress<A> { fn sync_address(&mut self) -> Addr<Syn, A> {
self.inner.sync_address() self.inner.sync_address()
} }
} }
@ -127,7 +138,7 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
} }
} }
/// Indicate end of streamimng payload. Also this method calls `Self::close`. /// Indicate end of streaming payload. Also this method calls `Self::close`.
#[inline] #[inline]
pub fn write_eof(&mut self) { pub fn write_eof(&mut self) {
self.add_frame(Frame::Chunk(None)); self.add_frame(Frame::Chunk(None));
@ -153,26 +164,14 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
self.stream = Some(SmallVec::new()); self.stream = Some(SmallVec::new());
} }
self.stream.as_mut().map(|s| s.push(frame)); self.stream.as_mut().map(|s| s.push(frame));
} self.inner.modify();
}
impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
#[inline]
#[doc(hidden)]
pub fn subscriber<M>(&mut self) -> Box<Subscriber<M>>
where A: Handler<M>, M: ResponseType + 'static
{
self.inner.subscriber()
} }
#[inline] /// Handle of the running future
#[doc(hidden)] ///
pub fn sync_subscriber<M>(&mut self) -> Box<Subscriber<M> + Send> /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method.
where A: Handler<M>, pub fn handle(&self) -> SpawnHandle {
M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send, self.inner.curr_handle()
{
self.inner.sync_subscriber()
} }
} }
@ -184,9 +183,9 @@ impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>,
self.stop(); self.stop();
} }
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 2]>>, Error> { fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
let ctx: &mut HttpContext<A, S> = unsafe { let ctx: &mut HttpContext<A, S> = unsafe {
std::mem::transmute(self as &mut HttpContext<A, S>) mem::transmute(self as &mut HttpContext<A, S>)
}; };
if self.inner.alive() { if self.inner.alive() {
@ -207,16 +206,12 @@ impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>,
} }
} }
impl<A, S> ToEnvelope<A> for HttpContext<A, S> impl<A, M, S> ToEnvelope<Syn, A, M> for HttpContext<A, S>
where A: Actor<Context=HttpContext<A, S>>, where A: Actor<Context=HttpContext<A, S>> + Handler<M>,
M: Message + Send + 'static, M::Result: Send,
{ {
#[inline] fn pack(msg: M, tx: Option<Sender<M::Result>>) -> SyncEnvelope<A> {
fn pack<M>(msg: M, tx: Option<Sender<Result<M::Item, M::Error>>>, SyncEnvelope::new(msg, tx)
channel_on_drop: bool) -> Envelope<A>
where A: Handler<M>,
M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send
{
RemoteEnvelope::new(msg, tx, channel_on_drop).into()
} }
} }
@ -236,10 +231,7 @@ pub struct Drain<A> {
impl<A> Drain<A> { impl<A> Drain<A> {
pub fn new(fut: oneshot::Receiver<()>) -> Self { pub fn new(fut: oneshot::Receiver<()>) -> Self {
Drain { Drain { fut, _a: PhantomData }
fut: fut,
_a: PhantomData
}
} }
} }

View File

@ -4,26 +4,27 @@ use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use std::io::Error as IoError; use std::io::Error as IoError;
#[cfg(actix_nightly)]
use std::error::Error as StdError;
use cookie; use cookie;
use httparse; use httparse;
use failure::Fail; use actix::MailboxError;
use futures::Canceled; use futures::Canceled;
use failure;
use failure::{Fail, Backtrace};
use http2::Error as Http2Error; use http2::Error as Http2Error;
use http::{header, StatusCode, Error as HttpError}; use http::{header, StatusCode, Error as HttpError};
use http::uri::InvalidUriBytes; use http::uri::InvalidUriBytes;
use http_range::HttpRangeParseError; use http_range::HttpRangeParseError;
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use url::ParseError as UrlParseError; pub use url::ParseError as UrlParseError;
// re-exports // re-exports
pub use cookie::{ParseError as CookieParseError}; pub use cookie::{ParseError as CookieParseError};
use body::Body; use body::Body;
use handler::Responder;
use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed}; use httpcodes::{self, HttpExpectationFailed};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations /// for actix web operations
@ -33,9 +34,9 @@ use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed};
pub type Result<T, E=Error> = result::Result<T, E>; pub type Result<T, E=Error> = result::Result<T, E>;
/// General purpose actix web error /// General purpose actix web error
#[derive(Fail, Debug)]
pub struct Error { pub struct Error {
cause: Box<ResponseError>, cause: Box<ResponseError>,
backtrace: Option<Backtrace>,
} }
impl Error { impl Error {
@ -64,6 +65,16 @@ impl fmt::Display for Error {
} }
} }
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(bt) = self.cause.backtrace() {
write!(f, "{:?}\n\n{:?}", &self.cause, bt)
} else {
write!(f, "{:?}\n\n{:?}", &self.cause, self.backtrace.as_ref().unwrap())
}
}
}
/// `HttpResponse` for `Error` /// `HttpResponse` for `Error`
impl From<Error> for HttpResponse { impl From<Error> for HttpResponse {
fn from(err: Error) -> Self { fn from(err: Error) -> Self {
@ -74,21 +85,31 @@ impl From<Error> for HttpResponse {
/// `Error` for any error that implements `ResponseError` /// `Error` for any error that implements `ResponseError`
impl<T: ResponseError> From<T> for Error { impl<T: ResponseError> From<T> for Error {
fn from(err: T) -> Error { fn from(err: T) -> Error {
Error { cause: Box::new(err) } let backtrace = if err.backtrace().is_none() {
Some(Backtrace::new())
} else {
None
};
Error { cause: Box::new(err), backtrace }
} }
} }
/// Default error is `InternalServerError` /// Compatibility for `failure::Error`
#[cfg(actix_nightly)] impl<T> ResponseError for failure::Compat<T>
default impl<T: StdError + Sync + Send + 'static> ResponseError for T { where T: fmt::Display + fmt::Debug + Sync + Send + 'static { }
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty) impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error {
err.compat().into()
} }
} }
/// `InternalServerError` for `JsonError` /// `InternalServerError` for `JsonError`
impl ResponseError for JsonError {} impl ResponseError for JsonError {}
/// `InternalServerError` for `UrlParseError`
impl ResponseError for UrlParseError {}
/// Return `InternalServerError` for `HttpError`, /// Return `InternalServerError` for `HttpError`,
/// Response generation can return `HttpError`, so it is internal error /// Response generation can return `HttpError`, so it is internal error
impl ResponseError for HttpError {} impl ResponseError for HttpError {}
@ -114,6 +135,9 @@ impl ResponseError for header::InvalidHeaderValue {}
/// `InternalServerError` for `futures::Canceled` /// `InternalServerError` for `futures::Canceled`
impl ResponseError for Canceled {} impl ResponseError for Canceled {}
/// `InternalServerError` for `actix::MailboxError`
impl ResponseError for MailboxError {}
/// A set of errors that can occur during parsing HTTP streams /// A set of errors that can occur during parsing HTTP streams
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
pub enum ParseError { pub enum ParseError {
@ -202,9 +226,9 @@ pub enum PayloadError {
/// A payload length is unknown. /// A payload length is unknown.
#[fail(display="A payload length is unknown.")] #[fail(display="A payload length is unknown.")]
UnknownLength, UnknownLength,
/// Parse error /// Io error
#[fail(display="{}", _0)] #[fail(display="{}", _0)]
ParseError(#[cause] IoError), Io(#[cause] IoError),
/// Http2 error /// Http2 error
#[fail(display="{}", _0)] #[fail(display="{}", _0)]
Http2(#[cause] Http2Error), Http2(#[cause] Http2Error),
@ -212,7 +236,7 @@ pub enum PayloadError {
impl From<IoError> for PayloadError { impl From<IoError> for PayloadError {
fn from(err: IoError) -> PayloadError { fn from(err: IoError) -> PayloadError {
PayloadError::ParseError(err) PayloadError::Io(err)
} }
} }
@ -268,6 +292,9 @@ pub enum MultipartError {
/// Multipart boundary is not found /// Multipart boundary is not found
#[fail(display="Multipart boundary is not found")] #[fail(display="Multipart boundary is not found")]
Boundary, Boundary,
/// Multipart stream is incomplete
#[fail(display="Multipart stream is incomplete")]
Incomplete,
/// Error during field parsing /// Error during field parsing
#[fail(display="{}", _0)] #[fail(display="{}", _0)]
Parse(#[cause] ParseError), Parse(#[cause] ParseError),
@ -308,57 +335,26 @@ pub enum ExpectError {
} }
impl ResponseError for ExpectError { impl ResponseError for ExpectError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HTTPExpectationFailed.with_body("Unknown Expect") HttpExpectationFailed.with_body("Unknown Expect")
} }
} }
/// Websocket handshake errors /// A set of error that can occure during parsing content type
#[derive(Fail, PartialEq, Debug)] #[derive(Fail, PartialEq, Debug)]
pub enum WsHandshakeError { pub enum ContentTypeError {
/// Only get method is allowed /// Can not parse content type
#[fail(display="Method not allowed")] #[fail(display="Can not parse content type")]
GetMethodRequired, ParseError,
/// Ugrade header if not set to websocket /// Unknown content encoding
#[fail(display="Websocket upgrade is expected")] #[fail(display="Unknown content encoding")]
NoWebsocketUpgrade, UnknownEncoding,
/// Connection header is not set to upgrade
#[fail(display="Connection upgrade is expected")]
NoConnectionUpgrade,
/// Websocket version header is not set
#[fail(display="Websocket version header is required")]
NoVersionHeader,
/// Unsupported websockt version
#[fail(display="Unsupported version")]
UnsupportedVersion,
/// Websocket key is not set or wrong
#[fail(display="Unknown websocket key")]
BadWebsocketKey,
} }
impl ResponseError for WsHandshakeError { /// Return `BadRequest` for `ContentTypeError`
impl ResponseError for ContentTypeError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
WsHandshakeError::GetMethodRequired => {
HTTPMethodNotAllowed
.build()
.header(header::ALLOW, "GET")
.finish()
.unwrap()
}
WsHandshakeError::NoWebsocketUpgrade =>
HTTPBadRequest.with_reason("No WebSocket UPGRADE header found"),
WsHandshakeError::NoConnectionUpgrade =>
HTTPBadRequest.with_reason("No CONNECTION upgrade"),
WsHandshakeError::NoVersionHeader =>
HTTPBadRequest.with_reason("Websocket version header is required"),
WsHandshakeError::UnsupportedVersion =>
HTTPBadRequest.with_reason("Unsupported version"),
WsHandshakeError::BadWebsocketKey =>
HTTPBadRequest.with_reason("Handshake error"),
}
} }
} }
@ -377,16 +373,23 @@ pub enum UrlencodedError {
/// Content type error /// Content type error
#[fail(display="Content type error")] #[fail(display="Content type error")]
ContentType, ContentType,
/// Parse error
#[fail(display="Parse error")]
Parse,
/// Payload error /// Payload error
#[fail(display="Error that occur during reading payload")] #[fail(display="Error that occur during reading payload: {}", _0)]
Payload(PayloadError), Payload(#[cause] PayloadError),
} }
/// Return `BadRequest` for `UrlencodedError` /// Return `BadRequest` for `UrlencodedError`
impl ResponseError for UrlencodedError { impl ResponseError for UrlencodedError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) match *self {
UrlencodedError::Overflow => httpcodes::HttpPayloadTooLarge.into(),
UrlencodedError::UnknownLength => httpcodes::HttpLengthRequired.into(),
_ => httpcodes::HttpBadRequest.into(),
}
} }
} }
@ -406,18 +409,21 @@ pub enum JsonPayloadError {
#[fail(display="Content type error")] #[fail(display="Content type error")]
ContentType, ContentType,
/// Deserialize error /// Deserialize error
#[fail(display="Json deserialize error")] #[fail(display="Json deserialize error: {}", _0)]
Deserialize(JsonError), Deserialize(#[cause] JsonError),
/// Payload error /// Payload error
#[fail(display="Error that occur during reading payload")] #[fail(display="Error that occur during reading payload: {}", _0)]
Payload(PayloadError), Payload(#[cause] PayloadError),
} }
/// Return `BadRequest` for `UrlencodedError` /// Return `BadRequest` for `UrlencodedError`
impl ResponseError for JsonPayloadError { impl ResponseError for JsonPayloadError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) match *self {
JsonPayloadError::Overflow => httpcodes::HttpPayloadTooLarge.into(),
_ => httpcodes::HttpBadRequest.into(),
}
} }
} }
@ -478,39 +484,10 @@ impl From<UrlParseError> for UrlGenerationError {
} }
} }
macro_rules! ERROR_WRAP { /// Helper type that can wrap any error and generate custom response.
($type:ty, $status:expr) => {
unsafe impl<T> Sync for $type {}
unsafe impl<T> Send for $type {}
impl<T> $type {
pub fn cause(&self) -> &T {
&self.0
}
}
impl<T: fmt::Debug + 'static> Fail for $type {}
impl<T: fmt::Debug + 'static> fmt::Display for $type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl<T> ResponseError for $type
where T: Send + Sync + fmt::Debug + 'static,
{
fn error_response(&self) -> HttpResponse {
HttpResponse::new($status, Body::Empty)
}
}
}
}
/// Helper type that can wrap any error and generate *BAD REQUEST* response.
/// ///
/// In following example any `io::Error` will be converted into "BAD REQUEST" response /// In following example any `io::Error` will be converted into "BAD REQUEST" response
/// as oposite to *INNTERNAL SERVER ERROR* which is defined by default. /// as opposite to *INNTERNAL SERVER ERROR* which is defined by default.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
@ -523,67 +500,143 @@ macro_rules! ERROR_WRAP {
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
#[derive(Debug)] pub struct InternalError<T> {
pub struct ErrorBadRequest<T>(pub T); cause: T,
ERROR_WRAP!(ErrorBadRequest<T>, StatusCode::BAD_REQUEST); status: StatusCode,
backtrace: Backtrace,
}
#[derive(Debug)] unsafe impl<T> Sync for InternalError<T> {}
/// Helper type that can wrap any error and generate *UNAUTHORIZED* response. unsafe impl<T> Send for InternalError<T> {}
pub struct ErrorUnauthorized<T>(pub T);
ERROR_WRAP!(ErrorUnauthorized<T>, StatusCode::UNAUTHORIZED);
#[derive(Debug)] impl<T> InternalError<T> {
/// Helper type that can wrap any error and generate *FORBIDDEN* response. pub fn new(cause: T, status: StatusCode) -> Self {
pub struct ErrorForbidden<T>(pub T); InternalError {
ERROR_WRAP!(ErrorForbidden<T>, StatusCode::FORBIDDEN); cause,
status,
backtrace: Backtrace::new(),
}
}
}
#[derive(Debug)] impl<T> Fail for InternalError<T>
/// Helper type that can wrap any error and generate *NOT FOUND* response. where T: Send + Sync + fmt::Debug + 'static
pub struct ErrorNotFound<T>(pub T); {
ERROR_WRAP!(ErrorNotFound<T>, StatusCode::NOT_FOUND); fn backtrace(&self) -> Option<&Backtrace> {
Some(&self.backtrace)
}
}
#[derive(Debug)] impl<T> fmt::Debug for InternalError<T>
/// Helper type that can wrap any error and generate *METHOD NOT ALLOWED* response. where T: Send + Sync + fmt::Debug + 'static
pub struct ErrorMethodNotAllowed<T>(pub T); {
ERROR_WRAP!(ErrorMethodNotAllowed<T>, StatusCode::METHOD_NOT_ALLOWED); fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.cause, f)
}
}
#[derive(Debug)] impl<T> fmt::Display for InternalError<T>
/// Helper type that can wrap any error and generate *REQUEST TIMEOUT* response. where T: Send + Sync + fmt::Debug + 'static
pub struct ErrorRequestTimeout<T>(pub T); {
ERROR_WRAP!(ErrorRequestTimeout<T>, StatusCode::REQUEST_TIMEOUT); fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.cause, f)
}
}
#[derive(Debug)] impl<T> ResponseError for InternalError<T>
/// Helper type that can wrap any error and generate *CONFLICT* response. where T: Send + Sync + fmt::Debug + 'static
pub struct ErrorConflict<T>(pub T); {
ERROR_WRAP!(ErrorConflict<T>, StatusCode::CONFLICT); fn error_response(&self) -> HttpResponse {
HttpResponse::new(self.status, Body::Empty)
}
}
#[derive(Debug)] impl<T> Responder for InternalError<T>
/// Helper type that can wrap any error and generate *GONE* response. where T: Send + Sync + fmt::Debug + 'static
pub struct ErrorGone<T>(pub T); {
ERROR_WRAP!(ErrorGone<T>, StatusCode::GONE); type Item = HttpResponse;
type Error = Error;
#[derive(Debug)] fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
/// Helper type that can wrap any error and generate *PRECONDITION FAILED* response. Err(self.into())
pub struct ErrorPreconditionFailed<T>(pub T); }
ERROR_WRAP!(ErrorPreconditionFailed<T>, StatusCode::PRECONDITION_FAILED); }
#[derive(Debug)] /// Helper function that creates wrapper of any error and generate *BAD REQUEST* response.
/// Helper type that can wrap any error and generate *EXPECTATION FAILED* response. #[allow(non_snake_case)]
pub struct ErrorExpectationFailed<T>(pub T); pub fn ErrorBadRequest<T>(err: T) -> InternalError<T> {
ERROR_WRAP!(ErrorExpectationFailed<T>, StatusCode::EXPECTATION_FAILED); InternalError::new(err, StatusCode::BAD_REQUEST)
}
#[derive(Debug)] /// Helper function that creates wrapper of any error and generate *UNAUTHORIZED* response.
/// Helper type that can wrap any error and generate *INTERNAL SERVER ERROR* response. #[allow(non_snake_case)]
pub struct ErrorInternalServerError<T>(pub T); pub fn ErrorUnauthorized<T>(err: T) -> InternalError<T> {
ERROR_WRAP!(ErrorInternalServerError<T>, StatusCode::INTERNAL_SERVER_ERROR); InternalError::new(err, StatusCode::UNAUTHORIZED)
}
/// Helper function that creates wrapper of any error and generate *FORBIDDEN* response.
#[allow(non_snake_case)]
pub fn ErrorForbidden<T>(err: T) -> InternalError<T> {
InternalError::new(err, StatusCode::FORBIDDEN)
}
/// Helper function that creates wrapper of any error and generate *NOT FOUND* response.
#[allow(non_snake_case)]
pub fn ErrorNotFound<T>(err: T) -> InternalError<T> {
InternalError::new(err, StatusCode::NOT_FOUND)
}
/// Helper function that creates wrapper of any error and generate *METHOD NOT ALLOWED* response.
#[allow(non_snake_case)]
pub fn ErrorMethodNotAllowed<T>(err: T) -> InternalError<T> {
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED)
}
/// Helper function that creates wrapper of any error and generate *REQUEST TIMEOUT* response.
#[allow(non_snake_case)]
pub fn ErrorRequestTimeout<T>(err: T) -> InternalError<T> {
InternalError::new(err, StatusCode::REQUEST_TIMEOUT)
}
/// Helper function that creates wrapper of any error and generate *CONFLICT* response.
#[allow(non_snake_case)]
pub fn ErrorConflict<T>(err: T) -> InternalError<T> {
InternalError::new(err, StatusCode::CONFLICT)
}
/// Helper function that creates wrapper of any error and generate *GONE* response.
#[allow(non_snake_case)]
pub fn ErrorGone<T>(err: T) -> InternalError<T> {
InternalError::new(err, StatusCode::GONE)
}
/// Helper function that creates wrapper of any error and generate *PRECONDITION FAILED* response.
#[allow(non_snake_case)]
pub fn ErrorPreconditionFailed<T>(err: T) -> InternalError<T> {
InternalError::new(err, StatusCode::PRECONDITION_FAILED)
}
/// Helper function that creates wrapper of any error and generate *EXPECTATION FAILED* response.
#[allow(non_snake_case)]
pub fn ErrorExpectationFailed<T>(err: T) -> InternalError<T> {
InternalError::new(err, StatusCode::EXPECTATION_FAILED)
}
/// Helper function that creates wrapper of any error and generate *INTERNAL SERVER ERROR* response.
#[allow(non_snake_case)]
pub fn ErrorInternalServerError<T>(err: T) -> InternalError<T> {
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::env;
use std::error::Error as StdError; use std::error::Error as StdError;
use std::io; use std::io;
use httparse; use httparse;
use http::{StatusCode, Error as HttpError}; use http::{StatusCode, Error as HttpError};
use cookie::ParseError as CookieParseError; use cookie::ParseError as CookieParseError;
use failure;
use super::*; use super::*;
#[test] #[test]
@ -660,22 +713,6 @@ mod tests {
assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED); assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED);
} }
#[test]
fn test_wserror_http_response() {
let resp: HttpResponse = WsHandshakeError::GetMethodRequired.error_response();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let resp: HttpResponse = WsHandshakeError::NoWebsocketUpgrade.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::NoConnectionUpgrade.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::NoVersionHeader.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::UnsupportedVersion.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = WsHandshakeError::BadWebsocketKey.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
macro_rules! from { macro_rules! from {
($from:expr => $error:pat) => { ($from:expr => $error:pat) => {
match ParseError::from($from) { match ParseError::from($from) {
@ -712,4 +749,18 @@ mod tests {
from!(httparse::Error::TooManyHeaders => ParseError::TooLarge); from!(httparse::Error::TooManyHeaders => ParseError::TooLarge);
from!(httparse::Error::Version => ParseError::Version); from!(httparse::Error::Version => ParseError::Version);
} }
#[test]
fn failure_error() {
const NAME: &str = "RUST_BACKTRACE";
let old_tb = env::var(NAME);
env::set_var(NAME, "0");
let error = failure::err_msg("Hello!");
let resp: Error = error.into();
assert_eq!(format!("{:?}", resp), "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n");
match old_tb {
Ok(x) => env::set_var(NAME, x),
_ => env::remove_var(NAME),
}
}
} }

View File

@ -15,7 +15,7 @@ use handler::{Handler, Responder};
use headers::ContentEncoding; use headers::ContentEncoding;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use httpcodes::HTTPOk; use httpcodes::{HttpOk, HttpFound};
/// A file with an associated name; responds with the Content-Type based on the /// A file with an associated name; responds with the Content-Type based on the
/// file extension. /// file extension.
@ -84,7 +84,7 @@ impl Responder for NamedFile {
type Error = io::Error; type Error = io::Error;
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> { fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> {
let mut resp = HTTPOk.build(); let mut resp = HttpOk.build();
resp.content_encoding(ContentEncoding::Identity); resp.content_encoding(ContentEncoding::Identity);
if let Some(ext) = self.path().extension() { if let Some(ext) = self.path().extension() {
let mime = get_mime_type(&ext.to_string_lossy()); let mime = get_mime_type(&ext.to_string_lossy());
@ -105,10 +105,7 @@ pub struct Directory{
impl Directory { impl Directory {
pub fn new(base: PathBuf, path: PathBuf) -> Directory { pub fn new(base: PathBuf, path: PathBuf) -> Directory {
Directory { Directory { base, path }
base: base,
path: path
}
} }
fn can_list(&self, entry: &io::Result<DirEntry>) -> bool { fn can_list(&self, entry: &io::Result<DirEntry>) -> bool {
@ -167,7 +164,7 @@ impl Responder for Directory {
<ul>\ <ul>\
{}\ {}\
</ul></body>\n</html>", index_of, index_of, body); </ul></body>\n</html>", index_of, index_of, body);
Ok(HTTPOk.build() Ok(HttpOk.build()
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(html).unwrap()) .body(html).unwrap())
} }
@ -177,6 +174,7 @@ impl Responder for Directory {
pub enum FilesystemElement { pub enum FilesystemElement {
File(NamedFile), File(NamedFile),
Directory(Directory), Directory(Directory),
Redirect(HttpResponse),
} }
impl Responder for FilesystemElement { impl Responder for FilesystemElement {
@ -187,6 +185,7 @@ impl Responder for FilesystemElement {
match self { match self {
FilesystemElement::File(file) => file.respond_to(req), FilesystemElement::File(file) => file.respond_to(req),
FilesystemElement::Directory(dir) => dir.respond_to(req), FilesystemElement::Directory(dir) => dir.respond_to(req),
FilesystemElement::Redirect(resp) => Ok(resp),
} }
} }
} }
@ -210,6 +209,7 @@ impl Responder for FilesystemElement {
pub struct StaticFiles { pub struct StaticFiles {
directory: PathBuf, directory: PathBuf,
accessible: bool, accessible: bool,
index: Option<String>,
show_index: bool, show_index: bool,
_chunk_size: usize, _chunk_size: usize,
_follow_symlinks: bool, _follow_symlinks: bool,
@ -221,7 +221,7 @@ impl StaticFiles {
/// `dir` - base directory /// `dir` - base directory
/// ///
/// `index` - show index for directory /// `index` - show index for directory
pub fn new<D: Into<PathBuf>>(dir: D, index: bool) -> StaticFiles { pub fn new<T: Into<PathBuf>>(dir: T, index: bool) -> StaticFiles {
let dir = dir.into(); let dir = dir.into();
let (dir, access) = match dir.canonicalize() { let (dir, access) = match dir.canonicalize() {
@ -242,12 +242,21 @@ impl StaticFiles {
StaticFiles { StaticFiles {
directory: dir, directory: dir,
accessible: access, accessible: access,
index: None,
show_index: index, show_index: index,
_chunk_size: 0, _chunk_size: 0,
_follow_symlinks: false, _follow_symlinks: false,
} }
} }
/// Set index file
///
/// Redirects to specific index file for directory "/" instead of
/// showing files listing.
pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles {
self.index = Some(index.into());
self
}
} }
impl<S> Handler<S> for StaticFiles { impl<S> Handler<S> for StaticFiles {
@ -270,7 +279,21 @@ impl<S> Handler<S> for StaticFiles {
let path = self.directory.join(&relpath).canonicalize()?; let path = self.directory.join(&relpath).canonicalize()?;
if path.is_dir() { if path.is_dir() {
if self.show_index { if let Some(ref redir_index) = self.index {
// TODO: Don't redirect, just return the index content.
// TODO: It'd be nice if there were a good usable URL manipulation library
let mut new_path: String = req.path().to_owned();
for el in relpath.iter() {
new_path.push_str(&el.to_string_lossy());
new_path.push('/');
}
new_path.push_str(redir_index);
Ok(FilesystemElement::Redirect(
HttpFound
.build()
.header::<_, &str>("LOCATION", &new_path)
.finish().unwrap()))
} else if self.show_index {
Ok(FilesystemElement::Directory(Directory::new(self.directory.clone(), path))) Ok(FilesystemElement::Directory(Directory::new(self.directory.clone(), path)))
} else { } else {
Err(io::Error::new(io::ErrorKind::NotFound, "not found")) Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
@ -285,7 +308,7 @@ impl<S> Handler<S> for StaticFiles {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use http::header; use http::{header, StatusCode};
#[test] #[test]
fn test_named_file() { fn test_named_file() {
@ -318,4 +341,33 @@ mod tests {
assert!(resp.body().is_binary()); assert!(resp.body().is_binary());
assert!(format!("{:?}", resp.body()).contains("README.md")); assert!(format!("{:?}", resp.body()).contains("README.md"));
} }
#[test]
fn test_redirect_to_index() {
let mut st = StaticFiles::new(".", false).index_file("index.html");
let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "guide");
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");
let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "guide/");
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");
}
#[test]
fn test_redirect_to_index_nested() {
let mut st = StaticFiles::new(".", false).index_file("Cargo.toml");
let mut req = HttpRequest::default();
req.match_info_mut().add("tail", "examples/basics");
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/examples/basics/Cargo.toml");
}
} }

View File

@ -9,7 +9,7 @@ use error::Error;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
/// Trait defines object that could be regestered as route handler /// Trait defines object that could be registered as route handler
#[allow(unused_variables)] #[allow(unused_variables)]
pub trait Handler<S>: 'static { pub trait Handler<S>: 'static {
@ -35,7 +35,7 @@ pub trait Responder {
} }
#[doc(hidden)] #[doc(hidden)]
/// Convinience trait that convert `Future` object into `Boxed` future /// Convenience trait that convert `Future` object into `Boxed` future
pub trait AsyncResponder<I, E>: Sized { pub trait AsyncResponder<I, E>: Sized {
fn responder(self) -> Box<Future<Item=I, Error=E>>; fn responder(self) -> Box<Future<Item=I, Error=E>>;
} }
@ -193,7 +193,7 @@ impl<I, E> Responder for Box<Future<Item=I, Error=E>>
} }
} }
/// Trait defines object that could be regestered as resource route /// Trait defines object that could be registered as resource route
pub(crate) trait RouteHandler<S>: 'static { pub(crate) trait RouteHandler<S>: 'static {
fn handle(&mut self, req: HttpRequest<S>) -> Reply; fn handle(&mut self, req: HttpRequest<S>) -> Reply;
} }
@ -215,7 +215,7 @@ impl<S, H, R> WrapHandler<S, H, R>
S: 'static, S: 'static,
{ {
pub fn new(h: H) -> Self { pub fn new(h: H) -> Self {
WrapHandler{h: h, s: PhantomData} WrapHandler{h, s: PhantomData}
} }
} }
@ -225,7 +225,7 @@ impl<S, H, R> RouteHandler<S> for WrapHandler<S, H, R>
S: 'static, S: 'static,
{ {
fn handle(&mut self, req: HttpRequest<S>) -> Reply { fn handle(&mut self, req: HttpRequest<S>) -> Reply {
let req2 = req.clone_without_state(); let req2 = req.without_state();
match self.h.handle(req).respond_to(req2) { match self.h.handle(req).respond_to(req2) {
Ok(reply) => reply.into(), Ok(reply) => reply.into(),
Err(err) => Reply::response(err.into()), Err(err) => Reply::response(err.into()),
@ -266,7 +266,7 @@ impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
S: 'static, S: 'static,
{ {
fn handle(&mut self, req: HttpRequest<S>) -> Reply { fn handle(&mut self, req: HttpRequest<S>) -> Reply {
let req2 = req.clone_without_state(); let req2 = req.without_state();
let fut = (self.h)(req) let fut = (self.h)(req)
.map_err(|e| e.into()) .map_err(|e| e.into())
.then(move |r| { .then(move |r| {
@ -287,6 +287,7 @@ impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
/// By normalizing it means: /// By normalizing it means:
/// ///
/// - Add a trailing slash to the path. /// - Add a trailing slash to the path.
/// - Remove a trailing slash from the path.
/// - Double slashes are replaced by one. /// - Double slashes are replaced by one.
/// ///
/// The handler returns as soon as it finds a path that resolves /// The handler returns as soon as it finds a path that resolves
@ -308,7 +309,7 @@ impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
/// # use actix_web::*; /// # use actix_web::*;
/// # /// #
/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse { /// # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
/// # httpcodes::HTTPOk /// # httpcodes::HttpOk
/// # } /// # }
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = Application::new()
@ -341,13 +342,13 @@ impl Default for NormalizePath {
} }
impl NormalizePath { impl NormalizePath {
/// Create new `NoramlizePath` instance /// Create new `NormalizePath` instance
pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath {
NormalizePath { NormalizePath {
append: append, append,
merge: merge, merge,
redirect,
re_merge: Regex::new("//+").unwrap(), re_merge: Regex::new("//+").unwrap(),
redirect: redirect,
not_found: StatusCode::NOT_FOUND, not_found: StatusCode::NOT_FOUND,
} }
} }
@ -379,6 +380,32 @@ impl<S> Handler<S> for NormalizePath {
.body(Body::Empty); .body(Body::Empty);
} }
} }
// try to remove trailing slash
if p.ends_with('/') {
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str())
} else {
req.header(header::LOCATION, p)
}
.body(Body::Empty);
}
}
} else if p.ends_with('/') {
// try to remove trailing slash
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str())
} else {
req.header(header::LOCATION, p)
}
.body(Body::Empty);
}
} }
} }
// append trailing slash // append trailing slash
@ -416,16 +443,17 @@ mod tests {
.finish(); .finish();
// trailing slashes // trailing slashes
let params = vec![("/resource1", "", StatusCode::OK), let params =
("/resource1/", "", StatusCode::NOT_FOUND), vec![("/resource1", "", StatusCode::OK),
("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource2/", "", StatusCode::OK), ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
("/resource1?p1=1&p2=2", "", StatusCode::OK), ("/resource2/", "", StatusCode::OK),
("/resource1/?p1=1&p2=2", "", StatusCode::NOT_FOUND), ("/resource1?p1=1&p2=2", "", StatusCode::OK),
("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2", ("/resource1/?p1=1&p2=2", "/resource1?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
StatusCode::MOVED_PERMANENTLY), ("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2",
("/resource2/?p1=1&p2=2", "", StatusCode::OK) StatusCode::MOVED_PERMANENTLY),
]; ("/resource2/?p1=1&p2=2", "", StatusCode::OK)
];
for (path, target, code) in params { for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish()); let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req); let resp = app.run(req);
@ -450,11 +478,11 @@ mod tests {
// trailing slashes // trailing slashes
let params = vec![("/resource1", StatusCode::OK), let params = vec![("/resource1", StatusCode::OK),
("/resource1/", StatusCode::NOT_FOUND), ("/resource1/", StatusCode::MOVED_PERMANENTLY),
("/resource2", StatusCode::NOT_FOUND), ("/resource2", StatusCode::NOT_FOUND),
("/resource2/", StatusCode::OK), ("/resource2/", StatusCode::OK),
("/resource1?p1=1&p2=2", StatusCode::OK), ("/resource1?p1=1&p2=2", StatusCode::OK),
("/resource1/?p1=1&p2=2", StatusCode::NOT_FOUND), ("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND), ("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND),
("/resource2/?p1=1&p2=2", StatusCode::OK) ("/resource2/?p1=1&p2=2", StatusCode::OK)
]; ];
@ -477,17 +505,21 @@ mod tests {
// trailing slashes // trailing slashes
let params = vec![ let params = vec![
("/resource1/a/b", "", StatusCode::OK), ("/resource1/a/b", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), ("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/", "", StatusCode::NOT_FOUND), ("//resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b//", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/", "", StatusCode::NOT_FOUND), ("/////resource1/a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK), ("/resource1/a/b?p=1", "", StatusCode::OK),
("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), ("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/?p=1", "", StatusCode::NOT_FOUND), ("//resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), ("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), ("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/?p=1", "", StatusCode::NOT_FOUND), ("/////resource1/a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
]; ];
for (path, target, code) in params { for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish()); let req = app.prepare_request(TestRequest::with_uri(path).finish());
@ -515,13 +547,14 @@ mod tests {
// trailing slashes // trailing slashes
let params = vec![ let params = vec![
("/resource1/a/b", "", StatusCode::OK), ("/resource1/a/b", "", StatusCode::OK),
("/resource1/a/b/", "", StatusCode::NOT_FOUND), ("/resource1/a/b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), ("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), ("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b//", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/", "", StatusCode::NOT_FOUND), ("///resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/", "", StatusCode::NOT_FOUND), ("/////resource1/a///b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), ("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b/", "", StatusCode::OK), ("/resource2/a/b/", "", StatusCode::OK),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), ("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
@ -531,13 +564,14 @@ mod tests {
("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), ("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), ("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK), ("/resource1/a/b?p=1", "", StatusCode::OK),
("/resource1/a/b/?p=1", "", StatusCode::NOT_FOUND), ("/resource1/a/b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), ("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), ("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), ("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/?p=1", "", StatusCode::NOT_FOUND), ("///resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), ("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/?p=1", "", StatusCode::NOT_FOUND), ("/////resource1/a///b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), ("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), ("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), ("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),

View File

@ -8,7 +8,7 @@ use time;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use http::Version; use http::Version;
use httprequest::HttpMessage; use httprequest::HttpInnerMessage;
// "Sun, 06 Nov 1994 08:49:37 GMT".len() // "Sun, 06 Nov 1994 08:49:37 GMT".len()
pub(crate) const DATE_VALUE_LENGTH: usize = 29; pub(crate) const DATE_VALUE_LENGTH: usize = 29;
@ -67,100 +67,24 @@ impl fmt::Write for CachedDate {
} }
/// Internal use only! unsafe /// Internal use only! unsafe
#[derive(Debug)] pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpInnerMessage>>>);
pub(crate) struct SharedBytesPool(RefCell<VecDeque<Rc<BytesMut>>>);
impl SharedBytesPool {
pub fn new() -> SharedBytesPool {
SharedBytesPool(RefCell::new(VecDeque::with_capacity(128)))
}
pub fn get_bytes(&self) -> Rc<BytesMut> {
if let Some(bytes) = self.0.borrow_mut().pop_front() {
bytes
} else {
Rc::new(BytesMut::new())
}
}
pub fn release_bytes(&self, mut bytes: Rc<BytesMut>) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
Rc::get_mut(&mut bytes).unwrap().take();
v.push_front(bytes);
}
}
}
#[derive(Debug)]
pub(crate) struct SharedBytes(
Option<Rc<BytesMut>>, Option<Rc<SharedBytesPool>>);
impl Drop for SharedBytes {
fn drop(&mut self) {
if let Some(ref pool) = self.1 {
if let Some(bytes) = self.0.take() {
if Rc::strong_count(&bytes) == 1 {
pool.release_bytes(bytes);
}
}
}
}
}
impl SharedBytes {
pub fn empty() -> Self {
SharedBytes(None, None)
}
pub fn new(bytes: Rc<BytesMut>, pool: Rc<SharedBytesPool>) -> SharedBytes {
SharedBytes(Some(bytes), Some(pool))
}
#[inline(always)]
#[allow(mutable_transmutes)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub fn get_mut(&self) -> &mut BytesMut {
let r: &BytesMut = self.0.as_ref().unwrap().as_ref();
unsafe{mem::transmute(r)}
}
#[inline]
pub fn get_ref(&self) -> &BytesMut {
self.0.as_ref().unwrap()
}
}
impl Default for SharedBytes {
fn default() -> Self {
SharedBytes(Some(Rc::new(BytesMut::new())), None)
}
}
impl Clone for SharedBytes {
fn clone(&self) -> SharedBytes {
SharedBytes(self.0.clone(), self.1.clone())
}
}
/// Internal use only! unsafe
pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpMessage>>>);
impl SharedMessagePool { impl SharedMessagePool {
pub fn new() -> SharedMessagePool { pub fn new() -> SharedMessagePool {
SharedMessagePool(RefCell::new(VecDeque::with_capacity(128))) SharedMessagePool(RefCell::new(VecDeque::with_capacity(128)))
} }
pub fn get(&self) -> Rc<HttpMessage> { #[inline]
pub fn get(&self) -> Rc<HttpInnerMessage> {
if let Some(msg) = self.0.borrow_mut().pop_front() { if let Some(msg) = self.0.borrow_mut().pop_front() {
msg msg
} else { } else {
Rc::new(HttpMessage::default()) Rc::new(HttpInnerMessage::default())
} }
} }
pub fn release(&self, mut msg: Rc<HttpMessage>) { #[inline]
pub fn release(&self, mut msg: Rc<HttpInnerMessage>) {
let v = &mut self.0.borrow_mut(); let v = &mut self.0.borrow_mut();
if v.len() < 128 { if v.len() < 128 {
Rc::get_mut(&mut msg).unwrap().reset(); Rc::get_mut(&mut msg).unwrap().reset();
@ -169,10 +93,10 @@ impl SharedMessagePool {
} }
} }
pub(crate) struct SharedHttpMessage( pub(crate) struct SharedHttpInnerMessage(
Option<Rc<HttpMessage>>, Option<Rc<SharedMessagePool>>); Option<Rc<HttpInnerMessage>>, Option<Rc<SharedMessagePool>>);
impl Drop for SharedHttpMessage { impl Drop for SharedHttpInnerMessage {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(ref pool) = self.1 { if let Some(ref pool) = self.1 {
if let Some(msg) = self.0.take() { if let Some(msg) = self.0.take() {
@ -184,56 +108,56 @@ impl Drop for SharedHttpMessage {
} }
} }
impl Deref for SharedHttpMessage { impl Deref for SharedHttpInnerMessage {
type Target = HttpMessage; type Target = HttpInnerMessage;
fn deref(&self) -> &HttpMessage { fn deref(&self) -> &HttpInnerMessage {
self.get_ref() self.get_ref()
} }
} }
impl DerefMut for SharedHttpMessage { impl DerefMut for SharedHttpInnerMessage {
fn deref_mut(&mut self) -> &mut HttpMessage { fn deref_mut(&mut self) -> &mut HttpInnerMessage {
self.get_mut() self.get_mut()
} }
} }
impl Clone for SharedHttpMessage { impl Clone for SharedHttpInnerMessage {
fn clone(&self) -> SharedHttpMessage { fn clone(&self) -> SharedHttpInnerMessage {
SharedHttpMessage(self.0.clone(), self.1.clone()) SharedHttpInnerMessage(self.0.clone(), self.1.clone())
} }
} }
impl Default for SharedHttpMessage { impl Default for SharedHttpInnerMessage {
fn default() -> SharedHttpMessage { fn default() -> SharedHttpInnerMessage {
SharedHttpMessage(Some(Rc::new(HttpMessage::default())), None) SharedHttpInnerMessage(Some(Rc::new(HttpInnerMessage::default())), None)
} }
} }
impl SharedHttpMessage { impl SharedHttpInnerMessage {
pub fn from_message(msg: HttpMessage) -> SharedHttpMessage { pub fn from_message(msg: HttpInnerMessage) -> SharedHttpInnerMessage {
SharedHttpMessage(Some(Rc::new(msg)), None) SharedHttpInnerMessage(Some(Rc::new(msg)), None)
} }
pub fn new(msg: Rc<HttpMessage>, pool: Rc<SharedMessagePool>) -> SharedHttpMessage { pub fn new(msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>) -> SharedHttpInnerMessage {
SharedHttpMessage(Some(msg), Some(pool)) SharedHttpInnerMessage(Some(msg), Some(pool))
} }
#[inline(always)] #[inline(always)]
#[allow(mutable_transmutes)] #[allow(mutable_transmutes)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub fn get_mut(&self) -> &mut HttpMessage { pub fn get_mut(&self) -> &mut HttpInnerMessage {
let r: &HttpMessage = self.0.as_ref().unwrap().as_ref(); let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref();
unsafe{mem::transmute(r)} unsafe{mem::transmute(r)}
} }
#[inline(always)] #[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
pub fn get_ref(&self) -> &HttpMessage { pub fn get_ref(&self) -> &HttpInnerMessage {
self.0.as_ref().unwrap() self.0.as_ref().unwrap()
} }
} }

View File

@ -7,45 +7,175 @@ use handler::{Reply, Handler, RouteHandler, Responder};
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::{HttpResponse, HttpResponseBuilder}; use httpresponse::{HttpResponse, HttpResponseBuilder};
pub const HTTPOk: StaticResponse = StaticResponse(StatusCode::OK); pub const HttpOk: StaticResponse = StaticResponse(StatusCode::OK);
pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED); pub const HttpCreated: StaticResponse = StaticResponse(StatusCode::CREATED);
pub const HTTPNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT); pub const HttpAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED);
pub const HttpNonAuthoritativeInformation: StaticResponse =
StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION);
pub const HttpNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT);
pub const HttpResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT);
pub const HttpPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT);
pub const HttpMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS);
pub const HttpAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED);
pub const HttpMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES);
pub const HttpMovedPermanenty: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY);
pub const HttpFound: StaticResponse = StaticResponse(StatusCode::FOUND);
pub const HttpSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER);
pub const HttpNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED);
pub const HttpUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY);
pub const HttpTemporaryRedirect: StaticResponse =
StaticResponse(StatusCode::TEMPORARY_REDIRECT);
pub const HttpPermanentRedirect: StaticResponse =
StaticResponse(StatusCode::PERMANENT_REDIRECT);
pub const HttpBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST);
pub const HttpUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED);
pub const HttpPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED);
pub const HttpForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN);
pub const HttpNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND);
pub const HttpMethodNotAllowed: StaticResponse =
StaticResponse(StatusCode::METHOD_NOT_ALLOWED);
pub const HttpNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE);
pub const HttpProxyAuthenticationRequired: StaticResponse =
StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED);
pub const HttpRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT);
pub const HttpConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT);
pub const HttpGone: StaticResponse = StaticResponse(StatusCode::GONE);
pub const HttpLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED);
pub const HttpPreconditionFailed: StaticResponse =
StaticResponse(StatusCode::PRECONDITION_FAILED);
pub const HttpPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE);
pub const HttpUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG);
pub const HttpUnsupportedMediaType: StaticResponse =
StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE);
pub const HttpRangeNotSatisfiable: StaticResponse =
StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE);
pub const HttpExpectationFailed: StaticResponse =
StaticResponse(StatusCode::EXPECTATION_FAILED);
pub const HttpInternalServerError: StaticResponse =
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
pub const HttpNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED);
pub const HttpBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY);
pub const HttpServiceUnavailable: StaticResponse =
StaticResponse(StatusCode::SERVICE_UNAVAILABLE);
pub const HttpGatewayTimeout: StaticResponse =
StaticResponse(StatusCode::GATEWAY_TIMEOUT);
pub const HttpVersionNotSupported: StaticResponse =
StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED);
pub const HttpVariantAlsoNegotiates: StaticResponse =
StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES);
pub const HttpInsufficientStorage: StaticResponse =
StaticResponse(StatusCode::INSUFFICIENT_STORAGE);
pub const HttpLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED);
#[doc(hidden)]
pub const HTTPOk: StaticResponse = StaticResponse(StatusCode::OK);
#[doc(hidden)]
pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED);
#[doc(hidden)]
pub const HTTPAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED);
#[doc(hidden)]
pub const HTTPNonAuthoritativeInformation: StaticResponse =
StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION);
#[doc(hidden)]
pub const HTTPNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT);
#[doc(hidden)]
pub const HTTPResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT);
#[doc(hidden)]
pub const HTTPPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT);
#[doc(hidden)]
pub const HTTPMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS);
#[doc(hidden)]
pub const HTTPAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED);
#[doc(hidden)]
pub const HTTPMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES); pub const HTTPMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES);
#[doc(hidden)]
pub const HTTPMovedPermanenty: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY); pub const HTTPMovedPermanenty: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY);
#[doc(hidden)]
pub const HTTPFound: StaticResponse = StaticResponse(StatusCode::FOUND); pub const HTTPFound: StaticResponse = StaticResponse(StatusCode::FOUND);
#[doc(hidden)]
pub const HTTPSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER); pub const HTTPSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER);
#[doc(hidden)]
pub const HTTPNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED); pub const HTTPNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED);
#[doc(hidden)]
pub const HTTPUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY); pub const HTTPUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY);
#[doc(hidden)]
pub const HTTPTemporaryRedirect: StaticResponse = pub const HTTPTemporaryRedirect: StaticResponse =
StaticResponse(StatusCode::TEMPORARY_REDIRECT); StaticResponse(StatusCode::TEMPORARY_REDIRECT);
#[doc(hidden)]
pub const HTTPPermanentRedirect: StaticResponse = pub const HTTPPermanentRedirect: StaticResponse =
StaticResponse(StatusCode::PERMANENT_REDIRECT); StaticResponse(StatusCode::PERMANENT_REDIRECT);
#[doc(hidden)]
pub const HTTPBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST); pub const HTTPBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST);
pub const HTTPNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND); #[doc(hidden)]
pub const HTTPUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED); pub const HTTPUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED);
#[doc(hidden)]
pub const HTTPPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED); pub const HTTPPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED);
#[doc(hidden)]
pub const HTTPForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN); pub const HTTPForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN);
#[doc(hidden)]
pub const HTTPNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND);
#[doc(hidden)]
pub const HTTPMethodNotAllowed: StaticResponse = pub const HTTPMethodNotAllowed: StaticResponse =
StaticResponse(StatusCode::METHOD_NOT_ALLOWED); StaticResponse(StatusCode::METHOD_NOT_ALLOWED);
#[doc(hidden)]
pub const HTTPNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE); pub const HTTPNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE);
#[doc(hidden)]
pub const HTTPProxyAuthenticationRequired: StaticResponse = pub const HTTPProxyAuthenticationRequired: StaticResponse =
StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED); StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED);
#[doc(hidden)]
pub const HTTPRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT); pub const HTTPRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT);
#[doc(hidden)]
pub const HTTPConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT); pub const HTTPConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT);
#[doc(hidden)]
pub const HTTPGone: StaticResponse = StaticResponse(StatusCode::GONE); pub const HTTPGone: StaticResponse = StaticResponse(StatusCode::GONE);
#[doc(hidden)]
pub const HTTPLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED); pub const HTTPLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED);
#[doc(hidden)]
pub const HTTPPreconditionFailed: StaticResponse = pub const HTTPPreconditionFailed: StaticResponse =
StaticResponse(StatusCode::PRECONDITION_FAILED); StaticResponse(StatusCode::PRECONDITION_FAILED);
#[doc(hidden)]
pub const HTTPPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE); pub const HTTPPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE);
#[doc(hidden)]
pub const HTTPUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG); pub const HTTPUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG);
#[doc(hidden)]
pub const HTTPUnsupportedMediaType: StaticResponse =
StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE);
#[doc(hidden)]
pub const HTTPRangeNotSatisfiable: StaticResponse =
StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE);
#[doc(hidden)]
pub const HTTPExpectationFailed: StaticResponse = pub const HTTPExpectationFailed: StaticResponse =
StaticResponse(StatusCode::EXPECTATION_FAILED); StaticResponse(StatusCode::EXPECTATION_FAILED);
#[doc(hidden)]
pub const HTTPInternalServerError: StaticResponse = pub const HTTPInternalServerError: StaticResponse =
StaticResponse(StatusCode::INTERNAL_SERVER_ERROR); StaticResponse(StatusCode::INTERNAL_SERVER_ERROR);
#[doc(hidden)]
pub const HTTPNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED);
#[doc(hidden)]
pub const HTTPBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY);
#[doc(hidden)]
pub const HTTPServiceUnavailable: StaticResponse =
StaticResponse(StatusCode::SERVICE_UNAVAILABLE);
#[doc(hidden)]
pub const HTTPGatewayTimeout: StaticResponse =
StaticResponse(StatusCode::GATEWAY_TIMEOUT);
#[doc(hidden)]
pub const HTTPVersionNotSupported: StaticResponse =
StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED);
#[doc(hidden)]
pub const HTTPVariantAlsoNegotiates: StaticResponse =
StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES);
#[doc(hidden)]
pub const HTTPInsufficientStorage: StaticResponse =
StaticResponse(StatusCode::INSUFFICIENT_STORAGE);
#[doc(hidden)]
pub const HTTPLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED);
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]

596
src/httpmessage.rs Normal file
View File

@ -0,0 +1,596 @@
use std::str;
use std::collections::HashMap;
use bytes::{Bytes, BytesMut};
use futures::{Future, Stream, Poll};
use http_range::HttpRange;
use serde::de::DeserializeOwned;
use mime::Mime;
use url::form_urlencoded;
use encoding::all::UTF_8;
use encoding::EncodingRef;
use encoding::label::encoding_from_whatwg_label;
use http::{header, HeaderMap};
use json::JsonBody;
use multipart::Multipart;
use error::{ParseError, ContentTypeError,
HttpRangeError, PayloadError, UrlencodedError};
/// Trait that implements general purpose operations on http messages
pub trait HttpMessage {
/// Read the message headers.
fn headers(&self) -> &HeaderMap;
/// Read the request content type. If request does not contain
/// *Content-Type* header, empty str get returned.
fn content_type(&self) -> &str {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
return content_type.split(';').next().unwrap().trim()
}
}
""
}
/// Get content type encoding
///
/// UTF-8 is used by default, If request charset is not set.
fn encoding(&self) -> Result<EncodingRef, ContentTypeError> {
if let Some(mime_type) = self.mime_type()? {
if let Some(charset) = mime_type.get_param("charset") {
if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) {
Ok(enc)
} else {
Err(ContentTypeError::UnknownEncoding)
}
} else {
Ok(UTF_8)
}
} else {
Ok(UTF_8)
}
}
/// Convert the request content type to a known mime type.
fn mime_type(&self) -> Result<Option<Mime>, ContentTypeError> {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
return match content_type.parse() {
Ok(mt) => Ok(Some(mt)),
Err(_) => Err(ContentTypeError::ParseError),
};
} else {
return Err(ContentTypeError::ParseError)
}
}
Ok(None)
}
/// Check if request has chunked transfer encoding
fn chunked(&self) -> Result<bool, ParseError> {
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() {
Ok(s.to_lowercase().contains("chunked"))
} else {
Err(ParseError::Header)
}
} else {
Ok(false)
}
}
/// Parses Range HTTP header string as per RFC 2616.
/// `size` is full size of response (file).
fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> {
if let Some(range) = self.headers().get(header::RANGE) {
HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size)
.map_err(|e| e.into())
} else {
Ok(Vec::new())
}
}
/// Load http message body.
///
/// By default only 256Kb payload reads to a memory, then `PayloadError::Overflow`
/// get returned. Use `MessageBody::limit()` method to change upper limit.
///
/// ## Server example
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use bytes::Bytes;
/// use futures::future::Future;
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.body() // <- get Body future
/// .limit(1024) // <- change max size of the body to a 1kb
/// .from_err()
/// .and_then(|bytes: Bytes| { // <- complete body
/// println!("==== BODY ==== {:?}", bytes);
/// Ok(httpcodes::HttpOk.into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
fn body(self) -> MessageBody<Self>
where Self: Stream<Item=Bytes, Error=PayloadError> + Sized
{
MessageBody::new(self)
}
/// Parse `application/x-www-form-urlencoded` encoded body.
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>` which
/// contains decoded parameters.
///
/// Returns error:
///
/// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k
///
/// ## Server example
///
/// ```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() {}
/// ```
fn urlencoded(self) -> UrlEncoded<Self>
where Self: Stream<Item=Bytes, Error=PayloadError> + Sized
{
UrlEncoded::new(self)
}
/// Parse `application/json` encoded body.
/// Return `JsonBody<T>` future. It resolves to a `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 256k
///
/// ## Server example
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use futures::future::{Future, ok};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
/// println!("==== BODY ==== {:?}", val);
/// Ok(httpcodes::HttpOk.into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
fn json<T: DeserializeOwned>(self) -> JsonBody<Self, T>
where Self: Stream<Item=Bytes, Error=PayloadError> + Sized
{
JsonBody::new(self)
}
/// Return stream to http payload processes as multipart.
///
/// Content-type: multipart/form-data;
///
/// ## Server example
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate env_logger;
/// # extern crate futures;
/// # use std::str;
/// # use actix::*;
/// # use actix_web::*;
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.multipart().from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items
/// multipart::MultipartItem::Field(field) => {
/// // Field in turn is stream of *Bytes* object
/// Either::A(field.from_err()
/// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c)))
/// .finish())
/// },
/// multipart::MultipartItem::Nested(mp) => {
/// // Or item could be nested Multipart stream
/// Either::B(ok(()))
/// }
/// })
/// .finish() // <- Stream::finish() combinator from actix
/// .map(|_| httpcodes::HTTPOk.into())
/// .responder()
/// }
/// # fn main() {}
/// ```
fn multipart(self) -> Multipart<Self>
where Self: Stream<Item=Bytes, Error=PayloadError> + Sized
{
let boundary = Multipart::boundary(self.headers());
Multipart::new(boundary, self)
}
}
/// Future that resolves to a complete http message body.
pub struct MessageBody<T> {
limit: usize,
req: Option<T>,
fut: Option<Box<Future<Item=Bytes, Error=PayloadError>>>,
}
impl<T> MessageBody<T> {
/// Create `RequestBody` for request.
pub fn new(req: T) -> MessageBody<T> {
MessageBody {
limit: 262_144,
req: Some(req),
fut: None,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl<T> Future for MessageBody<T>
where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static
{
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(req) = self.req.take() {
if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<usize>() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
} else {
return Err(PayloadError::UnknownLength);
}
} else {
return Err(PayloadError::UnknownLength);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
req.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.map(|body| body.freeze())
));
}
self.fut.as_mut().expect("UrlEncoded could not be used second time").poll()
}
}
/// Future that resolves to a parsed urlencoded values.
pub struct UrlEncoded<T> {
req: Option<T>,
limit: usize,
fut: Option<Box<Future<Item=HashMap<String, String>, Error=UrlencodedError>>>,
}
impl<T> UrlEncoded<T> {
pub fn new(req: T) -> UrlEncoded<T> {
UrlEncoded {
req: Some(req),
limit: 262_144,
fut: None,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl<T> Future for UrlEncoded<T>
where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static
{
type Item = HashMap<String, String>;
type Error = UrlencodedError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(req) = self.req.take() {
if req.chunked().unwrap_or(false) {
return Err(UrlencodedError::Chunked)
} else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
if len > 262_144 {
return Err(UrlencodedError::Overflow);
}
} else {
return Err(UrlencodedError::UnknownLength)
}
} else {
return Err(UrlencodedError::UnknownLength)
}
}
// check content type
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Err(UrlencodedError::ContentType)
}
let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?;
// future
let limit = self.limit;
let fut = req.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(move |body| {
let mut m = HashMap::new();
let parsed = form_urlencoded::parse_with_encoding(
&body, Some(encoding), false).map_err(|_| UrlencodedError::Parse)?;
for (k, v) in parsed {
m.insert(k.into(), v.into());
}
Ok(m)
});
self.fut = Some(Box::new(fut));
}
self.fut.as_mut().expect("UrlEncoded could not be used second time").poll()
}
}
#[cfg(test)]
mod tests {
use super::*;
use mime;
use encoding::Encoding;
use encoding::all::ISO_8859_2;
use futures::Async;
use http::{Method, Version, Uri};
use httprequest::HttpRequest;
use std::str::FromStr;
use std::iter::FromIterator;
use test::TestRequest;
#[test]
fn test_content_type() {
let req = TestRequest::with_header("content-type", "text/plain").finish();
assert_eq!(req.content_type(), "text/plain");
let req = TestRequest::with_header(
"content-type", "application/json; charset=utf=8").finish();
assert_eq!(req.content_type(), "application/json");
let req = HttpRequest::default();
assert_eq!(req.content_type(), "");
}
#[test]
fn test_mime_type() {
let req = TestRequest::with_header("content-type", "application/json").finish();
assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON));
let req = HttpRequest::default();
assert_eq!(req.mime_type().unwrap(), None);
let req = TestRequest::with_header(
"content-type", "application/json; charset=utf-8").finish();
let mt = req.mime_type().unwrap().unwrap();
assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8));
assert_eq!(mt.type_(), mime::APPLICATION);
assert_eq!(mt.subtype(), mime::JSON);
}
#[test]
fn test_mime_type_error() {
let req = TestRequest::with_header(
"content-type", "applicationadfadsfasdflknadsfklnadsfjson").finish();
assert_eq!(Err(ContentTypeError::ParseError), req.mime_type());
}
#[test]
fn test_encoding() {
let req = HttpRequest::default();
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
let req = TestRequest::with_header(
"content-type", "application/json").finish();
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
let req = TestRequest::with_header(
"content-type", "application/json; charset=ISO-8859-2").finish();
assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name());
}
#[test]
fn test_encoding_error() {
let req = TestRequest::with_header(
"content-type", "applicatjson").finish();
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
let req = TestRequest::with_header(
"content-type", "application/json; charset=kkkttktk").finish();
assert_eq!(Some(ContentTypeError::UnknownEncoding), req.encoding().err());
}
#[test]
fn test_no_request_range_header() {
let req = HttpRequest::default();
let ranges = req.range(100).unwrap();
assert!(ranges.is_empty());
}
#[test]
fn test_request_range_header() {
let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish();
let ranges = req.range(100).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].start, 0);
assert_eq!(ranges[0].length, 5);
}
#[test]
fn test_chunked() {
let req = HttpRequest::default();
assert!(!req.chunked().unwrap());
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert!(req.chunked().unwrap());
let mut headers = HeaderMap::new();
let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())};
headers.insert(header::TRANSFER_ENCODING,
header::HeaderValue::from_str(s).unwrap());
let req = HttpRequest::new(
Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, None);
assert!(req.chunked().is_err());
}
impl PartialEq for UrlencodedError {
fn eq(&self, other: &UrlencodedError) -> bool {
match *self {
UrlencodedError::Chunked => match *other {
UrlencodedError::Chunked => true,
_ => false,
},
UrlencodedError::Overflow => match *other {
UrlencodedError::Overflow => true,
_ => false,
},
UrlencodedError::UnknownLength => match *other {
UrlencodedError::UnknownLength => true,
_ => false,
},
UrlencodedError::ContentType => match *other {
UrlencodedError::ContentType => true,
_ => false,
},
_ => false,
}
}
}
#[test]
fn test_urlencoded_error() {
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "xxxx")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "1000000")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "text/plain")
.header(header::CONTENT_LENGTH, "10")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType);
}
#[test]
fn test_urlencoded() {
let mut req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "11")
.finish();
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
let result = req.urlencoded().poll().ok().unwrap();
assert_eq!(result, Async::Ready(
HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())])));
let mut req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8")
.header(header::CONTENT_LENGTH, "11")
.finish();
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
let result = req.urlencoded().poll().ok().unwrap();
assert_eq!(result, Async::Ready(
HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())])));
}
#[test]
fn test_message_body() {
let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish();
match req.body().poll().err().unwrap() {
PayloadError::UnknownLength => (),
_ => panic!("error"),
}
let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish();
match req.body().poll().err().unwrap() {
PayloadError::Overflow => (),
_ => panic!("error"),
}
let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(b"test"));
match req.body().poll().ok().unwrap() {
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
_ => panic!("error"),
}
let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(b"11111111111111"));
match req.body().limit(5).poll().err().unwrap() {
PayloadError::Overflow => (),
_ => panic!("error"),
}
}
}

View File

@ -1,28 +1,25 @@
//! HTTP Request message related code. //! HTTP Request message related code.
use std::{str, fmt, mem}; use std::{io, cmp, str, fmt, mem};
use std::rc::Rc; use std::rc::Rc;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::collections::HashMap; use bytes::Bytes;
use bytes::{Bytes, BytesMut};
use cookie::Cookie; use cookie::Cookie;
use futures::{Async, Future, Stream, Poll}; use futures::{Async, Stream, Poll};
use http_range::HttpRange; use failure;
use serde::de::DeserializeOwned;
use url::{Url, form_urlencoded}; use url::{Url, form_urlencoded};
use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use http::{header, Uri, Method, Version, HeaderMap, Extensions};
use tokio_io::AsyncRead;
use info::ConnectionInfo; use info::ConnectionInfo;
use param::Params; use param::Params;
use router::Router; use router::Router;
use payload::{Payload, ReadAny}; use payload::Payload;
use json::JsonBody; use httpmessage::HttpMessage;
use multipart::Multipart; use helpers::SharedHttpInnerMessage;
use helpers::SharedHttpMessage; use error::{UrlGenerationError, CookieParseError, PayloadError};
use error::{ParseError, UrlGenerationError,
CookieParseError, HttpRangeError, PayloadError, UrlencodedError};
pub struct HttpMessage { pub struct HttpInnerMessage {
pub version: Version, pub version: Version,
pub method: Method, pub method: Method,
pub uri: Uri, pub uri: Uri,
@ -37,10 +34,10 @@ pub struct HttpMessage {
pub info: Option<ConnectionInfo<'static>>, pub info: Option<ConnectionInfo<'static>>,
} }
impl Default for HttpMessage { impl Default for HttpInnerMessage {
fn default() -> HttpMessage { fn default() -> HttpInnerMessage {
HttpMessage { HttpInnerMessage {
method: Method::GET, method: Method::GET,
uri: Uri::default(), uri: Uri::default(),
version: Version::HTTP_11, version: Version::HTTP_11,
@ -57,7 +54,7 @@ impl Default for HttpMessage {
} }
} }
impl HttpMessage { impl HttpInnerMessage {
/// Checks if a connection should be kept alive. /// Checks if a connection should be kept alive.
#[inline] #[inline]
@ -93,7 +90,7 @@ impl HttpMessage {
} }
/// An HTTP Request /// An HTTP Request
pub struct HttpRequest<S=()>(SharedHttpMessage, Option<Rc<S>>, Option<Router>); pub struct HttpRequest<S=()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>);
impl HttpRequest<()> { impl HttpRequest<()> {
/// Construct a new Request. /// Construct a new Request.
@ -102,17 +99,17 @@ impl HttpRequest<()> {
version: Version, headers: HeaderMap, payload: Option<Payload>) -> HttpRequest version: Version, headers: HeaderMap, payload: Option<Payload>) -> HttpRequest
{ {
HttpRequest( HttpRequest(
SharedHttpMessage::from_message(HttpMessage { SharedHttpInnerMessage::from_message(HttpInnerMessage {
method: method, method,
uri: uri, uri,
version: version, version,
headers: headers, headers,
payload,
params: Params::new(), params: Params::new(),
query: Params::new(), query: Params::new(),
query_loaded: false, query_loaded: false,
cookies: None, cookies: None,
addr: None, addr: None,
payload: payload,
extensions: Extensions::new(), extensions: Extensions::new(),
info: None, info: None,
}), }),
@ -123,7 +120,7 @@ impl HttpRequest<()> {
#[inline(always)] #[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
pub(crate) fn from_message(msg: SharedHttpMessage) -> HttpRequest { pub(crate) fn from_message(msg: SharedHttpInnerMessage) -> HttpRequest {
HttpRequest(msg, None, None) HttpRequest(msg, None, None)
} }
@ -140,6 +137,14 @@ impl HttpRequest<()> {
} }
} }
impl<S> HttpMessage for HttpRequest<S> {
#[inline]
fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
}
}
impl<S> HttpRequest<S> { impl<S> HttpRequest<S> {
#[inline] #[inline]
@ -150,26 +155,26 @@ impl<S> HttpRequest<S> {
#[inline] #[inline]
/// Construct new http request without state. /// Construct new http request without state.
pub(crate) fn clone_without_state(&self) -> HttpRequest { pub(crate) fn without_state(&self) -> HttpRequest {
HttpRequest(self.0.clone(), None, None) HttpRequest(self.0.clone(), None, None)
} }
// get mutable reference for inner message /// get mutable reference for inner message
// mutable reference should not be returned as result for request's method /// mutable reference should not be returned as result for request's method
#[inline(always)] #[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub(crate) fn as_mut(&self) -> &mut HttpMessage { pub(crate) fn as_mut(&self) -> &mut HttpInnerMessage {
self.0.get_mut() self.0.get_mut()
} }
#[inline(always)] #[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
fn as_ref(&self) -> &HttpMessage { fn as_ref(&self) -> &HttpInnerMessage {
self.0.get_ref() self.0.get_ref()
} }
#[inline] #[inline]
pub(crate) fn get_inner(&mut self) -> &mut HttpMessage { pub(crate) fn get_inner(&mut self) -> &mut HttpInnerMessage {
self.as_mut() self.as_mut()
} }
@ -194,6 +199,15 @@ impl<S> HttpRequest<S> {
#[inline] #[inline]
pub fn uri(&self) -> &Uri { &self.as_ref().uri } pub fn uri(&self) -> &Uri { &self.as_ref().uri }
#[doc(hidden)]
#[inline]
/// Modify the Request Uri.
///
/// This might be useful for middlewares, i.e. path normalization
pub fn uri_mut(&mut self) -> &mut Uri {
&mut self.as_mut().uri
}
/// Read the Request method. /// Read the Request method.
#[inline] #[inline]
pub fn method(&self) -> &Method { &self.as_ref().method } pub fn method(&self) -> &Method { &self.as_ref().method }
@ -204,12 +218,6 @@ impl<S> HttpRequest<S> {
self.as_ref().version self.as_ref().version
} }
/// Read the Request Headers.
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
}
#[doc(hidden)] #[doc(hidden)]
#[inline] #[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap { pub fn headers_mut(&mut self) -> &mut HeaderMap {
@ -222,7 +230,7 @@ impl<S> HttpRequest<S> {
self.uri().path() self.uri().path()
} }
/// Get *ConnectionInfo* for currect request. /// Get *ConnectionInfo* for correct request.
pub fn connection_info(&self) -> &ConnectionInfo { pub fn connection_info(&self) -> &ConnectionInfo {
if self.as_ref().info.is_none() { if self.as_ref().info.is_none() {
let info: ConnectionInfo<'static> = unsafe{ let info: ConnectionInfo<'static> = unsafe{
@ -241,14 +249,14 @@ impl<S> HttpRequest<S> {
/// # /// #
/// fn index(req: HttpRequest) -> HttpResponse { /// fn index(req: HttpRequest) -> HttpResponse {
/// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
/// HTTPOk.into() /// HttpOk.into()
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let app = Application::new() /// let app = Application::new()
/// .resource("/test/{one}/{two}/{three}", |r| { /// .resource("/test/{one}/{two}/{three}", |r| {
/// r.name("foo"); // <- set resource name, then it could be used in `url_for` /// r.name("foo"); // <- set resource name, then it could be used in `url_for`
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::GET).f(|_| httpcodes::HttpOk);
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
@ -278,7 +286,7 @@ impl<S> HttpRequest<S> {
/// Peer socket address /// Peer socket address
/// ///
/// Peer address is actuall socket address, if proxy is used in front of /// Peer address is actual socket address, if proxy is used in front of
/// actix http server, then peer address would be address of this proxy. /// actix http server, then peer address would be address of this proxy.
/// ///
/// To get client connection information `connection_info()` method should be used. /// To get client connection information `connection_info()` method should be used.
@ -357,7 +365,7 @@ impl<S> HttpRequest<S> {
/// Get mutable reference to request's Params. /// Get mutable reference to request's Params.
#[inline] #[inline]
pub(crate) fn match_info_mut(&mut self) -> &mut Params { pub fn match_info_mut(&mut self) -> &mut Params {
unsafe{ mem::transmute(&mut self.as_mut().params) } unsafe{ mem::transmute(&mut self.as_mut().params) }
} }
@ -366,17 +374,6 @@ impl<S> HttpRequest<S> {
self.as_ref().keep_alive() self.as_ref().keep_alive()
} }
/// Read the request content type. If request does not contain
/// *Content-Type* header, empty str get returned.
pub fn content_type(&self) -> &str {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
return content_type
}
}
""
}
/// Check if request requires connection upgrade /// Check if request requires connection upgrade
pub(crate) fn upgrade(&self) -> bool { pub(crate) fn upgrade(&self) -> bool {
if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) { if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) {
@ -387,33 +384,8 @@ impl<S> HttpRequest<S> {
self.as_ref().method == Method::CONNECT self.as_ref().method == Method::CONNECT
} }
/// Check if request has chunked transfer encoding #[cfg(test)]
pub fn chunked(&self) -> Result<bool, ParseError> { pub(crate) fn payload(&self) -> &Payload {
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() {
Ok(s.to_lowercase().contains("chunked"))
} else {
Err(ParseError::Header)
}
} else {
Ok(false)
}
}
/// Parses Range HTTP header string as per RFC 2616.
/// `size` is full size of response (file).
pub fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> {
if let Some(range) = self.headers().get(header::RANGE) {
HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size)
.map_err(|e| e.into())
} else {
Ok(Vec::new())
}
}
/// Returns reference to the associated http payload.
#[inline]
pub fn payload(&self) -> &Payload {
let msg = self.as_mut(); let msg = self.as_mut();
if msg.payload.is_none() { if msg.payload.is_none() {
msg.payload = Some(Payload::empty()); msg.payload = Some(Payload::empty());
@ -421,164 +393,80 @@ impl<S> HttpRequest<S> {
msg.payload.as_ref().unwrap() msg.payload.as_ref().unwrap()
} }
/// Returns mutable reference to the associated http payload. #[cfg(test)]
#[inline] pub(crate) fn payload_mut(&mut self) -> &mut Payload {
pub fn payload_mut(&mut self) -> &mut Payload {
let msg = self.as_mut(); let msg = self.as_mut();
if msg.payload.is_none() { if msg.payload.is_none() {
msg.payload = Some(Payload::empty()); msg.payload = Some(Payload::empty());
} }
msg.payload.as_mut().unwrap() msg.payload.as_mut().unwrap()
} }
/// Load request body.
///
/// By default only 256Kb payload reads to a memory, then `BAD REQUEST`
/// http response get returns to a peer. Use `RequestBody::limit()`
/// method to change upper limit.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use bytes::Bytes;
/// use futures::future::Future;
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.body() // <- get Body future
/// .limit(1024) // <- change max size of the body to a 1kb
/// .from_err()
/// .and_then(|bytes: Bytes| { // <- complete body
/// println!("==== BODY ==== {:?}", bytes);
/// Ok(httpcodes::HTTPOk.into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
pub fn body(&self) -> RequestBody {
RequestBody::from_request(self)
}
/// Return stream to http payload processes as multipart.
///
/// Content-type: multipart/form-data;
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate env_logger;
/// # extern crate futures;
/// # use std::str;
/// # use actix::*;
/// # use actix_web::*;
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.multipart().from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items
/// multipart::MultipartItem::Field(field) => {
/// // Field in turn is stream of *Bytes* object
/// Either::A(field.from_err()
/// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c)))
/// .finish())
/// },
/// multipart::MultipartItem::Nested(mp) => {
/// // Or item could be nested Multipart stream
/// Either::B(ok(()))
/// }
/// })
/// .finish() // <- Stream::finish() combinator from actix
/// .map(|_| httpcodes::HTTPOk.into())
/// .responder()
/// }
/// # fn main() {}
/// ```
pub fn multipart(&mut self) -> Multipart {
Multipart::from_request(self)
}
/// Parse `application/x-www-form-urlencoded` encoded body.
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>` which
/// contains decoded parameters.
///
/// Returns error:
///
/// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k
///
/// ```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() {}
/// ```
pub fn urlencoded(&self) -> UrlEncoded {
UrlEncoded::from_request(self)
}
/// Parse `application/json` encoded body.
/// Return `JsonBody<T>` future. It resolves to a `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 256k
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use futures::future::{Future, ok};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
/// println!("==== BODY ==== {:?}", val);
/// Ok(httpcodes::HTTPOk.into())
/// }).responder()
/// }
/// # fn main() {}
/// ```
pub fn json<T: DeserializeOwned>(&self) -> JsonBody<S, T> {
JsonBody::from_request(self)
}
} }
impl Default for HttpRequest<()> { impl Default for HttpRequest<()> {
/// Construct default request /// Construct default request
fn default() -> HttpRequest { fn default() -> HttpRequest {
HttpRequest(SharedHttpMessage::default(), None, None) HttpRequest(SharedHttpInnerMessage::default(), None, None)
} }
} }
impl<S> Clone for HttpRequest<S> { impl<S> Clone for HttpRequest<S> {
fn clone(&self) -> HttpRequest<S> { fn clone(&self) -> HttpRequest<S> {
HttpRequest(self.0.clone(), self.1.clone(), None) HttpRequest(self.0.clone(), self.1.clone(), self.2.clone())
} }
} }
impl<S> Stream for HttpRequest<S> {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
let msg = self.as_mut();
if msg.payload.is_none() {
Ok(Async::Ready(None))
} else {
msg.payload.as_mut().unwrap().poll()
}
}
}
impl<S> io::Read for HttpRequest<S> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.as_mut().payload.is_some() {
match self.as_mut().payload.as_mut().unwrap().poll() {
Ok(Async::Ready(Some(mut b))) => {
let i = cmp::min(b.len(), buf.len());
buf.copy_from_slice(&b.split_to(i)[..i]);
if !b.is_empty() {
self.as_mut().payload.as_mut().unwrap().unread_data(b);
}
if i < buf.len() {
match self.read(&mut buf[i..]) {
Ok(n) => Ok(i + n),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(i),
Err(e) => Err(e),
}
} else {
Ok(i)
}
}
Ok(Async::Ready(None)) => Ok(0),
Ok(Async::NotReady) =>
Err(io::Error::new(io::ErrorKind::WouldBlock, "Not ready")),
Err(e) =>
Err(io::Error::new(io::ErrorKind::Other, failure::Error::from(e).compat())),
}
} else {
Ok(0)
}
}
}
impl<S> AsyncRead for HttpRequest<S> {}
impl<S> fmt::Debug for HttpRequest<S> { impl<S> fmt::Debug for HttpRequest<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nHttpRequest {:?} {}:{}\n", let res = write!(f, "\nHttpRequest {:?} {}:{}\n",
@ -602,160 +490,10 @@ impl<S> fmt::Debug for HttpRequest<S> {
} }
} }
/// Future that resolves to a parsed urlencoded values.
pub struct UrlEncoded {
pl: Payload,
body: BytesMut,
error: Option<UrlencodedError>,
}
impl UrlEncoded {
pub fn from_request<S>(req: &HttpRequest<S>) -> UrlEncoded {
let mut encoded = UrlEncoded {
pl: req.payload().clone(),
body: BytesMut::new(),
error: None
};
if let Ok(true) = req.chunked() {
encoded.error = Some(UrlencodedError::Chunked);
} else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
if len > 262_144 {
encoded.error = Some(UrlencodedError::Overflow);
}
} else {
encoded.error = Some(UrlencodedError::UnknownLength);
}
} else {
encoded.error = Some(UrlencodedError::UnknownLength);
}
}
// check content type
if encoded.error.is_none() {
if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if content_type.to_lowercase() == "application/x-www-form-urlencoded" {
return encoded
}
}
}
encoded.error = Some(UrlencodedError::ContentType);
return encoded
}
encoded
}
}
impl Future for UrlEncoded {
type Item = HashMap<String, String>;
type Error = UrlencodedError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(err) = self.error.take() {
return Err(err)
}
loop {
return match self.pl.poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
let mut m = HashMap::new();
for (k, v) in form_urlencoded::parse(&self.body) {
m.insert(k.into(), v.into());
}
Ok(Async::Ready(m))
},
Ok(Async::Ready(Some(item))) => {
self.body.extend_from_slice(&item);
continue
},
Err(err) => Err(err.into()),
}
}
}
}
/// Future that resolves to a complete request body.
pub struct RequestBody {
pl: ReadAny,
body: BytesMut,
limit: usize,
error: Option<PayloadError>,
}
impl RequestBody {
/// Create `RequestBody` for request.
pub fn from_request<S>(req: &HttpRequest<S>) -> RequestBody {
let mut body = RequestBody {
pl: req.payload().readany(),
body: BytesMut::new(),
limit: 262_144,
error: None
};
if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
if len > 262_144 {
body.error = Some(PayloadError::Overflow);
}
} else {
body.error = Some(PayloadError::UnknownLength);
}
} else {
body.error = Some(PayloadError::UnknownLength);
}
}
body
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl Future for RequestBody {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(err) = self.error.take() {
return Err(err)
}
loop {
return match self.pl.poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
Ok(Async::Ready(self.body.take().freeze()))
},
Ok(Async::Ready(Some(chunk))) => {
if (self.body.len() + chunk.len()) > self.limit {
Err(PayloadError::Overflow)
} else {
self.body.extend_from_slice(&chunk);
continue
}
},
Err(err) => Err(err),
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use http::Uri; use http::{Uri, HttpTryFrom};
use std::str::FromStr;
use router::Pattern; use router::Pattern;
use resource::Resource; use resource::Resource;
use test::TestRequest; use test::TestRequest;
@ -768,6 +506,14 @@ mod tests {
assert!(dbg.contains("HttpRequest")); assert!(dbg.contains("HttpRequest"));
} }
#[test]
fn test_uri_mut() {
let mut req = HttpRequest::default();
assert_eq!(req.path(), "/");
*req.uri_mut() = Uri::try_from("/test").unwrap();
assert_eq!(req.path(), "/test");
}
#[test] #[test]
fn test_no_request_cookies() { fn test_no_request_cookies() {
let req = HttpRequest::default(); let req = HttpRequest::default();
@ -797,22 +543,6 @@ mod tests {
assert!(cookie.is_none()); assert!(cookie.is_none());
} }
#[test]
fn test_no_request_range_header() {
let req = HttpRequest::default();
let ranges = req.range(100).unwrap();
assert!(ranges.is_empty());
}
#[test]
fn test_request_range_header() {
let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish();
let ranges = req.range(100).unwrap();
assert_eq!(ranges.len(), 1);
assert_eq!(ranges[0].start, 0);
assert_eq!(ranges[0].length, 5);
}
#[test] #[test]
fn test_request_query() { fn test_request_query() {
let req = TestRequest::with_uri("/?id=test").finish(); let req = TestRequest::with_uri("/?id=test").finish();
@ -827,110 +557,14 @@ mod tests {
let mut resource = Resource::<()>::default(); let mut resource = Resource::<()>::default();
resource.name("index"); resource.name("index");
let mut map = HashMap::new(); let mut routes = Vec::new();
map.insert(Pattern::new("index", "/{key}/", "^/"), Some(resource)); routes.push((Pattern::new("index", "/{key}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), map); let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some()); assert!(router.recognize(&mut req).is_some());
assert_eq!(req.match_info().get("key"), Some("value")); assert_eq!(req.match_info().get("key"), Some("value"));
} }
#[test]
fn test_chunked() {
let req = HttpRequest::default();
assert!(!req.chunked().unwrap());
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert!(req.chunked().unwrap());
let mut headers = HeaderMap::new();
let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())};
headers.insert(header::TRANSFER_ENCODING,
header::HeaderValue::from_str(s).unwrap());
let req = HttpRequest::new(
Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, None);
assert!(req.chunked().is_err());
}
impl PartialEq for UrlencodedError {
fn eq(&self, other: &UrlencodedError) -> bool {
match *self {
UrlencodedError::Chunked => match *other {
UrlencodedError::Chunked => true,
_ => false,
},
UrlencodedError::Overflow => match *other {
UrlencodedError::Overflow => true,
_ => false,
},
UrlencodedError::UnknownLength => match *other {
UrlencodedError::UnknownLength => true,
_ => false,
},
UrlencodedError::ContentType => match *other {
UrlencodedError::ContentType => true,
_ => false,
},
_ => false,
}
}
}
#[test]
fn test_urlencoded_error() {
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "xxxx")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(header::CONTENT_LENGTH, "1000000")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow);
let req = TestRequest::with_header(
header::CONTENT_TYPE, "text/plain")
.header(header::CONTENT_LENGTH, "10")
.finish();
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType);
}
#[test]
fn test_request_body() {
let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish();
match req.body().poll().err().unwrap() {
PayloadError::UnknownLength => (),
_ => panic!("error"),
}
let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish();
match req.body().poll().err().unwrap() {
PayloadError::Overflow => (),
_ => panic!("error"),
}
let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(b"test"));
match req.body().poll().ok().unwrap() {
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
_ => panic!("error"),
}
let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(b"11111111111111"));
match req.body().limit(5).poll().err().unwrap() {
PayloadError::Overflow => (),
_ => panic!("error"),
}
}
#[test] #[test]
fn test_url_for() { fn test_url_for() {
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
@ -938,9 +572,8 @@ mod tests {
let mut resource = Resource::<()>::default(); let mut resource = Resource::<()>::default();
resource.name("index"); resource.name("index");
let mut map = HashMap::new(); let routes = vec!((Pattern::new("index", "/user/{name}.{ext}"), Some(resource)));
map.insert(Pattern::new("index", "/user/{name}.{ext}", "^/"), Some(resource)); let (router, _) = Router::new("/", ServerSettings::default(), routes);
let (router, _) = Router::new("/", ServerSettings::default(), map);
assert!(router.has_route("/user/test.html")); assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/test/unknown")); assert!(!router.has_route("/test/unknown"));
@ -963,9 +596,8 @@ mod tests {
let mut resource = Resource::<()>::default(); let mut resource = Resource::<()>::default();
resource.name("index"); resource.name("index");
let mut map = HashMap::new(); let routes = vec![(Pattern::new("index", "/user/{name}.{ext}"), Some(resource))];
map.insert(Pattern::new("index", "/user/{name}.{ext}", "^/"), Some(resource)); let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
let (router, _) = Router::new("/prefix/", ServerSettings::default(), map);
assert!(router.has_route("/user/test.html")); assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/prefix/user/test.html")); assert!(!router.has_route("/prefix/user/test.html"));
@ -980,9 +612,9 @@ mod tests {
let mut resource = Resource::<()>::default(); let mut resource = Resource::<()>::default();
resource.name("index"); resource.name("index");
let mut map = HashMap::new(); let routes = vec![
map.insert(Pattern::new("youtube", "https://youtube.com/watch/{video_id}", "^/"), None); (Pattern::new("youtube", "https://youtube.com/watch/{video_id}"), None)];
let (router, _) = Router::new::<()>("", ServerSettings::default(), map); let (router, _) = Router::new::<()>("", ServerSettings::default(), routes);
assert!(!router.has_route("https://youtube.com/watch/unknown")); assert!(!router.has_route("https://youtube.com/watch/unknown"));
let req = req.with_state(Rc::new(()), router); let req = req.with_state(Rc::new(()), router);

View File

@ -1,17 +1,15 @@
//! Pieces pertaining to the HTTP response. //! Http response
use std::{mem, str, fmt}; use std::{mem, str, fmt};
use std::io::Write; use std::io::Write;
use std::cell::RefCell; use std::cell::RefCell;
use std::convert::Into;
use std::collections::VecDeque; use std::collections::VecDeque;
use cookie::CookieJar; use cookie::{Cookie, CookieJar};
use bytes::{Bytes, BytesMut, BufMut}; use bytes::{Bytes, BytesMut, BufMut};
use http::{StatusCode, Version, HeaderMap, HttpTryFrom, Error as HttpError}; use http::{StatusCode, Version, HeaderMap, HttpTryFrom, Error as HttpError};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use serde_json; use serde_json;
use serde::Serialize; use serde::Serialize;
use cookie::Cookie;
use body::Body; use body::Body;
use error::Error; use error::Error;
@ -85,37 +83,37 @@ impl HttpResponse {
self.get_ref().error.as_ref() self.get_ref().error.as_ref()
} }
/// Get the HTTP version of this response. /// Get the HTTP version of this response
#[inline] #[inline]
pub fn version(&self) -> Option<Version> { pub fn version(&self) -> Option<Version> {
self.get_ref().version self.get_ref().version
} }
/// Get the headers from the response. /// Get the headers from the response
#[inline] #[inline]
pub fn headers(&self) -> &HeaderMap { pub fn headers(&self) -> &HeaderMap {
&self.get_ref().headers &self.get_ref().headers
} }
/// Get a mutable reference to the headers. /// Get a mutable reference to the headers
#[inline] #[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap { pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.get_mut().headers &mut self.get_mut().headers
} }
/// Get the status from the server. /// Get the response status code
#[inline] #[inline]
pub fn status(&self) -> StatusCode { pub fn status(&self) -> StatusCode {
self.get_ref().status self.get_ref().status
} }
/// Set the `StatusCode` for this response. /// Set the `StatusCode` for this response
#[inline] #[inline]
pub fn status_mut(&mut self) -> &mut StatusCode { pub fn status_mut(&mut self) -> &mut StatusCode {
&mut self.get_mut().status &mut self.get_mut().status
} }
/// Get custom reason for the response. /// Get custom reason for the response
#[inline] #[inline]
pub fn reason(&self) -> &str { pub fn reason(&self) -> &str {
if let Some(reason) = self.get_ref().reason { if let Some(reason) = self.get_ref().reason {
@ -125,7 +123,7 @@ impl HttpResponse {
} }
} }
/// Set the custom reason for the response. /// Set the custom reason for the response
#[inline] #[inline]
pub fn set_reason(&mut self, reason: &'static str) -> &mut Self { pub fn set_reason(&mut self, reason: &'static str) -> &mut Self {
self.get_mut().reason = Some(reason); self.get_mut().reason = Some(reason);
@ -164,13 +162,13 @@ impl HttpResponse {
/// Content encoding /// Content encoding
#[inline] #[inline]
pub fn content_encoding(&self) -> &ContentEncoding { pub fn content_encoding(&self) -> Option<ContentEncoding> {
&self.get_ref().encoding self.get_ref().encoding
} }
/// Set content encoding /// Set content encoding
pub fn set_content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { pub fn set_content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
self.get_mut().encoding = enc; self.get_mut().encoding = Some(enc);
self self
} }
@ -254,14 +252,13 @@ impl HttpResponseBuilder {
/// use http::header; /// use http::header;
/// ///
/// fn index(req: HttpRequest) -> Result<HttpResponse> { /// fn index(req: HttpRequest) -> Result<HttpResponse> {
/// Ok(HTTPOk.build() /// Ok(HttpOk.build()
/// .header("X-TEST", "value") /// .header("X-TEST", "value")
/// .header(header::CONTENT_TYPE, "application/json") /// .header(header::CONTENT_TYPE, "application/json")
/// .finish()?) /// .finish()?)
/// } /// }
/// fn main() {} /// fn main() {}
/// ``` /// ```
#[inline]
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where HeaderName: HttpTryFrom<K>, where HeaderName: HttpTryFrom<K>,
HeaderValue: HttpTryFrom<V> HeaderValue: HttpTryFrom<V>
@ -297,7 +294,7 @@ impl HttpResponseBuilder {
#[inline] #[inline]
pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
if let Some(parts) = parts(&mut self.response, &self.err) { if let Some(parts) = parts(&mut self.response, &self.err) {
parts.encoding = enc; parts.encoding = Some(enc);
} }
self self
} }
@ -375,7 +372,7 @@ impl HttpResponseBuilder {
/// use actix_web::headers::Cookie; /// use actix_web::headers::Cookie;
/// ///
/// fn index(req: HttpRequest) -> Result<HttpResponse> { /// fn index(req: HttpRequest) -> Result<HttpResponse> {
/// Ok(HTTPOk.build() /// Ok(HttpOk.build()
/// .cookie( /// .cookie(
/// Cookie::build("name", "value") /// Cookie::build("name", "value")
/// .domain("www.rust-lang.org") /// .domain("www.rust-lang.org")
@ -423,8 +420,8 @@ impl HttpResponseBuilder {
} }
/// This method calls provided closure with builder reference if value is Some. /// This method calls provided closure with builder reference if value is Some.
pub fn if_some<T, F>(&mut self, value: Option<&T>, f: F) -> &mut Self pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
where F: FnOnce(&T, &mut HttpResponseBuilder) where F: FnOnce(T, &mut HttpResponseBuilder)
{ {
if let Some(val) = value { if let Some(val) = value {
f(val, self); f(val, self);
@ -651,7 +648,7 @@ struct InnerHttpResponse {
reason: Option<&'static str>, reason: Option<&'static str>,
body: Body, body: Body,
chunked: Option<bool>, chunked: Option<bool>,
encoding: ContentEncoding, encoding: Option<ContentEncoding>,
connection_type: Option<ConnectionType>, connection_type: Option<ConnectionType>,
response_size: u64, response_size: u64,
error: Option<Error>, error: Option<Error>,
@ -662,13 +659,13 @@ impl InnerHttpResponse {
#[inline] #[inline]
fn new(status: StatusCode, body: Body) -> InnerHttpResponse { fn new(status: StatusCode, body: Body) -> InnerHttpResponse {
InnerHttpResponse { InnerHttpResponse {
status,
body,
version: None, version: None,
headers: HeaderMap::with_capacity(16), headers: HeaderMap::with_capacity(16),
status: status,
reason: None, reason: None,
body: body,
chunked: None, chunked: None,
encoding: ContentEncoding::Auto, encoding: None,
connection_type: None, connection_type: None,
response_size: 0, response_size: 0,
error: None, error: None,
@ -720,7 +717,7 @@ impl Pool {
inner.version = None; inner.version = None;
inner.chunked = None; inner.chunked = None;
inner.reason = None; inner.reason = None;
inner.encoding = ContentEncoding::Auto; inner.encoding = None;
inner.connection_type = None; inner.connection_type = None;
inner.response_size = 0; inner.response_size = 0;
inner.error = None; inner.error = None;
@ -756,7 +753,7 @@ mod tests {
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
let cookies = req.cookies().unwrap(); let cookies = req.cookies().unwrap();
let resp = httpcodes::HTTPOk let resp = httpcodes::HttpOk
.build() .build()
.cookie(headers::Cookie::build("name", "value") .cookie(headers::Cookie::build("name", "value")
.domain("www.rust-lang.org") .domain("www.rust-lang.org")
@ -812,11 +809,11 @@ mod tests {
#[test] #[test]
fn test_content_encoding() { fn test_content_encoding() {
let resp = HttpResponse::build(StatusCode::OK).finish().unwrap(); let resp = HttpResponse::build(StatusCode::OK).finish().unwrap();
assert_eq!(*resp.content_encoding(), ContentEncoding::Auto); assert_eq!(resp.content_encoding(), None);
let resp = HttpResponse::build(StatusCode::OK) let resp = HttpResponse::build(StatusCode::OK)
.content_encoding(ContentEncoding::Br).finish().unwrap(); .content_encoding(ContentEncoding::Br).finish().unwrap();
assert_eq!(*resp.content_encoding(), ContentEncoding::Br); assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br));
} }
#[test] #[test]
@ -898,14 +895,14 @@ mod tests {
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
header::HeaderValue::from_static("text/plain; charset=utf-8")); header::HeaderValue::from_static("text/plain; charset=utf-8"));
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().binary().unwrap(), &Binary::from((&"test".to_owned()))); assert_eq!(resp.body().binary().unwrap(), &Binary::from(&"test".to_owned()));
let resp: HttpResponse = (&"test".to_owned()).respond_to(req.clone()).ok().unwrap(); let resp: HttpResponse = (&"test".to_owned()).respond_to(req.clone()).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
header::HeaderValue::from_static("text/plain; charset=utf-8")); header::HeaderValue::from_static("text/plain; charset=utf-8"));
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().binary().unwrap(), &Binary::from((&"test".to_owned()))); assert_eq!(resp.body().binary().unwrap(), &Binary::from(&"test".to_owned()));
let b = Bytes::from_static(b"test"); let b = Bytes::from_static(b"test");
let resp: HttpResponse = b.into(); let resp: HttpResponse = b.into();

View File

@ -1,5 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use http::header::{self, HeaderName}; use http::header::{self, HeaderName};
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR"; const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR";
@ -110,8 +111,8 @@ impl<'a> ConnectionInfo<'a> {
ConnectionInfo { ConnectionInfo {
scheme: scheme.unwrap_or("http"), scheme: scheme.unwrap_or("http"),
host: host.unwrap_or("localhost"), host: host.unwrap_or("localhost"),
remote: remote, remote,
peer: peer, peer,
} }
} }

View File

@ -1,4 +1,4 @@
use bytes::BytesMut; use bytes::{Bytes, BytesMut};
use futures::{Poll, Future, Stream}; use futures::{Poll, Future, Stream};
use http::header::CONTENT_LENGTH; use http::header::CONTENT_LENGTH;
@ -6,8 +6,9 @@ use serde_json;
use serde::Serialize; use serde::Serialize;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use error::{Error, JsonPayloadError}; use error::{Error, JsonPayloadError, PayloadError};
use handler::Responder; use handler::Responder;
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -54,6 +55,9 @@ impl<T: Serialize> Responder for Json<T> {
/// * content type is not `application/json` /// * content type is not `application/json`
/// * content length is greater than 256k /// * content length is greater than 256k
/// ///
///
/// # Server example
///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// # extern crate futures; /// # extern crate futures;
@ -71,25 +75,25 @@ impl<T: Serialize> Responder for Json<T> {
/// .from_err() /// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value /// .and_then(|val: MyObj| { // <- deserialized value
/// println!("==== BODY ==== {:?}", val); /// println!("==== BODY ==== {:?}", val);
/// Ok(httpcodes::HTTPOk.into()) /// Ok(httpcodes::HttpOk.into())
/// }).responder() /// }).responder()
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
pub struct JsonBody<S, T: DeserializeOwned>{ pub struct JsonBody<T, U: DeserializeOwned>{
limit: usize, limit: usize,
ct: &'static str, ct: &'static str,
req: Option<HttpRequest<S>>, req: Option<T>,
fut: Option<Box<Future<Item=T, Error=JsonPayloadError>>>, fut: Option<Box<Future<Item=U, Error=JsonPayloadError>>>,
} }
impl<S, T: DeserializeOwned> JsonBody<S, T> { impl<T, U: DeserializeOwned> JsonBody<T, U> {
/// Create `JsonBody` for request. /// Create `JsonBody` for request.
pub fn from_request(req: &HttpRequest<S>) -> Self { pub fn new(req: T) -> Self {
JsonBody{ JsonBody{
limit: 262_144, limit: 262_144,
req: Some(req.clone()), req: Some(req),
fut: None, fut: None,
ct: "application/json", ct: "application/json",
} }
@ -111,11 +115,13 @@ impl<S, T: DeserializeOwned> JsonBody<S, T> {
} }
} }
impl<S, T: DeserializeOwned + 'static> Future for JsonBody<S, T> { impl<T, U: DeserializeOwned + 'static> Future for JsonBody<T, U>
type Item = T; where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static
{
type Item = U;
type Error = JsonPayloadError; type Error = JsonPayloadError;
fn poll(&mut self) -> Poll<T, JsonPayloadError> { fn poll(&mut self) -> Poll<U, JsonPayloadError> {
if let Some(req) = self.req.take() { if let Some(req) = self.req.take() {
if let Some(len) = req.headers().get(CONTENT_LENGTH) { if let Some(len) = req.headers().get(CONTENT_LENGTH) {
if let Ok(s) = len.to_str() { if let Ok(s) = len.to_str() {
@ -134,8 +140,7 @@ impl<S, T: DeserializeOwned + 'static> Future for JsonBody<S, T> {
} }
let limit = self.limit; let limit = self.limit;
let fut = req.payload().readany() let fut = req.from_err()
.from_err()
.fold(BytesMut::new(), move |mut body, chunk| { .fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit { if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow) Err(JsonPayloadError::Overflow)
@ -144,7 +149,7 @@ impl<S, T: DeserializeOwned + 'static> Future for JsonBody<S, T> {
Ok(body) Ok(body)
} }
}) })
.and_then(|body| Ok(serde_json::from_slice::<T>(&body)?)); .and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut)); self.fut = Some(Box::new(fut));
} }
@ -189,27 +194,31 @@ mod tests {
#[test] #[test]
fn test_json_body() { fn test_json_body() {
let mut req = HttpRequest::default(); let req = HttpRequest::default();
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
let mut json = req.json::<MyObject>().content_type("text/json"); let mut req = HttpRequest::default();
req.headers_mut().insert(header::CONTENT_TYPE, req.headers_mut().insert(header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json")); header::HeaderValue::from_static("application/json"));
let mut json = req.json::<MyObject>().content_type("text/json");
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
let mut json = req.json::<MyObject>().limit(100); let mut req = HttpRequest::default();
req.headers_mut().insert(header::CONTENT_TYPE, req.headers_mut().insert(header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json")); header::HeaderValue::from_static("application/json"));
req.headers_mut().insert(header::CONTENT_LENGTH, req.headers_mut().insert(header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000")); header::HeaderValue::from_static("10000"));
let mut json = req.json::<MyObject>().limit(100);
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow);
let mut req = HttpRequest::default();
req.headers_mut().insert(header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"));
req.headers_mut().insert(header::CONTENT_LENGTH, req.headers_mut().insert(header::CONTENT_LENGTH,
header::HeaderValue::from_static("16")); header::HeaderValue::from_static("16"));
req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
let mut json = req.json::<MyObject>(); let mut json = req.json::<MyObject>();
assert_eq!(json.poll().ok().unwrap(), Async::Ready(MyObject{name: "test".to_owned()})); assert_eq!(json.poll().ok().unwrap(), Async::Ready(MyObject{name: "test".to_owned()}));
} }
} }

View File

@ -1,4 +1,4 @@
//! Actix web is a small, fast, pragmatic, open source rust web framework. //! Actix web is a small, pragmatic, extremely fast, web framework for Rust.
//! //!
//! ```rust //! ```rust
//! use actix_web::*; //! use actix_web::*;
@ -24,30 +24,35 @@
//! * [User Guide](http://actix.github.io/actix-web/guide/) //! * [User Guide](http://actix.github.io/actix-web/guide/)
//! * [Chat on gitter](https://gitter.im/actix/actix) //! * [Chat on gitter](https://gitter.im/actix/actix)
//! * [GitHub repository](https://github.com/actix/actix-web) //! * [GitHub repository](https://github.com/actix/actix-web)
//! * Cargo package: [actix-web](https://crates.io/crates/actix-web) //! * [Cargo package](https://crates.io/crates/actix-web)
//! * Supported Rust version: 1.20 or later //! * Supported Rust version: 1.21 or later
//! //!
//! ## Features //! ## Features
//! //!
//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols //! * Supported *HTTP/1.x* and *HTTP/2.0* protocols
//! * Streaming and pipelining //! * Streaming and pipelining
//! * Keep-alive and slow requests handling //! * Keep-alive and slow requests handling
//! * `WebSockets` //! * `WebSockets` server/client
//! * Transparent content compression/decompression (br, gzip, deflate) //! * Transparent content compression/decompression (br, gzip, deflate)
//! * Configurable request routing //! * Configurable request routing
//! * Multipart streams
//! * Middlewares (`Logger`, `Session`, `DefaultHeaders`)
//! * Graceful server shutdown //! * Graceful server shutdown
//! * Built on top of [Actix](https://github.com/actix/actix). //! * Multipart streams
//! * SSL support with openssl or native-tls
//! * Middlewares (`Logger`, `Session`, `CORS`, `DefaultHeaders`)
//! * Built on top of [Actix actor framework](https://github.com/actix/actix).
#![cfg_attr(actix_nightly, feature( #![cfg_attr(actix_nightly, feature(
specialization, // for impl ErrorResponse for std::error::Error specialization, // for impl ErrorResponse for std::error::Error
))] ))]
#![cfg_attr(feature = "cargo-clippy", allow(
decimal_literal_representation,suspicious_arithmetic_impl,))]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate time; extern crate time;
extern crate base64;
extern crate bytes; extern crate bytes;
extern crate byteorder;
extern crate sha1; extern crate sha1;
extern crate regex; extern crate regex;
#[macro_use] #[macro_use]
@ -66,16 +71,19 @@ extern crate httparse;
extern crate http_range; extern crate http_range;
extern crate mime; extern crate mime;
extern crate mime_guess; extern crate mime_guess;
extern crate rand;
extern crate url; extern crate url;
extern crate libc; extern crate libc;
extern crate serde; extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate flate2; extern crate flate2;
extern crate brotli2; extern crate brotli2;
extern crate encoding;
extern crate percent_encoding; extern crate percent_encoding;
extern crate smallvec; extern crate smallvec;
extern crate num_cpus; extern crate num_cpus;
extern crate h2 as http2; extern crate h2 as http2;
extern crate trust_dns_resolver;
#[macro_use] extern crate actix; #[macro_use] extern crate actix;
#[cfg(test)] #[cfg(test)]
@ -94,18 +102,21 @@ extern crate tokio_openssl;
mod application; mod application;
mod body; mod body;
mod context; mod context;
mod handler;
mod helpers; mod helpers;
mod httpmessage;
mod httprequest; mod httprequest;
mod httpresponse; mod httpresponse;
mod info; mod info;
mod json; mod json;
mod route; mod route;
mod router; mod router;
mod param;
mod resource; mod resource;
mod handler; mod param;
mod payload;
mod pipeline; mod pipeline;
pub mod client;
pub mod fs; pub mod fs;
pub mod ws; pub mod ws;
pub mod error; pub mod error;
@ -114,12 +125,12 @@ pub mod multipart;
pub mod middleware; pub mod middleware;
pub mod pred; pub mod pred;
pub mod test; pub mod test;
pub mod payload;
pub mod server; pub mod server;
pub use error::{Error, Result, ResponseError}; pub use error::{Error, Result, ResponseError};
pub use body::{Body, Binary}; pub use body::{Body, Binary};
pub use json::Json; pub use json::Json;
pub use application::Application; pub use application::Application;
pub use httpmessage::HttpMessage;
pub use httprequest::HttpRequest; pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse; pub use httpresponse::HttpResponse;
pub use handler::{Reply, Responder, NormalizePath, AsyncResponder}; pub use handler::{Reply, Responder, NormalizePath, AsyncResponder};
@ -131,13 +142,16 @@ pub use server::HttpServer;
// re-exports // re-exports
pub use http::{Method, StatusCode, Version}; pub use http::{Method, StatusCode, Version};
#[doc(hidden)]
#[cfg(feature="tls")]
pub use native_tls::Pkcs12;
#[doc(hidden)]
#[cfg(feature="openssl")] #[cfg(feature="openssl")]
pub use openssl::pkcs12::Pkcs12; pub(crate) const HAS_OPENSSL: bool = true;
#[cfg(not(feature="openssl"))]
pub(crate) const HAS_OPENSSL: bool = false;
// #[cfg(feature="tls")]
// pub(crate) const HAS_TLS: bool = true;
// #[cfg(not(feature="tls"))]
// pub(crate) const HAS_TLS: bool = false;
pub mod headers { pub mod headers {
//! Headers implementation //! Headers implementation
@ -175,11 +189,12 @@ pub mod dev {
//! ``` //! ```
pub use body::BodyStream; pub use body::BodyStream;
pub use context::Drain;
pub use info::ConnectionInfo; pub use info::ConnectionInfo;
pub use handler::Handler; pub use handler::Handler;
pub use json::JsonBody; pub use json::JsonBody;
pub use router::{Router, Pattern}; pub use router::{Router, Pattern};
pub use param::{FromParam, Params}; pub use param::{FromParam, Params};
pub use httprequest::{UrlEncoded, RequestBody}; pub use httpmessage::{UrlEncoded, MessageBody};
pub use httpresponse::HttpResponseBuilder; pub use httpresponse::HttpResponseBuilder;
} }

View File

@ -38,8 +38,8 @@
//! .max_age(3600) //! .max_age(3600)
//! .finish().expect("Can not create CORS middleware") //! .finish().expect("Can not create CORS middleware")
//! .register(r); // <- Register CORS middleware //! .register(r); // <- Register CORS middleware
//! r.method(Method::GET).f(|_| httpcodes::HTTPOk); //! r.method(Method::GET).f(|_| httpcodes::HttpOk);
//! r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); //! r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
//! }) //! })
//! .finish(); //! .finish();
//! } //! }
@ -55,9 +55,10 @@ use http::header::{self, HeaderName, HeaderValue};
use error::{Result, ResponseError}; use error::{Result, ResponseError};
use resource::Resource; use resource::Resource;
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use httpcodes::{HTTPOk, HTTPBadRequest}; use httpcodes::{HttpOk, HttpBadRequest};
use middleware::{Middleware, Response, Started}; use middleware::{Middleware, Response, Started};
/// A set of errors that can occur during processing CORS /// A set of errors that can occur during processing CORS
@ -109,7 +110,7 @@ pub enum CorsBuilderError {
impl ResponseError for CorsError { impl ResponseError for CorsError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HTTPBadRequest.build().body(format!("{}", self)).unwrap() HttpBadRequest.build().body(format!("{}", self)).unwrap()
} }
} }
@ -214,11 +215,11 @@ impl Cors {
/// This method register cors middleware with resource and /// This method register cors middleware with resource and
/// adds route for *OPTIONS* preflight requests. /// adds route for *OPTIONS* preflight requests.
/// ///
/// It is possible to register *Cors* middlware with `Resource::middleware()` /// It is possible to register *Cors* middleware with `Resource::middleware()`
/// method, but in that case *Cors* middleware wont be able to handle *OPTIONS* /// method, but in that case *Cors* middleware wont be able to handle *OPTIONS*
/// requests. /// requests.
pub fn register<S: 'static>(self, resource: &mut Resource<S>) { pub fn register<S: 'static>(self, resource: &mut Resource<S>) {
resource.method(Method::OPTIONS).h(HTTPOk); resource.method(Method::OPTIONS).h(HttpOk);
resource.middleware(self); resource.middleware(self);
} }
@ -295,16 +296,23 @@ impl<S> Middleware<S> for Cors {
self.validate_allowed_method(req)?; self.validate_allowed_method(req)?;
self.validate_allowed_headers(req)?; self.validate_allowed_headers(req)?;
// allowed headers
let headers = if let Some(headers) = self.headers.as_ref() {
Some(HeaderValue::try_from(&headers.iter().fold(
String::new(), |s, v| s + "," + v.as_str()).as_str()[1..]).unwrap())
} else if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) {
Some(hdr.clone())
} else {
None
};
Ok(Started::Response( Ok(Started::Response(
HTTPOk.build() HttpOk.build()
.if_some(self.max_age.as_ref(), |max_age, resp| { .if_some(self.max_age.as_ref(), |max_age, resp| {
let _ = resp.header( let _ = resp.header(
header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());}) header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());})
.if_some(self.headers.as_ref(), |headers, resp| { .if_some(headers, |headers, resp| {
let _ = resp.header( let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); })
header::ACCESS_CONTROL_ALLOW_HEADERS,
&headers.iter().fold(
String::new(), |s, v| s + "," + v.as_str()).as_str()[1..]);})
.if_true(self.origins.is_all(), |resp| { .if_true(self.origins.is_all(), |resp| {
if self.send_wildcard { if self.send_wildcard {
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
@ -434,7 +442,6 @@ impl CorsBuilder {
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
/// ///
/// Defaults to `All`. /// Defaults to `All`.
/// ```
pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder { pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
match Uri::try_from(origin) { match Uri::try_from(origin) {
@ -572,7 +579,7 @@ impl CorsBuilder {
/// Set a wildcard origins /// Set a wildcard origins
/// ///
/// If send widlcard is set and the `allowed_origins` parameter is `All`, a wildcard /// If send wildcard is set and the `allowed_origins` parameter is `All`, a wildcard
/// `Access-Control-Allow-Origin` response header is sent, rather than the requests /// `Access-Control-Allow-Origin` response header is sent, rather than the requests
/// `Origin` header. /// `Origin` header.
/// ///
@ -596,7 +603,7 @@ impl CorsBuilder {
/// If true, injects the `Access-Control-Allow-Credentials` header in responses. /// If true, injects the `Access-Control-Allow-Credentials` header in responses.
/// This allows cookies and credentials to be submitted across domains. /// This allows cookies and credentials to be submitted across domains.
/// ///
/// This option cannot be used in conjuction with an `allowed_origin` set to `All` /// This option cannot be used in conjunction with an `allowed_origin` set to `All`
/// and `send_wildcards` set to `true`. /// and `send_wildcards` set to `true`.
/// ///
/// Defaults to `false`. /// Defaults to `false`.
@ -816,7 +823,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.finish(); .finish();
let resp: HttpResponse = HTTPOk.into(); let resp: HttpResponse = HttpOk.into();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
@ -825,7 +832,7 @@ mod tests {
&b"Origin"[..], &b"Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes()); resp.headers().get(header::VARY).unwrap().as_bytes());
let resp: HttpResponse = HTTPOk.build() let resp: HttpResponse = HttpOk.build()
.header(header::VARY, "Accept") .header(header::VARY, "Accept")
.finish().unwrap(); .finish().unwrap();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
@ -837,7 +844,7 @@ mod tests {
.disable_vary_header() .disable_vary_header()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.finish().unwrap(); .finish().unwrap();
let resp: HttpResponse = HTTPOk.into(); let resp: HttpResponse = HttpOk.into();
let resp = cors.response(&mut req, resp).unwrap().response(); let resp = cors.response(&mut req, resp).unwrap().response();
assert_eq!( assert_eq!(
&b"https://www.example.com"[..], &b"https://www.example.com"[..],

266
src/middleware/csrf.rs Normal file
View File

@ -0,0 +1,266 @@
//! A filter for cross-site request forgery (CSRF).
//!
//! This middleware is stateless and [based on request
//! headers](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers).
//!
//! By default requests are allowed only if one of these is true:
//!
//! * The request method is safe (`GET`, `HEAD`, `OPTIONS`). It is the
//! applications responsibility to ensure these methods cannot be used to
//! execute unwanted actions. Note that upgrade requests for websockets are
//! also considered safe.
//! * The `Origin` header (added automatically by the browser) matches one
//! of the allowed origins.
//! * There is no `Origin` header but the `Referer` header matches one of
//! the allowed origins.
//!
//! Use [`CsrfFilterBuilder::allow_xhr()`](struct.CsrfFilterBuilder.html#method.allow_xhr)
//! if you want to allow requests with unsafe methods via
//! [CORS](../cors/struct.Cors.html).
//!
//! # Example
//!
//! ```
//! # extern crate actix_web;
//! # use actix_web::*;
//!
//! use actix_web::middleware::csrf;
//!
//! fn handle_post(_req: HttpRequest) -> &'static str {
//! "This action should only be triggered with requests from the same site"
//! }
//!
//! fn main() {
//! let app = Application::new()
//! .middleware(
//! csrf::CsrfFilter::build()
//! .allowed_origin("https://www.example.com")
//! .finish())
//! .resource("/", |r| {
//! r.method(Method::GET).f(|_| httpcodes::HttpOk);
//! r.method(Method::POST).f(handle_post);
//! })
//! .finish();
//! }
//! ```
//!
//! In this example the entire application is protected from CSRF.
use std::borrow::Cow;
use std::collections::HashSet;
use bytes::Bytes;
use error::{Result, ResponseError};
use http::{HeaderMap, HttpTryFrom, Uri, header};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use httpmessage::HttpMessage;
use httpcodes::HttpForbidden;
use middleware::{Middleware, Started};
/// Potential cross-site request forgery detected.
#[derive(Debug, Fail)]
pub enum CsrfError {
/// The HTTP request header `Origin` was required but not provided.
#[fail(display="Origin header required")]
MissingOrigin,
/// The HTTP request header `Origin` could not be parsed correctly.
#[fail(display="Could not parse Origin header")]
BadOrigin,
/// The cross-site request was denied.
#[fail(display="Cross-site request denied")]
CsrDenied,
}
impl ResponseError for CsrfError {
fn error_response(&self) -> HttpResponse {
HttpForbidden.build().body(self.to_string()).unwrap()
}
}
fn uri_origin(uri: &Uri) -> Option<String> {
match (uri.scheme_part(), uri.host(), uri.port()) {
(Some(scheme), Some(host), Some(port)) => {
Some(format!("{}://{}:{}", scheme, host, port))
}
(Some(scheme), Some(host), None) => {
Some(format!("{}://{}", scheme, host))
}
_ => None
}
}
fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
headers.get(header::ORIGIN)
.map(|origin| {
origin
.to_str()
.map_err(|_| CsrfError::BadOrigin)
.map(|o| o.into())
})
.or_else(|| {
headers.get(header::REFERER)
.map(|referer| {
Uri::try_from(Bytes::from(referer.as_bytes()))
.ok()
.as_ref()
.and_then(uri_origin)
.ok_or(CsrfError::BadOrigin)
.map(|o| o.into())
})
})
}
/// A middleware that filters cross-site requests.
pub struct CsrfFilter {
origins: HashSet<String>,
allow_xhr: bool,
allow_missing_origin: bool,
}
impl CsrfFilter {
/// Start building a `CsrfFilter`.
pub fn build() -> CsrfFilterBuilder {
CsrfFilterBuilder {
cors: CsrfFilter {
origins: HashSet::new(),
allow_xhr: false,
allow_missing_origin: false,
}
}
}
fn validate<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CsrfError> {
if req.method().is_safe() || (self.allow_xhr && req.headers().contains_key("x-requested-with")) {
Ok(())
} else if let Some(header) = origin(req.headers()) {
match header {
Ok(ref origin) if self.origins.contains(origin.as_ref()) => Ok(()),
Ok(_) => Err(CsrfError::CsrDenied),
Err(err) => Err(err),
}
} else if self.allow_missing_origin {
Ok(())
} else {
Err(CsrfError::MissingOrigin)
}
}
}
impl<S> Middleware<S> for CsrfFilter {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
self.validate(req)?;
Ok(Started::Done)
}
}
/// Used to build a `CsrfFilter`.
///
/// To construct a CSRF filter:
///
/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to
/// start building.
/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed
/// origins.
/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve
/// the constructed filter.
///
/// # Example
///
/// ```
/// use actix_web::middleware::csrf;
///
/// let csrf = csrf::CsrfFilter::build()
/// .allowed_origin("https://www.example.com")
/// .finish();
/// ```
pub struct CsrfFilterBuilder {
cors: CsrfFilter,
}
impl CsrfFilterBuilder {
/// Add an origin that is allowed to make requests. Will be verified
/// against the `Origin` request header.
pub fn allowed_origin(mut self, origin: &str) -> CsrfFilterBuilder {
self.cors.origins.insert(origin.to_owned());
self
}
/// Allow all requests with an `X-Requested-With` header.
///
/// A cross-site attacker should not be able to send requests with custom
/// headers unless a CORS policy whitelists them. Therefore it should be
/// safe to allow requests with an `X-Requested-With` header (added
/// automatically by many JavaScript libraries).
///
/// This is disabled by default, because in Safari it is possible to
/// circumvent this using redirects and Flash.
///
/// Use this method to enable more lax filtering.
pub fn allow_xhr(mut self) -> CsrfFilterBuilder {
self.cors.allow_xhr = true;
self
}
/// Allow requests if the expected `Origin` header is missing (and
/// there is no `Referer` to fall back on).
///
/// The filter is conservative by default, but it should be safe to allow
/// missing `Origin` headers because a cross-site attacker cannot prevent
/// the browser from sending `Origin` on unsafe requests.
pub fn allow_missing_origin(mut self) -> CsrfFilterBuilder {
self.cors.allow_missing_origin = true;
self
}
/// Finishes building the `CsrfFilter` instance.
pub fn finish(self) -> CsrfFilter {
self.cors
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::Method;
use test::TestRequest;
#[test]
fn test_safe() {
let csrf = CsrfFilter::build()
.allowed_origin("https://www.example.com")
.finish();
let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::HEAD)
.finish();
assert!(csrf.start(&mut req).is_ok());
}
#[test]
fn test_csrf() {
let csrf = CsrfFilter::build()
.allowed_origin("https://www.example.com")
.finish();
let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::POST)
.finish();
assert!(csrf.start(&mut req).is_err());
}
#[test]
fn test_referer() {
let csrf = CsrfFilter::build()
.allowed_origin("https://www.example.com")
.finish();
let mut req = TestRequest::with_header("Referer", "https://www.example.com/some/path?query=param")
.method(Method::POST)
.finish();
assert!(csrf.start(&mut req).is_ok());
}
}

View File

@ -22,8 +22,8 @@ use middleware::{Response, Middleware};
/// .header("X-Version", "0.2") /// .header("X-Version", "0.2")
/// .finish()) /// .finish())
/// .resource("/test", |r| { /// .resource("/test", |r| {
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::GET).f(|_| httpcodes::HttpOk);
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); /// r.method(Method::HEAD).f(|_| httpcodes::HttpMethodNotAllowed);
/// }) /// })
/// .finish(); /// .finish();
/// } /// }
@ -95,7 +95,7 @@ impl DefaultHeadersBuilder {
/// Finishes building and returns the built `DefaultHeaders` middleware. /// Finishes building and returns the built `DefaultHeaders` middleware.
pub fn finish(&mut self) -> DefaultHeaders { pub fn finish(&mut self) -> DefaultHeaders {
let headers = self.headers.take().expect("cannot reuse middleware builder"); let headers = self.headers.take().expect("cannot reuse middleware builder");
DefaultHeaders{ ct: self.ct, headers: headers } DefaultHeaders{ ct: self.ct, headers }
} }
} }

View File

@ -8,11 +8,15 @@ use time;
use regex::Regex; use regex::Regex;
use error::Result; use error::Result;
use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::{Middleware, Started, Finished}; use middleware::{Middleware, Started, Finished};
/// `Middleware` for logging request and response info to the terminal. /// `Middleware` for logging request and response info to the terminal.
/// `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 /// ## Usage
/// ///
@ -24,10 +28,14 @@ use middleware::{Middleware, Started, Finished};
/// ``` /// ```
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
/// extern crate env_logger;
/// use actix_web::Application; /// use actix_web::Application;
/// use actix_web::middleware::Logger; /// use actix_web::middleware::Logger;
/// ///
/// fn main() { /// fn main() {
/// std::env::set_var("RUST_LOG", "actix_web=info");
/// env_logger::init();
///
/// let app = Application::new() /// let app = Application::new()
/// .middleware(Logger::default()) /// .middleware(Logger::default())
/// .middleware(Logger::new("%a %{User-Agent}i")) /// .middleware(Logger::new("%a %{User-Agent}i"))

View File

@ -9,6 +9,7 @@ mod logger;
mod session; mod session;
mod defaultheaders; mod defaultheaders;
pub mod cors; pub mod cors;
pub mod csrf;
pub use self::logger::Logger; pub use self::logger::Logger;
pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder}; pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder};
pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage, pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage,

View File

@ -1,6 +1,3 @@
#![allow(dead_code, unused_imports, unused_variables)]
use std::any::Any;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -49,8 +46,7 @@ impl<S> RequestSession for HttpRequest<S> {
return Session(s.0.as_mut()) return Session(s.0.as_mut())
} }
} }
//Session(&mut DUMMY) Session(unsafe{&mut DUMMY})
unreachable!()
} }
} }
@ -90,7 +86,7 @@ impl<'a> Session<'a> {
} }
/// Set a `value` from the session. /// Set a `value` from the session.
pub fn set<T: Serialize>(&'a mut self, key: &str, value: T) -> Result<()> { pub fn set<T: Serialize>(&mut self, key: &str, value: T) -> Result<()> {
self.0.set(key, serde_json::to_string(&value)?); self.0.set(key, serde_json::to_string(&value)?);
Ok(()) Ok(())
} }
@ -195,15 +191,13 @@ pub trait SessionBackend<S>: Sized + 'static {
/// Dummy session impl, does not do anything /// Dummy session impl, does not do anything
struct DummySessionImpl; struct DummySessionImpl;
static DUMMY: DummySessionImpl = DummySessionImpl; static mut DUMMY: DummySessionImpl = DummySessionImpl;
impl SessionImpl for DummySessionImpl { impl SessionImpl for DummySessionImpl {
fn get(&self, key: &str) -> Option<&str> { fn get(&self, _: &str) -> Option<&str> { None }
None fn set(&mut self, _: &str, _: String) {}
} fn remove(&mut self, _: &str) {}
fn set(&mut self, key: &str, value: String) {}
fn remove(&mut self, key: &str) {}
fn clear(&mut self) {} fn clear(&mut self) {}
fn write(&self, resp: HttpResponse) -> Result<Response> { fn write(&self, resp: HttpResponse) -> Result<Response> {
Ok(Response::Done(resp)) Ok(Response::Done(resp))
@ -217,7 +211,7 @@ pub struct CookieSession {
inner: Rc<CookieSessionInner>, inner: Rc<CookieSessionInner>,
} }
/// Errors that can occure during handling cookie session /// Errors that can occur during handling cookie session
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
pub enum CookieSessionError { pub enum CookieSessionError {
/// Size of the serialized session is greater than 4000 bytes. /// Size of the serialized session is greater than 4000 bytes.
@ -377,8 +371,8 @@ impl<S> SessionBackend<S> for CookieSessionBackend {
FutOk( FutOk(
CookieSession { CookieSession {
changed: false, changed: false,
state: state,
inner: Rc::clone(&self.0), inner: Rc::clone(&self.0),
state,
}) })
} }
} }

View File

@ -9,12 +9,11 @@ use httparse;
use bytes::Bytes; use bytes::Bytes;
use http::HttpTryFrom; use http::HttpTryFrom;
use http::header::{self, HeaderMap, HeaderName, HeaderValue}; use http::header::{self, HeaderMap, HeaderName, HeaderValue};
use futures::{Async, Future, Stream, Poll}; use futures::{Async, Stream, Poll};
use futures::task::{Task, current as current_task}; use futures::task::{Task, current as current_task};
use error::{ParseError, PayloadError, MultipartError}; use error::{ParseError, PayloadError, MultipartError};
use payload::Payload; use payload::PayloadHelper;
use httprequest::HttpRequest;
const MAX_HEADERS: usize = 32; const MAX_HEADERS: usize = 32;
@ -24,27 +23,24 @@ const MAX_HEADERS: usize = 32;
/// Stream implementation. /// Stream implementation.
/// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart` /// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart`
/// is used for nested multipart streams. /// is used for nested multipart streams.
#[derive(Debug)] pub struct Multipart<S> {
pub struct Multipart {
safety: Safety, safety: Safety,
error: Option<MultipartError>, error: Option<MultipartError>,
inner: Option<Rc<RefCell<InnerMultipart>>>, inner: Option<Rc<RefCell<InnerMultipart<S>>>>,
} }
/// ///
#[derive(Debug)] pub enum MultipartItem<S> {
pub enum MultipartItem {
/// Multipart field /// Multipart field
Field(Field), Field(Field<S>),
/// Nested multipart stream /// Nested multipart stream
Nested(Multipart), Nested(Multipart<S>),
} }
#[derive(Debug)] enum InnerMultipartItem<S> {
enum InnerMultipartItem {
None, None,
Field(Rc<RefCell<InnerField>>), Field(Rc<RefCell<InnerField<S>>>),
Multipart(Rc<RefCell<InnerMultipart>>), Multipart(Rc<RefCell<InnerMultipart<S>>>),
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -59,44 +55,14 @@ enum InnerState {
Headers, Headers,
} }
#[derive(Debug)] struct InnerMultipart<S> {
struct InnerMultipart { payload: PayloadRef<S>,
payload: PayloadRef,
boundary: String, boundary: String,
state: InnerState, state: InnerState,
item: InnerMultipartItem, item: InnerMultipartItem<S>,
} }
impl Multipart { impl Multipart<()> {
/// Create multipart instance for boundary.
pub fn new(boundary: String, payload: Payload) -> Multipart {
Multipart {
error: None,
safety: Safety::new(),
inner: Some(Rc::new(RefCell::new(
InnerMultipart {
payload: PayloadRef::new(payload),
boundary: boundary,
state: InnerState::FirstBoundary,
item: InnerMultipartItem::None,
})))
}
}
/// Create multipart instance for request.
pub fn from_request<S>(req: &mut HttpRequest<S>) -> Multipart {
match Multipart::boundary(req.headers()) {
Ok(boundary) => Multipart::new(boundary, req.payload().clone()),
Err(err) =>
Multipart {
error: Some(err),
safety: Safety::new(),
inner: None,
}
}
}
/// Extract boundary info from headers. /// Extract boundary info from headers.
pub fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> { pub fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> {
if let Some(content_type) = headers.get(header::CONTENT_TYPE) { if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
@ -119,8 +85,34 @@ impl Multipart {
} }
} }
impl Stream for Multipart { impl<S> Multipart<S> where S: Stream<Item=Bytes, Error=PayloadError> {
type Item = MultipartItem;
/// Create multipart instance for boundary.
pub fn new(boundary: Result<String, MultipartError>, stream: S) -> Multipart<S> {
match boundary {
Ok(boundary) => Multipart {
error: None,
safety: Safety::new(),
inner: Some(Rc::new(RefCell::new(
InnerMultipart {
boundary,
payload: PayloadRef::new(PayloadHelper::new(stream)),
state: InnerState::FirstBoundary,
item: InnerMultipartItem::None,
})))
},
Err(err) =>
Multipart {
error: Some(err),
safety: Safety::new(),
inner: None,
}
}
}
}
impl<S> Stream for Multipart<S> where S: Stream<Item=Bytes, Error=PayloadError> {
type Item = MultipartItem<S>;
type Error = MultipartError; type Error = MultipartError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
@ -134,13 +126,14 @@ impl Stream for Multipart {
} }
} }
impl InnerMultipart { impl<S> InnerMultipart<S> where S: Stream<Item=Bytes, Error=PayloadError> {
fn read_headers(payload: &mut Payload) -> Poll<HeaderMap, MultipartError> fn read_headers(payload: &mut PayloadHelper<S>) -> Poll<HeaderMap, MultipartError>
{ {
match payload.readuntil(b"\r\n\r\n").poll()? { match payload.readuntil(b"\r\n\r\n")? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(bytes) => { Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(bytes)) => {
let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS];
match httparse::parse_headers(&bytes, &mut hdrs) { match httparse::parse_headers(&bytes, &mut hdrs) {
Ok(httparse::Status::Complete((_, hdrs))) => { Ok(httparse::Status::Complete((_, hdrs))) => {
@ -166,12 +159,14 @@ impl InnerMultipart {
} }
} }
fn read_boundary(payload: &mut Payload, boundary: &str) -> Poll<bool, MultipartError> fn read_boundary(payload: &mut PayloadHelper<S>, boundary: &str)
-> Poll<bool, MultipartError>
{ {
// TODO: need to read epilogue // TODO: need to read epilogue
match payload.readline().poll()? { match payload.readline()? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(chunk) => { Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => {
if chunk.len() == boundary.len() + 4 && if chunk.len() == boundary.len() + 4 &&
&chunk[..2] == b"--" && &chunk[..2] == b"--" &&
&chunk[2..boundary.len()+2] == boundary.as_bytes() &chunk[2..boundary.len()+2] == boundary.as_bytes()
@ -190,39 +185,42 @@ impl InnerMultipart {
} }
} }
fn skip_until_boundary(payload: &mut Payload, boundary: &str) -> Poll<bool, MultipartError> fn skip_until_boundary(payload: &mut PayloadHelper<S>, boundary: &str)
-> Poll<bool, MultipartError>
{ {
let mut eof = false; let mut eof = false;
loop { loop {
if let Async::Ready(chunk) = payload.readline().poll()? { match payload.readline()? {
if chunk.is_empty() { Async::Ready(Some(chunk)) => {
//ValueError("Could not find starting boundary %r" if chunk.is_empty() {
//% (self._boundary)) //ValueError("Could not find starting boundary %r"
} //% (self._boundary))
if chunk.len() < boundary.len() { }
continue if chunk.len() < boundary.len() {
}
if &chunk[..2] == b"--" && &chunk[2..chunk.len()-2] == boundary.as_bytes() {
break;
} else {
if chunk.len() < boundary.len() + 2{
continue continue
} }
let b: &[u8] = boundary.as_ref(); if &chunk[..2] == b"--" && &chunk[2..chunk.len()-2] == boundary.as_bytes() {
if &chunk[..boundary.len()] == b && break;
&chunk[boundary.len()..boundary.len()+2] == b"--" { } else {
eof = true; if chunk.len() < boundary.len() + 2{
break; continue
} }
} let b: &[u8] = boundary.as_ref();
} else { if &chunk[..boundary.len()] == b &&
return Ok(Async::NotReady) &chunk[boundary.len()..boundary.len()+2] == b"--" {
eof = true;
break;
}
}
},
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(None) => return Err(MultipartError::Incomplete),
} }
} }
Ok(Async::Ready(eof)) Ok(Async::Ready(eof))
} }
fn poll(&mut self, safety: &Safety) -> Poll<Option<MultipartItem>, MultipartError> { fn poll(&mut self, safety: &Safety) -> Poll<Option<MultipartItem<S>>, MultipartError> {
if self.state == InnerState::Eof { if self.state == InnerState::Eof {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
@ -234,25 +232,18 @@ impl InnerMultipart {
let stop = match self.item { let stop = match self.item {
InnerMultipartItem::Field(ref mut field) => { InnerMultipartItem::Field(ref mut field) => {
match field.borrow_mut().poll(safety)? { match field.borrow_mut().poll(safety)? {
Async::NotReady => { Async::NotReady => return Ok(Async::NotReady),
return Ok(Async::NotReady) Async::Ready(Some(_)) => continue,
} Async::Ready(None) => true,
Async::Ready(Some(_)) =>
continue,
Async::Ready(None) =>
true,
} }
} },
InnerMultipartItem::Multipart(ref mut multipart) => { InnerMultipartItem::Multipart(ref mut multipart) => {
match multipart.borrow_mut().poll(safety)? { match multipart.borrow_mut().poll(safety)? {
Async::NotReady => Async::NotReady => return Ok(Async::NotReady),
return Ok(Async::NotReady), Async::Ready(Some(_)) => continue,
Async::Ready(Some(_)) => Async::Ready(None) => true,
continue,
Async::Ready(None) =>
true,
} }
} },
_ => false, _ => false,
}; };
if stop { if stop {
@ -268,25 +259,22 @@ impl InnerMultipart {
match self.state { match self.state {
// read until first boundary // read until first boundary
InnerState::FirstBoundary => { InnerState::FirstBoundary => {
if let Async::Ready(eof) = match InnerMultipart::skip_until_boundary(payload, &self.boundary)? {
InnerMultipart::skip_until_boundary(payload, &self.boundary)? Async::Ready(eof) => {
{ if eof {
if eof { self.state = InnerState::Eof;
self.state = InnerState::Eof; return Ok(Async::Ready(None));
return Ok(Async::Ready(None)); } else {
} else { self.state = InnerState::Headers;
self.state = InnerState::Headers; }
} },
} else { Async::NotReady => return Ok(Async::NotReady),
return Ok(Async::NotReady)
} }
} },
// read boundary // read boundary
InnerState::Boundary => { InnerState::Boundary => {
match InnerMultipart::read_boundary(payload, &self.boundary)? { match InnerMultipart::read_boundary(payload, &self.boundary)? {
Async::NotReady => { Async::NotReady => return Ok(Async::NotReady),
return Ok(Async::NotReady)
}
Async::Ready(eof) => { Async::Ready(eof) => {
if eof { if eof {
self.state = InnerState::Eof; self.state = InnerState::Eof;
@ -362,7 +350,7 @@ impl InnerMultipart {
} }
} }
impl Drop for InnerMultipart { impl<S> Drop for InnerMultipart<S> {
fn drop(&mut self) { fn drop(&mut self) {
// InnerMultipartItem::Field has to be dropped first because of Safety. // InnerMultipartItem::Field has to be dropped first because of Safety.
self.item = InnerMultipartItem::None; self.item = InnerMultipartItem::None;
@ -370,23 +358,18 @@ impl Drop for InnerMultipart {
} }
/// A single field in a multipart stream /// A single field in a multipart stream
pub struct Field { pub struct Field<S> {
ct: mime::Mime, ct: mime::Mime,
headers: HeaderMap, headers: HeaderMap,
inner: Rc<RefCell<InnerField>>, inner: Rc<RefCell<InnerField<S>>>,
safety: Safety, safety: Safety,
} }
impl Field { impl<S> Field<S> where S: Stream<Item=Bytes, Error=PayloadError> {
fn new(safety: Safety, headers: HeaderMap, fn new(safety: Safety, headers: HeaderMap,
ct: mime::Mime, inner: Rc<RefCell<InnerField>>) -> Self { ct: mime::Mime, inner: Rc<RefCell<InnerField<S>>>) -> Self {
Field { Field {ct, headers, inner, safety}
ct: ct,
headers: headers,
inner: inner,
safety: safety,
}
} }
pub fn headers(&self) -> &HeaderMap { pub fn headers(&self) -> &HeaderMap {
@ -398,7 +381,7 @@ impl Field {
} }
} }
impl Stream for Field { impl<S> Stream for Field<S> where S: Stream<Item=Bytes, Error=PayloadError> {
type Item = Bytes; type Item = Bytes;
type Error = MultipartError; type Error = MultipartError;
@ -411,7 +394,7 @@ impl Stream for Field {
} }
} }
impl fmt::Debug for Field { impl<S> fmt::Debug for Field<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nMultipartField: {}\n", self.ct); let res = write!(f, "\nMultipartField: {}\n", self.ct);
let _ = write!(f, " boundary: {}\n", self.inner.borrow().boundary); let _ = write!(f, " boundary: {}\n", self.inner.borrow().boundary);
@ -428,18 +411,17 @@ impl fmt::Debug for Field {
} }
} }
#[derive(Debug)] struct InnerField<S> {
struct InnerField { payload: Option<PayloadRef<S>>,
payload: Option<PayloadRef>,
boundary: String, boundary: String,
eof: bool, eof: bool,
length: Option<u64>, length: Option<u64>,
} }
impl InnerField { impl<S> InnerField<S> where S: Stream<Item=Bytes, Error=PayloadError> {
fn new(payload: PayloadRef, boundary: String, headers: &HeaderMap) fn new(payload: PayloadRef<S>, boundary: String, headers: &HeaderMap)
-> Result<InnerField, PayloadError> -> Result<InnerField<S>, PayloadError>
{ {
let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() { if let Ok(s) = len.to_str() {
@ -456,22 +438,23 @@ impl InnerField {
}; };
Ok(InnerField { Ok(InnerField {
boundary,
payload: Some(payload), payload: Some(payload),
boundary: boundary,
eof: false, eof: false,
length: len }) length: len })
} }
/// Reads body part content chunk of the specified size. /// Reads body part content chunk of the specified size.
/// The body part must has `Content-Length` header with proper value. /// The body part must has `Content-Length` header with proper value.
fn read_len(payload: &mut Payload, size: &mut u64) -> Poll<Option<Bytes>, MultipartError> fn read_len(payload: &mut PayloadHelper<S>, size: &mut u64)
-> Poll<Option<Bytes>, MultipartError>
{ {
if *size == 0 { if *size == 0 {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
match payload.readany().poll() { match payload.readany() {
Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Ok(Async::Ready(None)) => Err(MultipartError::Incomplete),
Ok(Async::Ready(Some(mut chunk))) => { Ok(Async::Ready(Some(mut chunk))) => {
let len = cmp::min(chunk.len() as u64, *size); let len = cmp::min(chunk.len() as u64, *size);
*size -= len; *size -= len;
@ -488,23 +471,26 @@ impl InnerField {
/// Reads content chunk of body part with unknown length. /// Reads content chunk of body part with unknown length.
/// The `Content-Length` header for body part is not necessary. /// The `Content-Length` header for body part is not necessary.
fn read_stream(payload: &mut Payload, boundary: &str) -> Poll<Option<Bytes>, MultipartError> fn read_stream(payload: &mut PayloadHelper<S>, boundary: &str)
-> Poll<Option<Bytes>, MultipartError>
{ {
match payload.readuntil(b"\r").poll()? { match payload.readuntil(b"\r")? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(mut chunk) => { Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(mut chunk)) => {
if chunk.len() == 1 { if chunk.len() == 1 {
payload.unread_data(chunk); payload.unread_data(chunk);
match payload.readexactly(boundary.len() + 4).poll()? { match payload.readexactly(boundary.len() + 4)? {
Async::NotReady => Ok(Async::NotReady), Async::NotReady => Ok(Async::NotReady),
Async::Ready(chunk) => { Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => {
if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" && if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" &&
&chunk[4..] == boundary.as_bytes() &chunk[4..] == boundary.as_bytes()
{ {
payload.unread_data(chunk); payload.unread_data(chunk.freeze());
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
Ok(Async::Ready(Some(chunk))) Ok(Async::Ready(Some(chunk.freeze())))
} }
} }
} }
@ -522,24 +508,6 @@ impl InnerField {
if self.payload.is_none() { if self.payload.is_none() {
return Ok(Async::Ready(None)) return Ok(Async::Ready(None))
} }
if self.eof {
if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
match payload.readline().poll()? {
Async::NotReady =>
return Ok(Async::NotReady),
Async::Ready(chunk) => {
assert_eq!(
chunk.as_ref(), b"\r\n",
"reader did not read all the data or it is malformed");
}
}
} else {
return Ok(Async::NotReady);
}
self.payload.take();
return Ok(Async::Ready(None))
}
let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
let res = if let Some(ref mut len) = self.length { let res = if let Some(ref mut len) = self.length {
@ -553,12 +521,13 @@ impl InnerField {
Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)),
Async::Ready(None) => { Async::Ready(None) => {
self.eof = true; self.eof = true;
match payload.readline().poll()? { match payload.readline()? {
Async::NotReady => Async::NotReady, Async::NotReady => Async::NotReady,
Async::Ready(chunk) => { Async::Ready(None) => Async::Ready(None),
assert_eq!( Async::Ready(Some(line)) => {
chunk.as_ref(), b"\r\n", if line.as_ref() != b"\r\n" {
"reader did not read all the data or it is malformed"); warn!("multipart field did not read all the data or it is malformed");
}
Async::Ready(None) Async::Ready(None)
} }
} }
@ -575,25 +544,22 @@ impl InnerField {
} }
} }
#[derive(Debug)] struct PayloadRef<S> {
struct PayloadRef { payload: Rc<PayloadHelper<S>>,
task: Option<Task>,
payload: Rc<Payload>,
} }
impl PayloadRef { impl<S> PayloadRef<S> where S: Stream<Item=Bytes, Error=PayloadError> {
fn new(payload: Payload) -> PayloadRef { fn new(payload: PayloadHelper<S>) -> PayloadRef<S> {
PayloadRef { PayloadRef {
task: None,
payload: Rc::new(payload), payload: Rc::new(payload),
} }
} }
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut Payload> fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadHelper<S>>
where 'a: 'b where 'a: 'b
{ {
if s.current() { if s.current() {
let payload: &mut Payload = unsafe { let payload: &mut PayloadHelper<S> = unsafe {
&mut *(self.payload.as_ref() as *const _ as *mut _)}; &mut *(self.payload.as_ref() as *const _ as *mut _)};
Some(payload) Some(payload)
} else { } else {
@ -602,10 +568,9 @@ impl PayloadRef {
} }
} }
impl Clone for PayloadRef { impl<S> Clone for PayloadRef<S> {
fn clone(&self) -> PayloadRef { fn clone(&self) -> PayloadRef<S> {
PayloadRef { PayloadRef {
task: Some(current_task()),
payload: Rc::clone(&self.payload), payload: Rc::clone(&self.payload),
} }
} }
@ -626,7 +591,7 @@ impl Safety {
Safety { Safety {
task: None, task: None,
level: Rc::strong_count(&payload), level: Rc::strong_count(&payload),
payload: payload, payload,
} }
} }
@ -642,7 +607,7 @@ impl Clone for Safety {
Safety { Safety {
task: Some(current_task()), task: Some(current_task()),
level: Rc::strong_count(&payload), level: Rc::strong_count(&payload),
payload: payload, payload,
} }
} }
} }
@ -720,7 +685,7 @@ mod tests {
sender.feed_data(bytes); sender.feed_data(bytes);
let mut multipart = Multipart::new( let mut multipart = Multipart::new(
"abbc761f78ff4d7cb7573b5a23f96ef0".to_owned(), payload); Ok("abbc761f78ff4d7cb7573b5a23f96ef0".to_owned()), payload);
match multipart.poll() { match multipart.poll() {
Ok(Async::Ready(Some(item))) => { Ok(Async::Ready(Some(item))) => {
match item { match item {

View File

@ -6,7 +6,7 @@ use std::slice::Iter;
use std::borrow::Cow; use std::borrow::Cow;
use smallvec::SmallVec; use smallvec::SmallVec;
use error::{ResponseError, UriSegmentError, ErrorBadRequest}; use error::{ResponseError, UriSegmentError, InternalError, ErrorBadRequest};
/// A trait to abstract the idea of creating a new instance of a type from a path parameter. /// A trait to abstract the idea of creating a new instance of a type from a path parameter.
@ -77,7 +77,7 @@ impl<'a> Params<'a> {
} }
} }
/// Return iterator to items in paramter container /// Return iterator to items in parameter container
pub fn iter(&self) -> Iter<(Cow<'a, str>, Cow<'a, str>)> { pub fn iter(&self) -> Iter<(Cow<'a, str>, Cow<'a, str>)> {
self.0.iter() self.0.iter()
} }
@ -141,7 +141,7 @@ impl FromParam for PathBuf {
macro_rules! FROM_STR { macro_rules! FROM_STR {
($type:ty) => { ($type:ty) => {
impl FromParam for $type { impl FromParam for $type {
type Err = ErrorBadRequest<<$type as FromStr>::Err>; type Err = InternalError<<$type as FromStr>::Err>;
fn from_param(val: &str) -> Result<Self, Self::Err> { fn from_param(val: &str) -> Result<Self, Self::Err> {
<$type as FromStr>::from_str(val).map_err(ErrorBadRequest) <$type as FromStr>::from_str(val).map_err(ErrorBadRequest)

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