1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-04 01:51:30 +02:00

Compare commits

...

498 Commits

Author SHA1 Message Date
64a2c13cdf the big three point oh (#1668) 2020-09-11 13:50:10 +01:00
bf53fe5a22 bump actix dependency to v0.10 (#1666) 2020-09-11 12:09:52 +01:00
cf5138e740 fix clippy async_yields_async lints (#1667) 2020-09-11 11:29:17 +01:00
121075c1ef awc: Rename Client::build to Client::builder (#1665) 2020-09-11 09:24:39 +01:00
22089aff87 Improve json, form and query extractor config docs (#1661) 2020-09-10 15:40:20 +01:00
7787638f26 fix CI clippy warnings (#1664) 2020-09-10 14:46:35 +01:00
2f6e9738c4 prepare multipart and actors releases (#1663) 2020-09-10 12:54:27 +01:00
e39d166a17 Fix examples hyperlink in README (#1660) 2020-09-10 00:12:50 +01:00
059d1671d7 prepare release beta 4 (#1659) 2020-09-09 22:14:11 +01:00
3a27580ebe awc: improve module documentation (#1656)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-09-09 14:24:12 +01:00
9d0534999d bump connect and tls versions (#1655) 2020-09-09 09:20:54 +01:00
c54d73e0bb Improve awc websocket docs (#1654)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-09-07 12:04:54 +01:00
9a9d4b182e document all remaining unsafe usages (#1642)
adds some debug assertions where appropriate
2020-09-03 10:00:24 +01:00
4e321595bc extract more config types from Data<T> as well (#1641) 2020-09-02 22:12:07 +01:00
01cbef700f Fix a small typo in a doc comment. (#1649) 2020-08-28 22:16:41 +01:00
8497b5f490 integrate with updated actix-{codec, utils} (#1634) 2020-08-24 10:13:35 +01:00
LJ
75d86a6beb Configurable trailing slash behaviour for NormalizePath (#1639)
Co-authored-by: ljoonal <ljoona@ljoonal.xyz>
2020-08-19 12:21:52 +01:00
3892a95c11 Fix actix-web version to publish 2020-08-18 01:16:18 +09:00
5802eb797f awc,web: Bump up to next beta releases (#1638) 2020-08-18 01:08:40 +09:00
ff2ca0f420 Update rustls to 0.18 (#1637) 2020-08-18 00:28:39 +09:00
59ad1738e9 web: Bump up to 3.0.0-beta.2 (#1636) 2020-08-17 11:32:38 +01:00
aa2bd6fbfb http: Bump up to 2.0.0-beta.3 (#1630) 2020-08-14 19:42:14 +09:00
5aad8e24c7 Re-export all error types from awc (#1621) 2020-08-14 01:24:35 +01:00
6e97bc09f8 Use action to upload docs 2020-08-13 16:04:50 +09:00
160995b8d4 fix awc pool leak (#1626) 2020-08-09 21:49:43 +01:00
187646b2f9 match HttpRequest app_data behavior in ServiceRequest (#1618) 2020-08-09 15:51:38 +01:00
46627be36f add dep graph dot graphs (#1601) 2020-08-09 13:54:35 +01:00
a78380739e require rustls feature for client example (#1625) 2020-08-09 13:32:37 +01:00
cf1c8abe62 prepare release http & awc (#1617) 2020-07-22 01:13:10 +01:00
92b5bcd13f Check format and tweak CI config (#1619) 2020-07-22 00:28:33 +01:00
701bdacfa2 Fix illegal chunked encoding (#1615)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-07-21 17:24:56 +01:00
6dc47c4093 fix soundness concern in h1 decoder (#1614)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-07-21 16:25:33 +01:00
0ec335a39c bump MSRV to 1.42 (#1616) 2020-07-21 16:40:30 +09:00
f8d5ad6b53 Make web::Path a tuple struct with a public inner value (#1594)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-07-21 00:54:26 +01:00
43c362779d also try extracting payload config as Data<T> (#1610) 2020-07-20 17:40:58 +01:00
971ba3eee1 fix continous growth of app data in pooled requests (#1609)
fixes #1606
fixes #1607
2020-07-18 16:17:00 +01:00
2fd96c03e5 prepare beta.1 release for multipart/files/actors (#1605) 2020-07-16 11:38:57 +01:00
ad7c6d2633 prepare actix-web v3.0.0-beta.1 release (#1600) 2020-07-15 00:44:44 +01:00
3362a3d61b Merge pull request #1603 from JohnTitor/license
Avoid using deprecated `/` in license field
2020-07-14 15:25:48 +09:00
769ea6bd5b Merge pull request #1602 from actix/release/awc-beta-1
prepare awc v2.0.0-beta.1 release
2020-07-14 13:33:05 +09:00
1382094c15 Avoid using deprecated / in license field 2020-07-14 11:19:56 +09:00
78594a72bd prepare awc v2.0.0-beta.1 release 2020-07-14 03:16:26 +01:00
327e472e44 prepare http-2.0.0-beta.1 release (#1596) 2020-07-13 15:35:30 +01:00
e10eb648d9 Fix leaks with actix_http's client (#1580) 2020-07-10 22:35:22 +01:00
a2662b928b Update PULL_REQUEST_TEMPLATE.md 2020-07-10 14:55:56 +01:00
84583799be Merge pull request #1592 from JohnTitor/fix-tarpaulin
Use tarpaulin 0.13 to avoid failure
2020-07-07 05:11:09 +09:00
08f9a34075 Use tarpaulin 0.13 to avoid failure 2020-07-07 04:00:18 +09:00
056803d534 revamp readme and root doc page (#1590) 2020-07-05 01:16:53 +01:00
deab634247 actix-http: Update sha-1 to 0.9 (#1586) 2020-07-03 01:08:24 +01:00
0b641a2db2 Merge pull request #1585 from JohnTitor/v-htmlescape
Update `v_htmlescape` to 0.10
2020-07-03 07:46:26 +09:00
f2d641b772 Update v_htmlescape to 0.10 2020-07-02 17:52:42 +09:00
23c8191cca add method to extract matched resource name (#1577)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-06-27 16:22:16 +01:00
487f90be5b move pull request template (#1578) 2020-06-23 14:26:07 +09:00
fa28175a74 add method to extract matched resource pattern (#1566) 2020-06-23 00:58:20 +01:00
a70e599ff5 re-export rt in web and add main macro (#1575) 2020-06-22 20:09:48 +01:00
c11052f826 add PR template for bugs and features (#1570)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-06-22 18:28:06 +09:00
73ec01e83b Merge pull request #1576 from b4skyx/patch-1
Update benchmark url in README.md
2020-06-22 16:48:49 +09:00
a7c8533291 Update benchmark url in README.md
Updated benchmark url from r18 to the latest r19.
2020-06-22 04:14:48 +00:00
eb0eda69c6 migrate cookie handling to cookie crate (#1558) 2020-06-19 14:34:14 +01:00
dc74db1f2f re-export actix_rt::main macro (#1559) 2020-06-18 15:45:30 +01:00
0ba73fc44c exclude default -web features on -actors (#1562) 2020-06-18 11:23:36 +01:00
9af07d66ae Fix NormalizePath trailing slash behavior (#1548) 2020-06-17 10:54:20 +01:00
e72ee28232 Enforce HW_BUFFER_SIZE inside h1::dispatcher (#1550) 2020-06-17 08:58:23 +01:00
4f9a1ac3b7 Merge pull request #1553 from taiki-e/pin-project
Remove uses of pin_project::project attribute
2020-06-07 02:00:01 +09:00
6c5c4ea230 Remove uses of pin_project::project attribute
pin-project will deprecate the project attribute due to some unfixable
limitations.

Refs: https://github.com/taiki-e/pin-project/issues/225
2020-06-06 06:44:14 +09:00
a79450482c Merge pull request #1547 from JohnTitor/appveyor
Remove AppVeyor config
2020-06-03 10:43:01 +09:00
5286b8aed7 Remove AppVeyor config 2020-06-03 03:39:35 +09:00
621ebec01a Fix typo in timeout error display (#1552) 2020-06-02 18:04:49 +01:00
5e5c8b1c83 Merge pull request #1544 from JohnTitor/remove-framed
Remove actix-framed from workspace
2020-05-31 16:10:46 +09:00
322e7c15d1 Remove actix-framed from workspace 2020-05-31 05:50:32 +09:00
7aa757ad5a Merge pull request #1540 from JohnTitor/next-multipart
multipart: Bump up to 0.3.0-alpha.1
2020-05-25 22:04:07 +09:00
482f74e409 multipart: Bump up to 0.3.0-alpha.1 2020-05-25 19:12:20 +09:00
19967c41cc Merge pull request #1538 from JohnTitor/codegen-meta
Tweak codegen metadata
2020-05-25 17:37:58 +09:00
8a106c07b4 Tweak codegen metadata 2020-05-25 16:45:34 +09:00
11a9fdad95 Merge pull request #1535 from JohnTitor/next-files
files: Bump up to 0.3.0-alpha.1
2020-05-24 11:52:19 +09:00
75a34dc8bc files: Bump up to 0.3.0-alpha.1 2020-05-23 18:47:08 +09:00
5b60ee8cfd Merge pull request #1534 from JohnTitor/next-codegen
codegen: Bump up to 0.2.2
2020-05-23 18:08:36 +09:00
bb89d04080 codegen: Bump up to 0.2.2 2020-05-23 17:22:30 +09:00
f57b5659da Merge pull request #1533 from JohnTitor/next-test-server
http-test: Bump up to 2.0.0-alpha.1
2020-05-23 15:22:40 +09:00
4a955c425d Update actix-http-test dependency to 2.0.0-alpha.1 2020-05-23 12:14:17 +09:00
905f86b540 http-test: Bump up to 2.0.0-alpha.1 2020-05-23 12:13:43 +09:00
0348114ad8 Merge pull request #1532 from JohnTitor/issue-template
Add links to Gitter on the issue template
2020-05-23 12:11:59 +09:00
9c5a2d6580 Add links to Gitter on the issue template 2020-05-22 13:14:01 +09:00
2550f00702 Merge pull request #1530 from NickKolpinskiy/itoa
Use `itoa` in the content-length helper
2020-05-22 13:03:50 +09:00
7d8fb631a0 Use itoa in the content-length helper 2020-05-21 22:25:34 +03:00
b9e268e95f Merge pull request #1529 from JohnTitor/next-web
web: Bump up to 3.0.0-alpha.3
2020-05-21 19:32:24 +09:00
6dd78d9355 Run rustfmt 2020-05-21 17:56:53 +09:00
fe89ba7027 Update actix-web dependency to 3.0.0-alpha.3 2020-05-21 17:32:36 +09:00
5d39110470 web: Bump up to 3.0.0-alpha.3 2020-05-21 17:31:22 +09:00
5bde4e2529 Merge pull request #1528 from JohnTitor/next-awc
awc: Bump up to 2.0.0-alpha.2
2020-05-21 17:25:37 +09:00
9b72d33b79 Update awc to 2.0.0-alpha.2 2020-05-21 16:48:20 +09:00
0f826fd11a awc: Bump up to 2.0.0-alpha.2 2020-05-21 16:47:16 +09:00
cf92cfa777 Merge pull request #1527 from JohnTitor/next-http
http: Bump up to 2.0.0-alpha.4
2020-05-21 16:35:34 +09:00
9cfb32c780 Update actix-http to 2.0.0-alpha.4 2020-05-21 15:22:42 +09:00
48fa78e182 http: Bump up to 2.0.0-alpha.4 2020-05-21 15:22:07 +09:00
184683a698 Merge pull request #1525 from JohnTitor/deps
Update dependencies
2020-05-21 11:58:59 +09:00
6c78f57a70 test-server: Update dependencies 2020-05-21 09:52:15 +09:00
603973dede awc: Update base64 to 0.12 2020-05-21 09:51:58 +09:00
8391427905 http: Update base64 to 0.12 2020-05-21 09:51:32 +09:00
2314dc30b5 Merge pull request #1519 from JohnTitor/sample-size
bench: Reduce sample count to remove warning
2020-05-21 01:49:17 +09:00
864fc489ce CI: Reduce sample size 2020-05-20 22:33:25 +09:00
c48af0c822 Merge pull request #1522 from actix/only-server-bench
CI: Only run the server benchmark to avoid SIGKILL
2020-05-20 15:22:13 +09:00
50adbdecbe CI: Only run the server benchmark to avoid SIGKILL 2020-05-20 13:12:08 +09:00
f8f5a82f40 Merge pull request #1518 from JohnTitor/replace-net2
Replace deprecated `net2` crate with `socket2`
2020-05-19 10:25:22 +09:00
9a7f93610a web: Replace net2 crate with socket2 2020-05-19 09:34:37 +09:00
2dac9afc4e test-server: Replace net2 crate with socket2 2020-05-19 09:25:51 +09:00
74491dca59 Merge pull request #1515 from JohnTitor/minimize-futures
Minimize `futures` dependencies
2020-05-19 09:18:07 +09:00
81b0c32062 test-server: Minimize futures dependencies 2020-05-19 08:29:12 +09:00
a98e53ecb8 web: Minimize futures dependencies 2020-05-19 08:29:12 +09:00
d7abbff3b0 awc: Minimize futures dependencies 2020-05-19 08:29:12 +09:00
24372467d9 codegen: Minimize futures dependencies 2020-05-19 08:29:11 +09:00
fc8e07b947 actors: Minimize futures dependencies 2020-05-19 08:29:11 +09:00
ab4d8704f1 multipart: Minimize futures dependencies 2020-05-19 08:29:11 +09:00
292af145cb http: Minimize futures dependencies 2020-05-19 08:29:11 +09:00
9bd6407730 framed: Minimize futures dependencies 2020-05-19 08:24:34 +09:00
245dd471dd files: Minimize futures dependencies 2020-05-19 08:24:34 +09:00
32a37b7282 Remove content_length from ResponseBuilder (#1491)
* Remove content_length since it'll be overwritten by the response body. FIXES #1439

* Add setting of Content-Length to the no_chunking function

* Add changes and migration documentations

* Update MIGRATION.md

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>

Co-authored-by: Rob Ede <robjtede@icloud.com>
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-05-19 07:46:31 +09:00
426a9b5d4d Merge pull request #1514 from actix/remove/sized64
remove needless BodySize::Sized64 variant
2020-05-18 10:40:01 +09:00
7e8ea44d5c remove needless BodySize::Sized64 variant 2020-05-18 00:42:51 +01:00
b0866a8a0f Actix-files will always send SizedStream (#1496)
* Fixes #1384

* There is no need to set no_chunking

* Test content-length for static files

* Update the tests

* Add Changelog

* Try to simply fix Windows test issues!

Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-05-18 06:54:42 +09:00
7fe426f626 Merge pull request #1512 from JohnTitor/remove-outdated-members
Remove outdated members
2020-05-17 12:30:03 +09:00
433a4563cf Remove outdated members 2020-05-17 10:56:06 +09:00
f3b0233477 use mem::take where possible (#1507) 2020-05-17 10:54:42 +09:00
201090d7a2 Provide impl<T> From<Arc<T>> for Data<T> (#1509) 2020-05-16 00:27:03 +01:00
4fc99d4a6f Fix audit issue logging by default peer address (#1485)
* Fix audit issue logging by default peer address

By default log format include remote address that is taken from headers.
This is very easy to replace making log untrusted.

Changing default log format value `%a` to peer address we are getting
this trusted data always. Also, remote address option is maintianed and
relegated to `%{r}a` value.

Related  kanidm/kanidm#191.

* Rename peer/remote to remote_addr/realip_remote_addr

Change names to avoid naming confusions. I choose this accord to Nginx
variables and
[ngx_http_realip_module](https://nginx.org/en/docs/http/ngx_http_realip_module.html).

Add more specific documentation about security concerns of using Real IP
in logger.

* Rename security advertise header in doc

* Add fix audit issue logging by default peer adress to changelog

Co-authored-by: Rob Ede <robjtede@icloud.com>
2020-05-15 09:07:27 +09:00
92ce975d87 Merge pull request #1506 from actix/chore/bump-140
bump msrv to 1.40
2020-05-13 23:46:13 +09:00
996f1d7eae bump msrv in ci and readme 2020-05-13 01:57:37 +01:00
63864ecf9e support parsing of SameSite=None (#1503) 2020-05-12 17:48:35 +01:00
bbd4d19830 Merge pull request #1486 from actix/feat/data-cascade
allow parent data containers to be accessed from child scopes
2020-05-09 09:40:25 +09:00
879cad9422 allow parent data containers to be accessed from child scopes 2020-05-09 00:31:26 +01:00
6e8ff5c905 Merge pull request #1495 from JohnTitor/new-web
actix-web: Bump up to 3.0.0-alpha.2
2020-05-08 07:28:18 +09:00
b66c3083a5 Update the actix-web dependency to 3.0.0-alpha.2 2020-05-08 06:46:42 +09:00
b6b3481c6f web: Bump up to 3.0.0-alpha.2 2020-05-08 06:46:13 +09:00
574714d156 Merge pull request #1494 from JohnTitor/new-actors
actors: Bump up to 3.0.0-alpha.1
2020-05-08 06:17:15 +09:00
54abf356d4 actors: Bump up to 3.0.0-alpha.1 2020-05-08 03:33:29 +09:00
9cb3b0ef58 Merge pull request #1493 from JohnTitor/http-next
http: Bump up to 2.0.0-alpha.3
2020-05-08 03:09:52 +09:00
9d0c80b6ce Update actix-http deps 2020-05-08 02:35:45 +09:00
0bc4a5e703 http: Bump up to 2.0.0-alpha.3 2020-05-08 02:35:45 +09:00
9d94fb91b2 correct spelling of ConnectError::Unresolved (#1487)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-05-08 02:26:48 +09:00
9164ed1f0c add resource middleware on actix-web-codegen (#1467)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-05-07 18:31:12 +09:00
b521e9b221 conditional test compilation [range, charset] (#1483)
* conditionally compile range and charset tests

* remove deprecated try macros

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-05-03 22:33:29 +09:00
f37cb6dd0b refactor h1 status line helper to remove unsafe usage (#1484)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-05-03 17:37:40 +09:00
d5ceae2074 Replace deprecated now with now_utc (#1481)
* Replace deprecated now with now_utc

* Update doctest
2020-05-02 10:14:50 +01:00
c27d3fad8e clarify resource/scope app data overriding (#1476)
* relocate FnDataFactory

* clarify app data overriding in Scope and Resource

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-04-30 02:20:47 +09:00
bb17280f51 simplify data factory future polling (#1473)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-04-29 15:38:53 +09:00
b047413b39 Small ws codec fix (#1465)
* Small ws codec fix

* Update actix-http/Changes.md

Co-authored-by: Huston Bokinsky <huston@deepgram.com>
2020-04-29 11:13:09 +09:00
Huy
ce24630d31 Fix typos in MIGRATION.md (#1470)
* Fix typos in MIGRATION.md

Those are `crate` not `create`

* Update MIGRATION.md
2020-04-23 03:39:09 +09:00
751253f23e Merge pull request #1463 from actix/fix/doc-typos
fix spelling errors in doc comments
2020-04-21 13:47:03 +09:00
5b0f7fff69 fix spelling errors in doc comments 2020-04-21 04:09:35 +01:00
54619cb680 actix-http: Remove failure support (#1449) 2020-04-16 06:54:34 +09:00
5b36381cb0 Merge pull request #1452 from actix/fix/default-service-data
set data container on default service calls
2020-04-16 06:01:56 +09:00
45e2e40140 set data container on default service calls
closes #1450
2020-04-14 02:33:19 +01:00
df3f722589 Merge pull request #1451 from actix/cache
Remove cache config from GHA workflows
2020-04-13 06:06:45 +09:00
e7ba871bbf Remove cache config from GHA workflows 2020-04-13 03:42:44 +09:00
ebc2e67015 Merge pull request #1442 from JohnTitor/workspace-doc
Deploy all the workspace crates' docs
2020-04-09 00:48:08 +09:00
74ddc852c8 Tweak README 2020-04-08 04:48:01 +09:00
dfaa330a94 Deploy all the workspace crates' docs 2020-04-08 04:42:38 +09:00
0ad02ee0e0 Add convenience functions for testing (#1401)
* Add convenience functions for testing

* Fix remarks from PR and add tests

* Add unpin to read_json_body

* Update changelog
2020-04-06 04:12:44 +09:00
aaff68bf05 Change NormalizePath to append trailing slash (#1433)
* Change NormalizePath to append trailing slash

* add tests

* Update CHANGES.md

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-04-05 03:26:40 +09:00
fcb1dec235 Merge pull request #1431 from OSSystems/topic/explicit-features-requirements
Add explicit feature requirements for examples and tests
2020-03-28 10:58:00 +09:00
7b7daa75a4 Add explicit feature requirements for examples and tests
This allow us to build 'actix-web' without default features and run all
tests.

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
2020-03-25 23:49:24 -03:00
2067331884 Refactor actix-codegen duplicate code (#1423)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-03-20 04:40:42 +09:00
bf630d9475 Merge pull request #1422 from OSSystems/topic/impl-error-more-types
Implement `std::error::Error` for our custom errors
2020-03-19 05:05:57 +09:00
146ae4da18 Implement std::error::Error for our custom errors
For allowing a more ergonomic use and better integration on the
ecosystem, this adds the `std::error::Error` `impl` for our custom
errors.

We intent to drop this hand made code once `derive_more` finishes the
addition of the Error derive support[1]. Until that is available, we
need to live with that.

1. https://github.com/JelteF/derive_more/issues/92

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
2020-03-18 00:22:18 -03:00
52c5755d56 Merge pull request #1421 from actix/JohnTitor-patch-1
Upload coverage on PRs
2020-03-18 06:16:41 +09:00
5548c57a09 Upload coverage on PRs 2020-03-18 05:04:30 +09:00
0d958fabd7 📝 Improve the code example for JsonConfig (#1418)
* 📝 Improve the code example for JsonConfig

* Remove a redundant comment
2020-03-17 08:23:54 +09:00
c67e4c1fe8 Refactored macros (#1333)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-03-15 07:23:28 +09:00
4875dfbec7 Merge pull request #1416 from JohnTitor/fix-doc
Fix `read_body` doc
2020-03-13 07:06:57 +09:00
d602a7e386 Fix read_body doc 2020-03-13 05:52:58 +09:00
9f196fe5a5 Merge pull request #1413 from OSSystems/topic/fix-warnings
Fix clippy warnings
2020-03-12 16:24:44 +09:00
e4adcd1935 Merge pull request #1411 from JohnTitor/patch
Clean-up metadata
2020-03-12 16:21:34 +09:00
7e0d898d5a Fix clippy warnings
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
2020-03-12 00:52:21 -03:00
51518721e5 Add notes to READMEs 2020-03-12 07:57:38 +09:00
c02d3e205b Clean-up metadata 2020-03-12 07:57:20 +09:00
a253d7d3ce Merge pull request #1410 from JohnTitor/new-web
Release actix-web 3.0.0-alpha.1
2020-03-12 05:12:54 +09:00
0152cedc5d Update changelog 2020-03-12 03:24:15 +09:00
a6a47b95ee Exclude actix-cors 2020-03-12 03:04:31 +09:00
1b28a5d48b Update actix-web dependency to 3.0.0-alpha.1 2020-03-12 03:03:50 +09:00
c147b94832 Bump up to 3.0.0-alpha.1 2020-03-12 03:03:22 +09:00
95f9a12a5e dev-deps: Update env_logger to 0.7 2020-03-12 02:58:22 +09:00
73eeab0e90 Merge pull request #1391 from JohnTitor/new-awc
Release awc v2.0.0-alpha.1
2020-03-11 21:15:48 +09:00
ce1e996b41 Update release date 2020-03-11 20:42:45 +09:00
e718f65121 Update tests 2020-03-08 16:42:45 +09:00
a9a475d555 Make test_server async fn 2020-03-08 16:42:26 +09:00
b93e1555ec Update actix-connect to 2.0.0-alpha.2 2020-03-08 15:27:40 +09:00
6f33b7ea42 Update awc dependency 2020-03-08 15:27:40 +09:00
294523a32f Bump up to 2.0.0-alpha.1 2020-03-08 15:27:39 +09:00
6b626c7d77 dev-deps: Update env_logger to 0.7 2020-03-08 03:07:53 +09:00
5da9e277a2 Merge pull request #1399 from JohnTitor/new-http
Release actix-http 2.0.0-alpha.2
2020-03-08 01:47:40 +09:00
0d5646a8b6 Run rustfmt 2020-03-08 00:52:39 +09:00
7941594f94 Update actix-http dependency 2020-03-08 00:50:20 +09:00
6f63acaf01 Bump up to 2.0.0-alpha.2 2020-03-08 00:48:45 +09:00
7172885beb Update changelog 2020-03-08 00:43:17 +09:00
cf721c5fff Update README example 2020-03-08 00:43:01 +09:00
10e3e72595 Http2 client configuration to improve performance (#1394)
* add defaults for http2 client configuration

* fix spaces

* Add changes text for extended H2 defaults buffers

* client: configurable H2 window sizes and max_http_version

* add H2 window size configuration and max_http_version to awc::ClientBuilder

* add awc::ClientBuilder H2 window sizes and max_http_version

* add test for H2 window size settings

* cleanup comment

* Apply code review fixes

* Code review fix for awc ClientBuilder

* Remove unnecessary comments on code review

* pin quote version to resolve build issue

* max_http_version to accept http::Version

* revert fix for quote broken build
2020-03-07 11:09:31 +09:00
a7d805aab7 Merge pull request #1396 from Aaron1011/fix/reapply-dispatcher
Re-apply commit 2cf7b3ad20
2020-03-05 02:48:20 +09:00
e90950fee1 Re-apply commit 2cf7b3ad20
This ended up getting reverted by #1367, which re-introduced an unsound
use of `Pin::new_unchecked`

See my original PR #1374 for the reasoning behind this change.
2020-03-04 11:27:58 -05:00
c8f0672ef7 Merge pull request #1395 from JohnTitor/rustls
Update `rustls` to 0.17
2020-03-04 15:56:27 +09:00
9d661dc4f3 Update changelog 2020-03-04 15:20:14 +09:00
687dc609dd Update rustls to 0.17 2020-03-04 15:11:31 +09:00
b9b52079e0 Update actix-tls to 2.0.0-alpha.1 2020-03-04 15:10:23 +09:00
117d28f7ba Update actix-connect to 2.0.0-alpha.1 2020-03-04 15:09:31 +09:00
795a575fc5 Merge pull request #1386 from JohnTitor/deny-to-warn
Demote lint level to warn
2020-02-28 14:17:11 +09:00
b4d63667df Demote lint level to warn 2020-02-27 22:39:11 +09:00
3dc859af58 Fix missing std::error::Error implement for MultipartError. (#1382)
* Fix missing `std::error::Error` implement for `MultipartError`.

* Update actix-multipart CHANGES.md.
2020-02-27 22:34:06 +09:00
1fa02b5f1c Merge pull request #1385 from JohnTitor/http-2-alpha
Release actix-http 2.0.0-alpha.1
2020-02-27 14:47:32 +09:00
c9fdcc596d Update actix to 0.10.0-alpha.1 2020-02-27 12:46:29 +09:00
6cc83dbb67 Allow clippy lint for compatibility 2020-02-27 12:45:11 +09:00
3b675c9125 Update actix-http to 2.0.0-alpha.1 2020-02-27 12:39:04 +09:00
15a2587887 Bump up to 2.0.0-alpha.1 2020-02-27 12:39:04 +09:00
0173f99726 Update changelog 2020-02-27 12:39:04 +09:00
f27dd19093 Fix Clippy warnings 2020-02-27 12:39:04 +09:00
7ba14fd113 Run rustfmt 2020-02-27 11:10:55 +09:00
903ae47baa dev-deps: Update env_logger to 0.7 2020-02-27 11:08:45 +09:00
95c18dbdf3 Merge pull request #1367 from actix/msg-body
Merge `MessageBody` improvements
2020-02-27 10:42:14 +09:00
d3ccf46e92 Clean-up metadata 2020-02-27 09:53:27 +09:00
cd1765035c Avoid re-definition 2020-02-27 09:42:32 +09:00
ea28219d0f reenable actix-http test-ws 2020-02-27 09:42:32 +09:00
77058ef779 adopt MessageBody Pin changes to actix-web root 2020-02-27 09:42:32 +09:00
e5f2feec45 reenable actix-http from local path 2020-02-27 09:42:32 +09:00
0a86907dd2 use mem::replace instead of mem::take rust 1.40+ 2020-02-27 09:37:05 +09:00
78749a4b7e rollback actix-http version change 2020-02-27 09:37:05 +09:00
de815dd99c Fixed condition for finishing transfer of response 2020-02-27 09:37:05 +09:00
e6078bf792 Fix EncoderBody enum to align with Body::Message 2020-02-27 09:37:05 +09:00
a84b37199a Add Unpin to Body to get rid of unsafe in MessageBody 2020-02-27 09:37:05 +09:00
c05f9475c5 refactor dispatcher to avoid possible UB with DispatcherState Pin 2020-02-27 09:37:05 +09:00
69dab0063c Get rid of one more unsafe 2020-02-27 09:37:05 +09:00
ec5c779732 unlink MessageBody from Unpin 2020-02-27 09:37:05 +09:00
2e2ea7ab80 remove extra whitespaces and Unpins 2020-02-27 09:37:05 +09:00
eeebc653fd change actix-http version to alpha 2020-02-27 09:37:05 +09:00
835a00599c rollback missed dependencies and CHANGES in crates except actix-http 2020-02-27 09:37:05 +09:00
d9c415e540 disable weird poll test until actix-web based on actix-http:2 2020-02-27 09:37:05 +09:00
09a391a3ca rollback changes to actix-web, awc and test-server for now 2020-02-27 09:37:05 +09:00
62aba424e2 Rollback actix-http-test dependency to show the issue 2020-02-27 09:37:05 +09:00
9d04b250f9 This is a squashed commit:
- Convert MessageBody to accept Pin in poll_next

- add CHANGES and increase versions aligned to semver

- update crates to accomodate MessageBody Pin change

- fix tests and dependencies
2020-02-27 09:37:05 +09:00
a4148de226 add test crashing with segfault according to #1321 2020-02-27 09:36:30 +09:00
48ef4d7a26 Add actix-http support for actix error messages (#1379)
* Moved actix-http for actix from actix crate

* remove resolver feature

* renamed actix feature to actor

* fixed doc attr for actors, add documentation
2020-02-27 09:34:49 +09:00
71c4bd1b30 Remove uses of Pin::new_unchecked in h1 Dispatcher (#1374)
This removes the last uses of unsafe `Pin` functions in actix-web.

This PR adds a `Pin<Box<_>>` wrapper to `DispatcherState::Upgrade`,
`State::ExpectCall`, and `State::ServiceCall`.

The previous uses of the futures `State::ExpectCall` and `State::ServiceCall`
were Undefined Behavior - a future was obtained from `self.expect.call`
or `self.service.call`, pinned on the stack, and then immediately
returned from `handle_request`. The only alternative to using `Box::pin`
would be to refactor `handle_request` to write the futures directly into
their final location, or avoid polling them before they are returned.

The previous use of `DispatcherState::Upgrade` doesn't seem to be
unsound. However, having data pinned inside an enum that we
`std::mem::replace` would require some careful `unsafe` code to ensure
that we never call `std::mem::replace` when the active variant contains
pinned data. By using `Box::pin`, we any possibility of future
refactoring accidentally introducing undefined behavior.

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-02-26 08:21:05 +09:00
de1d6ad5cb Merge pull request #1344 from actix/replace-unsafe-content-length-helper
Replace unsafe content length helper
2020-02-25 17:02:22 +09:00
2a72e8d119 Merge branch 'master' into replace-unsafe-content-length-helper 2020-02-25 14:30:04 +09:00
2a8e5fdc73 Merge pull request #1370 from mattgathu/feat/helper-function-for-trace-method
Create helper function for HTTP Trace Method
2020-02-25 14:24:09 +09:00
b213c07799 Merge branch 'master' into feat/helper-function-for-trace-method 2020-02-25 12:36:20 +09:00
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
94da08f506 increase content-length fast path to responses up to 1MB 2020-02-24 20:58:41 +00:00
d143c44130 Update the ChangeLog 2020-02-23 09:33:28 +01:00
8ec8ccf4fb Create helper function for HTTP Trace Method
Create *route* with `TRACE` method guard.
2020-02-23 09:25:55 +01: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
f266b44cb0 replace unsafe blocks in write_usize helper 2020-02-16 15:20:25 +00:00
31a3515e90 add safe vs unsafe benchmarks 2020-02-16 14:31:06 +00:00
82b2786d6b replace unsafe content length implementation 2020-02-16 14:31:05 +00: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
043f763c51 prepare actix-http release 2019-09-11 20:07:39 +06:00
8873e9b39e Added FrozenClientRequest for easier retrying HTTP calls (#1064)
* Initial commit

* Added extra_headers

* Added freeze() method to ClientRequest which produces a 'read-only' copy of a request suitable for retrying the send operation

* Additional methods for FrozenClientRequest

* Fix

* Increased crates versions

* Fixed a unit test. Added one more unit test.

* Added RequestHeaderWrapper

* Small fixes

* Renamed RequestHeadWrapper->RequestHeadType

* Updated CHANGES.md files

* Small fix

* Small changes

* Removed *_extra methods from Connection trait

* Added FrozenSendBuilder

* Added FrozenSendBuilder

* Minor fix

* Replaced impl Future with concrete Future implementation

* Small renaming

* Renamed Send->SendBody
2019-09-10 10:29:32 +06:00
5e8f1c338c fix h2 not using error response (#1080)
* fix h2 not using error response

* add fix change log

* fix h2 service error tests
2019-09-09 16:24:57 +06:00
1d96ae9bc3 actix-multipart: Correctly parse multipart body which does not end in CRLF (#1042)
* Correctly parse multipart body which does not end in CRLF

* Add in an eof guard for extra safety
2019-09-09 13:58:00 +06:00
8d61fe0925 Ensure that awc::ws::WebsocketsRequest sets the Host header (#1070)
* Ensure that awc::ws::WebsocketsRequest sets the Host header before connecting.

* Make sure to check if headers already have a HOST value before setting

* Update CHANGES.md to reflect WebSocket client update.
2019-09-09 12:27:13 +06:00
8a9fcddb3c Condition middleware (#1075)
* add condition middleware

* write tests

* update changes

* Update src/middleware/condition.rs

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

* Update src/middleware/condition.rs

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

* Update src/middleware/condition.rs

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

* Update src/middleware/condition.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>
2019-09-09 12:26:38 +06:00
c9400456f6 update actix-connect ver 2019-09-02 15:20:28 -07:00
63ddd30ee4 on_connect result isnt added to request extensions for http2 requests #1009 2019-09-01 13:15:02 +06:00
bae29897d6 prep actix-web release 2019-08-29 09:36:16 +06:00
616981ecf9 clear extensions before reclaiming HttpRequests in their pool (#1063)
Issue #1062
2019-08-29 09:35:05 +06:00
98bf8ab098 enable rust-tls feature for actix_web::client #1045 2019-08-28 21:40:24 +06:00
c193137905 actix_web::test::TestRequest::set_form (#1058) 2019-08-28 21:32:17 +06:00
a07cdd6533 Data::into_inner 2019-08-27 17:25:25 +01:00
61e492e7e3 Prepare actix-multipart 0.1.3 release 2019-08-18 10:39:22 +09:00
23d768a77b Add explicit dyns (#1041)
* Add explicit `dyn`s

* Remove unnecessary lines
2019-08-17 02:45:44 +09:00
87b7162473 chore(readme): fix copy paste error (#1040)
Fix actix-cors README
2019-08-16 09:21:30 +09:00
979c4d44f4 update awc dep 2019-08-13 12:41:26 -07:00
243 changed files with 17150 additions and 19199 deletions

View File

@ -1,41 +0,0 @@
environment:
global:
PROJECT_NAME: actix-web
matrix:
# Stable channel
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
# Nightly channel
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
install:
- ps: >-
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
$Env:PATH += ';C:\msys64\mingw64\bin'
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
$Env:PATH += ';C:\MinGW\bin'
}
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
# 'cargo test' takes care of building for us, so disable Appveyor's build stage.
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo clean
- cargo test --no-default-features --features="flate2-rust"

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:

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Gitter channel (actix-web)
url: https://gitter.im/actix/actix-web
about: Please ask and answer questions about the actix-web here.
- name: Gitter channel (actix)
url: https://gitter.im/actix/actix
about: Please ask and answer questions about the actix here.

27
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,27 @@
<!-- Thanks for considering contributing actix! -->
<!-- Please fill out the following to make our reviews easy. -->
## PR Type
<!-- What kind of change does this PR make? -->
<!-- Bug Fix / Feature / Refactor / Code Style / Other -->
INSERT_PR_TYPE
## PR Checklist
Check your PR fulfills the following:
<!-- For draft PRs check the boxes as you complete them. -->
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] A changelog entry has been made for the appropriate packages.
- [ ] Format code with the latest stable rustfmt
## Overview
<!-- Describe the current and new behavior. -->
<!-- Emphasize any breaking changes. -->
<!-- If this PR fixes or closes an issue, reference it here. -->
<!-- Closes #000 -->

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

@ -0,0 +1,28 @@
name: Benchmark (Linux)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
jobs:
check_benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: Check benchmark
uses: actions-rs/cargo@v1
with:
command: bench
args: --bench=server -- --sample-size=15

32
.github/workflows/clippy-fmt.yml vendored Normal file
View File

@ -0,0 +1,32 @@
on:
pull_request:
types: [opened, synchronize, reopened]
name: Clippy and rustfmt Check
jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
override: true
- name: Check with rustfmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: clippy
override: true
- name: Check with Clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features --all --tests

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

@ -0,0 +1,69 @@
name: CI (Linux)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- 1.42.0 # MSRV
- stable
- nightly
name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
profile: minimal
override: true
- 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' || github.event_name == 'pull_request')
run: |
cargo install cargo-tarpaulin --vers "^0.13"
cargo tarpaulin --out Xml
- name: Upload to Codecov
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
uses: codecov/codecov-action@v1
with:
file: cobertura.xml

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

@ -0,0 +1,44 @@
name: CI (macOS)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
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@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-apple-darwin
profile: minimal
override: true
- 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

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

@ -0,0 +1,37 @@
name: Upload documentation
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'actix/actix-web'
steps:
- uses: actions/checkout@v2
- 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 --workspace --all-features
- name: Tweak HTML
run: echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@3.5.8
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: target/doc

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

@ -0,0 +1,64 @@
name: CI (Windows)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
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@v2
- 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

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ guide/build/
*.pid *.pid
*.sock *.sock
*~ *~
.DS_Store
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk

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,6 +1,220 @@
# Changes # Changes
## [1.0.6] - 2019-xx-xx ## Unreleased - 2020-xx-xx
## 3.0.0 - 2020-09-11
* No significant changes from `3.0.0-beta.4`.
## 3.0.0-beta.4 - 2020-09-09
### Added
* `middleware::NormalizePath` now has configurable behaviour for either always having a trailing
slash, or as the new addition, always trimming trailing slashes. [#1639]
### Changed
* Update actix-codec and actix-utils dependencies. [#1634]
* `FormConfig` and `JsonConfig` configurations are now also considered when set
using `App::data`. [#1641]
* `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. [#1655]
* `HttpServer::maxconnrate` is renamed to the more expressive
`HttpServer::max_connection_rate`. [#1655]
[#1639]: https://github.com/actix/actix-web/pull/1639
[#1641]: https://github.com/actix/actix-web/pull/1641
[#1634]: https://github.com/actix/actix-web/pull/1634
[#1655]: https://github.com/actix/actix-web/pull/1655
## 3.0.0-beta.3 - 2020-08-17
### Changed
* Update `rustls` to 0.18
## 3.0.0-beta.2 - 2020-08-17
### Changed
* `PayloadConfig` is now also considered in `Bytes` and `String` extractors when set
using `App::data`. [#1610]
* `web::Path` now has a public representation: `web::Path(pub T)` that enables
destructuring. [#1594]
* `ServiceRequest::app_data` allows retrieval of non-Data data without splitting into parts to
access `HttpRequest` which already allows this. [#1618]
* Re-export all error types from `awc`. [#1621]
* MSRV is now 1.42.0.
### Fixed
* Memory leak of app data in pooled requests. [#1609]
[#1594]: https://github.com/actix/actix-web/pull/1594
[#1609]: https://github.com/actix/actix-web/pull/1609
[#1610]: https://github.com/actix/actix-web/pull/1610
[#1618]: https://github.com/actix/actix-web/pull/1618
[#1621]: https://github.com/actix/actix-web/pull/1621
## 3.0.0-beta.1 - 2020-07-13
### Added
* Re-export `actix_rt::main` as `actix_web::main`.
* `HttpRequest::match_pattern` and `ServiceRequest::match_pattern` for extracting the matched
resource pattern.
* `HttpRequest::match_name` and `ServiceRequest::match_name` for extracting matched resource name.
### Changed
* Fix actix_http::h1::dispatcher so it returns when HW_BUFFER_SIZE is reached. Should reduce peak memory consumption during large uploads. [#1550]
* Migrate cookie handling to `cookie` crate. Actix-web no longer requires `ring` dependency.
* MSRV is now 1.41.1
### Fixed
* `NormalizePath` improved consistency when path needs slashes added _and_ removed.
## 3.0.0-alpha.3 - 2020-05-21
### Added
* Add option to create `Data<T>` from `Arc<T>` [#1509]
### Changed
* Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486]
* Fix audit issue logging by default peer address [#1485]
* Bump minimum supported Rust version to 1.40
* Replace deprecated `net2` crate with `socket2`
[#1485]: https://github.com/actix/actix-web/pull/1485
[#1509]: https://github.com/actix/actix-web/pull/1509
## [3.0.0-alpha.2] - 2020-05-08
### Changed
* `{Resource,Scope}::default_service(f)` handlers now support app data extraction. [#1452]
* Implement `std::error::Error` for our custom errors [#1422]
* NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433]
* Remove the `failure` feature and support.
[#1422]: https://github.com/actix/actix-web/pull/1422
[#1433]: https://github.com/actix/actix-web/pull/1433
[#1452]: https://github.com/actix/actix-web/pull/1452
[#1486]: https://github.com/actix/actix-web/pull/1486
## [3.0.0-alpha.1] - 2020-03-11
### Added
* Add helper function for creating routes with `TRACE` method guard `web::trace()`
* Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing.
### 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
* Update `actix-tls` dependency to 2.0.0-alpha.1
* Update `rustls` dependency to 0.17
[#1308]: https://github.com/actix/actix-web/pull/1308
## [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
* 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
### Fixed
* Request Extensions leak #1062
## [1.0.6] - 2019-08-28
### Added ### Added
@ -8,10 +222,17 @@
* Form immplements Responder, returning a `application/x-www-form-urlencoded` response * Form immplements Responder, returning a `application/x-www-form-urlencoded` response
* Add `into_inner` to `Data`
* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set
the header in test requests.
### Changed ### Changed
* `Query` payload made `pub`. Allows user to pattern-match the payload. * `Query` payload made `pub`. Allows user to pattern-match the payload.
* Enable `rust-tls` feature for client #1045
* Update serde_urlencoded to 0.6.1 * Update serde_urlencoded to 0.6.1
* Update url to 2.1 * Update url to 2.1

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "1.0.5" version = "3.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"
@ -11,12 +11,11 @@ documentation = "https://docs.rs/actix-web/"
categories = ["network-programming", "asynchronous", 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 OR 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" }
@ -31,11 +30,7 @@ members = [
".", ".",
"awc", "awc",
"actix-http", "actix-http",
"actix-cors",
"actix-files", "actix-files",
"actix-framed",
"actix-session",
"actix-identity",
"actix-multipart", "actix-multipart",
"actix-web-actors", "actix-web-actors",
"actix-web-codegen", "actix-web-codegen",
@ -43,77 +38,78 @@ members = [
] ]
[features] [features]
default = ["brotli", "flate2-zlib", "client", "fail"] default = ["compress"]
# http client # content-encoding support
client = ["awc"] compress = ["actix-http/compress", "awc/compress"]
# brotli encoding, requires c compiler # sessions feature
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
secure-cookies = ["actix-http/secure-cookies"] secure-cookies = ["actix-http/secure-cookies"]
fail = ["actix-http/fail"]
# 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"] rustls = ["actix-tls/rustls", "awc/rustls", "rust-tls"]
# unix domain sockets support [[example]]
uds = ["actix-server/uds"] name = "basic"
required-features = ["compress"]
[[example]]
name = "uds"
required-features = ["compress"]
[[test]]
name = "test_server"
required-features = ["compress"]
[dependencies] [dependencies]
actix-codec = "0.1.2" actix-codec = "0.3.0"
actix-service = "0.4.1" actix-service = "1.0.6"
actix-utils = "0.4.4" actix-utils = "2.0.0"
actix-router = "0.1.5" actix-router = "0.2.4"
actix-rt = "0.2.4" actix-rt = "1.1.1"
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 = "2.0.0"
awc = { version = "0.2.2", optional = true }
bytes = "0.4" actix-web-codegen = "0.3.0"
derive_more = "0.15.0" actix-http = "2.0.0"
awc = { version = "2.0.0", 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-channel = { version = "0.3.5", default-features = false }
hashbrown = "0.5.0" futures-core = { version = "0.3.5", default-features = false }
futures-util = { version = "0.3.5", default-features = false }
fxhash = "0.2.1"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
net2 = "0.2.33" socket2 = "0.3"
parking_lot = "0.9" pin-project = "0.4.17"
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 = { package = "openssl", version = "0.10", optional = true }
# ssl support rust-tls = { package = "rustls", version = "0.18.0", optional = true }
openssl = { version="0.10", optional = true } tinyvec = { version = "0.3", features = ["alloc"] }
rustls = { version = "0.15", optional = true }
[dev-dependencies] [dev-dependencies]
actix = "0.8.3" actix = "0.10.0"
actix-connect = "0.2.2" actix-http = { version = "2.0.0-beta.4", features = ["actors"] }
actix-http-test = "0.2.4"
rand = "0.7" rand = "0.7"
env_logger = "0.6" env_logger = "0.7"
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 +121,18 @@ 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-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" }
[[example]]
name = "client"
required-features = ["rustls"]
[[bench]]
name = "server"
harness = false
[[bench]]
name = "service"
harness = false

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2017-NOW Nikolay Kim Copyright 2017-NOW Actix Team
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,4 +1,4 @@
Copyright (c) 2017 Nikolay Kim Copyright (c) 2017 Actix Team
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated person obtaining a copy of this software and associated

View File

@ -1,3 +1,97 @@
## Unreleased
## 3.0.0
* 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.
* actix-http support for Actors messages was moved to actix-http crate and is enabled
with feature `actors`
* content_length function is removed from actix-http.
You can set Content-Length by normally setting the response body or calling no_chunking function.
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
`u64` instead of a `usize`.
* Code that was using `path.<index>` to access a `web::Path<(A, B, C)>`s elements now needs to use
destructuring or `.into_inner()`. For example:
```rust
// Previously:
async fn some_route(path: web::Path<(String, String)>) -> String {
format!("Hello, {} {}", path.0, path.1)
}
// Now (this also worked before):
async fn some_route(path: web::Path<(String, String)>) -> String {
let (first_name, last_name) = path.into_inner();
format!("Hello, {} {}", first_name, last_name)
}
// Or (this wasn't previously supported):
async fn some_route(web::Path((first_name, last_name)): web::Path<(String, String)>) -> String {
format!("Hello, {} {}", first_name, last_name)
}
```
* `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one.
It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`,
or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::default())`.
* `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`.
* `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`.
## 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
@ -304,7 +398,7 @@
* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type * `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
* StaticFiles and NamedFile has been move to separate create. * StaticFiles and NamedFile have been moved to a separate crate.
instead of `use actix_web::fs::StaticFile` instead of `use actix_web::fs::StaticFile`
@ -314,7 +408,7 @@
use `use actix_files::NamedFile` use `use actix_files::NamedFile`
* Multipart has been move to separate create. * Multipart has been moved to a separate crate.
instead of `use actix_web::multipart::Multipart` instead of `use actix_web::multipart::Multipart`

106
README.md
View File

@ -1,79 +1,109 @@
# 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">
<h1>Actix web</h1>
<p>
<strong>Actix web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
</p>
<p>
Actix web is a simple, pragmatic and extremely fast web framework for Rust. [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg)](https://docs.rs/actix-web)
[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
![License](https://img.shields.io/crates/l/actix-web.svg)
<br />
[![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)
[![Download](https://img.shields.io/crates/d/actix-web.svg)](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)
* Supported *HTTP/1.x* and *HTTP/2.0* protocols </p>
</div>
## Features
* Supports *HTTP/1.x* and *HTTP/2*
* Streaming and pipelining * Streaming and pipelining
* Keep-alive and slow requests handling * Keep-alive and slow requests handling
* Client/server [WebSockets](https://actix.rs/docs/websockets/) support * Client/server [WebSockets](https://actix.rs/docs/websockets/) support
* Transparent content compression/decompression (br, gzip, deflate) * Transparent content compression/decompression (br, gzip, deflate)
* Configurable [request routing](https://actix.rs/docs/url-dispatch/) * Powerful [request routing](https://actix.rs/docs/url-dispatch/)
* Multipart streams * Multipart streams
* Static assets * Static assets
* SSL support with OpenSSL or Rustls * SSL support using OpenSSL or Rustls
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Includes an async [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)
* Runs on stable Rust 1.42+
## Documentation & community resources ## Documentation
* [User Guide](https://actix.rs/docs/) * [Website & User Guide](https://actix.rs)
* [API Documentation (1.0)](https://docs.rs/actix-web/) * [Examples Repository](https://github.com/actix/examples)
* [API Documentation (0.7)](https://docs.rs/actix-web/0.7.19/actix_web/) * [API Documentation](https://docs.rs/actix-web)
* [Chat on gitter](https://gitter.im/actix/actix) * [API Documentation (master branch)](https://actix.rs/actix-web/actix_web)
* 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
format!("Hello {}! id:{}", info.1, info.0) [dependencies]
actix-web = "3"
```
Code:
```rust
use actix_web::{get, web, App, HttpServer, Responder};
#[get("/{id}/{name}/index.html")]
async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
format!("Hello {}! id:{}", name, id)
} }
fn main() -> std::io::Result<()> { #[actix_web::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
} }
``` ```
### More examples ### More examples
* [Basics](https://github.com/actix/examples/tree/master/basics/) * [Basic Setup](https://github.com/actix/examples/tree/master/basics/)
* [Stateful](https://github.com/actix/examples/tree/master/state/) * [Application State](https://github.com/actix/examples/tree/master/state/)
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) * [JSON Handling](https://github.com/actix/examples/tree/master/json/)
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) * [Multipart Streams](https://github.com/actix/examples/tree/master/multipart/)
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) / * [Diesel Integration](https://github.com/actix/examples/tree/master/diesel/)
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates * [r2d2 Integration](https://github.com/actix/examples/tree/master/r2d2/)
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) * [Simple WebSocket](https://github.com/actix/examples/tree/master/websocket/)
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/) * [Tera Templates](https://github.com/actix/examples/tree/master/template_tera/)
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) * [Askama Templates](https://github.com/actix/examples/tree/master/template_askama/)
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) * [HTTPS using Rustls](https://github.com/actix/examples/tree/master/rustls/)
* [Json](https://github.com/actix/examples/tree/master/json/) * [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/openssl/)
* [WebSocket Chat](https://github.com/actix/examples/tree/master/websocket-chat/)
You may consider checking out You may consider checking out
[this directory](https://github.com/actix/examples/tree/master/) for more examples. [this directory](https://github.com/actix/examples/tree/master/) for more examples.
## Benchmarks ## Benchmarks
* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r18) One of the fastest web frameworks available according to the
[TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r19).
## License ## License
This project is licensed under either of This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
[http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
at your option. at your option.
## Code of Conduct ## Code of Conduct
Contribution to the actix-web crate is organized under the terms of the Contribution to the actix-web crate is organized under the terms of the Contributor Covenant, the
Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to maintainers of Actix web, promises to intervene to uphold that code of conduct.
intervene to uphold that code of conduct.

View File

@ -1,9 +0,0 @@
# Changes
## [0.1.1] - unreleased
* Bump `derive_more` crate version to 0.15.0
## [0.1.0] - 2019-06-15
* Move cors middleware to separate crate

View File

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

View File

@ -1 +0,0 @@
../LICENSE-APACHE

View File

@ -1 +0,0 @@
../LICENSE-MIT

View File

@ -1,9 +0,0 @@
# Identity service for actix web framework [![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-identity)](https://crates.io/crates/actix-identity) [![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 & community resources
* [User Guide](https://actix.rs/docs/)
* [API Documentation](https://docs.rs/actix-cors/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-cors](https://crates.io/crates/actix-cors)
* Minimum supported Rust version: 1.34 or later

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,55 @@
# Changes # Changes
## [0.1.5] - unreleased ## [Unreleased] - 2020-xx-xx
## [0.3.0-beta.1] - 2020-07-15
* Update `v_htmlescape` to 0.10
* Update `actix-web` and `actix-http` dependencies to beta.1
## [0.3.0-alpha.1] - 2020-05-23
* Update `actix-web` and `actix-http` dependencies to alpha
* Fix some typos in the docs
* Bump minimum supported Rust version to 1.40
* Support sending Content-Length when Content-Range is specified [#1384]
[#1384]: https://github.com/actix/actix-web/pull/1384
## [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.3.0"
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"
@ -9,27 +9,28 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-files/" documentation = "https://docs.rs/actix-files/"
categories = ["asynchronous", "web-programming::http-server"] categories = ["asynchronous", "web-programming::http-server"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
workspace = ".."
[lib] [lib]
name = "actix_files" 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 = "3.0.0", default-features = false }
actix-http = "0.2.9" actix-http = "2.0.0"
actix-service = "0.4.1" actix-service = "1.0.6"
bitflags = "1" bitflags = "1"
bytes = "0.4" bytes = "0.5.3"
futures = "0.1.25" futures-core = { version = "0.3.5", default-features = false }
derive_more = "0.15.0" futures-util = { version = "0.3.5", default-features = false }
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"
percent-encoding = "2.1" percent-encoding = "2.1"
v_htmlescape = "0.4" v_htmlescape = "0.10"
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.2", features=["ssl"] } actix-rt = "1.0.0"
actix-web = { version = "3.0.0", features = ["openssl"] }

View File

@ -6,4 +6,4 @@
* [API Documentation](https://docs.rs/actix-files/) * [API Documentation](https://docs.rs/actix-files/)
* [Chat on gitter](https://gitter.im/actix/actix) * [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-files](https://crates.io/crates/actix-files) * Cargo package: [actix-files](https://crates.io/crates/actix-files)
* Minimum supported Rust version: 1.33 or later * Minimum supported Rust version: 1.40 or later

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

@ -8,16 +8,16 @@ use std::time::{SystemTime, UNIX_EPOCH};
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use bitflags::bitflags; use bitflags::bitflags;
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_util::future::{ready, Ready};
use crate::range::HttpRange; use crate::range::HttpRange;
use crate::ChunkedReadFile; use crate::ChunkedReadFile;
@ -89,13 +89,22 @@ impl NamedFile {
}; };
let ct = from_path(&path).first_or_octet_stream(); let ct = from_path(&path).first_or_octet_stream();
let disposition_type = match ct.type_() { let disposition = match ct.type_() {
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,
parameters: vec![DispositionParam::Filename(filename.into_owned())], parameters,
}; };
(ct, cd) (ct, cd)
}; };
@ -246,62 +255,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 +279,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 {
@ -442,9 +387,69 @@ impl Responder for NamedFile {
fut: None, fut: None,
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)); resp.status(StatusCode::PARTIAL_CONTENT);
}; }
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,38 +0,0 @@
[package]
name = "actix-framed"
version = "0.2.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix framed app server"
readme = "README.md"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-framed/"
categories = ["network-programming", "asynchronous",
"web-programming::http-server",
"web-programming::websocket"]
license = "MIT/Apache-2.0"
edition = "2018"
workspace =".."
[lib]
name = "actix_framed"
path = "src/lib.rs"
[dependencies]
actix-codec = "0.1.2"
actix-service = "0.4.1"
actix-router = "0.1.2"
actix-rt = "0.2.2"
actix-http = "0.2.7"
actix-server-config = "0.1.2"
bytes = "0.4"
futures = "0.1.25"
log = "0.4"
[dev-dependencies]
actix-server = { version = "0.6.0", features=["ssl"] }
actix-connect = { version = "0.2.0", features=["ssl"] }
actix-http-test = { version = "0.2.4", features=["ssl"] }
actix-utils = "0.4.4"

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017-NOW Nikolay Kim
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

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

View File

@ -1,8 +0,0 @@
# Framed app for 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-framed)](https://crates.io/crates/actix-framed) [![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 & community resources
* [API Documentation](https://docs.rs/actix-framed/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-framed](https://crates.io/crates/actix-framed)
* Minimum supported Rust version: 1.33 or later

View File

@ -1,20 +0,0 @@
# Changes
## [0.2.1] - 2019-07-20
* Remove unneeded actix-utils dependency
## [0.2.0] - 2019-05-12
* Update dependencies
## [0.1.0] - 2019-04-16
* Update tests
## [0.1.0-alpha.1] - 2019-04-12
* Initial release

View File

@ -1,217 +0,0 @@
use std::rc::Rc;
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::h1::{Codec, SendResponse};
use actix_http::{Error, Request, Response};
use actix_router::{Path, Router, Url};
use actix_server_config::ServerConfig;
use actix_service::{IntoNewService, NewService, Service};
use futures::{Async, Future, Poll};
use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService};
use crate::request::FramedRequest;
use crate::state::State;
type BoxedResponse = Box<dyn Future<Item = (), Error = Error>>;
pub trait HttpServiceFactory {
type Factory: NewService;
fn path(&self) -> &str;
fn create(self) -> Self::Factory;
}
/// Application builder
pub struct FramedApp<T, S = ()> {
state: State<S>,
services: Vec<(String, BoxedHttpNewService<FramedRequest<T, S>>)>,
}
impl<T: 'static> FramedApp<T, ()> {
pub fn new() -> Self {
FramedApp {
state: State::new(()),
services: Vec::new(),
}
}
}
impl<T: 'static, S: 'static> FramedApp<T, S> {
pub fn with(state: S) -> FramedApp<T, S> {
FramedApp {
services: Vec::new(),
state: State::new(state),
}
}
pub fn service<U>(mut self, factory: U) -> Self
where
U: HttpServiceFactory,
U::Factory: NewService<
Config = (),
Request = FramedRequest<T, S>,
Response = (),
Error = Error,
InitError = (),
> + 'static,
<U::Factory as NewService>::Future: 'static,
<U::Factory as NewService>::Service: Service<
Request = FramedRequest<T, S>,
Response = (),
Error = Error,
Future = Box<dyn Future<Item = (), Error = Error>>,
>,
{
let path = factory.path().to_string();
self.services
.push((path, Box::new(HttpNewService::new(factory.create()))));
self
}
}
impl<T, S> IntoNewService<FramedAppFactory<T, S>> for FramedApp<T, S>
where
T: AsyncRead + AsyncWrite + 'static,
S: 'static,
{
fn into_new_service(self) -> FramedAppFactory<T, S> {
FramedAppFactory {
state: self.state,
services: Rc::new(self.services),
}
}
}
#[derive(Clone)]
pub struct FramedAppFactory<T, S> {
state: State<S>,
services: Rc<Vec<(String, BoxedHttpNewService<FramedRequest<T, S>>)>>,
}
impl<T, S> NewService for FramedAppFactory<T, S>
where
T: AsyncRead + AsyncWrite + 'static,
S: 'static,
{
type Config = ServerConfig;
type Request = (Request, Framed<T, Codec>);
type Response = ();
type Error = Error;
type InitError = ();
type Service = FramedAppService<T, S>;
type Future = CreateService<T, S>;
fn new_service(&self, _: &ServerConfig) -> Self::Future {
CreateService {
fut: self
.services
.iter()
.map(|(path, service)| {
CreateServiceItem::Future(
Some(path.clone()),
service.new_service(&()),
)
})
.collect(),
state: self.state.clone(),
}
}
}
#[doc(hidden)]
pub struct CreateService<T, S> {
fut: Vec<CreateServiceItem<T, S>>,
state: State<S>,
}
enum CreateServiceItem<T, S> {
Future(
Option<String>,
Box<dyn Future<Item = BoxedHttpService<FramedRequest<T, S>>, Error = ()>>,
),
Service(String, BoxedHttpService<FramedRequest<T, S>>),
}
impl<S: 'static, T: 'static> Future for CreateService<T, S>
where
T: AsyncRead + AsyncWrite,
{
type Item = FramedAppService<T, S>;
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut done = true;
// poll http services
for item in &mut self.fut {
let res = match item {
CreateServiceItem::Future(ref mut path, ref mut fut) => {
match fut.poll()? {
Async::Ready(service) => Some((path.take().unwrap(), service)),
Async::NotReady => {
done = false;
None
}
}
}
CreateServiceItem::Service(_, _) => continue,
};
if let Some((path, service)) = res {
*item = CreateServiceItem::Service(path, service);
}
}
if done {
let router = self
.fut
.drain(..)
.fold(Router::build(), |mut router, item| {
match item {
CreateServiceItem::Service(path, service) => {
router.path(&path, service);
}
CreateServiceItem::Future(_, _) => unreachable!(),
}
router
});
Ok(Async::Ready(FramedAppService {
router: router.finish(),
state: self.state.clone(),
}))
} else {
Ok(Async::NotReady)
}
}
}
pub struct FramedAppService<T, S> {
state: State<S>,
router: Router<BoxedHttpService<FramedRequest<T, S>>>,
}
impl<S: 'static, T: 'static> Service for FramedAppService<T, S>
where
T: AsyncRead + AsyncWrite,
{
type Request = (Request, Framed<T, Codec>);
type Response = ();
type Error = Error;
type Future = BoxedResponse;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
let mut path = Path::new(Url::new(req.uri().clone()));
if let Some((srv, _info)) = self.router.recognize_mut(&mut path) {
return srv.call(FramedRequest::new(req, framed, path, self.state.clone()));
}
Box::new(
SendResponse::new(framed, Response::NotFound().finish()).then(|_| Ok(())),
)
}
}

View File

@ -1,90 +0,0 @@
use actix_http::Error;
use actix_service::{NewService, Service};
use futures::{Future, Poll};
pub(crate) type BoxedHttpService<Req> = Box<
dyn Service<
Request = Req,
Response = (),
Error = Error,
Future = Box<dyn Future<Item = (), Error = Error>>,
>,
>;
pub(crate) type BoxedHttpNewService<Req> = Box<
dyn NewService<
Config = (),
Request = Req,
Response = (),
Error = Error,
InitError = (),
Service = BoxedHttpService<Req>,
Future = Box<dyn Future<Item = BoxedHttpService<Req>, Error = ()>>,
>,
>;
pub(crate) struct HttpNewService<T: NewService>(T);
impl<T> HttpNewService<T>
where
T: NewService<Response = (), Error = Error>,
T::Response: 'static,
T::Future: 'static,
T::Service: Service<Future = Box<dyn Future<Item = (), Error = Error>>> + 'static,
<T::Service as Service>::Future: 'static,
{
pub fn new(service: T) -> Self {
HttpNewService(service)
}
}
impl<T> NewService for HttpNewService<T>
where
T: NewService<Config = (), Response = (), Error = Error>,
T::Request: 'static,
T::Future: 'static,
T::Service: Service<Future = Box<dyn Future<Item = (), Error = Error>>> + 'static,
<T::Service as Service>::Future: 'static,
{
type Config = ();
type Request = T::Request;
type Response = ();
type Error = Error;
type InitError = ();
type Service = BoxedHttpService<T::Request>;
type Future = Box<dyn Future<Item = Self::Service, Error = ()>>;
fn new_service(&self, _: &()) -> Self::Future {
Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| {
let service: BoxedHttpService<_> = Box::new(HttpServiceWrapper { service });
Ok(service)
}))
}
}
struct HttpServiceWrapper<T: Service> {
service: T,
}
impl<T> Service for HttpServiceWrapper<T>
where
T: Service<
Response = (),
Future = Box<dyn Future<Item = (), Error = Error>>,
Error = Error,
>,
T::Request: 'static,
{
type Request = T::Request;
type Response = ();
type Error = Error;
type Future = Box<dyn Future<Item = (), Error = Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: Self::Request) -> Self::Future {
self.service.call(req)
}
}

View File

@ -1,17 +0,0 @@
#![allow(clippy::type_complexity, clippy::new_without_default, dead_code)]
mod app;
mod helpers;
mod request;
mod route;
mod service;
mod state;
pub mod test;
// re-export for convinience
pub use actix_http::{http, Error, HttpMessage, Response, ResponseError};
pub use self::app::{FramedApp, FramedAppService};
pub use self::request::FramedRequest;
pub use self::route::FramedRoute;
pub use self::service::{SendError, VerifyWebSockets};
pub use self::state::State;

View File

@ -1,170 +0,0 @@
use std::cell::{Ref, RefMut};
use actix_codec::Framed;
use actix_http::http::{HeaderMap, Method, Uri, Version};
use actix_http::{h1::Codec, Extensions, Request, RequestHead};
use actix_router::{Path, Url};
use crate::state::State;
pub struct FramedRequest<Io, S = ()> {
req: Request,
framed: Framed<Io, Codec>,
state: State<S>,
pub(crate) path: Path<Url>,
}
impl<Io, S> FramedRequest<Io, S> {
pub fn new(
req: Request,
framed: Framed<Io, Codec>,
path: Path<Url>,
state: State<S>,
) -> Self {
Self {
req,
framed,
state,
path,
}
}
}
impl<Io, S> FramedRequest<Io, S> {
/// Split request into a parts
pub fn into_parts(self) -> (Request, Framed<Io, Codec>, State<S>) {
(self.req, self.framed, self.state)
}
/// This method returns reference to the request head
#[inline]
pub fn head(&self) -> &RequestHead {
self.req.head()
}
/// This method returns muttable reference to the request head.
/// panics if multiple references of http request exists.
#[inline]
pub fn head_mut(&mut self) -> &mut RequestHead {
self.req.head_mut()
}
/// Shared application state
#[inline]
pub fn state(&self) -> &S {
self.state.get_ref()
}
/// Request's uri.
#[inline]
pub fn uri(&self) -> &Uri {
&self.head().uri
}
/// Read the Request method.
#[inline]
pub fn method(&self) -> &Method {
&self.head().method
}
/// Read the Request Version.
#[inline]
pub fn version(&self) -> Version {
self.head().version
}
#[inline]
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head().headers
}
/// The target path of this Request.
#[inline]
pub fn path(&self) -> &str {
self.head().uri.path()
}
/// The query string in the URL.
///
/// E.g., id=10
#[inline]
pub fn query_string(&self) -> &str {
if let Some(query) = self.uri().query().as_ref() {
query
} else {
""
}
}
/// Get a reference to the Path parameters.
///
/// Params is a container for url parameters.
/// A variable segment is specified in the form `{identifier}`,
/// where the identifier can be used later in a request handler to
/// access the matched value for that segment.
#[inline]
pub fn match_info(&self) -> &Path<Url> {
&self.path
}
/// Request extensions
#[inline]
pub fn extensions(&self) -> Ref<Extensions> {
self.head().extensions()
}
/// Mutable reference to a the request's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<Extensions> {
self.head().extensions_mut()
}
}
#[cfg(test)]
mod tests {
use actix_http::http::{HeaderName, HeaderValue, HttpTryFrom};
use actix_http::test::{TestBuffer, TestRequest};
use super::*;
#[test]
fn test_reqest() {
let buf = TestBuffer::empty();
let framed = Framed::new(buf, Codec::default());
let req = TestRequest::with_uri("/index.html?q=1")
.header("content-type", "test")
.finish();
let path = Path::new(Url::new(req.uri().clone()));
let mut freq = FramedRequest::new(req, framed, path, State::new(10u8));
assert_eq!(*freq.state(), 10);
assert_eq!(freq.version(), Version::HTTP_11);
assert_eq!(freq.method(), Method::GET);
assert_eq!(freq.path(), "/index.html");
assert_eq!(freq.query_string(), "q=1");
assert_eq!(
freq.headers()
.get("content-type")
.unwrap()
.to_str()
.unwrap(),
"test"
);
freq.head_mut().headers.insert(
HeaderName::try_from("x-hdr").unwrap(),
HeaderValue::from_static("test"),
);
assert_eq!(
freq.headers().get("x-hdr").unwrap().to_str().unwrap(),
"test"
);
freq.extensions_mut().insert(100usize);
assert_eq!(*freq.extensions().get::<usize>().unwrap(), 100usize);
let (_, _, state) = freq.into_parts();
assert_eq!(*state, 10);
}
}

View File

@ -1,157 +0,0 @@
use std::fmt;
use std::marker::PhantomData;
use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::{http::Method, Error};
use actix_service::{NewService, Service};
use futures::future::{ok, FutureResult};
use futures::{Async, Future, IntoFuture, Poll};
use log::error;
use crate::app::HttpServiceFactory;
use crate::request::FramedRequest;
/// Resource route definition
///
/// Route uses builder-like pattern for configuration.
/// If handler is not explicitly set, default *404 Not Found* handler is used.
pub struct FramedRoute<Io, S, F = (), R = ()> {
handler: F,
pattern: String,
methods: Vec<Method>,
state: PhantomData<(Io, S, R)>,
}
impl<Io, S> FramedRoute<Io, S> {
pub fn new(pattern: &str) -> Self {
FramedRoute {
handler: (),
pattern: pattern.to_string(),
methods: Vec::new(),
state: PhantomData,
}
}
pub fn get(path: &str) -> FramedRoute<Io, S> {
FramedRoute::new(path).method(Method::GET)
}
pub fn post(path: &str) -> FramedRoute<Io, S> {
FramedRoute::new(path).method(Method::POST)
}
pub fn put(path: &str) -> FramedRoute<Io, S> {
FramedRoute::new(path).method(Method::PUT)
}
pub fn delete(path: &str) -> FramedRoute<Io, S> {
FramedRoute::new(path).method(Method::DELETE)
}
pub fn method(mut self, method: Method) -> Self {
self.methods.push(method);
self
}
pub fn to<F, R>(self, handler: F) -> FramedRoute<Io, S, F, R>
where
F: FnMut(FramedRequest<Io, S>) -> R,
R: IntoFuture<Item = ()>,
R::Future: 'static,
R::Error: fmt::Debug,
{
FramedRoute {
handler,
pattern: self.pattern,
methods: self.methods,
state: PhantomData,
}
}
}
impl<Io, S, F, R> HttpServiceFactory for FramedRoute<Io, S, F, R>
where
Io: AsyncRead + AsyncWrite + 'static,
F: FnMut(FramedRequest<Io, S>) -> R + Clone,
R: IntoFuture<Item = ()>,
R::Future: 'static,
R::Error: fmt::Display,
{
type Factory = FramedRouteFactory<Io, S, F, R>;
fn path(&self) -> &str {
&self.pattern
}
fn create(self) -> Self::Factory {
FramedRouteFactory {
handler: self.handler,
methods: self.methods,
_t: PhantomData,
}
}
}
pub struct FramedRouteFactory<Io, S, F, R> {
handler: F,
methods: Vec<Method>,
_t: PhantomData<(Io, S, R)>,
}
impl<Io, S, F, R> NewService for FramedRouteFactory<Io, S, F, R>
where
Io: AsyncRead + AsyncWrite + 'static,
F: FnMut(FramedRequest<Io, S>) -> R + Clone,
R: IntoFuture<Item = ()>,
R::Future: 'static,
R::Error: fmt::Display,
{
type Config = ();
type Request = FramedRequest<Io, S>;
type Response = ();
type Error = Error;
type InitError = ();
type Service = FramedRouteService<Io, S, F, R>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(FramedRouteService {
handler: self.handler.clone(),
methods: self.methods.clone(),
_t: PhantomData,
})
}
}
pub struct FramedRouteService<Io, S, F, R> {
handler: F,
methods: Vec<Method>,
_t: PhantomData<(Io, S, R)>,
}
impl<Io, S, F, R> Service for FramedRouteService<Io, S, F, R>
where
Io: AsyncRead + AsyncWrite + 'static,
F: FnMut(FramedRequest<Io, S>) -> R + Clone,
R: IntoFuture<Item = ()>,
R::Future: 'static,
R::Error: fmt::Display,
{
type Request = FramedRequest<Io, S>;
type Response = ();
type Error = Error;
type Future = Box<dyn Future<Item = (), Error = Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: FramedRequest<Io, S>) -> Self::Future {
Box::new((self.handler)(req).into_future().then(|res| {
if let Err(e) = res {
error!("Error in request handler: {}", e);
}
Ok(())
}))
}
}

View File

@ -1,149 +0,0 @@
use std::marker::PhantomData;
use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::body::BodySize;
use actix_http::error::ResponseError;
use actix_http::h1::{Codec, Message};
use actix_http::ws::{verify_handshake, HandshakeError};
use actix_http::{Request, Response};
use actix_service::{NewService, Service};
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll, Sink};
/// Service that verifies incoming request if it is valid websocket
/// upgrade request. In case of error returns `HandshakeError`
pub struct VerifyWebSockets<T, C> {
_t: PhantomData<(T, C)>,
}
impl<T, C> Default for VerifyWebSockets<T, C> {
fn default() -> Self {
VerifyWebSockets { _t: PhantomData }
}
}
impl<T, C> NewService for VerifyWebSockets<T, C> {
type Config = C;
type Request = (Request, Framed<T, Codec>);
type Response = (Request, Framed<T, Codec>);
type Error = (HandshakeError, Framed<T, Codec>);
type InitError = ();
type Service = VerifyWebSockets<T, C>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &C) -> Self::Future {
ok(VerifyWebSockets { _t: PhantomData })
}
}
impl<T, C> Service for VerifyWebSockets<T, C> {
type Request = (Request, Framed<T, Codec>);
type Response = (Request, Framed<T, Codec>);
type Error = (HandshakeError, Framed<T, Codec>);
type Future = FutureResult<Self::Response, Self::Error>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
match verify_handshake(req.head()) {
Err(e) => Err((e, framed)).into_future(),
Ok(_) => Ok((req, framed)).into_future(),
}
}
}
/// Send http/1 error response
pub struct SendError<T, R, E, C>(PhantomData<(T, R, E, C)>);
impl<T, R, E, C> Default for SendError<T, R, E, C>
where
T: AsyncRead + AsyncWrite,
E: ResponseError,
{
fn default() -> Self {
SendError(PhantomData)
}
}
impl<T, R, E, C> NewService for SendError<T, R, E, C>
where
T: AsyncRead + AsyncWrite + 'static,
R: 'static,
E: ResponseError + 'static,
{
type Config = C;
type Request = Result<R, (E, Framed<T, Codec>)>;
type Response = R;
type Error = (E, Framed<T, Codec>);
type InitError = ();
type Service = SendError<T, R, E, C>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &C) -> Self::Future {
ok(SendError(PhantomData))
}
}
impl<T, R, E, C> Service for SendError<T, R, E, C>
where
T: AsyncRead + AsyncWrite + 'static,
R: 'static,
E: ResponseError + 'static,
{
type Request = Result<R, (E, Framed<T, Codec>)>;
type Response = R;
type Error = (E, Framed<T, Codec>);
type Future = Either<FutureResult<R, (E, Framed<T, Codec>)>, SendErrorFut<T, R, E>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: Result<R, (E, Framed<T, Codec>)>) -> Self::Future {
match req {
Ok(r) => Either::A(ok(r)),
Err((e, framed)) => {
let res = e.error_response().drop_body();
Either::B(SendErrorFut {
framed: Some(framed),
res: Some((res, BodySize::Empty).into()),
err: Some(e),
_t: PhantomData,
})
}
}
}
}
pub struct SendErrorFut<T, R, E> {
res: Option<Message<(Response<()>, BodySize)>>,
framed: Option<Framed<T, Codec>>,
err: Option<E>,
_t: PhantomData<R>,
}
impl<T, R, E> Future for SendErrorFut<T, R, E>
where
E: ResponseError,
T: AsyncRead + AsyncWrite,
{
type Item = R;
type Error = (E, Framed<T, Codec>);
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(res) = self.res.take() {
if self.framed.as_mut().unwrap().force_send(res).is_err() {
return Err((self.err.take().unwrap(), self.framed.take().unwrap()));
}
}
match self.framed.as_mut().unwrap().poll_complete() {
Ok(Async::Ready(_)) => {
Err((self.err.take().unwrap(), self.framed.take().unwrap()))
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())),
}
}
}

View File

@ -1,29 +0,0 @@
use std::ops::Deref;
use std::sync::Arc;
/// Application state
pub struct State<S>(Arc<S>);
impl<S> State<S> {
pub fn new(state: S) -> State<S> {
State(Arc::new(state))
}
pub fn get_ref(&self) -> &S {
self.0.as_ref()
}
}
impl<S> Deref for State<S> {
type Target = S;
fn deref(&self) -> &S {
self.0.as_ref()
}
}
impl<S> Clone for State<S> {
fn clone(&self) -> State<S> {
State(self.0.clone())
}
}

View File

@ -1,153 +0,0 @@
//! Various helpers for Actix applications to use during testing.
use actix_codec::Framed;
use actix_http::h1::Codec;
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
use actix_http::http::{HttpTryFrom, Method, Uri, Version};
use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest};
use actix_router::{Path, Url};
use actix_rt::Runtime;
use futures::IntoFuture;
use crate::{FramedRequest, State};
/// Test `Request` builder.
pub struct TestRequest<S = ()> {
req: HttpTestRequest,
path: Path<Url>,
state: State<S>,
}
impl Default for TestRequest<()> {
fn default() -> TestRequest {
TestRequest {
req: HttpTestRequest::default(),
path: Path::new(Url::new(Uri::default())),
state: State::new(()),
}
}
}
impl TestRequest<()> {
/// Create TestRequest and set request uri
pub fn with_uri(path: &str) -> Self {
Self::get().uri(path)
}
/// Create TestRequest and set header
pub fn with_hdr<H: Header>(hdr: H) -> Self {
Self::default().set(hdr)
}
/// Create TestRequest and set header
pub fn with_header<K, V>(key: K, value: V) -> Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
Self::default().header(key, value)
}
/// Create TestRequest and set method to `Method::GET`
pub fn get() -> Self {
Self::default().method(Method::GET)
}
/// Create TestRequest and set method to `Method::POST`
pub fn post() -> Self {
Self::default().method(Method::POST)
}
}
impl<S> TestRequest<S> {
/// Create TestRequest and set request uri
pub fn with_state(state: S) -> TestRequest<S> {
let req = TestRequest::get();
TestRequest {
state: State::new(state),
req: req.req,
path: req.path,
}
}
/// Set HTTP version of this request
pub fn version(mut self, ver: Version) -> Self {
self.req.version(ver);
self
}
/// Set HTTP method of this request
pub fn method(mut self, meth: Method) -> Self {
self.req.method(meth);
self
}
/// Set HTTP Uri of this request
pub fn uri(mut self, path: &str) -> Self {
self.req.uri(path);
self
}
/// Set a header
pub fn set<H: Header>(mut self, hdr: H) -> Self {
self.req.set(hdr);
self
}
/// Set a header
pub fn header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
self.req.header(key, value);
self
}
/// Set request path pattern parameter
pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
self.path.add_static(name, value);
self
}
/// Complete request creation and generate `Request` instance
pub fn finish(mut self) -> FramedRequest<TestBuffer, S> {
let req = self.req.finish();
self.path.get_mut().update(req.uri());
let framed = Framed::new(TestBuffer::empty(), Codec::default());
FramedRequest::new(req, framed, self.path, self.state)
}
/// This method generates `FramedRequest` instance and executes async handler
pub fn run<F, R, I, E>(self, f: F) -> Result<I, E>
where
F: FnOnce(FramedRequest<TestBuffer, S>) -> R,
R: IntoFuture<Item = I, Error = E>,
{
let mut rt = Runtime::new().unwrap();
rt.block_on(f(self.finish()).into_future())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let req = TestRequest::with_uri("/index.html")
.header("x-test", "test")
.param("test", "123")
.finish();
assert_eq!(*req.state(), ());
assert_eq!(req.version(), Version::HTTP_11);
assert_eq!(req.method(), Method::GET);
assert_eq!(req.path(), "/index.html");
assert_eq!(req.query_string(), "");
assert_eq!(
req.headers().get("x-test").unwrap().to_str().unwrap(),
"test"
);
assert_eq!(&req.match_info()["test"], "123");
}
}

View File

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

View File

@ -1,41 +0,0 @@
environment:
global:
PROJECT_NAME: actix-http
matrix:
# Stable channel
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
# Nightly channel
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
install:
- ps: >-
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
$Env:PATH += ';C:\msys64\mingw64\bin'
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
$Env:PATH += ';C:\MinGW\bin'
}
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
# 'cargo test' takes care of building for us, so disable Appveyor's build stage.
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo clean
- cargo test

View File

@ -1,5 +1,190 @@
# Changes # Changes
## Unreleased - 2020-xx-xx
## 2.0.0 - 2020-09-11
* No significant changes from `2.0.0-beta.4`.
## 2.0.0-beta.4 - 2020-09-09
### Changed
* Update actix-codec and actix-utils dependencies.
* Update actix-connect and actix-tls dependencies.
## [2.0.0-beta.3] - 2020-08-14
### Fixed
* Memory leak of `client::pool::ConnectorPoolSupport`. [#1626]
[#1626]: https://github.com/actix/actix-web/pull/1626
## [2.0.0-beta.2] - 2020-07-21
### Fixed
* Potential UB in h1 decoder using uninitialized memory. [#1614]
### Changed
* Fix illegal chunked encoding. [#1615]
[#1614]: https://github.com/actix/actix-web/pull/1614
[#1615]: https://github.com/actix/actix-web/pull/1615
## [2.0.0-beta.1] - 2020-07-11
### Changed
* Migrate cookie handling to `cookie` crate. [#1558]
* Update `sha-1` to 0.9. [#1586]
* Fix leak in client pool. [#1580]
* MSRV is now 1.41.1.
[#1558]: https://github.com/actix/actix-web/pull/1558
[#1586]: https://github.com/actix/actix-web/pull/1586
[#1580]: https://github.com/actix/actix-web/pull/1580
## [2.0.0-alpha.4] - 2020-05-21
### Changed
* Bump minimum supported Rust version to 1.40
* content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439]
* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
`u64` instead of a `usize`.
* Update `base64` dependency to 0.12
### Fixed
* Support parsing of `SameSite=None` [#1503]
[#1439]: https://github.com/actix/actix-web/pull/1439
[#1503]: https://github.com/actix/actix-web/pull/1503
## [2.0.0-alpha.3] - 2020-05-08
### Fixed
* Correct spelling of ConnectError::Unresolved [#1487]
* Fix a mistake in the encoding of websocket continuation messages wherein
Item::FirstText and Item::FirstBinary are each encoded as the other.
### Changed
* Implement `std::error::Error` for our custom errors [#1422]
* Remove `failure` support for `ResponseError` since that crate
will be deprecated in the near future.
[#1422]: https://github.com/actix/actix-web/pull/1422
[#1487]: https://github.com/actix/actix-web/pull/1487
## [2.0.0-alpha.2] - 2020-03-07
### Changed
* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395]
* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively
to improve download speed for awc when downloading large objects. [#1394]
* client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394]
* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394]
[#1394]: https://github.com/actix/actix-web/pull/1394
[#1395]: https://github.com/actix/actix-web/pull/1395
## [2.0.0-alpha.1] - 2020-02-27
### Changed
* Update the `time` dependency to 0.2.7.
* Moved actors messages support from actix crate, enabled with feature `actors`.
* Breaking change: trait MessageBody requires Unpin and accepting Pin<&mut Self> instead of &mut self in the poll_next().
* MessageBody is not implemented for &'static [u8] anymore.
### 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
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests with `RequestHead`
### Fixed
* h2 will use error response #1080
* on_connect result isn't added to request extensions for http2 requests #1009
## [0.2.9] - 2019-08-13 ## [0.2.9] - 2019-08-13
### Changed ### Changed

View File

@ -1,8 +1,8 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "0.2.9" version = "2.0.0"
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"
keywords = ["actix", "http", "framework", "async", "futures"] keywords = ["actix", "http", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
@ -11,12 +11,11 @@ documentation = "https://docs.rs/actix-http/"
categories = ["network-programming", "asynchronous", 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 OR 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", "compress", "secure-cookies", "actors"]
[lib] [lib]
name = "actix_http" name = "actix_http"
@ -26,85 +25,85 @@ 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
fail = ["failure"]
# support for secure cookies # support for secure cookies
secure-cookies = ["ring"] secure-cookies = ["cookie/secure"]
# support for actix Actor messages
actors = ["actix"]
[dependencies] [dependencies]
actix-service = "0.4.1" actix-service = "1.0.6"
actix-codec = "0.1.2" actix-codec = "0.3.0"
actix-connect = "0.2.2" actix-connect = "2.0.0"
actix-utils = "0.4.4" actix-utils = "2.0.0"
actix-server-config = "0.1.2" actix-rt = "1.0.0"
actix-threadpool = "0.1.1" actix-threadpool = "0.3.1"
actix-tls = { version = "2.0.0", optional = true }
actix = { version = "0.10.0", optional = true }
base64 = "0.10" base64 = "0.12"
bitflags = "1.0" bitflags = "1.2"
bytes = "0.4" bytes = "0.5.3"
cookie = { version = "0.14.1", features = ["percent-encode"] }
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-channel = { version = "0.3.5", default-features = false }
hashbrown = "0.5.0" futures-core = { version = "0.3.5", default-features = false }
h2 = "0.1.16" futures-util = { version = "0.3.5", default-features = false }
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" itoa = "0.4"
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.17"
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.9"
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
ring = { version = "0.14.6", 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
failure = { version = "0.1.5", 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.1"
actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } actix-connect = { version = "2.0.0", features = ["openssl"] }
actix-connect = { version = "0.2.0", features=["ssl"] } actix-http-test = { version = "2.0.0", features = ["openssl"] }
actix-http-test = { version = "0.2.4", features=["ssl"] } actix-tls = { version = "2.0.0", features = ["openssl"] }
env_logger = "0.6" criterion = "0.3"
env_logger = "0.7"
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.18", package = "rustls" }
[[bench]]
name = "content-length"
harness = false
[[bench]]
name = "status-line"
harness = false
[[bench]]
name = "uninit-headers"
harness = false

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017-NOW Nikolay Kim
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1
actix-http/LICENSE-APACHE Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-APACHE

View File

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

1
actix-http/LICENSE-MIT Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-MIT

View File

@ -8,25 +8,40 @@ Actix http
* [API Documentation](https://docs.rs/actix-http/) * [API Documentation](https://docs.rs/actix-http/)
* [Chat on gitter](https://gitter.im/actix/actix) * [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-http](https://crates.io/crates/actix-http) * Cargo package: [actix-http](https://crates.io/crates/actix-http)
* Minimum supported Rust version: 1.31 or later * Minimum supported Rust version: 1.40 or later
## Example ## Example
```rust ```rust
// see examples/framed_hello.rs for complete list of used crates. // see examples/framed_hello.rs for complete list of used crates.
extern crate actix_http; use std::{env, io};
use actix_http::{h1, Response, ServiceConfig};
fn main() { use actix_http::{HttpService, Response};
Server::new().bind("framed_hello", "127.0.0.1:8080", || { use actix_server::Server;
IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec use futures::future;
.and_then(TakeItem::new().map_err(|_| ())) // <- read one request use http::header::HeaderValue;
.and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn use log::info;
SendResponse::send(_framed, Response::Ok().body("Hello world!"))
.map_err(|_| ()) #[actix_rt::main]
.map(|_| ()) async fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "hello_world=info");
env_logger::init();
Server::build()
.bind("hello-world", "127.0.0.1:8080", || {
HttpService::build()
.client_timeout(1000)
.client_disconnect(1000)
.finish(|_req| {
info!("{:?}", _req);
let mut res = Response::Ok();
res.header("x-head", HeaderValue::from_static("dummy value!"));
future::ok::<_, ()>(res.body("Hello world!"))
}) })
}).unwrap().run(); .tcp()
})?
.run()
.await
} }
``` ```

View File

@ -0,0 +1,291 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use bytes::BytesMut;
// benchmark sending all requests at the same time
fn bench_write_content_length(c: &mut Criterion) {
let mut group = c.benchmark_group("write_content_length");
let sizes = [
0, 1, 11, 83, 101, 653, 1001, 6323, 10001, 56329, 100001, 123456, 98724245,
4294967202,
];
for i in sizes.iter() {
group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_original::write_content_length(i, &mut b)
})
});
group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_new::write_content_length(i, &mut b)
})
});
group.bench_with_input(BenchmarkId::new("itoa", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_itoa::write_content_length(i, &mut b)
})
});
}
group.finish();
}
criterion_group!(benches, bench_write_content_length);
criterion_main!(benches);
mod _itoa {
use bytes::{BufMut, BytesMut};
pub fn write_content_length(n: usize, bytes: &mut BytesMut) {
if n == 0 {
bytes.put_slice(b"\r\ncontent-length: 0\r\n");
return;
}
let mut buf = itoa::Buffer::new();
bytes.put_slice(b"\r\ncontent-length: ");
bytes.put_slice(buf.format(n).as_bytes());
bytes.put_slice(b"\r\n");
}
}
mod _new {
use bytes::{BufMut, BytesMut};
const DIGITS_START: u8 = b'0';
/// NOTE: bytes object has to contain enough space
pub fn write_content_length(n: usize, bytes: &mut BytesMut) {
if n == 0 {
bytes.put_slice(b"\r\ncontent-length: 0\r\n");
return;
}
bytes.put_slice(b"\r\ncontent-length: ");
if n < 10 {
bytes.put_u8(DIGITS_START + (n as u8));
} else if n < 100 {
let n = n as u8;
let d10 = n / 10;
let d1 = n % 10;
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
} else if n < 1000 {
let n = n as u16;
let d100 = (n / 100) as u8;
let d10 = ((n / 10) % 10) as u8;
let d1 = (n % 10) as u8;
bytes.put_u8(DIGITS_START + d100);
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
} else if n < 10_000 {
let n = n as u16;
let d1000 = (n / 1000) as u8;
let d100 = ((n / 100) % 10) as u8;
let d10 = ((n / 10) % 10) as u8;
let d1 = (n % 10) as u8;
bytes.put_u8(DIGITS_START + d1000);
bytes.put_u8(DIGITS_START + d100);
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
} else if n < 100_000 {
let n = n as u32;
let d10000 = (n / 10000) as u8;
let d1000 = ((n / 1000) % 10) as u8;
let d100 = ((n / 100) % 10) as u8;
let d10 = ((n / 10) % 10) as u8;
let d1 = (n % 10) as u8;
bytes.put_u8(DIGITS_START + d10000);
bytes.put_u8(DIGITS_START + d1000);
bytes.put_u8(DIGITS_START + d100);
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
} else if n < 1_000_000 {
let n = n as u32;
let d100000 = (n / 100000) as u8;
let d10000 = ((n / 10000) % 10) as u8;
let d1000 = ((n / 1000) % 10) as u8;
let d100 = ((n / 100) % 10) as u8;
let d10 = ((n / 10) % 10) as u8;
let d1 = (n % 10) as u8;
bytes.put_u8(DIGITS_START + d100000);
bytes.put_u8(DIGITS_START + d10000);
bytes.put_u8(DIGITS_START + d1000);
bytes.put_u8(DIGITS_START + d100);
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
} else {
write_usize(n, bytes);
}
bytes.put_slice(b"\r\n");
}
fn write_usize(n: usize, bytes: &mut BytesMut) {
let mut n = n;
// 20 chars is max length of a usize (2^64)
// digits will be added to the buffer from lsd to msd
let mut buf = BytesMut::with_capacity(20);
while n > 9 {
// "pop" the least-significant digit
let lsd = (n % 10) as u8;
// remove the lsd from n
n = n / 10;
buf.put_u8(DIGITS_START + lsd);
}
// put msd to result buffer
bytes.put_u8(DIGITS_START + (n as u8));
// put, in reverse (msd to lsd), remaining digits to buffer
for i in (0..buf.len()).rev() {
bytes.put_u8(buf[i]);
}
}
}
mod _original {
use std::{mem, ptr, slice};
use bytes::{BufMut, BytesMut};
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
/// NOTE: bytes object has to contain enough space
pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
if n < 10 {
let mut buf: [u8; 21] = [
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'\r', b'\n',
];
buf[18] = (n as u8) + b'0';
bytes.put_slice(&buf);
} else if n < 100 {
let mut buf: [u8; 22] = [
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'\r', b'\n',
];
let d1 = n << 1;
unsafe {
ptr::copy_nonoverlapping(
DEC_DIGITS_LUT.as_ptr().add(d1),
buf.as_mut_ptr().offset(18),
2,
);
}
bytes.put_slice(&buf);
} else if n < 1000 {
let mut buf: [u8; 23] = [
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'0', b'\r',
b'\n',
];
// decode 2 more chars, if > 2 chars
let d1 = (n % 100) << 1;
n /= 100;
unsafe {
ptr::copy_nonoverlapping(
DEC_DIGITS_LUT.as_ptr().add(d1),
buf.as_mut_ptr().offset(19),
2,
)
};
// decode last 1
buf[18] = (n as u8) + b'0';
bytes.put_slice(&buf);
} else {
bytes.put_slice(b"\r\ncontent-length: ");
convert_usize(n, bytes);
}
}
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
let mut curr: isize = 39;
let mut buf: [u8; 41] = unsafe { mem::MaybeUninit::uninit().assume_init() };
buf[39] = b'\r';
buf[40] = b'\n';
let buf_ptr = buf.as_mut_ptr();
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
// eagerly decode 4 characters at a time
while n >= 10_000 {
let rem = (n % 10_000) as isize;
n /= 10_000;
let d1 = (rem / 100) << 1;
let d2 = (rem % 100) << 1;
curr -= 4;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
ptr::copy_nonoverlapping(
lut_ptr.offset(d2),
buf_ptr.offset(curr + 2),
2,
);
}
}
// if we reach here numbers are <= 9999, so at most 4 chars long
let mut n = n as isize; // possibly reduce 64bit math
// decode 2 more chars, if > 2 chars
if n >= 100 {
let d1 = (n % 100) << 1;
n /= 100;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
}
// decode last 1 or 2 chars
if n < 10 {
curr -= 1;
unsafe {
*buf_ptr.offset(curr) = (n as u8) + b'0';
}
} else {
let d1 = n << 1;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
}
unsafe {
bytes.extend_from_slice(slice::from_raw_parts(
buf_ptr.offset(curr),
41 - curr as usize,
));
}
}
}

View File

@ -0,0 +1,222 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use bytes::BytesMut;
use http::Version;
const CODES: &[u16] = &[201, 303, 404, 515];
fn bench_write_status_line_11(c: &mut Criterion) {
let mut group = c.benchmark_group("write_status_line v1.1");
let version = Version::HTTP_11;
for i in CODES.iter() {
group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_original::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_new::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_naive::write_status_line(version, i, &mut b);
})
});
}
group.finish();
}
fn bench_write_status_line_10(c: &mut Criterion) {
let mut group = c.benchmark_group("write_status_line v1.0");
let version = Version::HTTP_10;
for i in CODES.iter() {
group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_original::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_new::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_naive::write_status_line(version, i, &mut b);
})
});
}
group.finish();
}
fn bench_write_status_line_09(c: &mut Criterion) {
let mut group = c.benchmark_group("write_status_line v0.9");
let version = Version::HTTP_09;
for i in CODES.iter() {
group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_original::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_new::write_status_line(version, i, &mut b);
})
});
group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| {
b.iter(|| {
let mut b = BytesMut::with_capacity(35);
_naive::write_status_line(version, i, &mut b);
})
});
}
group.finish();
}
criterion_group!(
benches,
bench_write_status_line_11,
bench_write_status_line_10,
bench_write_status_line_09
);
criterion_main!(benches);
mod _naive {
use bytes::{BufMut, BytesMut};
use http::Version;
pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) {
match version {
Version::HTTP_11 => bytes.put_slice(b"HTTP/1.1 "),
Version::HTTP_10 => bytes.put_slice(b"HTTP/1.0 "),
Version::HTTP_09 => bytes.put_slice(b"HTTP/0.9 "),
_ => {
// other HTTP version handlers do not use this method
}
}
bytes.put_slice(n.to_string().as_bytes());
}
}
mod _new {
use bytes::{BufMut, BytesMut};
use http::Version;
const DIGITS_START: u8 = b'0';
pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) {
match version {
Version::HTTP_11 => bytes.put_slice(b"HTTP/1.1 "),
Version::HTTP_10 => bytes.put_slice(b"HTTP/1.0 "),
Version::HTTP_09 => bytes.put_slice(b"HTTP/0.9 "),
_ => {
// other HTTP version handlers do not use this method
}
}
let d100 = (n / 100) as u8;
let d10 = ((n / 10) % 10) as u8;
let d1 = (n % 10) as u8;
bytes.put_u8(DIGITS_START + d100);
bytes.put_u8(DIGITS_START + d10);
bytes.put_u8(DIGITS_START + d1);
bytes.put_u8(b' ');
}
}
mod _original {
use std::ptr;
use bytes::{BufMut, BytesMut};
use http::Version;
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
pub(crate) const STATUS_LINE_BUF_SIZE: usize = 13;
pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
let mut buf: [u8; STATUS_LINE_BUF_SIZE] = *b"HTTP/1.1 ";
match version {
Version::HTTP_2 => buf[5] = b'2',
Version::HTTP_10 => buf[7] = b'0',
Version::HTTP_09 => {
buf[5] = b'0';
buf[7] = b'9';
}
_ => (),
}
let mut curr: isize = 12;
let buf_ptr = buf.as_mut_ptr();
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
let four = n > 999;
// decode 2 more chars, if > 2 chars
let d1 = (n % 100) << 1;
n /= 100;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(
lut_ptr.offset(d1 as isize),
buf_ptr.offset(curr),
2,
);
}
// decode last 1 or 2 chars
if n < 10 {
curr -= 1;
unsafe {
*buf_ptr.offset(curr) = (n as u8) + b'0';
}
} else {
let d1 = n << 1;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(
lut_ptr.offset(d1 as isize),
buf_ptr.offset(curr),
2,
);
}
}
bytes.put_slice(&buf);
if four {
bytes.put_u8(b' ');
}
}
}

View File

@ -0,0 +1,137 @@
use criterion::{criterion_group, criterion_main, Criterion};
use bytes::BytesMut;
// A Miri run detects UB, seen on this playground:
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f5d9aa166aa48df8dca05fce2b6c3915
fn bench_header_parsing(c: &mut Criterion) {
c.bench_function("Original (Unsound) [short]", |b| {
b.iter(|| {
let mut buf = BytesMut::from(REQ_SHORT);
_original::parse_headers(&mut buf);
})
});
c.bench_function("New (safe) [short]", |b| {
b.iter(|| {
let mut buf = BytesMut::from(REQ_SHORT);
_new::parse_headers(&mut buf);
})
});
c.bench_function("Original (Unsound) [realistic]", |b| {
b.iter(|| {
let mut buf = BytesMut::from(REQ);
_original::parse_headers(&mut buf);
})
});
c.bench_function("New (safe) [realistic]", |b| {
b.iter(|| {
let mut buf = BytesMut::from(REQ);
_new::parse_headers(&mut buf);
})
});
}
criterion_group!(benches, bench_header_parsing);
criterion_main!(benches);
const MAX_HEADERS: usize = 96;
const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] =
[httparse::EMPTY_HEADER; MAX_HEADERS];
#[derive(Clone, Copy)]
struct HeaderIndex {
name: (usize, usize),
value: (usize, usize),
}
const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
name: (0, 0),
value: (0, 0),
};
const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
[EMPTY_HEADER_INDEX; MAX_HEADERS];
impl HeaderIndex {
fn record(
bytes: &[u8],
headers: &[httparse::Header<'_>],
indices: &mut [HeaderIndex],
) {
let bytes_ptr = bytes.as_ptr() as usize;
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
let name_start = header.name.as_ptr() as usize - bytes_ptr;
let name_end = name_start + header.name.len();
indices.name = (name_start, name_end);
let value_start = header.value.as_ptr() as usize - bytes_ptr;
let value_end = value_start + header.value.len();
indices.value = (value_start, value_end);
}
}
}
// test cases taken from:
// https://github.com/seanmonstar/httparse/blob/master/benches/parse.rs
const REQ_SHORT: &'static [u8] = b"\
GET / HTTP/1.0\r\n\
Host: example.com\r\n\
Cookie: session=60; user_id=1\r\n\r\n";
const REQ: &'static [u8] = b"\
GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\
Host: www.kittyhell.com\r\n\
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\
Accept-Encoding: gzip,deflate\r\n\
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\
Keep-Alive: 115\r\n\
Connection: keep-alive\r\n\
Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral|padding=under256\r\n\r\n";
mod _new {
use super::*;
pub fn parse_headers(src: &mut BytesMut) -> usize {
let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY;
let mut req = httparse::Request::new(&mut parsed);
match req.parse(src).unwrap() {
httparse::Status::Complete(_len) => {
HeaderIndex::record(src, req.headers, &mut headers);
req.headers.len()
}
_ => unreachable!(),
}
}
}
mod _original {
use super::*;
use std::mem::MaybeUninit;
pub fn parse_headers(src: &mut BytesMut) -> usize {
let mut headers: [HeaderIndex; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() };
let mut parsed: [httparse::Header<'_>; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() };
let mut req = httparse::Request::new(&mut parsed);
match req.parse(src).unwrap() {
httparse::Status::Complete(_len) => {
HeaderIndex::record(src, req.headers, &mut headers);
req.headers.len()
}
_ => unreachable!(),
}
}
}

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_util::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();
@ -16,22 +17,21 @@ fn main() -> io::Result<()> {
HttpService::build() HttpService::build()
.client_timeout(1000) .client_timeout(1000)
.client_disconnect(1000) .client_disconnect(1000)
.finish(|mut req: Request| { .finish(|mut req: Request| async move {
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) }
})
.and_then(|bytes| { info!("request body: {:?}", body);
info!("request body: {:?}", bytes); Ok::<_, Error>(
let mut res = Response::Ok(); Response::Ok()
res.header( .header("x-head", HeaderValue::from_static("dummy value!"))
"x-head", .body(body),
HeaderValue::from_static("dummy value!"), )
);
Ok(res.body(bytes))
})
}) })
.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_util::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()
.and_then(|bytes| {
info!("request body: {:?}", bytes);
let mut res = Response::Ok();
res.header("x-head", HeaderValue::from_static("dummy value!"));
Ok(res.body(bytes))
})
} }
fn main() -> io::Result<()> { info!("request body: {:?}", body);
Ok(Response::Ok()
.header("x-head", HeaderValue::from_static("dummy value!"))
.body(body))
}
#[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

@ -2,11 +2,12 @@ use std::{env, io};
use actix_http::{HttpService, Response}; use actix_http::{HttpService, Response};
use actix_server::Server; use actix_server::Server;
use futures::future; use futures_util::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;
use crate::error::Error; use crate::error::Error;
@ -11,20 +15,13 @@ use crate::error::Error;
pub enum BodySize { pub enum BodySize {
None, None,
Empty, Empty,
Sized(usize), Sized(u64),
Sized64(u64),
Stream, Stream,
} }
impl BodySize { impl BodySize {
pub fn is_eof(&self) -> bool { pub fn is_eof(&self) -> bool {
match self { matches!(self, BodySize::None | BodySize::Empty | BodySize::Sized(0))
BodySize::None
| BodySize::Empty
| BodySize::Sized(0)
| BodySize::Sized64(0) => true,
_ => false,
}
} }
} }
@ -32,32 +29,46 @@ 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(
self: Pin<&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(
Ok(Async::Ready(None)) self: Pin<&mut Self>,
_: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Error>>> {
Poll::Ready(None)
} }
} }
impl<T: MessageBody> MessageBody for Box<T> { impl<T: MessageBody + Unpin> MessageBody for Box<T> {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
self.as_ref().size() self.as_ref().size()
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(
self.as_mut().poll_next() self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Error>>> {
Pin::new(self.get_mut().as_mut()).poll_next(cx)
} }
} }
#[pin_project(project = ResponseBodyProj)]
pub enum ResponseBody<B> { pub enum ResponseBody<B> {
Body(B), Body(#[pin] B),
Other(Body), Other(#[pin] Body),
} }
impl ResponseBody<Body> { impl ResponseBody<Body> {
@ -93,23 +104,32 @@ impl<B: MessageBody> MessageBody for ResponseBody<B> {
} }
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(
match self { self: Pin<&mut Self>,
ResponseBody::Body(ref mut body) => body.poll_next(), cx: &mut Context<'_>,
ResponseBody::Other(ref mut body) => body.poll_next(), ) -> Poll<Option<Result<Bytes, Error>>> {
match self.project() {
ResponseBodyProj::Body(body) => body.poll_next(cx),
ResponseBodyProj::Other(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> { fn poll_next(
self.poll_next() self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
match self.project() {
ResponseBodyProj::Body(body) => body.poll_next(cx),
ResponseBodyProj::Other(body) => body.poll_next(cx),
}
} }
} }
#[pin_project(project = BodyProj)]
/// Represents various types of http message body. /// Represents various types of http message body.
pub enum Body { pub enum Body {
/// Empty response. `Content-Length` header is not set. /// Empty response. `Content-Length` header is not set.
@ -119,17 +139,17 @@ pub enum Body {
/// Specific response body. /// Specific response body.
Bytes(Bytes), Bytes(Bytes),
/// Generic message body. /// Generic message body.
Message(Box<dyn MessageBody>), Message(Box<dyn MessageBody + Unpin>),
} }
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.
pub fn from_message<B: MessageBody + 'static>(body: B) -> Body { pub fn from_message<B: MessageBody + Unpin + 'static>(body: B) -> Body {
Body::Message(Box::new(body)) Body::Message(Box::new(body))
} }
} }
@ -139,24 +159,27 @@ impl MessageBody for Body {
match self { match self {
Body::None => BodySize::None, Body::None => BodySize::None,
Body::Empty => BodySize::Empty, Body::Empty => BodySize::Empty,
Body::Bytes(ref bin) => BodySize::Sized(bin.len()), Body::Bytes(ref bin) => BodySize::Sized(bin.len() as u64),
Body::Message(ref body) => body.size(), Body::Message(ref body) => body.size(),
} }
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(
match self { self: Pin<&mut Self>,
Body::None => Ok(Async::Ready(None)), cx: &mut Context<'_>,
Body::Empty => Ok(Async::Ready(None)), ) -> Poll<Option<Result<Bytes, Error>>> {
Body::Bytes(ref mut bin) => { match self.project() {
BodyProj::None => Poll::Ready(None),
BodyProj::Empty => Poll::Ready(None),
BodyProj::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::take(bin))))
} }
} }
Body::Message(ref mut body) => body.poll_next(), BodyProj::Message(ref mut body) => Pin::new(body.as_mut()).poll_next(cx),
} }
} }
} }
@ -164,14 +187,8 @@ impl MessageBody for Body {
impl PartialEq for Body { impl PartialEq for Body {
fn eq(&self, other: &Body) -> bool { fn eq(&self, other: &Body) -> bool {
match *self { match *self {
Body::None => match *other { Body::None => matches!(*other, Body::None),
Body::None => true, Body::Empty => matches!(*other, Body::Empty),
_ => false,
},
Body::Empty => match *other {
Body::Empty => true,
_ => false,
},
Body::Bytes(ref b) => match *other { Body::Bytes(ref b) => match *other {
Body::Bytes(ref b2) => b == b2, Body::Bytes(ref b2) => b == b2,
_ => false, _ => false,
@ -182,7 +199,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 +235,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 +251,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>> + Unpin + 'static,
{ {
fn from(s: SizedStream<S>) -> Body { fn from(s: SizedStream<S>) -> Body {
Body::from_message(s) Body::from_message(s)
@ -245,7 +268,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>> + Unpin + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
{ {
fn from(s: BodyStream<S, E>) -> Body { fn from(s: BodyStream<S, E>) -> Body {
@ -255,94 +278,88 @@ where
impl MessageBody for Bytes { impl MessageBody for Bytes {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len() as u64)
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(
self: Pin<&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::take(self.get_mut()))))
} }
} }
} }
impl MessageBody for BytesMut { impl MessageBody for BytesMut {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len() as u64)
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(
self: Pin<&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::take(self.get_mut()).freeze())))
mem::replace(self, BytesMut::new()).freeze(),
)))
} }
} }
} }
impl MessageBody for &'static str { impl MessageBody for &'static str {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len() as u64)
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(
self: Pin<&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::take(self.get_mut()).as_ref(),
)))) ))))
} }
} }
} }
impl MessageBody for &'static [u8] {
fn size(&self) -> BodySize {
BodySize::Sized(self.len())
}
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
if self.is_empty() {
Ok(Async::Ready(None))
} else {
Ok(Async::Ready(Some(Bytes::from_static(mem::replace(
self, b"",
)))))
}
}
}
impl MessageBody for Vec<u8> { impl MessageBody for Vec<u8> {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len() as u64)
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(
self: Pin<&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::take(self.get_mut())))))
self,
Vec::new(),
)))))
} }
} }
} }
impl MessageBody for String { impl MessageBody for String {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized(self.len()) BodySize::Sized(self.len() as u64)
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(
self: Pin<&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::take(self.get_mut()).into_bytes(),
)))) ))))
} }
} }
@ -350,14 +367,16 @@ 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> { #[pin_project]
pub struct BodyStream<S: Unpin, E> {
#[pin]
stream: S, stream: 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>> + Unpin,
E: Into<Error>, E: Into<Error>,
{ {
pub fn new(stream: S) -> Self { pub fn new(stream: S) -> Self {
@ -370,28 +389,45 @@ 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>> + Unpin,
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(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Error>>> {
let mut stream = self.project().stream;
loop {
let stream = stream.as_mut();
return Poll::Ready(match ready!(stream.poll_next(cx)) {
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
opt => opt.map(|res| res.map_err(Into::into)),
});
}
} }
} }
/// Type represent streaming body. This body implementation should be used /// Type represent streaming body. This body implementation should be used
/// 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> { #[pin_project]
pub struct SizedStream<S: Unpin> {
size: u64, size: u64,
#[pin]
stream: S, stream: S,
} }
impl<S> SizedStream<S> impl<S> SizedStream<S>
where where
S: Stream<Item = Bytes, Error = Error>, S: Stream<Item = Result<Bytes, Error>> + Unpin,
{ {
pub fn new(size: u64, stream: S) -> Self { pub fn new(size: u64, stream: S) -> Self {
SizedStream { size, stream } SizedStream { size, stream }
@ -400,20 +436,38 @@ where
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>> + Unpin,
{ {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Sized64(self.size) BodySize::Sized(self.size as u64)
} }
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(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Error>>> {
let mut stream: Pin<&mut S> = self.project().stream;
loop {
let stream = stream.as_mut();
return Poll::Ready(match ready!(stream.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_util::future::poll_fn;
use futures_util::pin_mut;
use futures_util::stream;
impl Body { impl Body {
pub(crate) fn get_ref(&self) -> &[u8] { pub(crate) fn get_ref(&self) -> &[u8] {
@ -433,21 +487,24 @@ 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| Pin::new(&mut "test").poll_next(cx))
Async::Ready(Some(Bytes::from("test"))) .await
.unwrap()
.ok(),
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!(
@ -455,86 +512,95 @@ mod tests {
BodySize::Sized(4) BodySize::Sized(4)
); );
assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test");
let sb = Bytes::from(&b"test"[..]);
pin_mut!(sb);
assert_eq!((&b"test"[..]).size(), BodySize::Sized(4)); assert_eq!(sb.size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
(&b"test"[..]).poll_next().unwrap(), poll_fn(|cx| sb.as_mut().poll_next(cx)).await.unwrap().ok(),
Async::Ready(Some(Bytes::from("test"))) 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");
let test_vec = Vec::from("test");
pin_mut!(test_vec);
assert_eq!(Vec::from("test").size(), BodySize::Sized(4)); assert_eq!(test_vec.size(), BodySize::Sized(4));
assert_eq!( assert_eq!(
Vec::from("test").poll_next().unwrap(), poll_fn(|cx| test_vec.as_mut().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 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");
pin_mut!(b);
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.as_mut().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 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");
pin_mut!(b);
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.as_mut().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 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");
assert_eq!(Body::from(&b).size(), BodySize::Sized(4)); assert_eq!(Body::from(&b).size(), BodySize::Sized(4));
assert_eq!(Body::from(&b).get_ref(), b"test"); assert_eq!(Body::from(&b).get_ref(), b"test");
pin_mut!(b);
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.as_mut().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| Pin::new(&mut ()).poll_next(cx))
.await
.is_none());
} }
#[test] #[actix_rt::test]
fn test_box() { async fn test_box() {
let mut val = Box::new(()); let val = Box::new(());
pin_mut!(val);
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.as_mut().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::Empty);
assert!(Body::Empty == Body::Empty);
assert!(Body::Empty != Body::None);
assert!( assert!(
Body::Bytes(Bytes::from_static(b"1")) Body::Bytes(Bytes::from_static(b"1"))
== Body::Bytes(Bytes::from_static(b"1")) == Body::Bytes(Bytes::from_static(b"1"))
@ -542,10 +608,116 @@ 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::*;
//use futures::task::noop_waker;
//use futures::stream::once;
#[actix_rt::test]
async fn skips_empty_chunks() {
let body = BodyStream::new(stream::iter(
["1", "", "2"]
.iter()
.map(|&v| Ok(Bytes::from(v)) as Result<Bytes, ()>),
));
pin_mut!(body);
assert_eq!(
poll_fn(|cx| body.as_mut().poll_next(cx))
.await
.unwrap()
.ok(),
Some(Bytes::from("1")),
);
assert_eq!(
poll_fn(|cx| body.as_mut().poll_next(cx))
.await
.unwrap()
.ok(),
Some(Bytes::from("2")),
);
}
/* Now it does not compile as it should
#[actix_rt::test]
async fn move_pinned_pointer() {
let (sender, receiver) = futures::channel::oneshot::channel();
let mut body_stream = Ok(BodyStream::new(once(async {
let x = Box::new(0i32);
let y = &x;
receiver.await.unwrap();
let _z = **y;
Ok::<_, ()>(Bytes::new())
})));
let waker = noop_waker();
let mut context = Context::from_waker(&waker);
pin_mut!(body_stream);
let _ = body_stream.as_mut().unwrap().poll_next(&mut context);
sender.send(()).unwrap();
let _ = std::mem::replace(&mut body_stream, Err([0; 32])).unwrap().poll_next(&mut context);
}*/
}
mod sized_stream {
use super::*;
#[actix_rt::test]
async fn skips_empty_chunks() {
let body = SizedStream::new(
2,
stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))),
);
pin_mut!(body);
assert_eq!(
poll_fn(|cx| body.as_mut().poll_next(cx))
.await
.unwrap()
.ok(),
Some(Bytes::from("1")),
);
assert_eq!(
poll_fn(|cx| body.as_mut().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('!');
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,47 +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)
} }
/// 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

@ -0,0 +1,39 @@
use std::time::Duration;
// These values are taken from hyper/src/proto/h2/client.rs
const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2mb
const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1mb
/// Connector configuration
#[derive(Clone)]
pub(crate) struct ConnectorConfig {
pub(crate) timeout: Duration,
pub(crate) conn_lifetime: Duration,
pub(crate) conn_keep_alive: Duration,
pub(crate) disconnect_timeout: Option<Duration>,
pub(crate) limit: usize,
pub(crate) conn_window_size: u32,
pub(crate) stream_window_size: u32,
}
impl Default for ConnectorConfig {
fn default() -> Self {
Self {
timeout: Duration::from_secs(1),
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
disconnect_timeout: Some(Duration::from_millis(3000)),
limit: 100,
conn_window_size: DEFAULT_H2_CONN_WINDOW,
stream_window_size: DEFAULT_H2_STREAM_WINDOW,
}
}
}
impl ConnectorConfig {
pub(crate) fn no_disconnect_timeout(&self) -> Self {
let mut res = self.clone();
res.disconnect_timeout = None;
res
}
}

View File

@ -1,14 +1,17 @@
use std::{fmt, io, time}; use std::future::Future;
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, FutureExt, LocalBoxFuture, Ready};
use futures::Poll;
use h2::client::SendRequest; use h2::client::SendRequest;
use pin_project::pin_project;
use crate::body::MessageBody; use crate::body::MessageBody;
use crate::h1::ClientCodec; use crate::h1::ClientCodec;
use crate::message::{RequestHead, ResponseHead}; use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::Payload; use crate::payload::Payload;
use super::error::SendRequestError; use super::error::SendRequestError;
@ -21,33 +24,32 @@ 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;
/// Send request and body /// Send request and body
fn send_request<B: MessageBody + 'static>( fn send_request<B: MessageBody + 'static, H: Into<RequestHeadType>>(
self, self,
head: RequestHead, head: H,
body: B, body: B,
) -> 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
fn open_tunnel(self, head: RequestHead) -> Self::TunnelFuture; fn open_tunnel<H: Into<RequestHeadType>>(self, head: H) -> Self::TunnelFuture;
} }
pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static {
/// Close connection /// Close connection
fn close(&mut self); fn close(self: Pin<&mut Self>);
/// Release connection to the connection pool /// Release connection to the connection pool
fn release(&mut self); fn release(self: Pin<&mut Self>);
} }
#[doc(hidden)] #[doc(hidden)]
@ -62,7 +64,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 +73,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 +93,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 {
@ -105,44 +107,36 @@ where
} }
} }
fn send_request<B: MessageBody + 'static>( fn send_request<B: MessageBody + 'static, H: Into<RequestHeadType>>(
mut self, mut self,
head: RequestHead, head: H,
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, .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,
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(mut self, head: RequestHead) -> 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))) 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 +146,7 @@ where
None, None,
)); ));
} }
Either::B(err(SendRequestError::TunnelNotSupported)) Either::Right(err(SendRequestError::TunnelNotSupported))
} }
} }
} }
@ -166,12 +160,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 {
@ -180,9 +174,9 @@ where
} }
} }
fn send_request<RB: MessageBody + 'static>( fn send_request<RB: MessageBody + 'static, H: Into<RequestHeadType>>(
self, self,
head: RequestHead, head: H,
body: RB, body: RB,
) -> Self::Future { ) -> Self::Future {
match self { match self {
@ -191,44 +185,34 @@ 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(self, head: RequestHead) -> 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.into_map_io(EitherIo::A)))
EitherConnection::B(con) => Box::new( })
con.open_tunnel(head) .boxed_local(),
.map(|(head, framed)| (head, framed.map_io(EitherIo::B))), EitherConnection::B(con) => con
), .open_tunnel(head)
.map(|res| {
res.map(|(head, framed)| (head, framed.into_map_io(EitherIo::B)))
})
.boxed_local(),
} }
} }
} }
#[pin_project(project = EitherIoProj)]
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 +220,21 @@ where
A: AsyncRead, A: AsyncRead,
B: AsyncRead, B: AsyncRead,
{ {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
match self.project() {
EitherIoProj::A(val) => val.poll_read(cx, buf),
EitherIoProj::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 +242,50 @@ 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> { fn poll_write(
match self { self: Pin<&mut Self>,
EitherIo::A(ref mut val) => val.shutdown(), cx: &mut Context<'_>,
EitherIo::B(ref mut val) => val.shutdown(), buf: &[u8],
) -> Poll<io::Result<usize>> {
match self.project() {
EitherIoProj::A(val) => val.poll_write(cx, buf),
EitherIoProj::B(val) => val.poll_write(cx, buf),
} }
} }
fn write_buf<U: Buf>(&mut self, buf: &mut U) -> Poll<usize, io::Error> fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match self.project() {
EitherIoProj::A(val) => val.poll_flush(cx),
EitherIoProj::B(val) => val.poll_flush(cx),
}
}
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
match self.project() {
EitherIoProj::A(val) => val.poll_shutdown(cx),
EitherIoProj::B(val) => val.poll_shutdown(cx),
}
}
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 { match self.project() {
EitherIo::A(ref mut val) => val.write_buf(buf), EitherIoProj::A(val) => val.poll_write_buf(cx, buf),
EitherIo::B(ref mut val) => val.write_buf(buf), EitherIoProj::B(val) => val.poll_write_buf(cx, buf),
} }
} }
} }

View File

@ -6,32 +6,33 @@ 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::config::ConnectorConfig;
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
@ -48,21 +49,17 @@ type SslConnector = ();
/// ``` /// ```
pub struct Connector<T, U> { pub struct Connector<T, U> {
connector: T, connector: T,
timeout: Duration, config: ConnectorConfig,
conn_lifetime: Duration,
conn_keep_alive: Duration,
disconnect_timeout: Duration,
limit: usize,
#[allow(dead_code)] #[allow(dead_code)]
ssl: SslConnector, ssl: SslConnector,
_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>,
@ -71,49 +68,54 @@ impl Connector<(), ()> {
> + Clone, > + Clone,
TcpStream, TcpStream,
> { > {
let ssl = {
#[cfg(feature = "ssl")]
{
use openssl::ssl::SslMethod;
let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap();
let _ = ssl
.set_alpn_protos(b"\x02h2\x08http/1.1")
.map_err(|e| error!("Can not set alpn protocol: {:?}", e));
SslConnector::Openssl(ssl.build())
}
#[cfg(all(not(feature = "ssl"), feature = "rust-tls"))]
{
let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
let mut config = ClientConfig::new();
config.set_protocols(&protos);
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
SslConnector::Rustls(Arc::new(config))
}
#[cfg(not(any(feature = "ssl", feature = "rust-tls")))]
{}
};
Connector { Connector {
ssl, ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
connector: default_connector(), connector: default_connector(),
timeout: Duration::from_secs(1), config: ConnectorConfig::default(),
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
disconnect_timeout: Duration::from_millis(3000),
limit: 100,
_t: PhantomData, _t: PhantomData,
} }
} }
// Build Ssl connector with openssl, based on supplied alpn protocols
#[cfg(feature = "openssl")]
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
use actix_connect::ssl::openssl::SslMethod;
use bytes::{BufMut, BytesMut};
let mut alpn = BytesMut::with_capacity(20);
for proto in protocols.iter() {
alpn.put_u8(proto.len() as u8);
alpn.put(proto.as_slice());
}
let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap();
let _ = ssl
.set_alpn_protos(&alpn)
.map_err(|e| error!("Can not set alpn protocol: {:?}", e));
SslConnector::Openssl(ssl.build())
}
// Build Ssl connector with rustls, based on supplied alpn protocols
#[cfg(all(not(feature = "openssl"), feature = "rustls"))]
fn build_ssl(protocols: Vec<Vec<u8>>) -> SslConnector {
let mut config = ClientConfig::new();
config.set_protocols(&protocols);
config
.root_store
.add_server_trust_anchors(&actix_tls::rustls::TLS_SERVER_ROOTS);
SslConnector::Rustls(Arc::new(config))
}
// ssl turned off, provides empty ssl connector
#[cfg(not(any(feature = "openssl", feature = "rustls")))]
fn build_ssl(_: Vec<Vec<u8>>) -> SslConnector {}
} }
impl<T, U> Connector<T, U> { 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>,
@ -122,11 +124,7 @@ impl<T, U> Connector<T, U> {
{ {
Connector { Connector {
connector, connector,
timeout: self.timeout, config: self.config,
conn_lifetime: self.conn_lifetime,
conn_keep_alive: self.conn_keep_alive,
disconnect_timeout: self.disconnect_timeout,
limit: self.limit,
ssl: self.ssl, ssl: self.ssl,
_t: PhantomData, _t: PhantomData,
} }
@ -135,7 +133,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>,
@ -146,29 +144,61 @@ where
/// Connection timeout, i.e. max time to connect to remote host including dns name resolution. /// Connection timeout, i.e. max time to connect to remote host including dns name resolution.
/// Set to 1 second by default. /// Set to 1 second by default.
pub fn timeout(mut self, timeout: Duration) -> Self { pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout; self.config.timeout = timeout;
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
} }
/// Maximum supported http major version
/// Supported versions http/1.1, http/2
pub fn max_http_version(mut self, val: http::Version) -> Self {
let versions = match val {
http::Version::HTTP_11 => vec![b"http/1.1".to_vec()],
http::Version::HTTP_2 => vec![b"h2".to_vec(), b"http/1.1".to_vec()],
_ => {
unimplemented!("actix-http:client: supported versions http/1.1, http/2")
}
};
self.ssl = Connector::build_ssl(versions);
self
}
/// Indicates the initial window size (in octets) for
/// HTTP2 stream-level flow control for received data.
///
/// The default value is 65,535 and is good for APIs, but not for big objects.
pub fn initial_window_size(mut self, size: u32) -> Self {
self.config.stream_window_size = size;
self
}
/// Indicates the initial window size (in octets) for
/// HTTP2 connection-level flow control for received data.
///
/// The default value is 65,535 and is good for APIs, but not for big objects.
pub fn initial_connection_window_size(mut self, size: u32) -> Self {
self.config.conn_window_size = size;
self
}
/// Set total number of simultaneous connections per type of scheme. /// Set total number of simultaneous connections per type of scheme.
/// ///
/// If limit is 0, the connector has no limit. /// If limit is 0, the connector has no limit.
/// The default limit size is 100. /// The default limit size is 100.
pub fn limit(mut self, limit: usize) -> Self { pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit; self.config.limit = limit;
self self
} }
@ -179,7 +209,7 @@ where
/// exceeds this period, the connection is closed. /// exceeds this period, the connection is closed.
/// Default keep-alive period is 15 seconds. /// Default keep-alive period is 15 seconds.
pub fn conn_keep_alive(mut self, dur: Duration) -> Self { pub fn conn_keep_alive(mut self, dur: Duration) -> Self {
self.conn_keep_alive = dur; self.config.conn_keep_alive = dur;
self self
} }
@ -189,7 +219,7 @@ where
/// until it is closed regardless of keep-alive period. /// until it is closed regardless of keep-alive period.
/// Default lifetime period is 75 seconds. /// Default lifetime period is 75 seconds.
pub fn conn_lifetime(mut self, dur: Duration) -> Self { pub fn conn_lifetime(mut self, dur: Duration) -> Self {
self.conn_lifetime = dur; self.config.conn_lifetime = dur;
self self
} }
@ -202,7 +232,7 @@ where
/// ///
/// By default disconnect timeout is set to 3000 milliseconds. /// By default disconnect timeout is set to 3000 milliseconds.
pub fn disconnect_timeout(mut self, dur: Duration) -> Self { pub fn disconnect_timeout(mut self, dur: Duration) -> Self {
self.disconnect_timeout = dur; self.config.disconnect_timeout = Some(dur);
self self
} }
@ -213,10 +243,10 @@ 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.config.timeout,
apply_fn(self.connector, |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))
}) })
@ -231,51 +261,47 @@ where
connect_impl::InnerConnector { connect_impl::InnerConnector {
tcp_pool: ConnectionPool::new( tcp_pool: ConnectionPool::new(
connector, connector,
self.conn_lifetime, self.config.no_disconnect_timeout(),
self.conn_keep_alive,
None,
self.limit,
), ),
} }
} }
#[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.config.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))
.unwrap_or(false); .unwrap_or(false);
if h2 { if h2 {
(Box::new(sock) as Box<Io>, Protocol::Http2) (Box::new(sock) as Box<dyn Io>, Protocol::Http2)
} else { } else {
(Box::new(sock) as Box<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)
@ -288,9 +314,9 @@ where
.map(|protos| protos.windows(2).any(|w| w == H2)) .map(|protos| protos.windows(2).any(|w| w == H2))
.unwrap_or(false); .unwrap_or(false);
if h2 { if h2 {
(Box::new(sock) as Box<Io>, Protocol::Http2) (Box::new(sock) as Box<dyn Io>, Protocol::Http2)
} else { } else {
(Box::new(sock) as Box<Io>, Protocol::Http1) (Box::new(sock) as Box<dyn Io>, Protocol::Http1)
} }
}), }),
), ),
@ -302,8 +328,8 @@ where
}); });
let tcp_service = TimeoutService::new( let tcp_service = TimeoutService::new(
self.timeout, self.config.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)
@ -317,36 +343,27 @@ where
connect_impl::InnerConnector { connect_impl::InnerConnector {
tcp_pool: ConnectionPool::new( tcp_pool: ConnectionPool::new(
tcp_service, tcp_service,
self.conn_lifetime, self.config.no_disconnect_timeout(),
self.conn_keep_alive,
None,
self.limit,
),
ssl_pool: ConnectionPool::new(
ssl_service,
self.conn_lifetime,
self.conn_keep_alive,
Some(self.disconnect_timeout),
self.limit,
), ),
ssl_pool: ConnectionPool::new(ssl_service, self.config),
} }
} }
} }
} }
#[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 +371,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 +384,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 +393,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 +437,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 +454,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 +502,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 +531,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),
@ -35,7 +39,7 @@ pub enum ConnectError {
H2(h2::Error), H2(h2::Error),
/// Connecting took too long /// Connecting took too long
#[display(fmt = "Timeout out while establishing connection")] #[display(fmt = "Timeout while establishing connection")]
Timeout, Timeout,
/// Connector has been disconnected /// Connector has been disconnected
@ -44,33 +48,31 @@ pub enum ConnectError {
/// Unresolved host name /// Unresolved host name
#[display(fmt = "Connector received `Connect` method with unresolved host")] #[display(fmt = "Connector received `Connect` method with unresolved host")]
Unresolverd, Unresolved,
/// Connection io error /// Connection io error
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
Io(io::Error), Io(io::Error),
} }
impl std::error::Error for ConnectError {}
impl From<actix_connect::ConnectError> for ConnectError { impl From<actix_connect::ConnectError> for ConnectError {
fn from(err: actix_connect::ConnectError) -> ConnectError { fn from(err: actix_connect::ConnectError) -> ConnectError {
match err { match err {
actix_connect::ConnectError::Resolver(e) => ConnectError::Resolver(e), actix_connect::ConnectError::Resolver(e) => ConnectError::Resolver(e),
actix_connect::ConnectError::NoRecords => ConnectError::NoRecords, actix_connect::ConnectError::NoRecords => ConnectError::NoRecords,
actix_connect::ConnectError::InvalidInput => panic!(), actix_connect::ConnectError::InvalidInput => panic!(),
actix_connect::ConnectError::Unresolverd => ConnectError::Unresolverd, actix_connect::ConnectError::Unresolved => ConnectError::Unresolved,
actix_connect::ConnectError::Io(e) => ConnectError::Io(e), actix_connect::ConnectError::Io(e) => ConnectError::Io(e),
} }
} }
} }
#[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(),
}
} }
} }
@ -86,6 +88,8 @@ pub enum InvalidUrl {
HttpError(http::Error), HttpError(http::Error),
} }
impl std::error::Error for InvalidUrl {}
/// A set of errors that can occur during request sending and response reading /// A set of errors that can occur during request sending and response reading
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
pub enum SendRequestError { pub enum SendRequestError {
@ -106,7 +110,7 @@ pub enum SendRequestError {
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
H2(h2::Error), H2(h2::Error),
/// Response took too long /// Response took too long
#[display(fmt = "Timeout out while waiting for response")] #[display(fmt = "Timeout while waiting for response")]
Timeout, Timeout,
/// Tunnels are not supported for http2 connection /// Tunnels are not supported for http2 connection
#[display(fmt = "Tunnels are not supported for http2 connection")] #[display(fmt = "Tunnels are not supported for http2 connection")]
@ -115,16 +119,39 @@ pub enum SendRequestError {
Body(Error), Body(Error),
} }
impl std::error::Error for 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(_) => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
/// A set of errors that can occur during freezing a request
#[derive(Debug, Display, From)]
pub enum FreezeRequestError {
/// Invalid URL
#[display(fmt = "Invalid URL: {}", _0)]
Url(InvalidUrl),
/// Http error
#[display(fmt = "{}", _0)]
Http(HttpError),
}
impl std::error::Error for FreezeRequestError {}
impl From<FreezeRequestError> for SendRequestError {
fn from(e: FreezeRequestError) -> Self {
match e {
FreezeRequestError::Url(e) => e.into(),
FreezeRequestError::Http(e) => e.into(),
} }
SendRequestError::Connect(_) => Response::BadGateway(),
_ => Response::InternalServerError(),
}
.into()
} }
} }

View File

@ -1,15 +1,20 @@
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::{pin_mut, 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::{RequestHead, ResponseHead}; use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::{Payload, PayloadStream}; use crate::payload::{Payload, PayloadStream};
use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::connection::{ConnectionLifetime, ConnectionType, IoConnection};
@ -17,34 +22,40 @@ 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: RequestHead, 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.headers.contains_key(HOST) { if !head.as_ref().headers.contains_key(HOST)
if let Some(host) = head.uri.host() { && !head.extra_headers().iter().any(|h| h.contains_key(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();
let _ = match head.uri.port_u16() { let _ = match head.as_ref().uri.port_u16() {
None | Some(80) | Some(443) => write!(wrt, "{}", host), None | Some(80) | Some(443) => write!(wrt, "{}", host),
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 {
head.headers.insert(HOST, value); RequestHeadType::Owned(ref mut head) => {
head.headers.insert(HOST, value)
} }
Err(e) => { RequestHeadType::Rc(_, ref mut extra_headers) => {
log::error!("Can not set HOST header {}", e); let headers = extra_headers.get_or_insert(HeaderMap::new());
headers.insert(HOST, value)
} }
},
Err(e) => log::error!("Can not set HOST header {}", e),
} }
} }
} }
@ -55,81 +66,118 @@ where
io: Some(io), io: Some(io),
}; };
let len = body.size(); // create Framed and send request
let mut framed_inner = Framed::new(io, h1::ClientCodec::default());
framed_inner.send((head, body.size()).into()).await?;
// create Framed and send reqest
Framed::new(io, h1::ClientCodec::default())
.send((head, len).into())
.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, Pin::new(&mut framed_inner)).await?,
} };
_ => Either::B(SendBody::new(body, framed)),
})
// read response and init read body // read response and init read body
.and_then(|framed| { let res = Pin::new(&mut framed_inner).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.codec_ref().message_type() {
h1::MessageType::None => { h1::MessageType::None => {
let force_close = !framed.get_codec().keepalive(); let force_close = !framed.codec_ref().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_inner).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: RequestHead, 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 reqest // 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<T, B>(
body: B,
mut framed: Pin<&mut Framed<T, h1::ClientCodec>>,
) -> Result<(), SendRequestError>
where
T: ConnectionLifetime + Unpin,
B: MessageBody,
{
pin_mut!(body);
let mut eof = false;
while !eof {
while !eof && !framed.as_ref().is_write_buf_full() {
match poll_fn(|cx| body.as_mut().poll_next(cx)).await {
Some(result) => {
framed.as_mut().write(h1::Message::Chunk(Some(result?)))?;
}
None => {
eof = true;
framed.as_mut().write(h1::Message::Chunk(None))?;
}
}
}
if !framed.as_ref().is_write_buf_empty() {
poll_fn(|cx| match framed.as_mut().flush(cx) {
Poll::Ready(Ok(_)) => Poll::Ready(Ok(())),
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
Poll::Pending => {
if !framed.as_ref().is_write_buf_full() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
}) })
}) .await?;
}
}
SinkExt::flush(Pin::into_inner(framed)).await?;
Ok(())
} }
#[doc(hidden)] #[doc(hidden)]
/// HTTP client connection /// HTTP client connection
pub struct H1Connection<T> { pub struct H1Connection<T> {
/// T should be `Unpin`
io: Option<T>, io: Option<T>,
created: time::Instant, created: time::Instant,
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: Pin<&mut Self>) {
if let Some(mut pool) = self.pool.take() { if let Some(mut pool) = self.pool.take() {
if let Some(io) = self.io.take() { if let Some(io) = self.io.take() {
pool.close(IoConnection::new( pool.close(IoConnection::new(
@ -142,7 +190,7 @@ impl<T: AsyncRead + AsyncWrite + 'static> ConnectionLifetime for H1Connection<T>
} }
/// Release this connection to the connection pool /// Release this connection to the connection pool
fn release(&mut self) { fn release(mut self: Pin<&mut Self>) {
if let Some(mut pool) = self.pool.take() { if let Some(mut pool) = self.pool.take() {
if let Some(io) = self.io.take() { if let Some(io) = self.io.take() {
pool.release(IoConnection::new( pool.release(IoConnection::new(
@ -155,143 +203,96 @@ 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() fn poll_shutdown(
} mut self: Pin<&mut Self>,
} cx: &mut Context<'_>,
) -> Poll<Result<(), io::Error>> {
/// Future responsible for sending request body to the peer Pin::new(self.io.as_mut().unwrap()).poll_shutdown(cx)
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 {
match self.framed.as_mut().unwrap().poll_complete()? {
Async::Ready(_) => {
self.flushed = true;
continue;
}
Async::NotReady => return Ok(Async::NotReady),
}
}
if self.body.is_none() {
return Ok(Async::Ready(self.framed.take().unwrap()));
}
return Ok(Async::NotReady);
}
} }
} }
#[pin_project::pin_project]
pub(crate) struct PlStream<Io> { pub(crate) struct PlStream<Io> {
#[pin]
framed: Option<Framed<Io, h1::ClientPayloadCodec>>, framed: Option<Framed<Io, h1::ClientPayloadCodec>>,
} }
impl<Io: ConnectionLifetime> PlStream<Io> { impl<Io: ConnectionLifetime> PlStream<Io> {
fn new(framed: Framed<Io, h1::ClientCodec>) -> Self { fn new(framed: Framed<Io, h1::ClientCodec>) -> Self {
let framed = framed.into_map_codec(|codec| codec.into_payload_codec());
PlStream { PlStream {
framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), framed: Some(framed),
} }
} }
} }
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 mut this = self.project();
match this.framed.as_mut().as_pin_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.as_mut().as_pin_mut().unwrap();
let force_close = !framed.get_codec().keepalive(); let force_close = !framed.codec_ref().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),
} }
} }
} }
fn release_connection<T, U>(framed: Framed<T, U>, force_close: bool) fn release_connection<T, U>(framed: Pin<&mut Framed<T, U>>, force_close: bool)
where where
T: ConnectionLifetime, T: ConnectionLifetime,
{ {
let mut parts = framed.into_parts(); if !force_close && framed.is_read_buf_empty() && framed.is_write_buf_empty() {
if !force_close && parts.read_buf.is_empty() && parts.write_buf.is_empty() { framed.io_pin().release()
parts.io.release()
} else { } else {
parts.io.close() framed.io_pin().close()
} }
} }

View File

@ -1,46 +1,50 @@
use std::convert::TryFrom;
use std::future::Future;
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 futures_util::pin_mut;
use h2::{client::SendRequest, SendStream}; use h2::{
client::{Builder, Connection, 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::message::{RequestHead, ResponseHead}; use crate::header::HeaderMap;
use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::Payload; use crate::payload::Payload;
use super::config::ConnectorConfig;
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: RequestHead, 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());
let head_req = head.method == Method::HEAD; let head_req = head.as_ref().method == Method::HEAD;
let length = body.size(); let length = body.size();
let eof = match length { let eof = matches!(
BodySize::None | BodySize::Empty | BodySize::Sized(0) => true, length,
_ => false, BodySize::None | BodySize::Empty | BodySize::Sized(0)
}; );
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.uri; *req.uri_mut() = head.as_ref().uri.clone();
*req.method_mut() = head.method; *req.method_mut() = head.as_ref().method.clone();
*req.version_mut() = Version::HTTP_2; *req.version_mut() = Version::HTTP_2;
let mut skip_len = true; let mut skip_len = true;
@ -60,14 +64,27 @@ where
CONTENT_LENGTH, CONTENT_LENGTH,
HeaderValue::try_from(format!("{}", len)).unwrap(), HeaderValue::try_from(format!("{}", len)).unwrap(),
), ),
BodySize::Sized64(len) => req.headers_mut().insert( };
CONTENT_LENGTH,
HeaderValue::try_from(format!("{}", len)).unwrap(), // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate.
let (head, extra_headers) = match head {
RequestHeadType::Owned(head) => (RequestHeadType::Owned(head), 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.
let headers = head
.as_ref()
.headers
.iter()
.filter(|(name, _)| !extra_headers.contains_key(*name))
.chain(extra_headers.iter());
// copy headers // copy headers
for (key, value) in head.headers.iter() { for (key, value) in headers {
match *key { match *key {
CONNECTION | TRANSFER_ENCODING => continue, // http2 specific CONNECTION | TRANSFER_ENCODING => continue, // http2 specific
CONTENT_LENGTH if skip_len => continue, CONTENT_LENGTH if skip_len => continue,
@ -77,30 +94,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() };
@ -108,66 +122,57 @@ 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, body: B,
send: SendStream<Bytes>, mut send: SendStream<Bytes>,
buf: Option<Bytes>, ) -> Result<(), SendRequestError> {
} let mut buf = None;
pin_mut!(body);
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.as_mut().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,
@ -181,3 +186,18 @@ fn release<T: AsyncRead + AsyncWrite + 'static>(
} }
} }
} }
pub(crate) fn handshake<Io>(
io: Io,
config: &ConnectorConfig,
) -> impl Future<Output = Result<(SendRequest<Bytes>, Connection<Io, Bytes>), h2::Error>>
where
Io: AsyncRead + AsyncWrite + Unpin + 'static,
{
let mut builder = Builder::new();
builder
.initial_window_size(config.stream_window_size)
.initial_connection_window_size(config.conn_window_size)
.enable_push(false);
builder.handshake(io)
}

View File

@ -1,6 +1,7 @@
//! Http client api //! Http client api
use http::Uri; use http::Uri;
mod config;
mod connection; mod connection;
mod connector; mod connector;
mod error; mod error;
@ -10,7 +11,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}; 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,25 +1,28 @@
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::{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 pin_project::pin_project;
use slab::Slab; use slab::Slab;
use tokio_timer::{sleep, Delay};
use super::config::ConnectorConfig;
use super::connection::{ConnectionType, IoConnection}; use super::connection::{ConnectionType, IoConnection};
use super::error::ConnectError; use super::error::ConnectError;
use super::h2proto::handshake;
use super::Connect; use super::Connect;
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
@ -41,247 +44,202 @@ 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(connector: T, config: ConnectorConfig) -> Self {
connector: T, let connector_rc = Rc::new(RefCell::new(connector));
conn_lifetime: Duration, let inner_rc = Rc::new(RefCell::new(Inner {
conn_keep_alive: Duration, config,
disconnect_timeout: Option<Duration>,
limit: usize,
) -> Self {
ConnectionPool(
connector,
Rc::new(RefCell::new(Inner {
conn_lifetime,
conn_keep_alive,
disconnect_timeout,
limit,
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(),
})), }));
)
// start support future
actix_rt::spawn(ConnectorPoolSupport {
connector: Rc::clone(&connector_rc),
inner: Rc::clone(&inner_rc),
});
ConnectionPool(connector_rc, inner_rc)
} }
} }
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())
} }
} }
impl<T, Io> Drop for ConnectionPool<T, Io> {
fn drop(&mut self) {
// wake up the ConnectorPoolSupport when dropping so it can exit properly.
self.1.borrow().waker.wake();
}
}
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() { 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::Unresolved);
}; };
// 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( 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 config = inner.borrow().config.clone();
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, &config).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() {
let mut inner = i.as_ref().borrow_mut(); let mut inner = i.as_ref().borrow_mut();
inner.release_waiter(&self.key, self.token); inner.release_waiter(&self.key, self.token);
inner.check_availibility(); inner.check_availability();
} }
} }
} }
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,
}
} }
} }
impl<F, Io> Drop for OpenConnection<F, Io> fn consume(mut self) -> Acquired<Io> {
Acquired(self.key.clone(), self.inner.take())
}
}
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_availability();
}
}
}
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),
} }
} }
} }
@ -299,12 +257,9 @@ struct AvailableConnection<Io> {
} }
pub(crate) struct Inner<Io> { pub(crate) struct Inner<Io> {
conn_lifetime: Duration, config: ConnectorConfig,
conn_keep_alive: Duration,
disconnect_timeout: Option<Duration>,
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 +267,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 +281,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,22 +296,21 @@ 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.config.limit > 0 && self.acquired >= self.config.limit {
return Acquire::NotAvailable; return Acquire::NotAvailable;
} }
@ -368,33 +322,31 @@ where
let now = Instant::now(); let now = Instant::now();
while let Some(conn) = connections.pop_back() { while let Some(conn) = connections.pop_back() {
// check if it still usable // check if it still usable
if (now - conn.used) > self.conn_keep_alive if (now - conn.used) > self.config.conn_keep_alive
|| (now - conn.created) > self.conn_lifetime || (now - conn.created) > self.config.conn_lifetime
{ {
if let Some(timeout) = self.disconnect_timeout { if let Some(timeout) = self.config.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.config.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);
@ -414,24 +366,22 @@ where
created, created,
used: Instant::now(), used: Instant::now(),
}); });
self.check_availibility(); self.check_availability();
} }
fn release_close(&mut self, io: ConnectionType<Io>) { fn release_close(&mut self, io: ConnectionType<Io>) {
self.acquired -= 1; self.acquired -= 1;
if let Some(timeout) = self.disconnect_timeout { if let Some(timeout) = self.config.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_availability();
} }
fn check_availibility(&self) { fn check_availability(&self) {
if !self.waiters_queue.is_empty() && self.acquired < self.limit { if !self.waiters_queue.is_empty() && self.acquired < self.config.limit {
if let Some(t) = self.task.as_ref() { self.waker.wake();
t.notify()
}
} }
} }
} }
@ -443,37 +393,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 +433,23 @@ 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();
if Rc::strong_count(this.inner) == 1 {
// If we are last copy of Inner<Io> it means the ConnectionPool is already gone
// and we are safe to exit.
return Poll::Ready(());
}
let mut inner = this.inner.borrow_mut();
inner.waker.register(cx.waker());
// check waiters // check waiters
loop { loop {
@ -505,14 +464,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,118 +483,131 @@ 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),
inner.config.clone(),
); );
} }
} }
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>>>>,
config: ConnectorConfig,
} }
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,
rx: oneshot::Sender<Result<IoConnection<Io>, ConnectError>>, rx: oneshot::Sender<Result<IoConnection<Io>, ConnectError>>,
inner: Rc<RefCell<Inner<Io>>>, inner: Rc<RefCell<Inner<Io>>>,
fut: F, fut: F,
config: ConnectorConfig,
) { ) {
tokio_current_thread::spawn(OpenWaitingConnection { actix_rt::spawn(OpenWaitingConnection {
key, key,
fut, fut,
h2: None, h2: None,
rx: Some(rx), rx: Some(rx),
inner: Some(inner), inner: Some(inner),
config,
}) })
} }
} }
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_availability();
} }
} }
} }
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, this.config).boxed_local());
self.poll() self.poll(cx)
} }
} }
Ok(Async::NotReady) => Ok(Async::NotReady), Poll::Pending => Poll::Pending,
} }
} }
} }
@ -644,7 +616,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;
@ -17,7 +17,7 @@ const DATE_VALUE_LENGTH: usize = 29;
pub enum KeepAlive { pub enum KeepAlive {
/// Keep alive in seconds /// Keep alive in seconds
Timeout(usize), Timeout(usize),
/// Relay on OS to shutdown tcp connection /// Rely on OS to shutdown tcp connection
Os, Os,
/// Disabled /// Disabled
Disabled, Disabled,
@ -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> {
@ -96,7 +114,7 @@ impl ServiceConfig {
} }
#[inline] #[inline]
/// Return state of connection keep-alive funcitonality /// Return state of connection keep-alive functionality
pub fn keep_alive_enabled(&self) -> bool { pub fn keep_alive_enabled(&self) -> bool {
self.0.ka_enabled self.0.ka_enabled
} }
@ -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
} }
@ -191,9 +209,15 @@ impl Date {
date.update(); date.update();
date 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_utc().format("%a, %d %b %Y %H:%M:%S GMT")
)
.unwrap();
} }
} }
@ -210,24 +234,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 +261,55 @@ 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,260 +0,0 @@
use std::borrow::Cow;
use chrono::Duration;
use time::Tm;
use super::{Cookie, SameSite};
/// Structure that follows the builder pattern for building `Cookie` structs.
///
/// To construct a cookie:
///
/// 1. Call [`Cookie::build`](struct.Cookie.html#method.build) to start building.
/// 2. Use any of the builder methods to set fields in the cookie.
/// 3. Call [finish](#method.finish) to retrieve the built cookie.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// # fn main() {
/// let cookie: Cookie = Cookie::build("name", "value")
/// .domain("www.rust-lang.org")
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .max_age(84600)
/// .finish();
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct CookieBuilder {
/// The cookie being built.
cookie: Cookie<'static>,
}
impl CookieBuilder {
/// Creates a new `CookieBuilder` instance from the given name and value.
///
/// This method is typically called indirectly via
/// [Cookie::build](struct.Cookie.html#method.build).
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar").finish();
/// assert_eq!(c.name_value(), ("foo", "bar"));
/// ```
pub fn new<N, V>(name: N, value: V) -> CookieBuilder
where
N: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>,
{
CookieBuilder {
cookie: Cookie::new(name, value),
}
}
/// Sets the `expires` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// # fn main() {
/// let c = Cookie::build("foo", "bar")
/// .expires(time::now())
/// .finish();
///
/// assert!(c.expires().is_some());
/// # }
/// ```
#[inline]
pub fn expires(mut self, when: Tm) -> CookieBuilder {
self.cookie.set_expires(when);
self
}
/// Sets the `max_age` field in seconds in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// # fn main() {
/// let c = Cookie::build("foo", "bar")
/// .max_age(1800)
/// .finish();
///
/// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60)));
/// # }
/// ```
#[inline]
pub fn max_age(self, seconds: i64) -> CookieBuilder {
self.max_age_time(Duration::seconds(seconds))
}
/// Sets the `max_age` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// # fn main() {
/// let c = Cookie::build("foo", "bar")
/// .max_age_time(time::Duration::minutes(30))
/// .finish();
///
/// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60)));
/// # }
/// ```
#[inline]
pub fn max_age_time(mut self, value: Duration) -> CookieBuilder {
self.cookie.set_max_age(value);
self
}
/// Sets the `domain` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .domain("www.rust-lang.org")
/// .finish();
///
/// assert_eq!(c.domain(), Some("www.rust-lang.org"));
/// ```
pub fn domain<D: Into<Cow<'static, str>>>(mut self, value: D) -> CookieBuilder {
self.cookie.set_domain(value);
self
}
/// Sets the `path` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .path("/")
/// .finish();
///
/// assert_eq!(c.path(), Some("/"));
/// ```
pub fn path<P: Into<Cow<'static, str>>>(mut self, path: P) -> CookieBuilder {
self.cookie.set_path(path);
self
}
/// Sets the `secure` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .secure(true)
/// .finish();
///
/// assert_eq!(c.secure(), Some(true));
/// ```
#[inline]
pub fn secure(mut self, value: bool) -> CookieBuilder {
self.cookie.set_secure(value);
self
}
/// Sets the `http_only` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .http_only(true)
/// .finish();
///
/// assert_eq!(c.http_only(), Some(true));
/// ```
#[inline]
pub fn http_only(mut self, value: bool) -> CookieBuilder {
self.cookie.set_http_only(value);
self
}
/// Sets the `same_site` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{Cookie, SameSite};
///
/// let c = Cookie::build("foo", "bar")
/// .same_site(SameSite::Strict)
/// .finish();
///
/// assert_eq!(c.same_site(), Some(SameSite::Strict));
/// ```
#[inline]
pub fn same_site(mut self, value: SameSite) -> CookieBuilder {
self.cookie.set_same_site(value);
self
}
/// Makes the cookie being built 'permanent' by extending its expiration and
/// max age 20 years into the future.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
/// use chrono::Duration;
///
/// # fn main() {
/// let c = Cookie::build("foo", "bar")
/// .permanent()
/// .finish();
///
/// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
/// # assert!(c.expires().is_some());
/// # }
/// ```
#[inline]
pub fn permanent(mut self) -> CookieBuilder {
self.cookie.make_permanent();
self
}
/// Finishes building and returns the built `Cookie`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .domain("crates.io")
/// .path("/")
/// .finish();
///
/// assert_eq!(c.name_value(), ("foo", "bar"));
/// assert_eq!(c.domain(), Some("crates.io"));
/// assert_eq!(c.path(), Some("/"));
/// ```
#[inline]
pub fn finish(self) -> Cookie<'static> {
self.cookie
}
}

View File

@ -1,71 +0,0 @@
use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use super::Cookie;
/// A `DeltaCookie` is a helper structure used in a cookie jar. It wraps a
/// `Cookie` so that it can be hashed and compared purely by name. It further
/// records whether the wrapped cookie is a "removal" cookie, that is, a cookie
/// that when sent to the client removes the named cookie on the client's
/// machine.
#[derive(Clone, Debug)]
pub struct DeltaCookie {
pub cookie: Cookie<'static>,
pub removed: bool,
}
impl DeltaCookie {
/// Create a new `DeltaCookie` that is being added to a jar.
#[inline]
pub fn added(cookie: Cookie<'static>) -> DeltaCookie {
DeltaCookie {
cookie,
removed: false,
}
}
/// Create a new `DeltaCookie` that is being removed from a jar. The
/// `cookie` should be a "removal" cookie.
#[inline]
pub fn removed(cookie: Cookie<'static>) -> DeltaCookie {
DeltaCookie {
cookie,
removed: true,
}
}
}
impl Deref for DeltaCookie {
type Target = Cookie<'static>;
fn deref(&self) -> &Cookie<'static> {
&self.cookie
}
}
impl DerefMut for DeltaCookie {
fn deref_mut(&mut self) -> &mut Cookie<'static> {
&mut self.cookie
}
}
impl PartialEq for DeltaCookie {
fn eq(&self, other: &DeltaCookie) -> bool {
self.name() == other.name()
}
}
impl Eq for DeltaCookie {}
impl Hash for DeltaCookie {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name().hash(state);
}
}
impl Borrow<str> for DeltaCookie {
fn borrow(&self) -> &str {
self.name()
}
}

View File

@ -1,98 +0,0 @@
//! This module contains types that represent cookie properties that are not yet
//! standardized. That is, _draft_ features.
use std::fmt;
/// The `SameSite` cookie attribute.
///
/// A cookie with a `SameSite` attribute is imposed restrictions on when it is
/// sent to the origin server in a cross-site request. If the `SameSite`
/// 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
/// requests with "safe" HTTP methods, i.e, `GET`, `HEAD`, `OPTIONS`, `TRACE`.
/// If the `SameSite` attribute is not present (made explicit via the
/// `SameSite::None` variant), then the cookie will be sent as normal.
///
/// **Note:** This cookie attribute is an HTTP draft! Its meaning and definition
/// are subject to change.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SameSite {
/// The "Strict" `SameSite` attribute.
Strict,
/// The "Lax" `SameSite` attribute.
Lax,
/// No `SameSite` attribute.
None,
}
impl SameSite {
/// Returns `true` if `self` is `SameSite::Strict` and `false` otherwise.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::SameSite;
///
/// let strict = SameSite::Strict;
/// assert!(strict.is_strict());
/// assert!(!strict.is_lax());
/// assert!(!strict.is_none());
/// ```
#[inline]
pub fn is_strict(self) -> bool {
match self {
SameSite::Strict => true,
SameSite::Lax | SameSite::None => false,
}
}
/// Returns `true` if `self` is `SameSite::Lax` and `false` otherwise.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::SameSite;
///
/// let lax = SameSite::Lax;
/// assert!(lax.is_lax());
/// assert!(!lax.is_strict());
/// assert!(!lax.is_none());
/// ```
#[inline]
pub fn is_lax(self) -> bool {
match self {
SameSite::Lax => true,
SameSite::Strict | SameSite::None => false,
}
}
/// Returns `true` if `self` is `SameSite::None` and `false` otherwise.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::SameSite;
///
/// let none = SameSite::None;
/// assert!(none.is_none());
/// assert!(!none.is_lax());
/// assert!(!none.is_strict());
/// ```
#[inline]
pub fn is_none(self) -> bool {
match self {
SameSite::None => true,
SameSite::Lax | SameSite::Strict => false,
}
}
}
impl fmt::Display for SameSite {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
SameSite::Strict => write!(f, "Strict"),
SameSite::Lax => write!(f, "Lax"),
SameSite::None => Ok(()),
}
}
}

View File

@ -1,655 +0,0 @@
use std::collections::HashSet;
use std::mem::replace;
use chrono::Duration;
use super::delta::DeltaCookie;
use super::Cookie;
#[cfg(feature = "secure-cookies")]
use super::secure::{Key, PrivateJar, SignedJar};
/// A collection of cookies that tracks its modifications.
///
/// A `CookieJar` provides storage for any number of cookies. Any changes made
/// to the jar are tracked; the changes can be retrieved via the
/// [delta](#method.delta) method which returns an interator over the changes.
///
/// # Usage
///
/// A jar's life begins via [new](#method.new) and calls to
/// [`add_original`](#method.add_original):
///
/// ```rust
/// use actix_http::cookie::{Cookie, CookieJar};
///
/// let mut jar = CookieJar::new();
/// jar.add_original(Cookie::new("name", "value"));
/// jar.add_original(Cookie::new("second", "another"));
/// ```
///
/// Cookies can be added via [add](#method.add) and removed via
/// [remove](#method.remove). Finally, cookies can be looked up via
/// [get](#method.get):
///
/// ```rust
/// # use actix_http::cookie::{Cookie, CookieJar};
/// let mut jar = CookieJar::new();
/// jar.add(Cookie::new("a", "one"));
/// jar.add(Cookie::new("b", "two"));
///
/// assert_eq!(jar.get("a").map(|c| c.value()), Some("one"));
/// assert_eq!(jar.get("b").map(|c| c.value()), Some("two"));
///
/// jar.remove(Cookie::named("b"));
/// assert!(jar.get("b").is_none());
/// ```
///
/// # Deltas
///
/// A jar keeps track of any modifications made to it over time. The
/// modifications are recorded as cookies. The modifications can be retrieved
/// via [delta](#method.delta). Any new `Cookie` added to a jar via `add`
/// results in the same `Cookie` appearing in the `delta`; cookies added via
/// `add_original` do not count towards the delta. Any _original_ cookie that is
/// removed from a jar results in a "removal" cookie appearing in the delta. A
/// "removal" cookie is a cookie that a server sends so that the cookie is
/// removed from the client's machine.
///
/// Deltas are typically used to create `Set-Cookie` headers corresponding to
/// the changes made to a cookie jar over a period of time.
///
/// ```rust
/// # use actix_http::cookie::{Cookie, CookieJar};
/// let mut jar = CookieJar::new();
///
/// // original cookies don't affect the delta
/// jar.add_original(Cookie::new("original", "value"));
/// assert_eq!(jar.delta().count(), 0);
///
/// // new cookies result in an equivalent `Cookie` in the delta
/// jar.add(Cookie::new("a", "one"));
/// jar.add(Cookie::new("b", "two"));
/// assert_eq!(jar.delta().count(), 2);
///
/// // removing an original cookie adds a "removal" cookie to the delta
/// jar.remove(Cookie::named("original"));
/// assert_eq!(jar.delta().count(), 3);
///
/// // removing a new cookie that was added removes that `Cookie` from the delta
/// jar.remove(Cookie::named("a"));
/// assert_eq!(jar.delta().count(), 2);
/// ```
#[derive(Default, Debug, Clone)]
pub struct CookieJar {
original_cookies: HashSet<DeltaCookie>,
delta_cookies: HashSet<DeltaCookie>,
}
impl CookieJar {
/// Creates an empty cookie jar.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::CookieJar;
///
/// let jar = CookieJar::new();
/// assert_eq!(jar.iter().count(), 0);
/// ```
pub fn new() -> CookieJar {
CookieJar::default()
}
/// Returns a reference to the `Cookie` inside this jar with the name
/// `name`. If no such cookie exists, returns `None`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
/// assert!(jar.get("name").is_none());
///
/// jar.add(Cookie::new("name", "value"));
/// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
/// ```
pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
self.delta_cookies
.get(name)
.or_else(|| self.original_cookies.get(name))
.and_then(|c| if !c.removed { Some(&c.cookie) } else { None })
}
/// Adds an "original" `cookie` to this jar. If an original cookie with the
/// same name already exists, it is replaced with `cookie`. Cookies added
/// with `add` take precedence and are not replaced by this method.
///
/// Adding an original cookie does not affect the [delta](#method.delta)
/// computation. This method is intended to be used to seed the cookie jar
/// with cookies received from a client's HTTP message.
///
/// For accurate `delta` computations, this method should not be called
/// after calling `remove`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
/// jar.add_original(Cookie::new("name", "value"));
/// jar.add_original(Cookie::new("second", "two"));
///
/// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
/// assert_eq!(jar.get("second").map(|c| c.value()), Some("two"));
/// assert_eq!(jar.iter().count(), 2);
/// assert_eq!(jar.delta().count(), 0);
/// ```
pub fn add_original(&mut self, cookie: Cookie<'static>) {
self.original_cookies.replace(DeltaCookie::added(cookie));
}
/// Adds `cookie` to this jar. If a cookie with the same name already
/// exists, it is replaced with `cookie`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
/// jar.add(Cookie::new("name", "value"));
/// jar.add(Cookie::new("second", "two"));
///
/// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
/// assert_eq!(jar.get("second").map(|c| c.value()), Some("two"));
/// assert_eq!(jar.iter().count(), 2);
/// assert_eq!(jar.delta().count(), 2);
/// ```
pub fn add(&mut self, cookie: Cookie<'static>) {
self.delta_cookies.replace(DeltaCookie::added(cookie));
}
/// Removes `cookie` from this jar. If an _original_ cookie with the same
/// name as `cookie` is present in the jar, a _removal_ cookie will be
/// present in the `delta` computation. To properly generate the removal
/// cookie, `cookie` must contain the same `path` and `domain` as the cookie
/// that was initially set.
///
/// A "removal" cookie is a cookie that has the same name as the original
/// cookie but has an empty value, a max-age of 0, and an expiration date
/// far in the past.
///
/// # Example
///
/// Removing an _original_ cookie results in a _removal_ cookie:
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
/// use chrono::Duration;
///
/// # fn main() {
/// let mut jar = CookieJar::new();
///
/// // Assume this cookie originally had a path of "/" and domain of "a.b".
/// jar.add_original(Cookie::new("name", "value"));
///
/// // If the path and domain were set, they must be provided to `remove`.
/// jar.remove(Cookie::build("name", "").path("/").domain("a.b").finish());
///
/// // The delta will contain the removal cookie.
/// let delta: Vec<_> = jar.delta().collect();
/// assert_eq!(delta.len(), 1);
/// assert_eq!(delta[0].name(), "name");
/// assert_eq!(delta[0].max_age(), Some(Duration::seconds(0)));
/// # }
/// ```
///
/// Removing a new cookie does not result in a _removal_ cookie:
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
/// jar.add(Cookie::new("name", "value"));
/// assert_eq!(jar.delta().count(), 1);
///
/// jar.remove(Cookie::named("name"));
/// assert_eq!(jar.delta().count(), 0);
/// ```
pub fn remove(&mut self, mut cookie: Cookie<'static>) {
if self.original_cookies.contains(cookie.name()) {
cookie.set_value("");
cookie.set_max_age(Duration::seconds(0));
cookie.set_expires(time::now() - Duration::days(365));
self.delta_cookies.replace(DeltaCookie::removed(cookie));
} else {
self.delta_cookies.remove(cookie.name());
}
}
/// Removes `cookie` from this jar completely. This method differs from
/// `remove` in that no delta cookie is created under any condition. Neither
/// the `delta` nor `iter` methods will return a cookie that is removed
/// using this method.
///
/// # Example
///
/// Removing an _original_ cookie; no _removal_ cookie is generated:
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
/// use chrono::Duration;
///
/// # fn main() {
/// let mut jar = CookieJar::new();
///
/// // Add an original cookie and a new cookie.
/// jar.add_original(Cookie::new("name", "value"));
/// jar.add(Cookie::new("key", "value"));
/// assert_eq!(jar.delta().count(), 1);
/// assert_eq!(jar.iter().count(), 2);
///
/// // Now force remove the original cookie.
/// jar.force_remove(Cookie::new("name", "value"));
/// assert_eq!(jar.delta().count(), 1);
/// assert_eq!(jar.iter().count(), 1);
///
/// // Now force remove the new cookie.
/// jar.force_remove(Cookie::new("key", "value"));
/// assert_eq!(jar.delta().count(), 0);
/// assert_eq!(jar.iter().count(), 0);
/// # }
/// ```
pub fn force_remove<'a>(&mut self, cookie: Cookie<'a>) {
self.original_cookies.remove(cookie.name());
self.delta_cookies.remove(cookie.name());
}
/// Removes all cookies from this cookie jar.
#[deprecated(
since = "0.7.0",
note = "calling this method may not remove \
all cookies since the path and domain are not specified; use \
`remove` instead"
)]
pub fn clear(&mut self) {
self.delta_cookies.clear();
for delta in replace(&mut self.original_cookies, HashSet::new()) {
self.remove(delta.cookie);
}
}
/// Returns an iterator over cookies that represent the changes to this jar
/// over time. These cookies can be rendered directly as `Set-Cookie` header
/// values to affect the changes made to this jar on the client.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
/// jar.add_original(Cookie::new("name", "value"));
/// jar.add_original(Cookie::new("second", "two"));
///
/// // Add new cookies.
/// jar.add(Cookie::new("new", "third"));
/// jar.add(Cookie::new("another", "fourth"));
/// jar.add(Cookie::new("yac", "fifth"));
///
/// // Remove some cookies.
/// jar.remove(Cookie::named("name"));
/// jar.remove(Cookie::named("another"));
///
/// // Delta contains two new cookies ("new", "yac") and a removal ("name").
/// assert_eq!(jar.delta().count(), 3);
/// ```
pub fn delta(&self) -> Delta {
Delta {
iter: self.delta_cookies.iter(),
}
}
/// Returns an iterator over all of the cookies present in this jar.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
///
/// jar.add_original(Cookie::new("name", "value"));
/// jar.add_original(Cookie::new("second", "two"));
///
/// jar.add(Cookie::new("new", "third"));
/// jar.add(Cookie::new("another", "fourth"));
/// jar.add(Cookie::new("yac", "fifth"));
///
/// jar.remove(Cookie::named("name"));
/// jar.remove(Cookie::named("another"));
///
/// // There are three cookies in the jar: "second", "new", and "yac".
/// # assert_eq!(jar.iter().count(), 3);
/// for cookie in jar.iter() {
/// match cookie.name() {
/// "second" => assert_eq!(cookie.value(), "two"),
/// "new" => assert_eq!(cookie.value(), "third"),
/// "yac" => assert_eq!(cookie.value(), "fifth"),
/// _ => unreachable!("there are only three cookies in the jar")
/// }
/// }
/// ```
pub fn iter(&self) -> Iter {
Iter {
delta_cookies: self
.delta_cookies
.iter()
.chain(self.original_cookies.difference(&self.delta_cookies)),
}
}
/// Returns a `PrivateJar` with `self` as its parent jar using the key `key`
/// to sign/encrypt and verify/decrypt cookies added/retrieved from the
/// child jar.
///
/// Any modifications to the child jar will be reflected on the parent jar,
/// and any retrievals from the child jar will be made from the parent jar.
///
/// This method is only available when the `secure` feature is enabled.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{Cookie, CookieJar, Key};
///
/// // Generate a secure key.
/// let key = Key::generate();
///
/// // Add a private (signed + encrypted) cookie.
/// let mut jar = CookieJar::new();
/// jar.private(&key).add(Cookie::new("private", "text"));
///
/// // The cookie's contents are encrypted.
/// assert_ne!(jar.get("private").unwrap().value(), "text");
///
/// // They can be decrypted and verified through the child jar.
/// assert_eq!(jar.private(&key).get("private").unwrap().value(), "text");
///
/// // A tampered with cookie does not validate but still exists.
/// let mut cookie = jar.get("private").unwrap().clone();
/// jar.add(Cookie::new("private", cookie.value().to_string() + "!"));
/// assert!(jar.private(&key).get("private").is_none());
/// assert!(jar.get("private").is_some());
/// ```
#[cfg(feature = "secure-cookies")]
pub fn private(&mut self, key: &Key) -> PrivateJar {
PrivateJar::new(self, key)
}
/// Returns a `SignedJar` with `self` as its parent jar using the key `key`
/// to sign/verify cookies added/retrieved from the child jar.
///
/// Any modifications to the child jar will be reflected on the parent jar,
/// and any retrievals from the child jar will be made from the parent jar.
///
/// This method is only available when the `secure` feature is enabled.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{Cookie, CookieJar, Key};
///
/// // Generate a secure key.
/// let key = Key::generate();
///
/// // Add a signed cookie.
/// let mut jar = CookieJar::new();
/// jar.signed(&key).add(Cookie::new("signed", "text"));
///
/// // The cookie's contents are signed but still in plaintext.
/// assert_ne!(jar.get("signed").unwrap().value(), "text");
/// assert!(jar.get("signed").unwrap().value().contains("text"));
///
/// // They can be verified through the child jar.
/// assert_eq!(jar.signed(&key).get("signed").unwrap().value(), "text");
///
/// // A tampered with cookie does not validate but still exists.
/// let mut cookie = jar.get("signed").unwrap().clone();
/// jar.add(Cookie::new("signed", cookie.value().to_string() + "!"));
/// assert!(jar.signed(&key).get("signed").is_none());
/// assert!(jar.get("signed").is_some());
/// ```
#[cfg(feature = "secure-cookies")]
pub fn signed(&mut self, key: &Key) -> SignedJar {
SignedJar::new(self, key)
}
}
use std::collections::hash_set::Iter as HashSetIter;
/// Iterator over the changes to a cookie jar.
pub struct Delta<'a> {
iter: HashSetIter<'a, DeltaCookie>,
}
impl<'a> Iterator for Delta<'a> {
type Item = &'a Cookie<'static>;
fn next(&mut self) -> Option<&'a Cookie<'static>> {
self.iter.next().map(|c| &c.cookie)
}
}
use std::collections::hash_map::RandomState;
use std::collections::hash_set::Difference;
use std::iter::Chain;
/// Iterator over all of the cookies in a jar.
pub struct Iter<'a> {
delta_cookies:
Chain<HashSetIter<'a, DeltaCookie>, Difference<'a, DeltaCookie, RandomState>>,
}
impl<'a> Iterator for Iter<'a> {
type Item = &'a Cookie<'static>;
fn next(&mut self) -> Option<&'a Cookie<'static>> {
for cookie in self.delta_cookies.by_ref() {
if !cookie.removed {
return Some(&*cookie);
}
}
None
}
}
#[cfg(test)]
mod test {
#[cfg(feature = "secure-cookies")]
use super::Key;
use super::{Cookie, CookieJar};
#[test]
#[allow(deprecated)]
fn simple() {
let mut c = CookieJar::new();
c.add(Cookie::new("test", ""));
c.add(Cookie::new("test2", ""));
c.remove(Cookie::named("test"));
assert!(c.get("test").is_none());
assert!(c.get("test2").is_some());
c.add(Cookie::new("test3", ""));
c.clear();
assert!(c.get("test").is_none());
assert!(c.get("test2").is_none());
assert!(c.get("test3").is_none());
}
#[test]
fn jar_is_send() {
fn is_send<T: Send>(_: T) -> bool {
true
}
assert!(is_send(CookieJar::new()))
}
#[test]
#[cfg(feature = "secure-cookies")]
fn iter() {
let key = Key::generate();
let mut c = CookieJar::new();
c.add_original(Cookie::new("original", "original"));
c.add(Cookie::new("test", "test"));
c.add(Cookie::new("test2", "test2"));
c.add(Cookie::new("test3", "test3"));
assert_eq!(c.iter().count(), 4);
c.signed(&key).add(Cookie::new("signed", "signed"));
c.private(&key).add(Cookie::new("encrypted", "encrypted"));
assert_eq!(c.iter().count(), 6);
c.remove(Cookie::named("test"));
assert_eq!(c.iter().count(), 5);
c.remove(Cookie::named("signed"));
c.remove(Cookie::named("test2"));
assert_eq!(c.iter().count(), 3);
c.add(Cookie::new("test2", "test2"));
assert_eq!(c.iter().count(), 4);
c.remove(Cookie::named("test2"));
assert_eq!(c.iter().count(), 3);
}
#[test]
#[cfg(feature = "secure-cookies")]
fn delta() {
use chrono::Duration;
use std::collections::HashMap;
let mut c = CookieJar::new();
c.add_original(Cookie::new("original", "original"));
c.add_original(Cookie::new("original1", "original1"));
c.add(Cookie::new("test", "test"));
c.add(Cookie::new("test2", "test2"));
c.add(Cookie::new("test3", "test3"));
c.add(Cookie::new("test4", "test4"));
c.remove(Cookie::named("test"));
c.remove(Cookie::named("original"));
assert_eq!(c.delta().count(), 4);
let names: HashMap<_, _> = c.delta().map(|c| (c.name(), c.max_age())).collect();
assert!(names.get("test2").unwrap().is_none());
assert!(names.get("test3").unwrap().is_none());
assert!(names.get("test4").unwrap().is_none());
assert_eq!(names.get("original").unwrap(), &Some(Duration::seconds(0)));
}
#[test]
fn replace_original() {
let mut jar = CookieJar::new();
jar.add_original(Cookie::new("original_a", "a"));
jar.add_original(Cookie::new("original_b", "b"));
assert_eq!(jar.get("original_a").unwrap().value(), "a");
jar.add(Cookie::new("original_a", "av2"));
assert_eq!(jar.get("original_a").unwrap().value(), "av2");
}
#[test]
fn empty_delta() {
let mut jar = CookieJar::new();
jar.add(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 1);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().count(), 0);
jar.add_original(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 0);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().count(), 1);
jar.add(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 1);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().count(), 1);
}
#[test]
fn add_remove_add() {
let mut jar = CookieJar::new();
jar.add_original(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 0);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().count(), 1);
// The cookie's been deleted. Another original doesn't change that.
jar.add_original(Cookie::new("name", "val"));
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().count(), 1);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().count(), 1);
jar.add(Cookie::new("name", "val"));
assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().count(), 1);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().count(), 1);
}
#[test]
fn replace_remove() {
let mut jar = CookieJar::new();
jar.add_original(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 0);
jar.add(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 1);
assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
}
#[test]
fn remove_with_path() {
let mut jar = CookieJar::new();
jar.add_original(Cookie::build("name", "val").finish());
assert_eq!(jar.iter().count(), 1);
assert_eq!(jar.delta().count(), 0);
assert_eq!(jar.iter().filter(|c| c.path().is_none()).count(), 1);
jar.remove(Cookie::build("name", "").path("/").finish());
assert_eq!(jar.iter().count(), 0);
assert_eq!(jar.delta().count(), 1);
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().filter(|c| c.path() == Some("/")).count(), 1);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,425 +0,0 @@
use std::borrow::Cow;
use std::cmp;
use std::convert::From;
use std::error::Error;
use std::fmt;
use std::str::Utf8Error;
use chrono::Duration;
use percent_encoding::percent_decode;
use super::{Cookie, CookieStr, SameSite};
/// Enum corresponding to a parsing error.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ParseError {
/// The cookie did not contain a name/value pair.
MissingPair,
/// The cookie's name was empty.
EmptyName,
/// Decoding the cookie's name or value resulted in invalid UTF-8.
Utf8Error(Utf8Error),
/// It is discouraged to exhaustively match on this enum as its variants may
/// grow without a breaking-change bump in version numbers.
#[doc(hidden)]
__Nonexhasutive,
}
impl ParseError {
/// Returns a description of this error as a string
pub fn as_str(&self) -> &'static str {
match *self {
ParseError::MissingPair => "the cookie is missing a name/value pair",
ParseError::EmptyName => "the cookie's name is empty",
ParseError::Utf8Error(_) => {
"decoding the cookie's name or value resulted in invalid UTF-8"
}
ParseError::__Nonexhasutive => unreachable!("__Nonexhasutive ParseError"),
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<Utf8Error> for ParseError {
fn from(error: Utf8Error) -> ParseError {
ParseError::Utf8Error(error)
}
}
impl Error for ParseError {
fn description(&self) -> &str {
self.as_str()
}
}
fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> {
let haystack_start = haystack.as_ptr() as usize;
let needle_start = needle.as_ptr() as usize;
if needle_start < haystack_start {
return None;
}
if (needle_start + needle.len()) > (haystack_start + haystack.len()) {
return None;
}
let start = needle_start - haystack_start;
let end = start + needle.len();
Some((start, end))
}
fn name_val_decoded(
name: &str,
val: &str,
) -> Result<(CookieStr, CookieStr), ParseError> {
let decoded_name = percent_decode(name.as_bytes()).decode_utf8()?;
let decoded_value = percent_decode(val.as_bytes()).decode_utf8()?;
let name = CookieStr::Concrete(Cow::Owned(decoded_name.into_owned()));
let val = CookieStr::Concrete(Cow::Owned(decoded_value.into_owned()));
Ok((name, val))
}
// This function does the real parsing but _does not_ set the `cookie_string` in
// the returned cookie object. This only exists so that the borrow to `s` is
// returned at the end of the call, allowing the `cookie_string` field to be
// set in the outer `parse` function.
fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
let mut attributes = s.split(';');
let key_value = match attributes.next() {
Some(s) => s,
_ => panic!(),
};
// Determine the name = val.
let (name, value) = match key_value.find('=') {
Some(i) => (key_value[..i].trim(), key_value[(i + 1)..].trim()),
None => return Err(ParseError::MissingPair),
};
if name.is_empty() {
return Err(ParseError::EmptyName);
}
// Create a cookie with all of the defaults. We'll fill things in while we
// iterate through the parameters below.
let (name, value) = if decode {
name_val_decoded(name, value)?
} else {
let name_indexes = indexes_of(name, s).expect("name sub");
let value_indexes = indexes_of(value, s).expect("value sub");
let name = CookieStr::Indexed(name_indexes.0, name_indexes.1);
let value = CookieStr::Indexed(value_indexes.0, value_indexes.1);
(name, value)
};
let mut cookie = Cookie {
name,
value,
cookie_string: None,
expires: None,
max_age: None,
domain: None,
path: None,
secure: None,
http_only: None,
same_site: None,
};
for attr in attributes {
let (key, value) = match attr.find('=') {
Some(i) => (attr[..i].trim(), Some(attr[(i + 1)..].trim())),
None => (attr.trim(), None),
};
match (&*key.to_ascii_lowercase(), value) {
("secure", _) => cookie.secure = Some(true),
("httponly", _) => cookie.http_only = Some(true),
("max-age", Some(v)) => {
// See RFC 6265 Section 5.2.2, negative values indicate that the
// earliest possible expiration time should be used, so set the
// max age as 0 seconds.
cookie.max_age = match v.parse() {
Ok(val) if val <= 0 => Some(Duration::zero()),
Ok(val) => {
// Don't panic if the max age seconds is greater than what's supported by
// `Duration`.
let val = cmp::min(val, Duration::max_value().num_seconds());
Some(Duration::seconds(val))
}
Err(_) => continue,
};
}
("domain", Some(mut domain)) if !domain.is_empty() => {
if domain.starts_with('.') {
domain = &domain[1..];
}
let (i, j) = indexes_of(domain, s).expect("domain sub");
cookie.domain = Some(CookieStr::Indexed(i, j));
}
("path", Some(v)) => {
let (i, j) = indexes_of(v, s).expect("path sub");
cookie.path = Some(CookieStr::Indexed(i, j));
}
("samesite", Some(v)) => {
if v.eq_ignore_ascii_case("strict") {
cookie.same_site = Some(SameSite::Strict);
} else if v.eq_ignore_ascii_case("lax") {
cookie.same_site = Some(SameSite::Lax);
} else {
// We do nothing here, for now. When/if the `SameSite`
// attribute becomes standard, the spec says that we should
// ignore this cookie, i.e, fail to parse it, when an
// invalid value is passed in. The draft is at
// http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-same-site.html.
}
}
("expires", Some(v)) => {
// Try strptime with three date formats according to
// http://tools.ietf.org/html/rfc2616#section-3.3.1. Try
// additional ones as encountered in the real world.
let tm = time::strptime(v, "%a, %d %b %Y %H:%M:%S %Z")
.or_else(|_| time::strptime(v, "%A, %d-%b-%y %H:%M:%S %Z"))
.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 {
cookie.expires = Some(time)
}
}
_ => {
// We're going to be permissive here. If we have no idea what
// this is, then it's something nonstandard. We're not going to
// store it (because it's not compliant), but we're also not
// going to emit an error.
}
}
}
Ok(cookie)
}
pub fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result<Cookie<'c>, ParseError>
where
S: Into<Cow<'c, str>>,
{
let s = cow.into();
let mut cookie = parse_inner(&s, decode)?;
cookie.cookie_string = Some(s);
Ok(cookie)
}
#[cfg(test)]
mod tests {
use super::{Cookie, SameSite};
use chrono::Duration;
use time::strptime;
macro_rules! assert_eq_parse {
($string:expr, $expected:expr) => {
let cookie = match Cookie::parse($string) {
Ok(cookie) => cookie,
Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e),
};
assert_eq!(cookie, $expected);
};
}
macro_rules! assert_ne_parse {
($string:expr, $expected:expr) => {
let cookie = match Cookie::parse($string) {
Ok(cookie) => cookie,
Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e),
};
assert_ne!(cookie, $expected);
};
}
#[test]
fn parse_same_site() {
let expected = Cookie::build("foo", "bar")
.same_site(SameSite::Lax)
.finish();
assert_eq_parse!("foo=bar; SameSite=Lax", expected);
assert_eq_parse!("foo=bar; SameSite=lax", expected);
assert_eq_parse!("foo=bar; SameSite=LAX", expected);
assert_eq_parse!("foo=bar; samesite=Lax", expected);
assert_eq_parse!("foo=bar; SAMESITE=Lax", expected);
let expected = Cookie::build("foo", "bar")
.same_site(SameSite::Strict)
.finish();
assert_eq_parse!("foo=bar; SameSite=Strict", expected);
assert_eq_parse!("foo=bar; SameSITE=Strict", expected);
assert_eq_parse!("foo=bar; SameSite=strict", expected);
assert_eq_parse!("foo=bar; SameSite=STrICT", expected);
assert_eq_parse!("foo=bar; SameSite=STRICT", expected);
}
#[test]
fn parse() {
assert!(Cookie::parse("bar").is_err());
assert!(Cookie::parse("=bar").is_err());
assert!(Cookie::parse(" =bar").is_err());
assert!(Cookie::parse("foo=").is_ok());
let expected = Cookie::build("foo", "bar=baz").finish();
assert_eq_parse!("foo=bar=baz", expected);
let mut expected = Cookie::build("foo", "bar").finish();
assert_eq_parse!("foo=bar", expected);
assert_eq_parse!("foo = bar", expected);
assert_eq_parse!(" foo=bar ", expected);
assert_eq_parse!(" foo=bar ;Domain=", expected);
assert_eq_parse!(" foo=bar ;Domain= ", expected);
assert_eq_parse!(" foo=bar ;Ignored", expected);
let mut unexpected = Cookie::build("foo", "bar").http_only(false).finish();
assert_ne_parse!(" foo=bar ;HttpOnly", unexpected);
assert_ne_parse!(" foo=bar; httponly", unexpected);
expected.set_http_only(true);
assert_eq_parse!(" foo=bar ;HttpOnly", expected);
assert_eq_parse!(" foo=bar ;httponly", expected);
assert_eq_parse!(" foo=bar ;HTTPONLY=whatever", expected);
assert_eq_parse!(" foo=bar ; sekure; HTTPONLY", expected);
expected.set_secure(true);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure=aaaa", expected);
unexpected.set_http_only(true);
unexpected.set_secure(true);
assert_ne_parse!(" foo=bar ;HttpOnly; skeure", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; =secure", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly;", unexpected);
unexpected.set_secure(false);
assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
expected.set_max_age(Duration::zero());
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=0", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0 ", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=-1", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", expected);
expected.set_max_age(Duration::minutes(1));
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=60", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 60 ", expected);
expected.set_max_age(Duration::seconds(4));
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 4 ", expected);
unexpected.set_secure(true);
unexpected.set_max_age(Duration::minutes(1));
assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=122", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 38 ", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=51", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0", unexpected);
expected.set_path("/");
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/", expected);
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/", expected);
expected.set_path("/foo");
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", expected);
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/foo", expected);
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path=/foo", expected);
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path = /foo", expected);
unexpected.set_max_age(Duration::seconds(4));
unexpected.set_path("/bar");
assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", unexpected);
assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/baz", unexpected);
expected.set_domain("www.foo.com");
assert_eq_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=www.foo.com",
expected
);
expected.set_domain("foo.com");
assert_eq_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=foo.com",
expected
);
assert_eq_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=FOO.COM",
expected
);
unexpected.set_path("/foo");
unexpected.set_domain("bar.com");
assert_ne_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=foo.com",
unexpected
);
assert_ne_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=FOO.COM",
unexpected
);
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();
expected.set_expires(expires);
assert_eq_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT",
expected
);
unexpected.set_domain("foo.com");
let bad_expires = strptime(time_str, "%a, %d %b %Y %H:%S:%M %Z").unwrap();
expected.set_expires(bad_expires);
assert_ne_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT",
unexpected
);
}
#[test]
fn odd_characters() {
let expected = Cookie::new("foo", "b%2Fr");
assert_eq_parse!("foo=b%2Fr", expected);
}
#[test]
fn odd_characters_encoded() {
let expected = Cookie::new("foo", "b/r");
let cookie = match Cookie::parse_encoded("foo=b%2Fr") {
Ok(cookie) => cookie,
Err(e) => panic!("Failed to parse: {:?}", e),
};
assert_eq!(cookie, expected);
}
#[test]
fn do_not_panic_on_large_max_ages() {
let max_seconds = Duration::max_value().num_seconds();
let expected = Cookie::build("foo", "bar").max_age(max_seconds).finish();
assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected);
}
}

View File

@ -1,180 +0,0 @@
use ring::digest::{Algorithm, SHA256};
use ring::hkdf::expand;
use ring::hmac::SigningKey;
use ring::rand::{SecureRandom, SystemRandom};
use super::private::KEY_LEN as PRIVATE_KEY_LEN;
use super::signed::KEY_LEN as SIGNED_KEY_LEN;
static HKDF_DIGEST: &Algorithm = &SHA256;
const KEYS_INFO: &str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM";
/// A cryptographic master key for use with `Signed` and/or `Private` jars.
///
/// This structure encapsulates secure, cryptographic keys for use with both
/// [PrivateJar](struct.PrivateJar.html) and [SignedJar](struct.SignedJar.html).
/// It can be derived from a single master key via
/// [from_master](#method.from_master) or generated from a secure random source
/// via [generate](#method.generate). A single instance of `Key` can be used for
/// both a `PrivateJar` and a `SignedJar`.
///
/// This type is only available when the `secure` feature is enabled.
#[derive(Clone)]
pub struct Key {
signing_key: [u8; SIGNED_KEY_LEN],
encryption_key: [u8; PRIVATE_KEY_LEN],
}
impl Key {
/// Derives new signing/encryption keys from a master key.
///
/// The master key must be at least 256-bits (32 bytes). For security, the
/// master key _must_ be cryptographically random. The keys are derived
/// deterministically from the master key.
///
/// # Panics
///
/// Panics if `key` is less than 32 bytes in length.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Key;
///
/// # /*
/// let master_key = { /* a cryptographically random key >= 32 bytes */ };
/// # */
/// # let master_key: &Vec<u8> = &(0..32).collect();
///
/// let key = Key::from_master(master_key);
/// ```
pub fn from_master(key: &[u8]) -> Key {
if key.len() < 32 {
panic!(
"bad master key length: expected at least 32 bytes, found {}",
key.len()
);
}
// Expand the user's key into two.
let prk = SigningKey::new(HKDF_DIGEST, key);
let mut both_keys = [0; SIGNED_KEY_LEN + PRIVATE_KEY_LEN];
expand(&prk, KEYS_INFO.as_bytes(), &mut both_keys);
// Copy the keys into their respective arrays.
let mut signing_key = [0; SIGNED_KEY_LEN];
let mut encryption_key = [0; PRIVATE_KEY_LEN];
signing_key.copy_from_slice(&both_keys[..SIGNED_KEY_LEN]);
encryption_key.copy_from_slice(&both_keys[SIGNED_KEY_LEN..]);
Key {
signing_key,
encryption_key,
}
}
/// Generates signing/encryption keys from a secure, random source. Keys are
/// generated nondeterministically.
///
/// # Panics
///
/// Panics if randomness cannot be retrieved from the operating system. See
/// [try_generate](#method.try_generate) for a non-panicking version.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Key;
///
/// let key = Key::generate();
/// ```
pub fn generate() -> Key {
Self::try_generate().expect("failed to generate `Key` from randomness")
}
/// Attempts to generate signing/encryption keys from a secure, random
/// source. Keys are generated nondeterministically. If randomness cannot be
/// retrieved from the underlying operating system, returns `None`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Key;
///
/// let key = Key::try_generate();
/// ```
pub fn try_generate() -> Option<Key> {
let mut sign_key = [0; SIGNED_KEY_LEN];
let mut enc_key = [0; PRIVATE_KEY_LEN];
let rng = SystemRandom::new();
if rng.fill(&mut sign_key).is_err() || rng.fill(&mut enc_key).is_err() {
return None;
}
Some(Key {
signing_key: sign_key,
encryption_key: enc_key,
})
}
/// Returns the raw bytes of a key suitable for signing cookies.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Key;
///
/// let key = Key::generate();
/// let signing_key = key.signing();
/// ```
pub fn signing(&self) -> &[u8] {
&self.signing_key[..]
}
/// Returns the raw bytes of a key suitable for encrypting cookies.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Key;
///
/// let key = Key::generate();
/// let encryption_key = key.encryption();
/// ```
pub fn encryption(&self) -> &[u8] {
&self.encryption_key[..]
}
}
#[cfg(test)]
mod test {
use super::Key;
#[test]
fn deterministic_from_master() {
let master_key: Vec<u8> = (0..32).collect();
let key_a = Key::from_master(&master_key);
let key_b = Key::from_master(&master_key);
assert_eq!(key_a.signing(), key_b.signing());
assert_eq!(key_a.encryption(), key_b.encryption());
assert_ne!(key_a.encryption(), key_a.signing());
let master_key_2: Vec<u8> = (32..64).collect();
let key_2 = Key::from_master(&master_key_2);
assert_ne!(key_2.signing(), key_a.signing());
assert_ne!(key_2.encryption(), key_a.encryption());
}
#[test]
fn non_deterministic_generate() {
let key_a = Key::generate();
let key_b = Key::generate();
assert_ne!(key_a.signing(), key_b.signing());
assert_ne!(key_a.encryption(), key_b.encryption());
}
}

View File

@ -1,40 +0,0 @@
#[cfg(test)]
macro_rules! assert_simple_behaviour {
($clear:expr, $secure:expr) => {{
assert_eq!($clear.iter().count(), 0);
$secure.add(Cookie::new("name", "val"));
assert_eq!($clear.iter().count(), 1);
assert_eq!($secure.get("name").unwrap().value(), "val");
assert_ne!($clear.get("name").unwrap().value(), "val");
$secure.add(Cookie::new("another", "two"));
assert_eq!($clear.iter().count(), 2);
$clear.remove(Cookie::named("another"));
assert_eq!($clear.iter().count(), 1);
$secure.remove(Cookie::named("name"));
assert_eq!($clear.iter().count(), 0);
}};
}
#[cfg(test)]
macro_rules! assert_secure_behaviour {
($clear:expr, $secure:expr) => {{
$secure.add(Cookie::new("secure", "secure"));
assert!($clear.get("secure").unwrap().value() != "secure");
assert!($secure.get("secure").unwrap().value() == "secure");
let mut cookie = $clear.get("secure").unwrap().clone();
let new_val = format!("{}l", cookie.value());
cookie.set_value(new_val);
$clear.add(cookie);
assert!($secure.get("secure").is_none());
let mut cookie = $clear.get("secure").unwrap().clone();
cookie.set_value("foobar");
$clear.add(cookie);
assert!($secure.get("secure").is_none());
}};
}

View File

@ -1,10 +0,0 @@
//! Fork of https://github.com/alexcrichton/cookie-rs
#[macro_use]
mod macros;
mod key;
mod private;
mod signed;
pub use self::key::*;
pub use self::private::*;
pub use self::signed::*;

View File

@ -1,269 +0,0 @@
use std::str;
use log::warn;
use ring::aead::{open_in_place, seal_in_place, Aad, Algorithm, Nonce, AES_256_GCM};
use ring::aead::{OpeningKey, SealingKey};
use ring::rand::{SecureRandom, SystemRandom};
use super::Key;
use crate::cookie::{Cookie, CookieJar};
// Keep these in sync, and keep the key len synced with the `private` docs as
// well as the `KEYS_INFO` const in secure::Key.
static ALGO: &Algorithm = &AES_256_GCM;
const NONCE_LEN: usize = 12;
pub const KEY_LEN: usize = 32;
/// A child cookie jar that provides authenticated encryption for its cookies.
///
/// A _private_ child jar signs and encrypts all the cookies added to it and
/// verifies and decrypts cookies retrieved from it. Any cookies stored in a
/// `PrivateJar` are simultaneously assured confidentiality, integrity, and
/// authenticity. In other words, clients cannot discover nor tamper with the
/// contents of a cookie, nor can they fabricate cookie data.
///
/// This type is only available when the `secure` feature is enabled.
pub struct PrivateJar<'a> {
parent: &'a mut CookieJar,
key: [u8; KEY_LEN],
}
impl<'a> PrivateJar<'a> {
/// Creates a new child `PrivateJar` with parent `parent` and key `key`.
/// This method is typically called indirectly via the `signed` method of
/// `CookieJar`.
#[doc(hidden)]
pub fn new(parent: &'a mut CookieJar, key: &Key) -> PrivateJar<'a> {
let mut key_array = [0u8; KEY_LEN];
key_array.copy_from_slice(key.encryption());
PrivateJar {
parent,
key: key_array,
}
}
/// Given a sealed value `str` and a key name `name`, where the nonce is
/// prepended to the original value and then both are Base64 encoded,
/// verifies and decrypts the sealed value and returns it. If there's a
/// problem, returns an `Err` with a string describing the issue.
fn unseal(&self, name: &str, value: &str) -> Result<String, &'static str> {
let mut data = base64::decode(value).map_err(|_| "bad base64 value")?;
if data.len() <= NONCE_LEN {
return Err("length of decoded data is <= NONCE_LEN");
}
let ad = Aad::from(name.as_bytes());
let key = OpeningKey::new(ALGO, &self.key).expect("opening key");
let (nonce, sealed) = data.split_at_mut(NONCE_LEN);
let nonce =
Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`");
let unsealed = open_in_place(&key, nonce, ad, 0, sealed)
.map_err(|_| "invalid key/nonce/value: bad seal")?;
if let Ok(unsealed_utf8) = str::from_utf8(unsealed) {
Ok(unsealed_utf8.to_string())
} else {
warn!(
"Private cookie does not have utf8 content!
It is likely the secret key used to encrypt them has been leaked.
Please change it as soon as possible."
);
Err("bad unsealed utf8")
}
}
/// Returns a reference to the `Cookie` inside this jar with the name `name`
/// and authenticates and decrypts the cookie's value, returning a `Cookie`
/// with the decrypted value. If the cookie cannot be found, or the cookie
/// fails to authenticate or decrypt, `None` is returned.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// let mut private_jar = jar.private(&key);
/// assert!(private_jar.get("name").is_none());
///
/// private_jar.add(Cookie::new("name", "value"));
/// assert_eq!(private_jar.get("name").unwrap().value(), "value");
/// ```
pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
if let Some(cookie_ref) = self.parent.get(name) {
let mut cookie = cookie_ref.clone();
if let Ok(value) = self.unseal(name, cookie.value()) {
cookie.set_value(value);
return Some(cookie);
}
}
None
}
/// Adds `cookie` to the parent jar. The cookie's value is encrypted with
/// authenticated encryption assuring confidentiality, integrity, and
/// authenticity.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// jar.private(&key).add(Cookie::new("name", "value"));
///
/// assert_ne!(jar.get("name").unwrap().value(), "value");
/// assert_eq!(jar.private(&key).get("name").unwrap().value(), "value");
/// ```
pub fn add(&mut self, mut cookie: Cookie<'static>) {
self.encrypt_cookie(&mut cookie);
// Add the sealed cookie to the parent.
self.parent.add(cookie);
}
/// Adds an "original" `cookie` to parent jar. The cookie's value is
/// encrypted with authenticated encryption assuring confidentiality,
/// integrity, and authenticity. Adding an original cookie does not affect
/// the [`CookieJar::delta()`](struct.CookieJar.html#method.delta)
/// computation. This method is intended to be used to seed the cookie jar
/// with cookies received from a client's HTTP message.
///
/// For accurate `delta` computations, this method should not be called
/// after calling `remove`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// jar.private(&key).add_original(Cookie::new("name", "value"));
///
/// assert_eq!(jar.iter().count(), 1);
/// assert_eq!(jar.delta().count(), 0);
/// ```
pub fn add_original(&mut self, mut cookie: Cookie<'static>) {
self.encrypt_cookie(&mut cookie);
// Add the sealed cookie to the parent.
self.parent.add_original(cookie);
}
/// Encrypts the cookie's value with
/// authenticated encryption assuring confidentiality, integrity, and authenticity.
fn encrypt_cookie(&self, cookie: &mut Cookie) {
let name = cookie.name().as_bytes();
let value = cookie.value().as_bytes();
let data = encrypt_name_value(name, value, &self.key);
// Base64 encode the nonce and encrypted value.
let sealed_value = base64::encode(&data);
cookie.set_value(sealed_value);
}
/// Removes `cookie` from the parent jar.
///
/// For correct removal, the passed in `cookie` must contain the same `path`
/// and `domain` as the cookie that was initially set.
///
/// See [CookieJar::remove](struct.CookieJar.html#method.remove) for more
/// details.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// let mut private_jar = jar.private(&key);
///
/// private_jar.add(Cookie::new("name", "value"));
/// assert!(private_jar.get("name").is_some());
///
/// private_jar.remove(Cookie::named("name"));
/// assert!(private_jar.get("name").is_none());
/// ```
pub fn remove(&mut self, cookie: Cookie<'static>) {
self.parent.remove(cookie);
}
}
fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec<u8> {
// Create the `SealingKey` structure.
let key = SealingKey::new(ALGO, key).expect("sealing key creation");
// Create a vec to hold the [nonce | cookie value | overhead].
let overhead = ALGO.tag_len();
let mut data = vec![0; NONCE_LEN + value.len() + overhead];
// Randomly generate the nonce, then copy the cookie value as input.
let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
SystemRandom::new()
.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 length of `nonce`");
// Use cookie's name as associated data to prevent value swapping.
let ad = Aad::from(name);
// Perform the actual sealing operation and get the output length.
let output_len =
seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal");
// Remove the overhead and return the sealed content.
data.truncate(NONCE_LEN + output_len);
data
}
#[cfg(test)]
mod test {
use super::{encrypt_name_value, Cookie, CookieJar, Key};
#[test]
fn simple() {
let key = Key::generate();
let mut jar = CookieJar::new();
assert_simple_behaviour!(jar, jar.private(&key));
}
#[test]
fn private() {
let key = Key::generate();
let mut jar = CookieJar::new();
assert_secure_behaviour!(jar, jar.private(&key));
}
#[test]
fn non_utf8() {
let key = Key::generate();
let mut jar = CookieJar::new();
let name = "malicious";
let mut assert_non_utf8 = |value: &[u8]| {
let sealed = encrypt_name_value(name.as_bytes(), value, &key.encryption());
let encoded = base64::encode(&sealed);
assert_eq!(
jar.private(&key).unseal(name, &encoded),
Err("bad unsealed utf8")
);
jar.add(Cookie::new(name, encoded));
assert_eq!(jar.private(&key).get(name), None);
};
assert_non_utf8(&[0x72, 0xfb, 0xdf, 0x74]); // rûst in ISO/IEC 8859-1
let mut malicious =
String::from(r#"{"id":"abc123??%X","admin":true}"#).into_bytes();
malicious[8] |= 0b1100_0000;
malicious[9] |= 0b1100_0000;
assert_non_utf8(&malicious);
}
}

View File

@ -1,185 +0,0 @@
use ring::digest::{Algorithm, SHA256};
use ring::hmac::{sign, verify_with_own_key as verify, SigningKey};
use super::Key;
use crate::cookie::{Cookie, CookieJar};
// Keep these in sync, and keep the key len synced with the `signed` docs as
// well as the `KEYS_INFO` const in secure::Key.
static HMAC_DIGEST: &Algorithm = &SHA256;
const BASE64_DIGEST_LEN: usize = 44;
pub const KEY_LEN: usize = 32;
/// A child cookie jar that authenticates its cookies.
///
/// A _signed_ child jar signs all the cookies added to it and verifies cookies
/// retrieved from it. Any cookies stored in a `SignedJar` are assured integrity
/// and authenticity. In other words, clients cannot tamper with the contents of
/// a cookie nor can they fabricate cookie values, but the data is visible in
/// plaintext.
///
/// This type is only available when the `secure` feature is enabled.
pub struct SignedJar<'a> {
parent: &'a mut CookieJar,
key: SigningKey,
}
impl<'a> SignedJar<'a> {
/// Creates a new child `SignedJar` with parent `parent` and key `key`. This
/// method is typically called indirectly via the `signed` method of
/// `CookieJar`.
#[doc(hidden)]
pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> {
SignedJar {
parent,
key: SigningKey::new(HMAC_DIGEST, key.signing()),
}
}
/// Given a signed value `str` where the signature is prepended to `value`,
/// verifies the signed value and returns it. If there's a problem, returns
/// an `Err` with a string describing the issue.
fn verify(&self, cookie_value: &str) -> Result<String, &'static str> {
if cookie_value.len() < BASE64_DIGEST_LEN {
return Err("length of value is <= BASE64_DIGEST_LEN");
}
let (digest_str, value) = cookie_value.split_at(BASE64_DIGEST_LEN);
let sig = base64::decode(digest_str).map_err(|_| "bad base64 digest")?;
verify(&self.key, value.as_bytes(), &sig)
.map(|_| value.to_string())
.map_err(|_| "value did not verify")
}
/// Returns a reference to the `Cookie` inside this jar with the name `name`
/// and verifies the authenticity and integrity of the cookie's value,
/// returning a `Cookie` with the authenticated value. If the cookie cannot
/// be found, or the cookie fails to verify, `None` is returned.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// let mut signed_jar = jar.signed(&key);
/// assert!(signed_jar.get("name").is_none());
///
/// signed_jar.add(Cookie::new("name", "value"));
/// assert_eq!(signed_jar.get("name").unwrap().value(), "value");
/// ```
pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
if let Some(cookie_ref) = self.parent.get(name) {
let mut cookie = cookie_ref.clone();
if let Ok(value) = self.verify(cookie.value()) {
cookie.set_value(value);
return Some(cookie);
}
}
None
}
/// Adds `cookie` to the parent jar. The cookie's value is signed assuring
/// integrity and authenticity.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// jar.signed(&key).add(Cookie::new("name", "value"));
///
/// assert_ne!(jar.get("name").unwrap().value(), "value");
/// assert!(jar.get("name").unwrap().value().contains("value"));
/// assert_eq!(jar.signed(&key).get("name").unwrap().value(), "value");
/// ```
pub fn add(&mut self, mut cookie: Cookie<'static>) {
self.sign_cookie(&mut cookie);
self.parent.add(cookie);
}
/// Adds an "original" `cookie` to this jar. The cookie's value is signed
/// assuring integrity and authenticity. Adding an original cookie does not
/// affect the [`CookieJar::delta()`](struct.CookieJar.html#method.delta)
/// computation. This method is intended to be used to seed the cookie jar
/// with cookies received from a client's HTTP message.
///
/// For accurate `delta` computations, this method should not be called
/// after calling `remove`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// jar.signed(&key).add_original(Cookie::new("name", "value"));
///
/// assert_eq!(jar.iter().count(), 1);
/// assert_eq!(jar.delta().count(), 0);
/// ```
pub fn add_original(&mut self, mut cookie: Cookie<'static>) {
self.sign_cookie(&mut cookie);
self.parent.add_original(cookie);
}
/// Signs the cookie's value assuring integrity and authenticity.
fn sign_cookie(&self, cookie: &mut Cookie) {
let digest = sign(&self.key, cookie.value().as_bytes());
let mut new_value = base64::encode(digest.as_ref());
new_value.push_str(cookie.value());
cookie.set_value(new_value);
}
/// Removes `cookie` from the parent jar.
///
/// For correct removal, the passed in `cookie` must contain the same `path`
/// and `domain` as the cookie that was initially set.
///
/// See [CookieJar::remove](struct.CookieJar.html#method.remove) for more
/// details.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// let mut signed_jar = jar.signed(&key);
///
/// signed_jar.add(Cookie::new("name", "value"));
/// assert!(signed_jar.get("name").is_some());
///
/// signed_jar.remove(Cookie::named("name"));
/// assert!(signed_jar.get("name").is_none());
/// ```
pub fn remove(&mut self, cookie: Cookie<'static>) {
self.parent.remove(cookie);
}
}
#[cfg(test)]
mod test {
use super::{Cookie, CookieJar, Key};
#[test]
fn simple() {
let key = Key::generate();
let mut jar = CookieJar::new();
assert_simple_behaviour!(jar, jar.signed(&key));
}
#[test]
fn private() {
let key = Key::generate();
let mut jar = CookieJar::new();
assert_secure_behaviour!(jar, jar.signed(&key));
}
}

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,25 +1,29 @@
//! 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 pin_project::pin_project;
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;
#[pin_project]
pub struct Encoder<B> { pub struct Encoder<B> {
eof: bool, eof: bool,
#[pin]
body: EncoderBody<B>, body: EncoderBody<B>,
encoder: Option<ContentEncoder>, encoder: Option<ContentEncoder>,
fut: Option<CpuFuture<ContentEncoder, io::Error>>, fut: Option<CpuFuture<ContentEncoder, io::Error>>,
@ -75,86 +79,110 @@ impl<B: MessageBody> Encoder<B> {
} }
} }
#[pin_project(project = EncoderBodyProj)]
enum EncoderBody<B> { enum EncoderBody<B> {
Bytes(Bytes), Bytes(Bytes),
Stream(B), Stream(#[pin] B),
BoxedStream(Box<dyn MessageBody>), BoxedStream(Box<dyn MessageBody + Unpin>),
}
impl<B: MessageBody> MessageBody for EncoderBody<B> {
fn size(&self) -> BodySize {
match self {
EncoderBody::Bytes(ref b) => b.size(),
EncoderBody::Stream(ref b) => b.size(),
EncoderBody::BoxedStream(ref b) => b.size(),
}
}
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Error>>> {
match self.project() {
EncoderBodyProj::Bytes(b) => {
if b.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(std::mem::take(b))))
}
}
EncoderBodyProj::Stream(b) => b.poll_next(cx),
EncoderBodyProj::BoxedStream(ref mut b) => {
Pin::new(b.as_mut()).poll_next(cx)
}
}
}
} }
impl<B: MessageBody> MessageBody for Encoder<B> { impl<B: MessageBody> MessageBody for Encoder<B> {
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
if self.encoder.is_none() { if self.encoder.is_none() {
match self.body { self.body.size()
EncoderBody::Bytes(ref b) => b.size(),
EncoderBody::Stream(ref b) => b.size(),
EncoderBody::BoxedStream(ref b) => b.size(),
}
} else { } else {
BodySize::Stream BodySize::Stream
} }
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Error>>> {
let mut this = self.project();
loop { loop {
if self.eof { if *this.eof {
return Ok(Async::Ready(None)); return Poll::Ready(None);
} }
if let Some(ref mut fut) = self.fut { if let Some(ref mut fut) = this.fut {
let mut encoder = futures::try_ready!(fut.poll()); let mut encoder = match ready!(Pin::new(fut).poll(cx)) {
let chunk = encoder.take(); Ok(item) => item,
self.encoder = Some(encoder); Err(e) => return Poll::Ready(Some(Err(e.into()))),
self.fut.take();
if !chunk.is_empty() {
return Ok(Async::Ready(Some(chunk)));
}
}
let result = match self.body {
EncoderBody::Bytes(ref mut b) => {
if b.is_empty() {
Async::Ready(None)
} else {
Async::Ready(Some(std::mem::replace(b, Bytes::new())))
}
}
EncoderBody::Stream(ref mut b) => b.poll_next()?,
EncoderBody::BoxedStream(ref mut b) => b.poll_next()?,
}; };
let chunk = encoder.take();
*this.encoder = Some(encoder);
this.fut.take();
if !chunk.is_empty() {
return Poll::Ready(Some(Ok(chunk)));
}
}
let result = this.body.as_mut().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) = this.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); *this.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 || { *this.fut = Some(run(move || {
encoder.write(&chunk)?; encoder.write(&chunk)?;
Ok(encoder) Ok(encoder)
})); }));
} }
} 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) = this.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; *this.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 +191,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 +222,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 +247,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 +254,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 +261,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::dispatcher::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 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 convenience
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
@ -35,7 +35,7 @@ pub type Result<T, E = Error> = result::Result<T, E>;
/// General purpose actix web error. /// General purpose actix web error.
/// ///
/// An actix web error is used to carry errors from `failure` or `std::error` /// An actix web error is used to carry errors from `std::error`
/// through actix in a convenient way. It can be created through /// through actix in a convenient way. It can be created through
/// converting errors with `into()`. /// converting errors with `into()`.
/// ///
@ -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
} }
} }
@ -339,6 +334,8 @@ pub enum PayloadError {
Io(io::Error), Io(io::Error),
} }
impl std::error::Error for PayloadError {}
impl From<h2::Error> for PayloadError { impl From<h2::Error> for PayloadError {
fn from(err: h2::Error) -> Self { fn from(err: h2::Error) -> Self {
PayloadError::Http2Payload(err) PayloadError::Http2Payload(err)
@ -363,7 +360,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 +371,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
} }
} }
@ -436,7 +433,7 @@ pub enum DispatchError {
Unknown, Unknown,
} }
/// A set of error that can occure during parsing content type /// A set of error that can occur during parsing content type
#[derive(PartialEq, Debug, Display)] #[derive(PartialEq, Debug, Display)]
pub enum ContentTypeError { pub enum ContentTypeError {
/// Can not parse content type /// Can not parse content type
@ -447,13 +444,23 @@ pub enum ContentTypeError {
UnknownEncoding, UnknownEncoding,
} }
impl std::error::Error for 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<I> + Decoder, I> ResponseError for FramedDispatcherError<E, U, I>
where
E: fmt::Debug + fmt::Display,
<U as Encoder<I>>::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 +468,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 +507,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 +516,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 +525,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 +546,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 +559,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 +951,20 @@ where
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
} }
#[cfg(feature = "fail")] #[cfg(feature = "actors")]
mod failure_integration { /// `InternalServerError` for `actix::MailboxError`
use super::*; /// This is supported on feature=`actors` only
impl ResponseError for actix::MailboxError {}
/// Compatibility for `failure::Error` #[cfg(feature = "actors")]
impl ResponseError for failure::Error { /// `InternalServerError` for `actix::ResolverError`
fn error_response(&self) -> Response { /// This is supported on feature=`actors` only
Response::new(StatusCode::INTERNAL_SERVER_ERROR) impl ResponseError for actix::actors::resolver::ResolverError {}
}
}
}
#[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 std::error::Error as StdError;
use std::io; use std::io;
#[test] #[test]
@ -991,7 +993,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 +1001,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 +1043,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),
} }
@ -1072,7 +1074,7 @@ mod tests {
#[test] #[test]
fn test_error_casting() { fn test_error_casting() {
let err = PayloadError::Overflow; let err = PayloadError::Overflow;
let resp_err: &ResponseError = &err; let resp_err: &dyn ResponseError = &err;
let err = resp_err.downcast_ref::<PayloadError>().unwrap(); let err = resp_err.downcast_ref::<PayloadError>().unwrap();
assert_eq!(err.to_string(), "A payload reached size limit."); assert_eq!(err.to_string(), "A payload reached size limit.");
let not_err = resp_err.downcast_ref::<ContentTypeError>(); let not_err = resp_err.downcast_ref::<ContentTypeError>();

View File

@ -1,12 +1,14 @@
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>>, /// Use FxHasher with a std HashMap with for faster
/// lookups on the small `TypeId` (u64 equivalent) keys.
map: FxHashMap<TypeId, Box<dyn Any>>,
} }
impl Extensions { impl Extensions {
@ -14,7 +16,7 @@ impl Extensions {
#[inline] #[inline]
pub fn new() -> Extensions { pub fn new() -> Extensions {
Extensions { Extensions {
map: HashMap::default(), map: FxHashMap::default(),
} }
} }
@ -28,33 +30,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 +64,101 @@ 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()
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[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)]
@ -89,3 +178,4 @@ fn test_extensions() {
assert_eq!(extensions.get::<bool>(), None); assert_eq!(extensions.get::<bool>(), None);
assert_eq!(extensions.get(), Some(&MyType(10))); assert_eq!(extensions.get(), Some(&MyType(10)));
} }
}

View File

@ -1,12 +1,8 @@
#![allow(unused_imports, unused_variables, dead_code)] use std::io;
use std::io::{self, Write};
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};
@ -15,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, PayloadError}; use crate::error::{ParseError, PayloadError};
use crate::helpers; use crate::message::{ConnectionType, RequestHeadType, ResponseHead};
use crate::message::{ConnectionType, Head, MessagePool, RequestHead, ResponseHead};
bitflags! { bitflags! {
struct Flags: u8 { struct Flags: u8 {
@ -26,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,
@ -47,8 +40,7 @@ struct ClientCodecInner {
// encoder part // encoder part
flags: Flags, flags: Flags,
headers_size: u32, encoder: encoder::MessageEncoder<RequestHeadType>,
encoder: encoder::MessageEncoder<RequestHead>,
} }
impl Default for ClientCodec { impl Default for ClientCodec {
@ -76,7 +68,6 @@ impl ClientCodec {
ctype: ConnectionType::Close, ctype: ConnectionType::Close,
flags, flags,
headers_size: 0,
encoder: encoder::MessageEncoder::default(), encoder: encoder::MessageEncoder::default(),
}, },
} }
@ -182,23 +173,24 @@ impl Decoder for ClientPayloadCodec {
} }
} }
impl Encoder for ClientCodec { impl Encoder<Message<(RequestHeadType, BodySize)>> for ClientCodec {
type Item = Message<(RequestHead, BodySize)>;
type Error = io::Error; type Error = io::Error;
fn encode( fn encode(
&mut self, &mut self,
item: Self::Item, item: Message<(RequestHeadType, BodySize)>,
dst: &mut BytesMut, dst: &mut BytesMut,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
match item { match item {
Message::Item((mut msg, length)) => { Message::Item((mut head, length)) => {
let inner = &mut self.inner; let inner = &mut self.inner;
inner.version = msg.version; inner.version = head.as_ref().version;
inner.flags.set(Flags::HEAD, msg.method == Method::HEAD); inner
.flags
.set(Flags::HEAD, head.as_ref().method == Method::HEAD);
// connection status // connection status
inner.ctype = match msg.connection_type() { inner.ctype = match head.as_ref().connection_type() {
ConnectionType::KeepAlive => { ConnectionType::KeepAlive => {
if inner.flags.contains(Flags::KEEPALIVE_ENABLED) { if inner.flags.contains(Flags::KEEPALIVE_ENABLED) {
ConnectionType::KeepAlive ConnectionType::KeepAlive
@ -212,7 +204,7 @@ impl Encoder for ClientCodec {
inner.encoder.encode( inner.encoder.encode(
dst, dst,
&mut msg, &mut head,
false, false,
false, false,
inner.version, inner.version,

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)
} }
} }
@ -150,13 +144,12 @@ impl Decoder for Codec {
} }
} }
impl Encoder for Codec { impl Encoder<Message<(Response<()>, BodySize)>> for Codec {
type Item = Message<(Response<()>, BodySize)>;
type Error = io::Error; type Error = io::Error;
fn encode( fn encode(
&mut self, &mut self,
item: Self::Item, item: Message<(Response<()>, BodySize)>,
dst: &mut BytesMut, dst: &mut BytesMut,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
match item { match item {
@ -176,7 +169,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 +194,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,12 +1,12 @@
use std::convert::TryFrom;
use std::io;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::{io, mem}; 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 log::{debug, error, trace}; use log::{debug, error, trace};
use crate::error::ParseError; use crate::error::ParseError;
@ -17,7 +17,7 @@ use crate::request::Request;
const MAX_BUFFER_SIZE: usize = 131_072; const MAX_BUFFER_SIZE: usize = 131_072;
const MAX_HEADERS: usize = 96; const MAX_HEADERS: usize = 96;
/// Incoming messagd decoder /// Incoming message decoder
pub(crate) struct MessageDecoder<T: MessageType>(PhantomData<T>); pub(crate) struct MessageDecoder<T: MessageType>(PhantomData<T>);
#[derive(Debug)] #[derive(Debug)]
@ -45,7 +45,7 @@ impl<T: MessageType> Decoder for MessageDecoder<T> {
pub(crate) enum PayloadLength { pub(crate) enum PayloadLength {
Payload(PayloadType), Payload(PayloadType),
Upgrade, UpgradeWebSocket,
None, None,
} }
@ -64,7 +64,7 @@ pub(crate) trait MessageType: Sized {
raw_headers: &[HeaderIndex], raw_headers: &[HeaderIndex],
) -> Result<PayloadLength, ParseError> { ) -> Result<PayloadLength, ParseError> {
let mut ka = None; let mut ka = None;
let mut has_upgrade = false; let mut has_upgrade_websocket = false;
let mut expect = false; let mut expect = false;
let mut chunked = false; let mut chunked = false;
let mut content_length = None; let mut content_length = None;
@ -76,12 +76,14 @@ pub(crate) trait MessageType: Sized {
let name = let name =
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap(); HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();
// Unsafe: httparse check header value for valid utf-8 // SAFETY: httparse already checks header value is only visible ASCII bytes
// from_maybe_shared_unchecked contains debug assertions so they are omitted here
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 {
header::CONTENT_LENGTH => { header::CONTENT_LENGTH => {
if let Ok(s) = value.to_str() { if let Ok(s) = value.to_str() {
@ -123,12 +125,9 @@ pub(crate) trait MessageType: Sized {
}; };
} }
header::UPGRADE => { header::UPGRADE => {
has_upgrade = true;
// check content-length, some clients (dart)
// sends "content-length: 0" with websocket upgrade
if let Ok(val) = value.to_str().map(|val| val.trim()) { if let Ok(val) = value.to_str().map(|val| val.trim()) {
if val.eq_ignore_ascii_case("websocket") { if val.eq_ignore_ascii_case("websocket") {
content_length = None; has_upgrade_websocket = true;
} }
} }
} }
@ -155,13 +154,13 @@ pub(crate) trait MessageType: Sized {
Ok(PayloadLength::Payload(PayloadType::Payload( Ok(PayloadLength::Payload(PayloadType::Payload(
PayloadDecoder::chunked(), PayloadDecoder::chunked(),
))) )))
} else if has_upgrade_websocket {
Ok(PayloadLength::UpgradeWebSocket)
} else if let Some(len) = content_length { } else if let Some(len) = content_length {
// Content-Length // Content-Length
Ok(PayloadLength::Payload(PayloadType::Payload( Ok(PayloadLength::Payload(PayloadType::Payload(
PayloadDecoder::length(len), PayloadDecoder::length(len),
))) )))
} else if has_upgrade {
Ok(PayloadLength::Upgrade)
} else { } else {
Ok(PayloadLength::None) Ok(PayloadLength::None)
} }
@ -184,13 +183,10 @@ impl MessageType for Request {
} }
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. let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() };
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] = EMPTY_HEADER_ARRAY;
unsafe { mem::uninitialized() };
let mut req = httparse::Request::new(&mut parsed); let mut req = httparse::Request::new(&mut parsed);
match req.parse(src)? { match req.parse(src)? {
@ -219,7 +215,7 @@ impl MessageType for Request {
// payload decoder // payload decoder
let decoder = match length { let decoder = match length {
PayloadLength::Payload(pl) => pl, PayloadLength::Payload(pl) => pl,
PayloadLength::Upgrade => { PayloadLength::UpgradeWebSocket => {
// upgrade(websocket) // upgrade(websocket)
PayloadType::Stream(PayloadDecoder::eof()) PayloadType::Stream(PayloadDecoder::eof())
} }
@ -258,13 +254,10 @@ impl MessageType for ResponseHead {
} }
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. let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY;
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() };
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] = EMPTY_HEADER_ARRAY;
unsafe { mem::uninitialized() };
let mut res = httparse::Response::new(&mut parsed); let mut res = httparse::Response::new(&mut parsed);
match res.parse(src)? { match res.parse(src)? {
@ -319,10 +312,21 @@ pub(crate) struct HeaderIndex {
pub(crate) value: (usize, usize), pub(crate) value: (usize, usize),
} }
pub(crate) const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
name: (0, 0),
value: (0, 0),
};
pub(crate) const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
[EMPTY_HEADER_INDEX; MAX_HEADERS];
pub(crate) const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] =
[httparse::EMPTY_HEADER; MAX_HEADERS];
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 +429,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 +443,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 +464,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 +475,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 +489,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 +501,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 +523,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 +573,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 +590,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",
)), ))),
} }
} }
} }
@ -644,10 +654,7 @@ mod tests {
} }
fn is_unhandled(&self) -> bool { fn is_unhandled(&self) -> bool {
match self { matches!(self, PayloadType::Stream(_))
PayloadType::Stream(_) => true,
_ => false,
}
} }
} }
@ -659,10 +666,7 @@ mod tests {
} }
} }
fn eof(&self) -> bool { fn eof(&self) -> bool {
match *self { matches!(*self, PayloadItem::Eof)
PayloadItem::Eof => true,
_ => false,
}
} }
} }
@ -968,7 +972,7 @@ mod tests {
unreachable!("Error"); unreachable!("Error");
} }
// type in chunked // intentional typo in "chunked"
let mut buf = BytesMut::from( let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\ "GET /test HTTP/1.1\r\n\
transfer-encoding: chnked\r\n\r\n", transfer-encoding: chnked\r\n\r\n",
@ -1029,7 +1033,7 @@ mod tests {
} }
#[test] #[test]
fn test_http_request_upgrade() { fn test_http_request_upgrade_websocket() {
let mut buf = BytesMut::from( let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\ "GET /test HTTP/1.1\r\n\
connection: upgrade\r\n\ connection: upgrade\r\n\
@ -1043,6 +1047,26 @@ mod tests {
assert!(pl.is_unhandled()); assert!(pl.is_unhandled());
} }
#[test]
fn test_http_request_upgrade_h2c() {
let mut buf = BytesMut::from(
"GET /test HTTP/1.1\r\n\
connection: upgrade, http2-settings\r\n\
upgrade: h2c\r\n\
http2-settings: dummy\r\n\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
// `connection: upgrade, http2-settings` doesn't work properly..
// see MessageType::set_headers().
//
// The line below should be:
// assert_eq!(req.head().connection_type(), ConnectionType::Upgrade);
assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive);
assert!(req.upgrade());
assert!(!pl.is_unhandled());
}
#[test] #[test]
fn test_http_request_parser_utf8() { fn test_http_request_parser_utf8() {
let mut buf = BytesMut::from( let mut buf = BytesMut::from(

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +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::{cmp, io};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{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};
use crate::request::Request;
use crate::response::Response; use crate::response::Response;
const AVERAGE_HEADER_SIZE: usize = 30; const AVERAGE_HEADER_SIZE: usize = 30;
@ -43,6 +39,8 @@ pub(crate) trait MessageType: Sized {
fn headers(&self) -> &HeaderMap; fn headers(&self) -> &HeaderMap;
fn extra_headers(&self) -> Option<&HeaderMap>;
fn camel_case(&self) -> bool { fn camel_case(&self) -> bool {
false false
} }
@ -97,14 +95,6 @@ pub(crate) trait MessageType: Sized {
} }
} }
BodySize::Sized(len) => helpers::write_content_length(len, dst), BodySize::Sized(len) => helpers::write_content_length(len, dst),
BodySize::Sized64(len) => {
if camel_case {
dst.put_slice(b"\r\nContent-Length: ");
} else {
dst.put_slice(b"\r\ncontent-length: ");
}
write!(dst.writer(), "{}\r\n", len)?;
}
BodySize::None => dst.put_slice(b"\r\n"), BodySize::None => dst.put_slice(b"\r\n"),
} }
@ -128,84 +118,144 @@ pub(crate) trait MessageType: Sized {
_ => (), _ => (),
} }
// merging headers from head and extra headers. HeaderMap::new() does not allocate.
let empty_headers = HeaderMap::new();
let extra_headers = self.extra_headers().unwrap_or(&empty_headers);
let headers = self
.headers()
.inner
.iter()
.filter(|(name, _)| !extra_headers.contains_key(*name))
.chain(extra_headers.inner.iter());
// write headers // write headers
let mut pos = 0;
let mut has_date = false; let mut has_date = false;
let mut remaining = dst.remaining_mut();
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 self.headers().inner.iter() { let mut remaining = dst.capacity() - dst.len();
// tracks bytes written since last buffer resize
// since buf is a raw pointer to a bytes container storage but is written to without the
// container's knowledge, this is used to sync the containers cursor after data is written
let mut pos = 0;
for (key, value) in headers {
match *key { match *key {
CONNECTION => continue, CONNECTION => continue,
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue,
DATE => { DATE => has_date = true,
has_date = true;
}
_ => (), _ => (),
} }
let k = key.as_str().as_bytes(); let k = key.as_str().as_bytes();
let k_len = k.len();
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();
// key length + value length + colon + space + \r\n
let len = k_len + v_len + 4;
if len > remaining { if len > remaining {
// not enough room in buffer for this header; reserve more space
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
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();
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
}
// SAFETY: on each write, it is enough to ensure that the advancement of the
// cursor matches the number of bytes written
unsafe { unsafe {
buf = &mut *(dst.bytes_mut() as *mut _);
}
}
// use upper Camel-Case // use upper Camel-Case
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[pos..pos + 2].copy_from_slice(b": "); buf = buf.add(k_len);
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v); write_data(b": ", buf, 2);
pos += v.len(); buf = buf.add(2);
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2; write_data(v, buf, v_len);
buf = buf.add(v_len);
write_data(b"\r\n", buf, 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 len = k_len + v_len + 4;
if len > remaining { if len > remaining {
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
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();
// re-assign buf raw pointer since it's possible that the buffer was
// reallocated and/or resized
buf = dst.bytes_mut().as_mut_ptr() as *mut u8;
}
// SAFETY: on each write, it is enough to ensure that the advancement of
// the cursor matches the number of bytes written
unsafe { unsafe {
buf = &mut *(dst.bytes_mut() as *mut _);
}
}
// use upper Camel-Case
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[pos..pos + 2].copy_from_slice(b": "); buf = buf.add(k_len);
pos += 2;
buf[pos..pos + v.len()].copy_from_slice(v); write_data(b": ", buf, 2);
pos += v.len(); buf = buf.add(2);
buf[pos..pos + 2].copy_from_slice(b"\r\n");
pos += 2; write_data(v, buf, v_len);
buf = buf.add(v_len);
write_data(b"\r\n", buf, 2);
buf = buf.add(2);
};
pos += len;
remaining -= len; remaining -= len;
} }
} }
} }
} }
// final cursor synchronization with the bytes container
//
// SAFETY: all the bytes written up to position "pos" are initialized
// the written byte count and pointer advancement are kept in sync
unsafe { unsafe {
dst.advance_mut(pos); dst.advance_mut(pos);
} }
@ -235,6 +285,10 @@ impl MessageType for Response<()> {
&self.head().headers &self.head().headers
} }
fn extra_headers(&self) -> Option<&HeaderMap> {
None
}
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
let head = self.head(); let head = self.head();
let reason = head.reason().as_bytes(); let reason = head.reason().as_bytes();
@ -247,35 +301,46 @@ impl MessageType for Response<()> {
} }
} }
impl MessageType for RequestHead { impl MessageType for RequestHeadType {
fn status(&self) -> Option<StatusCode> { fn status(&self) -> Option<StatusCode> {
None None
} }
fn chunked(&self) -> bool { fn chunked(&self) -> bool {
self.chunked() self.as_ref().chunked()
} }
fn camel_case(&self) -> bool { fn camel_case(&self) -> bool {
RequestHead::camel_case_headers(self) self.as_ref().camel_case_headers()
} }
fn headers(&self) -> &HeaderMap { fn headers(&self) -> &HeaderMap {
&self.headers self.as_ref().headers()
}
fn extra_headers(&self) -> Option<&HeaderMap> {
self.extra_headers()
} }
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
dst.reserve(256 + self.headers.len() * AVERAGE_HEADER_SIZE); let head = self.as_ref();
dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE);
write!( write!(
Writer(dst), Writer(dst),
"{} {} {}", "{} {} {}",
self.method, head.method,
self.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"),
match self.version { match head.version {
Version::HTTP_09 => "HTTP/0.9", Version::HTTP_09 => "HTTP/0.9",
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))
@ -308,8 +373,7 @@ impl<T: MessageType> MessageEncoder<T> {
if !head { if !head {
self.te = match length { self.te = match length {
BodySize::Empty => TransferEncoding::empty(), BodySize::Empty => TransferEncoding::empty(),
BodySize::Sized(len) => TransferEncoding::length(len as u64), BodySize::Sized(len) => TransferEncoding::length(len),
BodySize::Sized64(len) => TransferEncoding::length(len),
BodySize::Stream => { BodySize::Stream => {
if message.chunked() && !stream { if message.chunked() && !stream {
TransferEncoding::chunked() TransferEncoding::chunked()
@ -457,6 +521,13 @@ impl<'a> io::Write for Writer<'a> {
} }
} }
/// # Safety
/// Callers must ensure that the given length matches given value length.
unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) {
debug_assert_eq!(value.len(), len);
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;
@ -487,10 +558,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 http::header::AUTHORIZATION;
use super::*; use super::*;
use crate::http::header::{HeaderValue, CONTENT_TYPE}; use crate::http::header::{HeaderValue, CONTENT_TYPE};
use crate::RequestHead;
#[test] #[test]
fn test_chunked_te() { fn test_chunked_te() {
@ -501,7 +576,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")
); );
} }
@ -515,6 +590,8 @@ mod tests {
head.headers head.headers
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text")); .insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
let mut head = RequestHeadType::Owned(head);
let _ = head.encode_headers( let _ = head.encode_headers(
&mut bytes, &mut bytes,
Version::HTTP_11, Version::HTTP_11,
@ -522,10 +599,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,
@ -534,25 +613,21 @@ 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(
&mut bytes,
Version::HTTP_11,
BodySize::Sized64(100),
ConnectionType::KeepAlive,
&ServiceConfig::default(),
);
assert_eq!(
bytes.take().freeze(),
Bytes::from_static(b"\r\nContent-Length: 100\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n")
);
let mut head = RequestHead::default();
head.set_camel_case_headers(false);
head.headers.insert(DATE, HeaderValue::from_static("date"));
head.headers
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
head.headers head.headers
.append(CONTENT_TYPE, HeaderValue::from_static("xml")); .append(CONTENT_TYPE, HeaderValue::from_static("xml"));
let mut head = RequestHeadType::Owned(head);
let _ = head.encode_headers( let _ = head.encode_headers(
&mut bytes, &mut bytes,
Version::HTTP_11, Version::HTTP_11,
@ -560,22 +635,45 @@ 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]
fn test_extra_headers() {
let mut bytes = BytesMut::with_capacity(2048);
let mut head = RequestHead::default();
head.headers.insert(
AUTHORIZATION,
HeaderValue::from_static("some authorization"),
); );
head.set_camel_case_headers(false); let mut extra_headers = HeaderMap::new();
extra_headers.insert(
AUTHORIZATION,
HeaderValue::from_static("another authorization"),
);
extra_headers.insert(DATE, HeaderValue::from_static("date"));
let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers));
let _ = head.encode_headers( let _ = head.encode_headers(
&mut bytes, &mut bytes,
Version::HTTP_11, Version::HTTP_11,
BodySize::Stream, BodySize::Empty,
ConnectionType::KeepAlive, 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\ntransfer-encoding: chunked\r\ndate: date\r\ncontent-type: xml\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("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, TlsError};
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 = TlsError<HandshakeError<TcpStream>, DispatchError>,
InitError = (),
> {
pipeline_factory(
Acceptor::new(acceptor)
.map_err(TlsError::Tls)
.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(TlsError::Service))
}
}
}
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
use actix_tls::TlsError;
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 = TlsError<io::Error, DispatchError>,
InitError = (),
> {
pipeline_factory(
Acceptor::new(config)
.map_err(TlsError::Tls)
.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(TlsError::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,81 +500,82 @@ 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()),
)),
} }
} }
} }
#[doc(hidden)] #[doc(hidden)]
#[pin_project::pin_project]
pub struct OneRequestServiceResponse<T> pub struct OneRequestServiceResponse<T>
where where
T: IoStream, T: AsyncRead + AsyncWrite + Unpin,
{ {
#[pin]
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()? { let this = self.as_mut().project();
Async::Ready(Some(req)) => match req {
match ready!(this.framed.as_pin_mut().unwrap().next_item(cx)) {
Some(Ok(req)) => match req {
Message::Item(req) => { Message::Item(req) => {
Ok(Async::Ready((req, self.framed.take().unwrap()))) let mut this = self.as_mut().project();
Poll::Ready(Ok((req, this.framed.take().unwrap())))
} }
Message::Chunk(_) => unreachable!("Something is wrong"), Message::Chunk(_) => unreachable!("Something is wrong"),
}, },
Async::Ready(None) => Err(ParseError::Incomplete), Some(Err(err)) => Poll::Ready(Err(err)),
Async::NotReady => Ok(Async::NotReady), None => Poll::Ready(Err(ParseError::Incomplete)),
} }
} }
} }

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,15 +1,21 @@
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;
use crate::h1::{Codec, Message}; 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)>>,
#[pin]
body: Option<ResponseBody<B>>, body: Option<ResponseBody<B>>,
#[pin]
framed: Option<Framed<T, Codec>>, framed: Option<Framed<T, Codec>>,
} }
@ -30,63 +36,80 @@ where
impl<T, B> Future for SendResponse<T, B> impl<T, B> Future for SendResponse<T, B>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite + Unpin,
B: MessageBody, B: MessageBody + Unpin,
{ {
type Item = Framed<T, Codec>; type Output = Result<Framed<T, Codec>, Error>;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { // TODO: rethink if we need loops in polls
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
let mut body_done = this.body.is_none();
loop { loop {
let mut body_ready = self.body.is_some(); let mut body_ready = !body_done;
let framed = self.framed.as_mut().unwrap();
// send body // send body
if self.res.is_none() && self.body.is_some() { if this.res.is_none() && body_ready {
while body_ready && self.body.is_some() && !framed.is_write_buf_full() { while body_ready
match self.body.as_mut().unwrap().poll_next()? { && !body_done
Async::Ready(item) => { && !this
// body is done .framed
if item.is_none() { .as_ref()
let _ = self.body.take(); .as_pin_ref()
.unwrap()
.is_write_buf_full()
{
match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx)? {
Poll::Ready(item) => {
// body is done when item is None
body_done = item.is_none();
if body_done {
let _ = this.body.take();
} }
framed.force_send(Message::Chunk(item))?; let framed = this.framed.as_mut().as_pin_mut().unwrap();
framed.write(Message::Chunk(item))?;
} }
Async::NotReady => body_ready = false, Poll::Pending => body_ready = false,
} }
} }
} }
let framed = this.framed.as_mut().as_pin_mut().unwrap();
// 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 !body_done {
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()))
let framed = this.framed.take().unwrap();
Poll::Ready(Ok(framed))
} }
} }

View File

@ -1,28 +1,25 @@
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::message::ResponseHead; use crate::message::ResponseHead;
use crate::payload::Payload; use crate::payload::Payload;
use crate::request::Request; use crate::request::Request;
@ -31,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>>,
@ -44,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>,
@ -90,70 +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;
tokio_current_thread::spawn(ServiceResponse::<S::Future, B> {
// set on_connect data
if let Some(ref on_connect) = this.on_connect {
on_connect.set(&mut req.extensions_mut());
}
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(project = ServiceResponseStateProj)]
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>, #[pin] 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,
@ -187,10 +210,6 @@ where
CONTENT_LENGTH, CONTENT_LENGTH,
HeaderValue::try_from(format!("{}", len)).unwrap(), HeaderValue::try_from(format!("{}", len)).unwrap(),
), ),
BodySize::Sized64(len) => res.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(format!("{}", len)).unwrap(),
),
}; };
// copy headers // copy headers
@ -208,122 +227,137 @@ 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(
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); DATE,
// SAFETY: serialized date-times are known ASCII strings
unsafe { 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> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.state { let mut this = self.as_mut().project();
ServiceResponseState::ServiceCall(ref mut call, ref mut send) => {
match call.poll() { match this.state.project() {
Ok(Async::Ready(res)) => { ServiceResponseStateProj::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
self.poll() .set(ServiceResponseState::SendPayload(stream, body));
self.poll(cx)
} }
} }
Ok(Async::NotReady) => Ok(Async::NotReady), Poll::Pending => Poll::Pending,
Err(_e) => { Poll::Ready(Err(e)) => {
let res: Response = Response::InternalServerError().finish(); 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)
} }
} }
} },
} ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => {
ServiceResponseState::SendPayload(ref mut stream, ref mut body) => loop {
loop { loop {
if let Some(ref mut buffer) = self.buffer { loop {
match stream.poll_capacity().map_err(|e| warn!("{:?}", e))? { if let Some(ref mut buffer) = this.buffer {
Async::NotReady => return Ok(Async::NotReady), match stream.poll_capacity(cx) {
Async::Ready(None) => return Ok(Async::Ready(())), Poll::Pending => return Poll::Pending,
Async::Ready(Some(cap)) => { Poll::Ready(None) => return Poll::Ready(()),
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))) => {
} else {
match body.poll_next() {
Ok(Async::NotReady) => {
return Ok(Async::NotReady);
}
Ok(Async::Ready(None)) => {
if let Err(e) = stream.send_data(Bytes::new(), true) {
warn!("{:?}", e); warn!("{:?}", e);
return Err(()); return Poll::Ready(());
}
}
} else { } else {
return Ok(Async::Ready(())); match body.as_mut().poll_next(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => {
if let Err(e) = stream.send_data(Bytes::new(), true)
{
warn!("{:?}", e);
} }
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,142 @@ 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, TlsError};
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 = TlsError<HandshakeError<TcpStream>, DispatchError>,
InitError = S::InitError,
> {
pipeline_factory(
Acceptor::new(acceptor)
.map_err(TlsError::Tls)
.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(TlsError::Service))
}
}
}
#[cfg(feature = "rustls")]
mod rustls {
use super::*;
use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream};
use actix_tls::TlsError;
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 = TlsError<io::Error, DispatchError>,
InitError = S::InitError,
> {
let protos = vec!["h2".to_string().into()];
config.set_protocols(&protos);
pipeline_factory(
Acceptor::new(config)
.map_err(TlsError::Tls)
.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(TlsError::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 +209,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 +273,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 +306,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 +314,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 +331,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 +343,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 +371,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"]);
} }
} }

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