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

Compare commits

...

521 Commits

Author SHA1 Message Date
4ca9fd2ad1 remove debug print 2018-09-03 22:09:12 -07:00
f0f67072ae Read client response until eof if connection header set to close #464 2018-09-03 21:35:59 -07:00
24d1228943 simplify handler path processing 2018-09-03 11:28:47 -07:00
b7a73e0a4f fix Scope::handler doc test 2018-09-02 08:51:26 -07:00
968c81e267 Handling scoped paths without leading slashes #460 2018-09-02 08:14:54 -07:00
d5957a8466 Merge branch 'master' of https://github.com/actix/actix-web 2018-09-02 07:47:45 -07:00
f2f05e7715 allow to register handlers on scope level #465 2018-09-02 07:47:19 -07:00
3439f55288 doc: Add example for using custom nativetls connector (#497) 2018-09-01 18:13:52 +03:00
0425e2776f Fix Issue #490 (#498)
* Add failing testcase for HTTP 404 response with no reason text.

* Include canonical reason test for HTTP error responses.

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

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

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

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

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

* Add test to verify enum in query

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

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

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

View File

@ -3,41 +3,19 @@ environment:
PROJECT_NAME: actix
matrix:
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: 1.24.0
- TARGET: i686-pc-windows-msvc
CHANNEL: 1.24.0
- TARGET: x86_64-pc-windows-gnu
CHANNEL: 1.24.0
- TARGET: x86_64-pc-windows-msvc
CHANNEL: 1.24.0
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
# Beta channel
- TARGET: i686-pc-windows-gnu
CHANNEL: beta
- TARGET: i686-pc-windows-msvc
CHANNEL: beta
- TARGET: x86_64-pc-windows-gnu
CHANNEL: beta
- TARGET: x86_64-pc-windows-msvc
CHANNEL: beta
# Nightly channel
- TARGET: i686-pc-windows-gnu
CHANNEL: nightly-2017-12-21
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly-2017-12-21
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly-2017-12-21
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly-2017-12-21
CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)

View File

@ -1,5 +1,5 @@
language: rust
sudo: false
sudo: required
dist: trusty
cache:
@ -8,7 +8,6 @@ cache:
matrix:
include:
- rust: 1.24.0
- rust: stable
- rust: beta
- rust: nightly
@ -23,7 +22,7 @@ env:
before_install:
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
- sudo apt-get update -qq
- sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
# Add clippy
before_script:
@ -31,14 +30,14 @@ before_script:
script:
- |
if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then
if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then
cargo clean
cargo test --features="alpn,tls" -- --nocapture
cargo test --features="alpn,tls,rust-tls" -- --nocapture
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin
cargo tarpaulin --features="alpn,tls,rust-tls" --out Xml --no-count
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi
@ -46,8 +45,8 @@ script:
# Upload docs
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --features "alpn, tls, session" --no-deps &&
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then
cargo doc --features "alpn, tls, rust-tls, session" --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import.git &&
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&

View File

@ -1,5 +1,318 @@
# Changes
## [0.7.5] - 2018-09-04
### Added
* Added the ability to pass a custom `TlsConnector`.
* Allow to register handlers on scope level #465
### Fixed
* Handle socket read disconnect
* Handling scoped paths without leading slashes #460
### Changed
* Read client response until eof if connection header set to close #464
## [0.7.4] - 2018-08-23
### Added
* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`,
accept backpressure #250
* Allow to customize connection handshake process via `HttpServer::listen_with()`
and `HttpServer::bind_with()` methods
* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472
### Changed
* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`.
`Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
* native-tls - 0.2
* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461
### Fixed
* Use zlib instead of raw deflate for decoding and encoding payloads with
`Content-Encoding: deflate`.
* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436
* Fix adding multiple response headers #446
* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448
* Panic during access without routing being set #452
* Fixed http/2 error handling
### Deprecated
* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or
`RustlsAcceptor::with_flags()` instead
* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been
deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`.
* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been
deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`.
## [0.7.3] - 2018-08-01
### Added
* Support HTTP/2 with rustls #36
* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433
### Fixed
* Fixed failure 0.1.2 compatibility
* Do not override HOST header for client request #428
* Gz streaming, use `flate2::write::GzDecoder` #228
* HttpRequest::url_for is not working with scopes #429
* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436
## [0.7.2] - 2018-07-26
### Added
* Add implementation of `FromRequest<S>` for `Option<T>` and `Result<T, Error>`
* Allow to handle application prefix, i.e. allow to handle `/app` path
for application with `/app` prefix.
Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix)
api doc.
* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies
### Changed
* Upgrade to cookie 0.11
* Removed the timestamp from the default logger middleware
### Fixed
* Missing response header "content-encoding" #421
* Fix stream draining for http/2 connections #290
## [0.7.1] - 2018-07-21
### Fixed
* Fixed default_resource 'not yet implemented' panic #410
## [0.7.0] - 2018-07-21
### Added
* Add `fs::StaticFileConfig` to provide means of customizing static
file services. It allows to map `mime` to `Content-Disposition`,
specify whether to use `ETag` and `Last-Modified` and allowed methods.
* Add `.has_prefixed_resource()` method to `router::ResourceInfo`
for route matching with prefix awareness
* Add `HttpMessage::readlines()` for reading line by line.
* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests.
* Add method to configure custom error handler to `Form` extractor.
* Add methods to `HttpResponse` to retrieve, add, and delete cookies
* Add `.set_content_type()` and `.set_content_disposition()` methods
to `fs::NamedFile` to allow overriding the values inferred by default
* Add `fs::file_extension_to_mime()` helper function to get the MIME
type for a file extension
* Add `.content_disposition()` method to parse Content-Disposition of
multipart fields
* Re-export `actix::prelude::*` as `actix_web::actix` module.
* `HttpRequest::url_for_static()` for a named route with no variables segments
* Propagation of the application's default resource to scopes that haven't set a default resource.
### Changed
* Min rustc version is 1.26
* Use tokio instead of tokio-core
* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages.
* Became possible to use enums with query extractor.
Issue [#371](https://github.com/actix/actix-web/issues/371).
[Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134)
* `HttpResponse::into_builder()` now moves cookies into the builder
instead of dropping them
* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest`
* Added header `User-Agent: Actix-web/<current_version>` to default headers when building a request
* port `Extensions` type from http create, we don't need `Send + Sync`
* `HttpRequest::query()` returns `Ref<HashMap<String, String>>`
* `HttpRequest::cookies()` returns `Ref<Vec<Cookie<'static>>>`
* `StaticFiles::new()` returns `Result<StaticFiles<S>, Error>` instead of `StaticFiles<S>`
* `StaticFiles` uses the default handler if the file does not exist
### Removed
* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead.
* Remove `HttpMessage::range()`
## [0.6.15] - 2018-07-11
### Fixed
* Fix h2 compatibility #352
* Fix duplicate tail of StaticFiles with index_file. #344
## [0.6.14] - 2018-06-21
### Added
* Allow to disable masking for websockets client
### Fixed
* SendRequest execution fails with the "internal error: entered unreachable code" #329
## [0.6.13] - 2018-06-11
* http/2 end-of-frame is not set if body is empty bytes #307
* InternalError can trigger memory unsafety #301
## [0.6.12] - 2018-06-08
### Added
* Add `Host` filter #287
* Allow to filter applications
* Improved failure interoperability with downcasting #285
* Allow to use custom resolver for `ClientConnector`
## [0.6.11] - 2018-06-05
* Support chunked encoding for UrlEncoded body #262
* `HttpRequest::url_for()` for a named route with no variables segments #265
* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255
* CORS: Do not validate Origin header on non-OPTION requests #271
* Fix multipart upload "Incomplete" error #282
## [0.6.10] - 2018-05-24
### Added
* Allow to use path without trailing slashes for scope registration #241
* Allow to set encoding for exact NamedFile #239
### Fixed
* `TestServer::post()` actually sends `GET` request #240
## 0.6.9 (2018-05-22)
* Drop connection if request's payload is not fully consumed #236
* Fix streaming response with body compression
## 0.6.8 (2018-05-20)
* Fix scope resource path extractor #234
* Re-use tcp listener on pause/resume
## 0.6.7 (2018-05-17)
* Fix compilation with --no-default-features
## 0.6.6 (2018-05-17)
* Panic during middleware execution #226
* Add support for listen_tls/listen_ssl #224
* Implement extractor for `Session`
* Ranges header support for NamedFile #60
## 0.6.5 (2018-05-15)
* Fix error handling during request decoding #222
## 0.6.4 (2018-05-11)
* Fix segfault in ServerSettings::get_response_builder()
## 0.6.3 (2018-05-10)
* Add `Router::with_async()` method for async handler registration.
* Added error response functions for 501,502,503,504
* Fix client request timeout handling
## 0.6.2 (2018-05-09)
* WsWriter trait is optional.
@ -303,7 +616,7 @@
* Server multi-threading
* Gracefull shutdown support
* Graceful shutdown support
## 0.2.1 (2017-11-03)

View File

@ -1,13 +1,13 @@
[package]
name = "actix-web"
version = "0.6.2"
version = "0.7.5"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://github.com/actix/actix-web"
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/"
documentation = "https://actix.rs/api/actix-web/stable/actix_web/"
categories = ["network-programming", "asynchronous",
"web-programming::http-server",
"web-programming::http-client",
@ -16,6 +16,9 @@ license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs"
[package.metadata.docs.rs]
features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"]
[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
appveyor = { repository = "fafhrd91/actix-web-hdy9d" }
@ -34,6 +37,12 @@ tls = ["native-tls", "tokio-tls"]
# openssl
alpn = ["openssl", "tokio-openssl"]
# rustls
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"]
# unix sockets
uds = ["tokio-uds"]
# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]
@ -47,56 +56,71 @@ flate2-c = ["flate2/miniz-sys"]
flate2-rust = ["flate2/rust_backend"]
[dependencies]
actix = "^0.5.5"
actix = "0.7.0"
base64 = "0.9"
bitflags = "1.0"
failure = "0.1.1"
h2 = "0.1"
http = "^0.1.5"
httparse = "1.2"
http-range = "0.1"
libc = "0.2"
htmlescape = "0.3"
http = "^0.1.8"
httparse = "1.3"
log = "0.4"
mime = "0.3"
mime_guess = "2.0.0-alpha"
num_cpus = "1.0"
percent-encoding = "1.0"
rand = "0.4"
rand = "0.5"
regex = "1.0"
serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.5"
sha1 = "0.6"
smallvec = "0.6"
time = "0.1"
encoding = "0.2"
language-tags = "0.2"
lazy_static = "1.0"
lazycell = "1.0.0"
parking_lot = "0.6"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode"] }
cookie = { version="0.11", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="1.0", optional = true, default-features = false }
flate2 = { version="^1.0.2", optional = true, default-features = false }
failure = "^0.1.2"
# io
mio = "^0.6.13"
net2 = "0.2"
bytes = "0.4"
byteorder = "1"
byteorder = "1.2"
futures = "0.1"
futures-cpupool = "0.1"
slab = "0.4"
tokio = "0.1"
tokio-io = "0.1"
tokio-core = "0.1"
tokio-tcp = "0.1"
tokio-timer = "0.2"
tokio-reactor = "0.1"
# native-tls
native-tls = { version="0.1", optional = true }
tokio-tls = { version="0.1", optional = true }
native-tls = { version="0.2", optional = true }
tokio-tls = { version="0.2", optional = true }
# openssl
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }
#rustls
rustls = { version = "^0.13.1", optional = true }
tokio-rustls = { version = "^0.7.2", optional = true }
webpki = { version = "0.18", optional = true }
webpki-roots = { version = "0.15", optional = true }
# unix sockets
tokio-uds = { version="0.2", optional = true }
serde_urlencoded = "^0.5.3"
[dev-dependencies]
env_logger = "0.5"
serde_derive = "1.0"
@ -112,5 +136,4 @@ codegen-units = 1
[workspace]
members = [
"./",
"tools/wsload/",
]

View File

@ -1,4 +1,98 @@
## Migration from 0.5 to 0.6
## 0.7.4
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
## 0.7
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload
use `HttpMessage::payload()` method.
instead of
```rust
fn index(req: HttpRequest) -> impl Responder {
req
.from_err()
.fold(...)
....
}
```
use `.payload()`
```rust
fn index(req: HttpRequest) -> impl Responder {
req
.payload() // <- get request payload stream
.from_err()
.fold(...)
....
}
```
* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html)
trait uses `&HttpRequest` instead of `&mut HttpRequest`.
* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead.
instead of
```rust
fn index(query: Query<..>, info: Json<MyStruct) -> impl Responder {}
```
use tuple of extractors and use `.with()` for registration:
```rust
fn index((query, json): (Query<..>, Json<MyStruct)) -> impl Responder {}
```
* `Handler::handle()` uses `&self` instead of `&mut self`
* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value
* Removed deprecated `HttpServer::threads()`, use
[HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead.
* Renamed `client::ClientConnectorError::Connector` to
`client::ClientConnectorError::Resolver`
* `Route::with()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_config()`
instead of
```rust
fn main() {
let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET)
.with(index)
.limit(4096); // <- limit size of the payload
});
}
```
use
```rust
fn main() {
let app = App::new().resource("/index.html", |r| {
r.method(http::Method::GET)
.with_config(index, |cfg| { // <- register handler
cfg.limit(4096); // <- limit size of the payload
})
});
}
```
* `Route::with_async()` does not return `ExtractorConfig`, to configure
extractor use `Route::with_async_config()`
## 0.6
* `Path<T>` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest`
@ -32,7 +126,7 @@
https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to)
is generic over `S`
* `HttpRequest::query()` is deprecated. Use `Query` extractor.
* Use `Query` extractor instead of HttpRequest::query()`.
```rust
fn index(q: Query<HashMap<String, String>>) -> Result<..> {
@ -50,7 +144,7 @@
you need to use `use actix_web::ws::WsWriter`
## Migration from 0.4 to 0.5
## 0.5
* `HttpResponseBuilder::body()`, `.finish()`, `.json()`
methods return `HttpResponse` instead of `Result<HttpResponse>`

View File

@ -1,6 +1,6 @@
.PHONY: default build test doc book clean
CARGO_FLAGS := --features "$(FEATURES) alpn"
CARGO_FLAGS := --features "$(FEATURES) alpn tls"
default: test

View File

@ -2,41 +2,37 @@
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support
* Client/server [WebSockets](https://actix.rs/docs/websockets/) support
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html)
* Configurable [request routing](https://actix.rs/docs/url-dispatch/)
* Graceful server shutdown
* Multipart streams
* Static assets
* SSL support with OpenSSL or `native-tls`
* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging),
[Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions),
[Redis sessions](https://github.com/actix/actix-redis),
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/))
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Built on top of [Actix actor framework](https://github.com/actix/actix)
## Documentation & community resources
* [User Guide](https://actix.rs/book/actix-web/)
* [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
* [API Documentation (Releases)](https://docs.rs/actix-web/)
* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.24 or later
* Minimum supported Rust version: 1.26 or later
## Example
```rust
extern crate actix_web;
use actix_web::{http, server, App, Path};
use actix_web::{http, server, App, Path, Responder};
fn index(info: Path<(u32, String)>) -> String {
format!("Hello {}! id:{}", info.0, info.1)
fn index(info: Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0)
}
fn main() {
@ -68,7 +64,7 @@ You may consider checking out
## Benchmarks
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext)
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext)
* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks).

View File

@ -1,8 +1,15 @@
extern crate version_check;
fn main() {
match version_check::is_min_version("1.26.0") {
Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"),
_ => (),
};
match version_check::is_nightly() {
Some(true) => println!("cargo:rustc-cfg=actix_nightly"),
Some(true) => {
println!("cargo:rustc-cfg=actix_nightly");
println!("cargo:rustc-cfg=actix_impl_trait");
}
Some(false) => (),
None => (),
};

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
use bytes::{Bytes, BytesMut};
use futures::Stream;
use std::rc::Rc;
use std::sync::Arc;
use std::{fmt, mem};
@ -35,10 +34,8 @@ pub enum Binary {
/// Static slice
Slice(&'static [u8]),
/// Shared string body
SharedString(Rc<String>),
/// Shared string body
#[doc(hidden)]
ArcSharedString(Arc<String>),
SharedString(Arc<String>),
/// Shared vec body
SharedVec(Arc<Vec<u8>>),
}
@ -62,10 +59,28 @@ impl Body {
}
}
/// Is this binary empy.
#[inline]
pub fn is_empty(&self) -> bool {
match *self {
Body::Empty => true,
_ => false,
}
}
/// Create body from slice (copy)
pub fn from_slice(s: &[u8]) -> Body {
Body::Binary(Binary::Bytes(Bytes::from(s)))
}
/// Is this binary body.
#[inline]
pub(crate) fn binary(self) -> Binary {
match self {
Body::Binary(b) => b,
_ => panic!(),
}
}
}
impl PartialEq for Body {
@ -112,17 +127,18 @@ impl From<Box<ActorHttpContext>> for Body {
impl Binary {
#[inline]
/// Returns `true` if body is empty
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
/// Length of body in bytes
pub fn len(&self) -> usize {
match *self {
Binary::Bytes(ref bytes) => bytes.len(),
Binary::Slice(slice) => slice.len(),
Binary::SharedString(ref s) => s.len(),
Binary::ArcSharedString(ref s) => s.len(),
Binary::SharedVec(ref s) => s.len(),
}
}
@ -144,7 +160,6 @@ impl Clone for Binary {
Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()),
Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)),
Binary::SharedString(ref s) => Binary::SharedString(s.clone()),
Binary::ArcSharedString(ref s) => Binary::ArcSharedString(s.clone()),
Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()),
}
}
@ -156,7 +171,6 @@ impl Into<Bytes> for Binary {
Binary::Bytes(bytes) => bytes,
Binary::Slice(slice) => Bytes::from(slice),
Binary::SharedString(s) => Bytes::from(s.as_str()),
Binary::ArcSharedString(s) => Bytes::from(s.as_str()),
Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())),
}
}
@ -204,27 +218,15 @@ impl From<BytesMut> for Binary {
}
}
impl From<Rc<String>> for Binary {
fn from(body: Rc<String>) -> Binary {
Binary::SharedString(body)
}
}
impl<'a> From<&'a Rc<String>> for Binary {
fn from(body: &'a Rc<String>) -> Binary {
Binary::SharedString(Rc::clone(body))
}
}
impl From<Arc<String>> for Binary {
fn from(body: Arc<String>) -> Binary {
Binary::ArcSharedString(body)
Binary::SharedString(body)
}
}
impl<'a> From<&'a Arc<String>> for Binary {
fn from(body: &'a Arc<String>) -> Binary {
Binary::ArcSharedString(Arc::clone(body))
Binary::SharedString(Arc::clone(body))
}
}
@ -247,7 +249,6 @@ impl AsRef<[u8]> for Binary {
Binary::Bytes(ref bytes) => bytes.as_ref(),
Binary::Slice(slice) => slice,
Binary::SharedString(ref s) => s.as_bytes(),
Binary::ArcSharedString(ref s) => s.as_bytes(),
Binary::SharedVec(ref s) => s.as_ref().as_ref(),
}
}
@ -306,22 +307,6 @@ mod tests {
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
}
#[test]
fn test_ref_string() {
let b = Rc::new("test".to_owned());
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), b"test");
}
#[test]
fn test_rc_string() {
let b = Rc::new("test".to_owned());
assert_eq!(Binary::from(b.clone()).len(), 4);
assert_eq!(Binary::from(b.clone()).as_ref(), b"test");
assert_eq!(Binary::from(&b).len(), 4);
assert_eq!(Binary::from(&b).as_ref(), b"test");
}
#[test]
fn test_arc_string() {
let b = Arc::new("test".to_owned());

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +1,26 @@
//! Http client api
//!
//! ```rust
//! # extern crate actix;
//! # extern crate actix_web;
//! # extern crate futures;
//! # extern crate tokio;
//! # use futures::Future;
//! use actix_web::client;
//! # use std::process;
//! use actix_web::{actix, client};
//!
//! fn main() {
//! let sys = actix::System::new("test");
//!
//! actix::Arbiter::handle().spawn({
//! client::get("http://www.rust-lang.org") // <- Create request builder
//! actix::run(
//! || client::get("http://www.rust-lang.org") // <- Create request builder
//! .header("User-Agent", "Actix-web")
//! .finish().unwrap()
//! .send() // <- Send http request
//! .map_err(|_| ())
//! .and_then(|response| { // <- server http response
//! println!("Response: {:?}", response);
//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
//! # actix::System::current().stop();
//! Ok(())
//! })
//! });
//!
//! sys.run();
//! );
//! }
//! ```
mod connector;
@ -33,9 +30,12 @@ mod request;
mod response;
mod writer;
pub use self::connector::{ClientConnector, ClientConnectorError, ClientConnectorStats,
Connect, Connection, Pause, Resume};
pub use self::connector::{
ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection,
Pause, Resume,
};
pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError};
pub(crate) use self::pipeline::Pipeline;
pub use self::pipeline::{SendRequest, SendRequestError};
pub use self::request::{ClientRequest, ClientRequestBuilder};
pub use self::response::ClientResponse;
@ -49,6 +49,7 @@ use httpresponse::HttpResponse;
impl ResponseError for SendRequestError {
fn error_response(&self) -> HttpResponse {
match *self {
SendRequestError::Timeout => HttpResponse::GatewayTimeout(),
SendRequestError::Connector(_) => HttpResponse::BadGateway(),
_ => HttpResponse::InternalServerError(),
}.into()
@ -57,30 +58,29 @@ impl ResponseError for SendRequestError {
/// Create request builder for `GET` requests
///
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate futures;
/// # extern crate tokio;
/// # extern crate env_logger;
/// # use futures::Future;
/// use actix_web::client;
/// # use std::process;
/// use actix_web::{actix, client};
///
/// fn main() {
/// let sys = actix::System::new("test");
///
/// actix::Arbiter::handle().spawn({
/// client::get("http://www.rust-lang.org") // <- Create request builder
/// actix::run(
/// || client::get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .finish().unwrap()
/// .send() // <- Send http request
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
/// .and_then(|response| { // <- server http response
/// println!("Response: {:?}", response);
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
/// # actix::System::current().stop();
/// Ok(())
/// })
/// });
///
/// sys.run();
/// }),
/// );
/// }
/// ```
pub fn get<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {

View File

@ -1,14 +1,15 @@
use std::mem;
use bytes::{Bytes, BytesMut};
use futures::{Async, Poll};
use http::header::{self, HeaderName, HeaderValue};
use http::{HeaderMap, HttpTryFrom, StatusCode, Version};
use http::{HeaderMap, StatusCode, Version};
use httparse;
use std::mem;
use error::{ParseError, PayloadError};
use server::h1decoder::EncodingDecoder;
use server::{utils, IoStream};
use server::h1decoder::{EncodingDecoder, HeaderIndex};
use server::IoStream;
use super::response::ClientMessage;
use super::ClientResponse;
@ -19,6 +20,7 @@ const MAX_HEADERS: usize = 96;
#[derive(Default)]
pub struct HttpResponseParser {
decoder: Option<EncodingDecoder>,
eof: bool, // indicate that we read payload until stream eof
}
#[derive(Debug, Fail)]
@ -37,41 +39,42 @@ impl HttpResponseParser {
where
T: IoStream,
{
// if buf is empty parse_message will always return NotReady, let's avoid that
if buf.is_empty() {
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect),
loop {
// Don't call parser until we have data to parse.
if !buf.is_empty() {
match HttpResponseParser::parse_message(buf)
.map_err(HttpResponseParserError::Error)?
{
Async::Ready((msg, info)) => {
if let Some((decoder, eof)) = info {
self.eof = eof;
self.decoder = Some(decoder);
} else {
self.eof = false;
self.decoder = None;
}
return Ok(Async::Ready(msg));
}
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(
ParseError::TooLarge,
));
}
// Parser needs more data.
}
}
}
// Read some more data into the buffer for the parser.
match io.read_available(buf) {
Ok(Async::Ready((false, true))) => {
return Err(HttpResponseParserError::Disconnect)
}
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => return Err(HttpResponseParserError::Error(err.into())),
}
}
loop {
match HttpResponseParser::parse_message(buf)
.map_err(HttpResponseParserError::Error)?
{
Async::Ready((msg, decoder)) => {
self.decoder = decoder;
return Ok(Async::Ready(msg));
}
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(ParseError::TooLarge));
}
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => {
return Err(HttpResponseParserError::Disconnect)
}
Ok(Async::Ready(_)) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => {
return Err(HttpResponseParserError::Error(err.into()))
}
}
}
}
}
}
pub fn parse_payload<T>(
@ -83,11 +86,11 @@ impl HttpResponseParser {
if self.decoder.is_some() {
loop {
// read payload
let (not_ready, stream_finished) = match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) => (false, true),
Err(err) => return Err(err.into()),
let (not_ready, stream_finished) = match io.read_available(buf) {
Ok(Async::Ready((_, true))) => (false, true),
Ok(Async::Ready((_, false))) => (false, false),
Ok(Async::NotReady) => (true, false),
_ => (false, false),
Err(err) => return Err(err.into()),
};
match self.decoder.as_mut().unwrap().decode(buf) {
@ -101,7 +104,12 @@ impl HttpResponseParser {
return Ok(Async::NotReady);
}
if stream_finished {
return Err(PayloadError::Incomplete);
// read untile eof?
if self.eof {
return Ok(Async::Ready(None));
} else {
return Err(PayloadError::Incomplete);
}
}
}
Err(err) => return Err(err.into()),
@ -114,25 +122,24 @@ impl HttpResponseParser {
fn parse_message(
buf: &mut BytesMut,
) -> Poll<(ClientResponse, Option<EncodingDecoder>), ParseError> {
// Parse http message
let bytes_ptr = buf.as_ref().as_ptr() as usize;
let mut headers: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
) -> Poll<(ClientResponse, Option<(EncodingDecoder, bool)>), ParseError> {
// Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() };
let (len, version, status, headers_len) = {
let b = unsafe {
let b: &[u8] = buf;
&*(b as *const _)
};
let mut resp = httparse::Response::new(&mut headers);
match resp.parse(b)? {
let mut parsed: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
let mut resp = httparse::Response::new(&mut parsed);
match resp.parse(buf)? {
httparse::Status::Complete(len) => {
let version = if resp.version.unwrap_or(1) == 1 {
Version::HTTP_11
} else {
Version::HTTP_10
};
HeaderIndex::record(buf, resp.headers, &mut headers);
let status = StatusCode::from_u16(resp.code.unwrap())
.map_err(|_| ParseError::Status)?;
@ -146,12 +153,13 @@ impl HttpResponseParser {
// 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();
for idx in headers[..headers_len].iter() {
if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) {
// Unsafe: httparse check header value for valid utf-8
let value = unsafe {
HeaderValue::from_shared_unchecked(slice.slice(v_start, v_end))
HeaderValue::from_shared_unchecked(
slice.slice(idx.value.0, idx.value.1),
)
};
hdrs.append(name, value);
} else {
@ -160,12 +168,12 @@ impl HttpResponseParser {
}
let decoder = if status == StatusCode::SWITCHING_PROTOCOLS {
Some(EncodingDecoder::eof())
Some((EncodingDecoder::eof(), true))
} else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
Some(EncodingDecoder::length(len))
Some((EncodingDecoder::length(len), false))
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header);
@ -176,7 +184,18 @@ impl HttpResponseParser {
}
} else if chunked(&hdrs)? {
// Chunked encoding
Some(EncodingDecoder::chunked())
Some((EncodingDecoder::chunked(), false))
} else if let Some(value) = hdrs.get(header::CONNECTION) {
let close = if let Ok(s) = value.to_str() {
s == "close"
} else {
false
};
if close {
Some((EncodingDecoder::eof(), true))
} else {
None
}
} else {
None
};

View File

@ -1,25 +1,25 @@
use bytes::{Bytes, BytesMut};
use futures::unsync::oneshot;
use futures::{Async, Future, Poll};
use futures::sync::oneshot;
use futures::{Async, Future, Poll, Stream};
use http::header::CONTENT_ENCODING;
use std::time::Duration;
use std::time::{Duration, Instant};
use std::{io, mem};
use tokio_core::reactor::Timeout;
use tokio_timer::Delay;
use actix::prelude::*;
use actix::{Addr, Request, SystemService};
use super::HttpClientWriter;
use super::{ClientConnector, ClientConnectorError, Connect, Connection};
use super::{ClientRequest, ClientResponse};
use super::{HttpResponseParser, HttpResponseParserError};
use super::{
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,
Connection, HttpClientWriter, HttpResponseParser, HttpResponseParserError,
};
use body::{Body, BodyStream};
use context::{ActorHttpContext, Frame};
use error::Error;
use error::PayloadError;
use header::ContentEncoding;
use http::{Method, Uri};
use httpmessage::HttpMessage;
use server::encoding::PayloadStream;
use server::shared::SharedBytes;
use server::input::PayloadStream;
use server::WriterState;
/// A set of errors that can occur during request sending and response reading
@ -56,7 +56,7 @@ impl From<ClientConnectorError> for SendRequestError {
enum State {
New,
Connect(actix::dev::Request<Unsync, ClientConnector, Connect>),
Connect(Request<ClientConnector, Connect>),
Connection(Connection),
Send(Box<Pipeline>),
None,
@ -68,23 +68,30 @@ enum State {
pub struct SendRequest {
req: ClientRequest,
state: State,
conn: Addr<Unsync, ClientConnector>,
conn: Option<Addr<ClientConnector>>,
conn_timeout: Duration,
wait_timeout: Duration,
timeout: Option<Timeout>,
timeout: Option<Duration>,
}
impl SendRequest {
pub(crate) fn new(req: ClientRequest) -> SendRequest {
SendRequest::with_connector(req, ClientConnector::from_registry())
SendRequest {
req,
conn: None,
state: State::New,
timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
}
}
pub(crate) fn with_connector(
req: ClientRequest, conn: Addr<Unsync, ClientConnector>,
req: ClientRequest, conn: Addr<ClientConnector>,
) -> SendRequest {
SendRequest {
req,
conn,
conn: Some(conn),
state: State::New,
timeout: None,
wait_timeout: Duration::from_secs(5),
@ -96,7 +103,7 @@ impl SendRequest {
SendRequest {
req,
state: State::Connection(conn),
conn: ClientConnector::from_registry(),
conn: None,
timeout: None,
wait_timeout: Duration::from_secs(5),
conn_timeout: Duration::from_secs(1),
@ -108,7 +115,7 @@ impl SendRequest {
/// Request timeout is the total time before a response must be received.
/// Default value is 5 seconds.
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(Timeout::new(timeout, Arbiter::handle()).unwrap());
self.timeout = Some(timeout);
self
}
@ -142,7 +149,12 @@ impl Future for SendRequest {
match state {
State::New => {
self.state = State::Connect(self.conn.send(Connect {
let conn = if let Some(conn) = self.conn.take() {
conn
} else {
ClientConnector::from_registry()
};
self.state = State::Connect(conn.send(Connect {
uri: self.req.uri().clone(),
wait_timeout: self.wait_timeout,
conn_timeout: self.conn_timeout,
@ -160,11 +172,11 @@ impl Future for SendRequest {
Err(_) => {
return Err(SendRequestError::Connector(
ClientConnectorError::Disconnected,
))
));
}
},
State::Connection(conn) => {
let mut writer = HttpClientWriter::new(SharedBytes::default());
let mut writer = HttpClientWriter::new();
writer.start(&mut self.req)?;
let body = match self.req.replace_body(Body::Empty) {
@ -173,9 +185,10 @@ impl Future for SendRequest {
_ => IoBody::Done,
};
let timeout = self.timeout.take().unwrap_or_else(|| {
Timeout::new(Duration::from_secs(5), Arbiter::handle()).unwrap()
});
let timeout = self
.timeout
.take()
.unwrap_or_else(|| Duration::from_secs(5));
let pl = Box::new(Pipeline {
body,
@ -189,17 +202,23 @@ impl Future for SendRequest {
decompress: None,
should_decompress: self.req.response_decompress(),
write_state: RunningState::Running,
timeout: Some(timeout),
timeout: Some(Delay::new(Instant::now() + timeout)),
meth: self.req.method().clone(),
path: self.req.uri().clone(),
});
self.state = State::Send(pl);
}
State::Send(mut pl) => {
pl.poll_timeout()?;
pl.poll_write().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str())
})?;
match pl.parse() {
Ok(Async::Ready(mut resp)) => {
if self.req.method() == Method::HEAD {
pl.parser.take();
}
resp.set_pipeline(pl);
return Ok(Async::Ready(resp));
}
@ -207,7 +226,9 @@ impl Future for SendRequest {
self.state = State::Send(pl);
return Ok(Async::NotReady);
}
Err(err) => return Err(SendRequestError::ParseError(err)),
Err(err) => {
return Err(SendRequestError::ParseError(err));
}
}
}
State::None => unreachable!(),
@ -216,7 +237,7 @@ impl Future for SendRequest {
}
}
pub(crate) struct Pipeline {
pub struct Pipeline {
body: IoBody,
body_completed: bool,
conn: Option<Connection>,
@ -228,7 +249,9 @@ pub(crate) struct Pipeline {
decompress: Option<PayloadStream>,
should_decompress: bool,
write_state: RunningState,
timeout: Option<Timeout>,
timeout: Option<Delay>,
meth: Method,
path: Uri,
}
enum IoBody {
@ -262,14 +285,19 @@ impl RunningState {
impl Pipeline {
fn release_conn(&mut self) {
if let Some(conn) = self.conn.take() {
conn.release()
if self.meth == Method::HEAD {
conn.close()
} else {
conn.release()
}
}
}
#[inline]
fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
if let Some(ref mut conn) = self.conn {
match self.parser
match self
.parser
.as_mut()
.unwrap()
.parse(conn, &mut self.parser_buf)
@ -300,22 +328,20 @@ impl Pipeline {
}
#[inline]
pub fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
pub(crate) fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if self.conn.is_none() {
return Ok(Async::Ready(None));
}
let conn: &mut Connection =
unsafe { &mut *(self.conn.as_mut().unwrap() as *mut _) };
let mut need_run = false;
// need write?
match self.poll_write()
match self
.poll_write()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?
{
Async::NotReady => need_run = true,
Async::Ready(_) => {
let _ = self.poll_timeout().map_err(|e| {
self.poll_timeout().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("{}", e))
})?;
}
@ -323,8 +349,11 @@ impl Pipeline {
// need read?
if self.parser.is_some() {
let conn: &mut Connection = self.conn.as_mut().unwrap();
loop {
match self.parser
match self
.parser
.as_mut()
.unwrap()
.parse_payload(conn, &mut self.parser_buf)?
@ -371,16 +400,15 @@ impl Pipeline {
}
}
fn poll_timeout(&mut self) -> Poll<(), SendRequestError> {
fn poll_timeout(&mut self) -> Result<(), SendRequestError> {
if self.timeout.is_some() {
match self.timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(())) => Err(SendRequestError::Timeout),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(_) => unreachable!(),
Ok(Async::Ready(())) => return Err(SendRequestError::Timeout),
Ok(Async::NotReady) => (),
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e).into()),
}
} else {
Ok(Async::NotReady)
}
Ok(())
}
#[inline]
@ -401,7 +429,7 @@ impl Pipeline {
}
Async::Ready(Some(chunk)) => {
self.body = IoBody::Payload(body);
self.writer.write(chunk.into())?
self.writer.write(chunk.as_ref())?
}
Async::NotReady => {
done = true;
@ -428,7 +456,8 @@ impl Pipeline {
break 'outter;
}
Frame::Chunk(Some(chunk)) => {
res = Some(self.writer.write(chunk)?)
res =
Some(self.writer.write(chunk.as_ref())?)
}
Frame::Drain(fut) => self.drain = Some(fut),
}
@ -469,7 +498,8 @@ impl Pipeline {
}
// flush io but only if we need to
match self.writer
match self
.writer
.poll_completed(self.conn.as_mut().unwrap(), false)
{
Ok(Async::Ready(_)) => {
@ -501,7 +531,22 @@ impl Pipeline {
impl Drop for Pipeline {
fn drop(&mut self) {
if let Some(conn) = self.conn.take() {
debug!(
"Client http transaction is not completed, dropping connection: {:?} {:?}",
self.meth,
self.path,
);
conn.close()
}
}
}
/// Future that resolves to a complete request body.
impl Stream for Box<Pipeline> {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
Pipeline::poll(self)
}
}

View File

@ -3,13 +3,14 @@ use std::io::Write;
use std::time::Duration;
use std::{fmt, mem};
use actix::{Addr, Unsync};
use actix::Addr;
use bytes::{BufMut, Bytes, BytesMut};
use cookie::{Cookie, CookieJar};
use futures::Stream;
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use serde::Serialize;
use serde_json;
use serde_urlencoded;
use url::Url;
use super::connector::{ClientConnector, Connection};
@ -25,29 +26,26 @@ use httprequest::HttpRequest;
/// An HTTP Client Request
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate futures;
/// # extern crate tokio;
/// # use futures::Future;
/// use actix_web::client::ClientRequest;
/// # use std::process;
/// use actix_web::{actix, client};
///
/// fn main() {
/// let sys = actix::System::new("test");
///
/// actix::Arbiter::handle().spawn({
/// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
/// actix::run(
/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder
/// .header("User-Agent", "Actix-web")
/// .finish().unwrap()
/// .send() // <- Send http request
/// .map_err(|_| ())
/// .and_then(|response| { // <- server http response
/// .and_then(|response| { // <- server http response
/// println!("Response: {:?}", response);
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
/// # actix::System::current().stop();
/// Ok(())
/// })
/// });
///
/// sys.run();
/// }),
/// );
/// }
/// ```
pub struct ClientRequest {
@ -67,7 +65,7 @@ pub struct ClientRequest {
enum ConnectionType {
Default,
Connector(Addr<Unsync, ClientConnector>),
Connector(Addr<ClientConnector>),
Connection(Connection),
}
@ -293,10 +291,6 @@ impl ClientRequestBuilder {
fn _uri(&mut self, url: &str) -> &mut Self {
match Uri::try_from(url) {
Ok(uri) => {
// set request host header
if let Some(host) = uri.host() {
self.set_header(header::HOST, host);
}
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.uri = uri;
}
@ -318,8 +312,7 @@ impl ClientRequestBuilder {
/// Set HTTP method of this request.
#[inline]
pub fn get_method(&mut self) -> &Method {
let parts =
parts(&mut self.request, &self.err).expect("cannot reuse request builder");
let parts = self.request.as_ref().expect("cannot reuse request builder");
&parts.method
}
@ -347,7 +340,8 @@ impl ClientRequestBuilder {
/// let req = client::ClientRequest::build()
/// .set(http::header::Date::now())
/// .set(http::header::ContentType(mime::TEXT_HTML))
/// .finish().unwrap();
/// .finish()
/// .unwrap();
/// }
/// ```
#[doc(hidden)]
@ -379,7 +373,8 @@ impl ClientRequestBuilder {
/// let req = ClientRequest::build()
/// .header("X-TEST", "value")
/// .header(header::CONTENT_TYPE, "application/json")
/// .finish().unwrap();
/// .finish()
/// .unwrap();
/// }
/// ```
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
@ -421,6 +416,28 @@ impl ClientRequestBuilder {
self
}
/// Set a header only if it is not yet set.
pub fn set_header_if_none<K, V>(&mut self, key: K, value: V) -> &mut Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if let Some(parts) = parts(&mut self.request, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => if !parts.headers.contains_key(&key) {
match value.try_into() {
Ok(value) => {
parts.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
}
},
Err(e) => self.err = Some(e.into()),
};
}
self
}
/// Set content encoding.
///
/// By default `ContentEncoding::Identity` is used.
@ -489,8 +506,10 @@ impl ClientRequestBuilder {
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .finish())
/// .finish().unwrap();
/// .finish(),
/// )
/// .finish()
/// .unwrap();
/// }
/// ```
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
@ -499,16 +518,13 @@ impl ClientRequestBuilder {
jar.add(cookie.into_owned());
self.cookies = Some(jar)
} else {
self.cookies
.as_mut()
.unwrap()
.add(cookie.into_owned());
self.cookies.as_mut().unwrap().add(cookie.into_owned());
}
self
}
/// Do not add default request headers.
/// By default `Accept-Encoding` header is set.
/// By default `Accept-Encoding` and `User-Agent` headers are set.
pub fn no_default_headers(&mut self) -> &mut Self {
self.default_headers = false;
self
@ -544,7 +560,7 @@ impl ClientRequestBuilder {
}
/// Send request using custom connector
pub fn with_connector(&mut self, conn: Addr<Unsync, ClientConnector>) -> &mut Self {
pub fn with_connector(&mut self, conn: Addr<ClientConnector>) -> &mut Self {
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.conn = ConnectionType::Connector(conn);
}
@ -604,15 +620,40 @@ impl ClientRequestBuilder {
};
if https {
self.header(header::ACCEPT_ENCODING, "br, gzip, deflate");
self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate");
} else {
self.header(header::ACCEPT_ENCODING, "gzip, deflate");
self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate");
}
// set request host header
if let Some(parts) = parts(&mut self.request, &self.err) {
if let Some(host) = parts.uri.host() {
if !parts.headers.contains_key(header::HOST) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match parts.uri.port() {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
parts.headers.insert(header::HOST, value);
}
Err(e) => self.err = Some(e.into()),
}
}
}
}
// user agent
self.set_header_if_none(
header::USER_AGENT,
concat!("actix-web/", env!("CARGO_PKG_VERSION")),
);
}
let mut request = self.request
.take()
.expect("cannot reuse request builder");
let mut request = self.request.take().expect("cannot reuse request builder");
// set cookies
if let Some(ref mut jar) = self.cookies {
@ -649,6 +690,24 @@ impl ClientRequestBuilder {
self.body(body)
}
/// Set a urlencoded body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn form<T: Serialize>(&mut self, value: T) -> Result<ClientRequest, Error> {
let body = serde_urlencoded::to_string(&value)?;
let contains = if let Some(parts) = parts(&mut self.request, &self.err) {
parts.headers.contains_key(header::CONTENT_TYPE)
} else {
true
};
if !contains {
self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded");
}
self.body(body)
}
/// Set a streaming body and generate `ClientRequest`.
///
/// `ClientRequestBuilder` can not be used after this call.
@ -657,9 +716,7 @@ impl ClientRequestBuilder {
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error>,
{
self.body(Body::Streaming(Box::new(
stream.map_err(|e| e.into()),
)))
self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
}
/// Set an empty body and generate `ClientRequest`

View File

@ -1,14 +1,11 @@
use std::cell::UnsafeCell;
use std::rc::Rc;
use std::cell::RefCell;
use std::{fmt, str};
use bytes::Bytes;
use cookie::Cookie;
use futures::{Async, Poll, Stream};
use http::header::{self, HeaderValue};
use http::{HeaderMap, StatusCode, Version};
use error::{CookieParseError, PayloadError};
use error::CookieParseError;
use httpmessage::HttpMessage;
use super::pipeline::Pipeline;
@ -32,64 +29,59 @@ impl Default for ClientMessage {
}
/// An HTTP Client response
pub struct ClientResponse(Rc<UnsafeCell<ClientMessage>>, Option<Box<Pipeline>>);
pub struct ClientResponse(ClientMessage, RefCell<Option<Box<Pipeline>>>);
impl HttpMessage for ClientResponse {
type Stream = Box<Pipeline>;
/// Get the headers from the response.
#[inline]
fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
&self.0.headers
}
#[inline]
fn payload(&self) -> Box<Pipeline> {
self.1
.borrow_mut()
.take()
.expect("Payload is already consumed.")
}
}
impl ClientResponse {
pub(crate) fn new(msg: ClientMessage) -> ClientResponse {
ClientResponse(Rc::new(UnsafeCell::new(msg)), None)
ClientResponse(msg, RefCell::new(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() }
*self.1.borrow_mut() = Some(pl);
}
/// Get the HTTP version of this response.
#[inline]
pub fn version(&self) -> Version {
self.as_ref().version
self.0.version
}
/// Get the status from the server.
#[inline]
pub fn status(&self) -> StatusCode {
self.as_ref().status
self.0.status
}
/// Load response cookies.
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
if self.as_ref().cookies.is_none() {
let msg = self.as_mut();
let mut cookies = Vec::new();
for val in msg.headers.get_all(header::SET_COOKIE).iter() {
let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
}
msg.cookies = Some(cookies)
pub fn cookies(&self) -> Result<Vec<Cookie<'static>>, CookieParseError> {
let mut cookies = Vec::new();
for val in self.0.headers.get_all(header::SET_COOKIE).iter() {
let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned());
}
Ok(self.as_ref().cookies.as_ref().unwrap())
Ok(cookies)
}
/// Return request cookie.
pub fn cookie(&self, name: &str) -> Option<&Cookie> {
pub fn cookie(&self, name: &str) -> Option<Cookie> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies {
if cookie.name() == name {
@ -103,12 +95,7 @@ impl ClientResponse {
impl fmt::Debug for ClientResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = writeln!(
f,
"\nClientResponse {:?} {}",
self.version(),
self.status()
);
let res = writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status());
let _ = writeln!(f, " headers:");
for (key, val) in self.headers().iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val);
@ -117,35 +104,19 @@ impl fmt::Debug for ClientResponse {
}
}
/// Future that resolves to a complete request body.
impl Stream for ClientResponse {
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(ref mut pl) = self.1 {
pl.poll()
} else {
Ok(Async::Ready(None))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debug() {
let resp = ClientResponse::new(ClientMessage::default());
resp.as_mut().headers.insert(
header::COOKIE,
HeaderValue::from_static("cookie1=value1"),
);
resp.as_mut().headers.insert(
header::COOKIE,
HeaderValue::from_static("cookie2=value2"),
);
let mut resp = ClientResponse::new(ClientMessage::default());
resp.0
.headers
.insert(header::COOKIE, HeaderValue::from_static("cookie1=value1"));
resp.0
.headers
.insert(header::COOKIE, HeaderValue::from_static("cookie2=value2"));
let dbg = format!("{:?}", resp);
assert!(dbg.contains("ClientResponse"));

View File

@ -8,20 +8,20 @@ use std::io::{self, Write};
use brotli2::write::BrotliEncoder;
use bytes::{BufMut, BytesMut};
#[cfg(feature = "flate2")]
use flate2::write::{DeflateEncoder, GzEncoder};
use flate2::write::{GzEncoder, ZlibEncoder};
#[cfg(feature = "flate2")]
use flate2::Compression;
use futures::{Async, Poll};
use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE,
TRANSFER_ENCODING};
use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::{HttpTryFrom, Version};
use time::{self, Duration};
use tokio_io::AsyncWrite;
use body::{Binary, Body};
use header::ContentEncoding;
use server::encoding::{ContentEncoder, TransferEncoding};
use server::shared::SharedBytes;
use server::output::{ContentEncoder, Output, TransferEncoding};
use server::WriterState;
use client::ClientRequest;
@ -41,21 +41,18 @@ pub(crate) struct HttpClientWriter {
flags: Flags,
written: u64,
headers_size: u32,
buffer: SharedBytes,
buffer: Output,
buffer_capacity: usize,
encoder: ContentEncoder,
}
impl HttpClientWriter {
pub fn new(buffer: SharedBytes) -> HttpClientWriter {
let encoder = ContentEncoder::Identity(TransferEncoding::eof(buffer.clone()));
pub fn new() -> HttpClientWriter {
HttpClientWriter {
flags: Flags::empty(),
written: 0,
headers_size: 0,
buffer_capacity: 0,
buffer,
encoder,
buffer: Output::Buffer(BytesMut::new()),
}
}
@ -75,7 +72,7 @@ impl HttpClientWriter {
&mut self, stream: &mut T,
) -> io::Result<WriterState> {
while !self.buffer.is_empty() {
match stream.write(self.buffer.as_ref()) {
match stream.write(self.buffer.as_ref().as_ref()) {
Ok(0) => {
self.disconnected();
return Ok(WriterState::Done);
@ -97,21 +94,35 @@ impl HttpClientWriter {
}
}
pub struct Writer<'a>(pub &'a mut BytesMut);
impl<'a> io::Write for Writer<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl HttpClientWriter {
pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> {
// prepare task
self.buffer = content_encoder(self.buffer.take(), msg);
self.flags.insert(Flags::STARTED);
self.encoder = content_encoder(self.buffer.clone(), msg);
if msg.upgrade() {
self.flags.insert(Flags::UPGRADE);
}
// render message
{
// output buffer
let buffer = self.buffer.as_mut();
// status line
writeln!(
self.buffer,
Writer(buffer),
"{} {} {:?}\r",
msg.method(),
msg.uri()
@ -119,10 +130,9 @@ impl HttpClientWriter {
.map(|u| u.as_str())
.unwrap_or("/"),
msg.version()
)?;
).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
// write headers
let mut buffer = self.buffer.get_mut();
if let Body::Binary(ref bytes) = *msg.body() {
buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
} else {
@ -142,33 +152,29 @@ impl HttpClientWriter {
// set date header
if !msg.headers().contains_key(DATE) {
buffer.extend_from_slice(b"date: ");
set_date(&mut buffer);
set_date(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;
}
self.headers_size = self.buffer.len() as u32;
if msg.body().is_binary() {
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
self.written += bytes.len() as u64;
self.encoder.write(bytes)?;
}
} else {
self.buffer_capacity = msg.write_buffer_capacity();
if msg.body().is_binary() {
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
self.written += bytes.len() as u64;
self.buffer.write(bytes.as_ref())?;
}
} else {
self.buffer_capacity = msg.write_buffer_capacity();
}
Ok(())
}
pub fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
pub fn write(&mut self, payload: &[u8]) -> 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)?;
}
self.buffer.write(payload)?;
}
if self.buffer.len() > self.buffer_capacity {
@ -179,9 +185,7 @@ impl HttpClientWriter {
}
pub fn write_eof(&mut self) -> io::Result<()> {
self.encoder.write_eof()?;
if self.encoder.is_eof() {
if self.buffer.write_eof()? {
Ok(())
} else {
Err(io::Error::new(
@ -209,7 +213,7 @@ impl HttpClientWriter {
}
}
fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder {
fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
let version = req.version();
let mut body = req.replace_body(Body::Empty);
let mut encoding = req.content_encoding();
@ -217,47 +221,57 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
let transfer = match body {
Body::Empty => {
req.headers_mut().remove(CONTENT_LENGTH);
TransferEncoding::length(0, buf)
return Output::Empty(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 {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::default()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
transfer,
Compression::default(),
)),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
ContentEncoder::Br(BrotliEncoder::new(transfer, 5))
}
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
ContentEncoding::Auto => unreachable!(),
};
// TODO return error!
let _ = enc.write(bytes.clone());
let _ = enc.write_eof();
*bytes = Binary::from(tmp.take());
#[cfg(any(feature = "flate2", feature = "brotli"))]
{
if encoding.is_compression() {
let mut tmp = BytesMut::new();
let mut transfer = TransferEncoding::eof(tmp);
let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
ZlibEncoder::new(transfer, Compression::default()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
transfer,
Compression::default(),
)),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
ContentEncoder::Br(BrotliEncoder::new(transfer, 5))
}
ContentEncoding::Auto | ContentEncoding::Identity => {
unreachable!()
}
};
// TODO return error!
let _ = enc.write(bytes.as_ref());
let _ = enc.write_eof();
*bytes = Binary::from(enc.buf_mut().take());
req.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
encoding = ContentEncoding::Identity;
req.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
encoding = ContentEncoding::Identity;
}
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
}
#[cfg(not(any(feature = "flate2", feature = "brotli")))]
{
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
TransferEncoding::eof(buf)
}
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() {
@ -286,26 +300,24 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
}
req.replace_body(body);
match encoding {
let enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
transfer,
Compression::default(),
)),
ContentEncoding::Deflate => {
ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::default()))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default()))
}
#[cfg(feature = "brotli")]
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 5)),
ContentEncoding::Identity | ContentEncoding::Auto => {
ContentEncoder::Identity(transfer)
}
}
ContentEncoding::Identity | ContentEncoding::Auto => return Output::TE(transfer),
};
Output::Encoder(enc)
}
fn streaming_encoding(
buf: SharedBytes, version: Version, req: &mut ClientRequest,
buf: BytesMut, version: Version, req: &mut ClientRequest,
) -> TransferEncoding {
if req.chunked() {
// Enable transfer encoding

View File

@ -1,13 +1,18 @@
extern crate actix;
use futures::sync::oneshot;
use futures::sync::oneshot::Sender;
use futures::unsync::oneshot;
use futures::{Async, Future, Poll};
use smallvec::SmallVec;
use std::marker::PhantomData;
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
use actix::fut::ActorFuture;
use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message,
SpawnHandle, Syn, Unsync};
use self::actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope,
};
use self::actix::fut::ActorFuture;
use self::actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle,
};
use body::{Binary, Body};
use error::{Error, ErrorInternalServerError};
@ -38,7 +43,7 @@ pub struct HttpContext<A, S = ()>
where
A: Actor<Context = HttpContext<A, S>>,
{
inner: ContextImpl<A>,
inner: ContextParts<A>,
stream: Option<SmallVec<[Frame; 4]>>,
request: HttpRequest<S>,
disconnected: bool,
@ -80,22 +85,17 @@ where
#[doc(hidden)]
#[inline]
fn waiting(&self) -> bool {
self.inner.waiting() || self.inner.state() == ActorState::Stopping
self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped
}
#[inline]
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
self.inner.cancel_future(handle)
}
#[doc(hidden)]
#[inline]
fn unsync_address(&mut self) -> Addr<Unsync, A> {
self.inner.unsync_address()
}
#[doc(hidden)]
#[inline]
fn sync_address(&mut self) -> Addr<Syn, A> {
self.inner.sync_address()
fn address(&self) -> Addr<A> {
self.inner.address()
}
}
@ -104,21 +104,33 @@ where
A: Actor<Context = Self>,
{
#[inline]
pub fn new(req: HttpRequest<S>, actor: A) -> HttpContext<A, S> {
HttpContext::from_request(req).actor(actor)
}
pub fn from_request(req: HttpRequest<S>) -> HttpContext<A, S> {
HttpContext {
inner: ContextImpl::new(None),
/// Create a new HTTP Context from a request and an actor
pub fn create(req: HttpRequest<S>, actor: A) -> Body {
let mb = Mailbox::default();
let ctx = HttpContext {
inner: ContextParts::new(mb.sender_producer()),
stream: None,
request: req,
disconnected: false,
}
};
Body::Actor(Box::new(HttpContextFut::new(ctx, actor, mb)))
}
#[inline]
pub fn actor(mut self, actor: A) -> HttpContext<A, S> {
self.inner.set_actor(actor);
self
/// Create a new HTTP Context
pub fn with_factory<F>(req: HttpRequest<S>, f: F) -> Body
where
F: FnOnce(&mut Self) -> A + 'static,
{
let mb = Mailbox::default();
let mut ctx = HttpContext {
inner: ContextParts::new(mb.sender_producer()),
stream: None,
request: req,
disconnected: false,
};
let act = f(&mut ctx);
Body::Actor(Box::new(HttpContextFut::new(ctx, act, mb)))
}
}
@ -157,7 +169,6 @@ where
/// Returns drain future
pub fn drain(&mut self) -> Drain<A> {
let (tx, rx) = oneshot::channel();
self.inner.modify();
self.add_frame(Frame::Drain(tx));
Drain::new(rx)
}
@ -176,7 +187,6 @@ where
if let Some(s) = self.stream.as_mut() {
s.push(frame)
}
self.inner.modify();
}
/// Handle of the running future
@ -187,32 +197,55 @@ where
}
}
impl<A, S> ActorHttpContext for HttpContext<A, S>
impl<A, S> AsyncContextParts<A> for HttpContext<A, S>
where
A: Actor<Context = Self>,
{
fn parts(&mut self) -> &mut ContextParts<A> {
&mut self.inner
}
}
struct HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
{
fut: ContextFut<A, HttpContext<A, S>>,
}
impl<A, S> HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
{
fn new(ctx: HttpContext<A, S>, act: A, mailbox: Mailbox<A>) -> Self {
let fut = ContextFut::new(ctx, act, mailbox);
HttpContextFut { fut }
}
}
impl<A, S> ActorHttpContext for HttpContextFut<A, S>
where
A: Actor<Context = HttpContext<A, S>>,
S: 'static,
{
#[inline]
fn disconnected(&mut self) {
self.disconnected = true;
self.stop();
self.fut.ctx().disconnected = true;
self.fut.ctx().stop();
}
fn poll(&mut self) -> Poll<Option<SmallVec<[Frame; 4]>>, Error> {
let ctx: &mut HttpContext<A, S> =
unsafe { &mut *(self as &mut HttpContext<A, S> as *mut _) };
if self.inner.alive() {
match self.inner.poll(ctx) {
if self.fut.alive() {
match self.fut.poll() {
Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
Err(_) => return Err(ErrorInternalServerError("error")),
}
}
// frames
if let Some(data) = self.stream.take() {
if let Some(data) = self.fut.ctx().stream.take() {
Ok(Async::Ready(Some(data)))
} else if self.inner.alive() {
} else if self.fut.alive() {
Ok(Async::NotReady)
} else {
Ok(Async::Ready(None))
@ -220,33 +253,25 @@ where
}
}
impl<A, M, S> ToEnvelope<Syn, A, M> for HttpContext<A, S>
impl<A, M, S> ToEnvelope<A, M> for HttpContext<A, S>
where
A: Actor<Context = HttpContext<A, S>> + Handler<M>,
M: Message + Send + 'static,
M::Result: Send,
{
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> SyncEnvelope<A> {
SyncEnvelope::new(msg, tx)
}
}
impl<A, S> From<HttpContext<A, S>> for Body
where
A: Actor<Context = HttpContext<A, S>>,
S: 'static,
{
fn from(ctx: HttpContext<A, S>) -> Body {
Body::Actor(Box::new(ctx))
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
Envelope::new(msg, tx)
}
}
/// Consume a future
pub struct Drain<A> {
fut: oneshot::Receiver<()>,
_a: PhantomData<A>,
}
impl<A> Drain<A> {
/// Create a drain from a future
pub fn new(fut: oneshot::Receiver<()>) -> Self {
Drain {
fut,

View File

@ -1,9 +1,7 @@
use serde::de::{self, Deserializer, Error as DeError, Visitor};
use std::borrow::Cow;
use std::convert::AsRef;
use std::slice::Iter;
use httprequest::HttpRequest;
use param::ParamsIter;
macro_rules! unsupported_type {
($trait_fn:ident, $name:expr) => {
@ -191,7 +189,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
}
struct ParamsDeserializer<'de> {
params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>,
params: ParamsIter<'de>,
current: Option<(&'de str, &'de str)>,
}
@ -202,9 +200,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
where
K: de::DeserializeSeed<'de>,
{
self.current = self.params
.next()
.map(|&(ref k, ref v)| (k.as_ref(), v.as_ref()));
self.current = self.params.next().map(|ref item| (item.0, item.1));
match self.current {
Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)),
None => Ok(None),
@ -336,9 +332,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
where
V: Visitor<'de>,
{
visitor.visit_enum(ValueEnum {
value: self.value,
})
visitor.visit_enum(ValueEnum { value: self.value })
}
fn deserialize_newtype_struct<V>(
@ -372,9 +366,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
where
V: Visitor<'de>,
{
Err(de::value::Error::custom(
"unsupported type: tuple struct",
))
Err(de::value::Error::custom("unsupported type: tuple struct"))
}
unsupported_type!(deserialize_any, "any");
@ -384,7 +376,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
}
struct ParamsSeq<'de> {
params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>,
params: ParamsIter<'de>,
}
impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
@ -395,9 +387,7 @@ impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
T: de::DeserializeSeed<'de>,
{
match self.params.next() {
Some(item) => Ok(Some(seed.deserialize(Value {
value: item.1.as_ref(),
})?)),
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)),
None => Ok(None),
}
}
@ -415,10 +405,7 @@ impl<'de> de::EnumAccess<'de> for ValueEnum<'de> {
where
V: de::DeserializeSeed<'de>,
{
Ok((
seed.deserialize(Key { key: self.value })?,
UnitVariant,
))
Ok((seed.deserialize(Key { key: self.value })?, UnitVariant))
}
}

View File

@ -1,8 +1,8 @@
//! Error and Result module
use std::cell::RefCell;
use std::io::Error as IoError;
use std::str::Utf8Error;
use std::string::FromUtf8Error;
use std::sync::Mutex;
use std::{fmt, io, result};
use actix::MailboxError;
@ -12,10 +12,11 @@ use futures::Canceled;
use http::uri::InvalidUri;
use http::{header, Error as HttpError, StatusCode};
use http2::Error as Http2Error;
use http_range::HttpRangeParseError;
use httparse;
use serde::de::value::Error as DeError;
use serde_json::error::Error as JsonError;
use serde_urlencoded::ser::Error as FormError;
use tokio_timer::Error as TimerError;
pub use url::ParseError as UrlParseError;
// re-exports
@ -23,7 +24,7 @@ pub use cookie::ParseError as CookieParseError;
use handler::Responder;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use httpresponse::{HttpResponse, HttpResponseParts};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations
@ -33,21 +34,45 @@ use httpresponse::HttpResponse;
/// `Result`.
pub type Result<T, E = Error> = result::Result<T, E>;
/// General purpose actix web error
/// General purpose actix web error.
///
/// An actix web error is used to carry errors from `failure` or `std::error`
/// through actix in a convenient way. It can be created through
/// converting errors with `into()`.
///
/// Whenever it is created from an external object a response error is created
/// for it that can be used to create an http response from it this means that
/// if you have access to an actix `Error` you can always get a
/// `ResponseError` reference from it.
pub struct Error {
cause: Box<ResponseError>,
backtrace: Option<Backtrace>,
}
impl Error {
/// Returns a reference to the underlying cause of this Error.
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665
/// Deprecated way to reference the underlying response error.
#[deprecated(
since = "0.6.0",
note = "please use `Error::as_response_error()` instead"
)]
pub fn cause(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Returns a reference to the underlying cause of this `Error` as `Fail`
pub fn as_fail(&self) -> &Fail {
self.cause.as_fail()
}
/// Returns the reference to the underlying `ResponseError`.
pub fn as_response_error(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Returns a reference to the Backtrace carried by this error, if it
/// carries one.
///
/// This uses the same `Backtrace` type that `failure` uses.
pub fn backtrace(&self) -> &Backtrace {
if let Some(bt) = self.cause.backtrace() {
bt
@ -55,10 +80,54 @@ impl Error {
self.backtrace.as_ref().unwrap()
}
}
/// Attempts to downcast this `Error` to a particular `Fail` type by
/// reference.
///
/// If the underlying error is not of type `T`, this will return `None`.
pub fn downcast_ref<T: Fail>(&self) -> Option<&T> {
// in the most trivial way the cause is directly of the requested type.
if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) {
return Some(rv);
}
// in the more complex case the error has been constructed from a failure
// error. This happens because we implement From<failure::Error> by
// calling compat() and then storing it here. In failure this is
// represented by a failure::Error being wrapped in a failure::Compat.
//
// So we first downcast into that compat, to then further downcast through
// the failure's Error downcasting system into the original failure.
let compat: Option<&failure::Compat<failure::Error>> =
Fail::downcast_ref(self.cause.as_fail());
compat.and_then(|e| e.get_ref().downcast_ref())
}
}
/// Helper trait to downcast a response error into a fail.
///
/// This is currently not exposed because it's unclear if this is the best way
/// to achieve the downcasting on `Error` for which this is needed.
#[doc(hidden)]
pub trait InternalResponseErrorAsFail {
#[doc(hidden)]
fn as_fail(&self) -> &Fail;
#[doc(hidden)]
fn as_mut_fail(&mut self) -> &mut Fail;
}
#[doc(hidden)]
impl<T: ResponseError> InternalResponseErrorAsFail for T {
fn as_fail(&self) -> &Fail {
self
}
fn as_mut_fail(&mut self) -> &mut Fail {
self
}
}
/// Error that can be converted to `HttpResponse`
pub trait ResponseError: Fail {
pub trait ResponseError: Fail + InternalResponseErrorAsFail {
/// Create response for error
///
/// Internal server error is generated by default.
@ -88,7 +157,7 @@ impl fmt::Debug for Error {
}
}
/// `HttpResponse` for `Error`
/// Convert `Error` to a `HttpResponse` instance
impl From<Error> for HttpResponse {
fn from(err: Error) -> Self {
HttpResponse::from_error(err)
@ -111,11 +180,9 @@ impl<T: ResponseError> From<T> for Error {
}
/// Compatibility for `failure::Error`
impl<T> ResponseError for failure::Compat<T>
where
T: fmt::Display + fmt::Debug + Sync + Send + 'static,
{
}
impl<T> ResponseError for failure::Compat<T> where
T: fmt::Display + fmt::Debug + Sync + Send + 'static
{}
impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error {
@ -126,6 +193,12 @@ impl From<failure::Error> for Error {
/// `InternalServerError` for `JsonError`
impl ResponseError for JsonError {}
/// `InternalServerError` for `FormError`
impl ResponseError for FormError {}
/// `InternalServerError` for `TimerError`
impl ResponseError for TimerError {}
/// `InternalServerError` for `UrlParseError`
impl ResponseError for UrlParseError {}
@ -289,8 +362,18 @@ impl From<IoError> for PayloadError {
}
}
/// `InternalServerError` for `PayloadError`
impl ResponseError for PayloadError {}
/// `PayloadError` returns two possible results:
///
/// - `Overflow` returns `PayloadTooLarge`
/// - Other errors returns `BadRequest`
impl ResponseError for PayloadError {
fn error_response(&self) -> HttpResponse {
match *self {
PayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE),
_ => HttpResponse::new(StatusCode::BAD_REQUEST),
}
}
}
/// Return `BadRequest` for `cookie::ParseError`
impl ResponseError for cookie::ParseError {
@ -299,40 +382,6 @@ impl ResponseError for cookie::ParseError {
}
}
/// Http range header parsing error
#[derive(Fail, PartialEq, Debug)]
pub enum HttpRangeError {
/// Returned if range is invalid.
#[fail(display = "Range header is invalid")]
InvalidRange,
/// Returned if first-byte-pos of all of the byte-range-spec
/// values is greater than the content size.
/// See `https://github.com/golang/go/commit/aa9b3d7`
#[fail(
display = "First-byte-pos of all of the byte-range-spec values is greater than the content size"
)]
NoOverlap,
}
/// Return `BadRequest` for `HttpRangeError`
impl ResponseError for HttpRangeError {
fn error_response(&self) -> HttpResponse {
HttpResponse::with_body(
StatusCode::BAD_REQUEST,
"Invalid Range header provided",
)
}
}
impl From<HttpRangeParseError> for HttpRangeError {
fn from(err: HttpRangeParseError) -> HttpRangeError {
match err {
HttpRangeParseError::InvalidRange => HttpRangeError::InvalidRange,
HttpRangeParseError::NoOverlap => HttpRangeError::NoOverlap,
}
}
}
/// A set of errors that can occur during parsing multipart streams
#[derive(Fail, Debug)]
pub enum MultipartError {
@ -416,8 +465,10 @@ pub enum UrlencodedError {
/// Can not decode chunked transfer encoding
#[fail(display = "Can not decode chunked transfer encoding")]
Chunked,
/// Payload size is bigger than 256k
#[fail(display = "Payload size is bigger than 256k")]
/// Payload size is bigger than allowed. (default: 256kB)
#[fail(
display = "Urlencoded payload size is bigger than allowed. (default: 256kB)"
)]
Overflow,
/// Payload size is now known
#[fail(display = "Payload size is now known")]
@ -457,8 +508,8 @@ impl From<PayloadError> for UrlencodedError {
/// A set of errors that can occur during parsing json payloads
#[derive(Fail, Debug)]
pub enum JsonPayloadError {
/// Payload size is bigger than 256k
#[fail(display = "Payload size is bigger than 256k")]
/// Payload size is bigger than allowed. (default: 256kB)
#[fail(display = "Json payload size is bigger than allowed. (default: 256kB)")]
Overflow,
/// Content type error
#[fail(display = "Content type error")]
@ -495,6 +546,30 @@ impl From<JsonError> for JsonPayloadError {
}
}
/// Error type returned when reading body as lines.
pub enum ReadlinesError {
/// Error when decoding a line.
EncodingError,
/// Payload error.
PayloadError(PayloadError),
/// Line limit exceeded.
LimitOverflow,
/// ContentType error.
ContentTypeError(ContentTypeError),
}
impl From<PayloadError> for ReadlinesError {
fn from(err: PayloadError) -> Self {
ReadlinesError::PayloadError(err)
}
}
impl From<ContentTypeError> for ReadlinesError {
fn from(err: ContentTypeError) -> Self {
ReadlinesError::ContentTypeError(err)
}
}
/// Errors which can occur when attempting to interpret a segment string as a
/// valid path segment.
#[derive(Fail, Debug, PartialEq)]
@ -520,12 +595,13 @@ impl ResponseError for UriSegmentError {
/// Errors which can occur when attempting to generate resource uri.
#[derive(Fail, Debug, PartialEq)]
pub enum UrlGenerationError {
/// Resource not found
#[fail(display = "Resource not found")]
ResourceNotFound,
/// Not all path pattern covered
#[fail(display = "Not all path pattern covered")]
NotEnoughElements,
#[fail(display = "Router is not available")]
RouterNotAvailable,
/// URL parse error
#[fail(display = "{}", _0)]
ParseError(#[cause] UrlParseError),
}
@ -539,6 +615,24 @@ impl From<UrlParseError> for UrlGenerationError {
}
}
/// Errors which can occur when serving static files.
#[derive(Fail, Debug, PartialEq)]
pub enum StaticFileError {
/// Path is not a directory
#[fail(display = "Path is not a directory. Unable to serve static files")]
IsNotDirectory,
/// Cannot render directory
#[fail(display = "Unable to render directory without index file")]
IsDirectory,
}
/// Return `NotFound` for `StaticFileError`
impl ResponseError for StaticFileError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::NOT_FOUND)
}
}
/// Helper type that can wrap any error and generate custom response.
///
/// In following example any `io::Error` will be converted into "BAD REQUEST"
@ -551,8 +645,8 @@ impl From<UrlParseError> for UrlGenerationError {
/// use actix_web::fs::NamedFile;
///
/// fn index(req: HttpRequest) -> Result<fs::NamedFile> {
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
/// Ok(f)
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
/// Ok(f)
/// }
/// # fn main() {}
/// ```
@ -562,12 +656,9 @@ pub struct InternalError<T> {
backtrace: Backtrace,
}
unsafe impl<T> Sync for InternalError<T> {}
unsafe impl<T> Send for InternalError<T> {}
enum InternalErrorType {
Status(StatusCode),
Response(RefCell<Option<HttpResponse>>),
Response(Box<Mutex<Option<HttpResponseParts>>>),
}
impl<T> InternalError<T> {
@ -582,9 +673,10 @@ impl<T> InternalError<T> {
/// Create `InternalError` with predefined `HttpResponse`.
pub fn from_response(cause: T, response: HttpResponse) -> Self {
let resp = response.into_parts();
InternalError {
cause,
status: InternalErrorType::Response(RefCell::new(Some(response))),
status: InternalErrorType::Response(Box::new(Mutex::new(Some(resp)))),
backtrace: Backtrace::new(),
}
}
@ -625,8 +717,8 @@ where
match self.status {
InternalErrorType::Status(st) => HttpResponse::new(st),
InternalErrorType::Response(ref resp) => {
if let Some(resp) = resp.borrow_mut().take() {
resp
if let Some(resp) = resp.lock().unwrap().take() {
HttpResponse::from_parts(resp)
} else {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
}
@ -757,6 +849,46 @@ where
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
}
/// Helper function that creates wrapper of any error and
/// generate *NOT IMPLEMENTED* response.
#[allow(non_snake_case)]
pub fn ErrorNotImplemented<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into()
}
/// Helper function that creates wrapper of any error and
/// generate *BAD GATEWAY* response.
#[allow(non_snake_case)]
pub fn ErrorBadGateway<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::BAD_GATEWAY).into()
}
/// Helper function that creates wrapper of any error and
/// generate *SERVICE UNAVAILABLE* response.
#[allow(non_snake_case)]
pub fn ErrorServiceUnavailable<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into()
}
/// Helper function that creates wrapper of any error and
/// generate *GATEWAY TIMEOUT* response.
#[allow(non_snake_case)]
pub fn ErrorGatewayTimeout<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
}
#[cfg(test)]
mod tests {
use super::*;
@ -781,9 +913,6 @@ mod tests {
let resp: HttpResponse = ParseError::Incomplete.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = HttpRangeError::InvalidRange.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = CookieParseError::EmptyName.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
@ -796,7 +925,7 @@ mod tests {
}
#[test]
fn test_cause() {
fn test_as_fail() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned();
let e = ParseError::Io(orig);
@ -814,7 +943,7 @@ mod tests {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned();
let e = Error::from(orig);
assert_eq!(format!("{}", e.cause()), desc);
assert_eq!(format!("{}", e.as_fail()), desc);
}
#[test]
@ -833,14 +962,6 @@ mod tests {
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_range_error() {
let e: HttpRangeError = HttpRangeParseError::InvalidRange.into();
assert_eq!(e, HttpRangeError::InvalidRange);
let e: HttpRangeError = HttpRangeParseError::NoOverlap.into();
assert_eq!(e, HttpRangeError::NoOverlap);
}
#[test]
fn test_expect_error() {
let resp: HttpResponse = ExpectError::Encoding.error_response();
@ -912,4 +1033,78 @@ mod tests {
let resp: HttpResponse = err.error_response();
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_error_downcasting_direct() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = DemoError.into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_downcasting_compat() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = failure::Error::from(DemoError).into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_helpers() {
let r: HttpResponse = ErrorBadRequest("err").into();
assert_eq!(r.status(), StatusCode::BAD_REQUEST);
let r: HttpResponse = ErrorUnauthorized("err").into();
assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
let r: HttpResponse = ErrorForbidden("err").into();
assert_eq!(r.status(), StatusCode::FORBIDDEN);
let r: HttpResponse = ErrorNotFound("err").into();
assert_eq!(r.status(), StatusCode::NOT_FOUND);
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
let r: HttpResponse = ErrorRequestTimeout("err").into();
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
let r: HttpResponse = ErrorConflict("err").into();
assert_eq!(r.status(), StatusCode::CONFLICT);
let r: HttpResponse = ErrorGone("err").into();
assert_eq!(r.status(), StatusCode::GONE);
let r: HttpResponse = ErrorPreconditionFailed("err").into();
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
let r: HttpResponse = ErrorExpectationFailed("err").into();
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
let r: HttpResponse = ErrorInternalServerError("err").into();
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
let r: HttpResponse = ErrorNotImplemented("err").into();
assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED);
let r: HttpResponse = ErrorBadGateway("err").into();
assert_eq!(r.status(), StatusCode::BAD_GATEWAY);
let r: HttpResponse = ErrorServiceUnavailable("err").into();
assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE);
let r: HttpResponse = ErrorGatewayTimeout("err").into();
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
}
}

113
src/extensions.rs Normal file
View File

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

View File

@ -1,21 +1,23 @@
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::str;
use std::rc::Rc;
use std::{fmt, str};
use bytes::Bytes;
use encoding::all::UTF_8;
use encoding::types::{DecoderTrap, Encoding};
use futures::{Async, Future, Poll};
use futures::{future, Async, Future, Poll};
use mime::Mime;
use serde::de::{self, DeserializeOwned};
use serde_urlencoded;
use de::PathDeserializer;
use error::{Error, ErrorNotFound, ErrorBadRequest};
use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError};
use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's path.
///
/// ## Example
@ -24,7 +26,7 @@ use httprequest::HttpRequest;
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// use actix_web::{App, Path, Result, http};
/// use actix_web::{http, App, Path, Result};
///
/// /// extract path info from "/{username}/{count}/index.html" url
/// /// {username} - deserializes to a String
@ -35,8 +37,9 @@ use httprequest::HttpRequest;
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/{count}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// "/{username}/{count}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
///
@ -48,7 +51,7 @@ use httprequest::HttpRequest;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Result, http};
/// use actix_web::{http, App, Path, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
@ -62,8 +65,9 @@ use httprequest::HttpRequest;
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub struct Path<T> {
@ -97,6 +101,12 @@ impl<T> Path<T> {
}
}
impl<T> From<T> for Path<T> {
fn from(inner: T) -> Path<T> {
Path { inner }
}
}
impl<T, S> FromRequest<S> for Path<T>
where
T: DeserializeOwned,
@ -113,6 +123,19 @@ where
}
}
impl<T: fmt::Debug> fmt::Debug for Path<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Path<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from from the request's query.
///
/// ## Example
@ -124,15 +147,24 @@ where
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Query, http};
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
///#[derive(Debug, Deserialize)]
///pub enum ResponseType {
/// Token,
/// Code
///}
///
///#[derive(Deserialize)]
///pub struct AuthRequest {
/// id: u64,
/// response_type: ResponseType,
///}
///
/// // use `with` extractor for query info
/// // this handler get called only if request's query contains `username` field
/// fn index(info: Query<Info>) -> String {
/// format!("Welcome {}!", info.username)
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
/// fn index(info: Query<AuthRequest>) -> String {
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
/// }
///
/// fn main() {
@ -173,13 +205,25 @@ where
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
let req = req.clone();
serde_urlencoded::from_str::<T>(req.query_string())
.map_err(|e| e.into())
.map(Query)
}
}
impl<T: fmt::Debug> fmt::Debug for Query<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Query<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's body.
///
/// To extract typed information from request's body, the type `T` must
@ -236,26 +280,40 @@ where
T: DeserializeOwned + 'static,
S: 'static,
{
type Config = FormConfig;
type Config = FormConfig<S>;
type Result = Box<Future<Item = Self, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let req2 = req.clone();
let err = Rc::clone(&cfg.ehandler);
Box::new(
UrlEncoded::new(req.clone())
UrlEncoded::new(req)
.limit(cfg.limit)
.from_err()
.map_err(move |e| (*err)(e, &req2))
.map(Form),
)
}
}
impl<T: fmt::Debug> fmt::Debug for Form<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Form<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
/// Form extractor configuration
///
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Form, Result, http};
/// use actix_web::{http, App, Form, Result};
///
/// #[derive(Deserialize)]
/// struct FormData {
@ -270,28 +328,43 @@ where
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::GET)
/// .with(index)
/// .limit(4096);} // <- change form extractor configuration
/// "/index.html",
/// |r| {
/// r.method(http::Method::GET)
/// // register form handler and change form extractor configuration
/// .with_config(index, |cfg| {cfg.0.limit(4096);})
/// },
/// );
/// }
/// ```
pub struct FormConfig {
pub struct FormConfig<S> {
limit: usize,
ehandler: Rc<Fn(UrlencodedError, &HttpRequest<S>) -> Error>,
}
impl FormConfig {
impl<S> FormConfig<S> {
/// Change max size of payload. By default max size is 256Kb
pub fn limit(&mut self, limit: usize) -> &mut Self {
self.limit = limit;
self
}
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
where
F: Fn(UrlencodedError, &HttpRequest<S>) -> Error + 'static,
{
self.ehandler = Rc::new(f);
self
}
}
impl Default for FormConfig {
impl<S> Default for FormConfig<S> {
fn default() -> Self {
FormConfig { limit: 262_144 }
FormConfig {
limit: 262_144,
ehandler: Rc::new(|e, _| e.into()),
}
}
}
@ -315,9 +388,8 @@ impl Default for FormConfig {
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r|
/// r.method(http::Method::GET).with(index));
/// let app = App::new()
/// .resource("/index.html", |r| r.method(http::Method::GET).with(index));
/// }
/// ```
impl<S: 'static> FromRequest<S> for Bytes {
@ -329,11 +401,7 @@ impl<S: 'static> FromRequest<S> for Bytes {
// check content-type
cfg.check_mimetype(req)?;
Ok(Box::new(
MessageBody::new(req.clone())
.limit(cfg.limit)
.from_err(),
))
Ok(Box::new(MessageBody::new(req).limit(cfg.limit).from_err()))
}
}
@ -356,12 +424,12 @@ impl<S: 'static> FromRequest<S> for Bytes {
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::GET)
/// .with(index) // <- register handler with extractor params
/// .limit(4096); // <- limit size of the payload
/// });
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET)
/// .with_config(index, |cfg| { // <- register handler with extractor params
/// cfg.0.limit(4096); // <- limit size of the payload
/// })
/// });
/// }
/// ```
impl<S: 'static> FromRequest<S> for String {
@ -377,7 +445,7 @@ impl<S: 'static> FromRequest<S> for String {
let encoding = req.encoding()?;
Ok(Box::new(
MessageBody::new(req.clone())
MessageBody::new(req)
.limit(cfg.limit)
.from_err()
.and_then(move |body| {
@ -396,6 +464,126 @@ impl<S: 'static> FromRequest<S> for String {
}
}
/// Optionally extract a field from the request
///
/// If the FromRequest for T fails, return None rather than returning an error response
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// extern crate rand;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing { name: String }
///
/// impl<S> FromRequest<S> for Thing {
/// type Config = ();
/// type Result = Result<Thing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
///
/// }
/// }
///
/// /// extract text data from request
/// fn index(supplied_thing: Option<Thing>) -> Result<String> {
/// match supplied_thing {
/// // Puns not intended
/// Some(thing) => Ok(format!("Got something: {:?}", thing)),
/// None => Ok(format!("No thing!"))
/// }
/// }
///
/// fn main() {
/// let app = App::new().resource("/users/:first", |r| {
/// r.method(http::Method::POST).with(index)
/// });
/// }
/// ```
impl<T: 'static, S: 'static> FromRequest<S> for Option<T>
where
T: FromRequest<S>,
{
type Config = T::Config;
type Result = Box<Future<Item = Option<T>, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new(T::from_request(req, cfg).into().then(|r| match r {
Ok(v) => future::ok(Some(v)),
Err(_) => future::ok(None),
}))
}
}
/// Optionally extract a field from the request or extract the Error if unsuccessful
///
/// If the FromRequest for T fails, inject Err into handler rather than returning an error response
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// extern crate rand;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing { name: String }
///
/// impl<S> FromRequest<S> for Thing {
/// type Config = ();
/// type Result = Result<Thing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
///
/// }
/// }
///
/// /// extract text data from request
/// fn index(supplied_thing: Result<Thing>) -> Result<String> {
/// match supplied_thing {
/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)),
/// Err(e) => Ok(format!("Error extracting thing: {}", e))
/// }
/// }
///
/// fn main() {
/// let app = App::new().resource("/users/:first", |r| {
/// r.method(http::Method::POST).with(index)
/// });
/// }
/// ```
impl<T: 'static, S: 'static> FromRequest<S> for Result<T, Error>
where
T: FromRequest<S>,
{
type Config = T::Config;
type Result = Box<Future<Item = Result<T, Error>, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new(T::from_request(req, cfg).into().then(future::ok))
}
}
/// Payload configuration for request's payload.
pub struct PayloadConfig {
limit: usize,
@ -508,18 +696,17 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
}
});
impl<S> FromRequest<S> for () {
type Config = ();
type Result = Self;
fn from_request(_req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {}
}
tuple_from_req!(TupleFromRequest1, (0, A));
tuple_from_req!(TupleFromRequest2, (0, A), (1, B));
tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C));
tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D));
tuple_from_req!(
TupleFromRequest5,
(0, A),
(1, B),
(2, C),
(3, D),
(4, E)
);
tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E));
tuple_from_req!(
TupleFromRequest6,
(0, A),
@ -570,9 +757,8 @@ mod tests {
use futures::{Async, Future};
use http::header;
use mime;
use resource::ResourceHandler;
use router::{Resource, Router};
use server::ServerSettings;
use resource::Resource;
use router::{ResourceDef, Router};
use test::TestRequest;
#[derive(Deserialize, Debug, PartialEq)]
@ -583,15 +769,11 @@ mod tests {
#[test]
fn test_bytes() {
let cfg = PayloadConfig::default();
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
let req = TestRequest::with_header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
match Bytes::from_request(&req, &cfg)
.unwrap()
.poll()
.unwrap()
{
match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s, Bytes::from_static(b"hello=world"));
}
@ -602,15 +784,11 @@ mod tests {
#[test]
fn test_string() {
let cfg = PayloadConfig::default();
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish();
req.payload_mut()
.unread_data(Bytes::from_static(b"hello=world"));
let req = TestRequest::with_header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
match String::from_request(&req, &cfg)
.unwrap()
.poll()
.unwrap()
{
match String::from_request(&req, &cfg).unwrap().poll().unwrap() {
Async::Ready(s) => {
assert_eq!(s, "hello=world");
}
@ -620,13 +798,12 @@ mod tests {
#[test]
fn test_form() {
let mut req = TestRequest::with_header(
let 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"));
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
let mut cfg = FormConfig::default();
cfg.limit(4096);
@ -638,9 +815,101 @@ mod tests {
}
}
#[test]
fn test_option() {
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).finish();
let mut cfg = FormConfig::default();
cfg.limit(4096);
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(r, None),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(
r,
Some(Form(Info {
hello: "world".into()
}))
),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world"))
.finish();
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(r, None),
_ => unreachable!(),
}
}
#[test]
fn test_result() {
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default())
.poll()
.unwrap()
{
Async::Ready(Ok(r)) => assert_eq!(
r,
Form(Info {
hello: "world".into()
})
),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world"))
.finish();
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default())
.poll()
.unwrap()
{
Async::Ready(r) => assert!(r.is_err()),
_ => unreachable!(),
}
}
#[test]
fn test_payload_config() {
let req = HttpRequest::default();
let req = TestRequest::default().finish();
let mut cfg = PayloadConfig::default();
cfg.mimetype(mime::APPLICATION_JSON);
assert!(cfg.check_mimetype(&req).is_err());
@ -675,17 +944,12 @@ mod tests {
#[test]
fn test_request_extract() {
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
let req = TestRequest::with_uri("/name/user1/?id=test").finish();
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut routes = Vec::new();
routes.push((
Resource::new("index", "/{key}/{value}/"),
Some(resource),
));
let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some());
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
let s = Path::<MyStruct>::from_request(&req, &()).unwrap();
assert_eq!(s.key, "name");
@ -698,8 +962,11 @@ mod tests {
let s = Query::<Id>::from_request(&req, &()).unwrap();
assert_eq!(s.id, "test");
let mut req = TestRequest::with_uri("/name/32/").finish();
assert!(router.recognize(&mut req).is_some());
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let req = TestRequest::with_uri("/name/32/").finish();
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
let s = Path::<Test2>::from_request(&req, &()).unwrap();
assert_eq!(s.as_ref().key, "name");
@ -716,31 +983,23 @@ mod tests {
#[test]
fn test_extract_path_single() {
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut routes = Vec::new();
routes.push((Resource::new("index", "/{value}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes);
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
let mut req = TestRequest::with_uri("/32/").finish();
assert!(router.recognize(&mut req).is_some());
assert_eq!(*Path::<i8>::from_request(&mut req, &()).unwrap(), 32);
let req = TestRequest::with_uri("/32/").finish();
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
assert_eq!(*Path::<i8>::from_request(&req, &()).unwrap(), 32);
}
#[test]
fn test_tuple_extract() {
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut routes = Vec::new();
routes.push((
Resource::new("index", "/{key}/{value}/"),
Some(resource),
));
let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some());
let req = TestRequest::with_uri("/name/user1/?id=test").finish();
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
let res = match <(Path<(String, String)>,)>::extract(&req).poll() {
Ok(Async::Ready(res)) => res,
@ -759,5 +1018,7 @@ mod tests {
assert_eq!((res.0).1, "user1");
assert_eq!((res.1).0, "name");
assert_eq!((res.1).1, "user1");
let () = <()>::extract(&req);
}
}

1349
src/fs.rs

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,10 @@ use futures::future::{err, ok, Future};
use futures::{Async, Poll};
use error::Error;
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use resource::DefaultResource;
/// Trait defines object that could be registered as route handler
#[allow(unused_variables)]
@ -15,7 +17,7 @@ pub trait Handler<S>: 'static {
type Result: Responder;
/// Handle request
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result;
fn handle(&self, req: &HttpRequest<S>) -> Self::Result;
}
/// Trait implemented by types that generate responses for clients.
@ -61,22 +63,24 @@ pub trait FromRequest<S>: Sized {
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::future::Future;
/// use actix_web::{AsyncResponder, Either, Error, HttpRequest, HttpResponse};
/// use futures::future::result;
/// use actix_web::{Either, Error, HttpRequest, HttpResponse, AsyncResponder};
///
/// type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>;
///
/// type RegisterResult =
/// Either<HttpResponse, Box<Future<Item = HttpResponse, Error = Error>>>;
///
/// fn index(req: HttpRequest) -> RegisterResult {
/// if is_a_variant() { // <- choose variant A
/// Either::A(
/// HttpResponse::BadRequest().body("Bad data"))
/// if is_a_variant() {
/// // <- choose variant A
/// Either::A(HttpResponse::BadRequest().body("Bad data"))
/// } else {
/// Either::B( // <- variant B
/// Either::B(
/// // <- variant B
/// result(Ok(HttpResponse::Ok()
/// .content_type("text/html")
/// .body("Hello!")))
/// .responder())
/// .content_type("text/html")
/// .body("Hello!")))
/// .responder(),
/// )
/// }
/// }
/// # fn is_a_variant() -> bool { true }
@ -130,6 +134,26 @@ where
}
}
impl<T> Responder for Option<T>
where
T: Responder,
{
type Item = AsyncResult<HttpResponse>;
type Error = Error;
fn respond_to<S: 'static>(
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
match self {
Some(t) => match t.respond_to(req) {
Ok(val) => Ok(val.into()),
Err(err) => Err(err.into()),
},
None => Ok(req.build_response(StatusCode::NOT_FOUND).finish().into()),
}
}
}
/// Convenience trait that converts `Future` object to a `Boxed` future
///
/// For example loading json from request's body is async operation.
@ -138,16 +162,17 @@ where
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use futures::future::Future;
/// use actix_web::{
/// App, HttpRequest, HttpResponse, HttpMessage, Error, AsyncResponder};
/// App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse,
/// };
/// use futures::future::Future;
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
@ -159,6 +184,7 @@ where
/// # fn main() {}
/// ```
pub trait AsyncResponder<I, E>: Sized {
/// Convert to a boxed future
fn responder(self) -> Box<Future<Item = I, Error = E>>;
}
@ -176,12 +202,12 @@ where
/// Handler<S> for Fn()
impl<F, R, S> Handler<S> for F
where
F: Fn(HttpRequest<S>) -> R + 'static,
F: Fn(&HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
type Result = R;
fn handle(&mut self, req: HttpRequest<S>) -> R {
fn handle(&self, req: &HttpRequest<S>) -> R {
(self)(req)
}
}
@ -327,13 +353,17 @@ impl<T, E: Into<Error>> From<Result<T, E>> for AsyncResult<T> {
}
}
impl<T, E: Into<Error>> From<Result<Box<Future<Item = T, Error = Error>>, E>>
for AsyncResult<T>
impl<T, E> From<Result<Box<Future<Item = T, Error = E>>, E>> for AsyncResult<T>
where
T: 'static,
E: Into<Error> + 'static,
{
#[inline]
fn from(res: Result<Box<Future<Item = T, Error = Error>>, E>) -> Self {
fn from(res: Result<Box<Future<Item = T, Error = E>>, E>) -> Self {
match res {
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(fut))),
Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(Box::new(
fut.map_err(|e| e.into()),
)))),
Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))),
}
}
@ -362,7 +392,8 @@ where
self, req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
let req = req.clone();
let fut = self.map_err(|e| e.into())
let fut = self
.map_err(|e| e.into())
.then(move |r| match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => ok(resp),
@ -374,9 +405,16 @@ where
}
}
// /// Trait defines object that could be registered as resource route
pub(crate) trait RouteHandler<S>: 'static {
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse>;
fn handle(&self, &HttpRequest<S>) -> AsyncResult<HttpResponse>;
fn has_default_resource(&self) -> bool {
false
}
fn default_resource(&mut self, _: DefaultResource<S>) {}
fn finish(&mut self) {}
}
/// Route handler wrapper for Handler
@ -397,10 +435,7 @@ where
S: 'static,
{
pub fn new(h: H) -> Self {
WrapHandler {
h,
s: PhantomData,
}
WrapHandler { h, s: PhantomData }
}
}
@ -410,8 +445,8 @@ where
R: Responder + 'static,
S: 'static,
{
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
match self.h.handle(req.clone()).respond_to(&req) {
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
match self.h.handle(req).respond_to(req) {
Ok(reply) => reply.into(),
Err(err) => AsyncResult::err(err.into()),
}
@ -421,7 +456,7 @@ where
/// Async route handler
pub(crate) struct AsyncHandler<S, H, F, R, E>
where
H: Fn(HttpRequest<S>) -> F + 'static,
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
@ -433,7 +468,7 @@ where
impl<S, H, F, R, E> AsyncHandler<S, H, F, R, E>
where
H: Fn(HttpRequest<S>) -> F + 'static,
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
@ -449,23 +484,24 @@ where
impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
where
H: Fn(HttpRequest<S>) -> F + 'static,
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
S: 'static,
{
fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
let fut = (self.h)(req.clone())
.map_err(|e| e.into())
.then(move |r| match r.respond_to(&req) {
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
let req = req.clone();
let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| {
match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => Either::A(ok(resp)),
AsyncResultItem::Err(e) => Either::A(err(e)),
AsyncResultItem::Future(fut) => Either::B(fut),
},
Err(e) => Either::A(err(e)),
});
}
});
AsyncResult::async(Box::new(fut))
}
}
@ -481,10 +517,12 @@ where
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, State, http};
/// use actix_web::{http, App, Path, State};
///
/// /// Application state
/// struct MyApp {msg: &'static str}
/// struct MyApp {
/// msg: &'static str,
/// }
///
/// #[derive(Deserialize)]
/// struct Info {
@ -492,14 +530,16 @@ where
/// }
///
/// /// extract path info using serde
/// fn index(state: State<MyApp>, info: Path<Info>) -> String {
/// format!("{} {}!", state.msg, info.username)
/// fn index(data: (State<MyApp>, Path<Info>)) -> String {
/// let (state, path) = data;
/// format!("{} {}!", state.msg, path.username)
/// }
///
/// fn main() {
/// let app = App::with_state(MyApp{msg: "Welcome"}).resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor
/// let app = App::with_state(MyApp { msg: "Welcome" }).resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub struct State<S>(HttpRequest<S>);

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,7 @@ header! {
}
impl Date {
/// Create a date instance set to the current system time
pub fn now() -> Date {
Date(SystemTime::now().into())
}

View File

@ -13,7 +13,7 @@ pub use self::accept_language::AcceptLanguage;
pub use self::accept::Accept;
pub use self::allow::Allow;
pub use self::cache_control::{CacheControl, CacheDirective};
//pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam};
pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam};
pub use self::content_language::ContentLanguage;
pub use self::content_range::{ContentRange, ContentRangeSpec};
pub use self::content_type::ContentType;
@ -74,8 +74,6 @@ macro_rules! test_header {
($id:ident, $raw:expr) => {
#[test]
fn $id() {
#[allow(unused, deprecated)]
use std::ascii::AsciiExt;
use test;
let raw = $raw;
let a: Vec<Vec<u8>> = raw.iter().map(|x| x.to_vec()).collect();
@ -336,7 +334,7 @@ mod accept_language;
mod accept;
mod allow;
mod cache_control;
//mod content_disposition;
mod content_disposition;
mod content_language;
mod content_range;
mod content_type;

View File

@ -8,6 +8,7 @@ use bytes::{Bytes, BytesMut};
use mime::Mime;
use modhttp::header::GetAll;
use modhttp::Error as HttpError;
use percent_encoding;
pub use modhttp::header::*;
@ -40,7 +41,7 @@ pub trait IntoHeaderValue: Sized {
/// The type returned in the event of a conversion error.
type Error: Into<HttpError>;
/// Cast from PyObject to a concrete Python object type.
/// Try to convert value to a Header value.
fn try_into(self) -> Result<HeaderValue, Self::Error>;
}
@ -127,16 +128,18 @@ pub enum ContentEncoding {
impl ContentEncoding {
#[inline]
pub fn is_compression(&self) -> bool {
match *self {
/// Is the content compressed?
pub fn is_compression(self) -> bool {
match self {
ContentEncoding::Identity | ContentEncoding::Auto => false,
_ => true,
}
}
#[inline]
pub fn as_str(&self) -> &'static str {
match *self {
/// Convert content encoding to string
pub fn as_str(self) -> &'static str {
match self {
#[cfg(feature = "brotli")]
ContentEncoding::Br => "br",
#[cfg(feature = "flate2")]
@ -149,8 +152,8 @@ impl ContentEncoding {
#[inline]
/// default quality value
pub fn quality(&self) -> f64 {
match *self {
pub fn quality(self) -> f64 {
match self {
#[cfg(feature = "brotli")]
ContentEncoding::Br => 1.1,
#[cfg(feature = "flate2")]
@ -220,8 +223,7 @@ pub fn from_comma_delimited<T: FromStr>(
.filter_map(|x| match x.trim() {
"" => None,
y => Some(y),
})
.filter_map(|x| x.trim().parse().ok()),
}).filter_map(|x| x.trim().parse().ok()),
)
}
Ok(result)
@ -257,3 +259,213 @@ where
}
Ok(())
}
// From hyper v0.11.27 src/header/parsing.rs
/// The value part of an extended parameter consisting of three parts:
/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
/// and a character sequence representing the actual value (`value`), separated by single quote
/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
#[derive(Clone, Debug, PartialEq)]
pub struct ExtendedValue {
/// The character set that is used to encode the `value` to a string.
pub charset: Charset,
/// The human language details of the `value`, if available.
pub language_tag: Option<LanguageTag>,
/// The parameter value, as expressed in octets.
pub value: Vec<u8>,
}
/// Parses extended header parameter values (`ext-value`), as defined in
/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
///
/// Extended values are denoted by parameter names that end with `*`.
///
/// ## ABNF
///
/// ```text
/// ext-value = charset "'" [ language ] "'" value-chars
/// ; like RFC 2231's <extended-initial-value>
/// ; (see [RFC2231], Section 7)
///
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
///
/// mime-charset = 1*mime-charsetc
/// mime-charsetc = ALPHA / DIGIT
/// / "!" / "#" / "$" / "%" / "&"
/// / "+" / "-" / "^" / "_" / "`"
/// / "{" / "}" / "~"
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
/// ; except that the single quote is not included
/// ; SHOULD be registered in the IANA charset registry
///
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
///
/// value-chars = *( pct-encoded / attr-char )
///
/// pct-encoded = "%" HEXDIG HEXDIG
/// ; see [RFC3986], Section 2.1
///
/// attr-char = ALPHA / DIGIT
/// / "!" / "#" / "$" / "&" / "+" / "-" / "."
/// / "^" / "_" / "`" / "|" / "~"
/// ; token except ( "*" / "'" / "%" )
/// ```
pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, ::error::ParseError> {
// Break into three pieces separated by the single-quote character
let mut parts = val.splitn(3, '\'');
// Interpret the first piece as a Charset
let charset: Charset = match parts.next() {
None => return Err(::error::ParseError::Header),
Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?,
};
// Interpret the second piece as a language tag
let language_tag: Option<LanguageTag> = match parts.next() {
None => return Err(::error::ParseError::Header),
Some("") => None,
Some(s) => match s.parse() {
Ok(lt) => Some(lt),
Err(_) => return Err(::error::ParseError::Header),
},
};
// Interpret the third piece as a sequence of value characters
let value: Vec<u8> = match parts.next() {
None => return Err(::error::ParseError::Header),
Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
};
Ok(ExtendedValue {
value,
charset,
language_tag,
})
}
impl fmt::Display for ExtendedValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let encoded_value = percent_encoding::percent_encode(
&self.value[..],
self::percent_encoding_http::HTTP_VALUE,
);
if let Some(ref lang) = self.language_tag {
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
} else {
write!(f, "{}''{}", self.charset, encoded_value)
}
}
}
/// Percent encode a sequence of bytes with a character set defined in
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
///
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result {
let encoded =
percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE);
fmt::Display::fmt(&encoded, f)
}
mod percent_encoding_http {
use percent_encoding;
// internal module because macro is hard-coded to make a public item
// but we don't want to public export this item
define_encode_set! {
// This encode set is used for HTTP header values and is defined at
// https://tools.ietf.org/html/rfc5987#section-3.2
pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | {
' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?',
'[', '\\', ']', '{', '}'
}
}
}
#[cfg(test)]
mod tests {
use super::{parse_extended_value, ExtendedValue};
use header::shared::Charset;
use language_tags::LanguageTag;
#[test]
fn test_parse_extended_value_with_encoding_and_language_tag() {
let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode character U+00A3 (POUND SIGN)
let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Iso_8859_1, extended_value.charset);
assert!(extended_value.language_tag.is_some());
assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
assert_eq!(
vec![163, b' ', b'r', b'a', b't', b'e', b's'],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_with_encoding() {
// RFC 5987, Section 3.2.2
// Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
// and U+20AC (EURO SIGN)
let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
assert!(result.is_ok());
let extended_value = result.unwrap();
assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
assert!(extended_value.language_tag.is_none());
assert_eq!(
vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
extended_value.value
);
}
#[test]
fn test_parse_extended_value_missing_language_tag_and_encoding() {
// From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
let result = parse_extended_value("foo%20bar.html");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted() {
let result = parse_extended_value("UTF-8'missing third part");
assert!(result.is_err());
}
#[test]
fn test_parse_extended_value_partially_formatted_blank() {
let result = parse_extended_value("blank second part'");
assert!(result.is_err());
}
#[test]
fn test_fmt_extended_value_with_encoding_and_language_tag() {
let extended_value = ExtendedValue {
charset: Charset::Iso_8859_1,
language_tag: Some("en".parse().expect("Could not parse language tag")),
value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
};
assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
}
#[test]
fn test_fmt_extended_value_with_encoding() {
let extended_value = ExtendedValue {
charset: Charset::Ext("UTF-8".to_string()),
language_tag: None,
value: vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
],
};
assert_eq!(
"UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
format!("{}", extended_value)
);
}
}

View File

@ -1,5 +1,3 @@
#![allow(unused, deprecated)]
use std::ascii::AsciiExt;
use std::fmt::{self, Display};
use std::str::FromStr;
@ -68,7 +66,7 @@ pub enum Charset {
}
impl Charset {
fn name(&self) -> &str {
fn label(&self) -> &str {
match *self {
Us_Ascii => "US-ASCII",
Iso_8859_1 => "ISO-8859-1",
@ -92,7 +90,7 @@ impl Charset {
Iso_8859_8_E => "ISO-8859-8-E",
Iso_8859_8_I => "ISO-8859-8-I",
Gb2312 => "GB2312",
Big5 => "5",
Big5 => "big5",
Koi8_R => "KOI8-R",
Ext(ref s) => s,
}
@ -101,7 +99,7 @@ impl Charset {
impl Display for Charset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.name())
f.write_str(self.label())
}
}
@ -131,7 +129,7 @@ impl FromStr for Charset {
"ISO-8859-8-E" => Iso_8859_8_E,
"ISO-8859-8-I" => Iso_8859_8_I,
"GB2312" => Gb2312,
"5" => Big5,
"big5" => Big5,
"KOI8-R" => Koi8_R,
s => Ext(s.to_owned()),
})

View File

@ -1,8 +1,9 @@
use std::fmt;
use std::str;
pub use self::Encoding::{Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip,
Identity, Trailers};
pub use self::Encoding::{
Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers,
};
/// A value to represent an encoding used in `Transfer-Encoding`
/// or `Accept-Encoding` header.

View File

@ -132,7 +132,8 @@ impl FromStr for EntityTag {
return Err(::error::ParseError::Header);
}
// The etag is weak if its first char is not a DQUOTE.
if slice.len() >= 2 && slice.starts_with('"')
if slice.len() >= 2
&& slice.starts_with('"')
&& check_slice_validity(&slice[1..length - 1])
{
// No need to check if the last char is a DQUOTE,
@ -141,7 +142,8 @@ impl FromStr for EntityTag {
weak: false,
tag: slice[1..length - 1].to_owned(),
});
} else if slice.len() >= 4 && slice.starts_with("W/\"")
} else if slice.len() >= 4
&& slice.starts_with("W/\"")
&& check_slice_validity(&slice[3..length - 1])
{
return Ok(EntityTag {
@ -159,7 +161,7 @@ impl IntoHeaderValue for EntityTag {
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = Writer::new();
write!(wrt, "{}", self).unwrap();
unsafe { Ok(HeaderValue::from_shared_unchecked(wrt.take())) }
HeaderValue::from_shared(wrt.take())
}
}
@ -213,10 +215,7 @@ mod tests {
format!("{}", EntityTag::strong("foobar".to_owned())),
"\"foobar\""
);
assert_eq!(
format!("{}", EntityTag::strong("".to_owned())),
"\"\""
);
assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\"");
assert_eq!(
format!("{}", EntityTag::weak("weak-etag".to_owned())),
"W/\"weak-etag\""
@ -225,10 +224,7 @@ mod tests {
format!("{}", EntityTag::weak("\u{0065}".to_owned())),
"W/\"\x65\""
);
assert_eq!(
format!("{}", EntityTag::weak("".to_owned())),
"W/\"\""
);
assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\"");
}
#[test]

View File

@ -64,11 +64,7 @@ impl IntoHeaderValue for HttpDate {
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = BytesMut::with_capacity(29).writer();
write!(wrt, "{}", self.0.rfc822()).unwrap();
unsafe {
Ok(HeaderValue::from_shared_unchecked(
wrt.get_mut().take().freeze(),
))
}
HeaderValue::from_shared(wrt.get_mut().take().freeze())
}
}
@ -105,9 +101,7 @@ mod tests {
#[test]
fn test_date() {
assert_eq!(
"Sun, 07 Nov 1994 08:48:37 GMT"
.parse::<HttpDate>()
.unwrap(),
"Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(),
NOV_07
);
assert_eq!(
@ -117,9 +111,7 @@ mod tests {
NOV_07
);
assert_eq!(
"Sun Nov 7 08:48:37 1994"
.parse::<HttpDate>()
.unwrap(),
"Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(),
NOV_07
);
assert!("this-is-no-date".parse::<HttpDate>().is_err());

View File

@ -1,5 +1,3 @@
#![allow(unused, deprecated)]
use std::ascii::AsciiExt;
use std::cmp;
use std::default::Default;
use std::fmt;
@ -63,11 +61,7 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
match self.quality.0 {
1000 => Ok(()),
0 => f.write_str("; q=0"),
x => write!(
f,
"; q=0.{}",
format!("{:03}", x).trim_right_matches('0')
),
x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')),
}
}
}
@ -295,10 +289,6 @@ mod tests {
#[test]
fn test_fuzzing_bugs() {
assert!("99999;".parse::<QualityItem<String>>().is_err());
assert!(
"\x0d;;;=\u{d6aa}=="
.parse::<QualityItem<String>>()
.is_err()
)
assert!("\x0d;;;=\u{d6aa}==".parse::<QualityItem<String>>().is_err())
}
}

View File

@ -36,7 +36,7 @@ use httpresponse::HttpResponse;
/// # use actix_web::*;
/// use actix_web::http::NormalizePath;
///
/// # fn index(req: HttpRequest) -> HttpResponse {
/// # fn index(req: &HttpRequest) -> HttpResponse {
/// # HttpResponse::Ok().into()
/// # }
/// fn main() {
@ -86,57 +86,41 @@ impl NormalizePath {
impl<S> Handler<S> for NormalizePath {
type Result = HttpResponse;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
if let Some(router) = req.router() {
let query = req.query_string();
if self.merge {
// merge slashes
let p = self.re_merge.replace_all(req.path(), "/");
if p.len() != req.path().len() {
if router.has_route(p.as_ref()) {
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
let query = req.query_string();
if self.merge {
// merge slashes
let p = self.re_merge.replace_all(req.path(), "/");
if p.len() != req.path().len() {
if req.resource().has_prefixed_resource(p.as_ref()) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref())
.finish();
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if req.resource().has_prefixed_resource(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref())
.header(header::LOCATION, p.as_str())
.finish();
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
}
}
}
// 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)
}.finish();
}
}
} else if p.ends_with('/') {
// try to remove trailing slash
// try to remove trailing slash
if p.ends_with('/') {
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
if req.resource().has_prefixed_resource(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(
@ -148,22 +132,36 @@ impl<S> Handler<S> for NormalizePath {
}.finish();
}
}
}
// append trailing slash
if self.append && !req.path().ends_with('/') {
let p = req.path().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else if p.ends_with('/') {
// try to remove trailing slash
let p = p.as_ref().trim_right_matches('/');
if req.resource().has_prefixed_resource(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(
header::LOCATION,
(p.to_owned() + "?" + query).as_str(),
)
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
req.header(header::LOCATION, p)
}.finish();
}
}
}
// append trailing slash
if self.append && !req.path().ends_with('/') {
let p = req.path().to_owned() + "/";
if req.resource().has_prefixed_resource(&p) {
let p = if !query.is_empty() {
p + "?" + query
} else {
p
};
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.finish();
}
}
HttpResponse::new(self.not_found)
}
}
@ -175,13 +173,13 @@ mod tests {
use http::{header, Method};
use test::TestRequest;
fn index(_req: HttpRequest) -> HttpResponse {
fn index(_req: &HttpRequest) -> HttpResponse {
HttpResponse::new(StatusCode::OK)
}
#[test]
fn test_normalize_path_trailing_slashes() {
let mut app = App::new()
let app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
@ -190,16 +188,8 @@ mod tests {
// trailing slashes
let params = vec![
("/resource1", "", StatusCode::OK),
(
"/resource1/",
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource2",
"/resource2/",
StatusCode::MOVED_PERMANENTLY,
),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
("/resource2/", "", StatusCode::OK),
("/resource1?p1=1&p2=2", "", StatusCode::OK),
(
@ -215,18 +205,64 @@ mod tests {
("/resource2/?p1=1&p2=2", "", StatusCode::OK),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = resp.as_msg();
let r = &resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
);
}
}
}
#[test]
fn test_prefixed_normalize_path_trailing_slashes() {
let app = App::new()
.prefix("/test")
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/test/resource1", "", StatusCode::OK),
(
"/test/resource1/",
"/test/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/test/resource2",
"/test/resource2/",
StatusCode::MOVED_PERMANENTLY,
),
("/test/resource2/", "", StatusCode::OK),
("/test/resource1?p1=1&p2=2", "", StatusCode::OK),
(
"/test/resource1/?p1=1&p2=2",
"/test/resource1?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY,
),
(
"/test/resource2?p1=1&p2=2",
"/test/resource2/?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY,
),
("/test/resource2/?p1=1&p2=2", "", StatusCode::OK),
];
for (path, target, code) in params {
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = &resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
);
}
}
@ -234,7 +270,7 @@ mod tests {
#[test]
fn test_normalize_path_trailing_slashes_disabled() {
let mut app = App::new()
let app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| {
@ -243,8 +279,7 @@ mod tests {
true,
StatusCode::MOVED_PERMANENTLY,
))
})
.finish();
}).finish();
// trailing slashes
let params = vec![
@ -258,16 +293,16 @@ mod tests {
("/resource2/?p1=1&p2=2", StatusCode::OK),
];
for (path, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = resp.as_msg();
let r = &resp.as_msg();
assert_eq!(r.status(), code);
}
}
#[test]
fn test_normalize_path_merge_slashes() {
let mut app = App::new()
let app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
@ -276,16 +311,8 @@ mod tests {
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
(
"/resource1/",
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
(
"/resource1//",
"/resource1",
StatusCode::MOVED_PERMANENTLY,
),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
(
"//resource1//a//b",
"/resource1/a/b",
@ -349,18 +376,14 @@ mod tests {
),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = resp.as_msg();
let r = &resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
);
}
}
@ -368,7 +391,7 @@ mod tests {
#[test]
fn test_normalize_path_merge_and_append_slashes() {
let mut app = App::new()
let app = App::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
@ -533,18 +556,14 @@ mod tests {
),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let req = TestRequest::with_uri(path).request();
let resp = app.run(req);
let r = resp.as_msg();
let r = &resp.as_msg();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers()
.get(header::LOCATION)
.unwrap()
.to_str()
.unwrap()
r.headers().get(header::LOCATION).unwrap().to_str().unwrap()
);
}
}

View File

@ -5,7 +5,7 @@ use httpresponse::{HttpResponse, HttpResponseBuilder};
macro_rules! STATIC_RESP {
($name:ident, $status:expr) => {
#[allow(non_snake_case)]
#[allow(non_snake_case, missing_docs)]
pub fn $name() -> HttpResponseBuilder {
HttpResponse::build($status)
}
@ -55,10 +55,7 @@ impl HttpResponse {
STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG);
STATIC_RESP!(
UnsupportedMediaType,
StatusCode::UNSUPPORTED_MEDIA_TYPE
);
STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
@ -67,14 +64,8 @@ impl HttpResponse {
STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY);
STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE);
STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT);
STATIC_RESP!(
VersionNotSupported,
StatusCode::HTTP_VERSION_NOT_SUPPORTED
);
STATIC_RESP!(
VariantAlsoNegotiates,
StatusCode::VARIANT_ALSO_NEGOTIATES
);
STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED);
}

View File

@ -3,24 +3,31 @@ use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label;
use encoding::types::{DecoderTrap, Encoding};
use encoding::EncodingRef;
use futures::{Future, Poll, Stream};
use futures::{Async, Future, Poll, Stream};
use http::{header, HeaderMap};
use http_range::HttpRange;
use mime::Mime;
use serde::de::DeserializeOwned;
use serde_urlencoded;
use std::str;
use error::{ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError};
use error::{
ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError,
};
use header::Header;
use json::JsonBody;
use multipart::Multipart;
/// Trait that implements general purpose operations on http messages
pub trait HttpMessage {
pub trait HttpMessage: Sized {
/// Type of message payload stream
type Stream: Stream<Item = Bytes, Error = PayloadError> + Sized;
/// Read the message headers.
fn headers(&self) -> &HeaderMap;
/// Message payload stream
fn payload(&self) -> Self::Stream;
#[doc(hidden)]
/// Get a header
fn get_header<H: Header>(&self) -> Option<H>
@ -92,19 +99,6 @@ pub trait HttpMessage {
}
}
/// 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
@ -118,10 +112,11 @@ pub trait HttpMessage {
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{
/// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse,
/// };
/// use bytes::Bytes;
/// use futures::future::Future;
/// use actix_web::{HttpMessage, HttpRequest, HttpResponse,
/// FutureResponse, AsyncResponder};
///
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
/// req.body() // <- get Body future
@ -134,10 +129,7 @@ pub trait HttpMessage {
/// }
/// # fn main() {}
/// ```
fn body(self) -> MessageBody<Self>
where
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
{
fn body(&self) -> MessageBody<Self> {
MessageBody::new(self)
}
@ -148,7 +140,6 @@ pub trait HttpMessage {
/// Returns error:
///
/// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k
///
/// ## Server example
@ -158,7 +149,7 @@ pub trait HttpMessage {
/// # extern crate futures;
/// # use futures::Future;
/// # use std::collections::HashMap;
/// use actix_web::{HttpMessage, HttpRequest, HttpResponse, FutureResponse};
/// use actix_web::{FutureResponse, HttpMessage, HttpRequest, HttpResponse};
///
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
/// Box::new(
@ -167,14 +158,12 @@ pub trait HttpMessage {
/// .and_then(|params| { // <- url encoded parameters
/// println!("==== BODY ==== {:?}", params);
/// Ok(HttpResponse::Ok().into())
/// }))
/// }),
/// )
/// }
/// # fn main() {}
/// ```
fn urlencoded<T: DeserializeOwned>(self) -> UrlEncoded<Self, T>
where
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
{
fn urlencoded<T: DeserializeOwned>(&self) -> UrlEncoded<Self, T> {
UrlEncoded::new(self)
}
@ -193,14 +182,14 @@ pub trait HttpMessage {
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::*;
/// use futures::future::{Future, ok};
/// use futures::future::{ok, Future};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
@ -210,10 +199,7 @@ pub trait HttpMessage {
/// }
/// # fn main() {}
/// ```
fn json<T: DeserializeOwned>(self) -> JsonBody<Self, T>
where
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
{
fn json<T: DeserializeOwned>(&self) -> JsonBody<Self, T> {
JsonBody::new(self)
}
@ -224,16 +210,15 @@ pub trait HttpMessage {
/// ## 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 actix_web::actix::fut::FinishStream;
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// req.multipart().from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items
/// multipart::MultipartItem::Field(field) => {
@ -253,29 +238,191 @@ pub trait HttpMessage {
/// }
/// # fn main() {}
/// ```
fn multipart(self) -> Multipart<Self>
where
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
{
fn multipart(&self) -> Multipart<Self::Stream> {
let boundary = Multipart::boundary(self.headers());
Multipart::new(boundary, self)
Multipart::new(boundary, self.payload())
}
/// Return stream of lines.
fn readlines(&self) -> Readlines<Self> {
Readlines::new(self)
}
}
/// Stream to read request line by line.
pub struct Readlines<T: HttpMessage> {
stream: T::Stream,
buff: BytesMut,
limit: usize,
checked_buff: bool,
encoding: EncodingRef,
err: Option<ReadlinesError>,
}
impl<T: HttpMessage> Readlines<T> {
/// Create a new stream to read request line by line.
fn new(req: &T) -> Self {
let encoding = match req.encoding() {
Ok(enc) => enc,
Err(err) => return Self::err(req, err.into()),
};
Readlines {
stream: req.payload(),
buff: BytesMut::with_capacity(262_144),
limit: 262_144,
checked_buff: true,
err: None,
encoding,
}
}
/// Change max line size. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
fn err(req: &T, err: ReadlinesError) -> Self {
Readlines {
stream: req.payload(),
buff: BytesMut::new(),
limit: 262_144,
checked_buff: true,
encoding: UTF_8,
err: Some(err),
}
}
}
impl<T: HttpMessage + 'static> Stream for Readlines<T> {
type Item = String;
type Error = ReadlinesError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if let Some(err) = self.err.take() {
return Err(err);
}
// check if there is a newline in the buffer
if !self.checked_buff {
let mut found: Option<usize> = None;
for (ind, b) in self.buff.iter().enumerate() {
if *b == b'\n' {
found = Some(ind);
break;
}
}
if let Some(ind) = found {
// check if line is longer than limit
if ind + 1 > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = self.encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&self.buff.split_to(ind + 1))
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
.decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
return Ok(Async::Ready(Some(line)));
}
self.checked_buff = true;
}
// poll req for more bytes
match self.stream.poll() {
Ok(Async::Ready(Some(mut bytes))) => {
// check if there is a newline in bytes
let mut found: Option<usize> = None;
for (ind, b) in bytes.iter().enumerate() {
if *b == b'\n' {
found = Some(ind);
break;
}
}
if let Some(ind) = found {
// check if line is longer than limit
if ind + 1 > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = self.encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&bytes.split_to(ind + 1))
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
.decode(&bytes.split_to(ind + 1), DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
// extend buffer with rest of the bytes;
self.buff.extend_from_slice(&bytes);
self.checked_buff = false;
return Ok(Async::Ready(Some(line)));
}
self.buff.extend_from_slice(&bytes);
Ok(Async::NotReady)
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
if self.buff.is_empty() {
return Ok(Async::Ready(None));
}
if self.buff.len() > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = self.encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&self.buff)
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
self.encoding
.decode(&self.buff, DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
self.buff.clear();
Ok(Async::Ready(Some(line)))
}
Err(e) => Err(ReadlinesError::from(e)),
}
}
}
/// Future that resolves to a complete http message body.
pub struct MessageBody<T> {
pub struct MessageBody<T: HttpMessage> {
limit: usize,
req: Option<T>,
length: Option<usize>,
stream: Option<T::Stream>,
err: Option<PayloadError>,
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
}
impl<T> MessageBody<T> {
/// Create `RequestBody` for request.
pub fn new(req: T) -> MessageBody<T> {
impl<T: HttpMessage> MessageBody<T> {
/// Create `MessageBody` for request.
pub fn new(req: &T) -> MessageBody<T> {
let mut len = None;
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
} else {
return Self::err(PayloadError::UnknownLength);
}
} else {
return Self::err(PayloadError::UnknownLength);
}
}
MessageBody {
limit: 262_144,
req: Some(req),
length: len,
stream: Some(req.payload()),
fut: None,
err: None,
}
}
@ -284,67 +431,113 @@ impl<T> MessageBody<T> {
self.limit = limit;
self
}
fn err(e: PayloadError) -> Self {
MessageBody {
stream: None,
limit: 262_144,
fut: None,
err: Some(e),
length: None,
}
}
}
impl<T> Future for MessageBody<T>
where
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
T: HttpMessage + '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()),
));
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
self.fut
.as_mut()
.expect("UrlEncoded could not be used second time")
.poll()
if let Some(err) = self.err.take() {
return Err(err);
}
if let Some(len) = self.length.take() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
self.stream
.take()
.expect("Can not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), 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.poll()
}
}
/// Future that resolves to a parsed urlencoded values.
pub struct UrlEncoded<T, U> {
req: Option<T>,
pub struct UrlEncoded<T: HttpMessage, U> {
stream: Option<T::Stream>,
limit: usize,
length: Option<usize>,
encoding: EncodingRef,
err: Option<UrlencodedError>,
fut: Option<Box<Future<Item = U, Error = UrlencodedError>>>,
}
impl<T, U> UrlEncoded<T, U> {
pub fn new(req: T) -> UrlEncoded<T, U> {
impl<T: HttpMessage, U> UrlEncoded<T, U> {
/// Create a new future to URL encode a request
pub fn new(req: &T) -> UrlEncoded<T, U> {
// check content type
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Self::err(UrlencodedError::ContentType);
}
let encoding = match req.encoding() {
Ok(enc) => enc,
Err(_) => return Self::err(UrlencodedError::ContentType),
};
let mut len = None;
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
} else {
return Self::err(UrlencodedError::UnknownLength);
}
} else {
return Self::err(UrlencodedError::UnknownLength);
}
};
UrlEncoded {
req: Some(req),
encoding,
stream: Some(req.payload()),
limit: 262_144,
length: len,
fut: None,
err: None,
}
}
fn err(e: UrlencodedError) -> Self {
UrlEncoded {
stream: None,
limit: 262_144,
fut: None,
err: Some(e),
length: None,
encoding: UTF_8,
}
}
@ -357,68 +550,57 @@ impl<T, U> UrlEncoded<T, U> {
impl<T, U> Future for UrlEncoded<T, U>
where
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
T: HttpMessage + 'static,
U: DeserializeOwned + 'static,
{
type Item = U;
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 enc: *const Encoding = encoding as *const Encoding;
if enc == UTF_8 {
serde_urlencoded::from_bytes::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
} else {
let body = encoding
.decode(&body, DecoderTrap::Strict)
.map_err(|_| UrlencodedError::Parse)?;
serde_urlencoded::from_str::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
}
});
self.fut = Some(Box::new(fut));
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
self.fut
.as_mut()
if let Some(err) = self.err.take() {
return Err(err);
}
// payload size
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(UrlencodedError::Overflow);
}
}
// future
let encoding = self.encoding;
let fut = self
.stream
.take()
.expect("UrlEncoded could not be used second time")
.poll()
.from_err()
.fold(BytesMut::with_capacity(8192), 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| {
if (encoding as *const Encoding) == UTF_8 {
serde_urlencoded::from_bytes::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
} else {
let body = encoding
.decode(&body, DecoderTrap::Strict)
.map_err(|_| UrlencodedError::Parse)?;
serde_urlencoded::from_str::<U>(&body)
.map_err(|_| UrlencodedError::Parse)
}
});
self.fut = Some(Box::new(fut));
self.poll()
}
}
@ -428,10 +610,7 @@ mod tests {
use encoding::all::ISO_8859_2;
use encoding::Encoding;
use futures::Async;
use http::{Method, Uri, Version};
use httprequest::HttpRequest;
use mime;
use std::str::FromStr;
use test::TestRequest;
#[test]
@ -442,7 +621,7 @@ mod tests {
TestRequest::with_header("content-type", "application/json; charset=utf=8")
.finish();
assert_eq!(req.content_type(), "application/json");
let req = HttpRequest::default();
let req = TestRequest::default().finish();
assert_eq!(req.content_type(), "");
}
@ -450,7 +629,7 @@ mod tests {
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();
let req = TestRequest::default().finish();
assert_eq!(req.mime_type().unwrap(), None);
let req =
TestRequest::with_header("content-type", "application/json; charset=utf-8")
@ -472,7 +651,7 @@ mod tests {
#[test]
fn test_encoding() {
let req = HttpRequest::default();
let req = TestRequest::default().finish();
assert_eq!(UTF_8.name(), req.encoding().unwrap().name());
let req = TestRequest::with_header("content-type", "application/json").finish();
@ -488,10 +667,7 @@ mod tests {
#[test]
fn test_encoding_error() {
let req = TestRequest::with_header("content-type", "applicatjson").finish();
assert_eq!(
Some(ContentTypeError::ParseError),
req.encoding().err()
);
assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err());
let req = TestRequest::with_header(
"content-type",
@ -503,47 +679,20 @@ mod tests {
);
}
#[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();
let req = TestRequest::default().finish();
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,
);
let req = TestRequest::default()
.header(
header::TRANSFER_ENCODING,
Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"),
).finish();
assert!(req.chunked().is_err());
}
@ -578,18 +727,11 @@ mod tests {
#[test]
fn test_urlencoded_error() {
let req =
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::Chunked
);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "xxxx")
.finish();
.finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::UnknownLength
@ -599,7 +741,7 @@ mod tests {
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "1000000")
.finish();
.finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::Overflow
@ -616,13 +758,12 @@ mod tests {
#[test]
fn test_urlencoded() {
let mut req = TestRequest::with_header(
let 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"));
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
let result = req.urlencoded::<Info>().poll().ok().unwrap();
assert_eq!(
@ -632,13 +773,12 @@ mod tests {
})
);
let mut req = TestRequest::with_header(
let 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"));
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
let result = req.urlencoded().poll().ok().unwrap();
assert_eq!(
@ -663,20 +803,52 @@ mod tests {
_ => unreachable!("error"),
}
let mut req = HttpRequest::default();
req.payload_mut()
.unread_data(Bytes::from_static(b"test"));
let req = TestRequest::default()
.set_payload(Bytes::from_static(b"test"))
.finish();
match req.body().poll().ok().unwrap() {
Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")),
_ => unreachable!("error"),
}
let mut req = HttpRequest::default();
req.payload_mut()
.unread_data(Bytes::from_static(b"11111111111111"));
let req = TestRequest::default()
.set_payload(Bytes::from_static(b"11111111111111"))
.finish();
match req.body().limit(5).poll().err().unwrap() {
PayloadError::Overflow => (),
_ => unreachable!("error"),
}
}
#[test]
fn test_readlines() {
let req = TestRequest::default()
.set_payload(Bytes::from_static(
b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\
industry. Lorem Ipsum has been the industry's standard dummy\n\
Contrary to popular belief, Lorem Ipsum is not simply random text.",
)).finish();
let mut r = Readlines::new(&req);
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(
s,
"Lorem Ipsum is simply dummy text of the printing and typesetting\n"
),
_ => unreachable!("error"),
}
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(
s,
"industry. Lorem Ipsum has been the industry's standard dummy\n"
),
_ => unreachable!("error"),
}
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(
s,
"Contrary to popular belief, Lorem Ipsum is not simply random text."
),
_ => unreachable!("error"),
}
}
}

View File

@ -1,288 +1,180 @@
//! HTTP Request message related code.
#![cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ptr))]
use std::cell::{Ref, RefMut};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::ops::Deref;
use std::rc::Rc;
use std::{cmp, fmt, io, mem, str};
use std::{fmt, str};
use bytes::Bytes;
use cookie::Cookie;
use failure;
use futures::{Async, Poll, Stream};
use futures_cpupool::CpuPool;
use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version};
use tokio_io::AsyncRead;
use http::{header, HeaderMap, Method, StatusCode, Uri, Version};
use url::{form_urlencoded, Url};
use body::Body;
use error::{CookieParseError, PayloadError, UrlGenerationError};
use error::{CookieParseError, UrlGenerationError};
use extensions::Extensions;
use handler::FromRequest;
use httpmessage::HttpMessage;
use httpresponse::{HttpResponse, HttpResponseBuilder};
use info::ConnectionInfo;
use param::Params;
use payload::Payload;
use router::{Resource, Router};
use server::helpers::SharedHttpInnerMessage;
use uri::Url as InnerUrl;
use router::ResourceInfo;
use server::Request;
bitflags! {
pub(crate) struct MessageFlags: u8 {
const QUERY = 0b0000_0001;
const KEEPALIVE = 0b0000_0010;
struct Query(HashMap<String, String>);
struct Cookies(Vec<Cookie<'static>>);
/// An HTTP Request
pub struct HttpRequest<S = ()> {
req: Option<Request>,
state: Rc<S>,
resource: ResourceInfo,
}
impl<S> HttpMessage for HttpRequest<S> {
type Stream = Payload;
#[inline]
fn headers(&self) -> &HeaderMap {
self.request().headers()
}
}
pub struct HttpInnerMessage {
pub version: Version,
pub method: Method,
pub(crate) url: InnerUrl,
pub(crate) flags: MessageFlags,
pub headers: HeaderMap,
pub extensions: Extensions,
pub params: Params<'static>,
pub cookies: Option<Vec<Cookie<'static>>>,
pub query: Params<'static>,
pub addr: Option<SocketAddr>,
pub payload: Option<Payload>,
pub info: Option<ConnectionInfo<'static>>,
pub prefix: u16,
resource: RouterResource,
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum RouterResource {
Notset,
Normal(u16),
}
impl Default for HttpInnerMessage {
fn default() -> HttpInnerMessage {
HttpInnerMessage {
method: Method::GET,
url: InnerUrl::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
flags: MessageFlags::empty(),
params: Params::new(),
query: Params::new(),
addr: None,
cookies: None,
payload: None,
extensions: Extensions::new(),
info: None,
prefix: 0,
resource: RouterResource::Notset,
#[inline]
fn payload(&self) -> Payload {
if let Some(payload) = self.request().inner.payload.borrow_mut().take() {
payload
} else {
Payload::empty()
}
}
}
impl HttpInnerMessage {
/// Checks if a connection should be kept alive.
#[inline]
pub fn keep_alive(&self) -> bool {
self.flags.contains(MessageFlags::KEEPALIVE)
}
impl<S> Deref for HttpRequest<S> {
type Target = Request;
#[inline]
pub(crate) fn reset(&mut self) {
self.headers.clear();
self.extensions.clear();
self.params.clear();
self.addr = None;
self.info = None;
self.flags = MessageFlags::empty();
self.cookies = None;
self.payload = None;
self.prefix = 0;
self.resource = RouterResource::Notset;
}
}
lazy_static! {
static ref RESOURCE: Resource = Resource::unset();
}
/// An HTTP Request
pub struct HttpRequest<S = ()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>);
impl HttpRequest<()> {
/// Construct a new Request.
#[inline]
pub fn new(
method: Method, uri: Uri, version: Version, headers: HeaderMap,
payload: Option<Payload>,
) -> HttpRequest {
let url = InnerUrl::new(uri);
HttpRequest(
SharedHttpInnerMessage::from_message(HttpInnerMessage {
method,
url,
version,
headers,
payload,
params: Params::new(),
query: Params::new(),
extensions: Extensions::new(),
cookies: None,
addr: None,
info: None,
prefix: 0,
flags: MessageFlags::empty(),
resource: RouterResource::Notset,
}),
None,
None,
)
}
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
pub(crate) fn from_message(msg: SharedHttpInnerMessage) -> HttpRequest {
HttpRequest(msg, None, None)
}
#[inline]
/// Construct new http request with state.
pub fn with_state<S>(self, state: Rc<S>, router: Router) -> HttpRequest<S> {
HttpRequest(self.0, Some(state), Some(router))
}
}
impl<S> HttpMessage for HttpRequest<S> {
#[inline]
fn headers(&self) -> &HeaderMap {
&self.as_ref().headers
fn deref(&self) -> &Request {
self.request()
}
}
impl<S> HttpRequest<S> {
#[inline]
pub(crate) fn new(
req: Request, state: Rc<S>, resource: ResourceInfo,
) -> HttpRequest<S> {
HttpRequest {
state,
resource,
req: Some(req),
}
}
#[inline]
/// Construct new http request with state.
pub fn change_state<NS>(&self, state: Rc<NS>) -> HttpRequest<NS> {
HttpRequest(self.0.clone(), Some(state), self.2.clone())
pub(crate) fn with_state<NS>(&self, state: Rc<NS>) -> HttpRequest<NS> {
HttpRequest {
state,
req: self.req.as_ref().map(|r| r.clone()),
resource: self.resource.clone(),
}
}
#[inline]
/// Construct new http request without state.
/// Construct new http request with empty state.
pub fn drop_state(&self) -> HttpRequest {
HttpRequest(self.0.clone(), None, self.2.clone())
}
/// get mutable reference for inner message
/// mutable reference should not be returned as result for request's method
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub(crate) fn as_mut(&self) -> &mut HttpInnerMessage {
self.0.get_mut()
}
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
fn as_ref(&self) -> &HttpInnerMessage {
self.0.get_ref()
HttpRequest {
state: Rc::new(()),
req: self.req.as_ref().map(|r| r.clone()),
resource: self.resource.clone(),
}
}
#[inline]
pub(crate) fn get_inner(&mut self) -> &mut HttpInnerMessage {
self.as_mut()
/// Construct new http request with new RouteInfo.
pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest<S> {
resource.merge(&self.resource);
HttpRequest {
resource,
req: self.req.as_ref().map(|r| r.clone()),
state: self.state.clone(),
}
}
/// Shared application state
#[inline]
pub fn state(&self) -> &S {
self.1.as_ref().unwrap()
&self.state
}
#[inline]
/// Server request
pub fn request(&self) -> &Request {
self.req.as_ref().unwrap()
}
/// Request extensions
#[inline]
pub fn extensions(&self) -> &Extensions {
&self.as_ref().extensions
pub fn extensions(&self) -> Ref<Extensions> {
self.request().extensions()
}
/// Mutable reference to a the request's extensions
#[inline]
pub fn extensions_mut(&mut self) -> &mut Extensions {
&mut self.as_mut().extensions
pub fn extensions_mut(&self) -> RefMut<Extensions> {
self.request().extensions_mut()
}
/// Default `CpuPool`
#[inline]
#[doc(hidden)]
pub fn cpu_pool(&self) -> &CpuPool {
self.router()
.expect("HttpRequest has to have Router instance")
.server_settings()
.cpu_pool()
self.request().server_settings().cpu_pool()
}
#[inline]
/// Create http response
pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse {
if let Some(router) = self.router() {
router.server_settings().get_response(status, body)
} else {
HttpResponse::with_body(status, body)
}
self.request().server_settings().get_response(status, body)
}
#[inline]
/// Create http response builder
pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder {
if let Some(router) = self.router() {
router.server_settings().get_response_builder(status)
} else {
HttpResponse::build(status)
}
}
#[doc(hidden)]
pub fn prefix_len(&self) -> u16 {
self.as_ref().prefix as u16
}
#[doc(hidden)]
pub fn set_prefix_len(&mut self, len: u16) {
self.as_mut().prefix = len;
self.request()
.server_settings()
.get_response_builder(status)
}
/// Read the Request Uri.
#[inline]
pub fn uri(&self) -> &Uri {
self.as_ref().url.uri()
self.request().inner.url.uri()
}
/// Read the Request method.
#[inline]
pub fn method(&self) -> &Method {
&self.as_ref().method
&self.request().inner.method
}
/// Read the Request Version.
#[inline]
pub fn version(&self) -> Version {
self.as_ref().version
}
///Returns mutable Request's headers.
///
///This is intended to be used by middleware.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.as_mut().headers
self.request().inner.version
}
/// The target path of this Request.
#[inline]
pub fn path(&self) -> &str {
self.as_ref().url.path()
self.request().inner.url.path()
}
/// Get *ConnectionInfo* for correct request.
pub fn connection_info(&self) -> &ConnectionInfo {
if self.as_ref().info.is_none() {
let info: ConnectionInfo<'static> =
unsafe { mem::transmute(ConnectionInfo::new(self)) };
self.as_mut().info = Some(info);
}
self.as_ref().info.as_ref().unwrap()
/// Get *ConnectionInfo* for the correct request.
#[inline]
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
self.request().connection_info()
}
/// Generate url for named resource
@ -312,43 +204,22 @@ impl<S> HttpRequest<S> {
U: IntoIterator<Item = I>,
I: AsRef<str>,
{
if self.router().is_none() {
Err(UrlGenerationError::RouterNotAvailable)
} else {
let path = self.router().unwrap().resource_path(name, elements)?;
if path.starts_with('/') {
let conn = self.connection_info();
Ok(Url::parse(&format!(
"{}://{}{}",
conn.scheme(),
conn.host(),
path
))?)
} else {
Ok(Url::parse(&path)?)
}
}
self.resource.url_for(&self, name, elements)
}
/// This method returns reference to current `Router` object.
/// Generate url for named resource
///
/// This method is similar to `HttpRequest::url_for()` but it can be used
/// for urls that do not contain variable parts.
pub fn url_for_static(&self, name: &str) -> Result<Url, UrlGenerationError> {
const NO_PARAMS: [&str; 0] = [];
self.url_for(name, &NO_PARAMS)
}
/// This method returns reference to current `RouteInfo` object.
#[inline]
pub fn router(&self) -> Option<&Router> {
self.2.as_ref()
}
/// This method returns reference to matched `Resource` object.
#[inline]
pub fn resource(&self) -> &Resource {
if let Some(ref router) = self.2 {
if let RouterResource::Normal(idx) = self.as_ref().resource {
return router.get_resource(idx as usize);
}
}
&*RESOURCE
}
pub(crate) fn set_resource(&mut self, res: usize) {
self.as_mut().resource = RouterResource::Normal(res as u16);
pub fn resource(&self) -> &ResourceInfo {
&self.resource
}
/// Peer socket address
@ -360,29 +231,19 @@ impl<S> HttpRequest<S> {
/// be used.
#[inline]
pub fn peer_addr(&self) -> Option<SocketAddr> {
self.as_ref().addr
self.request().inner.addr
}
#[inline]
pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
self.as_mut().addr = addr;
}
#[doc(hidden)]
#[deprecated(since = "0.6.0", note = "please use `Query<T>` extractor")]
/// Get a reference to the Params object.
/// Params is a container for url query parameters.
pub fn query(&self) -> &Params {
if !self.as_ref().flags.contains(MessageFlags::QUERY) {
self.as_mut().flags.insert(MessageFlags::QUERY);
let params: &mut Params =
unsafe { mem::transmute(&mut self.as_mut().query) };
params.clear();
/// url query parameters.
pub fn query(&self) -> Ref<HashMap<String, String>> {
if self.extensions().get::<Query>().is_none() {
let mut query = HashMap::new();
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
params.add(key, val);
query.insert(key.as_ref().to_string(), val.to_string());
}
self.extensions_mut().insert(Query(query));
}
unsafe { mem::transmute(&self.as_ref().query) }
Ref::map(self.extensions(), |ext| &ext.get::<Query>().unwrap().0)
}
/// The query string in the URL.
@ -398,35 +259,45 @@ impl<S> HttpRequest<S> {
}
/// Load request cookies.
pub fn cookies(&self) -> Result<&Vec<Cookie<'static>>, CookieParseError> {
if self.as_ref().cookies.is_none() {
let msg = self.as_mut();
#[inline]
pub fn cookies(&self) -> Result<Ref<Vec<Cookie<'static>>>, CookieParseError> {
if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new();
for hdr in msg.headers.get_all(header::COOKIE) {
let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
for hdr in self.request().inner.headers.get_all(header::COOKIE) {
let s =
str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
for cookie_str in s.split(';').map(|s| s.trim()) {
if !cookie_str.is_empty() {
cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
}
}
}
msg.cookies = Some(cookies);
self.extensions_mut().insert(Cookies(cookies));
}
Ok(&self.as_ref().cookies.as_ref().unwrap())
Ok(Ref::map(self.extensions(), |ext| {
&ext.get::<Cookies>().unwrap().0
}))
}
/// Return request cookie.
pub fn cookie(&self, name: &str) -> Option<&Cookie> {
#[inline]
pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies {
for cookie in cookies.iter() {
if cookie.name() == name {
return Some(cookie);
return Some(cookie.to_owned());
}
}
}
None
}
pub(crate) fn set_cookies(&mut self, cookies: Option<Vec<Cookie<'static>>>) {
if let Some(cookies) = cookies {
self.extensions_mut().insert(Cookies(cookies));
}
}
/// Get a reference to the Params object.
///
/// Params is a container for url parameters.
@ -435,68 +306,39 @@ impl<S> HttpRequest<S> {
/// access the matched value for that segment.
#[inline]
pub fn match_info(&self) -> &Params {
unsafe { mem::transmute(&self.as_ref().params) }
}
/// Get mutable reference to request's Params.
#[inline]
pub fn match_info_mut(&mut self) -> &mut Params {
unsafe { mem::transmute(&mut self.as_mut().params) }
}
/// Checks if a connection should be kept alive.
pub fn keep_alive(&self) -> bool {
self.as_ref().flags.contains(MessageFlags::KEEPALIVE)
&self.resource.match_info()
}
/// Check if request requires connection upgrade
pub(crate) fn upgrade(&self) -> bool {
if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) {
if let Ok(s) = conn.to_str() {
return s.to_lowercase().contains("upgrade");
}
}
self.as_ref().method == Method::CONNECT
self.request().upgrade()
}
/// Set read buffer capacity
///
/// Default buffer capacity is 32Kb.
pub fn set_read_buffer_capacity(&mut self, cap: usize) {
if let Some(ref mut payload) = self.as_mut().payload {
if let Some(payload) = self.request().inner.payload.borrow_mut().as_mut() {
payload.set_read_buffer_capacity(cap)
}
}
#[cfg(test)]
pub(crate) fn payload(&self) -> &Payload {
let msg = self.as_mut();
if msg.payload.is_none() {
msg.payload = Some(Payload::empty());
}
msg.payload.as_ref().unwrap()
}
#[cfg(test)]
pub(crate) fn payload_mut(&mut self) -> &mut Payload {
let msg = self.as_mut();
if msg.payload.is_none() {
msg.payload = Some(Payload::empty());
}
msg.payload.as_mut().unwrap()
}
}
impl Default for HttpRequest<()> {
/// Construct default request
fn default() -> HttpRequest {
HttpRequest(SharedHttpInnerMessage::default(), None, None)
impl<S> Drop for HttpRequest<S> {
fn drop(&mut self) {
if let Some(req) = self.req.take() {
req.release();
}
}
}
impl<S> Clone for HttpRequest<S> {
fn clone(&self) -> HttpRequest<S> {
HttpRequest(self.0.clone(), self.1.clone(), self.2.clone())
HttpRequest {
req: self.req.as_ref().map(|r| r.clone()),
state: self.state.clone(),
resource: self.resource.clone(),
}
}
}
@ -510,76 +352,23 @@ impl<S> FromRequest<S> for HttpRequest<S> {
}
}
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> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = writeln!(
f,
"\nHttpRequest {:?} {}:{}",
self.as_ref().version,
self.as_ref().method,
self.version(),
self.method(),
self.path()
);
if !self.query_string().is_empty() {
let _ = writeln!(f, " query: ?{:?}", self.query_string());
}
if !self.match_info().is_empty() {
let _ = writeln!(f, " params: {:?}", self.as_ref().params);
let _ = writeln!(f, " params: {:?}", self.match_info());
}
let _ = writeln!(f, " headers:");
for (key, val) in self.as_ref().headers.iter() {
for (key, val) in self.headers().iter() {
let _ = writeln!(f, " {:?}: {:?}", key, val);
}
res
@ -589,9 +378,8 @@ impl<S> fmt::Debug for HttpRequest<S> {
#[cfg(test)]
mod tests {
use super::*;
use resource::ResourceHandler;
use router::Resource;
use server::ServerSettings;
use resource::Resource;
use router::{ResourceDef, Router};
use test::TestRequest;
#[test]
@ -603,7 +391,7 @@ mod tests {
#[test]
fn test_no_request_cookies() {
let req = HttpRequest::default();
let req = TestRequest::default().finish();
assert!(req.cookies().unwrap().is_empty());
}
@ -642,35 +430,27 @@ mod tests {
#[test]
fn test_request_match_info() {
let mut req = TestRequest::with_uri("/value/?id=test").finish();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{key}/")));
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let mut routes = Vec::new();
routes.push((Resource::new("index", "/{key}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some());
assert_eq!(req.match_info().get("key"), Some("value"));
let req = TestRequest::with_uri("/value/?id=test").finish();
let info = router.recognize(&req, &(), 0);
assert_eq!(info.match_info().get("key"), Some("value"));
}
#[test]
fn test_url_for() {
let req2 = HttpRequest::default();
assert_eq!(
req2.url_for("unknown", &["test"]),
Err(UrlGenerationError::RouterNotAvailable)
);
let mut resource = ResourceHandler::<()>::default();
let mut router = Router::<()>::default();
let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}"));
resource.name("index");
let routes = vec![(
Resource::new("index", "/user/{name}.{ext}"),
Some(resource),
)];
let (router, _) = Router::new("/", ServerSettings::default(), routes);
assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/test/unknown"));
router.register_resource(resource);
let info = router.default_route_info();
assert!(!info.has_prefixed_resource("/use/"));
assert!(info.has_resource("/user/test.html"));
assert!(info.has_prefixed_resource("/user/test.html"));
assert!(!info.has_resource("/test/unknown"));
assert!(!info.has_prefixed_resource("/test/unknown"));
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
.finish_with_router(router);
@ -692,20 +472,25 @@ mod tests {
#[test]
fn test_url_for_with_prefix() {
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
let mut resource = ResourceHandler::<()>::default();
let mut resource = Resource::new(ResourceDef::new("/user/{name}.html"));
resource.name("index");
let routes = vec![(
Resource::new("index", "/user/{name}.{ext}"),
Some(resource),
)];
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/prefix/user/test.html"));
let mut router = Router::<()>::default();
router.set_prefix("/prefix");
router.register_resource(resource);
let req = req.with_state(Rc::new(()), router);
let url = req.url_for("index", &["test", "html"]);
let mut info = router.default_route_info();
info.set_prefix(7);
assert!(!info.has_prefixed_resource("/use/"));
assert!(info.has_resource("/user/test.html"));
assert!(!info.has_prefixed_resource("/user/test.html"));
assert!(!info.has_resource("/prefix/user/test.html"));
assert!(info.has_prefixed_resource("/prefix/user/test.html"));
let req = TestRequest::with_uri("/prefix/test")
.prefix(7)
.header(header::HOST, "www.rust-lang.org")
.finish_with_router(router);
let url = req.url_for("index", &["test"]);
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/user/test.html"
@ -713,19 +498,44 @@ mod tests {
}
#[test]
fn test_url_for_external() {
let req = HttpRequest::default();
let mut resource = ResourceHandler::<()>::default();
fn test_url_for_static() {
let mut resource = Resource::new(ResourceDef::new("/index.html"));
resource.name("index");
let routes = vec![(
Resource::external("youtube", "https://youtube.com/watch/{video_id}"),
None,
)];
let (router, _) = Router::new::<()>("", ServerSettings::default(), routes);
assert!(!router.has_route("https://youtube.com/watch/unknown"));
let mut router = Router::<()>::default();
router.set_prefix("/prefix");
router.register_resource(resource);
let req = req.with_state(Rc::new(()), router);
let mut info = router.default_route_info();
info.set_prefix(7);
assert!(info.has_resource("/index.html"));
assert!(!info.has_prefixed_resource("/index.html"));
assert!(!info.has_resource("/prefix/index.html"));
assert!(info.has_prefixed_resource("/prefix/index.html"));
let req = TestRequest::with_uri("/prefix/test")
.prefix(7)
.header(header::HOST, "www.rust-lang.org")
.finish_with_router(router);
let url = req.url_for_static("index");
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/index.html"
);
}
#[test]
fn test_url_for_external() {
let mut router = Router::<()>::default();
router.register_external(
"youtube",
ResourceDef::external("https://youtube.com/watch/{video_id}"),
);
let info = router.default_route_info();
assert!(!info.has_resource("https://youtube.com/watch/unknown"));
assert!(!info.has_prefixed_resource("https://youtube.com/watch/unknown"));
let req = TestRequest::default().finish_with_router(router);
let url = req.url_for("youtube", &["oHg5SJYRHA0"]);
assert_eq!(
url.ok().unwrap().as_str(),

View File

@ -1,8 +1,7 @@
//! Http response
use std::cell::UnsafeCell;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::io::Write;
use std::rc::Rc;
use std::{fmt, mem, str};
use bytes::{BufMut, Bytes, BytesMut};
@ -36,30 +35,17 @@ pub enum ConnectionType {
}
/// An HTTP Response
pub struct HttpResponse(
Option<Box<InnerHttpResponse>>,
Rc<UnsafeCell<HttpResponsePool>>,
);
impl Drop for HttpResponse {
fn drop(&mut self) {
if let Some(inner) = self.0.take() {
HttpResponsePool::release(&self.1, inner)
}
}
}
pub struct HttpResponse(Box<InnerHttpResponse>, &'static HttpResponsePool);
impl HttpResponse {
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline]
fn get_ref(&self) -> &InnerHttpResponse {
self.0.as_ref().unwrap()
self.0.as_ref()
}
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline]
fn get_mut(&mut self) -> &mut InnerHttpResponse {
self.0.as_mut().unwrap()
self.0.as_mut()
}
/// Create http response builder with specific status.
@ -86,25 +72,34 @@ impl HttpResponse {
HttpResponsePool::with_body(status, body.into())
}
/// Constructs a error response
/// Constructs an error response
#[inline]
pub fn from_error(error: Error) -> HttpResponse {
let mut resp = error.cause().error_response();
let mut resp = error.as_response_error().error_response();
resp.get_mut().error = Some(error);
resp
}
/// Convert `HttpResponse` to a `HttpResponseBuilder`
#[inline]
pub fn into_builder(mut self) -> HttpResponseBuilder {
let response = self.0.take();
let pool = Some(Rc::clone(&self.1));
pub fn into_builder(self) -> HttpResponseBuilder {
// If this response has cookies, load them into a jar
let mut jar: Option<CookieJar> = None;
for c in self.cookies() {
if let Some(ref mut j) = jar {
j.add_original(c.into_owned());
} else {
let mut j = CookieJar::new();
j.add_original(c.into_owned());
jar = Some(j);
}
}
HttpResponseBuilder {
response,
pool,
pool: self.1,
response: Some(self.0),
err: None,
cookies: None, // TODO: convert set-cookie headers
cookies: jar,
}
}
@ -132,6 +127,51 @@ impl HttpResponse {
&mut self.get_mut().headers
}
/// Get an iterator for the cookies set by this response
#[inline]
pub fn cookies(&self) -> CookieIter {
CookieIter {
iter: self.get_ref().headers.get_all(header::SET_COOKIE).iter(),
}
}
/// Add a cookie to this response
#[inline]
pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> {
let h = &mut self.get_mut().headers;
HeaderValue::from_str(&cookie.to_string())
.map(|c| {
h.append(header::SET_COOKIE, c);
}).map_err(|e| e.into())
}
/// Remove all cookies with the given name from this response. Returns
/// the number of cookies removed.
#[inline]
pub fn del_cookie(&mut self, name: &str) -> usize {
let h = &mut self.get_mut().headers;
let vals: Vec<HeaderValue> = h
.get_all(header::SET_COOKIE)
.iter()
.map(|v| v.to_owned())
.collect();
h.remove(header::SET_COOKIE);
let mut count: usize = 0;
for v in vals {
if let Ok(s) = v.to_str() {
if let Ok(c) = Cookie::parse_encoded(s) {
if c.name() == name {
count += 1;
continue;
}
}
}
h.append(header::SET_COOKIE, v);
}
count
}
/// Get the response status code
#[inline]
pub fn status(&self) -> StatusCode {
@ -241,6 +281,21 @@ impl HttpResponse {
pub fn set_write_buffer_capacity(&mut self, cap: usize) {
self.get_mut().write_capacity = cap;
}
pub(crate) fn release(self) {
self.1.release(self.0);
}
pub(crate) fn into_parts(self) -> HttpResponseParts {
self.0.into_parts()
}
pub(crate) fn from_parts(parts: HttpResponseParts) -> HttpResponse {
HttpResponse(
Box::new(InnerHttpResponse::from_parts(parts)),
HttpResponsePool::get_pool(),
)
}
}
impl fmt::Debug for HttpResponse {
@ -261,13 +316,31 @@ impl fmt::Debug for HttpResponse {
}
}
pub struct CookieIter<'a> {
iter: header::ValueIter<'a, HeaderValue>,
}
impl<'a> Iterator for CookieIter<'a> {
type Item = Cookie<'a>;
#[inline]
fn next(&mut self) -> Option<Cookie<'a>> {
for v in self.iter.by_ref() {
if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) {
return Some(c);
}
}
None
}
}
/// An HTTP response builder
///
/// This type can be used to construct an instance of `HttpResponse` through a
/// builder-like pattern.
pub struct HttpResponseBuilder {
pool: &'static HttpResponsePool,
response: Option<Box<InnerHttpResponse>>,
pool: Option<Rc<UnsafeCell<HttpResponsePool>>>,
err: Option<HttpError>,
cookies: Option<CookieJar>,
}
@ -297,11 +370,13 @@ impl HttpResponseBuilder {
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{HttpRequest, HttpResponse, Result, http};
/// use actix_web::{http, HttpRequest, HttpResponse, Result};
///
/// fn index(req: HttpRequest) -> Result<HttpResponse> {
/// Ok(HttpResponse::Ok()
/// .set(http::header::IfModifiedSince("Sun, 07 Nov 1994 08:48:37 GMT".parse()?))
/// .set(http::header::IfModifiedSince(
/// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?,
/// ))
/// .finish())
/// }
/// fn main() {}
@ -455,10 +530,10 @@ impl HttpResponseBuilder {
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .finish())
/// .finish(),
/// )
/// .finish()
/// }
/// fn main() {}
/// ```
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
if self.cookies.is_none() {
@ -466,16 +541,27 @@ impl HttpResponseBuilder {
jar.add(cookie.into_owned());
self.cookies = Some(jar)
} else {
self.cookies
.as_mut()
.unwrap()
.add(cookie.into_owned());
self.cookies.as_mut().unwrap().add(cookie.into_owned());
}
self
}
/// Remove cookie, cookie has to be cookie from `HttpRequest::cookies()`
/// method.
/// Remove cookie
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, HttpRequest, HttpResponse, Result};
///
/// fn index(req: &HttpRequest) -> HttpResponse {
/// let mut builder = HttpResponse::Ok();
///
/// if let Some(ref cookie) = req.cookie("name") {
/// builder.del_cookie(cookie);
/// }
///
/// builder.finish()
/// }
/// ```
pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self {
{
if self.cookies.is_none() {
@ -534,9 +620,7 @@ impl HttpResponseBuilder {
if let Some(e) = self.err.take() {
return Error::from(e).into();
}
let mut response = self.response
.take()
.expect("cannot reuse response builder");
let mut response = self.response.take().expect("cannot reuse response builder");
if let Some(ref jar) = self.cookies {
for cookie in jar.delta() {
match HeaderValue::from_str(&cookie.to_string()) {
@ -546,7 +630,7 @@ impl HttpResponseBuilder {
}
}
response.body = body.into();
HttpResponse(Some(response), self.pool.take().unwrap())
HttpResponse(response, self.pool)
}
#[inline]
@ -558,16 +642,21 @@ impl HttpResponseBuilder {
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error>,
{
self.body(Body::Streaming(Box::new(
stream.map_err(|e| e.into()),
)))
self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into()))))
}
/// Set a json body and generate `HttpResponse`
///
/// `HttpResponseBuilder` can not be used after this call.
pub fn json<T: Serialize>(&mut self, value: T) -> HttpResponse {
match serde_json::to_string(&value) {
self.json2(&value)
}
/// Set a json body and generate `HttpResponse`
///
/// `HttpResponseBuilder` can not be used after this call.
pub fn json2<T: Serialize>(&mut self, value: &T) -> HttpResponse {
match serde_json::to_string(value) {
Ok(body) => {
let contains = if let Some(parts) = parts(&mut self.response, &self.err)
{
@ -596,8 +685,8 @@ impl HttpResponseBuilder {
/// This method construct new `HttpResponseBuilder`
pub fn take(&mut self) -> HttpResponseBuilder {
HttpResponseBuilder {
pool: self.pool,
response: self.response.take(),
pool: self.pool.take(),
err: self.err.take(),
cookies: self.cookies.take(),
}
@ -654,7 +743,8 @@ impl Responder for &'static str {
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
}
@ -673,7 +763,8 @@ impl Responder for &'static [u8] {
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
}
@ -692,7 +783,8 @@ impl Responder for String {
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
}
@ -711,7 +803,8 @@ impl<'a> Responder for &'a String {
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
Ok(req
.build_response(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self))
}
@ -730,7 +823,8 @@ impl Responder for Bytes {
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
}
@ -749,7 +843,8 @@ impl Responder for BytesMut {
type Error = Error;
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
Ok(req.build_response(StatusCode::OK)
Ok(req
.build_response(StatusCode::OK)
.content_type("application/octet-stream")
.body(self))
}
@ -771,13 +866,9 @@ impl<'a> From<&'a ClientResponse> for HttpResponseBuilder {
impl<'a, S> From<&'a HttpRequest<S>> for HttpResponseBuilder {
fn from(req: &'a HttpRequest<S>) -> HttpResponseBuilder {
if let Some(router) = req.router() {
router
.server_settings()
.get_response_builder(StatusCode::OK)
} else {
HttpResponse::Ok()
}
req.request()
.server_settings()
.get_response_builder(StatusCode::OK)
}
}
@ -796,6 +887,17 @@ struct InnerHttpResponse {
error: Option<Error>,
}
pub(crate) struct HttpResponseParts {
version: Option<Version>,
headers: HeaderMap,
status: StatusCode,
reason: Option<&'static str>,
body: Option<Bytes>,
encoding: Option<ContentEncoding>,
connection_type: Option<ConnectionType>,
error: Option<Error>,
}
impl InnerHttpResponse {
#[inline]
fn new(status: StatusCode, body: Body) -> InnerHttpResponse {
@ -813,38 +915,85 @@ impl InnerHttpResponse {
error: None,
}
}
/// This is for failure, we can not have Send + Sync on Streaming and Actor response
fn into_parts(mut self) -> HttpResponseParts {
let body = match mem::replace(&mut self.body, Body::Empty) {
Body::Empty => None,
Body::Binary(mut bin) => Some(bin.take()),
Body::Streaming(_) | Body::Actor(_) => {
error!("Streaming or Actor body is not support by error response");
None
}
};
HttpResponseParts {
body,
version: self.version,
headers: self.headers,
status: self.status,
reason: self.reason,
encoding: self.encoding,
connection_type: self.connection_type,
error: self.error,
}
}
fn from_parts(parts: HttpResponseParts) -> InnerHttpResponse {
let body = if let Some(ref body) = parts.body {
Body::Binary(body.clone().into())
} else {
Body::Empty
};
InnerHttpResponse {
body,
status: parts.status,
version: parts.version,
headers: parts.headers,
reason: parts.reason,
chunked: None,
encoding: parts.encoding,
connection_type: parts.connection_type,
response_size: 0,
write_capacity: MAX_WRITE_BUFFER_SIZE,
error: parts.error,
}
}
}
/// Internal use only! unsafe
pub(crate) struct HttpResponsePool(VecDeque<Box<InnerHttpResponse>>);
/// Internal use only!
pub(crate) struct HttpResponsePool(RefCell<VecDeque<Box<InnerHttpResponse>>>);
thread_local!(static POOL: Rc<UnsafeCell<HttpResponsePool>> = HttpResponsePool::pool());
thread_local!(static POOL: &'static HttpResponsePool = HttpResponsePool::pool());
impl HttpResponsePool {
pub fn pool() -> Rc<UnsafeCell<HttpResponsePool>> {
Rc::new(UnsafeCell::new(HttpResponsePool(
VecDeque::with_capacity(128),
)))
fn pool() -> &'static HttpResponsePool {
let pool = HttpResponsePool(RefCell::new(VecDeque::with_capacity(128)));
Box::leak(Box::new(pool))
}
pub fn get_pool() -> &'static HttpResponsePool {
POOL.with(|p| *p)
}
#[inline]
pub fn get_builder(
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode,
pool: &'static HttpResponsePool, status: StatusCode,
) -> HttpResponseBuilder {
let p = unsafe { &mut *pool.as_ref().get() };
if let Some(mut msg) = p.0.pop_front() {
if let Some(mut msg) = pool.0.borrow_mut().pop_front() {
msg.status = status;
HttpResponseBuilder {
pool,
response: Some(msg),
pool: Some(Rc::clone(pool)),
err: None,
cookies: None,
}
} else {
let msg = Box::new(InnerHttpResponse::new(status, Body::Empty));
HttpResponseBuilder {
pool,
response: Some(msg),
pool: Some(Rc::clone(pool)),
err: None,
cookies: None,
}
@ -853,16 +1002,15 @@ impl HttpResponsePool {
#[inline]
pub fn get_response(
pool: &Rc<UnsafeCell<HttpResponsePool>>, status: StatusCode, body: Body,
pool: &'static HttpResponsePool, status: StatusCode, body: Body,
) -> HttpResponse {
let p = unsafe { &mut *pool.as_ref().get() };
if let Some(mut msg) = p.0.pop_front() {
if let Some(mut msg) = pool.0.borrow_mut().pop_front() {
msg.status = status;
msg.body = body;
HttpResponse(Some(msg), Rc::clone(pool))
HttpResponse(msg, pool)
} else {
let msg = Box::new(InnerHttpResponse::new(status, body));
HttpResponse(Some(msg), Rc::clone(pool))
HttpResponse(msg, pool)
}
}
@ -876,13 +1024,10 @@ impl HttpResponsePool {
POOL.with(|pool| HttpResponsePool::get_response(pool, status, body))
}
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))]
fn release(
pool: &Rc<UnsafeCell<HttpResponsePool>>, mut inner: Box<InnerHttpResponse>,
) {
let pool = unsafe { &mut *pool.as_ref().get() };
if pool.0.len() < 128 {
#[inline]
fn release(&self, mut inner: Box<InnerHttpResponse>) {
let mut p = self.0.borrow_mut();
if p.len() < 128 {
inner.headers.clear();
inner.version = None;
inner.chunked = None;
@ -892,7 +1037,7 @@ impl HttpResponsePool {
inner.response_size = 0;
inner.error = None;
inner.write_capacity = MAX_WRITE_BUFFER_SIZE;
pool.0.push_front(inner);
p.push_front(inner);
}
}
}
@ -903,10 +1048,10 @@ mod tests {
use body::Binary;
use http;
use http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
use http::{Method, Uri};
use std::str::FromStr;
use time::Duration;
use test::TestRequest;
#[test]
fn test_debug() {
let resp = HttpResponse::Ok()
@ -919,17 +1064,10 @@ mod tests {
#[test]
fn test_response_cookies() {
let mut headers = HeaderMap::new();
headers.insert(COOKIE, HeaderValue::from_static("cookie1=value1"));
headers.insert(COOKIE, HeaderValue::from_static("cookie2=value2"));
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let req = TestRequest::default()
.header(COOKIE, "cookie1=value1")
.header(COOKIE, "cookie2=value2")
.finish();
let cookies = req.cookies().unwrap();
let resp = HttpResponse::Ok()
@ -940,23 +1078,46 @@ mod tests {
.http_only(true)
.max_age(Duration::days(1))
.finish(),
)
.del_cookie(&cookies[0])
).del_cookie(&cookies[0])
.finish();
let mut val: Vec<_> = resp.headers()
let mut val: Vec<_> = resp
.headers()
.get_all("Set-Cookie")
.iter()
.map(|v| v.to_str().unwrap().to_owned())
.collect();
val.sort();
assert!(val[0].starts_with("cookie2=; Max-Age=0;"));
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
assert_eq!(
val[1],
"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"
);
}
#[test]
fn test_update_response_cookies() {
let mut r = HttpResponse::Ok()
.cookie(http::Cookie::new("original", "val100"))
.finish();
r.add_cookie(&http::Cookie::new("cookie2", "val200"))
.unwrap();
r.add_cookie(&http::Cookie::new("cookie2", "val250"))
.unwrap();
r.add_cookie(&http::Cookie::new("cookie3", "val300"))
.unwrap();
assert_eq!(r.cookies().count(), 4);
r.del_cookie("cookie2");
let mut iter = r.cookies();
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("original", "val100"));
let v = iter.next().unwrap();
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
}
#[test]
fn test_basic_builder() {
let resp = HttpResponse::Ok()
@ -975,9 +1136,7 @@ mod tests {
#[test]
fn test_force_close() {
let resp = HttpResponse::build(StatusCode::OK)
.force_close()
.finish();
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
assert!(!resp.keep_alive().unwrap())
}
@ -986,10 +1145,7 @@ mod tests {
let resp = HttpResponse::build(StatusCode::OK)
.content_type("text/plain")
.body(Body::Empty);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
"text/plain"
)
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
}
#[test]
@ -1035,18 +1191,42 @@ mod tests {
);
}
#[test]
fn test_json2() {
let resp = HttpResponse::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("application/json"));
assert_eq!(
*resp.body(),
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
);
}
#[test]
fn test_json2_ct() {
let resp = HttpResponse::build(StatusCode::OK)
.header(CONTENT_TYPE, "text/json")
.json2(&vec!["v1", "v2", "v3"]);
let ct = resp.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("text/json"));
assert_eq!(
*resp.body(),
Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))
);
}
impl Body {
pub(crate) fn binary(&self) -> Option<&Binary> {
pub(crate) fn bin_ref(&self) -> &Binary {
match *self {
Body::Binary(ref bin) => Some(bin),
_ => None,
Body::Binary(ref bin) => bin,
_ => panic!(),
}
}
}
#[test]
fn test_into_response() {
let req = HttpRequest::default();
let req = TestRequest::default().finish();
let resp: HttpResponse = "test".into();
assert_eq!(resp.status(), StatusCode::OK);
@ -1055,7 +1235,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test"));
assert_eq!(resp.body().bin_ref(), &Binary::from("test"));
let resp: HttpResponse = "test".respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK);
@ -1064,7 +1244,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test"));
assert_eq!(resp.body().bin_ref(), &Binary::from("test"));
let resp: HttpResponse = b"test".as_ref().into();
assert_eq!(resp.status(), StatusCode::OK);
@ -1073,10 +1253,7 @@ mod tests {
HeaderValue::from_static("application/octet-stream")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.body().binary().unwrap(),
&Binary::from(b"test".as_ref())
);
assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref()));
let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK);
@ -1085,10 +1262,7 @@ mod tests {
HeaderValue::from_static("application/octet-stream")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.body().binary().unwrap(),
&Binary::from(b"test".as_ref())
);
assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref()));
let resp: HttpResponse = "test".to_owned().into();
assert_eq!(resp.status(), StatusCode::OK);
@ -1097,10 +1271,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.body().binary().unwrap(),
&Binary::from("test".to_owned())
);
assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned()));
let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK);
@ -1109,10 +1280,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.body().binary().unwrap(),
&Binary::from("test".to_owned())
);
assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned()));
let resp: HttpResponse = (&"test".to_owned()).into();
assert_eq!(resp.status(), StatusCode::OK);
@ -1121,10 +1289,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.body().binary().unwrap(),
&Binary::from(&"test".to_owned())
);
assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned()));
let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap();
assert_eq!(resp.status(), StatusCode::OK);
@ -1133,10 +1298,7 @@ mod tests {
HeaderValue::from_static("text/plain; charset=utf-8")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.body().binary().unwrap(),
&Binary::from(&"test".to_owned())
);
assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned()));
let b = Bytes::from_static(b"test");
let resp: HttpResponse = b.into();
@ -1147,7 +1309,7 @@ mod tests {
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.body().binary().unwrap(),
resp.body().bin_ref(),
&Binary::from(Bytes::from_static(b"test"))
);
@ -1160,7 +1322,7 @@ mod tests {
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.body().binary().unwrap(),
resp.body().bin_ref(),
&Binary::from(Bytes::from_static(b"test"))
);
@ -1172,10 +1334,7 @@ mod tests {
HeaderValue::from_static("application/octet-stream")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.body().binary().unwrap(),
&Binary::from(BytesMut::from("test"))
);
assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test")));
let b = BytesMut::from("test");
let resp: HttpResponse = b.respond_to(&req).ok().unwrap();
@ -1185,19 +1344,22 @@ mod tests {
HeaderValue::from_static("application/octet-stream")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.body().binary().unwrap(),
&Binary::from(BytesMut::from("test"))
);
assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test")));
}
#[test]
fn test_into_builder() {
let resp: HttpResponse = "test".into();
let mut resp: HttpResponse = "test".into();
assert_eq!(resp.status(), StatusCode::OK);
resp.add_cookie(&http::Cookie::new("cookie1", "val100"))
.unwrap();
let mut builder = resp.into_builder();
let resp = builder.status(StatusCode::BAD_REQUEST).finish();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let cookie = resp.cookies().next().unwrap();
assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100"));
}
}

View File

@ -1,24 +1,23 @@
use http::header::{self, HeaderName};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use std::str::FromStr;
use server::Request;
const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR";
const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST";
const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO";
const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for";
const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host";
const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto";
/// `HttpRequest` connection information
pub struct ConnectionInfo<'a> {
scheme: &'a str,
host: &'a str,
remote: Option<&'a str>,
#[derive(Clone, Default)]
pub struct ConnectionInfo {
scheme: String,
host: String,
remote: Option<String>,
peer: Option<String>,
}
impl<'a> ConnectionInfo<'a> {
impl ConnectionInfo {
/// Create *ConnectionInfo* instance for a request.
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
pub fn new<S>(req: &'a HttpRequest<S>) -> ConnectionInfo<'a> {
pub fn update(&mut self, req: &Request) {
let mut host = None;
let mut scheme = None;
let mut remote = None;
@ -53,8 +52,9 @@ impl<'a> ConnectionInfo<'a> {
// scheme
if scheme.is_none() {
if let Some(h) = req.headers()
.get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap())
if let Some(h) = req
.headers()
.get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap())
{
if let Ok(h) = h.to_str() {
scheme = h.split(',').next().map(|v| v.trim());
@ -62,20 +62,17 @@ impl<'a> ConnectionInfo<'a> {
}
if scheme.is_none() {
scheme = req.uri().scheme_part().map(|a| a.as_str());
if scheme.is_none() {
if let Some(router) = req.router() {
if router.server_settings().secure() {
scheme = Some("https")
}
}
if scheme.is_none() && req.server_settings().secure() {
scheme = Some("https")
}
}
}
// host
if host.is_none() {
if let Some(h) = req.headers()
.get(HeaderName::from_str(X_FORWARDED_HOST).unwrap())
if let Some(h) = req
.headers()
.get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap())
{
if let Ok(h) = h.to_str() {
host = h.split(',').next().map(|v| v.trim());
@ -88,9 +85,7 @@ impl<'a> ConnectionInfo<'a> {
if host.is_none() {
host = req.uri().authority_part().map(|a| a.as_str());
if host.is_none() {
if let Some(router) = req.router() {
host = Some(router.server_settings().host());
}
host = Some(req.server_settings().host());
}
}
}
@ -98,8 +93,9 @@ impl<'a> ConnectionInfo<'a> {
// remote addr
if remote.is_none() {
if let Some(h) = req.headers()
.get(HeaderName::from_str(X_FORWARDED_FOR).unwrap())
if let Some(h) = req
.headers()
.get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap())
{
if let Ok(h) = h.to_str() {
remote = h.split(',').next().map(|v| v.trim());
@ -111,12 +107,10 @@ impl<'a> ConnectionInfo<'a> {
}
}
ConnectionInfo {
scheme: scheme.unwrap_or("http"),
host: host.unwrap_or("localhost"),
remote,
peer,
}
self.scheme = scheme.unwrap_or("http").to_owned();
self.host = host.unwrap_or("localhost").to_owned();
self.remote = remote.map(|s| s.to_owned());
self.peer = peer;
}
/// Scheme of the request.
@ -128,7 +122,7 @@ impl<'a> ConnectionInfo<'a> {
/// - Uri
#[inline]
pub fn scheme(&self) -> &str {
self.scheme
&self.scheme
}
/// Hostname of the request.
@ -141,7 +135,7 @@ impl<'a> ConnectionInfo<'a> {
/// - Uri
/// - Server hostname
pub fn host(&self) -> &str {
self.host
&self.host
}
/// Remote IP of client initiated HTTP request.
@ -153,7 +147,7 @@ impl<'a> ConnectionInfo<'a> {
/// - peer name of opened socket
#[inline]
pub fn remote(&self) -> Option<&str> {
if let Some(r) = self.remote {
if let Some(ref r) = self.remote {
Some(r)
} else if let Some(ref peer) = self.peer {
Some(peer)
@ -166,62 +160,58 @@ impl<'a> ConnectionInfo<'a> {
#[cfg(test)]
mod tests {
use super::*;
use http::header::HeaderValue;
use test::TestRequest;
#[test]
fn test_forwarded() {
let req = HttpRequest::default();
let info = ConnectionInfo::new(&req);
let req = TestRequest::default().request();
let mut info = ConnectionInfo::default();
info.update(&req);
assert_eq!(info.scheme(), "http");
assert_eq!(info.host(), "localhost");
assert_eq!(info.host(), "localhost:8080");
let mut req = HttpRequest::default();
req.headers_mut().insert(
header::FORWARDED,
HeaderValue::from_static(
let req = TestRequest::default()
.header(
header::FORWARDED,
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
),
);
).request();
let info = ConnectionInfo::new(&req);
let mut info = ConnectionInfo::default();
info.update(&req);
assert_eq!(info.scheme(), "https");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.remote(), Some("192.0.2.60"));
let mut req = HttpRequest::default();
req.headers_mut().insert(
header::HOST,
HeaderValue::from_static("rust-lang.org"),
);
let req = TestRequest::default()
.header(header::HOST, "rust-lang.org")
.request();
let info = ConnectionInfo::new(&req);
let mut info = ConnectionInfo::default();
info.update(&req);
assert_eq!(info.scheme(), "http");
assert_eq!(info.host(), "rust-lang.org");
assert_eq!(info.remote(), None);
let mut req = HttpRequest::default();
req.headers_mut().insert(
HeaderName::from_str(X_FORWARDED_FOR).unwrap(),
HeaderValue::from_static("192.0.2.60"),
);
let info = ConnectionInfo::new(&req);
let req = TestRequest::default()
.header(X_FORWARDED_FOR, "192.0.2.60")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
assert_eq!(info.remote(), Some("192.0.2.60"));
let mut req = HttpRequest::default();
req.headers_mut().insert(
HeaderName::from_str(X_FORWARDED_HOST).unwrap(),
HeaderValue::from_static("192.0.2.60"),
);
let info = ConnectionInfo::new(&req);
let req = TestRequest::default()
.header(X_FORWARDED_HOST, "192.0.2.60")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
assert_eq!(info.host(), "192.0.2.60");
assert_eq!(info.remote(), None);
let mut req = HttpRequest::default();
req.headers_mut().insert(
HeaderName::from_str(X_FORWARDED_PROTO).unwrap(),
HeaderValue::from_static("https"),
);
let info = ConnectionInfo::new(&req);
let req = TestRequest::default()
.header(X_FORWARDED_PROTO, "https")
.request();
let mut info = ConnectionInfo::default();
info.update(&req);
assert_eq!(info.scheme(), "https");
}
}

View File

@ -1,4 +1,4 @@
use bytes::{Bytes, BytesMut};
use bytes::BytesMut;
use futures::{Future, Poll, Stream};
use http::header::CONTENT_LENGTH;
use std::fmt;
@ -10,7 +10,7 @@ use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
use error::{Error, JsonPayloadError, PayloadError};
use error::{Error, JsonPayloadError};
use handler::{FromRequest, Responder};
use http::StatusCode;
use httpmessage::HttpMessage;
@ -69,7 +69,9 @@ use httpresponse::HttpResponse;
/// }
///
/// fn index(req: HttpRequest) -> Result<Json<MyObj>> {
/// Ok(Json(MyObj{name: req.match_info().query("name")?}))
/// Ok(Json(MyObj {
/// name: req.match_info().query("name")?,
/// }))
/// }
/// # fn main() {}
/// ```
@ -121,7 +123,8 @@ impl<T: Serialize> Responder for Json<T> {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
let body = serde_json::to_string(&self.0)?;
Ok(req.build_response(StatusCode::OK)
Ok(req
.build_response(StatusCode::OK)
.content_type("application/json")
.body(body))
}
@ -137,12 +140,12 @@ where
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
let req = req.clone();
let req2 = req.clone();
let err = Rc::clone(&cfg.ehandler);
Box::new(
JsonBody::new(req.clone())
JsonBody::new(req)
.limit(cfg.limit)
.map_err(move |e| (*err)(e, req))
.map_err(move |e| (*err)(e, &req2))
.map(Json),
)
}
@ -153,7 +156,7 @@ where
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Json, HttpResponse, Result, http, error};
/// use actix_web::{error, http, App, HttpResponse, Json, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
@ -166,21 +169,21 @@ where
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/index.html", |r| {
/// r.method(http::Method::POST)
/// .with(index)
/// .limit(4096) // <- change json extractor configuration
/// .error_handler(|err, req| { // <- create custom error response
/// error::InternalError::from_response(
/// err, HttpResponse::Conflict().finish()).into()
/// });
/// });
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::POST)
/// .with_config(index, |cfg| {
/// cfg.0.limit(4096) // <- change json extractor configuration
/// .error_handler(|err, req| { // <- create custom error response
/// error::InternalError::from_response(
/// err, HttpResponse::Conflict().finish()).into()
/// });
/// })
/// });
/// }
/// ```
pub struct JsonConfig<S> {
limit: usize,
ehandler: Rc<Fn(JsonPayloadError, HttpRequest<S>) -> Error>,
ehandler: Rc<Fn(JsonPayloadError, &HttpRequest<S>) -> Error>,
}
impl<S> JsonConfig<S> {
@ -193,7 +196,7 @@ impl<S> JsonConfig<S> {
/// Set custom error handler
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
where
F: Fn(JsonPayloadError, HttpRequest<S>) -> Error + 'static,
F: Fn(JsonPayloadError, &HttpRequest<S>) -> Error + 'static,
{
self.ehandler = Rc::new(f);
self
@ -222,15 +225,15 @@ impl<S> Default for JsonConfig<S> {
/// # extern crate actix_web;
/// # extern crate futures;
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse};
/// use futures::future::Future;
/// use actix_web::{AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error};
///
/// #[derive(Deserialize, Debug)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
/// req.json() // <- get JsonBody future
/// .from_err()
/// .and_then(|val: MyObj| { // <- deserialized value
@ -240,19 +243,48 @@ impl<S> Default for JsonConfig<S> {
/// }
/// # fn main() {}
/// ```
pub struct JsonBody<T, U: DeserializeOwned> {
pub struct JsonBody<T: HttpMessage, U: DeserializeOwned> {
limit: usize,
req: Option<T>,
length: Option<usize>,
stream: Option<T::Stream>,
err: Option<JsonPayloadError>,
fut: Option<Box<Future<Item = U, Error = JsonPayloadError>>>,
}
impl<T, U: DeserializeOwned> JsonBody<T, U> {
impl<T: HttpMessage, U: DeserializeOwned> JsonBody<T, U> {
/// Create `JsonBody` for request.
pub fn new(req: T) -> Self {
pub fn new(req: &T) -> Self {
// check content-type
let json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
} else {
false
};
if !json {
return JsonBody {
limit: 262_144,
length: None,
stream: None,
fut: None,
err: Some(JsonPayloadError::ContentType),
};
}
let mut len = None;
if let Some(l) = req.headers().get(CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
}
}
}
JsonBody {
limit: 262_144,
req: Some(req),
length: len,
stream: Some(req.payload()),
fut: None,
err: None,
}
}
@ -263,55 +295,41 @@ impl<T, U: DeserializeOwned> JsonBody<T, U> {
}
}
impl<T, U: DeserializeOwned + 'static> Future for JsonBody<T, U>
where
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
{
impl<T: HttpMessage + 'static, U: DeserializeOwned + 'static> Future for JsonBody<T, U> {
type Item = U;
type Error = JsonPayloadError;
fn poll(&mut self) -> Poll<U, JsonPayloadError> {
if let Some(req) = self.req.take() {
if let Some(len) = req.headers().get(CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<usize>() {
if len > self.limit {
return Err(JsonPayloadError::Overflow);
}
} else {
return Err(JsonPayloadError::Overflow);
}
}
}
// check content-type
let json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
} else {
false
};
if !json {
return Err(JsonPayloadError::ContentType);
}
let limit = self.limit;
let fut = req.from_err()
.fold(BytesMut::new(), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut));
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
self.fut
.as_mut()
if let Some(err) = self.err.take() {
return Err(err);
}
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(JsonPayloadError::Overflow);
}
}
let fut = self
.stream
.take()
.expect("JsonBody could not be used second time")
.poll()
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(JsonPayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
}).and_then(|body| Ok(serde_json::from_slice::<U>(&body)?));
self.fut = Some(Box::new(fut));
self.poll()
}
}
@ -323,7 +341,8 @@ mod tests {
use http::header;
use handler::Handler;
use with::{ExtractorConfig, With};
use test::TestRequest;
use with::With;
impl PartialEq for JsonPayloadError {
fn eq(&self, other: &JsonPayloadError) -> bool {
@ -351,7 +370,7 @@ mod tests {
let json = Json(MyObject {
name: "test".to_owned(),
});
let resp = json.respond_to(&HttpRequest::default()).unwrap();
let resp = json.respond_to(&TestRequest::default().finish()).unwrap();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"application/json"
@ -360,47 +379,39 @@ mod tests {
#[test]
fn test_json_body() {
let req = HttpRequest::default();
let req = TestRequest::default().finish();
let mut json = req.json::<MyObject>();
assert_eq!(
json.poll().err().unwrap(),
JsonPayloadError::ContentType
);
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
let mut req = HttpRequest::default();
req.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
);
let req = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
).finish();
let mut json = req.json::<MyObject>();
assert_eq!(
json.poll().err().unwrap(),
JsonPayloadError::ContentType
);
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
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,
header::HeaderValue::from_static("10000"),
);
let req = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
).finish();
let mut json = req.json::<MyObject>().limit(100);
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,
header::HeaderValue::from_static("16"),
);
req.payload_mut()
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
let req = TestRequest::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
let mut json = req.json::<MyObject>();
assert_eq!(
json.poll().ok().unwrap(),
@ -412,24 +423,21 @@ mod tests {
#[test]
fn test_with_json() {
let mut cfg = ExtractorConfig::<_, Json<MyObject>>::default();
let mut cfg = JsonConfig::default();
cfg.limit(4096);
let mut handler = With::new(|data: Json<MyObject>| data, cfg);
let handler = With::new(|data: Json<MyObject>| data, cfg);
let req = HttpRequest::default();
assert!(handler.handle(req).as_err().is_some());
let req = TestRequest::default().finish();
assert!(handler.handle(&req).as_err().is_some());
let mut req = HttpRequest::default();
req.headers_mut().insert(
let req = TestRequest::with_header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
);
req.headers_mut().insert(
).header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
);
req.payload_mut()
.unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
assert!(handler.handle(req).as_err().is_none())
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
assert!(handler.handle(&req).as_err().is_none())
}
}

View File

@ -2,21 +2,21 @@
//! for Rust.
//!
//! ```rust
//! use actix_web::{server, App, Path};
//! use actix_web::{server, App, Path, Responder};
//! # use std::thread;
//!
//! fn index(info: Path<(String, u32)>) -> String {
//! format!("Hello {}! id:{}", info.0, info.1)
//! fn index(info: Path<(String, u32)>) -> impl Responder {
//! format!("Hello {}! id:{}", info.0, info.1)
//! }
//!
//! fn main() {
//! # thread::spawn(|| {
//! server::new(
//! || App::new()
//! .resource("/{name}/{id}/index.html", |r| r.with(index)))
//! .bind("127.0.0.1:8080").unwrap()
//! # thread::spawn(|| {
//! server::new(|| {
//! App::new().resource("/{name}/{id}/index.html", |r| r.with(index))
//! }).bind("127.0.0.1:8080")
//! .unwrap()
//! .run();
//! # });
//! # });
//! }
//! ```
//!
@ -25,7 +25,7 @@
//! Besides the API documentation (which you are currently looking
//! at!), several other resources are available:
//!
//! * [User Guide](https://actix.rs/book/actix-web/)
//! * [User Guide](https://actix.rs/docs/)
//! * [Chat on gitter](https://gitter.im/actix/actix)
//! * [GitHub repository](https://github.com/actix/actix-web)
//! * [Cargo package](https://crates.io/crates/actix-web)
@ -59,15 +59,33 @@
//! * SSL support with OpenSSL or `native-tls`
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
//! * Built on top of [Actix actor framework](https://github.com/actix/actix)
//! * Supported Rust version: 1.24 or later
//! * Supported Rust version: 1.26 or later
//!
//! ## Package feature
//!
//! * `tls` - enables ssl support via `native-tls` crate
//! * `alpn` - enables ssl support via `openssl` crate, require for `http/2`
//! support
//! * `uds` - enables support for making client requests via Unix Domain Sockets.
//! Unix only. Not necessary for *serving* requests.
//! * `session` - enables session support, includes `ring` crate as
//! dependency
//! * `brotli` - enables `brotli` compression support, requires `c`
//! compiler
//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires
//! `c` compiler
//! * `flate2-rust` - experimental rust based implementation for
//! `gzip`, `deflate` compression.
//!
#![cfg_attr(actix_nightly, feature(
specialization, // for impl ErrorResponse for std::error::Error
extern_prelude,
))]
#![cfg_attr(
feature = "cargo-clippy",
allow(decimal_literal_representation, suspicious_arithmetic_impl)
)]
#![warn(missing_docs)]
#[macro_use]
extern crate log;
@ -87,19 +105,25 @@ extern crate lazy_static;
extern crate futures;
extern crate cookie;
extern crate futures_cpupool;
extern crate htmlescape;
extern crate http as modhttp;
extern crate http_range;
extern crate httparse;
extern crate language_tags;
extern crate libc;
extern crate lazycell;
extern crate mime;
extern crate mime_guess;
extern crate mio;
extern crate net2;
extern crate parking_lot;
extern crate rand;
extern crate slab;
extern crate tokio_core;
extern crate tokio;
extern crate tokio_io;
extern crate tokio_reactor;
extern crate tokio_tcp;
extern crate tokio_timer;
#[cfg(all(unix, feature = "uds"))]
extern crate tokio_uds;
extern crate url;
#[macro_use]
extern crate serde;
@ -110,12 +134,13 @@ extern crate encoding;
extern crate flate2;
extern crate h2 as http2;
extern crate num_cpus;
extern crate serde_urlencoded;
#[macro_use]
extern crate percent_encoding;
extern crate serde_json;
extern crate serde_urlencoded;
extern crate smallvec;
#[macro_use]
extern crate actix;
extern crate actix as actix_inner;
#[cfg(test)]
#[macro_use]
@ -131,10 +156,20 @@ extern crate openssl;
#[cfg(feature = "openssl")]
extern crate tokio_openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
#[cfg(feature = "rust-tls")]
extern crate tokio_rustls;
#[cfg(feature = "rust-tls")]
extern crate webpki;
#[cfg(feature = "rust-tls")]
extern crate webpki_roots;
mod application;
mod body;
mod context;
mod de;
mod extensions;
mod extractor;
mod handler;
mod header;
@ -168,17 +203,29 @@ pub use application::App;
pub use body::{Binary, Body};
pub use context::HttpContext;
pub use error::{Error, ResponseError, Result};
pub use extensions::Extensions;
pub use extractor::{Form, Path, Query};
pub use handler::{AsyncResponder, Either, FromRequest, FutureResponse, Responder, State};
pub use handler::{
AsyncResponder, Either, FromRequest, FutureResponse, Responder, State,
};
pub use httpmessage::HttpMessage;
pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse;
pub use json::Json;
pub use scope::Scope;
pub use server::Request;
#[doc(hidden)]
#[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")]
pub use ws::WsWriter;
pub mod actix {
//! Re-exports [actix's](https://docs.rs/actix/) prelude
extern crate actix;
pub use self::actix::actors::resolver;
pub use self::actix::actors::signal;
pub use self::actix::fut;
pub use self::actix::msgs;
pub use self::actix::prelude::*;
pub use self::actix::{run, spawn};
}
#[cfg(feature = "openssl")]
pub(crate) const HAS_OPENSSL: bool = true;
@ -190,6 +237,11 @@ pub(crate) const HAS_TLS: bool = true;
#[cfg(not(feature = "tls"))]
pub(crate) const HAS_TLS: bool = false;
#[cfg(feature = "rust-tls")]
pub(crate) const HAS_RUSTLS: bool = true;
#[cfg(not(feature = "rust-tls"))]
pub(crate) const HAS_RUSTLS: bool = false;
pub mod dev {
//! The `actix-web` prelude for library developers
//!
@ -205,14 +257,16 @@ pub mod dev {
pub use context::Drain;
pub use extractor::{FormConfig, PayloadConfig};
pub use handler::{AsyncResult, Handler};
pub use httpmessage::{MessageBody, UrlEncoded};
pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
pub use httpresponse::HttpResponseBuilder;
pub use info::ConnectionInfo;
pub use json::{JsonBody, JsonConfig};
pub use param::{FromParam, Params};
pub use resource::ResourceHandler;
pub use payload::{Payload, PayloadBuffer};
pub use pipeline::Pipeline;
pub use resource::Resource;
pub use route::Route;
pub use router::{Resource, ResourceType, Router};
pub use router::{ResourceDef, ResourceInfo, ResourceType, Router};
}
pub mod http {
@ -225,12 +279,15 @@ pub mod http {
pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri};
pub use cookie::{Cookie, CookieBuilder};
pub use http_range::HttpRange;
pub use helpers::NormalizePath;
/// Various http headers
pub mod header {
pub use header::*;
pub use header::{
Charset, ContentDisposition, DispositionParam, DispositionType, LanguageTag,
};
}
pub use header::ContentEncoding;
pub use httpresponse::ConnectionType;

View File

@ -11,7 +11,7 @@
//! constructed backend.
//!
//! Cors middleware could be used as parameter for `App::middleware()` or
//! `ResourceHandler::middleware()` methods. But you have to use
//! `Resource::middleware()` methods. But you have to use
//! `Cors::for_app()` method to support *preflight* OPTIONS request.
//!
//!
@ -19,16 +19,16 @@
//!
//! ```rust
//! # extern crate actix_web;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//! use actix_web::middleware::cors::Cors;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//!
//! fn index(mut req: HttpRequest) -> &'static str {
//! "Hello world"
//! "Hello world"
//! }
//!
//! fn main() {
//! let app = App::new()
//! .configure(|app| Cors::for_app(app) // <- Construct CORS middleware builder
//! let app = App::new().configure(|app| {
//! Cors::for_app(app) // <- Construct CORS middleware builder
//! .allowed_origin("https://www.rust-lang.org/")
//! .allowed_methods(vec!["GET", "POST"])
//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
@ -38,7 +38,8 @@
//! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
//! })
//! .register());
//! .register()
//! });
//! }
//! ```
//! In this example custom *CORS* middleware get registered for "/index.html"
@ -58,7 +59,9 @@ use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started};
use resource::ResourceHandler;
use resource::Resource;
use router::ResourceDef;
use server::Request;
/// A set of errors that can occur during processing CORS
#[derive(Debug, Fail)]
@ -206,6 +209,7 @@ impl Default for Cors {
}
impl Cors {
/// Build a new CORS middleware instance
pub fn build() -> CorsBuilder<()> {
CorsBuilder {
cors: Some(Inner {
@ -232,18 +236,20 @@ impl Cors {
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder
/// let app = App::new().configure(
/// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/")
/// .resource("/resource", |r| { // register resource
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// })
/// .register() // construct CORS and return application instance
/// );
/// .register()
/// }, // construct CORS and return application instance
/// );
/// }
/// ```
pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> {
@ -272,16 +278,16 @@ impl Cors {
/// adds route for *OPTIONS* preflight requests.
///
/// It is possible to register *Cors* middleware with
/// `ResourceHandler::middleware()` method, but in that case *Cors*
/// `Resource::middleware()` method, but in that case *Cors*
/// middleware wont be able to handle *OPTIONS* requests.
pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) {
pub fn register<S: 'static>(self, resource: &mut Resource<S>) {
resource
.method(Method::OPTIONS)
.h(|_| HttpResponse::Ok());
.h(|_: &_| HttpResponse::Ok());
resource.middleware(self);
}
fn validate_origin<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CorsError> {
fn validate_origin(&self, req: &Request) -> Result<(), CorsError> {
if let Some(hdr) = req.headers().get(header::ORIGIN) {
if let Ok(origin) = hdr.to_str() {
return match self.inner.origins {
@ -301,15 +307,12 @@ impl Cors {
}
}
fn validate_allowed_method<S>(
&self, req: &mut HttpRequest<S>,
) -> Result<(), CorsError> {
if let Some(hdr) = req.headers()
.get(header::ACCESS_CONTROL_REQUEST_METHOD)
{
fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> {
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
if let Ok(meth) = hdr.to_str() {
if let Ok(method) = Method::try_from(meth) {
return self.inner
return self
.inner
.methods
.get(&method)
.and_then(|_| Some(()))
@ -322,14 +325,12 @@ impl Cors {
}
}
fn validate_allowed_headers<S>(
&self, req: &mut HttpRequest<S>,
) -> Result<(), CorsError> {
fn validate_allowed_headers(&self, req: &Request) -> Result<(), CorsError> {
match self.inner.headers {
AllOrSome::All => Ok(()),
AllOrSome::Some(ref allowed_headers) => {
if let Some(hdr) = req.headers()
.get(header::ACCESS_CONTROL_REQUEST_HEADERS)
if let Some(hdr) =
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
{
if let Ok(headers) = hdr.to_str() {
let mut hdrs = HashSet::new();
@ -355,11 +356,11 @@ impl Cors {
}
impl<S> Middleware<S> for Cors {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
if self.inner.preflight && Method::OPTIONS == *req.method() {
self.validate_origin(req)?;
self.validate_allowed_method(req)?;
self.validate_allowed_headers(req)?;
self.validate_allowed_method(&req)?;
self.validate_allowed_headers(&req)?;
// allowed headers
let headers = if let Some(headers) = self.inner.headers.as_ref() {
@ -371,8 +372,8 @@ impl<S> Middleware<S> for Cors {
.as_str()[1..],
).unwrap(),
)
} else if let Some(hdr) = req.headers()
.get(header::ACCESS_CONTROL_REQUEST_HEADERS)
} else if let Some(hdr) =
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
{
Some(hdr.clone())
} else {
@ -386,12 +387,10 @@ impl<S> Middleware<S> for Cors {
header::ACCESS_CONTROL_MAX_AGE,
format!("{}", max_age).as_str(),
);
})
.if_some(headers, |headers, resp| {
}).if_some(headers, |headers, resp| {
let _ =
resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
})
.if_true(self.inner.origins.is_all(), |resp| {
}).if_true(self.inner.origins.is_all(), |resp| {
if self.inner.send_wildcard {
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
} else {
@ -401,35 +400,35 @@ impl<S> Middleware<S> for Cors {
origin.clone(),
);
}
})
.if_true(self.inner.origins.is_some(), |resp| {
}).if_true(self.inner.origins.is_some(), |resp| {
resp.header(
header::ACCESS_CONTROL_ALLOW_ORIGIN,
self.inner.origins_str.as_ref().unwrap().clone(),
);
})
.if_true(self.inner.supports_credentials, |resp| {
}).if_true(self.inner.supports_credentials, |resp| {
resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
})
.header(
}).header(
header::ACCESS_CONTROL_ALLOW_METHODS,
&self.inner
&self
.inner
.methods
.iter()
.fold(String::new(), |s, v| s + "," + v.as_str())
.as_str()[1..],
)
.finish(),
).finish(),
))
} else {
self.validate_origin(req)?;
// Only check requests with a origin header.
if req.headers().contains_key(header::ORIGIN) {
self.validate_origin(req)?;
}
Ok(Started::Done)
}
}
fn response(
&self, req: &mut HttpRequest<S>, mut resp: HttpResponse,
&self, req: &HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> {
match self.inner.origins {
AllOrSome::All => {
@ -493,8 +492,8 @@ impl<S> Middleware<S> for Cors {
/// ```rust
/// # extern crate http;
/// # extern crate actix_web;
/// use http::header;
/// use actix_web::middleware::cors;
/// use http::header;
///
/// # fn main() {
/// let cors = cors::Cors::build()
@ -511,7 +510,7 @@ pub struct CorsBuilder<S = ()> {
methods: bool,
error: Option<http::Error>,
expose_hdrs: HashSet<HeaderName>,
resources: Vec<(String, ResourceHandler<S>)>,
resources: Vec<Resource<S>>,
app: Option<App<S>>,
}
@ -766,12 +765,13 @@ impl<S: 'static> CorsBuilder<S> {
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder
/// let app = App::new().configure(
/// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/")
/// .allowed_methods(vec!["GET", "POST"])
/// .allowed_header(http::header::CONTENT_TYPE)
@ -783,19 +783,20 @@ impl<S: 'static> CorsBuilder<S> {
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .register() // construct CORS and return application instance
/// );
/// .register()
/// }, // construct CORS and return application instance
/// );
/// }
/// ```
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S>
where
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
F: FnOnce(&mut Resource<S>) -> R + 'static,
{
// add resource handler
let mut handler = ResourceHandler::default();
f(&mut handler);
let mut resource = Resource::new(ResourceDef::new(path));
f(&mut resource);
self.resources.push((path.to_owned(), handler));
self.resources.push(resource);
self
}
@ -833,7 +834,7 @@ impl<S: 'static> CorsBuilder<S> {
cors.expose_hdrs = Some(
self.expose_hdrs
.iter()
.fold(String::new(), |s, v| s + v.as_str())[1..]
.fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..]
.to_owned(),
);
}
@ -866,14 +867,15 @@ impl<S: 'static> CorsBuilder<S> {
}
let cors = self.construct();
let mut app = self.app
let mut app = self
.app
.take()
.expect("CorsBuilder has to be constructed with Cors::for_app(app)");
// register resources
for (path, mut resource) in self.resources.drain(..) {
for mut resource in self.resources.drain(..) {
cors.clone().register(&mut resource);
app.register_resource(&path, resource);
app.register_resource(resource);
}
app
@ -937,10 +939,9 @@ mod tests {
#[test]
fn validate_origin_allows_all_origins() {
let cors = Cors::default();
let mut req =
TestRequest::with_header("Origin", "https://www.example.com").finish();
let req = TestRequest::with_header("Origin", "https://www.example.com").finish();
assert!(cors.start(&mut req).ok().unwrap().is_done())
assert!(cors.start(&req).ok().unwrap().is_done())
}
#[test]
@ -953,29 +954,28 @@ mod tests {
.allowed_header(header::CONTENT_TYPE)
.finish();
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.finish();
assert!(cors.start(&mut req).is_err());
assert!(cors.start(&req).is_err());
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
.method(Method::OPTIONS)
.finish();
assert!(cors.start(&mut req).is_err());
assert!(cors.start(&req).is_err());
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.header(
header::ACCESS_CONTROL_REQUEST_HEADERS,
"AUTHORIZATION,ACCEPT",
)
.method(Method::OPTIONS)
).method(Method::OPTIONS)
.finish();
let resp = cors.start(&mut req).unwrap().response();
let resp = cors.start(&req).unwrap().response();
assert_eq!(
&b"*"[..],
resp.headers()
@ -999,19 +999,18 @@ mod tests {
// as_bytes());
Rc::get_mut(&mut cors.inner).unwrap().preflight = false;
assert!(cors.start(&mut req).unwrap().is_done());
assert!(cors.start(&req).unwrap().is_done());
}
#[test]
#[should_panic(expected = "MissingOrigin")]
fn test_validate_missing_origin() {
let cors = Cors::build()
.allowed_origin("https://www.example.com")
.finish();
let mut req = HttpRequest::default();
cors.start(&mut req).unwrap();
}
// #[test]
// #[should_panic(expected = "MissingOrigin")]
// fn test_validate_missing_origin() {
// let cors = Cors::build()
// .allowed_origin("https://www.example.com")
// .finish();
// let mut req = HttpRequest::default();
// cors.start(&req).unwrap();
// }
#[test]
#[should_panic(expected = "OriginNotAllowed")]
@ -1020,10 +1019,10 @@ mod tests {
.allowed_origin("https://www.example.com")
.finish();
let mut req = TestRequest::with_header("Origin", "https://www.unknown.com")
let req = TestRequest::with_header("Origin", "https://www.unknown.com")
.method(Method::GET)
.finish();
cors.start(&mut req).unwrap();
cors.start(&req).unwrap();
}
#[test]
@ -1032,30 +1031,30 @@ mod tests {
.allowed_origin("https://www.example.com")
.finish();
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::GET)
.finish();
assert!(cors.start(&mut req).unwrap().is_done());
assert!(cors.start(&req).unwrap().is_done());
}
#[test]
fn test_no_origin_response() {
let cors = Cors::build().finish();
let mut req = TestRequest::default().method(Method::GET).finish();
let req = TestRequest::default().method(Method::GET).finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&mut req, resp).unwrap().response();
let resp = cors.response(&req, resp).unwrap().response();
assert!(
resp.headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
.is_none()
);
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.finish();
let resp = cors.response(&mut req, resp).unwrap().response();
let resp = cors.response(&req, resp).unwrap().response();
assert_eq!(
&b"https://www.example.com"[..],
resp.headers()
@ -1067,21 +1066,23 @@ mod tests {
#[test]
fn test_response() {
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
let cors = Cors::build()
.send_wildcard()
.disable_preflight()
.max_age(3600)
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone())
.allowed_header(header::CONTENT_TYPE)
.finish();
let mut req = TestRequest::with_header("Origin", "https://www.example.com")
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&mut req, resp).unwrap().response();
let resp = cors.response(&req, resp).unwrap().response();
assert_eq!(
&b"*"[..],
resp.headers()
@ -1094,10 +1095,25 @@ mod tests {
resp.headers().get(header::VARY).unwrap().as_bytes()
);
let resp: HttpResponse = HttpResponse::Ok()
.header(header::VARY, "Accept")
.finish();
let resp = cors.response(&mut req, resp).unwrap().response();
{
let headers = resp
.headers()
.get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
.unwrap()
.to_str()
.unwrap()
.split(',')
.map(|s| s.trim())
.collect::<Vec<&str>>();
for h in exposed_headers {
assert!(headers.contains(&h.as_str()));
}
}
let resp: HttpResponse =
HttpResponse::Ok().header(header::VARY, "Accept").finish();
let resp = cors.response(&req, resp).unwrap().response();
assert_eq!(
&b"Accept, Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes()
@ -1108,7 +1124,7 @@ mod tests {
.allowed_origin("https://www.example.com")
.finish();
let resp: HttpResponse = HttpResponse::Ok().into();
let resp = cors.response(&mut req, resp).unwrap().response();
let resp = cors.response(&req, resp).unwrap().response();
assert_eq!(
&b"https://www.example.com"[..],
resp.headers()
@ -1129,11 +1145,21 @@ mod tests {
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let request = srv
.get()
.uri(srv.url("/test"))
.header("ORIGIN", "https://www.example2.com")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv.get()
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv
.get()
.uri(srv.url("/test"))
.header("ORIGIN", "https://www.example.com")
.finish()

View File

@ -15,25 +15,25 @@
//! the allowed origins.
//!
//! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr)
//! if you want to allow requests with unsafe methods via
//! if you want to allow requests with unprotected methods via
//! [CORS](../cors/struct.Cors.html).
//!
//! # Example
//!
//! ```
//! # extern crate actix_web;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//! use actix_web::middleware::csrf;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//!
//! fn handle_post(_: HttpRequest) -> &'static str {
//! fn handle_post(_: &HttpRequest) -> &'static str {
//! "This action should only be triggered with requests from the same site"
//! }
//!
//! fn main() {
//! let app = App::new()
//! .middleware(
//! csrf::CsrfFilter::new()
//! .allowed_origin("https://www.example.com"))
//! csrf::CsrfFilter::new().allowed_origin("https://www.example.com"),
//! )
//! .resource("/", |r| {
//! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(http::Method::POST).f(handle_post);
@ -50,10 +50,10 @@ use std::collections::HashSet;
use bytes::Bytes;
use error::{ResponseError, Result};
use http::{header, HeaderMap, HttpTryFrom, Uri};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Started};
use server::Request;
/// Potential cross-site request forgery detected.
#[derive(Debug, Fail)]
@ -93,8 +93,7 @@ fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
.to_str()
.map_err(|_| CsrfError::BadOrigin)
.map(|o| o.into())
})
.or_else(|| {
}).or_else(|| {
headers.get(header::REFERER).map(|referer| {
Uri::try_from(Bytes::from(referer.as_bytes()))
.ok()
@ -120,13 +119,12 @@ fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
/// # Example
///
/// ```
/// use actix_web::App;
/// use actix_web::middleware::csrf;
/// use actix_web::App;
///
/// # fn main() {
/// let app = App::new().middleware(
/// csrf::CsrfFilter::new()
/// .allowed_origin("https://www.example.com"));
/// let app = App::new()
/// .middleware(csrf::CsrfFilter::new().allowed_origin("https://www.example.com"));
/// # }
/// ```
#[derive(Default)]
@ -176,7 +174,7 @@ impl CsrfFilter {
///
/// 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.
/// the browser from sending `Origin` on unprotected requests.
pub fn allow_missing_origin(mut self) -> CsrfFilter {
self.allow_missing_origin = true;
self
@ -188,7 +186,7 @@ impl CsrfFilter {
self
}
fn validate<S>(&self, req: &mut HttpRequest<S>) -> Result<(), CsrfError> {
fn validate(&self, req: &Request) -> Result<(), CsrfError> {
let is_upgrade = req.headers().contains_key(header::UPGRADE);
let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade);
@ -210,7 +208,7 @@ impl CsrfFilter {
}
impl<S> Middleware<S> for CsrfFilter {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
self.validate(req)?;
Ok(Started::Done)
}
@ -226,35 +224,35 @@ mod tests {
fn test_safe() {
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
let req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::HEAD)
.finish();
assert!(csrf.start(&mut req).is_ok());
assert!(csrf.start(&req).is_ok());
}
#[test]
fn test_csrf() {
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
let mut req = TestRequest::with_header("Origin", "https://www.w3.org")
let req = TestRequest::with_header("Origin", "https://www.w3.org")
.method(Method::POST)
.finish();
assert!(csrf.start(&mut req).is_err());
assert!(csrf.start(&req).is_err());
}
#[test]
fn test_referer() {
let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
let mut req = TestRequest::with_header(
let req = TestRequest::with_header(
"Referer",
"https://www.example.com/some/path?query=param",
).method(Method::POST)
.finish();
.finish();
assert!(csrf.start(&mut req).is_ok());
assert!(csrf.start(&req).is_ok());
}
#[test]
@ -265,13 +263,13 @@ mod tests {
.allowed_origin("https://www.example.com")
.allow_upgrade();
let mut req = TestRequest::with_header("Origin", "https://cswsh.com")
let req = TestRequest::with_header("Origin", "https://cswsh.com")
.header("Connection", "Upgrade")
.header("Upgrade", "websocket")
.method(Method::GET)
.finish();
assert!(strict_csrf.start(&mut req).is_err());
assert!(lax_csrf.start(&mut req).is_ok());
assert!(strict_csrf.start(&req).is_err());
assert!(lax_csrf.start(&req).is_ok());
}
}

View File

@ -17,12 +17,11 @@ use middleware::{Middleware, Response};
///
/// fn main() {
/// let app = App::new()
/// .middleware(
/// middleware::DefaultHeaders::new()
/// .header("X-Version", "0.2"))
/// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
/// .resource("/test", |r| {
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .finish();
/// }
@ -75,9 +74,7 @@ impl DefaultHeaders {
}
impl<S> Middleware<S> for DefaultHeaders {
fn response(
&self, _: &mut HttpRequest<S>, mut resp: HttpResponse,
) -> Result<Response> {
fn response(&self, _: &HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
for (key, value) in self.headers.iter() {
if !resp.headers().contains_key(key) {
resp.headers_mut().insert(key, value.clone());
@ -98,24 +95,23 @@ impl<S> Middleware<S> for DefaultHeaders {
mod tests {
use super::*;
use http::header::CONTENT_TYPE;
use test::TestRequest;
#[test]
fn test_default_headers() {
let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001");
let mut req = HttpRequest::default();
let req = TestRequest::default().finish();
let resp = HttpResponse::Ok().finish();
let resp = match mw.response(&mut req, resp) {
let resp = match mw.response(&req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let resp = HttpResponse::Ok()
.header(CONTENT_TYPE, "0002")
.finish();
let resp = match mw.response(&mut req, resp) {
let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish();
let resp = match mw.response(&req, resp) {
Ok(Response::Done(resp)) => resp,
_ => panic!(),
};

View File

@ -6,7 +6,7 @@ use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response};
type ErrorHandler<S> = Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response>;
type ErrorHandler<S> = Fn(&HttpRequest<S>, HttpResponse) -> Result<Response>;
/// `Middleware` for allowing custom handlers for responses.
///
@ -18,23 +18,25 @@ type ErrorHandler<S> = Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response>
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::{ErrorHandlers, Response};
/// use actix_web::{http, App, HttpRequest, HttpResponse, Result};
/// use actix_web::middleware::{Response, ErrorHandlers};
///
/// fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
/// let mut builder = resp.into_builder();
/// builder.header(http::header::CONTENT_TYPE, "application/json");
/// Ok(Response::Done(builder.into()))
/// fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
/// let mut builder = resp.into_builder();
/// builder.header(http::header::CONTENT_TYPE, "application/json");
/// Ok(Response::Done(builder.into()))
/// }
///
/// fn main() {
/// let app = App::new()
/// .middleware(
/// ErrorHandlers::new()
/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500))
/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500),
/// )
/// .resource("/test", |r| {
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .finish();
/// }
@ -60,7 +62,7 @@ impl<S> ErrorHandlers<S> {
/// Register error handler for specified status code
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
where
F: Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response> + 'static,
F: Fn(&HttpRequest<S>, HttpResponse) -> Result<Response> + 'static,
{
self.handlers.insert(status, Box::new(handler));
self
@ -68,9 +70,7 @@ impl<S> ErrorHandlers<S> {
}
impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(handler) = self.handlers.get(&resp.status()) {
handler(req, resp)
} else {
@ -82,10 +82,14 @@ impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
#[cfg(test)]
mod tests {
use super::*;
use error::{Error, ErrorInternalServerError};
use http::header::CONTENT_TYPE;
use http::StatusCode;
use httpmessage::HttpMessage;
use middleware::Started;
use test::{self, TestRequest};
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
fn render_500<S>(_: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
let mut builder = resp.into_builder();
builder.header(CONTENT_TYPE, "0001");
Ok(Response::Done(builder.into()))
@ -96,7 +100,7 @@ mod tests {
let mw =
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mut req = HttpRequest::default();
let mut req = TestRequest::default().finish();
let resp = HttpResponse::InternalServerError().finish();
let resp = match mw.response(&mut req, resp) {
Ok(Response::Done(resp)) => resp,
@ -111,4 +115,27 @@ mod tests {
};
assert!(!resp.headers().contains_key(CONTENT_TYPE));
}
struct MiddlewareOne;
impl<S> Middleware<S> for MiddlewareOne {
fn start(&self, _: &HttpRequest<S>) -> Result<Started, Error> {
Err(ErrorInternalServerError("middleware error"))
}
}
#[test]
fn test_middleware_start_error() {
let mut srv = test::TestServer::new(move |app| {
app.middleware(
ErrorHandlers::new()
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
).middleware(MiddlewareOne)
.handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
}

View File

@ -3,7 +3,7 @@
//! [**IdentityService**](struct.IdentityService.html) middleware can be
//! used with different policies types to store identity information.
//!
//! Bu default, only cookie identity policy is implemented. Other backend
//! By default, only cookie identity policy is implemented. Other backend
//! implementations can be added separately.
//!
//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html)
@ -62,8 +62,8 @@ use middleware::{Middleware, Response, Started};
/// The helper trait to obtain your identity from a request.
///
/// ```rust
/// use actix_web::*;
/// use actix_web::middleware::identity::RequestIdentity;
/// use actix_web::*;
///
/// fn index(req: HttpRequest) -> Result<String> {
/// // access request identity
@ -80,7 +80,7 @@ use middleware::{Middleware, Response, Started};
/// }
///
/// fn logout(mut req: HttpRequest) -> HttpResponse {
/// req.forget(); // <- remove identity
/// req.forget(); // <- remove identity
/// HttpResponse::Ok().finish()
/// }
/// # fn main() {}
@ -88,31 +88,31 @@ use middleware::{Middleware, Response, Started};
pub trait RequestIdentity {
/// Return the claimed identity of the user associated request or
/// ``None`` if no identity can be found associated with the request.
fn identity(&self) -> Option<&str>;
fn identity(&self) -> Option<String>;
/// Remember identity.
fn remember(&mut self, identity: String);
fn remember(&self, identity: String);
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&mut self);
fn forget(&self);
}
impl<S> RequestIdentity for HttpRequest<S> {
fn identity(&self) -> Option<&str> {
fn identity(&self) -> Option<String> {
if let Some(id) = self.extensions().get::<IdentityBox>() {
return id.0.identity();
return id.0.identity().map(|s| s.to_owned());
}
None
}
fn remember(&mut self, identity: String) {
fn remember(&self, identity: String) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.remember(identity);
return id.0.as_mut().remember(identity);
}
}
fn forget(&mut self) {
fn forget(&self) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.forget();
}
@ -121,10 +121,15 @@ impl<S> RequestIdentity for HttpRequest<S> {
/// An identity
pub trait Identity: 'static {
/// Return the claimed identity of the user associated request or
/// ``None`` if no identity can be found associated with the request.
fn identity(&self) -> Option<&str>;
/// Remember identity.
fn remember(&mut self, key: String);
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&mut self);
/// Write session to storage backend.
@ -133,28 +138,30 @@ pub trait Identity: 'static {
/// Identity policy definition.
pub trait IdentityPolicy<S>: Sized + 'static {
/// The associated identity
type Identity: Identity;
/// The return type of the middleware
type Future: Future<Item = Self::Identity, Error = Error>;
/// Parse the session from request and load data from a service identity.
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::Future;
fn from_request(&self, request: &HttpRequest<S>) -> Self::Future;
}
/// Request identity middleware
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
/// use actix_web::App;
/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
///
/// fn main() {
/// let app = App::new().middleware(
/// IdentityService::new( // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
/// let app = App::new().middleware(IdentityService::new(
/// // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
/// .name("auth-cookie")
/// .secure(false))
/// );
/// .secure(false),
/// ));
/// }
/// ```
pub struct IdentityService<T> {
@ -170,32 +177,22 @@ impl<T> IdentityService<T> {
struct IdentityBox(Box<Identity>);
#[doc(hidden)]
unsafe impl Send for IdentityBox {}
#[doc(hidden)]
unsafe impl Sync for IdentityBox {}
impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
let mut req = req.clone();
let fut = self.backend
.from_request(&mut req)
.then(move |res| match res {
Ok(id) => {
req.extensions_mut().insert(IdentityBox(Box::new(id)));
FutOk(None)
}
Err(err) => FutErr(err),
});
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
let req = req.clone();
let fut = self.backend.from_request(&req).then(move |res| match res {
Ok(id) => {
req.extensions_mut().insert(IdentityBox(Box::new(id)));
FutOk(None)
}
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut)))
}
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
if let Some(mut id) = req.extensions_mut().remove::<IdentityBox>() {
id.0.write(resp)
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(ref mut id) = req.extensions_mut().get_mut::<IdentityBox>() {
id.0.as_mut().write(resp)
} else {
Ok(Response::Done(resp))
}
@ -288,9 +285,9 @@ impl CookieIdentityInner {
Ok(())
}
fn load<S>(&self, req: &mut HttpRequest<S>) -> Option<String> {
fn load<S>(&self, req: &HttpRequest<S>) -> Option<String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies {
for cookie in cookies.iter() {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
@ -317,17 +314,18 @@ impl CookieIdentityInner {
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
/// use actix_web::App;
/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
///
/// fn main() {
/// let app = App::new().middleware(
/// IdentityService::new( // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy
/// let app = App::new().middleware(IdentityService::new(
/// // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy
/// .domain("www.rust-lang.org")
/// .name("actix_auth")
/// .path("/")
/// .secure(true)));
/// .secure(true),
/// ));
/// }
/// ```
pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
@ -378,7 +376,7 @@ impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
type Identity = CookieIdentity;
type Future = FutureResult<CookieIdentity, Error>;
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::Future {
fn from_request(&self, req: &HttpRequest<S>) -> Self::Future {
let identity = self.0.load(req);
FutOk(CookieIdentity {
identity,

View File

@ -3,7 +3,6 @@ use std::collections::HashSet;
use std::env;
use std::fmt::{self, Display, Formatter};
use libc;
use regex::Regex;
use time;
@ -26,13 +25,13 @@ use middleware::{Finished, Middleware, Started};
/// default format:
///
/// ```ignore
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
/// ```rust
/// # extern crate actix_web;
/// extern crate env_logger;
/// use actix_web::App;
/// use actix_web::middleware::Logger;
/// use actix_web::App;
///
/// fn main() {
/// std::env::set_var("RUST_LOG", "actix_web=info");
@ -53,8 +52,6 @@ use middleware::{Finished, Middleware, Started};
///
/// `%t` Time when the request was started to process
///
/// `%P` The process ID of the child that serviced the request
///
/// `%r` First line of request
///
/// `%s` Response status code
@ -97,7 +94,7 @@ impl Default for Logger {
/// Create `Logger` middleware with format:
///
/// ```ignore
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
fn default() -> Logger {
Logger {
@ -110,7 +107,7 @@ impl Default for Logger {
struct StartTime(time::Tm);
impl Logger {
fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) {
fn log<S>(&self, req: &HttpRequest<S>, resp: &HttpResponse) {
if let Some(entry_time) = req.extensions().get::<StartTime>() {
let render = |fmt: &mut Formatter| {
for unit in &self.format.0 {
@ -124,14 +121,14 @@ impl Logger {
}
impl<S> Middleware<S> for Logger {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
if !self.exclude.contains(req.path()) {
req.extensions_mut().insert(StartTime(time::now()));
}
Ok(Started::Done)
}
fn finish(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) -> Finished {
fn finish(&self, req: &HttpRequest<S>, resp: &HttpResponse) -> Finished {
self.log(req, resp);
Finished::Done
}
@ -146,7 +143,7 @@ struct Format(Vec<FormatText>);
impl Default for Format {
/// Return the default formatting style for the `Logger`:
fn default() -> Format {
Format::new(r#"%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#)
Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#)
}
}
@ -181,7 +178,6 @@ impl Format {
"%" => FormatText::Percent,
"a" => FormatText::RemoteAddr,
"t" => FormatText::RequestTime,
"P" => FormatText::Pid,
"r" => FormatText::RequestLine,
"s" => FormatText::ResponseStatus,
"b" => FormatText::ResponseSize,
@ -205,7 +201,6 @@ impl Format {
#[derive(Debug, Clone)]
pub enum FormatText {
Str(String),
Pid,
Percent,
RequestLine,
RequestTime,
@ -247,7 +242,6 @@ impl FormatText {
}
FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt),
FormatText::ResponseSize => resp.response_size().fmt(fmt),
FormatText::Pid => unsafe { libc::getpid().fmt(fmt) },
FormatText::Time => {
let rt = time::now() - entry_time;
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0;
@ -314,38 +308,30 @@ impl<'a> fmt::Display for FormatDisplay<'a> {
#[cfg(test)]
mod tests {
use super::*;
use http::header::{self, HeaderMap};
use http::{Method, StatusCode, Uri, Version};
use std::str::FromStr;
use time;
use super::*;
use http::{header, StatusCode};
use test::TestRequest;
#[test]
fn test_logger() {
let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test");
let mut headers = HeaderMap::new();
headers.insert(
let req = TestRequest::with_header(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
);
let mut req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
).finish();
let resp = HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt")
.force_close()
.finish();
match logger.start(&mut req) {
match logger.start(&req) {
Ok(Started::Done) => (),
_ => panic!(),
};
match logger.finish(&mut req, &resp) {
match logger.finish(&req, &resp) {
Finished::Done => (),
_ => panic!(),
}
@ -364,21 +350,11 @@ mod tests {
fn test_default_format() {
let format = Format::default();
let mut headers = HeaderMap::new();
headers.insert(
let req = TestRequest::with_header(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let resp = HttpResponse::build(StatusCode::OK)
.force_close()
.finish();
).finish();
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
let entry_time = time::now();
let render = |fmt: &mut Formatter| {
@ -392,16 +368,8 @@ mod tests {
assert!(s.contains("200 0"));
assert!(s.contains("ACTIX-WEB"));
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/?test").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
let resp = HttpResponse::build(StatusCode::OK)
.force_close()
.finish();
let req = TestRequest::with_uri("/?test").finish();
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
let entry_time = time::now();
let render = |fmt: &mut Formatter| {

View File

@ -51,20 +51,18 @@ pub enum Finished {
pub trait Middleware<S>: 'static {
/// Method is called when request is ready. It may return
/// future, which should resolve before next middleware get called.
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
Ok(Started::Done)
}
/// Method is called when handler returns response,
/// but before sending http message to peer.
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
Ok(Response::Done(resp))
}
/// Method is called after body stream get sent to peer.
fn finish(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) -> Finished {
fn finish(&self, req: &HttpRequest<S>, resp: &HttpResponse) -> Finished {
Finished::Done
}
}

View File

@ -32,9 +32,8 @@
//! session data.
//!
//! ```rust
//! # extern crate actix;
//! # extern crate actix_web;
//! use actix_web::{server, App, HttpRequest, Result};
//! use actix_web::{actix, server, App, HttpRequest, Result};
//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
//!
//! fn index(req: HttpRequest) -> Result<&'static str> {
@ -50,17 +49,17 @@
//! }
//!
//! fn main() {
//! let sys = actix::System::new("basic-example");
//! server::new(
//! || App::new().middleware(
//! SessionStorage::new( // <- create session middleware
//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
//! .secure(false)
//! )))
//! .bind("127.0.0.1:59880").unwrap()
//! .start();
//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
//! let _ = sys.run();
//! actix::System::run(|| {
//! server::new(
//! || App::new().middleware(
//! SessionStorage::new( // <- create session middleware
//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
//! .secure(false)
//! )))
//! .bind("127.0.0.1:59880").unwrap()
//! .start();
//! # actix::System::current().stop();
//! });
//! }
//! ```
use std::cell::RefCell;
@ -69,7 +68,7 @@ use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::Arc;
use cookie::{Cookie, CookieJar, Key};
use cookie::{Cookie, CookieJar, Key, SameSite};
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
use futures::Future;
use http::header::{self, HeaderValue};
@ -80,6 +79,7 @@ use serde_json::error::Error as JsonError;
use time::Duration;
use error::{Error, ResponseError, Result};
use handler::FromRequest;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started};
@ -87,13 +87,13 @@ use middleware::{Middleware, Response, Started};
/// The helper trait to obtain your session data from a request.
///
/// ```rust
/// use actix_web::*;
/// use actix_web::middleware::session::RequestSession;
/// use actix_web::*;
///
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = req.session().get::<i32>("counter")? {
/// req.session().set("counter", count+1)?;
/// req.session().set("counter", count + 1)?;
/// } else {
/// req.session().set("counter", 1)?;
/// }
@ -103,6 +103,7 @@ use middleware::{Middleware, Response, Started};
/// # fn main() {}
/// ```
pub trait RequestSession {
/// Get the session from the request
fn session(&self) -> Session;
}
@ -122,13 +123,13 @@ impl<S> RequestSession for HttpRequest<S> {
/// method. `RequestSession` trait is implemented for `HttpRequest`.
///
/// ```rust
/// use actix_web::*;
/// use actix_web::middleware::session::RequestSession;
/// use actix_web::*;
///
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = req.session().get::<i32>("counter")? {
/// req.session().set("counter", count+1)?;
/// req.session().set("counter", count + 1)?;
/// } else {
/// req.session().set("counter", 1)?;
/// }
@ -190,27 +191,49 @@ impl Session {
}
}
struct SessionImplCell(RefCell<Box<SessionImpl>>);
/// Extractor implementation for Session type.
///
/// ```rust
/// # use actix_web::*;
/// use actix_web::middleware::session::Session;
///
/// fn index(session: Session) -> Result<&'static str> {
/// // access session data
/// if let Some(count) = session.get::<i32>("counter")? {
/// session.set("counter", count + 1)?;
/// } else {
/// session.set("counter", 1)?;
/// }
///
/// Ok("Welcome!")
/// }
/// # fn main() {}
/// ```
impl<S> FromRequest<S> for Session {
type Config = ();
type Result = Session;
#[doc(hidden)]
unsafe impl Send for SessionImplCell {}
#[doc(hidden)]
unsafe impl Sync for SessionImplCell {}
#[inline]
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
req.session()
}
}
struct SessionImplCell(RefCell<Box<SessionImpl>>);
/// Session storage middleware
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// use actix_web::middleware::session::{CookieSessionBackend, SessionStorage};
/// use actix_web::App;
/// use actix_web::middleware::session::{SessionStorage, CookieSessionBackend};
///
/// fn main() {
/// let app = App::new().middleware(
/// SessionStorage::new( // <- create session middleware
/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend
/// .secure(false))
/// );
/// let app = App::new().middleware(SessionStorage::new(
/// // <- create session middleware
/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend
/// .secure(false),
/// ));
/// }
/// ```
pub struct SessionStorage<T, S>(T, PhantomData<S>);
@ -223,27 +246,22 @@ impl<S, T: SessionBackend<S>> SessionStorage<T, S> {
}
impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
let mut req = req.clone();
let fut = self.0
.from_request(&mut req)
.then(move |res| match res {
Ok(sess) => {
req.extensions_mut().insert(Arc::new(SessionImplCell(
RefCell::new(Box::new(sess)),
)));
FutOk(None)
}
Err(err) => FutErr(err),
});
let fut = self.0.from_request(&mut req).then(move |res| match res {
Ok(sess) => {
req.extensions_mut()
.insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess)))));
FutOk(None)
}
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut)))
}
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse,
) -> Result<Response> {
if let Some(s_box) = req.extensions_mut().remove::<Arc<SessionImplCell>>() {
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(s_box) = req.extensions().get::<Arc<SessionImplCell>>() {
s_box.0.borrow_mut().write(resp)
} else {
Ok(Response::Done(resp))
@ -340,7 +358,9 @@ struct CookieSessionInner {
path: String,
domain: Option<String>,
secure: bool,
http_only: bool,
max_age: Option<Duration>,
same_site: Option<SameSite>,
}
impl CookieSessionInner {
@ -352,7 +372,9 @@ impl CookieSessionInner {
path: "/".to_owned(),
domain: None,
secure: true,
http_only: true,
max_age: None,
same_site: None,
}
}
@ -368,7 +390,7 @@ impl CookieSessionInner {
let mut cookie = Cookie::new(self.name.clone(), value);
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(true);
cookie.set_http_only(self.http_only);
if let Some(ref domain) = self.domain {
cookie.set_domain(domain.clone());
@ -378,6 +400,10 @@ impl CookieSessionInner {
cookie.set_max_age(max_age);
}
if let Some(same_site) = self.same_site {
cookie.set_same_site(same_site);
}
let mut jar = CookieJar::new();
match self.security {
@ -386,7 +412,7 @@ impl CookieSessionInner {
}
for cookie in jar.delta() {
let val = HeaderValue::from_str(&cookie.to_string())?;
let val = HeaderValue::from_str(&cookie.encoded().to_string())?;
resp.headers_mut().append(header::SET_COOKIE, val);
}
@ -395,7 +421,7 @@ impl CookieSessionInner {
fn load<S>(&self, req: &mut HttpRequest<S>) -> HashMap<String, String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies {
for cookie in cookies.iter() {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
@ -440,6 +466,9 @@ impl CookieSessionInner {
/// all session data is lost. The constructors will panic if the key is less
/// than 32 bytes in length.
///
/// The backend relies on `cookie` crate to create and read cookies.
/// By default all cookies are percent encoded, but certain symbols may
/// cause troubles when reading cookie, if they are not properly percent encoded.
///
/// # Example
///
@ -505,6 +534,18 @@ impl CookieSessionBackend {
self
}
/// Sets the `http_only` field in the session cookie being built.
pub fn http_only(mut self, value: bool) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().http_only = value;
self
}
/// Sets the `same_site` field in the session cookie being built.
pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().same_site = Some(value);
self
}
/// Sets the `max-age` field in the session cookie being built.
pub fn max_age(mut self, value: Duration) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
@ -538,8 +579,7 @@ mod tests {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
))
.resource("/", |r| {
)).resource("/", |r| {
r.f(|req| {
let _ = req.session().set("counter", 100);
"test"
@ -551,4 +591,23 @@ mod tests {
let response = srv.execute(request.send()).unwrap();
assert!(response.cookie("actix-session").is_some());
}
#[test]
fn cookie_session_extractor() {
let mut srv = test::TestServer::with_factory(|| {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
)).resource("/", |r| {
r.with(|ses: Session| {
let _ = ses.set("counter", 100);
"test"
})
})
});
let request = srv.get().uri(srv.url("/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.cookie("actix-session").is_some());
}
}

View File

@ -1,5 +1,5 @@
//! Multipart requests support
use std::cell::RefCell;
use std::cell::{RefCell, UnsafeCell};
use std::marker::PhantomData;
use std::rc::Rc;
use std::{cmp, fmt};
@ -7,13 +7,13 @@ use std::{cmp, fmt};
use bytes::Bytes;
use futures::task::{current as current_task, Task};
use futures::{Async, Poll, Stream};
use http::header::{self, HeaderMap, HeaderName, HeaderValue};
use http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue};
use http::HttpTryFrom;
use httparse;
use mime;
use error::{MultipartError, ParseError, PayloadError};
use payload::PayloadHelper;
use payload::PayloadBuffer;
const MAX_HEADERS: usize = 32;
@ -97,7 +97,7 @@ where
safety: Safety::new(),
inner: Some(Rc::new(RefCell::new(InnerMultipart {
boundary,
payload: PayloadRef::new(PayloadHelper::new(stream)),
payload: PayloadRef::new(PayloadBuffer::new(stream)),
state: InnerState::FirstBoundary,
item: InnerMultipartItem::None,
}))),
@ -122,11 +122,7 @@ where
if let Some(err) = self.error.take() {
Err(err)
} else if self.safety.current() {
self.inner
.as_mut()
.unwrap()
.borrow_mut()
.poll(&self.safety)
self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety)
} else {
Ok(Async::NotReady)
}
@ -137,7 +133,7 @@ impl<S> InnerMultipart<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
fn read_headers(payload: &mut PayloadHelper<S>) -> Poll<HeaderMap, MultipartError> {
fn read_headers(payload: &mut PayloadBuffer<S>) -> Poll<HeaderMap, MultipartError> {
match payload.read_until(b"\r\n\r\n")? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
@ -168,18 +164,20 @@ where
}
fn read_boundary(
payload: &mut PayloadHelper<S>, boundary: &str,
payload: &mut PayloadBuffer<S>, boundary: &str,
) -> Poll<bool, MultipartError> {
// TODO: need to read epilogue
match payload.readline()? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => {
if chunk.len() == boundary.len() + 4 && &chunk[..2] == b"--"
if chunk.len() == boundary.len() + 4
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
{
Ok(Async::Ready(false))
} else if chunk.len() == boundary.len() + 6 && &chunk[..2] == b"--"
} else if chunk.len() == boundary.len() + 6
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
&& &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
{
@ -192,7 +190,7 @@ where
}
fn skip_until_boundary(
payload: &mut PayloadHelper<S>, boundary: &str,
payload: &mut PayloadBuffer<S>, boundary: &str,
) -> Poll<bool, MultipartError> {
let mut eof = false;
loop {
@ -401,13 +399,28 @@ where
}
}
/// Get a map of headers
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Get the content type of the field
pub fn content_type(&self) -> &mime::Mime {
&self.ct
}
/// Get the content disposition of the field, if it exists
pub fn content_disposition(&self) -> Option<ContentDisposition> {
// RFC 7578: 'Each part MUST contain a Content-Disposition header field
// where the disposition type is "form-data".'
if let Some(content_disposition) =
self.headers.get(::http::header::CONTENT_DISPOSITION)
{
ContentDisposition::from_raw(content_disposition).ok()
} else {
None
}
}
}
impl<S> Stream for Field<S>
@ -477,7 +490,7 @@ where
/// Reads body part content chunk of the specified size.
/// The body part must has `Content-Length` header with proper value.
fn read_len(
payload: &mut PayloadHelper<S>, size: &mut u64,
payload: &mut PayloadBuffer<S>, size: &mut u64,
) -> Poll<Option<Bytes>, MultipartError> {
if *size == 0 {
Ok(Async::Ready(None))
@ -490,7 +503,7 @@ where
*size -= len;
let ch = chunk.split_to(len as usize);
if !chunk.is_empty() {
payload.unread_data(chunk);
payload.unprocessed(chunk);
}
Ok(Async::Ready(Some(ch)))
}
@ -502,32 +515,36 @@ where
/// Reads content chunk of body part with unknown length.
/// The `Content-Length` header for body part is not necessary.
fn read_stream(
payload: &mut PayloadHelper<S>, boundary: &str,
payload: &mut PayloadBuffer<S>, boundary: &str,
) -> Poll<Option<Bytes>, MultipartError> {
match payload.read_until(b"\r")? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(mut chunk)) => {
if chunk.len() == 1 {
payload.unread_data(chunk);
payload.unprocessed(chunk);
match payload.read_exact(boundary.len() + 4)? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => {
if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--"
Async::Ready(Some(mut chunk)) => {
if &chunk[..2] == b"\r\n"
&& &chunk[2..4] == b"--"
&& &chunk[4..] == boundary.as_bytes()
{
payload.unread_data(chunk);
payload.unprocessed(chunk);
Ok(Async::Ready(None))
} else {
Ok(Async::Ready(Some(chunk)))
// \r might be part of data stream
let ch = chunk.split_to(1);
payload.unprocessed(chunk);
Ok(Async::Ready(Some(ch)))
}
}
}
} else {
let to = chunk.len() - 1;
let ch = chunk.split_to(to);
payload.unread_data(chunk);
payload.unprocessed(chunk);
Ok(Async::Ready(Some(ch)))
}
}
@ -575,26 +592,27 @@ where
}
struct PayloadRef<S> {
payload: Rc<PayloadHelper<S>>,
payload: Rc<UnsafeCell<PayloadBuffer<S>>>,
}
impl<S> PayloadRef<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
fn new(payload: PayloadHelper<S>) -> PayloadRef<S> {
fn new(payload: PayloadBuffer<S>) -> PayloadRef<S> {
PayloadRef {
payload: Rc::new(payload),
payload: Rc::new(payload.into()),
}
}
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadHelper<S>>
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer<S>>
where
'a: 'b,
{
// Unsafe: Invariant is inforced by Safety Safety is used as ref counter,
// only top most ref can have mutable access to payload.
if s.current() {
let payload: &mut PayloadHelper<S> =
unsafe { &mut *(self.payload.as_ref() as *const _ as *mut _) };
let payload: &mut PayloadBuffer<S> = unsafe { &mut *self.payload.get() };
Some(payload)
} else {
None
@ -664,7 +682,7 @@ mod tests {
use bytes::Bytes;
use futures::future::{lazy, result};
use payload::{Payload, PayloadWriter};
use tokio_core::reactor::Core;
use tokio::runtime::current_thread::Runtime;
#[test]
fn test_boundary() {
@ -711,14 +729,15 @@ mod tests {
#[test]
fn test_multipart() {
Core::new()
Runtime::new()
.unwrap()
.run(lazy(|| {
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
@ -734,6 +753,15 @@ mod tests {
match multipart.poll() {
Ok(Async::Ready(Some(item))) => match item {
MultipartItem::Field(mut field) => {
{
use http::header::{DispositionParam, DispositionType};
let cd = field.content_disposition().unwrap();
assert_eq!(cd.disposition, DispositionType::FormData);
assert_eq!(
cd.parameters[0],
DispositionParam::Name("file".into())
);
}
assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN);
@ -782,7 +810,6 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
})).unwrap();
}
}

View File

@ -1,13 +1,14 @@
use http::StatusCode;
use smallvec::SmallVec;
use std;
use std::borrow::Cow;
use std::ops::Index;
use std::path::PathBuf;
use std::slice::Iter;
use std::rc::Rc;
use std::str::FromStr;
use http::StatusCode;
use smallvec::SmallVec;
use error::{InternalError, ResponseError, UriSegmentError};
use uri::Url;
/// A trait to abstract the idea of creating a new instance of a type from a
/// path parameter.
@ -19,63 +20,92 @@ pub trait FromParam: Sized {
fn from_param(s: &str) -> Result<Self, Self::Err>;
}
#[derive(Debug, Clone)]
pub(crate) enum ParamItem {
Static(&'static str),
UrlSegment(u16, u16),
}
/// Route match information
///
/// If resource path contains variable patterns, `Params` stores this variables.
#[derive(Debug)]
pub struct Params<'a>(SmallVec<[(Cow<'a, str>, Cow<'a, str>); 3]>);
#[derive(Debug, Clone)]
pub struct Params {
url: Url,
pub(crate) tail: u16,
pub(crate) segments: SmallVec<[(Rc<String>, ParamItem); 3]>,
}
impl<'a> Params<'a> {
pub(crate) fn new() -> Params<'a> {
Params(SmallVec::new())
impl Params {
pub(crate) fn new() -> Params {
Params {
url: Url::default(),
tail: 0,
segments: SmallVec::new(),
}
}
pub(crate) fn with_url(url: &Url) -> Params {
Params {
url: url.clone(),
tail: 0,
segments: SmallVec::new(),
}
}
pub(crate) fn clear(&mut self) {
self.0.clear();
self.segments.clear();
}
pub(crate) fn add<N, V>(&mut self, name: N, value: V)
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.0.push((name.into(), value.into()));
pub(crate) fn set_tail(&mut self, tail: u16) {
self.tail = tail;
}
pub(crate) fn set<N, V>(&mut self, name: N, value: V)
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
let name = name.into();
let value = value.into();
for item in &mut self.0 {
if item.0 == name {
item.1 = value;
return;
}
}
self.0.push((name, value));
pub(crate) fn set_url(&mut self, url: Url) {
self.url = url;
}
pub(crate) fn add(&mut self, name: Rc<String>, value: ParamItem) {
self.segments.push((name, value));
}
pub(crate) fn add_static(&mut self, name: &str, value: &'static str) {
self.segments
.push((Rc::new(name.to_string()), ParamItem::Static(value)));
}
/// Check if there are any matched patterns
pub fn is_empty(&self) -> bool {
self.0.is_empty()
self.segments.is_empty()
}
/// Check number of extracted parameters
pub fn len(&self) -> usize {
self.0.len()
self.segments.len()
}
/// Get matched parameter by name without type conversion
pub fn get(&'a self, key: &str) -> Option<&'a str> {
for item in self.0.iter() {
if key == item.0 {
return Some(item.1.as_ref());
pub fn get(&self, key: &str) -> Option<&str> {
for item in self.segments.iter() {
if key == item.0.as_str() {
return match item.1 {
ParamItem::Static(ref s) => Some(&s),
ParamItem::UrlSegment(s, e) => {
Some(&self.url.path()[(s as usize)..(e as usize)])
}
};
}
}
None
if key == "tail" {
Some(&self.url.path()[(self.tail as usize)..])
} else {
None
}
}
/// Get unprocessed part of path
pub fn unprocessed(&self) -> &str {
&self.url.path()[(self.tail as usize)..]
}
/// Get matched `FromParam` compatible parameter by name.
@ -87,12 +117,12 @@ impl<'a> Params<'a> {
/// # extern crate actix_web;
/// # use actix_web::*;
/// fn index(req: HttpRequest) -> Result<String> {
/// let ivalue: isize = req.match_info().query("val")?;
/// Ok(format!("isuze value: {:?}", ivalue))
/// let ivalue: isize = req.match_info().query("val")?;
/// Ok(format!("isuze value: {:?}", ivalue))
/// }
/// # fn main() {}
/// ```
pub fn query<T: FromParam>(&'a self, key: &str) -> Result<T, <T as FromParam>::Err> {
pub fn query<T: FromParam>(&self, key: &str) -> Result<T, <T as FromParam>::Err> {
if let Some(s) = self.get(key) {
T::from_param(s)
} else {
@ -101,25 +131,57 @@ impl<'a> Params<'a> {
}
/// Return iterator to items in parameter container
pub fn iter(&self) -> Iter<(Cow<'a, str>, Cow<'a, str>)> {
self.0.iter()
pub fn iter(&self) -> ParamsIter {
ParamsIter {
idx: 0,
params: self,
}
}
}
impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> {
#[derive(Debug)]
pub struct ParamsIter<'a> {
idx: usize,
params: &'a Params,
}
impl<'a> Iterator for ParamsIter<'a> {
type Item = (&'a str, &'a str);
#[inline]
fn next(&mut self) -> Option<(&'a str, &'a str)> {
if self.idx < self.params.len() {
let idx = self.idx;
let res = match self.params.segments[idx].1 {
ParamItem::Static(ref s) => &s,
ParamItem::UrlSegment(s, e) => {
&self.params.url.path()[(s as usize)..(e as usize)]
}
};
self.idx += 1;
return Some((&self.params.segments[idx].0, res));
}
None
}
}
impl<'a> Index<&'a str> for Params {
type Output = str;
fn index(&self, name: &'b str) -> &str {
fn index(&self, name: &'a str) -> &str {
self.get(name)
.expect("Value for parameter is not available")
}
}
impl<'a, 'c: 'a> Index<usize> for &'c Params<'a> {
impl Index<usize> for Params {
type Output = str;
fn index(&self, idx: usize) -> &str {
self.0[idx].1.as_ref()
match self.segments[idx].1 {
ParamItem::Static(ref s) => &s,
ParamItem::UrlSegment(s, e) => &self.url.path()[(s as usize)..(e as usize)],
}
}
}
@ -174,7 +236,6 @@ macro_rules! FROM_STR {
($type:ty) => {
impl FromParam for $type {
type Err = InternalError<<$type as FromStr>::Err>;
fn from_param(val: &str) -> Result<Self, Self::Err> {
<$type as FromStr>::from_str(val)
.map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST))

View File

@ -59,20 +59,14 @@ impl Payload {
}
}
/// Indicates EOF of payload
#[inline]
pub fn eof(&self) -> bool {
self.inner.borrow().eof()
}
/// Length of the data in this payload
#[inline]
#[cfg(test)]
pub fn len(&self) -> usize {
self.inner.borrow().len()
}
/// Is payload empty
#[inline]
#[cfg(test)]
pub fn is_empty(&self) -> bool {
self.inner.borrow().len() == 0
}
@ -225,12 +219,7 @@ impl Inner {
}
}
#[inline]
fn eof(&self) -> bool {
self.items.is_empty() && self.eof
}
#[inline]
#[cfg(test)]
fn len(&self) -> usize {
self.len
}
@ -291,18 +280,20 @@ impl Inner {
}
}
pub struct PayloadHelper<S> {
/// Payload buffer
pub struct PayloadBuffer<S> {
len: usize,
items: VecDeque<Bytes>,
stream: S,
}
impl<S> PayloadHelper<S>
impl<S> PayloadBuffer<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
/// Create new `PayloadBuffer` instance
pub fn new(stream: S) -> Self {
PayloadHelper {
PayloadBuffer {
len: 0,
items: VecDeque::new(),
stream,
@ -327,6 +318,7 @@ where
})
}
/// Read first available chunk of bytes
#[inline]
pub fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
if let Some(data) = self.items.pop_front() {
@ -341,6 +333,7 @@ where
}
}
/// Check if buffer contains enough bytes
#[inline]
pub fn can_read(&mut self, size: usize) -> Poll<Option<bool>, PayloadError> {
if size <= self.len {
@ -354,6 +347,7 @@ where
}
}
/// Return reference to the first chunk of data
#[inline]
pub fn get_chunk(&mut self) -> Poll<Option<&[u8]>, PayloadError> {
if self.items.is_empty() {
@ -369,6 +363,7 @@ where
}
}
/// Read exact number of bytes
#[inline]
pub fn read_exact(&mut self, size: usize) -> Poll<Option<Bytes>, PayloadError> {
if size <= self.len {
@ -403,8 +398,9 @@ where
}
}
/// Remove specified amount if bytes from buffer
#[inline]
pub fn drop_payload(&mut self, size: usize) {
pub fn drop_bytes(&mut self, size: usize) {
if size <= self.len {
self.len -= size;
@ -421,6 +417,7 @@ where
}
}
/// Copy buffered data
pub fn copy(&mut self, size: usize) -> Poll<Option<BytesMut>, PayloadError> {
if size <= self.len {
let mut buf = BytesMut::with_capacity(size);
@ -442,6 +439,7 @@ where
}
}
/// Read until specified ending
pub fn read_until(&mut self, line: &[u8]) -> Poll<Option<Bytes>, PayloadError> {
let mut idx = 0;
let mut num = 0;
@ -497,24 +495,25 @@ where
}
}
/// Read bytes until new line delimiter
pub fn readline(&mut self) -> Poll<Option<Bytes>, PayloadError> {
self.read_until(b"\n")
}
pub fn unread_data(&mut self, data: Bytes) {
/// Put unprocessed data back to the buffer
pub fn unprocessed(&mut self, data: Bytes) {
self.len += data.len();
self.items.push_front(data);
}
#[allow(dead_code)]
/// Get remaining data from the buffer
pub fn remaining(&mut self) -> Bytes {
self.items
.iter_mut()
.fold(BytesMut::new(), |mut b, c| {
b.extend_from_slice(c);
b
})
.freeze()
}).freeze()
}
}
@ -524,7 +523,7 @@ mod tests {
use failure::Fail;
use futures::future::{lazy, result};
use std::io;
use tokio_core::reactor::Core;
use tokio::runtime::current_thread::Runtime;
#[test]
fn test_error() {
@ -542,28 +541,27 @@ mod tests {
#[test]
fn test_basic() {
Core::new()
Runtime::new()
.unwrap()
.run(lazy(|| {
.block_on(lazy(|| {
let (_, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(payload.len, 0);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
})).unwrap();
}
#[test]
fn test_eof() {
Core::new()
Runtime::new()
.unwrap()
.run(lazy(|| {
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
sender.feed_data(Bytes::from("data"));
@ -578,17 +576,16 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
})).unwrap();
}
#[test]
fn test_err() {
Core::new()
Runtime::new()
.unwrap()
.run(lazy(|| {
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
@ -596,17 +593,16 @@ mod tests {
payload.readany().err().unwrap();
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
})).unwrap();
}
#[test]
fn test_readany() {
Core::new()
Runtime::new()
.unwrap()
.run(lazy(|| {
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
let mut payload = PayloadBuffer::new(payload);
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
@ -625,17 +621,16 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
})).unwrap();
}
#[test]
fn test_readexactly() {
Core::new()
Runtime::new()
.unwrap()
.run(lazy(|| {
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap());
@ -659,22 +654,18 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
})).unwrap();
}
#[test]
fn test_readuntil() {
Core::new()
Runtime::new()
.unwrap()
.run(lazy(|| {
.block_on(lazy(|| {
let (mut sender, payload) = Payload::new(false);
let mut payload = PayloadHelper::new(payload);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(
Async::NotReady,
payload.read_until(b"ne").ok().unwrap()
);
assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap());
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
@ -696,15 +687,14 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
})).unwrap();
}
#[test]
fn test_unread_data() {
Core::new()
Runtime::new()
.unwrap()
.run(lazy(|| {
.block_on(lazy(|| {
let (_, mut payload) = Payload::new(false);
payload.unread_data(Bytes::from("data"));
@ -718,7 +708,6 @@ mod tests {
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
})).unwrap();
}
}

View File

@ -1,13 +1,11 @@
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::rc::Rc;
use std::{io, mem};
use futures::unsync::oneshot;
use futures::sync::oneshot;
use futures::{Async, Future, Poll, Stream};
use log::Level::Debug;
use application::Inner;
use body::{Body, BodyStream};
use context::{ActorHttpContext, Frame};
use error::Error;
@ -18,22 +16,19 @@ use httpresponse::HttpResponse;
use middleware::{Finished, Middleware, Response, Started};
use server::{HttpHandlerTask, Writer, WriterState};
#[derive(Debug, Clone, Copy)]
pub(crate) enum HandlerType {
Normal(usize),
Handler(usize),
Default,
}
pub(crate) trait PipelineHandler<S> {
#[doc(hidden)]
pub trait PipelineHandler<S> {
fn encoding(&self) -> ContentEncoding;
fn handle(
&mut self, req: HttpRequest<S>, htype: HandlerType,
) -> AsyncResult<HttpResponse>;
fn handle(&self, &HttpRequest<S>) -> AsyncResult<HttpResponse>;
}
pub(crate) struct Pipeline<S, H>(PipelineInfo<S>, PipelineState<S, H>);
#[doc(hidden)]
pub struct Pipeline<S: 'static, H>(
PipelineInfo<S>,
PipelineState<S, H>,
Rc<Vec<Box<Middleware<S>>>>,
);
enum PipelineState<S, H> {
None,
@ -47,64 +42,31 @@ enum PipelineState<S, H> {
}
impl<S: 'static, H: PipelineHandler<S>> PipelineState<S, H> {
fn is_response(&self) -> bool {
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
match *self {
PipelineState::Response(_) => true,
_ => false,
}
}
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
match *self {
PipelineState::Starting(ref mut state) => state.poll(info),
PipelineState::Handler(ref mut state) => state.poll(info),
PipelineState::RunMiddlewares(ref mut state) => state.poll(info),
PipelineState::Finishing(ref mut state) => state.poll(info),
PipelineState::Starting(ref mut state) => state.poll(info, mws),
PipelineState::Handler(ref mut state) => state.poll(info, mws),
PipelineState::RunMiddlewares(ref mut state) => state.poll(info, mws),
PipelineState::Finishing(ref mut state) => state.poll(info, mws),
PipelineState::Completed(ref mut state) => state.poll(info),
PipelineState::Response(_) | PipelineState::None | PipelineState::Error => {
None
}
PipelineState::Response(ref mut state) => state.poll(info, mws),
PipelineState::None | PipelineState::Error => None,
}
}
}
struct PipelineInfo<S> {
req: UnsafeCell<HttpRequest<S>>,
struct PipelineInfo<S: 'static> {
req: HttpRequest<S>,
count: u16,
mws: Rc<Vec<Box<Middleware<S>>>>,
context: Option<Box<ActorHttpContext>>,
error: Option<Error>,
disconnected: Option<bool>,
encoding: ContentEncoding,
}
impl<S> PipelineInfo<S> {
fn new(req: HttpRequest<S>) -> PipelineInfo<S> {
PipelineInfo {
req: UnsafeCell::new(req),
count: 0,
mws: Rc::new(Vec::new()),
error: None,
context: None,
disconnected: None,
encoding: ContentEncoding::Auto,
}
}
#[inline]
fn req(&self) -> &HttpRequest<S> {
unsafe { &*self.req.get() }
}
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
fn req_mut(&self) -> &mut HttpRequest<S> {
#[allow(mutable_transmutes)]
unsafe {
&mut *self.req.get()
}
}
impl<S: 'static> PipelineInfo<S> {
fn poll_context(&mut self) -> Poll<(), Error> {
if let Some(ref mut context) = self.context {
match context.poll() {
@ -119,35 +81,25 @@ impl<S> PipelineInfo<S> {
}
impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
pub fn new(
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
handler: Rc<UnsafeCell<H>>, htype: HandlerType,
pub(crate) fn new(
req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>, handler: Rc<H>,
) -> Pipeline<S, H> {
let mut info = PipelineInfo {
mws,
req: UnsafeCell::new(req),
req,
count: 0,
error: None,
context: None,
disconnected: None,
encoding: unsafe { &*handler.get() }.encoding(),
encoding: handler.encoding(),
};
let state = StartMiddlewares::init(&mut info, handler, htype);
let state = StartMiddlewares::init(&mut info, &mws, handler);
Pipeline(info, state)
}
}
impl Pipeline<(), Inner<()>> {
pub fn error<R: Into<HttpResponse>>(err: R) -> Box<HttpHandlerTask> {
Box::new(Pipeline::<(), Inner<()>>(
PipelineInfo::new(HttpRequest::default()),
ProcessResponse::init(err.into()),
))
Pipeline(info, state, mws)
}
}
impl<S: 'static, H> Pipeline<S, H> {
#[inline]
fn is_done(&self) -> bool {
match self.1 {
PipelineState::None
@ -167,57 +119,61 @@ impl<S: 'static, H: PipelineHandler<S>> HttpHandlerTask for Pipeline<S, H> {
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) };
let mut state = mem::replace(&mut self.1, PipelineState::None);
loop {
if self.1.is_response() {
let state = mem::replace(&mut self.1, PipelineState::None);
if let PipelineState::Response(st) = state {
match st.poll_io(io, info) {
Ok(state) => {
self.1 = state;
if let Some(error) = self.0.error.take() {
return Err(error);
} else {
return Ok(Async::Ready(self.is_done()));
}
}
Err(state) => {
self.1 = state;
return Ok(Async::NotReady);
if let PipelineState::Response(st) = state {
match st.poll_io(io, &mut self.0, &self.2) {
Ok(state) => {
self.1 = state;
if let Some(error) = self.0.error.take() {
return Err(error);
} else {
return Ok(Async::Ready(self.is_done()));
}
}
Err(state) => {
self.1 = state;
return Ok(Async::NotReady);
}
}
}
match self.1 {
match state {
PipelineState::None => return Ok(Async::Ready(true)),
PipelineState::Error => {
return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into())
return Err(
io::Error::new(io::ErrorKind::Other, "Internal error").into()
)
}
_ => (),
}
match self.1.poll(info) {
Some(state) => self.1 = state,
None => return Ok(Async::NotReady),
match state.poll(&mut self.0, &self.2) {
Some(st) => state = st,
None => {
return {
self.1 = state;
Ok(Async::NotReady)
}
}
}
}
}
fn poll(&mut self) -> Poll<(), Error> {
let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) };
fn poll_completed(&mut self) -> Poll<(), Error> {
let mut state = mem::replace(&mut self.1, PipelineState::None);
loop {
match self.1 {
match state {
PipelineState::None | PipelineState::Error => {
return Ok(Async::Ready(()))
}
_ => (),
}
if let Some(state) = self.1.poll(info) {
self.1 = state;
if let Some(st) = state.poll(&mut self.0, &self.2) {
state = st;
} else {
self.1 = state;
return Ok(Async::NotReady);
}
}
@ -228,76 +184,88 @@ type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
/// Middlewares start executor
struct StartMiddlewares<S, H> {
hnd: Rc<UnsafeCell<H>>,
htype: HandlerType,
hnd: Rc<H>,
fut: Option<Fut>,
_s: PhantomData<S>,
}
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
fn init(
info: &mut PipelineInfo<S>, hnd: Rc<UnsafeCell<H>>, htype: HandlerType,
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], hnd: Rc<H>,
) -> PipelineState<S, H> {
// execute middlewares, we need this stage because middlewares could be
// non-async and we can move to next state immediately
let len = info.mws.len() as u16;
let len = mws.len() as u16;
loop {
if info.count == len {
let reply = unsafe { &mut *hnd.get() }.handle(info.req().clone(), htype);
return WaitingResponse::init(info, reply);
let reply = hnd.handle(&info.req);
return WaitingResponse::init(info, mws, reply);
} else {
match info.mws[info.count as usize].start(info.req_mut()) {
match mws[info.count as usize].start(&info.req) {
Ok(Started::Done) => info.count += 1,
Ok(Started::Response(resp)) => {
return RunMiddlewares::init(info, resp)
return RunMiddlewares::init(info, mws, resp);
}
Ok(Started::Future(fut)) => {
return PipelineState::Starting(StartMiddlewares {
hnd,
htype,
fut: Some(fut),
_s: PhantomData,
})
}
Err(err) => return ProcessResponse::init(err.into()),
Err(err) => {
return RunMiddlewares::init(info, mws, err.into());
}
}
}
}
}
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
let len = info.mws.len() as u16;
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
let len = mws.len() as u16;
'outer: loop {
match self.fut.as_mut().unwrap().poll() {
Ok(Async::NotReady) => return None,
Ok(Async::NotReady) => {
return None;
}
Ok(Async::Ready(resp)) => {
info.count += 1;
if let Some(resp) = resp {
return Some(RunMiddlewares::init(info, resp));
return Some(RunMiddlewares::init(info, mws, resp));
}
if info.count == len {
let reply = unsafe { &mut *self.hnd.get() }
.handle(info.req().clone(), self.htype);
return Some(WaitingResponse::init(info, reply));
} else {
loop {
match info.mws[info.count as usize].start(info.req_mut()) {
loop {
if info.count == len {
let reply = self.hnd.handle(&info.req);
return Some(WaitingResponse::init(info, mws, reply));
} else {
let res = mws[info.count as usize].start(&info.req);
match res {
Ok(Started::Done) => info.count += 1,
Ok(Started::Response(resp)) => {
return Some(RunMiddlewares::init(info, resp));
return Some(RunMiddlewares::init(info, mws, resp));
}
Ok(Started::Future(fut)) => {
self.fut = Some(fut);
continue 'outer;
}
Err(err) => {
return Some(ProcessResponse::init(err.into()))
return Some(RunMiddlewares::init(
info,
mws,
err.into(),
));
}
}
}
}
}
Err(err) => return Some(ProcessResponse::init(err.into())),
Err(err) => {
return Some(RunMiddlewares::init(info, mws, err.into()));
}
}
}
}
@ -313,11 +281,12 @@ struct WaitingResponse<S, H> {
impl<S: 'static, H> WaitingResponse<S, H> {
#[inline]
fn init(
info: &mut PipelineInfo<S>, reply: AsyncResult<HttpResponse>,
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
reply: AsyncResult<HttpResponse>,
) -> PipelineState<S, H> {
match reply.into() {
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, mws, resp),
AsyncResultItem::Err(err) => RunMiddlewares::init(info, mws, err.into()),
AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse {
fut,
_s: PhantomData,
@ -326,11 +295,13 @@ impl<S: 'static, H> WaitingResponse<S, H> {
}
}
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
Err(err) => Some(RunMiddlewares::init(info, err.into())),
Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, mws, resp)),
Err(err) => Some(RunMiddlewares::init(info, mws, err.into())),
}
}
}
@ -344,15 +315,19 @@ struct RunMiddlewares<S, H> {
}
impl<S: 'static, H> RunMiddlewares<S, H> {
fn init(info: &mut PipelineInfo<S>, mut resp: HttpResponse) -> PipelineState<S, H> {
#[inline]
fn init(
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], mut resp: HttpResponse,
) -> PipelineState<S, H> {
if info.count == 0 {
return ProcessResponse::init(resp);
}
let mut curr = 0;
let len = info.mws.len();
let len = mws.len();
loop {
resp = match info.mws[curr].response(info.req_mut(), resp) {
let state = mws[curr].response(&info.req, resp);
resp = match state {
Err(err) => {
info.count = (curr + 1) as u16;
return ProcessResponse::init(err.into());
@ -371,14 +346,16 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
fut: Some(fut),
_s: PhantomData,
_h: PhantomData,
})
});
}
};
}
}
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
let len = info.mws.len();
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
let len = mws.len();
loop {
// poll latest fut
@ -395,7 +372,8 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
if self.curr == len {
return Some(ProcessResponse::init(resp));
} else {
match info.mws[self.curr].response(info.req_mut(), resp) {
let state = mws[self.curr].response(&info.req, resp);
match state {
Err(err) => return Some(ProcessResponse::init(err.into())),
Ok(Response::Done(r)) => {
self.curr += 1;
@ -413,7 +391,7 @@ impl<S: 'static, H> RunMiddlewares<S, H> {
}
struct ProcessResponse<S, H> {
resp: HttpResponse,
resp: Option<HttpResponse>,
iostate: IOState,
running: RunningState,
drain: Option<oneshot::Sender<()>>,
@ -421,7 +399,7 @@ struct ProcessResponse<S, H> {
_h: PhantomData<H>,
}
#[derive(PartialEq)]
#[derive(PartialEq, Debug)]
enum RunningState {
Running,
Paused,
@ -454,7 +432,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
#[inline]
fn init(resp: HttpResponse) -> PipelineState<S, H> {
PipelineState::Response(ProcessResponse {
resp,
resp: Some(resp),
iostate: IOState::Response,
running: RunningState::Running,
drain: None,
@ -463,8 +441,82 @@ impl<S: 'static, H> ProcessResponse<S, H> {
})
}
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
// connection is dead at this point
match mem::replace(&mut self.iostate, IOState::Done) {
IOState::Response => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
IOState::Payload(_) => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
IOState::Actor(mut ctx) => {
if info.disconnected.take().is_some() {
ctx.disconnected();
}
loop {
match ctx.poll() {
Ok(Async::Ready(Some(vec))) => {
if vec.is_empty() {
continue;
}
for frame in vec {
match frame {
Frame::Chunk(None) => {
info.context = Some(ctx);
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
Frame::Chunk(Some(_)) => (),
Frame::Drain(fut) => {
let _ = fut.send(());
}
}
}
}
Ok(Async::Ready(None)) => {
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
))
}
Ok(Async::NotReady) => {
self.iostate = IOState::Actor(ctx);
return None;
}
Err(err) => {
info.context = Some(ctx);
info.error = Some(err);
return Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
}
}
}
IOState::Done => Some(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
)),
}
}
fn poll_io(
mut self, io: &mut Writer, info: &mut PipelineInfo<S>,
mws: &[Box<Middleware<S>>],
) -> Result<PipelineState<S, H>, PipelineState<S, H>> {
loop {
if self.drain.is_none() && self.running != RunningState::Paused {
@ -472,28 +524,35 @@ impl<S: 'static, H> ProcessResponse<S, H> {
'inner: loop {
let result = match mem::replace(&mut self.iostate, IOState::Done) {
IOState::Response => {
let encoding =
self.resp.content_encoding().unwrap_or(info.encoding);
let encoding = self
.resp
.as_ref()
.unwrap()
.content_encoding()
.unwrap_or(info.encoding);
let result = match io.start(
info.req_mut().get_inner(),
&mut self.resp,
&info.req,
self.resp.as_mut().unwrap(),
encoding,
) {
Ok(res) => res,
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info, self.resp,
info,
mws,
self.resp.take().unwrap(),
));
}
};
if let Some(err) = self.resp.error() {
if self.resp.status().is_server_error() {
if let Some(err) = self.resp.as_ref().unwrap().error() {
if self.resp.as_ref().unwrap().status().is_server_error()
{
error!(
"Error occured during request handling, status: {} {}",
self.resp.status(), err
self.resp.as_ref().unwrap().status(), err
);
} else {
warn!(
@ -507,7 +566,7 @@ impl<S: 'static, H> ProcessResponse<S, H> {
}
// always poll stream or actor for the first time
match self.resp.replace_body(Body::Empty) {
match self.resp.as_mut().unwrap().replace_body(Body::Empty) {
Body::Streaming(stream) => {
self.iostate = IOState::Payload(stream);
continue 'inner;
@ -526,18 +585,22 @@ impl<S: 'static, H> ProcessResponse<S, H> {
if let Err(err) = io.write_eof() {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info, self.resp,
info,
mws,
self.resp.take().unwrap(),
));
}
break;
}
Ok(Async::Ready(Some(chunk))) => {
self.iostate = IOState::Payload(body);
match io.write(chunk.into()) {
match io.write(&chunk.into()) {
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(
info, self.resp,
info,
mws,
self.resp.take().unwrap(),
));
}
Ok(result) => result,
@ -549,7 +612,11 @@ impl<S: 'static, H> ProcessResponse<S, H> {
}
Err(err) => {
info.error = Some(err);
return Ok(FinishingMiddlewares::init(info, self.resp));
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
},
IOState::Actor(mut ctx) => {
@ -571,25 +638,30 @@ impl<S: 'static, H> ProcessResponse<S, H> {
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info, self.resp,
info,
mws,
self.resp.take().unwrap(),
),
);
}
break 'inner;
}
Frame::Chunk(Some(chunk)) => {
match io.write(chunk) {
Err(err) => {
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info, self.resp,
),
);
}
Ok(result) => res = Some(result),
Frame::Chunk(Some(chunk)) => match io
.write(&chunk)
{
Err(err) => {
info.context = Some(ctx);
info.error = Some(err.into());
return Ok(
FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
),
);
}
}
Ok(result) => res = Some(result),
},
Frame::Drain(fut) => self.drain = Some(fut),
}
}
@ -606,9 +678,12 @@ impl<S: 'static, H> ProcessResponse<S, H> {
break;
}
Err(err) => {
info.context = Some(ctx);
info.error = Some(err);
return Ok(FinishingMiddlewares::init(
info, self.resp,
info,
mws,
self.resp.take().unwrap(),
));
}
}
@ -641,8 +716,18 @@ impl<S: 'static, H> ProcessResponse<S, H> {
}
Ok(Async::NotReady) => return Err(PipelineState::Response(self)),
Err(err) => {
if let IOState::Actor(mut ctx) =
mem::replace(&mut self.iostate, IOState::Done)
{
ctx.disconnected();
info.context = Some(ctx);
}
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(info, self.resp));
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
}
}
@ -656,11 +741,19 @@ impl<S: 'static, H> ProcessResponse<S, H> {
Ok(_) => (),
Err(err) => {
info.error = Some(err.into());
return Ok(FinishingMiddlewares::init(info, self.resp));
return Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
));
}
}
self.resp.set_response_size(io.written());
Ok(FinishingMiddlewares::init(info, self.resp))
self.resp.as_mut().unwrap().set_response_size(io.written());
Ok(FinishingMiddlewares::init(
info,
mws,
self.resp.take().unwrap(),
))
}
_ => Err(PipelineState::Response(self)),
}
@ -669,24 +762,28 @@ impl<S: 'static, H> ProcessResponse<S, H> {
/// Middlewares start executor
struct FinishingMiddlewares<S, H> {
resp: HttpResponse,
resp: Option<HttpResponse>,
fut: Option<Box<Future<Item = (), Error = Error>>>,
_s: PhantomData<S>,
_h: PhantomData<H>,
}
impl<S: 'static, H> FinishingMiddlewares<S, H> {
fn init(info: &mut PipelineInfo<S>, resp: HttpResponse) -> PipelineState<S, H> {
#[inline]
fn init(
info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>], resp: HttpResponse,
) -> PipelineState<S, H> {
if info.count == 0 {
resp.release();
Completed::init(info)
} else {
let mut state = FinishingMiddlewares {
resp,
resp: Some(resp),
fut: None,
_s: PhantomData,
_h: PhantomData,
};
if let Some(st) = state.poll(info) {
if let Some(st) = state.poll(info, mws) {
st
} else {
PipelineState::Finishing(state)
@ -694,7 +791,9 @@ impl<S: 'static, H> FinishingMiddlewares<S, H> {
}
}
fn poll(&mut self, info: &mut PipelineInfo<S>) -> Option<PipelineState<S, H>> {
fn poll(
&mut self, info: &mut PipelineInfo<S>, mws: &[Box<Middleware<S>>],
) -> Option<PipelineState<S, H>> {
loop {
// poll latest fut
let not_ready = if let Some(ref mut fut) = self.fut {
@ -714,13 +813,17 @@ impl<S: 'static, H> FinishingMiddlewares<S, H> {
}
self.fut = None;
if info.count == 0 {
self.resp.take().unwrap().release();
return Some(Completed::init(info));
}
info.count -= 1;
match info.mws[info.count as usize].finish(info.req_mut(), &self.resp) {
let state =
mws[info.count as usize].finish(&info.req, self.resp.as_ref().unwrap());
match state {
Finished::Done => {
if info.count == 0 {
self.resp.take().unwrap().release();
return Some(Completed::init(info));
}
}
@ -745,7 +848,13 @@ impl<S, H> Completed<S, H> {
if info.context.is_none() {
PipelineState::None
} else {
PipelineState::Completed(Completed(PhantomData, PhantomData))
match info.poll_context() {
Ok(Async::NotReady) => {
PipelineState::Completed(Completed(PhantomData, PhantomData))
}
Ok(Async::Ready(())) => PipelineState::None,
Err(_) => PipelineState::Error,
}
}
}
@ -758,68 +867,3 @@ impl<S, H> Completed<S, H> {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use actix::*;
use context::HttpContext;
use futures::future::{lazy, result};
use tokio_core::reactor::Core;
impl<S, H> PipelineState<S, H> {
fn is_none(&self) -> Option<bool> {
if let PipelineState::None = *self {
Some(true)
} else {
None
}
}
fn completed(self) -> Option<Completed<S, H>> {
if let PipelineState::Completed(c) = self {
Some(c)
} else {
None
}
}
}
struct MyActor;
impl Actor for MyActor {
type Context = HttpContext<MyActor>;
}
#[test]
fn test_completed() {
Core::new()
.unwrap()
.run(lazy(|| {
let mut info = PipelineInfo::new(HttpRequest::default());
Completed::<(), Inner<()>>::init(&mut info)
.is_none()
.unwrap();
let req = HttpRequest::default();
let mut ctx = HttpContext::new(req.clone(), MyActor);
let addr: Addr<Unsync, _> = ctx.address();
let mut info = PipelineInfo::new(req);
info.context = Some(Box::new(ctx));
let mut state = Completed::<(), Inner<()>>::init(&mut info)
.completed()
.unwrap();
assert!(state.poll(&mut info).is_none());
let pp = Pipeline(info, PipelineState::Completed(state));
assert!(!pp.is_done());
let Pipeline(mut info, st) = pp;
let mut st = st.completed().unwrap();
drop(addr);
assert!(st.poll(&mut info).unwrap().is_none().unwrap());
result(Ok::<_, ()>(()))
}))
.unwrap();
}
}

View File

@ -1,10 +1,10 @@
//! Route match predicates
#![allow(non_snake_case)]
use std::marker::PhantomData;
use http;
use http::{header, HttpTryFrom};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use std::marker::PhantomData;
use server::message::Request;
/// Trait defines resource route predicate.
/// Predicate can modify request object. It is also possible to
@ -12,7 +12,7 @@ use std::marker::PhantomData;
/// Extensions container available via `HttpRequest::extensions()` method.
pub trait Predicate<S> {
/// Check if request matches predicate
fn check(&self, &mut HttpRequest<S>) -> bool;
fn check(&self, &Request, &S) -> bool;
}
/// Return predicate that matches if any of supplied predicate matches.
@ -22,10 +22,11 @@ pub trait Predicate<S> {
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new()
/// .resource("/index.html", |r| r.route()
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Post()))
/// .f(|r| HttpResponse::MethodNotAllowed()));
/// .f(|r| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn Any<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AnyPredicate<S> {
@ -44,9 +45,9 @@ impl<S> AnyPredicate<S> {
}
impl<S: 'static> Predicate<S> for AnyPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
fn check(&self, req: &Request, state: &S) -> bool {
for p in &self.0 {
if p.check(req) {
if p.check(req, state) {
return true;
}
}
@ -61,11 +62,14 @@ impl<S: 'static> Predicate<S> for AnyPredicate<S> {
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new()
/// .resource("/index.html", |r| r.route()
/// .filter(pred::All(pred::Get())
/// .and(pred::Header("content-type", "plain/text")))
/// .f(|_| HttpResponse::MethodNotAllowed()));
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .filter(
/// pred::All(pred::Get())
/// .and(pred::Header("content-type", "text/plain")),
/// )
/// .f(|_| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn All<S: 'static, P: Predicate<S> + 'static>(pred: P) -> AllPredicate<S> {
@ -84,9 +88,9 @@ impl<S> AllPredicate<S> {
}
impl<S: 'static> Predicate<S> for AllPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
fn check(&self, req: &Request, state: &S) -> bool {
for p in &self.0 {
if !p.check(req) {
if !p.check(req, state) {
return false;
}
}
@ -103,8 +107,8 @@ pub fn Not<S: 'static, P: Predicate<S> + 'static>(pred: P) -> NotPredicate<S> {
pub struct NotPredicate<S>(Box<Predicate<S>>);
impl<S: 'static> Predicate<S> for NotPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
!self.0.check(req)
fn check(&self, req: &Request, state: &S) -> bool {
!self.0.check(req, state)
}
}
@ -113,7 +117,7 @@ impl<S: 'static> Predicate<S> for NotPredicate<S> {
pub struct MethodPredicate<S>(http::Method, PhantomData<S>);
impl<S: 'static> Predicate<S> for MethodPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
fn check(&self, req: &Request, _: &S) -> bool {
*req.method() == self.0
}
}
@ -181,14 +185,10 @@ pub fn Header<S: 'static>(
}
#[doc(hidden)]
pub struct HeaderPredicate<S>(
header::HeaderName,
header::HeaderValue,
PhantomData<S>,
);
pub struct HeaderPredicate<S>(header::HeaderName, header::HeaderValue, PhantomData<S>);
impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
fn check(&self, req: &Request, _: &S) -> bool {
if let Some(val) = req.headers().get(&self.0) {
return val == self.1;
}
@ -196,148 +196,133 @@ impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
}
}
/// Return predicate that matches if request contains specified Host name.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .filter(pred::Host("www.rust-lang.org"))
/// .f(|_| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn Host<S: 'static, H: AsRef<str>>(host: H) -> HostPredicate<S> {
HostPredicate(host.as_ref().to_string(), None, PhantomData)
}
#[doc(hidden)]
pub struct HostPredicate<S>(String, Option<String>, PhantomData<S>);
impl<S> HostPredicate<S> {
/// Set reuest scheme to match
pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
self.1 = Some(scheme.as_ref().to_string())
}
}
impl<S: 'static> Predicate<S> for HostPredicate<S> {
fn check(&self, req: &Request, _: &S) -> bool {
let info = req.connection_info();
if let Some(ref scheme) = self.1 {
self.0 == info.host() && scheme == info.scheme()
} else {
self.0 == info.host()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::header::{self, HeaderMap};
use http::{Method, Uri, Version};
use std::str::FromStr;
use http::{header, Method};
use test::TestRequest;
#[test]
fn test_header() {
let mut headers = HeaderMap::new();
headers.insert(
let req = TestRequest::with_header(
header::TRANSFER_ENCODING,
header::HeaderValue::from_static("chunked"),
);
let mut req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
).finish();
let pred = Header("transfer-encoding", "chunked");
assert!(pred.check(&mut req));
assert!(pred.check(&req, req.state()));
let pred = Header("transfer-encoding", "other");
assert!(!pred.check(&mut req));
assert!(!pred.check(&req, req.state()));
let pred = Header("content-type", "other");
assert!(!pred.check(&mut req));
assert!(!pred.check(&req, req.state()));
}
#[test]
fn test_host() {
let req = TestRequest::default()
.header(
header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"),
).finish();
let pred = Host("www.rust-lang.org");
assert!(pred.check(&req, req.state()));
let pred = Host("localhost");
assert!(!pred.check(&req, req.state()));
}
#[test]
fn test_methods() {
let mut req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
let mut req2 = HttpRequest::new(
Method::POST,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
let req = TestRequest::default().finish();
let req2 = TestRequest::default().method(Method::POST).finish();
assert!(Get().check(&mut req));
assert!(!Get().check(&mut req2));
assert!(Post().check(&mut req2));
assert!(!Post().check(&mut req));
assert!(Get().check(&req, req.state()));
assert!(!Get().check(&req2, req2.state()));
assert!(Post().check(&req2, req2.state()));
assert!(!Post().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::PUT,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Put().check(&mut r));
assert!(!Put().check(&mut req));
let r = TestRequest::default().method(Method::PUT).finish();
assert!(Put().check(&r, r.state()));
assert!(!Put().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::DELETE,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Delete().check(&mut r));
assert!(!Delete().check(&mut req));
let r = TestRequest::default().method(Method::DELETE).finish();
assert!(Delete().check(&r, r.state()));
assert!(!Delete().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::HEAD,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Head().check(&mut r));
assert!(!Head().check(&mut req));
let r = TestRequest::default().method(Method::HEAD).finish();
assert!(Head().check(&r, r.state()));
assert!(!Head().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::OPTIONS,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Options().check(&mut r));
assert!(!Options().check(&mut req));
let r = TestRequest::default().method(Method::OPTIONS).finish();
assert!(Options().check(&r, r.state()));
assert!(!Options().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::CONNECT,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Connect().check(&mut r));
assert!(!Connect().check(&mut req));
let r = TestRequest::default().method(Method::CONNECT).finish();
assert!(Connect().check(&r, r.state()));
assert!(!Connect().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::PATCH,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Patch().check(&mut r));
assert!(!Patch().check(&mut req));
let r = TestRequest::default().method(Method::PATCH).finish();
assert!(Patch().check(&r, r.state()));
assert!(!Patch().check(&req, req.state()));
let mut r = HttpRequest::new(
Method::TRACE,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
assert!(Trace().check(&mut r));
assert!(!Trace().check(&mut req));
let r = TestRequest::default().method(Method::TRACE).finish();
assert!(Trace().check(&r, r.state()));
assert!(!Trace().check(&req, req.state()));
}
#[test]
fn test_preds() {
let mut r = HttpRequest::new(
Method::TRACE,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
let r = TestRequest::default().method(Method::TRACE).finish();
assert!(Not(Get()).check(&mut r));
assert!(!Not(Trace()).check(&mut r));
assert!(Not(Get()).check(&r, r.state()));
assert!(!Not(Trace()).check(&r, r.state()));
assert!(All(Trace()).and(Trace()).check(&mut r));
assert!(!All(Get()).and(Trace()).check(&mut r));
assert!(All(Trace()).and(Trace()).check(&r, r.state()));
assert!(!All(Get()).and(Trace()).check(&r, r.state()));
assert!(Any(Get()).or(Trace()).check(&mut r));
assert!(!Any(Get()).or(Get()).check(&mut r));
assert!(Any(Get()).or(Trace()).check(&r, r.state()));
assert!(!Any(Get()).or(Get()).check(&r, r.state()));
}
}

View File

@ -1,15 +1,22 @@
use std::marker::PhantomData;
use std::ops::Deref;
use std::rc::Rc;
use http::{Method, StatusCode};
use futures::Future;
use http::Method;
use smallvec::SmallVec;
use error::Error;
use handler::{AsyncResult, FromRequest, Handler, Responder};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::Middleware;
use pred;
use route::Route;
use router::ResourceDef;
use with::WithFactory;
#[derive(Copy, Clone)]
pub(crate) struct RouteId(usize);
/// *Resource* is an entry in route table which corresponds to requested URL.
///
@ -31,45 +38,39 @@ use route::Route;
/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok()))
/// .finish();
/// }
pub struct ResourceHandler<S = ()> {
name: String,
state: PhantomData<S>,
pub struct Resource<S = ()> {
rdef: ResourceDef,
routes: SmallVec<[Route<S>; 3]>,
middlewares: Rc<Vec<Box<Middleware<S>>>>,
}
impl<S> Default for ResourceHandler<S> {
fn default() -> Self {
ResourceHandler {
name: String::new(),
state: PhantomData,
impl<S> Resource<S> {
/// Create new resource with specified resource definition
pub fn new(rdef: ResourceDef) -> Self {
Resource {
rdef,
routes: SmallVec::new(),
middlewares: Rc::new(Vec::new()),
}
}
}
impl<S> ResourceHandler<S> {
pub(crate) fn default_not_found() -> Self {
ResourceHandler {
name: String::new(),
state: PhantomData,
routes: SmallVec::new(),
middlewares: Rc::new(Vec::new()),
}
/// Name of the resource
pub(crate) fn get_name(&self) -> &str {
self.rdef.name()
}
/// Set resource name
pub fn name<T: Into<String>>(&mut self, name: T) {
self.name = name.into();
pub fn name(&mut self, name: &str) {
self.rdef.set_name(name);
}
pub(crate) fn get_name(&self) -> &str {
&self.name
/// Resource definition
pub fn rdef(&self) -> &ResourceDef {
&self.rdef
}
}
impl<S: 'static> ResourceHandler<S> {
impl<S: 'static> Resource<S> {
/// Register a new route and return mutable reference to *Route* object.
/// *Route* is used for route configuration, i.e. adding predicates,
/// setting up handler.
@ -80,11 +81,12 @@ impl<S: 'static> ResourceHandler<S> {
///
/// fn main() {
/// let app = App::new()
/// .resource(
/// "/", |r| r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
/// .filter(pred::Header("Content-Type", "text/plain"))
/// .f(|r| HttpResponse::Ok()))
/// .resource("/", |r| {
/// r.route()
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
/// .filter(pred::Header("Content-Type", "text/plain"))
/// .f(|r| HttpResponse::Ok())
/// })
/// .finish();
/// }
/// ```
@ -125,25 +127,44 @@ impl<S: 'static> ResourceHandler<S> {
/// Register a new route and add method check to route.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.method(http::Method::GET).f(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust,ignore
/// Application::resource("/", |r| r.route().filter(pred::Get()).f(index)
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index));
/// ```
pub fn method(&mut self, method: Method) -> &mut Route<S> {
self.routes.push(Route::default());
self.routes
.last_mut()
.unwrap()
.filter(pred::Method(method))
self.routes.last_mut().unwrap().filter(pred::Method(method))
}
/// Register a new route and add handler object.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.h(handler));
/// ```
///
/// This is shortcut for:
///
/// ```rust,ignore
/// Application::resource("/", |r| r.route().h(handler)
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().h(handler));
/// ```
pub fn h<H: Handler<S>>(&mut self, handler: H) {
self.routes.push(Route::default());
@ -152,14 +173,25 @@ impl<S: 'static> ResourceHandler<S> {
/// Register a new route and add handler function.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.f(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust,ignore
/// Application::resource("/", |r| r.route().f(index)
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().f(index));
/// ```
pub fn f<F, R>(&mut self, handler: F)
where
F: Fn(HttpRequest<S>) -> R + 'static,
F: Fn(&HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
self.routes.push(Route::default());
@ -168,14 +200,25 @@ impl<S: 'static> ResourceHandler<S> {
/// Register a new route and add handler.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
/// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
///
/// App::new().resource("/", |r| r.with(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust,ignore
/// Application::resource("/", |r| r.route().with(index)
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().resource("/", |r| r.route().with(index));
/// ```
pub fn with<T, F, R>(&mut self, handler: F)
where
F: Fn(T) -> R + 'static,
F: WithFactory<T, S, R>,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
@ -183,6 +226,45 @@ impl<S: 'static> ResourceHandler<S> {
self.routes.last_mut().unwrap().with(handler);
}
/// Register a new route and add async handler.
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// use actix_web::*;
/// use futures::future::Future;
///
/// fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// unimplemented!()
/// }
///
/// App::new().resource("/", |r| r.with_async(index));
/// ```
///
/// This is shortcut for:
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate futures;
/// # use actix_web::*;
/// # use futures::future::Future;
/// # fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
/// # unimplemented!()
/// # }
/// App::new().resource("/", |r| r.route().with_async(index));
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.routes.push(Route::default());
self.routes.last_mut().unwrap().with_async(handler);
}
/// Register a resource middleware
///
/// This is similar to `App's` middlewares, but
@ -196,22 +278,47 @@ impl<S: 'static> ResourceHandler<S> {
.push(Box::new(mw));
}
pub(crate) fn handle(
&mut self, mut req: HttpRequest<S>, default: Option<&mut ResourceHandler<S>>,
) -> AsyncResult<HttpResponse> {
for route in &mut self.routes {
if route.check(&mut req) {
return if self.middlewares.is_empty() {
route.handle(req)
} else {
route.compose(req, Rc::clone(&self.middlewares))
};
#[inline]
pub(crate) fn get_route_id(&self, req: &HttpRequest<S>) -> Option<RouteId> {
for idx in 0..self.routes.len() {
if (&self.routes[idx]).check(req) {
return Some(RouteId(idx));
}
}
if let Some(resource) = default {
resource.handle(req, None)
None
}
#[inline]
pub(crate) fn handle(
&self, id: RouteId, req: &HttpRequest<S>,
) -> AsyncResult<HttpResponse> {
if self.middlewares.is_empty() {
(&self.routes[id.0]).handle(req)
} else {
AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND))
(&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares))
}
}
}
/// Default resource
pub struct DefaultResource<S>(Rc<Resource<S>>);
impl<S> Deref for DefaultResource<S> {
type Target = Resource<S>;
fn deref(&self) -> &Resource<S> {
self.0.as_ref()
}
}
impl<S> Clone for DefaultResource<S> {
fn clone(&self) -> Self {
DefaultResource(self.0.clone())
}
}
impl<S> From<Resource<S>> for DefaultResource<S> {
fn from(res: Resource<S>) -> Self {
DefaultResource(Rc::new(res))
}
}

View File

@ -1,19 +1,22 @@
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::rc::Rc;
use futures::{Async, Future, Poll};
use error::Error;
use handler::{AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler,
Responder, RouteHandler, WrapHandler};
use handler::{
AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder,
RouteHandler, WrapHandler,
};
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Finished as MiddlewareFinished, Middleware,
Response as MiddlewareResponse, Started as MiddlewareStarted};
use middleware::{
Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse,
Started as MiddlewareStarted,
};
use pred::Predicate;
use with::{ExtractorConfig, With, With2, With3};
use with::{WithAsyncFactory, WithFactory};
/// Resource route definition
///
@ -28,16 +31,17 @@ impl<S: 'static> Default for Route<S> {
fn default() -> Route<S> {
Route {
preds: Vec::new(),
handler: InnerHandler::new(|_| HttpResponse::new(StatusCode::NOT_FOUND)),
handler: InnerHandler::new(|_: &_| HttpResponse::new(StatusCode::NOT_FOUND)),
}
}
}
impl<S: 'static> Route<S> {
#[inline]
pub(crate) fn check(&self, req: &mut HttpRequest<S>) -> bool {
pub(crate) fn check(&self, req: &HttpRequest<S>) -> bool {
let state = req.state();
for pred in &self.preds {
if !pred.check(req) {
if !pred.check(req, state) {
return false;
}
}
@ -45,13 +49,13 @@ impl<S: 'static> Route<S> {
}
#[inline]
pub(crate) fn handle(&mut self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
pub(crate) fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
self.handler.handle(req)
}
#[inline]
pub(crate) fn compose(
&mut self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
&self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
) -> AsyncResult<HttpResponse> {
AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
}
@ -62,13 +66,12 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn main() {
/// App::new()
/// .resource("/path", |r|
/// r.route()
/// .filter(pred::Get())
/// .filter(pred::Header("content-type", "text/plain"))
/// .f(|req| HttpResponse::Ok())
/// )
/// App::new().resource("/path", |r| {
/// r.route()
/// .filter(pred::Get())
/// .filter(pred::Header("content-type", "text/plain"))
/// .f(|req| HttpResponse::Ok())
/// })
/// # .finish();
/// # }
/// ```
@ -87,7 +90,7 @@ impl<S: 'static> Route<S> {
/// during route configuration, so it does not return reference to self.
pub fn f<F, R>(&mut self, handler: F)
where
F: Fn(HttpRequest<S>) -> R + 'static,
F: Fn(&HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
self.handler = InnerHandler::new(handler);
@ -96,7 +99,7 @@ impl<S: 'static> Route<S> {
/// Set async handler function.
pub fn a<H, R, F, E>(&mut self, handler: H)
where
H: Fn(HttpRequest<S>) -> F + 'static,
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
@ -111,7 +114,7 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Result, http};
/// use actix_web::{http, App, Path, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
@ -125,128 +128,198 @@ impl<S: 'static> Route<S> {
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
where
F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
let cfg = ExtractorConfig::default();
self.h(With::new(handler, Clone::clone(&cfg)));
cfg
}
/// Set handler function, use request extractor for both parameters.
///
/// It is possible to use tuples for specifing multiple extractors for one
/// handler function.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Query, Path, Result, http};
/// # use std::collections::HashMap;
/// use actix_web::{http, App, Json, Path, Query, Result};
///
/// #[derive(Deserialize)]
/// struct PParam {
/// struct Info {
/// username: String,
/// }
///
/// #[derive(Deserialize)]
/// struct QParam {
/// count: u32,
/// }
///
/// /// extract path and query information using serde
/// fn index(p: Path<PParam>, q: Query<QParam>) -> Result<String> {
/// Ok(format!("Welcome {}!", p.username))
/// /// extract path info using serde
/// fn index(
/// info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>),
/// ) -> Result<String> {
/// Ok(format!("Welcome {}!", info.0.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with2<T1, T2, F, R>(
&mut self, handler: F,
) -> (ExtractorConfig<S, T1>, ExtractorConfig<S, T2>)
pub fn with<T, F, R>(&mut self, handler: F)
where
F: Fn(T1, T2) -> R + 'static,
F: WithFactory<T, S, R> + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T: FromRequest<S> + 'static,
{
let cfg1 = ExtractorConfig::default();
let cfg2 = ExtractorConfig::default();
self.h(With2::new(
handler,
Clone::clone(&cfg1),
Clone::clone(&cfg2),
));
(cfg1, cfg2)
self.h(handler.create());
}
/// Set handler function, use request extractor for all parameters.
pub fn with3<T1, T2, T3, F, R>(
&mut self, handler: F,
) -> (
ExtractorConfig<S, T1>,
ExtractorConfig<S, T2>,
ExtractorConfig<S, T3>,
)
/// Set handler function. Same as `.with()` but it allows to configure
/// extractor. Configuration closure accepts config objects as tuple.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Path, Result};
///
/// /// extract text data from request
/// fn index(body: String) -> Result<String> {
/// Ok(format!("Body {}!", body))
/// }
///
/// fn main() {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET)
/// .with_config(index, |cfg| { // <- register handler
/// cfg.0.limit(4096); // <- limit size of the payload
/// })
/// });
/// }
/// ```
pub fn with_config<T, F, R, C>(&mut self, handler: F, cfg_f: C)
where
F: Fn(T1, T2, T3) -> R + 'static,
F: WithFactory<T, S, R>,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
T: FromRequest<S> + 'static,
C: FnOnce(&mut T::Config),
{
let cfg1 = ExtractorConfig::default();
let cfg2 = ExtractorConfig::default();
let cfg3 = ExtractorConfig::default();
self.h(With3::new(
handler,
Clone::clone(&cfg1),
Clone::clone(&cfg2),
Clone::clone(&cfg3),
));
(cfg1, cfg2, cfg3)
let mut cfg = <T::Config as Default>::default();
cfg_f(&mut cfg);
self.h(handler.create_with_config(cfg));
}
/// Set async handler function, use request extractor for parameters.
/// Also this method needs to be used if your handler function returns
/// `impl Future<>`
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Error, Path};
/// use futures::Future;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Path<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
/// unimplemented!()
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with_async(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where
F: WithAsyncFactory<T, S, R, I, E>,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.h(handler.create());
}
/// Set async handler function, use request extractor for parameters.
/// This method allows to configure extractor. Configuration closure
/// accepts config objects as tuple.
///
/// ```rust
/// # extern crate bytes;
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Error, Form};
/// use futures::Future;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// extract path info using serde
/// fn index(info: Form<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
/// unimplemented!()
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET)
/// .with_async_config(index, |cfg| {
/// cfg.0.limit(4096);
/// }),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async_config<T, F, R, I, E, C>(&mut self, handler: F, cfg: C)
where
F: WithAsyncFactory<T, S, R, I, E>,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
C: FnOnce(&mut T::Config),
{
let mut extractor_cfg = <T::Config as Default>::default();
cfg(&mut extractor_cfg);
self.h(handler.create_with_config(extractor_cfg));
}
}
/// `RouteHandler` wrapper. This struct is required because it needs to be
/// shared for resource level middlewares.
struct InnerHandler<S>(Rc<UnsafeCell<Box<RouteHandler<S>>>>);
struct InnerHandler<S>(Rc<Box<RouteHandler<S>>>);
impl<S: 'static> InnerHandler<S> {
#[inline]
fn new<H: Handler<S>>(h: H) -> Self {
InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(
h,
)))))
InnerHandler(Rc::new(Box::new(WrapHandler::new(h))))
}
#[inline]
fn async<H, R, F, E>(h: H) -> Self
where
H: Fn(HttpRequest<S>) -> F + 'static,
H: Fn(&HttpRequest<S>) -> F + 'static,
F: Future<Item = R, Error = E> + 'static,
R: Responder + 'static,
E: Into<Error> + 'static,
{
InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(
h,
)))))
InnerHandler(Rc::new(Box::new(AsyncHandler::new(h))))
}
#[inline]
pub fn handle(&self, req: HttpRequest<S>) -> AsyncResult<HttpResponse> {
// reason: handler is unique per thread, handler get called from async code only
let h = unsafe { &mut *self.0.as_ref().get() };
h.handle(req)
pub fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
self.0.handle(req)
}
}
@ -336,23 +409,27 @@ type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
impl<S: 'static> StartMiddlewares<S> {
fn init(info: &mut ComposeInfo<S>) -> ComposeState<S> {
let len = info.mws.len();
loop {
if info.count == len {
let reply = info.handler.handle(info.req.clone());
let reply = info.handler.handle(&info.req);
return WaitingResponse::init(info, reply);
} else {
match info.mws[info.count].start(&mut info.req) {
let result = info.mws[info.count].start(&info.req);
match result {
Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => {
return RunMiddlewares::init(info, resp)
return RunMiddlewares::init(info, resp);
}
Ok(MiddlewareStarted::Future(fut)) => {
return ComposeState::Starting(StartMiddlewares {
fut: Some(fut),
_s: PhantomData,
})
});
}
Err(err) => {
return RunMiddlewares::init(info, err.into());
}
Err(err) => return FinishingMiddlewares::init(info, err.into()),
}
}
}
@ -360,20 +437,24 @@ impl<S: 'static> StartMiddlewares<S> {
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
let len = info.mws.len();
'outer: loop {
match self.fut.as_mut().unwrap().poll() {
Ok(Async::NotReady) => return None,
Ok(Async::NotReady) => {
return None;
}
Ok(Async::Ready(resp)) => {
info.count += 1;
if let Some(resp) = resp {
return Some(RunMiddlewares::init(info, resp));
}
if info.count == len {
let reply = info.handler.handle(info.req.clone());
return Some(WaitingResponse::init(info, reply));
} else {
loop {
match info.mws[info.count].start(&mut info.req) {
loop {
if info.count == len {
let reply = info.handler.handle(&info.req);
return Some(WaitingResponse::init(info, reply));
} else {
let result = info.mws[info.count].start(&info.req);
match result {
Ok(MiddlewareStarted::Done) => info.count += 1,
Ok(MiddlewareStarted::Response(resp)) => {
return Some(RunMiddlewares::init(info, resp));
@ -383,24 +464,25 @@ impl<S: 'static> StartMiddlewares<S> {
continue 'outer;
}
Err(err) => {
return Some(FinishingMiddlewares::init(
info,
err.into(),
))
return Some(RunMiddlewares::init(info, err.into()));
}
}
}
}
}
Err(err) => return Some(FinishingMiddlewares::init(info, err.into())),
Err(err) => {
return Some(RunMiddlewares::init(info, err.into()));
}
}
}
}
}
type HandlerFuture = Future<Item = HttpResponse, Error = Error>;
// waiting for response
struct WaitingResponse<S> {
fut: Box<Future<Item = HttpResponse, Error = Error>>,
fut: Box<HandlerFuture>,
_s: PhantomData<S>,
}
@ -410,8 +492,8 @@ impl<S: 'static> WaitingResponse<S> {
info: &mut ComposeInfo<S>, reply: AsyncResult<HttpResponse>,
) -> ComposeState<S> {
match reply.into() {
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse {
fut,
_s: PhantomData,
@ -422,7 +504,7 @@ impl<S: 'static> WaitingResponse<S> {
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)),
Err(err) => Some(RunMiddlewares::init(info, err.into())),
}
}
@ -441,7 +523,8 @@ impl<S: 'static> RunMiddlewares<S> {
let len = info.mws.len();
loop {
resp = match info.mws[curr].response(&mut info.req, resp) {
let state = info.mws[curr].response(&info.req, resp);
resp = match state {
Err(err) => {
info.count = curr + 1;
return FinishingMiddlewares::init(info, err.into());
@ -459,7 +542,7 @@ impl<S: 'static> RunMiddlewares<S> {
curr,
fut: Some(fut),
_s: PhantomData,
})
});
}
};
}
@ -483,7 +566,8 @@ impl<S: 'static> RunMiddlewares<S> {
if self.curr == len {
return Some(FinishingMiddlewares::init(info, resp));
} else {
match info.mws[self.curr].response(&mut info.req, resp) {
let state = info.mws[self.curr].response(&info.req, resp);
match state {
Err(err) => {
return Some(FinishingMiddlewares::init(info, err.into()))
}
@ -551,9 +635,10 @@ impl<S: 'static> FinishingMiddlewares<S> {
}
info.count -= 1;
match info.mws[info.count as usize]
.finish(&mut info.req, self.resp.as_ref().unwrap())
{
let state = info.mws[info.count as usize]
.finish(&info.req, self.resp.as_ref().unwrap());
match state {
MiddlewareFinished::Done => {
if info.count == 0 {
return Some(Response::init(self.resp.take().unwrap()));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

472
src/server/accept.rs Normal file
View File

@ -0,0 +1,472 @@
use std::sync::mpsc as sync_mpsc;
use std::time::{Duration, Instant};
use std::{io, net, thread};
use futures::{sync::mpsc, Future};
use mio;
use slab::Slab;
use tokio_timer::Delay;
use actix::{msgs::Execute, Arbiter, System};
use super::server::ServerCommand;
use super::worker::{Conn, WorkerClient};
use super::Token;
pub(crate) enum Command {
Pause,
Resume,
Stop,
Worker(WorkerClient),
}
struct ServerSocketInfo {
addr: net::SocketAddr,
token: Token,
handler: Token,
sock: mio::net::TcpListener,
timeout: Option<Instant>,
}
#[derive(Clone)]
pub(crate) struct AcceptNotify(mio::SetReadiness);
impl AcceptNotify {
pub(crate) fn new(ready: mio::SetReadiness) -> Self {
AcceptNotify(ready)
}
pub(crate) fn notify(&self) {
let _ = self.0.set_readiness(mio::Ready::readable());
}
}
impl Default for AcceptNotify {
fn default() -> Self {
AcceptNotify::new(mio::Registration::new2().1)
}
}
pub(crate) struct AcceptLoop {
cmd_reg: Option<mio::Registration>,
cmd_ready: mio::SetReadiness,
notify_reg: Option<mio::Registration>,
notify_ready: mio::SetReadiness,
tx: sync_mpsc::Sender<Command>,
rx: Option<sync_mpsc::Receiver<Command>>,
srv: Option<(
mpsc::UnboundedSender<ServerCommand>,
mpsc::UnboundedReceiver<ServerCommand>,
)>,
}
impl AcceptLoop {
pub fn new() -> AcceptLoop {
let (tx, rx) = sync_mpsc::channel();
let (cmd_reg, cmd_ready) = mio::Registration::new2();
let (notify_reg, notify_ready) = mio::Registration::new2();
AcceptLoop {
tx,
cmd_ready,
cmd_reg: Some(cmd_reg),
notify_ready,
notify_reg: Some(notify_reg),
rx: Some(rx),
srv: Some(mpsc::unbounded()),
}
}
pub fn send(&self, msg: Command) {
let _ = self.tx.send(msg);
let _ = self.cmd_ready.set_readiness(mio::Ready::readable());
}
pub fn get_notify(&self) -> AcceptNotify {
AcceptNotify::new(self.notify_ready.clone())
}
pub(crate) fn start(
&mut self, socks: Vec<Vec<(Token, net::TcpListener)>>,
workers: Vec<WorkerClient>,
) -> mpsc::UnboundedReceiver<ServerCommand> {
let (tx, rx) = self.srv.take().expect("Can not re-use AcceptInfo");
Accept::start(
self.rx.take().expect("Can not re-use AcceptInfo"),
self.cmd_reg.take().expect("Can not re-use AcceptInfo"),
self.notify_reg.take().expect("Can not re-use AcceptInfo"),
socks,
tx,
workers,
);
rx
}
}
struct Accept {
poll: mio::Poll,
rx: sync_mpsc::Receiver<Command>,
sockets: Slab<ServerSocketInfo>,
workers: Vec<WorkerClient>,
srv: mpsc::UnboundedSender<ServerCommand>,
timer: (mio::Registration, mio::SetReadiness),
next: usize,
backpressure: bool,
}
const DELTA: usize = 100;
const CMD: mio::Token = mio::Token(0);
const TIMER: mio::Token = mio::Token(1);
const NOTIFY: mio::Token = mio::Token(2);
/// This function defines errors that are per-connection. Which basically
/// means that if we get this error from `accept()` system call it means
/// next connection might be ready to be accepted.
///
/// All other errors will incur a timeout before next `accept()` is performed.
/// The timeout is useful to handle resource exhaustion errors like ENFILE
/// and EMFILE. Otherwise, could enter into tight loop.
fn connection_error(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::ConnectionAborted
|| e.kind() == io::ErrorKind::ConnectionReset
}
impl Accept {
#![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
pub(crate) fn start(
rx: sync_mpsc::Receiver<Command>, cmd_reg: mio::Registration,
notify_reg: mio::Registration, socks: Vec<Vec<(Token, net::TcpListener)>>,
srv: mpsc::UnboundedSender<ServerCommand>, workers: Vec<WorkerClient>,
) {
let sys = System::current();
// start accept thread
let _ = thread::Builder::new()
.name("actix-web accept loop".to_owned())
.spawn(move || {
System::set_current(sys);
let mut accept = Accept::new(rx, socks, workers, srv);
// Start listening for incoming commands
if let Err(err) = accept.poll.register(
&cmd_reg,
CMD,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err);
}
// Start listening for notify updates
if let Err(err) = accept.poll.register(
&notify_reg,
NOTIFY,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err);
}
accept.poll();
});
}
fn new(
rx: sync_mpsc::Receiver<Command>, socks: Vec<Vec<(Token, net::TcpListener)>>,
workers: Vec<WorkerClient>, srv: mpsc::UnboundedSender<ServerCommand>,
) -> Accept {
// Create a poll instance
let poll = match mio::Poll::new() {
Ok(poll) => poll,
Err(err) => panic!("Can not create mio::Poll: {}", err),
};
// Start accept
let mut sockets = Slab::new();
for (idx, srv_socks) in socks.into_iter().enumerate() {
for (hnd_token, lst) in srv_socks {
let addr = lst.local_addr().unwrap();
let server = mio::net::TcpListener::from_std(lst)
.expect("Can not create mio::net::TcpListener");
let entry = sockets.vacant_entry();
let token = entry.key();
// Start listening for incoming connections
if let Err(err) = poll.register(
&server,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register io: {}", err);
}
entry.insert(ServerSocketInfo {
addr,
token: hnd_token,
handler: Token(idx),
sock: server,
timeout: None,
});
}
}
// Timer
let (tm, tmr) = mio::Registration::new2();
if let Err(err) =
poll.register(&tm, TIMER, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register Registration: {}", err);
}
Accept {
poll,
rx,
sockets,
workers,
srv,
next: 0,
timer: (tm, tmr),
backpressure: false,
}
}
fn poll(&mut self) {
// Create storage for events
let mut events = mio::Events::with_capacity(128);
loop {
if let Err(err) = self.poll.poll(&mut events, None) {
panic!("Poll error: {}", err);
}
for event in events.iter() {
let token = event.token();
match token {
CMD => if !self.process_cmd() {
return;
},
TIMER => self.process_timer(),
NOTIFY => self.backpressure(false),
_ => {
let token = usize::from(token);
if token < DELTA {
continue;
}
self.accept(token - DELTA);
}
}
}
}
}
fn process_timer(&mut self) {
let now = Instant::now();
for (token, info) in self.sockets.iter_mut() {
if let Some(inst) = info.timeout.take() {
if now > inst {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not register server socket {}", err);
} else {
info!("Resume accepting connections on {}", info.addr);
}
} else {
info.timeout = Some(inst);
}
}
}
}
fn process_cmd(&mut self) -> bool {
loop {
match self.rx.try_recv() {
Ok(cmd) => match cmd {
Command::Pause => {
for (_, info) in self.sockets.iter_mut() {
if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err);
} else {
info!("Paused accepting connections on {}", info.addr);
}
}
}
Command::Resume => {
for (token, info) in self.sockets.iter() {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err);
} else {
info!(
"Accepting connections on {} has been resumed",
info.addr
);
}
}
}
Command::Stop => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
Command::Worker(worker) => {
self.backpressure(false);
self.workers.push(worker);
}
},
Err(err) => match err {
sync_mpsc::TryRecvError::Empty => break,
sync_mpsc::TryRecvError::Disconnected => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
},
}
}
true
}
fn backpressure(&mut self, on: bool) {
if self.backpressure {
if !on {
self.backpressure = false;
for (token, info) in self.sockets.iter() {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err);
} else {
info!("Accepting connections on {} has been resumed", info.addr);
}
}
}
} else if on {
self.backpressure = true;
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
}
}
fn accept_one(&mut self, mut msg: Conn<net::TcpStream>) {
if self.backpressure {
while !self.workers.is_empty() {
match self.workers[self.next].send(msg) {
Ok(_) => (),
Err(err) => {
let _ = self.srv.unbounded_send(ServerCommand::WorkerDied(
self.workers[self.next].idx,
));
msg = err.into_inner();
self.workers.swap_remove(self.next);
if self.workers.is_empty() {
error!("No workers");
return;
} else if self.workers.len() <= self.next {
self.next = 0;
}
continue;
}
}
self.next = (self.next + 1) % self.workers.len();
break;
}
} else {
let mut idx = 0;
while idx < self.workers.len() {
idx += 1;
if self.workers[self.next].available() {
match self.workers[self.next].send(msg) {
Ok(_) => {
self.next = (self.next + 1) % self.workers.len();
return;
}
Err(err) => {
let _ = self.srv.unbounded_send(ServerCommand::WorkerDied(
self.workers[self.next].idx,
));
msg = err.into_inner();
self.workers.swap_remove(self.next);
if self.workers.is_empty() {
error!("No workers");
self.backpressure(true);
return;
} else if self.workers.len() <= self.next {
self.next = 0;
}
continue;
}
}
}
self.next = (self.next + 1) % self.workers.len();
}
// enable backpressure
self.backpressure(true);
self.accept_one(msg);
}
}
fn accept(&mut self, token: usize) {
loop {
let msg = if let Some(info) = self.sockets.get_mut(token) {
match info.sock.accept_std() {
Ok((io, addr)) => Conn {
io,
token: info.token,
handler: info.handler,
peer: Some(addr),
},
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return,
Err(ref e) if connection_error(e) => continue,
Err(e) => {
error!("Error accepting connection: {}", e);
if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err);
}
// sleep after error
info.timeout = Some(Instant::now() + Duration::from_millis(500));
let r = self.timer.1.clone();
System::current().arbiter().do_send(Execute::new(
move || -> Result<(), ()> {
Arbiter::spawn(
Delay::new(
Instant::now() + Duration::from_millis(510),
).map_err(|_| ())
.and_then(move |_| {
let _ = r.set_readiness(mio::Ready::readable());
Ok(())
}),
);
Ok(())
},
));
return;
}
}
} else {
return;
};
self.accept_one(msg);
}
}
}

View File

@ -2,16 +2,16 @@ use std::net::{Shutdown, SocketAddr};
use std::rc::Rc;
use std::{io, ptr, time};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use bytes::{Buf, BufMut, BytesMut};
use futures::{Async, Future, Poll};
use tokio_io::{AsyncRead, AsyncWrite};
use super::settings::WorkerSettings;
use super::{h1, h2, utils, HttpHandler, IoStream};
use super::{h1, h2, ConnectionTag, HttpHandler, IoStream};
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
enum HttpProtocol<T: IoStream, H: 'static> {
enum HttpProtocol<T: IoStream, H: HttpHandler + 'static> {
H1(h1::Http1<T, H>),
H2(h2::Http2<T, H>),
Unknown(Rc<WorkerSettings<H>>, Option<SocketAddr>, T, BytesMut),
@ -30,6 +30,7 @@ where
{
proto: Option<HttpProtocol<T, H>>,
node: Option<Node<HttpChannel<T, H>>>,
_tag: ConnectionTag,
}
impl<T, H> HttpChannel<T, H>
@ -38,32 +39,19 @@ where
H: HttpHandler + 'static,
{
pub(crate) fn new(
settings: Rc<WorkerSettings<H>>, mut io: T, peer: Option<SocketAddr>,
http2: bool,
settings: Rc<WorkerSettings<H>>, io: T, peer: Option<SocketAddr>,
) -> HttpChannel<T, H> {
settings.add_channel();
let _ = io.set_nodelay(true);
let _tag = settings.connection();
if http2 {
HttpChannel {
node: None,
proto: Some(HttpProtocol::H2(h2::Http2::new(
settings,
io,
peer,
Bytes::new(),
))),
}
} else {
HttpChannel {
node: None,
proto: Some(HttpProtocol::Unknown(
settings,
peer,
io,
BytesMut::with_capacity(4096),
)),
}
HttpChannel {
_tag,
node: None,
proto: Some(HttpProtocol::Unknown(
settings,
peer,
io,
BytesMut::with_capacity(8192),
)),
}
}
@ -93,25 +81,25 @@ where
let el = self as *mut _;
self.node = Some(Node::new(el));
let _ = match self.proto {
Some(HttpProtocol::H1(ref mut h1)) => self.node
.as_ref()
.map(|n| h1.settings().head().insert(n)),
Some(HttpProtocol::H2(ref mut h2)) => self.node
.as_ref()
.map(|n| h2.settings().head().insert(n)),
Some(HttpProtocol::H1(ref mut h1)) => {
self.node.as_mut().map(|n| h1.settings().head().insert(n))
}
Some(HttpProtocol::H2(ref mut h2)) => {
self.node.as_mut().map(|n| h2.settings().head().insert(n))
}
Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => {
self.node.as_ref().map(|n| settings.head().insert(n))
self.node.as_mut().map(|n| settings.head().insert(n))
}
None => unreachable!(),
};
}
let mut is_eof = false;
let kind = match self.proto {
Some(HttpProtocol::H1(ref mut h1)) => {
let result = h1.poll();
match result {
Ok(Async::Ready(())) | Err(_) => {
h1.settings().remove_channel();
if let Some(n) = self.node.as_mut() {
n.remove()
};
@ -124,7 +112,6 @@ where
let result = h2.poll();
match result {
Ok(Async::Ready(())) | Err(_) => {
h2.settings().remove_channel();
if let Some(n) = self.node.as_mut() {
n.remove()
};
@ -133,23 +120,28 @@ where
}
return result;
}
Some(HttpProtocol::Unknown(
ref mut settings,
_,
ref mut io,
ref mut buf,
)) => {
match utils::read_from_io(io, buf) {
Ok(Async::Ready(0)) | Err(_) => {
debug!("Ignored premature client disconnection");
settings.remove_channel();
if let Some(n) = self.node.as_mut() {
n.remove()
};
return Err(());
Some(HttpProtocol::Unknown(_, _, ref mut io, ref mut buf)) => {
let mut disconnect = false;
match io.read_available(buf) {
Ok(Async::Ready((read_some, stream_closed))) => {
is_eof = stream_closed;
// Only disconnect if no data was read.
if is_eof && !read_some {
disconnect = true;
}
}
Err(_) => {
disconnect = true;
}
_ => (),
}
if disconnect {
debug!("Ignored premature client disconnection");
if let Some(n) = self.node.as_mut() {
n.remove()
};
return Err(());
}
if buf.len() >= 14 {
if buf[..14] == HTTP2_PREFACE[..] {
@ -169,7 +161,7 @@ where
match kind {
ProtocolKind::Http1 => {
self.proto = Some(HttpProtocol::H1(h1::Http1::new(
settings, io, addr, buf,
settings, io, addr, buf, is_eof,
)));
return self.poll();
}
@ -189,8 +181,8 @@ where
}
pub(crate) struct Node<T> {
next: Option<*mut Node<()>>,
prev: Option<*mut Node<()>>,
next: Option<*mut Node<T>>,
prev: Option<*mut Node<T>>,
element: *mut T,
}
@ -203,20 +195,18 @@ impl<T> Node<T> {
}
}
fn insert<I>(&self, next: &Node<I>) {
#[allow(mutable_transmutes)]
fn insert<I>(&mut self, next: &mut Node<I>) {
unsafe {
if let Some(ref next2) = self.next {
let n: &mut Node<()> =
&mut *(next2.as_ref().unwrap() as *const _ as *mut _);
n.prev = Some(next as *const _ as *mut _);
let next: *mut Node<T> = next as *const _ as *mut _;
if let Some(ref mut next2) = self.next {
let n = next2.as_mut().unwrap();
n.prev = Some(next);
}
let slf: &mut Node<T> = &mut *(self as *const _ as *mut _);
self.next = Some(next);
slf.next = Some(next as *const _ as *mut _);
let next: &mut Node<T> = &mut *(next as *const _ as *mut _);
next.prev = Some(slf as *const _ as *mut _);
let next: &mut Node<T> = &mut *next;
next.prev = Some(self as *mut _);
}
}

View File

@ -1,891 +0,0 @@
use std::fmt::Write as FmtWrite;
use std::io::{Read, Write};
use std::str::FromStr;
use std::{cmp, io, mem};
#[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::{BufMut, Bytes, BytesMut};
#[cfg(feature = "flate2")]
use flate2::read::GzDecoder;
#[cfg(feature = "flate2")]
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
#[cfg(feature = "flate2")]
use flate2::Compression;
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING,
CONTENT_LENGTH, TRANSFER_ENCODING};
use http::{HttpTryFrom, Method, Version};
use body::{Binary, Body};
use error::PayloadError;
use header::ContentEncoding;
use httprequest::HttpInnerMessage;
use httpresponse::HttpResponse;
use payload::{PayloadSender, PayloadStatus, PayloadWriter};
use super::shared::SharedBytes;
pub(crate) enum PayloadType {
Sender(PayloadSender),
Encoding(Box<EncodedPayload>),
}
impl PayloadType {
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
// check content-encoding
let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) {
if let Ok(enc) = enc.to_str() {
ContentEncoding::from(enc)
} else {
ContentEncoding::Auto
}
} else {
ContentEncoding::Auto
};
match enc {
ContentEncoding::Auto | ContentEncoding::Identity => {
PayloadType::Sender(sender)
}
_ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))),
}
}
}
impl PayloadWriter for PayloadType {
#[inline]
fn set_error(&mut self, err: PayloadError) {
match *self {
PayloadType::Sender(ref mut sender) => sender.set_error(err),
PayloadType::Encoding(ref mut enc) => enc.set_error(err),
}
}
#[inline]
fn feed_eof(&mut self) {
match *self {
PayloadType::Sender(ref mut sender) => sender.feed_eof(),
PayloadType::Encoding(ref mut enc) => enc.feed_eof(),
}
}
#[inline]
fn feed_data(&mut self, data: Bytes) {
match *self {
PayloadType::Sender(ref mut sender) => sender.feed_data(data),
PayloadType::Encoding(ref mut enc) => enc.feed_data(data),
}
}
#[inline]
fn need_read(&self) -> PayloadStatus {
match *self {
PayloadType::Sender(ref sender) => sender.need_read(),
PayloadType::Encoding(ref enc) => enc.need_read(),
}
}
}
/// Payload wrapper with content decompression support
pub(crate) struct EncodedPayload {
inner: PayloadSender,
error: bool,
payload: PayloadStream,
}
impl EncodedPayload {
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload {
EncodedPayload {
inner,
error: false,
payload: PayloadStream::new(enc),
}
}
}
impl PayloadWriter for EncodedPayload {
fn set_error(&mut self, err: PayloadError) {
self.inner.set_error(err)
}
fn feed_eof(&mut self) {
if !self.error {
match self.payload.feed_eof() {
Err(err) => {
self.error = true;
self.set_error(PayloadError::Io(err));
}
Ok(value) => {
if let Some(b) = value {
self.inner.feed_data(b);
}
self.inner.feed_eof();
}
}
}
}
fn feed_data(&mut self, data: Bytes) {
if self.error {
return;
}
match self.payload.feed_data(data) {
Ok(Some(b)) => self.inner.feed_data(b),
Ok(None) => (),
Err(e) => {
self.error = true;
self.set_error(e.into());
}
}
}
#[inline]
fn need_read(&self) -> PayloadStatus {
self.inner.need_read()
}
}
pub(crate) enum Decoder {
#[cfg(feature = "flate2")]
Deflate(Box<DeflateDecoder<Writer>>),
#[cfg(feature = "flate2")]
Gzip(Option<Box<GzDecoder<Wrapper>>>),
#[cfg(feature = "brotli")]
Br(Box<BrotliDecoder<Writer>>),
Identity,
}
// should go after write::GzDecoder get implemented
#[derive(Debug)]
pub(crate) struct Wrapper {
pub buf: BytesMut,
pub eof: bool,
}
impl io::Read for Wrapper {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = cmp::min(buf.len(), self.buf.len());
buf[..len].copy_from_slice(&self.buf[..len]);
self.buf.split_to(len);
if len == 0 {
if self.eof {
Ok(0)
} else {
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
}
} else {
Ok(len)
}
}
}
impl io::Write for Wrapper {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub(crate) struct Writer {
buf: BytesMut,
}
impl Writer {
fn new() -> Writer {
Writer {
buf: BytesMut::with_capacity(8192),
}
}
fn take(&mut self) -> Bytes {
self.buf.take().freeze()
}
}
impl io::Write for Writer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// Payload stream with decompression support
pub(crate) struct PayloadStream {
decoder: Decoder,
dst: BytesMut,
}
impl PayloadStream {
pub fn new(enc: ContentEncoding) -> PayloadStream {
let dec = match enc {
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => {
Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => Decoder::Gzip(None),
_ => Decoder::Identity,
};
PayloadStream {
decoder: dec,
dst: BytesMut::new(),
}
}
}
impl PayloadStream {
pub fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
match self.decoder {
#[cfg(feature = "brotli")]
Decoder::Br(ref mut decoder) => match decoder.finish() {
Ok(mut writer) => {
let b = writer.take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => {
if let Some(ref mut decoder) = *decoder {
decoder.as_mut().get_mut().eof = true;
self.dst.reserve(8192);
match decoder.read(unsafe { self.dst.bytes_mut() }) {
Ok(n) => {
unsafe { self.dst.advance_mut(n) };
return Ok(Some(self.dst.take().freeze()));
}
Err(e) => return Err(e),
}
} else {
Ok(None)
}
}
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
Decoder::Identity => Ok(None),
}
}
pub fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
match self.decoder {
#[cfg(feature = "brotli")]
Decoder::Br(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => {
if decoder.is_none() {
*decoder = Some(Box::new(GzDecoder::new(Wrapper {
buf: BytesMut::from(data),
eof: false,
})));
} else {
let _ = decoder.as_mut().unwrap().write(&data);
}
loop {
self.dst.reserve(8192);
match decoder
.as_mut()
.as_mut()
.unwrap()
.read(unsafe { self.dst.bytes_mut() })
{
Ok(n) => {
if n != 0 {
unsafe { self.dst.advance_mut(n) };
}
if n == 0 {
return Ok(Some(self.dst.take().freeze()));
}
}
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock
&& !self.dst.is_empty()
{
return Ok(Some(self.dst.take().freeze()));
}
return Err(e);
}
}
}
}
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
Decoder::Identity => Ok(Some(data)),
}
}
}
pub(crate) enum ContentEncoder {
#[cfg(feature = "flate2")]
Deflate(DeflateEncoder<TransferEncoding>),
#[cfg(feature = "flate2")]
Gzip(GzEncoder<TransferEncoding>),
#[cfg(feature = "brotli")]
Br(BrotliEncoder<TransferEncoding>),
Identity(TransferEncoding),
}
impl ContentEncoder {
pub fn empty(bytes: SharedBytes) -> ContentEncoder {
ContentEncoder::Identity(TransferEncoding::eof(bytes))
}
pub fn for_server(
buf: SharedBytes, req: &HttpInnerMessage, resp: &mut HttpResponse,
response_encoding: ContentEncoding,
) -> ContentEncoder {
let version = resp.version().unwrap_or_else(|| req.version);
let is_head = req.method == Method::HEAD;
let mut body = resp.replace_body(Body::Empty);
let has_body = match body {
Body::Empty => false,
Body::Binary(ref bin) => {
!(response_encoding == ContentEncoding::Auto && bin.len() < 96)
}
_ => true,
};
// Enable content encoding only if response does not contain Content-Encoding
// header
let mut encoding = if has_body {
let encoding = match response_encoding {
ContentEncoding::Auto => {
// negotiate content-encoding
if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
if let Ok(enc) = val.to_str() {
AcceptEncoding::parse(enc)
} else {
ContentEncoding::Identity
}
} else {
ContentEncoding::Identity
}
}
encoding => encoding,
};
if encoding.is_compression() {
resp.headers_mut().insert(
CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
}
encoding
} else {
ContentEncoding::Identity
};
let mut transfer = match body {
Body::Empty => {
if req.method != Method::HEAD {
resp.headers_mut().remove(CONTENT_LENGTH);
}
TransferEncoding::length(0, buf)
}
Body::Binary(ref mut bytes) => {
if !(encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto)
{
let tmp = SharedBytes::default();
let transfer = TransferEncoding::eof(tmp.clone());
let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::fast()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
transfer,
Compression::fast(),
)),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
}
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());
encoding = ContentEncoding::Identity;
}
if is_head {
let mut b = BytesMut::new();
let _ = write!(b, "{}", bytes.len());
resp.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(b.freeze()).unwrap(),
);
} else {
// resp.headers_mut().remove(CONTENT_LENGTH);
}
TransferEncoding::eof(buf)
}
Body::Streaming(_) | Body::Actor(_) => {
if resp.upgrade() {
if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2");
}
if encoding != ContentEncoding::Identity {
encoding = ContentEncoding::Identity;
resp.headers_mut().remove(CONTENT_ENCODING);
}
TransferEncoding::eof(buf)
} else {
ContentEncoder::streaming_encoding(buf, version, resp)
}
}
};
//
if is_head {
transfer.kind = TransferEncodingKind::Length(0);
} else {
resp.replace_body(body);
}
match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
transfer,
Compression::fast(),
)),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
}
#[cfg(feature = "brotli")]
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)),
ContentEncoding::Identity | ContentEncoding::Auto => {
ContentEncoder::Identity(transfer)
}
}
}
fn streaming_encoding(
buf: SharedBytes, version: Version, resp: &mut HttpResponse,
) -> TransferEncoding {
match resp.chunked() {
Some(true) => {
// Enable transfer encoding
resp.headers_mut().remove(CONTENT_LENGTH);
if version == Version::HTTP_2 {
resp.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
} else {
resp.headers_mut()
.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
TransferEncoding::chunked(buf)
}
}
Some(false) => TransferEncoding::eof(buf),
None => {
// if Content-Length is specified, then use it as length hint
let (len, chunked) =
if let Some(len) = resp.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 => {
resp.headers_mut().insert(
TRANSFER_ENCODING,
HeaderValue::from_static("chunked"),
);
TransferEncoding::chunked(buf)
}
_ => {
resp.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
}
}
}
}
}
}
}
impl ContentEncoder {
#[inline]
pub fn is_eof(&self) -> bool {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(),
ContentEncoder::Identity(ref encoder) => encoder.is_eof(),
}
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write_eof(&mut self) -> Result<(), io::Error> {
let encoder = mem::replace(
self,
ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())),
);
match encoder {
#[cfg(feature = "brotli")]
ContentEncoder::Br(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(())
}
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(())
}
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(())
}
Err(err) => Err(err),
},
ContentEncoder::Identity(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(())
}
}
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write(&mut self, data: Binary) -> Result<(), io::Error> {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => {
match encoder.write_all(data.as_ref()) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding br encoding: {}", err);
Err(err)
}
}
}
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => {
match encoder.write_all(data.as_ref()) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding gzip encoding: {}", err);
Err(err)
}
}
}
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => {
match encoder.write_all(data.as_ref()) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding deflate encoding: {}", err);
Err(err)
}
}
}
ContentEncoder::Identity(ref mut encoder) => {
encoder.encode(data)?;
Ok(())
}
}
}
}
/// Encoders to handle different Transfer-Encodings.
#[derive(Debug, Clone)]
pub(crate) struct TransferEncoding {
kind: TransferEncodingKind,
buffer: SharedBytes,
}
#[derive(Debug, PartialEq, Clone)]
enum TransferEncodingKind {
/// An Encoder for when Transfer-Encoding includes `chunked`.
Chunked(bool),
/// An Encoder for when Content-Length is set.
///
/// Enforces that the body is not longer than the Content-Length header.
Length(u64),
/// An Encoder for when Content-Length is not known.
///
/// Application decides when to stop writing.
Eof,
}
impl TransferEncoding {
#[inline]
pub fn eof(bytes: SharedBytes) -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Eof,
buffer: bytes,
}
}
#[inline]
pub fn chunked(bytes: SharedBytes) -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Chunked(false),
buffer: bytes,
}
}
#[inline]
pub fn length(len: u64, bytes: SharedBytes) -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Length(len),
buffer: bytes,
}
}
#[inline]
pub fn is_eof(&self) -> bool {
match self.kind {
TransferEncodingKind::Eof => true,
TransferEncodingKind::Chunked(ref eof) => *eof,
TransferEncodingKind::Length(ref remaining) => *remaining == 0,
}
}
/// Encode message. Return `EOF` state of encoder
#[inline]
pub fn encode(&mut self, mut msg: Binary) -> io::Result<bool> {
match self.kind {
TransferEncodingKind::Eof => {
let eof = msg.is_empty();
self.buffer.extend(msg);
Ok(eof)
}
TransferEncodingKind::Chunked(ref mut eof) => {
if *eof {
return Ok(true);
}
if msg.is_empty() {
*eof = true;
self.buffer.extend_from_slice(b"0\r\n\r\n");
} else {
let mut buf = BytesMut::new();
writeln!(&mut buf, "{:X}\r", msg.len())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
self.buffer.reserve(buf.len() + msg.len() + 2);
self.buffer.extend(buf.into());
self.buffer.extend(msg);
self.buffer.extend_from_slice(b"\r\n");
}
Ok(*eof)
}
TransferEncodingKind::Length(ref mut remaining) => {
if *remaining > 0 {
if msg.is_empty() {
return Ok(*remaining == 0);
}
let len = cmp::min(*remaining, msg.len() as u64);
self.buffer
.extend(msg.take().split_to(len as usize).into());
*remaining -= len as u64;
Ok(*remaining == 0)
} else {
Ok(true)
}
}
}
}
/// Encode eof. Return `EOF` state of encoder
#[inline]
pub fn encode_eof(&mut self) {
match self.kind {
TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (),
TransferEncodingKind::Chunked(ref mut eof) => {
if !*eof {
*eof = true;
self.buffer.extend_from_slice(b"0\r\n\r\n");
}
}
}
}
}
impl io::Write for TransferEncoding {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.encode(Binary::from_slice(buf))?;
Ok(buf.len())
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
struct AcceptEncoding {
encoding: ContentEncoding,
quality: f64,
}
impl Eq for AcceptEncoding {}
impl Ord for AcceptEncoding {
fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering {
if self.quality > other.quality {
cmp::Ordering::Less
} else if self.quality < other.quality {
cmp::Ordering::Greater
} else {
cmp::Ordering::Equal
}
}
}
impl PartialOrd for AcceptEncoding {
fn partial_cmp(&self, other: &AcceptEncoding) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for AcceptEncoding {
fn eq(&self, other: &AcceptEncoding) -> bool {
self.quality == other.quality
}
}
impl AcceptEncoding {
fn new(tag: &str) -> Option<AcceptEncoding> {
let parts: Vec<&str> = tag.split(';').collect();
let encoding = match parts.len() {
0 => return None,
_ => ContentEncoding::from(parts[0]),
};
let quality = match parts.len() {
1 => encoding.quality(),
_ => match f64::from_str(parts[1]) {
Ok(q) => q,
Err(_) => 0.0,
},
};
Some(AcceptEncoding {
encoding,
quality,
})
}
/// Parse a raw Accept-Encoding header value into an ordered list.
pub fn parse(raw: &str) -> ContentEncoding {
let mut encodings: Vec<_> = raw.replace(' ', "")
.split(',')
.map(|l| AcceptEncoding::new(l))
.collect();
encodings.sort();
for enc in encodings {
if let Some(enc) = enc {
return enc.encoding;
}
}
ContentEncoding::Identity
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chunked_te() {
let bytes = SharedBytes::default();
let mut enc = TransferEncoding::chunked(bytes.clone());
assert!(!enc.encode(Binary::from(b"test".as_ref()))
.ok()
.unwrap());
assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap());
assert_eq!(
bytes.get_mut().take().freeze(),
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")
);
}
}

33
src/server/error.rs Normal file
View File

@ -0,0 +1,33 @@
use futures::{Async, Poll};
use super::{helpers, HttpHandlerTask, Writer};
use http::{StatusCode, Version};
use Error;
pub(crate) struct ServerError(Version, StatusCode);
impl ServerError {
pub fn err(ver: Version, status: StatusCode) -> Box<HttpHandlerTask> {
Box::new(ServerError(ver, status))
}
}
impl HttpHandlerTask for ServerError {
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
{
let bytes = io.buffer();
// Buffer should have sufficient capacity for status line
// and extra space
bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1);
helpers::write_status_line(self.0, self.1.as_u16(), bytes);
}
// Convert Status Code to Reason.
let reason = self.1.canonical_reason().unwrap_or("");
io.buffer().extend_from_slice(reason.as_bytes());
// No response body.
io.buffer().extend_from_slice(b"\r\ncontent-length: 0\r\n");
// date header
io.set_date();
Ok(Async::Ready(true))
}
}

View File

@ -1,38 +1,35 @@
use std::collections::VecDeque;
use std::io;
use std::net::SocketAddr;
use std::rc::Rc;
use std::time::Duration;
use std::time::{Duration, Instant};
use actix::Arbiter;
use bytes::{BufMut, BytesMut};
use bytes::BytesMut;
use futures::{Async, Future, Poll};
use tokio_core::reactor::Timeout;
use tokio_timer::Delay;
use error::PayloadError;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use error::{Error, PayloadError};
use http::{StatusCode, Version};
use payload::{Payload, PayloadStatus, PayloadWriter};
use pipeline::Pipeline;
use super::encoding::PayloadType;
use super::error::ServerError;
use super::h1decoder::{DecoderError, H1Decoder, Message};
use super::h1writer::H1Writer;
use super::input::PayloadType;
use super::settings::WorkerSettings;
use super::Writer;
use super::{HttpHandler, HttpHandlerTask, IoStream};
const MAX_PIPELINED_MESSAGES: usize = 16;
const LW_BUFFER_SIZE: usize = 4096;
const HW_BUFFER_SIZE: usize = 32_768;
bitflags! {
struct Flags: u8 {
const STARTED = 0b0000_0001;
const ERROR = 0b0000_0010;
const KEEPALIVE = 0b0000_0100;
const SHUTDOWN = 0b0000_1000;
const DISCONNECTED = 0b0001_0000;
pub struct Flags: u8 {
const STARTED = 0b0000_0001;
const ERROR = 0b0000_0010;
const KEEPALIVE = 0b0000_0100;
const SHUTDOWN = 0b0000_1000;
const READ_DISCONNECTED = 0b0001_0000;
const WRITE_DISCONNECTED = 0b0010_0000;
const POLLED = 0b0100_0000;
}
}
@ -44,7 +41,7 @@ bitflags! {
}
}
pub(crate) struct Http1<T: IoStream, H: 'static> {
pub(crate) struct Http1<T: IoStream, H: HttpHandler + 'static> {
flags: Flags,
settings: Rc<WorkerSettings<H>>,
addr: Option<SocketAddr>,
@ -52,12 +49,38 @@ pub(crate) struct Http1<T: IoStream, H: 'static> {
decoder: H1Decoder,
payload: Option<PayloadType>,
buf: BytesMut,
tasks: VecDeque<Entry>,
keepalive_timer: Option<Timeout>,
tasks: VecDeque<Entry<H>>,
keepalive_timer: Option<Delay>,
}
struct Entry {
pipe: Box<HttpHandlerTask>,
enum EntryPipe<H: HttpHandler> {
Task(H::Task),
Error(Box<HttpHandlerTask>),
}
impl<H: HttpHandler> EntryPipe<H> {
fn disconnected(&mut self) {
match *self {
EntryPipe::Task(ref mut task) => task.disconnected(),
EntryPipe::Error(ref mut task) => task.disconnected(),
}
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
match *self {
EntryPipe::Task(ref mut task) => task.poll_io(io),
EntryPipe::Error(ref mut task) => task.poll_io(io),
}
}
fn poll_completed(&mut self) -> Poll<(), Error> {
match *self {
EntryPipe::Task(ref mut task) => task.poll_completed(),
EntryPipe::Error(ref mut task) => task.poll_completed(),
}
}
}
struct Entry<H: HttpHandler> {
pipe: EntryPipe<H>,
flags: EntryFlags,
}
@ -68,12 +91,15 @@ where
{
pub fn new(
settings: Rc<WorkerSettings<H>>, stream: T, addr: Option<SocketAddr>,
buf: BytesMut,
buf: BytesMut, is_eof: bool,
) -> Self {
let bytes = settings.get_shared_bytes();
Http1 {
flags: Flags::KEEPALIVE,
stream: H1Writer::new(stream, bytes, Rc::clone(&settings)),
flags: if is_eof {
Flags::READ_DISCONNECTED
} else {
Flags::KEEPALIVE
},
stream: H1Writer::new(stream, Rc::clone(&settings)),
decoder: H1Decoder::new(),
payload: None,
tasks: VecDeque::new(),
@ -96,6 +122,13 @@ where
#[inline]
fn can_read(&self) -> bool {
if self
.flags
.intersects(Flags::ERROR | Flags::READ_DISCONNECTED)
{
return false;
}
if let Some(ref info) = self.payload {
info.need_read() == PayloadStatus::Read
} else {
@ -103,6 +136,31 @@ where
}
}
fn notify_disconnect(&mut self) {
self.flags.insert(Flags::WRITE_DISCONNECTED);
// notify all tasks
self.stream.disconnected();
for task in &mut self.tasks {
task.pipe.disconnected();
}
}
fn client_disconnect(&mut self) {
// notify all tasks
self.notify_disconnect();
// kill keepalive
self.keepalive_timer.take();
// on parse error, stop reading stream but tasks need to be
// completed
self.flags.insert(Flags::ERROR);
if let Some(mut payload) = self.payload.take() {
payload.set_error(PayloadError::Incomplete);
}
}
#[inline]
pub fn poll(&mut self) -> Poll<(), ()> {
// keep-alive timer
@ -119,6 +177,11 @@ where
// shutdown
if self.flags.contains(Flags::SHUTDOWN) {
if self.flags.intersects(
Flags::ERROR | Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED,
) {
return Ok(Async::Ready(()));
}
match self.stream.poll_completed(true) {
Ok(Async::NotReady) => return Ok(Async::NotReady),
Ok(Async::Ready(_)) => return Ok(Async::Ready(())),
@ -146,30 +209,33 @@ where
}
#[inline]
/// read data from stream
pub fn poll_io(&mut self) {
if !self.flags.contains(Flags::POLLED) {
self.parse();
self.flags.insert(Flags::POLLED);
return;
}
// read io from socket
if !self.flags.intersects(Flags::ERROR)
&& self.tasks.len() < MAX_PIPELINED_MESSAGES && self.can_read()
{
if self.read() {
// notify all tasks
self.stream.disconnected();
for entry in &mut self.tasks {
entry.pipe.disconnected()
if self.can_read() && self.tasks.len() < MAX_PIPELINED_MESSAGES {
match self.stream.get_mut().read_available(&mut self.buf) {
Ok(Async::Ready((read_some, disconnected))) => {
if read_some {
self.parse();
}
if disconnected {
// delay disconnect until all tasks have finished.
self.flags.insert(Flags::READ_DISCONNECTED);
if self.tasks.is_empty() {
self.client_disconnect();
}
}
}
// kill keepalive
self.flags.remove(Flags::KEEPALIVE);
self.keepalive_timer.take();
// on parse error, stop reading stream but tasks need to be
// completed
self.flags.insert(Flags::ERROR);
if let Some(mut payload) = self.payload.take() {
payload.set_error(PayloadError::Incomplete);
Ok(Async::NotReady) => (),
Err(_) => {
self.client_disconnect();
}
} else {
self.parse();
}
}
}
@ -181,19 +247,21 @@ where
let mut io = false;
let mut idx = 0;
while idx < self.tasks.len() {
let item: &mut Entry = unsafe { &mut *(&mut self.tasks[idx] as *mut _) };
// only one task can do io operation in http/1
if !io && !item.flags.contains(EntryFlags::EOF) {
if !io
&& !self.tasks[idx].flags.contains(EntryFlags::EOF)
&& !self.flags.contains(Flags::WRITE_DISCONNECTED)
{
// io is corrupted, send buffer
if item.flags.contains(EntryFlags::ERROR) {
if self.tasks[idx].flags.contains(EntryFlags::ERROR) {
if let Ok(Async::NotReady) = self.stream.poll_completed(true) {
return Ok(Async::NotReady);
}
self.flags.insert(Flags::ERROR);
return Err(());
}
match item.pipe.poll_io(&mut self.stream) {
match self.tasks[idx].pipe.poll_io(&mut self.stream) {
Ok(Async::Ready(ready)) => {
// override keep-alive state
if self.stream.keepalive() {
@ -205,10 +273,11 @@ where
self.stream.reset();
if ready {
item.flags
self.tasks[idx]
.flags
.insert(EntryFlags::EOF | EntryFlags::FINISHED);
} else {
item.flags.insert(EntryFlags::FINISHED);
self.tasks[idx].flags.insert(EntryFlags::EOF);
}
}
// no more IO for this iteration
@ -222,23 +291,23 @@ where
Err(err) => {
// it is not possible to recover from error
// during pipe handling, so just drop connection
error!("Unhandled error: {}", err);
item.flags.insert(EntryFlags::ERROR);
// check stream state, we still can have valid data in buffer
if let Ok(Async::NotReady) = self.stream.poll_completed(true) {
return Ok(Async::NotReady);
}
return Err(());
self.notify_disconnect();
self.tasks[idx].flags.insert(EntryFlags::ERROR);
error!("Unhandled error1: {}", err);
continue;
}
}
} else if !item.flags.contains(EntryFlags::FINISHED) {
match item.pipe.poll() {
} else if !self.tasks[idx].flags.contains(EntryFlags::FINISHED) {
match self.tasks[idx].pipe.poll_completed() {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => item.flags.insert(EntryFlags::FINISHED),
Ok(Async::Ready(_)) => {
self.tasks[idx].flags.insert(EntryFlags::FINISHED)
}
Err(err) => {
item.flags.insert(EntryFlags::ERROR);
self.notify_disconnect();
self.tasks[idx].flags.insert(EntryFlags::ERROR);
error!("Unhandled error: {}", err);
continue;
}
}
}
@ -246,7 +315,6 @@ where
}
// cleanup finished tasks
let max = self.tasks.len() >= MAX_PIPELINED_MESSAGES;
while !self.tasks.is_empty() {
if self.tasks[0]
.flags
@ -257,10 +325,6 @@ where
break;
}
}
// read more message
if max && self.tasks.len() >= MAX_PIPELINED_MESSAGES {
return Ok(Async::Ready(true));
}
// check stream state
if self.flags.contains(Flags::STARTED) {
@ -268,14 +332,24 @@ where
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => {
debug!("Error sending data: {}", err);
self.notify_disconnect();
return Err(());
}
_ => (),
Ok(Async::Ready(_)) => {
// non consumed payload in that case close connection
if self.payload.is_some() && self.tasks.is_empty() {
return Ok(Async::Ready(false));
}
}
}
}
// deal with keep-alive
// deal with keep-alive and steam eof (client-side write shutdown)
if self.tasks.is_empty() {
// handle stream eof
if self.flags.contains(Flags::READ_DISCONNECTED) {
return Ok(Async::Ready(false));
}
// no keep-alive
if self.flags.contains(Flags::ERROR)
|| (!self.flags.contains(Flags::KEEPALIVE)
@ -290,8 +364,7 @@ where
if self.keepalive_timer.is_none() && keep_alive > 0 {
trace!("Start keep-alive timer");
let mut timer =
Timeout::new(Duration::new(keep_alive, 0), Arbiter::handle())
.unwrap();
Delay::new(Instant::now() + Duration::new(keep_alive, 0));
// register timer
let _ = timer.poll();
self.keepalive_timer = Some(timer);
@ -303,41 +376,74 @@ where
pub fn parse(&mut self) {
'outer: loop {
match self.decoder.decode(&mut self.buf, &self.settings) {
Ok(Some(Message::Message { msg, payload })) => {
Ok(Some(Message::Message { mut msg, payload })) => {
self.flags.insert(Flags::STARTED);
if payload {
let (ps, pl) = Payload::new(false);
msg.get_mut().payload = Some(pl);
self.payload =
Some(PayloadType::new(&msg.get_ref().headers, ps));
*msg.inner.payload.borrow_mut() = Some(pl);
self.payload = Some(PayloadType::new(&msg.inner.headers, ps));
}
let mut req = HttpRequest::from_message(msg);
// stream extensions
msg.inner_mut().stream_extensions =
self.stream.get_mut().extensions();
// set remote addr
req.set_peer_addr(self.addr);
msg.inner_mut().addr = self.addr;
// stop keepalive timer
self.keepalive_timer.take();
// search handler for request
for h in self.settings.handlers().iter_mut() {
req = match h.handle(req) {
Ok(pipe) => {
for h in self.settings.handlers().iter() {
msg = match h.handle(msg) {
Ok(mut pipe) => {
if self.tasks.is_empty() {
match pipe.poll_io(&mut self.stream) {
Ok(Async::Ready(ready)) => {
// override keep-alive state
if self.stream.keepalive() {
self.flags.insert(Flags::KEEPALIVE);
} else {
self.flags.remove(Flags::KEEPALIVE);
}
// prepare stream for next response
self.stream.reset();
if !ready {
let item = Entry {
pipe: EntryPipe::Task(pipe),
flags: EntryFlags::EOF,
};
self.tasks.push_back(item);
}
continue 'outer;
}
Ok(Async::NotReady) => {}
Err(err) => {
error!("Unhandled error: {}", err);
self.flags.insert(Flags::ERROR);
return;
}
}
}
self.tasks.push_back(Entry {
pipe,
pipe: EntryPipe::Task(pipe),
flags: EntryFlags::empty(),
});
continue 'outer;
}
Err(req) => req,
Err(msg) => msg,
}
}
// handler is not found
self.tasks.push_back(Entry {
pipe: Pipeline::error(HttpResponse::NotFound()),
pipe: EntryPipe::Error(ServerError::err(
Version::HTTP_11,
StatusCode::NOT_FOUND,
)),
flags: EntryFlags::empty(),
});
}
@ -347,6 +453,7 @@ where
} else {
error!("Internal server error: unexpected payload chunk");
self.flags.insert(Flags::ERROR);
break;
}
}
Ok(Some(Message::Eof)) => {
@ -355,9 +462,17 @@ where
} else {
error!("Internal server error: unexpected eof");
self.flags.insert(Flags::ERROR);
break;
}
}
Ok(None) => break,
Ok(None) => {
if self.flags.contains(Flags::READ_DISCONNECTED)
&& self.tasks.is_empty()
{
self.client_disconnect();
}
break;
}
Err(e) => {
self.flags.insert(Flags::ERROR);
if let Some(mut payload) = self.payload.take() {
@ -367,29 +482,7 @@ where
};
payload.set_error(e);
}
}
}
}
}
#[inline]
fn read(&mut self) -> bool {
loop {
unsafe {
if self.buf.remaining_mut() < LW_BUFFER_SIZE {
self.buf.reserve(HW_BUFFER_SIZE);
}
match self.stream.get_mut().read(self.buf.bytes_mut()) {
Ok(n) => {
if n == 0 {
return true;
} else {
self.buf.advance_mut(n);
}
}
Err(e) => {
return e.kind() != io::ErrorKind::WouldBlock;
}
break;
}
}
}
@ -398,19 +491,31 @@ where
#[cfg(test)]
mod tests {
use bytes::{Bytes, BytesMut};
use std::net::Shutdown;
use std::{cmp, io, time};
use bytes::{Buf, Bytes, BytesMut};
use http::{Method, Version};
use tokio_io::{AsyncRead, AsyncWrite};
use super::*;
use application::HttpApplication;
use httpmessage::HttpMessage;
use server::h1decoder::Message;
use server::helpers::SharedHttpInnerMessage;
use server::settings::WorkerSettings;
use server::KeepAlive;
use server::settings::{ServerSettings, WorkerSettings};
use server::{Connections, KeepAlive, Request};
fn wrk_settings() -> Rc<WorkerSettings<HttpApplication>> {
Rc::new(WorkerSettings::<HttpApplication>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
Connections::default(),
))
}
impl Message {
fn message(self) -> SharedHttpInnerMessage {
fn message(self) -> Request {
match self {
Message::Message { msg, payload: _ } => msg,
_ => panic!("error"),
@ -438,10 +543,9 @@ mod tests {
macro_rules! parse_ready {
($e:expr) => {{
let settings: WorkerSettings<HttpApplication> =
WorkerSettings::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
match H1Decoder::new().decode($e, &settings) {
Ok(Some(msg)) => HttpRequest::from_message(msg.message()),
Ok(Some(msg)) => msg.message(),
Ok(_) => unreachable!("Eof during parsing http request"),
Err(err) => unreachable!("Error during parsing http request: {:?}", err),
}
@ -450,8 +554,7 @@ mod tests {
macro_rules! expect_parse_err {
($e:expr) => {{
let settings: WorkerSettings<HttpApplication> =
WorkerSettings::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
match H1Decoder::new().decode($e, &settings) {
Err(err) => match err {
@ -463,15 +566,111 @@ mod tests {
}};
}
struct Buffer {
buf: Bytes,
err: Option<io::Error>,
}
impl Buffer {
fn new(data: &'static str) -> Buffer {
Buffer {
buf: Bytes::from(data),
err: None,
}
}
}
impl AsyncRead for Buffer {}
impl io::Read for Buffer {
fn read(&mut self, dst: &mut [u8]) -> Result<usize, io::Error> {
if self.buf.is_empty() {
if self.err.is_some() {
Err(self.err.take().unwrap())
} else {
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
}
} else {
let size = cmp::min(self.buf.len(), dst.len());
let b = self.buf.split_to(size);
dst[..size].copy_from_slice(&b);
Ok(size)
}
}
}
impl IoStream for Buffer {
fn shutdown(&mut self, _: Shutdown) -> io::Result<()> {
Ok(())
}
fn set_nodelay(&mut self, _: bool) -> io::Result<()> {
Ok(())
}
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
}
impl io::Write for Buffer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl AsyncWrite for Buffer {
fn shutdown(&mut self) -> Poll<(), io::Error> {
Ok(Async::Ready(()))
}
fn write_buf<B: Buf>(&mut self, _: &mut B) -> Poll<usize, io::Error> {
Ok(Async::NotReady)
}
}
#[test]
fn test_req_parse1() {
let buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n");
let readbuf = BytesMut::new();
let settings = Rc::new(wrk_settings());
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false);
h1.poll_io();
h1.poll_io();
assert_eq!(h1.tasks.len(), 1);
}
#[test]
fn test_req_parse2() {
let buf = Buffer::new("");
let readbuf =
BytesMut::from(Vec::<u8>::from(&b"GET /test HTTP/1.1\r\n\r\n"[..]));
let settings = Rc::new(wrk_settings());
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true);
h1.poll_io();
assert_eq!(h1.tasks.len(), 1);
}
#[test]
fn test_req_parse_err() {
let buf = Buffer::new("GET /test HTTP/1\r\n\r\n");
let readbuf = BytesMut::new();
let settings = Rc::new(wrk_settings());
let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false);
h1.poll_io();
h1.poll_io();
assert!(h1.flags.contains(Flags::ERROR));
}
#[test]
fn test_parse() {
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n");
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let req = HttpRequest::from_message(msg.message());
let req = msg.message();
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
@ -483,7 +682,7 @@ mod tests {
#[test]
fn test_parse_partial() {
let mut buf = BytesMut::from("PUT /test HTTP/1");
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
@ -494,7 +693,7 @@ mod tests {
buf.extend(b".1\r\n\r\n");
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let mut req = HttpRequest::from_message(msg.message());
let mut req = msg.message();
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::PUT);
assert_eq!(req.path(), "/test");
@ -506,12 +705,12 @@ mod tests {
#[test]
fn test_parse_post() {
let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n");
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let mut req = HttpRequest::from_message(msg.message());
let mut req = msg.message();
assert_eq!(req.version(), Version::HTTP_10);
assert_eq!(*req.method(), Method::POST);
assert_eq!(req.path(), "/test2");
@ -524,12 +723,12 @@ mod tests {
fn test_parse_body() {
let mut buf =
BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let mut req = HttpRequest::from_message(msg.message());
let mut req = msg.message();
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
@ -551,12 +750,12 @@ mod tests {
fn test_parse_body_crlf() {
let mut buf =
BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let mut req = HttpRequest::from_message(msg.message());
let mut req = msg.message();
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
@ -577,14 +776,14 @@ mod tests {
#[test]
fn test_parse_partial_eof() {
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n");
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
assert!(reader.decode(&mut buf, &settings).unwrap().is_none());
buf.extend(b"\r\n");
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let req = HttpRequest::from_message(msg.message());
let req = msg.message();
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
@ -596,7 +795,7 @@ mod tests {
#[test]
fn test_headers_split_field() {
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n");
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() }
@ -610,14 +809,11 @@ mod tests {
buf.extend(b"t: value\r\n\r\n");
match reader.decode(&mut buf, &settings) {
Ok(Some(msg)) => {
let req = HttpRequest::from_message(msg.message());
let req = msg.message();
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test");
assert_eq!(
req.headers().get("test").unwrap().as_bytes(),
b"value"
);
assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value");
}
Ok(_) | Err(_) => unreachable!("Error during parsing http request"),
}
@ -630,12 +826,13 @@ mod tests {
Set-Cookie: c1=cookie1\r\n\
Set-Cookie: c2=cookie2\r\n\r\n",
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
let req = HttpRequest::from_message(msg.message());
let req = msg.message();
let val: Vec<_> = req.headers()
let val: Vec<_> = req
.headers()
.get_all("Set-Cookie")
.iter()
.map(|v| v.to_str().unwrap().to_owned())
@ -825,7 +1022,7 @@ mod tests {
#[test]
fn test_http_request_upgrade() {
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
connection: upgrade\r\n\
@ -835,7 +1032,7 @@ mod tests {
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req = HttpRequest::from_message(msg.message());
let req = msg.message();
assert!(!req.keep_alive());
assert!(req.upgrade());
assert_eq!(
@ -891,12 +1088,11 @@ mod tests {
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req = HttpRequest::from_message(msg.message());
let req = msg.message();
assert!(req.chunked().unwrap());
buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n");
@ -918,13 +1114,7 @@ mod tests {
.as_ref(),
b"line"
);
assert!(
reader
.decode(&mut buf, &settings)
.unwrap()
.unwrap()
.eof()
);
assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof());
}
#[test]
@ -933,11 +1123,11 @@ mod tests {
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req = HttpRequest::from_message(msg.message());
let req = msg.message();
assert!(req.chunked().unwrap());
buf.extend(
@ -955,7 +1145,7 @@ mod tests {
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req2 = HttpRequest::from_message(msg.message());
let req2 = msg.message();
assert!(req2.chunked().unwrap());
assert_eq!(*req2.method(), Method::POST);
assert!(req2.chunked().unwrap());
@ -967,12 +1157,12 @@ mod tests {
"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n",
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req = HttpRequest::from_message(msg.message());
let req = msg.message();
assert!(req.chunked().unwrap());
buf.extend(b"4\r\n1111\r\n");
@ -1005,13 +1195,7 @@ mod tests {
assert!(reader.decode(&mut buf, &settings).unwrap().is_none());
buf.extend(b"\r\n");
assert!(
reader
.decode(&mut buf, &settings)
.unwrap()
.unwrap()
.eof()
);
assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof());
}
#[test]
@ -1020,26 +1204,17 @@ mod tests {
&"GET /test HTTP/1.1\r\n\
transfer-encoding: chunked\r\n\r\n"[..],
);
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), KeepAlive::Os);
let settings = wrk_settings();
let mut reader = H1Decoder::new();
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.is_payload());
let req = HttpRequest::from_message(msg.message());
assert!(req.chunked().unwrap());
assert!(msg.message().chunked().unwrap());
buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n")
let chunk = reader
.decode(&mut buf, &settings)
.unwrap()
.unwrap()
.chunk();
let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk();
assert_eq!(chunk, Bytes::from_static(b"data"));
let chunk = reader
.decode(&mut buf, &settings)
.unwrap()
.unwrap()
.chunk();
let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk();
assert_eq!(chunk, Bytes::from_static(b"line"));
let msg = reader.decode(&mut buf, &settings).unwrap().unwrap();
assert!(msg.eof());

View File

@ -4,12 +4,11 @@ use bytes::{Bytes, BytesMut};
use futures::{Async, Poll};
use httparse;
use super::helpers::SharedHttpInnerMessage;
use super::message::{MessageFlags, Request};
use super::settings::WorkerSettings;
use error::ParseError;
use http::header::{HeaderName, HeaderValue};
use http::{header, HttpTryFrom, Method, Uri, Version};
use httprequest::MessageFlags;
use uri::Url;
const MAX_BUFFER_SIZE: usize = 131_072;
@ -20,10 +19,7 @@ pub(crate) struct H1Decoder {
}
pub(crate) enum Message {
Message {
msg: SharedHttpInnerMessage,
payload: bool,
},
Message { msg: Request, payload: bool },
Chunk(Bytes),
Eof,
}
@ -60,22 +56,16 @@ impl H1Decoder {
}
}
match self.parse_message(src, settings)
match self
.parse_message(src, settings)
.map_err(DecoderError::Error)?
{
Async::Ready((msg, decoder)) => {
if let Some(decoder) = decoder {
self.decoder = Some(decoder);
Ok(Some(Message::Message {
msg,
payload: true,
}))
} else {
Ok(Some(Message::Message {
msg,
payload: false,
}))
}
self.decoder = decoder;
Ok(Some(Message::Message {
msg,
payload: self.decoder.is_some(),
}))
}
Async::NotReady => {
if src.len() >= MAX_BUFFER_SIZE {
@ -90,24 +80,24 @@ impl H1Decoder {
fn parse_message<H>(
&self, buf: &mut BytesMut, settings: &WorkerSettings<H>,
) -> Poll<(SharedHttpInnerMessage, Option<EncodingDecoder>), ParseError> {
) -> Poll<(Request, Option<EncodingDecoder>), ParseError> {
// Parse http message
let mut has_upgrade = false;
let mut chunked = false;
let mut content_length = None;
let msg = {
let bytes_ptr = buf.as_ref().as_ptr() as usize;
let mut headers: [httparse::Header; MAX_HEADERS] =
// Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] =
unsafe { mem::uninitialized() };
let (len, method, path, version, headers_len) = {
let b = unsafe {
let b: &[u8] = buf;
&*(b as *const [u8])
};
let mut req = httparse::Request::new(&mut headers);
match req.parse(b)? {
let mut parsed: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
let mut req = httparse::Request::new(&mut parsed);
match req.parse(buf)? {
httparse::Status::Complete(len) => {
let method = Method::from_bytes(req.method.unwrap().as_bytes())
.map_err(|_| ParseError::Method)?;
@ -117,6 +107,8 @@ impl H1Decoder {
} else {
Version::HTTP_10
};
HeaderIndex::record(buf, req.headers, &mut headers);
(len, method, path, version, req.headers.len())
}
httparse::Status::Partial => return Ok(Async::NotReady),
@ -126,29 +118,29 @@ impl H1Decoder {
let slice = buf.split_to(len).freeze();
// convert headers
let msg = settings.get_http_message();
let mut msg = settings.get_request();
{
let msg_mut = msg.get_mut();
msg_mut
let inner = msg.inner_mut();
inner
.flags
.get_mut()
.set(MessageFlags::KEEPALIVE, version != Version::HTTP_10);
for header in headers[..headers_len].iter() {
if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) {
has_upgrade = has_upgrade || name == header::UPGRADE;
let v_start = header.value.as_ptr() as usize - bytes_ptr;
let v_end = v_start + header.value.len();
for idx in headers[..headers_len].iter() {
if let Ok(name) =
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1])
{
// Unsafe: httparse check header value for valid utf-8
let value = unsafe {
HeaderValue::from_shared_unchecked(
slice.slice(v_start, v_end),
slice.slice(idx.value.0, idx.value.1),
)
};
match name {
header::CONTENT_LENGTH => {
if let Ok(s) = value.to_str() {
if let Ok(len) = s.parse::<u64>() {
content_length = Some(len)
content_length = Some(len);
} else {
debug!("illegal Content-Length: {:?}", len);
return Err(ParseError::Header);
@ -174,27 +166,30 @@ impl H1Decoder {
{
true
} else {
version == Version::HTTP_11
&& !(conn.contains("close")
|| conn.contains("upgrade"))
version == Version::HTTP_11 && !(conn
.contains("close")
|| conn.contains("upgrade"))
}
} else {
false
};
msg_mut.flags.set(MessageFlags::KEEPALIVE, ka);
inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka);
}
header::UPGRADE => {
has_upgrade = true;
}
_ => (),
}
msg_mut.headers.append(name, value);
inner.headers.append(name, value);
} else {
return Err(ParseError::Header);
}
}
msg_mut.url = path;
msg_mut.method = method;
msg_mut.version = version;
inner.url = path;
inner.method = method;
inner.version = version;
}
msg
};
@ -206,7 +201,7 @@ impl H1Decoder {
} else if let Some(len) = content_length {
// Content-Length
Some(EncodingDecoder::length(len))
} else if has_upgrade || msg.get_ref().method == Method::CONNECT {
} else if has_upgrade || msg.inner.method == Method::CONNECT {
// upgrade(websocket) or connect
Some(EncodingDecoder::eof())
} else {
@ -217,6 +212,28 @@ impl H1Decoder {
}
}
#[derive(Clone, Copy)]
pub(crate) struct HeaderIndex {
pub(crate) name: (usize, usize),
pub(crate) value: (usize, usize),
}
impl HeaderIndex {
pub(crate) fn record(
bytes: &[u8], headers: &[httparse::Header], indices: &mut [HeaderIndex],
) {
let bytes_ptr = bytes.as_ptr() as usize;
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
let name_start = header.name.as_ptr() as usize - bytes_ptr;
let name_end = name_start + header.name.len();
indices.name = (name_start, name_end);
let value_start = header.value.as_ptr() as usize - bytes_ptr;
let value_end = value_start + header.value.len();
indices.value = (value_start, value_end);
}
}
}
/// Decoders to handle different Transfer-Encodings.
///
/// If a message body does not include a Transfer-Encoding, it *should*

View File

@ -1,21 +1,23 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
// #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use bytes::BufMut;
use futures::{Async, Poll};
use std::io;
use std::io::{self, Write};
use std::rc::Rc;
use bytes::{BufMut, BytesMut};
use futures::{Async, Poll};
use tokio_io::AsyncWrite;
use super::encoding::ContentEncoder;
use super::helpers;
use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::WorkerSettings;
use super::shared::SharedBytes;
use super::Request;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
use body::{Binary, Body};
use header::ContentEncoding;
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use http::{Method, Version};
use httprequest::HttpInnerMessage;
use httpresponse::HttpResponse;
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
@ -32,24 +34,20 @@ bitflags! {
pub(crate) struct H1Writer<T: AsyncWrite, H: 'static> {
flags: Flags,
stream: T,
encoder: ContentEncoder,
written: u64,
headers_size: u32,
buffer: SharedBytes,
buffer: Output,
buffer_capacity: usize,
settings: Rc<WorkerSettings<H>>,
}
impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
pub fn new(
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
) -> H1Writer<T, H> {
pub fn new(stream: T, settings: Rc<WorkerSettings<H>>) -> H1Writer<T, H> {
H1Writer {
flags: Flags::empty(),
encoder: ContentEncoder::empty(buf.clone()),
flags: Flags::KEEPALIVE,
written: 0,
headers_size: 0,
buffer: buf,
buffer: Output::Buffer(settings.get_bytes()),
buffer_capacity: 0,
stream,
settings,
@ -62,23 +60,22 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
pub fn reset(&mut self) {
self.written = 0;
self.flags = Flags::empty();
self.flags = Flags::KEEPALIVE;
}
pub fn disconnected(&mut self) {
self.buffer.take();
self.flags.insert(Flags::DISCONNECTED);
}
pub fn keepalive(&self) -> bool {
self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE)
}
fn write_data(&mut self, data: &[u8]) -> io::Result<usize> {
fn write_data(stream: &mut T, data: &[u8]) -> io::Result<usize> {
let mut written = 0;
while written < data.len() {
match self.stream.write(&data[written..]) {
match stream.write(&data[written..]) {
Ok(0) => {
self.disconnected();
return Err(io::Error::new(io::ErrorKind::WriteZero, ""));
}
Ok(n) => {
@ -94,27 +91,44 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
}
}
impl<T: AsyncWrite, H: 'static> Drop for H1Writer<T, H> {
fn drop(&mut self) {
if let Some(bytes) = self.buffer.take_option() {
self.settings.release_bytes(bytes);
}
}
}
impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
#[inline]
fn written(&self) -> u64 {
self.written
}
#[inline]
fn set_date(&mut self) {
self.settings.set_date(self.buffer.as_mut(), true)
}
#[inline]
fn buffer(&mut self) -> &mut BytesMut {
self.buffer.as_mut()
}
fn start(
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
encoding: ContentEncoding,
&mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding,
) -> io::Result<WriterState> {
// prepare task
self.encoder =
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
let mut info = ResponseInfo::new(req.inner.method == Method::HEAD);
self.buffer.for_server(&mut info, &req.inner, msg, encoding);
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
self.flags.insert(Flags::STARTED | Flags::KEEPALIVE);
self.flags = Flags::STARTED | Flags::KEEPALIVE;
} else {
self.flags.insert(Flags::STARTED);
self.flags = Flags::STARTED;
}
// Connection upgrade
let version = msg.version().unwrap_or_else(|| req.version);
let version = msg.version().unwrap_or_else(|| req.inner.version);
if msg.upgrade() {
self.flags.insert(Flags::UPGRADE);
msg.headers_mut()
@ -134,84 +148,109 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
// render message
{
let mut buffer = self.buffer.get_mut();
// output buffer
let mut buffer = self.buffer.as_mut();
let reason = msg.reason().as_bytes();
let mut is_bin = if let Body::Binary(ref bytes) = body {
if let Body::Binary(ref bytes) = body {
buffer.reserve(
256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()
256 + msg.headers().len() * AVERAGE_HEADER_SIZE
+ bytes.len()
+ reason.len(),
);
true
} else {
buffer.reserve(
256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(),
);
false
};
}
// status line
helpers::write_status_line(version, msg.status().as_u16(), &mut buffer);
SharedBytes::extend_from_slice_(buffer, reason);
buffer.extend_from_slice(reason);
match body {
Body::Empty => if req.method != Method::HEAD {
SharedBytes::put_slice(buffer, b"\r\ncontent-length: 0\r\n");
} else {
SharedBytes::put_slice(buffer, b"\r\n");
},
Body::Binary(ref bytes) => {
helpers::write_content_length(bytes.len(), &mut buffer)
// content length
match info.length {
ResponseLength::Chunked => {
buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n")
}
_ => SharedBytes::put_slice(buffer, b"\r\n"),
ResponseLength::Zero => {
buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n")
}
ResponseLength::Length(len) => {
helpers::write_content_length(len, &mut buffer)
}
ResponseLength::Length64(len) => {
buffer.extend_from_slice(b"\r\ncontent-length: ");
write!(buffer.writer(), "{}", len)?;
buffer.extend_from_slice(b"\r\n");
}
ResponseLength::None => buffer.extend_from_slice(b"\r\n"),
}
if let Some(ce) = info.content_encoding {
buffer.extend_from_slice(b"content-encoding: ");
buffer.extend_from_slice(ce.as_ref());
buffer.extend_from_slice(b"\r\n");
}
// write headers
let mut pos = 0;
let mut has_date = false;
let mut remaining = buffer.remaining_mut();
let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) };
for (key, value) in msg.headers() {
if is_bin && key == CONTENT_LENGTH {
is_bin = false;
continue;
}
has_date = has_date || key == DATE;
let v = value.as_ref();
let k = key.as_str().as_bytes();
let len = k.len() + v.len() + 4;
if len > remaining {
unsafe { buffer.advance_mut(pos) };
pos = 0;
buffer.reserve(len);
remaining = buffer.remaining_mut();
buf = unsafe { &mut *(buffer.bytes_mut() as *mut _) };
}
unsafe {
let mut buf = &mut *(buffer.bytes_mut() as *mut [u8]);
for (key, value) in msg.headers() {
match *key {
TRANSFER_ENCODING => continue,
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
continue;
},
CONTENT_LENGTH => match info.length {
ResponseLength::None => (),
_ => continue,
},
DATE => {
has_date = true;
}
_ => (),
}
buf[pos..pos + k.len()].copy_from_slice(k);
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
let v = value.as_ref();
let k = key.as_str().as_bytes();
let len = k.len() + v.len() + 4;
if len > remaining {
buffer.advance_mut(pos);
pos = 0;
buffer.reserve(len);
remaining = buffer.remaining_mut();
buf = &mut *(buffer.bytes_mut() as *mut _);
}
buf[pos..pos + k.len()].copy_from_slice(k);
pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v);
pos += v.len();
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2;
remaining -= len;
}
buffer.advance_mut(pos);
}
unsafe { buffer.advance_mut(pos) };
// optimized date header, set_date writes \r\n
if !has_date {
self.settings.set_date(&mut buffer);
self.settings.set_date(&mut buffer, true);
} else {
// msg eof
SharedBytes::extend_from_slice_(buffer, b"\r\n");
buffer.extend_from_slice(b"\r\n");
}
self.headers_size = buffer.len() as u32;
}
if let Body::Binary(bytes) = body {
self.written = bytes.len() as u64;
self.encoder.write(bytes)?;
self.buffer.write(bytes.as_ref())?;
} else {
// capacity, makes sense only for streaming or actor
self.buffer_capacity = msg.write_buffer_capacity();
@ -221,7 +260,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
Ok(WriterState::Done)
}
fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
self.written += payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) {
if self.flags.contains(Flags::STARTED) {
@ -229,21 +268,27 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
if self.flags.contains(Flags::UPGRADE) {
if self.buffer.is_empty() {
let pl: &[u8] = payload.as_ref();
let n = self.write_data(pl)?;
let n = match Self::write_data(&mut self.stream, pl) {
Err(err) => {
self.disconnected();
return Err(err);
}
Ok(val) => val,
};
if n < pl.len() {
self.buffer.extend_from_slice(&pl[n..]);
self.buffer.write(&pl[n..])?;
return Ok(WriterState::Done);
}
} else {
self.buffer.extend(payload);
self.buffer.write(payload.as_ref())?;
}
} else {
// TODO: add warning, write after EOF
self.encoder.write(payload)?;
self.buffer.write(payload.as_ref())?;
}
} else {
// could be response to EXCEPT header
self.buffer.extend_from_slice(payload.as_ref())
self.buffer.write(payload.as_ref())?;
}
}
@ -255,9 +300,7 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
}
fn write_eof(&mut self) -> io::Result<WriterState> {
self.encoder.write_eof()?;
if !self.encoder.is_eof() {
if !self.buffer.write_eof()? {
Err(io::Error::new(
io::ErrorKind::Other,
"Last payload item, but eof is not reached",
@ -271,19 +314,32 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
#[inline]
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
if self.flags.contains(Flags::DISCONNECTED) {
return Err(io::Error::new(io::ErrorKind::Other, "disconnected"));
}
if !self.buffer.is_empty() {
let buf: &[u8] =
unsafe { &mut *(self.buffer.as_ref() as *const _ as *mut _) };
let written = self.write_data(buf)?;
let written = {
match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) {
Err(err) => {
self.disconnected();
return Err(err);
}
Ok(val) => val,
}
};
let _ = self.buffer.split_to(written);
if self.buffer.len() > self.buffer_capacity {
if shutdown && !self.buffer.is_empty()
|| (self.buffer.len() > self.buffer_capacity)
{
return Ok(Async::NotReady);
}
}
if shutdown {
self.stream.poll_flush()?;
self.stream.shutdown()
} else {
Ok(Async::Ready(()))
Ok(self.stream.poll_flush()?)
}
}
}

View File

@ -1,33 +1,29 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use std::collections::VecDeque;
use std::io::{Read, Write};
use std::net::SocketAddr;
use std::rc::Rc;
use std::time::Duration;
use std::time::{Duration, Instant};
use std::{cmp, io, mem};
use actix::Arbiter;
use bytes::{Buf, Bytes};
use futures::{Async, Future, Poll, Stream};
use http2::server::{self, Connection, Handshake, SendResponse};
use http2::{Reason, RecvStream};
use modhttp::request::Parts;
use tokio_core::reactor::Timeout;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_timer::Delay;
use error::PayloadError;
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use error::{Error, PayloadError};
use extensions::Extensions;
use http::{StatusCode, Version};
use payload::{Payload, PayloadStatus, PayloadWriter};
use pipeline::Pipeline;
use uri::Url;
use super::encoding::PayloadType;
use super::error::ServerError;
use super::h2writer::H2Writer;
use super::input::PayloadType;
use super::settings::WorkerSettings;
use super::{HttpHandler, HttpHandlerTask, Writer};
use super::{HttpHandler, HttpHandlerTask, IoStream, Writer};
bitflags! {
struct Flags: u8 {
@ -39,14 +35,15 @@ bitflags! {
pub(crate) struct Http2<T, H>
where
T: AsyncRead + AsyncWrite + 'static,
H: 'static,
H: HttpHandler + 'static,
{
flags: Flags,
settings: Rc<WorkerSettings<H>>,
addr: Option<SocketAddr>,
state: State<IoWrapper<T>>,
tasks: VecDeque<Entry<H>>,
keepalive_timer: Option<Timeout>,
keepalive_timer: Option<Delay>,
extensions: Option<Rc<Extensions>>,
}
enum State<T: AsyncRead + AsyncWrite> {
@ -57,22 +54,24 @@ enum State<T: AsyncRead + AsyncWrite> {
impl<T, H> Http2<T, H>
where
T: AsyncRead + AsyncWrite + 'static,
T: IoStream + 'static,
H: HttpHandler + 'static,
{
pub fn new(
settings: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes,
) -> Self {
let extensions = io.extensions();
Http2 {
flags: Flags::empty(),
tasks: VecDeque::new(),
state: State::Handshake(server::handshake(IoWrapper {
unread: Some(buf),
unread: if buf.is_empty() { None } else { Some(buf) },
inner: io,
})),
keepalive_timer: None,
addr,
settings,
extensions,
}
}
@ -103,54 +102,69 @@ where
loop {
let mut not_ready = true;
let disconnected = self.flags.contains(Flags::DISCONNECTED);
// check in-flight connections
for item in &mut self.tasks {
// read payload
item.poll_payload();
if !disconnected {
item.poll_payload();
}
if !item.flags.contains(EntryFlags::EOF) {
let retry = item.payload.need_read() == PayloadStatus::Read;
loop {
match item.task.poll_io(&mut item.stream) {
Ok(Async::Ready(ready)) => {
if ready {
if disconnected {
item.flags.insert(EntryFlags::EOF);
} else {
let retry = item.payload.need_read() == PayloadStatus::Read;
loop {
match item.task.poll_io(&mut item.stream) {
Ok(Async::Ready(ready)) => {
if ready {
item.flags.insert(
EntryFlags::EOF | EntryFlags::FINISHED,
);
} else {
item.flags.insert(EntryFlags::EOF);
}
not_ready = false;
}
Ok(Async::NotReady) => {
if item.payload.need_read()
== PayloadStatus::Read
&& !retry
{
continue;
}
}
Err(err) => {
error!("Unhandled error: {}", err);
item.flags.insert(
EntryFlags::EOF | EntryFlags::FINISHED,
EntryFlags::EOF
| EntryFlags::ERROR
| EntryFlags::WRITE_DONE,
);
} else {
item.flags.insert(EntryFlags::EOF);
}
not_ready = false;
}
Ok(Async::NotReady) => {
if item.payload.need_read() == PayloadStatus::Read
&& !retry
{
continue;
item.stream.reset(Reason::INTERNAL_ERROR);
}
}
Err(err) => {
error!("Unhandled error: {}", err);
item.flags.insert(
EntryFlags::EOF | EntryFlags::ERROR
| EntryFlags::WRITE_DONE,
);
item.stream.reset(Reason::INTERNAL_ERROR);
}
break;
}
break;
}
} else if !item.flags.contains(EntryFlags::FINISHED) {
match item.task.poll() {
}
if item.flags.contains(EntryFlags::EOF)
&& !item.flags.contains(EntryFlags::FINISHED)
{
match item.task.poll_completed() {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => {
not_ready = false;
item.flags.insert(EntryFlags::FINISHED);
item.flags.insert(
EntryFlags::FINISHED | EntryFlags::WRITE_DONE,
);
}
Err(err) => {
item.flags.insert(
EntryFlags::ERROR | EntryFlags::WRITE_DONE
EntryFlags::ERROR
| EntryFlags::WRITE_DONE
| EntryFlags::FINISHED,
);
error!("Unhandled error: {}", err);
@ -158,14 +172,17 @@ where
}
}
if !item.flags.contains(EntryFlags::WRITE_DONE) {
if item.flags.contains(EntryFlags::FINISHED)
&& !item.flags.contains(EntryFlags::WRITE_DONE)
&& !disconnected
{
match item.stream.poll_completed(false) {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => {
not_ready = false;
item.flags.insert(EntryFlags::WRITE_DONE);
}
Err(_err) => {
Err(_) => {
item.flags.insert(EntryFlags::ERROR);
}
}
@ -174,7 +191,7 @@ where
// cleanup finished tasks
while !self.tasks.is_empty() {
if self.tasks[0].flags.contains(EntryFlags::EOF)
if self.tasks[0].flags.contains(EntryFlags::FINISHED)
&& self.tasks[0].flags.contains(EntryFlags::WRITE_DONE)
|| self.tasks[0].flags.contains(EntryFlags::ERROR)
{
@ -207,6 +224,7 @@ where
resp,
self.addr,
&self.settings,
self.extensions.clone(),
));
}
Ok(Async::NotReady) => {
@ -216,9 +234,10 @@ where
let keep_alive = self.settings.keep_alive();
if keep_alive > 0 && self.keepalive_timer.is_none() {
trace!("Start keep-alive timer");
let mut timeout = Timeout::new(
Duration::new(keep_alive, 0),
Arbiter::handle()).unwrap();
let mut timeout = Delay::new(
Instant::now()
+ Duration::new(keep_alive, 0),
);
// register timeout
let _ = timeout.poll();
self.keepalive_timer = Some(timeout);
@ -248,7 +267,8 @@ where
if not_ready {
if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED)
{
return conn.poll_close()
return conn
.poll_close()
.map_err(|e| error!("Error during connection close: {}", e));
} else {
return Ok(Async::NotReady);
@ -285,18 +305,45 @@ bitflags! {
}
}
struct Entry<H: 'static> {
task: Box<HttpHandlerTask>,
enum EntryPipe<H: HttpHandler> {
Task(H::Task),
Error(Box<HttpHandlerTask>),
}
impl<H: HttpHandler> EntryPipe<H> {
fn disconnected(&mut self) {
match *self {
EntryPipe::Task(ref mut task) => task.disconnected(),
EntryPipe::Error(ref mut task) => task.disconnected(),
}
}
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
match *self {
EntryPipe::Task(ref mut task) => task.poll_io(io),
EntryPipe::Error(ref mut task) => task.poll_io(io),
}
}
fn poll_completed(&mut self) -> Poll<(), Error> {
match *self {
EntryPipe::Task(ref mut task) => task.poll_completed(),
EntryPipe::Error(ref mut task) => task.poll_completed(),
}
}
}
struct Entry<H: HttpHandler + 'static> {
task: EntryPipe<H>,
payload: PayloadType,
recv: RecvStream,
stream: H2Writer<H>,
flags: EntryFlags,
}
impl<H: 'static> Entry<H> {
impl<H: HttpHandler + 'static> Entry<H> {
fn new(
parts: Parts, recv: RecvStream, resp: SendResponse<Bytes>,
addr: Option<SocketAddr>, settings: &Rc<WorkerSettings<H>>,
extensions: Option<Rc<Extensions>>,
) -> Entry<H>
where
H: HttpHandler + 'static,
@ -304,39 +351,42 @@ impl<H: 'static> Entry<H> {
// Payload and Content-Encoding
let (psender, payload) = Payload::new(false);
let msg = settings.get_http_message();
msg.get_mut().url = Url::new(parts.uri);
msg.get_mut().method = parts.method;
msg.get_mut().version = parts.version;
msg.get_mut().headers = parts.headers;
msg.get_mut().payload = Some(payload);
msg.get_mut().addr = addr;
let mut req = HttpRequest::from_message(msg);
let mut msg = settings.get_request();
{
let inner = msg.inner_mut();
inner.url = Url::new(parts.uri);
inner.method = parts.method;
inner.version = parts.version;
inner.headers = parts.headers;
inner.stream_extensions = extensions;
*inner.payload.borrow_mut() = Some(payload);
inner.addr = addr;
}
// Payload sender
let psender = PayloadType::new(req.headers(), psender);
let psender = PayloadType::new(msg.headers(), psender);
// start request processing
let mut task = None;
for h in settings.handlers().iter_mut() {
req = match h.handle(req) {
for h in settings.handlers().iter() {
msg = match h.handle(msg) {
Ok(t) => {
task = Some(t);
break;
}
Err(req) => req,
Err(msg) => msg,
}
}
Entry {
task: task.unwrap_or_else(|| Pipeline::error(HttpResponse::NotFound())),
task: task.map(EntryPipe::Task).unwrap_or_else(|| {
EntryPipe::Error(ServerError::err(
Version::HTTP_2,
StatusCode::NOT_FOUND,
))
}),
payload: psender,
stream: H2Writer::new(
resp,
settings.get_shared_bytes(),
Rc::clone(settings),
),
stream: H2Writer::new(resp, Rc::clone(settings)),
flags: EntryFlags::empty(),
recv,
}

View File

@ -8,17 +8,18 @@ use modhttp::Response;
use std::rc::Rc;
use std::{cmp, io};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
use http::{HttpTryFrom, Version};
use http::{HttpTryFrom, Method, Version};
use super::encoding::ContentEncoder;
use super::helpers;
use super::message::Request;
use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::WorkerSettings;
use super::shared::SharedBytes;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
use body::{Binary, Body};
use header::ContentEncoding;
use httprequest::HttpInnerMessage;
use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use httpresponse::HttpResponse;
const CHUNK_SIZE: usize = 16_384;
@ -35,27 +36,25 @@ bitflags! {
pub(crate) struct H2Writer<H: 'static> {
respond: SendResponse<Bytes>,
stream: Option<SendStream<Bytes>>,
encoder: ContentEncoder,
flags: Flags,
written: u64,
buffer: SharedBytes,
buffer: Output,
buffer_capacity: usize,
settings: Rc<WorkerSettings<H>>,
}
impl<H: 'static> H2Writer<H> {
pub fn new(
respond: SendResponse<Bytes>, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
respond: SendResponse<Bytes>, settings: Rc<WorkerSettings<H>>,
) -> H2Writer<H> {
H2Writer {
respond,
settings,
stream: None,
encoder: ContentEncoder::empty(buf.clone()),
flags: Flags::empty(),
written: 0,
buffer: buf,
buffer: Output::Buffer(settings.get_bytes()),
buffer_capacity: 0,
respond,
settings,
}
}
@ -66,78 +65,115 @@ impl<H: 'static> H2Writer<H> {
}
}
impl<H: 'static> Drop for H2Writer<H> {
fn drop(&mut self) {
self.settings.release_bytes(self.buffer.take());
}
}
impl<H: 'static> Writer for H2Writer<H> {
fn written(&self) -> u64 {
self.written
}
#[inline]
fn set_date(&mut self) {
self.settings.set_date(self.buffer.as_mut(), true)
}
#[inline]
fn buffer(&mut self) -> &mut BytesMut {
self.buffer.as_mut()
}
fn start(
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
encoding: ContentEncoding,
&mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding,
) -> io::Result<WriterState> {
// prepare response
self.flags.insert(Flags::STARTED);
self.encoder =
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
if let Body::Empty = *msg.body() {
self.flags.insert(Flags::EOF);
}
// http2 specific
msg.headers_mut().remove(CONNECTION);
msg.headers_mut().remove(TRANSFER_ENCODING);
// using helpers::date is quite a lot faster
if !msg.headers().contains_key(DATE) {
let mut bytes = BytesMut::with_capacity(29);
self.settings.set_date_simple(&mut bytes);
msg.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
}
let body = msg.replace_body(Body::Empty);
match body {
Body::Binary(ref bytes) => {
let mut val = BytesMut::new();
helpers::convert_usize(bytes.len(), &mut val);
let l = val.len();
msg.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
);
}
Body::Empty => {
msg.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
}
_ => (),
}
let mut info = ResponseInfo::new(req.inner.method == Method::HEAD);
self.buffer.for_server(&mut info, &req.inner, msg, encoding);
let mut has_date = false;
let mut resp = Response::new(());
*resp.status_mut() = msg.status();
*resp.version_mut() = Version::HTTP_2;
for (key, value) in msg.headers().iter() {
resp.headers_mut().insert(key, value.clone());
match *key {
// http2 specific
CONNECTION | TRANSFER_ENCODING => continue,
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
continue;
},
CONTENT_LENGTH => match info.length {
ResponseLength::None => (),
_ => continue,
},
DATE => has_date = true,
_ => (),
}
resp.headers_mut().append(key, value.clone());
}
match self.respond
// set date header
if !has_date {
let mut bytes = BytesMut::with_capacity(29);
self.settings.set_date(&mut bytes, false);
resp.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
}
// content length
match info.length {
ResponseLength::Zero => {
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
self.flags.insert(Flags::EOF);
}
ResponseLength::Length(len) => {
let mut val = BytesMut::new();
helpers::convert_usize(len, &mut val);
let l = val.len();
resp.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
);
}
ResponseLength::Length64(len) => {
let l = format!("{}", len);
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap());
}
_ => (),
}
if let Some(ce) = info.content_encoding {
resp.headers_mut()
.insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap());
}
trace!("Response: {:?}", resp);
match self
.respond
.send_response(resp, self.flags.contains(Flags::EOF))
{
Ok(stream) => self.stream = Some(stream),
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")),
}
trace!("Response: {:?}", msg);
let body = msg.replace_body(Body::Empty);
if let Body::Binary(bytes) = body {
self.flags.insert(Flags::EOF);
self.written = bytes.len() as u64;
self.encoder.write(bytes)?;
if let Some(ref mut stream) = self.stream {
self.flags.insert(Flags::RESERVED);
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
if bytes.is_empty() {
Ok(WriterState::Done)
} else {
self.flags.insert(Flags::EOF);
self.buffer.write(bytes.as_ref())?;
if let Some(ref mut stream) = self.stream {
self.flags.insert(Flags::RESERVED);
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
}
Ok(WriterState::Pause)
}
Ok(WriterState::Pause)
} else {
msg.replace_body(body);
self.buffer_capacity = msg.write_buffer_capacity();
@ -145,16 +181,14 @@ impl<H: 'static> Writer for H2Writer<H> {
}
}
fn write(&mut self, payload: Binary) -> io::Result<WriterState> {
self.written = payload.len() as u64;
fn write(&mut self, payload: &Binary) -> io::Result<WriterState> {
if !self.flags.contains(Flags::DISCONNECTED) {
if self.flags.contains(Flags::STARTED) {
// TODO: add warning, write after EOF
self.encoder.write(payload)?;
self.buffer.write(payload.as_ref())?;
} else {
// might be response for EXCEPT
self.buffer.extend_from_slice(payload.as_ref())
error!("Not supported");
}
}
@ -166,10 +200,8 @@ impl<H: 'static> Writer for H2Writer<H> {
}
fn write_eof(&mut self) -> io::Result<WriterState> {
self.encoder.write_eof()?;
self.flags.insert(Flags::EOF);
if !self.encoder.is_eof() {
if !self.buffer.write_eof()? {
Err(io::Error::new(
io::ErrorKind::Other,
"Last payload item, but eof is not reached",
@ -210,14 +242,18 @@ impl<H: 'static> Writer for H2Writer<H> {
let cap = cmp::min(self.buffer.len(), CHUNK_SIZE);
stream.reserve_capacity(cap);
} else {
if eof {
stream.reserve_capacity(0);
continue;
}
self.flags.remove(Flags::RESERVED);
return Ok(Async::NotReady);
return Ok(Async::Ready(()));
}
}
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
}
}
}
Ok(Async::NotReady)
Ok(Async::Ready(()))
}
}

View File

@ -1,102 +1,17 @@
use bytes::{BufMut, BytesMut};
use http::Version;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use std::{mem, ptr, slice};
use httprequest::HttpInnerMessage;
/// Internal use only! unsafe
pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpInnerMessage>>>);
impl SharedMessagePool {
pub fn new() -> SharedMessagePool {
SharedMessagePool(RefCell::new(VecDeque::with_capacity(128)))
}
#[inline]
pub fn get(&self) -> Rc<HttpInnerMessage> {
if let Some(msg) = self.0.borrow_mut().pop_front() {
msg
} else {
Rc::new(HttpInnerMessage::default())
}
}
#[inline]
pub fn release(&self, mut msg: Rc<HttpInnerMessage>) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
Rc::get_mut(&mut msg).unwrap().reset();
v.push_front(msg);
}
}
}
pub(crate) struct SharedHttpInnerMessage(
Option<Rc<HttpInnerMessage>>,
Option<Rc<SharedMessagePool>>,
);
impl Drop for SharedHttpInnerMessage {
fn drop(&mut self) {
if let Some(ref pool) = self.1 {
if let Some(msg) = self.0.take() {
if Rc::strong_count(&msg) == 1 {
pool.release(msg);
}
}
}
}
}
impl Clone for SharedHttpInnerMessage {
fn clone(&self) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(self.0.clone(), self.1.clone())
}
}
impl Default for SharedHttpInnerMessage {
fn default() -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(Rc::new(HttpInnerMessage::default())), None)
}
}
impl SharedHttpInnerMessage {
pub fn from_message(msg: HttpInnerMessage) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(Rc::new(msg)), None)
}
pub fn new(
msg: Rc<HttpInnerMessage>, pool: Rc<SharedMessagePool>,
) -> SharedHttpInnerMessage {
SharedHttpInnerMessage(Some(msg), Some(pool))
}
#[inline(always)]
#[allow(mutable_transmutes)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
pub fn get_mut(&self) -> &mut HttpInnerMessage {
let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref();
unsafe { &mut *(r as *const _ as *mut _) }
}
#[inline(always)]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
pub fn get_ref(&self) -> &HttpInnerMessage {
self.0.as_ref().unwrap()
}
}
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
pub(crate) const STATUS_LINE_BUF_SIZE: usize = 13;
pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
let mut buf: [u8; 13] = [
let mut buf: [u8; STATUS_LINE_BUF_SIZE] = [
b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ',
];
match version {
@ -143,7 +58,7 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM
}
/// NOTE: bytes object has to contain enough space
pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
if n < 10 {
let mut buf: [u8; 21] = [
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e',
@ -192,14 +107,14 @@ pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
}
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
let mut curr: isize = 39;
let mut buf: [u8; 41] = unsafe { mem::uninitialized() };
buf[39] = b'\r';
buf[40] = b'\n';
let buf_ptr = buf.as_mut_ptr();
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
unsafe {
let mut curr: isize = 39;
let mut buf: [u8; 41] = mem::uninitialized();
buf[39] = b'\r';
buf[40] = b'\n';
let buf_ptr = buf.as_mut_ptr();
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
// eagerly decode 4 characters at a time
while n >= 10_000 {
let rem = (n % 10_000) as isize;
@ -232,9 +147,7 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
curr -= 2;
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
}
unsafe {
bytes.extend_from_slice(slice::from_raw_parts(
buf_ptr.offset(curr),
41 - curr as usize,
@ -251,63 +164,33 @@ mod tests {
let mut bytes = BytesMut::new();
bytes.reserve(50);
write_content_length(0, &mut bytes);
assert_eq!(
bytes.take().freeze(),
b"\r\ncontent-length: 0\r\n"[..]
);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
bytes.reserve(50);
write_content_length(9, &mut bytes);
assert_eq!(
bytes.take().freeze(),
b"\r\ncontent-length: 9\r\n"[..]
);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
bytes.reserve(50);
write_content_length(10, &mut bytes);
assert_eq!(
bytes.take().freeze(),
b"\r\ncontent-length: 10\r\n"[..]
);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
bytes.reserve(50);
write_content_length(99, &mut bytes);
assert_eq!(
bytes.take().freeze(),
b"\r\ncontent-length: 99\r\n"[..]
);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
bytes.reserve(50);
write_content_length(100, &mut bytes);
assert_eq!(
bytes.take().freeze(),
b"\r\ncontent-length: 100\r\n"[..]
);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
bytes.reserve(50);
write_content_length(101, &mut bytes);
assert_eq!(
bytes.take().freeze(),
b"\r\ncontent-length: 101\r\n"[..]
);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
bytes.reserve(50);
write_content_length(998, &mut bytes);
assert_eq!(
bytes.take().freeze(),
b"\r\ncontent-length: 998\r\n"[..]
);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
bytes.reserve(50);
write_content_length(1000, &mut bytes);
assert_eq!(
bytes.take().freeze(),
b"\r\ncontent-length: 1000\r\n"[..]
);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
bytes.reserve(50);
write_content_length(1001, &mut bytes);
assert_eq!(
bytes.take().freeze(),
b"\r\ncontent-length: 1001\r\n"[..]
);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
bytes.reserve(50);
write_content_length(5909, &mut bytes);
assert_eq!(
bytes.take().freeze(),
b"\r\ncontent-length: 5909\r\n"[..]
);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
}
}

812
src/server/http.rs Normal file
View File

@ -0,0 +1,812 @@
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::Arc;
use std::{io, mem, net, time};
use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, System};
use futures::{Future, Stream};
use net2::{TcpBuilder, TcpStreamExt};
use num_cpus;
use tokio::executor::current_thread;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tcp::TcpStream;
#[cfg(feature = "tls")]
use native_tls::TlsAcceptor;
#[cfg(feature = "alpn")]
use openssl::ssl::SslAcceptorBuilder;
#[cfg(feature = "rust-tls")]
use rustls::ServerConfig;
use super::channel::{HttpChannel, WrapperStream};
use super::server::{Connections, Server, Service, ServiceHandler};
use super::settings::{ServerSettings, WorkerSettings};
use super::worker::{Conn, Socket};
use super::{
AcceptorService, HttpHandler, IntoAsyncIo, IntoHttpHandler, IoStream, KeepAlive,
Token,
};
/// An HTTP Server
///
/// By default it serves HTTP2 when HTTPs is enabled,
/// in order to change it, use `ServerFlags` that can be provided
/// to acceptor service.
pub struct HttpServer<H>
where
H: IntoHttpHandler + 'static,
{
factory: Arc<Fn() -> Vec<H> + Send + Sync>,
host: Option<String>,
keep_alive: KeepAlive,
backlog: i32,
threads: usize,
exit: bool,
shutdown_timeout: u16,
no_http2: bool,
no_signals: bool,
maxconn: usize,
maxconnrate: usize,
sockets: Vec<Socket>,
handlers: Vec<Box<IoStreamHandler<H::Handler, net::TcpStream>>>,
}
impl<H> HttpServer<H>
where
H: IntoHttpHandler + 'static,
{
/// Create new http server with application factory
pub fn new<F, U>(factory: F) -> Self
where
F: Fn() -> U + Sync + Send + 'static,
U: IntoIterator<Item = H> + 'static,
{
let f = move || (factory)().into_iter().collect();
HttpServer {
threads: num_cpus::get(),
factory: Arc::new(f),
host: None,
backlog: 2048,
keep_alive: KeepAlive::Os,
shutdown_timeout: 30,
exit: true,
no_http2: false,
no_signals: false,
maxconn: 102_400,
maxconnrate: 256,
// settings: None,
sockets: Vec::new(),
handlers: Vec::new(),
}
}
/// Set number of workers to start.
///
/// By default http server uses number of available logical cpu as threads
/// count.
pub fn workers(mut self, num: usize) -> Self {
self.threads = num;
self
}
/// Set the maximum number of pending connections.
///
/// This refers to the number of clients that can be waiting to be served.
/// Exceeding this number results in the client getting an error when
/// attempting to connect. It should only affect servers under significant
/// load.
///
/// Generally set in the 64-2048 range. Default value is 2048.
///
/// This method should be called before `bind()` method call.
pub fn backlog(mut self, num: i32) -> Self {
self.backlog = num;
self
}
/// Sets the maximum per-worker number of concurrent connections.
///
/// All socket listeners will stop accepting connections when this limit is reached
/// for each worker.
///
/// By default max connections is set to a 100k.
pub fn maxconn(mut self, num: usize) -> Self {
self.maxconn = num;
self
}
/// Sets the maximum per-worker concurrent connection establish process.
///
/// All listeners will stop accepting connections when this limit is reached. It
/// can be used to limit the global SSL CPU usage.
///
/// By default max connections is set to a 256.
pub fn maxconnrate(mut self, num: usize) -> Self {
self.maxconnrate = num;
self
}
/// Set server keep-alive setting.
///
/// By default keep alive is set to a `Os`.
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
self.keep_alive = val.into();
self
}
/// Set server host name.
///
/// Host name is used by application router aa a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
pub fn server_hostname(mut self, val: String) -> Self {
self.host = Some(val);
self
}
/// Stop actix system.
///
/// `SystemExit` message stops currently running system.
pub fn system_exit(mut self) -> Self {
self.exit = true;
self
}
/// Disable signal handling
pub fn disable_signals(mut self) -> Self {
self.no_signals = true;
self
}
/// Timeout for graceful workers shutdown.
///
/// After receiving a stop signal, workers have this much time to finish
/// serving requests. Workers still alive after the timeout are force
/// dropped.
///
/// By default shutdown timeout sets to 30 seconds.
pub fn shutdown_timeout(mut self, sec: u16) -> Self {
self.shutdown_timeout = sec;
self
}
/// Disable `HTTP/2` support
// #[doc(hidden)]
// #[deprecated(
// since = "0.7.4",
// note = "please use acceptor service with proper ServerFlags parama"
// )]
pub fn no_http2(mut self) -> Self {
self.no_http2 = true;
self
}
/// Get addresses of bound sockets.
pub fn addrs(&self) -> Vec<net::SocketAddr> {
self.sockets.iter().map(|s| s.addr).collect()
}
/// Get addresses of bound sockets and the scheme for it.
///
/// This is useful when the server is bound from different sources
/// with some sockets listening on http and some listening on https
/// and the user should be presented with an enumeration of which
/// socket requires which protocol.
pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> {
self.handlers
.iter()
.map(|s| (s.addr(), s.scheme()))
.collect()
}
/// Use listener for accepting incoming connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen(mut self, lst: net::TcpListener) -> Self {
let token = Token(self.handlers.len());
let addr = lst.local_addr().unwrap();
self.handlers
.push(Box::new(SimpleHandler::new(lst.local_addr().unwrap())));
self.sockets.push(Socket { lst, addr, token });
self
}
#[doc(hidden)]
/// Use listener for accepting incoming connection requests
pub fn listen_with<A>(mut self, lst: net::TcpListener, acceptor: A) -> Self
where
A: AcceptorService<TcpStream> + Send + 'static,
{
let token = Token(self.handlers.len());
let addr = lst.local_addr().unwrap();
self.handlers.push(Box::new(StreamHandler::new(
lst.local_addr().unwrap(),
acceptor,
)));
self.sockets.push(Socket { lst, addr, token });
self
}
#[cfg(feature = "tls")]
/// Use listener for accepting incoming tls connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self {
use super::NativeTlsAcceptor;
self.listen_with(lst, NativeTlsAcceptor::new(acceptor))
}
#[cfg(feature = "alpn")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_ssl(
self, lst: net::TcpListener, builder: SslAcceptorBuilder,
) -> io::Result<Self> {
use super::{OpensslAcceptor, ServerFlags};
// alpn support
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
Ok(self.listen_with(lst, OpensslAcceptor::with_flags(builder, flags)?))
}
#[cfg(feature = "rust-tls")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_rustls(self, lst: net::TcpListener, builder: ServerConfig) -> Self {
use super::{RustlsAcceptor, ServerFlags};
// alpn support
let flags = if self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
self.listen_with(lst, RustlsAcceptor::with_flags(builder, flags))
}
/// The socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
for lst in sockets {
let token = Token(self.handlers.len());
let addr = lst.local_addr().unwrap();
self.handlers
.push(Box::new(SimpleHandler::new(lst.local_addr().unwrap())));
self.sockets.push(Socket { lst, addr, token })
}
Ok(self)
}
/// Start listening for incoming connections with supplied acceptor.
#[doc(hidden)]
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
pub fn bind_with<S, A>(mut self, addr: S, acceptor: A) -> io::Result<Self>
where
S: net::ToSocketAddrs,
A: AcceptorService<TcpStream> + Send + 'static,
{
let sockets = self.bind2(addr)?;
for lst in sockets {
let token = Token(self.handlers.len());
let addr = lst.local_addr().unwrap();
self.handlers.push(Box::new(StreamHandler::new(
lst.local_addr().unwrap(),
acceptor.clone(),
)));
self.sockets.push(Socket { lst, addr, token })
}
Ok(self)
}
fn bind2<S: net::ToSocketAddrs>(
&self, addr: S,
) -> io::Result<Vec<net::TcpListener>> {
let mut err = None;
let mut succ = false;
let mut sockets = Vec::new();
for addr in addr.to_socket_addrs()? {
match create_tcp_listener(addr, self.backlog) {
Ok(lst) => {
succ = true;
sockets.push(lst);
}
Err(e) => err = Some(e),
}
}
if !succ {
if let Some(e) = err.take() {
Err(e)
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Can not bind to address.",
))
}
} else {
Ok(sockets)
}
}
#[cfg(feature = "tls")]
/// The ssl socket address to bind
///
/// To bind multiple addresses this method can be called multiple times.
pub fn bind_tls<S: net::ToSocketAddrs>(
self, addr: S, acceptor: TlsAcceptor,
) -> io::Result<Self> {
use super::NativeTlsAcceptor;
self.bind_with(addr, NativeTlsAcceptor::new(acceptor))
}
#[cfg(feature = "alpn")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_ssl<S>(self, addr: S, builder: SslAcceptorBuilder) -> io::Result<Self>
where
S: net::ToSocketAddrs,
{
use super::{OpensslAcceptor, ServerFlags};
// alpn support
let flags = if !self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
self.bind_with(addr, OpensslAcceptor::with_flags(builder, flags)?)
}
#[cfg(feature = "rust-tls")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_rustls<S: net::ToSocketAddrs>(
self, addr: S, builder: ServerConfig,
) -> io::Result<Self> {
use super::{RustlsAcceptor, ServerFlags};
// alpn support
let flags = if !self.no_http2 {
ServerFlags::HTTP1
} else {
ServerFlags::HTTP1 | ServerFlags::HTTP2
};
self.bind_with(addr, RustlsAcceptor::with_flags(builder, flags))
}
}
impl<H: IntoHttpHandler> Into<(Box<Service>, Vec<(Token, net::TcpListener)>)>
for HttpServer<H>
{
fn into(mut self) -> (Box<Service>, Vec<(Token, net::TcpListener)>) {
let sockets: Vec<_> = mem::replace(&mut self.sockets, Vec::new())
.into_iter()
.map(|item| (item.token, item.lst))
.collect();
(
Box::new(HttpService {
factory: self.factory,
host: self.host,
keep_alive: self.keep_alive,
handlers: self.handlers,
}),
sockets,
)
}
}
struct HttpService<H: IntoHttpHandler> {
factory: Arc<Fn() -> Vec<H> + Send + Sync>,
host: Option<String>,
keep_alive: KeepAlive,
handlers: Vec<Box<IoStreamHandler<H::Handler, net::TcpStream>>>,
}
impl<H: IntoHttpHandler + 'static> Service for HttpService<H> {
fn clone(&self) -> Box<Service> {
Box::new(HttpService {
factory: self.factory.clone(),
host: self.host.clone(),
keep_alive: self.keep_alive,
handlers: self.handlers.iter().map(|v| v.clone()).collect(),
})
}
fn create(&self, conns: Connections) -> Box<ServiceHandler> {
let addr = self.handlers[0].addr();
let s = ServerSettings::new(Some(addr), &self.host, false);
let apps: Vec<_> = (*self.factory)()
.into_iter()
.map(|h| h.into_handler())
.collect();
let handlers = self.handlers.iter().map(|h| h.clone()).collect();
Box::new(HttpServiceHandler::new(
apps,
handlers,
self.keep_alive,
s,
conns,
))
}
}
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming connections.
///
/// This method starts number of http workers in separate threads.
/// For each address this method starts separate thread which does
/// `accept()` in a loop.
///
/// This methods panics if no socket addresses get bound.
///
/// This method requires to run within properly configured `Actix` system.
///
/// ```rust
/// extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system
///
/// server::new(|| App::new().resource("/", |r| r.h(|_: &_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .expect("Can not bind to 127.0.0.1:0")
/// .start();
/// # actix::System::current().stop();
/// sys.run(); // <- Run actix system, this method starts all async processes
/// }
/// ```
pub fn start(self) -> Addr<Server> {
let mut srv = Server::new()
.workers(self.threads)
.maxconn(self.maxconn)
.maxconnrate(self.maxconnrate)
.shutdown_timeout(self.shutdown_timeout);
srv = if self.exit { srv.system_exit() } else { srv };
srv = if self.no_signals {
srv.disable_signals()
} else {
srv
};
srv.service(self).start()
}
/// Spawn new thread and start listening for incoming connections.
///
/// This method spawns new thread and starts new actix system. Other than
/// that it is similar to `start()` method. This method blocks.
///
/// This methods panics if no socket addresses get bound.
///
/// ```rust,ignore
/// # extern crate futures;
/// # extern crate actix_web;
/// # use futures::Future;
/// use actix_web::*;
///
/// fn main() {
/// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .expect("Can not bind to 127.0.0.1:0")
/// .run();
/// }
/// ```
pub fn run(self) {
let sys = System::new("http-server");
self.start();
sys.run();
}
}
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming connections from a stream.
///
/// This method uses only one thread for handling incoming connections.
pub fn start_incoming<T, S>(self, stream: S, secure: bool)
where
S: Stream<Item = T, Error = io::Error> + Send + 'static,
T: AsyncRead + AsyncWrite + Send + 'static,
{
// set server settings
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
let srv_settings = ServerSettings::new(Some(addr), &self.host, secure);
let apps: Vec<_> = (*self.factory)()
.into_iter()
.map(|h| h.into_handler())
.collect();
let settings = WorkerSettings::create(
apps,
self.keep_alive,
srv_settings,
Connections::default(),
);
// start server
HttpIncoming::create(move |ctx| {
ctx.add_message_stream(stream.map_err(|_| ()).map(move |t| Conn {
io: WrapperStream::new(t),
handler: Token::new(0),
token: Token::new(0),
peer: None,
}));
HttpIncoming { settings }
});
}
}
struct HttpIncoming<H: HttpHandler> {
settings: Rc<WorkerSettings<H>>,
}
impl<H> Actor for HttpIncoming<H>
where
H: HttpHandler,
{
type Context = Context<Self>;
}
impl<T, H> Handler<Conn<T>> for HttpIncoming<H>
where
T: IoStream,
H: HttpHandler,
{
type Result = ();
fn handle(&mut self, msg: Conn<T>, _: &mut Context<Self>) -> Self::Result {
Arbiter::spawn(HttpChannel::new(
Rc::clone(&self.settings),
msg.io,
msg.peer,
));
}
}
struct HttpServiceHandler<H>
where
H: HttpHandler + 'static,
{
settings: Rc<WorkerSettings<H>>,
handlers: Vec<Box<IoStreamHandler<H, net::TcpStream>>>,
tcp_ka: Option<time::Duration>,
}
impl<H: HttpHandler + 'static> HttpServiceHandler<H> {
fn new(
apps: Vec<H>, handlers: Vec<Box<IoStreamHandler<H, net::TcpStream>>>,
keep_alive: KeepAlive, settings: ServerSettings, conns: Connections,
) -> HttpServiceHandler<H> {
let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive {
Some(time::Duration::new(val as u64, 0))
} else {
None
};
let settings = WorkerSettings::create(apps, keep_alive, settings, conns);
HttpServiceHandler {
handlers,
tcp_ka,
settings,
}
}
}
impl<H> ServiceHandler for HttpServiceHandler<H>
where
H: HttpHandler + 'static,
{
fn handle(
&mut self, token: Token, io: net::TcpStream, peer: Option<net::SocketAddr>,
) {
if self.tcp_ka.is_some() && io.set_keepalive(self.tcp_ka).is_err() {
error!("Can not set socket keep-alive option");
}
self.handlers[token.0].handle(Rc::clone(&self.settings), io, peer);
}
fn shutdown(&self, force: bool) {
if force {
self.settings.head().traverse::<TcpStream, H>();
}
}
}
struct SimpleHandler<Io> {
addr: net::SocketAddr,
io: PhantomData<Io>,
}
impl<Io: IntoAsyncIo> Clone for SimpleHandler<Io> {
fn clone(&self) -> Self {
SimpleHandler {
addr: self.addr,
io: PhantomData,
}
}
}
impl<Io: IntoAsyncIo> SimpleHandler<Io> {
fn new(addr: net::SocketAddr) -> Self {
SimpleHandler {
addr,
io: PhantomData,
}
}
}
impl<H, Io> IoStreamHandler<H, Io> for SimpleHandler<Io>
where
H: HttpHandler,
Io: IntoAsyncIo + Send + 'static,
Io::Io: IoStream,
{
fn addr(&self) -> net::SocketAddr {
self.addr
}
fn clone(&self) -> Box<IoStreamHandler<H, Io>> {
Box::new(Clone::clone(self))
}
fn scheme(&self) -> &'static str {
"http"
}
fn handle(&self, h: Rc<WorkerSettings<H>>, io: Io, peer: Option<net::SocketAddr>) {
let mut io = match io.into_async_io() {
Ok(io) => io,
Err(err) => {
trace!("Failed to create async io: {}", err);
return;
}
};
let _ = io.set_nodelay(true);
current_thread::spawn(HttpChannel::new(h, io, peer));
}
}
struct StreamHandler<A, Io> {
acceptor: A,
addr: net::SocketAddr,
io: PhantomData<Io>,
}
impl<Io: IntoAsyncIo, A: AcceptorService<Io::Io>> StreamHandler<A, Io> {
fn new(addr: net::SocketAddr, acceptor: A) -> Self {
StreamHandler {
addr,
acceptor,
io: PhantomData,
}
}
}
impl<Io: IntoAsyncIo, A: AcceptorService<Io::Io>> Clone for StreamHandler<A, Io> {
fn clone(&self) -> Self {
StreamHandler {
addr: self.addr,
acceptor: self.acceptor.clone(),
io: PhantomData,
}
}
}
impl<H, Io, A> IoStreamHandler<H, Io> for StreamHandler<A, Io>
where
H: HttpHandler,
Io: IntoAsyncIo + Send + 'static,
Io::Io: IoStream,
A: AcceptorService<Io::Io> + Send + 'static,
{
fn addr(&self) -> net::SocketAddr {
self.addr
}
fn clone(&self) -> Box<IoStreamHandler<H, Io>> {
Box::new(Clone::clone(self))
}
fn scheme(&self) -> &'static str {
self.acceptor.scheme()
}
fn handle(&self, h: Rc<WorkerSettings<H>>, io: Io, peer: Option<net::SocketAddr>) {
let mut io = match io.into_async_io() {
Ok(io) => io,
Err(err) => {
trace!("Failed to create async io: {}", err);
return;
}
};
let _ = io.set_nodelay(true);
let rate = h.connection_rate();
current_thread::spawn(self.acceptor.accept(io).then(move |res| {
drop(rate);
match res {
Ok(io) => current_thread::spawn(HttpChannel::new(h, io, peer)),
Err(err) => trace!("Can not establish connection: {}", err),
}
Ok(())
}))
}
}
impl<H, Io: 'static> IoStreamHandler<H, Io> for Box<IoStreamHandler<H, Io>>
where
H: HttpHandler,
Io: IntoAsyncIo,
{
fn addr(&self) -> net::SocketAddr {
self.as_ref().addr()
}
fn clone(&self) -> Box<IoStreamHandler<H, Io>> {
self.as_ref().clone()
}
fn scheme(&self) -> &'static str {
self.as_ref().scheme()
}
fn handle(&self, h: Rc<WorkerSettings<H>>, io: Io, peer: Option<net::SocketAddr>) {
self.as_ref().handle(h, io, peer)
}
}
trait IoStreamHandler<H, Io>: Send
where
H: HttpHandler,
{
fn clone(&self) -> Box<IoStreamHandler<H, Io>>;
fn addr(&self) -> net::SocketAddr;
fn scheme(&self) -> &'static str;
fn handle(&self, h: Rc<WorkerSettings<H>>, io: Io, peer: Option<net::SocketAddr>);
}
fn create_tcp_listener(
addr: net::SocketAddr, backlog: i32,
) -> io::Result<net::TcpListener> {
let builder = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
};
builder.reuse_address(true)?;
builder.bind(addr)?;
Ok(builder.listen(backlog)?)
}

288
src/server/input.rs Normal file
View File

@ -0,0 +1,288 @@
use std::io::{self, Write};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliDecoder;
use bytes::{Bytes, BytesMut};
use error::PayloadError;
#[cfg(feature = "flate2")]
use flate2::write::{GzDecoder, ZlibDecoder};
use header::ContentEncoding;
use http::header::{HeaderMap, CONTENT_ENCODING};
use payload::{PayloadSender, PayloadStatus, PayloadWriter};
pub(crate) enum PayloadType {
Sender(PayloadSender),
Encoding(Box<EncodedPayload>),
}
impl PayloadType {
#[cfg(any(feature = "brotli", feature = "flate2"))]
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
// check content-encoding
let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) {
if let Ok(enc) = enc.to_str() {
ContentEncoding::from(enc)
} else {
ContentEncoding::Auto
}
} else {
ContentEncoding::Auto
};
match enc {
ContentEncoding::Auto | ContentEncoding::Identity => {
PayloadType::Sender(sender)
}
_ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))),
}
}
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
PayloadType::Sender(sender)
}
}
impl PayloadWriter for PayloadType {
#[inline]
fn set_error(&mut self, err: PayloadError) {
match *self {
PayloadType::Sender(ref mut sender) => sender.set_error(err),
PayloadType::Encoding(ref mut enc) => enc.set_error(err),
}
}
#[inline]
fn feed_eof(&mut self) {
match *self {
PayloadType::Sender(ref mut sender) => sender.feed_eof(),
PayloadType::Encoding(ref mut enc) => enc.feed_eof(),
}
}
#[inline]
fn feed_data(&mut self, data: Bytes) {
match *self {
PayloadType::Sender(ref mut sender) => sender.feed_data(data),
PayloadType::Encoding(ref mut enc) => enc.feed_data(data),
}
}
#[inline]
fn need_read(&self) -> PayloadStatus {
match *self {
PayloadType::Sender(ref sender) => sender.need_read(),
PayloadType::Encoding(ref enc) => enc.need_read(),
}
}
}
/// Payload wrapper with content decompression support
pub(crate) struct EncodedPayload {
inner: PayloadSender,
error: bool,
payload: PayloadStream,
}
impl EncodedPayload {
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload {
EncodedPayload {
inner,
error: false,
payload: PayloadStream::new(enc),
}
}
}
impl PayloadWriter for EncodedPayload {
fn set_error(&mut self, err: PayloadError) {
self.inner.set_error(err)
}
fn feed_eof(&mut self) {
if !self.error {
match self.payload.feed_eof() {
Err(err) => {
self.error = true;
self.set_error(PayloadError::Io(err));
}
Ok(value) => {
if let Some(b) = value {
self.inner.feed_data(b);
}
self.inner.feed_eof();
}
}
}
}
fn feed_data(&mut self, data: Bytes) {
if self.error {
return;
}
match self.payload.feed_data(data) {
Ok(Some(b)) => self.inner.feed_data(b),
Ok(None) => (),
Err(e) => {
self.error = true;
self.set_error(e.into());
}
}
}
#[inline]
fn need_read(&self) -> PayloadStatus {
self.inner.need_read()
}
}
pub(crate) enum Decoder {
#[cfg(feature = "flate2")]
Deflate(Box<ZlibDecoder<Writer>>),
#[cfg(feature = "flate2")]
Gzip(Box<GzDecoder<Writer>>),
#[cfg(feature = "brotli")]
Br(Box<BrotliDecoder<Writer>>),
Identity,
}
pub(crate) struct Writer {
buf: BytesMut,
}
impl Writer {
fn new() -> Writer {
Writer {
buf: BytesMut::with_capacity(8192),
}
}
fn take(&mut self) -> Bytes {
self.buf.take().freeze()
}
}
impl io::Write for Writer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// Payload stream with decompression support
pub(crate) struct PayloadStream {
decoder: Decoder,
}
impl PayloadStream {
pub fn new(enc: ContentEncoding) -> PayloadStream {
let decoder = match enc {
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => {
Decoder::Deflate(Box::new(ZlibDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => {
Decoder::Gzip(Box::new(GzDecoder::new(Writer::new())))
}
_ => Decoder::Identity,
};
PayloadStream { decoder }
}
}
impl PayloadStream {
pub fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
match self.decoder {
#[cfg(feature = "brotli")]
Decoder::Br(ref mut decoder) => match decoder.finish() {
Ok(mut writer) => {
let b = writer.take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => match decoder.try_finish() {
Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
Decoder::Identity => Ok(None),
}
}
pub fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
match self.decoder {
#[cfg(feature = "brotli")]
Decoder::Br(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
Decoder::Identity => Ok(Some(data)),
}
}
}

263
src/server/message.rs Normal file
View File

@ -0,0 +1,263 @@
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::collections::VecDeque;
use std::net::SocketAddr;
use std::rc::Rc;
use http::{header, HeaderMap, Method, Uri, Version};
use extensions::Extensions;
use httpmessage::HttpMessage;
use info::ConnectionInfo;
use payload::Payload;
use server::ServerSettings;
use uri::Url as InnerUrl;
bitflags! {
pub(crate) struct MessageFlags: u8 {
const KEEPALIVE = 0b0000_0001;
const CONN_INFO = 0b0000_0010;
}
}
/// Request's context
pub struct Request {
pub(crate) inner: Rc<InnerRequest>,
}
pub(crate) struct InnerRequest {
pub(crate) version: Version,
pub(crate) method: Method,
pub(crate) url: InnerUrl,
pub(crate) flags: Cell<MessageFlags>,
pub(crate) headers: HeaderMap,
pub(crate) extensions: RefCell<Extensions>,
pub(crate) addr: Option<SocketAddr>,
pub(crate) info: RefCell<ConnectionInfo>,
pub(crate) payload: RefCell<Option<Payload>>,
pub(crate) settings: ServerSettings,
pub(crate) stream_extensions: Option<Rc<Extensions>>,
pool: &'static RequestPool,
}
impl InnerRequest {
#[inline]
/// Reset request instance
pub fn reset(&mut self) {
self.headers.clear();
self.extensions.borrow_mut().clear();
self.flags.set(MessageFlags::empty());
*self.payload.borrow_mut() = None;
}
}
impl HttpMessage for Request {
type Stream = Payload;
fn headers(&self) -> &HeaderMap {
&self.inner.headers
}
#[inline]
fn payload(&self) -> Payload {
if let Some(payload) = self.inner.payload.borrow_mut().take() {
payload
} else {
Payload::empty()
}
}
}
impl Request {
/// Create new RequestContext instance
pub(crate) fn new(pool: &'static RequestPool, settings: ServerSettings) -> Request {
Request {
inner: Rc::new(InnerRequest {
pool,
settings,
method: Method::GET,
url: InnerUrl::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
flags: Cell::new(MessageFlags::empty()),
addr: None,
info: RefCell::new(ConnectionInfo::default()),
payload: RefCell::new(None),
extensions: RefCell::new(Extensions::new()),
stream_extensions: None,
}),
}
}
#[inline]
pub(crate) fn inner(&self) -> &InnerRequest {
self.inner.as_ref()
}
#[inline]
pub(crate) fn inner_mut(&mut self) -> &mut InnerRequest {
Rc::get_mut(&mut self.inner).expect("Multiple copies exist")
}
#[inline]
pub(crate) fn url(&self) -> &InnerUrl {
&self.inner().url
}
/// Read the Request Uri.
#[inline]
pub fn uri(&self) -> &Uri {
self.inner().url.uri()
}
/// Read the Request method.
#[inline]
pub fn method(&self) -> &Method {
&self.inner().method
}
/// Read the Request Version.
#[inline]
pub fn version(&self) -> Version {
self.inner().version
}
/// The target path of this Request.
#[inline]
pub fn path(&self) -> &str {
self.inner().url.path()
}
#[inline]
/// Returns Request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.inner().headers
}
#[inline]
/// Returns mutable Request's headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.inner_mut().headers
}
/// Peer socket address
///
/// 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.
///
/// To get client connection information `connection_info()` method should
/// be used.
pub fn peer_addr(&self) -> Option<SocketAddr> {
self.inner().addr
}
/// Checks if a connection should be kept alive.
#[inline]
pub fn keep_alive(&self) -> bool {
self.inner().flags.get().contains(MessageFlags::KEEPALIVE)
}
/// Request extensions
#[inline]
pub fn extensions(&self) -> Ref<Extensions> {
self.inner().extensions.borrow()
}
/// Mutable reference to a the request's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> {
self.inner().extensions.borrow_mut()
}
/// Check if request requires connection upgrade
pub fn upgrade(&self) -> bool {
if let Some(conn) = self.inner().headers.get(header::CONNECTION) {
if let Ok(s) = conn.to_str() {
return s.to_lowercase().contains("upgrade");
}
}
self.inner().method == Method::CONNECT
}
/// Get *ConnectionInfo* for the correct request.
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
if self.inner().flags.get().contains(MessageFlags::CONN_INFO) {
self.inner().info.borrow()
} else {
let mut flags = self.inner().flags.get();
flags.insert(MessageFlags::CONN_INFO);
self.inner().flags.set(flags);
self.inner().info.borrow_mut().update(self);
self.inner().info.borrow()
}
}
/// Io stream extensions
#[inline]
pub fn stream_extensions(&self) -> Option<&Extensions> {
self.inner().stream_extensions.as_ref().map(|e| e.as_ref())
}
/// Server settings
#[inline]
pub fn server_settings(&self) -> &ServerSettings {
&self.inner().settings
}
pub(crate) fn clone(&self) -> Self {
Request {
inner: self.inner.clone(),
}
}
pub(crate) fn release(self) {
let mut inner = self.inner;
if let Some(r) = Rc::get_mut(&mut inner) {
r.reset();
} else {
return;
}
inner.pool.release(inner);
}
}
pub(crate) struct RequestPool(
RefCell<VecDeque<Rc<InnerRequest>>>,
RefCell<ServerSettings>,
);
thread_local!(static POOL: &'static RequestPool = RequestPool::create());
impl RequestPool {
fn create() -> &'static RequestPool {
let pool = RequestPool(
RefCell::new(VecDeque::with_capacity(128)),
RefCell::new(ServerSettings::default()),
);
Box::leak(Box::new(pool))
}
pub fn pool(settings: ServerSettings) -> &'static RequestPool {
POOL.with(|p| {
*p.1.borrow_mut() = settings;
*p
})
}
#[inline]
pub fn get(pool: &'static RequestPool) -> Request {
if let Some(msg) = pool.0.borrow_mut().pop_front() {
Request { inner: msg }
} else {
Request::new(pool, pool.1.borrow().clone())
}
}
#[inline]
/// Release request instance
pub fn release(&self, msg: Rc<InnerRequest>) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
v.push_front(msg);
}
}
}

View File

@ -1,50 +1,178 @@
//! Http server
//! Http server module
//!
//! The module contains everything necessary to setup
//! HTTP server.
//!
//! In order to start HTTP server, first you need to create and configure it
//! using factory that can be supplied to [new](fn.new.html).
//!
//! ## Factory
//!
//! Factory is a function that returns Application, describing how
//! to serve incoming HTTP requests.
//!
//! As the server uses worker pool, the factory function is restricted to trait bounds
//! `Sync + Send + 'static` so that each worker would be able to accept Application
//! without a need for synchronization.
//!
//! If you wish to share part of state among all workers you should
//! wrap it in `Arc` and potentially synchronization primitive like
//! [RwLock](https://doc.rust-lang.org/std/sync/struct.RwLock.html)
//! If the wrapped type is not thread safe.
//!
//! Note though that locking is not advisable for asynchronous programming
//! and you should minimize all locks in your request handlers
//!
//! ## HTTPS Support
//!
//! Actix-web provides support for major crates that provides TLS.
//! Each TLS implementation is provided with [AcceptorService](trait.AcceptorService.html)
//! that describes how HTTP Server accepts connections.
//!
//! For `bind` and `listen` there are corresponding `bind_with` and `listen_with` that accepts
//! these services.
//!
//! By default, acceptor would work with both HTTP2 and HTTP1 protocols.
//! But it can be controlled using [ServerFlags](struct.ServerFlags.html) which
//! can be supplied when creating `AcceptorService`.
//!
//! **NOTE:** `native-tls` doesn't support `HTTP2` yet
//!
//! ## Signal handling and shutdown
//!
//! By default HTTP Server listens for system signals
//! and, gracefully shuts down at most after 30 seconds.
//!
//! Both signal handling and shutdown timeout can be controlled
//! using corresponding methods.
//!
//! If worker, for some reason, unable to shut down within timeout
//! it is forcibly dropped.
//!
//! ## Example
//!
//! ```rust,ignore
//!extern crate actix;
//!extern crate actix_web;
//!extern crate rustls;
//!
//!use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse, Responder};
//!use std::io::BufReader;
//!use rustls::internal::pemfile::{certs, rsa_private_keys};
//!use rustls::{NoClientAuth, ServerConfig};
//!
//!fn index(req: &HttpRequest) -> Result<HttpResponse, Error> {
//! Ok(HttpResponse::Ok().content_type("text/plain").body("Welcome!"))
//!}
//!
//!fn load_ssl() -> ServerConfig {
//! use std::io::BufReader;
//!
//! const CERT: &'static [u8] = include_bytes!("../cert.pem");
//! const KEY: &'static [u8] = include_bytes!("../key.pem");
//!
//! let mut cert = BufReader::new(CERT);
//! let mut key = BufReader::new(KEY);
//!
//! let mut config = ServerConfig::new(NoClientAuth::new());
//! let cert_chain = certs(&mut cert).unwrap();
//! let mut keys = rsa_private_keys(&mut key).unwrap();
//! config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
//!
//! config
//!}
//!
//!fn main() {
//! let sys = actix::System::new("http-server");
//! // load ssl keys
//! let config = load_ssl();
//!
//! // Create acceptor service for only HTTP1 protocol
//! // You can use ::new(config) to leave defaults
//! let acceptor = server::RustlsAcceptor::with_flags(config, actix_web::server::ServerFlags::HTTP1);
//!
//! // create and start server at once
//! server::new(|| {
//! App::new()
//! // register simple handler, handle all methods
//! .resource("/index.html", |r| r.f(index))
//! }))
//! }).bind_with("127.0.0.1:8080", acceptor)
//! .unwrap()
//! .start();
//!
//! println!("Started http server: 127.0.0.1:8080");
//! //Run system so that server would start accepting connections
//! let _ = sys.run();
//!}
//! ```
use std::net::Shutdown;
use std::{io, time};
use std::rc::Rc;
use std::{io, net, time};
use actix;
use futures::Poll;
use tokio_core::net::TcpStream;
use bytes::{BufMut, BytesMut};
use futures::{Async, Future, Poll};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_reactor::Handle;
use tokio_tcp::TcpStream;
pub(crate) mod accept;
mod channel;
pub(crate) mod encoding;
mod error;
pub(crate) mod h1;
pub(crate) mod h1decoder;
mod h1writer;
mod h2;
mod h2writer;
pub(crate) mod helpers;
mod settings;
pub(crate) mod shared;
mod srv;
pub(crate) mod utils;
mod http;
pub(crate) mod input;
pub(crate) mod message;
pub(crate) mod output;
mod server;
pub(crate) mod settings;
mod ssl;
mod worker;
use actix::Message;
pub use self::message::Request;
pub use self::http::HttpServer;
#[doc(hidden)]
pub use self::server::{
ConnectionRateTag, ConnectionTag, Connections, Server, Service, ServiceHandler,
};
pub use self::settings::ServerSettings;
pub use self::srv::HttpServer;
#[doc(hidden)]
pub use self::ssl::*;
#[doc(hidden)]
pub use self::helpers::write_content_length;
use body::Binary;
use error::Error;
use extensions::Extensions;
use header::ContentEncoding;
use httprequest::{HttpInnerMessage, HttpRequest};
use httpresponse::HttpResponse;
/// max buffer size 64k
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
const LW_BUFFER_SIZE: usize = 4096;
const HW_BUFFER_SIZE: usize = 32_768;
/// Create new http server with application factory.
///
/// This is shortcut for `server::HttpServer::new()` method.
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// use actix::*;
/// use actix_web::{server, App, HttpResponse};
/// use actix_web::{actix, server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("guide");
/// let sys = actix::System::new("example"); // <- create Actix system
///
/// server::new(
/// || App::new()
@ -52,8 +180,8 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
/// .bind("127.0.0.1:59090").unwrap()
/// .start();
///
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
/// let _ = sys.run();
/// # actix::System::current().stop();
/// sys.run();
/// }
/// ```
pub fn new<F, U, H>(factory: F) -> HttpServer<H>
@ -65,6 +193,17 @@ where
HttpServer::new(factory)
}
#[doc(hidden)]
bitflags! {
///Flags that can be used to configure HTTP Server.
pub struct ServerFlags: u8 {
///Use HTTP1 protocol
const HTTP1 = 0b0000_0001;
///Use HTTP2 protocol
const HTTP2 = 0b0000_0010;
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
/// Server keep-alive setting
pub enum KeepAlive {
@ -109,36 +248,61 @@ pub struct ResumeServer;
///
/// If server starts with `spawn()` method, then spawned thread get terminated.
pub struct StopServer {
/// Whether to try and shut down gracefully
pub graceful: bool,
}
impl actix::Message for StopServer {
impl Message for StopServer {
type Result = Result<(), ()>;
}
/// Socket id token
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct Token(usize);
impl Token {
pub(crate) fn new(val: usize) -> Token {
Token(val)
}
}
/// Low level http request handler
#[allow(unused_variables)]
pub trait HttpHandler: 'static {
/// Request handling task
type Task: HttpHandlerTask;
/// Handle request
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest>;
fn handle(&self, req: Request) -> Result<Self::Task, Request>;
}
impl HttpHandler for Box<HttpHandler> {
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest> {
self.as_mut().handle(req)
impl HttpHandler for Box<HttpHandler<Task = Box<HttpHandlerTask>>> {
type Task = Box<HttpHandlerTask>;
fn handle(&self, req: Request) -> Result<Box<HttpHandlerTask>, Request> {
self.as_ref().handle(req)
}
}
#[doc(hidden)]
/// Low level http request handler
pub trait HttpHandlerTask {
/// Poll task, this method is used before or after *io* object is available
fn poll(&mut self) -> Poll<(), Error>;
fn poll_completed(&mut self) -> Poll<(), Error> {
Ok(Async::Ready(()))
}
/// Poll task when *io* object is available
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
/// Connection is disconnected
fn disconnected(&mut self);
fn disconnected(&mut self) {}
}
impl HttpHandlerTask for Box<HttpHandlerTask> {
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
self.as_mut().poll_io(io)
}
}
/// Conversion helper trait
@ -147,17 +311,46 @@ pub trait IntoHttpHandler {
type Handler: HttpHandler;
/// Convert into `HttpHandler` object.
fn into_handler(self, settings: ServerSettings) -> Self::Handler;
fn into_handler(self) -> Self::Handler;
}
impl<T: HttpHandler> IntoHttpHandler for T {
type Handler = T;
fn into_handler(self, _: ServerSettings) -> Self::Handler {
fn into_handler(self) -> Self::Handler {
self
}
}
pub(crate) trait IntoAsyncIo {
type Io: AsyncRead + AsyncWrite;
fn into_async_io(self) -> Result<Self::Io, io::Error>;
}
impl IntoAsyncIo for net::TcpStream {
type Io = TcpStream;
fn into_async_io(self) -> Result<Self::Io, io::Error> {
TcpStream::from_std(self, &Handle::default())
}
}
#[doc(hidden)]
/// Trait implemented by types that could accept incomming socket connections.
pub trait AcceptorService<Io: AsyncRead + AsyncWrite>: Clone {
/// Established connection type
type Accepted: IoStream;
/// Future describes async accept process.
type Future: Future<Item = Self::Accepted, Error = io::Error> + 'static;
/// Establish new connection
fn accept(&self, io: Io) -> Self::Future;
/// Scheme
fn scheme(&self) -> &'static str;
}
#[doc(hidden)]
#[derive(Debug)]
pub enum WriterState {
@ -168,14 +361,20 @@ pub enum WriterState {
#[doc(hidden)]
/// Stream writer
pub trait Writer {
/// number of bytes written to the stream
fn written(&self) -> u64;
#[doc(hidden)]
fn set_date(&mut self);
#[doc(hidden)]
fn buffer(&mut self) -> &mut BytesMut;
fn start(
&mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse,
encoding: ContentEncoding,
&mut self, req: &Request, resp: &mut HttpResponse, encoding: ContentEncoding,
) -> io::Result<WriterState>;
fn write(&mut self, payload: Binary) -> io::Result<WriterState>;
fn write(&mut self, payload: &Binary) -> io::Result<WriterState>;
fn write_eof(&mut self) -> io::Result<WriterState>;
@ -190,6 +389,61 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static {
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>;
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
fn read_available(&mut self, buf: &mut BytesMut) -> Poll<(bool, bool), io::Error> {
let mut read_some = false;
loop {
if buf.remaining_mut() < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE);
}
unsafe {
match self.read(buf.bytes_mut()) {
Ok(n) => {
if n == 0 {
return Ok(Async::Ready((read_some, true)));
} else {
read_some = true;
buf.advance_mut(n);
}
}
Err(e) => {
return if e.kind() == io::ErrorKind::WouldBlock {
if read_some {
Ok(Async::Ready((read_some, false)))
} else {
Ok(Async::NotReady)
}
} else {
Err(e)
};
}
}
}
}
}
/// Extra io stream extensions
fn extensions(&self) -> Option<Rc<Extensions>> {
None
}
}
#[cfg(all(unix, feature = "uds"))]
impl IoStream for ::tokio_uds::UnixStream {
#[inline]
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
::tokio_uds::UnixStream::shutdown(self, how)
}
#[inline]
fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> {
Ok(())
}
#[inline]
fn set_linger(&mut self, _dur: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
}
impl IoStream for TcpStream {
@ -208,47 +462,3 @@ impl IoStream for TcpStream {
TcpStream::set_linger(self, dur)
}
}
#[cfg(feature = "alpn")]
use tokio_openssl::SslStream;
#[cfg(feature = "alpn")]
impl IoStream for SslStream<TcpStream> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
}
#[cfg(feature = "tls")]
use tokio_tls::TlsStream;
#[cfg(feature = "tls")]
impl IoStream for TlsStream<TcpStream> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
}

763
src/server/output.rs Normal file
View File

@ -0,0 +1,763 @@
use std::fmt::Write as FmtWrite;
use std::io::Write;
use std::str::FromStr;
use std::{cmp, fmt, io, mem};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliEncoder;
use bytes::BytesMut;
#[cfg(feature = "flate2")]
use flate2::write::{GzEncoder, ZlibEncoder};
#[cfg(feature = "flate2")]
use flate2::Compression;
use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH};
use http::Version;
use super::message::InnerRequest;
use body::{Binary, Body};
use header::ContentEncoding;
use httpresponse::HttpResponse;
#[derive(Debug)]
pub(crate) enum ResponseLength {
Chunked,
Zero,
Length(usize),
Length64(u64),
None,
}
#[derive(Debug)]
pub(crate) struct ResponseInfo {
head: bool,
pub length: ResponseLength,
pub content_encoding: Option<&'static str>,
}
impl ResponseInfo {
pub fn new(head: bool) -> Self {
ResponseInfo {
head,
length: ResponseLength::None,
content_encoding: None,
}
}
}
#[derive(Debug)]
pub(crate) enum Output {
Empty(BytesMut),
Buffer(BytesMut),
Encoder(ContentEncoder),
TE(TransferEncoding),
Done,
}
impl Output {
pub fn take(&mut self) -> BytesMut {
match mem::replace(self, Output::Done) {
Output::Empty(bytes) => bytes,
Output::Buffer(bytes) => bytes,
Output::Encoder(mut enc) => enc.take_buf(),
Output::TE(mut te) => te.take(),
Output::Done => panic!(),
}
}
pub fn take_option(&mut self) -> Option<BytesMut> {
match mem::replace(self, Output::Done) {
Output::Empty(bytes) => Some(bytes),
Output::Buffer(bytes) => Some(bytes),
Output::Encoder(mut enc) => Some(enc.take_buf()),
Output::TE(mut te) => Some(te.take()),
Output::Done => None,
}
}
pub fn as_ref(&mut self) -> &BytesMut {
match self {
Output::Empty(ref mut bytes) => bytes,
Output::Buffer(ref mut bytes) => bytes,
Output::Encoder(ref mut enc) => enc.buf_ref(),
Output::TE(ref mut te) => te.buf_ref(),
Output::Done => panic!(),
}
}
pub fn as_mut(&mut self) -> &mut BytesMut {
match self {
Output::Empty(ref mut bytes) => bytes,
Output::Buffer(ref mut bytes) => bytes,
Output::Encoder(ref mut enc) => enc.buf_mut(),
Output::TE(ref mut te) => te.buf_mut(),
Output::Done => panic!(),
}
}
pub fn split_to(&mut self, cap: usize) -> BytesMut {
match self {
Output::Empty(ref mut bytes) => bytes.split_to(cap),
Output::Buffer(ref mut bytes) => bytes.split_to(cap),
Output::Encoder(ref mut enc) => enc.buf_mut().split_to(cap),
Output::TE(ref mut te) => te.buf_mut().split_to(cap),
Output::Done => BytesMut::new(),
}
}
pub fn len(&self) -> usize {
match self {
Output::Empty(ref bytes) => bytes.len(),
Output::Buffer(ref bytes) => bytes.len(),
Output::Encoder(ref enc) => enc.len(),
Output::TE(ref te) => te.len(),
Output::Done => 0,
}
}
pub fn is_empty(&self) -> bool {
match self {
Output::Empty(ref bytes) => bytes.is_empty(),
Output::Buffer(ref bytes) => bytes.is_empty(),
Output::Encoder(ref enc) => enc.is_empty(),
Output::TE(ref te) => te.is_empty(),
Output::Done => true,
}
}
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
match self {
Output::Buffer(ref mut bytes) => {
bytes.extend_from_slice(data);
Ok(())
}
Output::Encoder(ref mut enc) => enc.write(data),
Output::TE(ref mut te) => te.encode(data).map(|_| ()),
Output::Empty(_) | Output::Done => Ok(()),
}
}
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
match self {
Output::Buffer(_) => Ok(true),
Output::Encoder(ref mut enc) => enc.write_eof(),
Output::TE(ref mut te) => Ok(te.encode_eof()),
Output::Empty(_) | Output::Done => Ok(true),
}
}
pub(crate) fn for_server(
&mut self, info: &mut ResponseInfo, req: &InnerRequest, resp: &mut HttpResponse,
response_encoding: ContentEncoding,
) {
let buf = self.take();
let version = resp.version().unwrap_or_else(|| req.version);
let mut len = 0;
#[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
let has_body = match resp.body() {
&Body::Empty => false,
&Body::Binary(ref bin) => {
len = bin.len();
!(response_encoding == ContentEncoding::Auto && len < 96)
}
_ => true,
};
// Enable content encoding only if response does not contain Content-Encoding
// header
#[cfg(any(feature = "brotli", feature = "flate2"))]
let mut encoding = if has_body {
let encoding = match response_encoding {
ContentEncoding::Auto => {
// negotiate content-encoding
if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
if let Ok(enc) = val.to_str() {
AcceptEncoding::parse(enc)
} else {
ContentEncoding::Identity
}
} else {
ContentEncoding::Identity
}
}
encoding => encoding,
};
if encoding.is_compression() {
info.content_encoding = Some(encoding.as_str());
}
encoding
} else {
ContentEncoding::Identity
};
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
let mut encoding = ContentEncoding::Identity;
#[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
let transfer = match resp.body() {
&Body::Empty => {
if !info.head {
info.length = ResponseLength::Zero;
}
*self = Output::Empty(buf);
return;
}
&Body::Binary(_) => {
#[cfg(any(feature = "brotli", feature = "flate2"))]
{
if !(encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto)
{
let mut tmp = BytesMut::new();
let mut transfer = TransferEncoding::eof(tmp);
let mut enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => ContentEncoder::Deflate(
ZlibEncoder::new(transfer, Compression::fast()),
),
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => ContentEncoder::Gzip(
GzEncoder::new(transfer, Compression::fast()),
),
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
}
ContentEncoding::Identity | ContentEncoding::Auto => {
unreachable!()
}
};
let bin = resp.replace_body(Body::Empty).binary();
// TODO return error!
let _ = enc.write(bin.as_ref());
let _ = enc.write_eof();
let body = enc.buf_mut().take();
len = body.len();
resp.replace_body(Binary::from(body));
}
}
info.length = ResponseLength::Length(len);
if info.head {
*self = Output::Empty(buf);
} else {
*self = Output::Buffer(buf);
}
return;
}
&Body::Streaming(_) | &Body::Actor(_) => {
if resp.upgrade() {
if version == Version::HTTP_2 {
error!("Connection upgrade is forbidden for HTTP/2");
}
if encoding != ContentEncoding::Identity {
encoding = ContentEncoding::Identity;
info.content_encoding.take();
}
TransferEncoding::eof(buf)
} else {
if !(encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto)
{
resp.headers_mut().remove(CONTENT_LENGTH);
}
Output::streaming_encoding(info, buf, version, resp)
}
}
};
// check for head response
if info.head {
resp.set_body(Body::Empty);
*self = Output::Empty(transfer.buf.unwrap());
return;
}
let enc = match encoding {
#[cfg(feature = "flate2")]
ContentEncoding::Deflate => {
ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::fast()))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => {
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
}
#[cfg(feature = "brotli")]
ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)),
ContentEncoding::Identity | ContentEncoding::Auto => {
*self = Output::TE(transfer);
return;
}
};
*self = Output::Encoder(enc);
}
fn streaming_encoding(
info: &mut ResponseInfo, buf: BytesMut, version: Version,
resp: &mut HttpResponse,
) -> TransferEncoding {
match resp.chunked() {
Some(true) => {
// Enable transfer encoding
if version == Version::HTTP_2 {
info.length = ResponseLength::None;
TransferEncoding::eof(buf)
} else {
info.length = ResponseLength::Chunked;
TransferEncoding::chunked(buf)
}
}
Some(false) => TransferEncoding::eof(buf),
None => {
// if Content-Length is specified, then use it as length hint
let (len, chunked) =
if let Some(len) = resp.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 {
info.length = ResponseLength::Length64(len);
TransferEncoding::length(len, buf)
} else {
TransferEncoding::eof(buf)
}
} else {
// Enable transfer encoding
match version {
Version::HTTP_11 => {
info.length = ResponseLength::Chunked;
TransferEncoding::chunked(buf)
}
_ => {
info.length = ResponseLength::None;
TransferEncoding::eof(buf)
}
}
}
}
}
}
}
pub(crate) enum ContentEncoder {
#[cfg(feature = "flate2")]
Deflate(ZlibEncoder<TransferEncoding>),
#[cfg(feature = "flate2")]
Gzip(GzEncoder<TransferEncoding>),
#[cfg(feature = "brotli")]
Br(BrotliEncoder<TransferEncoding>),
Identity(TransferEncoding),
}
impl fmt::Debug for ContentEncoder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"),
ContentEncoder::Identity(_) => writeln!(f, "ContentEncoder(Identity)"),
}
}
}
impl ContentEncoder {
#[inline]
pub fn len(&self) -> usize {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref encoder) => encoder.get_ref().len(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().len(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().len(),
ContentEncoder::Identity(ref encoder) => encoder.len(),
}
}
#[inline]
pub fn is_empty(&self) -> bool {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_empty(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_empty(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_empty(),
ContentEncoder::Identity(ref encoder) => encoder.is_empty(),
}
}
#[inline]
pub(crate) fn take_buf(&mut self) -> BytesMut {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
ContentEncoder::Identity(ref mut encoder) => encoder.take(),
}
}
#[inline]
pub(crate) fn buf_mut(&mut self) -> &mut BytesMut {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_mut(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_mut(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_mut(),
ContentEncoder::Identity(ref mut encoder) => encoder.buf_mut(),
}
}
#[inline]
pub(crate) fn buf_ref(&mut self) -> &BytesMut {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_ref(),
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_ref(),
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_ref(),
ContentEncoder::Identity(ref mut encoder) => encoder.buf_ref(),
}
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
let encoder =
mem::replace(self, ContentEncoder::Identity(TransferEncoding::empty()));
match encoder {
#[cfg(feature = "brotli")]
ContentEncoder::Br(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(true)
}
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(true)
}
Err(err) => Err(err),
},
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(mut writer) => {
writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(true)
}
Err(err) => Err(err),
},
ContentEncoder::Identity(mut writer) => {
let res = writer.encode_eof();
*self = ContentEncoder::Identity(writer);
Ok(res)
}
}
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding br encoding: {}", err);
Err(err)
}
},
#[cfg(feature = "flate2")]
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding gzip encoding: {}", err);
Err(err)
}
},
#[cfg(feature = "flate2")]
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
Err(err) => {
trace!("Error decoding deflate encoding: {}", err);
Err(err)
}
},
ContentEncoder::Identity(ref mut encoder) => {
encoder.encode(data)?;
Ok(())
}
}
}
}
/// Encoders to handle different Transfer-Encodings.
#[derive(Debug)]
pub(crate) struct TransferEncoding {
buf: Option<BytesMut>,
kind: TransferEncodingKind,
}
#[derive(Debug, PartialEq, Clone)]
enum TransferEncodingKind {
/// An Encoder for when Transfer-Encoding includes `chunked`.
Chunked(bool),
/// An Encoder for when Content-Length is set.
///
/// Enforces that the body is not longer than the Content-Length header.
Length(u64),
/// An Encoder for when Content-Length is not known.
///
/// Application decides when to stop writing.
Eof,
}
impl TransferEncoding {
fn take(&mut self) -> BytesMut {
self.buf.take().unwrap()
}
fn buf_ref(&mut self) -> &BytesMut {
self.buf.as_ref().unwrap()
}
fn len(&self) -> usize {
self.buf.as_ref().unwrap().len()
}
fn is_empty(&self) -> bool {
self.buf.as_ref().unwrap().is_empty()
}
fn buf_mut(&mut self) -> &mut BytesMut {
self.buf.as_mut().unwrap()
}
#[inline]
pub fn empty() -> TransferEncoding {
TransferEncoding {
buf: None,
kind: TransferEncodingKind::Eof,
}
}
#[inline]
pub fn eof(buf: BytesMut) -> TransferEncoding {
TransferEncoding {
buf: Some(buf),
kind: TransferEncodingKind::Eof,
}
}
#[inline]
pub fn chunked(buf: BytesMut) -> TransferEncoding {
TransferEncoding {
buf: Some(buf),
kind: TransferEncodingKind::Chunked(false),
}
}
#[inline]
pub fn length(len: u64, buf: BytesMut) -> TransferEncoding {
TransferEncoding {
buf: Some(buf),
kind: TransferEncodingKind::Length(len),
}
}
/// Encode message. Return `EOF` state of encoder
#[inline]
pub fn encode(&mut self, msg: &[u8]) -> io::Result<bool> {
match self.kind {
TransferEncodingKind::Eof => {
let eof = msg.is_empty();
self.buf.as_mut().unwrap().extend_from_slice(msg);
Ok(eof)
}
TransferEncodingKind::Chunked(ref mut eof) => {
if *eof {
return Ok(true);
}
if msg.is_empty() {
*eof = true;
self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n");
} else {
let mut buf = BytesMut::new();
writeln!(&mut buf, "{:X}\r", msg.len())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let b = self.buf.as_mut().unwrap();
b.reserve(buf.len() + msg.len() + 2);
b.extend_from_slice(buf.as_ref());
b.extend_from_slice(msg);
b.extend_from_slice(b"\r\n");
}
Ok(*eof)
}
TransferEncodingKind::Length(ref mut remaining) => {
if *remaining > 0 {
if msg.is_empty() {
return Ok(*remaining == 0);
}
let len = cmp::min(*remaining, msg.len() as u64);
self.buf
.as_mut()
.unwrap()
.extend_from_slice(&msg[..len as usize]);
*remaining -= len as u64;
Ok(*remaining == 0)
} else {
Ok(true)
}
}
}
}
/// Encode eof. Return `EOF` state of encoder
#[inline]
pub fn encode_eof(&mut self) -> bool {
match self.kind {
TransferEncodingKind::Eof => true,
TransferEncodingKind::Length(rem) => rem == 0,
TransferEncodingKind::Chunked(ref mut eof) => {
if !*eof {
*eof = true;
self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n");
}
true
}
}
}
}
impl io::Write for TransferEncoding {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.buf.is_some() {
self.encode(buf)?;
}
Ok(buf.len())
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
struct AcceptEncoding {
encoding: ContentEncoding,
quality: f64,
}
impl Eq for AcceptEncoding {}
impl Ord for AcceptEncoding {
fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering {
if self.quality > other.quality {
cmp::Ordering::Less
} else if self.quality < other.quality {
cmp::Ordering::Greater
} else {
cmp::Ordering::Equal
}
}
}
impl PartialOrd for AcceptEncoding {
fn partial_cmp(&self, other: &AcceptEncoding) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for AcceptEncoding {
fn eq(&self, other: &AcceptEncoding) -> bool {
self.quality == other.quality
}
}
impl AcceptEncoding {
fn new(tag: &str) -> Option<AcceptEncoding> {
let parts: Vec<&str> = tag.split(';').collect();
let encoding = match parts.len() {
0 => return None,
_ => ContentEncoding::from(parts[0]),
};
let quality = match parts.len() {
1 => encoding.quality(),
_ => match f64::from_str(parts[1]) {
Ok(q) => q,
Err(_) => 0.0,
},
};
Some(AcceptEncoding { encoding, quality })
}
/// Parse a raw Accept-Encoding header value into an ordered list.
pub fn parse(raw: &str) -> ContentEncoding {
let mut encodings: Vec<_> = raw
.replace(' ', "")
.split(',')
.map(|l| AcceptEncoding::new(l))
.collect();
encodings.sort();
for enc in encodings {
if let Some(enc) = enc {
return enc.encoding;
}
}
ContentEncoding::Identity
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
#[test]
fn test_chunked_te() {
let bytes = BytesMut::new();
let mut enc = TransferEncoding::chunked(bytes);
{
assert!(!enc.encode(b"test").ok().unwrap());
assert!(enc.encode(b"").ok().unwrap());
}
assert_eq!(
enc.buf_mut().take().freeze(),
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")
);
}
}

528
src/server/server.rs Normal file
View File

@ -0,0 +1,528 @@
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use std::time::Duration;
use std::{mem, net};
use futures::sync::{mpsc, mpsc::unbounded};
use futures::{Future, Sink, Stream};
use num_cpus;
use actix::{
fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler,
Response, StreamHandler, System, WrapFuture,
};
use super::accept::{AcceptLoop, AcceptNotify, Command};
use super::worker::{Conn, StopWorker, Worker, WorkerClient};
use super::{PauseServer, ResumeServer, StopServer, Token};
#[doc(hidden)]
/// Describes service that could be used
/// with [Server](struct.Server.html)
pub trait Service: Send + 'static {
/// Clone service
fn clone(&self) -> Box<Service>;
/// Create service handler for this service
fn create(&self, conn: Connections) -> Box<ServiceHandler>;
}
impl Service for Box<Service> {
fn clone(&self) -> Box<Service> {
self.as_ref().clone()
}
fn create(&self, conn: Connections) -> Box<ServiceHandler> {
self.as_ref().create(conn)
}
}
#[doc(hidden)]
/// Describes the way serivce handles incoming
/// TCP connections.
pub trait ServiceHandler {
/// Handle incoming stream
fn handle(
&mut self, token: Token, io: net::TcpStream, peer: Option<net::SocketAddr>,
);
/// Shutdown open handlers
fn shutdown(&self, _: bool) {}
}
pub(crate) enum ServerCommand {
WorkerDied(usize),
}
/// Generic server
#[doc(hidden)]
pub struct Server {
threads: usize,
workers: Vec<(usize, Addr<Worker>)>,
services: Vec<Box<Service>>,
sockets: Vec<Vec<(Token, net::TcpListener)>>,
accept: AcceptLoop,
exit: bool,
shutdown_timeout: u16,
signals: Option<Addr<signal::ProcessSignals>>,
no_signals: bool,
maxconn: usize,
maxconnrate: usize,
}
impl Default for Server {
fn default() -> Self {
Self::new()
}
}
impl Server {
/// Create new Server instance
pub fn new() -> Server {
Server {
threads: num_cpus::get(),
workers: Vec::new(),
services: Vec::new(),
sockets: Vec::new(),
accept: AcceptLoop::new(),
exit: false,
shutdown_timeout: 30,
signals: None,
no_signals: false,
maxconn: 102_400,
maxconnrate: 256,
}
}
/// Set number of workers to start.
///
/// By default http server uses number of available logical cpu as threads
/// count.
pub fn workers(mut self, num: usize) -> Self {
self.threads = num;
self
}
/// Sets the maximum per-worker number of concurrent connections.
///
/// All socket listeners will stop accepting connections when this limit is reached
/// for each worker.
///
/// By default max connections is set to a 100k.
pub fn maxconn(mut self, num: usize) -> Self {
self.maxconn = num;
self
}
/// Sets the maximum per-worker concurrent connection establish process.
///
/// All listeners will stop accepting connections when this limit is reached. It
/// can be used to limit the global SSL CPU usage.
///
/// By default max connections is set to a 256.
pub fn maxconnrate(mut self, num: usize) -> Self {
self.maxconnrate = num;
self
}
/// Stop actix system.
///
/// `SystemExit` message stops currently running system.
pub fn system_exit(mut self) -> Self {
self.exit = true;
self
}
#[doc(hidden)]
/// Set alternative address for `ProcessSignals` actor.
pub fn signals(mut self, addr: Addr<signal::ProcessSignals>) -> Self {
self.signals = Some(addr);
self
}
/// Disable signal handling
pub fn disable_signals(mut self) -> Self {
self.no_signals = true;
self
}
/// Timeout for graceful workers shutdown.
///
/// After receiving a stop signal, workers have this much time to finish
/// serving requests. Workers still alive after the timeout are force
/// dropped.
///
/// By default shutdown timeout sets to 30 seconds.
pub fn shutdown_timeout(mut self, sec: u16) -> Self {
self.shutdown_timeout = sec;
self
}
/// Add new service to server
pub fn service<T>(mut self, srv: T) -> Self
where
T: Into<(Box<Service>, Vec<(Token, net::TcpListener)>)>,
{
let (srv, sockets) = srv.into();
self.services.push(srv);
self.sockets.push(sockets);
self
}
/// Spawn new thread and start listening for incoming connections.
///
/// This method spawns new thread and starts new actix system. Other than
/// that it is similar to `start()` method. This method blocks.
///
/// This methods panics if no socket addresses get bound.
///
/// ```rust,ignore
/// # extern crate futures;
/// # extern crate actix_web;
/// # use futures::Future;
/// use actix_web::*;
///
/// fn main() {
/// Server::new().
/// .service(
/// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0")
/// .expect("Can not bind to 127.0.0.1:0"))
/// .run();
/// }
/// ```
pub fn run(self) {
let sys = System::new("http-server");
self.start();
sys.run();
}
/// Starts Server Actor and returns its address
pub fn start(mut self) -> Addr<Server> {
if self.sockets.is_empty() {
panic!("Service should have at least one bound socket");
} else {
info!("Starting {} http workers", self.threads);
// start workers
let mut workers = Vec::new();
for idx in 0..self.threads {
let (addr, worker) = self.start_worker(idx, self.accept.get_notify());
workers.push(worker);
self.workers.push((idx, addr));
}
// start accept thread
for sock in &self.sockets {
for s in sock.iter() {
info!("Starting server on http://{}", s.1.local_addr().unwrap());
}
}
let rx = self
.accept
.start(mem::replace(&mut self.sockets, Vec::new()), workers);
// start http server actor
let signals = self.subscribe_to_signals();
let addr = Actor::create(move |ctx| {
ctx.add_stream(rx);
self
});
if let Some(signals) = signals {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
}
addr
}
}
// subscribe to os signals
fn subscribe_to_signals(&self) -> Option<Addr<signal::ProcessSignals>> {
if !self.no_signals {
if let Some(ref signals) = self.signals {
Some(signals.clone())
} else {
Some(System::current().registry().get::<signal::ProcessSignals>())
}
} else {
None
}
}
fn start_worker(
&self, idx: usize, notify: AcceptNotify,
) -> (Addr<Worker>, WorkerClient) {
let (tx, rx) = unbounded::<Conn<net::TcpStream>>();
let conns = Connections::new(notify, self.maxconn, self.maxconnrate);
let worker = WorkerClient::new(idx, tx, conns.clone());
let services: Vec<_> = self.services.iter().map(|v| v.clone()).collect();
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
ctx.add_message_stream(rx);
let handlers: Vec<_> = services
.into_iter()
.map(|s| s.create(conns.clone()))
.collect();
Worker::new(conns, handlers)
});
(addr, worker)
}
}
impl Actor for Server {
type Context = Context<Self>;
}
/// Signals support
/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system
/// message to `System` actor.
impl Handler<signal::Signal> for Server {
type Result = ();
fn handle(&mut self, msg: signal::Signal, ctx: &mut Context<Self>) {
match msg.0 {
signal::SignalType::Int => {
info!("SIGINT received, exiting");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: false }, ctx);
}
signal::SignalType::Term => {
info!("SIGTERM received, stopping");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: true }, ctx);
}
signal::SignalType::Quit => {
info!("SIGQUIT received, exiting");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: false }, ctx);
}
_ => (),
}
}
}
impl Handler<PauseServer> for Server {
type Result = ();
fn handle(&mut self, _: PauseServer, _: &mut Context<Self>) {
self.accept.send(Command::Pause);
}
}
impl Handler<ResumeServer> for Server {
type Result = ();
fn handle(&mut self, _: ResumeServer, _: &mut Context<Self>) {
self.accept.send(Command::Resume);
}
}
impl Handler<StopServer> for Server {
type Result = Response<(), ()>;
fn handle(&mut self, msg: StopServer, ctx: &mut Context<Self>) -> Self::Result {
// stop accept thread
self.accept.send(Command::Stop);
// stop workers
let (tx, rx) = mpsc::channel(1);
let dur = if msg.graceful {
Some(Duration::new(u64::from(self.shutdown_timeout), 0))
} else {
None
};
for worker in &self.workers {
let tx2 = tx.clone();
ctx.spawn(
worker
.1
.send(StopWorker { graceful: dur })
.into_actor(self)
.then(move |_, slf, ctx| {
slf.workers.pop();
if slf.workers.is_empty() {
let _ = tx2.send(());
// we need to stop system if server was spawned
if slf.exit {
ctx.run_later(Duration::from_millis(300), |_, _| {
System::current().stop();
});
}
}
fut::ok(())
}),
);
}
if !self.workers.is_empty() {
Response::async(rx.into_future().map(|_| ()).map_err(|_| ()))
} else {
// we need to stop system if server was spawned
if self.exit {
ctx.run_later(Duration::from_millis(300), |_, _| {
System::current().stop();
});
}
Response::reply(Ok(()))
}
}
}
/// Commands from accept threads
impl StreamHandler<ServerCommand, ()> for Server {
fn finished(&mut self, _: &mut Context<Self>) {}
fn handle(&mut self, msg: ServerCommand, _: &mut Context<Self>) {
match msg {
ServerCommand::WorkerDied(idx) => {
let mut found = false;
for i in 0..self.workers.len() {
if self.workers[i].0 == idx {
self.workers.swap_remove(i);
found = true;
break;
}
}
if found {
error!("Worker has died {:?}, restarting", idx);
let mut new_idx = self.workers.len();
'found: loop {
for i in 0..self.workers.len() {
if self.workers[i].0 == new_idx {
new_idx += 1;
continue 'found;
}
}
break;
}
let (addr, worker) =
self.start_worker(new_idx, self.accept.get_notify());
self.workers.push((new_idx, addr));
self.accept.send(Command::Worker(worker));
}
}
}
}
}
#[derive(Clone, Default)]
///Contains information about connection.
pub struct Connections(Arc<ConnectionsInner>);
impl Connections {
fn new(notify: AcceptNotify, maxconn: usize, maxconnrate: usize) -> Self {
let maxconn_low = if maxconn > 10 { maxconn - 10 } else { 0 };
let maxconnrate_low = if maxconnrate > 10 {
maxconnrate - 10
} else {
0
};
Connections(Arc::new(ConnectionsInner {
notify,
maxconn,
maxconnrate,
maxconn_low,
maxconnrate_low,
conn: AtomicUsize::new(0),
connrate: AtomicUsize::new(0),
}))
}
pub(crate) fn available(&self) -> bool {
self.0.available()
}
pub(crate) fn num_connections(&self) -> usize {
self.0.conn.load(Ordering::Relaxed)
}
/// Report opened connection
pub fn connection(&self) -> ConnectionTag {
ConnectionTag::new(self.0.clone())
}
/// Report rate connection, rate is usually ssl handshake
pub fn connection_rate(&self) -> ConnectionRateTag {
ConnectionRateTag::new(self.0.clone())
}
}
#[derive(Default)]
struct ConnectionsInner {
notify: AcceptNotify,
conn: AtomicUsize,
connrate: AtomicUsize,
maxconn: usize,
maxconnrate: usize,
maxconn_low: usize,
maxconnrate_low: usize,
}
impl ConnectionsInner {
fn available(&self) -> bool {
if self.maxconnrate <= self.connrate.load(Ordering::Relaxed) {
false
} else {
self.maxconn > self.conn.load(Ordering::Relaxed)
}
}
fn notify_maxconn(&self, maxconn: usize) {
if maxconn > self.maxconn_low && maxconn <= self.maxconn {
self.notify.notify();
}
}
fn notify_maxconnrate(&self, connrate: usize) {
if connrate > self.maxconnrate_low && connrate <= self.maxconnrate {
self.notify.notify();
}
}
}
/// Type responsible for max connection stat.
///
/// Max connections stat get updated on drop.
pub struct ConnectionTag(Arc<ConnectionsInner>);
impl ConnectionTag {
fn new(inner: Arc<ConnectionsInner>) -> Self {
inner.conn.fetch_add(1, Ordering::Relaxed);
ConnectionTag(inner)
}
}
impl Drop for ConnectionTag {
fn drop(&mut self) {
let conn = self.0.conn.fetch_sub(1, Ordering::Relaxed);
self.0.notify_maxconn(conn);
}
}
/// Type responsible for max connection rate stat.
///
/// Max connections rate stat get updated on drop.
pub struct ConnectionRateTag(Arc<ConnectionsInner>);
impl ConnectionRateTag {
fn new(inner: Arc<ConnectionsInner>) -> Self {
inner.connrate.fetch_add(1, Ordering::Relaxed);
ConnectionRateTag(inner)
}
}
impl Drop for ConnectionRateTag {
fn drop(&mut self) {
let connrate = self.0.connrate.fetch_sub(1, Ordering::Relaxed);
self.0.notify_maxconnrate(connrate);
}
}

View File

@ -1,70 +1,76 @@
use bytes::BytesMut;
use futures_cpupool::{Builder, CpuPool};
use http::StatusCode;
use std::cell::{Cell, RefCell, RefMut, UnsafeCell};
use std::cell::{RefCell, RefMut, UnsafeCell};
use std::collections::VecDeque;
use std::fmt::Write;
use std::rc::Rc;
use std::sync::Arc;
use std::{fmt, mem, net};
use std::time::{Duration, Instant};
use std::{env, fmt, net};
use actix::Arbiter;
use bytes::BytesMut;
use futures::Stream;
use futures_cpupool::CpuPool;
use http::StatusCode;
use lazycell::LazyCell;
use parking_lot::Mutex;
use time;
use tokio_timer::Interval;
use super::channel::Node;
use super::helpers;
use super::shared::{SharedBytes, SharedBytesPool};
use super::message::{Request, RequestPool};
use super::server::{ConnectionRateTag, ConnectionTag, Connections};
use super::KeepAlive;
use body::Body;
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
/// Env variable for default cpu pool size
const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL";
lazy_static! {
pub(crate) static ref DEFAULT_CPUPOOL: Mutex<CpuPool> = {
let default = match env::var(ENV_CPU_POOL_VAR) {
Ok(val) => {
if let Ok(val) = val.parse() {
val
} else {
error!("Can not parse ACTIX_CPU_POOL value");
20
}
}
Err(_) => 20,
};
Mutex::new(CpuPool::new(default))
};
}
/// Various server settings
#[derive(Clone)]
pub struct ServerSettings {
addr: Option<net::SocketAddr>,
secure: bool,
host: String,
cpu_pool: Arc<InnerCpuPool>,
responses: Rc<UnsafeCell<HttpResponsePool>>,
cpu_pool: LazyCell<CpuPool>,
responses: &'static HttpResponsePool,
}
unsafe impl Sync for ServerSettings {}
unsafe impl Send for ServerSettings {}
struct InnerCpuPool {
cpu_pool: UnsafeCell<Option<CpuPool>>,
}
impl fmt::Debug for InnerCpuPool {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CpuPool")
}
}
impl InnerCpuPool {
fn new() -> Self {
InnerCpuPool {
cpu_pool: UnsafeCell::new(None),
}
}
fn cpu_pool(&self) -> &CpuPool {
unsafe {
let val = &mut *self.cpu_pool.get();
if val.is_none() {
*val = Some(Builder::new().create());
}
val.as_ref().unwrap()
impl Clone for ServerSettings {
fn clone(&self) -> Self {
ServerSettings {
addr: self.addr,
secure: self.secure,
host: self.host.clone(),
cpu_pool: LazyCell::new(),
responses: HttpResponsePool::get_pool(),
}
}
}
unsafe impl Sync for InnerCpuPool {}
impl Default for ServerSettings {
fn default() -> Self {
ServerSettings {
addr: None,
secure: false,
host: "localhost:8080".to_owned(),
responses: HttpResponsePool::pool(),
cpu_pool: Arc::new(InnerCpuPool::new()),
responses: HttpResponsePool::get_pool(),
cpu_pool: LazyCell::new(),
}
}
}
@ -81,8 +87,8 @@ impl ServerSettings {
} else {
"localhost".to_owned()
};
let cpu_pool = Arc::new(InnerCpuPool::new());
let responses = HttpResponsePool::pool();
let cpu_pool = LazyCell::new();
let responses = HttpResponsePool::get_pool();
ServerSettings {
addr,
secure,
@ -109,7 +115,7 @@ impl ServerSettings {
/// Returns default `CpuPool` for server
pub fn cpu_pool(&self) -> &CpuPool {
self.cpu_pool.cpu_pool()
self.cpu_pool.borrow_with(|| DEFAULT_CPUPOOL.lock().clone())
}
#[inline]
@ -129,18 +135,42 @@ impl ServerSettings {
const DATE_VALUE_LENGTH: usize = 29;
pub(crate) struct WorkerSettings<H> {
h: RefCell<Vec<H>>,
h: Vec<H>,
keep_alive: u64,
ka_enabled: bool,
bytes: Rc<SharedBytesPool>,
messages: Rc<helpers::SharedMessagePool>,
channels: Cell<usize>,
node: Box<Node<()>>,
messages: &'static RequestPool,
conns: Connections,
node: RefCell<Node<()>>,
date: UnsafeCell<Date>,
}
impl<H: 'static> WorkerSettings<H> {
pub(crate) fn create(
apps: Vec<H>, keep_alive: KeepAlive, settings: ServerSettings,
conns: Connections,
) -> Rc<WorkerSettings<H>> {
let settings = Rc::new(Self::new(apps, keep_alive, settings, conns));
// periodic date update
let s = settings.clone();
Arbiter::spawn(
Interval::new(Instant::now(), Duration::from_secs(1))
.map_err(|_| ())
.and_then(move |_| {
s.update_date();
Ok(())
}).fold((), |(), _| Ok(())),
);
settings
}
}
impl<H> WorkerSettings<H> {
pub(crate) fn new(h: Vec<H>, keep_alive: KeepAlive) -> WorkerSettings<H> {
pub(crate) fn new(
h: Vec<H>, keep_alive: KeepAlive, settings: ServerSettings, conns: Connections,
) -> WorkerSettings<H> {
let (keep_alive, ka_enabled) = match keep_alive {
KeepAlive::Timeout(val) => (val as u64, true),
KeepAlive::Os | KeepAlive::Tcp(_) => (0, true),
@ -148,27 +178,23 @@ impl<H> WorkerSettings<H> {
};
WorkerSettings {
h,
bytes: Rc::new(SharedBytesPool::new()),
messages: RequestPool::pool(settings),
node: RefCell::new(Node::head()),
date: UnsafeCell::new(Date::new()),
keep_alive,
ka_enabled,
h: RefCell::new(h),
bytes: Rc::new(SharedBytesPool::new()),
messages: Rc::new(helpers::SharedMessagePool::new()),
channels: Cell::new(0),
node: Box::new(Node::head()),
date: UnsafeCell::new(Date::new()),
conns,
}
}
pub fn num_channels(&self) -> usize {
self.channels.get()
pub fn head(&self) -> RefMut<Node<()>> {
self.node.borrow_mut()
}
pub fn head(&self) -> &Node<()> {
&self.node
}
pub fn handlers(&self) -> RefMut<Vec<H>> {
self.h.borrow_mut()
pub fn handlers(&self) -> &Vec<H> {
&self.h
}
pub fn keep_alive(&self) -> u64 {
@ -179,44 +205,44 @@ impl<H> WorkerSettings<H> {
self.ka_enabled
}
pub fn get_shared_bytes(&self) -> SharedBytes {
SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes))
pub fn get_bytes(&self) -> BytesMut {
self.bytes.get_bytes()
}
pub fn get_http_message(&self) -> helpers::SharedHttpInnerMessage {
helpers::SharedHttpInnerMessage::new(
self.messages.get(),
Rc::clone(&self.messages),
)
pub fn release_bytes(&self, bytes: BytesMut) {
self.bytes.release_bytes(bytes)
}
pub fn add_channel(&self) {
self.channels.set(self.channels.get() + 1);
pub fn get_request(&self) -> Request {
RequestPool::get(self.messages)
}
pub fn remove_channel(&self) {
let num = self.channels.get();
if num > 0 {
self.channels.set(num - 1);
} else {
error!("Number of removed channels is bigger than added channel. Bug in actix-web");
}
pub fn connection(&self) -> ConnectionTag {
self.conns.connection()
}
pub fn update_date(&self) {
fn update_date(&self) {
// Unsafe: WorkerSetting is !Sync and !Send
unsafe { &mut *self.date.get() }.update();
}
pub fn set_date(&self, dst: &mut BytesMut) {
let mut buf: [u8; 39] = unsafe { mem::uninitialized() };
buf[..6].copy_from_slice(b"date: ");
buf[6..35].copy_from_slice(&(unsafe { &*self.date.get() }.bytes));
buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf);
pub fn set_date(&self, dst: &mut BytesMut, full: bool) {
// Unsafe: WorkerSetting is !Sync and !Send
let date_bytes = unsafe { &(*self.date.get()).bytes };
if full {
let mut buf: [u8; 39] = [0; 39];
buf[..6].copy_from_slice(b"date: ");
buf[6..35].copy_from_slice(date_bytes);
buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf);
} else {
dst.extend_from_slice(date_bytes);
}
}
pub fn set_date_simple(&self, dst: &mut BytesMut) {
dst.extend_from_slice(&(unsafe { &*self.date.get() }.bytes));
#[allow(dead_code)]
pub(crate) fn connection_rate(&self) -> ConnectionRateTag {
self.conns.connection_rate()
}
}
@ -249,25 +275,52 @@ impl fmt::Write for Date {
}
}
#[derive(Debug)]
pub(crate) struct SharedBytesPool(RefCell<VecDeque<BytesMut>>);
impl SharedBytesPool {
pub fn new() -> SharedBytesPool {
SharedBytesPool(RefCell::new(VecDeque::with_capacity(128)))
}
pub fn get_bytes(&self) -> BytesMut {
if let Some(bytes) = self.0.borrow_mut().pop_front() {
bytes
} else {
BytesMut::new()
}
}
pub fn release_bytes(&self, mut bytes: BytesMut) {
let v = &mut self.0.borrow_mut();
if v.len() < 128 {
bytes.clear();
v.push_front(bytes);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_date_len() {
assert_eq!(
DATE_VALUE_LENGTH,
"Sun, 06 Nov 1994 08:49:37 GMT".len()
);
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
}
#[test]
fn test_date() {
let settings = WorkerSettings::<()>::new(Vec::new(), KeepAlive::Os);
let settings = WorkerSettings::<()>::new(
Vec::new(),
KeepAlive::Os,
ServerSettings::default(),
Connections::default(),
);
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf1);
settings.set_date(&mut buf1, true);
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf2);
settings.set_date(&mut buf2, true);
assert_eq!(buf1, buf2);
}
}

View File

@ -1,147 +0,0 @@
use bytes::{BufMut, BytesMut};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::io;
use std::rc::Rc;
use body::Binary;
/// Internal use only! unsafe
#[derive(Debug)]
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().clear();
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(pool) = self.1.take() {
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(crate) fn get_mut(&self) -> &mut BytesMut {
let r: &BytesMut = self.0.as_ref().unwrap().as_ref();
unsafe { &mut *(r as *const _ as *mut _) }
}
#[inline]
pub fn len(&self) -> usize {
self.0.as_ref().unwrap().len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.as_ref().unwrap().is_empty()
}
#[inline]
pub fn as_ref(&self) -> &[u8] {
self.0.as_ref().unwrap().as_ref()
}
pub fn split_to(&self, n: usize) -> BytesMut {
self.get_mut().split_to(n)
}
pub fn take(&self) -> BytesMut {
self.get_mut().take()
}
#[inline]
pub fn reserve(&self, cnt: usize) {
self.get_mut().reserve(cnt)
}
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
pub fn extend(&self, data: Binary) {
let buf = self.get_mut();
let data = data.as_ref();
buf.reserve(data.len());
SharedBytes::put_slice(buf, data);
}
#[inline]
pub fn extend_from_slice(&self, data: &[u8]) {
let buf = self.get_mut();
buf.reserve(data.len());
SharedBytes::put_slice(buf, data);
}
#[inline]
pub(crate) fn put_slice(buf: &mut BytesMut, src: &[u8]) {
let len = src.len();
unsafe {
buf.bytes_mut()[..len].copy_from_slice(src);
buf.advance_mut(len);
}
}
#[inline]
pub(crate) fn extend_from_slice_(buf: &mut BytesMut, data: &[u8]) {
buf.reserve(data.len());
SharedBytes::put_slice(buf, data);
}
}
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())
}
}
impl io::Write for SharedBytes {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

View File

@ -1,932 +0,0 @@
use std::rc::Rc;
use std::sync::{mpsc as sync_mpsc, Arc};
use std::time::Duration;
use std::{io, net, thread};
use actix::actors::signal;
use actix::prelude::*;
use futures::sync::mpsc;
use futures::{Future, Sink, Stream};
use mio;
use net2::TcpBuilder;
use num_cpus;
use slab::Slab;
use tokio_io::{AsyncRead, AsyncWrite};
#[cfg(feature = "tls")]
use native_tls::TlsAcceptor;
#[cfg(feature = "alpn")]
use openssl::ssl::{AlpnError, SslAcceptorBuilder};
use super::channel::{HttpChannel, WrapperStream};
use super::settings::{ServerSettings, WorkerSettings};
use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker};
use super::{IntoHttpHandler, IoStream, KeepAlive};
use super::{PauseServer, ResumeServer, StopServer};
/// An HTTP Server
pub struct HttpServer<H>
where
H: IntoHttpHandler + 'static,
{
h: Option<Rc<WorkerSettings<H::Handler>>>,
threads: usize,
backlog: i32,
host: Option<String>,
keep_alive: KeepAlive,
factory: Arc<Fn() -> Vec<H> + Send + Sync>,
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
workers: Vec<(usize, Addr<Syn, Worker<H::Handler>>)>,
sockets: Vec<Socket>,
accept: Vec<(mio::SetReadiness, sync_mpsc::Sender<Command>)>,
exit: bool,
shutdown_timeout: u16,
signals: Option<Addr<Syn, signal::ProcessSignals>>,
no_http2: bool,
no_signals: bool,
}
unsafe impl<H> Sync for HttpServer<H>
where
H: IntoHttpHandler,
{
}
unsafe impl<H> Send for HttpServer<H>
where
H: IntoHttpHandler,
{
}
enum ServerCommand {
WorkerDied(usize, Slab<SocketInfo>),
}
impl<H> Actor for HttpServer<H>
where
H: IntoHttpHandler,
{
type Context = Context<Self>;
}
struct Socket {
lst: net::TcpListener,
addr: net::SocketAddr,
tp: StreamHandlerType,
}
impl<H> HttpServer<H>
where
H: IntoHttpHandler + 'static,
{
/// Create new http server with application factory
pub fn new<F, U>(factory: F) -> Self
where
F: Fn() -> U + Sync + Send + 'static,
U: IntoIterator<Item = H> + 'static,
{
let f = move || (factory)().into_iter().collect();
HttpServer {
h: None,
threads: num_cpus::get(),
backlog: 2048,
host: None,
keep_alive: KeepAlive::Os,
factory: Arc::new(f),
workers: Vec::new(),
sockets: Vec::new(),
accept: Vec::new(),
exit: false,
shutdown_timeout: 30,
signals: None,
no_http2: false,
no_signals: false,
}
}
/// Set number of workers to start.
///
/// By default http server uses number of available logical cpu as threads
/// count.
pub fn workers(mut self, num: usize) -> Self {
self.threads = num;
self
}
#[doc(hidden)]
#[deprecated(since = "0.6.0", note = "please use `HttpServer::workers()` instead")]
pub fn threads(self, num: usize) -> Self {
self.workers(num)
}
/// Set the maximum number of pending connections.
///
/// This refers to the number of clients that can be waiting to be served.
/// Exceeding this number results in the client getting an error when
/// attempting to connect. It should only affect servers under significant
/// load.
///
/// Generally set in the 64-2048 range. Default value is 2048.
///
/// This method should be called before `bind()` method call.
pub fn backlog(mut self, num: i32) -> Self {
self.backlog = num;
self
}
/// Set server keep-alive setting.
///
/// By default keep alive is set to a `Os`.
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
self.keep_alive = val.into();
self
}
/// Set server host name.
///
/// Host name is used by application router aa a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
pub fn server_hostname(mut self, val: String) -> Self {
self.host = Some(val);
self
}
/// Send `SystemExit` message to actix system
///
/// `SystemExit` message stops currently running system arbiter and all
/// nested arbiters.
pub fn system_exit(mut self) -> Self {
self.exit = true;
self
}
/// Set alternative address for `ProcessSignals` actor.
pub fn signals(mut self, addr: Addr<Syn, signal::ProcessSignals>) -> Self {
self.signals = Some(addr);
self
}
/// Disable signal handling
pub fn disable_signals(mut self) -> Self {
self.no_signals = true;
self
}
/// Timeout for graceful workers shutdown.
///
/// After receiving a stop signal, workers have this much time to finish
/// serving requests. Workers still alive after the timeout are force
/// dropped.
///
/// By default shutdown timeout sets to 30 seconds.
pub fn shutdown_timeout(mut self, sec: u16) -> Self {
self.shutdown_timeout = sec;
self
}
/// Disable `HTTP/2` support
pub fn no_http2(mut self) -> Self {
self.no_http2 = true;
self
}
/// Get addresses of bound sockets.
pub fn addrs(&self) -> Vec<net::SocketAddr> {
self.sockets.iter().map(|s| s.addr).collect()
}
/// Use listener for accepting incoming connection requests
///
/// HttpServer does not change any configuration for TcpListener,
/// it needs to be configured before passing it to listen() method.
pub fn listen(mut self, lst: net::TcpListener) -> Self {
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Normal,
});
self
}
fn bind2<S: net::ToSocketAddrs>(&mut self, addr: S) -> io::Result<Vec<Socket>> {
let mut err = None;
let mut succ = false;
let mut sockets = Vec::new();
for addr in addr.to_socket_addrs()? {
match create_tcp_listener(addr, self.backlog) {
Ok(lst) => {
succ = true;
let addr = lst.local_addr().unwrap();
sockets.push(Socket {
lst,
addr,
tp: StreamHandlerType::Normal,
});
}
Err(e) => err = Some(e),
}
}
if !succ {
if let Some(e) = err.take() {
Err(e)
} else {
Err(io::Error::new(
io::ErrorKind::Other,
"Can not bind to address.",
))
}
} else {
Ok(sockets)
}
}
/// The socket address to bind
///
/// To mind multiple addresses this method can be call multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets);
Ok(self)
}
#[cfg(feature = "tls")]
/// The ssl socket address to bind
///
/// To mind multiple addresses this method can be call multiple times.
pub fn bind_tls<S: net::ToSocketAddrs>(
mut self, addr: S, acceptor: TlsAcceptor,
) -> io::Result<Self> {
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(|mut s| {
s.tp = StreamHandlerType::Tls(acceptor.clone());
s
}));
Ok(self)
}
#[cfg(feature = "alpn")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_ssl<S: net::ToSocketAddrs>(
mut self, addr: S, mut builder: SslAcceptorBuilder,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";
if protos.windows(3).any(|window| window == H2) {
Ok(b"h2")
} else {
Err(AlpnError::NOACK)
}
});
}
let acceptor = builder.build();
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(|mut s| {
s.tp = StreamHandlerType::Alpn(acceptor.clone());
s
}));
Ok(self)
}
fn start_workers(
&mut self, settings: &ServerSettings, sockets: &Slab<SocketInfo>,
) -> Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)> {
// start workers
let mut workers = Vec::new();
for idx in 0..self.threads {
let s = settings.clone();
let (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>();
let ka = self.keep_alive;
let socks = sockets.clone();
let factory = Arc::clone(&self.factory);
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
let apps: Vec<_> = (*factory)()
.into_iter()
.map(|h| h.into_handler(s.clone()))
.collect();
ctx.add_message_stream(rx);
Worker::new(apps, socks, ka)
});
workers.push((idx, tx));
self.workers.push((idx, addr));
}
info!("Starting {} http workers", self.threads);
workers
}
// subscribe to os signals
fn subscribe_to_signals(&self) -> Option<Addr<Syn, signal::ProcessSignals>> {
if !self.no_signals {
if let Some(ref signals) = self.signals {
Some(signals.clone())
} else {
Some(Arbiter::system_registry().get::<signal::ProcessSignals>())
}
} else {
None
}
}
}
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming connections.
///
/// This method starts number of http handler workers in separate threads.
/// For each address this method starts separate thread which does
/// `accept()` in a loop.
///
/// This methods panics if no socket addresses get bound.
///
/// This method requires to run within properly configured `Actix` system.
///
/// ```rust
/// extern crate actix;
/// extern crate actix_web;
/// use actix_web::{server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system
///
/// server::new(
/// || App::new()
/// .resource("/", |r| r.h(|_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
/// .start();
/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
///
/// let _ = sys.run(); // <- Run actix system, this method actually starts all async processes
/// }
/// ```
pub fn start(mut self) -> Addr<Syn, Self> {
if self.sockets.is_empty() {
panic!("HttpServer::bind() has to be called before start()");
} else {
let (tx, rx) = mpsc::unbounded();
let mut socks = Slab::new();
let mut addrs: Vec<(usize, Socket)> = Vec::new();
for socket in self.sockets.drain(..) {
let entry = socks.vacant_entry();
let token = entry.key();
entry.insert(SocketInfo {
addr: socket.addr,
htype: socket.tp.clone(),
});
addrs.push((token, socket));
}
let settings = ServerSettings::new(Some(addrs[0].1.addr), &self.host, false);
let workers = self.start_workers(&settings, &socks);
// start acceptors threads
for (token, sock) in addrs {
info!("Starting server on http://{}", sock.addr);
self.accept.push(start_accept_thread(
token,
sock,
self.backlog,
tx.clone(),
socks.clone(),
workers.clone(),
));
}
// start http server actor
let signals = self.subscribe_to_signals();
let addr: Addr<Syn, _> = Actor::create(move |ctx| {
ctx.add_stream(rx);
self
});
if let Some(signals) = signals {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
}
addr
}
}
/// Spawn new thread and start listening for incoming connections.
///
/// This method spawns new thread and starts new actix system. Other than
/// that it is similar to `start()` method. This method blocks.
///
/// This methods panics if no socket addresses get bound.
///
/// ```rust,ignore
/// # extern crate futures;
/// # extern crate actix;
/// # extern crate actix_web;
/// # use futures::Future;
/// use actix_web::*;
///
/// fn main() {
/// HttpServer::new(
/// || App::new()
/// .resource("/", |r| r.h(|_| HttpResponse::Ok())))
/// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
/// .run();
/// }
/// ```
pub fn run(mut self) {
self.exit = true;
self.no_signals = false;
let _ = thread::spawn(move || {
let sys = System::new("http-server");
self.start();
let _ = sys.run();
}).join();
}
}
#[doc(hidden)]
#[cfg(feature = "tls")]
#[deprecated(
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_tls` instead"
)]
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming tls connections.
pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result<Addr<Syn, Self>> {
for sock in &mut self.sockets {
match sock.tp {
StreamHandlerType::Normal => (),
_ => continue,
}
sock.tp = StreamHandlerType::Tls(acceptor.clone());
}
Ok(self.start())
}
}
#[doc(hidden)]
#[cfg(feature = "alpn")]
#[deprecated(
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_ssl` instead"
)]
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn start_ssl(
mut self, mut builder: SslAcceptorBuilder,
) -> io::Result<Addr<Syn, Self>> {
// alpn support
if !self.no_http2 {
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";
if protos.windows(3).any(|window| window == H2) {
Ok(b"h2")
} else {
Err(AlpnError::NOACK)
}
});
}
let acceptor = builder.build();
for sock in &mut self.sockets {
match sock.tp {
StreamHandlerType::Normal => (),
_ => continue,
}
sock.tp = StreamHandlerType::Alpn(acceptor.clone());
}
Ok(self.start())
}
}
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming connections from a stream.
///
/// This method uses only one thread for handling incoming connections.
pub fn start_incoming<T, A, S>(mut self, stream: S, secure: bool) -> Addr<Syn, Self>
where
S: Stream<Item = (T, A), Error = io::Error> + 'static,
T: AsyncRead + AsyncWrite + 'static,
A: 'static,
{
// set server settings
let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap();
let settings = ServerSettings::new(Some(addr), &self.host, secure);
let apps: Vec<_> = (*self.factory)()
.into_iter()
.map(|h| h.into_handler(settings.clone()))
.collect();
self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive)));
// start server
let signals = self.subscribe_to_signals();
let addr: Addr<Syn, _> = HttpServer::create(move |ctx| {
ctx.add_message_stream(stream.map_err(|_| ()).map(move |(t, _)| Conn {
io: WrapperStream::new(t),
token: 0,
peer: None,
http2: false,
}));
self
});
if let Some(signals) = signals {
signals.do_send(signal::Subscribe(addr.clone().recipient()))
}
addr
}
}
/// Signals support
/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)`
/// message to `System` actor.
impl<H: IntoHttpHandler> Handler<signal::Signal> for HttpServer<H> {
type Result = ();
fn handle(&mut self, msg: signal::Signal, ctx: &mut Context<Self>) {
match msg.0 {
signal::SignalType::Int => {
info!("SIGINT received, exiting");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: false }, ctx);
}
signal::SignalType::Term => {
info!("SIGTERM received, stopping");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: true }, ctx);
}
signal::SignalType::Quit => {
info!("SIGQUIT received, exiting");
self.exit = true;
Handler::<StopServer>::handle(self, StopServer { graceful: false }, ctx);
}
_ => (),
}
}
}
/// Commands from accept threads
impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
fn finished(&mut self, _: &mut Context<Self>) {}
fn handle(&mut self, msg: ServerCommand, _: &mut Context<Self>) {
match msg {
ServerCommand::WorkerDied(idx, socks) => {
let mut found = false;
for i in 0..self.workers.len() {
if self.workers[i].0 == idx {
self.workers.swap_remove(i);
found = true;
break;
}
}
if found {
error!("Worker has died {:?}, restarting", idx);
let (tx, rx) = mpsc::unbounded::<Conn<net::TcpStream>>();
let mut new_idx = self.workers.len();
'found: loop {
for i in 0..self.workers.len() {
if self.workers[i].0 == new_idx {
new_idx += 1;
continue 'found;
}
}
break;
}
let ka = self.keep_alive;
let factory = Arc::clone(&self.factory);
let settings =
ServerSettings::new(Some(socks[0].addr), &self.host, false);
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
let apps: Vec<_> = (*factory)()
.into_iter()
.map(|h| h.into_handler(settings.clone()))
.collect();
ctx.add_message_stream(rx);
Worker::new(apps, socks, ka)
});
for item in &self.accept {
let _ = item.1.send(Command::Worker(new_idx, tx.clone()));
let _ = item.0.set_readiness(mio::Ready::readable());
}
self.workers.push((new_idx, addr));
}
}
}
}
}
impl<T, H> Handler<Conn<T>> for HttpServer<H>
where
T: IoStream,
H: IntoHttpHandler,
{
type Result = ();
fn handle(&mut self, msg: Conn<T>, _: &mut Context<Self>) -> Self::Result {
Arbiter::handle().spawn(HttpChannel::new(
Rc::clone(self.h.as_ref().unwrap()),
msg.io,
msg.peer,
msg.http2,
));
}
}
impl<H: IntoHttpHandler> Handler<PauseServer> for HttpServer<H> {
type Result = ();
fn handle(&mut self, _: PauseServer, _: &mut Context<Self>) {
for item in &self.accept {
let _ = item.1.send(Command::Pause);
let _ = item.0.set_readiness(mio::Ready::readable());
}
}
}
impl<H: IntoHttpHandler> Handler<ResumeServer> for HttpServer<H> {
type Result = ();
fn handle(&mut self, _: ResumeServer, _: &mut Context<Self>) {
for item in &self.accept {
let _ = item.1.send(Command::Resume);
let _ = item.0.set_readiness(mio::Ready::readable());
}
}
}
impl<H: IntoHttpHandler> Handler<StopServer> for HttpServer<H> {
type Result = actix::Response<(), ()>;
fn handle(&mut self, msg: StopServer, ctx: &mut Context<Self>) -> Self::Result {
// stop accept threads
for item in &self.accept {
let _ = item.1.send(Command::Stop);
let _ = item.0.set_readiness(mio::Ready::readable());
}
// stop workers
let (tx, rx) = mpsc::channel(1);
let dur = if msg.graceful {
Some(Duration::new(u64::from(self.shutdown_timeout), 0))
} else {
None
};
for worker in &self.workers {
let tx2 = tx.clone();
worker
.1
.send(StopWorker { graceful: dur })
.into_actor(self)
.then(move |_, slf, ctx| {
slf.workers.pop();
if slf.workers.is_empty() {
let _ = tx2.send(());
// we need to stop system if server was spawned
if slf.exit {
ctx.run_later(Duration::from_millis(300), |_, _| {
Arbiter::system().do_send(actix::msgs::SystemExit(0))
});
}
}
actix::fut::ok(())
})
.spawn(ctx);
}
if !self.workers.is_empty() {
Response::async(rx.into_future().map(|_| ()).map_err(|_| ()))
} else {
// we need to stop system if server was spawned
if self.exit {
ctx.run_later(Duration::from_millis(300), |_, _| {
Arbiter::system().do_send(actix::msgs::SystemExit(0))
});
}
Response::reply(Ok(()))
}
}
}
enum Command {
Pause,
Resume,
Stop,
Worker(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>),
}
fn start_accept_thread(
token: usize, sock: Socket, backlog: i32, srv: mpsc::UnboundedSender<ServerCommand>,
socks: Slab<SocketInfo>,
mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) {
let (tx, rx) = sync_mpsc::channel();
let (reg, readiness) = mio::Registration::new2();
// start accept thread
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
let _ = thread::Builder::new()
.name(format!("Accept on {}", sock.addr))
.spawn(move || {
const SRV: mio::Token = mio::Token(0);
const CMD: mio::Token = mio::Token(1);
let addr = sock.addr;
let mut server = Some(
mio::net::TcpListener::from_std(sock.lst)
.expect("Can not create mio::net::TcpListener"),
);
// Create a poll instance
let poll = match mio::Poll::new() {
Ok(poll) => poll,
Err(err) => panic!("Can not create mio::Poll: {}", err),
};
// Start listening for incoming connections
if let Some(ref srv) = server {
if let Err(err) =
poll.register(srv, SRV, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register io: {}", err);
}
}
// Start listening for incoming commands
if let Err(err) = poll.register(
&reg,
CMD,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err);
}
// Create storage for events
let mut events = mio::Events::with_capacity(128);
// Sleep on error
let sleep = Duration::from_millis(100);
let mut next = 0;
loop {
if let Err(err) = poll.poll(&mut events, None) {
panic!("Poll error: {}", err);
}
for event in events.iter() {
match event.token() {
SRV => if let Some(ref server) = server {
loop {
match server.accept_std() {
Ok((io, addr)) => {
let mut msg = Conn {
io,
token,
peer: Some(addr),
http2: false,
};
while !workers.is_empty() {
match workers[next].1.unbounded_send(msg) {
Ok(_) => (),
Err(err) => {
let _ = srv.unbounded_send(
ServerCommand::WorkerDied(
workers[next].0,
socks.clone(),
),
);
msg = err.into_inner();
workers.swap_remove(next);
if workers.is_empty() {
error!("No workers");
thread::sleep(sleep);
break;
} else if workers.len() <= next {
next = 0;
}
continue;
}
}
next = (next + 1) % workers.len();
break;
}
}
Err(ref e)
if e.kind() == io::ErrorKind::WouldBlock =>
{
break
}
Err(ref e) if connection_error(e) => continue,
Err(e) => {
error!("Error accepting connection: {}", e);
// sleep after error
thread::sleep(sleep);
break;
}
}
}
},
CMD => match rx.try_recv() {
Ok(cmd) => match cmd {
Command::Pause => if let Some(server) = server.take() {
if let Err(err) = poll.deregister(&server) {
error!(
"Can not deregister server socket {}",
err
);
} else {
info!(
"Paused accepting connections on {}",
addr
);
}
},
Command::Resume => {
let lst = create_tcp_listener(addr, backlog)
.expect("Can not create net::TcpListener");
server = Some(
mio::net::TcpListener::from_std(lst).expect(
"Can not create mio::net::TcpListener",
),
);
if let Some(ref server) = server {
if let Err(err) = poll.register(
server,
SRV,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err);
} else {
info!("Accepting connections on {} has been resumed",
addr);
}
}
}
Command::Stop => {
if let Some(server) = server.take() {
let _ = poll.deregister(&server);
}
return;
}
Command::Worker(idx, addr) => {
workers.push((idx, addr));
}
},
Err(err) => match err {
sync_mpsc::TryRecvError::Empty => (),
sync_mpsc::TryRecvError::Disconnected => {
if let Some(server) = server.take() {
let _ = poll.deregister(&server);
}
return;
}
},
},
_ => unreachable!(),
}
}
}
});
(readiness, tx)
}
fn create_tcp_listener(
addr: net::SocketAddr, backlog: i32,
) -> io::Result<net::TcpListener> {
let builder = match addr {
net::SocketAddr::V4(_) => TcpBuilder::new_v4()?,
net::SocketAddr::V6(_) => TcpBuilder::new_v6()?,
};
builder.reuse_address(true)?;
builder.bind(addr)?;
Ok(builder.listen(backlog)?)
}
/// This function defines errors that are per-connection. Which basically
/// means that if we get this error from `accept()` system call it means
/// next connection might be ready to be accepted.
///
/// All other errors will incur a timeout before next `accept()` is performed.
/// The timeout is useful to handle resource exhaustion errors like ENFILE
/// and EMFILE. Otherwise, could enter into tight loop.
fn connection_error(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::ConnectionAborted
|| e.kind() == io::ErrorKind::ConnectionReset
}

14
src/server/ssl/mod.rs Normal file
View File

@ -0,0 +1,14 @@
#[cfg(feature = "alpn")]
mod openssl;
#[cfg(feature = "alpn")]
pub use self::openssl::OpensslAcceptor;
#[cfg(feature = "tls")]
mod nativetls;
#[cfg(feature = "tls")]
pub use self::nativetls::{NativeTlsAcceptor, TlsStream};
#[cfg(feature = "rust-tls")]
mod rustls;
#[cfg(feature = "rust-tls")]
pub use self::rustls::RustlsAcceptor;

143
src/server/ssl/nativetls.rs Normal file
View File

@ -0,0 +1,143 @@
use std::net::Shutdown;
use std::{io, time};
use futures::{Async, Future, Poll};
use native_tls::{self, HandshakeError, TlsAcceptor};
use tokio_io::{AsyncRead, AsyncWrite};
use server::{AcceptorService, IoStream};
#[derive(Clone)]
/// Support `SSL` connections via native-tls package
///
/// `tls` feature enables `NativeTlsAcceptor` type
pub struct NativeTlsAcceptor {
acceptor: TlsAcceptor,
}
/// A wrapper around an underlying raw stream which implements the TLS or SSL
/// protocol.
///
/// A `TlsStream<S>` represents a handshake that has been completed successfully
/// and both the server and the client are ready for receiving and sending
/// data. Bytes read from a `TlsStream` are decrypted from `S` and bytes written
/// to a `TlsStream` are encrypted when passing through to `S`.
#[derive(Debug)]
pub struct TlsStream<S> {
inner: native_tls::TlsStream<S>,
}
/// Future returned from `NativeTlsAcceptor::accept` which will resolve
/// once the accept handshake has finished.
pub struct Accept<S> {
inner: Option<Result<native_tls::TlsStream<S>, HandshakeError<S>>>,
}
impl NativeTlsAcceptor {
/// Create `NativeTlsAcceptor` instance
pub fn new(acceptor: TlsAcceptor) -> Self {
NativeTlsAcceptor {
acceptor: acceptor.into(),
}
}
}
impl<Io: IoStream> AcceptorService<Io> for NativeTlsAcceptor {
type Accepted = TlsStream<Io>;
type Future = Accept<Io>;
fn scheme(&self) -> &'static str {
"https"
}
fn accept(&self, io: Io) -> Self::Future {
Accept {
inner: Some(self.acceptor.accept(io)),
}
}
}
impl<Io: IoStream> IoStream for TlsStream<Io> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
}
impl<Io: IoStream> Future for Accept<Io> {
type Item = TlsStream<Io>;
type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.inner.take().expect("cannot poll MidHandshake twice") {
Ok(stream) => Ok(TlsStream { inner: stream }.into()),
Err(HandshakeError::Failure(e)) => {
Err(io::Error::new(io::ErrorKind::Other, e))
}
Err(HandshakeError::WouldBlock(s)) => match s.handshake() {
Ok(stream) => Ok(TlsStream { inner: stream }.into()),
Err(HandshakeError::Failure(e)) => {
Err(io::Error::new(io::ErrorKind::Other, e))
}
Err(HandshakeError::WouldBlock(s)) => {
self.inner = Some(Err(HandshakeError::WouldBlock(s)));
Ok(Async::NotReady)
}
},
}
}
}
impl<S> TlsStream<S> {
/// Get access to the internal `native_tls::TlsStream` stream which also
/// transitively allows access to `S`.
pub fn get_ref(&self) -> &native_tls::TlsStream<S> {
&self.inner
}
/// Get mutable access to the internal `native_tls::TlsStream` stream which
/// also transitively allows mutable access to `S`.
pub fn get_mut(&mut self) -> &mut native_tls::TlsStream<S> {
&mut self.inner
}
}
impl<S: io::Read + io::Write> io::Read for TlsStream<S> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<S: io::Read + io::Write> io::Write for TlsStream<S> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<S: AsyncRead + AsyncWrite> AsyncRead for TlsStream<S> {}
impl<S: AsyncRead + AsyncWrite> AsyncWrite for TlsStream<S> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
match self.inner.shutdown() {
Ok(_) => (),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (),
Err(e) => return Err(e),
}
self.inner.get_mut().shutdown()
}
}

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

@ -0,0 +1,96 @@
use std::net::Shutdown;
use std::{io, time};
use futures::{Future, Poll};
use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder};
use tokio_openssl::{AcceptAsync, SslAcceptorExt, SslStream};
use server::{AcceptorService, IoStream, ServerFlags};
#[derive(Clone)]
/// Support `SSL` connections via openssl package
///
/// `alpn` feature enables `OpensslAcceptor` type
pub struct OpensslAcceptor {
acceptor: SslAcceptor,
}
impl OpensslAcceptor {
/// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support.
pub fn new(builder: SslAcceptorBuilder) -> io::Result<Self> {
OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2)
}
/// Create `OpensslAcceptor` with custom server flags.
pub fn with_flags(
mut builder: SslAcceptorBuilder, flags: ServerFlags,
) -> io::Result<Self> {
let mut protos = Vec::new();
if flags.contains(ServerFlags::HTTP1) {
protos.extend(b"\x08http/1.1");
}
if flags.contains(ServerFlags::HTTP2) {
protos.extend(b"\x02h2");
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";
if protos.windows(3).any(|window| window == H2) {
Ok(b"h2")
} else {
Err(AlpnError::NOACK)
}
});
}
if !protos.is_empty() {
builder.set_alpn_protos(&protos)?;
}
Ok(OpensslAcceptor {
acceptor: builder.build(),
})
}
}
pub struct AcceptorFut<Io>(AcceptAsync<Io>);
impl<Io: IoStream> Future for AcceptorFut<Io> {
type Item = SslStream<Io>;
type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.0
.poll()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
}
}
impl<Io: IoStream> AcceptorService<Io> for OpensslAcceptor {
type Accepted = SslStream<Io>;
type Future = AcceptorFut<Io>;
fn scheme(&self) -> &'static str {
"https"
}
fn accept(&self, io: Io) -> Self::Future {
AcceptorFut(SslAcceptorExt::accept_async(&self.acceptor, io))
}
}
impl<T: IoStream> IoStream for SslStream<T> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = self.get_mut().shutdown();
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().get_mut().set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().get_mut().set_linger(dur)
}
}

91
src/server/ssl/rustls.rs Normal file
View File

@ -0,0 +1,91 @@
use std::net::Shutdown;
use std::sync::Arc;
use std::{io, time};
use rustls::{ClientSession, ServerConfig, ServerSession};
use tokio_io::AsyncWrite;
use tokio_rustls::{AcceptAsync, ServerConfigExt, TlsStream};
use server::{AcceptorService, IoStream, ServerFlags};
#[derive(Clone)]
/// Support `SSL` connections via rustls package
///
/// `rust-tls` feature enables `RustlsAcceptor` type
pub struct RustlsAcceptor {
config: Arc<ServerConfig>,
}
impl RustlsAcceptor {
/// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support.
pub fn new(config: ServerConfig) -> Self {
RustlsAcceptor::with_flags(config, ServerFlags::HTTP1 | ServerFlags::HTTP2)
}
/// Create `OpensslAcceptor` with custom server flags.
pub fn with_flags(mut config: ServerConfig, flags: ServerFlags) -> Self {
let mut protos = Vec::new();
if flags.contains(ServerFlags::HTTP2) {
protos.push("h2".to_string());
}
if flags.contains(ServerFlags::HTTP1) {
protos.push("http/1.1".to_string());
}
if !protos.is_empty() {
config.set_protocols(&protos);
}
RustlsAcceptor {
config: Arc::new(config),
}
}
}
impl<Io: IoStream> AcceptorService<Io> for RustlsAcceptor {
type Accepted = TlsStream<Io, ServerSession>;
type Future = AcceptAsync<Io>;
fn scheme(&self) -> &'static str {
"https"
}
fn accept(&self, io: Io) -> Self::Future {
ServerConfigExt::accept_async(&self.config, io)
}
}
impl<Io: IoStream> IoStream for TlsStream<Io, ClientSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
}
impl<Io: IoStream> IoStream for TlsStream<Io, ServerSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
}

View File

@ -1,31 +0,0 @@
use bytes::{BufMut, BytesMut};
use futures::{Async, Poll};
use std::io;
use super::IoStream;
const LW_BUFFER_SIZE: usize = 4096;
const HW_BUFFER_SIZE: usize = 32_768;
pub fn read_from_io<T: IoStream>(
io: &mut T, buf: &mut BytesMut,
) -> Poll<usize, io::Error> {
unsafe {
if buf.remaining_mut() < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE);
}
match io.read(buf.bytes_mut()) {
Ok(n) => {
buf.advance_mut(n);
Ok(Async::Ready(n))
}
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock {
Ok(Async::NotReady)
} else {
Err(e)
}
}
}
}
}

View File

@ -1,44 +1,52 @@
use futures::unsync::oneshot;
use futures::Future;
use net2::TcpStreamExt;
use slab::Slab;
use std::rc::Rc;
use std::{net, time};
use tokio_core::net::TcpStream;
use tokio_core::reactor::Handle;
#[cfg(any(feature = "tls", feature = "alpn"))]
use futures::future;
#[cfg(feature = "tls")]
use native_tls::TlsAcceptor;
#[cfg(feature = "tls")]
use tokio_tls::TlsAcceptorExt;
#[cfg(feature = "alpn")]
use openssl::ssl::SslAcceptor;
#[cfg(feature = "alpn")]
use tokio_openssl::SslAcceptorExt;
use futures::sync::mpsc::{SendError, UnboundedSender};
use futures::sync::oneshot;
use futures::Future;
use actix::msgs::StopArbiter;
use actix::*;
use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response};
use server::channel::HttpChannel;
use server::settings::WorkerSettings;
use server::{HttpHandler, KeepAlive};
use super::server::{Connections, ServiceHandler};
use super::Token;
#[derive(Message)]
pub(crate) struct Conn<T> {
pub io: T,
pub token: usize,
pub handler: Token,
pub token: Token,
pub peer: Option<net::SocketAddr>,
pub http2: bool,
}
pub(crate) struct Socket {
pub lst: net::TcpListener,
pub addr: net::SocketAddr,
pub token: Token,
}
#[derive(Clone)]
pub(crate) struct SocketInfo {
pub addr: net::SocketAddr,
pub htype: StreamHandlerType,
pub(crate) struct WorkerClient {
pub idx: usize,
tx: UnboundedSender<Conn<net::TcpStream>>,
conns: Connections,
}
impl WorkerClient {
pub fn new(
idx: usize, tx: UnboundedSender<Conn<net::TcpStream>>, conns: Connections,
) -> Self {
WorkerClient { idx, tx, conns }
}
pub fn send(
&self, msg: Conn<net::TcpStream>,
) -> Result<(), SendError<Conn<net::TcpStream>>> {
self.tx.unbounded_send(msg)
}
pub fn available(&self) -> bool {
self.conns.available()
}
}
/// Stop worker message. Returns `true` on successful shutdown
@ -55,188 +63,77 @@ impl Message for StopWorker {
///
/// Worker accepts Socket objects via unbounded channel and start requests
/// processing.
pub(crate) struct Worker<H>
where
H: HttpHandler + 'static,
{
settings: Rc<WorkerSettings<H>>,
hnd: Handle,
socks: Slab<SocketInfo>,
tcp_ka: Option<time::Duration>,
pub(crate) struct Worker {
conns: Connections,
handlers: Vec<Box<ServiceHandler>>,
}
impl<H: HttpHandler + 'static> Worker<H> {
pub(crate) fn new(
h: Vec<H>, socks: Slab<SocketInfo>, keep_alive: KeepAlive,
) -> Worker<H> {
let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive {
Some(time::Duration::new(val as u64, 0))
} else {
None
};
impl Actor for Worker {
type Context = Context<Self>;
}
Worker {
settings: Rc::new(WorkerSettings::new(h, keep_alive)),
hnd: Arbiter::handle().clone(),
socks,
tcp_ka,
}
impl Worker {
pub(crate) fn new(conns: Connections, handlers: Vec<Box<ServiceHandler>>) -> Self {
Worker { conns, handlers }
}
fn update_time(&self, ctx: &mut Context<Self>) {
self.settings.update_date();
ctx.run_later(time::Duration::new(1, 0), |slf, ctx| {
slf.update_time(ctx)
});
fn shutdown(&self, force: bool) {
self.handlers.iter().for_each(|h| h.shutdown(force));
}
fn shutdown_timeout(
&self, ctx: &mut Context<Self>, tx: oneshot::Sender<bool>, dur: time::Duration,
&self, ctx: &mut Context<Worker>, tx: oneshot::Sender<bool>, dur: time::Duration,
) {
// sleep for 1 second and then check again
ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| {
let num = slf.settings.num_channels();
let num = slf.conns.num_connections();
if num == 0 {
let _ = tx.send(true);
Arbiter::arbiter().do_send(StopArbiter(0));
Arbiter::current().do_send(StopArbiter(0));
} else if let Some(d) = dur.checked_sub(time::Duration::new(1, 0)) {
slf.shutdown_timeout(ctx, tx, d);
} else {
info!("Force shutdown http worker, {} connections", num);
slf.settings.head().traverse::<TcpStream, H>();
slf.shutdown(true);
let _ = tx.send(false);
Arbiter::arbiter().do_send(StopArbiter(0));
Arbiter::current().do_send(StopArbiter(0));
}
});
}
}
impl<H: 'static> Actor for Worker<H>
where
H: HttpHandler + 'static,
{
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
self.update_time(ctx);
}
}
impl<H> Handler<Conn<net::TcpStream>> for Worker<H>
where
H: HttpHandler + 'static,
{
impl Handler<Conn<net::TcpStream>> for Worker {
type Result = ();
fn handle(&mut self, msg: Conn<net::TcpStream>, _: &mut Context<Self>) {
if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() {
error!("Can not set socket keep-alive option");
}
self.socks.get_mut(msg.token).unwrap().htype.handle(
Rc::clone(&self.settings),
&self.hnd,
msg,
);
self.handlers[msg.handler.0].handle(msg.token, msg.io, msg.peer)
}
}
/// `StopWorker` message handler
impl<H> Handler<StopWorker> for Worker<H>
where
H: HttpHandler + 'static,
{
impl Handler<StopWorker> for Worker {
type Result = Response<bool, ()>;
fn handle(&mut self, msg: StopWorker, ctx: &mut Context<Self>) -> Self::Result {
let num = self.settings.num_channels();
let num = self.conns.num_connections();
if num == 0 {
info!("Shutting down http worker, 0 connections");
Response::reply(Ok(true))
} else if let Some(dur) = msg.graceful {
info!("Graceful http worker shutdown, {} connections", num);
self.shutdown(false);
let (tx, rx) = oneshot::channel();
self.shutdown_timeout(ctx, tx, dur);
Response::async(rx.map_err(|_| ()))
let num = self.conns.num_connections();
if num != 0 {
info!("Graceful http worker shutdown, {} connections", num);
self.shutdown_timeout(ctx, tx, dur);
Response::reply(Ok(true))
} else {
Response::async(rx.map_err(|_| ()))
}
} else {
info!("Force shutdown http worker, {} connections", num);
self.settings.head().traverse::<TcpStream, H>();
self.shutdown(true);
Response::reply(Ok(false))
}
}
}
#[derive(Clone)]
pub(crate) enum StreamHandlerType {
Normal,
#[cfg(feature = "tls")]
Tls(TlsAcceptor),
#[cfg(feature = "alpn")]
Alpn(SslAcceptor),
}
impl StreamHandlerType {
fn handle<H: HttpHandler>(
&mut self, h: Rc<WorkerSettings<H>>, hnd: &Handle, msg: Conn<net::TcpStream>,
) {
match *self {
StreamHandlerType::Normal => {
let _ = msg.io.set_nodelay(true);
let io = TcpStream::from_stream(msg.io, hnd)
.expect("failed to associate TCP stream");
hnd.spawn(HttpChannel::new(h, io, msg.peer, msg.http2));
}
#[cfg(feature = "tls")]
StreamHandlerType::Tls(ref acceptor) => {
let Conn {
io, peer, http2, ..
} = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream");
hnd.spawn(
TlsAcceptorExt::accept_async(acceptor, io).then(move |res| {
match res {
Ok(io) => Arbiter::handle()
.spawn(HttpChannel::new(h, io, peer, http2)),
Err(err) => {
trace!("Error during handling tls connection: {}", err)
}
};
future::result(Ok(()))
}),
);
}
#[cfg(feature = "alpn")]
StreamHandlerType::Alpn(ref acceptor) => {
let Conn { io, peer, .. } = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_stream(io, hnd)
.expect("failed to associate TCP stream");
hnd.spawn(
SslAcceptorExt::accept_async(acceptor, io).then(move |res| {
match res {
Ok(io) => {
let http2 = if let Some(p) =
io.get_ref().ssl().selected_alpn_protocol()
{
p.len() == 2 && &p == b"h2"
} else {
false
};
Arbiter::handle()
.spawn(HttpChannel::new(h, io, peer, http2));
}
Err(err) => {
trace!("Error during handling tls connection: {}", err)
}
};
future::result(Ok(()))
}),
);
}
}
}
}

View File

@ -1,36 +1,43 @@
//! Various helpers for Actix applications to use during testing.
use std::rc::Rc;
use std::str::FromStr;
use std::sync::mpsc;
use std::{net, thread};
use actix::{msgs, Actor, Addr, Arbiter, Syn, System, SystemRunner, Unsync};
use actix_inner::{Actor, Addr, System};
use cookie::Cookie;
use futures::Future;
use http::header::HeaderName;
use http::{HeaderMap, HttpTryFrom, Method, Uri, Version};
use net2::TcpBuilder;
use tokio_core::net::TcpListener;
use tokio_core::reactor::Core;
use tokio::runtime::current_thread::Runtime;
#[cfg(feature = "alpn")]
use openssl::ssl::SslAcceptor;
use openssl::ssl::SslAcceptorBuilder;
#[cfg(feature = "rust-tls")]
use rustls::ServerConfig;
#[cfg(feature = "alpn")]
use server::OpensslAcceptor;
#[cfg(feature = "rust-tls")]
use server::RustlsAcceptor;
use application::{App, HttpApplication};
use body::Binary;
use client::{ClientConnector, ClientRequest, ClientRequestBuilder};
use error::Error;
use handler::{AsyncResultItem, Handler, Responder};
use handler::{AsyncResult, AsyncResultItem, Handler, Responder};
use header::{Header, IntoHeaderValue};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::Middleware;
use param::Params;
use payload::Payload;
use resource::ResourceHandler;
use resource::Resource;
use router::Router;
use server::message::{Request, RequestPool};
use server::{HttpServer, IntoHttpHandler, ServerSettings};
use uri::Url as InnerUrl;
use ws;
/// The `TestServer` type.
@ -41,11 +48,10 @@ use ws;
/// # Examples
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// # use actix_web::*;
/// #
/// # fn my_handler(req: HttpRequest) -> HttpResponse {
/// # fn my_handler(req: &HttpRequest) -> HttpResponse {
/// # HttpResponse::Ok().into()
/// # }
/// #
@ -61,11 +67,9 @@ use ws;
/// ```
pub struct TestServer {
addr: net::SocketAddr,
thread: Option<thread::JoinHandle<()>>,
system: SystemRunner,
server_sys: Addr<Syn, System>,
ssl: bool,
conn: Addr<Unsync, ClientConnector>,
conn: Addr<ClientConnector>,
rt: Runtime,
}
impl TestServer {
@ -108,34 +112,32 @@ impl TestServer {
let (tx, rx) = mpsc::channel();
// run server in separate thread
let join = thread::spawn(move || {
thread::spawn(move || {
let sys = System::new("actix-test-server");
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
let local_addr = tcp.local_addr().unwrap();
let tcp =
TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap();
HttpServer::new(factory)
.disable_signals()
.start_incoming(tcp.incoming(), false);
.listen(tcp)
.start();
tx.send((Arbiter::system(), local_addr)).unwrap();
let _ = sys.run();
tx.send((System::current(), local_addr, TestServer::get_conn()))
.unwrap();
sys.run();
});
let sys = System::new("actix-test");
let (server_sys, addr) = rx.recv().unwrap();
let (system, addr, conn) = rx.recv().unwrap();
System::set_current(system);
TestServer {
addr,
server_sys,
conn,
ssl: false,
conn: TestServer::get_conn(),
thread: Some(join),
system: sys,
rt: Runtime::new().unwrap(),
}
}
fn get_conn() -> Addr<Unsync, ClientConnector> {
fn get_conn() -> Addr<ClientConnector> {
#[cfg(feature = "alpn")]
{
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
@ -144,7 +146,17 @@ impl TestServer {
builder.set_verify(SslVerifyMode::NONE);
ClientConnector::with_connector(builder.build()).start()
}
#[cfg(not(feature = "alpn"))]
#[cfg(all(feature = "rust-tls", not(feature = "alpn")))]
{
use rustls::ClientConfig;
use std::fs::File;
use std::io::BufReader;
let mut config = ClientConfig::new();
let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
config.root_store.add_pem_file(pem_file).unwrap();
ClientConnector::with_connector(config).start()
}
#[cfg(not(any(feature = "alpn", feature = "rust-tls")))]
{
ClientConnector::default().start()
}
@ -169,16 +181,16 @@ impl TestServer {
pub fn url(&self, uri: &str) -> String {
if uri.starts_with('/') {
format!(
"{}://{}{}",
"{}://localhost:{}{}",
if self.ssl { "https" } else { "http" },
self.addr,
self.addr.port(),
uri
)
} else {
format!(
"{}://{}/{}",
"{}://localhost:{}/{}",
if self.ssl { "https" } else { "http" },
self.addr,
self.addr.port(),
uri
)
}
@ -186,10 +198,7 @@ impl TestServer {
/// Stop http server
fn stop(&mut self) {
if let Some(handle) = self.thread.take() {
self.server_sys.do_send(msgs::SystemExit(0));
let _ = handle.join();
}
System::current().stop();
}
/// Execute future on current core
@ -197,17 +206,23 @@ impl TestServer {
where
F: Future<Item = I, Error = E>,
{
self.system.run_until_complete(fut)
self.rt.block_on(fut)
}
/// Connect to websocket server
/// Connect to websocket server at a given path
pub fn ws_at(
&mut self, path: &str,
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
let url = self.url(path);
self.rt
.block_on(ws::Client::with_connector(url, self.conn.clone()).connect())
}
/// Connect to a websocket server
pub fn ws(
&mut self,
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
let url = self.url("/");
self.system.run_until_complete(
ws::Client::with_connector(url, self.conn.clone()).connect(),
)
self.ws_at("/")
}
/// Create `GET` request
@ -217,7 +232,7 @@ impl TestServer {
/// Create `POST` request
pub fn post(&self) -> ClientRequestBuilder {
ClientRequest::get(self.url("/").as_str())
ClientRequest::post(self.url("/").as_str())
}
/// Create `HEAD` request
@ -248,10 +263,13 @@ impl Drop for TestServer {
pub struct TestServerBuilder<S> {
state: Box<Fn() -> S + Sync + Send + 'static>,
#[cfg(feature = "alpn")]
ssl: Option<SslAcceptor>,
ssl: Option<SslAcceptorBuilder>,
#[cfg(feature = "rust-tls")]
rust_ssl: Option<ServerConfig>,
}
impl<S: 'static> TestServerBuilder<S> {
/// Create a new test server
pub fn new<F>(state: F) -> TestServerBuilder<S>
where
F: Fn() -> S + Sync + Send + 'static,
@ -260,16 +278,25 @@ impl<S: 'static> TestServerBuilder<S> {
state: Box::new(state),
#[cfg(feature = "alpn")]
ssl: None,
#[cfg(feature = "rust-tls")]
rust_ssl: None,
}
}
#[cfg(feature = "alpn")]
/// Create ssl server
pub fn ssl(mut self, ssl: SslAcceptor) -> Self {
pub fn ssl(mut self, ssl: SslAcceptorBuilder) -> Self {
self.ssl = Some(ssl);
self
}
#[cfg(feature = "rust-tls")]
/// Create rust tls server
pub fn rustls(mut self, ssl: ServerConfig) -> Self {
self.rust_ssl = Some(ssl);
self
}
#[allow(unused_mut)]
/// Configure test application and run test server
pub fn start<F>(mut self, config: F) -> TestServer
@ -278,66 +305,66 @@ impl<S: 'static> TestServerBuilder<S> {
{
let (tx, rx) = mpsc::channel();
let mut has_ssl = false;
#[cfg(feature = "alpn")]
let ssl = self.ssl.is_some();
#[cfg(not(feature = "alpn"))]
let ssl = false;
{
has_ssl = has_ssl || self.ssl.is_some();
}
#[cfg(feature = "rust-tls")]
{
has_ssl = has_ssl || self.rust_ssl.is_some();
}
// run server in separate thread
let join = thread::spawn(move || {
thread::spawn(move || {
let addr = TestServer::unused_addr();
let sys = System::new("actix-test-server");
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
let local_addr = tcp.local_addr().unwrap();
let tcp =
TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap();
let state = self.state;
let srv = HttpServer::new(move || {
let mut srv = HttpServer::new(move || {
let mut app = TestApp::new(state());
config(&mut app);
vec![app]
}).disable_signals();
}).workers(1)
.disable_signals();
tx.send((System::current(), addr, TestServer::get_conn()))
.unwrap();
#[cfg(feature = "alpn")]
{
use futures::Stream;
use std::io;
use tokio_openssl::SslAcceptorExt;
let ssl = self.ssl.take();
if let Some(ssl) = ssl {
srv.start_incoming(
tcp.incoming().and_then(move |(sock, addr)| {
ssl.accept_async(sock)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
.map(move |s| (s, addr))
}),
false,
);
} else {
srv.start_incoming(tcp.incoming(), false);
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen_with(tcp, OpensslAcceptor::new(ssl).unwrap());
}
}
#[cfg(not(feature = "alpn"))]
#[cfg(feature = "rust-tls")]
{
srv.start_incoming(tcp.incoming(), false);
let ssl = self.rust_ssl.take();
if let Some(ssl) = ssl {
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen_with(tcp, RustlsAcceptor::new(ssl));
}
}
if !has_ssl {
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen(tcp);
}
srv.start();
tx.send((Arbiter::system(), local_addr)).unwrap();
let _ = sys.run();
sys.run();
});
let system = System::new("actix-test");
let (server_sys, addr) = rx.recv().unwrap();
let (system, addr, conn) = rx.recv().unwrap();
System::set_current(system);
TestServer {
addr,
server_sys,
ssl,
system,
conn: TestServer::get_conn(),
thread: Some(join),
conn,
ssl: has_ssl,
rt: Runtime::new().unwrap(),
}
}
}
@ -354,13 +381,12 @@ impl<S: 'static> TestApp<S> {
}
/// Register handler for "/"
pub fn handler<H: Handler<S>>(&mut self, handler: H) {
self.app = Some(
self.app
.take()
.unwrap()
.resource("/", |r| r.h(handler)),
);
pub fn handler<F, R>(&mut self, handler: F)
where
F: Fn(&HttpRequest<S>) -> R + 'static,
R: Responder + 'static,
{
self.app = Some(self.app.take().unwrap().resource("/", |r| r.f(handler)));
}
/// Register middleware
@ -376,7 +402,7 @@ impl<S: 'static> TestApp<S> {
/// to `App::resource()` method.
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut TestApp<S>
where
F: FnOnce(&mut ResourceHandler<S>) -> R + 'static,
F: FnOnce(&mut Resource<S>) -> R + 'static,
{
self.app = Some(self.app.take().unwrap().resource(path, f));
self
@ -386,8 +412,8 @@ impl<S: 'static> TestApp<S> {
impl<S: 'static> IntoHttpHandler for TestApp<S> {
type Handler = HttpApplication<S>;
fn into_handler(mut self, settings: ServerSettings) -> HttpApplication<S> {
self.app.take().unwrap().into_handler(settings)
fn into_handler(mut self) -> HttpApplication<S> {
self.app.take().unwrap().into_handler()
}
}
@ -413,7 +439,7 @@ impl<S: 'static> Iterator for TestApp<S> {
/// # use actix_web::*;
/// 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) {
/// HttpResponse::Ok().into()
/// } else {
@ -423,11 +449,11 @@ impl<S: 'static> Iterator for TestApp<S> {
///
/// fn main() {
/// let resp = TestRequest::with_header("content-type", "text/plain")
/// .run(index).unwrap();
/// .run(&index)
/// .unwrap();
/// assert_eq!(resp.status(), StatusCode::OK);
///
/// let resp = TestRequest::default()
/// .run(index).unwrap();
/// let resp = TestRequest::default().run(&index).unwrap();
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
/// }
/// ```
@ -437,9 +463,10 @@ pub struct TestRequest<S> {
method: Method,
uri: Uri,
headers: HeaderMap,
params: Params<'static>,
params: Params,
cookies: Option<Vec<Cookie<'static>>>,
payload: Option<Payload>,
prefix: u16,
}
impl Default for TestRequest<()> {
@ -453,6 +480,7 @@ impl Default for TestRequest<()> {
params: Params::new(),
cookies: None,
payload: None,
prefix: 0,
}
}
}
@ -490,6 +518,7 @@ impl<S: 'static> TestRequest<S> {
params: Params::new(),
cookies: None,
payload: None,
prefix: 0,
}
}
@ -537,7 +566,7 @@ impl<S: 'static> TestRequest<S> {
/// Set request path pattern parameter
pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
self.params.add(name, value);
self.params.add_static(name, value);
self
}
@ -550,6 +579,12 @@ impl<S: 'static> TestRequest<S> {
self
}
/// Set request's prefix
pub fn prefix(mut self, prefix: u16) -> Self {
self.prefix = prefix;
self
}
/// Complete request creation and generate `HttpRequest` instance
pub fn finish(self) -> HttpRequest<S> {
let TestRequest {
@ -558,50 +593,103 @@ impl<S: 'static> TestRequest<S> {
uri,
version,
headers,
params,
mut params,
cookies,
payload,
prefix,
} = self;
let req = HttpRequest::new(method, uri, version, headers, payload);
req.as_mut().cookies = cookies;
req.as_mut().params = params;
let (router, _) = Router::new::<S>("/", ServerSettings::default(), Vec::new());
req.with_state(Rc::new(state), router)
let router = Router::<()>::default();
let pool = RequestPool::pool(ServerSettings::default());
let mut req = RequestPool::get(pool);
{
let inner = req.inner_mut();
inner.method = method;
inner.url = InnerUrl::new(uri);
inner.version = version;
inner.headers = headers;
*inner.payload.borrow_mut() = payload;
}
params.set_url(req.url().clone());
let mut info = router.route_info_params(0, params);
info.set_prefix(prefix);
let mut req = HttpRequest::new(req, Rc::new(state), info);
req.set_cookies(cookies);
req
}
#[cfg(test)]
/// Complete request creation and generate `HttpRequest` instance
pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest<S> {
pub(crate) fn finish_with_router(self, router: Router<S>) -> HttpRequest<S> {
let TestRequest {
state,
method,
uri,
version,
headers,
params,
mut params,
cookies,
payload,
prefix,
} = self;
let req = HttpRequest::new(method, uri, version, headers, payload);
req.as_mut().cookies = cookies;
req.as_mut().params = params;
req.with_state(Rc::new(state), router)
let pool = RequestPool::pool(ServerSettings::default());
let mut req = RequestPool::get(pool);
{
let inner = req.inner_mut();
inner.method = method;
inner.url = InnerUrl::new(uri);
inner.version = version;
inner.headers = headers;
*inner.payload.borrow_mut() = payload;
}
params.set_url(req.url().clone());
let mut info = router.route_info_params(0, params);
info.set_prefix(prefix);
let mut req = HttpRequest::new(req, Rc::new(state), info);
req.set_cookies(cookies);
req
}
/// Complete request creation and generate server `Request` instance
pub fn request(self) -> Request {
let TestRequest {
method,
uri,
version,
headers,
payload,
..
} = self;
let pool = RequestPool::pool(ServerSettings::default());
let mut req = RequestPool::get(pool);
{
let inner = req.inner_mut();
inner.method = method;
inner.url = InnerUrl::new(uri);
inner.version = version;
inner.headers = headers;
*inner.payload.borrow_mut() = payload;
}
req
}
/// This method generates `HttpRequest` instance and runs handler
/// with generated request.
///
/// This method panics is handler returns actor or async result.
pub fn run<H: Handler<S>>(self, mut h: H) -> Result<HttpResponse, Error> {
pub fn run<H: Handler<S>>(self, h: &H) -> Result<HttpResponse, Error> {
let req = self.finish();
let resp = h.handle(req.clone());
let resp = h.handle(&req);
match resp.respond_to(&req) {
Ok(resp) => match resp.into().into() {
AsyncResultItem::Ok(resp) => Ok(resp),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(_) => panic!("Async handler is not supported."),
AsyncResultItem::Future(fut) => {
let mut sys = System::new("test");
sys.block_on(fut)
}
},
Err(err) => Err(err.into()),
}
@ -621,8 +709,8 @@ impl<S: 'static> TestRequest<S> {
let req = self.finish();
let fut = h(req.clone());
let mut core = Core::new().unwrap();
match core.run(fut) {
let mut sys = System::new("test");
match sys.block_on(fut) {
Ok(r) => match r.respond_to(&req) {
Ok(reply) => match reply.into().into() {
AsyncResultItem::Ok(resp) => Ok(resp),
@ -633,4 +721,45 @@ impl<S: 'static> TestRequest<S> {
Err(err) => Err(err),
}
}
/// This method generates `HttpRequest` instance and executes handler
pub fn run_async_result<F, R, I, E>(self, f: F) -> Result<I, E>
where
F: FnOnce(&HttpRequest<S>) -> R,
R: Into<AsyncResult<I, E>>,
{
let req = self.finish();
let res = f(&req);
match res.into().into() {
AsyncResultItem::Ok(resp) => Ok(resp),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(fut) => {
let mut sys = System::new("test");
sys.block_on(fut)
}
}
}
/// This method generates `HttpRequest` instance and executes handler
pub fn execute<F, R>(self, f: F) -> Result<HttpResponse, Error>
where
F: FnOnce(&HttpRequest<S>) -> R,
R: Responder + 'static,
{
let req = self.finish();
let resp = f(&req);
match resp.respond_to(&req) {
Ok(resp) => match resp.into().into() {
AsyncResultItem::Ok(resp) => Ok(resp),
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Future(fut) => {
let mut sys = System::new("test");
sys.block_on(fut)
}
},
Err(err) => Err(err.into()),
}
}
}

View File

@ -1,4 +1,5 @@
use http::Uri;
use std::rc::Rc;
#[allow(dead_code)]
const GEN_DELIMS: &[u8] = b":/?#[]@";
@ -34,10 +35,10 @@ lazy_static! {
static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") };
}
#[derive(Default)]
#[derive(Default, Clone, Debug)]
pub(crate) struct Url {
uri: Uri,
path: Option<String>,
path: Option<Rc<String>>,
}
impl Url {
@ -95,7 +96,7 @@ impl Quoter {
q
}
pub fn requote(&self, val: &[u8]) -> Option<String> {
pub fn requote(&self, val: &[u8]) -> Option<Rc<String>> {
let mut has_pct = 0;
let mut pct = [b'%', 0, 0];
let mut idx = 0;
@ -145,7 +146,9 @@ impl Quoter {
}
if let Some(data) = cloned {
Some(unsafe { String::from_utf8_unchecked(data) })
// Unsafe: we get data from http::Uri, which does utf-8 checks already
// this code only decodes valid pct encoded values
Some(unsafe { Rc::new(String::from_utf8_unchecked(data)) })
} else {
None
}

View File

@ -1,7 +1,5 @@
use futures::{Async, Future, Poll};
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use error::Error;
@ -9,84 +7,95 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
pub struct ExtractorConfig<S: 'static, T: FromRequest<S>> {
cfg: Rc<UnsafeCell<T::Config>>,
trait FnWith<T, R>: 'static {
fn call_with(self: &Self, T) -> R;
}
impl<S: 'static, T: FromRequest<S>> Default for ExtractorConfig<S, T> {
fn default() -> Self {
ExtractorConfig {
cfg: Rc::new(UnsafeCell::new(T::Config::default())),
}
impl<T, R, F: Fn(T) -> R + 'static> FnWith<T, R> for F {
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local))]
fn call_with(self: &Self, arg: T) -> R {
(*self)(arg)
}
}
impl<S: 'static, T: FromRequest<S>> Clone for ExtractorConfig<S, T> {
fn clone(&self) -> Self {
ExtractorConfig {
cfg: Rc::clone(&self.cfg),
}
}
}
impl<S: 'static, T: FromRequest<S>> AsRef<T::Config> for ExtractorConfig<S, T> {
fn as_ref(&self) -> &T::Config {
unsafe { &*self.cfg.get() }
}
}
impl<S: 'static, T: FromRequest<S>> Deref for ExtractorConfig<S, T> {
type Target = T::Config;
fn deref(&self) -> &T::Config {
unsafe { &*self.cfg.get() }
}
}
impl<S: 'static, T: FromRequest<S>> DerefMut for ExtractorConfig<S, T> {
fn deref_mut(&mut self) -> &mut T::Config {
unsafe { &mut *self.cfg.get() }
}
}
pub struct With<T, S, F, R>
#[doc(hidden)]
pub trait WithFactory<T, S, R>: 'static
where
T: FromRequest<S>,
R: Responder,
{
fn create(self) -> With<T, S, R>;
fn create_with_config(self, T::Config) -> With<T, S, R>;
}
#[doc(hidden)]
pub trait WithAsyncFactory<T, S, R, I, E>: 'static
where
T: FromRequest<S>,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<Error>,
{
fn create(self) -> WithAsync<T, S, R, I, E>;
fn create_with_config(self, T::Config) -> WithAsync<T, S, R, I, E>;
}
// impl<T1, T2, T3, S, F, R> WithFactory<(T1, T2, T3), S, R> for F
// where F: Fn(T1, T2, T3) -> R + 'static,
// T1: FromRequest<S> + 'static,
// T2: FromRequest<S> + 'static,
// T3: FromRequest<S> + 'static,
// R: Responder + 'static,
// S: 'static,
// {
// fn create(self) -> With<(T1, T2, T3), S, R> {
// With::new(move |(t1, t2, t3)| (self)(t1, t2, t3), (
// T1::Config::default(), T2::Config::default(), T3::Config::default()))
// }
// fn create_with_config(self, cfg: (T1::Config, T2::Config, T3::Config,)) -> With<(T1, T2, T3), S, R> {
// With::new(move |(t1, t2, t3)| (self)(t1, t2, t3), cfg)
// }
// }
#[doc(hidden)]
pub struct With<T, S, R>
where
F: Fn(T) -> R,
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
_s: PhantomData<S>,
}
impl<T, S, F, R> With<T, S, F, R>
impl<T, S, R> With<T, S, R>
where
F: Fn(T) -> R,
T: FromRequest<S>,
S: 'static,
{
pub fn new(f: F, cfg: ExtractorConfig<S, T>) -> Self {
pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
With {
cfg,
hnd: Rc::new(UnsafeCell::new(f)),
cfg: Rc::new(cfg),
hnd: Rc::new(f),
_s: PhantomData,
}
}
}
impl<T, S, F, R> Handler<S> for With<T, S, F, R>
impl<T, S, R> Handler<S> for With<T, S, R>
where
F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut {
req,
req: req.clone(),
started: false,
hnd: Rc::clone(&self.hnd),
cfg: self.cfg.clone(),
@ -102,24 +111,22 @@ where
}
}
struct WithHandlerFut<T, S, F, R>
struct WithHandlerFut<T, S, R>
where
F: Fn(T) -> R,
R: Responder,
T: FromRequest<S> + 'static,
S: 'static,
{
started: bool,
hnd: Rc<UnsafeCell<F>>,
cfg: ExtractorConfig<S, T>,
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T, S, F, R> Future for WithHandlerFut<T, S, F, R>
impl<T, S, R> Future for WithHandlerFut<T, S, R>
where
F: Fn(T) -> R,
R: Responder + 'static,
T: FromRequest<S> + 'static,
S: 'static,
@ -150,8 +157,7 @@ where
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)(item).respond_to(&self.req) {
let item = match self.hnd.as_ref().call_with(item).respond_to(&self.req) {
Ok(item) => item.into(),
Err(e) => return Err(e.into()),
};
@ -167,93 +173,89 @@ where
}
}
pub struct With2<T1, T2, S, F, R>
#[doc(hidden)]
pub struct WithAsync<T, S, R, I, E>
where
F: Fn(T1, T2) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<E>,
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
_s: PhantomData<S>,
}
impl<T1, T2, S, F, R> With2<T1, T2, S, F, R>
impl<T, S, R, I, E> WithAsync<T, S, R, I, E>
where
F: Fn(T1, T2) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<Error>,
T: FromRequest<S>,
S: 'static,
{
pub fn new(
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
) -> Self {
With2 {
hnd: Rc::new(UnsafeCell::new(f)),
cfg1,
cfg2,
pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
WithAsync {
cfg: Rc::new(cfg),
hnd: Rc::new(f),
_s: PhantomData,
}
}
}
impl<T1, T2, S, F, R> Handler<S> for With2<T1, T2, S, F, R>
impl<T, S, R, I, E> Handler<S> for WithAsync<T, S, R, I, E>
where
F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut2 {
req,
fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
let mut fut = WithAsyncHandlerFut {
req: req.clone(),
started: false,
hnd: Rc::clone(&self.hnd),
cfg1: self.cfg1.clone(),
cfg2: self.cfg2.clone(),
item: None,
cfg: Rc::clone(&self.cfg),
fut1: None,
fut2: None,
fut3: None,
};
match fut.poll() {
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::ok(e),
Err(e) => AsyncResult::err(e),
}
}
}
struct WithHandlerFut2<T1, T2, S, F, R>
struct WithAsyncHandlerFut<T, S, R, I, E>
where
F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
started: bool,
hnd: Rc<UnsafeCell<F>>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
req: HttpRequest<S>,
item: Option<T1>,
fut1: Option<Box<Future<Item = T1, Error = Error>>>,
fut2: Option<Box<Future<Item = T2, Error = Error>>>,
fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<R>,
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T1, T2, S, F, R> Future for WithHandlerFut2<T1, T2, S, F, R>
impl<T, S, R, I, E> Future for WithAsyncHandlerFut<T, S, R, I, E>
where
F: Fn(T1, T2) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
S: 'static,
{
type Item = HttpResponse;
@ -264,357 +266,137 @@ where
return fut.poll();
}
if !self.started {
self.started = true;
let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
let item1 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
};
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item = Some(item1);
self.fut2 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item1, item2).respond_to(&self.req) {
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut3 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
if self.fut1.is_some() {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => {
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item = Some(item);
self.fut2 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item, item2).respond_to(&self.req) {
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut3 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
Async::NotReady => return Ok(Async::NotReady),
}
}
let item = match self.fut2.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)(self.item.take().unwrap(), item).respond_to(&self.req) {
Ok(item) => item.into(),
Err(err) => return Err(err.into()),
};
match item.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => self.fut3 = Some(fut),
}
self.poll()
}
}
pub struct With3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
cfg3: ExtractorConfig<S, T3>,
_s: PhantomData<S>,
}
impl<T1, T2, T3, S, F, R> With3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
pub fn new(
f: F, cfg1: ExtractorConfig<S, T1>, cfg2: ExtractorConfig<S, T2>,
cfg3: ExtractorConfig<S, T3>,
) -> Self {
With3 {
hnd: Rc::new(UnsafeCell::new(f)),
cfg1,
cfg2,
cfg3,
_s: PhantomData,
}
}
}
impl<T1, T2, T3, S, F, R> Handler<S> for With3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S>,
T2: FromRequest<S>,
T3: FromRequest<S>,
T1: 'static,
T2: 'static,
T3: 'static,
S: 'static,
{
type Result = AsyncResult<HttpResponse>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
let mut fut = WithHandlerFut3 {
req,
hnd: Rc::clone(&self.hnd),
cfg1: self.cfg1.clone(),
cfg2: self.cfg2.clone(),
cfg3: self.cfg3.clone(),
started: false,
item1: None,
item2: None,
fut1: None,
fut2: None,
fut3: None,
fut4: None,
};
match fut.poll() {
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
Err(e) => AsyncResult::err(e),
}
}
}
struct WithHandlerFut3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
hnd: Rc<UnsafeCell<F>>,
req: HttpRequest<S>,
cfg1: ExtractorConfig<S, T1>,
cfg2: ExtractorConfig<S, T2>,
cfg3: ExtractorConfig<S, T3>,
started: bool,
item1: Option<T1>,
item2: Option<T2>,
fut1: Option<Box<Future<Item = T1, Error = Error>>>,
fut2: Option<Box<Future<Item = T2, Error = Error>>>,
fut3: Option<Box<Future<Item = T3, Error = Error>>>,
fut4: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T1, T2, T3, S, F, R> Future for WithHandlerFut3<T1, T2, T3, S, F, R>
where
F: Fn(T1, T2, T3) -> R + 'static,
R: Responder + 'static,
T1: FromRequest<S> + 'static,
T2: FromRequest<S> + 'static,
T3: FromRequest<S> + 'static,
S: 'static,
{
type Item = HttpResponse;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut4 {
return fut.poll();
}
if !self.started {
self.started = true;
let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into();
let item1 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
};
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item1 = Some(item1);
self.fut2 = Some(fut);
return self.poll();
}
};
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
let item3 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item1 = Some(item1);
self.item2 = Some(item2);
self.fut3 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(item1, item2, item3).respond_to(&self.req) {
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut4 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
if self.fut1.is_some() {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => {
self.item1 = Some(item);
self.fut1.take();
let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into();
let item2 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut2 = Some(fut);
return self.poll();
}
};
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
let item3 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.item2 = Some(item2);
self.fut3 = Some(fut);
return self.poll();
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(self.item1.take().unwrap(), item2, item3)
.respond_to(&self.req)
{
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut4 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
}
Async::NotReady => return Ok(Async::NotReady),
}
}
if self.fut2.is_some() {
match self.fut2.as_mut().unwrap().poll()? {
Async::Ready(item) => {
self.fut2.take();
let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into();
let item3 = match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
return match self.fut2.as_mut().unwrap().poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(r)) => match r.respond_to(&self.req) {
Ok(r) => match r.into().into() {
AsyncResultItem::Err(err) => Err(err),
AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.item2 = Some(item);
self.fut3 = Some(fut);
return self.poll();
self.poll()
}
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
match (*hnd)(self.item1.take().unwrap(), item, item3)
.respond_to(&self.req)
{
Ok(item) => match item.into().into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => {
self.fut4 = Some(fut);
return self.poll();
}
},
Err(e) => return Err(e.into()),
}
},
Err(e) => Err(e.into()),
},
Err(e) => Err(e.into()),
};
}
let item = if !self.started {
self.started = true;
let reply = T::from_request(&self.req, self.cfg.as_ref()).into();
match reply.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(msg) => msg,
AsyncResultItem::Future(fut) => {
self.fut1 = Some(fut);
return self.poll();
}
}
} else {
match self.fut1.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
}
}
let item = match self.fut3.as_mut().unwrap().poll()? {
Async::Ready(item) => item,
Async::NotReady => return Ok(Async::NotReady),
};
let hnd: &mut F = unsafe { &mut *self.hnd.get() };
let item = match (*hnd)(
self.item1.take().unwrap(),
self.item2.take().unwrap(),
item,
).respond_to(&self.req)
{
Ok(item) => item.into(),
Err(err) => return Err(err.into()),
};
match item.into() {
AsyncResultItem::Err(err) => return Err(err),
AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)),
AsyncResultItem::Future(fut) => self.fut4 = Some(fut),
}
self.fut2 = Some(self.hnd.as_ref().call_with(item));
self.poll()
}
}
macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => {
impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func
where Func: Fn($($T,)+) -> Res + 'static,
$($T: FromRequest<State> + 'static,)+
Res: Responder + 'static,
State: 'static,
{
fn create(self) -> With<($($T,)+), State, Res> {
With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
}
fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> {
With::new(move |($($n,)+)| (self)($($n,)+), cfg)
}
}
});
macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => {
impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func
where Func: Fn($($T,)+) -> Res + 'static,
$($T: FromRequest<State> + 'static,)+
Res: Future<Item=Item, Error=Err>,
Item: Responder + 'static,
Err: Into<Error>,
State: 'static,
{
fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> {
WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
}
fn create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> {
WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg)
}
}
});
with_factory_tuple!((a, A));
with_factory_tuple!((a, A), (b, B));
with_factory_tuple!((a, A), (b, B), (c, C));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
with_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H)
);
with_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H),
(i, I)
);
with_async_factory_tuple!((a, A));
with_async_factory_tuple!((a, A), (b, B));
with_async_factory_tuple!((a, A), (b, B), (c, C));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
with_async_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H)
);
with_async_factory_tuple!(
(a, A),
(b, B),
(c, C),
(d, D),
(e, E),
(f, F),
(g, G),
(h, H),
(i, I)
);

View File

@ -1,5 +1,5 @@
//! Http client request
use std::cell::UnsafeCell;
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use std::{fmt, io, str};
@ -7,57 +7,73 @@ use std::{fmt, io, str};
use base64;
use bytes::Bytes;
use cookie::Cookie;
use futures::unsync::mpsc::{unbounded, UnboundedSender};
use futures::sync::mpsc::{unbounded, UnboundedSender};
use futures::{Async, Future, Poll, Stream};
use http::header::{self, HeaderName, HeaderValue};
use http::{Error as HttpError, HttpTryFrom, StatusCode};
use rand;
use sha1::Sha1;
use actix::prelude::*;
use actix::{Addr, SystemService};
use body::{Binary, Body};
use error::{Error, UrlParseError};
use header::IntoHeaderValue;
use httpmessage::HttpMessage;
use payload::PayloadHelper;
use payload::PayloadBuffer;
use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse,
HttpResponseParserError, SendRequest, SendRequestError};
use client::{
ClientConnector, ClientRequest, ClientRequestBuilder, HttpResponseParserError,
Pipeline, SendRequest, SendRequestError,
};
use super::frame::Frame;
use super::frame::{Frame, FramedMessage};
use super::proto::{CloseReason, OpCode};
use super::{Message, ProtocolError, WsWriter};
/// Websocket client error
#[derive(Fail, Debug)]
pub enum ClientError {
/// Invalid url
#[fail(display = "Invalid url")]
InvalidUrl,
/// Invalid response status
#[fail(display = "Invalid response status")]
InvalidResponseStatus(StatusCode),
/// Invalid upgrade header
#[fail(display = "Invalid upgrade header")]
InvalidUpgradeHeader,
/// Invalid connection header
#[fail(display = "Invalid connection header")]
InvalidConnectionHeader(HeaderValue),
/// Missing CONNECTION header
#[fail(display = "Missing CONNECTION header")]
MissingConnectionHeader,
/// Missing SEC-WEBSOCKET-ACCEPT header
#[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")]
MissingWebSocketAcceptHeader,
/// Invalid challenge response
#[fail(display = "Invalid challenge response")]
InvalidChallengeResponse(String, HeaderValue),
/// Http parsing error
#[fail(display = "Http parsing error")]
Http(Error),
/// Url parsing error
#[fail(display = "Url parsing error")]
Url(UrlParseError),
/// Response parsing error
#[fail(display = "Response parsing error")]
ResponseParseError(HttpResponseParserError),
/// Send request error
#[fail(display = "{}", _0)]
SendRequest(SendRequestError),
/// Protocol error
#[fail(display = "{}", _0)]
Protocol(#[cause] ProtocolError),
/// IO Error
#[fail(display = "{}", _0)]
Io(io::Error),
/// "Disconnected"
#[fail(display = "Disconnected")]
Disconnected,
}
@ -102,15 +118,16 @@ impl From<HttpResponseParserError> for ClientError {
///
/// Example of `WebSocket` client usage is available in
/// [websocket example](
/// https://github.com/actix/actix-web/blob/master/examples/websocket/src/client.rs#L24)
/// https://github.com/actix/examples/blob/master/websocket/src/client.rs#L24)
pub struct Client {
request: ClientRequestBuilder,
err: Option<ClientError>,
http_err: Option<HttpError>,
origin: Option<HeaderValue>,
protocols: Option<String>,
conn: Addr<Unsync, ClientConnector>,
conn: Addr<ClientConnector>,
max_size: usize,
no_masking: bool,
}
impl Client {
@ -120,9 +137,7 @@ impl Client {
}
/// Create new websocket connection with custom `ClientConnector`
pub fn with_connector<S: AsRef<str>>(
uri: S, conn: Addr<Unsync, ClientConnector>,
) -> Client {
pub fn with_connector<S: AsRef<str>>(uri: S, conn: Addr<ClientConnector>) -> Client {
let mut cl = Client {
request: ClientRequest::build(),
err: None,
@ -130,6 +145,7 @@ impl Client {
origin: None,
protocols: None,
max_size: 65_536,
no_masking: false,
conn,
};
cl.request.uri(uri.as_ref());
@ -184,6 +200,12 @@ impl Client {
self
}
/// Disable payload masking. By default ws client masks frame payload.
pub fn no_masking(mut self) -> Self {
self.no_masking = true;
self
}
/// Set request header
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
@ -218,8 +240,7 @@ impl Client {
self.request.upgrade();
self.request.set_header(header::UPGRADE, "websocket");
self.request.set_header(header::CONNECTION, "upgrade");
self.request
.set_header(header::SEC_WEBSOCKET_VERSION, "13");
self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13");
self.request.with_connector(self.conn.clone());
if let Some(protocols) = self.protocols.take() {
@ -235,7 +256,9 @@ impl Client {
return ClientHandshake::error(ClientError::InvalidUrl);
}
if let Some(scheme) = request.uri().scheme_part() {
if scheme != "http" && scheme != "https" && scheme != "ws"
if scheme != "http"
&& scheme != "https"
&& scheme != "ws"
&& scheme != "wss"
{
return ClientHandshake::error(ClientError::InvalidUrl);
@ -245,14 +268,14 @@ impl Client {
}
// start handshake
ClientHandshake::new(request, self.max_size)
ClientHandshake::new(request, self.max_size, self.no_masking)
}
}
}
struct Inner {
tx: UnboundedSender<Bytes>,
rx: PayloadHelper<ClientResponse>,
rx: PayloadBuffer<Box<Pipeline>>,
closed: bool,
}
@ -266,10 +289,13 @@ pub struct ClientHandshake {
key: String,
error: Option<ClientError>,
max_size: usize,
no_masking: bool,
}
impl ClientHandshake {
fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake {
fn new(
mut request: ClientRequest, max_size: usize, no_masking: bool,
) -> ClientHandshake {
// Generate a random key for the `Sec-WebSocket-Key` header.
// a base64-encoded (see Section 4 of [RFC4648]) value that,
// when decoded, is 16 bytes in length (RFC 6455)
@ -289,6 +315,7 @@ impl ClientHandshake {
ClientHandshake {
key,
max_size,
no_masking,
request: Some(request.send()),
tx: Some(tx),
error: None,
@ -302,6 +329,7 @@ impl ClientHandshake {
tx: None,
error: Some(err),
max_size: 0,
no_masking: false,
}
}
@ -394,10 +422,7 @@ impl Future for ClientHandshake {
encoded,
key
);
return Err(ClientError::InvalidChallengeResponse(
encoded,
key.clone(),
));
return Err(ClientError::InvalidChallengeResponse(encoded, key.clone()));
}
} else {
trace!("Missing SEC-WEBSOCKET-ACCEPT header");
@ -406,24 +431,27 @@ impl Future for ClientHandshake {
let inner = Inner {
tx: self.tx.take().unwrap(),
rx: PayloadHelper::new(resp),
rx: PayloadBuffer::new(resp.payload()),
closed: false,
};
let inner = Rc::new(UnsafeCell::new(inner));
let inner = Rc::new(RefCell::new(inner));
Ok(Async::Ready((
ClientReader {
inner: Rc::clone(&inner),
max_size: self.max_size,
no_masking: self.no_masking,
},
ClientWriter { inner },
)))
}
}
/// Websocket reader client
pub struct ClientReader {
inner: Rc<UnsafeCell<Inner>>,
inner: Rc<RefCell<Inner>>,
max_size: usize,
no_masking: bool,
}
impl fmt::Debug for ClientReader {
@ -432,26 +460,20 @@ impl fmt::Debug for ClientReader {
}
}
impl ClientReader {
#[inline]
fn as_mut(&mut self) -> &mut Inner {
unsafe { &mut *self.inner.get() }
}
}
impl Stream for ClientReader {
type Item = Message;
type Error = ProtocolError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
let max_size = self.max_size;
let inner = self.as_mut();
let no_masking = self.no_masking;
let mut inner = self.inner.borrow_mut();
if inner.closed {
return Ok(Async::Ready(None));
}
// read
match Frame::parse(&mut inner.rx, false, max_size) {
match Frame::parse(&mut inner.rx, no_masking, max_size) {
Ok(Async::Ready(Some(frame))) => {
let (_finished, opcode, payload) = frame.unpack();
@ -499,26 +521,23 @@ impl Stream for ClientReader {
}
}
/// Websocket writer client
pub struct ClientWriter {
inner: Rc<UnsafeCell<Inner>>,
inner: Rc<RefCell<Inner>>,
}
impl ClientWriter {
/// Write payload
#[inline]
fn write(&mut self, mut data: Binary) {
if !self.as_mut().closed {
let _ = self.as_mut().tx.unbounded_send(data.take());
fn write(&mut self, mut data: FramedMessage) {
let inner = self.inner.borrow_mut();
if !inner.closed {
let _ = inner.tx.unbounded_send(data.0.take());
} else {
warn!("Trying to write to disconnected response");
}
}
#[inline]
fn as_mut(&mut self) -> &mut Inner {
unsafe { &mut *self.inner.get() }
}
/// Send text frame
#[inline]
pub fn text<T: Into<Binary>>(&mut self, text: T) {
@ -534,23 +553,13 @@ impl ClientWriter {
/// Send ping frame
#[inline]
pub fn ping(&mut self, message: &str) {
self.write(Frame::message(
Vec::from(message),
OpCode::Ping,
true,
true,
));
self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true));
}
/// Send pong frame
#[inline]
pub fn pong(&mut self, message: &str) {
self.write(Frame::message(
Vec::from(message),
OpCode::Pong,
true,
true,
));
self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true));
}
/// Send close frame

View File

@ -1,28 +1,35 @@
use futures::sync::oneshot::Sender;
use futures::unsync::oneshot;
use futures::{Async, Poll};
extern crate actix;
use bytes::Bytes;
use futures::sync::oneshot::{self, Sender};
use futures::{Async, Future, Poll, Stream};
use smallvec::SmallVec;
use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope};
use actix::fut::ActorFuture;
use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message,
SpawnHandle, Syn, Unsync};
use self::actix::dev::{
AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler,
ToEnvelope,
};
use self::actix::fut::ActorFuture;
use self::actix::{
Actor, ActorContext, ActorState, Addr, AsyncContext, Handler,
Message as ActixMessage, SpawnHandle,
};
use body::{Binary, Body};
use context::{ActorHttpContext, Drain, Frame as ContextFrame};
use error::{Error, ErrorInternalServerError};
use error::{Error, ErrorInternalServerError, PayloadError};
use httprequest::HttpRequest;
use ws::frame::Frame;
use ws::frame::{Frame, FramedMessage};
use ws::proto::{CloseReason, OpCode};
use ws::WsWriter;
use ws::{Message, ProtocolError, WsStream, WsWriter};
/// Execution context for `WebSockets` actors
pub struct WebsocketContext<A, S = ()>
where
A: Actor<Context = WebsocketContext<A, S>>,
{
inner: ContextImpl<A>,
inner: ContextParts<A>,
stream: Option<SmallVec<[ContextFrame; 4]>>,
request: HttpRequest<S>,
disconnected: bool,
@ -64,7 +71,8 @@ where
#[doc(hidden)]
#[inline]
fn waiting(&self) -> bool {
self.inner.waiting() || self.inner.state() == ActorState::Stopping
self.inner.waiting()
|| self.inner.state() == ActorState::Stopping
|| self.inner.state() == ActorState::Stopped
}
@ -72,16 +80,9 @@ where
self.inner.cancel_future(handle)
}
#[doc(hidden)]
#[inline]
fn unsync_address(&mut self) -> Addr<Unsync, A> {
self.inner.unsync_address()
}
#[doc(hidden)]
#[inline]
fn sync_address(&mut self) -> Addr<Syn, A> {
self.inner.sync_address()
fn address(&self) -> Addr<A> {
self.inner.address()
}
}
@ -90,23 +91,39 @@ where
A: Actor<Context = Self>,
{
#[inline]
pub fn new(req: HttpRequest<S>, actor: A) -> WebsocketContext<A, S> {
WebsocketContext::from_request(req).actor(actor)
}
pub fn from_request(req: HttpRequest<S>) -> WebsocketContext<A, S> {
WebsocketContext {
inner: ContextImpl::new(None),
/// Create a new Websocket context from a request and an actor
pub fn create<P>(req: HttpRequest<S>, actor: A, stream: WsStream<P>) -> Body
where
A: StreamHandler<Message, ProtocolError>,
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
let mb = Mailbox::default();
let mut ctx = WebsocketContext {
inner: ContextParts::new(mb.sender_producer()),
stream: None,
request: req,
disconnected: false,
}
};
ctx.add_stream(stream);
Body::Actor(Box::new(WebsocketContextFut::new(ctx, actor, mb)))
}
#[inline]
pub fn actor(mut self, actor: A) -> WebsocketContext<A, S> {
self.inner.set_actor(actor);
self
/// Create a new Websocket context
pub fn with_factory<F>(req: HttpRequest<S>, f: F) -> Body
where
F: FnOnce(&mut Self) -> A + 'static,
{
let mb = Mailbox::default();
let mut ctx = WebsocketContext {
inner: ContextParts::new(mb.sender_producer()),
stream: None,
request: req,
disconnected: false,
};
let act = f(&mut ctx);
Body::Actor(Box::new(WebsocketContextFut::new(ctx, act, mb)))
}
}
@ -115,15 +132,19 @@ where
A: Actor<Context = Self>,
{
/// Write payload
///
/// This is a low-level function that accepts framed messages that should
/// be created using `Frame::message()`. If you want to send text or binary
/// data you should prefer the `text()` or `binary()` convenience functions
/// that handle the framing for you.
#[inline]
fn write(&mut self, data: Binary) {
pub fn write_raw(&mut self, data: FramedMessage) {
if !self.disconnected {
if self.stream.is_none() {
self.stream = Some(SmallVec::new());
}
let stream = self.stream.as_mut().unwrap();
stream.push(ContextFrame::Chunk(Some(data)));
self.inner.modify();
stream.push(ContextFrame::Chunk(Some(data.0)));
} else {
warn!("Trying to write to disconnected response");
}
@ -144,7 +165,6 @@ where
/// Returns drain future
pub fn drain(&mut self) -> Drain<A> {
let (tx, rx) = oneshot::channel();
self.inner.modify();
self.add_frame(ContextFrame::Drain(tx));
Drain::new(rx)
}
@ -152,19 +172,19 @@ where
/// Send text frame
#[inline]
pub fn text<T: Into<Binary>>(&mut self, text: T) {
self.write(Frame::message(text.into(), OpCode::Text, true, false));
self.write_raw(Frame::message(text.into(), OpCode::Text, true, false));
}
/// Send binary frame
#[inline]
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
self.write(Frame::message(data, OpCode::Binary, true, false));
self.write_raw(Frame::message(data, OpCode::Binary, true, false));
}
/// Send ping frame
#[inline]
pub fn ping(&mut self, message: &str) {
self.write(Frame::message(
self.write_raw(Frame::message(
Vec::from(message),
OpCode::Ping,
true,
@ -175,7 +195,7 @@ where
/// Send pong frame
#[inline]
pub fn pong(&mut self, message: &str) {
self.write(Frame::message(
self.write_raw(Frame::message(
Vec::from(message),
OpCode::Pong,
true,
@ -186,7 +206,7 @@ where
/// Send close frame
#[inline]
pub fn close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(reason, false));
self.write_raw(Frame::close(reason, false));
}
/// Check if connection still open
@ -203,7 +223,6 @@ where
if let Some(s) = self.stream.as_mut() {
s.push(frame)
}
self.inner.modify();
}
/// Handle of the running future
@ -250,28 +269,52 @@ where
}
}
impl<A, S> ActorHttpContext for WebsocketContext<A, S>
impl<A, S> AsyncContextParts<A> for WebsocketContext<A, S>
where
A: Actor<Context = Self>,
{
fn parts(&mut self) -> &mut ContextParts<A> {
&mut self.inner
}
}
struct WebsocketContextFut<A, S>
where
A: Actor<Context = WebsocketContext<A, S>>,
{
fut: ContextFut<A, WebsocketContext<A, S>>,
}
impl<A, S> WebsocketContextFut<A, S>
where
A: Actor<Context = WebsocketContext<A, S>>,
{
fn new(ctx: WebsocketContext<A, S>, act: A, mailbox: Mailbox<A>) -> Self {
let fut = ContextFut::new(ctx, act, mailbox);
WebsocketContextFut { fut }
}
}
impl<A, S> ActorHttpContext for WebsocketContextFut<A, S>
where
A: Actor<Context = WebsocketContext<A, S>>,
S: 'static,
{
#[inline]
fn disconnected(&mut self) {
self.disconnected = true;
self.stop();
self.fut.ctx().disconnected = true;
self.fut.ctx().stop();
}
fn poll(&mut self) -> Poll<Option<SmallVec<[ContextFrame; 4]>>, Error> {
let ctx: &mut WebsocketContext<A, S> = unsafe { &mut *(self as *mut _) };
if self.inner.alive() && self.inner.poll(ctx).is_err() {
if self.fut.alive() && self.fut.poll().is_err() {
return Err(ErrorInternalServerError("error"));
}
// frames
if let Some(data) = self.stream.take() {
if let Some(data) = self.fut.ctx().stream.take() {
Ok(Async::Ready(Some(data)))
} else if self.inner.alive() {
} else if self.fut.alive() {
Ok(Async::NotReady)
} else {
Ok(Async::Ready(None))
@ -279,23 +322,13 @@ where
}
}
impl<A, M, S> ToEnvelope<Syn, A, M> for WebsocketContext<A, S>
impl<A, M, S> ToEnvelope<A, M> for WebsocketContext<A, S>
where
A: Actor<Context = WebsocketContext<A, S>> + Handler<M>,
M: Message + Send + 'static,
M: ActixMessage + Send + 'static,
M::Result: Send,
{
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> SyncEnvelope<A> {
SyncEnvelope::new(msg, tx)
}
}
impl<A, S> From<WebsocketContext<A, S>> for Body
where
A: Actor<Context = WebsocketContext<A, S>>,
S: 'static,
{
fn from(ctx: WebsocketContext<A, S>) -> Body {
Body::Actor(Box::new(ctx))
fn pack(msg: M, tx: Option<Sender<M::Result>>) -> Envelope<A> {
Envelope::new(msg, tx)
}
}

View File

@ -1,13 +1,12 @@
#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
use byteorder::{BigEndian, ByteOrder, NetworkEndian};
use byteorder::{ByteOrder, LittleEndian, NetworkEndian};
use bytes::{BufMut, Bytes, BytesMut};
use futures::{Async, Poll, Stream};
use rand;
use std::{fmt, ptr};
use std::fmt;
use body::Binary;
use error::PayloadError;
use payload::PayloadHelper;
use payload::PayloadBuffer;
use ws::mask::apply_mask;
use ws::proto::{CloseCode, CloseReason, OpCode};
@ -29,7 +28,7 @@ impl Frame {
/// Create a new Close control frame.
#[inline]
pub fn close(reason: Option<CloseReason>, genmask: bool) -> Binary {
pub fn close(reason: Option<CloseReason>, genmask: bool) -> FramedMessage {
let payload = match reason {
None => Vec::new(),
Some(reason) => {
@ -49,7 +48,7 @@ impl Frame {
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
fn read_copy_md<S>(
pl: &mut PayloadHelper<S>, server: bool, max_size: usize,
pl: &mut PayloadBuffer<S>, server: bool, max_size: usize,
) -> Poll<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError>
where
S: Stream<Item = Bytes, Error = PayloadError>,
@ -95,9 +94,12 @@ impl Frame {
Async::Ready(None) => return Ok(Async::Ready(None)),
Async::NotReady => return Ok(Async::NotReady),
};
let len = NetworkEndian::read_uint(&buf[idx..], 8) as usize;
let len = NetworkEndian::read_uint(&buf[idx..], 8);
if len > max_size as u64 {
return Err(ProtocolError::Overflow);
}
idx += 8;
len
len as usize
} else {
len as usize
};
@ -115,17 +117,14 @@ impl Frame {
};
let mask: &[u8] = &buf[idx..idx + 4];
let mask_u32: u32 =
unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) };
let mask_u32 = LittleEndian::read_u32(mask);
idx += 4;
Some(mask_u32)
} else {
None
};
Ok(Async::Ready(Some((
idx, finished, opcode, length, mask,
))))
Ok(Async::Ready(Some((idx, finished, opcode, length, mask))))
}
fn read_chunk_md(
@ -169,9 +168,12 @@ impl Frame {
if chunk_len < 10 {
return Ok(Async::NotReady);
}
let len = NetworkEndian::read_uint(&chunk[idx..], 8) as usize;
let len = NetworkEndian::read_uint(&chunk[idx..], 8);
if len > max_size as u64 {
return Err(ProtocolError::Overflow);
}
idx += 8;
len
len as usize
} else {
len as usize
};
@ -187,8 +189,7 @@ impl Frame {
}
let mask: &[u8] = &chunk[idx..idx + 4];
let mask_u32: u32 =
unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) };
let mask_u32 = LittleEndian::read_u32(mask);
idx += 4;
Some(mask_u32)
} else {
@ -200,7 +201,7 @@ impl Frame {
/// Parse the input stream into a frame.
pub fn parse<S>(
pl: &mut PayloadHelper<S>, server: bool, max_size: usize,
pl: &mut PayloadBuffer<S>, server: bool, max_size: usize,
) -> Poll<Option<Frame>, ProtocolError>
where
S: Stream<Item = Bytes, Error = PayloadError>,
@ -229,7 +230,7 @@ impl Frame {
}
// remove prefix
pl.drop_payload(idx);
pl.drop_bytes(idx);
// no need for body
if length == 0 {
@ -259,13 +260,14 @@ impl Frame {
}
// unmask
if let Some(mask) = mask {
let p: &mut [u8] = unsafe {
let ptr: &[u8] = &data;
&mut *(ptr as *const _ as *mut _)
};
apply_mask(p, mask);
}
let data = if let Some(mask) = mask {
let mut buf = BytesMut::new();
buf.extend_from_slice(&data);
apply_mask(&mut buf, mask);
buf.freeze()
} else {
data
};
Ok(Async::Ready(Some(Frame {
finished,
@ -277,17 +279,14 @@ impl Frame {
/// Parse the payload of a close frame.
pub fn parse_close_payload(payload: &Binary) -> Option<CloseReason> {
if payload.len() >= 2 {
let raw_code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
let raw_code = NetworkEndian::read_u16(payload.as_ref());
let code = CloseCode::from(raw_code);
let description = if payload.len() > 2 {
Some(String::from_utf8_lossy(&payload.as_ref()[2..]).into())
} else {
None
};
Some(CloseReason {
code,
description,
})
Some(CloseReason { code, description })
} else {
None
}
@ -296,7 +295,7 @@ impl Frame {
/// Generate binary representation
pub fn message<B: Into<Binary>>(
data: B, code: OpCode, finished: bool, genmask: bool,
) -> Binary {
) -> FramedMessage {
let payload = data.into();
let one: u8 = if finished {
0x80 | Into::<u8>::into(code)
@ -317,39 +316,28 @@ impl Frame {
} else if payload_len <= 65_535 {
let mut buf = BytesMut::with_capacity(p_len + 4);
buf.put_slice(&[one, two | 126]);
{
let buf_mut = unsafe { buf.bytes_mut() };
BigEndian::write_u16(&mut buf_mut[..2], payload_len as u16);
}
unsafe { buf.advance_mut(2) };
buf.put_u16_be(payload_len as u16);
buf
} else {
let mut buf = BytesMut::with_capacity(p_len + 10);
buf.put_slice(&[one, two | 127]);
{
let buf_mut = unsafe { buf.bytes_mut() };
BigEndian::write_u64(&mut buf_mut[..8], payload_len as u64);
}
unsafe { buf.advance_mut(8) };
buf.put_u64_be(payload_len as u64);
buf
};
if genmask {
let binary = if genmask {
let mask = rand::random::<u32>();
unsafe {
{
let buf_mut = buf.bytes_mut();
*(buf_mut as *mut _ as *mut u32) = mask;
buf_mut[4..payload_len + 4].copy_from_slice(payload.as_ref());
apply_mask(&mut buf_mut[4..], mask);
}
buf.advance_mut(payload_len + 4);
}
buf.put_u32_le(mask);
buf.extend_from_slice(payload.as_ref());
let pos = buf.len() - payload_len;
apply_mask(&mut buf[pos..], mask);
buf.into()
} else {
buf.put_slice(payload.as_ref());
buf.into()
}
};
FramedMessage(binary)
}
}
@ -386,6 +374,10 @@ impl fmt::Display for Frame {
}
}
/// `WebSocket` message with framing.
#[derive(Debug)]
pub struct FramedMessage(pub(crate) Binary);
#[cfg(test)]
mod tests {
use super::*;
@ -407,14 +399,14 @@ mod tests {
#[test]
fn test_parse() {
let mut buf = PayloadHelper::new(once(Ok(BytesMut::from(
let mut buf = PayloadBuffer::new(once(Ok(BytesMut::from(
&[0b0000_0001u8, 0b0000_0001u8][..],
).freeze())));
assert!(is_none(&Frame::parse(&mut buf, false, 1024)));
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
buf.extend(b"1");
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished);
@ -425,7 +417,7 @@ mod tests {
#[test]
fn test_parse_length0() {
let buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]);
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished);
@ -436,13 +428,13 @@ mod tests {
#[test]
fn test_parse_length2() {
let buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
assert!(is_none(&Frame::parse(&mut buf, false, 1024)));
let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]);
buf.extend(&[0u8, 4u8][..]);
buf.extend(b"1234");
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished);
@ -453,13 +445,13 @@ mod tests {
#[test]
fn test_parse_length4() {
let buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
assert!(is_none(&Frame::parse(&mut buf, false, 1024)));
let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]);
buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]);
buf.extend(b"1234");
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
let frame = extract(Frame::parse(&mut buf, false, 1024));
assert!(!frame.finished);
@ -472,7 +464,7 @@ mod tests {
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]);
buf.extend(b"0001");
buf.extend(b"1");
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
assert!(Frame::parse(&mut buf, false, 1024).is_err());
@ -486,7 +478,7 @@ mod tests {
fn test_parse_frame_no_mask() {
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]);
buf.extend(&[1u8]);
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
assert!(Frame::parse(&mut buf, true, 1024).is_err());
@ -500,7 +492,7 @@ mod tests {
fn test_parse_frame_max_size() {
let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]);
buf.extend(&[1u8, 1u8]);
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
let mut buf = PayloadBuffer::new(once(Ok(buf.freeze())));
assert!(Frame::parse(&mut buf, true, 1).is_err());
@ -516,7 +508,7 @@ mod tests {
let mut v = vec![137u8, 4u8];
v.extend(b"data");
assert_eq!(frame, v.into());
assert_eq!(frame.0, v.into());
}
#[test]
@ -525,7 +517,7 @@ mod tests {
let mut v = vec![138u8, 4u8];
v.extend(b"data");
assert_eq!(frame, v.into());
assert_eq!(frame.0, v.into());
}
#[test]
@ -535,12 +527,12 @@ mod tests {
let mut v = vec![136u8, 6u8, 3u8, 232u8];
v.extend(b"data");
assert_eq!(frame, v.into());
assert_eq!(frame.0, v.into());
}
#[test]
fn test_empty_close_frame() {
let frame = Frame::close(None, false);
assert_eq!(frame, vec![0x88, 0x00].into());
assert_eq!(frame.0, vec![0x88, 0x00].into());
}
}

View File

@ -1,119 +1,123 @@
//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs)
#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
use std::cmp::min;
use std::mem::uninitialized;
use std::ptr::copy_nonoverlapping;
use std::slice;
/// Mask/unmask a frame.
#[inline]
pub fn apply_mask(buf: &mut [u8], mask: u32) {
apply_mask_fast32(buf, mask)
}
// Holds a slice guaranteed to be shorter than 8 bytes
struct ShortSlice<'a>(&'a mut [u8]);
/// A safe unoptimized mask application.
#[inline]
#[allow(dead_code)]
fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) {
for (i, byte) in buf.iter_mut().enumerate() {
*byte ^= mask[i & 3];
impl<'a> ShortSlice<'a> {
unsafe fn new(slice: &'a mut [u8]) -> Self {
// Sanity check for debug builds
debug_assert!(slice.len() < 8);
ShortSlice(slice)
}
fn len(&self) -> usize {
self.0.len()
}
}
/// Faster version of `apply_mask()` which operates on 8-byte blocks.
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))]
fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) {
let mut ptr = buf.as_mut_ptr();
let mut len = buf.len();
pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
// Extend the mask to 64 bits
let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64);
// Split the buffer into three segments
let (head, mid, tail) = align_buf(buf);
// Possible first unaligned block.
let head = min(len, (8 - (ptr as usize & 0x7)) & 0x3);
let mask_u32 = if head > 0 {
let n = if head > 4 { head - 4 } else { head };
let mask_u32 = if n > 0 {
unsafe {
xor_mem(ptr, mask_u32, n);
ptr = ptr.offset(head as isize);
}
len -= n;
if cfg!(target_endian = "big") {
mask_u32.rotate_left(8 * n as u32)
} else {
mask_u32.rotate_right(8 * n as u32)
}
// Initial unaligned segment
let head_len = head.len();
if head_len > 0 {
xor_short(head, mask_u64);
if cfg!(target_endian = "big") {
mask_u64 = mask_u64.rotate_left(8 * head_len as u32);
} else {
mask_u32
};
if head > 4 {
unsafe {
*(ptr as *mut u32) ^= mask_u32;
ptr = ptr.offset(4);
len -= 4;
}
}
mask_u32
} else {
mask_u32
};
if len > 0 {
debug_assert_eq!(ptr as usize % 4, 0);
}
// Properly aligned middle of the data.
if len >= 8 {
let mut mask_u64 = mask_u32 as u64;
mask_u64 = mask_u64 << 32 | mask_u32 as u64;
while len >= 8 {
unsafe {
*(ptr as *mut u64) ^= mask_u64;
ptr = ptr.offset(8);
len -= 8;
}
mask_u64 = mask_u64.rotate_right(8 * head_len as u32);
}
}
while len >= 4 {
unsafe {
*(ptr as *mut u32) ^= mask_u32;
ptr = ptr.offset(4);
len -= 4;
}
// Aligned segment
for v in mid {
*v ^= mask_u64;
}
// Possible last block.
if len > 0 {
unsafe {
xor_mem(ptr, mask_u32, len);
}
// Final unaligned segment
if tail.len() > 0 {
xor_short(tail, mask_u64);
}
}
#[inline]
// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so
// inefficient, it could be done better. The compiler does not see that len is
// limited to 3.
unsafe fn xor_mem(ptr: *mut u8, mask: u32, len: usize) {
let mut b: u32 = uninitialized();
#[allow(trivial_casts)]
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
b ^= mask;
#[allow(trivial_casts)]
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len);
// inefficient, it could be done better. The compiler does not understand that
// a `ShortSlice` must be smaller than a u64.
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
fn xor_short(buf: ShortSlice, mask: u64) {
// Unsafe: we know that a `ShortSlice` fits in a u64
unsafe {
let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len());
let mut b: u64 = 0;
#[allow(trivial_casts)]
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
b ^= mask;
#[allow(trivial_casts)]
copy_nonoverlapping(&b as *const _ as *const u8, ptr, len);
}
}
#[inline]
// Unsafe: caller must ensure the buffer has the correct size and alignment
unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
// Assert correct size and alignment in debug builds
debug_assert!(buf.len().trailing_zeros() >= 3);
debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3);
slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3)
}
#[inline]
// Splits a slice into three parts: an unaligned short head and tail, plus an aligned
// u64 mid section.
fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) {
let start_ptr = buf.as_ptr() as usize;
let end_ptr = start_ptr + buf.len();
// Round *up* to next aligned boundary for start
let start_aligned = (start_ptr + 7) & !0x7;
// Round *down* to last aligned boundary for end
let end_aligned = end_ptr & !0x7;
if end_aligned >= start_aligned {
// We have our three segments (head, mid, tail)
let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr);
let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr);
// Unsafe: we know the middle section is correctly aligned, and the outer
// sections are smaller than 8 bytes
unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) }
} else {
// We didn't cross even one aligned boundary!
// Unsafe: The outer sections are smaller than 8 bytes
unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) }
}
}
#[cfg(test)]
mod tests {
use super::{apply_mask_fallback, apply_mask_fast32};
use std::ptr;
use super::apply_mask;
use byteorder::{ByteOrder, LittleEndian};
/// A safe unoptimized mask application.
fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) {
for (i, byte) in buf.iter_mut().enumerate() {
*byte ^= mask[i & 3];
}
}
#[test]
fn test_apply_mask() {
let mask = [0x6d, 0xb6, 0xb2, 0x80];
let mask_u32: u32 = unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) };
let mask_u32: u32 = LittleEndian::read_u32(&mask);
let unmasked = vec![
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
@ -126,7 +130,7 @@ mod tests {
apply_mask_fallback(&mut masked, &mask);
let mut masked_fast = unmasked.clone();
apply_mask_fast32(&mut masked_fast, mask_u32);
apply_mask(&mut masked_fast, mask_u32);
assert_eq!(masked, masked_fast);
}
@ -137,7 +141,7 @@ mod tests {
apply_mask_fallback(&mut masked[1..], &mask);
let mut masked_fast = unmasked.clone();
apply_mask_fast32(&mut masked_fast[1..], mask_u32);
apply_mask(&mut masked_fast[1..], mask_u32);
assert_eq!(masked, masked_fast);
}

View File

@ -7,14 +7,13 @@
//! ## Example
//!
//! ```rust
//! # extern crate actix;
//! # extern crate actix_web;
//! # use actix::*;
//! # use actix_web::actix::*;
//! # use actix_web::*;
//! use actix_web::{ws, HttpRequest, HttpResponse};
//!
//! // do websocket handshake and start actor
//! fn ws_index(req: HttpRequest) -> Result<HttpResponse> {
//! fn ws_index(req: &HttpRequest) -> Result<HttpResponse> {
//! ws::start(req, Ws)
//! }
//!
@ -26,7 +25,6 @@
//!
//! // Handler for ws::Message messages
//! impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
//!
//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
//! match msg {
//! ws::Message::Ping(msg) => ctx.pong(&msg),
@ -47,14 +45,14 @@ use bytes::Bytes;
use futures::{Async, Poll, Stream};
use http::{header, Method, StatusCode};
use actix::{Actor, AsyncContext, StreamHandler};
use super::actix::{Actor, StreamHandler};
use body::Binary;
use error::{Error, PayloadError, ResponseError};
use httpmessage::HttpMessage;
use httprequest::HttpRequest;
use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder};
use payload::PayloadHelper;
use payload::PayloadBuffer;
mod client;
mod context;
@ -62,9 +60,11 @@ mod frame;
mod mask;
mod proto;
pub use self::client::{Client, ClientError, ClientHandshake, ClientReader, ClientWriter};
pub use self::client::{
Client, ClientError, ClientHandshake, ClientReader, ClientWriter,
};
pub use self::context::WebsocketContext;
pub use self::frame::Frame;
pub use self::frame::{Frame, FramedMessage};
pub use self::proto::{CloseCode, CloseReason, OpCode};
/// Websocket protocol errors
@ -158,26 +158,29 @@ impl ResponseError for HandshakeError {
/// `WebSocket` Message
#[derive(Debug, PartialEq, Message)]
pub enum Message {
/// Text message
Text(String),
/// Binary message
Binary(Binary),
/// Ping message
Ping(String),
/// Pong message
Pong(String),
/// Close message with optional reason
Close(Option<CloseReason>),
}
/// Do websocket handshake and start actor
pub fn start<A, S>(req: HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
pub fn start<A, S>(req: &HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
where
A: Actor<Context = WebsocketContext<A, S>> + StreamHandler<Message, ProtocolError>,
S: 'static,
{
let mut resp = handshake(&req)?;
let stream = WsStream::new(req.clone());
let mut resp = handshake(req)?;
let stream = WsStream::new(req.payload());
let mut ctx = WebsocketContext::new(req, actor);
ctx.add_stream(stream);
Ok(resp.body(ctx))
let body = WebsocketContext::create(req.clone(), actor, stream);
Ok(resp.body(body))
}
/// Prepare `WebSocket` handshake response.
@ -216,9 +219,7 @@ pub fn handshake<S>(
}
// check supported version
if !req.headers()
.contains_key(header::SEC_WEBSOCKET_VERSION)
{
if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
return Err(HandshakeError::NoVersionHeader);
}
let supported_ver = {
@ -251,7 +252,7 @@ pub fn handshake<S>(
/// Maps `Payload` stream into stream of `ws::Message` items
pub struct WsStream<S> {
rx: PayloadHelper<S>,
rx: PayloadBuffer<S>,
closed: bool,
max_size: usize,
}
@ -263,7 +264,7 @@ where
/// Create new websocket frames stream
pub fn new(stream: S) -> WsStream<S> {
WsStream {
rx: PayloadHelper::new(stream),
rx: PayloadBuffer::new(stream),
closed: false,
max_size: 65_536,
}
@ -357,164 +358,100 @@ pub trait WsWriter {
#[cfg(test)]
mod tests {
use super::*;
use http::{header, HeaderMap, Method, Uri, Version};
use std::str::FromStr;
use http::{header, Method};
use test::TestRequest;
#[test]
fn test_handshake() {
let req = HttpRequest::new(
Method::POST,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
let req = TestRequest::default().method(Method::POST).finish();
assert_eq!(
HandshakeError::GetMethodRequired,
handshake(&req).err().unwrap()
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
HeaderMap::new(),
None,
);
let req = TestRequest::default().finish();
assert_eq!(
HandshakeError::NoWebsocketUpgrade,
handshake(&req).err().unwrap()
);
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("test"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let req = TestRequest::default()
.header(header::UPGRADE, header::HeaderValue::from_static("test"))
.finish();
assert_eq!(
HandshakeError::NoWebsocketUpgrade,
handshake(&req).err().unwrap()
);
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
).finish();
assert_eq!(
HandshakeError::NoConnectionUpgrade,
handshake(&req).err().unwrap()
);
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
);
headers.insert(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
).header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
).finish();
assert_eq!(
HandshakeError::NoVersionHeader,
handshake(&req).err().unwrap()
);
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
);
headers.insert(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
);
headers.insert(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("5"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
).header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
).header(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("5"),
).finish();
assert_eq!(
HandshakeError::UnsupportedVersion,
handshake(&req).err().unwrap()
);
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
);
headers.insert(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
);
headers.insert(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
).header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
).header(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
).finish();
assert_eq!(
HandshakeError::BadWebsocketKey,
handshake(&req).err().unwrap()
);
let mut headers = HeaderMap::new();
headers.insert(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
);
headers.insert(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
);
headers.insert(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
);
headers.insert(
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
);
let req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let req = TestRequest::default()
.header(
header::UPGRADE,
header::HeaderValue::from_static("websocket"),
).header(
header::CONNECTION,
header::HeaderValue::from_static("upgrade"),
).header(
header::SEC_WEBSOCKET_VERSION,
header::HeaderValue::from_static("13"),
).header(
header::SEC_WEBSOCKET_KEY,
header::HeaderValue::from_static("13"),
).finish();
assert_eq!(
StatusCode::SWITCHING_PROTOCOLS,
handshake(&req).unwrap().finish().status()

View File

@ -180,8 +180,11 @@ impl From<u16> for CloseCode {
}
#[derive(Debug, Eq, PartialEq, Clone)]
/// Reason for closing the connection
pub struct CloseReason {
/// Exit code
pub code: CloseCode,
/// Optional description of the exit code
pub description: Option<String>,
}

View File

@ -1,31 +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=
MIIFXTCCA0WgAwIBAgIJAJ3tqfd0MLLNMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDRjELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBh
bnkxDDAKBgNVBAsMA09yZzEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMB4XDTE4
MDcyOTE4MDgzNFoXDTE5MDcyOTE4MDgzNFowYTELMAkGA1UEBhMCVVMxCzAJBgNV
BAgMAkNGMQswCQYDVQQHDAJTRjEQMA4GA1UECgwHQ29tcGFueTEMMAoGA1UECwwD
T3JnMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDZbMgDYilVH1Nv0QWEhOXG6ETmtjZrdLqrNg3NBWBIWCDF
cQ+fyTWxARx6vkF8A/3zpJyTcfQW8HgG38jw/A61QKaHBxzwq0HlNwY9Hh+Neeuk
L4wgrlQ0uTC7IEMrOJjNN0GPyRQVfVbGa8QcSCpOg85l8GCxLvVwkBH/M5atoMtJ
EzniNfK+gtk3hOL2tBqBCu9NDjhXPnJwNDLtTG1tQaHUJW/r281Wvv9I46H83DkU
05lYtauh0bKh5znCH2KpFmBGqJNRzou3tXZFZzZfaCPBJPZR8j5TjoinehpDtkPh
4CSio0PF2eIFkDKRUbdz/327HgEARJMXx+w1yHpS2JwHFgy5O76i68/Smx8j3DDA
2WIkOYAJFRMH0CBHKdsvUDOGpCgN+xv3whl+N806nCfC4vCkwA+FuB3ko11logng
dvr+y0jIUSU4THF3dMDEXYayF3+WrUlw0cBnUNJdXky85ZP81aBfBsjNSBDx4iL4
e4NhfZRS5oHpHy1t3nYfuttS/oet+Ke5KUpaqNJguSIoeTBSmgzDzL1TJxFLOzUT
2c/A9M69FdvSY0JB4EJX0W9K01Vd0JRNPwsY+/zvFIPama3suKOUTqYcsbwxx9xa
TMDr26cIQcgUAUOKZO43sQGWNzXX3FYVNwczKhkB8UX6hOrBJsEYiau4LGdokQID
AQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIB
AIX+Qb4QRBxHl5X2UjRyLfWVkimtGlwI8P+eJZL3DrHBH/TpqAaCvTf0EbRC32nm
ASDMwIghaMvyrW40QN6V/CWRRi25cXUfsIZr1iHAHK0eZJV8SWooYtt4iNrcUs3g
4OTvDxhNmDyNwV9AXhJsBKf80dCW6/84jItqVAj20/OO4Rkd2tEeI8NomiYBc6a1
hgwvv02myYF5hG/xZ9YSqeroBCZHwGYoJJnSpMPqJsxbCVnx2/U9FzGwcRmNHFCe
0g7EJZd3//8Plza6nkTBjJ/V7JnLqMU+ltx4mAgZO8rfzIr84qZdt0YN33VJQhYq
seuMySxrsuaAoxAmm8IoK9cW4IPzx1JveBQiroNlq5YJGf2UW7BTc3gz6c2tINZi
7ailBVdhlMnDXAf3/9xiiVlRAHOxgZh/7sRrKU7kDEHM4fGoc0YyZBTQKndPYMwO
3Bd82rlQ4sd46XYutTrB+mBYClVrJs+OzbNedTsR61DVNKKsRG4mNPyKSAIgOfM5
XmSvCMPN5JK9U0DsNIV2/SnVsmcklQczT35FLTxl9ntx8ys7ZYK+SppD7XuLfWMq
GT9YMWhlpw0aRDg/aayeeOcnsNBhzAFMcOpQj1t6Fgv4+zbS9BM2bT0hbX86xjkr
E6wWgkuCslMgQlEJ+TM5RhYrI5/rVZQhvmgcob/9gPZv
-----END CERTIFICATE-----

View File

@ -5,8 +5,11 @@ extern crate bytes;
extern crate flate2;
extern crate futures;
extern crate rand;
#[cfg(all(unix, feature = "uds"))]
extern crate tokio_uds;
use std::io::Read;
use std::io::{Read, Write};
use std::{net, thread};
use bytes::Bytes;
use flate2::read::GzDecoder;
@ -64,19 +67,26 @@ fn test_simple() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_connection_close() {
let mut srv =
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let request = srv.get().header("Connection", "close").finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_with_query_parameter() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| match req.query().get("qp") {
app.handler(|req: &HttpRequest| match req.query().get("qp") {
Some(_) => HttpResponse::Ok().finish(),
None => HttpResponse::BadRequest().finish(),
})
});
let request = srv.get()
.uri(srv.url("/?qp=5").as_str())
.finish()
.unwrap();
let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
@ -113,19 +123,19 @@ fn test_no_decompress() {
#[test]
fn test_client_gzip_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Deflate)
.body(bytes))
})
.responder()
}).responder()
})
});
// client request
let request = srv.post()
let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip)
.body(STR)
.unwrap();
@ -142,19 +152,19 @@ fn test_client_gzip_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Deflate)
.body(bytes))
})
.responder()
}).responder()
})
});
// client request
let request = srv.post()
let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip)
.body(data.clone())
.unwrap();
@ -174,19 +184,19 @@ fn test_client_gzip_encoding_large_random() {
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Deflate)
.body(bytes))
})
.responder()
}).responder()
})
});
// client request
let request = srv.post()
let request = srv
.post()
.content_encoding(http::ContentEncoding::Gzip)
.body(data.clone())
.unwrap();
@ -198,23 +208,30 @@ fn test_client_gzip_encoding_large_random() {
assert_eq!(bytes, Bytes::from(data));
}
#[cfg(all(unix, feature = "uds"))]
#[test]
fn test_compatible_with_unix_socket_stream() {
let (stream, _) = tokio_uds::UnixStream::pair().unwrap();
let _ = client::Connection::from_stream(stream);
}
#[cfg(feature = "brotli")]
#[test]
fn test_client_brotli_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Gzip)
.body(bytes))
})
.responder()
}).responder()
})
});
// client request
let request = srv.client(http::Method::POST, "/")
let request = srv
.client(http::Method::POST, "/")
.content_encoding(http::ContentEncoding::Br)
.body(STR)
.unwrap();
@ -235,19 +252,19 @@ fn test_client_brotli_encoding_large_random() {
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(move |bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Gzip)
.body(bytes))
})
.responder()
}).responder()
})
});
// client request
let request = srv.client(http::Method::POST, "/")
let request = srv
.client(http::Method::POST, "/")
.content_encoding(http::ContentEncoding::Br)
.body(data.clone())
.unwrap();
@ -264,19 +281,19 @@ fn test_client_brotli_encoding_large_random() {
#[test]
fn test_client_deflate_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Br)
.body(bytes))
})
.responder()
}).responder()
})
});
// client request
let request = srv.post()
let request = srv
.post()
.content_encoding(http::ContentEncoding::Deflate)
.body(STR)
.unwrap();
@ -297,19 +314,19 @@ fn test_client_deflate_encoding_large_random() {
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Br)
.body(bytes))
})
.responder()
}).responder()
})
});
// client request
let request = srv.post()
let request = srv
.post()
.content_encoding(http::ContentEncoding::Deflate)
.body(data.clone())
.unwrap();
@ -324,7 +341,7 @@ fn test_client_deflate_encoding_large_random() {
#[test]
fn test_client_streaming_explicit() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.map_err(Error::from)
.and_then(|body| {
@ -332,16 +349,13 @@ fn test_client_streaming_explicit() {
.chunked()
.content_encoding(http::ContentEncoding::Identity)
.body(body))
})
.responder()
}).responder()
})
});
let body = once(Ok(Bytes::from_static(STR.as_ref())));
let request = srv.get()
.body(Body::Streaming(Box::new(body)))
.unwrap();
let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
@ -391,7 +405,7 @@ fn test_client_cookie_handling() {
let mut srv = test::TestServer::new(move |app| {
let cookie1 = cookie1b.clone();
let cookie2 = cookie2b.clone();
app.handler(move |req: HttpRequest| {
app.handler(move |req: &HttpRequest| {
// Check cookies were sent correctly
req.cookie("cookie1").ok_or_else(err)
.and_then(|c1| if c1.value() == "value1" {
@ -414,7 +428,8 @@ fn test_client_cookie_handling() {
})
});
let request = srv.get()
let request = srv
.get()
.cookie(cookie1.clone())
.cookie(cookie2.clone())
.finish()
@ -422,7 +437,67 @@ fn test_client_cookie_handling() {
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
let c1 = response.cookie("cookie1").expect("Missing cookie1");
assert_eq!(c1, &cookie1);
assert_eq!(c1, cookie1);
let c2 = response.cookie("cookie2").expect("Missing cookie2");
assert_eq!(c2, &cookie2);
assert_eq!(c2, cookie2);
}
#[test]
fn test_default_headers() {
let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let request = srv.get().finish().unwrap();
let repr = format!("{:?}", request);
assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
assert!(repr.contains(concat!(
"\"user-agent\": \"actix-web/",
env!("CARGO_PKG_VERSION"),
"\""
)));
let request_override = srv
.get()
.header("User-Agent", "test")
.header("Accept-Encoding", "over_test")
.finish()
.unwrap();
let repr_override = format!("{:?}", request_override);
assert!(repr_override.contains("\"user-agent\": \"test\""));
assert!(repr_override.contains("\"accept-encoding\": \"over_test\""));
assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\""));
assert!(!repr_override.contains(concat!(
"\"user-agent\": \"Actix-web/",
env!("CARGO_PKG_VERSION"),
"\""
)));
}
#[test]
fn client_read_until_eof() {
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
let lst = net::TcpListener::bind(addr).unwrap();
for stream in lst.incoming() {
let mut stream = stream.unwrap();
let mut b = [0; 1000];
let _ = stream.read(&mut b).unwrap();
let _ = stream
.write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!");
}
});
let mut sys = actix::System::new("test");
// client request
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.block_on(req.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = sys.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"welcome!"));
}

View File

@ -4,20 +4,20 @@ extern crate bytes;
extern crate futures;
extern crate h2;
extern crate http;
extern crate tokio_core;
extern crate tokio_timer;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use std::time::Duration;
use std::io;
use std::time::{Duration, Instant};
use actix::*;
use actix_web::*;
use bytes::Bytes;
use futures::Future;
use http::StatusCode;
use serde_json::Value;
use tokio_core::reactor::Timeout;
use tokio_timer::Delay;
#[derive(Deserialize)]
struct PParam {
@ -33,10 +33,29 @@ fn test_path_extractor() {
});
// client request
let request = srv.get()
.uri(srv.url("/test/index.html"))
.finish()
.unwrap();
let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
}
#[test]
fn test_async_handler() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|p: Path<PParam>| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| Ok(format!("Welcome {}!", p.username)))
.responder()
})
});
});
// client request
let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
@ -54,7 +73,8 @@ fn test_query_extractor() {
});
// client request
let request = srv.get()
let request = srv
.get()
.uri(srv.url("/index.html?username=test"))
.finish()
.unwrap();
@ -66,8 +86,58 @@ fn test_query_extractor() {
assert_eq!(bytes, Bytes::from_static(b"Welcome test!"));
// client request
let request = srv.get()
.uri(srv.url("/index.html"))
let request = srv.get().uri(srv.url("/index.html")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[derive(Deserialize, Debug)]
pub enum ResponseType {
Token,
Code,
}
#[derive(Debug, Deserialize)]
pub struct AuthRequest {
id: u64,
response_type: ResponseType,
}
#[test]
fn test_query_enum_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/index.html", |r| {
r.with(|p: Query<AuthRequest>| format!("{:?}", p.into_inner()))
});
});
// client request
let request = srv
.get()
.uri(srv.url("/index.html?id=64&response_type=Code"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"AuthRequest { id: 64, response_type: Code }")
);
let request = srv
.get()
.uri(srv.url("/index.html?id=64&response_type=Co"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv
.get()
.uri(srv.url("/index.html?response_type=Code"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
@ -79,8 +149,7 @@ fn test_async_extractor_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(|data: Json<Value>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| Ok(format!("{}", data.0)))
.responder()
})
@ -88,7 +157,8 @@ fn test_async_extractor_async() {
});
// client request
let request = srv.post()
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
@ -101,18 +171,77 @@ fn test_async_extractor_async() {
assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}"));
}
#[derive(Deserialize, Serialize)]
struct FormData {
username: String,
}
#[test]
fn test_form_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|form: Form<FormData>| format!("{}", form.username))
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.form(FormData {
username: "test".to_string(),
}).unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"test"));
}
#[test]
fn test_form_extractor2() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_config(
|form: Form<FormData>| format!("{}", form.username),
|cfg| {
cfg.0.error_handler(|err, _| {
error::InternalError::from_response(
err,
HttpResponse::Conflict().finish(),
).into()
});
},
);
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/x-www-form-urlencoded")
.body("918237129hdk:D:D:D:D:D:DjASHDKJhaswkjeq")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_client_error());
}
#[test]
fn test_path_and_query_extractor() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with2(|p: Path<PParam>, q: Query<PParam>| {
r.route().with(|(p, q): (Path<PParam>, Query<PParam>)| {
format!("Welcome {} - {}!", p.username, q.username)
})
});
});
// client request
let request = srv.get()
let request = srv
.get()
.uri(srv.url("/test1/index.html?username=test2"))
.finish()
.unwrap();
@ -124,7 +253,8 @@ fn test_path_and_query_extractor() {
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!"));
// client request
let request = srv.get()
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
@ -137,14 +267,15 @@ fn test_path_and_query_extractor2() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route()
.with3(|_: HttpRequest, p: Path<PParam>, q: Query<PParam>| {
.with(|(_r, p, q): (HttpRequest, Path<PParam>, Query<PParam>)| {
format!("Welcome {} - {}!", p.username, q.username)
})
});
});
// client request
let request = srv.get()
let request = srv
.get()
.uri(srv.url("/test1/index.html?username=test2"))
.finish()
.unwrap();
@ -156,7 +287,8 @@ fn test_path_and_query_extractor2() {
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!"));
// client request
let request = srv.get()
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
@ -168,21 +300,20 @@ fn test_path_and_query_extractor2() {
fn test_path_and_query_extractor2_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with3(
|p: Path<PParam>, _: Query<PParam>, data: Json<Value>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
r.route().with(
|(p, _q, data): (Path<PParam>, Query<PParam>, Json<Value>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
})
.responder()
}).responder()
},
)
});
});
// client request
let request = srv.post()
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
@ -192,29 +323,25 @@ fn test_path_and_query_extractor2_async() {
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
}
#[test]
fn test_path_and_query_extractor3_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with2(|p: Path<PParam>, data: Json<Value>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
r.route().with(|(p, data): (Path<PParam>, Json<Value>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
})
.responder()
}).responder()
})
});
});
// client request
let request = srv.post()
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
@ -227,19 +354,18 @@ fn test_path_and_query_extractor3_async() {
fn test_path_and_query_extractor4_async() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with2(|data: Json<Value>, p: Path<PParam>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
r.route().with(|(data, p): (Json<Value>, Path<PParam>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
})
.responder()
}).responder()
})
});
});
// client request
let request = srv.post()
let request = srv
.post()
.uri(srv.url("/test1/index.html"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
@ -252,21 +378,20 @@ fn test_path_and_query_extractor4_async() {
fn test_path_and_query_extractor2_async2() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with3(
|p: Path<PParam>, data: Json<Value>, _: Query<PParam>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
r.route().with(
|(p, data, _q): (Path<PParam>, Json<Value>, Query<PParam>)| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
})
.responder()
}).responder()
},
)
});
});
// client request
let request = srv.post()
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
@ -276,13 +401,11 @@ fn test_path_and_query_extractor2_async2() {
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
// client request
let request = srv.get()
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
@ -294,21 +417,19 @@ fn test_path_and_query_extractor2_async2() {
fn test_path_and_query_extractor2_async3() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with3(
|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
r.route()
.with(|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))
})
.responder()
},
)
}).responder()
})
});
});
// client request
let request = srv.post()
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
@ -318,13 +439,11 @@ fn test_path_and_query_extractor2_async3() {
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
// client request
let request = srv.get()
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
@ -338,22 +457,17 @@ fn test_path_and_query_extractor2_async4() {
app.resource("/{username}/index.html", |r| {
r.route()
.with(|data: (Json<Value>, Path<PParam>, Query<PParam>)| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!(
"Welcome {} - {}!",
data.1.username,
(data.0).0
))
})
.responder()
Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))
}).responder()
})
});
});
// client request
let request = srv.post()
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
@ -363,13 +477,11 @@ fn test_path_and_query_extractor2_async4() {
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"Welcome test1 - {\"test\":1}!")
);
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
// client request
let request = srv.get()
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
@ -377,6 +489,148 @@ fn test_path_and_query_extractor2_async4() {
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_scope_and_path_extractor() {
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/sc", |scope| {
scope.resource("/{num}/index.html", |r| {
r.route()
.with(|p: Path<(usize,)>| format!("Welcome {}!", p.0))
})
})
});
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome 10!"));
// client request
let request = srv
.get()
.uri(srv.url("/sc/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_nested_scope_and_path_extractor() {
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/sc", |scope| {
scope.nested("/{num}", |scope| {
scope.resource("/{num}/index.html", |r| {
r.route().with(|p: Path<(usize, usize)>| {
format!("Welcome {} {}!", p.0, p.1)
})
})
})
})
});
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/12/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome 10 12!"));
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[cfg(actix_impl_trait)]
fn test_impl_trait(
data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Delay::new(Instant::now() + Duration::from_millis(10))
.map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout"))
.and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)))
}
#[cfg(actix_impl_trait)]
fn test_impl_trait_err(
_data: (Json<Value>, Path<PParam>, Query<PParam>),
) -> impl Future<Item = String, Error = io::Error> {
Delay::new(Instant::now() + Duration::from_millis(10))
.map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout"))
.and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other")))
}
#[cfg(actix_impl_trait)]
#[test]
fn test_path_and_query_extractor2_async4_impl_trait() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_async(test_impl_trait)
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!"));
// client request
let request = srv
.get()
.uri(srv.url("/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[cfg(actix_impl_trait)]
#[test]
fn test_path_and_query_extractor2_async4_impl_trait_err() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with_async(test_impl_trait_err)
});
});
// client request
let request = srv
.post()
.uri(srv.url("/test1/index.html?username=test2"))
.header("content-type", "application/json")
.body("{\"test\": 1}")
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_non_ascii_route() {
let mut srv = test::TestServer::new(|app| {
@ -384,7 +638,8 @@ fn test_non_ascii_route() {
});
// client request
let request = srv.get()
let request = srv
.get()
.uri(srv.url("/中文/index.html"))
.finish()
.unwrap();
@ -405,7 +660,8 @@ fn test_unsafe_path_route() {
});
// client request
let request = srv.get()
let request = srv
.get()
.uri(srv.url("/test/http%3A%2F%2Fexample.com"))
.finish()
.unwrap();

View File

@ -1,17 +1,17 @@
extern crate actix;
extern crate actix_web;
extern crate futures;
extern crate tokio_core;
extern crate tokio_timer;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use std::time::{Duration, Instant};
use actix::*;
use actix_web::error::{Error, ErrorInternalServerError};
use actix_web::*;
use futures::{future, Future};
use tokio_core::reactor::Timeout;
use tokio_timer::Delay;
struct MiddlewareTest {
start: Arc<AtomicUsize>,
@ -20,29 +20,23 @@ struct MiddlewareTest {
}
impl<S> middleware::Middleware<S> for MiddlewareTest {
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
self.start.store(
self.start.load(Ordering::Relaxed) + 1,
Ordering::Relaxed,
);
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started> {
self.start
.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
Ok(middleware::Started::Done)
}
fn response(
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
&self, _: &HttpRequest<S>, resp: HttpResponse,
) -> Result<middleware::Response> {
self.response.store(
self.response.load(Ordering::Relaxed) + 1,
Ordering::Relaxed,
);
self.response
.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
Ok(middleware::Response::Done(resp))
}
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
self.finish.store(
self.finish.load(Ordering::Relaxed) + 1,
Ordering::Relaxed,
);
fn finish(&self, _: &HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
self.finish
.store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
middleware::Finished::Done
}
}
@ -90,11 +84,10 @@ fn test_middleware_multiple() {
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.handler(|_| HttpResponse::Ok())
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
@ -149,11 +142,10 @@ fn test_resource_middleware_multiple() {
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.handler(|_| HttpResponse::Ok())
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).handler(|_| HttpResponse::Ok())
});
let request = srv.get().finish().unwrap();
@ -182,15 +174,11 @@ fn test_scope_middleware() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
@ -216,20 +204,15 @@ fn test_scope_middleware_multiple() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.middleware(MiddlewareTest {
}).middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
@ -254,11 +237,9 @@ fn test_middleware_async_handler() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/", |r| {
}).resource("/", |r| {
r.route().a(|_| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(|_| Ok(HttpResponse::Ok()))
})
})
@ -293,8 +274,7 @@ fn test_resource_middleware_async_handler() {
App::new().resource("/test", |r| {
r.middleware(mw);
r.route().a(|_| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(|_| Ok(HttpResponse::Ok()))
})
})
@ -326,21 +306,16 @@ fn test_scope_middleware_async_handler() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| {
}).resource("/test", |r| {
r.route().a(|_| {
Timeout::new(Duration::from_millis(10), &Arbiter::handle())
.unwrap()
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(|_| Ok(HttpResponse::Ok()))
})
})
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
@ -349,7 +324,7 @@ fn test_scope_middleware_async_handler() {
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse<HttpResponse> {
fn index_test_middleware_async_error(_: &HttpRequest) -> FutureResponse<HttpResponse> {
future::result(Err(error::ErrorBadRequest("TEST"))).responder()
}
@ -397,15 +372,11 @@ fn test_scope_middleware_async_error() {
start: Arc::clone(&act_req),
response: Arc::clone(&act_resp),
finish: Arc::clone(&act_fin),
})
.resource("/test", |r| r.f(index_test_middleware_async_error))
}).resource("/test", |r| r.f(index_test_middleware_async_error))
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
@ -433,7 +404,7 @@ fn test_resource_middleware_async_error() {
App::new().resource("/test", move |r| {
r.middleware(mw);
r.h(index_test_middleware_async_error);
r.f(index_test_middleware_async_error);
})
});
@ -453,8 +424,8 @@ struct MiddlewareAsyncTest {
}
impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
fn start(&self, _: &mut HttpRequest<S>) -> Result<middleware::Started> {
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started> {
let to = Delay::new(Instant::now() + Duration::from_millis(10));
let start = Arc::clone(&self.start);
Ok(middleware::Started::Future(Box::new(
@ -466,9 +437,9 @@ impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
}
fn response(
&self, _: &mut HttpRequest<S>, resp: HttpResponse,
&self, _: &HttpRequest<S>, resp: HttpResponse,
) -> Result<middleware::Response> {
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
let to = Delay::new(Instant::now() + Duration::from_millis(10));
let response = Arc::clone(&self.response);
Ok(middleware::Response::Future(Box::new(
@ -479,8 +450,8 @@ impl<S> middleware::Middleware<S> for MiddlewareAsyncTest {
)))
}
fn finish(&self, _: &mut HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap();
fn finish(&self, _: &HttpRequest<S>, _: &HttpResponse) -> middleware::Finished {
let to = Delay::new(Instant::now() + Duration::from_millis(10));
let finish = Arc::clone(&self.finish);
middleware::Finished::Future(Box::new(to.from_err().and_then(move |_| {
@ -535,13 +506,45 @@ fn test_async_middleware_multiple() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
}).middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
thread::sleep(Duration::from_millis(50));
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
#[test]
fn test_async_sync_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new()
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}).middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
@ -572,15 +575,11 @@ fn test_async_scope_middleware() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
@ -608,20 +607,51 @@ fn test_async_scope_middleware_multiple() {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
}).middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
thread::sleep(Duration::from_millis(20));
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
#[test]
fn test_async_async_scope_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/scope", |scope| {
scope
.middleware(MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
})
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
}).middleware(MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
}).resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get()
.uri(srv.url("/scope/test"))
.finish()
.unwrap();
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
@ -650,7 +680,7 @@ fn test_async_resource_middleware() {
};
App::new().resource("/test", move |r| {
r.middleware(mw);
r.h(|_| HttpResponse::Ok());
r.f(|_| HttpResponse::Ok());
})
});
@ -689,7 +719,7 @@ fn test_async_resource_middleware_multiple() {
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(mw2);
r.h(|_| HttpResponse::Ok());
r.f(|_| HttpResponse::Ok());
})
});
@ -703,3 +733,323 @@ fn test_async_resource_middleware_multiple() {
thread::sleep(Duration::from_millis(40));
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
#[test]
fn test_async_sync_resource_middleware_multiple() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareAsyncTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
let mw2 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(mw2);
r.f(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(num1.load(Ordering::Relaxed), 2);
assert_eq!(num2.load(Ordering::Relaxed), 2);
thread::sleep(Duration::from_millis(40));
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
struct MiddlewareWithErr;
impl<S> middleware::Middleware<S> for MiddlewareWithErr {
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started, Error> {
Err(ErrorInternalServerError("middleware error"))
}
}
struct MiddlewareAsyncWithErr;
impl<S> middleware::Middleware<S> for MiddlewareAsyncWithErr {
fn start(&self, _: &HttpRequest<S>) -> Result<middleware::Started, Error> {
Ok(middleware::Started::Future(Box::new(future::err(
ErrorInternalServerError("middleware error"),
))))
}
}
#[test]
fn test_middleware_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new()
.middleware(mw1)
.middleware(MiddlewareWithErr)
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_middleware_async_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new()
.middleware(mw1)
.middleware(MiddlewareAsyncWithErr)
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().scope("/scope", |scope| {
scope
.middleware(mw1)
.middleware(MiddlewareWithErr)
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_async_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().scope("/scope", |scope| {
scope
.middleware(mw1)
.middleware(MiddlewareAsyncWithErr)
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(MiddlewareWithErr);
r.f(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_async_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(MiddlewareAsyncWithErr);
r.f(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[cfg(feature = "session")]
#[test]
fn test_session_storage_middleware() {
use actix_web::middleware::session::{
CookieSessionBackend, RequestSession, SessionStorage,
};
const SIMPLE_NAME: &'static str = "simple";
const SIMPLE_PAYLOAD: &'static str = "kantan";
const COMPLEX_NAME: &'static str = "test";
const COMPLEX_PAYLOAD: &'static str = "url=https://test.com&generate_204";
//TODO: investigate how to handle below input
//const COMPLEX_PAYLOAD: &'static str = "FJc%26continue_url%3Dhttp%253A%252F%252Fconnectivitycheck.gstatic.com%252Fgenerate_204";
let mut srv = test::TestServer::with_factory(move || {
App::new()
.middleware(SessionStorage::new(
CookieSessionBackend::signed(&[0; 32]).secure(false),
)).resource("/index", move |r| {
r.f(|req| {
let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD);
assert!(res.is_ok());
let value = req.session().get::<String>(COMPLEX_NAME);
assert!(value.is_ok());
let value = value.unwrap();
assert!(value.is_some());
assert_eq!(value.unwrap(), COMPLEX_PAYLOAD);
let res = req.session().set(SIMPLE_NAME, SIMPLE_PAYLOAD);
assert!(res.is_ok());
let value = req.session().get::<String>(SIMPLE_NAME);
assert!(value.is_ok());
let value = value.unwrap();
assert!(value.is_some());
assert_eq!(value.unwrap(), SIMPLE_PAYLOAD);
HttpResponse::Ok()
})
}).resource("/expect_cookie", move |r| {
r.f(|req| {
let _cookies = req.cookies().expect("To get cookies");
let value = req.session().get::<String>(SIMPLE_NAME);
assert!(value.is_ok());
let value = value.unwrap();
assert!(value.is_some());
assert_eq!(value.unwrap(), SIMPLE_PAYLOAD);
let value = req.session().get::<String>(COMPLEX_NAME);
assert!(value.is_ok());
let value = value.unwrap();
assert!(value.is_some());
assert_eq!(value.unwrap(), COMPLEX_PAYLOAD);
HttpResponse::Ok()
})
})
});
let request = srv.get().uri(srv.url("/index")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.headers().contains_key("set-cookie"));
let set_cookie = response.headers().get("set-cookie");
assert!(set_cookie.is_some());
let set_cookie = set_cookie.unwrap().to_str().expect("Convert to str");
let request = srv
.get()
.uri(srv.url("/expect_cookie"))
.header("cookie", set_cookie.split(';').next().unwrap())
.finish()
.unwrap();
srv.execute(request.send()).unwrap();
}

View File

@ -1,34 +1,37 @@
extern crate actix;
extern crate actix_web;
#[cfg(feature = "brotli")]
extern crate brotli2;
extern crate bytes;
extern crate flate2;
extern crate futures;
extern crate h2;
extern crate http as modhttp;
extern crate rand;
extern crate tokio_core;
extern crate tokio;
extern crate tokio_reactor;
extern crate tokio_tcp;
#[cfg(feature = "brotli")]
extern crate brotli2;
use std::io::{Read, Write};
use std::sync::Arc;
use std::{thread, time};
#[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::{Bytes, BytesMut};
use flate2::read::GzDecoder;
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder};
use flate2::Compression;
use futures::stream::once;
use futures::{Future, Stream};
use h2::client as h2client;
use modhttp::Request;
use rand::distributions::Alphanumeric;
use rand::Rng;
use std::io::{Read, Write};
use std::sync::{mpsc, Arc};
use std::{net, thread, time};
use tokio_core::net::TcpStream;
use tokio_core::reactor::Core;
use tokio::executor::current_thread;
use tokio::runtime::current_thread::Runtime;
use tokio_tcp::TcpStream;
use actix::System;
use actix_web::*;
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
@ -54,88 +57,164 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World";
#[test]
#[cfg(unix)]
fn test_start() {
use actix::System;
use std::sync::mpsc;
let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let sys = System::new("test");
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
thread::spawn(|| {
System::run(move || {
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
});
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
let srv_addr = srv.start();
let _ = tx.send((addr, srv_addr, System::current()));
});
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
let srv_addr = srv.start();
let _ = tx.send((addr, srv_addr));
sys.run();
});
let (addr, srv_addr) = rx.recv().unwrap();
let (addr, srv_addr, sys) = rx.recv().unwrap();
System::set_current(sys.clone());
let mut sys = System::new("test-server");
let mut rt = Runtime::new().unwrap();
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.run_until_complete(req.send()).unwrap();
let response = rt.block_on(req.send()).unwrap();
assert!(response.status().is_success());
}
// pause
let _ = srv_addr.send(server::PauseServer).wait();
thread::sleep(time::Duration::from_millis(200));
assert!(net::TcpStream::connect(addr).is_err());
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.timeout(time::Duration::from_millis(200))
.finish()
.unwrap();
assert!(rt.block_on(req.send()).is_err());
}
// resume
let _ = srv_addr.send(server::ResumeServer).wait();
thread::sleep(time::Duration::from_millis(200));
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.run_until_complete(req.send()).unwrap();
let response = rt.block_on(req.send()).unwrap();
assert!(response.status().is_success());
}
let _ = sys.stop();
}
#[test]
#[cfg(unix)]
fn test_shutdown() {
use actix::System;
use std::net;
use std::sync::mpsc;
let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let sys = System::new("test");
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
thread::spawn(|| {
System::run(move || {
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
});
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
let srv_addr = srv.shutdown_timeout(1).start();
let _ = tx.send((addr, srv_addr, System::current()));
});
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
let srv_addr = srv.shutdown_timeout(1).start();
let _ = tx.send((addr, srv_addr));
sys.run();
});
let (addr, srv_addr) = rx.recv().unwrap();
let mut sys = System::new("test-server");
let (addr, srv_addr, sys) = rx.recv().unwrap();
System::set_current(sys.clone());
let mut rt = Runtime::new().unwrap();
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = sys.run_until_complete(req.send()).unwrap();
let response = rt.block_on(req.send()).unwrap();
srv_addr.do_send(server::StopServer { graceful: true });
assert!(response.status().is_success());
}
thread::sleep(time::Duration::from_millis(1000));
assert!(net::TcpStream::connect(addr).is_err());
let _ = sys.stop();
}
#[test]
#[cfg(unix)]
fn test_panic() {
use actix::System;
use std::sync::mpsc;
let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel();
thread::spawn(|| {
System::run(move || {
let srv = server::new(|| {
App::new()
.resource("/panic", |r| {
r.method(http::Method::GET).f(|_| -> &'static str {
panic!("error");
});
}).resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})
}).workers(1);
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
srv.start();
let _ = tx.send((addr, System::current()));
});
});
let (addr, sys) = rx.recv().unwrap();
System::set_current(sys.clone());
let mut rt = Runtime::new().unwrap();
{
let req = client::ClientRequest::get(format!("http://{}/panic", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send());
assert!(response.is_err());
}
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send());
assert!(response.is_err());
}
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send()).unwrap();
assert!(response.status().is_success());
}
let _ = sys.stop();
}
#[test]
@ -254,7 +333,7 @@ fn test_body_gzip_large() {
#[test]
fn test_body_gzip_large_random() {
let data = rand::thread_rng()
.gen_ascii_chars()
.sample_iter(&Alphanumeric)
.take(70_000)
.collect::<String>();
let srv_data = Arc::new(data.clone());
@ -337,11 +416,7 @@ fn test_body_br_streaming() {
#[test]
fn test_head_empty() {
let mut srv = test::TestServer::new(|app| {
app.handler(|_| {
HttpResponse::Ok()
.content_length(STR.len() as u64)
.finish()
})
app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish())
});
let request = srv.head().finish().unwrap();
@ -357,8 +432,8 @@ fn test_head_empty() {
}
// read response
//let bytes = srv.execute(response.body()).unwrap();
//assert!(bytes.is_empty());
let bytes = srv.execute(response.body()).unwrap();
assert!(bytes.is_empty());
}
#[test]
@ -385,8 +460,8 @@ fn test_head_binary() {
}
// read response
//let bytes = srv.execute(response.body()).unwrap();
//assert!(bytes.is_empty());
let bytes = srv.execute(response.body()).unwrap();
assert!(bytes.is_empty());
}
#[test]
@ -459,6 +534,39 @@ fn test_body_chunked_explicit() {
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
}
#[test]
fn test_body_identity() {
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let enc = e.finish().unwrap();
let enc2 = enc.clone();
let mut srv = test::TestServer::new(move |app| {
let enc3 = enc2.clone();
app.handler(move |_| {
HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.header(http::header::CONTENT_ENCODING, "deflate")
.body(enc3.clone())
})
});
// client request
let request = srv
.get()
.header("accept-encoding", "deflate")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
// decode deflate
assert_eq!(bytes, Bytes::from(STR));
}
#[test]
fn test_body_deflate() {
let mut srv = test::TestServer::new(|app| {
@ -478,7 +586,7 @@ fn test_body_deflate() {
let bytes = srv.execute(response.body()).unwrap();
// decode deflate
let mut e = DeflateDecoder::new(Vec::new());
let mut e = ZlibDecoder::new(Vec::new());
e.write_all(bytes.as_ref()).unwrap();
let dec = e.finish().unwrap();
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
@ -513,14 +621,13 @@ fn test_body_brotli() {
#[test]
fn test_gzip_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
})
.responder()
}).responder()
})
});
@ -529,7 +636,8 @@ fn test_gzip_encoding() {
e.write_all(STR.as_ref()).unwrap();
let enc = e.finish().unwrap();
let request = srv.post()
let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "gzip")
.body(enc.clone())
.unwrap();
@ -545,14 +653,13 @@ fn test_gzip_encoding() {
fn test_gzip_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
})
.responder()
}).responder()
})
});
@ -561,7 +668,8 @@ fn test_gzip_encoding_large() {
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
let request = srv.post()
let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "gzip")
.body(enc.clone())
.unwrap();
@ -576,19 +684,18 @@ fn test_gzip_encoding_large() {
#[test]
fn test_reading_gzip_encoding_large_random() {
let data = rand::thread_rng()
.gen_ascii_chars()
.sample_iter(&Alphanumeric)
.take(60_000)
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
})
.responder()
}).responder()
})
});
@ -597,7 +704,8 @@ fn test_reading_gzip_encoding_large_random() {
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
let request = srv.post()
let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "gzip")
.body(enc.clone())
.unwrap();
@ -613,23 +721,23 @@ fn test_reading_gzip_encoding_large_random() {
#[test]
fn test_reading_deflate_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
})
.responder()
}).responder()
})
});
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let enc = e.finish().unwrap();
// client request
let request = srv.post()
let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "deflate")
.body(enc)
.unwrap();
@ -645,23 +753,23 @@ fn test_reading_deflate_encoding() {
fn test_reading_deflate_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
})
.responder()
}).responder()
})
});
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
// client request
let request = srv.post()
let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "deflate")
.body(enc)
.unwrap();
@ -676,28 +784,28 @@ fn test_reading_deflate_encoding_large() {
#[test]
fn test_reading_deflate_encoding_large_random() {
let data = rand::thread_rng()
.gen_ascii_chars()
.sample_iter(&Alphanumeric)
.take(160_000)
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
})
.responder()
}).responder()
})
});
let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(data.as_ref()).unwrap();
let enc = e.finish().unwrap();
// client request
let request = srv.post()
let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "deflate")
.body(enc)
.unwrap();
@ -714,14 +822,13 @@ fn test_reading_deflate_encoding_large_random() {
#[test]
fn test_brotli_encoding() {
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
})
.responder()
}).responder()
})
});
@ -730,7 +837,8 @@ fn test_brotli_encoding() {
let enc = e.finish().unwrap();
// client request
let request = srv.post()
let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "br")
.body(enc)
.unwrap();
@ -747,14 +855,13 @@ fn test_brotli_encoding() {
fn test_brotli_encoding_large() {
let data = STR.repeat(10);
let mut srv = test::TestServer::new(|app| {
app.handler(|req: HttpRequest| {
app.handler(|req: &HttpRequest| {
req.body()
.and_then(|bytes: Bytes| {
Ok(HttpResponse::Ok()
.content_encoding(http::ContentEncoding::Identity)
.body(bytes))
})
.responder()
}).responder()
})
});
@ -763,7 +870,8 @@ fn test_brotli_encoding_large() {
let enc = e.finish().unwrap();
// client request
let request = srv.post()
let request = srv
.post()
.header(http::header::CONTENT_ENCODING, "br")
.body(enc)
.unwrap();
@ -779,12 +887,13 @@ fn test_brotli_encoding_large() {
fn test_h2() {
let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
let addr = srv.addr();
thread::sleep(time::Duration::from_millis(500));
let mut core = Core::new().unwrap();
let handle = core.handle();
let tcp = TcpStream::connect(&addr, &handle);
let mut core = Runtime::new().unwrap();
let tcp = TcpStream::connect(&addr);
let tcp = tcp.then(|res| h2client::handshake(res.unwrap()))
let tcp = tcp
.then(|res| h2client::handshake(res.unwrap()))
.then(move |res| {
let (mut client, h2) = res.unwrap();
@ -795,7 +904,7 @@ fn test_h2() {
let (response, _) = client.send_request(request, false).unwrap();
// Spawn a task to run the conn...
handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e)));
current_thread::spawn(h2.map_err(|e| println!("GOT ERR={:?}", e)));
response.and_then(|response| {
assert_eq!(response.status(), http::StatusCode::OK);
@ -808,7 +917,7 @@ fn test_h2() {
})
})
});
let _res = core.run(tcp);
let _res = core.block_on(tcp);
// assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref()));
}
@ -822,3 +931,80 @@ fn test_application() {
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_default_404_handler_response() {
let mut srv = test::TestServer::with_factory(|| {
App::new()
.prefix("/app")
.resource("", |r| r.f(|_| HttpResponse::Ok()))
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
});
let addr = srv.addr();
let mut buf = [0; 24];
let request = TcpStream::connect(&addr)
.and_then(|sock| {
tokio::io::write_all(sock, "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n")
.and_then(|(sock, _)| tokio::io::read_exact(sock, &mut buf))
.and_then(|(_, buf)| Ok(buf))
}).map_err(|e| panic!("{:?}", e));
let response = srv.execute(request).unwrap();
let rep = String::from_utf8_lossy(&response[..]);
assert!(rep.contains("HTTP/1.1 404 Not Found"));
}
#[test]
fn test_server_cookies() {
use actix_web::http;
let mut srv = test::TestServer::with_factory(|| {
App::new().resource("/", |r| {
r.f(|_| {
HttpResponse::Ok()
.cookie(
http::CookieBuilder::new("first", "first_value")
.http_only(true)
.finish(),
).cookie(http::Cookie::new("second", "first_value"))
.cookie(http::Cookie::new("second", "second_value"))
.finish()
})
})
});
let first_cookie = http::CookieBuilder::new("first", "first_value")
.http_only(true)
.finish();
let second_cookie = http::Cookie::new("second", "second_value");
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
let cookies = response.cookies().expect("To have cookies");
assert_eq!(cookies.len(), 2);
if cookies[0] == first_cookie {
assert_eq!(cookies[1], second_cookie);
} else {
assert_eq!(cookies[0], second_cookie);
assert_eq!(cookies[1], first_cookie);
}
let first_cookie = first_cookie.to_string();
let second_cookie = second_cookie.to_string();
//Check that we have exactly two instances of raw cookie headers
let cookies = response
.headers()
.get_all(http::header::SET_COOKIE)
.iter()
.map(|header| header.to_str().expect("To str").to_string())
.collect::<Vec<_>>();
assert_eq!(cookies.len(), 2);
if cookies[0] == first_cookie {
assert_eq!(cookies[1], second_cookie);
} else {
assert_eq!(cookies[0], second_cookie);
assert_eq!(cookies[1], first_cookie);
}
}

View File

@ -7,10 +7,13 @@ extern crate rand;
use bytes::Bytes;
use futures::Stream;
use rand::distributions::Alphanumeric;
use rand::Rng;
#[cfg(feature = "alpn")]
extern crate openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
use actix::prelude::*;
use actix_web::*;
@ -46,9 +49,46 @@ fn test_simple() {
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Binary(
Bytes::from_static(b"text").into()
))
Some(ws::Message::Binary(Bytes::from_static(b"text").into()))
);
writer.ping("ping");
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Pong("ping".to_owned())));
writer.close(Some(ws::CloseCode::Normal.into()));
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
);
}
// websocket resource helper function
fn start_ws_resource(req: &HttpRequest) -> Result<HttpResponse, Error> {
ws::start(req, Ws)
}
#[test]
fn test_simple_path() {
const PATH: &str = "/v1/ws/";
// Create a websocket at a specific path.
let mut srv = test::TestServer::new(|app| {
app.resource(PATH, |r| r.route().f(start_ws_resource));
});
// fetch the sockets for the resource at a given path.
let (reader, mut writer) = srv.ws_at(PATH).unwrap();
writer.text("text");
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
writer.binary(b"text".as_ref());
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Binary(Bytes::from_static(b"text").into()))
);
writer.ping("ping");
@ -88,7 +128,7 @@ fn test_close_description() {
#[test]
fn test_large_text() {
let data = rand::thread_rng()
.gen_ascii_chars()
.sample_iter(&Alphanumeric)
.take(65_536)
.collect::<String>();
@ -106,7 +146,7 @@ fn test_large_text() {
#[test]
fn test_large_bin() {
let data = rand::thread_rng()
.gen_ascii_chars()
.sample_iter(&Alphanumeric)
.take(65_536)
.collect::<String>();
@ -117,10 +157,32 @@ fn test_large_bin() {
writer.binary(data.clone());
let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r;
assert_eq!(
item,
Some(ws::Message::Binary(Binary::from(data.clone())))
);
assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone()))));
}
}
#[test]
fn test_client_frame_size() {
let data = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(131_072)
.collect::<String>();
let mut srv = test::TestServer::new(|app| {
app.handler(|req| -> Result<HttpResponse> {
let mut resp = ws::handshake(req)?;
let stream = ws::WsStream::new(req.payload()).max_size(131_072);
let body = ws::WebsocketContext::create(req.clone(), Ws, stream);
Ok(resp.body(body))
})
});
let (reader, mut writer) = srv.ws().unwrap();
writer.binary(data.clone());
match srv.execute(reader.into_future()).err().unwrap().0 {
ws::ProtocolError::Overflow => (),
_ => panic!(),
}
}
@ -151,8 +213,7 @@ impl Ws2 {
act.send(ctx);
}
actix::fut::ok(())
})
.wait(ctx);
}).wait(ctx);
}
}
@ -231,19 +292,56 @@ fn test_ws_server_ssl() {
.set_certificate_chain_file("tests/cert.pem")
.unwrap();
let mut srv = test::TestServer::build()
.ssl(builder.build())
.start(|app| {
app.handler(|req| {
ws::start(
req,
Ws2 {
count: 0,
bin: false,
},
)
})
});
let mut srv = test::TestServer::build().ssl(builder).start(|app| {
app.handler(|req| {
ws::start(
req,
Ws2 {
count: 0,
bin: false,
},
)
})
});
let (mut reader, _writer) = srv.ws().unwrap();
let data = Some(ws::Message::Text("0".repeat(65_536)));
for _ in 0..10_000 {
let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r;
assert_eq!(item, data);
}
}
#[test]
#[cfg(feature = "rust-tls")]
fn test_ws_server_rust_tls() {
extern crate rustls;
use rustls::internal::pemfile::{certs, rsa_private_keys};
use rustls::{NoClientAuth, ServerConfig};
use std::fs::File;
use std::io::BufReader;
// load ssl keys
let mut config = ServerConfig::new(NoClientAuth::new());
let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap());
let cert_chain = certs(cert_file).unwrap();
let mut keys = rsa_private_keys(key_file).unwrap();
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
let mut srv = test::TestServer::build().rustls(config).start(|app| {
app.handler(|req| {
ws::start(
req,
Ws2 {
count: 0,
bin: false,
},
)
})
});
let (mut reader, _writer) = srv.ws().unwrap();
let data = Some(ws::Message::Text("0".repeat(65_536)));

View File

@ -1,21 +0,0 @@
[package]
name = "wsclient"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "wsclient"
path = "src/wsclient.rs"
[dependencies]
env_logger = "*"
futures = "0.1"
clap = "2"
url = "1.6"
rand = "0.4"
time = "*"
num_cpus = "1"
tokio-core = "0.1"
actix = "0.5"
actix-web = { path="../../" }

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