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

Compare commits

...

232 Commits

Author SHA1 Message Date
3d6b8686ad Merge pull request #1373 from JohnTitor/new-codegen
Release `actix-web-codegen` v0.2.1
2020-02-25 09:32:48 +09:00
a4f87a53da Update CHANGES.md 2020-02-25 08:42:39 +09:00
08f172a0aa Merge branch 'master' into new-codegen 2020-02-25 08:29:31 +09:00
7792eaa16e Merge pull request #1378 from JohnTitor/fix-doc
Fix doc comment
2020-02-25 08:29:14 +09:00
845ce3cf34 Fix doc comment 2020-02-25 07:46:03 +09:00
7daef22e24 Merge branch 'master' into new-codegen 2020-02-25 06:58:49 +09:00
1249262c35 Merge pull request #1372 from JohnTitor/time-0.2.7
Update `time` to 0.2.7
2020-02-25 06:58:33 +09:00
c8ccc69b93 actix-http: update time to 0.2.7 2020-02-23 07:09:00 +09:00
f9f9fb4c84 actix-http-test: update time to 0.2.7 2020-02-23 07:08:50 +09:00
1b77963aac actix-web: update time to 0.2.7 2020-02-23 07:08:22 +09:00
036ffd43f9 Prepare for new release 2020-02-23 06:40:02 +09:00
bdccccd536 Merge pull request #1368 from mattgathu/add-missing-docs-attr-to-codegen-structs
Add`#[allow(missing_docs)]` attribute to generated structs
2020-02-23 06:26:42 +09:00
060c392c67 Add missing_docs attribute to generated structs 2020-02-22 10:32:12 +01:00
245f96868a impl downcast_ref for MessageBody (#1287)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-02-21 13:31:51 +09:00
b3f1071aaf Merge pull request #1361 from Aaron1011/fix/connector-pool-support
Use #[pin_project] with `ConnectorPoolSupport`
2020-02-21 06:01:26 +09:00
e6811e8818 Use #[pin_project] with ConnectorPoolSupport
This removes a use of `Pin::get_unchecked_mut`
2020-02-19 21:42:53 -05:00
809930d36e Add dependencies to docs example (#1343)
* Add dependencies to docs example

* Change codeblock type to toml

* Clarify the need for actix-rt

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-02-20 05:13:10 +09:00
6ab7cfa2be Remove descriptions about undefined uds feature from docs (#1356) 2020-02-16 04:18:31 +09:00
9b3f7248a8 Merge pull request #1354 from actix/JohnTitor-patch-1
Disable coverage for PRs
2020-02-14 08:18:09 +09:00
a1835d6510 Disable coverage for PRs 2020-02-14 07:31:29 +09:00
4484b3f66e Merge pull request #1347 from actix/bye-travis
Use Actions fully
2020-02-12 05:54:32 +09:00
cde3ae5f61 Remove Travis config 2020-02-08 05:36:11 +09:00
7d40b66300 Add some Actions workflows 2020-02-08 04:28:34 +09:00
63730c1f73 Merge pull request #1345 from JohnTitor/fix-warnings
Fix warnings
2020-02-08 04:17:04 +09:00
53ff3ad099 More ignore test causes timeout 2020-02-08 02:20:01 +09:00
6406f56ca2 Fix/suppress warnings 2020-02-08 02:20:01 +09:00
728b944360 Extensions module improvement and tests. (#1297)
* replace get.is_some to contains_key

* Add tests

* remove unnecessary box cast

* fix missing uints

* asserts fix

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-02-07 16:08:25 +09:00
3851a377df Fix minor grammatical errors (#1341) 2020-02-07 03:00:22 +09:00
fe13789345 Use Pin<Box<S>> in BodyStream and SizedStream (#1328)
Fixes #1321

A better fix would be to change `MessageBody` to take a `Pin<&mut
Self>`, rather than a `Pin<&mut Self>`. This will avoid requiring the
use of `Box` for all consumers by allowing the caller to determine how
to pin the `MessageBody` implementation (e.g. via stack pinning).

However, doing so is a breaking change that will affect every user of
`MessageBody`. By pinning the inner stream ourselves, we can fix the
undefined behavior without breaking the API.

I've included @sebzim4500's reproduction case as a new test case.
However, due to the nature of undefined behavior, this could pass (and
not segfault) even if underlying issue were to regress.

Unfortunately, until rust-lang/unsafe-code-guidelines#148 is resolved,
it's not even possible to write a Miri test that will pass when the bug
is fixed.

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-31 09:39:34 +09:00
3033f187d2 Enforce safety of downcast_ref at compile time. (#1326)
* Enforce safety of `downcast_ref` at compile time.

The safety of `downcast_ref` requires that `__private_get_type_id__` not
be overriden by callers, since the returned `TypeId` is used to check if
the cast is safe. However, all trait methods in Rust are public, so
users can override `__private_get_type_id__` despite it being
`#[doc(hidden)]`.

This commit makes `__private_get_type_id__` return a type with a private
constructor, ensuring that the only possible implementation is the
default implementation. A more detailed explanation is provided in the
comments added to the file.

Note that the standard library was affected by this type of issue with
the `Error::type_id` function: see https://blog.rust-lang.org/2019/05/14/Rust-1.34.2.html#whats-in-1.34.2-stable

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-30 23:43:35 +09:00
276a5a3ee4 Replace UnsafeCell with Cell in DateServiceInner (#1325)
* Replace `UnsafeCell` with `Cell` in `DateServiceInner`

This ensures that it's impossible to cause undefined behavior by
accidentally violating Rust's aliasing rules (e.g. passing a closure to
`set_date` which ends up invoking `reset` or `update` on the inner
`DateServiceInner`).

There might be a tiny amount of overhead from copying the `Option<(Date,
Instant)>` rather than taking a reference, but it shouldn't be
measurable.

Since the wrapped type is `Copy`, a `Cell` can be used, avoiding the
runtime overhead of a `RefCell`.

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-29 21:05:08 +09:00
664f9a8b2d Long lasting auto-prolonged session (#1292)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-29 10:26:39 +09:00
c73c2dc12c Don't use cache in Windows CI (#1327) 2020-01-29 09:00:04 +09:00
e634e64847 Upgrade time to 0.2.5 (#1254)
* Use `OffsetDateTime` instead of `PrimitiveDateTime`

* Parse time strings with `PrimitiveDateTime::parse` instead of `OffsetDateTime::parse`

* Remove unused `time` dependency from actix-multipart

* Fix a few errors with time related tests from the `time` upgrade

* Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions

* Upgrade `time` to 0.2.2

* Correctly parse C's asctime time format using time 0.2's new format patterns

* Update CHANGES.md

* Use `time` without any of its deprecated functions

* Enforce a UTC time offset when converting an `OffsetDateTime` into a Header value

* Use the more readable version of `Duration::seconds(0)`, `Duration::zero()`

* Remove unneeded conversion of time::Duration to std::time::Duration

* Use `OffsetDateTime::as_seconds_f64` instead of manually calculating the amount of seconds from nanoseconds

* Replace a few additional instances of `Duration::seconds(0)` with `Duration::zero()`

* Truncate any nanoseconds from a supplied `Duration` within `Cookie::set_max_age` to ensure two Cookies with the same amount whole seconds equate to one another

* Fix the actix-http:🍪:do_not_panic_on_large_max_ages test

* Convert `Cookie::max_age` and `Cookie::expires` examples to `time` 0.2

Mainly minor  changes. Type inference can be used alongside the new
`time::parse` method, such that the type doesn't need to be specified.
This will be useful if a refactoring takes place that changes the type.
There are also new macros, which are used where possible.

One change that is not immediately obvious, in `HttpDate`, there was an
unnecessary conditional. As the time crate allows for negative durations
(and can perform arithmetic with such), the if/else can be removed
entirely.

Time v0.2.3 also has some bug fixes, which is why I am not using a more
general v0.2 in Cargo.toml.

v0.2.3 has been yanked, as it was backwards imcompatible. This version
reverts the breaking change, while still supporting rustc back to
1.34.0.

* Add missing `time::offset` macro import

* Fix type confusion when using `time::parse` followed by `using_offset`

* Update `time` to 0.2.5

* Update CHANGES.md

Co-authored-by: Jacob Pratt <the.z.cuber@gmail.com>
2020-01-28 20:44:22 +09:00
cdba30d45f Skip empty chucks for BodyStream and SizedStream (#1308)
* Skip empty chucks for BodyStream and SizedStream when streaming response (#1267)

* Fix tests to fail on previous implementation

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-28 18:28:09 +09:00
74dcc7366d Remove several uses of Pin::new_unchecked (#1294)
Most of the relevant struct already had a `#[pin_project]` attribute,
but it wasn't being used.

The remaining uses of `Pin::new_unchecked` all involve going from a
`&mut T` to a `Pin<&mut T>`, without directly observing a `Pin<&mut T>`
first. As such, they cannot be replaced by `pin_project`

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-28 12:35:51 +09:00
d137a8635b Replace Pin::new_unchecked with #[pin_project] in tuple_from_req! (#1293)
Using some module trickery, we can generate a tuple struct for each
invocation of the macro. This allows us to use `pin_project` to project
through to the tuple fields, removing the need to use
`Pin::new_unchecked`

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-28 10:45:25 +09:00
a2d4ff157e Update call_service documentation (#1302)
Co-authored-by: Christian Battaglia <christian.d.battaglia@gmail.com>
2020-01-28 08:09:46 +09:00
71d11644a7 Add ability to name a handler function as 'config' (#1290)
* eliminate handler naming restrictions #1277

* Update actix-web-codegen/CHANGES.md

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-26 07:22:40 +09:00
8888520d83 Add benchmark for full stack request lifecycle (#1298)
* add benchmark for full stack request lifecycle

* add direct service benchmarks

* fix newline

* add cloneable service benchmarks

* remove cloneable bench experiments + cargo fmt

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-25 08:05:25 +09:00
cf3577550c Tweak caches (#1319)
* Try to use `cargo-cache`

* Tweak issue template
2020-01-25 02:27:13 +09:00
58844874a0 Fixing #1295 convert UnsafeCell to RefCell in CloneableService (#1303)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-24 14:51:38 +09:00
78f24dda03 Initial Issue template (#1311)
* Initial Issue template

* First round of changes for the bug report

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-24 07:32:34 +09:00
e17b3accb9 Remove codecoverage for tests and examples (#1299)
* Ignore Tests & Examples for CodeCoverage

Ignore Tests & Examples for CodeCoverage
2020-01-24 05:10:02 +09:00
c6fa007e72 Fix vcpkg cache (#1312) 2020-01-23 11:27:34 +09:00
a3287948d1 allow explicit SameSite=None cookies (#1282)
fixes #1035
2020-01-23 10:08:23 +09:00
2e9ab0625e Tweak actions (#1305)
* Add benchmark action

* Fix Windows build
2020-01-23 06:23:53 +09:00
3a5b62b550 Add dependencies instruction (#1281) 2020-01-16 23:17:17 +09:00
412e54ce10 Fixed documentation typo for actix-files (#1278) 2020-01-15 11:09:58 -08:00
bca41f8d40 Changes to Cors builder (#1266)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-11 04:53:17 +09:00
7c974ee668 Update doc comment for HttpRequest::app_data (#1265)
* update doc comment for `HttpRequest::app_data`

* add `no_run` to doc comment

* add `ignore` to doc comment

* Update src/request.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-01-11 03:55:20 +09:00
abb462ef85 Replace sha1 dependency with sha-1 (#1258)
* Replace sha1 dependency with sha-1

This other crate is being maintained, and it offers better performances
when using the `asm` feature (especially [on
AArch64](https://github.com/RustCrypto/hashes/pull/97)).

* Update CHANGES.md with the sha-1 migration

* Add a test for hash_key()
2020-01-11 02:34:31 +09:00
e66312b664 add extra constraints 2020-01-10 11:36:59 +06:00
39f4b2b39e Merge branch 'master' of github.com:actix/actix-web 2020-01-10 11:28:58 +06:00
f6ff056b8a Fix panic with already borrowed: BorrowMutError #1263 2020-01-10 11:26:54 +06:00
51ab4fb73d Tweak actions to use cache and not to be stuck on the way (#1264) 2020-01-10 03:30:45 +09:00
f5fd6bc49f Fix actix-http examples (#1259)
Fix actix-http examples
2020-01-07 00:15:04 +09:00
2803fcbe22 Small grammaritical update to lib.rs (#1248) 2020-01-03 08:45:17 +06:00
67793c5d92 add ssl feature migration 2019-12-30 21:22:04 +06:00
bcb5086c91 Added 2.0.0 rustls feature name change (#1244) 2019-12-30 21:16:04 +06:00
7bd2270290 Fix link to example in readme.md (#1236)
* Fix link to example in readme.md

* Add links to openssl and rustls examples

* Rustls should be uppercase
2019-12-26 19:42:07 +09:00
a4ad5e6b69 update timeouts for test server 2019-12-25 20:52:20 +04:00
6db909a3e7 update migration 2019-12-25 20:27:30 +04:00
642ae161c0 prep actix-web release 2019-12-25 20:21:00 +04:00
7b3c99b933 prep actix-framed release 2019-12-25 20:17:22 +04:00
f86ce0390e allow to specify multi pattern for resources 2019-12-25 20:14:44 +04:00
7882f545e5 Allow to gracefully stop test server via TestServer::stop() 2019-12-25 12:10:48 +04:00
1c75e6876b update migration 2019-12-22 17:16:07 +04:00
6a0cd2dced Rename HttpServer::start() to HttpServer::run() 2019-12-22 17:12:22 +04:00
c7f3915779 update actix-service dep 2019-12-22 16:39:25 +04:00
f45db1f909 Enable GitHub Actions and fix file URL behavior (#1232)
* Use GitHub Actions

* Fix unused imports on Windows

* Fix test for Windows

* Stop to run CI for i686-pc-windows-msvc for now

* Use `/` instead of `\` on Windows

* Add entry to changelog

* Prepare actix-files release
2019-12-22 16:43:41 +09:00
3751a4018e fixed test::init_service api docs (missing await) (#1230) 2019-12-21 08:47:18 +06:00
0cb1b0642f add test server data test 2019-12-20 23:18:59 +06:00
48476362a3 update changes 2019-12-20 17:59:34 +06:00
2b4256baab add links to configs 2019-12-20 17:49:05 +06:00
e5a50f423d Make web::Data deref to Arc<T> #1214 2019-12-20 17:45:35 +06:00
8b8a9a995d bump ver 2019-12-20 17:36:48 +06:00
74fa4060c2 fix awc tests 2019-12-20 17:27:32 +06:00
c877840c07 rename App::register_data to App::app_data and HttpRequest::app_data returns Option<&T> instead of Option<&Data<T>> 2019-12-20 17:13:09 +06:00
20248daeda Allow to set peer_addr for TestRequest #1074 2019-12-20 16:11:51 +06:00
a08d8dab70 AppConfig::secure() is always false. #1202 2019-12-20 16:04:51 +06:00
fbbb4a86e9 feat: add access to the session also from immutable references (#1225) 2019-12-20 13:59:07 +06:00
1d12ba9d5f Replace brotli with brotli2 #1224 2019-12-20 13:50:07 +06:00
8c54054844 Use .advance() intead of .split_to() 2019-12-19 09:56:14 +06:00
1732ae8c79 fix Bodyencoding trait usage 2019-12-18 09:30:14 +06:00
3b860ebdc7 Fix poll_ready call for WebSockets upgrade (#1219)
* Fix poll_ready call for WebSockets upgrade

* Poll upgrade service from H1ServiceHandler too
2019-12-17 13:34:25 +06:00
29ac6463e1 Merge branch 'master' of github.com:actix/actix-web 2019-12-16 17:22:49 +06:00
01613f334b Move BodyEncoding to dev module #1220 2019-12-16 17:22:26 +06:00
30dcaf9da0 fix deprecated Error::description (#1218) 2019-12-16 07:43:19 +06:00
b0aa9395da prep actix-web alpha.6 release 2019-12-15 22:51:14 +06:00
a153374b61 migrate actix-web-actors 2019-12-15 22:45:38 +06:00
a791aab418 prep awc release 2019-12-15 13:36:05 +06:00
cb705317b8 compile with default-features off 2019-12-15 13:28:54 +06:00
e8e0f98f96 fix docs.rs features list 2019-12-13 12:41:48 +06:00
c878f66d05 fix docs.rs features list 2019-12-13 12:40:22 +06:00
fac6dec3c9 update deps 2019-12-13 12:36:15 +06:00
232f71b3b5 update changes 2019-12-13 12:18:30 +06:00
8881c13e60 update changes 2019-12-13 12:16:43 +06:00
d006a7b31f update changes 2019-12-13 12:10:45 +06:00
3d64d565d9 fix warnings 2019-12-13 11:46:02 +06:00
c1deaaeb2f cleanup imports 2019-12-13 11:24:57 +06:00
b81417c2fa fix warnings 2019-12-13 10:59:02 +06:00
4937c9f9c2 refactor http-test server 2019-12-12 23:08:38 +06:00
db1d6b7963 refactor test server impl 2019-12-12 22:28:47 +06:00
fa07415721 Replace flate2-xxx features with compress 2019-12-12 15:08:08 +06:00
b4b3350b3e Add websockets continuation frame support 2019-12-12 14:06:54 +06:00
4a1695f719 fixes missing import in example (#1210) 2019-12-12 07:06:22 +06:00
1b8d747937 Fix extra line feed (#1209) 2019-12-12 07:05:39 +06:00
a43a005f59 Log path if it is not a directory (#1208) 2019-12-12 07:04:53 +06:00
a612b74aeb actix-multipart: Fix multipart boundary reading (#1205)
* actix-multipart: Fix multipart boundary reading

If we're not ready to read the first line after the multipart field
(which should be a "\r\n" line) then return Pending instead of Ready(None)
so that we will get called again to read that line.

Without this I was getting MultipartError::Boundary from read_boundary()
because it got the "\r\n" line instead of the boundary.

Also tweaks the test_stream test to test partial reads.

This is a forward port of #1189 from 1.0

* actix-multipart: Update changes for boundary fix
2019-12-12 07:03:44 +06:00
131c897099 upgrade to actix-net release 2019-12-11 19:20:20 +06:00
ef3a33b9d6 use std mutext instead of parking_lot 2019-12-10 09:00:51 +06:00
5132257b0d Fix buffer remaining capacity calcualtion 2019-12-09 21:55:22 +06:00
0c1f5f9edc Check Upgrade service readiness before calling it 2019-12-09 17:40:15 +06:00
e4382e4fc1 Fix broken docs (#1204)
Fixed un escaped brackets in lib.rs, and reflowed links to ConnectionInfo in app, config, and server.rs
2019-12-09 10:02:43 +06:00
a3ce371312 ws ping and pong uses bytes #1049 2019-12-09 07:01:22 +06:00
42258ee289 deps 2019-12-08 20:22:39 +06:00
b92eafb839 prepare actix-http release 2019-12-08 20:15:51 +06:00
3b2e78db47 add link to chat 2019-12-08 19:27:06 +06:00
63da1a5560 Merge branch 'master' of github.com:actix/actix-web 2019-12-08 19:26:12 +06:00
1f3ffe38e8 update actix-service dep 2019-12-08 19:25:24 +06:00
c23b6b3879 Merge pull request #1192 from krircc/master
Add rich project metadata
2019-12-08 16:03:39 +08:00
909c7c8b5b Merge branch 'master' into master 2019-12-08 16:26:35 +09:00
4a8a9ef405 update tests and clippy warnings 2019-12-08 12:31:16 +06:00
6c9f9fff73 clippy warnings 2019-12-08 00:46:51 +06:00
8df33f7a81 remove HttpServer::run() as it is not useful with async/await 2019-12-08 00:06:04 +06:00
7ec5ca88a1 update changes 2019-12-07 22:01:55 +06:00
e5f3d88a4e Switch brotli compressor to rust. (#1197)
* Switch to a rustified version of brotli.

* Some memory optimizations.

* Make brotli not optional anymore.
2019-12-07 21:55:41 +06:00
0ba125444a Add impl ResponseBuilder for Error 2019-12-07 21:41:34 +06:00
6c226e47bd prepare actix-web-actors release 2019-12-07 20:10:36 +06:00
8c3f58db9d Allow comma-separated websocket subprotocols without spaces (#1172)
* Allow comma-separated websocket subprotocols without spaces

* [CHANGES] Added an entry to CHANGES.md
2019-12-07 20:08:06 +06:00
4921243add Fix rustls build. (#1195) 2019-12-07 16:14:09 +06:00
91b3fcf85c Fix dependency features. (#1196) 2019-12-07 16:13:26 +06:00
1729a52f8b prepare alpha.3 release 2019-12-07 13:00:03 +06:00
ed2f3fe80d use actix-net alpha.3 release 2019-12-07 12:28:26 +06:00
f2ba389496 Merge branch 'master' into master 2019-12-06 16:57:42 +09:00
439f02b6b1 Update README.md 2019-12-06 14:59:11 +08:00
e32da08a26 Update README.md 2019-12-06 14:34:14 +08:00
82110e0927 Update README.md 2019-12-06 14:29:10 +08:00
7b3354a9ad Update README.md 2019-12-06 14:26:23 +08:00
5243e8baca Update README.md 2019-12-06 14:23:28 +08:00
98903028c7 Update README.md 2019-12-06 14:22:29 +08:00
7dd676439c update changes for actix-session 2019-12-06 11:24:25 +06:00
fbead137f0 feat: add access to UserSession from RequestHead (#1164)
* feat: add access to UserSession from RequestHead

* add test case for session from RequestHead and changes entry for the new feature
2019-12-06 11:21:43 +06:00
205a964d8f upgrade to tokio 0.2 2019-12-05 23:35:43 +06:00
b45c6cd66b replace hashbrown with std hashmap 2019-12-04 18:33:43 +06:00
0015a204aa update version 2019-12-03 19:03:53 +06:00
c7ed6d3428 update version 2019-12-03 16:35:31 +06:00
cf30eafb49 update md 2019-12-03 00:49:12 +06:00
14075ebf7f use released versions of actix-net 2019-12-02 23:33:39 +06:00
068f047dd5 update service factory config 2019-12-02 21:37:13 +06:00
f4c01384ec update to latest actix-net 2019-12-02 17:33:11 +06:00
b7d44d6c4c Merge pull request #1 from actix/master
git pull
2019-12-01 16:56:42 +08:00
33574403b5 Remove rustls from package.metadata.docs.rs (#1182) 2019-11-28 06:25:21 +06:00
dcc6efa3e6 Merge branch 'master' of github.com:actix/actix-web 2019-11-27 21:08:13 +06:00
56b9f11c98 disable rustls 2019-11-27 21:07:49 +06:00
f43a706364 Set name for each generated resource 2019-11-26 19:25:28 +06:00
f2b3dc5625 update examples 2019-11-26 17:16:33 +06:00
f73f97353b refactor ResponseError trait 2019-11-26 16:07:39 +06:00
4dc31aac93 use actix_rt::test for test setup 2019-11-26 11:25:50 +06:00
c1c44a7dd6 upgrade derive_more 2019-11-25 17:59:14 +06:00
c5907747ad Remove implementation of Responder for (). Fixes #1108.
Rationale:

- In Rust, one can omit a semicolon after a function's final expression to make
  its value the function's return value. It's common for people to include a
  semicolon after the last expression by mistake - common enough that the Rust
  compiler suggests removing the semicolon when there's a type mismatch between
  the function's signature and body. By implementing Responder for (), Actix makes
  this common mistake a silent error in handler functions.

- Functions returning an empty body should return HTTP status 204 ("No Content"),
  so the current Responder impl for (), which returns status 200 ("OK"), is not
  really what one wants anyway.

- It's not much of a burden to ask handlers to explicitly return
  `HttpResponse::Ok()` if that is what they want; all the examples in the
  documentation do this already.
2019-11-23 21:10:02 +06:00
525c22de15 fix typos from updating to futures 0.3 2019-11-22 13:25:55 +06:00
57981ca04a update tests to async handlers 2019-11-22 11:49:35 +06:00
e668acc596 update travis config 2019-11-22 10:13:32 +06:00
512dd2be63 disable rustls support 2019-11-22 07:01:05 +06:00
8683ba8bb0 rename .to_async() to .to() 2019-11-21 21:36:35 +06:00
0b9e3d381b add test with custom connector 2019-11-21 17:36:18 +06:00
1f0577f8d5 cleanup api doc examples 2019-11-21 16:02:17 +06:00
53c5151692 use response instead of result for asyn c handlers 2019-11-21 16:02:17 +06:00
55698f2524 migrade rest of middlewares 2019-11-21 16:02:17 +06:00
471f82f0e0 migrate actix-multipart 2019-11-21 16:02:17 +06:00
60ada97b3d migrate actix-session 2019-11-21 16:02:17 +06:00
0de101bc4d update actix-web-codegen tests 2019-11-21 16:02:17 +06:00
95e2a0ef2e migrate actix-framed 2019-11-21 16:02:17 +06:00
69cadcdedb migrate actix-files 2019-11-21 16:02:17 +06:00
6ac4ac66b9 migrate actix-cors 2019-11-21 16:02:17 +06:00
3646725cf6 migrate actix-identity 2019-11-21 16:02:17 +06:00
ff62facc0d disable unmigrated crates 2019-11-21 16:02:17 +06:00
b510527a9f update awc tests 2019-11-21 16:02:17 +06:00
3127dd4db6 migrate actix-web to std::future 2019-11-21 16:02:17 +06:00
d081e57316 fix h2 client send body 2019-11-21 16:02:17 +06:00
1ffa7d18d3 drop unpin constraint 2019-11-21 16:02:17 +06:00
687884fb94 update test-server tests 2019-11-21 16:02:17 +06:00
5ab29b2e62 migrate awc and test-server to std::future 2019-11-21 16:02:17 +06:00
a6a2d2f444 update ssl impls 2019-11-21 16:02:17 +06:00
9e95efcc16 migrate client to std::future 2019-11-21 16:02:17 +06:00
8cba1170e6 make actix-http compile with std::future 2019-11-21 16:02:17 +06:00
5cb2d500d1 update actix-web-actors 2019-11-14 08:58:24 +06:00
0212c618c6 prepare actix-web release 2019-11-14 08:55:37 +06:00
88110ed268 Add security note to ConnectionInfo::remote() (#1158) 2019-11-14 08:32:47 +06:00
fba02fdd8c prep awc release 2019-11-06 11:33:25 -08:00
b2934ad8d2 prep actix-file release 2019-11-06 11:25:26 -08:00
f7f410d033 fix test order dep 2019-11-06 11:20:47 -08:00
885ff7396e prepare actox-http release 2019-11-06 10:35:13 -08:00
61b38e8d0d Increase timeouts in test-server (#1153) 2019-11-06 06:09:22 -08:00
edcde67076 Fix escaping/encoding problems in Content-Disposition header (#1151)
* Fix filename encoding in Content-Disposition of acitx_files::NamedFile

* Add more comments on how to use Content-Disposition header properly & Fix some trivial problems

* Improve Content-Disposition filename(*) parameters of actix_files::NamedFile

* Tweak Content-Disposition parse to accept empty param value in quoted-string

* Fix typos in comments in .../content_disposition.rs (pointed out by @JohnTitor)

* Update CHANGES.md

* Update CHANGES.md again
2019-11-06 06:08:37 -08:00
f0612f7570 awc: Add support for setting query from Serialize type for client request (#1130)
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2019-10-26 08:27:14 +03:00
ace98e3a1e support Host guards when Host header is unset (#1129) 2019-10-15 05:05:54 +06:00
1ca9d87f0a prep actix-web-codegen release 2019-10-14 21:35:53 +06:00
967f965405 Update syn & quote to 1.0 (#1133)
* chore(actix-web-codegen): Upgrade syn and quote to 1.0

* feat(actix-web-codegen): Generate better error message

* doc(actix-web-codegen): Update CHANGES.md

* fix: Build with stable rust
2019-10-14 21:34:17 +06:00
062e51e8ce prep actix-file release 2019-10-14 21:26:26 +06:00
a48e616def feat(files): add possibility to redirect to slash-ended path (#1134)
When accessing to a folder without a final slash, the index file will be loaded ok, but if it has
references (like a css or an image in an html file) these resources won't be loaded correctly if
they are using relative paths. In order to solve this, this PR adds the possibility to detect
folders without a final slash and make a 302 redirect to mitigate this issue. The behavior is off by
default. We're adding a new method called `redirect_to_slash_directory` which can be used to enable
this behavior.
2019-10-14 21:23:15 +06:00
effa96f5e4 Removed httpcode 'MovedPermanenty'. (#1128) 2019-10-12 06:45:12 +06:00
cc0b4be5b7 Fix typo in response.rs body() comment (#1126)
Fixes https://github.com/actix/actix-web/issues/1125
2019-10-09 19:11:55 +06:00
a464ffc23d prepare actix-files release 2019-10-08 10:13:16 +06:00
4de2e8a898 [actix-files] Allow user defined guards for NamedFile (actix#1113) (#1115)
* [actix-files] remove request method checks from NamedFile

* [actix-files] added custom guard checks to FilesService

* [actix-files] modify method check tests (NamedFile -> Files)

* [actix-files] add test for custom guards in Files

* [actix-files] update changelog
2019-10-08 10:09:40 +06:00
0f09415469 Convert documentation examples to Rust 2018 edition (#1120)
* Convert types::query examples to rust-2018 edition

* Convert types::json examples to rust-2018 edition

* Convert types::path examples to rust-2018 edition

* Convert types::form examples to rust-2018 edition

* Convert rest of the examples to rust-2018 edition.
2019-10-07 11:29:11 +06:00
f089cf185b Let ResponseError render w/ 'text/plain; charset=utf-8' header (#1118) (#1119)
* Let ResponseError render w/ 'text/plain; charset=utf-8' header (#1118)

Trait ResponseError originally render Error messages with header
`text/plain` , which causes browsers (i.e. Firefox 70.0) with
Non-English locale unable to render UTF-8 responses with non-English
characters correctly. i.e. emoji.

This fix solved this problem by specifying the charset of `text/plain`
as utf-8, which is the default charset in rust.

Before actix-web consider to support other charsets, this hotfix is
 enough.

Test case:

fn test() -> Result<String, actix_web::Error> {
    Err(actix_web::error::ErrorForbidden("😋test"))
}

* Update actix-http/CHANGES.md for #1118
2019-10-07 10:56:24 +06:00
15d3c1ae81 Update docs of guard.rs (#1116)
* Update guard.rs
2019-10-07 12:05:17 +09:00
fba31d4e0a Expose ContentDisposition in actix-multipart to fix broken doc link (#1114)
* Expose ContentDisposition in actix-multipart to fix broken doc link

* Revert "Expose ContentDisposition in actix-multipart to fix broken doc link"

This reverts commit e90d71d16c.

* Unhide actix-http::header::common docs

These types are used in other exported documented interfaces and create
broken links if not documented.
See `actix_multipart::Field.content_disposition`
2019-10-02 09:48:25 +06:00
f81ae37677 Add From<Payload> for crate::dev::Payload (#1110)
* Add From<Payload> for crate::dev::Payload

* Make dev::Payload field of Payload public and add into_inner method

* Add changelog entry
2019-10-01 14:05:38 +06:00
5169d306ae update ConnectionInfo.remote() doc string 2019-09-27 07:03:12 +06:00
4f3e97fff8 prepare actix-web release 2019-09-25 15:39:09 +06:00
3ff01a9fc4 Add changelog entry for #1101 (#1102) 2019-09-25 15:35:28 +06:00
3d4e45a0e5 prepare release 2019-09-25 15:30:20 +06:00
c659c33919 Feature uds: Add listen_uds to ServerBuilder (#1085)
Allows using an existing Unix Listener instead of binding to a path.
Useful for when running as a daemon under systemd.

Change-Id: I54a0e78c321d8b7a9ded381083217af590e9a7fa
2019-09-25 15:16:51 +06:00
959f7754b2 Merge pull request #1101 from actix/add-awc-get-head-methods
Add remaining getter methods from private head field
2019-09-25 10:23:23 +02:00
23f04c4f38 Add remaining getter methods from private head field 2019-09-25 08:50:45 +02:00
d9af8f66ba Use actix-testing for testing utils 2019-09-25 10:28:41 +06:00
aa39b8ca6f Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() (#1096)
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()

* Update actix-http/CHANGES.md
2019-09-25 09:33:52 +06:00
58c7065f08 Implement register_data method on Resource and Scope. (#1094)
* Implement `register_data` method on `Resource` and `Scope`.

* Split Scope::register_data tests out from Scope::data tests.

* CHANGES.md: Mention {Scope,Resource}::register_data.
2019-09-18 06:36:39 +06:00
b3783b403e Merge branch 'master' of github.com:actix/actix-web 2019-09-17 21:46:45 +06:00
e4503046de Do not override current System 2019-09-17 21:45:06 +06:00
32a1c36597 Make UrlencodedError::Overflow more informative (#1089) 2019-09-17 06:58:04 +06:00
7c9f9afc46 Add ability to use Infallible as HttpResponse error type (#1093)
* Add `std::convert::Infallible` implementantion for `ResponseError`

* Add from `std::convert::Infallible` to `Error`

* Remove `ResponseError` implementantion for `Infallible`

* Remove useless docs

* Better comment

* Update changelog

* Update actix_http::changelog
2019-09-17 06:57:38 +06:00
c1f99e0775 Remove mem::uninitialized() (#1090) 2019-09-16 07:52:23 +09:00
a32573bb58 Allow to re-construct ServiceRequest from HttpRequest and Payload #1088 2019-09-13 11:56:24 +06:00
e35d930ef9 prepare releases 2019-09-12 21:58:08 +06:00
60b7aebd0a fmt & clippy 2019-09-12 21:52:46 +06:00
45d2fd4299 export frozen request related types; refactor code layout 2019-09-12 10:40:56 +06:00
71f8577713 prepare awc release 2019-09-11 20:13:28 +06:00
195 changed files with 13647 additions and 10680 deletions

37
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,37 @@
---
name: bug report
about: create a bug report
---
Your issue may already be reported!
Please search on the [Actix Web issue tracker](https://github.com/actix/actix-web/issues) before creating one.
## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
## Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
## Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.
## Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
* Rust Version (I.e, output of `rustc -V`):
* Actix Web Version:

47
.github/workflows/bench.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: Benchmark (Linux)
on: [push, pull_request]
jobs:
check_benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1
with:
path: target
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Check benchmark
uses: actions-rs/cargo@v1
with:
command: bench
- name: Clear the cargo caches
run: |
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

90
.github/workflows/linux.yml vendored Normal file
View File

@ -0,0 +1,90 @@
name: CI (Linux)
on: [push, pull_request]
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- 1.39.0 # MSRV
- stable
- nightly
name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1
with:
path: target
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture
- name: tests (actix-http)
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: test
args: --package=actix-http --no-default-features --features=rustls -- --nocapture
- name: tests (awc)
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: test
args: --package=awc --no-default-features --features=rustls -- --nocapture
- name: Generate coverage file
if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
run: |
cargo install cargo-tarpaulin
cargo tarpaulin --out Xml
- name: Upload to Codecov
if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: cobertura.xml
- name: Clear the cargo caches
run: |
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

64
.github/workflows/macos.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: CI (macOS)
on: [push, pull_request]
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-apple-darwin
runs-on: macOS-latest
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-apple-darwin
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1
with:
path: target
key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture
--skip=test_h2_content_length
--skip=test_reading_deflate_encoding_large_random_rustls
- name: Clear the cargo caches
run: |
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

35
.github/workflows/upload-doc.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Upload documentation
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'actix/actix-web'
steps:
- uses: actions/checkout@master
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: check build
uses: actions-rs/cargo@v1
with:
command: doc
args: --no-deps --all-features
- name: Tweak HTML
run: echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html
- name: Upload documentation
run: |
git clone https://github.com/davisp/ghp-import.git
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://${{ secrets.GITHUB_TOKEN }}@github.com/"${{ github.repository }}.git" target/doc

59
.github/workflows/windows.yml vendored Normal file
View File

@ -0,0 +1,59 @@
name: CI (Windows)
on: [push, pull_request]
env:
VCPKGRS_DYNAMIC: 1
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-pc-windows-msvc
runs-on: windows-latest
steps:
- uses: actions/checkout@master
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-pc-windows-msvc
profile: minimal
override: true
- name: Install OpenSSL
run: |
vcpkg integrate install
vcpkg install openssl:x64-windows
Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll
Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
Get-ChildItem C:\vcpkg\installed\x64-windows\lib
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --all --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all --all-features --no-fail-fast -- --nocapture
--skip=test_h2_content_length
--skip=test_reading_deflate_encoding_large_random_rustls
--skip=test_params
--skip=test_simple
--skip=test_expect_continue
--skip=test_http10_keepalive
--skip=test_slow_request
--skip=test_connection_force_close
--skip=test_connection_server_close
--skip=test_connection_wait_queue_force_close

View File

@ -1,58 +0,0 @@
language: rust
sudo: required
dist: trusty
cache:
# cargo: true
apt: true
matrix:
include:
- rust: stable
- rust: beta
- rust: nightly-2019-08-10
allow_failures:
- rust: nightly-2019-08-10
env:
global:
# - RUSTFLAGS="-C link-dead-code"
- OPENSSL_VERSION=openssl-1.0.2
before_install:
- sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
- sudo apt-get update -qq
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
before_cache: |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-08-10" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --version 0.6.11 cargo-tarpaulin
fi
# Add clippy
before_script:
- export PATH=$PATH:~/.cargo/bin
script:
- cargo update
- cargo check --all --no-default-features
- cargo test --all-features --all -- --nocapture
- cd actix-http; cargo test --no-default-features --features="rust-tls" -- --nocapture; cd ..
- cd awc; cargo test --no-default-features --features="rust-tls" -- --nocapture; cd ..
# Upload docs
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --no-deps --all-features &&
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 &&
echo "Uploaded documentation"
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-08-10" ]]; then
taskset -c 0 cargo tarpaulin --out Xml --all --all-features
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi

View File

@ -1,13 +1,110 @@
# Changes # Changes
## not released yet
### Added
* Add `middleware::Conditon` that conditionally enables another middleware ## [2.0.NEXT] - 2020-01-xx
### Changed
* Use `sha-1` crate instead of unmaintained `sha1` crate
* Skip empty chunks when returning response from a `Stream` #1308
* Update the `time` dependency to 0.2.7
## [2.0.0] - 2019-12-25
### Changed
* Rename `HttpServer::start()` to `HttpServer::run()`
* Allow to gracefully stop test server via `TestServer::stop()`
* Allow to specify multi-patterns for resources
## [2.0.0-rc] - 2019-12-20
### Changed
* Move `BodyEncoding` to `dev` module #1220
* Allow to set `peer_addr` for TestRequest #1074
* Make web::Data deref to Arc<T> #1214
* Rename `App::register_data()` to `App::app_data()`
* `HttpRequest::app_data<T>()` returns `Option<&T>` instead of `Option<&Data<T>>`
### Fixed ### Fixed
* h2 will use error response #1080 * Fix `AppConfig::secure()` is always false. #1202
## [2.0.0-alpha.6] - 2019-12-15
### Fixed
* Fixed compilation with default features off
## [2.0.0-alpha.5] - 2019-12-13
### Added
* Add test server, `test::start()` and `test::start_with()`
## [2.0.0-alpha.4] - 2019-12-08
### Deleted
* Delete HttpServer::run(), it is not useful witht async/await
## [2.0.0-alpha.3] - 2019-12-07
### Changed
* Migrate to tokio 0.2
## [2.0.0-alpha.1] - 2019-11-22
### Changed
* Migrated to `std::future`
* Remove implementation of `Responder` for `()`. (#1167)
## [1.0.9] - 2019-11-14
### Added
* Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110)
### Changed
* Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129)
## [1.0.8] - 2019-09-25
### Added
* Add `Scope::register_data` and `Resource::register_data` methods, parallel to
`App::register_data`.
* Add `middleware::Condition` that conditionally enables another middleware
* Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload`
* Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path,
which is useful for example with systemd.
### Changed
* Make UrlEncodedError::Overflow more informativve
* Use actix-testing for testing utils
## [1.0.7] - 2019-08-29 ## [1.0.7] - 2019-08-29

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "1.0.7" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"
@ -12,11 +12,10 @@ categories = ["network-programming", "asynchronous",
"web-programming::http-server", "web-programming::http-server",
"web-programming::websocket"] "web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls", "uds"] features = ["openssl", "rustls", "compress", "secure-cookies"]
[badges] [badges]
travis-ci = { repository = "actix/actix-web", branch = "master" } travis-ci = { repository = "actix/actix-web", branch = "master" }
@ -43,77 +42,64 @@ members = [
] ]
[features] [features]
default = ["brotli", "flate2-zlib", "client", "fail"] default = ["compress", "failure"]
# http client # content-encoding support
client = ["awc"] compress = ["actix-http/compress", "awc/compress"]
# brotli encoding, requires c compiler
brotli = ["actix-http/brotli"]
# miniz-sys backend for flate2 crate
flate2-zlib = ["actix-http/flate2-zlib"]
# rust backend for flate2 crate
flate2-rust = ["actix-http/flate2-rust"]
# sessions feature, session require "ring" crate and c compiler # sessions feature, session require "ring" crate and c compiler
secure-cookies = ["actix-http/secure-cookies"] secure-cookies = ["actix-http/secure-cookies"]
fail = ["actix-http/fail"] failure = ["actix-http/failure"]
# openssl # openssl
ssl = ["openssl", "actix-server/ssl", "awc/ssl"] openssl = ["actix-tls/openssl", "awc/openssl", "open-ssl"]
# rustls # rustls
rust-tls = ["rustls", "actix-server/rust-tls", "awc/rust-tls"] rustls = ["actix-tls/rustls", "awc/rustls", "rust-tls"]
# unix domain sockets support
uds = ["actix-server/uds"]
[dependencies] [dependencies]
actix-codec = "0.1.2" actix-codec = "0.2.0"
actix-service = "0.4.1" actix-service = "1.0.2"
actix-utils = "0.4.4" actix-utils = "1.0.6"
actix-router = "0.1.5" actix-router = "0.2.4"
actix-rt = "0.2.4" actix-rt = "1.0.0"
actix-web-codegen = "0.1.2" actix-server = "1.0.0"
actix-http = "0.2.9" actix-testing = "1.0.0"
actix-server = "0.6.0" actix-macros = "0.1.0"
actix-server-config = "0.1.2" actix-threadpool = "0.3.1"
actix-threadpool = "0.1.1" actix-tls = "1.0.0"
awc = { version = "0.2.4", optional = true }
bytes = "0.4" actix-web-codegen = "0.2.0"
derive_more = "0.15.0" actix-http = "1.0.1"
awc = { version = "1.0.1", default-features = false }
bytes = "0.5.3"
derive_more = "0.99.2"
encoding_rs = "0.8" encoding_rs = "0.8"
futures = "0.1.25" futures = "0.3.1"
hashbrown = "0.5.0" fxhash = "0.2.1"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
net2 = "0.2.33" net2 = "0.2.33"
parking_lot = "0.9" pin-project = "0.4.6"
regex = "1.0" regex = "1.3"
serde = { version = "1.0", features=["derive"] } serde = { version = "1.0", features=["derive"] }
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.6.1"
time = "0.1.42" time = { version = "0.2.7", default-features = false, features = ["std"] }
url = "2.1" url = "2.1"
open-ssl = { version="0.10", package = "openssl", optional = true }
# ssl support rust-tls = { version = "0.16.0", package = "rustls", optional = true }
openssl = { version="0.10", optional = true }
rustls = { version = "0.15", optional = true }
[dev-dependencies] [dev-dependencies]
actix = "0.8.3" actix = "0.9.0"
actix-connect = "0.2.2"
actix-http-test = "0.2.4"
rand = "0.7" rand = "0.7"
env_logger = "0.6" env_logger = "0.6"
serde_derive = "1.0" serde_derive = "1.0"
tokio-timer = "0.2.8"
brotli2 = "0.3.2" brotli2 = "0.3.2"
flate2 = "1.0.2" flate2 = "1.0.13"
criterion = "0.3"
[profile.release] [profile.release]
lto = true lto = true
@ -125,8 +111,17 @@ actix-web = { path = "." }
actix-http = { path = "actix-http" } actix-http = { path = "actix-http" }
actix-http-test = { path = "test-server" } actix-http-test = { path = "test-server" }
actix-web-codegen = { path = "actix-web-codegen" } actix-web-codegen = { path = "actix-web-codegen" }
actix-web-actors = { path = "actix-web-actors" } actix-cors = { path = "actix-cors" }
actix-identity = { path = "actix-identity" }
actix-session = { path = "actix-session" } actix-session = { path = "actix-session" }
actix-files = { path = "actix-files" } actix-files = { path = "actix-files" }
actix-multipart = { path = "actix-multipart" } actix-multipart = { path = "actix-multipart" }
awc = { path = "awc" } awc = { path = "awc" }
[[bench]]
name = "server"
harness = false
[[bench]]
name = "service"
harness = false

View File

@ -1,3 +1,57 @@
## Unreleased
* Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now
result in `SameSite=None` being sent with the response Set-Cookie header.
To create a cookie without a SameSite attribute, remove any calls setting same_site.
## 2.0.0
* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
`.await` on `run` method result, in that case it awaits server exit.
* `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`.
Stored data is available via `HttpRequest::app_data()` method at runtime.
* Extractor configuration must be registered with `App::app_data()` instead of `App::data()`
* Sync handlers has been removed. `.to_async()` method has been renamed to `.to()`
replace `fn` with `async fn` to convert sync handler to async
* `actix_http_test::TestServer` moved to `actix_web::test` module. To start
test server use `test::start()` or `test_start_with_config()` methods
* `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders
http response.
* Feature `rust-tls` renamed to `rustls`
instead of
```rust
actix-web = { version = "2.0.0", features = ["rust-tls"] }
```
use
```rust
actix-web = { version = "2.0.0", features = ["rustls"] }
```
* Feature `ssl` renamed to `openssl`
instead of
```rust
actix-web = { version = "2.0.0", features = ["ssl"] }
```
use
```rust
actix-web = { version = "2.0.0", features = ["openssl"] }
```
* `Cors` builder now requires that you call `.finish()` to construct the middleware
## 1.0.1 ## 1.0.1
* Cors middleware has been moved to `actix-cors` crate * Cors middleware has been moved to `actix-cors` crate

View File

@ -1,4 +1,28 @@
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) <div align="center">
<p><h1>Actix web</h1> </p>
<p><strong>Actix web is a small, pragmatic, and extremely fast rust web framework</strong> </p>
<p>
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)
[![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web)
[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Documentation](https://docs.rs/actix-web/badge.svg)](https://docs.rs/actix-web)
[![Download](https://img.shields.io/crates/d/actix-web.svg)](https://crates.io/crates/actix-web)
[![Version](https://img.shields.io/badge/rustc-1.39+-lightgray.svg)](https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html)
![License](https://img.shields.io/crates/l/actix-web.svg)
</p>
<h3>
<a href="https://actix.rs">Website</a>
<span> | </span>
<a href="https://gitter.im/actix/actix">Chat</a>
<span> | </span>
<a href="https://github.com/actix/examples">Examples</a>
</h3>
</div>
<br>
Actix web is a simple, pragmatic and extremely fast web framework for Rust. Actix web is a simple, pragmatic and extremely fast web framework for Rust.
@ -15,30 +39,32 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Supports [Actix actor framework](https://github.com/actix/actix) * Supports [Actix actor framework](https://github.com/actix/actix)
## Documentation & community resources
* [User Guide](https://actix.rs/docs/)
* [API Documentation (1.0)](https://docs.rs/actix-web/)
* [API Documentation (0.7)](https://docs.rs/actix-web/0.7.19/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.36 or later
## Example ## Example
```rust Dependencies:
use actix_web::{web, App, HttpServer, Responder};
fn index(info: web::Path<(u32, String)>) -> impl Responder { ```toml
[dependencies]
actix-web = "2"
actix-rt = "1"
```
Code:
```rust
use actix_web::{get, web, App, HttpServer, Responder};
#[get("/{id}/{name}/index.html")]
async fn index(info: web::Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", info.1, info.0) format!("Hello {}! id:{}", info.1, info.0)
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
HttpServer::new( async fn main() -> std::io::Result<()> {
|| App::new().service( HttpServer::new(|| App::new().service(index))
web::resource("/{id}/{name}/index.html").to(index)))
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .run()
.await
} }
``` ```
@ -49,10 +75,11 @@ fn main() -> std::io::Result<()> {
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) * [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) * [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) / * [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates * [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) * [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/) * [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) * [OpenSSL](https://github.com/actix/examples/tree/master/openssl/)
* [Rustls](https://github.com/actix/examples/tree/master/rustls/)
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) * [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
* [Json](https://github.com/actix/examples/tree/master/json/) * [Json](https://github.com/actix/examples/tree/master/json/)

View File

@ -1,8 +1,14 @@
# Changes # Changes
## [0.1.1] - unreleased ## [0.2.0] - 2019-12-20
* Bump `derive_more` crate version to 0.15.0 * Release
## [0.2.0-alpha.3] - 2019-12-07
* Migrate to actix-web 2.0.0
* Bump `derive_more` crate version to 0.99.0
## [0.1.0] - 2019-06-15 ## [0.1.0] - 2019-06-15

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-cors" name = "actix-cors"
version = "0.1.0" version = "0.2.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Cross-origin resource sharing (CORS) for Actix applications." description = "Cross-origin resource sharing (CORS) for Actix applications."
readme = "README.md" readme = "README.md"
@ -10,14 +10,17 @@ repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-cors/" documentation = "https://docs.rs/actix-cors/"
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
edition = "2018" edition = "2018"
#workspace = ".." workspace = ".."
[lib] [lib]
name = "actix_cors" name = "actix_cors"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = "1.0.0" actix-web = "2.0.0-rc"
actix-service = "0.4.0" actix-service = "1.0.1"
derive_more = "0.15.0" derive_more = "0.99.2"
futures = "0.1.25" futures = "0.3.1"
[dev-dependencies]
actix-rt = "1.0.0"

View File

@ -11,7 +11,7 @@
//! use actix_cors::Cors; //! use actix_cors::Cors;
//! use actix_web::{http, web, App, HttpRequest, HttpResponse, HttpServer}; //! use actix_web::{http, web, App, HttpRequest, HttpResponse, HttpServer};
//! //!
//! fn index(req: HttpRequest) -> &'static str { //! async fn index(req: HttpRequest) -> &'static str {
//! "Hello world" //! "Hello world"
//! } //! }
//! //!
@ -23,7 +23,8 @@
//! .allowed_methods(vec!["GET", "POST"]) //! .allowed_methods(vec!["GET", "POST"])
//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) //! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
//! .allowed_header(http::header::CONTENT_TYPE) //! .allowed_header(http::header::CONTENT_TYPE)
//! .max_age(3600)) //! .max_age(3600)
//! .finish())
//! .service( //! .service(
//! web::resource("/index.html") //! web::resource("/index.html")
//! .route(web::get().to(index)) //! .route(web::get().to(index))
@ -39,18 +40,19 @@
//! //!
//! Cors middleware automatically handle *OPTIONS* preflight request. //! Cors middleware automatically handle *OPTIONS* preflight request.
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll};
use actix_service::{IntoTransform, Service, Transform}; use actix_service::{Service, Transform};
use actix_web::dev::{RequestHead, ServiceRequest, ServiceResponse}; use actix_web::dev::{RequestHead, ServiceRequest, ServiceResponse};
use actix_web::error::{Error, ResponseError, Result}; use actix_web::error::{Error, ResponseError, Result};
use actix_web::http::header::{self, HeaderName, HeaderValue}; use actix_web::http::header::{self, HeaderName, HeaderValue};
use actix_web::http::{self, HttpTryFrom, Method, StatusCode, Uri}; use actix_web::http::{self, Error as HttpError, Method, StatusCode, Uri};
use actix_web::HttpResponse; use actix_web::HttpResponse;
use derive_more::Display; use derive_more::Display;
use futures::future::{ok, Either, Future, FutureResult}; use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready};
use futures::Poll;
/// A set of errors that can occur during processing CORS /// A set of errors that can occur during processing CORS
#[derive(Debug, Display)] #[derive(Debug, Display)]
@ -92,6 +94,10 @@ pub enum CorsError {
} }
impl ResponseError for CorsError { impl ResponseError for CorsError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self).into()) HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self).into())
} }
@ -269,7 +275,8 @@ impl Cors {
pub fn allowed_methods<U, M>(mut self, methods: U) -> Cors pub fn allowed_methods<U, M>(mut self, methods: U) -> Cors
where where
U: IntoIterator<Item = M>, U: IntoIterator<Item = M>,
Method: HttpTryFrom<M>, Method: TryFrom<M>,
<Method as TryFrom<M>>::Error: Into<HttpError>,
{ {
self.methods = true; self.methods = true;
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
@ -291,7 +298,8 @@ impl Cors {
/// Set an allowed header /// Set an allowed header
pub fn allowed_header<H>(mut self, header: H) -> Cors pub fn allowed_header<H>(mut self, header: H) -> Cors
where where
HeaderName: HttpTryFrom<H>, HeaderName: TryFrom<H>,
<HeaderName as TryFrom<H>>::Error: Into<HttpError>,
{ {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
match HeaderName::try_from(header) { match HeaderName::try_from(header) {
@ -323,7 +331,8 @@ impl Cors {
pub fn allowed_headers<U, H>(mut self, headers: U) -> Cors pub fn allowed_headers<U, H>(mut self, headers: U) -> Cors
where where
U: IntoIterator<Item = H>, U: IntoIterator<Item = H>,
HeaderName: HttpTryFrom<H>, HeaderName: TryFrom<H>,
<HeaderName as TryFrom<H>>::Error: Into<HttpError>,
{ {
if let Some(cors) = cors(&mut self.cors, &self.error) { if let Some(cors) = cors(&mut self.cors, &self.error) {
for h in headers { for h in headers {
@ -357,7 +366,8 @@ impl Cors {
pub fn expose_headers<U, H>(mut self, headers: U) -> Cors pub fn expose_headers<U, H>(mut self, headers: U) -> Cors
where where
U: IntoIterator<Item = H>, U: IntoIterator<Item = H>,
HeaderName: HttpTryFrom<H>, HeaderName: TryFrom<H>,
<HeaderName as TryFrom<H>>::Error: Into<HttpError>,
{ {
for h in headers { for h in headers {
match HeaderName::try_from(h) { match HeaderName::try_from(h) {
@ -456,25 +466,9 @@ impl Cors {
} }
self self
} }
}
fn cors<'a>( /// Construct cors middleware
parts: &'a mut Option<Inner>, pub fn finish(self) -> CorsFactory {
err: &Option<http::Error>,
) -> Option<&'a mut Inner> {
if err.is_some() {
return None;
}
parts.as_mut()
}
impl<S, B> IntoTransform<CorsFactory, S> for Cors
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
fn into_transform(self) -> CorsFactory {
let mut slf = if !self.methods { let mut slf = if !self.methods {
self.allowed_methods(vec![ self.allowed_methods(vec![
Method::GET, Method::GET,
@ -521,6 +515,16 @@ where
} }
} }
fn cors<'a>(
parts: &'a mut Option<Inner>,
err: &Option<http::Error>,
) -> Option<&'a mut Inner> {
if err.is_some() {
return None;
}
parts.as_mut()
}
/// `Middleware` for Cross-origin resource sharing support /// `Middleware` for Cross-origin resource sharing support
/// ///
/// The Cors struct contains the settings for CORS requests to be validated and /// The Cors struct contains the settings for CORS requests to be validated and
@ -540,7 +544,7 @@ where
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = CorsMiddleware<S>; type Transform = CorsMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
ok(CorsMiddleware { ok(CorsMiddleware {
@ -682,12 +686,12 @@ where
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
type Future = Either< type Future = Either<
FutureResult<Self::Response, Error>, Ready<Result<Self::Response, Error>>,
Either<S::Future, Box<dyn Future<Item = Self::Response, Error = Error>>>, LocalBoxFuture<'static, Result<Self::Response, Error>>,
>; >;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready() self.service.poll_ready(cx)
} }
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
@ -698,7 +702,7 @@ where
.and_then(|_| self.inner.validate_allowed_method(req.head())) .and_then(|_| self.inner.validate_allowed_method(req.head()))
.and_then(|_| self.inner.validate_allowed_headers(req.head())) .and_then(|_| self.inner.validate_allowed_headers(req.head()))
{ {
return Either::A(ok(req.error_response(e))); return Either::Left(ok(req.error_response(e)));
} }
// allowed headers // allowed headers
@ -751,22 +755,32 @@ where
.finish() .finish()
.into_body(); .into_body();
Either::A(ok(req.into_response(res))) Either::Left(ok(req.into_response(res)))
} else if req.headers().contains_key(&header::ORIGIN) { } else {
if req.headers().contains_key(&header::ORIGIN) {
// Only check requests with a origin header. // Only check requests with a origin header.
if let Err(e) = self.inner.validate_origin(req.head()) { if let Err(e) = self.inner.validate_origin(req.head()) {
return Either::A(ok(req.error_response(e))); return Either::Left(ok(req.error_response(e)));
}
} }
let inner = self.inner.clone(); let inner = self.inner.clone();
let has_origin = req.headers().contains_key(&header::ORIGIN);
let fut = self.service.call(req);
Either::B(Either::B(Box::new(self.service.call(req).and_then( Either::Right(
move |mut res| { async move {
let res = fut.await;
if has_origin {
let mut res = res?;
if let Some(origin) = if let Some(origin) =
inner.access_control_allow_origin(res.request().head()) inner.access_control_allow_origin(res.request().head())
{ {
res.headers_mut() res.headers_mut().insert(
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); header::ACCESS_CONTROL_ALLOW_ORIGIN,
origin.clone(),
);
}; };
if let Some(ref expose) = inner.expose_hdrs { if let Some(ref expose) = inner.expose_hdrs {
@ -782,8 +796,9 @@ where
); );
} }
if inner.vary_header { if inner.vary_header {
let value = let value = if let Some(hdr) =
if let Some(hdr) = res.headers_mut().get(&header::VARY) { res.headers_mut().get(&header::VARY)
{
let mut val: Vec<u8> = let mut val: Vec<u8> =
Vec::with_capacity(hdr.as_bytes().len() + 8); Vec::with_capacity(hdr.as_bytes().len() + 8);
val.extend(hdr.as_bytes()); val.extend(hdr.as_bytes());
@ -795,80 +810,68 @@ where
res.headers_mut().insert(header::VARY, value); res.headers_mut().insert(header::VARY, value);
} }
Ok(res) Ok(res)
},
))))
} else { } else {
Either::B(Either::A(self.service.call(req))) res
}
}
.boxed_local(),
)
} }
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_service::{IntoService, Transform}; use actix_service::{fn_service, Transform};
use actix_web::test::{self, block_on, TestRequest}; use actix_web::test::{self, TestRequest};
use super::*; use super::*;
impl Cors { #[actix_rt::test]
fn finish<F, S, B>(self, srv: F) -> CorsMiddleware<S>
where
F: IntoService<S>,
S: Service<
Request = ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
> + 'static,
S::Future: 'static,
B: 'static,
{
block_on(
IntoTransform::<CorsFactory, S>::into_transform(self)
.new_transform(srv.into_service()),
)
.unwrap()
}
}
#[test]
#[should_panic(expected = "Credentials are allowed, but the Origin is set to")] #[should_panic(expected = "Credentials are allowed, but the Origin is set to")]
fn cors_validates_illegal_allow_credentials() { async fn cors_validates_illegal_allow_credentials() {
let _cors = Cors::new() let _cors = Cors::new().supports_credentials().send_wildcard().finish();
.supports_credentials()
.send_wildcard()
.finish(test::ok_service());
} }
#[test] #[actix_rt::test]
fn validate_origin_allows_all_origins() { async fn validate_origin_allows_all_origins() {
let mut cors = Cors::new().finish(test::ok_service()); let mut cors = Cors::new()
.finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test] #[actix_rt::test]
fn default() { async fn default() {
let mut cors = let mut cors = Cors::default()
block_on(Cors::default().new_transform(test::ok_service())).unwrap(); .new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test] #[actix_rt::test]
fn test_preflight() { async fn test_preflight() {
let mut cors = Cors::new() let mut cors = Cors::new()
.send_wildcard() .send_wildcard()
.max_age(3600) .max_age(3600)
.allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.allowed_header(header::CONTENT_TYPE) .allowed_header(header::CONTENT_TYPE)
.finish(test::ok_service()); .finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
@ -877,7 +880,7 @@ mod tests {
assert!(cors.inner.validate_allowed_method(req.head()).is_err()); assert!(cors.inner.validate_allowed_method(req.head()).is_err());
assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); assert!(cors.inner.validate_allowed_headers(req.head()).is_err());
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
@ -897,7 +900,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
resp.headers() resp.headers()
@ -943,13 +946,13 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
// #[test] // #[actix_rt::test]
// #[should_panic(expected = "MissingOrigin")] // #[should_panic(expected = "MissingOrigin")]
// fn test_validate_missing_origin() { // async fn test_validate_missing_origin() {
// let cors = Cors::build() // let cors = Cors::build()
// .allowed_origin("https://www.example.com") // .allowed_origin("https://www.example.com")
// .finish(); // .finish();
@ -957,12 +960,15 @@ mod tests {
// cors.start(&req).unwrap(); // cors.start(&req).unwrap();
// } // }
#[test] #[actix_rt::test]
#[should_panic(expected = "OriginNotAllowed")] #[should_panic(expected = "OriginNotAllowed")]
fn test_validate_not_allowed_origin() { async fn test_validate_not_allowed_origin() {
let cors = Cors::new() let cors = Cors::new()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.finish(test::ok_service()); .finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.unknown.com") let req = TestRequest::with_header("Origin", "https://www.unknown.com")
.method(Method::GET) .method(Method::GET)
@ -972,26 +978,34 @@ mod tests {
cors.inner.validate_allowed_headers(req.head()).unwrap(); cors.inner.validate_allowed_headers(req.head()).unwrap();
} }
#[test] #[actix_rt::test]
fn test_validate_origin() { async fn test_validate_origin() {
let mut cors = Cors::new() let mut cors = Cors::new()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.finish(test::ok_service()); .finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
#[test] #[actix_rt::test]
fn test_no_origin_response() { async fn test_no_origin_response() {
let mut cors = Cors::new().disable_preflight().finish(test::ok_service()); let mut cors = Cors::new()
.disable_preflight()
.finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::default().method(Method::GET).to_srv_request(); let req = TestRequest::default().method(Method::GET).to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert!(resp assert!(resp
.headers() .headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
@ -1000,7 +1014,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"https://www.example.com"[..], &b"https://www.example.com"[..],
resp.headers() resp.headers()
@ -1010,8 +1024,8 @@ mod tests {
); );
} }
#[test] #[actix_rt::test]
fn test_response() { async fn test_response() {
let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
let mut cors = Cors::new() let mut cors = Cors::new()
.send_wildcard() .send_wildcard()
@ -1021,13 +1035,16 @@ mod tests {
.allowed_headers(exposed_headers.clone()) .allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone()) .expose_headers(exposed_headers.clone())
.allowed_header(header::CONTENT_TYPE) .allowed_header(header::CONTENT_TYPE)
.finish(test::ok_service()); .finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
resp.headers() resp.headers()
@ -1065,15 +1082,18 @@ mod tests {
.allowed_headers(exposed_headers.clone()) .allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone()) .expose_headers(exposed_headers.clone())
.allowed_header(header::CONTENT_TYPE) .allowed_header(header::CONTENT_TYPE)
.finish(|req: ServiceRequest| { .finish()
req.into_response( .new_transform(fn_service(|req: ServiceRequest| {
ok(req.into_response(
HttpResponse::Ok().header(header::VARY, "Accept").finish(), HttpResponse::Ok().header(header::VARY, "Accept").finish(),
) ))
}); }))
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"Accept, Origin"[..], &b"Accept, Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes() resp.headers().get(header::VARY).unwrap().as_bytes()
@ -1083,13 +1103,16 @@ mod tests {
.disable_vary_header() .disable_vary_header()
.allowed_origin("https://www.example.com") .allowed_origin("https://www.example.com")
.allowed_origin("https://www.google.com") .allowed_origin("https://www.google.com")
.finish(test::ok_service()); .finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
let origins_str = resp let origins_str = resp
.headers() .headers()
@ -1101,19 +1124,22 @@ mod tests {
assert_eq!("https://www.example.com", origins_str); assert_eq!("https://www.example.com", origins_str);
} }
#[test] #[actix_rt::test]
fn test_multiple_origins() { async fn test_multiple_origins() {
let mut cors = Cors::new() let mut cors = Cors::new()
.allowed_origin("https://example.com") .allowed_origin("https://example.com")
.allowed_origin("https://example.org") .allowed_origin("https://example.org")
.allowed_methods(vec![Method::GET]) .allowed_methods(vec![Method::GET])
.finish(test::ok_service()); .finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://example.com") let req = TestRequest::with_header("Origin", "https://example.com")
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"https://example.com"[..], &b"https://example.com"[..],
resp.headers() resp.headers()
@ -1126,7 +1152,7 @@ mod tests {
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"https://example.org"[..], &b"https://example.org"[..],
resp.headers() resp.headers()
@ -1136,20 +1162,23 @@ mod tests {
); );
} }
#[test] #[actix_rt::test]
fn test_multiple_origins_preflight() { async fn test_multiple_origins_preflight() {
let mut cors = Cors::new() let mut cors = Cors::new()
.allowed_origin("https://example.com") .allowed_origin("https://example.com")
.allowed_origin("https://example.org") .allowed_origin("https://example.org")
.allowed_methods(vec![Method::GET]) .allowed_methods(vec![Method::GET])
.finish(test::ok_service()); .finish()
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::with_header("Origin", "https://example.com") let req = TestRequest::with_header("Origin", "https://example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"https://example.com"[..], &b"https://example.com"[..],
resp.headers() resp.headers()
@ -1163,7 +1192,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_service(&mut cors, req); let resp = test::call_service(&mut cors, req).await;
assert_eq!( assert_eq!(
&b"https://example.org"[..], &b"https://example.org"[..],
resp.headers() resp.headers()

View File

@ -1,21 +1,41 @@
# Changes # Changes
## [0.1.5] - unreleased ## [0.2.1] - 2019-12-22
* Use the same format for file URLs regardless of platforms
## [0.2.0] - 2019-12-20
* Fix BodyEncoding trait import #1220
## [0.2.0-alpha.1] - 2019-12-07
* Migrate to `std::future`
## [0.1.7] - 2019-11-06
* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
## [0.1.6] - 2019-10-14
* Add option to redirect to a slash-ended path `Files` #1132
## [0.1.5] - 2019-10-08
* Bump up `mime_guess` crate version to 2.0.1 * Bump up `mime_guess` crate version to 2.0.1
* Bump up `percent-encoding` crate version to 2.1 * Bump up `percent-encoding` crate version to 2.1
* Allow user defined request guards for `Files` #1113
## [0.1.4] - 2019-07-20 ## [0.1.4] - 2019-07-20
* Allow to disable `Content-Disposition` header #686 * Allow to disable `Content-Disposition` header #686
## [0.1.3] - 2019-06-28 ## [0.1.3] - 2019-06-28
* Do not set `Content-Length` header, let actix-http set it #930 * Do not set `Content-Length` header, let actix-http set it #930
## [0.1.2] - 2019-06-13 ## [0.1.2] - 2019-06-13
* Content-Length is 0 for NamedFile HEAD request #914 * Content-Length is 0 for NamedFile HEAD request #914

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.1.4" version = "0.2.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web." description = "Static files support for actix web."
readme = "README.md" readme = "README.md"
@ -18,13 +18,13 @@ name = "actix_files"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "1.0.2", default-features = false } actix-web = { version = "2.0.0-rc", default-features = false }
actix-http = "0.2.9" actix-http = "1.0.1"
actix-service = "0.4.1" actix-service = "1.0.1"
bitflags = "1" bitflags = "1"
bytes = "0.4" bytes = "0.5.3"
futures = "0.1.25" futures = "0.3.1"
derive_more = "0.15.0" derive_more = "0.99.2"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
mime_guess = "2.0.1" mime_guess = "2.0.1"
@ -32,4 +32,5 @@ percent-encoding = "2.1"
v_htmlescape = "0.4" v_htmlescape = "0.4"
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.2", features=["ssl"] } actix-rt = "1.0.0"
actix-web = { version = "2.0.0-rc", features=["openssl"] }

View File

@ -5,6 +5,7 @@ use derive_more::Display;
#[derive(Display, Debug, PartialEq)] #[derive(Display, Debug, PartialEq)]
pub enum FilesError { pub enum FilesError {
/// Path is not a directory /// Path is not a directory
#[allow(dead_code)]
#[display(fmt = "Path is not a directory. Unable to serve static files")] #[display(fmt = "Path is not a directory. Unable to serve static files")]
IsNotDirectory, IsNotDirectory,
@ -35,7 +36,7 @@ pub enum UriSegmentError {
/// Return `BadRequest` for `UriSegmentError` /// Return `BadRequest` for `UriSegmentError`
impl ResponseError for UriSegmentError { impl ResponseError for UriSegmentError {
fn error_response(&self) -> HttpResponse { fn status_code(&self) -> StatusCode {
HttpResponse::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -12,12 +12,13 @@ use mime;
use mime_guess::from_path; use mime_guess::from_path;
use actix_http::body::SizedStream; use actix_http::body::SizedStream;
use actix_web::dev::BodyEncoding;
use actix_web::http::header::{ use actix_web::http::header::{
self, ContentDisposition, DispositionParam, DispositionType, self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
}; };
use actix_web::http::{ContentEncoding, Method, StatusCode}; use actix_web::http::{ContentEncoding, StatusCode};
use actix_web::middleware::BodyEncoding;
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
use futures::future::{ready, Ready};
use crate::range::HttpRange; use crate::range::HttpRange;
use crate::ChunkedReadFile; use crate::ChunkedReadFile;
@ -93,9 +94,18 @@ impl NamedFile {
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
_ => DispositionType::Attachment, _ => DispositionType::Attachment,
}; };
let mut parameters =
vec![DispositionParam::Filename(String::from(filename.as_ref()))];
if !filename.is_ascii() {
parameters.push(DispositionParam::FilenameExt(ExtendedValue {
charset: Charset::Ext(String::from("UTF-8")),
language_tag: None,
value: filename.into_owned().into_bytes(),
}))
}
let cd = ContentDisposition { let cd = ContentDisposition {
disposition: disposition_type, disposition: disposition_type,
parameters: vec![DispositionParam::Filename(filename.into_owned())], parameters: parameters,
}; };
(ct, cd) (ct, cd)
}; };
@ -246,62 +256,8 @@ impl NamedFile {
pub(crate) fn last_modified(&self) -> Option<header::HttpDate> { pub(crate) fn last_modified(&self) -> Option<header::HttpDate> {
self.modified.map(|mtime| mtime.into()) self.modified.map(|mtime| mtime.into())
} }
}
impl Deref for NamedFile { pub fn into_response(self, req: &HttpRequest) -> Result<HttpResponse, Error> {
type Target = File;
fn deref(&self) -> &File {
&self.file
}
}
impl DerefMut for NamedFile {
fn deref_mut(&mut self) -> &mut File {
&mut self.file
}
}
/// Returns true if `req` has no `If-Match` header or one which matches `etag`.
fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfMatch>() {
None | Some(header::IfMatch::Any) => true,
Some(header::IfMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.strong_eq(some_etag) {
return true;
}
}
}
false
}
}
}
/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`.
fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfNoneMatch>() {
Some(header::IfNoneMatch::Any) => false,
Some(header::IfNoneMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.weak_eq(some_etag) {
return false;
}
}
}
true
}
None => true,
}
}
impl Responder for NamedFile {
type Error = Error;
type Future = Result<HttpResponse, Error>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
if self.status_code != StatusCode::OK { if self.status_code != StatusCode::OK {
let mut resp = HttpResponse::build(self.status_code); let mut resp = HttpResponse::build(self.status_code);
resp.set(header::ContentType(self.content_type.clone())) resp.set(header::ContentType(self.content_type.clone()))
@ -324,16 +280,6 @@ impl Responder for NamedFile {
return Ok(resp.streaming(reader)); return Ok(resp.streaming(reader));
} }
match *req.method() {
Method::HEAD | Method::GET => (),
_ => {
return Ok(HttpResponse::MethodNotAllowed()
.header(header::CONTENT_TYPE, "text/plain")
.header(header::ALLOW, "GET, HEAD")
.body("This resource only supports GET and HEAD."));
}
}
let etag = if self.flags.contains(Flags::ETAG) { let etag = if self.flags.contains(Flags::ETAG) {
self.etag() self.etag()
} else { } else {
@ -443,8 +389,67 @@ impl Responder for NamedFile {
counter: 0, counter: 0,
}; };
if offset != 0 || length != self.md.len() { if offset != 0 || length != self.md.len() {
return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader))
}; } else {
Ok(resp.body(SizedStream::new(length, reader))) Ok(resp.body(SizedStream::new(length, reader)))
} }
}
}
impl Deref for NamedFile {
type Target = File;
fn deref(&self) -> &File {
&self.file
}
}
impl DerefMut for NamedFile {
fn deref_mut(&mut self) -> &mut File {
&mut self.file
}
}
/// Returns true if `req` has no `If-Match` header or one which matches `etag`.
fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfMatch>() {
None | Some(header::IfMatch::Any) => true,
Some(header::IfMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.strong_eq(some_etag) {
return true;
}
}
}
false
}
}
}
/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`.
fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
match req.get_header::<header::IfNoneMatch>() {
Some(header::IfNoneMatch::Any) => false,
Some(header::IfNoneMatch::Items(ref items)) => {
if let Some(some_etag) = etag {
for item in items {
if item.weak_eq(some_etag) {
return false;
}
}
}
true
}
None => true,
}
}
impl Responder for NamedFile {
type Error = Error;
type Future = Ready<Result<HttpResponse, Error>>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
ready(self.into_response(req))
}
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-framed" name = "actix-framed"
version = "0.2.1" version = "0.3.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix framed app server" description = "Actix framed app server"
readme = "README.md" readme = "README.md"
@ -13,26 +13,25 @@ categories = ["network-programming", "asynchronous",
"web-programming::websocket"] "web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
edition = "2018" edition = "2018"
workspace =".."
[lib] [lib]
name = "actix_framed" name = "actix_framed"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-codec = "0.1.2" actix-codec = "0.2.0"
actix-service = "0.4.1" actix-service = "1.0.1"
actix-router = "0.1.2" actix-router = "0.2.1"
actix-rt = "0.2.2" actix-rt = "1.0.0"
actix-http = "0.2.7" actix-http = "1.0.1"
actix-server-config = "0.1.2"
bytes = "0.4" bytes = "0.5.3"
futures = "0.1.25" futures = "0.3.1"
pin-project = "0.4.6"
log = "0.4" log = "0.4"
[dev-dependencies] [dev-dependencies]
actix-server = { version = "0.6.0", features=["ssl"] } actix-server = "1.0.0"
actix-connect = { version = "0.2.0", features=["ssl"] } actix-connect = { version = "1.0.0", features=["openssl"] }
actix-http-test = { version = "0.2.4", features=["ssl"] } actix-http-test = { version = "1.0.0", features=["openssl"] }
actix-utils = "0.4.4" actix-utils = "1.0.3"

View File

@ -1,5 +1,9 @@
# Changes # Changes
## [0.3.0] - 2019-12-25
* Migrate to actix-http 1.0
## [0.2.1] - 2019-07-20 ## [0.2.1] - 2019-07-20
* Remove unneeded actix-utils dependency * Remove unneeded actix-utils dependency

View File

@ -1,21 +1,23 @@
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::h1::{Codec, SendResponse}; use actix_http::h1::{Codec, SendResponse};
use actix_http::{Error, Request, Response}; use actix_http::{Error, Request, Response};
use actix_router::{Path, Router, Url}; use actix_router::{Path, Router, Url};
use actix_server_config::ServerConfig; use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use actix_service::{IntoNewService, NewService, Service}; use futures::future::{ok, FutureExt, LocalBoxFuture};
use futures::{Async, Future, Poll};
use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService}; use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService};
use crate::request::FramedRequest; use crate::request::FramedRequest;
use crate::state::State; use crate::state::State;
type BoxedResponse = Box<dyn Future<Item = (), Error = Error>>; type BoxedResponse = LocalBoxFuture<'static, Result<(), Error>>;
pub trait HttpServiceFactory { pub trait HttpServiceFactory {
type Factory: NewService; type Factory: ServiceFactory;
fn path(&self) -> &str; fn path(&self) -> &str;
@ -48,19 +50,19 @@ impl<T: 'static, S: 'static> FramedApp<T, S> {
pub fn service<U>(mut self, factory: U) -> Self pub fn service<U>(mut self, factory: U) -> Self
where where
U: HttpServiceFactory, U: HttpServiceFactory,
U::Factory: NewService< U::Factory: ServiceFactory<
Config = (), Config = (),
Request = FramedRequest<T, S>, Request = FramedRequest<T, S>,
Response = (), Response = (),
Error = Error, Error = Error,
InitError = (), InitError = (),
> + 'static, > + 'static,
<U::Factory as NewService>::Future: 'static, <U::Factory as ServiceFactory>::Future: 'static,
<U::Factory as NewService>::Service: Service< <U::Factory as ServiceFactory>::Service: Service<
Request = FramedRequest<T, S>, Request = FramedRequest<T, S>,
Response = (), Response = (),
Error = Error, Error = Error,
Future = Box<dyn Future<Item = (), Error = Error>>, Future = LocalBoxFuture<'static, Result<(), Error>>,
>, >,
{ {
let path = factory.path().to_string(); let path = factory.path().to_string();
@ -70,12 +72,12 @@ impl<T: 'static, S: 'static> FramedApp<T, S> {
} }
} }
impl<T, S> IntoNewService<FramedAppFactory<T, S>> for FramedApp<T, S> impl<T, S> IntoServiceFactory<FramedAppFactory<T, S>> for FramedApp<T, S>
where where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
S: 'static, S: 'static,
{ {
fn into_new_service(self) -> FramedAppFactory<T, S> { fn into_factory(self) -> FramedAppFactory<T, S> {
FramedAppFactory { FramedAppFactory {
state: self.state, state: self.state,
services: Rc::new(self.services), services: Rc::new(self.services),
@ -89,12 +91,12 @@ pub struct FramedAppFactory<T, S> {
services: Rc<Vec<(String, BoxedHttpNewService<FramedRequest<T, S>>)>>, services: Rc<Vec<(String, BoxedHttpNewService<FramedRequest<T, S>>)>>,
} }
impl<T, S> NewService for FramedAppFactory<T, S> impl<T, S> ServiceFactory for FramedAppFactory<T, S>
where where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
S: 'static, S: 'static,
{ {
type Config = ServerConfig; type Config = ();
type Request = (Request, Framed<T, Codec>); type Request = (Request, Framed<T, Codec>);
type Response = (); type Response = ();
type Error = Error; type Error = Error;
@ -102,7 +104,7 @@ where
type Service = FramedAppService<T, S>; type Service = FramedAppService<T, S>;
type Future = CreateService<T, S>; type Future = CreateService<T, S>;
fn new_service(&self, _: &ServerConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
CreateService { CreateService {
fut: self fut: self
.services .services
@ -110,7 +112,7 @@ where
.map(|(path, service)| { .map(|(path, service)| {
CreateServiceItem::Future( CreateServiceItem::Future(
Some(path.clone()), Some(path.clone()),
service.new_service(&()), service.new_service(()),
) )
}) })
.collect(), .collect(),
@ -128,28 +130,30 @@ pub struct CreateService<T, S> {
enum CreateServiceItem<T, S> { enum CreateServiceItem<T, S> {
Future( Future(
Option<String>, Option<String>,
Box<dyn Future<Item = BoxedHttpService<FramedRequest<T, S>>, Error = ()>>, LocalBoxFuture<'static, Result<BoxedHttpService<FramedRequest<T, S>>, ()>>,
), ),
Service(String, BoxedHttpService<FramedRequest<T, S>>), Service(String, BoxedHttpService<FramedRequest<T, S>>),
} }
impl<S: 'static, T: 'static> Future for CreateService<T, S> impl<S: 'static, T: 'static> Future for CreateService<T, S>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite + Unpin,
{ {
type Item = FramedAppService<T, S>; type Output = Result<FramedAppService<T, S>, ()>;
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let mut done = true; let mut done = true;
// poll http services // poll http services
for item in &mut self.fut { for item in &mut self.fut {
let res = match item { let res = match item {
CreateServiceItem::Future(ref mut path, ref mut fut) => { CreateServiceItem::Future(ref mut path, ref mut fut) => {
match fut.poll()? { match Pin::new(fut).poll(cx) {
Async::Ready(service) => Some((path.take().unwrap(), service)), Poll::Ready(Ok(service)) => {
Async::NotReady => { Some((path.take().unwrap(), service))
}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => {
done = false; done = false;
None None
} }
@ -176,12 +180,12 @@ where
} }
router router
}); });
Ok(Async::Ready(FramedAppService { Poll::Ready(Ok(FramedAppService {
router: router.finish(), router: router.finish(),
state: self.state.clone(), state: self.state.clone(),
})) }))
} else { } else {
Ok(Async::NotReady) Poll::Pending
} }
} }
} }
@ -193,15 +197,15 @@ pub struct FramedAppService<T, S> {
impl<S: 'static, T: 'static> Service for FramedAppService<T, S> impl<S: 'static, T: 'static> Service for FramedAppService<T, S>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite + Unpin,
{ {
type Request = (Request, Framed<T, Codec>); type Request = (Request, Framed<T, Codec>);
type Response = (); type Response = ();
type Error = Error; type Error = Error;
type Future = BoxedResponse; type Future = BoxedResponse;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Ok(Async::Ready(())) Poll::Ready(Ok(()))
} }
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future { fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
@ -210,8 +214,8 @@ where
if let Some((srv, _info)) = self.router.recognize_mut(&mut path) { if let Some((srv, _info)) = self.router.recognize_mut(&mut path) {
return srv.call(FramedRequest::new(req, framed, path, self.state.clone())); return srv.call(FramedRequest::new(req, framed, path, self.state.clone()));
} }
Box::new( SendResponse::new(framed, Response::NotFound().finish())
SendResponse::new(framed, Response::NotFound().finish()).then(|_| Ok(())), .then(|_| ok(()))
) .boxed_local()
} }
} }

View File

@ -1,36 +1,38 @@
use std::task::{Context, Poll};
use actix_http::Error; use actix_http::Error;
use actix_service::{NewService, Service}; use actix_service::{Service, ServiceFactory};
use futures::{Future, Poll}; use futures::future::{FutureExt, LocalBoxFuture};
pub(crate) type BoxedHttpService<Req> = Box< pub(crate) type BoxedHttpService<Req> = Box<
dyn Service< dyn Service<
Request = Req, Request = Req,
Response = (), Response = (),
Error = Error, Error = Error,
Future = Box<dyn Future<Item = (), Error = Error>>, Future = LocalBoxFuture<'static, Result<(), Error>>,
>, >,
>; >;
pub(crate) type BoxedHttpNewService<Req> = Box< pub(crate) type BoxedHttpNewService<Req> = Box<
dyn NewService< dyn ServiceFactory<
Config = (), Config = (),
Request = Req, Request = Req,
Response = (), Response = (),
Error = Error, Error = Error,
InitError = (), InitError = (),
Service = BoxedHttpService<Req>, Service = BoxedHttpService<Req>,
Future = Box<dyn Future<Item = BoxedHttpService<Req>, Error = ()>>, Future = LocalBoxFuture<'static, Result<BoxedHttpService<Req>, ()>>,
>, >,
>; >;
pub(crate) struct HttpNewService<T: NewService>(T); pub(crate) struct HttpNewService<T: ServiceFactory>(T);
impl<T> HttpNewService<T> impl<T> HttpNewService<T>
where where
T: NewService<Response = (), Error = Error>, T: ServiceFactory<Response = (), Error = Error>,
T::Response: 'static, T::Response: 'static,
T::Future: 'static, T::Future: 'static,
T::Service: Service<Future = Box<dyn Future<Item = (), Error = Error>>> + 'static, T::Service: Service<Future = LocalBoxFuture<'static, Result<(), Error>>> + 'static,
<T::Service as Service>::Future: 'static, <T::Service as Service>::Future: 'static,
{ {
pub fn new(service: T) -> Self { pub fn new(service: T) -> Self {
@ -38,12 +40,12 @@ where
} }
} }
impl<T> NewService for HttpNewService<T> impl<T> ServiceFactory for HttpNewService<T>
where where
T: NewService<Config = (), Response = (), Error = Error>, T: ServiceFactory<Config = (), Response = (), Error = Error>,
T::Request: 'static, T::Request: 'static,
T::Future: 'static, T::Future: 'static,
T::Service: Service<Future = Box<dyn Future<Item = (), Error = Error>>> + 'static, T::Service: Service<Future = LocalBoxFuture<'static, Result<(), Error>>> + 'static,
<T::Service as Service>::Future: 'static, <T::Service as Service>::Future: 'static,
{ {
type Config = (); type Config = ();
@ -52,13 +54,19 @@ where
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = BoxedHttpService<T::Request>; type Service = BoxedHttpService<T::Request>;
type Future = Box<dyn Future<Item = Self::Service, Error = ()>>; type Future = LocalBoxFuture<'static, Result<Self::Service, ()>>;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| { let fut = self.0.new_service(());
let service: BoxedHttpService<_> = Box::new(HttpServiceWrapper { service });
Ok(service) async move {
})) fut.await.map_err(|_| ()).map(|service| {
let service: BoxedHttpService<_> =
Box::new(HttpServiceWrapper { service });
service
})
}
.boxed_local()
} }
} }
@ -70,7 +78,7 @@ impl<T> Service for HttpServiceWrapper<T>
where where
T: Service< T: Service<
Response = (), Response = (),
Future = Box<dyn Future<Item = (), Error = Error>>, Future = LocalBoxFuture<'static, Result<(), Error>>,
Error = Error, Error = Error,
>, >,
T::Request: 'static, T::Request: 'static,
@ -78,10 +86,10 @@ where
type Request = T::Request; type Request = T::Request;
type Response = (); type Response = ();
type Error = Error; type Error = Error;
type Future = Box<dyn Future<Item = (), Error = Error>>; type Future = LocalBoxFuture<'static, Result<(), Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready() self.service.poll_ready(cx)
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, req: Self::Request) -> Self::Future {

View File

@ -123,7 +123,9 @@ impl<Io, S> FramedRequest<Io, S> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_http::http::{HeaderName, HeaderValue, HttpTryFrom}; use std::convert::TryFrom;
use actix_http::http::{HeaderName, HeaderValue};
use actix_http::test::{TestBuffer, TestRequest}; use actix_http::test::{TestBuffer, TestRequest};
use super::*; use super::*;

View File

@ -1,11 +1,12 @@
use std::fmt; use std::fmt;
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::{http::Method, Error}; use actix_http::{http::Method, Error};
use actix_service::{NewService, Service}; use actix_service::{Service, ServiceFactory};
use futures::future::{ok, FutureResult}; use futures::future::{ok, FutureExt, LocalBoxFuture, Ready};
use futures::{Async, Future, IntoFuture, Poll};
use log::error; use log::error;
use crate::app::HttpServiceFactory; use crate::app::HttpServiceFactory;
@ -15,11 +16,11 @@ use crate::request::FramedRequest;
/// ///
/// Route uses builder-like pattern for configuration. /// Route uses builder-like pattern for configuration.
/// If handler is not explicitly set, default *404 Not Found* handler is used. /// If handler is not explicitly set, default *404 Not Found* handler is used.
pub struct FramedRoute<Io, S, F = (), R = ()> { pub struct FramedRoute<Io, S, F = (), R = (), E = ()> {
handler: F, handler: F,
pattern: String, pattern: String,
methods: Vec<Method>, methods: Vec<Method>,
state: PhantomData<(Io, S, R)>, state: PhantomData<(Io, S, R, E)>,
} }
impl<Io, S> FramedRoute<Io, S> { impl<Io, S> FramedRoute<Io, S> {
@ -53,12 +54,12 @@ impl<Io, S> FramedRoute<Io, S> {
self self
} }
pub fn to<F, R>(self, handler: F) -> FramedRoute<Io, S, F, R> pub fn to<F, R, E>(self, handler: F) -> FramedRoute<Io, S, F, R, E>
where where
F: FnMut(FramedRequest<Io, S>) -> R, F: FnMut(FramedRequest<Io, S>) -> R,
R: IntoFuture<Item = ()>, R: Future<Output = Result<(), E>> + 'static,
R::Future: 'static,
R::Error: fmt::Debug, E: fmt::Debug,
{ {
FramedRoute { FramedRoute {
handler, handler,
@ -69,15 +70,14 @@ impl<Io, S> FramedRoute<Io, S> {
} }
} }
impl<Io, S, F, R> HttpServiceFactory for FramedRoute<Io, S, F, R> impl<Io, S, F, R, E> HttpServiceFactory for FramedRoute<Io, S, F, R, E>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + 'static,
F: FnMut(FramedRequest<Io, S>) -> R + Clone, F: FnMut(FramedRequest<Io, S>) -> R + Clone,
R: IntoFuture<Item = ()>, R: Future<Output = Result<(), E>> + 'static,
R::Future: 'static, E: fmt::Display,
R::Error: fmt::Display,
{ {
type Factory = FramedRouteFactory<Io, S, F, R>; type Factory = FramedRouteFactory<Io, S, F, R, E>;
fn path(&self) -> &str { fn path(&self) -> &str {
&self.pattern &self.pattern
@ -92,29 +92,28 @@ where
} }
} }
pub struct FramedRouteFactory<Io, S, F, R> { pub struct FramedRouteFactory<Io, S, F, R, E> {
handler: F, handler: F,
methods: Vec<Method>, methods: Vec<Method>,
_t: PhantomData<(Io, S, R)>, _t: PhantomData<(Io, S, R, E)>,
} }
impl<Io, S, F, R> NewService for FramedRouteFactory<Io, S, F, R> impl<Io, S, F, R, E> ServiceFactory for FramedRouteFactory<Io, S, F, R, E>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + 'static,
F: FnMut(FramedRequest<Io, S>) -> R + Clone, F: FnMut(FramedRequest<Io, S>) -> R + Clone,
R: IntoFuture<Item = ()>, R: Future<Output = Result<(), E>> + 'static,
R::Future: 'static, E: fmt::Display,
R::Error: fmt::Display,
{ {
type Config = (); type Config = ();
type Request = FramedRequest<Io, S>; type Request = FramedRequest<Io, S>;
type Response = (); type Response = ();
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = FramedRouteService<Io, S, F, R>; type Service = FramedRouteService<Io, S, F, R, E>;
type Future = FutureResult<Self::Service, Self::InitError>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
ok(FramedRouteService { ok(FramedRouteService {
handler: self.handler.clone(), handler: self.handler.clone(),
methods: self.methods.clone(), methods: self.methods.clone(),
@ -123,35 +122,38 @@ where
} }
} }
pub struct FramedRouteService<Io, S, F, R> { pub struct FramedRouteService<Io, S, F, R, E> {
handler: F, handler: F,
methods: Vec<Method>, methods: Vec<Method>,
_t: PhantomData<(Io, S, R)>, _t: PhantomData<(Io, S, R, E)>,
} }
impl<Io, S, F, R> Service for FramedRouteService<Io, S, F, R> impl<Io, S, F, R, E> Service for FramedRouteService<Io, S, F, R, E>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + 'static,
F: FnMut(FramedRequest<Io, S>) -> R + Clone, F: FnMut(FramedRequest<Io, S>) -> R + Clone,
R: IntoFuture<Item = ()>, R: Future<Output = Result<(), E>> + 'static,
R::Future: 'static, E: fmt::Display,
R::Error: fmt::Display,
{ {
type Request = FramedRequest<Io, S>; type Request = FramedRequest<Io, S>;
type Response = (); type Response = ();
type Error = Error; type Error = Error;
type Future = Box<dyn Future<Item = (), Error = Error>>; type Future = LocalBoxFuture<'static, Result<(), Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Ok(Async::Ready(())) Poll::Ready(Ok(()))
} }
fn call(&mut self, req: FramedRequest<Io, S>) -> Self::Future { fn call(&mut self, req: FramedRequest<Io, S>) -> Self::Future {
Box::new((self.handler)(req).into_future().then(|res| { let fut = (self.handler)(req);
async move {
let res = fut.await;
if let Err(e) = res { if let Err(e) = res {
error!("Error in request handler: {}", e); error!("Error in request handler: {}", e);
} }
Ok(()) Ok(())
})) }
.boxed_local()
} }
} }

View File

@ -1,4 +1,6 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::body::BodySize; use actix_http::body::BodySize;
@ -6,9 +8,9 @@ use actix_http::error::ResponseError;
use actix_http::h1::{Codec, Message}; use actix_http::h1::{Codec, Message};
use actix_http::ws::{verify_handshake, HandshakeError}; use actix_http::ws::{verify_handshake, HandshakeError};
use actix_http::{Request, Response}; use actix_http::{Request, Response};
use actix_service::{NewService, Service}; use actix_service::{Service, ServiceFactory};
use futures::future::{ok, Either, FutureResult}; use futures::future::{err, ok, Either, Ready};
use futures::{Async, Future, IntoFuture, Poll, Sink}; use futures::Future;
/// Service that verifies incoming request if it is valid websocket /// Service that verifies incoming request if it is valid websocket
/// upgrade request. In case of error returns `HandshakeError` /// upgrade request. In case of error returns `HandshakeError`
@ -22,16 +24,16 @@ impl<T, C> Default for VerifyWebSockets<T, C> {
} }
} }
impl<T, C> NewService for VerifyWebSockets<T, C> { impl<T, C> ServiceFactory for VerifyWebSockets<T, C> {
type Config = C; type Config = C;
type Request = (Request, Framed<T, Codec>); type Request = (Request, Framed<T, Codec>);
type Response = (Request, Framed<T, Codec>); type Response = (Request, Framed<T, Codec>);
type Error = (HandshakeError, Framed<T, Codec>); type Error = (HandshakeError, Framed<T, Codec>);
type InitError = (); type InitError = ();
type Service = VerifyWebSockets<T, C>; type Service = VerifyWebSockets<T, C>;
type Future = FutureResult<Self::Service, Self::InitError>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &C) -> Self::Future { fn new_service(&self, _: C) -> Self::Future {
ok(VerifyWebSockets { _t: PhantomData }) ok(VerifyWebSockets { _t: PhantomData })
} }
} }
@ -40,16 +42,16 @@ impl<T, C> Service for VerifyWebSockets<T, C> {
type Request = (Request, Framed<T, Codec>); type Request = (Request, Framed<T, Codec>);
type Response = (Request, Framed<T, Codec>); type Response = (Request, Framed<T, Codec>);
type Error = (HandshakeError, Framed<T, Codec>); type Error = (HandshakeError, Framed<T, Codec>);
type Future = FutureResult<Self::Response, Self::Error>; type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Ok(Async::Ready(())) Poll::Ready(Ok(()))
} }
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future { fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
match verify_handshake(req.head()) { match verify_handshake(req.head()) {
Err(e) => Err((e, framed)).into_future(), Err(e) => err((e, framed)),
Ok(_) => Ok((req, framed)).into_future(), Ok(_) => ok((req, framed)),
} }
} }
} }
@ -67,9 +69,9 @@ where
} }
} }
impl<T, R, E, C> NewService for SendError<T, R, E, C> impl<T, R, E, C> ServiceFactory for SendError<T, R, E, C>
where where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
R: 'static, R: 'static,
E: ResponseError + 'static, E: ResponseError + 'static,
{ {
@ -79,34 +81,34 @@ where
type Error = (E, Framed<T, Codec>); type Error = (E, Framed<T, Codec>);
type InitError = (); type InitError = ();
type Service = SendError<T, R, E, C>; type Service = SendError<T, R, E, C>;
type Future = FutureResult<Self::Service, Self::InitError>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &C) -> Self::Future { fn new_service(&self, _: C) -> Self::Future {
ok(SendError(PhantomData)) ok(SendError(PhantomData))
} }
} }
impl<T, R, E, C> Service for SendError<T, R, E, C> impl<T, R, E, C> Service for SendError<T, R, E, C>
where where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
R: 'static, R: 'static,
E: ResponseError + 'static, E: ResponseError + 'static,
{ {
type Request = Result<R, (E, Framed<T, Codec>)>; type Request = Result<R, (E, Framed<T, Codec>)>;
type Response = R; type Response = R;
type Error = (E, Framed<T, Codec>); type Error = (E, Framed<T, Codec>);
type Future = Either<FutureResult<R, (E, Framed<T, Codec>)>, SendErrorFut<T, R, E>>; type Future = Either<Ready<Result<R, (E, Framed<T, Codec>)>>, SendErrorFut<T, R, E>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Ok(Async::Ready(())) Poll::Ready(Ok(()))
} }
fn call(&mut self, req: Result<R, (E, Framed<T, Codec>)>) -> Self::Future { fn call(&mut self, req: Result<R, (E, Framed<T, Codec>)>) -> Self::Future {
match req { match req {
Ok(r) => Either::A(ok(r)), Ok(r) => Either::Left(ok(r)),
Err((e, framed)) => { Err((e, framed)) => {
let res = e.error_response().drop_body(); let res = e.error_response().drop_body();
Either::B(SendErrorFut { Either::Right(SendErrorFut {
framed: Some(framed), framed: Some(framed),
res: Some((res, BodySize::Empty).into()), res: Some((res, BodySize::Empty).into()),
err: Some(e), err: Some(e),
@ -117,6 +119,7 @@ where
} }
} }
#[pin_project::pin_project]
pub struct SendErrorFut<T, R, E> { pub struct SendErrorFut<T, R, E> {
res: Option<Message<(Response<()>, BodySize)>>, res: Option<Message<(Response<()>, BodySize)>>,
framed: Option<Framed<T, Codec>>, framed: Option<Framed<T, Codec>>,
@ -127,23 +130,27 @@ pub struct SendErrorFut<T, R, E> {
impl<T, R, E> Future for SendErrorFut<T, R, E> impl<T, R, E> Future for SendErrorFut<T, R, E>
where where
E: ResponseError, E: ResponseError,
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite + Unpin,
{ {
type Item = R; type Output = Result<R, (E, Framed<T, Codec>)>;
type Error = (E, Framed<T, Codec>);
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
if let Some(res) = self.res.take() { if let Some(res) = self.res.take() {
if self.framed.as_mut().unwrap().force_send(res).is_err() { if self.framed.as_mut().unwrap().write(res).is_err() {
return Err((self.err.take().unwrap(), self.framed.take().unwrap())); return Poll::Ready(Err((
self.err.take().unwrap(),
self.framed.take().unwrap(),
)));
} }
} }
match self.framed.as_mut().unwrap().poll_complete() { match self.framed.as_mut().unwrap().flush(cx) {
Ok(Async::Ready(_)) => { Poll::Ready(Ok(_)) => {
Err((self.err.take().unwrap(), self.framed.take().unwrap())) Poll::Ready(Err((self.err.take().unwrap(), self.framed.take().unwrap())))
} }
Ok(Async::NotReady) => Ok(Async::NotReady), Poll::Ready(Err(_)) => {
Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())), Poll::Ready(Err((self.err.take().unwrap(), self.framed.take().unwrap())))
}
Poll::Pending => Poll::Pending,
} }
} }
} }

View File

@ -1,12 +1,13 @@
//! Various helpers for Actix applications to use during testing. //! Various helpers for Actix applications to use during testing.
use std::convert::TryFrom;
use std::future::Future;
use actix_codec::Framed; use actix_codec::Framed;
use actix_http::h1::Codec; use actix_http::h1::Codec;
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
use actix_http::http::{HttpTryFrom, Method, Uri, Version}; use actix_http::http::{Error as HttpError, Method, Uri, Version};
use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest}; use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest};
use actix_router::{Path, Url}; use actix_router::{Path, Url};
use actix_rt::Runtime;
use futures::IntoFuture;
use crate::{FramedRequest, State}; use crate::{FramedRequest, State};
@ -41,7 +42,8 @@ impl TestRequest<()> {
/// Create TestRequest and set header /// Create TestRequest and set header
pub fn with_header<K, V>(key: K, value: V) -> Self pub fn with_header<K, V>(key: K, value: V) -> Self
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
Self::default().header(key, value) Self::default().header(key, value)
@ -96,7 +98,8 @@ impl<S> TestRequest<S> {
/// Set a header /// Set a header
pub fn header<K, V>(mut self, key: K, value: V) -> Self pub fn header<K, V>(mut self, key: K, value: V) -> Self
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
self.req.header(key, value); self.req.header(key, value);
@ -118,13 +121,12 @@ impl<S> TestRequest<S> {
} }
/// This method generates `FramedRequest` instance and executes async handler /// This method generates `FramedRequest` instance and executes async handler
pub fn run<F, R, I, E>(self, f: F) -> Result<I, E> pub async fn run<F, R, I, E>(self, f: F) -> Result<I, E>
where where
F: FnOnce(FramedRequest<TestBuffer, S>) -> R, F: FnOnce(FramedRequest<TestBuffer, S>) -> R,
R: IntoFuture<Item = I, Error = E>, R: Future<Output = Result<I, E>>,
{ {
let mut rt = Runtime::new().unwrap(); f(self.finish()).await
rt.block_on(f(self.finish()).into_future())
} }
} }

View File

@ -1,141 +1,159 @@
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response}; use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response};
use actix_http_test::TestServer; use actix_http_test::test_server;
use actix_service::{IntoNewService, NewService}; use actix_service::{pipeline_factory, IntoServiceFactory, ServiceFactory};
use actix_utils::framed::FramedTransport; use actix_utils::framed::Dispatcher;
use bytes::{Bytes, BytesMut}; use bytes::Bytes;
use futures::future::{self, ok}; use futures::{future, SinkExt, StreamExt};
use futures::{Future, Sink, Stream};
use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets}; use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets};
fn ws_service<T: AsyncRead + AsyncWrite>( async fn ws_service<T: AsyncRead + AsyncWrite>(
req: FramedRequest<T>, req: FramedRequest<T>,
) -> impl Future<Item = (), Error = Error> { ) -> Result<(), Error> {
let (req, framed, _) = req.into_parts(); let (req, mut framed, _) = req.into_parts();
let res = ws::handshake(req.head()).unwrap().message_body(()); let res = ws::handshake(req.head()).unwrap().message_body(());
framed framed
.send((res, body::BodySize::None).into()) .send((res, body::BodySize::None).into())
.map_err(|_| panic!()) .await
.and_then(|framed| { .unwrap();
FramedTransport::new(framed.into_framed(ws::Codec::new()), service) Dispatcher::new(framed.into_framed(ws::Codec::new()), service)
.map_err(|_| panic!()) .await
}) .unwrap();
Ok(())
} }
fn service(msg: ws::Frame) -> impl Future<Item = ws::Message, Error = Error> { async fn service(msg: ws::Frame) -> Result<ws::Message, Error> {
let msg = match msg { let msg = match msg {
ws::Frame::Ping(msg) => ws::Message::Pong(msg), ws::Frame::Ping(msg) => ws::Message::Pong(msg),
ws::Frame::Text(text) => { ws::Frame::Text(text) => {
ws::Message::Text(String::from_utf8_lossy(&text.unwrap()).to_string()) ws::Message::Text(String::from_utf8_lossy(&text).to_string())
} }
ws::Frame::Binary(bin) => ws::Message::Binary(bin.unwrap().freeze()), ws::Frame::Binary(bin) => ws::Message::Binary(bin),
ws::Frame::Close(reason) => ws::Message::Close(reason), ws::Frame::Close(reason) => ws::Message::Close(reason),
_ => panic!(), _ => panic!(),
}; };
ok(msg) Ok(msg)
} }
#[test] #[actix_rt::test]
fn test_simple() { async fn test_simple() {
let mut srv = TestServer::new(|| { let mut srv = test_server(|| {
HttpService::build() HttpService::build()
.upgrade( .upgrade(
FramedApp::new().service(FramedRoute::get("/index.html").to(ws_service)), FramedApp::new().service(FramedRoute::get("/index.html").to(ws_service)),
) )
.finish(|_| future::ok::<_, Error>(Response::NotFound())) .finish(|_| future::ok::<_, Error>(Response::NotFound()))
.tcp()
}); });
assert!(srv.ws_at("/test").is_err()); assert!(srv.ws_at("/test").await.is_err());
// client service // client service
let framed = srv.ws_at("/index.html").unwrap(); let mut framed = srv.ws_at("/index.html").await.unwrap();
let framed = srv framed
.block_on(framed.send(ws::Message::Text("text".to_string()))) .send(ws::Message::Text("text".to_string()))
.await
.unwrap(); .unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); let (item, mut framed) = framed.into_future().await;
assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
let framed = srv
.block_on(framed.send(ws::Message::Binary("text".into())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!( assert_eq!(
item, item.unwrap().unwrap(),
Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) ws::Frame::Text(Bytes::from_static(b"text"))
); );
let framed = srv framed
.block_on(framed.send(ws::Message::Ping("text".into()))) .send(ws::Message::Binary("text".into()))
.await
.unwrap(); .unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); let (item, mut framed) = framed.into_future().await;
assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
let framed = srv
.block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
.unwrap();
let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!( assert_eq!(
item, item.unwrap().unwrap(),
Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) ws::Frame::Binary(Bytes::from_static(b"text"))
);
framed.send(ws::Message::Ping("text".into())).await.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Pong("text".to_string().into())
);
framed
.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
.await
.unwrap();
let (item, _) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Close(Some(ws::CloseCode::Normal.into()))
); );
} }
#[test] #[actix_rt::test]
fn test_service() { async fn test_service() {
let mut srv = TestServer::new(|| { let mut srv = test_server(|| {
actix_http::h1::OneRequest::new().map_err(|_| ()).and_then( pipeline_factory(actix_http::h1::OneRequest::new().map_err(|_| ())).and_then(
VerifyWebSockets::default() pipeline_factory(
pipeline_factory(VerifyWebSockets::default())
.then(SendError::default()) .then(SendError::default())
.map_err(|_| ()) .map_err(|_| ()),
)
.and_then( .and_then(
FramedApp::new() FramedApp::new()
.service(FramedRoute::get("/index.html").to(ws_service)) .service(FramedRoute::get("/index.html").to(ws_service))
.into_new_service() .into_factory()
.map_err(|_| ()), .map_err(|_| ()),
), ),
) )
}); });
// non ws request // non ws request
let res = srv.block_on(srv.get("/index.html").send()).unwrap(); let res = srv.get("/index.html").send().await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST); assert_eq!(res.status(), StatusCode::BAD_REQUEST);
// not found // not found
assert!(srv.ws_at("/test").is_err()); assert!(srv.ws_at("/test").await.is_err());
// client service // client service
let framed = srv.ws_at("/index.html").unwrap(); let mut framed = srv.ws_at("/index.html").await.unwrap();
let framed = srv framed
.block_on(framed.send(ws::Message::Text("text".to_string()))) .send(ws::Message::Text("text".to_string()))
.await
.unwrap(); .unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); let (item, mut framed) = framed.into_future().await;
assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
let framed = srv
.block_on(framed.send(ws::Message::Binary("text".into())))
.unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!( assert_eq!(
item, item.unwrap().unwrap(),
Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) ws::Frame::Text(Bytes::from_static(b"text"))
); );
let framed = srv framed
.block_on(framed.send(ws::Message::Ping("text".into()))) .send(ws::Message::Binary("text".into()))
.await
.unwrap(); .unwrap();
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); let (item, mut framed) = framed.into_future().await;
assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
let framed = srv
.block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
.unwrap();
let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
assert_eq!( assert_eq!(
item, item.unwrap().unwrap(),
Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) ws::Frame::Binary(Bytes::from_static(b"text"))
);
framed.send(ws::Message::Ping("text".into())).await.unwrap();
let (item, mut framed) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Pong("text".to_string().into())
);
framed
.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
.await
.unwrap();
let (item, _) = framed.into_future().await;
assert_eq!(
item.unwrap().unwrap(),
ws::Frame::Close(Some(ws::CloseCode::Normal.into()))
); );
} }

View File

@ -1,6 +1,80 @@
# Changes # Changes
## [0.2.11] - 2019-09-11 # [Unreleased]
### Changed
* Update the `time` dependency to 0.2.7
### Fixed
* Allow `SameSite=None` cookies to be sent in a response.
## [1.0.1] - 2019-12-20
### Fixed
* Poll upgrade service's readiness from HTTP service handlers
* Replace brotli with brotli2 #1224
## [1.0.0] - 2019-12-13
### Added
* Add websockets continuation frame support
### Changed
* Replace `flate2-xxx` features with `compress`
## [1.0.0-alpha.5] - 2019-12-09
### Fixed
* Check `Upgrade` service readiness before calling it
* Fix buffer remaining capacity calcualtion
### Changed
* Websockets: Ping and Pong should have binary data #1049
## [1.0.0-alpha.4] - 2019-12-08
### Added
* Add impl ResponseBuilder for Error
### Changed
* Use rust based brotli compression library
## [1.0.0-alpha.3] - 2019-12-07
### Changed
* Migrate to tokio 0.2
* Migrate to `std::future`
## [0.2.11] - 2019-11-06
### Added
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
* Allow to use `std::convert::Infallible` as `actix_http::error::Error`
### Fixed
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118
## [0.2.10] - 2019-09-11
### Added ### Added
@ -8,6 +82,8 @@
### Fixed ### Fixed
* h2 will use error response #1080
* on_connect result isn't added to request extensions for http2 requests #1009 * on_connect result isn't added to request extensions for http2 requests #1009

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "0.2.10" version = "1.0.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http primitives" description = "Actix http primitives"
readme = "README.md" readme = "README.md"
@ -13,10 +13,9 @@ categories = ["network-programming", "asynchronous",
"web-programming::websocket"] "web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
edition = "2018" edition = "2018"
workspace = ".."
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["ssl", "fail", "brotli", "flate2-zlib", "secure-cookies"] features = ["openssl", "rustls", "failure", "compress", "secure-cookies"]
[lib] [lib]
name = "actix_http" name = "actix_http"
@ -26,85 +25,76 @@ path = "src/lib.rs"
default = [] default = []
# openssl # openssl
ssl = ["openssl", "actix-connect/ssl"] openssl = ["actix-tls/openssl", "actix-connect/openssl"]
# rustls support # rustls support
rust-tls = ["rustls", "webpki-roots", "actix-connect/rust-tls"] rustls = ["actix-tls/rustls", "actix-connect/rustls"]
# brotli encoding, requires c compiler # enable compressison support
brotli = ["brotli2"] compress = ["flate2", "brotli2"]
# miniz-sys backend for flate2 crate
flate2-zlib = ["flate2/miniz-sys"]
# rust backend for flate2 crate
flate2-rust = ["flate2/rust_backend"]
# failure integration. actix does not use failure anymore # failure integration. actix does not use failure anymore
fail = ["failure"] failure = ["fail-ure"]
# support for secure cookies # support for secure cookies
secure-cookies = ["ring"] secure-cookies = ["ring"]
[dependencies] [dependencies]
actix-service = "0.4.1" actix-service = "1.0.1"
actix-codec = "0.1.2" actix-codec = "0.2.0"
actix-connect = "0.2.4" actix-connect = "1.0.1"
actix-utils = "0.4.4" actix-utils = "1.0.3"
actix-server-config = "0.1.2" actix-rt = "1.0.0"
actix-threadpool = "0.1.1" actix-threadpool = "0.3.1"
actix-tls = { version = "1.0.0", optional = true }
base64 = "0.10" base64 = "0.11"
bitflags = "1.0" bitflags = "1.2"
bytes = "0.4" bytes = "0.5.3"
copyless = "0.1.4" copyless = "0.1.4"
derive_more = "0.15.0" derive_more = "0.99.2"
either = "1.5.2" either = "1.5.3"
encoding_rs = "0.8" encoding_rs = "0.8"
futures = "0.1.25" futures-core = "0.3.1"
hashbrown = "0.5.0" futures-util = "0.3.1"
h2 = "0.1.16" futures-channel = "0.3.1"
http = "0.1.17" fxhash = "0.2.1"
h2 = "0.2.1"
http = "0.2.0"
httparse = "1.3" httparse = "1.3"
indexmap = "1.0" indexmap = "1.3"
lazy_static = "1.0" lazy_static = "1.4"
language-tags = "0.2" language-tags = "0.2"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
percent-encoding = "2.1" percent-encoding = "2.1"
pin-project = "0.4.6"
rand = "0.7" rand = "0.7"
regex = "1.0" regex = "1.3"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
sha1 = "0.6" sha-1 = "0.8"
slab = "0.4" slab = "0.4"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.6.1"
time = "0.1.42" time = { version = "0.2.7", default-features = false, features = ["std"] }
tokio-tcp = "0.1.3"
tokio-timer = "0.2.8"
tokio-current-thread = "0.1"
trust-dns-resolver = { version="0.11.1", default-features = false }
# for secure cookie # for secure cookie
ring = { version = "0.14.6", optional = true } ring = { version = "0.16.9", optional = true }
# compression # compression
brotli2 = { version="0.3.2", optional = true } brotli2 = { version="0.3.2", optional = true }
flate2 = { version="1.0.7", optional = true, default-features = false } flate2 = { version = "1.0.13", optional = true }
# optional deps # optional deps
failure = { version = "0.1.5", optional = true } fail-ure = { version = "0.1.5", package="failure", optional = true }
openssl = { version="0.10", optional = true }
rustls = { version = "0.15.2", optional = true }
webpki-roots = { version = "0.16", optional = true }
chrono = "0.4.6"
[dev-dependencies] [dev-dependencies]
actix-rt = "0.2.2" actix-server = "1.0.0"
actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } actix-connect = { version = "1.0.0", features=["openssl"] }
actix-connect = { version = "0.2.0", features=["ssl"] } actix-http-test = { version = "1.0.0", features=["openssl"] }
actix-http-test = { version = "0.2.4", features=["ssl"] } actix-tls = { version = "1.0.0", features=["openssl"] }
futures = "0.3.1"
env_logger = "0.6" env_logger = "0.6"
serde_derive = "1.0" serde_derive = "1.0"
openssl = { version="0.10" } open-ssl = { version="0.10", package = "openssl" }
tokio-tcp = "0.1" rust-tls = { version="0.16", package = "rustls" }

View File

@ -1,13 +1,14 @@
use std::{env, io}; use std::{env, io};
use actix_http::{error::PayloadError, HttpService, Request, Response}; use actix_http::{Error, HttpService, Request, Response};
use actix_server::Server; use actix_server::Server;
use bytes::BytesMut; use bytes::BytesMut;
use futures::{Future, Stream}; use futures::StreamExt;
use http::header::HeaderValue; use http::header::HeaderValue;
use log::info; use log::info;
fn main() -> io::Result<()> { #[actix_rt::main]
async fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "echo=info"); env::set_var("RUST_LOG", "echo=info");
env_logger::init(); env_logger::init();
@ -17,21 +18,25 @@ fn main() -> io::Result<()> {
.client_timeout(1000) .client_timeout(1000)
.client_disconnect(1000) .client_disconnect(1000)
.finish(|mut req: Request| { .finish(|mut req: Request| {
req.take_payload() async move {
.fold(BytesMut::new(), move |mut body, chunk| { let mut body = BytesMut::new();
body.extend_from_slice(&chunk); while let Some(item) = req.payload().next().await {
Ok::<_, PayloadError>(body) body.extend_from_slice(&item?);
}) }
.and_then(|bytes| {
info!("request body: {:?}", bytes); info!("request body: {:?}", body);
let mut res = Response::Ok(); Ok::<_, Error>(
res.header( Response::Ok()
.header(
"x-head", "x-head",
HeaderValue::from_static("dummy value!"), HeaderValue::from_static("dummy value!"),
); )
Ok(res.body(bytes)) .body(body),
}) )
}
}) })
.tcp()
})? })?
.run() .run()
.await
} }

View File

@ -1,34 +1,33 @@
use std::{env, io}; use std::{env, io};
use actix_http::http::HeaderValue; use actix_http::http::HeaderValue;
use actix_http::{error::PayloadError, Error, HttpService, Request, Response}; use actix_http::{Error, HttpService, Request, Response};
use actix_server::Server; use actix_server::Server;
use bytes::BytesMut; use bytes::BytesMut;
use futures::{Future, Stream}; use futures::StreamExt;
use log::info; use log::info;
fn handle_request(mut req: Request) -> impl Future<Item = Response, Error = Error> { async fn handle_request(mut req: Request) -> Result<Response, Error> {
req.take_payload() let mut body = BytesMut::new();
.fold(BytesMut::new(), move |mut body, chunk| { while let Some(item) = req.payload().next().await {
body.extend_from_slice(&chunk); body.extend_from_slice(&item?)
Ok::<_, PayloadError>(body) }
})
.from_err() info!("request body: {:?}", body);
.and_then(|bytes| { Ok(Response::Ok()
info!("request body: {:?}", bytes); .header("x-head", HeaderValue::from_static("dummy value!"))
let mut res = Response::Ok(); .body(body))
res.header("x-head", HeaderValue::from_static("dummy value!"));
Ok(res.body(bytes))
})
} }
fn main() -> io::Result<()> { #[actix_rt::main]
async fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "echo=info"); env::set_var("RUST_LOG", "echo=info");
env_logger::init(); env_logger::init();
Server::build() Server::build()
.bind("echo", "127.0.0.1:8080", || { .bind("echo", "127.0.0.1:8080", || {
HttpService::build().finish(|_req: Request| handle_request(_req)) HttpService::build().finish(handle_request).tcp()
})? })?
.run() .run()
.await
} }

View File

@ -6,7 +6,8 @@ use futures::future;
use http::header::HeaderValue; use http::header::HeaderValue;
use log::info; use log::info;
fn main() -> io::Result<()> { #[actix_rt::main]
async fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "hello_world=info"); env::set_var("RUST_LOG", "hello_world=info");
env_logger::init(); env_logger::init();
@ -21,6 +22,8 @@ fn main() -> io::Result<()> {
res.header("x-head", HeaderValue::from_static("dummy value!")); res.header("x-head", HeaderValue::from_static("dummy value!"));
future::ok::<_, ()>(res.body("Hello world!")) future::ok::<_, ()>(res.body("Hello world!"))
}) })
.tcp()
})? })?
.run() .run()
.await
} }

View File

@ -1,8 +1,12 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, mem}; use std::{fmt, mem};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{Async, Poll, Stream}; use futures_core::Stream;
use futures_util::ready;
use pin_project::{pin_project, project};
use crate::error::Error; use crate::error::Error;
@ -32,16 +36,20 @@ impl BodySize {
pub trait MessageBody { pub trait MessageBody {
fn size(&self) -> BodySize; fn size(&self) -> BodySize;
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error>; fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>>;
downcast_get_type_id!();
} }
downcast!(MessageBody);
impl MessageBody for () { impl MessageBody for () {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Empty BodySize::Empty
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
Ok(Async::Ready(None)) Poll::Ready(None)
} }
} }
@ -50,11 +58,12 @@ impl<T: MessageBody> MessageBody for Box<T> {
self.as_ref().size() self.as_ref().size()
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
self.as_mut().poll_next() self.as_mut().poll_next(cx)
} }
} }
#[pin_project]
pub enum ResponseBody<B> { pub enum ResponseBody<B> {
Body(B), Body(B),
Other(Body), Other(Body),
@ -93,20 +102,27 @@ impl<B: MessageBody> MessageBody for ResponseBody<B> {
} }
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
match self { match self {
ResponseBody::Body(ref mut body) => body.poll_next(), ResponseBody::Body(ref mut body) => body.poll_next(cx),
ResponseBody::Other(ref mut body) => body.poll_next(), ResponseBody::Other(ref mut body) => body.poll_next(cx),
} }
} }
} }
impl<B: MessageBody> Stream for ResponseBody<B> { impl<B: MessageBody> Stream for ResponseBody<B> {
type Item = Bytes; type Item = Result<Bytes, Error>;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { #[project]
self.poll_next() fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
#[project]
match self.project() {
ResponseBody::Body(ref mut body) => body.poll_next(cx),
ResponseBody::Other(ref mut body) => body.poll_next(cx),
}
} }
} }
@ -125,7 +141,7 @@ pub enum Body {
impl Body { impl Body {
/// Create body from slice (copy) /// Create body from slice (copy)
pub fn from_slice(s: &[u8]) -> Body { pub fn from_slice(s: &[u8]) -> Body {
Body::Bytes(Bytes::from(s)) Body::Bytes(Bytes::copy_from_slice(s))
} }
/// Create body from generic message body. /// Create body from generic message body.
@ -144,19 +160,19 @@ impl MessageBody for Body {
} }
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
match self { match self {
Body::None => Ok(Async::Ready(None)), Body::None => Poll::Ready(None),
Body::Empty => Ok(Async::Ready(None)), Body::Empty => Poll::Ready(None),
Body::Bytes(ref mut bin) => { Body::Bytes(ref mut bin) => {
let len = bin.len(); let len = bin.len();
if len == 0 { if len == 0 {
Ok(Async::Ready(None)) Poll::Ready(None)
} else { } else {
Ok(Async::Ready(Some(mem::replace(bin, Bytes::new())))) Poll::Ready(Some(Ok(mem::replace(bin, Bytes::new()))))
} }
} }
Body::Message(ref mut body) => body.poll_next(), Body::Message(ref mut body) => body.poll_next(cx),
} }
} }
} }
@ -182,7 +198,7 @@ impl PartialEq for Body {
} }
impl fmt::Debug for Body { impl fmt::Debug for Body {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
Body::None => write!(f, "Body::None"), Body::None => write!(f, "Body::None"),
Body::Empty => write!(f, "Body::Empty"), Body::Empty => write!(f, "Body::Empty"),
@ -218,7 +234,7 @@ impl From<String> for Body {
impl<'a> From<&'a String> for Body { impl<'a> From<&'a String> for Body {
fn from(s: &'a String) -> Body { fn from(s: &'a String) -> Body {
Body::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s))) Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s)))
} }
} }
@ -234,9 +250,15 @@ impl From<BytesMut> for Body {
} }
} }
impl From<serde_json::Value> for Body {
fn from(v: serde_json::Value) -> Body {
Body::Bytes(v.to_string().into())
}
}
impl<S> From<SizedStream<S>> for Body impl<S> From<SizedStream<S>> for Body
where where
S: Stream<Item = Bytes, Error = Error> + 'static, S: Stream<Item = Result<Bytes, Error>> + 'static,
{ {
fn from(s: SizedStream<S>) -> Body { fn from(s: SizedStream<S>) -> Body {
Body::from_message(s) Body::from_message(s)
@ -245,7 +267,7 @@ where
impl<S, E> From<BodyStream<S, E>> for Body impl<S, E> From<BodyStream<S, E>> for Body
where where
S: Stream<Item = Bytes, Error = E> + 'static, S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
{ {
fn from(s: BodyStream<S, E>) -> Body { fn from(s: BodyStream<S, E>) -> Body {
@ -258,11 +280,11 @@ impl MessageBody for Bytes {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Ok(Async::Ready(None)) Poll::Ready(None)
} else { } else {
Ok(Async::Ready(Some(mem::replace(self, Bytes::new())))) Poll::Ready(Some(Ok(mem::replace(self, Bytes::new()))))
} }
} }
} }
@ -272,13 +294,11 @@ impl MessageBody for BytesMut {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Ok(Async::Ready(None)) Poll::Ready(None)
} else { } else {
Ok(Async::Ready(Some( Poll::Ready(Some(Ok(mem::replace(self, BytesMut::new()).freeze())))
mem::replace(self, BytesMut::new()).freeze(),
)))
} }
} }
} }
@ -288,11 +308,11 @@ impl MessageBody for &'static str {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Ok(Async::Ready(None)) Poll::Ready(None)
} else { } else {
Ok(Async::Ready(Some(Bytes::from_static( Poll::Ready(Some(Ok(Bytes::from_static(
mem::replace(self, "").as_ref(), mem::replace(self, "").as_ref(),
)))) ))))
} }
@ -304,13 +324,11 @@ impl MessageBody for &'static [u8] {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Ok(Async::Ready(None)) Poll::Ready(None)
} else { } else {
Ok(Async::Ready(Some(Bytes::from_static(mem::replace( Poll::Ready(Some(Ok(Bytes::from_static(mem::replace(self, b"")))))
self, b"",
)))))
} }
} }
} }
@ -320,14 +338,11 @@ impl MessageBody for Vec<u8> {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Ok(Async::Ready(None)) Poll::Ready(None)
} else { } else {
Ok(Async::Ready(Some(Bytes::from(mem::replace( Poll::Ready(Some(Ok(Bytes::from(mem::replace(self, Vec::new())))))
self,
Vec::new(),
)))))
} }
} }
} }
@ -337,11 +352,11 @@ impl MessageBody for String {
BodySize::Sized(self.len()) BodySize::Sized(self.len())
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, _: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
if self.is_empty() { if self.is_empty() {
Ok(Async::Ready(None)) Poll::Ready(None)
} else { } else {
Ok(Async::Ready(Some(Bytes::from( Poll::Ready(Some(Ok(Bytes::from(
mem::replace(self, String::new()).into_bytes(), mem::replace(self, String::new()).into_bytes(),
)))) ))))
} }
@ -351,18 +366,18 @@ impl MessageBody for String {
/// Type represent streaming body. /// Type represent streaming body.
/// Response does not contain `content-length` header and appropriate transfer encoding is used. /// Response does not contain `content-length` header and appropriate transfer encoding is used.
pub struct BodyStream<S, E> { pub struct BodyStream<S, E> {
stream: S, stream: Pin<Box<S>>,
_t: PhantomData<E>, _t: PhantomData<E>,
} }
impl<S, E> BodyStream<S, E> impl<S, E> BodyStream<S, E>
where where
S: Stream<Item = Bytes, Error = E>, S: Stream<Item = Result<Bytes, E>>,
E: Into<Error>, E: Into<Error>,
{ {
pub fn new(stream: S) -> Self { pub fn new(stream: S) -> Self {
BodyStream { BodyStream {
stream, stream: Box::pin(stream),
_t: PhantomData, _t: PhantomData,
} }
} }
@ -370,15 +385,26 @@ where
impl<S, E> MessageBody for BodyStream<S, E> impl<S, E> MessageBody for BodyStream<S, E>
where where
S: Stream<Item = Bytes, Error = E>, S: Stream<Item = Result<Bytes, E>>,
E: Into<Error>, E: Into<Error>,
{ {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Stream BodySize::Stream
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { /// Attempts to pull out the next value of the underlying [`Stream`].
self.stream.poll().map_err(std::convert::Into::into) ///
/// Empty values are skipped to prevent [`BodyStream`]'s transmission being
/// ended on a zero-length chunk, but rather proceed until the underlying
/// [`Stream`] ends.
fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
let mut stream = self.stream.as_mut();
loop {
return Poll::Ready(match ready!(stream.as_mut().poll_next(cx)) {
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
opt => opt.map(|res| res.map_err(Into::into)),
});
}
} }
} }
@ -386,34 +412,50 @@ where
/// if total size of stream is known. Data get sent as is without using transfer encoding. /// if total size of stream is known. Data get sent as is without using transfer encoding.
pub struct SizedStream<S> { pub struct SizedStream<S> {
size: u64, size: u64,
stream: S, stream: Pin<Box<S>>,
} }
impl<S> SizedStream<S> impl<S> SizedStream<S>
where where
S: Stream<Item = Bytes, Error = Error>, S: Stream<Item = Result<Bytes, Error>>,
{ {
pub fn new(size: u64, stream: S) -> Self { pub fn new(size: u64, stream: S) -> Self {
SizedStream { size, stream } SizedStream {
size,
stream: Box::pin(stream),
}
} }
} }
impl<S> MessageBody for SizedStream<S> impl<S> MessageBody for SizedStream<S>
where where
S: Stream<Item = Bytes, Error = Error>, S: Stream<Item = Result<Bytes, Error>>,
{ {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized64(self.size) BodySize::Sized64(self.size)
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { /// Attempts to pull out the next value of the underlying [`Stream`].
self.stream.poll() ///
/// Empty values are skipped to prevent [`SizedStream`]'s transmission being
/// ended on a zero-length chunk, but rather proceed until the underlying
/// [`Stream`] ends.
fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
let mut stream = self.stream.as_mut();
loop {
return Poll::Ready(match ready!(stream.as_mut().poll_next(cx)) {
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
val => val,
});
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use futures::stream;
use futures_util::future::poll_fn;
impl Body { impl Body {
pub(crate) fn get_ref(&self) -> &[u8] { pub(crate) fn get_ref(&self) -> &[u8] {
@ -433,21 +475,21 @@ mod tests {
} }
} }
#[test] #[actix_rt::test]
fn test_static_str() { async fn test_static_str() {
assert_eq!(Body::from("").size(), BodySize::Sized(0)); assert_eq!(Body::from("").size(), BodySize::Sized(0));
assert_eq!(Body::from("test").size(), BodySize::Sized(4)); assert_eq!(Body::from("test").size(), BodySize::Sized(4));
assert_eq!(Body::from("test").get_ref(), b"test"); assert_eq!(Body::from("test").get_ref(), b"test");
assert_eq!("test".size(), BodySize::Sized(4)); assert_eq!("test".size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
"test".poll_next().unwrap(), poll_fn(|cx| "test".poll_next(cx)).await.unwrap().ok(),
Async::Ready(Some(Bytes::from("test"))) Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_static_bytes() { async fn test_static_bytes() {
assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test");
assert_eq!( assert_eq!(
@ -458,51 +500,57 @@ mod tests {
assert_eq!((&b"test"[..]).size(), BodySize::Sized(4)); assert_eq!((&b"test"[..]).size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
(&b"test"[..]).poll_next().unwrap(), poll_fn(|cx| (&b"test"[..]).poll_next(cx))
Async::Ready(Some(Bytes::from("test"))) .await
.unwrap()
.ok(),
Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_vec() { async fn test_vec() {
assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4)); assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4));
assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test");
assert_eq!(Vec::from("test").size(), BodySize::Sized(4)); assert_eq!(Vec::from("test").size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
Vec::from("test").poll_next().unwrap(), poll_fn(|cx| Vec::from("test").poll_next(cx))
Async::Ready(Some(Bytes::from("test"))) .await
.unwrap()
.ok(),
Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_bytes() { async fn test_bytes() {
let mut b = Bytes::from("test"); let mut b = Bytes::from("test");
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b.clone()).get_ref(), b"test"); assert_eq!(Body::from(b.clone()).get_ref(), b"test");
assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
b.poll_next().unwrap(), poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(),
Async::Ready(Some(Bytes::from("test"))) Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_bytes_mut() { async fn test_bytes_mut() {
let mut b = BytesMut::from("test"); let mut b = BytesMut::from("test");
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b.clone()).get_ref(), b"test"); assert_eq!(Body::from(b.clone()).get_ref(), b"test");
assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
b.poll_next().unwrap(), poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(),
Async::Ready(Some(Bytes::from("test"))) Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_string() { async fn test_string() {
let mut b = "test".to_owned(); let mut b = "test".to_owned();
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(Body::from(b.clone()).get_ref(), b"test"); assert_eq!(Body::from(b.clone()).get_ref(), b"test");
@ -511,26 +559,26 @@ mod tests {
assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
b.poll_next().unwrap(), poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(),
Async::Ready(Some(Bytes::from("test"))) Some(Bytes::from("test"))
); );
} }
#[test] #[actix_rt::test]
fn test_unit() { async fn test_unit() {
assert_eq!(().size(), BodySize::Empty); assert_eq!(().size(), BodySize::Empty);
assert_eq!(().poll_next().unwrap(), Async::Ready(None)); assert!(poll_fn(|cx| ().poll_next(cx)).await.is_none());
} }
#[test] #[actix_rt::test]
fn test_box() { async fn test_box() {
let mut val = Box::new(()); let mut val = Box::new(());
assert_eq!(val.size(), BodySize::Empty); assert_eq!(val.size(), BodySize::Empty);
assert_eq!(val.poll_next().unwrap(), Async::Ready(None)); assert!(poll_fn(|cx| val.poll_next(cx)).await.is_none());
} }
#[test] #[actix_rt::test]
fn test_body_eq() { async fn test_body_eq() {
assert!(Body::None == Body::None); assert!(Body::None == Body::None);
assert!(Body::None != Body::Empty); assert!(Body::None != Body::Empty);
assert!(Body::Empty == Body::Empty); assert!(Body::Empty == Body::Empty);
@ -542,10 +590,78 @@ mod tests {
assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None); assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None);
} }
#[test] #[actix_rt::test]
fn test_body_debug() { async fn test_body_debug() {
assert!(format!("{:?}", Body::None).contains("Body::None")); assert!(format!("{:?}", Body::None).contains("Body::None"));
assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); assert!(format!("{:?}", Body::Empty).contains("Body::Empty"));
assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1")); assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1"));
} }
#[actix_rt::test]
async fn test_serde_json() {
use serde_json::json;
assert_eq!(
Body::from(serde_json::Value::String("test".into())).size(),
BodySize::Sized(6)
);
assert_eq!(
Body::from(json!({"test-key":"test-value"})).size(),
BodySize::Sized(25)
);
}
mod body_stream {
use super::*;
#[actix_rt::test]
async fn skips_empty_chunks() {
let mut body = BodyStream::new(stream::iter(
["1", "", "2"]
.iter()
.map(|&v| Ok(Bytes::from(v)) as Result<Bytes, ()>),
));
assert_eq!(
poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("1")),
);
assert_eq!(
poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("2")),
);
}
}
mod sized_stream {
use super::*;
#[actix_rt::test]
async fn skips_empty_chunks() {
let mut body = SizedStream::new(
2,
stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))),
);
assert_eq!(
poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("1")),
);
assert_eq!(
poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("2")),
);
}
}
#[actix_rt::test]
async fn test_body_casting() {
let mut body = String::from("hello cast");
let resp_body: &mut dyn MessageBody = &mut body;
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast");
let body = &mut resp_body.downcast_mut::<String>().unwrap();
body.push_str("!");
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast!");
let not_body = resp_body.downcast_ref::<()>();
assert!(not_body.is_none());
}
} }

View File

@ -1,10 +1,9 @@
use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, net};
use actix_codec::Framed; use actix_codec::Framed;
use actix_server_config::ServerConfig as SrvConfig; use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use actix_service::{IntoNewService, NewService, Service};
use crate::body::MessageBody; use crate::body::MessageBody;
use crate::config::{KeepAlive, ServiceConfig}; use crate::config::{KeepAlive, ServiceConfig};
@ -24,6 +23,8 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
keep_alive: KeepAlive, keep_alive: KeepAlive,
client_timeout: u64, client_timeout: u64,
client_disconnect: u64, client_disconnect: u64,
secure: bool,
local_addr: Option<net::SocketAddr>,
expect: X, expect: X,
upgrade: Option<U>, upgrade: Option<U>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
@ -32,9 +33,10 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler<T>> impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler<T>>
where where
S: NewService<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
<S::Service as Service>::Future: 'static,
{ {
/// Create instance of `ServiceConfigBuilder` /// Create instance of `ServiceConfigBuilder`
pub fn new() -> Self { pub fn new() -> Self {
@ -42,6 +44,8 @@ where
keep_alive: KeepAlive::Timeout(5), keep_alive: KeepAlive::Timeout(5),
client_timeout: 5000, client_timeout: 5000,
client_disconnect: 0, client_disconnect: 0,
secure: false,
local_addr: None,
expect: ExpectHandler, expect: ExpectHandler,
upgrade: None, upgrade: None,
on_connect: None, on_connect: None,
@ -52,19 +56,18 @@ where
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U> impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
where where
S: NewService<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
X: NewService<Config = SrvConfig, Request = Request, Response = Request>, <S::Service as Service>::Future: 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService< <X::Service as Service>::Future: 'static,
Config = SrvConfig, U: ServiceFactory<Config = (), Request = (Request, Framed<T, Codec>), Response = ()>,
Request = (Request, Framed<T, Codec>),
Response = (),
>,
U::Error: fmt::Display, U::Error: fmt::Display,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
{ {
/// Set server keep-alive setting. /// Set server keep-alive setting.
/// ///
@ -74,6 +77,18 @@ where
self self
} }
/// Set connection secure state
pub fn secure(mut self) -> Self {
self.secure = true;
self
}
/// Set the local address that this service is bound to.
pub fn local_addr(mut self, addr: net::SocketAddr) -> Self {
self.local_addr = Some(addr);
self
}
/// Set server client timeout in milliseconds for first request. /// Set server client timeout in milliseconds for first request.
/// ///
/// Defines a timeout for reading client request header. If a client does not transmit /// Defines a timeout for reading client request header. If a client does not transmit
@ -108,16 +123,19 @@ where
/// request will be forwarded to main service. /// request will be forwarded to main service.
pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U> pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U>
where where
F: IntoNewService<X1>, F: IntoServiceFactory<X1>,
X1: NewService<Config = SrvConfig, Request = Request, Response = Request>, X1: ServiceFactory<Config = (), Request = Request, Response = Request>,
X1::Error: Into<Error>, X1::Error: Into<Error>,
X1::InitError: fmt::Debug, X1::InitError: fmt::Debug,
<X1::Service as Service>::Future: 'static,
{ {
HttpServiceBuilder { HttpServiceBuilder {
keep_alive: self.keep_alive, keep_alive: self.keep_alive,
client_timeout: self.client_timeout, client_timeout: self.client_timeout,
client_disconnect: self.client_disconnect, client_disconnect: self.client_disconnect,
expect: expect.into_new_service(), secure: self.secure,
local_addr: self.local_addr,
expect: expect.into_factory(),
upgrade: self.upgrade, upgrade: self.upgrade,
on_connect: self.on_connect, on_connect: self.on_connect,
_t: PhantomData, _t: PhantomData,
@ -130,21 +148,24 @@ where
/// and this service get called with original request and framed object. /// and this service get called with original request and framed object.
pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1> pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1>
where where
F: IntoNewService<U1>, F: IntoServiceFactory<U1>,
U1: NewService< U1: ServiceFactory<
Config = SrvConfig, Config = (),
Request = (Request, Framed<T, Codec>), Request = (Request, Framed<T, Codec>),
Response = (), Response = (),
>, >,
U1::Error: fmt::Display, U1::Error: fmt::Display,
U1::InitError: fmt::Debug, U1::InitError: fmt::Debug,
<U1::Service as Service>::Future: 'static,
{ {
HttpServiceBuilder { HttpServiceBuilder {
keep_alive: self.keep_alive, keep_alive: self.keep_alive,
client_timeout: self.client_timeout, client_timeout: self.client_timeout,
client_disconnect: self.client_disconnect, client_disconnect: self.client_disconnect,
secure: self.secure,
local_addr: self.local_addr,
expect: self.expect, expect: self.expect,
upgrade: Some(upgrade.into_new_service()), upgrade: Some(upgrade.into_factory()),
on_connect: self.on_connect, on_connect: self.on_connect,
_t: PhantomData, _t: PhantomData,
} }
@ -164,10 +185,10 @@ where
} }
/// Finish service configuration and create *http service* for HTTP/1 protocol. /// Finish service configuration and create *http service* for HTTP/1 protocol.
pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B, X, U> pub fn h1<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
where where
B: MessageBody + 'static, B: MessageBody,
F: IntoNewService<S>, F: IntoServiceFactory<S>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@ -176,48 +197,53 @@ where
self.keep_alive, self.keep_alive,
self.client_timeout, self.client_timeout,
self.client_disconnect, self.client_disconnect,
self.secure,
self.local_addr,
); );
H1Service::with_config(cfg, service.into_new_service()) H1Service::with_config(cfg, service.into_factory())
.expect(self.expect) .expect(self.expect)
.upgrade(self.upgrade) .upgrade(self.upgrade)
.on_connect(self.on_connect) .on_connect(self.on_connect)
} }
/// Finish service configuration and create *http service* for HTTP/2 protocol. /// Finish service configuration and create *http service* for HTTP/2 protocol.
pub fn h2<F, P, B>(self, service: F) -> H2Service<T, P, S, B> pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
where where
B: MessageBody + 'static, B: MessageBody + 'static,
F: IntoNewService<S>, F: IntoServiceFactory<S>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
{ {
let cfg = ServiceConfig::new( let cfg = ServiceConfig::new(
self.keep_alive, self.keep_alive,
self.client_timeout, self.client_timeout,
self.client_disconnect, self.client_disconnect,
self.secure,
self.local_addr,
); );
H2Service::with_config(cfg, service.into_new_service()) H2Service::with_config(cfg, service.into_factory()).on_connect(self.on_connect)
.on_connect(self.on_connect)
} }
/// Finish service configuration and create `HttpService` instance. /// Finish service configuration and create `HttpService` instance.
pub fn finish<F, P, B>(self, service: F) -> HttpService<T, P, S, B, X, U> pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
where where
B: MessageBody + 'static, B: MessageBody + 'static,
F: IntoNewService<S>, F: IntoServiceFactory<S>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
{ {
let cfg = ServiceConfig::new( let cfg = ServiceConfig::new(
self.keep_alive, self.keep_alive,
self.client_timeout, self.client_timeout,
self.client_disconnect, self.client_disconnect,
self.secure,
self.local_addr,
); );
HttpService::with_config(cfg, service.into_new_service()) HttpService::with_config(cfg, service.into_factory())
.expect(self.expect) .expect(self.expect)
.upgrade(self.upgrade) .upgrade(self.upgrade)
.on_connect(self.on_connect) .on_connect(self.on_connect)

View File

@ -1,10 +1,12 @@
use std::{fmt, io, time}; use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, io, mem, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use bytes::{Buf, Bytes}; use bytes::{Buf, Bytes};
use futures::future::{err, Either, Future, FutureResult}; use futures_util::future::{err, Either, Future, FutureExt, LocalBoxFuture, Ready};
use futures::Poll;
use h2::client::SendRequest; use h2::client::SendRequest;
use pin_project::{pin_project, project};
use crate::body::MessageBody; use crate::body::MessageBody;
use crate::h1::ClientCodec; use crate::h1::ClientCodec;
@ -21,8 +23,8 @@ pub(crate) enum ConnectionType<Io> {
} }
pub trait Connection { pub trait Connection {
type Io: AsyncRead + AsyncWrite; type Io: AsyncRead + AsyncWrite + Unpin;
type Future: Future<Item = (ResponseHead, Payload), Error = SendRequestError>; type Future: Future<Output = Result<(ResponseHead, Payload), SendRequestError>>;
fn protocol(&self) -> Protocol; fn protocol(&self) -> Protocol;
@ -34,8 +36,7 @@ pub trait Connection {
) -> Self::Future; ) -> Self::Future;
type TunnelFuture: Future< type TunnelFuture: Future<
Item = (ResponseHead, Framed<Self::Io, ClientCodec>), Output = Result<(ResponseHead, Framed<Self::Io, ClientCodec>), SendRequestError>,
Error = SendRequestError,
>; >;
/// Send request, returns Response and Framed /// Send request, returns Response and Framed
@ -62,7 +63,7 @@ impl<T> fmt::Debug for IoConnection<T>
where where
T: fmt::Debug, T: fmt::Debug,
{ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.io { match self.io {
Some(ConnectionType::H1(ref io)) => write!(f, "H1Connection({:?})", io), Some(ConnectionType::H1(ref io)) => write!(f, "H1Connection({:?})", io),
Some(ConnectionType::H2(_)) => write!(f, "H2Connection"), Some(ConnectionType::H2(_)) => write!(f, "H2Connection"),
@ -71,7 +72,7 @@ where
} }
} }
impl<T: AsyncRead + AsyncWrite> IoConnection<T> { impl<T: AsyncRead + AsyncWrite + Unpin> IoConnection<T> {
pub(crate) fn new( pub(crate) fn new(
io: ConnectionType<T>, io: ConnectionType<T>,
created: time::Instant, created: time::Instant,
@ -91,11 +92,11 @@ impl<T: AsyncRead + AsyncWrite> IoConnection<T> {
impl<T> Connection for IoConnection<T> impl<T> Connection for IoConnection<T>
where where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
type Io = T; type Io = T;
type Future = type Future =
Box<dyn Future<Item = (ResponseHead, Payload), Error = SendRequestError>>; LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>;
fn protocol(&self) -> Protocol { fn protocol(&self) -> Protocol {
match self.io { match self.io {
@ -111,38 +112,30 @@ where
body: B, body: B,
) -> Self::Future { ) -> Self::Future {
match self.io.take().unwrap() { match self.io.take().unwrap() {
ConnectionType::H1(io) => Box::new(h1proto::send_request( ConnectionType::H1(io) => {
io, h1proto::send_request(io, head.into(), body, self.created, self.pool)
head.into(), .boxed_local()
body, }
self.created, ConnectionType::H2(io) => {
self.pool, h2proto::send_request(io, head.into(), body, self.created, self.pool)
)), .boxed_local()
ConnectionType::H2(io) => Box::new(h2proto::send_request( }
io,
head.into(),
body,
self.created,
self.pool,
)),
} }
} }
type TunnelFuture = Either< type TunnelFuture = Either<
Box< LocalBoxFuture<
dyn Future< 'static,
Item = (ResponseHead, Framed<Self::Io, ClientCodec>), Result<(ResponseHead, Framed<Self::Io, ClientCodec>), SendRequestError>,
Error = SendRequestError,
>, >,
>, Ready<Result<(ResponseHead, Framed<Self::Io, ClientCodec>), SendRequestError>>,
FutureResult<(ResponseHead, Framed<Self::Io, ClientCodec>), SendRequestError>,
>; >;
/// Send request, returns Response and Framed /// Send request, returns Response and Framed
fn open_tunnel<H: Into<RequestHeadType>>(mut self, head: H) -> Self::TunnelFuture { fn open_tunnel<H: Into<RequestHeadType>>(mut self, head: H) -> Self::TunnelFuture {
match self.io.take().unwrap() { match self.io.take().unwrap() {
ConnectionType::H1(io) => { ConnectionType::H1(io) => {
Either::A(Box::new(h1proto::open_tunnel(io, head.into()))) Either::Left(h1proto::open_tunnel(io, head.into()).boxed_local())
} }
ConnectionType::H2(io) => { ConnectionType::H2(io) => {
if let Some(mut pool) = self.pool.take() { if let Some(mut pool) = self.pool.take() {
@ -152,7 +145,7 @@ where
None, None,
)); ));
} }
Either::B(err(SendRequestError::TunnelNotSupported)) Either::Right(err(SendRequestError::TunnelNotSupported))
} }
} }
} }
@ -166,12 +159,12 @@ pub(crate) enum EitherConnection<A, B> {
impl<A, B> Connection for EitherConnection<A, B> impl<A, B> Connection for EitherConnection<A, B>
where where
A: AsyncRead + AsyncWrite + 'static, A: AsyncRead + AsyncWrite + Unpin + 'static,
B: AsyncRead + AsyncWrite + 'static, B: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
type Io = EitherIo<A, B>; type Io = EitherIo<A, B>;
type Future = type Future =
Box<dyn Future<Item = (ResponseHead, Payload), Error = SendRequestError>>; LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>;
fn protocol(&self) -> Protocol { fn protocol(&self) -> Protocol {
match self { match self {
@ -191,44 +184,30 @@ where
} }
} }
type TunnelFuture = Box< type TunnelFuture = LocalBoxFuture<
dyn Future< 'static,
Item = (ResponseHead, Framed<Self::Io, ClientCodec>), Result<(ResponseHead, Framed<Self::Io, ClientCodec>), SendRequestError>,
Error = SendRequestError,
>,
>; >;
/// Send request, returns Response and Framed /// Send request, returns Response and Framed
fn open_tunnel<H: Into<RequestHeadType>>(self, head: H) -> Self::TunnelFuture { fn open_tunnel<H: Into<RequestHeadType>>(self, head: H) -> Self::TunnelFuture {
match self { match self {
EitherConnection::A(con) => Box::new( EitherConnection::A(con) => con
con.open_tunnel(head) .open_tunnel(head)
.map(|(head, framed)| (head, framed.map_io(EitherIo::A))), .map(|res| res.map(|(head, framed)| (head, framed.map_io(EitherIo::A))))
), .boxed_local(),
EitherConnection::B(con) => Box::new( EitherConnection::B(con) => con
con.open_tunnel(head) .open_tunnel(head)
.map(|(head, framed)| (head, framed.map_io(EitherIo::B))), .map(|res| res.map(|(head, framed)| (head, framed.map_io(EitherIo::B))))
), .boxed_local(),
} }
} }
} }
#[pin_project]
pub enum EitherIo<A, B> { pub enum EitherIo<A, B> {
A(A), A(#[pin] A),
B(B), B(#[pin] B),
}
impl<A, B> io::Read for EitherIo<A, B>
where
A: io::Read,
B: io::Read,
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
EitherIo::A(ref mut val) => val.read(buf),
EitherIo::B(ref mut val) => val.read(buf),
}
}
} }
impl<A, B> AsyncRead for EitherIo<A, B> impl<A, B> AsyncRead for EitherIo<A, B>
@ -236,7 +215,23 @@ where
A: AsyncRead, A: AsyncRead,
B: AsyncRead, B: AsyncRead,
{ {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { #[project]
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
#[project]
match self.project() {
EitherIo::A(val) => val.poll_read(cx, buf),
EitherIo::B(val) => val.poll_read(cx, buf),
}
}
unsafe fn prepare_uninitialized_buffer(
&self,
buf: &mut [mem::MaybeUninit<u8>],
) -> bool {
match self { match self {
EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf), EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf),
EitherIo::B(ref val) => val.prepare_uninitialized_buffer(buf), EitherIo::B(ref val) => val.prepare_uninitialized_buffer(buf),
@ -244,45 +239,58 @@ where
} }
} }
impl<A, B> io::Write for EitherIo<A, B>
where
A: io::Write,
B: io::Write,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
EitherIo::A(ref mut val) => val.write(buf),
EitherIo::B(ref mut val) => val.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
EitherIo::A(ref mut val) => val.flush(),
EitherIo::B(ref mut val) => val.flush(),
}
}
}
impl<A, B> AsyncWrite for EitherIo<A, B> impl<A, B> AsyncWrite for EitherIo<A, B>
where where
A: AsyncWrite, A: AsyncWrite,
B: AsyncWrite, B: AsyncWrite,
{ {
fn shutdown(&mut self) -> Poll<(), io::Error> { #[project]
match self { fn poll_write(
EitherIo::A(ref mut val) => val.shutdown(), self: Pin<&mut Self>,
EitherIo::B(ref mut val) => val.shutdown(), cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
#[project]
match self.project() {
EitherIo::A(val) => val.poll_write(cx, buf),
EitherIo::B(val) => val.poll_write(cx, buf),
} }
} }
fn write_buf<U: Buf>(&mut self, buf: &mut U) -> Poll<usize, io::Error> #[project]
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
#[project]
match self.project() {
EitherIo::A(val) => val.poll_flush(cx),
EitherIo::B(val) => val.poll_flush(cx),
}
}
#[project]
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
#[project]
match self.project() {
EitherIo::A(val) => val.poll_shutdown(cx),
EitherIo::B(val) => val.poll_shutdown(cx),
}
}
#[project]
fn poll_write_buf<U: Buf>(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut U,
) -> Poll<Result<usize, io::Error>>
where where
Self: Sized, Self: Sized,
{ {
match self { #[project]
EitherIo::A(ref mut val) => val.write_buf(buf), match self.project() {
EitherIo::B(ref mut val) => val.write_buf(buf), EitherIo::A(val) => val.poll_write_buf(cx, buf),
EitherIo::B(val) => val.poll_write_buf(cx, buf),
} }
} }
} }

View File

@ -6,32 +6,32 @@ use actix_codec::{AsyncRead, AsyncWrite};
use actix_connect::{ use actix_connect::{
default_connector, Connect as TcpConnect, Connection as TcpConnection, default_connector, Connect as TcpConnect, Connection as TcpConnection,
}; };
use actix_service::{apply_fn, Service, ServiceExt}; use actix_rt::net::TcpStream;
use actix_service::{apply_fn, Service};
use actix_utils::timeout::{TimeoutError, TimeoutService}; use actix_utils::timeout::{TimeoutError, TimeoutService};
use http::Uri; use http::Uri;
use tokio_tcp::TcpStream;
use super::connection::Connection; use super::connection::Connection;
use super::error::ConnectError; use super::error::ConnectError;
use super::pool::{ConnectionPool, Protocol}; use super::pool::{ConnectionPool, Protocol};
use super::Connect; use super::Connect;
#[cfg(feature = "ssl")] #[cfg(feature = "openssl")]
use openssl::ssl::SslConnector as OpensslConnector; use actix_connect::ssl::openssl::SslConnector as OpensslConnector;
#[cfg(feature = "rust-tls")] #[cfg(feature = "rustls")]
use rustls::ClientConfig; use actix_connect::ssl::rustls::ClientConfig;
#[cfg(feature = "rust-tls")] #[cfg(feature = "rustls")]
use std::sync::Arc; use std::sync::Arc;
#[cfg(any(feature = "ssl", feature = "rust-tls"))] #[cfg(any(feature = "openssl", feature = "rustls"))]
enum SslConnector { enum SslConnector {
#[cfg(feature = "ssl")] #[cfg(feature = "openssl")]
Openssl(OpensslConnector), Openssl(OpensslConnector),
#[cfg(feature = "rust-tls")] #[cfg(feature = "rustls")]
Rustls(Arc<ClientConfig>), Rustls(Arc<ClientConfig>),
} }
#[cfg(not(any(feature = "ssl", feature = "rust-tls")))] #[cfg(not(any(feature = "openssl", feature = "rustls")))]
type SslConnector = (); type SslConnector = ();
/// Manages http client network connectivity /// Manages http client network connectivity
@ -58,11 +58,11 @@ pub struct Connector<T, U> {
_t: PhantomData<U>, _t: PhantomData<U>,
} }
trait Io: AsyncRead + AsyncWrite {} trait Io: AsyncRead + AsyncWrite + Unpin {}
impl<T: AsyncRead + AsyncWrite> Io for T {} impl<T: AsyncRead + AsyncWrite + Unpin> Io for T {}
impl Connector<(), ()> { impl Connector<(), ()> {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self, clippy::let_unit_value)]
pub fn new() -> Connector< pub fn new() -> Connector<
impl Service< impl Service<
Request = TcpConnect<Uri>, Request = TcpConnect<Uri>,
@ -72,9 +72,9 @@ impl Connector<(), ()> {
TcpStream, TcpStream,
> { > {
let ssl = { let ssl = {
#[cfg(feature = "ssl")] #[cfg(feature = "openssl")]
{ {
use openssl::ssl::SslMethod; use actix_connect::ssl::openssl::SslMethod;
let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap(); let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap();
let _ = ssl let _ = ssl
@ -82,17 +82,17 @@ impl Connector<(), ()> {
.map_err(|e| error!("Can not set alpn protocol: {:?}", e)); .map_err(|e| error!("Can not set alpn protocol: {:?}", e));
SslConnector::Openssl(ssl.build()) SslConnector::Openssl(ssl.build())
} }
#[cfg(all(not(feature = "ssl"), feature = "rust-tls"))] #[cfg(all(not(feature = "openssl"), feature = "rustls"))]
{ {
let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
let mut config = ClientConfig::new(); let mut config = ClientConfig::new();
config.set_protocols(&protos); config.set_protocols(&protos);
config config
.root_store .root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); .add_server_trust_anchors(&actix_tls::rustls::TLS_SERVER_ROOTS);
SslConnector::Rustls(Arc::new(config)) SslConnector::Rustls(Arc::new(config))
} }
#[cfg(not(any(feature = "ssl", feature = "rust-tls")))] #[cfg(not(any(feature = "openssl", feature = "rustls")))]
{} {}
}; };
@ -113,7 +113,7 @@ impl<T, U> Connector<T, U> {
/// Use custom connector. /// Use custom connector.
pub fn connector<T1, U1>(self, connector: T1) -> Connector<T1, U1> pub fn connector<T1, U1>(self, connector: T1) -> Connector<T1, U1>
where where
U1: AsyncRead + AsyncWrite + fmt::Debug, U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
T1: Service< T1: Service<
Request = TcpConnect<Uri>, Request = TcpConnect<Uri>,
Response = TcpConnection<Uri, U1>, Response = TcpConnection<Uri, U1>,
@ -135,7 +135,7 @@ impl<T, U> Connector<T, U> {
impl<T, U> Connector<T, U> impl<T, U> Connector<T, U>
where where
U: AsyncRead + AsyncWrite + fmt::Debug + 'static, U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
T: Service< T: Service<
Request = TcpConnect<Uri>, Request = TcpConnect<Uri>,
Response = TcpConnection<Uri, U>, Response = TcpConnection<Uri, U>,
@ -150,14 +150,14 @@ where
self self
} }
#[cfg(feature = "ssl")] #[cfg(feature = "openssl")]
/// Use custom `SslConnector` instance. /// Use custom `SslConnector` instance.
pub fn ssl(mut self, connector: OpensslConnector) -> Self { pub fn ssl(mut self, connector: OpensslConnector) -> Self {
self.ssl = SslConnector::Openssl(connector); self.ssl = SslConnector::Openssl(connector);
self self
} }
#[cfg(feature = "rust-tls")] #[cfg(feature = "rustls")]
pub fn rustls(mut self, connector: Arc<ClientConfig>) -> Self { pub fn rustls(mut self, connector: Arc<ClientConfig>) -> Self {
self.ssl = SslConnector::Rustls(connector); self.ssl = SslConnector::Rustls(connector);
self self
@ -213,7 +213,7 @@ where
self, self,
) -> impl Service<Request = Connect, Response = impl Connection, Error = ConnectError> ) -> impl Service<Request = Connect, Response = impl Connection, Error = ConnectError>
+ Clone { + Clone {
#[cfg(not(any(feature = "ssl", feature = "rust-tls")))] #[cfg(not(any(feature = "openssl", feature = "rustls")))]
{ {
let connector = TimeoutService::new( let connector = TimeoutService::new(
self.timeout, self.timeout,
@ -238,32 +238,30 @@ where
), ),
} }
} }
#[cfg(any(feature = "ssl", feature = "rust-tls"))] #[cfg(any(feature = "openssl", feature = "rustls"))]
{ {
const H2: &[u8] = b"h2"; const H2: &[u8] = b"h2";
#[cfg(feature = "ssl")] #[cfg(feature = "openssl")]
use actix_connect::ssl::OpensslConnector; use actix_connect::ssl::openssl::OpensslConnector;
#[cfg(feature = "rust-tls")] #[cfg(feature = "rustls")]
use actix_connect::ssl::RustlsConnector; use actix_connect::ssl::rustls::{RustlsConnector, Session};
use actix_service::boxed::service; use actix_service::{boxed::service, pipeline};
#[cfg(feature = "rust-tls")]
use rustls::Session;
let ssl_service = TimeoutService::new( let ssl_service = TimeoutService::new(
self.timeout, self.timeout,
pipeline(
apply_fn(self.connector.clone(), |msg: Connect, srv| { apply_fn(self.connector.clone(), |msg: Connect, srv| {
srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr))
}) })
.map_err(ConnectError::from) .map_err(ConnectError::from),
)
.and_then(match self.ssl { .and_then(match self.ssl {
#[cfg(feature = "ssl")] #[cfg(feature = "openssl")]
SslConnector::Openssl(ssl) => service( SslConnector::Openssl(ssl) => service(
OpensslConnector::service(ssl) OpensslConnector::service(ssl)
.map_err(ConnectError::from)
.map(|stream| { .map(|stream| {
let sock = stream.into_parts().0; let sock = stream.into_parts().0;
let h2 = sock let h2 = sock
.get_ref()
.ssl() .ssl()
.selected_alpn_protocol() .selected_alpn_protocol()
.map(|protos| protos.windows(2).any(|w| w == H2)) .map(|protos| protos.windows(2).any(|w| w == H2))
@ -273,9 +271,10 @@ where
} else { } else {
(Box::new(sock) as Box<dyn Io>, Protocol::Http1) (Box::new(sock) as Box<dyn Io>, Protocol::Http1)
} }
}), })
.map_err(ConnectError::from),
), ),
#[cfg(feature = "rust-tls")] #[cfg(feature = "rustls")]
SslConnector::Rustls(ssl) => service( SslConnector::Rustls(ssl) => service(
RustlsConnector::service(ssl) RustlsConnector::service(ssl)
.map_err(ConnectError::from) .map_err(ConnectError::from)
@ -303,7 +302,7 @@ where
let tcp_service = TimeoutService::new( let tcp_service = TimeoutService::new(
self.timeout, self.timeout,
apply_fn(self.connector.clone(), |msg: Connect, srv| { apply_fn(self.connector, |msg: Connect, srv| {
srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr))
}) })
.map_err(ConnectError::from) .map_err(ConnectError::from)
@ -334,19 +333,19 @@ where
} }
} }
#[cfg(not(any(feature = "ssl", feature = "rust-tls")))] #[cfg(not(any(feature = "openssl", feature = "rustls")))]
mod connect_impl { mod connect_impl {
use futures::future::{err, Either, FutureResult}; use std::task::{Context, Poll};
use futures::Poll;
use futures_util::future::{err, Either, Ready};
use super::*; use super::*;
use crate::client::connection::IoConnection; use crate::client::connection::IoConnection;
pub(crate) struct InnerConnector<T, Io> pub(crate) struct InnerConnector<T, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError> T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
{ {
pub(crate) tcp_pool: ConnectionPool<T, Io>, pub(crate) tcp_pool: ConnectionPool<T, Io>,
@ -354,9 +353,8 @@ mod connect_impl {
impl<T, Io> Clone for InnerConnector<T, Io> impl<T, Io> Clone for InnerConnector<T, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError> T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@ -368,9 +366,8 @@ mod connect_impl {
impl<T, Io> Service for InnerConnector<T, Io> impl<T, Io> Service for InnerConnector<T, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError> T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
{ {
type Request = Connect; type Request = Connect;
@ -378,38 +375,41 @@ mod connect_impl {
type Error = ConnectError; type Error = ConnectError;
type Future = Either< type Future = Either<
<ConnectionPool<T, Io> as Service>::Future, <ConnectionPool<T, Io> as Service>::Future,
FutureResult<IoConnection<Io>, ConnectError>, Ready<Result<IoConnection<Io>, ConnectError>>,
>; >;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.tcp_pool.poll_ready() self.tcp_pool.poll_ready(cx)
} }
fn call(&mut self, req: Connect) -> Self::Future { fn call(&mut self, req: Connect) -> Self::Future {
match req.uri.scheme_str() { match req.uri.scheme_str() {
Some("https") | Some("wss") => { Some("https") | Some("wss") => {
Either::B(err(ConnectError::SslIsNotSupported)) Either::Right(err(ConnectError::SslIsNotSupported))
} }
_ => Either::A(self.tcp_pool.call(req)), _ => Either::Left(self.tcp_pool.call(req)),
} }
} }
} }
} }
#[cfg(any(feature = "ssl", feature = "rust-tls"))] #[cfg(any(feature = "openssl", feature = "rustls"))]
mod connect_impl { mod connect_impl {
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::future::{Either, FutureResult}; use futures_core::ready;
use futures::{Async, Future, Poll}; use futures_util::future::Either;
use super::*; use super::*;
use crate::client::connection::EitherConnection; use crate::client::connection::EitherConnection;
pub(crate) struct InnerConnector<T1, T2, Io1, Io2> pub(crate) struct InnerConnector<T1, T2, Io1, Io2>
where where
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static,
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>, T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>,
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>, T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>,
{ {
@ -419,13 +419,11 @@ mod connect_impl {
impl<T1, T2, Io1, Io2> Clone for InnerConnector<T1, T2, Io1, Io2> impl<T1, T2, Io1, Io2> Clone for InnerConnector<T1, T2, Io1, Io2>
where where
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static,
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError> T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError> T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@ -438,53 +436,47 @@ mod connect_impl {
impl<T1, T2, Io1, Io2> Service for InnerConnector<T1, T2, Io1, Io2> impl<T1, T2, Io1, Io2> Service for InnerConnector<T1, T2, Io1, Io2>
where where
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static,
T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError> T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError> T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
{ {
type Request = Connect; type Request = Connect;
type Response = EitherConnection<Io1, Io2>; type Response = EitherConnection<Io1, Io2>;
type Error = ConnectError; type Error = ConnectError;
type Future = Either< type Future = Either<
FutureResult<Self::Response, Self::Error>,
Either<
InnerConnectorResponseA<T1, Io1, Io2>, InnerConnectorResponseA<T1, Io1, Io2>,
InnerConnectorResponseB<T2, Io1, Io2>, InnerConnectorResponseB<T2, Io1, Io2>,
>,
>; >;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.tcp_pool.poll_ready() self.tcp_pool.poll_ready(cx)
} }
fn call(&mut self, req: Connect) -> Self::Future { fn call(&mut self, req: Connect) -> Self::Future {
match req.uri.scheme_str() { match req.uri.scheme_str() {
Some("https") | Some("wss") => { Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB {
Either::B(Either::B(InnerConnectorResponseB {
fut: self.ssl_pool.call(req), fut: self.ssl_pool.call(req),
_t: PhantomData, _t: PhantomData,
})) }),
} _ => Either::Left(InnerConnectorResponseA {
_ => Either::B(Either::A(InnerConnectorResponseA {
fut: self.tcp_pool.call(req), fut: self.tcp_pool.call(req),
_t: PhantomData, _t: PhantomData,
})), }),
} }
} }
} }
#[pin_project::pin_project]
pub(crate) struct InnerConnectorResponseA<T, Io1, Io2> pub(crate) struct InnerConnectorResponseA<T, Io1, Io2>
where where
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError> T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
{ {
#[pin]
fut: <ConnectionPool<T, Io1> as Service>::Future, fut: <ConnectionPool<T, Io1> as Service>::Future,
_t: PhantomData<Io2>, _t: PhantomData<Io2>,
} }
@ -492,29 +484,28 @@ mod connect_impl {
impl<T, Io1, Io2> Future for InnerConnectorResponseA<T, Io1, Io2> impl<T, Io1, Io2> Future for InnerConnectorResponseA<T, Io1, Io2>
where where
T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError> T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
type Item = EitherConnection<Io1, Io2>; type Output = Result<EitherConnection<Io1, Io2>, ConnectError>;
type Error = ConnectError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.fut.poll()? { Poll::Ready(
Async::NotReady => Ok(Async::NotReady), ready!(Pin::new(&mut self.get_mut().fut).poll(cx))
Async::Ready(res) => Ok(Async::Ready(EitherConnection::A(res))), .map(EitherConnection::A),
} )
} }
} }
#[pin_project::pin_project]
pub(crate) struct InnerConnectorResponseB<T, Io1, Io2> pub(crate) struct InnerConnectorResponseB<T, Io1, Io2>
where where
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError> T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
{ {
#[pin]
fut: <ConnectionPool<T, Io2> as Service>::Future, fut: <ConnectionPool<T, Io2> as Service>::Future,
_t: PhantomData<Io1>, _t: PhantomData<Io1>,
} }
@ -522,19 +513,17 @@ mod connect_impl {
impl<T, Io1, Io2> Future for InnerConnectorResponseB<T, Io1, Io2> impl<T, Io1, Io2> Future for InnerConnectorResponseB<T, Io1, Io2>
where where
T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError> T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static,
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
type Item = EitherConnection<Io1, Io2>; type Output = Result<EitherConnection<Io1, Io2>, ConnectError>;
type Error = ConnectError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.fut.poll()? { Poll::Ready(
Async::NotReady => Ok(Async::NotReady), ready!(Pin::new(&mut self.get_mut().fut).poll(cx))
Async::Ready(res) => Ok(Async::Ready(EitherConnection::B(res))), .map(EitherConnection::B),
} )
} }
} }
} }

View File

@ -1,14 +1,13 @@
use std::io; use std::io;
use actix_connect::resolver::ResolveError;
use derive_more::{Display, From}; use derive_more::{Display, From};
use trust_dns_resolver::error::ResolveError;
#[cfg(feature = "ssl")] #[cfg(feature = "openssl")]
use openssl::ssl::{Error as SslError, HandshakeError}; use actix_connect::ssl::openssl::{HandshakeError, SslError};
use crate::error::{Error, ParseError, ResponseError}; use crate::error::{Error, ParseError, ResponseError};
use crate::http::Error as HttpError; use crate::http::{Error as HttpError, StatusCode};
use crate::response::Response;
/// A set of errors that can occur while connecting to an HTTP host /// A set of errors that can occur while connecting to an HTTP host
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
@ -18,10 +17,15 @@ pub enum ConnectError {
SslIsNotSupported, SslIsNotSupported,
/// SSL error /// SSL error
#[cfg(feature = "ssl")] #[cfg(feature = "openssl")]
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
SslError(SslError), SslError(SslError),
/// SSL Handshake error
#[cfg(feature = "openssl")]
#[display(fmt = "{}", _0)]
SslHandshakeError(String),
/// Failed to resolve the hostname /// Failed to resolve the hostname
#[display(fmt = "Failed resolving hostname: {}", _0)] #[display(fmt = "Failed resolving hostname: {}", _0)]
Resolver(ResolveError), Resolver(ResolveError),
@ -63,14 +67,10 @@ impl From<actix_connect::ConnectError> for ConnectError {
} }
} }
#[cfg(feature = "ssl")] #[cfg(feature = "openssl")]
impl<T> From<HandshakeError<T>> for ConnectError { impl<T: std::fmt::Debug> From<HandshakeError<T>> for ConnectError {
fn from(err: HandshakeError<T>) -> ConnectError { fn from(err: HandshakeError<T>) -> ConnectError {
match err { ConnectError::SslHandshakeError(format!("{:?}", err))
HandshakeError::SetupFailure(stack) => SslError::from(stack).into(),
HandshakeError::Failure(stream) => stream.into_error().into(),
HandshakeError::WouldBlock(stream) => stream.into_error().into(),
}
} }
} }
@ -117,15 +117,14 @@ pub enum SendRequestError {
/// Convert `SendRequestError` to a server `Response` /// Convert `SendRequestError` to a server `Response`
impl ResponseError for SendRequestError { impl ResponseError for SendRequestError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
match *self { match *self {
SendRequestError::Connect(ConnectError::Timeout) => { SendRequestError::Connect(ConnectError::Timeout) => {
Response::GatewayTimeout() StatusCode::GATEWAY_TIMEOUT
} }
SendRequestError::Connect(_) => Response::BadGateway(), SendRequestError::Connect(_) => StatusCode::BAD_REQUEST,
_ => Response::InternalServerError(), _ => StatusCode::INTERNAL_SERVER_ERROR,
} }
.into()
} }
} }

View File

@ -1,36 +1,42 @@
use std::io::Write; use std::io::Write;
use std::{io, time}; use std::pin::Pin;
use std::task::{Context, Poll};
use std::{io, mem, time};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::buf::BufMutExt;
use futures::future::{ok, Either}; use bytes::{Bytes, BytesMut};
use futures::{Async, Future, Poll, Sink, Stream}; use futures_core::Stream;
use futures_util::future::poll_fn;
use futures_util::{SinkExt, StreamExt};
use crate::error::PayloadError; use crate::error::PayloadError;
use crate::h1; use crate::h1;
use crate::header::HeaderMap;
use crate::http::header::{IntoHeaderValue, HOST}; use crate::http::header::{IntoHeaderValue, HOST};
use crate::message::{RequestHeadType, ResponseHead}; use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::{Payload, PayloadStream}; use crate::payload::{Payload, PayloadStream};
use crate::header::HeaderMap;
use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::connection::{ConnectionLifetime, ConnectionType, IoConnection};
use super::error::{ConnectError, SendRequestError}; use super::error::{ConnectError, SendRequestError};
use super::pool::Acquired; use super::pool::Acquired;
use crate::body::{BodySize, MessageBody}; use crate::body::{BodySize, MessageBody};
pub(crate) fn send_request<T, B>( pub(crate) async fn send_request<T, B>(
io: T, io: T,
mut head: RequestHeadType, mut head: RequestHeadType,
body: B, body: B,
created: time::Instant, created: time::Instant,
pool: Option<Acquired<T>>, pool: Option<Acquired<T>>,
) -> impl Future<Item = (ResponseHead, Payload), Error = SendRequestError> ) -> Result<(ResponseHead, Payload), SendRequestError>
where where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
B: MessageBody, B: MessageBody,
{ {
// set request host header // set request host header
if !head.as_ref().headers.contains_key(HOST) && !head.extra_headers().iter().any(|h| h.contains_key(HOST)) { if !head.as_ref().headers.contains_key(HOST)
&& !head.extra_headers().iter().any(|h| h.contains_key(HOST))
{
if let Some(host) = head.as_ref().uri.host() { if let Some(host) = head.as_ref().uri.host() {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
@ -39,21 +45,17 @@ where
Some(port) => write!(wrt, "{}:{}", host, port), Some(port) => write!(wrt, "{}:{}", host, port),
}; };
match wrt.get_mut().take().freeze().try_into() { match wrt.get_mut().split().freeze().try_into() {
Ok(value) => { Ok(value) => match head {
match head {
RequestHeadType::Owned(ref mut head) => { RequestHeadType::Owned(ref mut head) => {
head.headers.insert(HOST, value) head.headers.insert(HOST, value)
}, }
RequestHeadType::Rc(_, ref mut extra_headers) => { RequestHeadType::Rc(_, ref mut extra_headers) => {
let headers = extra_headers.get_or_insert(HeaderMap::new()); let headers = extra_headers.get_or_insert(HeaderMap::new());
headers.insert(HOST, value) headers.insert(HOST, value)
}
}, },
} Err(e) => log::error!("Can not set HOST header {}", e),
}
Err(e) => {
log::error!("Can not set HOST header {}", e)
}
} }
} }
} }
@ -64,68 +66,99 @@ where
io: Some(io), io: Some(io),
}; };
let len = body.size();
// create Framed and send request // create Framed and send request
Framed::new(io, h1::ClientCodec::default()) let mut framed = Framed::new(io, h1::ClientCodec::default());
.send((head, len).into()) framed.send((head, body.size()).into()).await?;
.from_err()
// send request body // send request body
.and_then(move |framed| match body.size() { match body.size() {
BodySize::None | BodySize::Empty | BodySize::Sized(0) => { BodySize::None | BodySize::Empty | BodySize::Sized(0) => (),
Either::A(ok(framed)) _ => send_body(body, &mut framed).await?,
} };
_ => Either::B(SendBody::new(body, framed)),
})
// read response and init read body // read response and init read body
.and_then(|framed| { let res = framed.into_future().await;
framed let (head, framed) = if let (Some(result), framed) = res {
.into_future() let item = result.map_err(SendRequestError::from)?;
.map_err(|(e, _)| SendRequestError::from(e)) (item, framed)
.and_then(|(item, framed)| { } else {
if let Some(res) = item { return Err(SendRequestError::from(ConnectError::Disconnected));
};
match framed.get_codec().message_type() { match framed.get_codec().message_type() {
h1::MessageType::None => { h1::MessageType::None => {
let force_close = !framed.get_codec().keepalive(); let force_close = !framed.get_codec().keepalive();
release_connection(framed, force_close); release_connection(framed, force_close);
Ok((res, Payload::None)) Ok((head, Payload::None))
} }
_ => { _ => {
let pl: PayloadStream = Box::new(PlStream::new(framed)); let pl: PayloadStream = PlStream::new(framed).boxed_local();
Ok((res, pl.into())) Ok((head, pl.into()))
} }
} }
} else {
Err(ConnectError::Disconnected.into())
}
})
})
} }
pub(crate) fn open_tunnel<T>( pub(crate) async fn open_tunnel<T>(
io: T, io: T,
head: RequestHeadType, head: RequestHeadType,
) -> impl Future<Item = (ResponseHead, Framed<T, h1::ClientCodec>), Error = SendRequestError> ) -> Result<(ResponseHead, Framed<T, h1::ClientCodec>), SendRequestError>
where where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
// create Framed and send request // create Framed and send request
Framed::new(io, h1::ClientCodec::default()) let mut framed = Framed::new(io, h1::ClientCodec::default());
.send((head, BodySize::None).into()) framed.send((head, BodySize::None).into()).await?;
.from_err()
// read response // read response
.and_then(|framed| { if let (Some(result), framed) = framed.into_future().await {
framed let head = result.map_err(SendRequestError::from)?;
.into_future()
.map_err(|(e, _)| SendRequestError::from(e))
.and_then(|(head, framed)| {
if let Some(head) = head {
Ok((head, framed)) Ok((head, framed))
} else { } else {
Err(SendRequestError::from(ConnectError::Disconnected)) Err(SendRequestError::from(ConnectError::Disconnected))
} }
}
/// send request body to the peer
pub(crate) async fn send_body<I, B>(
mut body: B,
framed: &mut Framed<I, h1::ClientCodec>,
) -> Result<(), SendRequestError>
where
I: ConnectionLifetime,
B: MessageBody,
{
let mut eof = false;
while !eof {
while !eof && !framed.is_write_buf_full() {
match poll_fn(|cx| body.poll_next(cx)).await {
Some(result) => {
framed.write(h1::Message::Chunk(Some(result?)))?;
}
None => {
eof = true;
framed.write(h1::Message::Chunk(None))?;
}
}
}
if !framed.is_write_buf_empty() {
poll_fn(|cx| match framed.flush(cx) {
Poll::Ready(Ok(_)) => Poll::Ready(Ok(())),
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
Poll::Pending => {
if !framed.is_write_buf_full() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
}) })
}) .await?;
}
}
SinkExt::flush(framed).await?;
Ok(())
} }
#[doc(hidden)] #[doc(hidden)]
@ -136,7 +169,10 @@ pub struct H1Connection<T> {
pool: Option<Acquired<T>>, pool: Option<Acquired<T>>,
} }
impl<T: AsyncRead + AsyncWrite + 'static> ConnectionLifetime for H1Connection<T> { impl<T> ConnectionLifetime for H1Connection<T>
where
T: AsyncRead + AsyncWrite + Unpin + 'static,
{
/// Close connection /// Close connection
fn close(&mut self) { fn close(&mut self) {
if let Some(mut pool) = self.pool.take() { if let Some(mut pool) = self.pool.take() {
@ -164,98 +200,44 @@ impl<T: AsyncRead + AsyncWrite + 'static> ConnectionLifetime for H1Connection<T>
} }
} }
impl<T: AsyncRead + AsyncWrite + 'static> io::Read for H1Connection<T> { impl<T: AsyncRead + AsyncWrite + Unpin + 'static> AsyncRead for H1Connection<T> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { unsafe fn prepare_uninitialized_buffer(
self.io.as_mut().unwrap().read(buf) &self,
buf: &mut [mem::MaybeUninit<u8>],
) -> bool {
self.io.as_ref().unwrap().prepare_uninitialized_buffer(buf)
}
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.io.as_mut().unwrap()).poll_read(cx, buf)
} }
} }
impl<T: AsyncRead + AsyncWrite + 'static> AsyncRead for H1Connection<T> {} impl<T: AsyncRead + AsyncWrite + Unpin + 'static> AsyncWrite for H1Connection<T> {
fn poll_write(
impl<T: AsyncRead + AsyncWrite + 'static> io::Write for H1Connection<T> { mut self: Pin<&mut Self>,
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { cx: &mut Context<'_>,
self.io.as_mut().unwrap().write(buf) buf: &[u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.io.as_mut().unwrap()).poll_write(cx, buf)
} }
fn flush(&mut self) -> io::Result<()> { fn poll_flush(
self.io.as_mut().unwrap().flush() mut self: Pin<&mut Self>,
} cx: &mut Context<'_>,
} ) -> Poll<io::Result<()>> {
Pin::new(self.io.as_mut().unwrap()).poll_flush(cx)
impl<T: AsyncRead + AsyncWrite + 'static> AsyncWrite for H1Connection<T> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.io.as_mut().unwrap().shutdown()
}
}
/// Future responsible for sending request body to the peer
pub(crate) struct SendBody<I, B> {
body: Option<B>,
framed: Option<Framed<I, h1::ClientCodec>>,
flushed: bool,
}
impl<I, B> SendBody<I, B>
where
I: AsyncRead + AsyncWrite + 'static,
B: MessageBody,
{
pub(crate) fn new(body: B, framed: Framed<I, h1::ClientCodec>) -> Self {
SendBody {
body: Some(body),
framed: Some(framed),
flushed: true,
}
}
}
impl<I, B> Future for SendBody<I, B>
where
I: ConnectionLifetime,
B: MessageBody,
{
type Item = Framed<I, h1::ClientCodec>;
type Error = SendRequestError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut body_ready = true;
loop {
while body_ready
&& self.body.is_some()
&& !self.framed.as_ref().unwrap().is_write_buf_full()
{
match self.body.as_mut().unwrap().poll_next()? {
Async::Ready(item) => {
// check if body is done
if item.is_none() {
let _ = self.body.take();
}
self.flushed = false;
self.framed
.as_mut()
.unwrap()
.force_send(h1::Message::Chunk(item))?;
break;
}
Async::NotReady => body_ready = false,
}
} }
if !self.flushed { fn poll_shutdown(
match self.framed.as_mut().unwrap().poll_complete()? { mut self: Pin<&mut Self>,
Async::Ready(_) => { cx: &mut Context<'_>,
self.flushed = true; ) -> Poll<Result<(), io::Error>> {
continue; Pin::new(self.io.as_mut().unwrap()).poll_shutdown(cx)
}
Async::NotReady => return Ok(Async::NotReady),
}
}
if self.body.is_none() {
return Ok(Async::Ready(self.framed.take().unwrap()));
}
return Ok(Async::NotReady);
}
} }
} }
@ -272,23 +254,27 @@ impl<Io: ConnectionLifetime> PlStream<Io> {
} }
impl<Io: ConnectionLifetime> Stream for PlStream<Io> { impl<Io: ConnectionLifetime> Stream for PlStream<Io> {
type Item = Bytes; type Item = Result<Bytes, PayloadError>;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll_next(
match self.framed.as_mut().unwrap().poll()? { self: Pin<&mut Self>,
Async::NotReady => Ok(Async::NotReady), cx: &mut Context<'_>,
Async::Ready(Some(chunk)) => { ) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
match this.framed.as_mut().unwrap().next_item(cx)? {
Poll::Pending => Poll::Pending,
Poll::Ready(Some(chunk)) => {
if let Some(chunk) = chunk { if let Some(chunk) = chunk {
Ok(Async::Ready(Some(chunk))) Poll::Ready(Some(Ok(chunk)))
} else { } else {
let framed = self.framed.take().unwrap(); let framed = this.framed.take().unwrap();
let force_close = !framed.get_codec().keepalive(); let force_close = !framed.get_codec().keepalive();
release_connection(framed, force_close); release_connection(framed, force_close);
Ok(Async::Ready(None)) Poll::Ready(None)
} }
} }
Async::Ready(None) => Ok(Async::Ready(None)), Poll::Ready(None) => Poll::Ready(None),
} }
} }
} }

View File

@ -1,31 +1,31 @@
use std::convert::TryFrom;
use std::time; use std::time;
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use bytes::Bytes; use bytes::Bytes;
use futures::future::{err, Either}; use futures_util::future::poll_fn;
use futures::{Async, Future, Poll};
use h2::{client::SendRequest, SendStream}; use h2::{client::SendRequest, SendStream};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING};
use http::{request::Request, HttpTryFrom, Method, Version}; use http::{request::Request, Method, Version};
use crate::body::{BodySize, MessageBody}; use crate::body::{BodySize, MessageBody};
use crate::header::HeaderMap;
use crate::message::{RequestHeadType, ResponseHead}; use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::Payload; use crate::payload::Payload;
use crate::header::HeaderMap;
use super::connection::{ConnectionType, IoConnection}; use super::connection::{ConnectionType, IoConnection};
use super::error::SendRequestError; use super::error::SendRequestError;
use super::pool::Acquired; use super::pool::Acquired;
pub(crate) fn send_request<T, B>( pub(crate) async fn send_request<T, B>(
io: SendRequest<Bytes>, mut io: SendRequest<Bytes>,
head: RequestHeadType, head: RequestHeadType,
body: B, body: B,
created: time::Instant, created: time::Instant,
pool: Option<Acquired<T>>, pool: Option<Acquired<T>>,
) -> impl Future<Item = (ResponseHead, Payload), Error = SendRequestError> ) -> Result<(ResponseHead, Payload), SendRequestError>
where where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
B: MessageBody, B: MessageBody,
{ {
trace!("Sending client request: {:?} {:?}", head, body.size()); trace!("Sending client request: {:?} {:?}", head, body.size());
@ -36,9 +36,6 @@ where
_ => false, _ => false,
}; };
io.ready()
.map_err(SendRequestError::from)
.and_then(move |mut io| {
let mut req = Request::new(()); let mut req = Request::new(());
*req.uri_mut() = head.as_ref().uri.clone(); *req.uri_mut() = head.as_ref().uri.clone();
*req.method_mut() = head.as_ref().method.clone(); *req.method_mut() = head.as_ref().method.clone();
@ -70,14 +67,18 @@ where
// Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate.
let (head, extra_headers) = match head { let (head, extra_headers) = match head {
RequestHeadType::Owned(head) => (RequestHeadType::Owned(head), HeaderMap::new()), RequestHeadType::Owned(head) => (RequestHeadType::Owned(head), HeaderMap::new()),
RequestHeadType::Rc(head, extra_headers) => (RequestHeadType::Rc(head, None), extra_headers.unwrap_or(HeaderMap::new())), RequestHeadType::Rc(head, extra_headers) => (
RequestHeadType::Rc(head, None),
extra_headers.unwrap_or_else(HeaderMap::new),
),
}; };
// merging headers from head and extra headers. // merging headers from head and extra headers.
let headers = head.as_ref().headers.iter() let headers = head
.filter(|(name, _)| { .as_ref()
!extra_headers.contains_key(*name) .headers
}) .iter()
.filter(|(name, _)| !extra_headers.contains_key(*name))
.chain(extra_headers.iter()); .chain(extra_headers.iter());
// copy headers // copy headers
@ -91,30 +92,27 @@ where
req.headers_mut().append(key, value.clone()); req.headers_mut().append(key, value.clone());
} }
match io.send_request(req, eof) { let res = poll_fn(|cx| io.poll_ready(cx)).await;
Ok((res, send)) => { if let Err(e) = res {
release(io, pool, created, e.is_io());
return Err(SendRequestError::from(e));
}
let resp = match io.send_request(req, eof) {
Ok((fut, send)) => {
release(io, pool, created, false); release(io, pool, created, false);
if !eof { if !eof {
Either::A(Either::B( send_body(body, send).await?;
SendBody {
body,
send,
buf: None,
}
.and_then(move |_| res.map_err(SendRequestError::from)),
))
} else {
Either::B(res.map_err(SendRequestError::from))
} }
fut.await.map_err(SendRequestError::from)?
} }
Err(e) => { Err(e) => {
release(io, pool, created, e.is_io()); release(io, pool, created, e.is_io());
Either::A(Either::A(err(e.into()))) return Err(e.into());
} }
} };
})
.and_then(move |resp| {
let (parts, body) = resp.into_parts(); let (parts, body) = resp.into_parts();
let payload = if head_req { Payload::None } else { body.into() }; let payload = if head_req { Payload::None } else { body.into() };
@ -122,66 +120,56 @@ where
head.version = parts.version; head.version = parts.version;
head.headers = parts.headers.into(); head.headers = parts.headers.into();
Ok((head, payload)) Ok((head, payload))
})
.from_err()
} }
struct SendBody<B: MessageBody> { async fn send_body<B: MessageBody>(
body: B, mut body: B,
send: SendStream<Bytes>, mut send: SendStream<Bytes>,
buf: Option<Bytes>, ) -> Result<(), SendRequestError> {
} let mut buf = None;
impl<B: MessageBody> Future for SendBody<B> {
type Item = ();
type Error = SendRequestError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop { loop {
if self.buf.is_none() { if buf.is_none() {
match self.body.poll_next() { match poll_fn(|cx| body.poll_next(cx)).await {
Ok(Async::Ready(Some(buf))) => { Some(Ok(b)) => {
self.send.reserve_capacity(buf.len()); send.reserve_capacity(b.len());
self.buf = Some(buf); buf = Some(b);
} }
Ok(Async::Ready(None)) => { Some(Err(e)) => return Err(e.into()),
if let Err(e) = self.send.send_data(Bytes::new(), true) { None => {
if let Err(e) = send.send_data(Bytes::new(), true) {
return Err(e.into()); return Err(e.into());
} }
self.send.reserve_capacity(0); send.reserve_capacity(0);
return Ok(Async::Ready(())); return Ok(());
} }
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(e) => return Err(e.into()),
} }
} }
match self.send.poll_capacity() { match poll_fn(|cx| send.poll_capacity(cx)).await {
Ok(Async::NotReady) => return Ok(Async::NotReady), None => return Ok(()),
Ok(Async::Ready(None)) => return Ok(Async::Ready(())), Some(Ok(cap)) => {
Ok(Async::Ready(Some(cap))) => { let b = buf.as_mut().unwrap();
let mut buf = self.buf.take().unwrap(); let len = b.len();
let len = buf.len(); let bytes = b.split_to(std::cmp::min(cap, len));
let bytes = buf.split_to(std::cmp::min(cap, len));
if let Err(e) = self.send.send_data(bytes, false) { if let Err(e) = send.send_data(bytes, false) {
return Err(e.into()); return Err(e.into());
} else { } else {
if !buf.is_empty() { if !b.is_empty() {
self.send.reserve_capacity(buf.len()); send.reserve_capacity(b.len());
self.buf = Some(buf); } else {
buf = None;
} }
continue; continue;
} }
} }
Err(e) => return Err(e.into()), Some(Err(e)) => return Err(e.into()),
}
} }
} }
} }
// release SendRequest object // release SendRequest object
fn release<T: AsyncRead + AsyncWrite + 'static>( fn release<T: AsyncRead + AsyncWrite + Unpin + 'static>(
io: SendRequest<Bytes>, io: SendRequest<Bytes>,
pool: Option<Acquired<T>>, pool: Option<Acquired<T>>,
created: time::Instant, created: time::Instant,

View File

@ -10,7 +10,7 @@ mod pool;
pub use self::connection::Connection; pub use self::connection::Connection;
pub use self::connector::Connector; pub use self::connector::Connector;
pub use self::error::{ConnectError, InvalidUrl, SendRequestError, FreezeRequestError}; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
pub use self::pool::Protocol; pub use self::pool::Protocol;
#[derive(Clone)] #[derive(Clone)]

View File

@ -1,22 +1,23 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::io; use std::future::Future;
use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::time::{delay_for, Delay};
use actix_service::Service; use actix_service::Service;
use actix_utils::{oneshot, task::LocalWaker};
use bytes::Bytes; use bytes::Bytes;
use futures::future::{err, ok, Either, FutureResult}; use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture};
use futures::task::AtomicTask; use fxhash::FxHashMap;
use futures::unsync::oneshot; use h2::client::{handshake, Connection, SendRequest};
use futures::{Async, Future, Poll};
use h2::client::{handshake, Handshake};
use hashbrown::HashMap;
use http::uri::Authority; use http::uri::Authority;
use indexmap::IndexSet; use indexmap::IndexSet;
use slab::Slab; use slab::Slab;
use tokio_timer::{sleep, Delay}; use pin_project::pin_project;
use super::connection::{ConnectionType, IoConnection}; use super::connection::{ConnectionType, IoConnection};
use super::error::ConnectError; use super::error::ConnectError;
@ -41,16 +42,12 @@ impl From<Authority> for Key {
} }
/// Connections pool /// Connections pool
pub(crate) struct ConnectionPool<T, Io: AsyncRead + AsyncWrite + 'static>( pub(crate) struct ConnectionPool<T, Io: 'static>(Rc<RefCell<T>>, Rc<RefCell<Inner<Io>>>);
T,
Rc<RefCell<Inner<Io>>>,
);
impl<T, Io> ConnectionPool<T, Io> impl<T, Io> ConnectionPool<T, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError> T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
{ {
pub(crate) fn new( pub(crate) fn new(
@ -61,7 +58,7 @@ where
limit: usize, limit: usize,
) -> Self { ) -> Self {
ConnectionPool( ConnectionPool(
connector, Rc::new(RefCell::new(connector)),
Rc::new(RefCell::new(Inner { Rc::new(RefCell::new(Inner {
conn_lifetime, conn_lifetime,
conn_keep_alive, conn_keep_alive,
@ -70,8 +67,8 @@ where
acquired: 0, acquired: 0,
waiters: Slab::new(), waiters: Slab::new(),
waiters_queue: IndexSet::new(), waiters_queue: IndexSet::new(),
available: HashMap::new(), available: FxHashMap::default(),
task: None, waker: LocalWaker::new(),
})), })),
) )
} }
@ -79,8 +76,7 @@ where
impl<T, Io> Clone for ConnectionPool<T, Io> impl<T, Io> Clone for ConnectionPool<T, Io>
where where
T: Clone, Io: 'static,
Io: AsyncRead + AsyncWrite + 'static,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
ConnectionPool(self.0.clone(), self.1.clone()) ConnectionPool(self.0.clone(), self.1.clone())
@ -89,86 +85,116 @@ where
impl<T, Io> Service for ConnectionPool<T, Io> impl<T, Io> Service for ConnectionPool<T, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError> T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ Clone
+ 'static, + 'static,
{ {
type Request = Connect; type Request = Connect;
type Response = IoConnection<Io>; type Response = IoConnection<Io>;
type Error = ConnectError; type Error = ConnectError;
type Future = Either< type Future = LocalBoxFuture<'static, Result<IoConnection<Io>, ConnectError>>;
FutureResult<Self::Response, Self::Error>,
Either<WaitForConnection<Io>, OpenConnection<T::Future, Io>>,
>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready() self.0.poll_ready(cx)
} }
fn call(&mut self, req: Connect) -> Self::Future { fn call(&mut self, req: Connect) -> Self::Future {
let key = if let Some(authority) = req.uri.authority_part() { // start support future
actix_rt::spawn(ConnectorPoolSupport {
connector: self.0.clone(),
inner: self.1.clone(),
});
let mut connector = self.0.clone();
let inner = self.1.clone();
let fut = async move {
let key = if let Some(authority) = req.uri.authority() {
authority.clone().into() authority.clone().into()
} else { } else {
return Either::A(err(ConnectError::Unresolverd)); return Err(ConnectError::Unresolverd);
}; };
// acquire connection // acquire connection
match self.1.as_ref().borrow_mut().acquire(&key) { match poll_fn(|cx| Poll::Ready(inner.borrow_mut().acquire(&key, cx))).await {
Acquire::Acquired(io, created) => { Acquire::Acquired(io, created) => {
// use existing connection // use existing connection
return Either::A(ok(IoConnection::new( return Ok(IoConnection::new(
io, io,
created, created,
Some(Acquired(key, Some(self.1.clone()))), Some(Acquired(key, Some(inner))),
))); ));
} }
Acquire::Available => { Acquire::Available => {
// open new connection // open tcp connection
return Either::B(Either::B(OpenConnection::new( let (io, proto) = connector.call(req).await?;
key,
self.1.clone(),
self.0.call(req),
)));
}
_ => (),
}
let guard = OpenGuard::new(key, inner);
if proto == Protocol::Http1 {
Ok(IoConnection::new(
ConnectionType::H1(io),
Instant::now(),
Some(guard.consume()),
))
} else {
let (snd, connection) = handshake(io).await?;
actix_rt::spawn(connection.map(|_| ()));
Ok(IoConnection::new(
ConnectionType::H2(snd),
Instant::now(),
Some(guard.consume()),
))
}
}
_ => {
// connection is not available, wait // connection is not available, wait
let (rx, token, support) = self.1.as_ref().borrow_mut().wait_for(req); let (rx, token) = inner.borrow_mut().wait_for(req);
// start support future let guard = WaiterGuard::new(key, token, inner);
if !support { let res = match rx.await {
self.1.as_ref().borrow_mut().task = Some(AtomicTask::new()); Err(_) => Err(ConnectError::Disconnected),
tokio_current_thread::spawn(ConnectorPoolSupport { Ok(res) => res,
connector: self.0.clone(), };
inner: self.1.clone(), guard.consume();
}) res
} }
}
};
Either::B(Either::A(WaitForConnection { fut.boxed_local()
rx,
key,
token,
inner: Some(self.1.clone()),
}))
} }
} }
#[doc(hidden)] struct WaiterGuard<Io>
pub struct WaitForConnection<Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
key: Key, key: Key,
token: usize, token: usize,
rx: oneshot::Receiver<Result<IoConnection<Io>, ConnectError>>,
inner: Option<Rc<RefCell<Inner<Io>>>>, inner: Option<Rc<RefCell<Inner<Io>>>>,
} }
impl<Io> Drop for WaitForConnection<Io> impl<Io> WaiterGuard<Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{
fn new(key: Key, token: usize, inner: Rc<RefCell<Inner<Io>>>) -> Self {
Self {
key,
token,
inner: Some(inner),
}
}
fn consume(mut self) {
let _ = self.inner.take();
}
}
impl<Io> Drop for WaiterGuard<Io>
where
Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(i) = self.inner.take() { if let Some(i) = self.inner.take() {
@ -179,113 +205,43 @@ where
} }
} }
impl<Io> Future for WaitForConnection<Io> struct OpenGuard<Io>
where where
Io: AsyncRead + AsyncWrite, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
type Item = IoConnection<Io>;
type Error = ConnectError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.rx.poll() {
Ok(Async::Ready(item)) => match item {
Err(err) => Err(err),
Ok(conn) => {
let _ = self.inner.take();
Ok(Async::Ready(conn))
}
},
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(_) => {
let _ = self.inner.take();
Err(ConnectError::Disconnected)
}
}
}
}
#[doc(hidden)]
pub struct OpenConnection<F, Io>
where
Io: AsyncRead + AsyncWrite + 'static,
{
fut: F,
key: Key, key: Key,
h2: Option<Handshake<Io, Bytes>>,
inner: Option<Rc<RefCell<Inner<Io>>>>, inner: Option<Rc<RefCell<Inner<Io>>>>,
} }
impl<F, Io> OpenConnection<F, Io> impl<Io> OpenGuard<Io>
where where
F: Future<Item = (Io, Protocol), Error = ConnectError>, Io: AsyncRead + AsyncWrite + Unpin + 'static,
Io: AsyncRead + AsyncWrite + 'static,
{ {
fn new(key: Key, inner: Rc<RefCell<Inner<Io>>>, fut: F) -> Self { fn new(key: Key, inner: Rc<RefCell<Inner<Io>>>) -> Self {
OpenConnection { Self {
key, key,
fut,
inner: Some(inner), inner: Some(inner),
h2: None,
} }
} }
fn consume(mut self) -> Acquired<Io> {
Acquired(self.key.clone(), self.inner.take())
}
} }
impl<F, Io> Drop for OpenConnection<F, Io> impl<Io> Drop for OpenGuard<Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(inner) = self.inner.take() { if let Some(i) = self.inner.take() {
let mut inner = inner.as_ref().borrow_mut(); let mut inner = i.as_ref().borrow_mut();
inner.release(); inner.release();
inner.check_availibility(); inner.check_availibility();
} }
} }
} }
impl<F, Io> Future for OpenConnection<F, Io>
where
F: Future<Item = (Io, Protocol), Error = ConnectError>,
Io: AsyncRead + AsyncWrite,
{
type Item = IoConnection<Io>;
type Error = ConnectError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut h2) = self.h2 {
return match h2.poll() {
Ok(Async::Ready((snd, connection))) => {
tokio_current_thread::spawn(connection.map_err(|_| ()));
Ok(Async::Ready(IoConnection::new(
ConnectionType::H2(snd),
Instant::now(),
Some(Acquired(self.key.clone(), self.inner.take())),
)))
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => Err(e.into()),
};
}
match self.fut.poll() {
Err(err) => Err(err),
Ok(Async::Ready((io, proto))) => {
if proto == Protocol::Http1 {
Ok(Async::Ready(IoConnection::new(
ConnectionType::H1(io),
Instant::now(),
Some(Acquired(self.key.clone(), self.inner.take())),
)))
} else {
self.h2 = Some(handshake(io));
self.poll()
}
}
Ok(Async::NotReady) => Ok(Async::NotReady),
}
}
}
enum Acquire<T> { enum Acquire<T> {
Acquired(ConnectionType<T>, Instant), Acquired(ConnectionType<T>, Instant),
Available, Available,
@ -304,7 +260,7 @@ pub(crate) struct Inner<Io> {
disconnect_timeout: Option<Duration>, disconnect_timeout: Option<Duration>,
limit: usize, limit: usize,
acquired: usize, acquired: usize,
available: HashMap<Key, VecDeque<AvailableConnection<Io>>>, available: FxHashMap<Key, VecDeque<AvailableConnection<Io>>>,
waiters: Slab< waiters: Slab<
Option<( Option<(
Connect, Connect,
@ -312,7 +268,7 @@ pub(crate) struct Inner<Io> {
)>, )>,
>, >,
waiters_queue: IndexSet<(Key, usize)>, waiters_queue: IndexSet<(Key, usize)>,
task: Option<AtomicTask>, waker: LocalWaker,
} }
impl<Io> Inner<Io> { impl<Io> Inner<Io> {
@ -326,13 +282,13 @@ impl<Io> Inner<Io> {
fn release_waiter(&mut self, key: &Key, token: usize) { fn release_waiter(&mut self, key: &Key, token: usize) {
self.waiters.remove(token); self.waiters.remove(token);
self.waiters_queue.remove(&(key.clone(), token)); let _ = self.waiters_queue.shift_remove(&(key.clone(), token));
} }
} }
impl<Io> Inner<Io> impl<Io> Inner<Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
/// connection is not available, wait /// connection is not available, wait
fn wait_for( fn wait_for(
@ -341,20 +297,19 @@ where
) -> ( ) -> (
oneshot::Receiver<Result<IoConnection<Io>, ConnectError>>, oneshot::Receiver<Result<IoConnection<Io>, ConnectError>>,
usize, usize,
bool,
) { ) {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let key: Key = connect.uri.authority_part().unwrap().clone().into(); let key: Key = connect.uri.authority().unwrap().clone().into();
let entry = self.waiters.vacant_entry(); let entry = self.waiters.vacant_entry();
let token = entry.key(); let token = entry.key();
entry.insert(Some((connect, tx))); entry.insert(Some((connect, tx)));
assert!(self.waiters_queue.insert((key, token))); assert!(self.waiters_queue.insert((key, token)));
(rx, token, self.task.is_some()) (rx, token)
} }
fn acquire(&mut self, key: &Key) -> Acquire<Io> { fn acquire(&mut self, key: &Key, cx: &mut Context<'_>) -> Acquire<Io> {
// check limits // check limits
if self.limit > 0 && self.acquired >= self.limit { if self.limit > 0 && self.acquired >= self.limit {
return Acquire::NotAvailable; return Acquire::NotAvailable;
@ -373,28 +328,26 @@ where
{ {
if let Some(timeout) = self.disconnect_timeout { if let Some(timeout) = self.disconnect_timeout {
if let ConnectionType::H1(io) = conn.io { if let ConnectionType::H1(io) = conn.io {
tokio_current_thread::spawn(CloseConnection::new( actix_rt::spawn(CloseConnection::new(io, timeout))
io, timeout,
))
} }
} }
} else { } else {
let mut io = conn.io; let mut io = conn.io;
let mut buf = [0; 2]; let mut buf = [0; 2];
if let ConnectionType::H1(ref mut s) = io { if let ConnectionType::H1(ref mut s) = io {
match s.read(&mut buf) { match Pin::new(s).poll_read(cx, &mut buf) {
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), Poll::Pending => (),
Ok(n) if n > 0 => { Poll::Ready(Ok(n)) if n > 0 => {
if let Some(timeout) = self.disconnect_timeout { if let Some(timeout) = self.disconnect_timeout {
if let ConnectionType::H1(io) = io { if let ConnectionType::H1(io) = io {
tokio_current_thread::spawn( actix_rt::spawn(CloseConnection::new(
CloseConnection::new(io, timeout), io, timeout,
) ))
} }
} }
continue; continue;
} }
Ok(_) | Err(_) => continue, _ => continue,
} }
} }
return Acquire::Acquired(io, conn.created); return Acquire::Acquired(io, conn.created);
@ -421,7 +374,7 @@ where
self.acquired -= 1; self.acquired -= 1;
if let Some(timeout) = self.disconnect_timeout { if let Some(timeout) = self.disconnect_timeout {
if let ConnectionType::H1(io) = io { if let ConnectionType::H1(io) = io {
tokio_current_thread::spawn(CloseConnection::new(io, timeout)) actix_rt::spawn(CloseConnection::new(io, timeout))
} }
} }
self.check_availibility(); self.check_availibility();
@ -429,9 +382,7 @@ where
fn check_availibility(&self) { fn check_availibility(&self) {
if !self.waiters_queue.is_empty() && self.acquired < self.limit { if !self.waiters_queue.is_empty() && self.acquired < self.limit {
if let Some(t) = self.task.as_ref() { self.waker.wake();
t.notify()
}
} }
} }
} }
@ -443,37 +394,39 @@ struct CloseConnection<T> {
impl<T> CloseConnection<T> impl<T> CloseConnection<T>
where where
T: AsyncWrite, T: AsyncWrite + Unpin,
{ {
fn new(io: T, timeout: Duration) -> Self { fn new(io: T, timeout: Duration) -> Self {
CloseConnection { CloseConnection {
io, io,
timeout: sleep(timeout), timeout: delay_for(timeout),
} }
} }
} }
impl<T> Future for CloseConnection<T> impl<T> Future for CloseConnection<T>
where where
T: AsyncWrite, T: AsyncWrite + Unpin,
{ {
type Item = (); type Output = ();
type Error = ();
fn poll(&mut self) -> Poll<(), ()> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
match self.timeout.poll() { let this = self.get_mut();
Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())),
Ok(Async::NotReady) => match self.io.shutdown() { match Pin::new(&mut this.timeout).poll(cx) {
Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())), Poll::Ready(_) => Poll::Ready(()),
Ok(Async::NotReady) => Ok(Async::NotReady), Poll::Pending => match Pin::new(&mut this.io).poll_shutdown(cx) {
Poll::Ready(_) => Poll::Ready(()),
Poll::Pending => Poll::Pending,
}, },
} }
} }
} }
#[pin_project]
struct ConnectorPoolSupport<T, Io> struct ConnectorPoolSupport<T, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
connector: T, connector: T,
inner: Rc<RefCell<Inner<Io>>>, inner: Rc<RefCell<Inner<Io>>>,
@ -481,16 +434,17 @@ where
impl<T, Io> Future for ConnectorPoolSupport<T, Io> impl<T, Io> Future for ConnectorPoolSupport<T, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>, T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>,
T::Future: 'static, T::Future: 'static,
{ {
type Item = (); type Output = ();
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut inner = self.inner.as_ref().borrow_mut(); let this = self.project();
inner.task.as_ref().unwrap().register();
let mut inner = this.inner.as_ref().borrow_mut();
inner.waker.register(cx.waker());
// check waiters // check waiters
loop { loop {
@ -505,14 +459,14 @@ where
continue; continue;
} }
match inner.acquire(&key) { match inner.acquire(&key, cx) {
Acquire::NotAvailable => break, Acquire::NotAvailable => break,
Acquire::Acquired(io, created) => { Acquire::Acquired(io, created) => {
let tx = inner.waiters.get_mut(token).unwrap().take().unwrap().1; let tx = inner.waiters.get_mut(token).unwrap().take().unwrap().1;
if let Err(conn) = tx.send(Ok(IoConnection::new( if let Err(conn) = tx.send(Ok(IoConnection::new(
io, io,
created, created,
Some(Acquired(key.clone(), Some(self.inner.clone()))), Some(Acquired(key.clone(), Some(this.inner.clone()))),
))) { ))) {
let (io, created) = conn.unwrap().into_inner(); let (io, created) = conn.unwrap().into_inner();
inner.release_conn(&key, io, created); inner.release_conn(&key, io, created);
@ -524,33 +478,40 @@ where
OpenWaitingConnection::spawn( OpenWaitingConnection::spawn(
key.clone(), key.clone(),
tx, tx,
self.inner.clone(), this.inner.clone(),
self.connector.call(connect), this.connector.call(connect),
); );
} }
} }
let _ = inner.waiters_queue.swap_remove_index(0); let _ = inner.waiters_queue.swap_remove_index(0);
} }
Ok(Async::NotReady) Poll::Pending
} }
} }
#[pin_project::pin_project(PinnedDrop)]
struct OpenWaitingConnection<F, Io> struct OpenWaitingConnection<F, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
#[pin]
fut: F, fut: F,
key: Key, key: Key,
h2: Option<Handshake<Io, Bytes>>, h2: Option<
LocalBoxFuture<
'static,
Result<(SendRequest<Bytes>, Connection<Io, Bytes>), h2::Error>,
>,
>,
rx: Option<oneshot::Sender<Result<IoConnection<Io>, ConnectError>>>, rx: Option<oneshot::Sender<Result<IoConnection<Io>, ConnectError>>>,
inner: Option<Rc<RefCell<Inner<Io>>>>, inner: Option<Rc<RefCell<Inner<Io>>>>,
} }
impl<F, Io> OpenWaitingConnection<F, Io> impl<F, Io> OpenWaitingConnection<F, Io>
where where
F: Future<Item = (Io, Protocol), Error = ConnectError> + 'static, F: Future<Output = Result<(Io, Protocol), ConnectError>> + 'static,
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
fn spawn( fn spawn(
key: Key, key: Key,
@ -558,7 +519,7 @@ where
inner: Rc<RefCell<Inner<Io>>>, inner: Rc<RefCell<Inner<Io>>>,
fut: F, fut: F,
) { ) {
tokio_current_thread::spawn(OpenWaitingConnection { actix_rt::spawn(OpenWaitingConnection {
key, key,
fut, fut,
h2: None, h2: None,
@ -568,12 +529,13 @@ where
} }
} }
impl<F, Io> Drop for OpenWaitingConnection<F, Io> #[pin_project::pinned_drop]
impl<F, Io> PinnedDrop for OpenWaitingConnection<F, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
fn drop(&mut self) { fn drop(self: Pin<&mut Self>) {
if let Some(inner) = self.inner.take() { if let Some(inner) = self.project().inner.take() {
let mut inner = inner.as_ref().borrow_mut(); let mut inner = inner.as_ref().borrow_mut();
inner.release(); inner.release();
inner.check_availibility(); inner.check_availibility();
@ -583,59 +545,60 @@ where
impl<F, Io> Future for OpenWaitingConnection<F, Io> impl<F, Io> Future for OpenWaitingConnection<F, Io>
where where
F: Future<Item = (Io, Protocol), Error = ConnectError>, F: Future<Output = Result<(Io, Protocol), ConnectError>>,
Io: AsyncRead + AsyncWrite, Io: AsyncRead + AsyncWrite + Unpin,
{ {
type Item = (); type Output = ();
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(ref mut h2) = self.h2 { let this = self.as_mut().project();
return match h2.poll() {
Ok(Async::Ready((snd, connection))) => { if let Some(ref mut h2) = this.h2 {
tokio_current_thread::spawn(connection.map_err(|_| ())); return match Pin::new(h2).poll(cx) {
let rx = self.rx.take().unwrap(); Poll::Ready(Ok((snd, connection))) => {
actix_rt::spawn(connection.map(|_| ()));
let rx = this.rx.take().unwrap();
let _ = rx.send(Ok(IoConnection::new( let _ = rx.send(Ok(IoConnection::new(
ConnectionType::H2(snd), ConnectionType::H2(snd),
Instant::now(), Instant::now(),
Some(Acquired(self.key.clone(), self.inner.take())), Some(Acquired(this.key.clone(), this.inner.take())),
))); )));
Ok(Async::Ready(())) Poll::Ready(())
} }
Ok(Async::NotReady) => Ok(Async::NotReady), Poll::Pending => Poll::Pending,
Err(err) => { Poll::Ready(Err(err)) => {
let _ = self.inner.take(); let _ = this.inner.take();
if let Some(rx) = self.rx.take() { if let Some(rx) = this.rx.take() {
let _ = rx.send(Err(ConnectError::H2(err))); let _ = rx.send(Err(ConnectError::H2(err)));
} }
Err(()) Poll::Ready(())
} }
}; };
} }
match self.fut.poll() { match this.fut.poll(cx) {
Err(err) => { Poll::Ready(Err(err)) => {
let _ = self.inner.take(); let _ = this.inner.take();
if let Some(rx) = self.rx.take() { if let Some(rx) = this.rx.take() {
let _ = rx.send(Err(err)); let _ = rx.send(Err(err));
} }
Err(()) Poll::Ready(())
} }
Ok(Async::Ready((io, proto))) => { Poll::Ready(Ok((io, proto))) => {
if proto == Protocol::Http1 { if proto == Protocol::Http1 {
let rx = self.rx.take().unwrap(); let rx = this.rx.take().unwrap();
let _ = rx.send(Ok(IoConnection::new( let _ = rx.send(Ok(IoConnection::new(
ConnectionType::H1(io), ConnectionType::H1(io),
Instant::now(), Instant::now(),
Some(Acquired(self.key.clone(), self.inner.take())), Some(Acquired(this.key.clone(), this.inner.take())),
))); )));
Ok(Async::Ready(())) Poll::Ready(())
} else { } else {
self.h2 = Some(handshake(io)); *this.h2 = Some(handshake(io).boxed_local());
self.poll() self.poll(cx)
} }
} }
Ok(Async::NotReady) => Ok(Async::NotReady), Poll::Pending => Poll::Pending,
} }
} }
} }
@ -644,7 +607,7 @@ pub(crate) struct Acquired<T>(Key, Option<Rc<RefCell<Inner<T>>>>);
impl<T> Acquired<T> impl<T> Acquired<T>
where where
T: AsyncRead + AsyncWrite + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
{ {
pub(crate) fn close(&mut self, conn: IoConnection<T>) { pub(crate) fn close(&mut self, conn: IoConnection<T>) {
if let Some(inner) = self.1.take() { if let Some(inner) = self.1.take() {

View File

@ -1,42 +1,40 @@
use std::cell::UnsafeCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll};
use actix_service::Service; use actix_service::Service;
use futures::Poll;
#[doc(hidden)] #[doc(hidden)]
/// Service that allows to turn non-clone service to a service with `Clone` impl /// Service that allows to turn non-clone service to a service with `Clone` impl
pub(crate) struct CloneableService<T>(Rc<UnsafeCell<T>>); ///
/// # Panics
/// CloneableService might panic with some creative use of thread local storage.
/// See https://github.com/actix/actix-web/issues/1295 for example
pub(crate) struct CloneableService<T: Service>(Rc<RefCell<T>>);
impl<T> CloneableService<T> { impl<T: Service> CloneableService<T> {
pub(crate) fn new(service: T) -> Self pub(crate) fn new(service: T) -> Self {
where Self(Rc::new(RefCell::new(service)))
T: Service,
{
Self(Rc::new(UnsafeCell::new(service)))
} }
} }
impl<T> Clone for CloneableService<T> { impl<T: Service> Clone for CloneableService<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(self.0.clone()) Self(self.0.clone())
} }
} }
impl<T> Service for CloneableService<T> impl<T: Service> Service for CloneableService<T> {
where
T: Service,
{
type Request = T::Request; type Request = T::Request;
type Response = T::Response; type Response = T::Response;
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
unsafe { &mut *self.0.as_ref().get() }.poll_ready() self.0.borrow_mut().poll_ready(cx)
} }
fn call(&mut self, req: T::Request) -> Self::Future { fn call(&mut self, req: T::Request) -> Self::Future {
unsafe { &mut *self.0.as_ref().get() }.call(req) self.0.borrow_mut().call(req)
} }
} }

View File

@ -1,13 +1,13 @@
use std::cell::UnsafeCell; use std::cell::Cell;
use std::fmt;
use std::fmt::Write; use std::fmt::Write;
use std::rc::Rc; use std::rc::Rc;
use std::time::{Duration, Instant}; use std::time::Duration;
use std::{fmt, net};
use actix_rt::time::{delay_for, delay_until, Delay, Instant};
use bytes::BytesMut; use bytes::BytesMut;
use futures::{future, Future}; use futures_util::{future, FutureExt};
use time; use time::OffsetDateTime;
use tokio_timer::{sleep, Delay};
// "Sun, 06 Nov 1994 08:49:37 GMT".len() // "Sun, 06 Nov 1994 08:49:37 GMT".len()
const DATE_VALUE_LENGTH: usize = 29; const DATE_VALUE_LENGTH: usize = 29;
@ -47,6 +47,8 @@ struct Inner {
client_timeout: u64, client_timeout: u64,
client_disconnect: u64, client_disconnect: u64,
ka_enabled: bool, ka_enabled: bool,
secure: bool,
local_addr: Option<std::net::SocketAddr>,
timer: DateService, timer: DateService,
} }
@ -58,7 +60,7 @@ impl Clone for ServiceConfig {
impl Default for ServiceConfig { impl Default for ServiceConfig {
fn default() -> Self { fn default() -> Self {
Self::new(KeepAlive::Timeout(5), 0, 0) Self::new(KeepAlive::Timeout(5), 0, 0, false, None)
} }
} }
@ -68,6 +70,8 @@ impl ServiceConfig {
keep_alive: KeepAlive, keep_alive: KeepAlive,
client_timeout: u64, client_timeout: u64,
client_disconnect: u64, client_disconnect: u64,
secure: bool,
local_addr: Option<net::SocketAddr>,
) -> ServiceConfig { ) -> ServiceConfig {
let (keep_alive, ka_enabled) = match keep_alive { let (keep_alive, ka_enabled) = match keep_alive {
KeepAlive::Timeout(val) => (val as u64, true), KeepAlive::Timeout(val) => (val as u64, true),
@ -85,10 +89,24 @@ impl ServiceConfig {
ka_enabled, ka_enabled,
client_timeout, client_timeout,
client_disconnect, client_disconnect,
secure,
local_addr,
timer: DateService::new(), timer: DateService::new(),
})) }))
} }
#[inline]
/// Returns true if connection is secure(https)
pub fn secure(&self) -> bool {
self.0.secure
}
#[inline]
/// Returns the local address that this server is bound to.
pub fn local_addr(&self) -> Option<net::SocketAddr> {
self.0.local_addr
}
#[inline] #[inline]
/// Keep alive duration if configured. /// Keep alive duration if configured.
pub fn keep_alive(&self) -> Option<Duration> { pub fn keep_alive(&self) -> Option<Duration> {
@ -104,10 +122,10 @@ impl ServiceConfig {
#[inline] #[inline]
/// Client timeout for first request. /// Client timeout for first request.
pub fn client_timer(&self) -> Option<Delay> { pub fn client_timer(&self) -> Option<Delay> {
let delay = self.0.client_timeout; let delay_time = self.0.client_timeout;
if delay != 0 { if delay_time != 0 {
Some(Delay::new( Some(delay_until(
self.0.timer.now() + Duration::from_millis(delay), self.0.timer.now() + Duration::from_millis(delay_time),
)) ))
} else { } else {
None None
@ -138,7 +156,7 @@ impl ServiceConfig {
/// Return keep-alive timer delay is configured. /// Return keep-alive timer delay is configured.
pub fn keep_alive_timer(&self) -> Option<Delay> { pub fn keep_alive_timer(&self) -> Option<Delay> {
if let Some(ka) = self.0.keep_alive { if let Some(ka) = self.0.keep_alive {
Some(Delay::new(self.0.timer.now() + ka)) Some(delay_until(self.0.timer.now() + ka))
} else { } else {
None None
} }
@ -193,7 +211,7 @@ impl Date {
} }
fn update(&mut self) { fn update(&mut self) {
self.pos = 0; self.pos = 0;
write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap(); write!(self, "{}", OffsetDateTime::now().format("%a, %d %b %Y %H:%M:%S GMT")).unwrap();
} }
} }
@ -210,24 +228,24 @@ impl fmt::Write for Date {
struct DateService(Rc<DateServiceInner>); struct DateService(Rc<DateServiceInner>);
struct DateServiceInner { struct DateServiceInner {
current: UnsafeCell<Option<(Date, Instant)>>, current: Cell<Option<(Date, Instant)>>,
} }
impl DateServiceInner { impl DateServiceInner {
fn new() -> Self { fn new() -> Self {
DateServiceInner { DateServiceInner {
current: UnsafeCell::new(None), current: Cell::new(None),
} }
} }
fn reset(&self) { fn reset(&self) {
unsafe { (&mut *self.current.get()).take() }; self.current.take();
} }
fn update(&self) { fn update(&self) {
let now = Instant::now(); let now = Instant::now();
let date = Date::new(); let date = Date::new();
*(unsafe { &mut *self.current.get() }) = Some((date, now)); self.current.set(Some((date, now)));
} }
} }
@ -237,54 +255,58 @@ impl DateService {
} }
fn check_date(&self) { fn check_date(&self) {
if unsafe { (&*self.0.current.get()).is_none() } { if self.0.current.get().is_none() {
self.0.update(); self.0.update();
// periodic date update // periodic date update
let s = self.clone(); let s = self.clone();
tokio_current_thread::spawn(sleep(Duration::from_millis(500)).then( actix_rt::spawn(delay_for(Duration::from_millis(500)).then(move |_| {
move |_| {
s.0.reset(); s.0.reset();
future::ok(()) future::ready(())
}, }));
));
} }
} }
fn now(&self) -> Instant { fn now(&self) -> Instant {
self.check_date(); self.check_date();
unsafe { (&*self.0.current.get()).as_ref().unwrap().1 } self.0.current.get().unwrap().1
} }
fn set_date<F: FnMut(&Date)>(&self, mut f: F) { fn set_date<F: FnMut(&Date)>(&self, mut f: F) {
self.check_date(); self.check_date();
f(&unsafe { (&*self.0.current.get()).as_ref().unwrap().0 }) f(&self.0.current.get().unwrap().0);
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use actix_rt::System;
use futures::future;
// Test modifying the date from within the closure
// passed to `set_date`
#[test]
fn test_evil_date() {
let service = DateService::new();
// Make sure that `check_date` doesn't try to spawn a task
service.0.update();
service.set_date(|_| {
service.0.reset()
});
}
#[test] #[test]
fn test_date_len() { 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] #[actix_rt::test]
fn test_date() { async fn test_date() {
let mut rt = System::new("test"); let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None);
let _ = rt.block_on(future::lazy(|| {
let settings = ServiceConfig::new(KeepAlive::Os, 0, 0);
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf1); settings.set_date(&mut buf1);
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
settings.set_date(&mut buf2); settings.set_date(&mut buf2);
assert_eq!(buf1, buf2); assert_eq!(buf1, buf2);
future::ok::<_, ()>(())
}));
} }
} }

View File

@ -1,7 +1,6 @@
use std::borrow::Cow; use std::borrow::Cow;
use chrono::Duration; use time::{Duration, OffsetDateTime};
use time::Tm;
use super::{Cookie, SameSite}; use super::{Cookie, SameSite};
@ -18,7 +17,6 @@ use super::{Cookie, SameSite};
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// ///
/// # fn main() {
/// let cookie: Cookie = Cookie::build("name", "value") /// let cookie: Cookie = Cookie::build("name", "value")
/// .domain("www.rust-lang.org") /// .domain("www.rust-lang.org")
/// .path("/") /// .path("/")
@ -26,7 +24,6 @@ use super::{Cookie, SameSite};
/// .http_only(true) /// .http_only(true)
/// .max_age(84600) /// .max_age(84600)
/// .finish(); /// .finish();
/// # }
/// ``` /// ```
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CookieBuilder { pub struct CookieBuilder {
@ -65,16 +62,14 @@ impl CookieBuilder {
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// ///
/// # fn main() {
/// let c = Cookie::build("foo", "bar") /// let c = Cookie::build("foo", "bar")
/// .expires(time::now()) /// .expires(time::OffsetDateTime::now())
/// .finish(); /// .finish();
/// ///
/// assert!(c.expires().is_some()); /// assert!(c.expires().is_some());
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn expires(mut self, when: Tm) -> CookieBuilder { pub fn expires(mut self, when: OffsetDateTime) -> CookieBuilder {
self.cookie.set_expires(when); self.cookie.set_expires(when);
self self
} }
@ -86,13 +81,11 @@ impl CookieBuilder {
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// ///
/// # fn main() {
/// let c = Cookie::build("foo", "bar") /// let c = Cookie::build("foo", "bar")
/// .max_age(1800) /// .max_age(1800)
/// .finish(); /// .finish();
/// ///
/// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60)));
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn max_age(self, seconds: i64) -> CookieBuilder { pub fn max_age(self, seconds: i64) -> CookieBuilder {
@ -106,17 +99,17 @@ impl CookieBuilder {
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// ///
/// # fn main() {
/// let c = Cookie::build("foo", "bar") /// let c = Cookie::build("foo", "bar")
/// .max_age_time(time::Duration::minutes(30)) /// .max_age_time(time::Duration::minutes(30))
/// .finish(); /// .finish();
/// ///
/// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60)));
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn max_age_time(mut self, value: Duration) -> CookieBuilder { pub fn max_age_time(mut self, value: Duration) -> CookieBuilder {
self.cookie.set_max_age(value); // Truncate any nanoseconds from the Duration, as they aren't represented within `Max-Age`
// and would cause two otherwise identical `Cookie` instances to not be equivalent to one another.
self.cookie.set_max_age(Duration::seconds(value.whole_seconds()));
self self
} }
@ -220,16 +213,14 @@ impl CookieBuilder {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use chrono::Duration; /// use time::Duration;
/// ///
/// # fn main() {
/// let c = Cookie::build("foo", "bar") /// let c = Cookie::build("foo", "bar")
/// .permanent() /// .permanent()
/// .finish(); /// .finish();
/// ///
/// assert_eq!(c.max_age(), Some(Duration::days(365 * 20))); /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
/// # assert!(c.expires().is_some()); /// # assert!(c.expires().is_some());
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn permanent(mut self) -> CookieBuilder { pub fn permanent(mut self) -> CookieBuilder {

View File

@ -10,18 +10,26 @@ use std::fmt;
/// attribute is "Strict", then the cookie is never sent in cross-site requests. /// attribute is "Strict", then the cookie is never sent in cross-site requests.
/// If the `SameSite` attribute is "Lax", the cookie is only sent in cross-site /// If the `SameSite` attribute is "Lax", the cookie is only sent in cross-site
/// requests with "safe" HTTP methods, i.e, `GET`, `HEAD`, `OPTIONS`, `TRACE`. /// requests with "safe" HTTP methods, i.e, `GET`, `HEAD`, `OPTIONS`, `TRACE`.
/// If the `SameSite` attribute is not present (made explicit via the /// If the `SameSite` attribute is not present then the cookie will be sent as
/// `SameSite::None` variant), then the cookie will be sent as normal. /// normal. In some browsers, this will implicitly handle the cookie as if "Lax"
/// and in others, "None". It's best to explicitly set the `SameSite` attribute
/// to avoid inconsistent behavior.
///
/// **Note:** Depending on browser, the `Secure` attribute may be required for
/// `SameSite` "None" cookies to be accepted.
/// ///
/// **Note:** This cookie attribute is an HTTP draft! Its meaning and definition /// **Note:** This cookie attribute is an HTTP draft! Its meaning and definition
/// are subject to change. /// are subject to change.
///
/// More info about these draft changes can be found in the draft spec:
/// - https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SameSite { pub enum SameSite {
/// The "Strict" `SameSite` attribute. /// The "Strict" `SameSite` attribute.
Strict, Strict,
/// The "Lax" `SameSite` attribute. /// The "Lax" `SameSite` attribute.
Lax, Lax,
/// No `SameSite` attribute. /// The "None" `SameSite` attribute.
None, None,
} }
@ -88,11 +96,11 @@ impl SameSite {
} }
impl fmt::Display for SameSite { impl fmt::Display for SameSite {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
SameSite::Strict => write!(f, "Strict"), SameSite::Strict => write!(f, "Strict"),
SameSite::Lax => write!(f, "Lax"), SameSite::Lax => write!(f, "Lax"),
SameSite::None => Ok(()), SameSite::None => write!(f, "None"),
} }
} }
} }

View File

@ -1,7 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::mem::replace; use std::mem::replace;
use chrono::Duration; use time::{Duration, OffsetDateTime};
use super::delta::DeltaCookie; use super::delta::DeltaCookie;
use super::Cookie; use super::Cookie;
@ -188,9 +188,8 @@ impl CookieJar {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::{CookieJar, Cookie}; /// use actix_http::cookie::{CookieJar, Cookie};
/// use chrono::Duration; /// use time::Duration;
/// ///
/// # fn main() {
/// let mut jar = CookieJar::new(); /// let mut jar = CookieJar::new();
/// ///
/// // Assume this cookie originally had a path of "/" and domain of "a.b". /// // Assume this cookie originally had a path of "/" and domain of "a.b".
@ -203,8 +202,7 @@ impl CookieJar {
/// let delta: Vec<_> = jar.delta().collect(); /// let delta: Vec<_> = jar.delta().collect();
/// assert_eq!(delta.len(), 1); /// assert_eq!(delta.len(), 1);
/// assert_eq!(delta[0].name(), "name"); /// assert_eq!(delta[0].name(), "name");
/// assert_eq!(delta[0].max_age(), Some(Duration::seconds(0))); /// assert_eq!(delta[0].max_age(), Some(Duration::zero()));
/// # }
/// ``` /// ```
/// ///
/// Removing a new cookie does not result in a _removal_ cookie: /// Removing a new cookie does not result in a _removal_ cookie:
@ -222,8 +220,8 @@ impl CookieJar {
pub fn remove(&mut self, mut cookie: Cookie<'static>) { pub fn remove(&mut self, mut cookie: Cookie<'static>) {
if self.original_cookies.contains(cookie.name()) { if self.original_cookies.contains(cookie.name()) {
cookie.set_value(""); cookie.set_value("");
cookie.set_max_age(Duration::seconds(0)); cookie.set_max_age(Duration::zero());
cookie.set_expires(time::now() - Duration::days(365)); cookie.set_expires(OffsetDateTime::now() - Duration::days(365));
self.delta_cookies.replace(DeltaCookie::removed(cookie)); self.delta_cookies.replace(DeltaCookie::removed(cookie));
} else { } else {
self.delta_cookies.remove(cookie.name()); self.delta_cookies.remove(cookie.name());
@ -241,9 +239,8 @@ impl CookieJar {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::{CookieJar, Cookie}; /// use actix_http::cookie::{CookieJar, Cookie};
/// use chrono::Duration; /// use time::Duration;
/// ///
/// # fn main() {
/// let mut jar = CookieJar::new(); /// let mut jar = CookieJar::new();
/// ///
/// // Add an original cookie and a new cookie. /// // Add an original cookie and a new cookie.
@ -261,7 +258,6 @@ impl CookieJar {
/// jar.force_remove(Cookie::new("key", "value")); /// jar.force_remove(Cookie::new("key", "value"));
/// assert_eq!(jar.delta().count(), 0); /// assert_eq!(jar.delta().count(), 0);
/// assert_eq!(jar.iter().count(), 0); /// assert_eq!(jar.iter().count(), 0);
/// # }
/// ``` /// ```
pub fn force_remove<'a>(&mut self, cookie: Cookie<'a>) { pub fn force_remove<'a>(&mut self, cookie: Cookie<'a>) {
self.original_cookies.remove(cookie.name()); self.original_cookies.remove(cookie.name());
@ -307,7 +303,7 @@ impl CookieJar {
/// // Delta contains two new cookies ("new", "yac") and a removal ("name"). /// // Delta contains two new cookies ("new", "yac") and a removal ("name").
/// assert_eq!(jar.delta().count(), 3); /// assert_eq!(jar.delta().count(), 3);
/// ``` /// ```
pub fn delta(&self) -> Delta { pub fn delta(&self) -> Delta<'_> {
Delta { Delta {
iter: self.delta_cookies.iter(), iter: self.delta_cookies.iter(),
} }
@ -343,7 +339,7 @@ impl CookieJar {
/// } /// }
/// } /// }
/// ``` /// ```
pub fn iter(&self) -> Iter { pub fn iter(&self) -> Iter<'_> {
Iter { Iter {
delta_cookies: self delta_cookies: self
.delta_cookies .delta_cookies
@ -386,7 +382,7 @@ impl CookieJar {
/// assert!(jar.get("private").is_some()); /// assert!(jar.get("private").is_some());
/// ``` /// ```
#[cfg(feature = "secure-cookies")] #[cfg(feature = "secure-cookies")]
pub fn private(&mut self, key: &Key) -> PrivateJar { pub fn private(&mut self, key: &Key) -> PrivateJar<'_> {
PrivateJar::new(self, key) PrivateJar::new(self, key)
} }
@ -424,7 +420,7 @@ impl CookieJar {
/// assert!(jar.get("signed").is_some()); /// assert!(jar.get("signed").is_some());
/// ``` /// ```
#[cfg(feature = "secure-cookies")] #[cfg(feature = "secure-cookies")]
pub fn signed(&mut self, key: &Key) -> SignedJar { pub fn signed(&mut self, key: &Key) -> SignedJar<'_> {
SignedJar::new(self, key) SignedJar::new(self, key)
} }
} }
@ -537,7 +533,7 @@ mod test {
#[test] #[test]
#[cfg(feature = "secure-cookies")] #[cfg(feature = "secure-cookies")]
fn delta() { fn delta() {
use chrono::Duration; use time::Duration;
use std::collections::HashMap; use std::collections::HashMap;
let mut c = CookieJar::new(); let mut c = CookieJar::new();
@ -560,7 +556,7 @@ mod test {
assert!(names.get("test2").unwrap().is_none()); assert!(names.get("test2").unwrap().is_none());
assert!(names.get("test3").unwrap().is_none()); assert!(names.get("test3").unwrap().is_none());
assert!(names.get("test4").unwrap().is_none()); assert!(names.get("test4").unwrap().is_none());
assert_eq!(names.get("original").unwrap(), &Some(Duration::seconds(0))); assert_eq!(names.get("original").unwrap(), &Some(Duration::zero()));
} }
#[test] #[test]

View File

@ -65,9 +65,8 @@ use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use chrono::Duration;
use percent_encoding::{percent_encode, AsciiSet, CONTROLS}; use percent_encoding::{percent_encode, AsciiSet, CONTROLS};
use time::Tm; use time::{Duration, OffsetDateTime};
pub use self::builder::CookieBuilder; pub use self::builder::CookieBuilder;
pub use self::draft::*; pub use self::draft::*;
@ -110,7 +109,7 @@ impl CookieStr {
/// # Panics /// # Panics
/// ///
/// Panics if `self` is an indexed string and `string` is None. /// Panics if `self` is an indexed string and `string` is None.
fn to_str<'s>(&'s self, string: Option<&'s Cow<str>>) -> &'s str { fn to_str<'s>(&'s self, string: Option<&'s Cow<'_, str>>) -> &'s str {
match *self { match *self {
CookieStr::Indexed(i, j) => { CookieStr::Indexed(i, j) => {
let s = string.expect( let s = string.expect(
@ -172,7 +171,7 @@ pub struct Cookie<'c> {
/// The cookie's value. /// The cookie's value.
value: CookieStr, value: CookieStr,
/// The cookie's expiration, if any. /// The cookie's expiration, if any.
expires: Option<Tm>, expires: Option<OffsetDateTime>,
/// The cookie's maximum age, if any. /// The cookie's maximum age, if any.
max_age: Option<Duration>, max_age: Option<Duration>,
/// The cookie's domain, if any. /// The cookie's domain, if any.
@ -479,7 +478,7 @@ impl<'c> Cookie<'c> {
/// assert_eq!(c.max_age(), None); /// assert_eq!(c.max_age(), None);
/// ///
/// let c = Cookie::parse("name=value; Max-Age=3600").unwrap(); /// let c = Cookie::parse("name=value; Max-Age=3600").unwrap();
/// assert_eq!(c.max_age().map(|age| age.num_hours()), Some(1)); /// assert_eq!(c.max_age().map(|age| age.whole_hours()), Some(1));
/// ``` /// ```
#[inline] #[inline]
pub fn max_age(&self) -> Option<Duration> { pub fn max_age(&self) -> Option<Duration> {
@ -544,10 +543,10 @@ impl<'c> Cookie<'c> {
/// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT"; /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
/// let cookie_str = format!("name=value; Expires={}", expire_time); /// let cookie_str = format!("name=value; Expires={}", expire_time);
/// let c = Cookie::parse(cookie_str).unwrap(); /// let c = Cookie::parse(cookie_str).unwrap();
/// assert_eq!(c.expires().map(|t| t.tm_year), Some(117)); /// assert_eq!(c.expires().map(|t| t.year()), Some(2017));
/// ``` /// ```
#[inline] #[inline]
pub fn expires(&self) -> Option<Tm> { pub fn expires(&self) -> Option<OffsetDateTime> {
self.expires self.expires
} }
@ -645,15 +644,13 @@ impl<'c> Cookie<'c> {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use chrono::Duration; /// use time::Duration;
/// ///
/// # fn main() {
/// let mut c = Cookie::new("name", "value"); /// let mut c = Cookie::new("name", "value");
/// assert_eq!(c.max_age(), None); /// assert_eq!(c.max_age(), None);
/// ///
/// c.set_max_age(Duration::hours(10)); /// c.set_max_age(Duration::hours(10));
/// assert_eq!(c.max_age(), Some(Duration::hours(10))); /// assert_eq!(c.max_age(), Some(Duration::hours(10)));
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn set_max_age(&mut self, value: Duration) { pub fn set_max_age(&mut self, value: Duration) {
@ -700,20 +697,19 @@ impl<'c> Cookie<'c> {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use time::{Duration, OffsetDateTime};
/// ///
/// # fn main() {
/// let mut c = Cookie::new("name", "value"); /// let mut c = Cookie::new("name", "value");
/// assert_eq!(c.expires(), None); /// assert_eq!(c.expires(), None);
/// ///
/// let mut now = time::now(); /// let mut now = OffsetDateTime::now();
/// now.tm_year += 1; /// now += Duration::week();
/// ///
/// c.set_expires(now); /// c.set_expires(now);
/// assert!(c.expires().is_some()) /// assert!(c.expires().is_some())
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn set_expires(&mut self, time: Tm) { pub fn set_expires(&mut self, time: OffsetDateTime) {
self.expires = Some(time); self.expires = Some(time);
} }
@ -724,9 +720,8 @@ impl<'c> Cookie<'c> {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use chrono::Duration; /// use time::Duration;
/// ///
/// # fn main() {
/// let mut c = Cookie::new("foo", "bar"); /// let mut c = Cookie::new("foo", "bar");
/// assert!(c.expires().is_none()); /// assert!(c.expires().is_none());
/// assert!(c.max_age().is_none()); /// assert!(c.max_age().is_none());
@ -734,15 +729,14 @@ impl<'c> Cookie<'c> {
/// c.make_permanent(); /// c.make_permanent();
/// assert!(c.expires().is_some()); /// assert!(c.expires().is_some());
/// assert_eq!(c.max_age(), Some(Duration::days(365 * 20))); /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
/// # }
/// ``` /// ```
pub fn make_permanent(&mut self) { pub fn make_permanent(&mut self) {
let twenty_years = Duration::days(365 * 20); let twenty_years = Duration::days(365 * 20);
self.set_max_age(twenty_years); self.set_max_age(twenty_years);
self.set_expires(time::now() + twenty_years); self.set_expires(OffsetDateTime::now() + twenty_years);
} }
fn fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt_parameters(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(true) = self.http_only() { if let Some(true) = self.http_only() {
write!(f, "; HttpOnly")?; write!(f, "; HttpOnly")?;
} }
@ -752,10 +746,8 @@ impl<'c> Cookie<'c> {
} }
if let Some(same_site) = self.same_site() { if let Some(same_site) = self.same_site() {
if !same_site.is_none() {
write!(f, "; SameSite={}", same_site)?; write!(f, "; SameSite={}", same_site)?;
} }
}
if let Some(path) = self.path() { if let Some(path) = self.path() {
write!(f, "; Path={}", path)?; write!(f, "; Path={}", path)?;
@ -766,11 +758,11 @@ impl<'c> Cookie<'c> {
} }
if let Some(max_age) = self.max_age() { if let Some(max_age) = self.max_age() {
write!(f, "; Max-Age={}", max_age.num_seconds())?; write!(f, "; Max-Age={}", max_age.whole_seconds())?;
} }
if let Some(time) = self.expires() { if let Some(time) = self.expires() {
write!(f, "; Expires={}", time.rfc822())?; write!(f, "; Expires={}", time.format("%a, %d %b %Y %H:%M:%S GMT"))?;
} }
Ok(()) Ok(())
@ -924,10 +916,10 @@ impl<'c> Cookie<'c> {
/// let mut c = Cookie::new("my name", "this; value?"); /// let mut c = Cookie::new("my name", "this; value?");
/// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F"); /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F");
/// ``` /// ```
pub struct EncodedCookie<'a, 'c: 'a>(&'a Cookie<'c>); pub struct EncodedCookie<'a, 'c>(&'a Cookie<'c>);
impl<'a, 'c: 'a> fmt::Display for EncodedCookie<'a, 'c> { impl<'a, 'c: 'a> fmt::Display for EncodedCookie<'a, 'c> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Percent-encode the name and value. // Percent-encode the name and value.
let name = percent_encode(self.0.name().as_bytes(), USERINFO); let name = percent_encode(self.0.name().as_bytes(), USERINFO);
let value = percent_encode(self.0.value().as_bytes(), USERINFO); let value = percent_encode(self.0.value().as_bytes(), USERINFO);
@ -952,7 +944,7 @@ impl<'c> fmt::Display for Cookie<'c> {
/// ///
/// assert_eq!(&cookie.to_string(), "foo=bar; Path=/"); /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
/// ``` /// ```
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}={}", self.name(), self.value())?; write!(f, "{}={}", self.name(), self.value())?;
self.fmt_parameters(f) self.fmt_parameters(f)
} }
@ -998,7 +990,7 @@ impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Cookie, SameSite}; use super::{Cookie, SameSite};
use time::strptime; use time::PrimitiveDateTime;
#[test] #[test]
fn format() { fn format() {
@ -1023,7 +1015,7 @@ mod tests {
assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org"); assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org");
let time_str = "Wed, 21 Oct 2015 07:28:00 GMT"; let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
let expires = strptime(time_str, "%a, %d %b %Y %H:%M:%S %Z").unwrap(); let expires = PrimitiveDateTime::parse(time_str, "%a, %d %b %Y %H:%M:%S").unwrap().assume_utc();
let cookie = Cookie::build("foo", "bar").expires(expires).finish(); let cookie = Cookie::build("foo", "bar").expires(expires).finish();
assert_eq!( assert_eq!(
&cookie.to_string(), &cookie.to_string(),
@ -1043,7 +1035,7 @@ mod tests {
let cookie = Cookie::build("foo", "bar") let cookie = Cookie::build("foo", "bar")
.same_site(SameSite::None) .same_site(SameSite::None)
.finish(); .finish();
assert_eq!(&cookie.to_string(), "foo=bar"); assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None");
} }
#[test] #[test]

View File

@ -5,11 +5,13 @@ use std::error::Error;
use std::fmt; use std::fmt;
use std::str::Utf8Error; use std::str::Utf8Error;
use chrono::Duration;
use percent_encoding::percent_decode; use percent_encoding::percent_decode;
use time::Duration;
use super::{Cookie, CookieStr, SameSite}; use super::{Cookie, CookieStr, SameSite};
use crate::time_parser;
/// Enum corresponding to a parsing error. /// Enum corresponding to a parsing error.
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ParseError { pub enum ParseError {
@ -40,7 +42,7 @@ impl ParseError {
} }
impl fmt::Display for ParseError { impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str()) write!(f, "{}", self.as_str())
} }
} }
@ -51,11 +53,7 @@ impl From<Utf8Error> for ParseError {
} }
} }
impl Error for ParseError { impl Error for ParseError {}
fn description(&self) -> &str {
self.as_str()
}
}
fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> { fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> {
let haystack_start = haystack.as_ptr() as usize; let haystack_start = haystack.as_ptr() as usize;
@ -151,7 +149,7 @@ fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
Ok(val) => { Ok(val) => {
// Don't panic if the max age seconds is greater than what's supported by // Don't panic if the max age seconds is greater than what's supported by
// `Duration`. // `Duration`.
let val = cmp::min(val, Duration::max_value().num_seconds()); let val = cmp::min(val, Duration::max_value().whole_seconds());
Some(Duration::seconds(val)) Some(Duration::seconds(val))
} }
Err(_) => continue, Err(_) => continue,
@ -183,16 +181,14 @@ fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
} }
} }
("expires", Some(v)) => { ("expires", Some(v)) => {
// Try strptime with three date formats according to // Try parsing with three date formats according to
// http://tools.ietf.org/html/rfc2616#section-3.3.1. Try // http://tools.ietf.org/html/rfc2616#section-3.3.1. Try
// additional ones as encountered in the real world. // additional ones as encountered in the real world.
let tm = time::strptime(v, "%a, %d %b %Y %H:%M:%S %Z") let tm = time_parser::parse_http_date(v)
.or_else(|_| time::strptime(v, "%A, %d-%b-%y %H:%M:%S %Z")) .or_else(|| time::parse(v, "%a, %d-%b-%Y %H:%M:%S").ok());
.or_else(|_| time::strptime(v, "%a, %d-%b-%Y %H:%M:%S %Z"))
.or_else(|_| time::strptime(v, "%a %b %d %H:%M:%S %Y"));
if let Ok(time) = tm { if let Some(time) = tm {
cookie.expires = Some(time) cookie.expires = Some(time.assume_utc())
} }
} }
_ => { _ => {
@ -220,8 +216,7 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Cookie, SameSite}; use super::{Cookie, SameSite};
use chrono::Duration; use time::{Duration, PrimitiveDateTime};
use time::strptime;
macro_rules! assert_eq_parse { macro_rules! assert_eq_parse {
($string:expr, $expected:expr) => { ($string:expr, $expected:expr) => {
@ -381,7 +376,7 @@ mod tests {
); );
let time_str = "Wed, 21 Oct 2015 07:28:00 GMT"; let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
let expires = strptime(time_str, "%a, %d %b %Y %H:%M:%S %Z").unwrap(); let expires = PrimitiveDateTime::parse(time_str, "%a, %d %b %Y %H:%M:%S").unwrap().assume_utc();
expected.set_expires(expires); expected.set_expires(expires);
assert_eq_parse!( assert_eq_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
@ -390,7 +385,7 @@ mod tests {
); );
unexpected.set_domain("foo.com"); unexpected.set_domain("foo.com");
let bad_expires = strptime(time_str, "%a, %d %b %Y %H:%S:%M %Z").unwrap(); let bad_expires = PrimitiveDateTime::parse(time_str, "%a, %d %b %Y %H:%S:%M").unwrap().assume_utc();
expected.set_expires(bad_expires); expected.set_expires(bad_expires);
assert_ne_parse!( assert_ne_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
@ -418,8 +413,9 @@ mod tests {
#[test] #[test]
fn do_not_panic_on_large_max_ages() { fn do_not_panic_on_large_max_ages() {
let max_seconds = Duration::max_value().num_seconds(); let max_duration = Duration::max_value();
let expected = Cookie::build("foo", "bar").max_age(max_seconds).finish(); let expected = Cookie::build("foo", "bar").max_age_time(max_duration).finish();
assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected); let overflow_duration = max_duration.checked_add(Duration::nanoseconds(1)).unwrap_or(max_duration);
assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", overflow_duration.whole_seconds()), expected);
} }
} }

View File

@ -1,13 +1,11 @@
use ring::digest::{Algorithm, SHA256}; use ring::hkdf::{Algorithm, KeyType, Prk, HKDF_SHA256};
use ring::hkdf::expand;
use ring::hmac::SigningKey;
use ring::rand::{SecureRandom, SystemRandom}; use ring::rand::{SecureRandom, SystemRandom};
use super::private::KEY_LEN as PRIVATE_KEY_LEN; use super::private::KEY_LEN as PRIVATE_KEY_LEN;
use super::signed::KEY_LEN as SIGNED_KEY_LEN; use super::signed::KEY_LEN as SIGNED_KEY_LEN;
static HKDF_DIGEST: &Algorithm = &SHA256; static HKDF_DIGEST: Algorithm = HKDF_SHA256;
const KEYS_INFO: &str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"; const KEYS_INFO: &[&[u8]] = &[b"COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"];
/// A cryptographic master key for use with `Signed` and/or `Private` jars. /// A cryptographic master key for use with `Signed` and/or `Private` jars.
/// ///
@ -25,6 +23,13 @@ pub struct Key {
encryption_key: [u8; PRIVATE_KEY_LEN], encryption_key: [u8; PRIVATE_KEY_LEN],
} }
impl KeyType for &Key {
#[inline]
fn len(&self) -> usize {
SIGNED_KEY_LEN + PRIVATE_KEY_LEN
}
}
impl Key { impl Key {
/// Derives new signing/encryption keys from a master key. /// Derives new signing/encryption keys from a master key.
/// ///
@ -56,21 +61,26 @@ impl Key {
); );
} }
// Expand the user's key into two. // An empty `Key` structure; will be filled in with HKDF derived keys.
let prk = SigningKey::new(HKDF_DIGEST, key); let mut output_key = Key {
signing_key: [0; SIGNED_KEY_LEN],
encryption_key: [0; PRIVATE_KEY_LEN],
};
// Expand the master key into two HKDF generated keys.
let mut both_keys = [0; SIGNED_KEY_LEN + PRIVATE_KEY_LEN]; let mut both_keys = [0; SIGNED_KEY_LEN + PRIVATE_KEY_LEN];
expand(&prk, KEYS_INFO.as_bytes(), &mut both_keys); let prk = Prk::new_less_safe(HKDF_DIGEST, key);
let okm = prk.expand(KEYS_INFO, &output_key).expect("okm expand");
okm.fill(&mut both_keys).expect("fill keys");
// Copy the keys into their respective arrays. // Copy the key parts into their respective fields.
let mut signing_key = [0; SIGNED_KEY_LEN]; output_key
let mut encryption_key = [0; PRIVATE_KEY_LEN]; .signing_key
signing_key.copy_from_slice(&both_keys[..SIGNED_KEY_LEN]); .copy_from_slice(&both_keys[..SIGNED_KEY_LEN]);
encryption_key.copy_from_slice(&both_keys[SIGNED_KEY_LEN..]); output_key
.encryption_key
Key { .copy_from_slice(&both_keys[SIGNED_KEY_LEN..]);
signing_key, output_key
encryption_key,
}
} }
/// Generates signing/encryption keys from a secure, random source. Keys are /// Generates signing/encryption keys from a secure, random source. Keys are

View File

@ -1,8 +1,8 @@
use std::str; use std::str;
use log::warn; use log::warn;
use ring::aead::{open_in_place, seal_in_place, Aad, Algorithm, Nonce, AES_256_GCM}; use ring::aead::{Aad, Algorithm, Nonce, AES_256_GCM};
use ring::aead::{OpeningKey, SealingKey}; use ring::aead::{LessSafeKey, UnboundKey};
use ring::rand::{SecureRandom, SystemRandom}; use ring::rand::{SecureRandom, SystemRandom};
use super::Key; use super::Key;
@ -53,11 +53,14 @@ impl<'a> PrivateJar<'a> {
} }
let ad = Aad::from(name.as_bytes()); let ad = Aad::from(name.as_bytes());
let key = OpeningKey::new(ALGO, &self.key).expect("opening key"); let key = LessSafeKey::new(
let (nonce, sealed) = data.split_at_mut(NONCE_LEN); UnboundKey::new(&ALGO, &self.key).expect("matching key length"),
);
let (nonce, mut sealed) = data.split_at_mut(NONCE_LEN);
let nonce = let nonce =
Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`"); Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`");
let unsealed = open_in_place(&key, nonce, ad, 0, sealed) let unsealed = key
.open_in_place(nonce, ad, &mut sealed)
.map_err(|_| "invalid key/nonce/value: bad seal")?; .map_err(|_| "invalid key/nonce/value: bad seal")?;
if let Ok(unsealed_utf8) = str::from_utf8(unsealed) { if let Ok(unsealed_utf8) = str::from_utf8(unsealed) {
@ -156,7 +159,7 @@ Please change it as soon as possible."
/// Encrypts the cookie's value with /// Encrypts the cookie's value with
/// authenticated encryption assuring confidentiality, integrity, and authenticity. /// authenticated encryption assuring confidentiality, integrity, and authenticity.
fn encrypt_cookie(&self, cookie: &mut Cookie) { fn encrypt_cookie(&self, cookie: &mut Cookie<'_>) {
let name = cookie.name().as_bytes(); let name = cookie.name().as_bytes();
let value = cookie.value().as_bytes(); let value = cookie.value().as_bytes();
let data = encrypt_name_value(name, value, &self.key); let data = encrypt_name_value(name, value, &self.key);
@ -196,30 +199,33 @@ Please change it as soon as possible."
fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec<u8> { fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec<u8> {
// Create the `SealingKey` structure. // Create the `SealingKey` structure.
let key = SealingKey::new(ALGO, key).expect("sealing key creation"); let unbound = UnboundKey::new(&ALGO, key).expect("matching key length");
let key = LessSafeKey::new(unbound);
// Create a vec to hold the [nonce | cookie value | overhead]. // Create a vec to hold the [nonce | cookie value | overhead].
let overhead = ALGO.tag_len(); let mut data = vec![0; NONCE_LEN + value.len() + ALGO.tag_len()];
let mut data = vec![0; NONCE_LEN + value.len() + overhead];
// Randomly generate the nonce, then copy the cookie value as input. // Randomly generate the nonce, then copy the cookie value as input.
let (nonce, in_out) = data.split_at_mut(NONCE_LEN); let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
let (in_out, tag) = in_out.split_at_mut(value.len());
in_out.copy_from_slice(value);
// Randomly generate the nonce into the nonce piece.
SystemRandom::new() SystemRandom::new()
.fill(nonce) .fill(nonce)
.expect("couldn't random fill nonce"); .expect("couldn't random fill nonce");
in_out[..value.len()].copy_from_slice(value); let nonce = Nonce::try_assume_unique_for_key(nonce).expect("invalid `nonce` length");
let nonce =
Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`");
// Use cookie's name as associated data to prevent value swapping. // Use cookie's name as associated data to prevent value swapping.
let ad = Aad::from(name); let ad = Aad::from(name);
let ad_tag = key
.seal_in_place_separate_tag(nonce, ad, in_out)
.expect("in-place seal");
// Perform the actual sealing operation and get the output length. // Copy the tag into the tag piece.
let output_len = tag.copy_from_slice(ad_tag.as_ref());
seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal");
// Remove the overhead and return the sealed content. // Remove the overhead and return the sealed content.
data.truncate(NONCE_LEN + output_len);
data data
} }

View File

@ -1,12 +1,11 @@
use ring::digest::{Algorithm, SHA256}; use ring::hmac::{self, sign, verify};
use ring::hmac::{sign, verify_with_own_key as verify, SigningKey};
use super::Key; use super::Key;
use crate::cookie::{Cookie, CookieJar}; use crate::cookie::{Cookie, CookieJar};
// Keep these in sync, and keep the key len synced with the `signed` docs as // Keep these in sync, and keep the key len synced with the `signed` docs as
// well as the `KEYS_INFO` const in secure::Key. // well as the `KEYS_INFO` const in secure::Key.
static HMAC_DIGEST: &Algorithm = &SHA256; static HMAC_DIGEST: hmac::Algorithm = hmac::HMAC_SHA256;
const BASE64_DIGEST_LEN: usize = 44; const BASE64_DIGEST_LEN: usize = 44;
pub const KEY_LEN: usize = 32; pub const KEY_LEN: usize = 32;
@ -21,7 +20,7 @@ pub const KEY_LEN: usize = 32;
/// This type is only available when the `secure` feature is enabled. /// This type is only available when the `secure` feature is enabled.
pub struct SignedJar<'a> { pub struct SignedJar<'a> {
parent: &'a mut CookieJar, parent: &'a mut CookieJar,
key: SigningKey, key: hmac::Key,
} }
impl<'a> SignedJar<'a> { impl<'a> SignedJar<'a> {
@ -32,7 +31,7 @@ impl<'a> SignedJar<'a> {
pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> { pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> {
SignedJar { SignedJar {
parent, parent,
key: SigningKey::new(HMAC_DIGEST, key.signing()), key: hmac::Key::new(HMAC_DIGEST, key.signing()),
} }
} }
@ -130,7 +129,7 @@ impl<'a> SignedJar<'a> {
} }
/// Signs the cookie's value assuring integrity and authenticity. /// Signs the cookie's value assuring integrity and authenticity.
fn sign_cookie(&self, cookie: &mut Cookie) { fn sign_cookie(&self, cookie: &mut Cookie<'_>) {
let digest = sign(&self.key, cookie.value().as_bytes()); let digest = sign(&self.key, cookie.value().as_bytes());
let mut new_value = base64::encode(digest.as_ref()); let mut new_value = base64::encode(digest.as_ref());
new_value.push_str(cookie.value()); new_value.push_str(cookie.value());

View File

@ -1,12 +1,13 @@
use std::future::Future;
use std::io::{self, Write}; use std::io::{self, Write};
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_threadpool::{run, CpuFuture}; use actix_threadpool::{run, CpuFuture};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliDecoder; use brotli2::write::BrotliDecoder;
use bytes::Bytes; use bytes::Bytes;
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
use flate2::write::{GzDecoder, ZlibDecoder}; use flate2::write::{GzDecoder, ZlibDecoder};
use futures::{try_ready, Async, Future, Poll, Stream}; use futures_core::{ready, Stream};
use super::Writer; use super::Writer;
use crate::error::PayloadError; use crate::error::PayloadError;
@ -23,21 +24,18 @@ pub struct Decoder<S> {
impl<S> Decoder<S> impl<S> Decoder<S>
where where
S: Stream<Item = Bytes, Error = PayloadError>, S: Stream<Item = Result<Bytes, PayloadError>>,
{ {
/// Construct a decoder. /// Construct a decoder.
#[inline] #[inline]
pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> { pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
let decoder = match encoding { let decoder = match encoding {
#[cfg(feature = "brotli")]
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new( ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(
BrotliDecoder::new(Writer::new()), BrotliDecoder::new(Writer::new()),
))), ))),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
ZlibDecoder::new(Writer::new()), ZlibDecoder::new(Writer::new()),
))), ))),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new( ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(
GzDecoder::new(Writer::new()), GzDecoder::new(Writer::new()),
))), ))),
@ -71,34 +69,40 @@ where
impl<S> Stream for Decoder<S> impl<S> Stream for Decoder<S>
where where
S: Stream<Item = Bytes, Error = PayloadError>, S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{ {
type Item = Bytes; type Item = Result<Bytes, PayloadError>;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
loop { loop {
if let Some(ref mut fut) = self.fut { if let Some(ref mut fut) = self.fut {
let (chunk, decoder) = try_ready!(fut.poll()); let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) {
Ok(item) => item,
Err(e) => return Poll::Ready(Some(Err(e.into()))),
};
self.decoder = Some(decoder); self.decoder = Some(decoder);
self.fut.take(); self.fut.take();
if let Some(chunk) = chunk { if let Some(chunk) = chunk {
return Ok(Async::Ready(Some(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} }
if self.eof { if self.eof {
return Ok(Async::Ready(None)); return Poll::Ready(None);
} }
match self.stream.poll()? { match Pin::new(&mut self.stream).poll_next(cx) {
Async::Ready(Some(chunk)) => { Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))),
Poll::Ready(Some(Ok(chunk))) => {
if let Some(mut decoder) = self.decoder.take() { if let Some(mut decoder) = self.decoder.take() {
if chunk.len() < INPLACE { if chunk.len() < INPLACE {
let chunk = decoder.feed_data(chunk)?; let chunk = decoder.feed_data(chunk)?;
self.decoder = Some(decoder); self.decoder = Some(decoder);
if let Some(chunk) = chunk { if let Some(chunk) = chunk {
return Ok(Async::Ready(Some(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} else { } else {
self.fut = Some(run(move || { self.fut = Some(run(move || {
@ -108,41 +112,40 @@ where
} }
continue; continue;
} else { } else {
return Ok(Async::Ready(Some(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} }
Async::Ready(None) => { Poll::Ready(None) => {
self.eof = true; self.eof = true;
return if let Some(mut decoder) = self.decoder.take() { return if let Some(mut decoder) = self.decoder.take() {
Ok(Async::Ready(decoder.feed_eof()?)) match decoder.feed_eof() {
Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
Ok(None) => Poll::Ready(None),
Err(err) => Poll::Ready(Some(Err(err.into()))),
}
} else { } else {
Ok(Async::Ready(None)) Poll::Ready(None)
}; };
} }
Async::NotReady => break, Poll::Pending => break,
} }
} }
Ok(Async::NotReady) Poll::Pending
} }
} }
enum ContentDecoder { enum ContentDecoder {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
Deflate(Box<ZlibDecoder<Writer>>), Deflate(Box<ZlibDecoder<Writer>>),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
Gzip(Box<GzDecoder<Writer>>), Gzip(Box<GzDecoder<Writer>>),
#[cfg(feature = "brotli")]
Br(Box<BrotliDecoder<Writer>>), Br(Box<BrotliDecoder<Writer>>),
} }
impl ContentDecoder { impl ContentDecoder {
#[allow(unreachable_patterns)]
fn feed_eof(&mut self) -> io::Result<Option<Bytes>> { fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
match self { match self {
#[cfg(feature = "brotli")] ContentDecoder::Br(ref mut decoder) => match decoder.flush() {
ContentDecoder::Br(ref mut decoder) => match decoder.finish() { Ok(()) => {
Ok(mut writer) => { let b = decoder.get_mut().take();
let b = writer.take();
if !b.is_empty() { if !b.is_empty() {
Ok(Some(b)) Ok(Some(b))
} else { } else {
@ -151,7 +154,6 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() { ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() {
Ok(_) => { Ok(_) => {
let b = decoder.get_mut().take(); let b = decoder.get_mut().take();
@ -163,7 +165,6 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() { ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() {
Ok(_) => { Ok(_) => {
let b = decoder.get_mut().take(); let b = decoder.get_mut().take();
@ -175,14 +176,11 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
_ => Ok(None),
} }
} }
#[allow(unreachable_patterns)]
fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> { fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
match self { match self {
#[cfg(feature = "brotli")]
ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) { ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => { Ok(_) => {
decoder.flush()?; decoder.flush()?;
@ -195,7 +193,6 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => { Ok(_) => {
decoder.flush()?; decoder.flush()?;
@ -208,7 +205,6 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => { Ok(_) => {
decoder.flush()?; decoder.flush()?;
@ -221,7 +217,6 @@ impl ContentDecoder {
} }
Err(e) => Err(e), Err(e) => Err(e),
}, },
_ => Ok(Some(data)),
} }
} }
} }

View File

@ -1,22 +1,23 @@
//! Stream encoder //! Stream encoder
use std::future::Future;
use std::io::{self, Write}; use std::io::{self, Write};
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_threadpool::{run, CpuFuture}; use actix_threadpool::{run, CpuFuture};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliEncoder; use brotli2::write::BrotliEncoder;
use bytes::Bytes; use bytes::Bytes;
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
use flate2::write::{GzEncoder, ZlibEncoder}; use flate2::write::{GzEncoder, ZlibEncoder};
use futures::{Async, Future, Poll}; use futures_core::ready;
use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::body::{Body, BodySize, MessageBody, ResponseBody};
use crate::http::header::{ContentEncoding, CONTENT_ENCODING}; use crate::http::header::{ContentEncoding, CONTENT_ENCODING};
use crate::http::{HeaderValue, HttpTryFrom, StatusCode}; use crate::http::{HeaderValue, StatusCode};
use crate::{Error, ResponseHead}; use crate::{Error, ResponseHead};
use super::Writer; use super::Writer;
const INPLACE: usize = 2049; const INPLACE: usize = 1024;
pub struct Encoder<B> { pub struct Encoder<B> {
eof: bool, eof: bool,
@ -94,43 +95,45 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
} }
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
loop { loop {
if self.eof { if self.eof {
return Ok(Async::Ready(None)); return Poll::Ready(None);
} }
if let Some(ref mut fut) = self.fut { if let Some(ref mut fut) = self.fut {
let mut encoder = futures::try_ready!(fut.poll()); let mut encoder = match ready!(Pin::new(fut).poll(cx)) {
Ok(item) => item,
Err(e) => return Poll::Ready(Some(Err(e.into()))),
};
let chunk = encoder.take(); let chunk = encoder.take();
self.encoder = Some(encoder); self.encoder = Some(encoder);
self.fut.take(); self.fut.take();
if !chunk.is_empty() { if !chunk.is_empty() {
return Ok(Async::Ready(Some(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} }
let result = match self.body { let result = match self.body {
EncoderBody::Bytes(ref mut b) => { EncoderBody::Bytes(ref mut b) => {
if b.is_empty() { if b.is_empty() {
Async::Ready(None) Poll::Ready(None)
} else { } else {
Async::Ready(Some(std::mem::replace(b, Bytes::new()))) Poll::Ready(Some(Ok(std::mem::replace(b, Bytes::new()))))
} }
} }
EncoderBody::Stream(ref mut b) => b.poll_next()?, EncoderBody::Stream(ref mut b) => b.poll_next(cx),
EncoderBody::BoxedStream(ref mut b) => b.poll_next()?, EncoderBody::BoxedStream(ref mut b) => b.poll_next(cx),
}; };
match result { match result {
Async::NotReady => return Ok(Async::NotReady), Poll::Ready(Some(Ok(chunk))) => {
Async::Ready(Some(chunk)) => {
if let Some(mut encoder) = self.encoder.take() { if let Some(mut encoder) = self.encoder.take() {
if chunk.len() < INPLACE { if chunk.len() < INPLACE {
encoder.write(&chunk)?; encoder.write(&chunk)?;
let chunk = encoder.take(); let chunk = encoder.take();
self.encoder = Some(encoder); self.encoder = Some(encoder);
if !chunk.is_empty() { if !chunk.is_empty() {
return Ok(Async::Ready(Some(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} else { } else {
self.fut = Some(run(move || { self.fut = Some(run(move || {
@ -139,22 +142,23 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
})); }));
} }
} else { } else {
return Ok(Async::Ready(Some(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} }
Async::Ready(None) => { Poll::Ready(None) => {
if let Some(encoder) = self.encoder.take() { if let Some(encoder) = self.encoder.take() {
let chunk = encoder.finish()?; let chunk = encoder.finish()?;
if chunk.is_empty() { if chunk.is_empty() {
return Ok(Async::Ready(None)); return Poll::Ready(None);
} else { } else {
self.eof = true; self.eof = true;
return Ok(Async::Ready(Some(chunk))); return Poll::Ready(Some(Ok(chunk)));
} }
} else { } else {
return Ok(Async::Ready(None)); return Poll::Ready(None);
} }
} }
val => return val,
} }
} }
} }
@ -163,33 +167,27 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
head.headers_mut().insert( head.headers_mut().insert(
CONTENT_ENCODING, CONTENT_ENCODING,
HeaderValue::try_from(Bytes::from_static(encoding.as_str().as_bytes())).unwrap(), HeaderValue::from_static(encoding.as_str()),
); );
} }
enum ContentEncoder { enum ContentEncoder {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
Deflate(ZlibEncoder<Writer>), Deflate(ZlibEncoder<Writer>),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
Gzip(GzEncoder<Writer>), Gzip(GzEncoder<Writer>),
#[cfg(feature = "brotli")]
Br(BrotliEncoder<Writer>), Br(BrotliEncoder<Writer>),
} }
impl ContentEncoder { impl ContentEncoder {
fn encoder(encoding: ContentEncoding) -> Option<Self> { fn encoder(encoding: ContentEncoding) -> Option<Self> {
match encoding { match encoding {
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new(
Writer::new(), Writer::new(),
flate2::Compression::fast(), flate2::Compression::fast(),
))), ))),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new( ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
Writer::new(), Writer::new(),
flate2::Compression::fast(), flate2::Compression::fast(),
))), ))),
#[cfg(feature = "brotli")]
ContentEncoding::Br => { ContentEncoding::Br => {
Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3))) Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
} }
@ -200,28 +198,22 @@ impl ContentEncoder {
#[inline] #[inline]
pub(crate) fn take(&mut self) -> Bytes { pub(crate) fn take(&mut self) -> Bytes {
match *self { match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
} }
} }
fn finish(self) -> Result<Bytes, io::Error> { fn finish(self) -> Result<Bytes, io::Error> {
match self { match self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(encoder) => match encoder.finish() { ContentEncoder::Br(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()), Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err), Err(err) => Err(err),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Gzip(encoder) => match encoder.finish() { ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()), Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err), Err(err) => Err(err),
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Deflate(encoder) => match encoder.finish() { ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()), Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err), Err(err) => Err(err),
@ -231,7 +223,6 @@ impl ContentEncoder {
fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
match *self { match *self {
#[cfg(feature = "brotli")]
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => { Err(err) => {
@ -239,7 +230,6 @@ impl ContentEncoder {
Err(err) Err(err)
} }
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => { Err(err) => {
@ -247,7 +237,6 @@ impl ContentEncoder {
Err(err) Err(err)
} }
}, },
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => { Err(err) => {

View File

@ -19,8 +19,9 @@ impl Writer {
buf: BytesMut::with_capacity(8192), buf: BytesMut::with_capacity(8192),
} }
} }
fn take(&mut self) -> Bytes { fn take(&mut self) -> Bytes {
self.buf.take().freeze() self.buf.split().freeze()
} }
} }
@ -29,6 +30,7 @@ impl io::Write for Writer {
self.buf.extend_from_slice(buf); self.buf.extend_from_slice(buf);
Ok(buf.len()) Ok(buf.len())
} }
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
Ok(()) Ok(())
} }

View File

@ -1,29 +1,29 @@
//! Error and Result module //! Error and Result module
use std::any::TypeId;
use std::cell::RefCell; use std::cell::RefCell;
use std::io::Write; use std::io::Write;
use std::str::Utf8Error; use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use std::{fmt, io, result}; use std::{fmt, io, result};
use actix_codec::{Decoder, Encoder};
pub use actix_threadpool::BlockingError; pub use actix_threadpool::BlockingError;
use actix_utils::framed::DispatcherError as FramedDispatcherError;
use actix_utils::timeout::TimeoutError; use actix_utils::timeout::TimeoutError;
use bytes::BytesMut; use bytes::BytesMut;
use derive_more::{Display, From}; use derive_more::{Display, From};
use futures::Canceled; pub use futures_channel::oneshot::Canceled;
use http::uri::InvalidUri; use http::uri::InvalidUri;
use http::{header, Error as HttpError, StatusCode}; use http::{header, Error as HttpError, StatusCode};
use httparse; use httparse;
use serde::de::value::Error as DeError; use serde::de::value::Error as DeError;
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use serde_urlencoded::ser::Error as FormError; use serde_urlencoded::ser::Error as FormError;
use tokio_timer::Error as TimerError;
// re-export for convinience // re-export for convinience
use crate::body::Body; use crate::body::Body;
pub use crate::cookie::ParseError as CookieParseError; pub use crate::cookie::ParseError as CookieParseError;
use crate::helpers::Writer; use crate::helpers::Writer;
use crate::response::Response; use crate::response::{Response, ResponseBuilder};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations /// for actix web operations
@ -61,54 +61,51 @@ impl Error {
/// Error that can be converted to `Response` /// Error that can be converted to `Response`
pub trait ResponseError: fmt::Debug + fmt::Display { pub trait ResponseError: fmt::Debug + fmt::Display {
/// Response's status code
///
/// Internal server error is generated by default.
fn status_code(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
/// Create response for error /// Create response for error
/// ///
/// Internal server error is generated by default. /// Internal server error is generated by default.
fn error_response(&self) -> Response { fn error_response(&self) -> Response {
Response::new(StatusCode::INTERNAL_SERVER_ERROR) let mut resp = Response::new(self.status_code());
}
/// Constructs an error response
fn render_response(&self) -> Response {
let mut resp = self.error_response();
let mut buf = BytesMut::new(); let mut buf = BytesMut::new();
let _ = write!(Writer(&mut buf), "{}", self); let _ = write!(Writer(&mut buf), "{}", self);
resp.headers_mut().insert( resp.headers_mut().insert(
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"), header::HeaderValue::from_static("text/plain; charset=utf-8"),
); );
resp.set_body(Body::from(buf)) resp.set_body(Body::from(buf))
} }
#[doc(hidden)] downcast_get_type_id!();
fn __private_get_type_id__(&self) -> TypeId
where
Self: 'static,
{
TypeId::of::<Self>()
}
} }
impl dyn ResponseError + 'static { downcast!(ResponseError);
/// Downcasts a response error to a specific type.
pub fn downcast_ref<T: ResponseError + 'static>(&self) -> Option<&T> {
if self.__private_get_type_id__() == TypeId::of::<T>() {
unsafe { Some(&*(self as *const dyn ResponseError as *const T)) }
} else {
None
}
}
}
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.cause, f) fmt::Display::fmt(&self.cause, f)
} }
} }
impl fmt::Debug for Error { impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{:?}", &self.cause) write!(f, "{:?}", &self.cause)
}
}
impl std::error::Error for Error {
fn cause(&self) -> Option<&dyn std::error::Error> {
None
}
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
} }
} }
@ -118,17 +115,11 @@ impl From<()> for Error {
} }
} }
impl std::error::Error for Error { impl From<std::convert::Infallible> for Error {
fn description(&self) -> &str { fn from(_: std::convert::Infallible) -> Self {
"actix-http::Error" // `std::convert::Infallible` indicates an error
} // that will never happen
unreachable!()
fn cause(&self) -> Option<&dyn std::error::Error> {
None
}
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
} }
} }
@ -148,12 +139,26 @@ impl<T: ResponseError + 'static> From<T> for Error {
} }
} }
/// Convert Response to a Error
impl From<Response> for Error {
fn from(res: Response) -> Error {
InternalError::from_response("", res).into()
}
}
/// Convert ResponseBuilder to a Error
impl From<ResponseBuilder> for Error {
fn from(mut res: ResponseBuilder) -> Error {
InternalError::from_response("", res.finish()).into()
}
}
/// Return `GATEWAY_TIMEOUT` for `TimeoutError` /// Return `GATEWAY_TIMEOUT` for `TimeoutError`
impl<E: ResponseError> ResponseError for TimeoutError<E> { impl<E: ResponseError> ResponseError for TimeoutError<E> {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
match self { match self {
TimeoutError::Service(e) => e.error_response(), TimeoutError::Service(e) => e.status_code(),
TimeoutError::Timeout => Response::new(StatusCode::GATEWAY_TIMEOUT), TimeoutError::Timeout => StatusCode::GATEWAY_TIMEOUT,
} }
} }
} }
@ -171,31 +176,31 @@ impl ResponseError for JsonError {}
/// `InternalServerError` for `FormError` /// `InternalServerError` for `FormError`
impl ResponseError for FormError {} impl ResponseError for FormError {}
/// `InternalServerError` for `TimerError` #[cfg(feature = "openssl")]
impl ResponseError for TimerError {}
#[cfg(feature = "ssl")]
/// `InternalServerError` for `openssl::ssl::Error` /// `InternalServerError` for `openssl::ssl::Error`
impl ResponseError for openssl::ssl::Error {} impl ResponseError for actix_connect::ssl::openssl::SslError {}
#[cfg(feature = "ssl")] #[cfg(feature = "openssl")]
/// `InternalServerError` for `openssl::ssl::HandshakeError` /// `InternalServerError` for `openssl::ssl::HandshakeError`
impl ResponseError for openssl::ssl::HandshakeError<tokio_tcp::TcpStream> {} impl<T: std::fmt::Debug> ResponseError for actix_tls::openssl::HandshakeError<T> {}
/// Return `BAD_REQUEST` for `de::value::Error` /// Return `BAD_REQUEST` for `de::value::Error`
impl ResponseError for DeError { impl ResponseError for DeError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
/// `InternalServerError` for `Canceled`
impl ResponseError for Canceled {}
/// `InternalServerError` for `BlockingError` /// `InternalServerError` for `BlockingError`
impl<E: fmt::Debug> ResponseError for BlockingError<E> {} impl<E: fmt::Debug> ResponseError for BlockingError<E> {}
/// Return `BAD_REQUEST` for `Utf8Error` /// Return `BAD_REQUEST` for `Utf8Error`
impl ResponseError for Utf8Error { impl ResponseError for Utf8Error {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
@ -205,32 +210,22 @@ impl ResponseError for HttpError {}
/// Return `InternalServerError` for `io::Error` /// Return `InternalServerError` for `io::Error`
impl ResponseError for io::Error { impl ResponseError for io::Error {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
match self.kind() { match self.kind() {
io::ErrorKind::NotFound => Response::new(StatusCode::NOT_FOUND), io::ErrorKind::NotFound => StatusCode::NOT_FOUND,
io::ErrorKind::PermissionDenied => Response::new(StatusCode::FORBIDDEN), io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
_ => Response::new(StatusCode::INTERNAL_SERVER_ERROR), _ => StatusCode::INTERNAL_SERVER_ERROR,
} }
} }
} }
/// `BadRequest` for `InvalidHeaderValue` /// `BadRequest` for `InvalidHeaderValue`
impl ResponseError for header::InvalidHeaderValue { impl ResponseError for header::InvalidHeaderValue {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
/// `BadRequest` for `InvalidHeaderValue`
impl ResponseError for header::InvalidHeaderValueBytes {
fn error_response(&self) -> Response {
Response::new(StatusCode::BAD_REQUEST)
}
}
/// `InternalServerError` for `futures::Canceled`
impl ResponseError for Canceled {}
/// A set of errors that can occur during parsing HTTP streams /// A set of errors that can occur during parsing HTTP streams
#[derive(Debug, Display)] #[derive(Debug, Display)]
pub enum ParseError { pub enum ParseError {
@ -270,8 +265,8 @@ pub enum ParseError {
/// Return `BadRequest` for `ParseError` /// Return `BadRequest` for `ParseError`
impl ResponseError for ParseError { impl ResponseError for ParseError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
@ -363,7 +358,7 @@ impl From<BlockingError<io::Error>> for PayloadError {
BlockingError::Error(e) => PayloadError::Io(e), BlockingError::Error(e) => PayloadError::Io(e),
BlockingError::Canceled => PayloadError::Io(io::Error::new( BlockingError::Canceled => PayloadError::Io(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"Thread pool is gone", "Operation is canceled",
)), )),
} }
} }
@ -374,18 +369,18 @@ impl From<BlockingError<io::Error>> for PayloadError {
/// - `Overflow` returns `PayloadTooLarge` /// - `Overflow` returns `PayloadTooLarge`
/// - Other errors returns `BadRequest` /// - Other errors returns `BadRequest`
impl ResponseError for PayloadError { impl ResponseError for PayloadError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
match *self { match *self {
PayloadError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE,
_ => Response::new(StatusCode::BAD_REQUEST), _ => StatusCode::BAD_REQUEST,
} }
} }
} }
/// Return `BadRequest` for `cookie::ParseError` /// Return `BadRequest` for `cookie::ParseError`
impl ResponseError for crate::cookie::ParseError { impl ResponseError for crate::cookie::ParseError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
@ -449,11 +444,19 @@ pub enum ContentTypeError {
/// Return `BadRequest` for `ContentTypeError` /// Return `BadRequest` for `ContentTypeError`
impl ResponseError for ContentTypeError { impl ResponseError for ContentTypeError {
fn error_response(&self) -> Response { fn status_code(&self) -> StatusCode {
Response::new(StatusCode::BAD_REQUEST) StatusCode::BAD_REQUEST
} }
} }
impl<E, U: Encoder + Decoder> ResponseError for FramedDispatcherError<E, U>
where
E: fmt::Debug + fmt::Display,
<U as Encoder>::Error: fmt::Debug,
<U as Decoder>::Error: fmt::Debug,
{
}
/// Helper type that can wrap any error and generate custom response. /// Helper type that can wrap any error and generate custom response.
/// ///
/// In following example any `io::Error` will be converted into "BAD REQUEST" /// In following example any `io::Error` will be converted into "BAD REQUEST"
@ -461,14 +464,12 @@ impl ResponseError for ContentTypeError {
/// default. /// default.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_http;
/// # use std::io; /// # use std::io;
/// # use actix_http::*; /// # use actix_http::*;
/// ///
/// fn index(req: Request) -> Result<&'static str> { /// fn index(req: Request) -> Result<&'static str> {
/// Err(error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "error"))) /// Err(error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "error")))
/// } /// }
/// # fn main() {}
/// ``` /// ```
pub struct InternalError<T> { pub struct InternalError<T> {
cause: T, cause: T,
@ -502,7 +503,7 @@ impl<T> fmt::Debug for InternalError<T>
where where
T: fmt::Debug + 'static, T: fmt::Debug + 'static,
{ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.cause, f) fmt::Debug::fmt(&self.cause, f)
} }
} }
@ -511,7 +512,7 @@ impl<T> fmt::Display for InternalError<T>
where where
T: fmt::Display + 'static, T: fmt::Display + 'static,
{ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.cause, f) fmt::Display::fmt(&self.cause, f)
} }
} }
@ -520,6 +521,19 @@ impl<T> ResponseError for InternalError<T>
where where
T: fmt::Debug + fmt::Display + 'static, T: fmt::Debug + fmt::Display + 'static,
{ {
fn status_code(&self) -> StatusCode {
match self.status {
InternalErrorType::Status(st) => st,
InternalErrorType::Response(ref resp) => {
if let Some(resp) = resp.borrow().as_ref() {
resp.head().status
} else {
StatusCode::INTERNAL_SERVER_ERROR
}
}
}
}
fn error_response(&self) -> Response { fn error_response(&self) -> Response {
match self.status { match self.status {
InternalErrorType::Status(st) => { InternalErrorType::Status(st) => {
@ -528,7 +542,7 @@ where
let _ = write!(Writer(&mut buf), "{}", self); let _ = write!(Writer(&mut buf), "{}", self);
res.headers_mut().insert( res.headers_mut().insert(
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"), header::HeaderValue::from_static("text/plain; charset=utf-8"),
); );
res.set_body(Body::from(buf)) res.set_body(Body::from(buf))
} }
@ -541,18 +555,6 @@ where
} }
} }
} }
/// Constructs an error response
fn render_response(&self) -> Response {
self.error_response()
}
}
/// Convert Response to a Error
impl From<Response> for Error {
fn from(res: Response) -> Error {
InternalError::from_response("", res).into()
}
} }
/// Helper function that creates wrapper of any error and generate *BAD /// Helper function that creates wrapper of any error and generate *BAD
@ -945,24 +947,15 @@ where
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
} }
#[cfg(feature = "fail")] #[cfg(feature = "failure")]
mod failure_integration { /// Compatibility for `failure::Error`
use super::*; impl ResponseError for fail_ure::Error {}
/// Compatibility for `failure::Error`
impl ResponseError for failure::Error {
fn error_response(&self) -> Response {
Response::new(StatusCode::INTERNAL_SERVER_ERROR)
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use http::{Error as HttpError, StatusCode}; use http::{Error as HttpError, StatusCode};
use httparse; use httparse;
use std::error::Error as StdError;
use std::io; use std::io;
#[test] #[test]
@ -991,7 +984,7 @@ mod tests {
#[test] #[test]
fn test_error_cause() { fn test_error_cause() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.to_string();
let e = Error::from(orig); let e = Error::from(orig);
assert_eq!(format!("{}", e.as_response_error()), desc); assert_eq!(format!("{}", e.as_response_error()), desc);
} }
@ -999,7 +992,7 @@ mod tests {
#[test] #[test]
fn test_error_display() { fn test_error_display() {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.to_string();
let e = Error::from(orig); let e = Error::from(orig);
assert_eq!(format!("{}", e), desc); assert_eq!(format!("{}", e), desc);
} }
@ -1041,7 +1034,7 @@ mod tests {
match ParseError::from($from) { match ParseError::from($from) {
e @ $error => { e @ $error => {
let desc = format!("{}", e); let desc = format!("{}", e);
assert_eq!(desc, format!("IO error: {}", $from.description())); assert_eq!(desc, format!("IO error: {}", $from));
} }
_ => unreachable!("{:?}", $from), _ => unreachable!("{:?}", $from),
} }

View File

@ -1,12 +1,12 @@
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::fmt; use std::fmt;
use hashbrown::HashMap; use fxhash::FxHashMap;
#[derive(Default)] #[derive(Default)]
/// A type map of request extensions. /// A type map of request extensions.
pub struct Extensions { pub struct Extensions {
map: HashMap<TypeId, Box<dyn Any>>, map: FxHashMap<TypeId, Box<dyn Any>>,
} }
impl Extensions { impl Extensions {
@ -14,7 +14,7 @@ impl Extensions {
#[inline] #[inline]
pub fn new() -> Extensions { pub fn new() -> Extensions {
Extensions { Extensions {
map: HashMap::default(), map: FxHashMap::default(),
} }
} }
@ -28,33 +28,30 @@ impl Extensions {
/// Check if container contains entry /// Check if container contains entry
pub fn contains<T: 'static>(&self) -> bool { pub fn contains<T: 'static>(&self) -> bool {
self.map.get(&TypeId::of::<T>()).is_some() self.map.contains_key(&TypeId::of::<T>())
} }
/// Get a reference to a type previously inserted on this `Extensions`. /// Get a reference to a type previously inserted on this `Extensions`.
pub fn get<T: 'static>(&self) -> Option<&T> { pub fn get<T: 'static>(&self) -> Option<&T> {
self.map self.map
.get(&TypeId::of::<T>()) .get(&TypeId::of::<T>())
.and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref()) .and_then(|boxed| boxed.downcast_ref())
} }
/// Get a mutable reference to a type previously inserted on this `Extensions`. /// Get a mutable reference to a type previously inserted on this `Extensions`.
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> { pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.map self.map
.get_mut(&TypeId::of::<T>()) .get_mut(&TypeId::of::<T>())
.and_then(|boxed| (&mut **boxed as &mut (dyn Any + 'static)).downcast_mut()) .and_then(|boxed| boxed.downcast_mut())
} }
/// Remove a type from this `Extensions`. /// Remove a type from this `Extensions`.
/// ///
/// If a extension of this type existed, it will be returned. /// If a extension of this type existed, it will be returned.
pub fn remove<T: 'static>(&mut self) -> Option<T> { pub fn remove<T: 'static>(&mut self) -> Option<T> {
self.map.remove(&TypeId::of::<T>()).and_then(|boxed| { self.map
(boxed as Box<dyn Any + 'static>) .remove(&TypeId::of::<T>())
.downcast() .and_then(|boxed| boxed.downcast().ok().map(|boxed| *boxed))
.ok()
.map(|boxed| *boxed)
})
} }
/// Clear the `Extensions` of all inserted extensions. /// Clear the `Extensions` of all inserted extensions.
@ -65,11 +62,97 @@ impl Extensions {
} }
impl fmt::Debug for Extensions { impl fmt::Debug for Extensions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Extensions").finish() f.debug_struct("Extensions").finish()
} }
} }
#[test]
fn test_remove() {
let mut map = Extensions::new();
map.insert::<i8>(123);
assert!(map.get::<i8>().is_some());
map.remove::<i8>();
assert!(map.get::<i8>().is_none());
}
#[test]
fn test_clear() {
let mut map = Extensions::new();
map.insert::<i8>(8);
map.insert::<i16>(16);
map.insert::<i32>(32);
assert!(map.contains::<i8>());
assert!(map.contains::<i16>());
assert!(map.contains::<i32>());
map.clear();
assert!(!map.contains::<i8>());
assert!(!map.contains::<i16>());
assert!(!map.contains::<i32>());
map.insert::<i8>(10);
assert_eq!(*map.get::<i8>().unwrap(), 10);
}
#[test]
fn test_integers() {
let mut map = Extensions::new();
map.insert::<i8>(8);
map.insert::<i16>(16);
map.insert::<i32>(32);
map.insert::<i64>(64);
map.insert::<i128>(128);
map.insert::<u8>(8);
map.insert::<u16>(16);
map.insert::<u32>(32);
map.insert::<u64>(64);
map.insert::<u128>(128);
assert!(map.get::<i8>().is_some());
assert!(map.get::<i16>().is_some());
assert!(map.get::<i32>().is_some());
assert!(map.get::<i64>().is_some());
assert!(map.get::<i128>().is_some());
assert!(map.get::<u8>().is_some());
assert!(map.get::<u16>().is_some());
assert!(map.get::<u32>().is_some());
assert!(map.get::<u64>().is_some());
assert!(map.get::<u128>().is_some());
}
#[test]
fn test_composition() {
struct Magi<T>(pub T);
struct Madoka {
pub god: bool,
}
struct Homura {
pub attempts: usize,
}
struct Mami {
pub guns: usize,
}
let mut map = Extensions::new();
map.insert(Magi(Madoka { god: false }));
map.insert(Magi(Homura { attempts: 0 }));
map.insert(Magi(Mami { guns: 999 }));
assert!(!map.get::<Magi<Madoka>>().unwrap().0.god);
assert_eq!(0, map.get::<Magi<Homura>>().unwrap().0.attempts);
assert_eq!(999, map.get::<Magi<Mami>>().unwrap().0.guns);
}
#[test] #[test]
fn test_extensions() { fn test_extensions() {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]

View File

@ -1,13 +1,8 @@
#![allow(unused_imports, unused_variables, dead_code)] use std::io;
use std::io::{self, Write};
use std::rc::Rc;
use actix_codec::{Decoder, Encoder}; use actix_codec::{Decoder, Encoder};
use bitflags::bitflags; use bitflags::bitflags;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use http::header::{
HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE,
};
use http::{Method, Version}; use http::{Method, Version};
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
@ -16,9 +11,7 @@ use super::{Message, MessageType};
use crate::body::BodySize; use crate::body::BodySize;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::error::{ParseError, PayloadError}; use crate::error::{ParseError, PayloadError};
use crate::helpers; use crate::message::{ConnectionType, RequestHeadType, ResponseHead};
use crate::message::{ConnectionType, Head, MessagePool, RequestHead, RequestHeadType, ResponseHead};
use crate::header::HeaderMap;
bitflags! { bitflags! {
struct Flags: u8 { struct Flags: u8 {
@ -28,8 +21,6 @@ bitflags! {
} }
} }
const AVERAGE_HEADER_SIZE: usize = 30;
/// HTTP/1 Codec /// HTTP/1 Codec
pub struct ClientCodec { pub struct ClientCodec {
inner: ClientCodecInner, inner: ClientCodecInner,
@ -49,7 +40,6 @@ struct ClientCodecInner {
// encoder part // encoder part
flags: Flags, flags: Flags,
headers_size: u32,
encoder: encoder::MessageEncoder<RequestHeadType>, encoder: encoder::MessageEncoder<RequestHeadType>,
} }
@ -78,7 +68,6 @@ impl ClientCodec {
ctype: ConnectionType::Close, ctype: ConnectionType::Close,
flags, flags,
headers_size: 0,
encoder: encoder::MessageEncoder::default(), encoder: encoder::MessageEncoder::default(),
}, },
} }
@ -197,7 +186,9 @@ impl Encoder for ClientCodec {
Message::Item((mut head, length)) => { Message::Item((mut head, length)) => {
let inner = &mut self.inner; let inner = &mut self.inner;
inner.version = head.as_ref().version; inner.version = head.as_ref().version;
inner.flags.set(Flags::HEAD, head.as_ref().method == Method::HEAD); inner
.flags
.set(Flags::HEAD, head.as_ref().method == Method::HEAD);
// connection status // connection status
inner.ctype = match head.as_ref().connection_type() { inner.ctype = match head.as_ref().connection_type() {

View File

@ -1,12 +1,9 @@
#![allow(unused_imports, unused_variables, dead_code)] use std::{fmt, io};
use std::io::Write;
use std::{fmt, io, net};
use actix_codec::{Decoder, Encoder}; use actix_codec::{Decoder, Encoder};
use bitflags::bitflags; use bitflags::bitflags;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::BytesMut;
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version};
use http::{Method, StatusCode, Version};
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
use super::{decoder, encoder}; use super::{decoder, encoder};
@ -14,8 +11,7 @@ use super::{Message, MessageType};
use crate::body::BodySize; use crate::body::BodySize;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::error::ParseError; use crate::error::ParseError;
use crate::helpers; use crate::message::ConnectionType;
use crate::message::{ConnectionType, Head, ResponseHead};
use crate::request::Request; use crate::request::Request;
use crate::response::Response; use crate::response::Response;
@ -27,8 +23,6 @@ bitflags! {
} }
} }
const AVERAGE_HEADER_SIZE: usize = 30;
/// HTTP/1 Codec /// HTTP/1 Codec
pub struct Codec { pub struct Codec {
config: ServiceConfig, config: ServiceConfig,
@ -49,7 +43,7 @@ impl Default for Codec {
} }
impl fmt::Debug for Codec { impl fmt::Debug for Codec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "h1::Codec({:?})", self.flags) write!(f, "h1::Codec({:?})", self.flags)
} }
} }
@ -176,7 +170,6 @@ impl Encoder for Codec {
}; };
// encode message // encode message
let len = dst.len();
self.encoder.encode( self.encoder.encode(
dst, dst,
&mut res, &mut res,
@ -202,17 +195,11 @@ impl Encoder for Codec {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{cmp, io}; use bytes::BytesMut;
use http::Method;
use actix_codec::{AsyncRead, AsyncWrite};
use bytes::{Buf, Bytes, BytesMut};
use http::{Method, Version};
use super::*; use super::*;
use crate::error::ParseError;
use crate::h1::Message;
use crate::httpmessage::HttpMessage; use crate::httpmessage::HttpMessage;
use crate::request::Request;
#[test] #[test]
fn test_http_request_chunked_payload_and_next_message() { fn test_http_request_chunked_payload_and_next_message() {

View File

@ -1,11 +1,13 @@
use std::convert::TryFrom;
use std::io;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::{io, mem}; use std::mem::MaybeUninit;
use std::task::Poll;
use actix_codec::Decoder; use actix_codec::Decoder;
use bytes::{Bytes, BytesMut}; use bytes::{Buf, Bytes, BytesMut};
use futures::{Async, Poll};
use http::header::{HeaderName, HeaderValue}; use http::header::{HeaderName, HeaderValue};
use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version}; use http::{header, Method, StatusCode, Uri, Version};
use httparse; use httparse;
use log::{debug, error, trace}; use log::{debug, error, trace};
@ -78,8 +80,8 @@ pub(crate) trait MessageType: Sized {
// Unsafe: httparse check header value for valid utf-8 // Unsafe: httparse check header value for valid utf-8
let value = unsafe { let value = unsafe {
HeaderValue::from_shared_unchecked( HeaderValue::from_maybe_shared_unchecked(
slice.slice(idx.value.0, idx.value.1), slice.slice(idx.value.0..idx.value.1),
) )
}; };
match name { match name {
@ -183,14 +185,16 @@ impl MessageType for Request {
&mut self.head_mut().headers &mut self.head_mut().headers
} }
#[allow(clippy::uninit_assumed_init)]
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> { fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
// Unsafe: we read only this data only after httparse parses headers into. // Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks. // performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; let mut headers: [HeaderIndex; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() };
let (len, method, uri, ver, h_len) = { let (len, method, uri, ver, h_len) = {
let mut parsed: [httparse::Header; MAX_HEADERS] = let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
unsafe { mem::uninitialized() }; unsafe { MaybeUninit::uninit().assume_init() };
let mut req = httparse::Request::new(&mut parsed); let mut req = httparse::Request::new(&mut parsed);
match req.parse(src)? { match req.parse(src)? {
@ -257,14 +261,16 @@ impl MessageType for ResponseHead {
&mut self.headers &mut self.headers
} }
#[allow(clippy::uninit_assumed_init)]
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> { fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
// Unsafe: we read only this data only after httparse parses headers into. // Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks. // performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; let mut headers: [HeaderIndex; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() };
let (len, ver, status, h_len) = { let (len, ver, status, h_len) = {
let mut parsed: [httparse::Header; MAX_HEADERS] = let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
unsafe { mem::uninitialized() }; unsafe { MaybeUninit::uninit().assume_init() };
let mut res = httparse::Response::new(&mut parsed); let mut res = httparse::Response::new(&mut parsed);
match res.parse(src)? { match res.parse(src)? {
@ -322,7 +328,7 @@ pub(crate) struct HeaderIndex {
impl HeaderIndex { impl HeaderIndex {
pub(crate) fn record( pub(crate) fn record(
bytes: &[u8], bytes: &[u8],
headers: &[httparse::Header], headers: &[httparse::Header<'_>],
indices: &mut [HeaderIndex], indices: &mut [HeaderIndex],
) { ) {
let bytes_ptr = bytes.as_ptr() as usize; let bytes_ptr = bytes.as_ptr() as usize;
@ -425,7 +431,7 @@ impl Decoder for PayloadDecoder {
let len = src.len() as u64; let len = src.len() as u64;
let buf; let buf;
if *remaining > len { if *remaining > len {
buf = src.take().freeze(); buf = src.split().freeze();
*remaining -= len; *remaining -= len;
} else { } else {
buf = src.split_to(*remaining as usize).freeze(); buf = src.split_to(*remaining as usize).freeze();
@ -439,9 +445,10 @@ impl Decoder for PayloadDecoder {
loop { loop {
let mut buf = None; let mut buf = None;
// advances the chunked state // advances the chunked state
*state = match state.step(src, size, &mut buf)? { *state = match state.step(src, size, &mut buf) {
Async::NotReady => return Ok(None), Poll::Pending => return Ok(None),
Async::Ready(state) => state, Poll::Ready(Ok(state)) => state,
Poll::Ready(Err(e)) => return Err(e),
}; };
if *state == ChunkedState::End { if *state == ChunkedState::End {
trace!("End of chunked stream"); trace!("End of chunked stream");
@ -459,7 +466,7 @@ impl Decoder for PayloadDecoder {
if src.is_empty() { if src.is_empty() {
Ok(None) Ok(None)
} else { } else {
Ok(Some(PayloadItem::Chunk(src.take().freeze()))) Ok(Some(PayloadItem::Chunk(src.split().freeze())))
} }
} }
} }
@ -470,10 +477,10 @@ macro_rules! byte (
($rdr:ident) => ({ ($rdr:ident) => ({
if $rdr.len() > 0 { if $rdr.len() > 0 {
let b = $rdr[0]; let b = $rdr[0];
$rdr.split_to(1); $rdr.advance(1);
b b
} else { } else {
return Ok(Async::NotReady) return Poll::Pending
} }
}) })
); );
@ -484,7 +491,7 @@ impl ChunkedState {
body: &mut BytesMut, body: &mut BytesMut,
size: &mut u64, size: &mut u64,
buf: &mut Option<Bytes>, buf: &mut Option<Bytes>,
) -> Poll<ChunkedState, io::Error> { ) -> Poll<Result<ChunkedState, io::Error>> {
use self::ChunkedState::*; use self::ChunkedState::*;
match *self { match *self {
Size => ChunkedState::read_size(body, size), Size => ChunkedState::read_size(body, size),
@ -496,10 +503,14 @@ impl ChunkedState {
BodyLf => ChunkedState::read_body_lf(body), BodyLf => ChunkedState::read_body_lf(body),
EndCr => ChunkedState::read_end_cr(body), EndCr => ChunkedState::read_end_cr(body),
EndLf => ChunkedState::read_end_lf(body), EndLf => ChunkedState::read_end_lf(body),
End => Ok(Async::Ready(ChunkedState::End)), End => Poll::Ready(Ok(ChunkedState::End)),
} }
} }
fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll<ChunkedState, io::Error> {
fn read_size(
rdr: &mut BytesMut,
size: &mut u64,
) -> Poll<Result<ChunkedState, io::Error>> {
let radix = 16; let radix = 16;
match byte!(rdr) { match byte!(rdr) {
b @ b'0'..=b'9' => { b @ b'0'..=b'9' => {
@ -514,48 +525,49 @@ impl ChunkedState {
*size *= radix; *size *= radix;
*size += u64::from(b + 10 - b'A'); *size += u64::from(b + 10 - b'A');
} }
b'\t' | b' ' => return Ok(Async::Ready(ChunkedState::SizeLws)), b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)),
b';' => return Ok(Async::Ready(ChunkedState::Extension)), b';' => return Poll::Ready(Ok(ChunkedState::Extension)),
b'\r' => return Ok(Async::Ready(ChunkedState::SizeLf)), b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)),
_ => { _ => {
return Err(io::Error::new( return Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"Invalid chunk size line: Invalid Size", "Invalid chunk size line: Invalid Size",
)); )));
} }
} }
Ok(Async::Ready(ChunkedState::Size)) Poll::Ready(Ok(ChunkedState::Size))
} }
fn read_size_lws(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> {
fn read_size_lws(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
trace!("read_size_lws"); trace!("read_size_lws");
match byte!(rdr) { match byte!(rdr) {
// LWS can follow the chunk size, but no more digits can come // LWS can follow the chunk size, but no more digits can come
b'\t' | b' ' => Ok(Async::Ready(ChunkedState::SizeLws)), b'\t' | b' ' => Poll::Ready(Ok(ChunkedState::SizeLws)),
b';' => Ok(Async::Ready(ChunkedState::Extension)), b';' => Poll::Ready(Ok(ChunkedState::Extension)),
b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)), b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
_ => Err(io::Error::new( _ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"Invalid chunk size linear white space", "Invalid chunk size linear white space",
)), ))),
} }
} }
fn read_extension(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> { fn read_extension(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) { match byte!(rdr) {
b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)), b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
_ => Ok(Async::Ready(ChunkedState::Extension)), // no supported extensions _ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
} }
} }
fn read_size_lf( fn read_size_lf(
rdr: &mut BytesMut, rdr: &mut BytesMut,
size: &mut u64, size: &mut u64,
) -> Poll<ChunkedState, io::Error> { ) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) { match byte!(rdr) {
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), b'\n' if *size > 0 => Poll::Ready(Ok(ChunkedState::Body)),
b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)), b'\n' if *size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)),
_ => Err(io::Error::new( _ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"Invalid chunk size LF", "Invalid chunk size LF",
)), ))),
} }
} }
@ -563,16 +575,16 @@ impl ChunkedState {
rdr: &mut BytesMut, rdr: &mut BytesMut,
rem: &mut u64, rem: &mut u64,
buf: &mut Option<Bytes>, buf: &mut Option<Bytes>,
) -> Poll<ChunkedState, io::Error> { ) -> Poll<Result<ChunkedState, io::Error>> {
trace!("Chunked read, remaining={:?}", rem); trace!("Chunked read, remaining={:?}", rem);
let len = rdr.len() as u64; let len = rdr.len() as u64;
if len == 0 { if len == 0 {
Ok(Async::Ready(ChunkedState::Body)) Poll::Ready(Ok(ChunkedState::Body))
} else { } else {
let slice; let slice;
if *rem > len { if *rem > len {
slice = rdr.take().freeze(); slice = rdr.split().freeze();
*rem -= len; *rem -= len;
} else { } else {
slice = rdr.split_to(*rem as usize).freeze(); slice = rdr.split_to(*rem as usize).freeze();
@ -580,47 +592,47 @@ impl ChunkedState {
} }
*buf = Some(slice); *buf = Some(slice);
if *rem > 0 { if *rem > 0 {
Ok(Async::Ready(ChunkedState::Body)) Poll::Ready(Ok(ChunkedState::Body))
} else { } else {
Ok(Async::Ready(ChunkedState::BodyCr)) Poll::Ready(Ok(ChunkedState::BodyCr))
} }
} }
} }
fn read_body_cr(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> { fn read_body_cr(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) { match byte!(rdr) {
b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)), b'\r' => Poll::Ready(Ok(ChunkedState::BodyLf)),
_ => Err(io::Error::new( _ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"Invalid chunk body CR", "Invalid chunk body CR",
)), ))),
} }
} }
fn read_body_lf(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> { fn read_body_lf(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) { match byte!(rdr) {
b'\n' => Ok(Async::Ready(ChunkedState::Size)), b'\n' => Poll::Ready(Ok(ChunkedState::Size)),
_ => Err(io::Error::new( _ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"Invalid chunk body LF", "Invalid chunk body LF",
)), ))),
} }
} }
fn read_end_cr(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> { fn read_end_cr(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) { match byte!(rdr) {
b'\r' => Ok(Async::Ready(ChunkedState::EndLf)), b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)),
_ => Err(io::Error::new( _ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"Invalid chunk end CR", "Invalid chunk end CR",
)), ))),
} }
} }
fn read_end_lf(rdr: &mut BytesMut) -> Poll<ChunkedState, io::Error> { fn read_end_lf(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) { match byte!(rdr) {
b'\n' => Ok(Async::Ready(ChunkedState::End)), b'\n' => Poll::Ready(Ok(ChunkedState::End)),
_ => Err(io::Error::new( _ => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
"Invalid chunk end LF", "Invalid chunk end LF",
)), ))),
} }
} }
} }

View File

@ -1,15 +1,15 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::time::Instant; use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, io, net}; use std::{fmt, io, net};
use actix_codec::{Decoder, Encoder, Framed, FramedParts}; use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts};
use actix_server_config::IoStream; use actix_rt::time::{delay_until, Delay, Instant};
use actix_service::Service; use actix_service::Service;
use bitflags::bitflags; use bitflags::bitflags;
use bytes::{BufMut, BytesMut}; use bytes::{Buf, BytesMut};
use futures::{Async, Future, Poll};
use log::{error, trace}; use log::{error, trace};
use tokio_timer::Delay;
use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::body::{Body, BodySize, MessageBody, ResponseBody};
use crate::cloneable::CloneableService; use crate::cloneable::CloneableService;
@ -166,7 +166,7 @@ impl PartialEq for PollResponse {
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U> impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@ -184,6 +184,7 @@ where
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>, upgrade: Option<CloneableService<U>>,
on_connect: Option<Box<dyn DataFactory>>, on_connect: Option<Box<dyn DataFactory>>,
peer_addr: Option<net::SocketAddr>,
) -> Self { ) -> Self {
Dispatcher::with_timeout( Dispatcher::with_timeout(
stream, stream,
@ -195,6 +196,7 @@ where
expect, expect,
upgrade, upgrade,
on_connect, on_connect,
peer_addr,
) )
} }
@ -209,6 +211,7 @@ where
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>, upgrade: Option<CloneableService<U>>,
on_connect: Option<Box<dyn DataFactory>>, on_connect: Option<Box<dyn DataFactory>>,
peer_addr: Option<net::SocketAddr>,
) -> Self { ) -> Self {
let keepalive = config.keep_alive_enabled(); let keepalive = config.keep_alive_enabled();
let flags = if keepalive { let flags = if keepalive {
@ -232,7 +235,6 @@ where
payload: None, payload: None,
state: State::None, state: State::None,
error: None, error: None,
peer_addr: io.peer_addr(),
messages: VecDeque::new(), messages: VecDeque::new(),
io, io,
codec, codec,
@ -242,6 +244,7 @@ where
upgrade, upgrade,
on_connect, on_connect,
flags, flags,
peer_addr,
ka_expire, ka_expire,
ka_timer, ka_timer,
}), }),
@ -251,7 +254,7 @@ where
impl<T, S, B, X, U> InnerDispatcher<T, S, B, X, U> impl<T, S, B, X, U> InnerDispatcher<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@ -261,14 +264,14 @@ where
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>, U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
fn can_read(&self) -> bool { fn can_read(&self, cx: &mut Context<'_>) -> bool {
if self if self
.flags .flags
.intersects(Flags::READ_DISCONNECT | Flags::UPGRADE) .intersects(Flags::READ_DISCONNECT | Flags::UPGRADE)
{ {
false false
} else if let Some(ref info) = self.payload { } else if let Some(ref info) = self.payload {
info.need_read() == PayloadStatus::Read info.need_read(cx) == PayloadStatus::Read
} else { } else {
true true
} }
@ -287,7 +290,7 @@ where
/// ///
/// true - got whouldblock /// true - got whouldblock
/// false - didnt get whouldblock /// false - didnt get whouldblock
fn poll_flush(&mut self) -> Result<bool, DispatchError> { fn poll_flush(&mut self, cx: &mut Context<'_>) -> Result<bool, DispatchError> {
if self.write_buf.is_empty() { if self.write_buf.is_empty() {
return Ok(false); return Ok(false);
} }
@ -295,31 +298,31 @@ where
let len = self.write_buf.len(); let len = self.write_buf.len();
let mut written = 0; let mut written = 0;
while written < len { while written < len {
match self.io.write(&self.write_buf[written..]) { match unsafe { Pin::new_unchecked(&mut self.io) }
Ok(0) => { .poll_write(cx, &self.write_buf[written..])
{
Poll::Ready(Ok(0)) => {
return Err(DispatchError::Io(io::Error::new( return Err(DispatchError::Io(io::Error::new(
io::ErrorKind::WriteZero, io::ErrorKind::WriteZero,
"", "",
))); )));
} }
Ok(n) => { Poll::Ready(Ok(n)) => {
written += n; written += n;
} }
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { Poll::Pending => {
if written > 0 { if written > 0 {
let _ = self.write_buf.split_to(written); self.write_buf.advance(written);
} }
return Ok(true); return Ok(true);
} }
Err(err) => return Err(DispatchError::Io(err)), Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)),
} }
} }
if written > 0 {
if written == self.write_buf.len() { if written == self.write_buf.len() {
unsafe { self.write_buf.set_len(0) } unsafe { self.write_buf.set_len(0) }
} else { } else {
let _ = self.write_buf.split_to(written); self.write_buf.advance(written);
}
} }
Ok(false) Ok(false)
} }
@ -350,12 +353,15 @@ where
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
} }
fn poll_response(&mut self) -> Result<PollResponse, DispatchError> { fn poll_response(
&mut self,
cx: &mut Context<'_>,
) -> Result<PollResponse, DispatchError> {
loop { loop {
let state = match self.state { let state = match self.state {
State::None => match self.messages.pop_front() { State::None => match self.messages.pop_front() {
Some(DispatcherMessage::Item(req)) => { Some(DispatcherMessage::Item(req)) => {
Some(self.handle_request(req)?) Some(self.handle_request(req, cx)?)
} }
Some(DispatcherMessage::Error(res)) => { Some(DispatcherMessage::Error(res)) => {
Some(self.send_response(res, ResponseBody::Other(Body::Empty))?) Some(self.send_response(res, ResponseBody::Other(Body::Empty))?)
@ -365,54 +371,58 @@ where
} }
None => None, None => None,
}, },
State::ExpectCall(ref mut fut) => match fut.poll() { State::ExpectCall(ref mut fut) => {
Ok(Async::Ready(req)) => { match unsafe { Pin::new_unchecked(fut) }.poll(cx) {
Poll::Ready(Ok(req)) => {
self.send_continue(); self.send_continue();
self.state = State::ServiceCall(self.service.call(req)); self.state = State::ServiceCall(self.service.call(req));
continue; continue;
} }
Ok(Async::NotReady) => None, Poll::Ready(Err(e)) => {
Err(e) => {
let res: Response = e.into().into(); let res: Response = e.into().into();
let (res, body) = res.replace_body(()); let (res, body) = res.replace_body(());
Some(self.send_response(res, body.into_body())?) Some(self.send_response(res, body.into_body())?)
} }
}, Poll::Pending => None,
State::ServiceCall(ref mut fut) => match fut.poll() { }
Ok(Async::Ready(res)) => { }
State::ServiceCall(ref mut fut) => {
match unsafe { Pin::new_unchecked(fut) }.poll(cx) {
Poll::Ready(Ok(res)) => {
let (res, body) = res.into().replace_body(()); let (res, body) = res.into().replace_body(());
self.state = self.send_response(res, body)?; self.state = self.send_response(res, body)?;
continue; continue;
} }
Ok(Async::NotReady) => None, Poll::Ready(Err(e)) => {
Err(e) => {
let res: Response = e.into().into(); let res: Response = e.into().into();
let (res, body) = res.replace_body(()); let (res, body) = res.replace_body(());
Some(self.send_response(res, body.into_body())?) Some(self.send_response(res, body.into_body())?)
} }
}, Poll::Pending => None,
}
}
State::SendPayload(ref mut stream) => { State::SendPayload(ref mut stream) => {
loop { loop {
if self.write_buf.len() < HW_BUFFER_SIZE { if self.write_buf.len() < HW_BUFFER_SIZE {
match stream match stream.poll_next(cx) {
.poll_next() Poll::Ready(Some(Ok(item))) => {
.map_err(|_| DispatchError::Unknown)?
{
Async::Ready(Some(item)) => {
self.codec.encode( self.codec.encode(
Message::Chunk(Some(item)), Message::Chunk(Some(item)),
&mut self.write_buf, &mut self.write_buf,
)?; )?;
continue; continue;
} }
Async::Ready(None) => { Poll::Ready(None) => {
self.codec.encode( self.codec.encode(
Message::Chunk(None), Message::Chunk(None),
&mut self.write_buf, &mut self.write_buf,
)?; )?;
self.state = State::None; self.state = State::None;
} }
Async::NotReady => return Ok(PollResponse::DoNothing), Poll::Ready(Some(Err(_))) => {
return Err(DispatchError::Unknown)
}
Poll::Pending => return Ok(PollResponse::DoNothing),
} }
} else { } else {
return Ok(PollResponse::DrainWriteBuf); return Ok(PollResponse::DrainWriteBuf);
@ -433,7 +443,7 @@ where
// if read-backpressure is enabled and we consumed some data. // if read-backpressure is enabled and we consumed some data.
// we may read more data and retry // we may read more data and retry
if self.state.is_call() { if self.state.is_call() {
if self.poll_request()? { if self.poll_request(cx)? {
continue; continue;
} }
} else if !self.messages.is_empty() { } else if !self.messages.is_empty() {
@ -446,17 +456,21 @@ where
Ok(PollResponse::DoNothing) Ok(PollResponse::DoNothing)
} }
fn handle_request(&mut self, req: Request) -> Result<State<S, B, X>, DispatchError> { fn handle_request(
&mut self,
req: Request,
cx: &mut Context<'_>,
) -> Result<State<S, B, X>, DispatchError> {
// Handle `EXPECT: 100-Continue` header // Handle `EXPECT: 100-Continue` header
let req = if req.head().expect() { let req = if req.head().expect() {
let mut task = self.expect.call(req); let mut task = self.expect.call(req);
match task.poll() { match unsafe { Pin::new_unchecked(&mut task) }.poll(cx) {
Ok(Async::Ready(req)) => { Poll::Ready(Ok(req)) => {
self.send_continue(); self.send_continue();
req req
} }
Ok(Async::NotReady) => return Ok(State::ExpectCall(task)), Poll::Pending => return Ok(State::ExpectCall(task)),
Err(e) => { Poll::Ready(Err(e)) => {
let e = e.into(); let e = e.into();
let res: Response = e.into(); let res: Response = e.into();
let (res, body) = res.replace_body(()); let (res, body) = res.replace_body(());
@ -469,13 +483,13 @@ where
// Call service // Call service
let mut task = self.service.call(req); let mut task = self.service.call(req);
match task.poll() { match unsafe { Pin::new_unchecked(&mut task) }.poll(cx) {
Ok(Async::Ready(res)) => { Poll::Ready(Ok(res)) => {
let (res, body) = res.into().replace_body(()); let (res, body) = res.into().replace_body(());
self.send_response(res, body) self.send_response(res, body)
} }
Ok(Async::NotReady) => Ok(State::ServiceCall(task)), Poll::Pending => Ok(State::ServiceCall(task)),
Err(e) => { Poll::Ready(Err(e)) => {
let res: Response = e.into().into(); let res: Response = e.into().into();
let (res, body) = res.replace_body(()); let (res, body) = res.replace_body(());
self.send_response(res, body.into_body()) self.send_response(res, body.into_body())
@ -484,9 +498,12 @@ where
} }
/// Process one incoming requests /// Process one incoming requests
pub(self) fn poll_request(&mut self) -> Result<bool, DispatchError> { pub(self) fn poll_request(
&mut self,
cx: &mut Context<'_>,
) -> Result<bool, DispatchError> {
// limit a mount of non processed requests // limit a mount of non processed requests
if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read() { if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read(cx) {
return Ok(false); return Ok(false);
} }
@ -521,7 +538,7 @@ where
// handle request early // handle request early
if self.state.is_empty() { if self.state.is_empty() {
self.state = self.handle_request(req)?; self.state = self.handle_request(req, cx)?;
} else { } else {
self.messages.push_back(DispatcherMessage::Item(req)); self.messages.push_back(DispatcherMessage::Item(req));
} }
@ -587,12 +604,12 @@ where
} }
/// keep-alive timer /// keep-alive timer
fn poll_keepalive(&mut self) -> Result<(), DispatchError> { fn poll_keepalive(&mut self, cx: &mut Context<'_>) -> Result<(), DispatchError> {
if self.ka_timer.is_none() { if self.ka_timer.is_none() {
// shutdown timeout // shutdown timeout
if self.flags.contains(Flags::SHUTDOWN) { if self.flags.contains(Flags::SHUTDOWN) {
if let Some(interval) = self.codec.config().client_disconnect_timer() { if let Some(interval) = self.codec.config().client_disconnect_timer() {
self.ka_timer = Some(Delay::new(interval)); self.ka_timer = Some(delay_until(interval));
} else { } else {
self.flags.insert(Flags::READ_DISCONNECT); self.flags.insert(Flags::READ_DISCONNECT);
if let Some(mut payload) = self.payload.take() { if let Some(mut payload) = self.payload.take() {
@ -605,11 +622,8 @@ where
} }
} }
match self.ka_timer.as_mut().unwrap().poll().map_err(|e| { match Pin::new(&mut self.ka_timer.as_mut().unwrap()).poll(cx) {
error!("Timer error {:?}", e); Poll::Ready(()) => {
DispatchError::Unknown
})? {
Async::Ready(_) => {
// if we get timeout during shutdown, drop connection // if we get timeout during shutdown, drop connection
if self.flags.contains(Flags::SHUTDOWN) { if self.flags.contains(Flags::SHUTDOWN) {
return Err(DispatchError::DisconnectTimeout); return Err(DispatchError::DisconnectTimeout);
@ -624,9 +638,9 @@ where
if let Some(deadline) = if let Some(deadline) =
self.codec.config().client_disconnect_timer() self.codec.config().client_disconnect_timer()
{ {
if let Some(timer) = self.ka_timer.as_mut() { if let Some(mut timer) = self.ka_timer.as_mut() {
timer.reset(deadline); timer.reset(deadline);
let _ = timer.poll(); let _ = Pin::new(&mut timer).poll(cx);
} }
} else { } else {
// no shutdown timeout, drop socket // no shutdown timeout, drop socket
@ -650,26 +664,26 @@ where
} else if let Some(deadline) = } else if let Some(deadline) =
self.codec.config().keep_alive_expire() self.codec.config().keep_alive_expire()
{ {
if let Some(timer) = self.ka_timer.as_mut() { if let Some(mut timer) = self.ka_timer.as_mut() {
timer.reset(deadline); timer.reset(deadline);
let _ = timer.poll(); let _ = Pin::new(&mut timer).poll(cx);
} }
} }
} else if let Some(timer) = self.ka_timer.as_mut() { } else if let Some(mut timer) = self.ka_timer.as_mut() {
timer.reset(self.ka_expire); timer.reset(self.ka_expire);
let _ = timer.poll(); let _ = Pin::new(&mut timer).poll(cx);
} }
} }
Async::NotReady => (), Poll::Pending => (),
} }
Ok(()) Ok(())
} }
} }
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U> impl<T, S, B, X, U> Unpin for Dispatcher<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@ -679,27 +693,42 @@ where
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>, U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
type Item = (); }
type Error = DispatchError;
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>,
S::Error: Into<Error>,
S::Response: Into<Response<B>>,
B: MessageBody,
X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
type Output = Result<(), DispatchError>;
#[inline] #[inline]
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.inner { match self.as_mut().inner {
DispatcherState::Normal(ref mut inner) => { DispatcherState::Normal(ref mut inner) => {
inner.poll_keepalive()?; inner.poll_keepalive(cx)?;
if inner.flags.contains(Flags::SHUTDOWN) { if inner.flags.contains(Flags::SHUTDOWN) {
if inner.flags.contains(Flags::WRITE_DISCONNECT) { if inner.flags.contains(Flags::WRITE_DISCONNECT) {
Ok(Async::Ready(())) Poll::Ready(Ok(()))
} else { } else {
// flush buffer // flush buffer
inner.poll_flush()?; inner.poll_flush(cx)?;
if !inner.write_buf.is_empty() { if !inner.write_buf.is_empty() {
Ok(Async::NotReady) Poll::Pending
} else { } else {
match inner.io.shutdown()? { match Pin::new(&mut inner.io).poll_shutdown(cx) {
Async::Ready(_) => Ok(Async::Ready(())), Poll::Ready(res) => {
Async::NotReady => Ok(Async::NotReady), Poll::Ready(res.map_err(DispatchError::from))
}
Poll::Pending => Poll::Pending,
} }
} }
} }
@ -707,12 +736,12 @@ where
// read socket into a buf // read socket into a buf
let should_disconnect = let should_disconnect =
if !inner.flags.contains(Flags::READ_DISCONNECT) { if !inner.flags.contains(Flags::READ_DISCONNECT) {
read_available(&mut inner.io, &mut inner.read_buf)? read_available(cx, &mut inner.io, &mut inner.read_buf)?
} else { } else {
None None
}; };
inner.poll_request()?; inner.poll_request(cx)?;
if let Some(true) = should_disconnect { if let Some(true) = should_disconnect {
inner.flags.insert(Flags::READ_DISCONNECT); inner.flags.insert(Flags::READ_DISCONNECT);
if let Some(mut payload) = inner.payload.take() { if let Some(mut payload) = inner.payload.take() {
@ -721,10 +750,12 @@ where
}; };
loop { loop {
if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE { let remaining =
inner.write_buf.reserve(HW_BUFFER_SIZE); inner.write_buf.capacity() - inner.write_buf.len();
if remaining < LW_BUFFER_SIZE {
inner.write_buf.reserve(HW_BUFFER_SIZE - remaining);
} }
let result = inner.poll_response()?; let result = inner.poll_response(cx)?;
let drain = result == PollResponse::DrainWriteBuf; let drain = result == PollResponse::DrainWriteBuf;
// switch to upgrade handler // switch to upgrade handler
@ -742,7 +773,7 @@ where
self.inner = DispatcherState::Upgrade( self.inner = DispatcherState::Upgrade(
inner.upgrade.unwrap().call((req, framed)), inner.upgrade.unwrap().call((req, framed)),
); );
return self.poll(); return self.poll(cx);
} else { } else {
panic!() panic!()
} }
@ -751,14 +782,14 @@ where
// we didnt get WouldBlock from write operation, // we didnt get WouldBlock from write operation,
// so data get written to kernel completely (OSX) // so data get written to kernel completely (OSX)
// and we have to write again otherwise response can get stuck // and we have to write again otherwise response can get stuck
if inner.poll_flush()? || !drain { if inner.poll_flush(cx)? || !drain {
break; break;
} }
} }
// client is gone // client is gone
if inner.flags.contains(Flags::WRITE_DISCONNECT) { if inner.flags.contains(Flags::WRITE_DISCONNECT) {
return Ok(Async::Ready(())); return Poll::Ready(Ok(()));
} }
let is_empty = inner.state.is_empty(); let is_empty = inner.state.is_empty();
@ -771,58 +802,64 @@ where
// keep-alive and stream errors // keep-alive and stream errors
if is_empty && inner.write_buf.is_empty() { if is_empty && inner.write_buf.is_empty() {
if let Some(err) = inner.error.take() { if let Some(err) = inner.error.take() {
Err(err) Poll::Ready(Err(err))
} }
// disconnect if keep-alive is not enabled // disconnect if keep-alive is not enabled
else if inner.flags.contains(Flags::STARTED) else if inner.flags.contains(Flags::STARTED)
&& !inner.flags.intersects(Flags::KEEPALIVE) && !inner.flags.intersects(Flags::KEEPALIVE)
{ {
inner.flags.insert(Flags::SHUTDOWN); inner.flags.insert(Flags::SHUTDOWN);
self.poll() self.poll(cx)
} }
// disconnect if shutdown // disconnect if shutdown
else if inner.flags.contains(Flags::SHUTDOWN) { else if inner.flags.contains(Flags::SHUTDOWN) {
self.poll() self.poll(cx)
} else { } else {
Ok(Async::NotReady) Poll::Pending
} }
} else { } else {
Ok(Async::NotReady) Poll::Pending
} }
} }
} }
DispatcherState::Upgrade(ref mut fut) => fut.poll().map_err(|e| { DispatcherState::Upgrade(ref mut fut) => {
unsafe { Pin::new_unchecked(fut) }.poll(cx).map_err(|e| {
error!("Upgrade handler error: {}", e); error!("Upgrade handler error: {}", e);
DispatchError::Upgrade DispatchError::Upgrade
}), })
}
DispatcherState::None => panic!(), DispatcherState::None => panic!(),
} }
} }
} }
fn read_available<T>(io: &mut T, buf: &mut BytesMut) -> Result<Option<bool>, io::Error> fn read_available<T>(
cx: &mut Context<'_>,
io: &mut T,
buf: &mut BytesMut,
) -> Result<Option<bool>, io::Error>
where where
T: io::Read, T: AsyncRead + Unpin,
{ {
let mut read_some = false; let mut read_some = false;
loop { loop {
if buf.remaining_mut() < LW_BUFFER_SIZE { let remaining = buf.capacity() - buf.len();
buf.reserve(HW_BUFFER_SIZE); if remaining < LW_BUFFER_SIZE {
buf.reserve(HW_BUFFER_SIZE - remaining);
} }
let read = unsafe { io.read(buf.bytes_mut()) }; match read(cx, io, buf) {
match read { Poll::Pending => {
Ok(n) => { return if read_some { Ok(Some(false)) } else { Ok(None) };
}
Poll::Ready(Ok(n)) => {
if n == 0 { if n == 0 {
return Ok(Some(true)); return Ok(Some(true));
} else { } else {
read_some = true; read_some = true;
unsafe {
buf.advance_mut(n);
} }
} }
} Poll::Ready(Err(e)) => {
Err(e) => {
return if e.kind() == io::ErrorKind::WouldBlock { return if e.kind() == io::ErrorKind::WouldBlock {
if read_some { if read_some {
Ok(Some(false)) Ok(Some(false))
@ -833,26 +870,36 @@ where
Ok(Some(true)) Ok(Some(true))
} else { } else {
Err(e) Err(e)
}; }
} }
} }
} }
} }
fn read<T>(
cx: &mut Context<'_>,
io: &mut T,
buf: &mut BytesMut,
) -> Poll<Result<usize, io::Error>>
where
T: AsyncRead + Unpin,
{
Pin::new(io).poll_read_buf(cx, buf)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_service::IntoService; use actix_service::IntoService;
use futures::future::{lazy, ok}; use futures_util::future::{lazy, ok};
use super::*; use super::*;
use crate::error::Error; use crate::error::Error;
use crate::h1::{ExpectHandler, UpgradeHandler}; use crate::h1::{ExpectHandler, UpgradeHandler};
use crate::test::TestBuffer; use crate::test::TestBuffer;
#[test] #[actix_rt::test]
fn test_req_parse_err() { async fn test_req_parse_err() {
let mut sys = actix_rt::System::new("test"); lazy(|cx| {
let _ = sys.block_on(lazy(|| {
let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n");
let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<TestBuffer>>::new( let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<TestBuffer>>::new(
@ -864,14 +911,18 @@ mod tests {
CloneableService::new(ExpectHandler), CloneableService::new(ExpectHandler),
None, None,
None, None,
None,
); );
assert!(h1.poll().is_err()); match Pin::new(&mut h1).poll(cx) {
Poll::Pending => panic!(),
Poll::Ready(res) => assert!(res.is_err()),
}
if let DispatcherState::Normal(ref inner) = h1.inner { if let DispatcherState::Normal(ref inner) = h1.inner {
assert!(inner.flags.contains(Flags::READ_DISCONNECT)); assert!(inner.flags.contains(Flags::READ_DISCONNECT));
assert_eq!(&inner.io.write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n"); assert_eq!(&inner.io.write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n");
} }
ok::<_, ()>(()) })
})); .await;
} }
} }

View File

@ -1,23 +1,18 @@
#![allow(unused_imports, unused_variables, dead_code)]
use std::fmt::Write as FmtWrite;
use std::io::Write; use std::io::Write;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::str::FromStr; use std::ptr::copy_nonoverlapping;
use std::{cmp, fmt, io, mem}; use std::slice::from_raw_parts_mut;
use std::rc::Rc; use std::{cmp, io};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{buf::BufMutExt, BufMut, BytesMut};
use crate::body::BodySize; use crate::body::BodySize;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::header::{map, ContentEncoding}; use crate::header::map;
use crate::helpers; use crate::helpers;
use crate::http::header::{ use crate::http::header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, use crate::http::{HeaderMap, StatusCode, Version};
}; use crate::message::{ConnectionType, RequestHeadType};
use crate::http::{HeaderMap, Method, StatusCode, Version};
use crate::message::{ConnectionType, Head, RequestHead, ResponseHead, RequestHeadType};
use crate::request::Request;
use crate::response::Response; use crate::response::Response;
const AVERAGE_HEADER_SIZE: usize = 30; const AVERAGE_HEADER_SIZE: usize = 30;
@ -106,6 +101,7 @@ pub(crate) trait MessageType: Sized {
} else { } else {
dst.put_slice(b"\r\ncontent-length: "); dst.put_slice(b"\r\ncontent-length: ");
} }
#[allow(clippy::write_with_newline)]
write!(dst.writer(), "{}\r\n", len)?; write!(dst.writer(), "{}\r\n", len)?;
} }
BodySize::None => dst.put_slice(b"\r\n"), BodySize::None => dst.put_slice(b"\r\n"),
@ -134,17 +130,18 @@ pub(crate) trait MessageType: Sized {
// merging headers from head and extra headers. HeaderMap::new() does not allocate. // merging headers from head and extra headers. HeaderMap::new() does not allocate.
let empty_headers = HeaderMap::new(); let empty_headers = HeaderMap::new();
let extra_headers = self.extra_headers().unwrap_or(&empty_headers); let extra_headers = self.extra_headers().unwrap_or(&empty_headers);
let headers = self.headers().inner.iter() let headers = self
.filter(|(name, _)| { .headers()
!extra_headers.contains_key(*name) .inner
}) .iter()
.filter(|(name, _)| !extra_headers.contains_key(*name))
.chain(extra_headers.inner.iter()); .chain(extra_headers.inner.iter());
// write headers // write headers
let mut pos = 0; let mut pos = 0;
let mut has_date = false; let mut has_date = false;
let mut remaining = dst.remaining_mut(); let mut remaining = dst.capacity() - dst.len();
let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) }; let mut buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
for (key, value) in headers { for (key, value) in headers {
match *key { match *key {
CONNECTION => continue, CONNECTION => continue,
@ -158,61 +155,67 @@ pub(crate) trait MessageType: Sized {
match value { match value {
map::Value::One(ref val) => { map::Value::One(ref val) => {
let v = val.as_ref(); let v = val.as_ref();
let len = k.len() + v.len() + 4; let v_len = v.len();
let k_len = k.len();
let len = k_len + v_len + 4;
if len > remaining { if len > remaining {
unsafe { unsafe {
dst.advance_mut(pos); dst.advance_mut(pos);
} }
pos = 0; pos = 0;
dst.reserve(len * 2); dst.reserve(len * 2);
remaining = dst.remaining_mut(); remaining = dst.capacity() - dst.len();
unsafe { buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
buf = &mut *(dst.bytes_mut() as *mut _);
}
} }
// use upper Camel-Case // use upper Camel-Case
unsafe {
if camel_case { if camel_case {
write_camel_case(k, &mut buf[pos..pos + k.len()]); write_camel_case(k, from_raw_parts_mut(buf, k_len))
} else { } else {
buf[pos..pos + k.len()].copy_from_slice(k); write_data(k, buf, k_len)
} }
pos += k.len(); buf = buf.add(k_len);
buf[pos..pos + 2].copy_from_slice(b": "); write_data(b": ", buf, 2);
pos += 2; buf = buf.add(2);
buf[pos..pos + v.len()].copy_from_slice(v); write_data(v, buf, v_len);
pos += v.len(); buf = buf.add(v_len);
buf[pos..pos + 2].copy_from_slice(b"\r\n"); write_data(b"\r\n", buf, 2);
pos += 2; buf = buf.add(2);
pos += len;
remaining -= len; remaining -= len;
} }
}
map::Value::Multi(ref vec) => { map::Value::Multi(ref vec) => {
for val in vec { for val in vec {
let v = val.as_ref(); let v = val.as_ref();
let len = k.len() + v.len() + 4; let v_len = v.len();
let k_len = k.len();
let len = k_len + v_len + 4;
if len > remaining { if len > remaining {
unsafe { unsafe {
dst.advance_mut(pos); dst.advance_mut(pos);
} }
pos = 0; pos = 0;
dst.reserve(len * 2); dst.reserve(len * 2);
remaining = dst.remaining_mut(); remaining = dst.capacity() - dst.len();
unsafe { buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
buf = &mut *(dst.bytes_mut() as *mut _);
}
} }
// use upper Camel-Case // use upper Camel-Case
unsafe {
if camel_case { if camel_case {
write_camel_case(k, &mut buf[pos..pos + k.len()]); write_camel_case(k, from_raw_parts_mut(buf, k_len));
} else { } else {
buf[pos..pos + k.len()].copy_from_slice(k); write_data(k, buf, k_len);
} }
pos += k.len(); buf = buf.add(k_len);
buf[pos..pos + 2].copy_from_slice(b": "); write_data(b": ", buf, 2);
pos += 2; buf = buf.add(2);
buf[pos..pos + v.len()].copy_from_slice(v); write_data(v, buf, v_len);
pos += v.len(); buf = buf.add(v_len);
buf[pos..pos + 2].copy_from_slice(b"\r\n"); write_data(b"\r\n", buf, 2);
pos += 2; buf = buf.add(2);
};
pos += len;
remaining -= len; remaining -= len;
} }
} }
@ -297,6 +300,12 @@ impl MessageType for RequestHeadType {
Version::HTTP_10 => "HTTP/1.0", Version::HTTP_10 => "HTTP/1.0",
Version::HTTP_11 => "HTTP/1.1", Version::HTTP_11 => "HTTP/1.1",
Version::HTTP_2 => "HTTP/2.0", Version::HTTP_2 => "HTTP/2.0",
Version::HTTP_3 => "HTTP/3.0",
_ =>
return Err(io::Error::new(
io::ErrorKind::Other,
"unsupported version"
)),
} }
) )
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
@ -478,6 +487,10 @@ impl<'a> io::Write for Writer<'a> {
} }
} }
unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) {
copy_nonoverlapping(value.as_ptr(), buf, len);
}
fn write_camel_case(value: &[u8], buffer: &mut [u8]) { fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
let mut index = 0; let mut index = 0;
let key = value; let key = value;
@ -508,12 +521,14 @@ fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::rc::Rc;
use bytes::Bytes; use bytes::Bytes;
//use std::rc::Rc; use http::header::AUTHORIZATION;
use super::*; use super::*;
use crate::http::header::{HeaderValue, CONTENT_TYPE}; use crate::http::header::{HeaderValue, CONTENT_TYPE};
use http::header::AUTHORIZATION; use crate::RequestHead;
#[test] #[test]
fn test_chunked_te() { fn test_chunked_te() {
@ -524,7 +539,7 @@ mod tests {
assert!(enc.encode(b"", &mut bytes).ok().unwrap()); assert!(enc.encode(b"", &mut bytes).ok().unwrap());
} }
assert_eq!( assert_eq!(
bytes.take().freeze(), bytes.split().freeze(),
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n") Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")
); );
} }
@ -547,10 +562,12 @@ mod tests {
ConnectionType::Close, ConnectionType::Close,
&ServiceConfig::default(), &ServiceConfig::default(),
); );
assert_eq!( let data =
bytes.take().freeze(), String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
Bytes::from_static(b"\r\nContent-Length: 0\r\nConnection: close\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n") assert!(data.contains("Content-Length: 0\r\n"));
); assert!(data.contains("Connection: close\r\n"));
assert!(data.contains("Content-Type: plain/text\r\n"));
assert!(data.contains("Date: date\r\n"));
let _ = head.encode_headers( let _ = head.encode_headers(
&mut bytes, &mut bytes,
@ -559,10 +576,11 @@ mod tests {
ConnectionType::KeepAlive, ConnectionType::KeepAlive,
&ServiceConfig::default(), &ServiceConfig::default(),
); );
assert_eq!( let data =
bytes.take().freeze(), String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
Bytes::from_static(b"\r\nTransfer-Encoding: chunked\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n") assert!(data.contains("Transfer-Encoding: chunked\r\n"));
); assert!(data.contains("Content-Type: plain/text\r\n"));
assert!(data.contains("Date: date\r\n"));
let _ = head.encode_headers( let _ = head.encode_headers(
&mut bytes, &mut bytes,
@ -571,10 +589,11 @@ mod tests {
ConnectionType::KeepAlive, ConnectionType::KeepAlive,
&ServiceConfig::default(), &ServiceConfig::default(),
); );
assert_eq!( let data =
bytes.take().freeze(), String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
Bytes::from_static(b"\r\nContent-Length: 100\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n") assert!(data.contains("Content-Length: 100\r\n"));
); assert!(data.contains("Content-Type: plain/text\r\n"));
assert!(data.contains("Date: date\r\n"));
let mut head = RequestHead::default(); let mut head = RequestHead::default();
head.set_camel_case_headers(false); head.set_camel_case_headers(false);
@ -585,7 +604,6 @@ mod tests {
.append(CONTENT_TYPE, HeaderValue::from_static("xml")); .append(CONTENT_TYPE, HeaderValue::from_static("xml"));
let mut head = RequestHeadType::Owned(head); let mut head = RequestHeadType::Owned(head);
let _ = head.encode_headers( let _ = head.encode_headers(
&mut bytes, &mut bytes,
Version::HTTP_11, Version::HTTP_11,
@ -593,10 +611,12 @@ mod tests {
ConnectionType::KeepAlive, ConnectionType::KeepAlive,
&ServiceConfig::default(), &ServiceConfig::default(),
); );
assert_eq!( let data =
bytes.take().freeze(), String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
Bytes::from_static(b"\r\ntransfer-encoding: chunked\r\ndate: date\r\ncontent-type: xml\r\ncontent-type: plain/text\r\n\r\n") assert!(data.contains("transfer-encoding: chunked\r\n"));
); assert!(data.contains("content-type: xml\r\n"));
assert!(data.contains("content-type: plain/text\r\n"));
assert!(data.contains("date: date\r\n"));
} }
#[test] #[test]
@ -604,10 +624,16 @@ mod tests {
let mut bytes = BytesMut::with_capacity(2048); let mut bytes = BytesMut::with_capacity(2048);
let mut head = RequestHead::default(); let mut head = RequestHead::default();
head.headers.insert(AUTHORIZATION, HeaderValue::from_static("some authorization")); head.headers.insert(
AUTHORIZATION,
HeaderValue::from_static("some authorization"),
);
let mut extra_headers = HeaderMap::new(); let mut extra_headers = HeaderMap::new();
extra_headers.insert(AUTHORIZATION,HeaderValue::from_static("another authorization")); extra_headers.insert(
AUTHORIZATION,
HeaderValue::from_static("another authorization"),
);
extra_headers.insert(DATE, HeaderValue::from_static("date")); extra_headers.insert(DATE, HeaderValue::from_static("date"));
let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers)); let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers));
@ -619,9 +645,11 @@ mod tests {
ConnectionType::Close, ConnectionType::Close,
&ServiceConfig::default(), &ServiceConfig::default(),
); );
assert_eq!( let data =
bytes.take().freeze(), String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
Bytes::from_static(b"\r\ncontent-length: 0\r\nconnection: close\r\nauthorization: another authorization\r\ndate: date\r\n\r\n") assert!(data.contains("content-length: 0\r\n"));
); assert!(data.contains("connection: close\r\n"));
assert!(data.contains("authorization: another authorization\r\n"));
assert!(data.contains("date: date\r\n"));
} }
} }

View File

@ -1,23 +1,23 @@
use actix_server_config::ServerConfig; use std::task::{Context, Poll};
use actix_service::{NewService, Service};
use futures::future::{ok, FutureResult}; use actix_service::{Service, ServiceFactory};
use futures::{Async, Poll}; use futures_util::future::{ok, Ready};
use crate::error::Error; use crate::error::Error;
use crate::request::Request; use crate::request::Request;
pub struct ExpectHandler; pub struct ExpectHandler;
impl NewService for ExpectHandler { impl ServiceFactory for ExpectHandler {
type Config = ServerConfig; type Config = ();
type Request = Request; type Request = Request;
type Response = Request; type Response = Request;
type Error = Error; type Error = Error;
type Service = ExpectHandler; type Service = ExpectHandler;
type InitError = Error; type InitError = Error;
type Future = FutureResult<Self::Service, Self::InitError>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &ServerConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
ok(ExpectHandler) ok(ExpectHandler)
} }
} }
@ -26,10 +26,10 @@ impl Service for ExpectHandler {
type Request = Request; type Request = Request;
type Response = Request; type Response = Request;
type Error = Error; type Error = Error;
type Future = FutureResult<Self::Response, Self::Error>; type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Ok(Async::Ready(())) Poll::Ready(Ok(()))
} }
fn call(&mut self, req: Request) -> Self::Future { fn call(&mut self, req: Request) -> Self::Future {

View File

@ -1,12 +1,13 @@
//! Payload stream //! Payload stream
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::pin::Pin;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use std::task::{Context, Poll};
use actix_utils::task::LocalWaker;
use bytes::Bytes; use bytes::Bytes;
use futures::task::current as current_task; use futures_core::Stream;
use futures::task::Task;
use futures::{Async, Poll, Stream};
use crate::error::PayloadError; use crate::error::PayloadError;
@ -77,15 +78,24 @@ impl Payload {
pub fn unread_data(&mut self, data: Bytes) { pub fn unread_data(&mut self, data: Bytes) {
self.inner.borrow_mut().unread_data(data); self.inner.borrow_mut().unread_data(data);
} }
#[inline]
pub fn readany(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> {
self.inner.borrow_mut().readany(cx)
}
} }
impl Stream for Payload { impl Stream for Payload {
type Item = Bytes; type Item = Result<Bytes, PayloadError>;
type Error = PayloadError;
#[inline] fn poll_next(
fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> { self: Pin<&mut Self>,
self.inner.borrow_mut().readany() cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> {
self.inner.borrow_mut().readany(cx)
} }
} }
@ -117,19 +127,14 @@ impl PayloadSender {
} }
#[inline] #[inline]
pub fn need_read(&self) -> PayloadStatus { pub fn need_read(&self, cx: &mut Context<'_>) -> PayloadStatus {
// we check need_read only if Payload (other side) is alive, // we check need_read only if Payload (other side) is alive,
// otherwise always return true (consume payload) // otherwise always return true (consume payload)
if let Some(shared) = self.inner.upgrade() { if let Some(shared) = self.inner.upgrade() {
if shared.borrow().need_read { if shared.borrow().need_read {
PayloadStatus::Read PayloadStatus::Read
} else { } else {
#[cfg(not(test))] shared.borrow_mut().io_task.register(cx.waker());
{
if shared.borrow_mut().io_task.is_none() {
shared.borrow_mut().io_task = Some(current_task());
}
}
PayloadStatus::Pause PayloadStatus::Pause
} }
} else { } else {
@ -145,8 +150,8 @@ struct Inner {
err: Option<PayloadError>, err: Option<PayloadError>,
need_read: bool, need_read: bool,
items: VecDeque<Bytes>, items: VecDeque<Bytes>,
task: Option<Task>, task: LocalWaker,
io_task: Option<Task>, io_task: LocalWaker,
} }
impl Inner { impl Inner {
@ -157,8 +162,8 @@ impl Inner {
err: None, err: None,
items: VecDeque::new(), items: VecDeque::new(),
need_read: true, need_read: true,
task: None, task: LocalWaker::new(),
io_task: None, io_task: LocalWaker::new(),
} }
} }
@ -178,7 +183,7 @@ impl Inner {
self.items.push_back(data); self.items.push_back(data);
self.need_read = self.len < MAX_BUFFER_SIZE; self.need_read = self.len < MAX_BUFFER_SIZE;
if let Some(task) = self.task.take() { if let Some(task) = self.task.take() {
task.notify() task.wake()
} }
} }
@ -187,34 +192,28 @@ impl Inner {
self.len self.len
} }
fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> { fn readany(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> {
if let Some(data) = self.items.pop_front() { if let Some(data) = self.items.pop_front() {
self.len -= data.len(); self.len -= data.len();
self.need_read = self.len < MAX_BUFFER_SIZE; self.need_read = self.len < MAX_BUFFER_SIZE;
if self.need_read && self.task.is_none() && !self.eof { if self.need_read && !self.eof {
self.task = Some(current_task()); self.task.register(cx.waker());
} }
if let Some(task) = self.io_task.take() { self.io_task.wake();
task.notify() Poll::Ready(Some(Ok(data)))
}
Ok(Async::Ready(Some(data)))
} else if let Some(err) = self.err.take() { } else if let Some(err) = self.err.take() {
Err(err) Poll::Ready(Some(Err(err)))
} else if self.eof { } else if self.eof {
Ok(Async::Ready(None)) Poll::Ready(None)
} else { } else {
self.need_read = true; self.need_read = true;
#[cfg(not(test))] self.task.register(cx.waker());
{ self.io_task.wake();
if self.task.is_none() { Poll::Pending
self.task = Some(current_task());
}
if let Some(task) = self.io_task.take() {
task.notify()
}
}
Ok(Async::NotReady)
} }
} }
@ -227,14 +226,10 @@ impl Inner {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use actix_rt::Runtime; use futures_util::future::poll_fn;
use futures::future::{lazy, result};
#[test] #[actix_rt::test]
fn test_unread_data() { async fn test_unread_data() {
Runtime::new()
.unwrap()
.block_on(lazy(|| {
let (_, mut payload) = Payload::create(false); let (_, mut payload) = Payload::create(false);
payload.unread_data(Bytes::from("data")); payload.unread_data(Bytes::from("data"));
@ -242,13 +237,8 @@ mod tests {
assert_eq!(payload.len(), 4); assert_eq!(payload.len(), 4);
assert_eq!( assert_eq!(
Async::Ready(Some(Bytes::from("data"))), Bytes::from("data"),
payload.poll().ok().unwrap() poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap()
); );
let res: Result<(), ()> = Ok(());
result(res)
}))
.unwrap();
} }
} }

View File

@ -1,16 +1,19 @@
use std::fmt; use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll};
use std::{fmt, net};
use actix_codec::Framed; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_rt::net::TcpStream;
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
use futures::future::{ok, FutureResult}; use futures_core::ready;
use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; use futures_util::future::{ok, Ready};
use crate::body::MessageBody; use crate::body::MessageBody;
use crate::cloneable::CloneableService; use crate::cloneable::CloneableService;
use crate::config::{KeepAlive, ServiceConfig}; use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error, ParseError}; use crate::error::{DispatchError, Error, ParseError};
use crate::helpers::DataFactory; use crate::helpers::DataFactory;
use crate::request::Request; use crate::request::Request;
@ -20,43 +23,32 @@ use super::codec::Codec;
use super::dispatcher::Dispatcher; use super::dispatcher::Dispatcher;
use super::{ExpectHandler, Message, UpgradeHandler}; use super::{ExpectHandler, Message, UpgradeHandler};
/// `NewService` implementation for HTTP1 transport /// `ServiceFactory` implementation for HTTP1 transport
pub struct H1Service<T, P, S, B, X = ExpectHandler, U = UpgradeHandler<T>> { pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler<T>> {
srv: S, srv: S,
cfg: ServiceConfig, cfg: ServiceConfig,
expect: X, expect: X,
upgrade: Option<U>, upgrade: Option<U>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B> H1Service<T, P, S, B> impl<T, S, B> H1Service<T, S, B>
where where
S: NewService<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody, B: MessageBody,
{ {
/// Create new `HttpService` instance with default config.
pub fn new<F: IntoNewService<S>>(service: F) -> Self {
let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0);
H1Service {
cfg,
srv: service.into_new_service(),
expect: ExpectHandler,
upgrade: None,
on_connect: None,
_t: PhantomData,
}
}
/// Create new `HttpService` instance with config. /// Create new `HttpService` instance with config.
pub fn with_config<F: IntoNewService<S>>(cfg: ServiceConfig, service: F) -> Self { pub(crate) fn with_config<F: IntoServiceFactory<S>>(
cfg: ServiceConfig,
service: F,
) -> Self {
H1Service { H1Service {
cfg, cfg,
srv: service.into_new_service(), srv: service.into_factory(),
expect: ExpectHandler, expect: ExpectHandler,
upgrade: None, upgrade: None,
on_connect: None, on_connect: None,
@ -65,17 +57,153 @@ where
} }
} }
impl<T, P, S, B, X, U> H1Service<T, P, S, B, X, U> impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
where where
S: NewService<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<TcpStream, Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
{
/// Create simple tcp stream service
pub fn tcp(
self,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = DispatchError,
InitError = (),
> {
pipeline_factory(|io: TcpStream| {
let peer_addr = io.peer_addr().ok();
ok((io, peer_addr))
})
.and_then(self)
}
}
#[cfg(feature = "openssl")]
mod openssl {
use super::*;
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
use actix_tls::{openssl::HandshakeError, SslError};
impl<S, B, X, U> H1Service<SslStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<SslStream<TcpStream>, Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
{
/// Create openssl based service
pub fn openssl(
self,
acceptor: SslAcceptor,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<HandshakeError<TcpStream>, DispatchError>,
InitError = (),
> {
pipeline_factory(
Acceptor::new(acceptor)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(|io: SslStream<TcpStream>| {
let peer_addr = io.get_ref().peer_addr().ok();
ok((io, peer_addr))
})
.and_then(self.map_err(SslError::Service))
}
}
}
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
use actix_tls::SslError;
use std::{fmt, io};
impl<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<TlsStream<TcpStream>, Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
{
/// Create rustls based service
pub fn rustls(
self,
config: ServerConfig,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<io::Error, DispatchError>,
InitError = (),
> {
pipeline_factory(
Acceptor::new(config)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
ok((io, peer_addr))
})
.and_then(self.map_err(SslError::Service))
}
}
}
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
B: MessageBody, B: MessageBody,
{ {
pub fn expect<X1>(self, expect: X1) -> H1Service<T, P, S, B, X1, U> pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
where where
X1: NewService<Request = Request, Response = Request>, X1: ServiceFactory<Request = Request, Response = Request>,
X1::Error: Into<Error>, X1::Error: Into<Error>,
X1::InitError: fmt::Debug, X1::InitError: fmt::Debug,
{ {
@ -89,9 +217,9 @@ where
} }
} }
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> H1Service<T, P, S, B, X, U1> pub fn upgrade<U1>(self, upgrade: Option<U1>) -> H1Service<T, S, B, X, U1>
where where
U1: NewService<Request = (Request, Framed<T, Codec>), Response = ()>, U1: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
U1::Error: fmt::Display, U1::Error: fmt::Display,
U1::InitError: fmt::Debug, U1::InitError: fmt::Debug,
{ {
@ -115,38 +243,34 @@ where
} }
} }
impl<T, P, S, B, X, U> NewService for H1Service<T, P, S, B, X, U> impl<T, S, B, X, U> ServiceFactory for H1Service<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: NewService<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
B: MessageBody, B: MessageBody,
X: NewService<Config = SrvConfig, Request = Request, Response = Request>, X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService< U: ServiceFactory<Config = (), Request = (Request, Framed<T, Codec>), Response = ()>,
Config = SrvConfig, U::Error: fmt::Display + Into<Error>,
Request = (Request, Framed<T, Codec>),
Response = (),
>,
U::Error: fmt::Display,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
type Config = SrvConfig; type Config = ();
type Request = Io<T, P>; type Request = (T, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type InitError = (); type InitError = ();
type Service = H1ServiceHandler<T, P, S::Service, B, X::Service, U::Service>; type Service = H1ServiceHandler<T, S::Service, B, X::Service, U::Service>;
type Future = H1ServiceResponse<T, P, S, B, X, U>; type Future = H1ServiceResponse<T, S, B, X, U>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
H1ServiceResponse { H1ServiceResponse {
fut: self.srv.new_service(cfg).into_future(), fut: self.srv.new_service(()),
fut_ex: Some(self.expect.new_service(cfg)), fut_ex: Some(self.expect.new_service(())),
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(cfg)), fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())),
expect: None, expect: None,
upgrade: None, upgrade: None,
on_connect: self.on_connect.clone(), on_connect: self.on_connect.clone(),
@ -157,88 +281,99 @@ where
} }
#[doc(hidden)] #[doc(hidden)]
pub struct H1ServiceResponse<T, P, S, B, X, U> #[pin_project::pin_project]
pub struct H1ServiceResponse<T, S, B, X, U>
where where
S: NewService<Request = Request>, S: ServiceFactory<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
X: NewService<Request = Request, Response = Request>, X: ServiceFactory<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>, U: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
#[pin]
fut: S::Future, fut: S::Future,
#[pin]
fut_ex: Option<X::Future>, fut_ex: Option<X::Future>,
#[pin]
fut_upg: Option<U::Future>, fut_upg: Option<U::Future>,
expect: Option<X::Service>, expect: Option<X::Service>,
upgrade: Option<U::Service>, upgrade: Option<U::Service>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
cfg: Option<ServiceConfig>, cfg: Option<ServiceConfig>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B, X, U> Future for H1ServiceResponse<T, P, S, B, X, U> impl<T, S, B, X, U> Future for H1ServiceResponse<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: NewService<Request = Request>, S: ServiceFactory<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
B: MessageBody, B: MessageBody,
X: NewService<Request = Request, Response = Request>, X: ServiceFactory<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>, U: ServiceFactory<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
type Item = H1ServiceHandler<T, P, S::Service, B, X::Service, U::Service>; type Output = Result<H1ServiceHandler<T, S::Service, B, X::Service, U::Service>, ()>;
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(ref mut fut) = self.fut_ex { let mut this = self.as_mut().project();
let expect = try_ready!(fut
.poll() if let Some(fut) = this.fut_ex.as_pin_mut() {
.map_err(|e| log::error!("Init http service error: {:?}", e))); let expect = ready!(fut
self.expect = Some(expect); .poll(cx)
self.fut_ex.take(); .map_err(|e| log::error!("Init http service error: {:?}", e)))?;
this = self.as_mut().project();
*this.expect = Some(expect);
this.fut_ex.set(None);
} }
if let Some(ref mut fut) = self.fut_upg { if let Some(fut) = this.fut_upg.as_pin_mut() {
let upgrade = try_ready!(fut let upgrade = ready!(fut
.poll() .poll(cx)
.map_err(|e| log::error!("Init http service error: {:?}", e))); .map_err(|e| log::error!("Init http service error: {:?}", e)))?;
self.upgrade = Some(upgrade); this = self.as_mut().project();
self.fut_ex.take(); *this.upgrade = Some(upgrade);
this.fut_ex.set(None);
} }
let service = try_ready!(self let result = ready!(this
.fut .fut
.poll() .poll(cx)
.map_err(|e| log::error!("Init http service error: {:?}", e))); .map_err(|e| log::error!("Init http service error: {:?}", e)));
Ok(Async::Ready(H1ServiceHandler::new(
self.cfg.take().unwrap(), Poll::Ready(result.map(|service| {
let this = self.as_mut().project();
H1ServiceHandler::new(
this.cfg.take().unwrap(),
service, service,
self.expect.take().unwrap(), this.expect.take().unwrap(),
self.upgrade.take(), this.upgrade.take(),
self.on_connect.clone(), this.on_connect.clone(),
))) )
}))
} }
} }
/// `Service` implementation for HTTP1 transport /// `Service` implementation for HTTP1 transport
pub struct H1ServiceHandler<T, P, S, B, X, U> { pub struct H1ServiceHandler<T, S: Service, B, X: Service, U: Service> {
srv: CloneableService<S>, srv: CloneableService<S>,
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>, upgrade: Option<CloneableService<U>>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
cfg: ServiceConfig, cfg: ServiceConfig,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B, X, U> H1ServiceHandler<T, P, S, B, X, U> impl<T, S, B, X, U> H1ServiceHandler<T, S, B, X, U>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
@ -255,7 +390,7 @@ where
expect: X, expect: X,
upgrade: Option<U>, upgrade: Option<U>,
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
) -> H1ServiceHandler<T, P, S, B, X, U> { ) -> H1ServiceHandler<T, S, B, X, U> {
H1ServiceHandler { H1ServiceHandler {
srv: CloneableService::new(srv), srv: CloneableService::new(srv),
expect: CloneableService::new(expect), expect: CloneableService::new(expect),
@ -267,9 +402,9 @@ where
} }
} }
impl<T, P, S, B, X, U> Service for H1ServiceHandler<T, P, S, B, X, U> impl<T, S, B, X, U> Service for H1ServiceHandler<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@ -277,17 +412,17 @@ where
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>, U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display + Into<Error>,
{ {
type Request = Io<T, P>; type Request = (T, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type Future = Dispatcher<T, S, B, X, U>; type Future = Dispatcher<T, S, B, X, U>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let ready = self let ready = self
.expect .expect
.poll_ready() .poll_ready(cx)
.map_err(|e| { .map_err(|e| {
let e = e.into(); let e = e.into();
log::error!("Http service readiness error: {:?}", e); log::error!("Http service readiness error: {:?}", e);
@ -297,7 +432,7 @@ where
let ready = self let ready = self
.srv .srv
.poll_ready() .poll_ready(cx)
.map_err(|e| { .map_err(|e| {
let e = e.into(); let e = e.into();
log::error!("Http service readiness error: {:?}", e); log::error!("Http service readiness error: {:?}", e);
@ -306,16 +441,27 @@ where
.is_ready() .is_ready()
&& ready; && ready;
if ready { let ready = if let Some(ref mut upg) = self.upgrade {
Ok(Async::Ready(())) upg.poll_ready(cx)
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready()
&& ready
} else { } else {
Ok(Async::NotReady) ready
};
if ready {
Poll::Ready(Ok(()))
} else {
Poll::Pending
} }
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
let io = req.into_parts().0;
let on_connect = if let Some(ref on_connect) = self.on_connect { let on_connect = if let Some(ref on_connect) = self.on_connect {
Some(on_connect(&io)) Some(on_connect(&io))
} else { } else {
@ -329,20 +475,21 @@ where
self.expect.clone(), self.expect.clone(),
self.upgrade.clone(), self.upgrade.clone(),
on_connect, on_connect,
addr,
) )
} }
} }
/// `NewService` implementation for `OneRequestService` service /// `ServiceFactory` implementation for `OneRequestService` service
#[derive(Default)] #[derive(Default)]
pub struct OneRequest<T, P> { pub struct OneRequest<T> {
config: ServiceConfig, config: ServiceConfig,
_t: PhantomData<(T, P)>, _t: PhantomData<T>,
} }
impl<T, P> OneRequest<T, P> impl<T> OneRequest<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
/// Create new `H1SimpleService` instance. /// Create new `H1SimpleService` instance.
pub fn new() -> Self { pub fn new() -> Self {
@ -353,52 +500,49 @@ where
} }
} }
impl<T, P> NewService for OneRequest<T, P> impl<T> ServiceFactory for OneRequest<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
type Config = SrvConfig; type Config = ();
type Request = Io<T, P>; type Request = T;
type Response = (Request, Framed<T, Codec>); type Response = (Request, Framed<T, Codec>);
type Error = ParseError; type Error = ParseError;
type InitError = (); type InitError = ();
type Service = OneRequestService<T, P>; type Service = OneRequestService<T>;
type Future = FutureResult<Self::Service, Self::InitError>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &SrvConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
ok(OneRequestService { ok(OneRequestService {
config: self.config.clone(),
_t: PhantomData, _t: PhantomData,
config: self.config.clone(),
}) })
} }
} }
/// `Service` implementation for HTTP1 transport. Reads one request and returns /// `Service` implementation for HTTP1 transport. Reads one request and returns
/// request and framed object. /// request and framed object.
pub struct OneRequestService<T, P> { pub struct OneRequestService<T> {
_t: PhantomData<T>,
config: ServiceConfig, config: ServiceConfig,
_t: PhantomData<(T, P)>,
} }
impl<T, P> Service for OneRequestService<T, P> impl<T> Service for OneRequestService<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
type Request = Io<T, P>; type Request = T;
type Response = (Request, Framed<T, Codec>); type Response = (Request, Framed<T, Codec>);
type Error = ParseError; type Error = ParseError;
type Future = OneRequestServiceResponse<T>; type Future = OneRequestServiceResponse<T>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Ok(Async::Ready(())) Poll::Ready(Ok(()))
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, req: Self::Request) -> Self::Future {
OneRequestServiceResponse { OneRequestServiceResponse {
framed: Some(Framed::new( framed: Some(Framed::new(req, Codec::new(self.config.clone()))),
req.into_parts().0,
Codec::new(self.config.clone()),
)),
} }
} }
} }
@ -406,28 +550,28 @@ where
#[doc(hidden)] #[doc(hidden)]
pub struct OneRequestServiceResponse<T> pub struct OneRequestServiceResponse<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
framed: Option<Framed<T, Codec>>, framed: Option<Framed<T, Codec>>,
} }
impl<T> Future for OneRequestServiceResponse<T> impl<T> Future for OneRequestServiceResponse<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
type Item = (Request, Framed<T, Codec>); type Output = Result<(Request, Framed<T, Codec>), ParseError>;
type Error = ParseError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.framed.as_mut().unwrap().poll()? { match self.framed.as_mut().unwrap().next_item(cx) {
Async::Ready(Some(req)) => match req { Poll::Ready(Some(Ok(req))) => match req {
Message::Item(req) => { Message::Item(req) => {
Ok(Async::Ready((req, self.framed.take().unwrap()))) Poll::Ready(Ok((req, self.framed.take().unwrap())))
} }
Message::Chunk(_) => unreachable!("Something is wrong"), Message::Chunk(_) => unreachable!("Something is wrong"),
}, },
Async::Ready(None) => Err(ParseError::Incomplete), Poll::Ready(Some(Err(err))) => Poll::Ready(Err(err)),
Async::NotReady => Ok(Async::NotReady), Poll::Ready(None) => Poll::Ready(Err(ParseError::Incomplete)),
Poll::Pending => Poll::Pending,
} }
} }
} }

View File

@ -1,10 +1,9 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::task::{Context, Poll};
use actix_codec::Framed; use actix_codec::Framed;
use actix_server_config::ServerConfig; use actix_service::{Service, ServiceFactory};
use actix_service::{NewService, Service}; use futures_util::future::Ready;
use futures::future::FutureResult;
use futures::{Async, Poll};
use crate::error::Error; use crate::error::Error;
use crate::h1::Codec; use crate::h1::Codec;
@ -12,16 +11,16 @@ use crate::request::Request;
pub struct UpgradeHandler<T>(PhantomData<T>); pub struct UpgradeHandler<T>(PhantomData<T>);
impl<T> NewService for UpgradeHandler<T> { impl<T> ServiceFactory for UpgradeHandler<T> {
type Config = ServerConfig; type Config = ();
type Request = (Request, Framed<T, Codec>); type Request = (Request, Framed<T, Codec>);
type Response = (); type Response = ();
type Error = Error; type Error = Error;
type Service = UpgradeHandler<T>; type Service = UpgradeHandler<T>;
type InitError = Error; type InitError = Error;
type Future = FutureResult<Self::Service, Self::InitError>; type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: &ServerConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
unimplemented!() unimplemented!()
} }
} }
@ -30,10 +29,10 @@ impl<T> Service for UpgradeHandler<T> {
type Request = (Request, Framed<T, Codec>); type Request = (Request, Framed<T, Codec>);
type Response = (); type Response = ();
type Error = Error; type Error = Error;
type Future = FutureResult<Self::Response, Self::Error>; type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Ok(Async::Ready(())) Poll::Ready(Ok(()))
} }
fn call(&mut self, _: Self::Request) -> Self::Future { fn call(&mut self, _: Self::Request) -> Self::Future {

View File

@ -1,5 +1,8 @@
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use futures::{Async, Future, Poll, Sink};
use crate::body::{BodySize, MessageBody, ResponseBody}; use crate::body::{BodySize, MessageBody, ResponseBody};
use crate::error::Error; use crate::error::Error;
@ -7,6 +10,7 @@ use crate::h1::{Codec, Message};
use crate::response::Response; use crate::response::Response;
/// Send http/1 response /// Send http/1 response
#[pin_project::pin_project]
pub struct SendResponse<T, B> { pub struct SendResponse<T, B> {
res: Option<Message<(Response<()>, BodySize)>>, res: Option<Message<(Response<()>, BodySize)>>,
body: Option<ResponseBody<B>>, body: Option<ResponseBody<B>>,
@ -33,60 +37,61 @@ where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
B: MessageBody, B: MessageBody,
{ {
type Item = Framed<T, Codec>; type Output = Result<Framed<T, Codec>, Error>;
type Error = Error;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop { loop {
let mut body_ready = self.body.is_some(); let mut body_ready = this.body.is_some();
let framed = self.framed.as_mut().unwrap(); let framed = this.framed.as_mut().unwrap();
// send body // send body
if self.res.is_none() && self.body.is_some() { if this.res.is_none() && this.body.is_some() {
while body_ready && self.body.is_some() && !framed.is_write_buf_full() { while body_ready && this.body.is_some() && !framed.is_write_buf_full() {
match self.body.as_mut().unwrap().poll_next()? { match this.body.as_mut().unwrap().poll_next(cx)? {
Async::Ready(item) => { Poll::Ready(item) => {
// body is done // body is done
if item.is_none() { if item.is_none() {
let _ = self.body.take(); let _ = this.body.take();
} }
framed.force_send(Message::Chunk(item))?; framed.write(Message::Chunk(item))?;
} }
Async::NotReady => body_ready = false, Poll::Pending => body_ready = false,
} }
} }
} }
// flush write buffer // flush write buffer
if !framed.is_write_buf_empty() { if !framed.is_write_buf_empty() {
match framed.poll_complete()? { match framed.flush(cx)? {
Async::Ready(_) => { Poll::Ready(_) => {
if body_ready { if body_ready {
continue; continue;
} else { } else {
return Ok(Async::NotReady); return Poll::Pending;
} }
} }
Async::NotReady => return Ok(Async::NotReady), Poll::Pending => return Poll::Pending,
} }
} }
// send response // send response
if let Some(res) = self.res.take() { if let Some(res) = this.res.take() {
framed.force_send(res)?; framed.write(res)?;
continue; continue;
} }
if self.body.is_some() { if this.body.is_some() {
if body_ready { if body_ready {
continue; continue;
} else { } else {
return Ok(Async::NotReady); return Poll::Pending;
} }
} else { } else {
break; break;
} }
} }
Ok(Async::Ready(self.framed.take().unwrap())) Poll::Ready(Ok(this.framed.take().unwrap()))
} }
} }

View File

@ -1,27 +1,23 @@
use std::collections::VecDeque; use std::convert::TryFrom;
use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::time::Instant; use std::net;
use std::{fmt, mem, net}; use std::pin::Pin;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::IoStream; use actix_rt::time::{Delay, Instant};
use actix_service::Service; use actix_service::Service;
use bitflags::bitflags;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{try_ready, Async, Future, Poll, Sink, Stream};
use h2::server::{Connection, SendResponse}; use h2::server::{Connection, SendResponse};
use h2::{RecvStream, SendStream}; use h2::SendStream;
use http::header::{ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, use log::{error, trace};
};
use http::HttpTryFrom;
use log::{debug, error, trace};
use tokio_timer::Delay;
use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::body::{BodySize, MessageBody, ResponseBody};
use crate::cloneable::CloneableService; use crate::cloneable::CloneableService;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; use crate::error::{DispatchError, Error};
use crate::helpers::DataFactory; use crate::helpers::DataFactory;
use crate::httpmessage::HttpMessage; use crate::httpmessage::HttpMessage;
use crate::message::ResponseHead; use crate::message::ResponseHead;
@ -32,7 +28,11 @@ use crate::response::Response;
const CHUNK_SIZE: usize = 16_384; const CHUNK_SIZE: usize = 16_384;
/// Dispatcher for HTTP/2 protocol /// Dispatcher for HTTP/2 protocol
pub struct Dispatcher<T: IoStream, S: Service<Request = Request>, B: MessageBody> { #[pin_project::pin_project]
pub struct Dispatcher<T, S: Service<Request = Request>, B: MessageBody>
where
T: AsyncRead + AsyncWrite + Unpin,
{
service: CloneableService<S>, service: CloneableService<S>,
connection: Connection<T, Bytes>, connection: Connection<T, Bytes>,
on_connect: Option<Box<dyn DataFactory>>, on_connect: Option<Box<dyn DataFactory>>,
@ -45,12 +45,12 @@ pub struct Dispatcher<T: IoStream, S: Service<Request = Request>, B: MessageBody
impl<T, S, B> Dispatcher<T, S, B> impl<T, S, B> Dispatcher<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, // S::Future: 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
B: MessageBody + 'static, B: MessageBody,
{ {
pub(crate) fn new( pub(crate) fn new(
service: CloneableService<S>, service: CloneableService<S>,
@ -91,76 +91,92 @@ where
impl<T, S, B> Future for Dispatcher<T, S, B> impl<T, S, B> Future for Dispatcher<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
type Item = (); type Output = Result<(), DispatchError>;
type Error = DispatchError;
#[inline] #[inline]
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
loop { loop {
match self.connection.poll()? { match Pin::new(&mut this.connection).poll_accept(cx) {
Async::Ready(None) => return Ok(Async::Ready(())), Poll::Ready(None) => return Poll::Ready(Ok(())),
Async::Ready(Some((req, res))) => { Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())),
Poll::Ready(Some(Ok((req, res)))) => {
// update keep-alive expire // update keep-alive expire
if self.ka_timer.is_some() { if this.ka_timer.is_some() {
if let Some(expire) = self.config.keep_alive_expire() { if let Some(expire) = this.config.keep_alive_expire() {
self.ka_expire = expire; this.ka_expire = expire;
} }
} }
let (parts, body) = req.into_parts(); let (parts, body) = req.into_parts();
let mut req = Request::with_payload(body.into()); let mut req = Request::with_payload(Payload::<
crate::payload::PayloadStream,
>::H2(
crate::h2::Payload::new(body)
));
let head = &mut req.head_mut(); let head = &mut req.head_mut();
head.uri = parts.uri; head.uri = parts.uri;
head.method = parts.method; head.method = parts.method;
head.version = parts.version; head.version = parts.version;
head.headers = parts.headers.into(); head.headers = parts.headers.into();
head.peer_addr = self.peer_addr; head.peer_addr = this.peer_addr;
// set on_connect data // set on_connect data
if let Some(ref on_connect) = self.on_connect { if let Some(ref on_connect) = this.on_connect {
on_connect.set(&mut req.extensions_mut()); on_connect.set(&mut req.extensions_mut());
} }
tokio_current_thread::spawn(ServiceResponse::<S::Future, B> { actix_rt::spawn(ServiceResponse::<
S::Future,
S::Response,
S::Error,
B,
> {
state: ServiceResponseState::ServiceCall( state: ServiceResponseState::ServiceCall(
self.service.call(req), this.service.call(req),
Some(res), Some(res),
), ),
config: self.config.clone(), config: this.config.clone(),
buffer: None, buffer: None,
}) _t: PhantomData,
});
} }
Async::NotReady => return Ok(Async::NotReady), Poll::Pending => return Poll::Pending,
} }
} }
} }
} }
struct ServiceResponse<F, B> { #[pin_project::pin_project]
struct ServiceResponse<F, I, E, B> {
#[pin]
state: ServiceResponseState<F, B>, state: ServiceResponseState<F, B>,
config: ServiceConfig, config: ServiceConfig,
buffer: Option<Bytes>, buffer: Option<Bytes>,
_t: PhantomData<(I, E)>,
} }
#[pin_project::pin_project]
enum ServiceResponseState<F, B> { enum ServiceResponseState<F, B> {
ServiceCall(F, Option<SendResponse<Bytes>>), ServiceCall(#[pin] F, Option<SendResponse<Bytes>>),
SendPayload(SendStream<Bytes>, ResponseBody<B>), SendPayload(SendStream<Bytes>, ResponseBody<B>),
} }
impl<F, B> ServiceResponse<F, B> impl<F, I, E, B> ServiceResponse<F, I, E, B>
where where
F: Future, F: Future<Output = Result<I, E>>,
F::Error: Into<Error>, E: Into<Error>,
F::Item: Into<Response<B>>, I: Into<Response<B>>,
B: MessageBody + 'static, B: MessageBody,
{ {
fn prepare_response( fn prepare_response(
&self, &self,
@ -215,117 +231,131 @@ where
if !has_date { if !has_date {
let mut bytes = BytesMut::with_capacity(29); let mut bytes = BytesMut::with_capacity(29);
self.config.set_date_header(&mut bytes); self.config.set_date_header(&mut bytes);
res.headers_mut() res.headers_mut().insert(DATE, unsafe {
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); HeaderValue::from_maybe_shared_unchecked(bytes.freeze())
});
} }
res res
} }
} }
impl<F, B> Future for ServiceResponse<F, B> impl<F, I, E, B> Future for ServiceResponse<F, I, E, B>
where where
F: Future, F: Future<Output = Result<I, E>>,
F::Error: Into<Error>, E: Into<Error>,
F::Item: Into<Response<B>>, I: Into<Response<B>>,
B: MessageBody + 'static, B: MessageBody,
{ {
type Item = (); type Output = ();
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { #[pin_project::project]
match self.state { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
ServiceResponseState::ServiceCall(ref mut call, ref mut send) => { let mut this = self.as_mut().project();
match call.poll() {
Ok(Async::Ready(res)) => { #[project]
match this.state.project() {
ServiceResponseState::ServiceCall(call, send) => {
match call.poll(cx) {
Poll::Ready(Ok(res)) => {
let (res, body) = res.into().replace_body(()); let (res, body) = res.into().replace_body(());
let mut send = send.take().unwrap(); let mut send = send.take().unwrap();
let mut size = body.size(); let mut size = body.size();
let h2_res = self.prepare_response(res.head(), &mut size); let h2_res =
self.as_mut().prepare_response(res.head(), &mut size);
this = self.as_mut().project();
let stream = let stream = match send.send_response(h2_res, size.is_eof()) {
send.send_response(h2_res, size.is_eof()).map_err(|e| { Err(e) => {
trace!("Error sending h2 response: {:?}", e); trace!("Error sending h2 response: {:?}", e);
})?; return Poll::Ready(());
}
Ok(stream) => stream,
};
if size.is_eof() { if size.is_eof() {
Ok(Async::Ready(())) Poll::Ready(())
} else { } else {
self.state = ServiceResponseState::SendPayload(stream, body); this.state.set(ServiceResponseState::SendPayload(stream, body));
self.poll() self.poll(cx)
} }
} }
Ok(Async::NotReady) => Ok(Async::NotReady), Poll::Pending => Poll::Pending,
Err(e) => { Poll::Ready(Err(e)) => {
let res: Response = e.into().into(); let res: Response = e.into().into();
let (res, body) = res.replace_body(()); let (res, body) = res.replace_body(());
let mut send = send.take().unwrap(); let mut send = send.take().unwrap();
let mut size = body.size(); let mut size = body.size();
let h2_res = self.prepare_response(res.head(), &mut size); let h2_res =
self.as_mut().prepare_response(res.head(), &mut size);
this = self.as_mut().project();
let stream = let stream = match send.send_response(h2_res, size.is_eof()) {
send.send_response(h2_res, size.is_eof()).map_err(|e| { Err(e) => {
trace!("Error sending h2 response: {:?}", e); trace!("Error sending h2 response: {:?}", e);
})?; return Poll::Ready(());
}
Ok(stream) => stream,
};
if size.is_eof() { if size.is_eof() {
Ok(Async::Ready(())) Poll::Ready(())
} else { } else {
self.state = ServiceResponseState::SendPayload( this.state.set(ServiceResponseState::SendPayload(
stream, stream,
body.into_body(), body.into_body(),
); ));
self.poll() self.poll(cx)
} }
} }
} }
} }
ServiceResponseState::SendPayload(ref mut stream, ref mut body) => loop { ServiceResponseState::SendPayload(ref mut stream, ref mut body) => loop {
loop { loop {
if let Some(ref mut buffer) = self.buffer { if let Some(ref mut buffer) = this.buffer {
match stream.poll_capacity().map_err(|e| warn!("{:?}", e))? { match stream.poll_capacity(cx) {
Async::NotReady => return Ok(Async::NotReady), Poll::Pending => return Poll::Pending,
Async::Ready(None) => return Ok(Async::Ready(())), Poll::Ready(None) => return Poll::Ready(()),
Async::Ready(Some(cap)) => { Poll::Ready(Some(Ok(cap))) => {
let len = buffer.len(); let len = buffer.len();
let bytes = buffer.split_to(std::cmp::min(cap, len)); let bytes = buffer.split_to(std::cmp::min(cap, len));
if let Err(e) = stream.send_data(bytes, false) { if let Err(e) = stream.send_data(bytes, false) {
warn!("{:?}", e); warn!("{:?}", e);
return Err(()); return Poll::Ready(());
} else if !buffer.is_empty() { } else if !buffer.is_empty() {
let cap = std::cmp::min(buffer.len(), CHUNK_SIZE); let cap = std::cmp::min(buffer.len(), CHUNK_SIZE);
stream.reserve_capacity(cap); stream.reserve_capacity(cap);
} else { } else {
self.buffer.take(); this.buffer.take();
} }
} }
Poll::Ready(Some(Err(e))) => {
warn!("{:?}", e);
return Poll::Ready(());
}
} }
} else { } else {
match body.poll_next() { match body.poll_next(cx) {
Ok(Async::NotReady) => { Poll::Pending => return Poll::Pending,
return Ok(Async::NotReady); Poll::Ready(None) => {
}
Ok(Async::Ready(None)) => {
if let Err(e) = stream.send_data(Bytes::new(), true) { if let Err(e) = stream.send_data(Bytes::new(), true) {
warn!("{:?}", e); warn!("{:?}", e);
return Err(());
} else {
return Ok(Async::Ready(()));
} }
return Poll::Ready(());
} }
Ok(Async::Ready(Some(chunk))) => { Poll::Ready(Some(Ok(chunk))) => {
stream.reserve_capacity(std::cmp::min( stream.reserve_capacity(std::cmp::min(
chunk.len(), chunk.len(),
CHUNK_SIZE, CHUNK_SIZE,
)); ));
self.buffer = Some(chunk); *this.buffer = Some(chunk);
} }
Err(e) => { Poll::Ready(Some(Err(e))) => {
error!("Response payload stream error: {:?}", e); error!("Response payload stream error: {:?}", e);
return Err(()); return Poll::Ready(());
} }
} }
} }

View File

@ -1,9 +1,9 @@
#![allow(dead_code, unused_imports)] //! HTTP/2 implementation
use std::pin::Pin;
use std::fmt; use std::task::{Context, Poll};
use bytes::Bytes; use bytes::Bytes;
use futures::{Async, Poll, Stream}; use futures_core::Stream;
use h2::RecvStream; use h2::RecvStream;
mod dispatcher; mod dispatcher;
@ -25,22 +25,26 @@ impl Payload {
} }
impl Stream for Payload { impl Stream for Payload {
type Item = Bytes; type Item = Result<Bytes, PayloadError>;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll_next(
match self.pl.poll() { self: Pin<&mut Self>,
Ok(Async::Ready(Some(chunk))) => { cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
match Pin::new(&mut this.pl).poll_data(cx) {
Poll::Ready(Some(Ok(chunk))) => {
let len = chunk.len(); let len = chunk.len();
if let Err(err) = self.pl.release_capacity().release_capacity(len) { if let Err(err) = this.pl.flow_control().release_capacity(len) {
Err(err.into()) Poll::Ready(Some(Err(err.into())))
} else { } else {
Ok(Async::Ready(Some(chunk))) Poll::Ready(Some(Ok(chunk)))
} }
} }
Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err.into()))),
Ok(Async::NotReady) => Ok(Async::NotReady), Poll::Pending => Poll::Pending,
Err(err) => Err(err.into()), Poll::Ready(None) => Poll::Ready(None),
} }
} }
} }

View File

@ -1,62 +1,56 @@
use std::fmt::Debug; use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::{io, net, rc}; use std::pin::Pin;
use std::task::{Context, Poll};
use std::{net, rc};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_rt::net::TcpStream;
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{
fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service,
ServiceFactory,
};
use bytes::Bytes; use bytes::Bytes;
use futures::future::{ok, FutureResult}; use futures_core::ready;
use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; use futures_util::future::ok;
use h2::server::{self, Connection, Handshake}; use h2::server::{self, Handshake};
use h2::RecvStream;
use log::error; use log::error;
use crate::body::MessageBody; use crate::body::MessageBody;
use crate::cloneable::CloneableService; use crate::cloneable::CloneableService;
use crate::config::{KeepAlive, ServiceConfig}; use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error, ParseError, ResponseError}; use crate::error::{DispatchError, Error};
use crate::helpers::DataFactory; use crate::helpers::DataFactory;
use crate::payload::Payload;
use crate::request::Request; use crate::request::Request;
use crate::response::Response; use crate::response::Response;
use super::dispatcher::Dispatcher; use super::dispatcher::Dispatcher;
/// `NewService` implementation for HTTP2 transport /// `ServiceFactory` implementation for HTTP2 transport
pub struct H2Service<T, P, S, B> { pub struct H2Service<T, S, B> {
srv: S, srv: S,
cfg: ServiceConfig, cfg: ServiceConfig,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B> H2Service<T, P, S, B> impl<T, S, B> H2Service<T, S, B>
where where
S: NewService<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
/// Create new `HttpService` instance.
pub fn new<F: IntoNewService<S>>(service: F) -> Self {
let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0);
H2Service {
cfg,
on_connect: None,
srv: service.into_new_service(),
_t: PhantomData,
}
}
/// Create new `HttpService` instance with config. /// Create new `HttpService` instance with config.
pub fn with_config<F: IntoNewService<S>>(cfg: ServiceConfig, service: F) -> Self { pub(crate) fn with_config<F: IntoServiceFactory<S>>(
cfg: ServiceConfig,
service: F,
) -> Self {
H2Service { H2Service {
cfg, cfg,
on_connect: None, on_connect: None,
srv: service.into_new_service(), srv: service.into_factory(),
_t: PhantomData, _t: PhantomData,
} }
} }
@ -71,26 +65,144 @@ where
} }
} }
impl<T, P, S, B> NewService for H2Service<T, P, S, B> impl<S, B> H2Service<TcpStream, S, B>
where where
T: IoStream, S: ServiceFactory<Config = (), Request = Request>,
S: NewService<Config = SrvConfig, Request = Request>, S::Error: Into<Error> + 'static,
S::Error: Into<Error>, S::Response: Into<Response<B>> + 'static,
S::Response: Into<Response<B>>,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
type Config = SrvConfig; /// Create simple tcp based service
type Request = Io<T, P>; pub fn tcp(
self,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = DispatchError,
InitError = S::InitError,
> {
pipeline_factory(fn_factory(|| {
async {
Ok::<_, S::InitError>(fn_service(|io: TcpStream| {
let peer_addr = io.peer_addr().ok();
ok::<_, DispatchError>((io, peer_addr))
}))
}
}))
.and_then(self)
}
}
#[cfg(feature = "openssl")]
mod openssl {
use actix_service::{fn_factory, fn_service};
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
use actix_tls::{openssl::HandshakeError, SslError};
use super::*;
impl<S, B> H2Service<SslStream<TcpStream>, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
/// Create ssl based service
pub fn openssl(
self,
acceptor: SslAcceptor,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<HandshakeError<TcpStream>, DispatchError>,
InitError = S::InitError,
> {
pipeline_factory(
Acceptor::new(acceptor)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(fn_factory(|| {
ok::<_, S::InitError>(fn_service(|io: SslStream<TcpStream>| {
let peer_addr = io.get_ref().peer_addr().ok();
ok((io, peer_addr))
}))
}))
.and_then(self.map_err(SslError::Service))
}
}
}
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
use actix_tls::SslError;
use std::io;
impl<S, B> H2Service<TlsStream<TcpStream>, S, B>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
/// Create openssl based service
pub fn rustls(
self,
mut config: ServerConfig,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<io::Error, DispatchError>,
InitError = S::InitError,
> {
let protos = vec!["h2".to_string().into()];
config.set_protocols(&protos);
pipeline_factory(
Acceptor::new(config)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(fn_factory(|| {
ok::<_, S::InitError>(fn_service(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
ok((io, peer_addr))
}))
}))
.and_then(self.map_err(SslError::Service))
}
}
}
impl<T, S, B> ServiceFactory for H2Service<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
{
type Config = ();
type Request = (T, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type InitError = S::InitError; type InitError = S::InitError;
type Service = H2ServiceHandler<T, P, S::Service, B>; type Service = H2ServiceHandler<T, S::Service, B>;
type Future = H2ServiceResponse<T, P, S, B>; type Future = H2ServiceResponse<T, S, B>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
H2ServiceResponse { H2ServiceResponse {
fut: self.srv.new_service(cfg).into_future(), fut: self.srv.new_service(()),
cfg: Some(self.cfg.clone()), cfg: Some(self.cfg.clone()),
on_connect: self.on_connect.clone(), on_connect: self.on_connect.clone(),
_t: PhantomData, _t: PhantomData,
@ -99,56 +211,61 @@ where
} }
#[doc(hidden)] #[doc(hidden)]
pub struct H2ServiceResponse<T, P, S: NewService, B> { #[pin_project::pin_project]
fut: <S::Future as IntoFuture>::Future, pub struct H2ServiceResponse<T, S: ServiceFactory, B> {
#[pin]
fut: S::Future,
cfg: Option<ServiceConfig>, cfg: Option<ServiceConfig>,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B> Future for H2ServiceResponse<T, P, S, B> impl<T, S, B> Future for H2ServiceResponse<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: NewService<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
type Item = H2ServiceHandler<T, P, S::Service, B>; type Output = Result<H2ServiceHandler<T, S::Service, B>, S::InitError>;
type Error = S::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let service = try_ready!(self.fut.poll()); let this = self.as_mut().project();
Ok(Async::Ready(H2ServiceHandler::new(
self.cfg.take().unwrap(), Poll::Ready(ready!(this.fut.poll(cx)).map(|service| {
self.on_connect.clone(), let this = self.as_mut().project();
H2ServiceHandler::new(
this.cfg.take().unwrap(),
this.on_connect.clone(),
service, service,
))) )
}))
} }
} }
/// `Service` implementation for http/2 transport /// `Service` implementation for http/2 transport
pub struct H2ServiceHandler<T, P, S, B> { pub struct H2ServiceHandler<T, S: Service, B> {
srv: CloneableService<S>, srv: CloneableService<S>,
cfg: ServiceConfig, cfg: ServiceConfig,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B> H2ServiceHandler<T, P, S, B> impl<T, S, B> H2ServiceHandler<T, S, B>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
fn new( fn new(
cfg: ServiceConfig, cfg: ServiceConfig,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
srv: S, srv: S,
) -> H2ServiceHandler<T, P, S, B> { ) -> H2ServiceHandler<T, S, B> {
H2ServiceHandler { H2ServiceHandler {
cfg, cfg,
on_connect, on_connect,
@ -158,31 +275,29 @@ where
} }
} }
impl<T, P, S, B> Service for H2ServiceHandler<T, P, S, B> impl<T, S, B> Service for H2ServiceHandler<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
type Request = Io<T, P>; type Request = (T, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type Future = H2ServiceHandlerResponse<T, S, B>; type Future = H2ServiceHandlerResponse<T, S, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.srv.poll_ready().map_err(|e| { self.srv.poll_ready(cx).map_err(|e| {
let e = e.into(); let e = e.into();
error!("Service readiness error: {:?}", e); error!("Service readiness error: {:?}", e);
DispatchError::Service(e) DispatchError::Service(e)
}) })
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
let io = req.into_parts().0;
let peer_addr = io.peer_addr();
let on_connect = if let Some(ref on_connect) = self.on_connect { let on_connect = if let Some(ref on_connect) = self.on_connect {
Some(on_connect(&io)) Some(on_connect(&io))
} else { } else {
@ -193,7 +308,7 @@ where
state: State::Handshake( state: State::Handshake(
Some(self.srv.clone()), Some(self.srv.clone()),
Some(self.cfg.clone()), Some(self.cfg.clone()),
peer_addr, addr,
on_connect, on_connect,
server::handshake(io), server::handshake(io),
), ),
@ -201,8 +316,9 @@ where
} }
} }
enum State<T: IoStream, S: Service<Request = Request>, B: MessageBody> enum State<T, S: Service<Request = Request>, B: MessageBody>
where where
T: AsyncRead + AsyncWrite + Unpin,
S::Future: 'static, S::Future: 'static,
{ {
Incoming(Dispatcher<T, S, B>), Incoming(Dispatcher<T, S, B>),
@ -217,11 +333,11 @@ where
pub struct H2ServiceHandlerResponse<T, S, B> pub struct H2ServiceHandlerResponse<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
state: State<T, S, B>, state: State<T, S, B>,
@ -229,27 +345,26 @@ where
impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B> impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
B: MessageBody, B: MessageBody,
{ {
type Item = (); type Output = Result<(), DispatchError>;
type Error = DispatchError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.state { match self.state {
State::Incoming(ref mut disp) => disp.poll(), State::Incoming(ref mut disp) => Pin::new(disp).poll(cx),
State::Handshake( State::Handshake(
ref mut srv, ref mut srv,
ref mut config, ref mut config,
ref peer_addr, ref peer_addr,
ref mut on_connect, ref mut on_connect,
ref mut handshake, ref mut handshake,
) => match handshake.poll() { ) => match Pin::new(handshake).poll(cx) {
Ok(Async::Ready(conn)) => { Poll::Ready(Ok(conn)) => {
self.state = State::Incoming(Dispatcher::new( self.state = State::Incoming(Dispatcher::new(
srv.take().unwrap(), srv.take().unwrap(),
conn, conn,
@ -258,13 +373,13 @@ where
None, None,
*peer_addr, *peer_addr,
)); ));
self.poll() self.poll(cx)
} }
Ok(Async::NotReady) => Ok(Async::NotReady), Poll::Ready(Err(err)) => {
Err(err) => {
trace!("H2 handshake error: {}", err); trace!("H2 handshake error: {}", err);
Err(err.into()) Poll::Ready(Err(err.into()))
} }
Poll::Pending => Poll::Pending,
}, },
} }
} }

View File

@ -63,7 +63,7 @@ header! {
(AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+ (AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+
test_accept_charset { test_accept_charset {
/// Test case from RFC // Test case from RFC
test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]);
} }
} }

View File

@ -74,18 +74,18 @@ impl Header for CacheControl {
} }
impl fmt::Display for CacheControl { impl fmt::Display for CacheControl {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_comma_delimited(f, &self[..]) fmt_comma_delimited(f, &self[..])
} }
} }
impl IntoHeaderValue for CacheControl { impl IntoHeaderValue for CacheControl {
type Error = header::InvalidHeaderValueBytes; type Error = header::InvalidHeaderValue;
fn try_into(self) -> Result<header::HeaderValue, Self::Error> { fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
header::HeaderValue::from_shared(writer.take()) header::HeaderValue::from_maybe_shared(writer.take())
} }
} }
@ -126,7 +126,7 @@ pub enum CacheDirective {
} }
impl fmt::Display for CacheDirective { impl fmt::Display for CacheDirective {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::CacheDirective::*; use self::CacheDirective::*;
fmt::Display::fmt( fmt::Display::fmt(
match *self { match *self {

View File

@ -76,6 +76,11 @@ pub enum DispositionParam {
/// the form. /// the form.
Name(String), Name(String),
/// A plain file name. /// A plain file name.
///
/// It is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any
/// non-ASCII characters when used in a *Content-Disposition* HTTP response header, where
/// [`FilenameExt`](DispositionParam::FilenameExt) with charset UTF-8 may be used instead
/// in case there are Unicode characters in file names.
Filename(String), Filename(String),
/// An extended file name. It must not exist for `ContentType::Formdata` according to /// An extended file name. It must not exist for `ContentType::Formdata` according to
/// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2).
@ -220,7 +225,16 @@ impl DispositionParam {
/// ext-token = <the characters in token, followed by "*"> /// ext-token = <the characters in token, followed by "*">
/// ``` /// ```
/// ///
/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within /// # Note
///
/// filename is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any
/// non-ASCII characters when used in a *Content-Disposition* HTTP response header, where
/// filename* with charset UTF-8 may be used instead in case there are Unicode characters in file
/// names.
/// filename is [acceptable](https://tools.ietf.org/html/rfc7578#section-4.2) to be UTF-8 encoded
/// directly in a *Content-Disposition* header for *multipart/form-data*, though.
///
/// filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within
/// *multipart/form-data*. /// *multipart/form-data*.
/// ///
/// # Example /// # Example
@ -251,6 +265,22 @@ impl DispositionParam {
/// }; /// };
/// assert_eq!(cd2.get_name(), Some("file")); // field name /// assert_eq!(cd2.get_name(), Some("file")); // field name
/// assert_eq!(cd2.get_filename(), Some("bill.odt")); /// assert_eq!(cd2.get_filename(), Some("bill.odt"));
///
/// // HTTP response header with Unicode characters in file names
/// let cd3 = ContentDisposition {
/// disposition: DispositionType::Attachment,
/// parameters: vec![
/// DispositionParam::FilenameExt(ExtendedValue {
/// charset: Charset::Ext(String::from("UTF-8")),
/// language_tag: None,
/// value: String::from("\u{1f600}.svg").into_bytes(),
/// }),
/// // fallback for better compatibility
/// DispositionParam::Filename(String::from("Grinning-Face-Emoji.svg"))
/// ],
/// };
/// assert_eq!(cd3.get_filename_ext().map(|ev| ev.value.as_ref()),
/// Some("\u{1f600}.svg".as_bytes()));
/// ``` /// ```
/// ///
/// # WARN /// # WARN
@ -333,15 +363,17 @@ impl ContentDisposition {
// token: won't contains semicolon according to RFC 2616 Section 2.2 // token: won't contains semicolon according to RFC 2616 Section 2.2
let (token, new_left) = split_once_and_trim(left, ';'); let (token, new_left) = split_once_and_trim(left, ';');
left = new_left; left = new_left;
token.to_owned() if token.is_empty() {
}; // quoted-string can be empty, but token cannot be empty
if value.is_empty() {
return Err(crate::error::ParseError::Header); return Err(crate::error::ParseError::Header);
} }
token.to_owned()
};
let param = if param_name.eq_ignore_ascii_case("name") { let param = if param_name.eq_ignore_ascii_case("name") {
DispositionParam::Name(value) DispositionParam::Name(value)
} else if param_name.eq_ignore_ascii_case("filename") { } else if param_name.eq_ignore_ascii_case("filename") {
// See also comments in test_from_raw_uncessary_percent_decode.
DispositionParam::Filename(value) DispositionParam::Filename(value)
} else { } else {
DispositionParam::Unknown(param_name.to_owned(), value) DispositionParam::Unknown(param_name.to_owned(), value)
@ -430,12 +462,12 @@ impl ContentDisposition {
} }
impl IntoHeaderValue for ContentDisposition { impl IntoHeaderValue for ContentDisposition {
type Error = header::InvalidHeaderValueBytes; type Error = header::InvalidHeaderValue;
fn try_into(self) -> Result<header::HeaderValue, Self::Error> { fn try_into(self) -> Result<header::HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
header::HeaderValue::from_shared(writer.take()) header::HeaderValue::from_maybe_shared(writer.take())
} }
} }
@ -454,7 +486,7 @@ impl Header for ContentDisposition {
} }
impl fmt::Display for DispositionType { impl fmt::Display for DispositionType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
DispositionType::Inline => write!(f, "inline"), DispositionType::Inline => write!(f, "inline"),
DispositionType::Attachment => write!(f, "attachment"), DispositionType::Attachment => write!(f, "attachment"),
@ -465,12 +497,41 @@ impl fmt::Display for DispositionType {
} }
impl fmt::Display for DispositionParam { impl fmt::Display for DispositionParam {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and // All ASCII control characters (0-30, 127) including horizontal tab, double quote, and
// backslash should be escaped in quoted-string (i.e. "foobar"). // backslash should be escaped in quoted-string (i.e. "foobar").
// Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... . // Ref: RFC6266 S4.1 -> RFC2616 S3.6
// filename-parm = "filename" "=" value
// value = token | quoted-string
// quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
// qdtext = <any TEXT except <">>
// quoted-pair = "\" CHAR
// TEXT = <any OCTET except CTLs,
// but including LWS>
// LWS = [CRLF] 1*( SP | HT )
// OCTET = <any 8-bit sequence of data>
// CHAR = <any US-ASCII character (octets 0 - 127)>
// CTL = <any US-ASCII control character
// (octets 0 - 31) and DEL (127)>
//
// Ref: RFC7578 S4.2 -> RFC2183 S2 -> RFC2045 S5.1
// parameter := attribute "=" value
// attribute := token
// ; Matching of attributes
// ; is ALWAYS case-insensitive.
// value := token / quoted-string
// token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
// or tspecials>
// tspecials := "(" / ")" / "<" / ">" / "@" /
// "," / ";" / ":" / "\" / <">
// "/" / "[" / "]" / "?" / "="
// ; Must be in quoted-string,
// ; to use within parameter values
//
//
// See also comments in test_from_raw_uncessary_percent_decode.
lazy_static! { lazy_static! {
static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap(); static ref RE: Regex = Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap();
} }
match self { match self {
DispositionParam::Name(ref value) => write!(f, "name={}", value), DispositionParam::Name(ref value) => write!(f, "name={}", value),
@ -494,7 +555,7 @@ impl fmt::Display for DispositionParam {
} }
impl fmt::Display for ContentDisposition { impl fmt::Display for ContentDisposition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.disposition)?; write!(f, "{}", self.disposition)?;
self.parameters self.parameters
.iter() .iter()
@ -707,8 +768,7 @@ mod tests {
Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above.
(And now, only UTF-8 is handled by this implementation.) (And now, only UTF-8 is handled by this implementation.)
*/ */
let a = let a = HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"")
HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"")
.unwrap(); .unwrap();
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition { let b = ContentDisposition {
@ -774,8 +834,18 @@ mod tests {
#[test] #[test]
fn test_from_raw_uncessary_percent_decode() { fn test_from_raw_uncessary_percent_decode() {
// In fact, RFC7578 (multipart/form-data) Section 2 and 4.2 suggests that filename with
// non-ASCII characters MAY be percent-encoded.
// On the contrary, RFC6266 or other RFCs related to Content-Disposition response header
// do not mention such percent-encoding.
// So, it appears to be undecidable whether to percent-decode or not without
// knowing the usage scenario (multipart/form-data v.s. HTTP response header) and
// inevitable to unnecessarily percent-decode filename with %XX in the former scenario.
// Fortunately, it seems that almost all mainstream browsers just send UTF-8 encoded file
// names in quoted-string format (tested on Edge, IE11, Chrome and Firefox) without
// percent-encoding. So we do not bother to attempt to percent-decode.
let a = HeaderValue::from_static( let a = HeaderValue::from_static(
"form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded! "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"",
); );
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition { let b = ContentDisposition {
@ -811,6 +881,13 @@ mod tests {
let a = HeaderValue::from_static("inline; filename= "); let a = HeaderValue::from_static("inline; filename= ");
assert!(ContentDisposition::from_raw(&a).is_err()); assert!(ContentDisposition::from_raw(&a).is_err());
let a = HeaderValue::from_static("inline; filename=\"\"");
assert!(ContentDisposition::from_raw(&a)
.expect("parse cd")
.get_filename()
.expect("filename")
.is_empty());
} }
#[test] #[test]

View File

@ -3,7 +3,7 @@ use std::str::FromStr;
use crate::error::ParseError; use crate::error::ParseError;
use crate::header::{ use crate::header::{
HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer, CONTENT_RANGE, HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE,
}; };
header! { header! {
@ -166,7 +166,7 @@ impl FromStr for ContentRangeSpec {
} }
impl Display for ContentRangeSpec { impl Display for ContentRangeSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
ContentRangeSpec::Bytes { ContentRangeSpec::Bytes {
range, range,
@ -198,11 +198,11 @@ impl Display for ContentRangeSpec {
} }
impl IntoHeaderValue for ContentRangeSpec { impl IntoHeaderValue for ContentRangeSpec {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
HeaderValue::from_shared(writer.take()) HeaderValue::from_maybe_shared(writer.take())
} }
} }

View File

@ -3,7 +3,7 @@ use std::fmt::{self, Display, Write};
use crate::error::ParseError; use crate::error::ParseError;
use crate::header::{ use crate::header::{
self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate,
IntoHeaderValue, InvalidHeaderValueBytes, Writer, IntoHeaderValue, InvalidHeaderValue, Writer,
}; };
use crate::httpmessage::HttpMessage; use crate::httpmessage::HttpMessage;
@ -87,7 +87,7 @@ impl Header for IfRange {
} }
impl Display for IfRange { impl Display for IfRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
IfRange::EntityTag(ref x) => Display::fmt(x, f), IfRange::EntityTag(ref x) => Display::fmt(x, f),
IfRange::Date(ref x) => Display::fmt(x, f), IfRange::Date(ref x) => Display::fmt(x, f),
@ -96,12 +96,12 @@ impl Display for IfRange {
} }
impl IntoHeaderValue for IfRange { impl IntoHeaderValue for IfRange {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut writer = Writer::new(); let mut writer = Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
HeaderValue::from_shared(writer.take()) HeaderValue::from_maybe_shared(writer.take())
} }
} }

View File

@ -159,18 +159,18 @@ macro_rules! header {
} }
impl std::fmt::Display for $id { impl std::fmt::Display for $id {
#[inline] #[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::fmt::Result {
$crate::http::header::fmt_comma_delimited(f, &self.0[..]) $crate::http::header::fmt_comma_delimited(f, &self.0[..])
} }
} }
impl $crate::http::header::IntoHeaderValue for $id { impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValueBytes; type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
use std::fmt::Write; use std::fmt::Write;
let mut writer = $crate::http::header::Writer::new(); let mut writer = $crate::http::header::Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
$crate::http::header::HeaderValue::from_shared(writer.take()) $crate::http::header::HeaderValue::from_maybe_shared(writer.take())
} }
} }
}; };
@ -195,18 +195,18 @@ macro_rules! header {
} }
impl std::fmt::Display for $id { impl std::fmt::Display for $id {
#[inline] #[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
$crate::http::header::fmt_comma_delimited(f, &self.0[..]) $crate::http::header::fmt_comma_delimited(f, &self.0[..])
} }
} }
impl $crate::http::header::IntoHeaderValue for $id { impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValueBytes; type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
use std::fmt::Write; use std::fmt::Write;
let mut writer = $crate::http::header::Writer::new(); let mut writer = $crate::http::header::Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
$crate::http::header::HeaderValue::from_shared(writer.take()) $crate::http::header::HeaderValue::from_maybe_shared(writer.take())
} }
} }
}; };
@ -231,12 +231,12 @@ macro_rules! header {
} }
impl std::fmt::Display for $id { impl std::fmt::Display for $id {
#[inline] #[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f) std::fmt::Display::fmt(&self.0, f)
} }
} }
impl $crate::http::header::IntoHeaderValue for $id { impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValueBytes; type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
self.0.try_into() self.0.try_into()
@ -276,7 +276,7 @@ macro_rules! header {
} }
impl std::fmt::Display for $id { impl std::fmt::Display for $id {
#[inline] #[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self { match *self {
$id::Any => f.write_str("*"), $id::Any => f.write_str("*"),
$id::Items(ref fields) => $crate::http::header::fmt_comma_delimited( $id::Items(ref fields) => $crate::http::header::fmt_comma_delimited(
@ -285,13 +285,13 @@ macro_rules! header {
} }
} }
impl $crate::http::header::IntoHeaderValue for $id { impl $crate::http::header::IntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValueBytes; type Error = $crate::http::header::InvalidHeaderValue;
fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> {
use std::fmt::Write; use std::fmt::Write;
let mut writer = $crate::http::header::Writer::new(); let mut writer = $crate::http::header::Writer::new();
let _ = write!(&mut writer, "{}", self); let _ = write!(&mut writer, "{}", self);
$crate::http::header::HeaderValue::from_shared(writer.take()) $crate::http::header::HeaderValue::from_maybe_shared(writer.take())
} }
} }
}; };

View File

@ -1,8 +1,9 @@
use std::collections::hash_map::{self, Entry};
use std::convert::TryFrom;
use either::Either; use either::Either;
use hashbrown::hash_map::{self, Entry}; use fxhash::FxHashMap;
use hashbrown::HashMap;
use http::header::{HeaderName, HeaderValue}; use http::header::{HeaderName, HeaderValue};
use http::HttpTryFrom;
/// A set of HTTP headers /// A set of HTTP headers
/// ///
@ -11,7 +12,7 @@ use http::HttpTryFrom;
/// [`HeaderName`]: struct.HeaderName.html /// [`HeaderName`]: struct.HeaderName.html
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct HeaderMap { pub struct HeaderMap {
pub(crate) inner: HashMap<HeaderName, Value>, pub(crate) inner: FxHashMap<HeaderName, Value>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -56,7 +57,7 @@ impl HeaderMap {
/// allocate. /// allocate.
pub fn new() -> Self { pub fn new() -> Self {
HeaderMap { HeaderMap {
inner: HashMap::new(), inner: FxHashMap::default(),
} }
} }
@ -70,7 +71,7 @@ impl HeaderMap {
/// More capacity than requested may be allocated. /// More capacity than requested may be allocated.
pub fn with_capacity(capacity: usize) -> HeaderMap { pub fn with_capacity(capacity: usize) -> HeaderMap {
HeaderMap { HeaderMap {
inner: HashMap::with_capacity(capacity), inner: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
} }
} }
@ -142,7 +143,7 @@ impl HeaderMap {
/// Returns `None` if there are no values associated with the key. /// Returns `None` if there are no values associated with the key.
/// ///
/// [`GetAll`]: struct.GetAll.html /// [`GetAll`]: struct.GetAll.html
pub fn get_all<N: AsName>(&self, name: N) -> GetAll { pub fn get_all<N: AsName>(&self, name: N) -> GetAll<'_> {
GetAll { GetAll {
idx: 0, idx: 0,
item: self.get2(name), item: self.get2(name),
@ -186,7 +187,7 @@ impl HeaderMap {
/// The iteration order is arbitrary, but consistent across platforms for /// The iteration order is arbitrary, but consistent across platforms for
/// the same crate version. Each key will be yielded once per associated /// the same crate version. Each key will be yielded once per associated
/// value. So, if a key has 3 associated values, it will be yielded 3 times. /// value. So, if a key has 3 associated values, it will be yielded 3 times.
pub fn iter(&self) -> Iter { pub fn iter(&self) -> Iter<'_> {
Iter::new(self.inner.iter()) Iter::new(self.inner.iter())
} }
@ -195,7 +196,7 @@ impl HeaderMap {
/// The iteration order is arbitrary, but consistent across platforms for /// The iteration order is arbitrary, but consistent across platforms for
/// the same crate version. Each key will be yielded only once even if it /// the same crate version. Each key will be yielded only once even if it
/// has multiple associated values. /// has multiple associated values.
pub fn keys(&self) -> Keys { pub fn keys(&self) -> Keys<'_> {
Keys(self.inner.keys()) Keys(self.inner.keys())
} }

View File

@ -1,6 +1,7 @@
//! Various http headers //! Various http headers
// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) // This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header)
use std::convert::TryFrom;
use std::{fmt, str::FromStr}; use std::{fmt, str::FromStr};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
@ -16,7 +17,6 @@ use crate::httpmessage::HttpMessage;
mod common; mod common;
pub(crate) mod map; pub(crate) mod map;
mod shared; mod shared;
#[doc(hidden)]
pub use self::common::*; pub use self::common::*;
#[doc(hidden)] #[doc(hidden)]
pub use self::shared::*; pub use self::shared::*;
@ -74,58 +74,58 @@ impl<'a> IntoHeaderValue for &'a [u8] {
} }
impl IntoHeaderValue for Bytes { impl IntoHeaderValue for Bytes {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(self) HeaderValue::from_maybe_shared(self)
} }
} }
impl IntoHeaderValue for Vec<u8> { impl IntoHeaderValue for Vec<u8> {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(Bytes::from(self)) HeaderValue::try_from(self)
} }
} }
impl IntoHeaderValue for String { impl IntoHeaderValue for String {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(Bytes::from(self)) HeaderValue::try_from(self)
} }
} }
impl IntoHeaderValue for usize { impl IntoHeaderValue for usize {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self); let s = format!("{}", self);
HeaderValue::from_shared(Bytes::from(s)) HeaderValue::try_from(s)
} }
} }
impl IntoHeaderValue for u64 { impl IntoHeaderValue for u64 {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self); let s = format!("{}", self);
HeaderValue::from_shared(Bytes::from(s)) HeaderValue::try_from(s)
} }
} }
impl IntoHeaderValue for Mime { impl IntoHeaderValue for Mime {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
#[inline] #[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_shared(Bytes::from(format!("{}", self))) HeaderValue::try_from(format!("{}", self))
} }
} }
@ -205,7 +205,7 @@ impl Writer {
} }
} }
fn take(&mut self) -> Bytes { fn take(&mut self) -> Bytes {
self.buf.take().freeze() self.buf.split().freeze()
} }
} }
@ -217,7 +217,7 @@ impl fmt::Write for Writer {
} }
#[inline] #[inline]
fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
fmt::write(self, args) fmt::write(self, args)
} }
} }
@ -259,7 +259,7 @@ pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, Pars
#[inline] #[inline]
#[doc(hidden)] #[doc(hidden)]
/// Format an array into a comma-delimited string. /// Format an array into a comma-delimited string.
pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result
where where
T: fmt::Display, T: fmt::Display,
{ {
@ -361,7 +361,7 @@ pub fn parse_extended_value(
} }
impl fmt::Display for ExtendedValue { impl fmt::Display for ExtendedValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let encoded_value = let encoded_value =
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE); percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
if let Some(ref lang) = self.language_tag { if let Some(ref lang) = self.language_tag {
@ -376,7 +376,7 @@ impl fmt::Display for ExtendedValue {
/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] /// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
/// ///
/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 /// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
fmt::Display::fmt(&encoded, f) fmt::Display::fmt(&encoded, f)
} }

View File

@ -98,7 +98,7 @@ impl Charset {
} }
impl Display for Charset { impl Display for Charset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.label()) f.write_str(self.label())
} }
} }

View File

@ -27,7 +27,7 @@ pub enum Encoding {
} }
impl fmt::Display for Encoding { impl fmt::Display for Encoding {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match *self { f.write_str(match *self {
Chunked => "chunked", Chunked => "chunked",
Brotli => "br", Brotli => "br",

View File

@ -1,7 +1,7 @@
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
use std::str::FromStr; use std::str::FromStr;
use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer}; use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
/// check that each char in the slice is either: /// check that each char in the slice is either:
/// 1. `%x21`, or /// 1. `%x21`, or
@ -113,7 +113,7 @@ impl EntityTag {
} }
impl Display for EntityTag { impl Display for EntityTag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.weak { if self.weak {
write!(f, "W/\"{}\"", self.tag) write!(f, "W/\"{}\"", self.tag)
} else { } else {
@ -157,12 +157,12 @@ impl FromStr for EntityTag {
} }
impl IntoHeaderValue for EntityTag { impl IntoHeaderValue for EntityTag {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = Writer::new(); let mut wrt = Writer::new();
write!(wrt, "{}", self).unwrap(); write!(wrt, "{}", self).unwrap();
HeaderValue::from_shared(wrt.take()) HeaderValue::from_maybe_shared(wrt.take())
} }
} }

View File

@ -1,117 +1,93 @@
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::io::Write; use std::io::Write;
use std::str::FromStr; use std::str::FromStr;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use bytes::{BufMut, BytesMut}; use bytes::{buf::BufMutExt, BytesMut};
use http::header::{HeaderValue, InvalidHeaderValueBytes}; use http::header::{HeaderValue, InvalidHeaderValue};
use time::{PrimitiveDateTime, OffsetDateTime, offset};
use crate::error::ParseError; use crate::error::ParseError;
use crate::header::IntoHeaderValue; use crate::header::IntoHeaderValue;
use crate::time_parser;
/// A timestamp with HTTP formatting and parsing /// A timestamp with HTTP formatting and parsing
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct HttpDate(time::Tm); pub struct HttpDate(OffsetDateTime);
impl FromStr for HttpDate { impl FromStr for HttpDate {
type Err = ParseError; type Err = ParseError;
fn from_str(s: &str) -> Result<HttpDate, ParseError> { fn from_str(s: &str) -> Result<HttpDate, ParseError> {
match time::strptime(s, "%a, %d %b %Y %T %Z") match time_parser::parse_http_date(s) {
.or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z")) Some(t) => Ok(HttpDate(t.assume_utc())),
.or_else(|_| time::strptime(s, "%c")) None => Err(ParseError::Header)
{
Ok(t) => Ok(HttpDate(t)),
Err(_) => Err(ParseError::Header),
} }
} }
} }
impl Display for HttpDate { impl Display for HttpDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0.to_utc().rfc822(), f) fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f)
} }
} }
impl From<time::Tm> for HttpDate { impl From<OffsetDateTime> for HttpDate {
fn from(tm: time::Tm) -> HttpDate { fn from(dt: OffsetDateTime) -> HttpDate {
HttpDate(tm) HttpDate(dt)
} }
} }
impl From<SystemTime> for HttpDate { impl From<SystemTime> for HttpDate {
fn from(sys: SystemTime) -> HttpDate { fn from(sys: SystemTime) -> HttpDate {
let tmspec = match sys.duration_since(UNIX_EPOCH) { HttpDate(PrimitiveDateTime::from(sys).assume_utc())
Ok(dur) => {
time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32)
}
Err(err) => {
let neg = err.duration();
time::Timespec::new(
-(neg.as_secs() as i64),
-(neg.subsec_nanos() as i32),
)
}
};
HttpDate(time::at_utc(tmspec))
} }
} }
impl IntoHeaderValue for HttpDate { impl IntoHeaderValue for HttpDate {
type Error = InvalidHeaderValueBytes; type Error = InvalidHeaderValue;
fn try_into(self) -> Result<HeaderValue, Self::Error> { fn try_into(self) -> Result<HeaderValue, Self::Error> {
let mut wrt = BytesMut::with_capacity(29).writer(); let mut wrt = BytesMut::with_capacity(29).writer();
write!(wrt, "{}", self.0.rfc822()).unwrap(); write!(wrt, "{}", self.0.to_offset(offset!(UTC)).format("%a, %d %b %Y %H:%M:%S GMT")).unwrap();
HeaderValue::from_shared(wrt.get_mut().take().freeze()) HeaderValue::from_maybe_shared(wrt.get_mut().split().freeze())
} }
} }
impl From<HttpDate> for SystemTime { impl From<HttpDate> for SystemTime {
fn from(date: HttpDate) -> SystemTime { fn from(date: HttpDate) -> SystemTime {
let spec = date.0.to_timespec(); let dt = date.0;
if spec.sec >= 0 { let epoch = OffsetDateTime::unix_epoch();
UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32)
} else { UNIX_EPOCH + (dt - epoch)
UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32)
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::HttpDate; use super::HttpDate;
use time::Tm; use time::{PrimitiveDateTime, date, time};
const NOV_07: HttpDate = HttpDate(Tm {
tm_nsec: 0,
tm_sec: 37,
tm_min: 48,
tm_hour: 8,
tm_mday: 7,
tm_mon: 10,
tm_year: 94,
tm_wday: 0,
tm_isdst: 0,
tm_yday: 0,
tm_utcoff: 0,
});
#[test] #[test]
fn test_date() { fn test_date() {
let nov_07 = HttpDate(PrimitiveDateTime::new(
date!(1994-11-07),
time!(8:48:37)
).assume_utc());
assert_eq!( 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 nov_07
); );
assert_eq!( assert_eq!(
"Sunday, 07-Nov-94 08:48:37 GMT" "Sunday, 07-Nov-94 08:48:37 GMT"
.parse::<HttpDate>() .parse::<HttpDate>()
.unwrap(), .unwrap(),
NOV_07 nov_07
); );
assert_eq!( assert_eq!(
"Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(), "Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(),
NOV_07 nov_07
); );
assert!("this-is-no-date".parse::<HttpDate>().is_err()); assert!("this-is-no-date".parse::<HttpDate>().is_err());
} }

View File

@ -53,7 +53,7 @@ impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
} }
impl<T: fmt::Display> fmt::Display for QualityItem<T> { impl<T: fmt::Display> fmt::Display for QualityItem<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.item, f)?; fmt::Display::fmt(&self.item, f)?;
match self.quality.0 { match self.quality.0 {
1000 => Ok(()), 1000 => Ok(()),

View File

@ -60,7 +60,7 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM
bytes.put_slice(&buf); bytes.put_slice(&buf);
if four { if four {
bytes.put(b' '); bytes.put_u8(b' ');
} }
} }
@ -115,7 +115,7 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
let mut curr: isize = 39; let mut curr: isize = 39;
let mut buf: [u8; 41] = unsafe { mem::uninitialized() }; let mut buf: [u8; 41] = unsafe { mem::MaybeUninit::uninit().assume_init() };
buf[39] = b'\r'; buf[39] = b'\r';
buf[40] = b'\n'; buf[40] = b'\n';
let buf_ptr = buf.as_mut_ptr(); let buf_ptr = buf.as_mut_ptr();
@ -203,33 +203,33 @@ mod tests {
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
bytes.reserve(50); bytes.reserve(50);
write_content_length(0, &mut bytes); write_content_length(0, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(9, &mut bytes); write_content_length(9, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(10, &mut bytes); write_content_length(10, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(99, &mut bytes); write_content_length(99, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(100, &mut bytes); write_content_length(100, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(101, &mut bytes); write_content_length(101, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(998, &mut bytes); write_content_length(998, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(1000, &mut bytes); write_content_length(1000, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(1001, &mut bytes); write_content_length(1001, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
bytes.reserve(50); bytes.reserve(50);
write_content_length(5909, &mut bytes); write_content_length(5909, &mut bytes);
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
} }
} }

View File

@ -29,7 +29,6 @@ impl Response {
STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED); STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED);
STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(Found, StatusCode::FOUND); STATIC_RESP!(Found, StatusCode::FOUND);
STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER); STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER);

View File

@ -25,10 +25,10 @@ pub trait HttpMessage: Sized {
fn take_payload(&mut self) -> Payload<Self::Stream>; fn take_payload(&mut self) -> Payload<Self::Stream>;
/// Request's extensions container /// Request's extensions container
fn extensions(&self) -> Ref<Extensions>; fn extensions(&self) -> Ref<'_, Extensions>;
/// Mutable reference to a the request's extensions container /// Mutable reference to a the request's extensions container
fn extensions_mut(&self) -> RefMut<Extensions>; fn extensions_mut(&self) -> RefMut<'_, Extensions>;
#[doc(hidden)] #[doc(hidden)]
/// Get a header /// Get a header
@ -105,7 +105,7 @@ pub trait HttpMessage: Sized {
/// Load request cookies. /// Load request cookies.
#[inline] #[inline]
fn cookies(&self) -> Result<Ref<Vec<Cookie<'static>>>, CookieParseError> { fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
if self.extensions().get::<Cookies>().is_none() { if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new(); let mut cookies = Vec::new();
for hdr in self.headers().get_all(header::COOKIE) { for hdr in self.headers().get_all(header::COOKIE) {
@ -153,12 +153,12 @@ where
} }
/// Request's extensions container /// Request's extensions container
fn extensions(&self) -> Ref<Extensions> { fn extensions(&self) -> Ref<'_, Extensions> {
(**self).extensions() (**self).extensions()
} }
/// Mutable reference to a the request's extensions container /// Mutable reference to a the request's extensions container
fn extensions_mut(&self) -> RefMut<Extensions> { fn extensions_mut(&self) -> RefMut<'_, Extensions> {
(**self).extensions_mut() (**self).extensions_mut()
} }
} }

View File

@ -1,20 +1,24 @@
//! Basic http primitives for actix-net framework. //! Basic http primitives for actix-net framework.
#![deny(rust_2018_idioms, warnings)]
#![allow( #![allow(
clippy::type_complexity, clippy::type_complexity,
clippy::too_many_arguments, clippy::too_many_arguments,
clippy::new_without_default, clippy::new_without_default,
clippy::borrow_interior_mutable_const, clippy::borrow_interior_mutable_const
clippy::write_with_newline
)] )]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
#[macro_use]
mod macros;
pub mod body; pub mod body;
mod builder; mod builder;
pub mod client; pub mod client;
mod cloneable; mod cloneable;
mod config; mod config;
#[cfg(feature = "compress")]
pub mod encoding; pub mod encoding;
mod extensions; mod extensions;
mod header; mod header;
@ -26,6 +30,7 @@ mod payload;
mod request; mod request;
mod response; mod response;
mod service; mod service;
mod time_parser;
pub mod cookie; pub mod cookie;
pub mod error; pub mod error;
@ -51,7 +56,7 @@ pub mod http {
// re-exports // re-exports
pub use http::header::{HeaderName, HeaderValue}; pub use http::header::{HeaderName, HeaderValue};
pub use http::uri::PathAndQuery; pub use http::uri::PathAndQuery;
pub use http::{uri, Error, HttpTryFrom, Uri}; pub use http::{uri, Error, Uri};
pub use http::{Method, StatusCode, Version}; pub use http::{Method, StatusCode, Version};
pub use crate::cookie::{Cookie, CookieBuilder}; pub use crate::cookie::{Cookie, CookieBuilder};
@ -64,3 +69,10 @@ pub mod http {
pub use crate::header::ContentEncoding; pub use crate::header::ContentEncoding;
pub use crate::message::ConnectionType; pub use crate::message::ConnectionType;
} }
/// Http protocol
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Protocol {
Http1,
Http2,
}

95
actix-http/src/macros.rs Normal file
View File

@ -0,0 +1,95 @@
#[macro_export]
macro_rules! downcast_get_type_id {
() => {
/// A helper method to get the type ID of the type
/// this trait is implemented on.
/// This method is unsafe to *implement*, since `downcast_ref` relies
/// on the returned `TypeId` to perform a cast.
///
/// Unfortunately, Rust has no notion of a trait method that is
/// unsafe to implement (marking it as `unsafe` makes it unsafe
/// to *call*). As a workaround, we require this method
/// to return a private type along with the `TypeId`. This
/// private type (`PrivateHelper`) has a private constructor,
/// making it impossible for safe code to construct outside of
/// this module. This ensures that safe code cannot violate
/// type-safety by implementing this method.
#[doc(hidden)]
fn __private_get_type_id__(&self) -> (std::any::TypeId, PrivateHelper)
where
Self: 'static,
{
(std::any::TypeId::of::<Self>(), PrivateHelper(()))
}
}
}
//Generate implementation for dyn $name
#[macro_export]
macro_rules! downcast {
($name:ident) => {
/// A struct with a private constructor, for use with
/// `__private_get_type_id__`. Its single field is private,
/// ensuring that it can only be constructed from this module
#[doc(hidden)]
pub struct PrivateHelper(());
impl dyn $name + 'static {
/// Downcasts generic body to a specific type.
pub fn downcast_ref<T: $name + 'static>(&self) -> Option<&T> {
if self.__private_get_type_id__().0 == std::any::TypeId::of::<T>() {
// Safety: external crates cannot override the default
// implementation of `__private_get_type_id__`, since
// it requires returning a private type. We can therefore
// rely on the returned `TypeId`, which ensures that this
// case is correct.
unsafe { Some(&*(self as *const dyn $name as *const T)) }
} else {
None
}
}
/// Downcasts a generic body to a mutable specific type.
pub fn downcast_mut<T: $name + 'static>(&mut self) -> Option<&mut T> {
if self.__private_get_type_id__().0 == std::any::TypeId::of::<T>() {
// Safety: external crates cannot override the default
// implementation of `__private_get_type_id__`, since
// it requires returning a private type. We can therefore
// rely on the returned `TypeId`, which ensures that this
// case is correct.
unsafe {
Some(&mut *(self as *const dyn $name as *const T as *mut T))
}
} else {
None
}
}
}
};
}
#[cfg(test)]
mod tests {
trait MB {
downcast_get_type_id!();
}
downcast!(MB);
impl MB for String {}
impl MB for () {}
#[actix_rt::test]
async fn test_any_casting() {
let mut body = String::from("hello cast");
let resp_body: &mut dyn MB = &mut body;
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast");
let body = &mut resp_body.downcast_mut::<String>().unwrap();
body.push_str("!");
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast!");
let not_body = resp_body.downcast_ref::<()>();
assert!(not_body.is_none());
}
}

View File

@ -78,13 +78,13 @@ impl Head for RequestHead {
impl RequestHead { impl RequestHead {
/// Message extensions /// Message extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
self.extensions.borrow() self.extensions.borrow()
} }
/// Mutable reference to a the message's extensions /// Mutable reference to a the message's extensions
#[inline] #[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> { pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.extensions.borrow_mut() self.extensions.borrow_mut()
} }
@ -237,13 +237,13 @@ impl ResponseHead {
/// Message extensions /// Message extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
self.extensions.borrow() self.extensions.borrow()
} }
/// Mutable reference to a the message's extensions /// Mutable reference to a the message's extensions
#[inline] #[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> { pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.extensions.borrow_mut() self.extensions.borrow_mut()
} }
@ -388,6 +388,12 @@ impl BoxedResponseHead {
pub fn new(status: StatusCode) -> Self { pub fn new(status: StatusCode) -> Self {
RESPONSE_POOL.with(|p| p.get_message(status)) RESPONSE_POOL.with(|p| p.get_message(status))
} }
pub(crate) fn take(&mut self) -> Self {
BoxedResponseHead {
head: self.head.take(),
}
}
} }
impl std::ops::Deref for BoxedResponseHead { impl std::ops::Deref for BoxedResponseHead {
@ -406,7 +412,9 @@ impl std::ops::DerefMut for BoxedResponseHead {
impl Drop for BoxedResponseHead { impl Drop for BoxedResponseHead {
fn drop(&mut self) { fn drop(&mut self) {
RESPONSE_POOL.with(|p| p.release(self.head.take().unwrap())) if let Some(head) = self.head.take() {
RESPONSE_POOL.with(move |p| p.release(head))
}
} }
} }

View File

@ -1,11 +1,14 @@
use std::pin::Pin;
use std::task::{Context, Poll};
use bytes::Bytes; use bytes::Bytes;
use futures::{Async, Poll, Stream}; use futures_core::Stream;
use h2::RecvStream; use h2::RecvStream;
use crate::error::PayloadError; use crate::error::PayloadError;
/// Type represent boxed payload /// Type represent boxed payload
pub type PayloadStream = Box<dyn Stream<Item = Bytes, Error = PayloadError>>; pub type PayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
/// Type represent streaming payload /// Type represent streaming payload
pub enum Payload<S = PayloadStream> { pub enum Payload<S = PayloadStream> {
@ -48,18 +51,20 @@ impl<S> Payload<S> {
impl<S> Stream for Payload<S> impl<S> Stream for Payload<S>
where where
S: Stream<Item = Bytes, Error = PayloadError>, S: Stream<Item = Result<Bytes, PayloadError>> + Unpin,
{ {
type Item = Bytes; type Item = Result<Bytes, PayloadError>;
type Error = PayloadError;
#[inline] #[inline]
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll_next(
match self { self: Pin<&mut Self>,
Payload::None => Ok(Async::Ready(None)), cx: &mut Context<'_>,
Payload::H1(ref mut pl) => pl.poll(), ) -> Poll<Option<Self::Item>> {
Payload::H2(ref mut pl) => pl.poll(), match self.get_mut() {
Payload::Stream(ref mut pl) => pl.poll(), Payload::None => Poll::Ready(None),
Payload::H1(ref mut pl) => pl.readany(cx),
Payload::H2(ref mut pl) => Pin::new(pl).poll_next(cx),
Payload::Stream(ref mut pl) => Pin::new(pl).poll_next(cx),
} }
} }
} }

View File

@ -25,13 +25,13 @@ impl<P> HttpMessage for Request<P> {
/// Request extensions /// Request extensions
#[inline] #[inline]
fn extensions(&self) -> Ref<Extensions> { fn extensions(&self) -> Ref<'_, Extensions> {
self.head.extensions() self.head.extensions()
} }
/// Mutable reference to a the request's extensions /// Mutable reference to a the request's extensions
#[inline] #[inline]
fn extensions_mut(&self) -> RefMut<Extensions> { fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.head.extensions_mut() self.head.extensions_mut()
} }
@ -80,6 +80,11 @@ impl<P> Request<P> {
) )
} }
/// Get request's payload
pub fn payload(&mut self) -> &mut Payload<P> {
&mut self.payload
}
/// Get request's payload /// Get request's payload
pub fn take_payload(&mut self) -> Payload<P> { pub fn take_payload(&mut self) -> Payload<P> {
std::mem::replace(&mut self.payload, Payload::None) std::mem::replace(&mut self.payload, Payload::None)
@ -160,7 +165,7 @@ impl<P> Request<P> {
} }
impl<P> fmt::Debug for Request<P> { impl<P> fmt::Debug for Request<P> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!( writeln!(
f, f,
"\nRequest {:?} {}:{}", "\nRequest {:?} {}:{}",
@ -182,7 +187,7 @@ impl<P> fmt::Debug for Request<P> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use http::HttpTryFrom; use std::convert::TryFrom;
#[test] #[test]
fn test_basics() { fn test_basics() {
@ -199,7 +204,6 @@ mod tests {
assert_eq!(req.uri().query(), Some("q=1")); assert_eq!(req.uri().query(), Some("q=1"));
let s = format!("{:?}", req); let s = format!("{:?}", req);
println!("T: {:?}", s);
assert!(s.contains("Request HTTP/1.1 GET:/index.html")); assert!(s.contains("Request HTTP/1.1 GET:/index.html"));
} }
} }

View File

@ -1,11 +1,13 @@
//! Http response //! Http response
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
use std::io::Write; use std::convert::TryFrom;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, str}; use std::{fmt, str};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::future::{ok, FutureResult, IntoFuture}; use futures_core::Stream;
use futures::Stream;
use serde::Serialize; use serde::Serialize;
use serde_json; use serde_json;
@ -15,7 +17,7 @@ use crate::error::Error;
use crate::extensions::Extensions; use crate::extensions::Extensions;
use crate::header::{Header, IntoHeaderValue}; use crate::header::{Header, IntoHeaderValue};
use crate::http::header::{self, HeaderName, HeaderValue}; use crate::http::header::{self, HeaderName, HeaderValue};
use crate::http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; use crate::http::{Error as HttpError, HeaderMap, StatusCode};
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
/// An HTTP Response /// An HTTP Response
@ -51,7 +53,7 @@ impl Response<Body> {
/// Constructs an error response /// Constructs an error response
#[inline] #[inline]
pub fn from_error(error: Error) -> Response { pub fn from_error(error: Error) -> Response {
let mut resp = error.as_response_error().render_response(); let mut resp = error.as_response_error().error_response();
if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR {
error!("Internal Server Error: {:?}", error); error!("Internal Server Error: {:?}", error);
} }
@ -128,7 +130,7 @@ impl<B> Response<B> {
/// Get an iterator for the cookies set by this response /// Get an iterator for the cookies set by this response
#[inline] #[inline]
pub fn cookies(&self) -> CookieIter { pub fn cookies(&self) -> CookieIter<'_> {
CookieIter { CookieIter {
iter: self.head.headers.get_all(header::SET_COOKIE), iter: self.head.headers.get_all(header::SET_COOKIE),
} }
@ -136,7 +138,7 @@ impl<B> Response<B> {
/// Add a cookie to this response /// Add a cookie to this response
#[inline] #[inline]
pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> {
let h = &mut self.head.headers; let h = &mut self.head.headers;
HeaderValue::from_str(&cookie.to_string()) HeaderValue::from_str(&cookie.to_string())
.map(|c| { .map(|c| {
@ -184,17 +186,17 @@ impl<B> Response<B> {
/// Responses extensions /// Responses extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
self.head.extensions.borrow() self.head.extensions.borrow()
} }
/// Mutable reference to a the response's extensions /// Mutable reference to a the response's extensions
#[inline] #[inline]
pub fn extensions_mut(&mut self) -> RefMut<Extensions> { pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
self.head.extensions.borrow_mut() self.head.extensions.borrow_mut()
} }
/// Get body os this response /// Get body of this response
#[inline] #[inline]
pub fn body(&self) -> &ResponseBody<B> { pub fn body(&self) -> &ResponseBody<B> {
&self.body &self.body
@ -263,7 +265,7 @@ impl<B> Response<B> {
} }
impl<B: MessageBody> fmt::Debug for Response<B> { impl<B: MessageBody> fmt::Debug for Response<B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let res = writeln!( let res = writeln!(
f, f,
"\nResponse {:?} {}{}", "\nResponse {:?} {}{}",
@ -280,13 +282,15 @@ impl<B: MessageBody> fmt::Debug for Response<B> {
} }
} }
impl IntoFuture for Response { impl Future for Response {
type Item = Response; type Output = Result<Response, Error>;
type Error = Error;
type Future = FutureResult<Response, Error>;
fn into_future(self) -> Self::Future { fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
ok(self) Poll::Ready(Ok(Response {
head: self.head.take(),
body: self.body.take_body(),
error: self.error.take(),
}))
} }
} }
@ -350,7 +354,6 @@ impl ResponseBuilder {
/// )) /// ))
/// .finish()) /// .finish())
/// } /// }
/// fn main() {}
/// ``` /// ```
#[doc(hidden)] #[doc(hidden)]
pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self { pub fn set<H: Header>(&mut self, hdr: H) -> &mut Self {
@ -376,11 +379,11 @@ impl ResponseBuilder {
/// .header(http::header::CONTENT_TYPE, "application/json") /// .header(http::header::CONTENT_TYPE, "application/json")
/// .finish() /// .finish()
/// } /// }
/// fn main() {}
/// ``` /// ```
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(parts) = parts(&mut self.head, &self.err) {
@ -408,11 +411,11 @@ impl ResponseBuilder {
/// .set_header(http::header::CONTENT_TYPE, "application/json") /// .set_header(http::header::CONTENT_TYPE, "application/json")
/// .finish() /// .finish()
/// } /// }
/// fn main() {}
/// ``` /// ```
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(parts) = parts(&mut self.head, &self.err) {
@ -481,7 +484,8 @@ impl ResponseBuilder {
#[inline] #[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self pub fn content_type<V>(&mut self, value: V) -> &mut Self
where where
HeaderValue: HttpTryFrom<V>, HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
{ {
if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(parts) = parts(&mut self.head, &self.err) {
match HeaderValue::try_from(value) { match HeaderValue::try_from(value) {
@ -497,9 +501,7 @@ impl ResponseBuilder {
/// Set content length /// Set content length
#[inline] #[inline]
pub fn content_length(&mut self, len: u64) -> &mut Self { pub fn content_length(&mut self, len: u64) -> &mut Self {
let mut wrt = BytesMut::new().writer(); self.header(header::CONTENT_LENGTH, len)
let _ = write!(wrt, "{}", len);
self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze())
} }
/// Set a cookie /// Set a cookie
@ -583,14 +585,14 @@ impl ResponseBuilder {
/// Responses extensions /// Responses extensions
#[inline] #[inline]
pub fn extensions(&self) -> Ref<Extensions> { pub fn extensions(&self) -> Ref<'_, Extensions> {
let head = self.head.as_ref().expect("cannot reuse response builder"); let head = self.head.as_ref().expect("cannot reuse response builder");
head.extensions.borrow() head.extensions.borrow()
} }
/// Mutable reference to a the response's extensions /// Mutable reference to a the response's extensions
#[inline] #[inline]
pub fn extensions_mut(&mut self) -> RefMut<Extensions> { pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
let head = self.head.as_ref().expect("cannot reuse response builder"); let head = self.head.as_ref().expect("cannot reuse response builder");
head.extensions.borrow_mut() head.extensions.borrow_mut()
} }
@ -635,7 +637,7 @@ impl ResponseBuilder {
/// `ResponseBuilder` can not be used after this call. /// `ResponseBuilder` can not be used after this call.
pub fn streaming<S, E>(&mut self, stream: S) -> Response pub fn streaming<S, E>(&mut self, stream: S) -> Response
where where
S: Stream<Item = Bytes, Error = E> + 'static, S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
{ {
self.body(Body::from_message(BodyStream::new(stream))) self.body(Body::from_message(BodyStream::new(stream)))
@ -757,18 +759,16 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
} }
} }
impl IntoFuture for ResponseBuilder { impl Future for ResponseBuilder {
type Item = Response; type Output = Result<Response, Error>;
type Error = Error;
type Future = FutureResult<Response, Error>;
fn into_future(mut self) -> Self::Future { fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
ok(self.finish()) Poll::Ready(Ok(self.finish()))
} }
} }
impl fmt::Debug for ResponseBuilder { impl fmt::Debug for ResponseBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let head = self.head.as_ref().unwrap(); let head = self.head.as_ref().unwrap();
let res = writeln!( let res = writeln!(
@ -992,6 +992,14 @@ mod tests {
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
} }
#[test]
fn test_serde_json_in_body() {
use serde_json::json;
let resp =
Response::build(StatusCode::OK).body(json!({"test-key":"test-value"}));
assert_eq!(resp.body().get_ref(), br#"{"test-key":"test-value"}"#);
}
#[test] #[test]
fn test_into_response() { fn test_into_response() {
let resp: Response = "test".into(); let resp: Response = "test".into();

View File

@ -1,14 +1,16 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::{fmt, io, net, rc}; use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, net, rc};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_server_config::{ use actix_rt::net::TcpStream;
Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig, use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory};
}; use bytes::Bytes;
use actix_service::{IntoNewService, NewService, Service}; use futures_core::{ready, Future};
use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures_util::future::ok;
use futures::{try_ready, Async, Future, IntoFuture, Poll};
use h2::server::{self, Handshake}; use h2::server::{self, Handshake};
use pin_project::{pin_project, project};
use crate::body::MessageBody; use crate::body::MessageBody;
use crate::builder::HttpServiceBuilder; use crate::builder::HttpServiceBuilder;
@ -18,24 +20,24 @@ use crate::error::{DispatchError, Error};
use crate::helpers::DataFactory; use crate::helpers::DataFactory;
use crate::request::Request; use crate::request::Request;
use crate::response::Response; use crate::response::Response;
use crate::{h1, h2::Dispatcher}; use crate::{h1, h2::Dispatcher, Protocol};
/// `NewService` HTTP1.1/HTTP2 transport implementation /// `ServiceFactory` HTTP1.1/HTTP2 transport implementation
pub struct HttpService<T, P, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler<T>> { pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler<T>> {
srv: S, srv: S,
cfg: ServiceConfig, cfg: ServiceConfig,
expect: X, expect: X,
upgrade: Option<U>, upgrade: Option<U>,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, S, B> HttpService<T, (), S, B> impl<T, S, B> HttpService<T, S, B>
where where
S: NewService<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
@ -45,22 +47,22 @@ where
} }
} }
impl<T, P, S, B> HttpService<T, P, S, B> impl<T, S, B> HttpService<T, S, B>
where where
S: NewService<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
/// Create new `HttpService` instance. /// Create new `HttpService` instance.
pub fn new<F: IntoNewService<S>>(service: F) -> Self { pub fn new<F: IntoServiceFactory<S>>(service: F) -> Self {
let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0, false, None);
HttpService { HttpService {
cfg, cfg,
srv: service.into_new_service(), srv: service.into_factory(),
expect: h1::ExpectHandler, expect: h1::ExpectHandler,
upgrade: None, upgrade: None,
on_connect: None, on_connect: None,
@ -69,13 +71,13 @@ where
} }
/// Create new `HttpService` instance with config. /// Create new `HttpService` instance with config.
pub(crate) fn with_config<F: IntoNewService<S>>( pub(crate) fn with_config<F: IntoServiceFactory<S>>(
cfg: ServiceConfig, cfg: ServiceConfig,
service: F, service: F,
) -> Self { ) -> Self {
HttpService { HttpService {
cfg, cfg,
srv: service.into_new_service(), srv: service.into_factory(),
expect: h1::ExpectHandler, expect: h1::ExpectHandler,
upgrade: None, upgrade: None,
on_connect: None, on_connect: None,
@ -84,12 +86,13 @@ where
} }
} }
impl<T, P, S, B, X, U> HttpService<T, P, S, B, X, U> impl<T, S, B, X, U> HttpService<T, S, B, X, U>
where where
S: NewService<Config = SrvConfig, Request = Request>, S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody, B: MessageBody,
{ {
/// Provide service for `EXPECT: 100-Continue` support. /// Provide service for `EXPECT: 100-Continue` support.
@ -97,11 +100,12 @@ where
/// Service get called with request that contains `EXPECT` header. /// Service get called with request that contains `EXPECT` header.
/// Service must return request in case of success, in that case /// Service must return request in case of success, in that case
/// request will be forwarded to main service. /// request will be forwarded to main service.
pub fn expect<X1>(self, expect: X1) -> HttpService<T, P, S, B, X1, U> pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
where where
X1: NewService<Config = SrvConfig, Request = Request, Response = Request>, X1: ServiceFactory<Config = (), Request = Request, Response = Request>,
X1::Error: Into<Error>, X1::Error: Into<Error>,
X1::InitError: fmt::Debug, X1::InitError: fmt::Debug,
<X1::Service as Service>::Future: 'static,
{ {
HttpService { HttpService {
expect, expect,
@ -117,15 +121,16 @@ where
/// ///
/// If service is provided then normal requests handling get halted /// If service is provided then normal requests handling get halted
/// and this service get called with original request and framed object. /// and this service get called with original request and framed object.
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, P, S, B, X, U1> pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, S, B, X, U1>
where where
U1: NewService< U1: ServiceFactory<
Config = SrvConfig, Config = (),
Request = (Request, Framed<T, h1::Codec>), Request = (Request, Framed<T, h1::Codec>),
Response = (), Response = (),
>, >,
U1::Error: fmt::Display, U1::Error: fmt::Display,
U1::InitError: fmt::Debug, U1::InitError: fmt::Debug,
<U1::Service as Service>::Future: 'static,
{ {
HttpService { HttpService {
upgrade, upgrade,
@ -147,126 +152,312 @@ where
} }
} }
impl<T, P, S, B, X, U> NewService for HttpService<T, P, S, B, X, U> impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
where where
T: IoStream, S: ServiceFactory<Config = (), Request = Request>,
S: NewService<Config = SrvConfig, Request = Request>, S::Error: Into<Error> + 'static,
S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
X: NewService<Config = SrvConfig, Request = Request, Response = Request>, X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService< <X::Service as Service>::Future: 'static,
Config = SrvConfig, U: ServiceFactory<
Config = (),
Request = (Request, Framed<TcpStream, h1::Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
{
/// Create simple tcp stream service
pub fn tcp(
self,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = DispatchError,
InitError = (),
> {
pipeline_factory(|io: TcpStream| {
let peer_addr = io.peer_addr().ok();
ok((io, Protocol::Http1, peer_addr))
})
.and_then(self)
}
}
#[cfg(feature = "openssl")]
mod openssl {
use super::*;
use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream};
use actix_tls::{openssl::HandshakeError, SslError};
impl<S, B, X, U> HttpService<SslStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<SslStream<TcpStream>, h1::Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
{
/// Create openssl based service
pub fn openssl(
self,
acceptor: SslAcceptor,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<HandshakeError<TcpStream>, DispatchError>,
InitError = (),
> {
pipeline_factory(
Acceptor::new(acceptor)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(|io: SslStream<TcpStream>| {
let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() {
if protos.windows(2).any(|window| window == b"h2") {
Protocol::Http2
} else {
Protocol::Http1
}
} else {
Protocol::Http1
};
let peer_addr = io.get_ref().peer_addr().ok();
ok((io, proto, peer_addr))
})
.and_then(self.map_err(SslError::Service))
}
}
}
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, Session, TlsStream};
use actix_tls::SslError;
use std::io;
impl<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<TlsStream<TcpStream>, h1::Codec>),
Response = (),
>,
U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
{
/// Create openssl based service
pub fn rustls(
self,
mut config: ServerConfig,
) -> impl ServiceFactory<
Config = (),
Request = TcpStream,
Response = (),
Error = SslError<io::Error, DispatchError>,
InitError = (),
> {
let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()];
config.set_protocols(&protos);
pipeline_factory(
Acceptor::new(config)
.map_err(SslError::Ssl)
.map_init_err(|_| panic!()),
)
.and_then(|io: TlsStream<TcpStream>| {
let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() {
if protos.windows(2).any(|window| window == b"h2") {
Protocol::Http2
} else {
Protocol::Http1
}
} else {
Protocol::Http1
};
let peer_addr = io.get_ref().0.peer_addr().ok();
ok((io, proto, peer_addr))
})
.and_then(self.map_err(SslError::Service))
}
}
}
impl<T, S, B, X, U> ServiceFactory for HttpService<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
S: ServiceFactory<Config = (), Request = Request>,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Config = (), Request = Request, Response = Request>,
X::Error: Into<Error>,
X::InitError: fmt::Debug,
<X::Service as Service>::Future: 'static,
U: ServiceFactory<
Config = (),
Request = (Request, Framed<T, h1::Codec>), Request = (Request, Framed<T, h1::Codec>),
Response = (), Response = (),
>, >,
U::Error: fmt::Display, U::Error: fmt::Display + Into<Error>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
{ {
type Config = SrvConfig; type Config = ();
type Request = ServerIo<T, P>; type Request = (T, Protocol, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type InitError = (); type InitError = ();
type Service = HttpServiceHandler<T, P, S::Service, B, X::Service, U::Service>; type Service = HttpServiceHandler<T, S::Service, B, X::Service, U::Service>;
type Future = HttpServiceResponse<T, P, S, B, X, U>; type Future = HttpServiceResponse<T, S, B, X, U>;
fn new_service(&self, cfg: &SrvConfig) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
HttpServiceResponse { HttpServiceResponse {
fut: self.srv.new_service(cfg).into_future(), fut: self.srv.new_service(()),
fut_ex: Some(self.expect.new_service(cfg)), fut_ex: Some(self.expect.new_service(())),
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(cfg)), fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())),
expect: None, expect: None,
upgrade: None, upgrade: None,
on_connect: self.on_connect.clone(), on_connect: self.on_connect.clone(),
cfg: Some(self.cfg.clone()), cfg: self.cfg.clone(),
_t: PhantomData, _t: PhantomData,
} }
} }
} }
#[doc(hidden)] #[doc(hidden)]
pub struct HttpServiceResponse<T, P, S: NewService, B, X: NewService, U: NewService> { #[pin_project]
pub struct HttpServiceResponse<
T,
S: ServiceFactory,
B,
X: ServiceFactory,
U: ServiceFactory,
> {
#[pin]
fut: S::Future, fut: S::Future,
#[pin]
fut_ex: Option<X::Future>, fut_ex: Option<X::Future>,
#[pin]
fut_upg: Option<U::Future>, fut_upg: Option<U::Future>,
expect: Option<X::Service>, expect: Option<X::Service>,
upgrade: Option<U::Service>, upgrade: Option<U::Service>,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
cfg: Option<ServiceConfig>, cfg: ServiceConfig,
_t: PhantomData<(T, P, B)>, _t: PhantomData<(T, B)>,
} }
impl<T, P, S, B, X, U> Future for HttpServiceResponse<T, P, S, B, X, U> impl<T, S, B, X, U> Future for HttpServiceResponse<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: NewService<Request = Request>, S: ServiceFactory<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
<S::Service as Service>::Future: 'static, <S::Service as Service>::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
X: NewService<Request = Request, Response = Request>, X: ServiceFactory<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
X::InitError: fmt::Debug, X::InitError: fmt::Debug,
U: NewService<Request = (Request, Framed<T, h1::Codec>), Response = ()>, <X::Service as Service>::Future: 'static,
U: ServiceFactory<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
<U::Service as Service>::Future: 'static,
{ {
type Item = HttpServiceHandler<T, P, S::Service, B, X::Service, U::Service>; type Output =
type Error = (); Result<HttpServiceHandler<T, S::Service, B, X::Service, U::Service>, ()>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(ref mut fut) = self.fut_ex { let mut this = self.as_mut().project();
let expect = try_ready!(fut
.poll() if let Some(fut) = this.fut_ex.as_pin_mut() {
.map_err(|e| log::error!("Init http service error: {:?}", e))); let expect = ready!(fut
self.expect = Some(expect); .poll(cx)
self.fut_ex.take(); .map_err(|e| log::error!("Init http service error: {:?}", e)))?;
this = self.as_mut().project();
*this.expect = Some(expect);
this.fut_ex.set(None);
} }
if let Some(ref mut fut) = self.fut_upg { if let Some(fut) = this.fut_upg.as_pin_mut() {
let upgrade = try_ready!(fut let upgrade = ready!(fut
.poll() .poll(cx)
.map_err(|e| log::error!("Init http service error: {:?}", e))); .map_err(|e| log::error!("Init http service error: {:?}", e)))?;
self.upgrade = Some(upgrade); this = self.as_mut().project();
self.fut_ex.take(); *this.upgrade = Some(upgrade);
this.fut_ex.set(None);
} }
let service = try_ready!(self let result = ready!(this
.fut .fut
.poll() .poll(cx)
.map_err(|e| log::error!("Init http service error: {:?}", e))); .map_err(|e| log::error!("Init http service error: {:?}", e)));
Ok(Async::Ready(HttpServiceHandler::new( Poll::Ready(result.map(|service| {
self.cfg.take().unwrap(), let this = self.as_mut().project();
HttpServiceHandler::new(
this.cfg.clone(),
service, service,
self.expect.take().unwrap(), this.expect.take().unwrap(),
self.upgrade.take(), this.upgrade.take(),
self.on_connect.clone(), this.on_connect.clone(),
))) )
}))
} }
} }
/// `Service` implementation for http transport /// `Service` implementation for http transport
pub struct HttpServiceHandler<T, P, S, B, X, U> { pub struct HttpServiceHandler<T, S: Service, B, X: Service, U: Service> {
srv: CloneableService<S>, srv: CloneableService<S>,
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>, upgrade: Option<CloneableService<U>>,
cfg: ServiceConfig, cfg: ServiceConfig,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
_t: PhantomData<(T, P, B, X)>, _t: PhantomData<(T, B, X)>,
} }
impl<T, P, S, B, X, U> HttpServiceHandler<T, P, S, B, X, U> impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
@ -279,7 +470,7 @@ where
expect: X, expect: X,
upgrade: Option<U>, upgrade: Option<U>,
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>, on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
) -> HttpServiceHandler<T, P, S, B, X, U> { ) -> HttpServiceHandler<T, S, B, X, U> {
HttpServiceHandler { HttpServiceHandler {
cfg, cfg,
on_connect, on_connect,
@ -291,28 +482,28 @@ where
} }
} }
impl<T, P, S, B, X, U> Service for HttpServiceHandler<T, P, S, B, X, U> impl<T, S, B, X, U> Service for HttpServiceHandler<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>, U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display + Into<Error>,
{ {
type Request = ServerIo<T, P>; type Request = (T, Protocol, Option<net::SocketAddr>);
type Response = (); type Response = ();
type Error = DispatchError; type Error = DispatchError;
type Future = HttpServiceHandlerResponse<T, S, B, X, U>; type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let ready = self let ready = self
.expect .expect
.poll_ready() .poll_ready(cx)
.map_err(|e| { .map_err(|e| {
let e = e.into(); let e = e.into();
log::error!("Http service readiness error: {:?}", e); log::error!("Http service readiness error: {:?}", e);
@ -322,7 +513,7 @@ where
let ready = self let ready = self
.srv .srv
.poll_ready() .poll_ready(cx)
.map_err(|e| { .map_err(|e| {
let e = e.into(); let e = e.into();
log::error!("Http service readiness error: {:?}", e); log::error!("Http service readiness error: {:?}", e);
@ -331,16 +522,27 @@ where
.is_ready() .is_ready()
&& ready; && ready;
if ready { let ready = if let Some(ref mut upg) = self.upgrade {
Ok(Async::Ready(())) upg.poll_ready(cx)
.map_err(|e| {
let e = e.into();
log::error!("Http service readiness error: {:?}", e);
DispatchError::Service(e)
})?
.is_ready()
&& ready
} else { } else {
Ok(Async::NotReady) ready
};
if ready {
Poll::Ready(Ok(()))
} else {
Poll::Pending
} }
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future {
let (io, _, proto) = req.into_parts();
let on_connect = if let Some(ref on_connect) = self.on_connect { let on_connect = if let Some(ref on_connect) = self.on_connect {
Some(on_connect(&io)) Some(on_connect(&io))
} else { } else {
@ -348,23 +550,16 @@ where
}; };
match proto { match proto {
Protocol::Http2 => { Protocol::Http2 => HttpServiceHandlerResponse {
let peer_addr = io.peer_addr(); state: State::H2Handshake(Some((
let io = Io {
inner: io,
unread: None,
};
HttpServiceHandlerResponse {
state: State::Handshake(Some((
server::handshake(io), server::handshake(io),
self.cfg.clone(), self.cfg.clone(),
self.srv.clone(), self.srv.clone(),
peer_addr,
on_connect, on_connect,
peer_addr,
))), ))),
} },
} Protocol::Http1 => HttpServiceHandlerResponse {
Protocol::Http10 | Protocol::Http11 => HttpServiceHandlerResponse {
state: State::H1(h1::Dispatcher::new( state: State::H1(h1::Dispatcher::new(
io, io,
self.cfg.clone(), self.cfg.clone(),
@ -372,234 +567,117 @@ where
self.expect.clone(), self.expect.clone(),
self.upgrade.clone(), self.upgrade.clone(),
on_connect, on_connect,
peer_addr,
)), )),
}, },
_ => HttpServiceHandlerResponse {
state: State::Unknown(Some((
io,
BytesMut::with_capacity(14),
self.cfg.clone(),
self.srv.clone(),
self.expect.clone(),
self.upgrade.clone(),
on_connect,
))),
},
} }
} }
} }
#[pin_project]
enum State<T, S, B, X, U> enum State<T, S, B, X, U>
where where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Future: 'static, S::Future: 'static,
S::Error: Into<Error>, S::Error: Into<Error>,
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>, U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
H1(h1::Dispatcher<T, S, B, X, U>), H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
H2(Dispatcher<Io<T>, S, B>), H2(#[pin] Dispatcher<T, S, B>),
Unknown( H2Handshake(
Option<( Option<(
T, Handshake<T, Bytes>,
BytesMut,
ServiceConfig, ServiceConfig,
CloneableService<S>, CloneableService<S>,
CloneableService<X>,
Option<CloneableService<U>>,
Option<Box<dyn DataFactory>>, Option<Box<dyn DataFactory>>,
)>,
),
Handshake(
Option<(
Handshake<Io<T>, Bytes>,
ServiceConfig,
CloneableService<S>,
Option<net::SocketAddr>, Option<net::SocketAddr>,
Option<Box<dyn DataFactory>>,
)>, )>,
), ),
} }
#[pin_project]
pub struct HttpServiceHandlerResponse<T, S, B, X, U> pub struct HttpServiceHandlerResponse<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>, U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
#[pin]
state: State<T, S, B, X, U>, state: State<T, S, B, X, U>,
} }
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U> impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error> + 'static,
S::Future: 'static, S::Future: 'static,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>> + 'static,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>, U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
type Item = (); type Output = Result<(), DispatchError>;
type Error = DispatchError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.state { self.project().state.poll(cx)
State::H1(ref mut disp) => disp.poll(),
State::H2(ref mut disp) => disp.poll(),
State::Unknown(ref mut data) => {
if let Some(ref mut item) = data {
loop {
// Safety - we only write to the returned slice.
let b = unsafe { item.1.bytes_mut() };
let n = try_ready!(item.0.poll_read(b));
if n == 0 {
return Ok(Async::Ready(()));
} }
// Safety - we know that 'n' bytes have }
// been initialized via the contract of
// 'poll_read' impl<T, S, B, X, U> State<T, S, B, X, U>
unsafe { item.1.advance_mut(n) }; where
if item.1.len() >= HTTP2_PREFACE.len() { T: AsyncRead + AsyncWrite + Unpin,
break; S: Service<Request = Request>,
} S::Error: Into<Error> + 'static,
} S::Response: Into<Response<B>> + 'static,
} else { B: MessageBody + 'static,
panic!() X: Service<Request = Request, Response = Request>,
} X::Error: Into<Error>,
let (io, buf, cfg, srv, expect, upgrade, on_connect) = U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
data.take().unwrap(); U::Error: fmt::Display,
if buf[..14] == HTTP2_PREFACE[..] { {
let peer_addr = io.peer_addr(); #[project]
let io = Io { fn poll(
inner: io, mut self: Pin<&mut Self>,
unread: Some(buf), cx: &mut Context<'_>,
}; ) -> Poll<Result<(), DispatchError>> {
self.state = State::Handshake(Some(( #[project]
server::handshake(io), match self.as_mut().project() {
cfg, State::H1(disp) => disp.poll(cx),
srv, State::H2(disp) => disp.poll(cx),
peer_addr, State::H2Handshake(ref mut data) => {
on_connect,
)));
} else {
self.state = State::H1(h1::Dispatcher::with_timeout(
io,
h1::Codec::new(cfg.clone()),
cfg,
buf,
None,
srv,
expect,
upgrade,
on_connect,
))
}
self.poll()
}
State::Handshake(ref mut data) => {
let conn = if let Some(ref mut item) = data { let conn = if let Some(ref mut item) = data {
match item.0.poll() { match Pin::new(&mut item.0).poll(cx) {
Ok(Async::Ready(conn)) => conn, Poll::Ready(Ok(conn)) => conn,
Ok(Async::NotReady) => return Ok(Async::NotReady), Poll::Ready(Err(err)) => {
Err(err) => {
trace!("H2 handshake error: {}", err); trace!("H2 handshake error: {}", err);
return Err(err.into()); return Poll::Ready(Err(err.into()));
} }
Poll::Pending => return Poll::Pending,
} }
} else { } else {
panic!() panic!()
}; };
let (_, cfg, srv, peer_addr, on_connect) = data.take().unwrap(); let (_, cfg, srv, on_connect, peer_addr) = data.take().unwrap();
self.state = State::H2(Dispatcher::new( self.set(State::H2(Dispatcher::new(
srv, conn, on_connect, cfg, None, peer_addr, srv, conn, on_connect, cfg, None, peer_addr,
)); )));
self.poll() self.poll(cx)
} }
} }
} }
} }
/// Wrapper for `AsyncRead + AsyncWrite` types
struct Io<T> {
unread: Option<BytesMut>,
inner: T,
}
impl<T: io::Read> io::Read for Io<T> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if let Some(mut bytes) = self.unread.take() {
let size = std::cmp::min(buf.len(), bytes.len());
buf[..size].copy_from_slice(&bytes[..size]);
if bytes.len() > size {
bytes.split_to(size);
self.unread = Some(bytes);
}
Ok(size)
} else {
self.inner.read(buf)
}
}
}
impl<T: io::Write> io::Write for Io<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<T: AsyncRead> AsyncRead for Io<T> {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
self.inner.prepare_uninitialized_buffer(buf)
}
}
impl<T: AsyncWrite> AsyncWrite for Io<T> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.inner.shutdown()
}
fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
self.inner.write_buf(buf)
}
}
impl<T: IoStream> IoStream for Io<T> {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
self.inner.peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.inner.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<std::time::Duration>) -> io::Result<()> {
self.inner.set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<std::time::Duration>) -> io::Result<()> {
self.inner.set_keepalive(dur)
}
}

View File

@ -1,14 +1,15 @@
//! Test Various helpers for Actix applications to use during testing. //! Test Various helpers for Actix applications to use during testing.
use std::convert::TryFrom;
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::io; use std::io::{self, Read, Write};
use std::pin::Pin;
use std::str::FromStr; use std::str::FromStr;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::IoStream; use bytes::{Bytes, BytesMut};
use bytes::{Buf, Bytes, BytesMut};
use futures::{Async, Poll};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use http::{HttpTryFrom, Method, Uri, Version}; use http::{Error as HttpError, Method, Uri, Version};
use percent_encoding::percent_encode; use percent_encoding::percent_encode;
use crate::cookie::{Cookie, CookieJar, USERINFO}; use crate::cookie::{Cookie, CookieJar, USERINFO};
@ -20,8 +21,6 @@ use crate::Request;
/// Test `Request` builder /// Test `Request` builder
/// ///
/// ```rust,ignore /// ```rust,ignore
/// # extern crate http;
/// # extern crate actix_web;
/// # use http::{header, StatusCode}; /// # use http::{header, StatusCode};
/// # use actix_web::*; /// # use actix_web::*;
/// use actix_web::test::TestRequest; /// use actix_web::test::TestRequest;
@ -34,7 +33,6 @@ use crate::Request;
/// } /// }
/// } /// }
/// ///
/// fn main() {
/// let resp = TestRequest::with_header("content-type", "text/plain") /// let resp = TestRequest::with_header("content-type", "text/plain")
/// .run(&index) /// .run(&index)
/// .unwrap(); /// .unwrap();
@ -42,7 +40,6 @@ use crate::Request;
/// ///
/// let resp = TestRequest::default().run(&index).unwrap(); /// let resp = TestRequest::default().run(&index).unwrap();
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
/// }
/// ``` /// ```
pub struct TestRequest(Option<Inner>); pub struct TestRequest(Option<Inner>);
@ -82,7 +79,8 @@ impl TestRequest {
/// Create TestRequest and set header /// Create TestRequest and set header
pub fn with_header<K, V>(key: K, value: V) -> TestRequest pub fn with_header<K, V>(key: K, value: V) -> TestRequest
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
TestRequest::default().header(key, value).take() TestRequest::default().header(key, value).take()
@ -118,7 +116,8 @@ impl TestRequest {
/// Set a header /// Set a header
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where where
HeaderName: HttpTryFrom<K>, HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue, V: IntoHeaderValue,
{ {
if let Ok(key) = HeaderName::try_from(key) { if let Ok(key) = HeaderName::try_from(key) {
@ -150,7 +149,7 @@ impl TestRequest {
/// Complete request creation and generate `Request` instance /// Complete request creation and generate `Request` instance
pub fn finish(&mut self) -> Request { pub fn finish(&mut self) -> Request {
let inner = self.0.take().expect("cannot reuse test request builder");; let inner = self.0.take().expect("cannot reuse test request builder");
let mut req = if let Some(pl) = inner.payload { let mut req = if let Some(pl) = inner.payload {
Request::with_payload(pl) Request::with_payload(pl)
@ -244,27 +243,30 @@ impl io::Write for TestBuffer {
} }
} }
impl AsyncRead for TestBuffer {} impl AsyncRead for TestBuffer {
fn poll_read(
self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(self.get_mut().read(buf))
}
}
impl AsyncWrite for TestBuffer { impl AsyncWrite for TestBuffer {
fn shutdown(&mut self) -> Poll<(), io::Error> { fn poll_write(
Ok(Async::Ready(())) self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(self.get_mut().write(buf))
} }
fn write_buf<B: Buf>(&mut self, _: &mut B) -> Poll<usize, io::Error> {
Ok(Async::NotReady) fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
} Poll::Ready(Ok(()))
} }
impl IoStream for TestBuffer { fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { Poll::Ready(Ok(()))
Ok(())
}
fn set_linger(&mut self, _dur: Option<std::time::Duration>) -> io::Result<()> {
Ok(())
}
fn set_keepalive(&mut self, _dur: Option<std::time::Duration>) -> io::Result<()> {
Ok(())
} }
} }

View File

@ -0,0 +1,42 @@
use time::{OffsetDateTime, PrimitiveDateTime, Date};
/// Attempt to parse a `time` string as one of either RFC 1123, RFC 850, or asctime.
pub fn parse_http_date(time: &str) -> Option<PrimitiveDateTime> {
try_parse_rfc_1123(time)
.or_else(|| try_parse_rfc_850(time))
.or_else(|| try_parse_asctime(time))
}
/// Attempt to parse a `time` string as a RFC 1123 formatted date time string.
fn try_parse_rfc_1123(time: &str) -> Option<PrimitiveDateTime> {
time::parse(time, "%a, %d %b %Y %H:%M:%S").ok()
}
/// Attempt to parse a `time` string as a RFC 850 formatted date time string.
fn try_parse_rfc_850(time: &str) -> Option<PrimitiveDateTime> {
match PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S") {
Ok(dt) => {
// If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3,
// we consider the year as part of this century if it's within the next 50 years,
// otherwise we consider as part of the previous century.
let now = OffsetDateTime::now();
let century_start_year = (now.year() / 100) * 100;
let mut expanded_year = century_start_year + dt.year();
if expanded_year > now.year() + 50 {
expanded_year -= 100;
}
match Date::try_from_ymd(expanded_year, dt.month(), dt.day()) {
Ok(date) => Some(PrimitiveDateTime::new(date, dt.time())),
Err(_) => None
}
}
Err(_) => None
}
}
/// Attempt to parse a `time` string using ANSI C's `asctime` format.
fn try_parse_asctime(time: &str) -> Option<PrimitiveDateTime> {
time::parse(time, "%a %b %_d %H:%M:%S %Y").ok()
}

View File

@ -12,10 +12,12 @@ pub enum Message {
Text(String), Text(String),
/// Binary message /// Binary message
Binary(Bytes), Binary(Bytes),
/// Continuation
Continuation(Item),
/// Ping message /// Ping message
Ping(String), Ping(Bytes),
/// Pong message /// Pong message
Pong(String), Pong(Bytes),
/// Close message with optional reason /// Close message with optional reason
Close(Option<CloseReason>), Close(Option<CloseReason>),
/// No-op. Useful for actix-net services /// No-op. Useful for actix-net services
@ -26,22 +28,41 @@ pub enum Message {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Frame { pub enum Frame {
/// Text frame, codec does not verify utf8 encoding /// Text frame, codec does not verify utf8 encoding
Text(Option<BytesMut>), Text(Bytes),
/// Binary frame /// Binary frame
Binary(Option<BytesMut>), Binary(Bytes),
/// Continuation
Continuation(Item),
/// Ping message /// Ping message
Ping(String), Ping(Bytes),
/// Pong message /// Pong message
Pong(String), Pong(Bytes),
/// Close message with optional reason /// Close message with optional reason
Close(Option<CloseReason>), Close(Option<CloseReason>),
} }
/// `WebSocket` continuation item
#[derive(Debug, PartialEq)]
pub enum Item {
FirstText(Bytes),
FirstBinary(Bytes),
Continue(Bytes),
Last(Bytes),
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
/// WebSockets protocol codec /// WebSockets protocol codec
pub struct Codec { pub struct Codec {
flags: Flags,
max_size: usize, max_size: usize,
server: bool, }
bitflags::bitflags! {
struct Flags: u8 {
const SERVER = 0b0000_0001;
const CONTINUATION = 0b0000_0010;
const W_CONTINUATION = 0b0000_0100;
}
} }
impl Codec { impl Codec {
@ -49,7 +70,7 @@ impl Codec {
pub fn new() -> Codec { pub fn new() -> Codec {
Codec { Codec {
max_size: 65_536, max_size: 65_536,
server: true, flags: Flags::SERVER,
} }
} }
@ -65,7 +86,7 @@ impl Codec {
/// ///
/// By default decoder works in server mode. /// By default decoder works in server mode.
pub fn client_mode(mut self) -> Self { pub fn client_mode(mut self) -> Self {
self.server = false; self.flags.remove(Flags::SERVER);
self self
} }
} }
@ -76,19 +97,94 @@ impl Encoder for Codec {
fn encode(&mut self, item: Message, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: Message, dst: &mut BytesMut) -> Result<(), Self::Error> {
match item { match item {
Message::Text(txt) => { Message::Text(txt) => Parser::write_message(
Parser::write_message(dst, txt, OpCode::Text, true, !self.server) dst,
txt,
OpCode::Text,
true,
!self.flags.contains(Flags::SERVER),
),
Message::Binary(bin) => Parser::write_message(
dst,
bin,
OpCode::Binary,
true,
!self.flags.contains(Flags::SERVER),
),
Message::Ping(txt) => Parser::write_message(
dst,
txt,
OpCode::Ping,
true,
!self.flags.contains(Flags::SERVER),
),
Message::Pong(txt) => Parser::write_message(
dst,
txt,
OpCode::Pong,
true,
!self.flags.contains(Flags::SERVER),
),
Message::Close(reason) => {
Parser::write_close(dst, reason, !self.flags.contains(Flags::SERVER))
} }
Message::Binary(bin) => { Message::Continuation(cont) => match cont {
Parser::write_message(dst, bin, OpCode::Binary, true, !self.server) Item::FirstText(data) => {
if self.flags.contains(Flags::W_CONTINUATION) {
return Err(ProtocolError::ContinuationStarted);
} else {
self.flags.insert(Flags::W_CONTINUATION);
Parser::write_message(
dst,
&data[..],
OpCode::Binary,
false,
!self.flags.contains(Flags::SERVER),
)
} }
Message::Ping(txt) => {
Parser::write_message(dst, txt, OpCode::Ping, true, !self.server)
} }
Message::Pong(txt) => { Item::FirstBinary(data) => {
Parser::write_message(dst, txt, OpCode::Pong, true, !self.server) if self.flags.contains(Flags::W_CONTINUATION) {
return Err(ProtocolError::ContinuationStarted);
} else {
self.flags.insert(Flags::W_CONTINUATION);
Parser::write_message(
dst,
&data[..],
OpCode::Text,
false,
!self.flags.contains(Flags::SERVER),
)
} }
Message::Close(reason) => Parser::write_close(dst, reason, !self.server), }
Item::Continue(data) => {
if self.flags.contains(Flags::W_CONTINUATION) {
Parser::write_message(
dst,
&data[..],
OpCode::Continue,
false,
!self.flags.contains(Flags::SERVER),
)
} else {
return Err(ProtocolError::ContinuationNotStarted);
}
}
Item::Last(data) => {
if self.flags.contains(Flags::W_CONTINUATION) {
self.flags.remove(Flags::W_CONTINUATION);
Parser::write_message(
dst,
&data[..],
OpCode::Continue,
true,
!self.flags.contains(Flags::SERVER),
)
} else {
return Err(ProtocolError::ContinuationNotStarted);
}
}
},
Message::Nop => (), Message::Nop => (),
} }
Ok(()) Ok(())
@ -100,15 +196,64 @@ impl Decoder for Codec {
type Error = ProtocolError; type Error = ProtocolError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
match Parser::parse(src, self.server, self.max_size) { match Parser::parse(src, self.flags.contains(Flags::SERVER), self.max_size) {
Ok(Some((finished, opcode, payload))) => { Ok(Some((finished, opcode, payload))) => {
// continuation is not supported // continuation is not supported
if !finished { if !finished {
return Err(ProtocolError::NoContinuation); return match opcode {
OpCode::Continue => {
if self.flags.contains(Flags::CONTINUATION) {
Ok(Some(Frame::Continuation(Item::Continue(
payload
.map(|pl| pl.freeze())
.unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationNotStarted)
}
}
OpCode::Binary => {
if !self.flags.contains(Flags::CONTINUATION) {
self.flags.insert(Flags::CONTINUATION);
Ok(Some(Frame::Continuation(Item::FirstBinary(
payload
.map(|pl| pl.freeze())
.unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationStarted)
}
}
OpCode::Text => {
if !self.flags.contains(Flags::CONTINUATION) {
self.flags.insert(Flags::CONTINUATION);
Ok(Some(Frame::Continuation(Item::FirstText(
payload
.map(|pl| pl.freeze())
.unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationStarted)
}
}
_ => {
error!("Unfinished fragment {:?}", opcode);
Err(ProtocolError::ContinuationFragment(opcode))
}
};
} }
match opcode { match opcode {
OpCode::Continue => Err(ProtocolError::NoContinuation), OpCode::Continue => {
if self.flags.contains(Flags::CONTINUATION) {
self.flags.remove(Flags::CONTINUATION);
Ok(Some(Frame::Continuation(Item::Last(
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationNotStarted)
}
}
OpCode::Bad => Err(ProtocolError::BadOpCode), OpCode::Bad => Err(ProtocolError::BadOpCode),
OpCode::Close => { OpCode::Close => {
if let Some(ref pl) = payload { if let Some(ref pl) = payload {
@ -118,29 +263,18 @@ impl Decoder for Codec {
Ok(Some(Frame::Close(None))) Ok(Some(Frame::Close(None)))
} }
} }
OpCode::Ping => { OpCode::Ping => Ok(Some(Frame::Ping(
if let Some(ref pl) = payload { payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
Ok(Some(Frame::Ping(String::from_utf8_lossy(pl).into()))) ))),
} else { OpCode::Pong => Ok(Some(Frame::Pong(
Ok(Some(Frame::Ping(String::new()))) payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
} ))),
} OpCode::Binary => Ok(Some(Frame::Binary(
OpCode::Pong => { payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
if let Some(ref pl) = payload { ))),
Ok(Some(Frame::Pong(String::from_utf8_lossy(pl).into()))) OpCode::Text => Ok(Some(Frame::Text(
} else { payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
Ok(Some(Frame::Pong(String::new()))) ))),
}
}
OpCode::Binary => Ok(Some(Frame::Binary(payload))),
OpCode::Text => {
Ok(Some(Frame::Text(payload)))
//let tmp = Vec::from(payload.as_ref());
//match String::from_utf8(tmp) {
// Ok(s) => Ok(Some(Message::Text(s))),
// Err(_) => Err(ProtocolError::BadEncoding),
//}
}
} }
} }
Ok(None) => Ok(None), Ok(None) => Ok(None),

View File

@ -1,19 +1,22 @@
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_service::{IntoService, Service}; use actix_service::{IntoService, Service};
use actix_utils::framed::{FramedTransport, FramedTransportError}; use actix_utils::framed;
use futures::{Future, Poll};
use super::{Codec, Frame, Message}; use super::{Codec, Frame, Message};
pub struct Transport<S, T> pub struct Dispatcher<S, T>
where where
S: Service<Request = Frame, Response = Message> + 'static, S: Service<Request = Frame, Response = Message> + 'static,
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
{ {
inner: FramedTransport<S, T, Codec>, inner: framed::Dispatcher<S, T, Codec>,
} }
impl<S, T> Transport<S, T> impl<S, T> Dispatcher<S, T>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
S: Service<Request = Frame, Response = Message>, S: Service<Request = Frame, Response = Message>,
@ -21,29 +24,28 @@ where
S::Error: 'static, S::Error: 'static,
{ {
pub fn new<F: IntoService<S>>(io: T, service: F) -> Self { pub fn new<F: IntoService<S>>(io: T, service: F) -> Self {
Transport { Dispatcher {
inner: FramedTransport::new(Framed::new(io, Codec::new()), service), inner: framed::Dispatcher::new(Framed::new(io, Codec::new()), service),
} }
} }
pub fn with<F: IntoService<S>>(framed: Framed<T, Codec>, service: F) -> Self { pub fn with<F: IntoService<S>>(framed: Framed<T, Codec>, service: F) -> Self {
Transport { Dispatcher {
inner: FramedTransport::new(framed, service), inner: framed::Dispatcher::new(framed, service),
} }
} }
} }
impl<S, T> Future for Transport<S, T> impl<S, T> Future for Dispatcher<S, T>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
S: Service<Request = Frame, Response = Message>, S: Service<Request = Frame, Response = Message>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
{ {
type Item = (); type Output = Result<(), framed::DispatcherError<S::Error, Codec>>;
type Error = FramedTransportError<S::Error, Codec>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.inner.poll() Pin::new(&mut self.inner).poll(cx)
} }
} }

View File

@ -1,6 +1,6 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use log::debug; use log::debug;
use rand; use rand;
@ -108,7 +108,7 @@ impl Parser {
} }
// remove prefix // remove prefix
src.split_to(idx); src.advance(idx);
// no need for body // no need for body
if length == 0 { if length == 0 {
@ -154,14 +154,14 @@ impl Parser {
} }
/// Generate binary representation /// Generate binary representation
pub fn write_message<B: Into<Bytes>>( pub fn write_message<B: AsRef<[u8]>>(
dst: &mut BytesMut, dst: &mut BytesMut,
pl: B, pl: B,
op: OpCode, op: OpCode,
fin: bool, fin: bool,
mask: bool, mask: bool,
) { ) {
let payload = pl.into(); let payload = pl.as_ref();
let one: u8 = if fin { let one: u8 = if fin {
0x80 | Into::<u8>::into(op) 0x80 | Into::<u8>::into(op)
} else { } else {
@ -180,11 +180,11 @@ impl Parser {
} else if payload_len <= 65_535 { } else if payload_len <= 65_535 {
dst.reserve(p_len + 4 + if mask { 4 } else { 0 }); dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
dst.put_slice(&[one, two | 126]); dst.put_slice(&[one, two | 126]);
dst.put_u16_be(payload_len as u16); dst.put_u16(payload_len as u16);
} else { } else {
dst.reserve(p_len + 10 + if mask { 4 } else { 0 }); dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
dst.put_slice(&[one, two | 127]); dst.put_slice(&[one, two | 127]);
dst.put_u64_be(payload_len as u64); dst.put_u64(payload_len as u64);
}; };
if mask { if mask {

View File

@ -51,7 +51,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
// inefficient, it could be done better. The compiler does not understand that // inefficient, it could be done better. The compiler does not understand that
// a `ShortSlice` must be smaller than a u64. // a `ShortSlice` must be smaller than a u64.
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
fn xor_short(buf: ShortSlice, mask: u64) { fn xor_short(buf: ShortSlice<'_>, mask: u64) {
// Unsafe: we know that a `ShortSlice` fits in a u64 // Unsafe: we know that a `ShortSlice` fits in a u64
unsafe { unsafe {
let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len()); let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len());
@ -77,7 +77,7 @@ unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] {
#[inline] #[inline]
// Splits a slice into three parts: an unaligned short head and tail, plus an aligned // Splits a slice into three parts: an unaligned short head and tail, plus an aligned
// u64 mid section. // u64 mid section.
fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) { fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
let start_ptr = buf.as_ptr() as usize; let start_ptr = buf.as_ptr() as usize;
let end_ptr = start_ptr + buf.len(); let end_ptr = start_ptr + buf.len();

View File

@ -13,15 +13,15 @@ use crate::message::RequestHead;
use crate::response::{Response, ResponseBuilder}; use crate::response::{Response, ResponseBuilder};
mod codec; mod codec;
mod dispatcher;
mod frame; mod frame;
mod mask; mod mask;
mod proto; mod proto;
mod transport;
pub use self::codec::{Codec, Frame, Message}; pub use self::codec::{Codec, Frame, Item, Message};
pub use self::dispatcher::Dispatcher;
pub use self::frame::Parser; pub use self::frame::Parser;
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
pub use self::transport::Transport;
/// Websocket protocol errors /// Websocket protocol errors
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
@ -44,12 +44,15 @@ pub enum ProtocolError {
/// A payload reached size limit. /// A payload reached size limit.
#[display(fmt = "A payload reached size limit.")] #[display(fmt = "A payload reached size limit.")]
Overflow, Overflow,
/// Continuation is not supported /// Continuation is not started
#[display(fmt = "Continuation is not supported.")] #[display(fmt = "Continuation is not started.")]
NoContinuation, ContinuationNotStarted,
/// Bad utf-8 encoding /// Received new continuation but it is already started
#[display(fmt = "Bad utf-8 encoding.")] #[display(fmt = "Received new continuation but it is already started")]
BadEncoding, ContinuationStarted,
/// Unknown continuation fragment
#[display(fmt = "Unknown continuation fragment.")]
ContinuationFragment(OpCode),
/// Io error /// Io error
#[display(fmt = "io error: {}", _0)] #[display(fmt = "io error: {}", _0)]
Io(io::Error), Io(io::Error),

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