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

Compare commits

...

92 Commits

Author SHA1 Message Date
Nikolay Kim
3d1af19080 prepare actix-http release 2019-05-04 19:51:13 -07:00
Nikolay Kim
fa78da8156 unify route and app data, it allows to provide global extractor config #775 2019-05-04 19:43:49 -07:00
Nikolay Kim
01cfcf3b75 update changes 2019-05-04 08:42:27 -07:00
James
7ef4f5ac0b Make request headers optional in CORS preflight (#816) 2019-05-04 08:41:37 -07:00
Nikolay Kim
fc19ce41c4 Clean up response extensions in response pool #817 2019-05-03 15:26:34 -07:00
Otavio Salvador
6e00eef63a awc: Fix typo on ResponseError documentation (#815)
* awc: Fix typo on ResponseError documentation

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>

* http: Fix typo on ResponseError documentation

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>

* http: Expand type names for openssl related errors documentation

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
2019-05-03 14:30:00 -07:00
Nikolay Kim
337c2febe3 add more tests 2019-05-02 09:49:10 -07:00
Nikolay Kim
f27beab016 fix case for transfer-encoding header name 2019-05-02 09:30:00 -07:00
Max Bo
4f1c6d1bb7 Update MIGRATION.md (#811) 2019-05-02 09:26:51 -07:00
Nikolay Kim
6b34909537 Fix NormalizePath middleware impl #806 2019-05-01 12:40:56 -07:00
Douman
87284f0951 Add doctest to verify NormalizePath middleware (#809) 2019-05-01 11:47:51 -07:00
Nikolay Kim
24bd5b1344 update readmes 2019-04-29 20:47:21 -07:00
Nikolay Kim
94a0d1a6bc remove old api doc refs 2019-04-29 18:42:21 -07:00
Nikolay Kim
f4e1205cbb fix reactor drop panic 2019-04-29 10:14:08 -07:00
Nikolay Kim
d2c1791067 add async handler test with blocking call 2019-04-29 09:45:37 -07:00
Nikolay Kim
f4b4875cb1 Add helper function for executing futures test::block_fn() 2019-04-29 09:34:14 -07:00
Nikolay Kim
29a841529f Allow to construct Data instances to avoid double Arc for Send + Sync types. 2019-04-29 09:26:12 -07:00
Darin
b51b5b763c added clarification to docs regarding middleware processing sequence, added delete method to TestRequest (#799)
* added clarification to docs regarding middleware processing sequnce

* added delete method to TestRequest, doc, and test
2019-04-29 09:14:36 -07:00
Nikolay Kim
8db6b48a76 update version 2019-04-28 09:09:18 -07:00
Nikolay Kim
ffd2c04cd3 Add helper trait UserSession which allows to get session for ServiceRequest and HttpRequest 2019-04-28 09:08:51 -07:00
Nikolay Kim
70a4c36496 use Error explicitly 2019-04-25 11:14:32 -07:00
Nikolay Kim
cba78e06ae update changes 2019-04-24 15:42:34 -07:00
Nikolay Kim
3b3dbb4f40 add raw services support 2019-04-24 15:29:15 -07:00
Darin
7300002226 grammar fixes (#796) 2019-04-24 13:21:42 -07:00
Nikolay Kim
5426413cb6 update dependencies 2019-04-24 13:00:30 -07:00
Nikolay Kim
2bc937f6c3 prepare release 2019-04-24 12:50:44 -07:00
Maciej Piechotka
60fa0d5427 Store visit and login timestamp in the identity cookie (#502)
This allows to verify time of login or last visit and therfore limiting
the danger of leaked cookies.
2019-04-24 12:49:56 -07:00
Nikolay Kim
f429d3319f Read until eof for http/1.0 responses #771 2019-04-24 11:57:40 -07:00
Nikolay Kim
2e19f572ee add tests for camel case headers rendering 2019-04-24 11:27:57 -07:00
Peter Ding
64f603b076 Support to set header names of ClientRequest as Camel-Case (#713)
* Support to set header names of `ClientRequest` as Camel-Case

This is the case for supporting to request for servers which don't
perfectly implement the `RFC 7230`. It is important for an app
which uses `ClientRequest` as core part.

* Add field `upper_camel_case_headers` to `ClientRequest`.

* Add function `set_upper_camel_case_headers` to `ClientRequest`
  and `ClientRequestBuilder` to set field `upper_camel_case_headers`.

* Add trait `client::writer::UpperCamelCaseHeader` for
  `http::header::HeaderName`, let it can be converted to Camel-Case
  then writed to buffer.

* Add test `test_client::test_upper_camel_case_headers`.

* Support upper Camel-Case headers

* [actix-http] Add field `upper_camel_case_headers` for `RequestHead`
* [actix-http] Add code for `MessageType` to support upper camel case
* [awc] Add functions for `ClientRequest` to set upper camel case

* Use `Flags::CAMEL_CASE` for upper camel case of headers
2019-04-24 10:48:49 -07:00
Nikolay Kim
679d1cd513 allow to override responder's status code and headers 2019-04-24 10:25:46 -07:00
Nikolay Kim
42644dac3f prepare actix-http-test release 2019-04-24 07:31:33 -07:00
Nikolay Kim
898ef57080 Fix async web::Data factory handling 2019-04-23 21:21:49 -07:00
Nikolay Kim
9702b2d88e add client h2 reuse test 2019-04-23 15:06:30 -07:00
Nikolay Kim
d2b0afd859 Fix http client pool and wait queue management 2019-04-23 14:57:03 -07:00
Nikolay Kim
5f6a1a8249 update version 2019-04-23 09:45:39 -07:00
Nikolay Kim
5d531989e7 Fix BorrowMutError panic in client connector #793 2019-04-23 09:42:19 -07:00
Nikolay Kim
3532602299 Added support for remainder match (i.e /path/{tail}*) 2019-04-22 21:22:17 -07:00
Nikolay Kim
48bee55087 .to_async() handler can return Responder type #792 2019-04-22 14:22:08 -07:00
Nikolay Kim
d00c9bb844 do not consume boundary 2019-04-21 16:14:09 -07:00
Nikolay Kim
895e409d57 Optimize multipart handling #634, #769 2019-04-21 15:41:01 -07:00
Nikolay Kim
f0789aad05 update dep versions 2019-04-21 09:03:46 -07:00
Nikolay Kim
7e480ab2f7 beta.1 release 2019-04-20 21:16:51 -07:00
Nikolay Kim
891f857547 update changes 2019-04-20 11:18:04 -07:00
Nikolay Kim
01b1350dcc update versions 2019-04-19 18:16:01 -07:00
Nikolay Kim
5e4e95fb0a update create version 2019-04-19 18:13:05 -07:00
Nikolay Kim
9f421b81b8 fix non-ssl connector 2019-04-19 18:10:53 -07:00
Nikolay Kim
6decfdda1f update deps 2019-04-19 18:06:34 -07:00
Nikolay Kim
fc9b14a933 allow to specify server address for http and ws requests 2019-04-19 18:03:44 -07:00
Nikolay Kim
7292d0b696 drop chrono and use i64 for max age 2019-04-19 17:23:17 -07:00
Nikolay Kim
a3844c1bfd update version 2019-04-19 13:55:36 -07:00
Kilerd Chan
791f22bbc8 replate time::Duration with chrono::Duration and add max_age_time method (#789)
* feat: replate time::Duration with chrono::Duration

* feat: rename max_age method which accepts `Duration` to max_age_time and add new max_age method accepting isize of seconds

* feat: replace `time:Duration` with `chrono:Duration` in repo `actix-http`
2019-04-19 13:54:44 -07:00
Douman
1e7f97a111 Add Normalization middleware for in place (#783) 2019-04-19 13:53:49 -07:00
Darin
bc40f5ae40 Merge pull request #788 from Dowwie/master
added put and patch to TestRequest, docs, and test
2019-04-19 06:55:44 -04:00
Darin
3504a8fc0a Merge branch 'master' into master 2019-04-19 04:38:41 -04:00
Nikolay Kim
bfe0df5ab0 update tests 2019-04-18 21:28:23 -07:00
Darin
ed94df189f Merge branch 'master' into master 2019-04-18 19:03:48 -04:00
Nikolay Kim
aa255298ef make ServiceRequest::from_parts private, as it is not safe to create from parts 2019-04-18 16:03:13 -07:00
dowwie
da86b6e062 added put and patch to TestRequest, docs, and test 2019-04-18 18:06:32 -04:00
Nikolay Kim
75e340137d use local version of http-test 2019-04-18 12:23:56 -07:00
Nikolay Kim
e659e09e29 update tests 2019-04-18 11:01:04 -07:00
Nikolay Kim
163ca89cf4 more tests 2019-04-17 17:48:25 -07:00
Nikolay Kim
85b598a614 add cookie session test 2019-04-17 11:02:03 -07:00
Nikolay Kim
b64851c5ec enable runtime for test:: methods 2019-04-17 10:28:27 -07:00
Nikolay Kim
cc8420377e pass request ownership to closure instead of ref 2019-04-16 15:43:55 -07:00
Nikolay Kim
5740f1e63a prepare actix-framed release 2019-04-16 11:18:47 -07:00
Nikolay Kim
c943e95812 update dependencies 2019-04-16 11:17:29 -07:00
Nikolay Kim
4c0ebd55d3 prepare actix-http-test release 2019-04-16 11:02:26 -07:00
Nikolay Kim
e7ec77aa81 update readme 2019-04-16 10:50:37 -07:00
Nikolay Kim
ddfd7523f7 prepare awc release 2019-04-16 10:49:38 -07:00
Nikolay Kim
2986077a28 no need for feature 2019-04-16 10:32:48 -07:00
Nikolay Kim
3744957804 actix_http::encoding always available 2019-04-16 10:27:58 -07:00
Nikolay Kim
420d3064c5 Add .peer_addr() #744 2019-04-16 10:11:38 -07:00
Nikolay Kim
eb4f6b74fb Merge branch 'master' of github.com:actix/actix-web 2019-04-16 09:58:07 -07:00
Nikolay Kim
a116c4c2c7 Expose peer addr via Request::peer_addr() and RequestHead::peer_addr 2019-04-16 09:54:02 -07:00
Travis Harmon
7f674febb1 add 422 to httpcodes.rs (#782) 2019-04-15 16:55:06 -07:00
Nikolay Kim
14252f5ef2 use test::call_service 2019-04-15 09:09:21 -07:00
Nikolay Kim
7a28b32f6d Rename test::call_success to test::call_service 2019-04-15 07:44:07 -07:00
Nikolay Kim
09cdf1e302 Rename RouterConfig to ServiceConfig 2019-04-15 07:32:49 -07:00
Nikolay Kim
1eebd47072 fix warnings 2019-04-14 21:00:16 -07:00
Nikolay Kim
002c41a7ca update trust-dns 2019-04-14 20:45:44 -07:00
Nikolay Kim
ab4fda6084 update tests 2019-04-14 20:20:33 -07:00
Nikolay Kim
f9078d41cd add test::read_response; fix TestRequest::app_data() 2019-04-14 19:52:12 -07:00
Darin
4cc2b38059 added read_response_json for testing (#776)
* added read_response_json for testing

* cleaned up

* modied docs for read_response_json

* typo in doc

* test code in doc should compile now

* use type coercion in doc

* removed generic R, replaced with Request
2019-04-14 16:25:45 -07:00
Nikolay Kim
d7040dc303 alpha.6 release 2019-04-14 08:09:32 -07:00
Nikolay Kim
6bc1a0c76b Do not set default headers for websocket request 2019-04-14 07:43:53 -07:00
Nikolay Kim
5bd5651faa Allow to use any service as default service 2019-04-13 22:25:00 -07:00
Nikolay Kim
32ac159ba2 update migration 2019-04-13 16:51:41 -07:00
Nikolay Kim
ee33f52736 make extractor config type explicit 2019-04-13 16:35:25 -07:00
Nikolay Kim
4f30fa9d46 Remove generic type for request payload, always use default 2019-04-13 14:50:54 -07:00
Nikolay Kim
043f6e77ae remove nested multipart support 2019-04-13 10:11:07 -07:00
Nikolay Kim
48518df883 do not generate all docs; use docs.rs for 1.0 docs 2019-04-13 09:35:23 -07:00
107 changed files with 5139 additions and 2661 deletions

View File

@@ -42,7 +42,7 @@ script:
after_success: after_success:
- | - |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --all-features && cargo doc --no-deps --all-features &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html && echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import.git && 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 && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&

View File

@@ -1,5 +1,95 @@
# Changes # Changes
## [1.0.0-beta.3] - 2019-05-04
### Added
* Add helper function for executing futures `test::block_fn()`
### Changed
* Extractor configuration could be registered with `App::data()`
or with `Resource::data()` #775
* Route data is unified with app data, `Route::data()` moved to resource
level to `Resource::data()`
* CORS handling without headers #702
* Allow to construct `Data` instances to avoid double `Arc` for `Send + Sync` types.
### Fixed
* Fix `NormalizePath` middleware impl #806
### Deleted
* `App::data_factory()` is deleted.
## [1.0.0-beta.2] - 2019-04-24
### Added
* Add raw services support via `web::service()`
* Add helper functions for reading response body `test::read_body()`
* Add support for `remainder match` (i.e "/path/{tail}*")
* Extend `Responder` trait, allow to override status code and headers.
* Store visit and login timestamp in the identity cookie #502
### Changed
* `.to_async()` handler can return `Responder` type #792
### Fixed
* Fix async web::Data factory handling
## [1.0.0-beta.1] - 2019-04-20
### Added
* Add helper functions for reading test response body,
`test::read_response()` and test::read_response_json()`
* Add `.peer_addr()` #744
* Add `NormalizePath` middleware
### Changed
* Rename `RouterConfig` to `ServiceConfig`
* Rename `test::call_success` to `test::call_service`
* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts.
* `CookieIdentityPolicy::max_age()` accepts value in seconds
### Fixed
* Fixed `TestRequest::app_data()`
## [1.0.0-alpha.6] - 2019-04-14
### Changed
* Allow to use any service as default service.
* Remove generic type for request payload, always use default.
* Removed `Decompress` middleware. Bytes, String, Json, Form extractors
automatically decompress payload.
* Make extractor config type explicit. Add `FromRequest::Config` associated type.
## [1.0.0-alpha.5] - 2019-04-12 ## [1.0.0-alpha.5] - 2019-04-12
### Added ### Added

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "1.0.0-alpha.5" version = "1.0.0-beta.2"
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"
@@ -15,6 +15,9 @@ license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018" edition = "2018"
[package.metadata.docs.rs]
features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"]
[badges] [badges]
travis-ci = { repository = "actix/actix-web", branch = "master" } travis-ci = { repository = "actix/actix-web", branch = "master" }
codecov = { repository = "actix/actix-web", branch = "master", service = "github" } codecov = { repository = "actix/actix-web", branch = "master", service = "github" }
@@ -37,9 +40,6 @@ members = [
"test-server", "test-server",
] ]
[package.metadata.docs.rs]
features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"]
[features] [features]
default = ["brotli", "flate2-zlib", "secure-cookies", "client"] default = ["brotli", "flate2-zlib", "secure-cookies", "client"]
@@ -68,20 +68,21 @@ rust-tls = ["rustls", "actix-server/rust-tls"]
actix-codec = "0.1.2" actix-codec = "0.1.2"
actix-service = "0.3.6" actix-service = "0.3.6"
actix-utils = "0.3.4" actix-utils = "0.3.4"
actix-router = "0.1.2" actix-router = "0.1.3"
actix-rt = "0.2.2" actix-rt = "0.2.2"
actix-web-codegen = "0.1.0-alpha.1" actix-web-codegen = "0.1.0-beta.1"
actix-http = { version = "0.1.0-alpha.5", features=["fail"] } actix-http = { version = "0.1.4", features=["fail"] }
actix-server = "0.4.2" actix-server = "0.4.3"
actix-server-config = "0.1.0" actix-server-config = "0.1.1"
actix-threadpool = "0.1.0" actix-threadpool = "0.1.0"
awc = { version = "0.1.0-alpha.5", optional = true } actix = { version = "0.8.1", features=["http"], optional = true }
awc = { version = "0.1.1", optional = true }
bytes = "0.4" bytes = "0.4"
derive_more = "0.14" derive_more = "0.14"
encoding = "0.2" encoding = "0.2"
futures = "0.1" futures = "0.1"
hashbrown = "0.1.8" hashbrown = "0.2.2"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
net2 = "0.2.33" net2 = "0.2.33"
@@ -90,7 +91,7 @@ regex = "1.0"
serde = { version = "1.0", features=["derive"] } serde = { version = "1.0", features=["derive"] }
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.5.3" serde_urlencoded = "0.5.3"
time = "0.1" time = "0.1.42"
url = { version="1.7", features=["query_encoding"] } url = { version="1.7", features=["query_encoding"] }
# ssl support # ssl support
@@ -98,8 +99,9 @@ openssl = { version="0.10", optional = true }
rustls = { version = "^0.15", optional = true } rustls = { version = "^0.15", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http = { version = "0.1.0-alpha.5", features=["ssl", "brotli", "flate2-zlib"] } actix-http = { version = "0.1.4", features=["ssl", "brotli", "flate2-zlib"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-http-test = { version = "0.1.1", features=["ssl"] }
actix-files = { version = "0.1.0-beta.1" }
rand = "0.6" rand = "0.6"
env_logger = "0.6" env_logger = "0.6"
serde_derive = "1.0" serde_derive = "1.0"
@@ -120,4 +122,4 @@ actix-web-codegen = { path = "actix-web-codegen" }
actix-web-actors = { path = "actix-web-actors" } actix-web-actors = { path = "actix-web-actors" }
actix-session = { path = "actix-session" } actix-session = { path = "actix-session" }
actix-files = { path = "actix-files" } actix-files = { path = "actix-files" }
awc = { path = "awc" } awc = { path = "awc" }

View File

@@ -1,7 +1,7 @@
## 1.0 ## 1.0
* Resource registration. 1.0 version uses generalized resource * Resource registration. 1.0 version uses generalized resource
registration via `.service()` method. registration via `.service()` method.
instead of instead of
@@ -44,9 +44,41 @@ registration via `.service()` method.
); );
``` ```
* `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`.
instead of
```rust
App.new().resource("/welcome", |r| r.with(welcome))
```
use `.to()` or `.to_async()` methods
```rust
App.new().service(web::resource("/welcome").to(welcome))
```
* Passing arguments to handler with extractors, multiple arguments are allowed
instead of
```rust
fn welcome((body, req): (Bytes, HttpRequest)) -> ... {
...
}
```
use multiple arguments
```rust
fn welcome(body: Bytes, req: HttpRequest) -> ... {
...
}
```
* `.f()`, `.a()` and `.h()` handler registration methods have been removed. * `.f()`, `.a()` and `.h()` handler registration methods have been removed.
Use `.to()` for handlers and `.to_async()` for async handlers. Handler function Use `.to()` for handlers and `.to_async()` for async handlers. Handler function
must use extractors. must use extractors.
instead of instead of
@@ -61,8 +93,8 @@ must use extractors.
``` ```
* `State` is now `Data`. You register Data during the App initialization process * `State` is now `Data`. You register Data during the App initialization process
and then access it from handlers either using a Data extractor or using and then access it from handlers either using a Data extractor or using
HttpRequest's api. HttpRequest's api.
instead of instead of
@@ -96,7 +128,7 @@ HttpRequest's api.
``` ```
* AsyncResponder is removed. * AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type.
instead of instead of
@@ -132,7 +164,7 @@ HttpRequest's api.
* `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()` * `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()`
method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead. method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead.
instead if instead of
```rust ```rust
fn index(req: &HttpRequest) -> Responder { fn index(req: &HttpRequest) -> Responder {
@@ -141,6 +173,7 @@ HttpRequest's api.
... ...
}) })
} }
```
use use
@@ -150,6 +183,8 @@ HttpRequest's api.
} }
``` ```
* `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 has been move to separate create.
instead of `use actix_web::fs::StaticFile` instead of `use actix_web::fs::StaticFile`
@@ -166,10 +201,10 @@ HttpRequest's api.
use `use actix_multipart::Multipart` use `use actix_multipart::Multipart`
* Request/response compression/decompression is not enabled by default. * Response compression is not enabled by default.
To enable use `App::enable_encoding()` method. To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`.
* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type * Session middleware moved to actix-session crate
* Actors support have been moved to `actix-web-actors` crate * Actors support have been moved to `actix-web-actors` crate

View File

@@ -18,8 +18,8 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
## Documentation & community resources ## Documentation & community resources
* [User Guide](https://actix.rs/docs/) * [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) * [API Documentation (1.0)](https://docs.rs/actix-web/)
* [API Documentation (0.7 Release)](https://docs.rs/actix-web/0.7.19/actix_web/) * [API Documentation (0.7)](https://docs.rs/actix-web/0.7.19/actix_web/)
* [Chat on gitter](https://gitter.im/actix/actix) * [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web](https://crates.io/crates/actix-web) * Cargo package: [actix-web](https://crates.io/crates/actix-web)
* Minimum supported Rust version: 1.32 or later * Minimum supported Rust version: 1.32 or later
@@ -36,8 +36,7 @@ fn index(info: web::Path<(u32, String)>) -> impl Responder {
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
HttpServer::new( HttpServer::new(
|| App::new().service( || App::new().service(
web::resource("/{id}/{name}/index.html") web::resource("/{id}/{name}/index.html").to(index)))
.route(web::get().to(index))))
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .run()
} }

View File

@@ -1,15 +1,21 @@
# Changes # Changes
## [0.1.0-beta.1] - 2019-04-20
* Update actix-web to beta.1
## [0.1.0-alpha.6] - 2019-04-14
* Update actix-web to alpha6
## [0.1.0-alpha.4] - 2019-04-08 ## [0.1.0-alpha.4] - 2019-04-08
* Update actix-web to alpha4 * Update actix-web to alpha4
## [0.1.0-alpha.2] - 2019-04-02 ## [0.1.0-alpha.2] - 2019-04-02
* Add default handler support * Add default handler support
## [0.1.0-alpha.1] - 2019-03-28 ## [0.1.0-alpha.1] - 2019-03-28
* Initial impl * Initial impl

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.1.0-alpha.4" version = "0.1.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web." description = "Static files support for actix web."
readme = "README.md" readme = "README.md"
@@ -18,7 +18,7 @@ name = "actix_files"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = "1.0.0-alpha.5" actix-web = "1.0.0-beta.1"
actix-service = "0.3.4" actix-service = "0.3.4"
bitflags = "1" bitflags = "1"
bytes = "0.4" bytes = "0.4"
@@ -31,4 +31,4 @@ percent-encoding = "1.0"
v_htmlescape = "0.4" v_htmlescape = "0.4"
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.0-alpha.5", features=["ssl"] } actix-web = { version = "1.0.0-beta.1", features=["ssl"] }

View File

@@ -1 +1,9 @@
# Static files support 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-files)](https://crates.io/crates/actix-files) [![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) # Static files support 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-files)](https://crates.io/crates/actix-files) [![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-files/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-files](https://crates.io/crates/actix-files)
* Minimum supported Rust version: 1.33 or later

View File

@@ -10,7 +10,7 @@ use std::{cmp, io};
use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{IntoNewService, NewService, Service};
use actix_web::dev::{ use actix_web::dev::{
HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceRequest, AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest,
ServiceResponse, ServiceResponse,
}; };
use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
@@ -32,9 +32,8 @@ use self::error::{FilesError, UriSegmentError};
pub use crate::named::NamedFile; pub use crate::named::NamedFile;
pub use crate::range::HttpRange; pub use crate::range::HttpRange;
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>; type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService<P> = type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
/// Return the MIME type associated with a filename extension (case-insensitive). /// Return the MIME type associated with a filename extension (case-insensitive).
/// If `ext` is empty or no associated type for the extension was found, returns /// If `ext` is empty or no associated type for the extension was found, returns
@@ -225,18 +224,18 @@ type MimeOverride = Fn(&mime::Name) -> DispositionType;
/// .service(fs::Files::new("/static", ".")); /// .service(fs::Files::new("/static", "."));
/// } /// }
/// ``` /// ```
pub struct Files<S> { pub struct Files {
path: String, path: String,
directory: PathBuf, directory: PathBuf,
index: Option<String>, index: Option<String>,
show_index: bool, show_index: bool,
default: Rc<RefCell<Option<Rc<HttpNewService<S>>>>>, default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags, file_flags: named::Flags,
} }
impl<S> Clone for Files<S> { impl Clone for Files {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
directory: self.directory.clone(), directory: self.directory.clone(),
@@ -251,13 +250,13 @@ impl<S> Clone for Files<S> {
} }
} }
impl<S: 'static> Files<S> { impl Files {
/// Create new `Files` instance for specified base directory. /// Create new `Files` instance for specified base directory.
/// ///
/// `File` uses `ThreadPool` for blocking filesystem operations. /// `File` uses `ThreadPool` for blocking filesystem operations.
/// By default pool with 5x threads of available cpus is used. /// By default pool with 5x threads of available cpus is used.
/// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable.
pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files<S> { pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files {
let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new()); let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new());
if !dir.is_dir() { if !dir.is_dir() {
log::error!("Specified path is not a directory"); log::error!("Specified path is not a directory");
@@ -335,7 +334,7 @@ impl<S: 'static> Files<S> {
where where
F: IntoNewService<U>, F: IntoNewService<U>,
U: NewService< U: NewService<
Request = ServiceRequest<S>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
> + 'static, > + 'static,
@@ -349,11 +348,8 @@ impl<S: 'static> Files<S> {
} }
} }
impl<P> HttpServiceFactory<P> for Files<P> impl HttpServiceFactory for Files {
where fn register(self, config: &mut AppService) {
P: 'static,
{
fn register(self, config: &mut ServiceConfig<P>) {
if self.default.borrow().is_none() { if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service()); *self.default.borrow_mut() = Some(config.default_service());
} }
@@ -366,11 +362,11 @@ where
} }
} }
impl<P: 'static> NewService for Files<P> { impl NewService for Files {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Service = FilesService<P>; type Service = FilesService;
type InitError = (); type InitError = ();
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>; type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
@@ -401,37 +397,36 @@ impl<P: 'static> NewService for Files<P> {
} }
} }
pub struct FilesService<P> { pub struct FilesService {
directory: PathBuf, directory: PathBuf,
index: Option<String>, index: Option<String>,
show_index: bool, show_index: bool,
default: Option<HttpService<P>>, default: Option<HttpService>,
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags, file_flags: named::Flags,
} }
impl<P> FilesService<P> { impl FilesService {
fn handle_err( fn handle_err(
&mut self, &mut self,
e: io::Error, e: io::Error,
req: HttpRequest, req: ServiceRequest,
payload: Payload<P>,
) -> Either< ) -> Either<
FutureResult<ServiceResponse, Error>, FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>, Box<Future<Item = ServiceResponse, Error = Error>>,
> { > {
log::debug!("Files: Failed to handle {}: {}", req.path(), e); log::debug!("Files: Failed to handle {}: {}", req.path(), e);
if let Some(ref mut default) = self.default { if let Some(ref mut default) = self.default {
default.call(ServiceRequest::from_parts(req, payload)) default.call(req)
} else { } else {
Either::A(ok(ServiceResponse::from_err(e, req.clone()))) Either::A(ok(req.error_response(e)))
} }
} }
} }
impl<P> Service for FilesService<P> { impl Service for FilesService {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = Either< type Future = Either<
@@ -443,18 +438,18 @@ impl<P> Service for FilesService<P> {
Ok(Async::Ready(())) Ok(Async::Ready(()))
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let (req, pl) = req.into_parts(); // let (req, pl) = req.into_parts();
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) { let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
Ok(item) => item, Ok(item) => item,
Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req.clone()))), Err(e) => return Either::A(ok(req.error_response(e))),
}; };
// full filepath // full filepath
let path = match self.directory.join(&real_path.0).canonicalize() { let path = match self.directory.join(&real_path.0).canonicalize() {
Ok(path) => path, Ok(path) => path,
Err(e) => return self.handle_err(e, req, pl), Err(e) => return self.handle_err(e, req),
}; };
if path.is_dir() { if path.is_dir() {
@@ -470,24 +465,26 @@ impl<P> Service for FilesService<P> {
} }
named_file.flags = self.file_flags; named_file.flags = self.file_flags;
let (req, _) = req.into_parts();
Either::A(ok(match named_file.respond_to(&req) { Either::A(ok(match named_file.respond_to(&req) {
Ok(item) => ServiceResponse::new(req.clone(), item), Ok(item) => ServiceResponse::new(req, item),
Err(e) => ServiceResponse::from_err(e, req.clone()), Err(e) => ServiceResponse::from_err(e, req),
})) }))
} }
Err(e) => return self.handle_err(e, req, pl), Err(e) => return self.handle_err(e, req),
} }
} else if self.show_index { } else if self.show_index {
let dir = Directory::new(self.directory.clone(), path); let dir = Directory::new(self.directory.clone(), path);
let (req, _) = req.into_parts();
let x = (self.renderer)(&dir, &req); let x = (self.renderer)(&dir, &req);
match x { match x {
Ok(resp) => Either::A(ok(resp)), Ok(resp) => Either::A(ok(resp)),
Err(e) => return self.handle_err(e, req, pl), Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req))),
} }
} else { } else {
Either::A(ok(ServiceResponse::from_err( Either::A(ok(ServiceResponse::from_err(
FilesError::IsDirectory, FilesError::IsDirectory,
req.clone(), req.into_parts().0,
))) )))
} }
} else { } else {
@@ -500,16 +497,15 @@ impl<P> Service for FilesService<P> {
} }
named_file.flags = self.file_flags; named_file.flags = self.file_flags;
let (req, _) = req.into_parts();
match named_file.respond_to(&req) { match named_file.respond_to(&req) {
Ok(item) => { Ok(item) => {
Either::A(ok(ServiceResponse::new(req.clone(), item))) Either::A(ok(ServiceResponse::new(req.clone(), item)))
} }
Err(e) => { Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))),
Either::A(ok(ServiceResponse::from_err(e, req.clone())))
}
} }
} }
Err(e) => self.handle_err(e, req, pl), Err(e) => self.handle_err(e, req),
} }
} }
} }
@@ -547,11 +543,12 @@ impl PathBufWrp {
} }
} }
impl<P> FromRequest<P> for PathBufWrp { impl FromRequest for PathBufWrp {
type Error = UriSegmentError; type Error = UriSegmentError;
type Future = Result<Self, Self::Error>; type Future = Result<Self, Self::Error>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
PathBufWrp::get_pathbuf(req.match_info().path()) PathBufWrp::get_pathbuf(req.match_info().path())
} }
} }
@@ -570,6 +567,7 @@ mod tests {
self, ContentDisposition, DispositionParam, DispositionType, self, ContentDisposition, DispositionParam, DispositionType,
}; };
use actix_web::http::{Method, StatusCode}; use actix_web::http::{Method, StatusCode};
use actix_web::middleware::Compress;
use actix_web::test::{self, TestRequest}; use actix_web::test::{self, TestRequest};
use actix_web::App; use actix_web::App;
@@ -777,7 +775,7 @@ mod tests {
); );
let request = TestRequest::get().uri("/").to_request(); let request = TestRequest::get().uri("/").to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let content_disposition = response let content_disposition = response
@@ -801,7 +799,7 @@ mod tests {
.uri("/t%65st/Cargo.toml") .uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-20")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
// Invalid range header // Invalid range header
@@ -809,7 +807,7 @@ mod tests {
.uri("/t%65st/Cargo.toml") .uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=1-0") .header(header::RANGE, "bytes=1-0")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
} }
@@ -826,7 +824,7 @@ mod tests {
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-20")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
let contentrange = response let contentrange = response
.headers() .headers()
.get(header::CONTENT_RANGE) .get(header::CONTENT_RANGE)
@@ -841,7 +839,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-5") .header(header::RANGE, "bytes=10-5")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
let contentrange = response let contentrange = response
.headers() .headers()
@@ -864,7 +862,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-20")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
let contentlength = response let contentlength = response
.headers() .headers()
@@ -880,7 +878,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-8") .header(header::RANGE, "bytes=10-8")
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
// Without range header // Without range header
@@ -888,7 +886,7 @@ mod tests {
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
// .no_default_headers() // .no_default_headers()
.to_request(); .to_request();
let response = test::call_success(&mut srv, request); let response = test::call_service(&mut srv, request);
let contentlength = response let contentlength = response
.headers() .headers()
@@ -903,7 +901,7 @@ mod tests {
let request = TestRequest::get() let request = TestRequest::get()
.uri("/t%65st/tests/test.binary") .uri("/t%65st/tests/test.binary")
.to_request(); .to_request();
let mut response = test::call_success(&mut srv, request); let mut response = test::call_service(&mut srv, request);
// with enabled compression // with enabled compression
// { // {
@@ -934,7 +932,7 @@ mod tests {
let request = TestRequest::get() let request = TestRequest::get()
.uri("/tests/test%20space.binary") .uri("/tests/test%20space.binary")
.to_request(); .to_request();
let mut response = test::call_success(&mut srv, request); let mut response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let bytes = let bytes =
@@ -965,7 +963,7 @@ mod tests {
#[test] #[test]
fn test_named_file_content_encoding() { fn test_named_file_content_encoding() {
let mut srv = test::init_service(App::new().enable_encoding().service( let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| { web::resource("/").to(|| {
NamedFile::open("Cargo.toml") NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
@@ -977,14 +975,14 @@ mod tests {
.uri("/") .uri("/")
.header(header::ACCEPT_ENCODING, "gzip") .header(header::ACCEPT_ENCODING, "gzip")
.to_request(); .to_request();
let res = test::call_success(&mut srv, request); let res = test::call_service(&mut srv, request);
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
} }
#[test] #[test]
fn test_named_file_content_encoding_gzip() { fn test_named_file_content_encoding_gzip() {
let mut srv = test::init_service(App::new().enable_encoding().service( let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| { web::resource("/").to(|| {
NamedFile::open("Cargo.toml") NamedFile::open("Cargo.toml")
.unwrap() .unwrap()
@@ -996,7 +994,7 @@ mod tests {
.uri("/") .uri("/")
.header(header::ACCEPT_ENCODING, "gzip") .header(header::ACCEPT_ENCODING, "gzip")
.to_request(); .to_request();
let res = test::call_success(&mut srv, request); let res = test::call_service(&mut srv, request);
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!( assert_eq!(
res.headers() res.headers()
@@ -1023,20 +1021,20 @@ mod tests {
); );
let req = TestRequest::with_uri("/missing").to_request(); let req = TestRequest::with_uri("/missing").to_request();
let resp = test::call_success(&mut srv, req); let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = test::init_service(App::new().service(Files::new("/", "."))); let mut srv = test::init_service(App::new().service(Files::new("/", ".")));
let req = TestRequest::default().to_request(); let req = TestRequest::default().to_request();
let resp = test::call_success(&mut srv, req); let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = test::init_service( let mut srv = test::init_service(
App::new().service(Files::new("/", ".").show_files_listing()), App::new().service(Files::new("/", ".").show_files_listing()),
); );
let req = TestRequest::with_uri("/tests").to_request(); let req = TestRequest::with_uri("/tests").to_request();
let mut resp = test::call_success(&mut srv, req); let mut resp = test::call_service(&mut srv, req);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/html; charset=utf-8" "text/html; charset=utf-8"
@@ -1053,15 +1051,15 @@ mod tests {
#[test] #[test]
fn test_static_files_bad_directory() { fn test_static_files_bad_directory() {
let _st: Files<()> = Files::new("/", "missing"); let _st: Files = Files::new("/", "missing");
let _st: Files<()> = Files::new("/", "Cargo.toml"); let _st: Files = Files::new("/", "Cargo.toml");
} }
#[test] #[test]
fn test_default_handler_file_missing() { fn test_default_handler_file_missing() {
let mut st = test::block_on( let mut st = test::block_on(
Files::new("/", ".") Files::new("/", ".")
.default_handler(|req: ServiceRequest<_>| { .default_handler(|req: ServiceRequest| {
Ok(req.into_response(HttpResponse::Ok().body("default content"))) Ok(req.into_response(HttpResponse::Ok().body("default content")))
}) })
.new_service(&()), .new_service(&()),
@@ -1069,7 +1067,7 @@ mod tests {
.unwrap(); .unwrap();
let req = TestRequest::with_uri("/missing").to_srv_request(); let req = TestRequest::with_uri("/missing").to_srv_request();
let mut resp = test::call_success(&mut st, req); let mut resp = test::call_service(&mut st, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let bytes = let bytes =
test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| { test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| {

View File

@@ -15,7 +15,7 @@ use actix_web::http::header::{
self, ContentDisposition, DispositionParam, DispositionType, self, ContentDisposition, DispositionParam, DispositionType,
}; };
use actix_web::http::{ContentEncoding, Method, StatusCode}; use actix_web::http::{ContentEncoding, Method, StatusCode};
use actix_web::middleware::encoding::BodyEncoding; use actix_web::middleware::BodyEncoding;
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
use crate::range::HttpRange; use crate::range::HttpRange;
@@ -66,6 +66,7 @@ impl NamedFile {
/// let mut file = File::create("foo.txt")?; /// let mut file = File::create("foo.txt")?;
/// file.write_all(b"Hello, world!")?; /// file.write_all(b"Hello, world!")?;
/// let named_file = NamedFile::from_file(file, "bar.txt")?; /// let named_file = NamedFile::from_file(file, "bar.txt")?;
/// # std::fs::remove_file("foo.txt");
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-framed" name = "actix-framed"
version = "0.1.0-alpha.1" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix framed app server" description = "Actix framed app server"
readme = "README.md" readme = "README.md"
@@ -25,13 +25,13 @@ actix-service = "0.3.6"
actix-utils = "0.3.4" actix-utils = "0.3.4"
actix-router = "0.1.2" actix-router = "0.1.2"
actix-rt = "0.2.2" actix-rt = "0.2.2"
actix-http = "0.1.0-alpha.5" actix-http = "0.1.0"
bytes = "0.4" bytes = "0.4"
futures = "0.1.25" futures = "0.1.25"
log = "0.4" log = "0.4"
[dev-dependencies] [dev-dependencies]
actix-server = { version = "0.4.1", features=["ssl"] } actix-server = { version = "0.4.3", features=["ssl"] }
actix-connect = { version = "0.1.4", features=["ssl"] } actix-connect = { version = "0.1.4", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] }

View File

@@ -1 +1,8 @@
# 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) # 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,5 +1,10 @@
# Changes # Changes
## [0.1.0] - 2019-04-16
* Update tests
## [0.1.0-alpha.1] - 2019-04-12 ## [0.1.0-alpha.1] - 2019-04-12
* Initial release * Initial release

View File

@@ -1,5 +1,5 @@
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::{body, ws, Error, HttpService, Response}; use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response};
use actix_http_test::TestServer; use actix_http_test::TestServer;
use actix_service::{IntoNewService, NewService}; use actix_service::{IntoNewService, NewService};
use actix_utils::framed::FramedTransport; use actix_utils::framed::FramedTransport;
@@ -99,6 +99,11 @@ fn test_service() {
) )
}); });
// 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()); assert!(srv.ws_at("/test").is_err());
// client service // client service

View File

@@ -1,5 +1,63 @@
# Changes # Changes
## [0.1.5] - 2019-05-04
### Fixed
* Clean up response extensions in response pool #817
## [0.1.4] - 2019-04-24
### Added
* Allow to render h1 request headers in `Camel-Case`
### Fixed
* Read until eof for http/1.0 responses #771
## [0.1.3] - 2019-04-23
### Fixed
* Fix http client pool management
* Fix http client wait queue management #794
## [0.1.2] - 2019-04-23
### Fixed
* Fix BorrowMutError panic in client connector #793
## [0.1.1] - 2019-04-19
### Changes
* Cookie::max_age() accepts value in seconds
* Cookie::max_age_time() accepts value in time::Duration
* Allow to specify server address for client connector
## [0.1.0] - 2019-04-16
### Added
* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
### Changed
* `actix_http::encoding` always available
* use trust-dns-resolver 0.11.0
## [0.1.0-alpha.5] - 2019-04-12 ## [0.1.0-alpha.5] - 2019-04-12
### Added ### Added

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "0.1.0-alpha.5" version = "0.1.5"
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"
@@ -46,9 +46,9 @@ secure-cookies = ["ring"]
[dependencies] [dependencies]
actix-service = "0.3.6" actix-service = "0.3.6"
actix-codec = "0.1.2" actix-codec = "0.1.2"
actix-connect = "0.1.4" actix-connect = "0.1.5"
actix-utils = "0.3.5" actix-utils = "0.3.5"
actix-server-config = "0.1.0" actix-server-config = "0.1.1"
actix-threadpool = "0.1.0" actix-threadpool = "0.1.0"
base64 = "0.10" base64 = "0.10"
@@ -60,7 +60,7 @@ derive_more = "0.14"
either = "1.5.2" either = "1.5.2"
encoding = "0.2" encoding = "0.2"
futures = "0.1" futures = "0.1"
hashbrown = "0.2.0" hashbrown = "0.2.2"
h2 = "0.1.16" h2 = "0.1.16"
http = "0.1.17" http = "0.1.17"
httparse = "1.3" httparse = "1.3"
@@ -76,12 +76,12 @@ serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
sha1 = "0.6" sha1 = "0.6"
slab = "0.4" slab = "0.4"
serde_urlencoded = "0.5.3" serde_urlencoded = "0.5.5"
time = "0.1" time = "0.1.42"
tokio-tcp = "0.1.3" tokio-tcp = "0.1.3"
tokio-timer = "0.2" tokio-timer = "0.2.8"
tokio-current-thread = "0.1" tokio-current-thread = "0.1"
trust-dns-resolver = { version="0.11.0-alpha.3", default-features = false } trust-dns-resolver = { version="0.11.0", default-features = false }
# for secure cookie # for secure cookie
ring = { version = "0.14.6", optional = true } ring = { version = "0.14.6", optional = true }
@@ -93,12 +93,13 @@ flate2 = { version="1.0.7", optional = true, default-features = false }
# optional deps # optional deps
failure = { version = "0.1.5", optional = true } failure = { version = "0.1.5", optional = true }
openssl = { version="0.10", optional = true } openssl = { version="0.10", optional = true }
chrono = "0.4.6"
[dev-dependencies] [dev-dependencies]
actix-rt = "0.2.2" actix-rt = "0.2.2"
actix-server = { version = "0.4.1", features=["ssl"] } actix-server = { version = "0.4.3", features=["ssl"] }
actix-connect = { version = "0.1.4", features=["ssl"] } actix-connect = { version = "0.1.4", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] }
env_logger = "0.6" env_logger = "0.6"
serde_derive = "1.0" serde_derive = "1.0"
openssl = { version="0.10" } openssl = { version="0.10" }

View File

@@ -14,6 +14,7 @@ use tokio_tcp::TcpStream;
use super::connection::Connection; use super::connection::Connection;
use super::error::ConnectError; use super::error::ConnectError;
use super::pool::{ConnectionPool, Protocol}; use super::pool::{ConnectionPool, Protocol};
use super::Connect;
#[cfg(feature = "ssl")] #[cfg(feature = "ssl")]
use openssl::ssl::SslConnector; use openssl::ssl::SslConnector;
@@ -57,8 +58,7 @@ impl Connector<(), ()> {
let ssl = { let ssl = {
#[cfg(feature = "ssl")] #[cfg(feature = "ssl")]
{ {
use log::error; use openssl::ssl::SslMethod;
use openssl::ssl::{SslConnector, SslMethod};
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
let _ = ssl let _ = ssl
@@ -114,7 +114,8 @@ where
Request = TcpConnect<Uri>, Request = TcpConnect<Uri>,
Response = TcpConnection<Uri, U>, Response = TcpConnection<Uri, U>,
Error = actix_connect::ConnectError, Error = actix_connect::ConnectError,
> + Clone, > + Clone
+ 'static,
{ {
/// 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.
@@ -178,15 +179,17 @@ where
/// its combinator chain. /// its combinator chain.
pub fn finish( pub fn finish(
self, self,
) -> impl Service<Request = Uri, Response = impl Connection, Error = ConnectError> + Clone ) -> impl Service<Request = Connect, Response = impl Connection, Error = ConnectError>
{ + Clone {
#[cfg(not(feature = "ssl"))] #[cfg(not(feature = "ssl"))]
{ {
let connector = TimeoutService::new( let connector = TimeoutService::new(
self.timeout, self.timeout,
apply_fn(self.connector, |msg: Uri, srv| srv.call(msg.into())) apply_fn(self.connector, |msg: Connect, srv| {
.map_err(ConnectError::from) srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr))
.map(|stream| (stream.into_parts().0, Protocol::Http1)), })
.map_err(ConnectError::from)
.map(|stream| (stream.into_parts().0, Protocol::Http1)),
) )
.map_err(|e| match e { .map_err(|e| match e {
TimeoutError::Service(e) => e, TimeoutError::Service(e) => e,
@@ -210,26 +213,28 @@ where
let ssl_service = TimeoutService::new( let ssl_service = TimeoutService::new(
self.timeout, self.timeout,
apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into())) apply_fn(self.connector.clone(), |msg: Connect, srv| {
.map_err(ConnectError::from) srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr))
.and_then( })
OpensslConnector::service(self.ssl) .map_err(ConnectError::from)
.map_err(ConnectError::from) .and_then(
.map(|stream| { OpensslConnector::service(self.ssl)
let sock = stream.into_parts().0; .map_err(ConnectError::from)
let h2 = sock .map(|stream| {
.get_ref() let sock = stream.into_parts().0;
.ssl() let h2 = sock
.selected_alpn_protocol() .get_ref()
.map(|protos| protos.windows(2).any(|w| w == H2)) .ssl()
.unwrap_or(false); .selected_alpn_protocol()
if h2 { .map(|protos| protos.windows(2).any(|w| w == H2))
(sock, Protocol::Http2) .unwrap_or(false);
} else { if h2 {
(sock, Protocol::Http1) (sock, Protocol::Http2)
} } else {
}), (sock, Protocol::Http1)
), }
}),
),
) )
.map_err(|e| match e { .map_err(|e| match e {
TimeoutError::Service(e) => e, TimeoutError::Service(e) => e,
@@ -238,9 +243,11 @@ where
let tcp_service = TimeoutService::new( let tcp_service = TimeoutService::new(
self.timeout, self.timeout,
apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into())) apply_fn(self.connector.clone(), |msg: Connect, srv| {
.map_err(ConnectError::from) srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr))
.map(|stream| (stream.into_parts().0, Protocol::Http1)), })
.map_err(ConnectError::from)
.map(|stream| (stream.into_parts().0, Protocol::Http1)),
) )
.map_err(|e| match e { .map_err(|e| match e {
TimeoutError::Service(e) => e, TimeoutError::Service(e) => e,
@@ -265,15 +272,6 @@ where
} }
} }
} }
#[doc(hidden)]
#[deprecated(since = "0.1.0-alpha4", note = "please use `.finish()` method")]
pub fn service(
self,
) -> impl Service<Request = Uri, Response = impl Connection, Error = ConnectError> + Clone
{
self.finish()
}
} }
#[cfg(not(feature = "ssl"))] #[cfg(not(feature = "ssl"))]
@@ -287,7 +285,9 @@ mod connect_impl {
pub(crate) struct InnerConnector<T, Io> pub(crate) struct InnerConnector<T, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + 'static,
T: Service<Request = Uri, Response = (Io, Protocol), Error = ConnectError>, T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ Clone
+ 'static,
{ {
pub(crate) tcp_pool: ConnectionPool<T, Io>, pub(crate) tcp_pool: ConnectionPool<T, Io>,
} }
@@ -295,8 +295,9 @@ 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 + 'static,
T: Service<Request = Uri, Response = (Io, Protocol), Error = ConnectError> T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ Clone, + Clone
+ 'static,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
InnerConnector { InnerConnector {
@@ -308,9 +309,11 @@ 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 + 'static,
T: Service<Request = Uri, Response = (Io, Protocol), Error = ConnectError>, T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ Clone
+ 'static,
{ {
type Request = Uri; type Request = Connect;
type Response = IoConnection<Io>; type Response = IoConnection<Io>;
type Error = ConnectError; type Error = ConnectError;
type Future = Either< type Future = Either<
@@ -322,8 +325,8 @@ mod connect_impl {
self.tcp_pool.poll_ready() self.tcp_pool.poll_ready()
} }
fn call(&mut self, req: Uri) -> Self::Future { fn call(&mut self, req: Connect) -> Self::Future {
match req.scheme_str() { match req.uri.scheme_str() {
Some("https") | Some("wss") => { Some("https") | Some("wss") => {
Either::B(err(ConnectError::SslIsNotSupported)) Either::B(err(ConnectError::SslIsNotSupported))
} }
@@ -347,8 +350,8 @@ mod connect_impl {
where where
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + 'static,
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static,
T1: Service<Request = Uri, Response = (Io1, Protocol), Error = ConnectError>, T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>,
T2: Service<Request = Uri, Response = (Io2, Protocol), Error = ConnectError>, T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>,
{ {
pub(crate) tcp_pool: ConnectionPool<T1, Io1>, pub(crate) tcp_pool: ConnectionPool<T1, Io1>,
pub(crate) ssl_pool: ConnectionPool<T2, Io2>, pub(crate) ssl_pool: ConnectionPool<T2, Io2>,
@@ -358,10 +361,12 @@ mod connect_impl {
where where
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + 'static,
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static,
T1: Service<Request = Uri, Response = (Io1, Protocol), Error = ConnectError> T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ Clone, + Clone
T2: Service<Request = Uri, Response = (Io2, Protocol), Error = ConnectError> + 'static,
+ Clone, T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ Clone
+ 'static,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
InnerConnector { InnerConnector {
@@ -375,10 +380,14 @@ mod connect_impl {
where where
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + 'static,
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static,
T1: Service<Request = Uri, Response = (Io1, Protocol), Error = ConnectError>, T1: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
T2: Service<Request = Uri, Response = (Io2, Protocol), Error = ConnectError>, + Clone
+ 'static,
T2: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ Clone
+ 'static,
{ {
type Request = Uri; 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<
@@ -393,8 +402,8 @@ mod connect_impl {
self.tcp_pool.poll_ready() self.tcp_pool.poll_ready()
} }
fn call(&mut self, req: Uri) -> Self::Future { fn call(&mut self, req: Connect) -> Self::Future {
match req.scheme_str() { match req.uri.scheme_str() {
Some("https") | Some("wss") => { Some("https") | Some("wss") => {
Either::B(Either::B(InnerConnectorResponseB { Either::B(Either::B(InnerConnectorResponseB {
fut: self.ssl_pool.call(req), fut: self.ssl_pool.call(req),
@@ -412,7 +421,9 @@ mod connect_impl {
pub(crate) struct InnerConnectorResponseA<T, Io1, Io2> pub(crate) struct InnerConnectorResponseA<T, Io1, Io2>
where where
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + 'static,
T: Service<Request = Uri, Response = (Io1, Protocol), Error = ConnectError>, T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ Clone
+ 'static,
{ {
fut: <ConnectionPool<T, Io1> as Service>::Future, fut: <ConnectionPool<T, Io1> as Service>::Future,
_t: PhantomData<Io2>, _t: PhantomData<Io2>,
@@ -420,7 +431,9 @@ 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 = Uri, Response = (Io1, Protocol), Error = ConnectError>, T: Service<Request = Connect, Response = (Io1, Protocol), Error = ConnectError>
+ Clone
+ 'static,
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + 'static,
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static,
{ {
@@ -438,7 +451,9 @@ mod connect_impl {
pub(crate) struct InnerConnectorResponseB<T, Io1, Io2> pub(crate) struct InnerConnectorResponseB<T, Io1, Io2>
where where
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static,
T: Service<Request = Uri, Response = (Io2, Protocol), Error = ConnectError>, T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ Clone
+ 'static,
{ {
fut: <ConnectionPool<T, Io2> as Service>::Future, fut: <ConnectionPool<T, Io2> as Service>::Future,
_t: PhantomData<Io1>, _t: PhantomData<Io1>,
@@ -446,7 +461,9 @@ 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 = Uri, Response = (Io2, Protocol), Error = ConnectError>, T: Service<Request = Connect, Response = (Io2, Protocol), Error = ConnectError>
+ Clone
+ 'static,
Io1: AsyncRead + AsyncWrite + 'static, Io1: AsyncRead + AsyncWrite + 'static,
Io2: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static,
{ {

View File

@@ -274,7 +274,7 @@ impl<Io: ConnectionLifetime> Stream for PlStream<Io> {
Ok(Async::Ready(Some(chunk))) Ok(Async::Ready(Some(chunk)))
} else { } else {
let framed = self.framed.take().unwrap(); let framed = self.framed.take().unwrap();
let force_close = framed.get_codec().keepalive(); let force_close = !framed.get_codec().keepalive();
release_connection(framed, force_close); release_connection(framed, force_close);
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} }

View File

@@ -1,4 +1,6 @@
//! Http client api //! Http client api
use http::Uri;
mod connection; mod connection;
mod connector; mod connector;
mod error; mod error;
@@ -10,3 +12,9 @@ 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, InvalidUrl, SendRequestError};
pub use self::pool::Protocol; pub use self::pool::Protocol;
#[derive(Clone)]
pub struct Connect {
pub uri: Uri,
pub addr: Option<std::net::SocketAddr>,
}

View File

@@ -13,13 +13,14 @@ use futures::unsync::oneshot;
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use h2::client::{handshake, Handshake}; use h2::client::{handshake, Handshake};
use hashbrown::HashMap; use hashbrown::HashMap;
use http::uri::{Authority, Uri}; use http::uri::Authority;
use indexmap::IndexSet; use indexmap::IndexSet;
use slab::Slab; use slab::Slab;
use tokio_timer::{sleep, Delay}; use tokio_timer::{sleep, Delay};
use super::connection::{ConnectionType, IoConnection}; use super::connection::{ConnectionType, IoConnection};
use super::error::ConnectError; use super::error::ConnectError;
use super::Connect;
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
/// Protocol version /// Protocol version
@@ -48,7 +49,9 @@ pub(crate) struct ConnectionPool<T, Io: AsyncRead + AsyncWrite + 'static>(
impl<T, Io> ConnectionPool<T, Io> impl<T, Io> ConnectionPool<T, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + 'static,
T: Service<Request = Uri, Response = (Io, Protocol), Error = ConnectError>, T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ Clone
+ 'static,
{ {
pub(crate) fn new( pub(crate) fn new(
connector: T, connector: T,
@@ -68,7 +71,7 @@ where
waiters: Slab::new(), waiters: Slab::new(),
waiters_queue: IndexSet::new(), waiters_queue: IndexSet::new(),
available: HashMap::new(), available: HashMap::new(),
task: AtomicTask::new(), task: None,
})), })),
) )
} }
@@ -87,9 +90,11 @@ where
impl<T, Io> Service for ConnectionPool<T, Io> impl<T, Io> Service for ConnectionPool<T, Io>
where where
Io: AsyncRead + AsyncWrite + 'static, Io: AsyncRead + AsyncWrite + 'static,
T: Service<Request = Uri, Response = (Io, Protocol), Error = ConnectError>, T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>
+ Clone
+ 'static,
{ {
type Request = Uri; type Request = Connect;
type Response = IoConnection<Io>; type Response = IoConnection<Io>;
type Error = ConnectError; type Error = ConnectError;
type Future = Either< type Future = Either<
@@ -101,8 +106,8 @@ where
self.0.poll_ready() self.0.poll_ready()
} }
fn call(&mut self, req: Uri) -> Self::Future { fn call(&mut self, req: Connect) -> Self::Future {
let key = if let Some(authority) = req.authority_part() { let key = if let Some(authority) = req.uri.authority_part() {
authority.clone().into() authority.clone().into()
} else { } else {
return Either::A(err(ConnectError::Unresolverd)); return Either::A(err(ConnectError::Unresolverd));
@@ -112,31 +117,41 @@ where
match self.1.as_ref().borrow_mut().acquire(&key) { match self.1.as_ref().borrow_mut().acquire(&key) {
Acquire::Acquired(io, created) => { Acquire::Acquired(io, created) => {
// use existing connection // use existing connection
Either::A(ok(IoConnection::new( return Either::A(ok(IoConnection::new(
io, io,
created, created,
Some(Acquired(key, Some(self.1.clone()))), Some(Acquired(key, Some(self.1.clone()))),
))) )));
}
Acquire::NotAvailable => {
// connection is not available, wait
let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req);
Either::B(Either::A(WaitForConnection {
rx,
key,
token,
inner: Some(self.1.clone()),
}))
} }
Acquire::Available => { Acquire::Available => {
// open new connection // open new connection
Either::B(Either::B(OpenConnection::new( return Either::B(Either::B(OpenConnection::new(
key, key,
self.1.clone(), self.1.clone(),
self.0.call(req), self.0.call(req),
))) )));
} }
_ => (),
} }
// connection is not available, wait
let (rx, token, support) = self.1.as_ref().borrow_mut().wait_for(req);
// start support future
if !support {
self.1.as_ref().borrow_mut().task = Some(AtomicTask::new());
tokio_current_thread::spawn(ConnectorPoolSupport {
connector: self.0.clone(),
inner: self.1.clone(),
})
}
Either::B(Either::A(WaitForConnection {
rx,
key,
token,
inner: Some(self.1.clone()),
}))
} }
} }
@@ -244,7 +259,7 @@ where
Ok(Async::Ready(IoConnection::new( Ok(Async::Ready(IoConnection::new(
ConnectionType::H2(snd), ConnectionType::H2(snd),
Instant::now(), Instant::now(),
Some(Acquired(self.key.clone(), self.inner.clone())), Some(Acquired(self.key.clone(), self.inner.take())),
))) )))
} }
Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::NotReady) => Ok(Async::NotReady),
@@ -255,12 +270,11 @@ where
match self.fut.poll() { match self.fut.poll() {
Err(err) => Err(err), Err(err) => Err(err),
Ok(Async::Ready((io, proto))) => { Ok(Async::Ready((io, proto))) => {
let _ = self.inner.take();
if proto == Protocol::Http1 { if proto == Protocol::Http1 {
Ok(Async::Ready(IoConnection::new( Ok(Async::Ready(IoConnection::new(
ConnectionType::H1(io), ConnectionType::H1(io),
Instant::now(), Instant::now(),
Some(Acquired(self.key.clone(), self.inner.clone())), Some(Acquired(self.key.clone(), self.inner.take())),
))) )))
} else { } else {
self.h2 = Some(handshake(io)); self.h2 = Some(handshake(io));
@@ -278,7 +292,6 @@ enum Acquire<T> {
NotAvailable, NotAvailable,
} }
// #[derive(Debug)]
struct AvailableConnection<Io> { struct AvailableConnection<Io> {
io: ConnectionType<Io>, io: ConnectionType<Io>,
used: Instant, used: Instant,
@@ -292,9 +305,12 @@ pub(crate) struct Inner<Io> {
limit: usize, limit: usize,
acquired: usize, acquired: usize,
available: HashMap<Key, VecDeque<AvailableConnection<Io>>>, available: HashMap<Key, VecDeque<AvailableConnection<Io>>>,
waiters: Slab<(Uri, oneshot::Sender<Result<IoConnection<Io>, ConnectError>>)>, waiters: Slab<(
Connect,
oneshot::Sender<Result<IoConnection<Io>, ConnectError>>,
)>,
waiters_queue: IndexSet<(Key, usize)>, waiters_queue: IndexSet<(Key, usize)>,
task: AtomicTask, task: Option<AtomicTask>,
} }
impl<Io> Inner<Io> { impl<Io> Inner<Io> {
@@ -310,18 +326,6 @@ impl<Io> Inner<Io> {
self.waiters.remove(token); self.waiters.remove(token);
self.waiters_queue.remove(&(key.clone(), token)); self.waiters_queue.remove(&(key.clone(), token));
} }
fn release_conn(&mut self, key: &Key, io: ConnectionType<Io>, created: Instant) {
self.acquired -= 1;
self.available
.entry(key.clone())
.or_insert_with(VecDeque::new)
.push_back(AvailableConnection {
io,
created,
used: Instant::now(),
});
}
} }
impl<Io> Inner<Io> impl<Io> Inner<Io>
@@ -331,19 +335,21 @@ where
/// connection is not available, wait /// connection is not available, wait
fn wait_for( fn wait_for(
&mut self, &mut self,
connect: Uri, connect: Connect,
) -> ( ) -> (
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.authority_part().unwrap().clone().into(); let key: Key = connect.uri.authority_part().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((connect, tx)); entry.insert((connect, tx));
assert!(!self.waiters_queue.insert((key, token))); assert!(self.waiters_queue.insert((key, token)));
(rx, token)
(rx, token, self.task.is_some())
} }
fn acquire(&mut self, key: &Key) -> Acquire<Io> { fn acquire(&mut self, key: &Key) -> Acquire<Io> {
@@ -396,6 +402,19 @@ where
Acquire::Available Acquire::Available
} }
fn release_conn(&mut self, key: &Key, io: ConnectionType<Io>, created: Instant) {
self.acquired -= 1;
self.available
.entry(key.clone())
.or_insert_with(VecDeque::new)
.push_back(AvailableConnection {
io,
created,
used: Instant::now(),
});
self.check_availibility();
}
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.disconnect_timeout {
@@ -403,11 +422,12 @@ where
tokio_current_thread::spawn(CloseConnection::new(io, timeout)) tokio_current_thread::spawn(CloseConnection::new(io, timeout))
} }
} }
self.check_availibility();
} }
fn check_availibility(&self) { fn check_availibility(&self) {
if !self.waiters_queue.is_empty() && self.acquired < self.limit { if !self.waiters_queue.is_empty() && self.acquired < self.limit {
self.task.notify() self.task.as_ref().map(|t| t.notify());
} }
} }
} }
@@ -447,6 +467,147 @@ where
} }
} }
struct ConnectorPoolSupport<T, Io>
where
Io: AsyncRead + AsyncWrite + 'static,
{
connector: T,
inner: Rc<RefCell<Inner<Io>>>,
}
impl<T, Io> Future for ConnectorPoolSupport<T, Io>
where
Io: AsyncRead + AsyncWrite + 'static,
T: Service<Request = Connect, Response = (Io, Protocol), Error = ConnectError>,
T::Future: 'static,
{
type Item = ();
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut inner = self.inner.as_ref().borrow_mut();
inner.task.as_ref().unwrap().register();
// check waiters
loop {
let (key, token) = {
if let Some((key, token)) = inner.waiters_queue.get_index(0) {
(key.clone(), *token)
} else {
break;
}
};
match inner.acquire(&key) {
Acquire::NotAvailable => break,
Acquire::Acquired(io, created) => {
let (_, tx) = inner.waiters.remove(token);
if let Err(conn) = tx.send(Ok(IoConnection::new(
io,
created,
Some(Acquired(key.clone(), Some(self.inner.clone()))),
))) {
let (io, created) = conn.unwrap().into_inner();
inner.release_conn(&key, io, created);
}
}
Acquire::Available => {
let (connect, tx) = inner.waiters.remove(token);
OpenWaitingConnection::spawn(
key.clone(),
tx,
self.inner.clone(),
self.connector.call(connect),
);
}
}
let _ = inner.waiters_queue.swap_remove_index(0);
}
Ok(Async::NotReady)
}
}
struct OpenWaitingConnection<F, Io>
where
Io: AsyncRead + AsyncWrite + 'static,
{
fut: F,
key: Key,
h2: Option<Handshake<Io, Bytes>>,
rx: Option<oneshot::Sender<Result<IoConnection<Io>, ConnectError>>>,
inner: Option<Rc<RefCell<Inner<Io>>>>,
}
impl<F, Io> OpenWaitingConnection<F, Io>
where
F: Future<Item = (Io, Protocol), Error = ConnectError> + 'static,
Io: AsyncRead + AsyncWrite + 'static,
{
fn spawn(
key: Key,
rx: oneshot::Sender<Result<IoConnection<Io>, ConnectError>>,
inner: Rc<RefCell<Inner<Io>>>,
fut: F,
) {
tokio_current_thread::spawn(OpenWaitingConnection {
key,
fut,
h2: None,
rx: Some(rx),
inner: Some(inner),
})
}
}
impl<F, Io> Drop for OpenWaitingConnection<F, Io>
where
Io: AsyncRead + AsyncWrite + 'static,
{
fn drop(&mut self) {
if let Some(inner) = self.inner.take() {
let mut inner = inner.as_ref().borrow_mut();
inner.release();
inner.check_availibility();
}
}
}
impl<F, Io> Future for OpenWaitingConnection<F, Io>
where
F: Future<Item = (Io, Protocol), Error = ConnectError>,
Io: AsyncRead + AsyncWrite,
{
type Item = ();
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.fut.poll() {
Err(err) => {
let _ = self.inner.take();
if let Some(rx) = self.rx.take() {
let _ = rx.send(Err(err));
}
Err(())
}
Ok(Async::Ready((io, proto))) => {
if proto == Protocol::Http1 {
let rx = self.rx.take().unwrap();
let _ = rx.send(Ok(IoConnection::new(
ConnectionType::H1(io),
Instant::now(),
Some(Acquired(self.key.clone(), self.inner.take())),
)));
Ok(Async::Ready(()))
} else {
self.h2 = Some(handshake(io));
self.poll()
}
}
Ok(Async::NotReady) => Ok(Async::NotReady),
}
}
}
pub(crate) struct Acquired<T>(Key, Option<Rc<RefCell<Inner<T>>>>); pub(crate) struct Acquired<T>(Key, Option<Rc<RefCell<Inner<T>>>>);
impl<T> Acquired<T> impl<T> Acquired<T>

View File

@@ -1,6 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use time::{Duration, Tm}; use chrono::Duration;
use time::Tm;
use super::{Cookie, SameSite}; use super::{Cookie, SameSite};
@@ -16,7 +17,6 @@ use super::{Cookie, SameSite};
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use time::Duration;
/// ///
/// # fn main() { /// # fn main() {
/// let cookie: Cookie = Cookie::build("name", "value") /// let cookie: Cookie = Cookie::build("name", "value")
@@ -24,7 +24,7 @@ use super::{Cookie, SameSite};
/// .path("/") /// .path("/")
/// .secure(true) /// .secure(true)
/// .http_only(true) /// .http_only(true)
/// .max_age(Duration::days(1)) /// .max_age(84600)
/// .finish(); /// .finish();
/// # } /// # }
/// ``` /// ```
@@ -79,6 +79,26 @@ impl CookieBuilder {
self 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. /// Sets the `max_age` field in the cookie being built.
/// ///
/// # Example /// # Example
@@ -88,14 +108,14 @@ impl CookieBuilder {
/// ///
/// # fn main() { /// # fn main() {
/// let c = Cookie::build("foo", "bar") /// let c = Cookie::build("foo", "bar")
/// .max_age(time::Duration::minutes(30)) /// .max_age_time(time::Duration::minutes(30))
/// .finish(); /// .finish();
/// ///
/// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60)));
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
pub fn max_age(mut self, value: Duration) -> CookieBuilder { pub fn max_age_time(mut self, value: Duration) -> CookieBuilder {
self.cookie.set_max_age(value); self.cookie.set_max_age(value);
self self
} }
@@ -200,7 +220,7 @@ impl CookieBuilder {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use time::Duration; /// use chrono::Duration;
/// ///
/// # fn main() { /// # fn main() {
/// let c = Cookie::build("foo", "bar") /// let c = Cookie::build("foo", "bar")

View File

@@ -1,7 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::mem::replace; use std::mem::replace;
use time::{self, Duration}; use chrono::Duration;
use super::delta::DeltaCookie; use super::delta::DeltaCookie;
use super::Cookie; use super::Cookie;
@@ -188,7 +188,7 @@ impl CookieJar {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::{CookieJar, Cookie}; /// use actix_http::cookie::{CookieJar, Cookie};
/// use time::Duration; /// use chrono::Duration;
/// ///
/// # fn main() { /// # fn main() {
/// let mut jar = CookieJar::new(); /// let mut jar = CookieJar::new();
@@ -241,7 +241,7 @@ impl CookieJar {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::{CookieJar, Cookie}; /// use actix_http::cookie::{CookieJar, Cookie};
/// use time::Duration; /// use chrono::Duration;
/// ///
/// # fn main() { /// # fn main() {
/// let mut jar = CookieJar::new(); /// let mut jar = CookieJar::new();
@@ -537,8 +537,8 @@ mod test {
#[test] #[test]
#[cfg(feature = "secure-cookies")] #[cfg(feature = "secure-cookies")]
fn delta() { fn delta() {
use chrono::Duration;
use std::collections::HashMap; use std::collections::HashMap;
use time::Duration;
let mut c = CookieJar::new(); let mut c = CookieJar::new();

View File

@@ -65,8 +65,9 @@ use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use chrono::Duration;
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use time::{Duration, Tm}; use time::Tm;
pub use self::builder::CookieBuilder; pub use self::builder::CookieBuilder;
pub use self::draft::*; pub use self::draft::*;
@@ -624,7 +625,7 @@ impl<'c> Cookie<'c> {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use time::Duration; /// use chrono::Duration;
/// ///
/// # fn main() { /// # fn main() {
/// let mut c = Cookie::new("name", "value"); /// let mut c = Cookie::new("name", "value");
@@ -703,7 +704,7 @@ impl<'c> Cookie<'c> {
/// ///
/// ```rust /// ```rust
/// use actix_http::cookie::Cookie; /// use actix_http::cookie::Cookie;
/// use time::Duration; /// use chrono::Duration;
/// ///
/// # fn main() { /// # fn main() {
/// let mut c = Cookie::new("foo", "bar"); /// let mut c = Cookie::new("foo", "bar");
@@ -977,7 +978,7 @@ impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Cookie, SameSite}; use super::{Cookie, SameSite};
use time::{strptime, Duration}; use time::strptime;
#[test] #[test]
fn format() { fn format() {
@@ -987,9 +988,7 @@ mod tests {
let cookie = Cookie::build("foo", "bar").http_only(true).finish(); let cookie = Cookie::build("foo", "bar").http_only(true).finish();
assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly"); assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly");
let cookie = Cookie::build("foo", "bar") let cookie = Cookie::build("foo", "bar").max_age(10).finish();
.max_age(Duration::seconds(10))
.finish();
assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10"); assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10");
let cookie = Cookie::build("foo", "bar").secure(true).finish(); let cookie = Cookie::build("foo", "bar").secure(true).finish();

View File

@@ -5,8 +5,8 @@ use std::error::Error;
use std::fmt; use std::fmt;
use std::str::Utf8Error; use std::str::Utf8Error;
use chrono::Duration;
use percent_encoding::percent_decode; use percent_encoding::percent_decode;
use time::{self, Duration};
use super::{Cookie, CookieStr, SameSite}; use super::{Cookie, CookieStr, SameSite};
@@ -220,7 +220,8 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Cookie, SameSite}; use super::{Cookie, SameSite};
use time::{strptime, Duration}; use chrono::Duration;
use time::strptime;
macro_rules! assert_eq_parse { macro_rules! assert_eq_parse {
($string:expr, $expected:expr) => { ($string:expr, $expected:expr) => {
@@ -418,9 +419,7 @@ mod tests {
#[test] #[test]
fn do_not_panic_on_large_max_ages() { fn do_not_panic_on_large_max_ages() {
let max_seconds = Duration::max_value().num_seconds(); let max_seconds = Duration::max_value().num_seconds();
let expected = Cookie::build("foo", "bar") let expected = Cookie::build("foo", "bar").max_age(max_seconds).finish();
.max_age(Duration::seconds(max_seconds))
.finish();
assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected); assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected);
} }
} }

View File

@@ -137,7 +137,7 @@ impl<E: ResponseError> ResponseError for TimeoutError<E> {
#[display(fmt = "UnknownError")] #[display(fmt = "UnknownError")]
struct UnitError; struct UnitError;
/// `InternalServerError` for `JsonError` /// `InternalServerError` for `UnitError`
impl ResponseError for UnitError {} impl ResponseError for UnitError {}
/// `InternalServerError` for `JsonError` /// `InternalServerError` for `JsonError`
@@ -150,11 +150,11 @@ impl ResponseError for FormError {}
impl ResponseError for TimerError {} impl ResponseError for TimerError {}
#[cfg(feature = "ssl")] #[cfg(feature = "ssl")]
/// `InternalServerError` for `SslError` /// `InternalServerError` for `openssl::ssl::Error`
impl ResponseError for openssl::ssl::Error {} impl ResponseError for openssl::ssl::Error {}
#[cfg(feature = "ssl")] #[cfg(feature = "ssl")]
/// `InternalServerError` for `SslError` /// `InternalServerError` for `openssl::ssl::HandshakeError`
impl ResponseError for openssl::ssl::HandshakeError<tokio_tcp::TcpStream> {} impl ResponseError for openssl::ssl::HandshakeError<tokio_tcp::TcpStream> {}
/// Return `BAD_REQUEST` for `de::value::Error` /// Return `BAD_REQUEST` for `de::value::Error`

View File

@@ -1,6 +1,6 @@
#![allow(unused_imports, unused_variables, dead_code)] #![allow(unused_imports, unused_variables, dead_code)]
use std::fmt; use std::io::Write;
use std::io::{self, Write}; use std::{fmt, io, net};
use actix_codec::{Decoder, Encoder}; use actix_codec::{Decoder, Encoder};
use bitflags::bitflags; use bitflags::bitflags;
@@ -40,7 +40,6 @@ pub struct Codec {
// encoder part // encoder part
flags: Flags, flags: Flags,
encoder: encoder::MessageEncoder<Response<()>>, encoder: encoder::MessageEncoder<Response<()>>,
// headers_size: u32,
} }
impl Default for Codec { impl Default for Codec {
@@ -67,13 +66,11 @@ impl Codec {
}; };
Codec { Codec {
config, config,
flags,
decoder: decoder::MessageDecoder::default(), decoder: decoder::MessageDecoder::default(),
payload: None, payload: None,
version: Version::HTTP_11, version: Version::HTTP_11,
ctype: ConnectionType::Close, ctype: ConnectionType::Close,
flags,
// headers_size: 0,
encoder: encoder::MessageEncoder::default(), encoder: encoder::MessageEncoder::default(),
} }
} }

View File

@@ -300,7 +300,13 @@ impl MessageType for ResponseHead {
error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); error!("MAX_BUFFER_SIZE unprocessed data reached, closing");
return Err(ParseError::TooLarge); return Err(ParseError::TooLarge);
} else { } else {
PayloadType::None // for HTTP/1.0 read to eof and close connection
if msg.version == Version::HTTP_10 {
msg.set_connection_type(ConnectionType::Close);
PayloadType::Payload(PayloadDecoder::eof())
} else {
PayloadType::None
}
}; };
Ok(Some((msg, decoder))) Ok(Some((msg, decoder)))
@@ -331,7 +337,7 @@ impl HeaderIndex {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
/// Http payload item /// Http payload item
pub enum PayloadItem { pub enum PayloadItem {
Chunk(Bytes), Chunk(Bytes),
@@ -1191,4 +1197,16 @@ mod tests {
let msg = pl.decode(&mut buf).unwrap().unwrap(); let msg = pl.decode(&mut buf).unwrap().unwrap();
assert!(msg.eof()); assert!(msg.eof());
} }
#[test]
fn test_response_http10_read_until_eof() {
let mut buf = BytesMut::from(&"HTTP/1.0 200 Ok\r\n\r\ntest data"[..]);
let mut reader = MessageDecoder::<ResponseHead>::default();
let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap();
let mut pl = pl.unwrap();
let chunk = pl.decode(&mut buf).unwrap().unwrap();
assert_eq!(chunk, PayloadItem::Chunk(Bytes::from_static(b"test data")));
}
} }

View File

@@ -1,8 +1,9 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::time::Instant; use std::time::Instant;
use std::{fmt, io}; use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; use actix_codec::{Decoder, Encoder, Framed, FramedParts};
use actix_server_config::IoStream;
use actix_service::Service; use actix_service::Service;
use actix_utils::cloneable::CloneableService; use actix_utils::cloneable::CloneableService;
use bitflags::bitflags; use bitflags::bitflags;
@@ -81,6 +82,7 @@ where
expect: CloneableService<X>, expect: CloneableService<X>,
upgrade: Option<CloneableService<U>>, upgrade: Option<CloneableService<U>>,
flags: Flags, flags: Flags,
peer_addr: Option<net::SocketAddr>,
error: Option<DispatchError>, error: Option<DispatchError>,
state: State<S, B, X>, state: State<S, B, X>,
@@ -161,7 +163,7 @@ impl PartialEq for PollResponse {
impl<T, S, B, X, U> Dispatcher<T, S, B, X, U> impl<T, S, B, X, U> Dispatcher<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
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>>,
@@ -220,14 +222,15 @@ where
Dispatcher { Dispatcher {
inner: DispatcherState::Normal(InnerDispatcher { inner: DispatcherState::Normal(InnerDispatcher {
io,
codec,
read_buf,
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
payload: None, payload: None,
state: State::None, state: State::None,
error: None, error: None,
peer_addr: io.peer_addr(),
messages: VecDeque::new(), messages: VecDeque::new(),
io,
codec,
read_buf,
service, service,
expect, expect,
upgrade, upgrade,
@@ -241,7 +244,7 @@ where
impl<T, S, B, X, U> InnerDispatcher<T, S, B, X, U> impl<T, S, B, X, U> InnerDispatcher<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
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>>,
@@ -490,6 +493,7 @@ where
match msg { match msg {
Message::Item(mut req) => { Message::Item(mut req) => {
let pl = self.codec.message_type(); let pl = self.codec.message_type();
req.head_mut().peer_addr = self.peer_addr;
if pl == MessageType::Stream && self.upgrade.is_some() { if pl == MessageType::Stream && self.upgrade.is_some() {
self.messages.push_back(DispatcherMessage::Upgrade(req)); self.messages.push_back(DispatcherMessage::Upgrade(req));
@@ -649,7 +653,7 @@ where
impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U> impl<T, S, B, X, U> Future for Dispatcher<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
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>>,

View File

@@ -43,6 +43,10 @@ pub(crate) trait MessageType: Sized {
fn headers(&self) -> &HeaderMap; fn headers(&self) -> &HeaderMap;
fn camel_case(&self) -> bool {
false
}
fn chunked(&self) -> bool; fn chunked(&self) -> bool;
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()>; fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()>;
@@ -57,6 +61,7 @@ pub(crate) trait MessageType: Sized {
) -> io::Result<()> { ) -> io::Result<()> {
let chunked = self.chunked(); let chunked = self.chunked();
let mut skip_len = length != BodySize::Stream; let mut skip_len = length != BodySize::Stream;
let camel_case = self.camel_case();
// Content length // Content length
if let Some(status) = self.status() { if let Some(status) = self.status() {
@@ -74,18 +79,30 @@ pub(crate) trait MessageType: Sized {
match length { match length {
BodySize::Stream => { BodySize::Stream => {
if chunked { if chunked {
dst.put_slice(b"\r\ntransfer-encoding: chunked\r\n") if camel_case {
dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n")
} else {
dst.put_slice(b"\r\ntransfer-encoding: chunked\r\n")
}
} else { } else {
skip_len = false; skip_len = false;
dst.put_slice(b"\r\n"); dst.put_slice(b"\r\n");
} }
} }
BodySize::Empty => { BodySize::Empty => {
dst.put_slice(b"\r\ncontent-length: 0\r\n"); if camel_case {
dst.put_slice(b"\r\nContent-Length: 0\r\n");
} else {
dst.put_slice(b"\r\ncontent-length: 0\r\n");
}
} }
BodySize::Sized(len) => helpers::write_content_length(len, dst), BodySize::Sized(len) => helpers::write_content_length(len, dst),
BodySize::Sized64(len) => { BodySize::Sized64(len) => {
dst.put_slice(b"\r\ncontent-length: "); if camel_case {
dst.put_slice(b"\r\nContent-Length: ");
} else {
dst.put_slice(b"\r\ncontent-length: ");
}
write!(dst.writer(), "{}\r\n", len)?; write!(dst.writer(), "{}\r\n", len)?;
} }
BodySize::None => dst.put_slice(b"\r\n"), BodySize::None => dst.put_slice(b"\r\n"),
@@ -95,10 +112,18 @@ pub(crate) trait MessageType: Sized {
match ctype { match ctype {
ConnectionType::Upgrade => dst.put_slice(b"connection: upgrade\r\n"), ConnectionType::Upgrade => dst.put_slice(b"connection: upgrade\r\n"),
ConnectionType::KeepAlive if version < Version::HTTP_11 => { ConnectionType::KeepAlive if version < Version::HTTP_11 => {
dst.put_slice(b"connection: keep-alive\r\n") if camel_case {
dst.put_slice(b"Connection: keep-alive\r\n")
} else {
dst.put_slice(b"connection: keep-alive\r\n")
}
} }
ConnectionType::Close if version >= Version::HTTP_11 => { ConnectionType::Close if version >= Version::HTTP_11 => {
dst.put_slice(b"connection: close\r\n") if camel_case {
dst.put_slice(b"Connection: close\r\n")
} else {
dst.put_slice(b"connection: close\r\n")
}
} }
_ => (), _ => (),
} }
@@ -133,7 +158,12 @@ pub(crate) trait MessageType: Sized {
buf = &mut *(dst.bytes_mut() as *mut _); buf = &mut *(dst.bytes_mut() as *mut _);
} }
} }
buf[pos..pos + k.len()].copy_from_slice(k); // use upper Camel-Case
if camel_case {
write_camel_case(k, &mut buf[pos..pos + k.len()]);
} else {
buf[pos..pos + k.len()].copy_from_slice(k);
}
pos += k.len(); pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": "); buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2; pos += 2;
@@ -158,7 +188,12 @@ pub(crate) trait MessageType: Sized {
buf = &mut *(dst.bytes_mut() as *mut _); buf = &mut *(dst.bytes_mut() as *mut _);
} }
} }
buf[pos..pos + k.len()].copy_from_slice(k); // use upper Camel-Case
if camel_case {
write_camel_case(k, &mut buf[pos..pos + k.len()]);
} else {
buf[pos..pos + k.len()].copy_from_slice(k);
}
pos += k.len(); pos += k.len();
buf[pos..pos + 2].copy_from_slice(b": "); buf[pos..pos + 2].copy_from_slice(b": ");
pos += 2; pos += 2;
@@ -221,6 +256,10 @@ impl MessageType for RequestHead {
self.chunked() self.chunked()
} }
fn camel_case(&self) -> bool {
RequestHead::camel_case_headers(self)
}
fn headers(&self) -> &HeaderMap { fn headers(&self) -> &HeaderMap {
&self.headers &self.headers
} }
@@ -418,11 +457,41 @@ impl<'a> io::Write for Writer<'a> {
} }
} }
fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
let mut index = 0;
let key = value;
let mut key_iter = key.iter();
if let Some(c) = key_iter.next() {
if *c >= b'a' && *c <= b'z' {
buffer[index] = *c ^ b' ';
index += 1;
}
} else {
return;
}
while let Some(c) = key_iter.next() {
buffer[index] = *c;
index += 1;
if *c == b'-' {
if let Some(c) = key_iter.next() {
if *c >= b'a' && *c <= b'z' {
buffer[index] = *c ^ b' ';
index += 1;
}
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use bytes::Bytes; use bytes::Bytes;
use super::*;
use crate::http::header::{HeaderValue, CONTENT_TYPE};
#[test] #[test]
fn test_chunked_te() { fn test_chunked_te() {
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
@@ -436,4 +505,77 @@ mod tests {
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")
); );
} }
#[test]
fn test_camel_case() {
let mut bytes = BytesMut::with_capacity(2048);
let mut head = RequestHead::default();
head.set_camel_case_headers(true);
head.headers.insert(DATE, HeaderValue::from_static("date"));
head.headers
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Empty,
ConnectionType::Close,
&ServiceConfig::default(),
);
assert_eq!(
bytes.take().freeze(),
Bytes::from_static(b"\r\nContent-Length: 0\r\nConnection: close\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n")
);
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Stream,
ConnectionType::KeepAlive,
&ServiceConfig::default(),
);
assert_eq!(
bytes.take().freeze(),
Bytes::from_static(b"\r\nTransfer-Encoding: chunked\r\nDate: date\r\nContent-Type: plain/text\r\n\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")
);
head.headers
.append(CONTENT_TYPE, HeaderValue::from_static("xml"));
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Stream,
ConnectionType::KeepAlive,
&ServiceConfig::default(),
);
assert_eq!(
bytes.take().freeze(),
Bytes::from_static(b"\r\nTransfer-Encoding: chunked\r\nDate: date\r\nContent-Type: xml\r\nContent-Type: plain/text\r\n\r\n")
);
head.set_camel_case_headers(false);
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Stream,
ConnectionType::KeepAlive,
&ServiceConfig::default(),
);
assert_eq!(
bytes.take().freeze(),
Bytes::from_static(b"\r\ntransfer-encoding: chunked\r\ndate: date\r\ncontent-type: xml\r\ncontent-type: plain/text\r\n\r\n")
);
}
} }

View File

@@ -1,8 +1,8 @@
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::Framed;
use actix_server_config::{Io, ServerConfig as SrvConfig}; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig};
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService; use actix_utils::cloneable::CloneableService;
use futures::future::{ok, FutureResult}; use futures::future::{ok, FutureResult};
@@ -104,7 +104,7 @@ where
impl<T, P, S, B, X, U> NewService<SrvConfig> for H1Service<T, P, S, B, X, U> impl<T, P, S, B, X, U> NewService<SrvConfig> for H1Service<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -161,7 +161,7 @@ where
impl<T, P, S, B, X, U> Future for H1ServiceResponse<T, P, S, B, X, U> impl<T, P, S, B, X, U> Future for H1ServiceResponse<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -245,7 +245,7 @@ where
impl<T, P, S, B, X, U> Service for H1ServiceHandler<T, P, S, B, X, U> impl<T, P, S, B, X, U> Service for H1ServiceHandler<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
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>>,
@@ -309,7 +309,7 @@ pub struct OneRequest<T, P> {
impl<T, P> OneRequest<T, P> impl<T, P> OneRequest<T, P>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
{ {
/// Create new `H1SimpleService` instance. /// Create new `H1SimpleService` instance.
pub fn new() -> Self { pub fn new() -> Self {
@@ -322,7 +322,7 @@ where
impl<T, P> NewService<SrvConfig> for OneRequest<T, P> impl<T, P> NewService<SrvConfig> for OneRequest<T, P>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
{ {
type Request = Io<T, P>; type Request = Io<T, P>;
type Response = (Request, Framed<T, Codec>); type Response = (Request, Framed<T, Codec>);
@@ -348,7 +348,7 @@ pub struct OneRequestService<T, P> {
impl<T, P> Service for OneRequestService<T, P> impl<T, P> Service for OneRequestService<T, P>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
{ {
type Request = Io<T, P>; type Request = Io<T, P>;
type Response = (Request, Framed<T, Codec>); type Response = (Request, Framed<T, Codec>);
@@ -372,14 +372,14 @@ where
#[doc(hidden)] #[doc(hidden)]
pub struct OneRequestServiceResponse<T> pub struct OneRequestServiceResponse<T>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
{ {
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: AsyncRead + AsyncWrite, T: IoStream,
{ {
type Item = (Request, Framed<T, Codec>); type Item = (Request, Framed<T, Codec>);
type Error = ParseError; type Error = ParseError;

View File

@@ -1,9 +1,10 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::time::Instant; use std::time::Instant;
use std::{fmt, mem}; use std::{fmt, mem, net};
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::IoStream;
use actix_service::Service; use actix_service::Service;
use actix_utils::cloneable::CloneableService; use actix_utils::cloneable::CloneableService;
use bitflags::bitflags; use bitflags::bitflags;
@@ -29,14 +30,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< pub struct Dispatcher<T: IoStream, S: Service<Request = Request>, B: MessageBody> {
T: AsyncRead + AsyncWrite,
S: Service<Request = Request>,
B: MessageBody,
> {
service: CloneableService<S>, service: CloneableService<S>,
connection: Connection<T, Bytes>, connection: Connection<T, Bytes>,
config: ServiceConfig, config: ServiceConfig,
peer_addr: Option<net::SocketAddr>,
ka_expire: Instant, ka_expire: Instant,
ka_timer: Option<Delay>, ka_timer: Option<Delay>,
_t: PhantomData<B>, _t: PhantomData<B>,
@@ -44,7 +42,7 @@ pub struct Dispatcher<
impl<T, S, B> Dispatcher<T, S, B> impl<T, S, B> Dispatcher<T, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -56,6 +54,7 @@ where
connection: Connection<T, Bytes>, connection: Connection<T, Bytes>,
config: ServiceConfig, config: ServiceConfig,
timeout: Option<Delay>, timeout: Option<Delay>,
peer_addr: Option<net::SocketAddr>,
) -> Self { ) -> Self {
// let keepalive = config.keep_alive_enabled(); // let keepalive = config.keep_alive_enabled();
// let flags = if keepalive { // let flags = if keepalive {
@@ -76,9 +75,10 @@ where
Dispatcher { Dispatcher {
service, service,
config, config,
peer_addr,
connection,
ka_expire, ka_expire,
ka_timer, ka_timer,
connection,
_t: PhantomData, _t: PhantomData,
} }
} }
@@ -86,7 +86,7 @@ where
impl<T, S, B> Future for Dispatcher<T, S, B> impl<T, S, B> Future for Dispatcher<T, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -117,6 +117,7 @@ where
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;
tokio_current_thread::spawn(ServiceResponse::<S::Future, B> { tokio_current_thread::spawn(ServiceResponse::<S::Future, B> {
state: ServiceResponseState::ServiceCall( state: ServiceResponseState::ServiceCall(
self.service.call(req), self.service.call(req),

View File

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use std::{io, net}; use std::{io, net};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_server_config::{Io, ServerConfig as SrvConfig}; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig};
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService; use actix_utils::cloneable::CloneableService;
use bytes::Bytes; use bytes::Bytes;
@@ -63,7 +63,7 @@ where
impl<T, P, S, B> NewService<SrvConfig> for H2Service<T, P, S, B> impl<T, P, S, B> NewService<SrvConfig> for H2Service<T, P, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -95,7 +95,7 @@ pub struct H2ServiceResponse<T, P, S: NewService<SrvConfig, Request = Request>,
impl<T, P, S, B> Future for H2ServiceResponse<T, P, S, B> impl<T, P, S, B> Future for H2ServiceResponse<T, P, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Response: Into<Response<B>>, S::Response: Into<Response<B>>,
@@ -140,7 +140,7 @@ where
impl<T, P, S, B> Service for H2ServiceHandler<T, P, S, B> impl<T, P, S, B> Service for H2ServiceHandler<T, P, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -161,17 +161,20 @@ where
} }
fn call(&mut self, req: Self::Request) -> Self::Future { fn call(&mut self, req: Self::Request) -> Self::Future {
let io = req.into_parts().0;
let peer_addr = io.peer_addr();
H2ServiceHandlerResponse { H2ServiceHandlerResponse {
state: State::Handshake( state: State::Handshake(
Some(self.srv.clone()), Some(self.srv.clone()),
Some(self.cfg.clone()), Some(self.cfg.clone()),
server::handshake(req.into_parts().0), peer_addr,
server::handshake(io),
), ),
} }
} }
} }
enum State<T: AsyncRead + AsyncWrite, S: Service<Request = Request>, B: MessageBody> enum State<T: IoStream, S: Service<Request = Request>, B: MessageBody>
where where
S::Future: 'static, S::Future: 'static,
{ {
@@ -179,13 +182,14 @@ where
Handshake( Handshake(
Option<CloneableService<S>>, Option<CloneableService<S>>,
Option<ServiceConfig>, Option<ServiceConfig>,
Option<net::SocketAddr>,
Handshake<T, Bytes>, Handshake<T, Bytes>,
), ),
} }
pub struct H2ServiceHandlerResponse<T, S, B> pub struct H2ServiceHandlerResponse<T, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -197,7 +201,7 @@ where
impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B> impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -210,24 +214,28 @@ where
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.state { match self.state {
State::Incoming(ref mut disp) => disp.poll(), State::Incoming(ref mut disp) => disp.poll(),
State::Handshake(ref mut srv, ref mut config, ref mut handshake) => { State::Handshake(
match handshake.poll() { ref mut srv,
Ok(Async::Ready(conn)) => { ref mut config,
self.state = State::Incoming(Dispatcher::new( ref peer_addr,
srv.take().unwrap(), ref mut handshake,
conn, ) => match handshake.poll() {
config.take().unwrap(), Ok(Async::Ready(conn)) => {
None, self.state = State::Incoming(Dispatcher::new(
)); srv.take().unwrap(),
self.poll() conn,
} config.take().unwrap(),
Ok(Async::NotReady) => Ok(Async::NotReady), None,
Err(err) => { peer_addr.clone(),
trace!("H2 handshake error: {}", err); ));
Err(err.into()) self.poll()
}
} }
} Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => {
trace!("H2 handshake error: {}", err);
Err(err.into())
}
},
} }
} }
} }

View File

@@ -60,6 +60,7 @@ impl Response {
STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
STATIC_RESP!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY);
STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR);
STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED); STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED);

View File

@@ -12,7 +12,6 @@ pub mod body;
mod builder; mod builder;
pub mod client; pub mod client;
mod config; mod config;
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust", feature = "brotli"))]
pub mod encoding; pub mod encoding;
mod extensions; mod extensions;
mod header; mod header;

View File

@@ -1,4 +1,5 @@
use std::cell::{Ref, RefCell, RefMut}; use std::cell::{Ref, RefCell, RefMut};
use std::net;
use std::rc::Rc; use std::rc::Rc;
use bitflags::bitflags; use bitflags::bitflags;
@@ -26,6 +27,7 @@ bitflags! {
const UPGRADE = 0b0000_0100; const UPGRADE = 0b0000_0100;
const EXPECT = 0b0000_1000; const EXPECT = 0b0000_1000;
const NO_CHUNKING = 0b0001_0000; const NO_CHUNKING = 0b0001_0000;
const CAMEL_CASE = 0b0010_0000;
} }
} }
@@ -43,6 +45,7 @@ pub struct RequestHead {
pub version: Version, pub version: Version,
pub headers: HeaderMap, pub headers: HeaderMap,
pub extensions: RefCell<Extensions>, pub extensions: RefCell<Extensions>,
pub peer_addr: Option<net::SocketAddr>,
flags: Flags, flags: Flags,
} }
@@ -54,6 +57,7 @@ impl Default for RequestHead {
version: Version::HTTP_11, version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16), headers: HeaderMap::with_capacity(16),
flags: Flags::empty(), flags: Flags::empty(),
peer_addr: None,
extensions: RefCell::new(Extensions::new()), extensions: RefCell::new(Extensions::new()),
} }
} }
@@ -94,6 +98,23 @@ impl RequestHead {
&mut self.headers &mut self.headers
} }
/// Is to uppercase headers with Camel-Case.
/// Befault is `false`
#[inline]
pub fn camel_case_headers(&self) -> bool {
self.flags.contains(Flags::CAMEL_CASE)
}
/// Set `true` to send headers which are uppercased with Camel-Case.
#[inline]
pub fn set_camel_case_headers(&mut self, val: bool) {
if val {
self.flags.insert(Flags::CAMEL_CASE);
} else {
self.flags.remove(Flags::CAMEL_CASE);
}
}
#[inline] #[inline]
/// Set connection type of the message /// Set connection type of the message
pub fn set_connection_type(&mut self, ctype: ConnectionType) { pub fn set_connection_type(&mut self, ctype: ConnectionType) {
@@ -428,6 +449,7 @@ impl BoxedResponsePool {
fn release(&self, msg: Box<ResponseHead>) { fn release(&self, msg: Box<ResponseHead>) {
let v = &mut self.0.borrow_mut(); let v = &mut self.0.borrow_mut();
if v.len() < 128 { if v.len() < 128 {
msg.extensions.borrow_mut().clear();
v.push(msg); v.push(msg);
} }
} }

View File

@@ -53,6 +53,7 @@ where
type Item = Bytes; type Item = Bytes;
type Error = PayloadError; type Error = PayloadError;
#[inline]
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
match self { match self {
Payload::None => Ok(Async::Ready(None)), Payload::None => Ok(Async::Ready(None)),

View File

@@ -1,5 +1,5 @@
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
use std::fmt; use std::{fmt, net};
use http::{header, Method, Uri, Version}; use http::{header, Method, Uri, Version};
@@ -139,6 +139,7 @@ impl<P> Request<P> {
} }
/// Check if request requires connection upgrade /// Check if request requires connection upgrade
#[inline]
pub fn upgrade(&self) -> bool { pub fn upgrade(&self) -> bool {
if let Some(conn) = self.head().headers.get(header::CONNECTION) { if let Some(conn) = self.head().headers.get(header::CONNECTION) {
if let Ok(s) = conn.to_str() { if let Ok(s) = conn.to_str() {
@@ -147,6 +148,15 @@ impl<P> Request<P> {
} }
self.head().method == Method::CONNECT self.head().method == Method::CONNECT
} }
/// Peer socket address
///
/// Peer address is actual socket address, if proxy is used in front of
/// actix http server, then peer address would be address of this proxy.
#[inline]
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
self.head().peer_addr
}
} }
impl<P> fmt::Debug for Request<P> { impl<P> fmt::Debug for Request<P> {
@@ -168,3 +178,28 @@ impl<P> fmt::Debug for Request<P> {
Ok(()) Ok(())
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use http::HttpTryFrom;
#[test]
fn test_basics() {
let msg = Message::new();
let mut req = Request::from(msg);
req.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
);
assert!(req.headers().contains_key(header::CONTENT_TYPE));
*req.uri_mut() = Uri::try_from("/index.html?q=1").unwrap();
assert_eq!(req.uri().path(), "/index.html");
assert_eq!(req.uri().query(), Some("q=1"));
let s = format!("{:?}", req);
println!("T: {:?}", s);
assert!(s.contains("Request HTTP/1.1 GET:/index.html"));
}
}

View File

@@ -860,7 +860,7 @@ mod tests {
.domain("www.rust-lang.org") .domain("www.rust-lang.org")
.path("/test") .path("/test")
.http_only(true) .http_only(true)
.max_age(time::Duration::days(1)) .max_age_time(time::Duration::days(1))
.finish(), .finish(),
) )
.del_cookie(&cookies[1]) .del_cookie(&cookies[1])

View File

@@ -1,8 +1,10 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::{fmt, io}; use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig}; use actix_server_config::{
Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig,
};
use actix_service::{IntoNewService, NewService, Service}; use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService; use actix_utils::cloneable::CloneableService;
use bytes::{Buf, BufMut, Bytes, BytesMut}; use bytes::{Buf, BufMut, Bytes, BytesMut};
@@ -128,7 +130,7 @@ where
impl<T, P, S, B, X, U> NewService<SrvConfig> for HttpService<T, P, S, B, X, U> impl<T, P, S, B, X, U> NewService<SrvConfig> for HttpService<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
@@ -182,7 +184,7 @@ pub struct HttpServiceResponse<
impl<T, P, S, B, X, U> Future for HttpServiceResponse<T, P, S, B, X, U> impl<T, P, S, B, X, U> Future for HttpServiceResponse<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: NewService<SrvConfig, Request = Request>, S: NewService<SrvConfig, Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::InitError: fmt::Debug, S::InitError: fmt::Debug,
@@ -268,7 +270,7 @@ where
impl<T, P, S, B, X, U> Service for HttpServiceHandler<T, P, S, B, X, U> impl<T, P, S, B, X, U> Service for HttpServiceHandler<T, P, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -317,6 +319,7 @@ where
let (io, _, proto) = req.into_parts(); let (io, _, proto) = req.into_parts();
match proto { match proto {
Protocol::Http2 => { Protocol::Http2 => {
let peer_addr = io.peer_addr();
let io = Io { let io = Io {
inner: io, inner: io,
unread: None, unread: None,
@@ -326,6 +329,7 @@ where
server::handshake(io), server::handshake(io),
self.cfg.clone(), self.cfg.clone(),
self.srv.clone(), self.srv.clone(),
peer_addr,
))), ))),
} }
} }
@@ -357,7 +361,7 @@ where
S: Service<Request = Request>, S: Service<Request = Request>,
S::Future: 'static, S::Future: 'static,
S::Error: Into<Error>, S::Error: Into<Error>,
T: AsyncRead + AsyncWrite, T: IoStream,
B: MessageBody, B: MessageBody,
X: Service<Request = Request, Response = Request>, X: Service<Request = Request, Response = Request>,
X::Error: Into<Error>, X::Error: Into<Error>,
@@ -376,12 +380,19 @@ where
Option<CloneableService<U>>, Option<CloneableService<U>>,
)>, )>,
), ),
Handshake(Option<(Handshake<Io<T>, Bytes>, ServiceConfig, CloneableService<S>)>), Handshake(
Option<(
Handshake<Io<T>, Bytes>,
ServiceConfig,
CloneableService<S>,
Option<net::SocketAddr>,
)>,
),
} }
pub struct HttpServiceHandlerResponse<T, S, B, X, U> pub struct HttpServiceHandlerResponse<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -399,7 +410,7 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U> impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
where where
T: AsyncRead + AsyncWrite, T: IoStream,
S: Service<Request = Request>, S: Service<Request = Request>,
S::Error: Into<Error>, S::Error: Into<Error>,
S::Future: 'static, S::Future: 'static,
@@ -437,12 +448,17 @@ where
} }
let (io, buf, cfg, srv, expect, upgrade) = data.take().unwrap(); let (io, buf, cfg, srv, expect, upgrade) = data.take().unwrap();
if buf[..14] == HTTP2_PREFACE[..] { if buf[..14] == HTTP2_PREFACE[..] {
let peer_addr = io.peer_addr();
let io = Io { let io = Io {
inner: io, inner: io,
unread: Some(buf), unread: Some(buf),
}; };
self.state = self.state = State::Handshake(Some((
State::Handshake(Some((server::handshake(io), cfg, srv))); server::handshake(io),
cfg,
srv,
peer_addr,
)));
} else { } else {
self.state = State::H1(h1::Dispatcher::with_timeout( self.state = State::H1(h1::Dispatcher::with_timeout(
io, io,
@@ -470,8 +486,8 @@ where
} else { } else {
panic!() panic!()
}; };
let (_, cfg, srv) = data.take().unwrap(); let (_, cfg, srv, peer_addr) = data.take().unwrap();
self.state = State::H2(Dispatcher::new(srv, conn, cfg, None)); self.state = State::H2(Dispatcher::new(srv, conn, cfg, None, peer_addr));
self.poll() self.poll()
} }
} }
@@ -523,3 +539,25 @@ impl<T: AsyncWrite> AsyncWrite for Io<T> {
self.inner.write_buf(buf) self.inner.write_buf(buf)
} }
} }
impl<T: IoStream> IoStream for Io<T> {
#[inline]
fn peer_addr(&self) -> Option<net::SocketAddr> {
self.inner.peer_addr()
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.inner.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<std::time::Duration>) -> io::Result<()> {
self.inner.set_linger(dur)
}
#[inline]
fn set_keepalive(&mut self, dur: Option<std::time::Duration>) -> io::Result<()> {
self.inner.set_keepalive(dur)
}
}

View File

@@ -4,6 +4,7 @@ use std::io;
use std::str::FromStr; use std::str::FromStr;
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_server_config::IoStream;
use bytes::{Buf, Bytes, BytesMut}; use bytes::{Buf, Bytes, BytesMut};
use futures::{Async, Poll}; use futures::{Async, Poll};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
@@ -253,3 +254,17 @@ impl AsyncWrite for TestBuffer {
Ok(Async::NotReady) Ok(Async::NotReady)
} }
} }
impl IoStream for TestBuffer {
fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> {
Ok(())
}
fn set_linger(&mut self, _dur: Option<std::time::Duration>) -> io::Result<()> {
Ok(())
}
fn set_keepalive(&mut self, _dur: Option<std::time::Duration>) -> io::Result<()> {
Ok(())
}
}

View File

@@ -59,9 +59,7 @@ fn test_connection_close() {
.finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .finish(|_| ok::<_, ()>(Response::Ok().body(STR)))
.map(|_| ()) .map(|_| ())
}); });
println!("REQ: {:?}", srv.get("/").force_close());
let response = srv.block_on(srv.get("/").force_close().send()).unwrap(); let response = srv.block_on(srv.get("/").force_close().send()).unwrap();
println!("RES: {:?}", response);
assert!(response.status().is_success()); assert!(response.status().is_success());
} }

View File

@@ -35,7 +35,10 @@ fn test_h1() {
.keep_alive(KeepAlive::Disabled) .keep_alive(KeepAlive::Disabled)
.client_timeout(1000) .client_timeout(1000)
.client_disconnect(1000) .client_disconnect(1000)
.h1(|_| future::ok::<_, ()>(Response::Ok().finish())) .h1(|req: Request| {
assert!(req.peer_addr().is_some());
future::ok::<_, ()>(Response::Ok().finish())
})
}); });
let response = srv.block_on(srv.get("/").send()).unwrap(); let response = srv.block_on(srv.get("/").send()).unwrap();
@@ -50,6 +53,7 @@ fn test_h1_2() {
.client_timeout(1000) .client_timeout(1000)
.client_disconnect(1000) .client_disconnect(1000)
.finish(|req: Request| { .finish(|req: Request| {
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), http::Version::HTTP_11); assert_eq!(req.version(), http::Version::HTTP_11);
future::ok::<_, ()>(Response::Ok().finish()) future::ok::<_, ()>(Response::Ok().finish())
}) })
@@ -115,6 +119,7 @@ fn test_h2_1() -> std::io::Result<()> {
.and_then( .and_then(
HttpService::build() HttpService::build()
.finish(|req: Request| { .finish(|req: Request| {
assert!(req.peer_addr().is_some());
assert_eq!(req.version(), http::Version::HTTP_2); assert_eq!(req.version(), http::Version::HTTP_2);
future::ok::<_, Error>(Response::Ok().finish()) future::ok::<_, Error>(Response::Ok().finish())
}) })

View File

@@ -1,5 +1,9 @@
# Changes # Changes
## [0.1.0-alpha.1] - 2019-04-xx ## [0.1.0-beta.1] - 2019-04-21
* Do not support nested multipart
* Split multipart support to separate crate * Split multipart support to separate crate
* Optimize multipart handling #634, #769

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-multipart" name = "actix-multipart"
version = "0.1.0-alpha.1" version = "0.1.0-beta.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart support for actix web framework." description = "Multipart support for actix web framework."
readme = "README.md" readme = "README.md"
@@ -18,8 +18,8 @@ name = "actix_multipart"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = "1.0.0-alpha.3" actix-web = "1.0.0-beta.1"
actix-service = "0.3.4" actix-service = "0.3.6"
bytes = "0.4" bytes = "0.4"
derive_more = "0.14" derive_more = "0.14"
httparse = "1.3" httparse = "1.3"
@@ -31,4 +31,4 @@ twoway = "0.2"
[dev-dependencies] [dev-dependencies]
actix-rt = "0.2.2" actix-rt = "0.2.2"
actix-http = "0.1.0-alpha.3" actix-http = "0.1.1"

View File

@@ -1 +1,8 @@
# Multipart support 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-multipart)](https://crates.io/crates/actix-multipart) [![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) # Multipart support 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-multipart)](https://crates.io/crates/actix-multipart) [![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-multipart/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-multipart](https://crates.io/crates/actix-multipart)
* Minimum supported Rust version: 1.33 or later

View File

@@ -16,6 +16,9 @@ pub enum MultipartError {
/// Multipart boundary is not found /// Multipart boundary is not found
#[display(fmt = "Multipart boundary is not found")] #[display(fmt = "Multipart boundary is not found")]
Boundary, Boundary,
/// Nested multipart is not supported
#[display(fmt = "Nested multipart is not supported")]
Nested,
/// Multipart stream is incomplete /// Multipart stream is incomplete
#[display(fmt = "Multipart stream is incomplete")] #[display(fmt = "Multipart stream is incomplete")]
Incomplete, Incomplete,

View File

@@ -1,9 +1,5 @@
//! Multipart payload support //! Multipart payload support
use bytes::Bytes; use actix_web::{dev::Payload, Error, FromRequest, HttpRequest};
use futures::Stream;
use actix_web::error::{Error, PayloadError};
use actix_web::{dev::Payload, FromRequest, HttpRequest};
use crate::server::Multipart; use crate::server::Multipart;
@@ -21,34 +17,26 @@ use crate::server::Multipart;
/// ///
/// fn index(payload: mp::Multipart) -> impl Future<Item = HttpResponse, Error = Error> { /// fn index(payload: mp::Multipart) -> impl Future<Item = HttpResponse, Error = Error> {
/// payload.from_err() // <- get multipart stream for current request /// payload.from_err() // <- get multipart stream for current request
/// .and_then(|item| match item { // <- iterate over multipart items /// .and_then(|field| { // <- iterate over multipart items
/// mp::Item::Field(field) => { /// // Field in turn is stream of *Bytes* object
/// // Field in turn is stream of *Bytes* object /// field.from_err()
/// Either::A(field.from_err() /// .fold((), |_, chunk| {
/// .fold((), |_, chunk| { /// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk));
/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk)); /// Ok::<_, Error>(())
/// Ok::<_, Error>(()) /// })
/// }))
/// },
/// mp::Item::Nested(mp) => {
/// // Or item could be nested Multipart stream
/// Either::B(ok(()))
/// }
/// }) /// })
/// .fold((), |_, _| Ok::<_, Error>(())) /// .fold((), |_, _| Ok::<_, Error>(()))
/// .map(|_| HttpResponse::Ok().into()) /// .map(|_| HttpResponse::Ok().into())
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
impl<P> FromRequest<P> for Multipart impl FromRequest for Multipart {
where
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Error = Error; type Error = Error;
type Future = Result<Multipart, Error>; type Future = Result<Multipart, Error>;
type Config = ();
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
Ok(Multipart::new(req.headers(), payload.take())) Ok(Multipart::new(req.headers(), payload.take()))
} }
} }

View File

@@ -3,4 +3,4 @@ mod extractor;
mod server; mod server;
pub use self::error::MultipartError; pub use self::error::MultipartError;
pub use self::server::{Field, Item, Multipart}; pub use self::server::{Field, Multipart};

View File

@@ -32,18 +32,9 @@ pub struct Multipart {
inner: Option<Rc<RefCell<InnerMultipart>>>, inner: Option<Rc<RefCell<InnerMultipart>>>,
} }
/// Multipart item
pub enum Item {
/// Multipart field
Field(Field),
/// Nested multipart stream
Nested(Multipart),
}
enum InnerMultipartItem { enum InnerMultipartItem {
None, None,
Field(Rc<RefCell<InnerField>>), Field(Rc<RefCell<InnerField>>),
Multipart(Rc<RefCell<InnerMultipart>>),
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@@ -113,7 +104,7 @@ impl Multipart {
} }
impl Stream for Multipart { impl Stream for Multipart {
type Item = Item; type Item = Field;
type Error = MultipartError; type Error = MultipartError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
@@ -177,7 +168,7 @@ impl InnerMultipart {
match payload.readline() { match payload.readline() {
None => { None => {
if payload.eof { if payload.eof {
Err(MultipartError::Incomplete) Ok(Some(true))
} else { } else {
Ok(None) Ok(None)
} }
@@ -210,8 +201,7 @@ impl InnerMultipart {
match payload.readline() { match payload.readline() {
Some(chunk) => { Some(chunk) => {
if chunk.is_empty() { if chunk.is_empty() {
//ValueError("Could not find starting boundary %r" return Err(MultipartError::Boundary);
//% (self._boundary))
} }
if chunk.len() < boundary.len() { if chunk.len() < boundary.len() {
continue; continue;
@@ -245,7 +235,7 @@ impl InnerMultipart {
Ok(Some(eof)) Ok(Some(eof))
} }
fn poll(&mut self, safety: &Safety) -> Poll<Option<Item>, MultipartError> { fn poll(&mut self, safety: &Safety) -> Poll<Option<Field>, MultipartError> {
if self.state == InnerState::Eof { if self.state == InnerState::Eof {
Ok(Async::Ready(None)) Ok(Async::Ready(None))
} else { } else {
@@ -262,14 +252,7 @@ impl InnerMultipart {
Async::Ready(None) => true, Async::Ready(None) => true,
} }
} }
InnerMultipartItem::Multipart(ref mut multipart) => { InnerMultipartItem::None => false,
match multipart.borrow_mut().poll(safety)? {
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(Some(_)) => continue,
Async::Ready(None) => true,
}
}
_ => false,
}; };
if stop { if stop {
self.item = InnerMultipartItem::None; self.item = InnerMultipartItem::None;
@@ -346,24 +329,7 @@ impl InnerMultipart {
// nested multipart stream // nested multipart stream
if mt.type_() == mime::MULTIPART { if mt.type_() == mime::MULTIPART {
let inner = if let Some(boundary) = mt.get_param(mime::BOUNDARY) { Err(MultipartError::Nested)
Rc::new(RefCell::new(InnerMultipart {
payload: self.payload.clone(),
boundary: boundary.as_str().to_owned(),
state: InnerState::FirstBoundary,
item: InnerMultipartItem::None,
}))
} else {
return Err(MultipartError::Boundary);
};
self.item = InnerMultipartItem::Multipart(Rc::clone(&inner));
Ok(Async::Ready(Some(Item::Nested(Multipart {
safety: safety.clone(),
error: None,
inner: Some(inner),
}))))
} else { } else {
let field = Rc::new(RefCell::new(InnerField::new( let field = Rc::new(RefCell::new(InnerField::new(
self.payload.clone(), self.payload.clone(),
@@ -372,12 +338,12 @@ impl InnerMultipart {
)?)); )?));
self.item = InnerMultipartItem::Field(Rc::clone(&field)); self.item = InnerMultipartItem::Field(Rc::clone(&field));
Ok(Async::Ready(Some(Item::Field(Field::new( Ok(Async::Ready(Some(Field::new(
safety.clone(), safety.clone(),
headers, headers,
mt, mt,
field, field,
))))) ))))
} }
} }
} }
@@ -538,47 +504,72 @@ impl InnerField {
payload: &mut PayloadBuffer, payload: &mut PayloadBuffer,
boundary: &str, boundary: &str,
) -> Poll<Option<Bytes>, MultipartError> { ) -> Poll<Option<Bytes>, MultipartError> {
match payload.read_until(b"\r") { let mut pos = 0;
None => {
if payload.eof { let len = payload.buf.len();
Err(MultipartError::Incomplete) if len == 0 {
return Ok(Async::NotReady);
}
// check boundary
if len > 4 && payload.buf[0] == b'\r' {
let b_len = if &payload.buf[..2] == b"\r\n" && &payload.buf[2..4] == b"--" {
Some(4)
} else if &payload.buf[1..3] == b"--" {
Some(3)
} else {
None
};
if let Some(b_len) = b_len {
let b_size = boundary.len() + b_len;
if len < b_size {
return Ok(Async::NotReady);
} else { } else {
Ok(Async::NotReady) if &payload.buf[b_len..b_size] == boundary.as_bytes() {
// found boundary
return Ok(Async::Ready(None));
} else {
pos = b_size;
}
} }
} }
Some(mut chunk) => { }
if chunk.len() == 1 {
payload.unprocessed(chunk); loop {
match payload.read_exact(boundary.len() + 4) { return if let Some(idx) = twoway::find_bytes(&payload.buf[pos..], b"\r") {
None => { let cur = pos + idx;
if payload.eof {
Err(MultipartError::Incomplete) // check if we have enough data for boundary detection
} else { if cur + 4 > len {
Ok(Async::NotReady) if cur > 0 {
} Ok(Async::Ready(Some(payload.buf.split_to(cur).freeze())))
} } else {
Some(mut chunk) => { Ok(Async::NotReady)
if &chunk[..2] == b"\r\n"
&& &chunk[2..4] == b"--"
&& &chunk[4..] == boundary.as_bytes()
{
payload.unprocessed(chunk);
Ok(Async::Ready(None))
} else {
// \r might be part of data stream
let ch = chunk.split_to(1);
payload.unprocessed(chunk);
Ok(Async::Ready(Some(ch)))
}
}
} }
} else { } else {
let to = chunk.len() - 1; // check boundary
let ch = chunk.split_to(to); if (&payload.buf[cur..cur + 2] == b"\r\n"
payload.unprocessed(chunk); && &payload.buf[cur + 2..cur + 4] == b"--")
Ok(Async::Ready(Some(ch))) || (&payload.buf[cur..cur + 1] == b"\r"
&& &payload.buf[cur + 1..cur + 3] == b"--")
{
if cur != 0 {
// return buffer
Ok(Async::Ready(Some(payload.buf.split_to(cur).freeze())))
} else {
pos = cur + 1;
continue;
}
} else {
// not boundary
pos = cur + 1;
continue;
}
} }
} } else {
return Ok(Async::Ready(Some(payload.buf.take().freeze())));
};
} }
} }
@@ -588,26 +579,27 @@ impl InnerField {
} }
let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
let res = if let Some(ref mut len) = self.length { if !self.eof {
InnerField::read_len(payload, len)? let res = if let Some(ref mut len) = self.length {
} else { InnerField::read_len(payload, len)?
InnerField::read_stream(payload, &self.boundary)? } else {
}; InnerField::read_stream(payload, &self.boundary)?
};
match res { match res {
Async::NotReady => Async::NotReady, Async::NotReady => return Ok(Async::NotReady),
Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), Async::Ready(Some(bytes)) => return Ok(Async::Ready(Some(bytes))),
Async::Ready(None) => { Async::Ready(None) => self.eof = true,
self.eof = true; }
match payload.readline() { }
None => Async::Ready(None),
Some(line) => { match payload.readline() {
if line.as_ref() != b"\r\n" { None => Async::Ready(None),
log::warn!("multipart field did not read all the data or it is malformed"); Some(line) => {
} if line.as_ref() != b"\r\n" {
Async::Ready(None) log::warn!("multipart field did not read all the data or it is malformed");
}
} }
Async::Ready(None)
} }
} }
} else { } else {
@@ -737,7 +729,7 @@ impl PayloadBuffer {
} }
/// Read exact number of bytes /// Read exact number of bytes
#[inline] #[cfg(test)]
fn read_exact(&mut self, size: usize) -> Option<Bytes> { fn read_exact(&mut self, size: usize) -> Option<Bytes> {
if size <= self.buf.len() { if size <= self.buf.len() {
Some(self.buf.split_to(size).freeze()) Some(self.buf.split_to(size).freeze())
@@ -864,50 +856,112 @@ mod tests {
let mut multipart = Multipart::new(&headers, payload); let mut multipart = Multipart::new(&headers, payload);
match multipart.poll().unwrap() { match multipart.poll().unwrap() {
Async::Ready(Some(item)) => match item { Async::Ready(Some(mut field)) => {
Item::Field(mut field) => { let cd = field.content_disposition().unwrap();
{ assert_eq!(cd.disposition, DispositionType::FormData);
let cd = field.content_disposition().unwrap(); assert_eq!(cd.parameters[0], DispositionParam::Name("file".into()));
assert_eq!(cd.disposition, DispositionType::FormData);
assert_eq!(
cd.parameters[0],
DispositionParam::Name("file".into())
);
}
assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN);
match field.poll().unwrap() { assert_eq!(field.content_type().type_(), mime::TEXT);
Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"), assert_eq!(field.content_type().subtype(), mime::PLAIN);
_ => unreachable!(),
} match field.poll().unwrap() {
match field.poll().unwrap() { Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"),
Async::Ready(None) => (), _ => unreachable!(),
_ => unreachable!(),
}
} }
_ => unreachable!(), match field.poll().unwrap() {
}, Async::Ready(None) => (),
_ => unreachable!(),
}
}
_ => unreachable!(), _ => unreachable!(),
} }
match multipart.poll().unwrap() { match multipart.poll().unwrap() {
Async::Ready(Some(item)) => match item { Async::Ready(Some(mut field)) => {
Item::Field(mut field) => { assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN);
assert_eq!(field.content_type().subtype(), mime::PLAIN);
match field.poll() { match field.poll() {
Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "data"), Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "data"),
_ => unreachable!(), _ => unreachable!(),
}
match field.poll() {
Ok(Async::Ready(None)) => (),
_ => unreachable!(),
}
} }
_ => unreachable!(), match field.poll() {
}, Ok(Async::Ready(None)) => (),
_ => unreachable!(),
}
}
_ => unreachable!(),
}
match multipart.poll().unwrap() {
Async::Ready(None) => (),
_ => unreachable!(),
}
});
}
#[test]
fn test_stream() {
run_on(|| {
let (sender, payload) = create_stream();
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
);
sender.unbounded_send(Ok(bytes)).unwrap();
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
let mut multipart = Multipart::new(&headers, payload);
match multipart.poll().unwrap() {
Async::Ready(Some(mut field)) => {
let cd = field.content_disposition().unwrap();
assert_eq!(cd.disposition, DispositionType::FormData);
assert_eq!(cd.parameters[0], DispositionParam::Name("file".into()));
assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN);
match field.poll().unwrap() {
Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"),
_ => unreachable!(),
}
match field.poll().unwrap() {
Async::Ready(None) => (),
_ => unreachable!(),
}
}
_ => unreachable!(),
}
match multipart.poll().unwrap() {
Async::Ready(Some(mut field)) => {
assert_eq!(field.content_type().type_(), mime::TEXT);
assert_eq!(field.content_type().subtype(), mime::PLAIN);
match field.poll() {
Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "data"),
_ => unreachable!(),
}
match field.poll() {
Ok(Async::Ready(None)) => (),
_ => unreachable!(),
}
}
_ => unreachable!(), _ => unreachable!(),
} }

View File

@@ -1,5 +1,19 @@
# Changes # Changes
## [0.1.0-beta.2] - 2019-04-28
* Add helper trait `UserSession` which allows to get session for ServiceRequest and HttpRequest
## [0.1.0-beta.1] - 2019-04-20
* Update actix-web to beta.1
* `CookieSession::max_age()` accepts value in seconds
## [0.1.0-alpha.6] - 2019-04-14
* Update actix-web alpha.6
## [0.1.0-alpha.4] - 2019-04-08 ## [0.1.0-alpha.4] - 2019-04-08
* Update actix-web * Update actix-web

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-session" name = "actix-session"
version = "0.1.0-alpha.4" version = "0.1.0-beta.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Session for actix web framework." description = "Session for actix web framework."
readme = "README.md" readme = "README.md"
@@ -24,15 +24,15 @@ default = ["cookie-session"]
cookie-session = ["actix-web/secure-cookies"] cookie-session = ["actix-web/secure-cookies"]
[dependencies] [dependencies]
actix-web = "1.0.0-alpha.5" actix-web = "1.0.0-beta.2"
actix-service = "0.3.4" actix-service = "0.3.4"
bytes = "0.4" bytes = "0.4"
derive_more = "0.14" derive_more = "0.14"
futures = "0.1.25" futures = "0.1.25"
hashbrown = "0.2.0" hashbrown = "0.2.2"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
time = "0.1" time = "0.1.42"
[dev-dependencies] [dev-dependencies]
actix-rt = "0.2.2" actix-rt = "0.2.2"

View File

@@ -1 +1,9 @@
# Session 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-session)](https://crates.io/crates/actix-session) [![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) # Session 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-session)](https://crates.io/crates/actix-session) [![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-session/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-session](https://crates.io/crates/actix-session)
* Minimum supported Rust version: 1.33 or later

View File

@@ -27,7 +27,6 @@ use derive_more::{Display, From};
use futures::future::{ok, Future, FutureResult}; use futures::future::{ok, Future, FutureResult};
use futures::Poll; use futures::Poll;
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use time::Duration;
use crate::Session; use crate::Session;
@@ -57,7 +56,7 @@ struct CookieSessionInner {
domain: Option<String>, domain: Option<String>,
secure: bool, secure: bool,
http_only: bool, http_only: bool,
max_age: Option<Duration>, max_age: Option<time::Duration>,
same_site: Option<SameSite>, same_site: Option<SameSite>,
} }
@@ -120,7 +119,7 @@ impl CookieSessionInner {
Ok(()) Ok(())
} }
fn load<P>(&self, req: &ServiceRequest<P>) -> HashMap<String, String> { fn load(&self, req: &ServiceRequest) -> HashMap<String, String> {
if let Ok(cookies) = req.cookies() { if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() { for cookie in cookies.iter() {
if cookie.name() == self.name { if cookie.name() == self.name {
@@ -250,19 +249,24 @@ impl CookieSession {
} }
/// Sets the `max-age` field in the session cookie being built. /// Sets the `max-age` field in the session cookie being built.
pub fn max_age(mut self, value: Duration) -> CookieSession { pub fn max_age(self, seconds: i64) -> CookieSession {
self.max_age_time(time::Duration::seconds(seconds))
}
/// Sets the `max-age` field in the session cookie being built.
pub fn max_age_time(mut self, value: time::Duration) -> CookieSession {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self self
} }
} }
impl<S, P, B: 'static> Transform<S> for CookieSession impl<S, B: 'static> Transform<S> for CookieSession
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = S::Error;
type InitError = (); type InitError = ();
@@ -283,13 +287,13 @@ pub struct CookieSessionMiddleware<S> {
inner: Rc<CookieSessionInner>, inner: Rc<CookieSessionInner>,
} }
impl<S, P, B: 'static> Service for CookieSessionMiddleware<S> impl<S, B: 'static> Service for CookieSessionMiddleware<S>
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static, S::Error: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = S::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
@@ -298,7 +302,7 @@ where
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone(); let inner = self.inner.clone();
let state = self.inner.load(&req); let state = self.inner.load(&req);
Session::set_session(state.into_iter(), &mut req); Session::set_session(state.into_iter(), &mut req);
@@ -317,6 +321,7 @@ where
mod tests { mod tests {
use super::*; use super::*;
use actix_web::{test, web, App}; use actix_web::{test, web, App};
use bytes::Bytes;
#[test] #[test]
fn cookie_session() { fn cookie_session() {
@@ -338,6 +343,26 @@ mod tests {
.is_some()); .is_some());
} }
#[test]
fn private_cookie() {
let mut app = test::init_service(
App::new()
.wrap(CookieSession::private(&[0; 32]).secure(false))
.service(web::resource("/").to(|ses: Session| {
let _ = ses.set("counter", 100);
"test"
})),
);
let request = test::TestRequest::get().to_request();
let response = test::block_on(app.call(request)).unwrap();
assert!(response
.response()
.cookies()
.find(|c| c.name() == "actix-session")
.is_some());
}
#[test] #[test]
fn cookie_session_extractor() { fn cookie_session_extractor() {
let mut app = test::init_service( let mut app = test::init_service(
@@ -357,4 +382,44 @@ mod tests {
.find(|c| c.name() == "actix-session") .find(|c| c.name() == "actix-session")
.is_some()); .is_some());
} }
#[test]
fn basics() {
let mut app = test::init_service(
App::new()
.wrap(
CookieSession::signed(&[0; 32])
.path("/test/")
.name("actix-test")
.domain("localhost")
.http_only(true)
.same_site(SameSite::Lax)
.max_age(100),
)
.service(web::resource("/").to(|ses: Session| {
let _ = ses.set("counter", 100);
"test"
}))
.service(web::resource("/test/").to(|ses: Session| {
let val: usize = ses.get("counter").unwrap().unwrap();
format!("counter: {}", val)
})),
);
let request = test::TestRequest::get().to_request();
let response = test::block_on(app.call(request)).unwrap();
let cookie = response
.response()
.cookies()
.find(|c| c.name() == "actix-test")
.unwrap()
.clone();
assert_eq!(cookie.path().unwrap(), "/test/");
let request = test::TestRequest::with_uri("/test/")
.cookie(cookie)
.to_request();
let body = test::read_response(&mut app, request);
assert_eq!(body, Bytes::from_static(b"counter: 100"));
}
} }

View File

@@ -79,6 +79,23 @@ pub use crate::cookie::CookieSession;
/// ``` /// ```
pub struct Session(Rc<RefCell<SessionInner>>); pub struct Session(Rc<RefCell<SessionInner>>);
/// Helper trait that allows to get session
pub trait UserSession {
fn get_session(&mut self) -> Session;
}
impl UserSession for HttpRequest {
fn get_session(&mut self) -> Session {
Session::get_session(&mut *self.extensions_mut())
}
}
impl UserSession for ServiceRequest {
fn get_session(&mut self) -> Session {
Session::get_session(&mut *self.extensions_mut())
}
}
#[derive(Default)] #[derive(Default)]
struct SessionInner { struct SessionInner {
state: HashMap<String, String>, state: HashMap<String, String>,
@@ -119,9 +136,9 @@ impl Session {
inner.state.clear() inner.state.clear()
} }
pub fn set_session<P>( pub fn set_session(
data: impl Iterator<Item = (String, String)>, data: impl Iterator<Item = (String, String)>,
req: &mut ServiceRequest<P>, req: &mut ServiceRequest,
) { ) {
let session = Session::get_session(&mut *req.extensions_mut()); let session = Session::get_session(&mut *req.extensions_mut());
let mut inner = session.0.borrow_mut(); let mut inner = session.0.borrow_mut();
@@ -172,12 +189,13 @@ impl Session {
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
impl<P> FromRequest<P> for Session { impl FromRequest for Session {
type Error = Error; type Error = Error;
type Future = Result<Session, Error>; type Future = Result<Session, Error>;
type Config = ();
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
Ok(Session::get_session(&mut *req.extensions_mut())) Ok(Session::get_session(&mut *req.extensions_mut()))
} }
} }
@@ -207,4 +225,18 @@ mod tests {
let changes: Vec<_> = Session::get_changes(&mut res).unwrap().collect(); let changes: Vec<_> = Session::get_changes(&mut res).unwrap().collect();
assert_eq!(changes, [("key2".to_string(), "\"value2\"".to_string())]); assert_eq!(changes, [("key2".to_string(), "\"value2\"".to_string())]);
} }
#[test]
fn get_session() {
let mut req = test::TestRequest::default().to_srv_request();
Session::set_session(
vec![("key".to_string(), "\"value\"".to_string())].into_iter(),
&mut req,
);
let session = req.get_session();
let res = session.get::<String>("key").unwrap();
assert_eq!(res, Some("value".to_string()));
}
} }

View File

@@ -18,13 +18,13 @@ name = "actix_web_actors"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix = "0.8.0-alpha.3" actix = "0.8.0"
actix-web = "1.0.0-alpha.5" actix-web = "1.0.0-beta.1"
actix-http = "0.1.0-alpha.5" actix-http = "0.1.1"
actix-codec = "0.1.2" actix-codec = "0.1.2"
bytes = "0.4" bytes = "0.4"
futures = "0.1.25" futures = "0.1.25"
[dev-dependencies] [dev-dependencies]
env_logger = "0.6" env_logger = "0.6"
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] }

View File

@@ -1 +1,8 @@
Actix actors support 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-web-actors)](https://crates.io/crates/actix-web-actors) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix actors support 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-web-actors)](https://crates.io/crates/actix-web-actors) [![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-web-actors/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-web-actors](https://crates.io/crates/actix-web-actors)
* Minimum supported Rust version: 1.33 or later

View File

@@ -199,7 +199,7 @@ mod tests {
use actix::Actor; use actix::Actor;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use actix_web::test::{block_on, call_success, init_service, TestRequest}; use actix_web::test::{block_on, call_service, init_service, TestRequest};
use actix_web::{web, App, HttpResponse}; use actix_web::{web, App, HttpResponse};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
@@ -237,7 +237,7 @@ mod tests {
}))); })));
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let mut resp = call_success(&mut srv, req); let mut resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let body = block_on(resp.take_body().fold( let body = block_on(resp.take_body().fold(

View File

@@ -1,5 +1,13 @@
# Changes # Changes
## [0.1.0-beta.1] - 2019-04-20
* Gen code for actix-web 1.0.0-beta.1
## [0.1.0-alpha.6] - 2019-04-14
* Gen code for actix-web 1.0.0-alpha.6
## [0.1.0-alpha.1] - 2019-03-28 ## [0.1.0-alpha.1] - 2019-03-28
* Initial impl * Initial impl

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-codegen" name = "actix-web-codegen"
version = "0.1.0-alpha.1" version = "0.1.0-beta.1"
description = "Actix web proc macros" description = "Actix web proc macros"
readme = "README.md" readme = "README.md"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
@@ -16,7 +16,7 @@ quote = "0.6"
syn = { version = "0.15", features = ["full", "parsing"] } syn = { version = "0.15", features = ["full", "parsing"] }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.0-alpha.5" } actix-web = { version = "1.0.0-alpha.6" }
actix-http = { version = "0.1.0-alpha.5", features=["ssl"] } actix-http = { version = "0.1.1", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] }
futures = { version = "0.1" } futures = { version = "0.1" }

View File

@@ -61,8 +61,8 @@ impl fmt::Display for Args {
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub struct {name}; pub struct {name};
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for {name} {{ impl actix_web::dev::HttpServiceFactory for {name} {{
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {{ fn register(self, config: &mut actix_web::dev::AppService) {{
{ast} {ast}
let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name}); let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name});

View File

@@ -1,19 +1,24 @@
use actix_http::HttpService; use actix_http::HttpService;
use actix_http_test::TestServer; use actix_http_test::TestServer;
use actix_web::{http, App, HttpResponse, Responder}; use actix_web::{http, App, HttpResponse, Responder};
use actix_web_codegen::get; use actix_web_codegen::{get, post, put};
use futures::{future, Future}; use futures::{future, Future};
//fn guard_head(head: &actix_web::dev::RequestHead) -> bool {
// true
//}
//#[get("/test", guard="guard_head")]
#[get("/test")] #[get("/test")]
fn test() -> impl Responder { fn test() -> impl Responder {
HttpResponse::Ok() HttpResponse::Ok()
} }
#[put("/test")]
fn put_test() -> impl Responder {
HttpResponse::Created()
}
#[post("/test")]
fn post_test() -> impl Responder {
HttpResponse::NoContent()
}
#[get("/test")] #[get("/test")]
fn auto_async() -> impl Future<Item = HttpResponse, Error = actix_web::Error> { fn auto_async() -> impl Future<Item = HttpResponse, Error = actix_web::Error> {
future::ok(HttpResponse::Ok().finish()) future::ok(HttpResponse::Ok().finish())
@@ -26,11 +31,28 @@ fn auto_sync() -> impl Future<Item = HttpResponse, Error = actix_web::Error> {
#[test] #[test]
fn test_body() { fn test_body() {
let mut srv = TestServer::new(|| HttpService::new(App::new().service(test))); let mut srv = TestServer::new(|| {
HttpService::new(
App::new()
.service(post_test)
.service(put_test)
.service(test),
)
});
let request = srv.request(http::Method::GET, srv.url("/test")); let request = srv.request(http::Method::GET, srv.url("/test"));
let response = srv.block_on(request.send()).unwrap(); let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success()); assert!(response.status().is_success());
let request = srv.request(http::Method::PUT, srv.url("/test"));
let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(response.status(), http::StatusCode::CREATED);
let request = srv.request(http::Method::POST, srv.url("/test"));
let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(response.status(), http::StatusCode::NO_CONTENT);
let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_sync))); let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_sync)));
let request = srv.request(http::Method::GET, srv.url("/test")); let request = srv.request(http::Method::GET, srv.url("/test"));
let response = srv.block_on(request.send()).unwrap(); let response = srv.block_on(request.send()).unwrap();

View File

@@ -1,5 +1,33 @@
# Changes # Changes
### Added
* Allow to send headers in `Camel-Case` form.
## [0.1.1] - 2019-04-19
### Added
* Allow to specify server address for http and ws requests.
### Changed
* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref
## [0.1.0] - 2019-04-16
* No changes
## [0.1.0-alpha.6] - 2019-04-14
### Changed
* Do not set default headers for websocket request
## [0.1.0-alpha.5] - 2019-04-12 ## [0.1.0-alpha.5] - 2019-04-12
### Changed ### Changed

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "awc" name = "awc"
version = "0.1.0-alpha.5" version = "0.1.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http client." description = "Actix http client."
readme = "README.md" readme = "README.md"
@@ -8,10 +8,13 @@ keywords = ["actix", "http", "framework", "async", "web"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/awc/" documentation = "https://docs.rs/awc/"
categories = ["network-programming", "asynchronous",
"web-programming::http-client",
"web-programming::websocket"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
workspace = ".."
edition = "2018" edition = "2018"
workspace = ".."
[lib] [lib]
name = "awc" name = "awc"
@@ -38,7 +41,7 @@ flate2-rust = ["actix-http/flate2-rust"]
[dependencies] [dependencies]
actix-codec = "0.1.2" actix-codec = "0.1.2"
actix-service = "0.3.6" actix-service = "0.3.6"
actix-http = "0.1.0-alpha.5" actix-http = "0.1.4"
base64 = "0.10.1" base64 = "0.10.1"
bytes = "0.4" bytes = "0.4"
derive_more = "0.14" derive_more = "0.14"
@@ -55,11 +58,11 @@ openssl = { version="0.10", optional = true }
[dev-dependencies] [dev-dependencies]
actix-rt = "0.2.2" actix-rt = "0.2.2"
actix-web = { version = "1.0.0-alpha.5", features=["ssl"] } actix-web = { version = "1.0.0-beta.1", features=["ssl"] }
actix-http = { version = "0.1.0-alpha.5", features=["ssl"] } actix-http = { version = "0.1.4", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-http-test = { version = "0.1.1", features=["ssl"] }
actix-utils = "0.3.4" actix-utils = "0.3.4"
actix-server = { version = "0.4.1", features=["ssl"] } actix-server = { version = "0.4.3", features=["ssl"] }
brotli2 = { version="0.3.2" } brotli2 = { version="0.3.2" }
flate2 = { version="1.0.2" } flate2 = { version="1.0.2" }
env_logger = "0.6" env_logger = "0.6"

View File

@@ -1 +1,33 @@
# Actix http client [![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/awc)](https://crates.io/crates/awc) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) # Actix http client [![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/awc)](https://crates.io/crates/awc) [![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)
An HTTP Client
## Documentation & community resources
* [User Guide](https://actix.rs/docs/)
* [API Documentation](https://docs.rs/awc/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [awc](https://crates.io/crates/awc)
* Minimum supported Rust version: 1.33 or later
## Example
```rust
use actix_rt::System;
use awc::Client;
use futures::future::{Future, lazy};
fn main() {
System::new("test").block_on(lazy(|| {
let mut client = Client::default();
client.get("http://www.rust-lang.org") // <- Create request builder
.header("User-Agent", "Actix-web")
.send() // <- Send http request
.and_then(|response| { // <- server http response
println!("Response: {:?}", response);
Ok(())
})
}));
}
```

View File

@@ -3,8 +3,8 @@ use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use actix_http::client::{ConnectError, Connection, Connector}; use actix_http::client::{Connect, ConnectError, Connection, Connector};
use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom, Uri}; use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom};
use actix_service::Service; use actix_service::Service;
use crate::connect::ConnectorWrapper; use crate::connect::ConnectorWrapper;
@@ -40,7 +40,7 @@ impl ClientBuilder {
/// Use custom connector service. /// Use custom connector service.
pub fn connector<T>(mut self, connector: T) -> Self pub fn connector<T>(mut self, connector: T) -> Self
where where
T: Service<Request = Uri, Error = ConnectError> + 'static, T: Service<Request = Connect, Error = ConnectError> + 'static,
T::Response: Connection, T::Response: Connection,
<T::Response as Connection>::Future: 'static, <T::Response as Connection>::Future: 'static,
T::Future: 'static, T::Future: 'static,

View File

@@ -1,10 +1,12 @@
use std::{fmt, io}; use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::body::Body; use actix_http::body::Body;
use actix_http::client::{ConnectError, Connection, SendRequestError}; use actix_http::client::{
Connect as ClientConnect, ConnectError, Connection, SendRequestError,
};
use actix_http::h1::ClientCodec; use actix_http::h1::ClientCodec;
use actix_http::{http, RequestHead, ResponseHead}; use actix_http::{RequestHead, ResponseHead};
use actix_service::Service; use actix_service::Service;
use futures::{Future, Poll}; use futures::{Future, Poll};
@@ -17,12 +19,14 @@ pub(crate) trait Connect {
&mut self, &mut self,
head: RequestHead, head: RequestHead,
body: Body, body: Body,
addr: Option<net::SocketAddr>,
) -> Box<Future<Item = ClientResponse, Error = SendRequestError>>; ) -> Box<Future<Item = ClientResponse, Error = SendRequestError>>;
/// Send request, returns Response and Framed /// Send request, returns Response and Framed
fn open_tunnel( fn open_tunnel(
&mut self, &mut self,
head: RequestHead, head: RequestHead,
addr: Option<net::SocketAddr>,
) -> Box< ) -> Box<
Future< Future<
Item = (ResponseHead, Framed<BoxedSocket, ClientCodec>), Item = (ResponseHead, Framed<BoxedSocket, ClientCodec>),
@@ -33,7 +37,7 @@ pub(crate) trait Connect {
impl<T> Connect for ConnectorWrapper<T> impl<T> Connect for ConnectorWrapper<T>
where where
T: Service<Request = http::Uri, Error = ConnectError>, T: Service<Request = ClientConnect, Error = ConnectError>,
T::Response: Connection, T::Response: Connection,
<T::Response as Connection>::Io: 'static, <T::Response as Connection>::Io: 'static,
<T::Response as Connection>::Future: 'static, <T::Response as Connection>::Future: 'static,
@@ -44,11 +48,15 @@ where
&mut self, &mut self,
head: RequestHead, head: RequestHead,
body: Body, body: Body,
addr: Option<net::SocketAddr>,
) -> Box<Future<Item = ClientResponse, Error = SendRequestError>> { ) -> Box<Future<Item = ClientResponse, Error = SendRequestError>> {
Box::new( Box::new(
self.0 self.0
// connect to the host // connect to the host
.call(head.uri.clone()) .call(ClientConnect {
uri: head.uri.clone(),
addr,
})
.from_err() .from_err()
// send request // send request
.and_then(move |connection| connection.send_request(head, body)) .and_then(move |connection| connection.send_request(head, body))
@@ -59,6 +67,7 @@ where
fn open_tunnel( fn open_tunnel(
&mut self, &mut self,
head: RequestHead, head: RequestHead,
addr: Option<net::SocketAddr>,
) -> Box< ) -> Box<
Future< Future<
Item = (ResponseHead, Framed<BoxedSocket, ClientCodec>), Item = (ResponseHead, Framed<BoxedSocket, ClientCodec>),
@@ -68,7 +77,10 @@ where
Box::new( Box::new(
self.0 self.0
// connect to the host // connect to the host
.call(head.uri.clone()) .call(ClientConnect {
uri: head.uri.clone(),
addr,
})
.from_err() .from_err()
// send request // send request
.and_then(move |connection| connection.open_tunnel(head)) .and_then(move |connection| connection.open_tunnel(head))

View File

@@ -65,7 +65,7 @@ pub enum JsonPayloadError {
Payload(PayloadError), Payload(PayloadError),
} }
/// Return `InternlaServerError` for `JsonPayloadError` /// Return `InternalServerError` for `JsonPayloadError`
impl ResponseError for JsonPayloadError { impl ResponseError for JsonPayloadError {
fn error_response(&self) -> Response { fn error_response(&self) -> Response {
Response::new(StatusCode::INTERNAL_SERVER_ERROR) Response::new(StatusCode::INTERNAL_SERVER_ERROR)

View File

@@ -1,8 +1,8 @@
use std::fmt;
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::io::Write; use std::io::Write;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use std::{fmt, net};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
use futures::future::{err, Either}; use futures::future::{err, Either};
@@ -60,6 +60,7 @@ const HTTPS_ENCODING: &str = "gzip, deflate";
pub struct ClientRequest { pub struct ClientRequest {
pub(crate) head: RequestHead, pub(crate) head: RequestHead,
err: Option<HttpError>, err: Option<HttpError>,
addr: Option<net::SocketAddr>,
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
response_decompress: bool, response_decompress: bool,
timeout: Option<Duration>, timeout: Option<Duration>,
@@ -76,6 +77,7 @@ impl ClientRequest {
config, config,
head: RequestHead::default(), head: RequestHead::default(),
err: None, err: None,
addr: None,
cookies: None, cookies: None,
timeout: None, timeout: None,
response_decompress: true, response_decompress: true,
@@ -97,6 +99,15 @@ impl ClientRequest {
self self
} }
/// Set socket address of the server.
///
/// This address is used for connection. If address is not
/// provided url's host name get resolved.
pub fn address(mut self, addr: net::SocketAddr) -> Self {
self.addr = Some(addr);
self
}
/// Set HTTP method of this request. /// Set HTTP method of this request.
#[inline] #[inline]
pub fn method(mut self, method: Method) -> Self { pub fn method(mut self, method: Method) -> Self {
@@ -224,6 +235,13 @@ impl ClientRequest {
self self
} }
/// Send headers in `Camel-Case` form.
#[inline]
pub fn camel_case(mut self) -> Self {
self.head.set_camel_case_headers(true);
self
}
/// Force close connection instead of returning it back to connections pool. /// Force close connection instead of returning it back to connections pool.
/// This setting affect only http/1 connections. /// This setting affect only http/1 connections.
#[inline] #[inline]
@@ -331,26 +349,28 @@ impl ClientRequest {
/// This method calls provided closure with builder reference if /// This method calls provided closure with builder reference if
/// value is `true`. /// value is `true`.
pub fn if_true<F>(mut self, value: bool, f: F) -> Self pub fn if_true<F>(self, value: bool, f: F) -> Self
where where
F: FnOnce(&mut ClientRequest), F: FnOnce(ClientRequest) -> ClientRequest,
{ {
if value { if value {
f(&mut self); f(self)
} else {
self
} }
self
} }
/// This method calls provided closure with builder reference if /// This method calls provided closure with builder reference if
/// value is `Some`. /// value is `Some`.
pub fn if_some<T, F>(mut self, value: Option<T>, f: F) -> Self pub fn if_some<T, F>(self, value: Option<T>, f: F) -> Self
where where
F: FnOnce(T, &mut ClientRequest), F: FnOnce(T, ClientRequest) -> ClientRequest,
{ {
if let Some(val) = value { if let Some(val) = value {
f(val, &mut self); f(val, self)
} else {
self
} }
self
} }
/// Complete request construction and send body. /// Complete request construction and send body.
@@ -433,7 +453,7 @@ impl ClientRequest {
let fut = config let fut = config
.connector .connector
.borrow_mut() .borrow_mut()
.send_request(head, body.into()) .send_request(head, body.into(), slf.addr)
.map(move |res| { .map(move |res| {
res.map_body(|head, payload| { res.map_body(|head, payload| {
if response_decompress { if response_decompress {
@@ -544,102 +564,121 @@ impl fmt::Debug for ClientRequest {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::time::SystemTime;
use super::*; use super::*;
use crate::{test, Client}; use crate::Client;
#[test] #[test]
fn test_debug() { fn test_debug() {
test::run_on(|| { let request = Client::new().get("/").header("x-test", "111");
let request = Client::new().get("/").header("x-test", "111"); let repr = format!("{:?}", request);
let repr = format!("{:?}", request); assert!(repr.contains("ClientRequest"));
assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test"));
assert!(repr.contains("x-test")); }
})
#[test]
fn test_basics() {
let mut req = Client::new()
.put("/")
.version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into()))
.content_type("plain/text")
.if_true(true, |req| req.header(header::SERVER, "awc"))
.if_true(false, |req| req.header(header::EXPECT, "awc"))
.if_some(Some("server"), |val, req| {
req.header(header::USER_AGENT, val)
})
.if_some(Option::<&str>::None, |_, req| {
req.header(header::ALLOW, "1")
})
.content_length(100);
assert!(req.headers().contains_key(header::CONTENT_TYPE));
assert!(req.headers().contains_key(header::DATE));
assert!(req.headers().contains_key(header::SERVER));
assert!(req.headers().contains_key(header::USER_AGENT));
assert!(!req.headers().contains_key(header::ALLOW));
assert!(!req.headers().contains_key(header::EXPECT));
assert_eq!(req.head.version, Version::HTTP_2);
let _ = req.headers_mut();
let _ = req.send_body("");
} }
#[test] #[test]
fn test_client_header() { fn test_client_header() {
test::run_on(|| { let req = Client::build()
let req = Client::build() .header(header::CONTENT_TYPE, "111")
.header(header::CONTENT_TYPE, "111") .finish()
.finish() .get("/");
.get("/");
assert_eq!( assert_eq!(
req.head req.head
.headers .headers
.get(header::CONTENT_TYPE) .get(header::CONTENT_TYPE)
.unwrap() .unwrap()
.to_str() .to_str()
.unwrap(), .unwrap(),
"111" "111"
); );
})
} }
#[test] #[test]
fn test_client_header_override() { fn test_client_header_override() {
test::run_on(|| { let req = Client::build()
let req = Client::build() .header(header::CONTENT_TYPE, "111")
.header(header::CONTENT_TYPE, "111") .finish()
.finish() .get("/")
.get("/") .set_header(header::CONTENT_TYPE, "222");
.set_header(header::CONTENT_TYPE, "222");
assert_eq!( assert_eq!(
req.head req.head
.headers .headers
.get(header::CONTENT_TYPE) .get(header::CONTENT_TYPE)
.unwrap() .unwrap()
.to_str() .to_str()
.unwrap(), .unwrap(),
"222" "222"
); );
})
} }
#[test] #[test]
fn client_basic_auth() { fn client_basic_auth() {
test::run_on(|| { let req = Client::new()
let req = Client::new() .get("/")
.get("/") .basic_auth("username", Some("password"));
.basic_auth("username", Some("password")); assert_eq!(
assert_eq!( req.head
req.head .headers
.headers .get(header::AUTHORIZATION)
.get(header::AUTHORIZATION) .unwrap()
.unwrap() .to_str()
.to_str() .unwrap(),
.unwrap(), "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
"Basic dXNlcm5hbWU6cGFzc3dvcmQ=" );
);
let req = Client::new().get("/").basic_auth("username", None); let req = Client::new().get("/").basic_auth("username", None);
assert_eq!( assert_eq!(
req.head req.head
.headers .headers
.get(header::AUTHORIZATION) .get(header::AUTHORIZATION)
.unwrap() .unwrap()
.to_str() .to_str()
.unwrap(), .unwrap(),
"Basic dXNlcm5hbWU=" "Basic dXNlcm5hbWU="
); );
});
} }
#[test] #[test]
fn client_bearer_auth() { fn client_bearer_auth() {
test::run_on(|| { let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n");
let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n"); assert_eq!(
assert_eq!( req.head
req.head .headers
.headers .get(header::AUTHORIZATION)
.get(header::AUTHORIZATION) .unwrap()
.unwrap() .to_str()
.to_str() .unwrap(),
.unwrap(), "Bearer someS3cr3tAutht0k3n"
"Bearer someS3cr3tAutht0k3n" );
);
})
} }
} }

View File

@@ -134,3 +134,23 @@ impl TestResponse {
} }
} }
} }
#[cfg(test)]
mod tests {
use std::time::SystemTime;
use super::*;
use crate::{cookie, http::header};
#[test]
fn test_basics() {
let res = TestResponse::default()
.version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into()))
.cookie(cookie::Cookie::build("name", "value").finish())
.finish();
assert!(res.headers().contains_key(header::SET_COOKIE));
assert!(res.headers().contains_key(header::DATE));
assert_eq!(res.version(), Version::HTTP_2);
}
}

View File

@@ -1,13 +1,12 @@
//! Websockets client //! Websockets client
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::io::Write; use std::net::SocketAddr;
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, str}; use std::{fmt, str};
use actix_codec::Framed; use actix_codec::Framed;
use actix_http::cookie::{Cookie, CookieJar}; use actix_http::cookie::{Cookie, CookieJar};
use actix_http::{ws, Payload, RequestHead}; use actix_http::{ws, Payload, RequestHead};
use bytes::{BufMut, BytesMut};
use futures::future::{err, Either, Future}; use futures::future::{err, Either, Future};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use tokio_timer::Timeout; use tokio_timer::Timeout;
@@ -31,9 +30,9 @@ pub struct WebsocketsRequest {
err: Option<HttpError>, err: Option<HttpError>,
origin: Option<HeaderValue>, origin: Option<HeaderValue>,
protocols: Option<String>, protocols: Option<String>,
addr: Option<SocketAddr>,
max_size: usize, max_size: usize,
server_mode: bool, server_mode: bool,
default_headers: bool,
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
config: Rc<ClientConfig>, config: Rc<ClientConfig>,
} }
@@ -58,15 +57,24 @@ impl WebsocketsRequest {
head, head,
err, err,
config, config,
addr: None,
origin: None, origin: None,
protocols: None, protocols: None,
max_size: 65_536, max_size: 65_536,
server_mode: false, server_mode: false,
cookies: None, cookies: None,
default_headers: true,
} }
} }
/// Set socket address of the server.
///
/// This address is used for connection. If address is not
/// provided url's host name get resolved.
pub fn address(mut self, addr: SocketAddr) -> Self {
self.addr = Some(addr);
self
}
/// Set supported websocket protocols /// Set supported websocket protocols
pub fn protocols<U, V>(mut self, protos: U) -> Self pub fn protocols<U, V>(mut self, protos: U) -> Self
where where
@@ -119,13 +127,6 @@ impl WebsocketsRequest {
self self
} }
/// Do not add default request headers.
/// By default `Date` and `User-Agent` headers are set.
pub fn no_default_headers(mut self) -> Self {
self.default_headers = false;
self
}
/// Append a header. /// Append a header.
/// ///
/// Header gets appended to existing header. /// Header gets appended to existing header.
@@ -188,10 +189,9 @@ impl WebsocketsRequest {
} }
/// Set HTTP basic authorization header /// Set HTTP basic authorization header
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Self pub fn basic_auth<U>(self, username: U, password: Option<&str>) -> Self
where where
U: fmt::Display, U: fmt::Display,
P: fmt::Display,
{ {
let auth = match password { let auth = match password {
Some(password) => format!("{}:{}", username, password), Some(password) => format!("{}:{}", username, password),
@@ -232,67 +232,36 @@ impl WebsocketsRequest {
return Either::A(err(InvalidUrl::UnknownScheme.into())); return Either::A(err(InvalidUrl::UnknownScheme.into()));
} }
// set default headers
let mut slf = if self.default_headers {
// set request host header
if let Some(host) = self.head.uri.host() {
if !self.head.headers.contains_key(&header::HOST) {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match self.head.uri.port_u16() {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
self.head.headers.insert(header::HOST, value);
}
Err(e) => return Either::A(err(HttpError::from(e).into())),
}
}
}
// user agent
self.set_header_if_none(
header::USER_AGENT,
concat!("awc/", env!("CARGO_PKG_VERSION")),
)
} else {
self
};
let mut head = slf.head;
// set cookies // set cookies
if let Some(ref mut jar) = slf.cookies { if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new(); let mut cookie = String::new();
for c in jar.delta() { for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value); let _ = write!(&mut cookie, "; {}={}", name, value);
} }
head.headers.insert( self.head.headers.insert(
header::COOKIE, header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
); );
} }
// origin // origin
if let Some(origin) = slf.origin.take() { if let Some(origin) = self.origin.take() {
head.headers.insert(header::ORIGIN, origin); self.head.headers.insert(header::ORIGIN, origin);
} }
head.set_connection_type(ConnectionType::Upgrade); self.head.set_connection_type(ConnectionType::Upgrade);
head.headers self.head
.headers
.insert(header::UPGRADE, HeaderValue::from_static("websocket")); .insert(header::UPGRADE, HeaderValue::from_static("websocket"));
head.headers.insert( self.head.headers.insert(
header::SEC_WEBSOCKET_VERSION, header::SEC_WEBSOCKET_VERSION,
HeaderValue::from_static("13"), HeaderValue::from_static("13"),
); );
if let Some(protocols) = slf.protocols.take() { if let Some(protocols) = self.protocols.take() {
head.headers.insert( self.head.headers.insert(
header::SEC_WEBSOCKET_PROTOCOL, header::SEC_WEBSOCKET_PROTOCOL,
HeaderValue::try_from(protocols.as_str()).unwrap(), HeaderValue::try_from(protocols.as_str()).unwrap(),
); );
@@ -304,19 +273,20 @@ impl WebsocketsRequest {
let sec_key: [u8; 16] = rand::random(); let sec_key: [u8; 16] = rand::random();
let key = base64::encode(&sec_key); let key = base64::encode(&sec_key);
head.headers.insert( self.head.headers.insert(
header::SEC_WEBSOCKET_KEY, header::SEC_WEBSOCKET_KEY,
HeaderValue::try_from(key.as_str()).unwrap(), HeaderValue::try_from(key.as_str()).unwrap(),
); );
let max_size = slf.max_size; let head = self.head;
let server_mode = slf.server_mode; let max_size = self.max_size;
let server_mode = self.server_mode;
let fut = slf let fut = self
.config .config
.connector .connector
.borrow_mut() .borrow_mut()
.open_tunnel(head) .open_tunnel(head, self.addr)
.from_err() .from_err()
.and_then(move |(head, framed)| { .and_then(move |(head, framed)| {
// verify response // verify response
@@ -387,7 +357,7 @@ impl WebsocketsRequest {
}); });
// set request timeout // set request timeout
if let Some(timeout) = slf.config.timeout { if let Some(timeout) = self.config.timeout {
Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| {
if let Some(e) = e.into_inner() { if let Some(e) = e.into_inner() {
e e
@@ -400,3 +370,128 @@ impl WebsocketsRequest {
} }
} }
} }
impl fmt::Debug for WebsocketsRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"\nWebsocketsRequest {}:{}",
self.head.method, self.head.uri
)?;
writeln!(f, " headers:")?;
for (key, val) in self.head.headers.iter() {
writeln!(f, " {:?}: {:?}", key, val)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Client;
#[test]
fn test_debug() {
let request = Client::new().ws("/").header("x-test", "111");
let repr = format!("{:?}", request);
assert!(repr.contains("WebsocketsRequest"));
assert!(repr.contains("x-test"));
}
#[test]
fn test_header_override() {
let req = Client::build()
.header(header::CONTENT_TYPE, "111")
.finish()
.ws("/")
.set_header(header::CONTENT_TYPE, "222");
assert_eq!(
req.head
.headers
.get(header::CONTENT_TYPE)
.unwrap()
.to_str()
.unwrap(),
"222"
);
}
#[test]
fn basic_auth() {
let req = Client::new()
.ws("/")
.basic_auth("username", Some("password"));
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
);
let req = Client::new().ws("/").basic_auth("username", None);
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Basic dXNlcm5hbWU="
);
}
#[test]
fn bearer_auth() {
let req = Client::new().ws("/").bearer_auth("someS3cr3tAutht0k3n");
assert_eq!(
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
.unwrap(),
"Bearer someS3cr3tAutht0k3n"
);
let _ = req.connect();
}
#[test]
fn basics() {
actix_http_test::run_on(|| {
let req = Client::new()
.ws("http://localhost/")
.origin("test-origin")
.max_frame_size(100)
.server_mode()
.protocols(&["v1", "v2"])
.set_header_if_none(header::CONTENT_TYPE, "json")
.set_header_if_none(header::CONTENT_TYPE, "text")
.cookie(Cookie::build("cookie1", "value1").finish());
assert_eq!(
req.origin.as_ref().unwrap().to_str().unwrap(),
"test-origin"
);
assert_eq!(req.max_size, 100);
assert_eq!(req.server_mode, true);
assert_eq!(req.protocols, Some("v1,v2".to_string()));
assert_eq!(
req.head.headers.get(header::CONTENT_TYPE).unwrap(),
header::HeaderValue::from_static("json")
);
let _ = req.connect();
});
assert!(Client::new().ws("/").connect().poll().is_err());
assert!(Client::new().ws("http:///test").connect().poll().is_err());
assert!(Client::new()
.ws("hmm://test.com/")
.connect()
.poll()
.is_err());
}
}

View File

@@ -1,15 +1,23 @@
use std::io::Write; use std::collections::HashMap;
use std::io::{Read, Write};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use brotli2::write::BrotliEncoder; use brotli2::write::BrotliEncoder;
use bytes::Bytes; use bytes::Bytes;
use flate2::read::GzDecoder;
use flate2::write::GzEncoder; use flate2::write::GzEncoder;
use flate2::Compression; use flate2::Compression;
use futures::future::Future; use futures::Future;
use rand::Rng; use rand::Rng;
use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::HttpService; use actix_http::HttpService;
use actix_http_test::TestServer; use actix_http_test::TestServer;
use actix_service::{fn_service, NewService};
use actix_web::http::{Cookie, Version};
use actix_web::middleware::{BodyEncoding, Compress};
use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse};
use awc::error::SendRequestError; use awc::error::SendRequestError;
@@ -35,6 +43,30 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World"; Hello World Hello World Hello World Hello World Hello World";
#[cfg(feature = "ssl")]
fn ssl_acceptor<T: AsyncRead + AsyncWrite>(
) -> std::io::Result<actix_server::ssl::OpensslAcceptor<T, ()>> {
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
.set_private_key_file("../tests/key.pem", SslFiletype::PEM)
.unwrap();
builder
.set_certificate_chain_file("../tests/cert.pem")
.unwrap();
builder.set_alpn_select_callback(|_, protos| {
const H2: &[u8] = b"\x02h2";
if protos.windows(3).any(|window| window == H2) {
Ok(b"h2")
} else {
Err(openssl::ssl::AlpnError::NOACK)
}
});
builder.set_alpn_protos(b"\x02h2")?;
Ok(actix_server::ssl::OpensslAcceptor::new(builder.build()))
}
#[test] #[test]
fn test_simple() { fn test_simple() {
let mut srv = let mut srv =
@@ -58,6 +90,42 @@ fn test_simple() {
// read response // read response
let bytes = srv.block_on(response.body()).unwrap(); let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
// camel case
let response = srv.block_on(srv.post("/").camel_case().send()).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_json() {
let mut srv = TestServer::new(|| {
HttpService::new(App::new().service(
web::resource("/").route(web::to(|_: web::Json<String>| HttpResponse::Ok())),
))
});
let request = srv
.get("/")
.header("x-test", "111")
.send_json(&"TEST".to_string());
let response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
}
#[test]
fn test_form() {
let mut srv = TestServer::new(|| {
HttpService::new(App::new().service(web::resource("/").route(web::to(
|_: web::Form<HashMap<String, String>>| HttpResponse::Ok(),
))))
});
let mut data = HashMap::new();
let _ = data.insert("key".to_string(), "TEST".to_string());
let request = srv.get("/").header("x-test", "111").send_form(&data);
let response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
} }
#[test] #[test]
@@ -94,11 +162,9 @@ fn test_timeout_override() {
)))) ))))
}); });
let client = srv.execute(|| { let client = awc::Client::build()
awc::Client::build() .timeout(Duration::from_millis(50000))
.timeout(Duration::from_millis(50000)) .finish();
.finish()
});
let request = client let request = client
.get(srv.url("/")) .get(srv.url("/"))
.timeout(Duration::from_millis(50)) .timeout(Duration::from_millis(50))
@@ -109,58 +175,309 @@ fn test_timeout_override() {
} }
} }
// #[test] #[test]
// fn test_connection_close() { fn test_connection_reuse() {
// let mut srv = let num = Arc::new(AtomicUsize::new(0));
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); let num2 = num.clone();
// let request = srv.get("/").header("Connection", "close").finish().unwrap(); let mut srv = TestServer::new(move || {
// let response = srv.execute(request.send()).unwrap(); let num2 = num2.clone();
// assert!(response.status().is_success()); fn_service(move |io| {
// } num2.fetch_add(1, Ordering::Relaxed);
Ok(io)
})
.and_then(HttpService::new(
App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))),
))
});
// #[test] let client = awc::Client::default();
// fn test_with_query_parameter() {
// let mut srv = test::TestServer::new(|app| {
// app.handler(|req: &HttpRequest| match req.query().get("qp") {
// Some(_) => HttpResponse::Ok().finish(),
// None => HttpResponse::BadRequest().finish(),
// })
// });
// let request = srv.get("/").uri(srv.url("/?qp=5").as_str()).finish().unwrap(); // req 1
let request = client.get(srv.url("/")).send();
let response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
// let response = srv.execute(request.send()).unwrap(); // req 2
// assert!(response.status().is_success()); let req = client.post(srv.url("/"));
// } let response = srv.block_on_fn(move || req.send()).unwrap();
assert!(response.status().is_success());
// #[test] // one connection
// fn test_no_decompress() { assert_eq!(num.load(Ordering::Relaxed), 1);
// let mut srv = }
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get("/").disable_decompress().finish().unwrap(); #[cfg(feature = "ssl")]
// let response = srv.execute(request.send()).unwrap(); #[test]
// assert!(response.status().is_success()); fn test_connection_reuse_h2() {
let openssl = ssl_acceptor().unwrap();
let num = Arc::new(AtomicUsize::new(0));
let num2 = num.clone();
// // read response let mut srv = TestServer::new(move || {
// let bytes = srv.execute(response.body()).unwrap(); let num2 = num2.clone();
fn_service(move |io| {
num2.fetch_add(1, Ordering::Relaxed);
Ok(io)
})
.and_then(
openssl
.clone()
.map_err(|e| println!("Openssl error: {}", e)),
)
.and_then(
HttpService::build()
.h2(App::new()
.service(web::resource("/").route(web::to(|| HttpResponse::Ok()))))
.map_err(|_| ()),
)
});
// let mut e = GzDecoder::new(&bytes[..]); // disable ssl verification
// let mut dec = Vec::new(); use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
// e.read_to_end(&mut dec).unwrap();
// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
// // POST let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
// let request = srv.post().disable_decompress().finish().unwrap(); builder.set_verify(SslVerifyMode::NONE);
// let response = srv.execute(request.send()).unwrap(); let _ = builder
.set_alpn_protos(b"\x02h2\x08http/1.1")
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
// let bytes = srv.execute(response.body()).unwrap(); let client = awc::Client::build()
// let mut e = GzDecoder::new(&bytes[..]); .connector(awc::Connector::new().ssl(builder.build()).finish())
// let mut dec = Vec::new(); .finish();
// e.read_to_end(&mut dec).unwrap();
// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); // req 1
// } let request = client.get(srv.surl("/")).send();
let response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
// req 2
let req = client.post(srv.surl("/"));
let response = srv.block_on_fn(move || req.send()).unwrap();
assert!(response.status().is_success());
assert_eq!(response.version(), Version::HTTP_2);
// one connection
assert_eq!(num.load(Ordering::Relaxed), 1);
}
#[test]
fn test_connection_force_close() {
let num = Arc::new(AtomicUsize::new(0));
let num2 = num.clone();
let mut srv = TestServer::new(move || {
let num2 = num2.clone();
fn_service(move |io| {
num2.fetch_add(1, Ordering::Relaxed);
Ok(io)
})
.and_then(HttpService::new(
App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))),
))
});
let client = awc::Client::default();
// req 1
let request = client.get(srv.url("/")).force_close().send();
let response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
// req 2
let req = client.post(srv.url("/")).force_close();
let response = srv.block_on_fn(move || req.send()).unwrap();
assert!(response.status().is_success());
// two connection
assert_eq!(num.load(Ordering::Relaxed), 2);
}
#[test]
fn test_connection_server_close() {
let num = Arc::new(AtomicUsize::new(0));
let num2 = num.clone();
let mut srv = TestServer::new(move || {
let num2 = num2.clone();
fn_service(move |io| {
num2.fetch_add(1, Ordering::Relaxed);
Ok(io)
})
.and_then(HttpService::new(
App::new().service(
web::resource("/")
.route(web::to(|| HttpResponse::Ok().force_close().finish())),
),
))
});
let client = awc::Client::default();
// req 1
let request = client.get(srv.url("/")).send();
let response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
// req 2
let req = client.post(srv.url("/"));
let response = srv.block_on_fn(move || req.send()).unwrap();
assert!(response.status().is_success());
// two connection
assert_eq!(num.load(Ordering::Relaxed), 2);
}
#[test]
fn test_connection_wait_queue() {
let num = Arc::new(AtomicUsize::new(0));
let num2 = num.clone();
let mut srv = TestServer::new(move || {
let num2 = num2.clone();
fn_service(move |io| {
num2.fetch_add(1, Ordering::Relaxed);
Ok(io)
})
.and_then(HttpService::new(App::new().service(
web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))),
)))
});
let client = awc::Client::build()
.connector(awc::Connector::new().limit(1).finish())
.finish();
// req 1
let request = client.get(srv.url("/")).send();
let mut response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
// req 2
let req2 = client.post(srv.url("/"));
let req2_fut = srv.execute(move || {
let mut fut = req2.send();
assert!(fut.poll().unwrap().is_not_ready());
fut
});
// read response 1
let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
// req 2
let response = srv.block_on(req2_fut).unwrap();
assert!(response.status().is_success());
// two connection
assert_eq!(num.load(Ordering::Relaxed), 1);
}
#[test]
fn test_connection_wait_queue_force_close() {
let num = Arc::new(AtomicUsize::new(0));
let num2 = num.clone();
let mut srv = TestServer::new(move || {
let num2 = num2.clone();
fn_service(move |io| {
num2.fetch_add(1, Ordering::Relaxed);
Ok(io)
})
.and_then(HttpService::new(
App::new().service(
web::resource("/")
.route(web::to(|| HttpResponse::Ok().force_close().body(STR))),
),
))
});
let client = awc::Client::build()
.connector(awc::Connector::new().limit(1).finish())
.finish();
// req 1
let request = client.get(srv.url("/")).send();
let mut response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
// req 2
let req2 = client.post(srv.url("/"));
let req2_fut = srv.execute(move || {
let mut fut = req2.send();
assert!(fut.poll().unwrap().is_not_ready());
fut
});
// read response 1
let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
// req 2
let response = srv.block_on(req2_fut).unwrap();
assert!(response.status().is_success());
// two connection
assert_eq!(num.load(Ordering::Relaxed), 2);
}
#[test]
fn test_with_query_parameter() {
let mut srv = TestServer::new(|| {
HttpService::new(App::new().service(web::resource("/").to(
|req: HttpRequest| {
if req.query_string().contains("qp") {
HttpResponse::Ok()
} else {
HttpResponse::BadRequest()
}
},
)))
});
let res = srv
.block_on(awc::Client::new().get(srv.url("/?qp=5")).send())
.unwrap();
assert!(res.status().is_success());
}
#[test]
fn test_no_decompress() {
let mut srv = TestServer::new(|| {
HttpService::new(App::new().wrap(Compress::default()).service(
web::resource("/").route(web::to(|| {
let mut res = HttpResponse::Ok().body(STR);
res.encoding(header::ContentEncoding::Gzip);
res
})),
))
});
let mut res = srv
.block_on(awc::Client::new().get(srv.url("/")).no_decompress().send())
.unwrap();
assert!(res.status().is_success());
// read response
let bytes = srv.block_on(res.body()).unwrap();
let mut e = GzDecoder::new(&bytes[..]);
let mut dec = Vec::new();
e.read_to_end(&mut dec).unwrap();
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
// POST
let mut res = srv
.block_on(awc::Client::new().post(srv.url("/")).no_decompress().send())
.unwrap();
assert!(res.status().is_success());
let bytes = srv.block_on(res.body()).unwrap();
let mut e = GzDecoder::new(&bytes[..]);
let mut dec = Vec::new();
e.read_to_end(&mut dec).unwrap();
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
}
#[test] #[test]
fn test_client_gzip_encoding() { fn test_client_gzip_encoding() {
@@ -406,7 +723,6 @@ fn test_client_brotli_encoding() {
#[test] #[test]
fn test_client_cookie_handling() { fn test_client_cookie_handling() {
use actix_web::http::Cookie;
fn err() -> Error { fn err() -> Error {
use std::io::{Error as IoError, ErrorKind}; use std::io::{Error as IoError, ErrorKind};
// stub some generic error // stub some generic error
@@ -468,36 +784,6 @@ fn test_client_cookie_handling() {
assert_eq!(c2, cookie2); assert_eq!(c2, cookie2);
} }
// #[test]
// fn test_default_headers() {
// let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get("/").finish().unwrap();
// let repr = format!("{:?}", request);
// assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
// assert!(repr.contains(concat!(
// "\"user-agent\": \"actix-web/",
// env!("CARGO_PKG_VERSION"),
// "\""
// )));
// let request_override = srv
// .get("/")
// .header("User-Agent", "test")
// .header("Accept-Encoding", "over_test")
// .finish()
// .unwrap();
// let repr_override = format!("{:?}", request_override);
// assert!(repr_override.contains("\"user-agent\": \"test\""));
// assert!(repr_override.contains("\"accept-encoding\": \"over_test\""));
// assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\""));
// assert!(!repr_override.contains(concat!(
// "\"user-agent\": \"Actix-web/",
// env!("CARGO_PKG_VERSION"),
// "\""
// )));
// }
// #[test] // #[test]
// fn client_read_until_eof() { // fn client_read_until_eof() {
// let addr = test::TestServer::unused_addr(); // let addr = test::TestServer::unused_addr();

View File

@@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> {
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
.wrap(middleware::encoding::Compress::default()) .wrap(middleware::Compress::default())
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.service(index) .service(index)
.service(no_params) .service(no_params)
@@ -36,9 +36,9 @@ fn main() -> std::io::Result<()> {
.wrap( .wrap(
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
) )
.default_resource(|r| { .default_service(
r.route(web::route().to(|| HttpResponse::MethodNotAllowed())) web::route().to(|| HttpResponse::MethodNotAllowed()),
}) )
.route(web::get().to_async(index_async)), .route(web::get().to_async(index_async)),
) )
.service(web::resource("/test1.html").to(|| "Test\r\n")) .service(web::resource("/test1.html").to(|| "Test\r\n"))

View File

@@ -1,24 +1,21 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use actix_http::body::{Body, MessageBody}; use actix_http::body::{Body, MessageBody};
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
use actix_http::encoding::{Decoder, Encoder};
use actix_server_config::ServerConfig; use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService}; use actix_service::boxed::{self, BoxedNewService};
use actix_service::{ use actix_service::{
apply_transform, IntoNewService, IntoTransform, NewService, Transform, apply_transform, IntoNewService, IntoTransform, NewService, Transform,
}; };
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] use futures::IntoFuture;
use bytes::Bytes;
use futures::{IntoFuture, Stream};
use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
use crate::config::{AppConfig, AppConfigInner, RouterConfig}; use crate::config::{AppConfig, AppConfigInner, ServiceConfig};
use crate::data::{Data, DataFactory}; use crate::data::{Data, DataFactory};
use crate::dev::{Payload, PayloadStream, ResourceDef}; use crate::dev::ResourceDef;
use crate::error::{Error, PayloadError}; use crate::error::Error;
use crate::resource::Resource; use crate::resource::Resource;
use crate::route::Route; use crate::route::Route;
use crate::service::{ use crate::service::{
@@ -26,40 +23,44 @@ use crate::service::{
ServiceResponse, ServiceResponse,
}; };
type HttpNewService<P> = type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
/// Application builder - structure that follows the builder pattern /// Application builder - structure that follows the builder pattern
/// for building application instances. /// for building application instances.
pub struct App<In, Out, T> pub struct App<T, B> {
where endpoint: T,
T: NewService<Request = ServiceRequest<In>, Response = ServiceRequest<Out>>, services: Vec<Box<ServiceFactory>>,
{ default: Option<Rc<HttpNewService>>,
chain: T, factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
data: Vec<Box<DataFactory>>, data: Vec<Box<DataFactory>>,
config: AppConfigInner, config: AppConfigInner,
_t: PhantomData<(In, Out)>, external: Vec<ResourceDef>,
_t: PhantomData<(B)>,
} }
impl App<PayloadStream, PayloadStream, AppChain> { impl App<AppEntry, Body> {
/// Create application builder. Application can be configured with a builder-like pattern. /// Create application builder. Application can be configured with a builder-like pattern.
pub fn new() -> Self { pub fn new() -> Self {
let fref = Rc::new(RefCell::new(None));
App { App {
chain: AppChain, endpoint: AppEntry::new(fref.clone()),
data: Vec::new(), data: Vec::new(),
services: Vec::new(),
default: None,
factory_ref: fref,
config: AppConfigInner::default(), config: AppConfigInner::default(),
external: Vec::new(),
_t: PhantomData, _t: PhantomData,
} }
} }
} }
impl<In, Out, T> App<In, Out, T> impl<T, B> App<T, B>
where where
In: 'static, B: MessageBody,
Out: 'static,
T: NewService< T: NewService<
Request = ServiceRequest<In>, Request = ServiceRequest,
Response = ServiceRequest<Out>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
@@ -94,169 +95,11 @@ where
/// web::get().to(index))); /// web::get().to(index)));
/// } /// }
/// ``` /// ```
pub fn data<S: 'static>(mut self, data: S) -> Self { pub fn data<U: Into<Data<U>> + 'static>(mut self, data: U) -> Self {
self.data.push(Box::new(Data::new(data))); self.data.push(Box::new(data.into()));
self self
} }
/// Set application data factory. This function is
/// similar to `.data()` but it accepts data factory. Data object get
/// constructed asynchronously during application initialization.
pub fn data_factory<F, R>(mut self, data: F) -> Self
where
F: Fn() -> R + 'static,
R: IntoFuture + 'static,
R::Error: std::fmt::Debug,
{
self.data.push(Box::new(data));
self
}
/// Registers middleware, in the form of a middleware component (type),
/// that runs during inbound and/or outbound processing in the request
/// lifecycle (request -> response), modifying request/response as
/// necessary, across all requests managed by the *Application*.
///
/// Use middleware when you need to read or modify *every* request or response in some way.
///
/// ```rust
/// use actix_service::Service;
/// # use futures::Future;
/// use actix_web::{middleware, web, App};
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
///
/// fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap(middleware::Logger::default())
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap<M, B, F>(
self,
mw: F,
) -> AppRouter<
T,
Out,
B,
impl NewService<
Request = ServiceRequest<Out>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
>
where
M: Transform<
AppRouting<Out>,
Request = ServiceRequest<Out>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
F: IntoTransform<M, AppRouting<Out>>,
{
let fref = Rc::new(RefCell::new(None));
let endpoint = apply_transform(mw, AppEntry::new(fref.clone()));
AppRouter {
endpoint,
chain: self.chain,
data: self.data,
services: Vec::new(),
default: None,
factory_ref: fref,
config: self.config,
external: Vec::new(),
_t: PhantomData,
}
}
/// Registers middleware, in the form of a closure, that runs during inbound
/// and/or outbound processing in the request lifecycle (request -> response),
/// modifying request/response as necessary, across all requests managed by
/// the *Application*.
///
/// Use middleware when you need to read or modify *every* request or response in some way.
///
/// ```rust
/// use actix_service::Service;
/// # use futures::Future;
/// use actix_web::{web, App};
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
///
/// fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap_fn(|req, srv|
/// srv.call(req).map(|mut res| {
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// res
/// }))
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap_fn<F, R, B>(
self,
mw: F,
) -> AppRouter<
T,
Out,
B,
impl NewService<
Request = ServiceRequest<Out>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
>
where
F: FnMut(ServiceRequest<Out>, &mut AppRouting<Out>) -> R + Clone,
R: IntoFuture<Item = ServiceResponse<B>, Error = Error>,
{
self.wrap(mw)
}
/// Register a request modifier. It can modify any request parameters
/// including request payload type.
pub fn chain<C, F, P>(
self,
chain: F,
) -> App<
In,
P,
impl NewService<
Request = ServiceRequest<In>,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
>
where
C: NewService<
Request = ServiceRequest<Out>,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
F: IntoNewService<C>,
{
let chain = self.chain.and_then(chain.into_new_service());
App {
chain,
data: self.data,
config: self.config,
_t: PhantomData,
}
}
/// Run external configuration as part of the application building /// Run external configuration as part of the application building
/// process /// process
/// ///
@@ -269,7 +112,7 @@ where
/// use actix_web::{web, middleware, App, HttpResponse}; /// use actix_web::{web, middleware, App, HttpResponse};
/// ///
/// // this function could be located in different module /// // this function could be located in different module
/// fn config<P>(cfg: &mut web::RouterConfig<P>) { /// fn config(cfg: &mut web::ServiceConfig) {
/// cfg.service(web::resource("/test") /// cfg.service(web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok())) /// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) /// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
@@ -283,27 +126,16 @@ where
/// .route("/index.html", web::get().to(|| HttpResponse::Ok())); /// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
/// } /// }
/// ``` /// ```
pub fn configure<F>(mut self, f: F) -> AppRouter<T, Out, Body, AppEntry<Out>> pub fn configure<F>(mut self, f: F) -> Self
where where
F: Fn(&mut RouterConfig<Out>), F: Fn(&mut ServiceConfig),
{ {
let mut cfg = RouterConfig::new(); let mut cfg = ServiceConfig::new();
f(&mut cfg); f(&mut cfg);
self.data.extend(cfg.data); self.data.extend(cfg.data);
self.services.extend(cfg.services);
let fref = Rc::new(RefCell::new(None)); self.external.extend(cfg.external);
self
AppRouter {
chain: self.chain,
default: None,
endpoint: AppEntry::new(fref.clone()),
factory_ref: fref,
data: self.data,
config: self.config,
services: cfg.services,
external: cfg.external,
_t: PhantomData,
}
} }
/// Configure route for a specific path. /// Configure route for a specific path.
@@ -325,171 +157,7 @@ where
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
/// } /// }
/// ``` /// ```
pub fn route( pub fn route(self, path: &str, mut route: Route) -> Self {
self,
path: &str,
mut route: Route<Out>,
) -> AppRouter<T, Out, Body, AppEntry<Out>> {
self.service(
Resource::new(path)
.add_guards(route.take_guards())
.route(route),
)
}
/// Register http service.
///
/// Http service is any type that implements `HttpServiceFactory` trait.
///
/// Actix web provides several services implementations:
///
/// * *Resource* is an entry in resource table which corresponds to requested URL.
/// * *Scope* is a set of resources with common root path.
/// * "StaticFiles" is a service for static files support
pub fn service<F>(self, service: F) -> AppRouter<T, Out, Body, AppEntry<Out>>
where
F: HttpServiceFactory<Out> + 'static,
{
let fref = Rc::new(RefCell::new(None));
AppRouter {
chain: self.chain,
default: None,
endpoint: AppEntry::new(fref.clone()),
factory_ref: fref,
data: self.data,
config: self.config,
services: vec![Box::new(ServiceFactoryWrapper::new(service))],
external: Vec::new(),
_t: PhantomData,
}
}
/// Set server host name.
///
/// Host name is used by application router as a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
///
/// By default host name is set to a "localhost" value.
pub fn hostname(mut self, val: &str) -> Self {
self.config.host = val.to_owned();
self
}
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
/// Enable content compression and decompression.
pub fn enable_encoding(
self,
) -> AppRouter<
impl NewService<
Request = ServiceRequest<In>,
Response = ServiceRequest<Decoder<Payload<Out>>>,
Error = Error,
InitError = (),
>,
Decoder<Payload<Out>>,
Encoder<Body>,
impl NewService<
Request = ServiceRequest<Decoder<Payload<Out>>>,
Response = ServiceResponse<Encoder<Body>>,
Error = Error,
InitError = (),
>,
>
where
Out: Stream<Item = Bytes, Error = PayloadError>,
{
use crate::middleware::encoding::{Compress, Decompress};
self.chain(Decompress::new()).wrap(Compress::default())
}
}
/// Application router builder - Structure that follows the builder pattern
/// for building application instances.
pub struct AppRouter<C, P, B, T> {
chain: C,
endpoint: T,
services: Vec<Box<ServiceFactory<P>>>,
default: Option<Rc<HttpNewService<P>>>,
factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>,
data: Vec<Box<DataFactory>>,
config: AppConfigInner,
external: Vec<ResourceDef>,
_t: PhantomData<(P, B)>,
}
impl<C, P, B, T> AppRouter<C, P, B, T>
where
P: 'static,
B: MessageBody,
T: NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>,
{
/// Run external configuration as part of the application building
/// process
///
/// This function is useful for moving parts of configuration to a
/// different module or even library. For example,
/// some of the resource's configuration could be moved to different module.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{web, middleware, App, HttpResponse};
///
/// // this function could be located in different module
/// fn config<P>(cfg: &mut web::RouterConfig<P>) {
/// cfg.service(web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap(middleware::Logger::default())
/// .configure(config) // <- register resources
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
/// }
/// ```
pub fn configure<F>(mut self, f: F) -> Self
where
F: Fn(&mut RouterConfig<P>),
{
let mut cfg = RouterConfig::new();
f(&mut cfg);
self.data.extend(cfg.data);
self.services.extend(cfg.services);
self.external.extend(cfg.external);
self
}
/// Configure route for a specific path.
///
/// This is a simplified version of the `App::service()` method.
/// This method can not be could multiple times, in that case
/// multiple resources with one route would be registered for same resource path.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn index(data: web::Path<(String, String)>) -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .route("/test1", web::get().to(index))
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()));
/// }
/// ```
pub fn route(self, path: &str, mut route: Route<P>) -> Self {
self.service( self.service(
Resource::new(path) Resource::new(path)
.add_guards(route.take_guards()) .add_guards(route.take_guards())
@@ -508,102 +176,75 @@ where
/// * "StaticFiles" is a service for static files support /// * "StaticFiles" is a service for static files support
pub fn service<F>(mut self, factory: F) -> Self pub fn service<F>(mut self, factory: F) -> Self
where where
F: HttpServiceFactory<P> + 'static, F: HttpServiceFactory + 'static,
{ {
self.services self.services
.push(Box::new(ServiceFactoryWrapper::new(factory))); .push(Box::new(ServiceFactoryWrapper::new(factory)));
self self
} }
/// Registers middleware, in the form of a middleware component (type), /// Set server host name.
/// that runs during inbound and/or outbound processing in the request
/// lifecycle (request -> response), modifying request/response as
/// necessary, across all requests managed by the *Route*.
/// ///
/// Use middleware when you need to read or modify *every* request or response in some way. /// Host name is used by application router as a hostname for url
/// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo.
/// html#method.host) documentation for more information.
/// ///
pub fn wrap<M, B1, F>( /// By default host name is set to a "localhost" value.
self, pub fn hostname(mut self, val: &str) -> Self {
mw: F, self.config.host = val.to_owned();
) -> AppRouter< self
C,
P,
B1,
impl NewService<
Request = ServiceRequest<P>,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
>
where
M: Transform<
T::Service,
Request = ServiceRequest<P>,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
B1: MessageBody,
F: IntoTransform<M, T::Service>,
{
let endpoint = apply_transform(mw, self.endpoint);
AppRouter {
endpoint,
chain: self.chain,
data: self.data,
services: self.services,
default: self.default,
factory_ref: self.factory_ref,
config: self.config,
external: self.external,
_t: PhantomData,
}
} }
/// Registers middleware, in the form of a closure, that runs during inbound /// Default service to be used if no matching resource could be found.
/// and/or outbound processing in the request lifecycle (request -> response),
/// modifying request/response as necessary, across all requests managed by
/// the *Route*.
/// ///
/// Use middleware when you need to read or modify *every* request or response in some way. /// It is possible to use services like `Resource`, `Route`.
/// ///
pub fn wrap_fn<B1, F, R>( /// ```rust
self, /// use actix_web::{web, App, HttpResponse};
mw: F, ///
) -> AppRouter< /// fn index() -> &'static str {
C, /// "Welcome!"
P, /// }
B1, ///
impl NewService< /// fn main() {
Request = ServiceRequest<P>, /// let app = App::new()
Response = ServiceResponse<B1>, /// .service(
Error = Error, /// web::resource("/index.html").route(web::get().to(index)))
InitError = (), /// .default_service(
>, /// web::route().to(|| HttpResponse::NotFound()));
> /// }
/// ```
///
/// It is also possible to use static files as default service.
///
/// ```rust
/// use actix_files::Files;
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .service(
/// web::resource("/index.html").to(|| HttpResponse::Ok()))
/// .default_service(
/// Files::new("", "./static")
/// );
/// }
/// ```
pub fn default_service<F, U>(mut self, f: F) -> Self
where where
B1: MessageBody, F: IntoNewService<U>,
F: FnMut(ServiceRequest<P>, &mut T::Service) -> R + Clone,
R: IntoFuture<Item = ServiceResponse<B1>, Error = Error>,
{
self.wrap(mw)
}
/// Default resource to be used if no matching resource could be found.
pub fn default_resource<F, U>(mut self, f: F) -> Self
where
F: FnOnce(Resource<P>) -> Resource<P, U>,
U: NewService< U: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (),
> + 'static, > + 'static,
U::InitError: fmt::Debug,
{ {
// create and configure default resource // create and configure default resource
self.default = Some(Rc::new(boxed::new_service( self.default = Some(Rc::new(boxed::new_service(
f(Resource::new("")).into_new_service().map_init_err(|_| ()), f.into_new_service().map_init_err(|e| {
log::error!("Can not construct default service: {:?}", e)
}),
))); )));
self self
@@ -641,30 +282,139 @@ where
self.external.push(rdef); self.external.push(rdef);
self self
} }
/// Registers middleware, in the form of a middleware component (type),
/// that runs during inbound and/or outbound processing in the request
/// lifecycle (request -> response), modifying request/response as
/// necessary, across all requests managed by the *Application*.
///
/// Use middleware when you need to read or modify *every* request or
/// response in some way.
///
/// Notice that the keyword for registering middleware is `wrap`. As you
/// register middleware using `wrap` in the App builder, imagine wrapping
/// layers around an inner App. The first middleware layer exposed to a
/// Request is the outermost layer-- the *last* registered in
/// the builder chain. Consequently, the *first* middleware registered
/// in the builder chain is the *last* to execute during request processing.
///
/// ```rust
/// use actix_service::Service;
/// # use futures::Future;
/// use actix_web::{middleware, web, App};
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
///
/// fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap(middleware::Logger::default())
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap<M, B1, F>(
self,
mw: F,
) -> App<
impl NewService<
Request = ServiceRequest,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
B1,
>
where
M: Transform<
T::Service,
Request = ServiceRequest,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
B1: MessageBody,
F: IntoTransform<M, T::Service>,
{
let endpoint = apply_transform(mw, self.endpoint);
App {
endpoint,
data: self.data,
services: self.services,
default: self.default,
factory_ref: self.factory_ref,
config: self.config,
external: self.external,
_t: PhantomData,
}
}
/// Registers middleware, in the form of a closure, that runs during inbound
/// and/or outbound processing in the request lifecycle (request -> response),
/// modifying request/response as necessary, across all requests managed by
/// the *Application*.
///
/// Use middleware when you need to read or modify *every* request or response in some way.
///
/// ```rust
/// use actix_service::Service;
/// # use futures::Future;
/// use actix_web::{web, App};
/// use actix_web::http::{header::CONTENT_TYPE, HeaderValue};
///
/// fn index() -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .wrap_fn(|req, srv|
/// srv.call(req).map(|mut res| {
/// res.headers_mut().insert(
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
/// );
/// res
/// }))
/// .route("/index.html", web::get().to(index));
/// }
/// ```
pub fn wrap_fn<B1, F, R>(
self,
mw: F,
) -> App<
impl NewService<
Request = ServiceRequest,
Response = ServiceResponse<B1>,
Error = Error,
InitError = (),
>,
B1,
>
where
B1: MessageBody,
F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
R: IntoFuture<Item = ServiceResponse<B1>, Error = Error>,
{
self.wrap(mw)
}
} }
impl<C, T, P: 'static, B: MessageBody> IntoNewService<AppInit<C, T, P, B>, ServerConfig> impl<T, B> IntoNewService<AppInit<T, B>, ServerConfig> for App<T, B>
for AppRouter<C, P, B, T>
where where
B: MessageBody,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
{ {
fn into_new_service(self) -> AppInit<C, T, P, B> { fn into_new_service(self) -> AppInit<T, B> {
AppInit { AppInit {
chain: self.chain, data: Rc::new(self.data),
data: self.data,
endpoint: self.endpoint, endpoint: self.endpoint,
services: RefCell::new(self.services), services: Rc::new(RefCell::new(self.services)),
external: RefCell::new(self.external), external: RefCell::new(self.external),
default: self.default, default: self.default,
factory_ref: self.factory_ref, factory_ref: self.factory_ref,
@@ -676,13 +426,16 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_service::Service; use actix_service::Service;
use bytes::Bytes;
use futures::{Future, IntoFuture}; use futures::{Future, IntoFuture};
use super::*; use super::*;
use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
use crate::test::{block_on, call_success, init_service, TestRequest}; use crate::test::{
use crate::{web, Error, HttpResponse}; block_fn, block_on, call_service, init_service, read_body, TestRequest,
};
use crate::{web, Error, HttpRequest, HttpResponse};
#[test] #[test]
fn test_default_resource() { fn test_default_resource() {
@@ -690,7 +443,7 @@ mod tests {
App::new().service(web::resource("/test").to(|| HttpResponse::Ok())), App::new().service(web::resource("/test").to(|| HttpResponse::Ok())),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = block_on(srv.call(req)).unwrap(); let resp = block_fn(|| srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/blah").to_request(); let req = TestRequest::with_uri("/blah").to_request();
@@ -702,10 +455,14 @@ mod tests {
.service(web::resource("/test").to(|| HttpResponse::Ok())) .service(web::resource("/test").to(|| HttpResponse::Ok()))
.service( .service(
web::resource("/test2") web::resource("/test2")
.default_resource(|r| r.to(|| HttpResponse::Created())) .default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::Created())
})
.route(web::get().to(|| HttpResponse::Ok())), .route(web::get().to(|| HttpResponse::Ok())),
) )
.default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())), .default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::MethodNotAllowed())
}),
); );
let req = TestRequest::with_uri("/blah").to_request(); let req = TestRequest::with_uri("/blah").to_request();
@@ -723,32 +480,32 @@ mod tests {
assert_eq!(resp.status(), StatusCode::CREATED); assert_eq!(resp.status(), StatusCode::CREATED);
} }
#[test] // #[test]
fn test_data_factory() { // fn test_data_factory() {
let mut srv = // let mut srv =
init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service( // init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()), // web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
)); // ));
let req = TestRequest::default().to_request(); // let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap(); // let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK); // assert_eq!(resp.status(), StatusCode::OK);
let mut srv = // let mut srv =
init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service( // init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()), // web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
)); // ));
let req = TestRequest::default().to_request(); // let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap(); // let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); // assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
} // }
fn md<S, P, B>( fn md<S, B>(
req: ServiceRequest<P>, req: ServiceRequest,
srv: &mut S, srv: &mut S,
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error> ) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
where where
S: Service< S: Service<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
>, >,
@@ -768,7 +525,7 @@ mod tests {
.route("/test", web::get().to(|| HttpResponse::Ok())), .route("/test", web::get().to(|| HttpResponse::Ok())),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -784,7 +541,7 @@ mod tests {
.wrap(md), .wrap(md),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -808,7 +565,7 @@ mod tests {
.service(web::resource("/test").to(|| HttpResponse::Ok())), .service(web::resource("/test").to(|| HttpResponse::Ok())),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -832,11 +589,33 @@ mod tests {
}), }),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
HeaderValue::from_static("0001") HeaderValue::from_static("0001")
); );
} }
#[test]
fn test_external_resource() {
let mut srv = init_service(
App::new()
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
.route(
"/test",
web::get().to(|req: HttpRequest| {
HttpResponse::Ok().body(format!(
"{}",
req.url_for("youtube", &["12345"]).unwrap()
))
}),
),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp);
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
}
} }

View File

@@ -2,16 +2,16 @@ use std::cell::RefCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use actix_http::{Request, Response}; use actix_http::{Extensions, Request, Response};
use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url};
use actix_server_config::ServerConfig; use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{fn_service, AndThen, NewService, Service, ServiceExt}; use actix_service::{fn_service, NewService, Service};
use futures::future::{ok, Either, FutureResult}; use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use crate::config::{AppConfig, ServiceConfig}; use crate::config::{AppConfig, AppService};
use crate::data::{DataFactory, DataFactoryResult}; use crate::data::DataFactory;
use crate::error::Error; use crate::error::Error;
use crate::guard::Guard; use crate::guard::Guard;
use crate::request::{HttpRequest, HttpRequestPool}; use crate::request::{HttpRequest, HttpRequestPool};
@@ -19,9 +19,8 @@ use crate::rmap::ResourceMap;
use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse};
type Guards = Vec<Box<Guard>>; type Guards = Vec<Box<Guard>>;
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>; type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService<P> = type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
type BoxedResponse = Either< type BoxedResponse = Either<
FutureResult<ServiceResponse, Error>, FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>, Box<Future<Item = ServiceResponse, Error = Error>>,
@@ -29,36 +28,28 @@ type BoxedResponse = Either<
/// Service factory to convert `Request` to a `ServiceRequest<S>`. /// Service factory to convert `Request` to a `ServiceRequest<S>`.
/// It also executes data factories. /// It also executes data factories.
pub struct AppInit<C, T, P, B> pub struct AppInit<T, B>
where where
C: NewService<Request = ServiceRequest, Response = ServiceRequest<P>>,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
{ {
pub(crate) chain: C,
pub(crate) endpoint: T, pub(crate) endpoint: T,
pub(crate) data: Vec<Box<DataFactory>>, pub(crate) data: Rc<Vec<Box<DataFactory>>>,
pub(crate) config: RefCell<AppConfig>, pub(crate) config: RefCell<AppConfig>,
pub(crate) services: RefCell<Vec<Box<ServiceFactory<P>>>>, pub(crate) services: Rc<RefCell<Vec<Box<ServiceFactory>>>>,
pub(crate) default: Option<Rc<HttpNewService<P>>>, pub(crate) default: Option<Rc<HttpNewService>>,
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory<P>>>>, pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
pub(crate) external: RefCell<Vec<ResourceDef>>, pub(crate) external: RefCell<Vec<ResourceDef>>,
} }
impl<C, T, P: 'static, B> NewService<ServerConfig> for AppInit<C, T, P, B> impl<T, B> NewService<ServerConfig> for AppInit<T, B>
where where
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -66,19 +57,20 @@ where
{ {
type Request = Request; type Request = Request;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = C::Error; type Error = T::Error;
type InitError = C::InitError; type InitError = T::InitError;
type Service = AndThen<AppInitService<C::Service, P>, T::Service>; type Service = AppInitService<T::Service, B>;
type Future = AppInitResult<C, T, P, B>; type Future = AppInitResult<T, B>;
fn new_service(&self, cfg: &ServerConfig) -> Self::Future { fn new_service(&self, cfg: &ServerConfig) -> Self::Future {
// update resource default service // update resource default service
let default = self.default.clone().unwrap_or_else(|| { let default = self.default.clone().unwrap_or_else(|| {
Rc::new(boxed::new_service(fn_service(|req: ServiceRequest<P>| { Rc::new(boxed::new_service(fn_service(|req: ServiceRequest| {
Ok(req.into_response(Response::NotFound().finish())) Ok(req.into_response(Response::NotFound().finish()))
}))) })))
}); });
// App config
{ {
let mut c = self.config.borrow_mut(); let mut c = self.config.borrow_mut();
let loc_cfg = Rc::get_mut(&mut c.0).unwrap(); let loc_cfg = Rc::get_mut(&mut c.0).unwrap();
@@ -86,8 +78,11 @@ where
loc_cfg.addr = cfg.local_addr(); loc_cfg.addr = cfg.local_addr();
} }
let mut config = let mut config = AppService::new(
ServiceConfig::new(self.config.borrow().clone(), default.clone()); self.config.borrow().clone(),
default.clone(),
self.data.clone(),
);
// register services // register services
std::mem::replace(&mut *self.services.borrow_mut(), Vec::new()) std::mem::replace(&mut *self.services.borrow_mut(), Vec::new())
@@ -96,12 +91,13 @@ where
let mut rmap = ResourceMap::new(ResourceDef::new("")); let mut rmap = ResourceMap::new(ResourceDef::new(""));
let (config, services) = config.into_services();
// complete pipeline creation // complete pipeline creation
*self.factory_ref.borrow_mut() = Some(AppRoutingFactory { *self.factory_ref.borrow_mut() = Some(AppRoutingFactory {
default, default,
services: Rc::new( services: Rc::new(
config services
.into_services()
.into_iter() .into_iter()
.map(|(mut rdef, srv, guards, nested)| { .map(|(mut rdef, srv, guards, nested)| {
rmap.add(&mut rdef, nested); rmap.add(&mut rdef, nested);
@@ -120,85 +116,62 @@ where
let rmap = Rc::new(rmap); let rmap = Rc::new(rmap);
rmap.finish(rmap.clone()); rmap.finish(rmap.clone());
// create app data container
let mut data = Extensions::new();
for f in self.data.iter() {
f.create(&mut data);
}
AppInitResult { AppInitResult {
chain: None,
chain_fut: self.chain.new_service(&()),
endpoint: None, endpoint: None,
endpoint_fut: self.endpoint.new_service(&()), endpoint_fut: self.endpoint.new_service(&()),
data: self.data.iter().map(|s| s.construct()).collect(), data: Rc::new(data),
config: self.config.borrow().clone(), config,
rmap, rmap,
_t: PhantomData, _t: PhantomData,
} }
} }
} }
pub struct AppInitResult<C, T, P, B> pub struct AppInitResult<T, B>
where where
C: NewService,
T: NewService, T: NewService,
{ {
chain: Option<C::Service>,
endpoint: Option<T::Service>, endpoint: Option<T::Service>,
chain_fut: C::Future,
endpoint_fut: T::Future, endpoint_fut: T::Future,
rmap: Rc<ResourceMap>, rmap: Rc<ResourceMap>,
data: Vec<Box<DataFactoryResult>>,
config: AppConfig, config: AppConfig,
_t: PhantomData<(P, B)>, data: Rc<Extensions>,
_t: PhantomData<B>,
} }
impl<C, T, P, B> Future for AppInitResult<C, T, P, B> impl<T, B> Future for AppInitResult<T, B>
where where
C: NewService<
Request = ServiceRequest,
Response = ServiceRequest<P>,
Error = Error,
InitError = (),
>,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
{ {
type Item = AndThen<AppInitService<C::Service, P>, T::Service>; type Item = AppInitService<T::Service, B>;
type Error = C::InitError; type Error = T::InitError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut idx = 0;
let mut extensions = self.config.0.extensions.borrow_mut();
while idx < self.data.len() {
if let Async::Ready(_) = self.data[idx].poll_result(&mut extensions)? {
self.data.remove(idx);
} else {
idx += 1;
}
}
if self.chain.is_none() {
if let Async::Ready(srv) = self.chain_fut.poll()? {
self.chain = Some(srv);
}
}
if self.endpoint.is_none() { if self.endpoint.is_none() {
if let Async::Ready(srv) = self.endpoint_fut.poll()? { if let Async::Ready(srv) = self.endpoint_fut.poll()? {
self.endpoint = Some(srv); self.endpoint = Some(srv);
} }
} }
if self.chain.is_some() && self.endpoint.is_some() { if self.endpoint.is_some() {
Ok(Async::Ready( Ok(Async::Ready(AppInitService {
AppInitService { service: self.endpoint.take().unwrap(),
chain: self.chain.take().unwrap(), rmap: self.rmap.clone(),
rmap: self.rmap.clone(), config: self.config.clone(),
config: self.config.clone(), data: self.data.clone(),
pool: HttpRequestPool::create(), pool: HttpRequestPool::create(),
} }))
.and_then(self.endpoint.take().unwrap()),
))
} else { } else {
Ok(Async::NotReady) Ok(Async::NotReady)
} }
@@ -206,27 +179,28 @@ where
} }
/// Service to convert `Request` to a `ServiceRequest<S>` /// Service to convert `Request` to a `ServiceRequest<S>`
pub struct AppInitService<C, P> pub struct AppInitService<T: Service, B>
where where
C: Service<Request = ServiceRequest, Response = ServiceRequest<P>, Error = Error>, T: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
chain: C, service: T,
rmap: Rc<ResourceMap>, rmap: Rc<ResourceMap>,
config: AppConfig, config: AppConfig,
data: Rc<Extensions>,
pool: &'static HttpRequestPool, pool: &'static HttpRequestPool,
} }
impl<C, P> Service for AppInitService<C, P> impl<T, B> Service for AppInitService<T, B>
where where
C: Service<Request = ServiceRequest, Response = ServiceRequest<P>, Error = Error>, T: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
type Request = Request; type Request = Request;
type Response = ServiceRequest<P>; type Response = ServiceResponse<B>;
type Error = C::Error; type Error = T::Error;
type Future = C::Future; type Future = T::Future;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.chain.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: Request) -> Self::Future { fn call(&mut self, req: Request) -> Self::Future {
@@ -237,6 +211,7 @@ where
inner.path.get_mut().update(&head.uri); inner.path.get_mut().update(&head.uri);
inner.path.reset(); inner.path.reset();
inner.head = head; inner.head = head;
inner.app_data = self.data.clone();
req req
} else { } else {
HttpRequest::new( HttpRequest::new(
@@ -244,25 +219,26 @@ where
head, head,
self.rmap.clone(), self.rmap.clone(),
self.config.clone(), self.config.clone(),
self.data.clone(),
self.pool, self.pool,
) )
}; };
self.chain.call(ServiceRequest::from_parts(req, payload)) self.service.call(ServiceRequest::from_parts(req, payload))
} }
} }
pub struct AppRoutingFactory<P> { pub struct AppRoutingFactory {
services: Rc<Vec<(ResourceDef, HttpNewService<P>, RefCell<Option<Guards>>)>>, services: Rc<Vec<(ResourceDef, HttpNewService, RefCell<Option<Guards>>)>>,
default: Rc<HttpNewService<P>>, default: Rc<HttpNewService>,
} }
impl<P: 'static> NewService for AppRoutingFactory<P> { impl NewService for AppRoutingFactory {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = AppRouting<P>; type Service = AppRouting;
type Future = AppRoutingFactoryResponse<P>; type Future = AppRoutingFactoryResponse;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
AppRoutingFactoryResponse { AppRoutingFactoryResponse {
@@ -283,23 +259,23 @@ impl<P: 'static> NewService for AppRoutingFactory<P> {
} }
} }
type HttpServiceFut<P> = Box<Future<Item = HttpService<P>, Error = ()>>; type HttpServiceFut = Box<Future<Item = HttpService, Error = ()>>;
/// Create app service /// Create app service
#[doc(hidden)] #[doc(hidden)]
pub struct AppRoutingFactoryResponse<P> { pub struct AppRoutingFactoryResponse {
fut: Vec<CreateAppRoutingItem<P>>, fut: Vec<CreateAppRoutingItem>,
default: Option<HttpService<P>>, default: Option<HttpService>,
default_fut: Option<Box<Future<Item = HttpService<P>, Error = ()>>>, default_fut: Option<Box<Future<Item = HttpService, Error = ()>>>,
} }
enum CreateAppRoutingItem<P> { enum CreateAppRoutingItem {
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut<P>), Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut),
Service(ResourceDef, Option<Guards>, HttpService<P>), Service(ResourceDef, Option<Guards>, HttpService),
} }
impl<P> Future for AppRoutingFactoryResponse<P> { impl Future for AppRoutingFactoryResponse {
type Item = AppRouting<P>; type Item = AppRouting;
type Error = (); type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -360,14 +336,14 @@ impl<P> Future for AppRoutingFactoryResponse<P> {
} }
} }
pub struct AppRouting<P> { pub struct AppRouting {
router: Router<HttpService<P>, Guards>, router: Router<HttpService, Guards>,
ready: Option<(ServiceRequest<P>, ResourceInfo)>, ready: Option<(ServiceRequest, ResourceInfo)>,
default: Option<HttpService<P>>, default: Option<HttpService>,
} }
impl<P> Service for AppRouting<P> { impl Service for AppRouting {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = BoxedResponse; type Future = BoxedResponse;
@@ -380,7 +356,7 @@ impl<P> Service for AppRouting<P> {
} }
} }
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let res = self.router.recognize_mut_checked(&mut req, |req, guards| { let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
if let Some(ref guards) = guards { if let Some(ref guards) = guards {
for f in guards { for f in guards {
@@ -404,58 +380,25 @@ impl<P> Service for AppRouting<P> {
} }
/// Wrapper service for routing /// Wrapper service for routing
pub struct AppEntry<P> { pub struct AppEntry {
factory: Rc<RefCell<Option<AppRoutingFactory<P>>>>, factory: Rc<RefCell<Option<AppRoutingFactory>>>,
} }
impl<P> AppEntry<P> { impl AppEntry {
pub fn new(factory: Rc<RefCell<Option<AppRoutingFactory<P>>>>) -> Self { pub fn new(factory: Rc<RefCell<Option<AppRoutingFactory>>>) -> Self {
AppEntry { factory } AppEntry { factory }
} }
} }
impl<P: 'static> NewService for AppEntry<P> { impl NewService for AppEntry {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = AppRouting<P>; type Service = AppRouting;
type Future = AppRoutingFactoryResponse<P>; type Future = AppRoutingFactoryResponse;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
self.factory.borrow_mut().as_mut().unwrap().new_service(&()) self.factory.borrow_mut().as_mut().unwrap().new_service(&())
} }
} }
#[doc(hidden)]
pub struct AppChain;
impl NewService for AppChain {
type Request = ServiceRequest;
type Response = ServiceRequest;
type Error = Error;
type InitError = ();
type Service = AppChain;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(AppChain)
}
}
impl Service for AppChain {
type Request = ServiceRequest;
type Response = ServiceRequest;
type Error = Error;
type Future = FutureResult<Self::Response, Self::Error>;
#[inline]
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
#[inline]
fn call(&mut self, req: ServiceRequest) -> Self::Future {
ok(req)
}
}

View File

@@ -1,11 +1,9 @@
use std::cell::{Ref, RefCell};
use std::net::SocketAddr; use std::net::SocketAddr;
use std::rc::Rc; use std::rc::Rc;
use actix_http::Extensions; use actix_http::Extensions;
use actix_router::ResourceDef; use actix_router::ResourceDef;
use actix_service::{boxed, IntoNewService, NewService}; use actix_service::{boxed, IntoNewService, NewService};
use futures::IntoFuture;
use crate::data::{Data, DataFactory}; use crate::data::{Data, DataFactory};
use crate::error::Error; use crate::error::Error;
@@ -19,28 +17,34 @@ use crate::service::{
}; };
type Guards = Vec<Box<Guard>>; type Guards = Vec<Box<Guard>>;
type HttpNewService<P> = type HttpNewService =
boxed::BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>; boxed::BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
/// Application configuration /// Application configuration
pub struct ServiceConfig<P> { pub struct AppService {
config: AppConfig, config: AppConfig,
root: bool, root: bool,
default: Rc<HttpNewService<P>>, default: Rc<HttpNewService>,
services: Vec<( services: Vec<(
ResourceDef, ResourceDef,
HttpNewService<P>, HttpNewService,
Option<Guards>, Option<Guards>,
Option<Rc<ResourceMap>>, Option<Rc<ResourceMap>>,
)>, )>,
route_data: Rc<Vec<Box<DataFactory>>>,
} }
impl<P: 'static> ServiceConfig<P> { impl AppService {
/// Crate server settings instance /// Crate server settings instance
pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService<P>>) -> Self { pub(crate) fn new(
ServiceConfig { config: AppConfig,
default: Rc<HttpNewService>,
route_data: Rc<Vec<Box<DataFactory>>>,
) -> Self {
AppService {
config, config,
default, default,
route_data,
root: true, root: true,
services: Vec::new(), services: Vec::new(),
} }
@@ -53,21 +57,25 @@ impl<P: 'static> ServiceConfig<P> {
pub(crate) fn into_services( pub(crate) fn into_services(
self, self,
) -> Vec<( ) -> (
ResourceDef, AppConfig,
HttpNewService<P>, Vec<(
Option<Guards>, ResourceDef,
Option<Rc<ResourceMap>>, HttpNewService,
)> { Option<Guards>,
self.services Option<Rc<ResourceMap>>,
)>,
) {
(self.config, self.services)
} }
pub(crate) fn clone_config(&self) -> Self { pub(crate) fn clone_config(&self) -> Self {
ServiceConfig { AppService {
config: self.config.clone(), config: self.config.clone(),
default: self.default.clone(), default: self.default.clone(),
services: Vec::new(), services: Vec::new(),
root: false, root: false,
route_data: self.route_data.clone(),
} }
} }
@@ -77,10 +85,19 @@ impl<P: 'static> ServiceConfig<P> {
} }
/// Default resource /// Default resource
pub fn default_service(&self) -> Rc<HttpNewService<P>> { pub fn default_service(&self) -> Rc<HttpNewService> {
self.default.clone() self.default.clone()
} }
/// Set global route data
pub fn set_route_data(&self, extensions: &mut Extensions) -> bool {
for f in self.route_data.iter() {
f.create(extensions);
}
!self.route_data.is_empty()
}
/// Register http service
pub fn register_service<F, S>( pub fn register_service<F, S>(
&mut self, &mut self,
rdef: ResourceDef, rdef: ResourceDef,
@@ -90,7 +107,7 @@ impl<P: 'static> ServiceConfig<P> {
) where ) where
F: IntoNewService<S>, F: IntoNewService<S>,
S: NewService< S: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -133,24 +150,12 @@ impl AppConfig {
pub fn local_addr(&self) -> SocketAddr { pub fn local_addr(&self) -> SocketAddr {
self.0.addr self.0.addr
} }
/// Resource map
pub fn rmap(&self) -> &ResourceMap {
&self.0.rmap
}
/// Application extensions
pub fn extensions(&self) -> Ref<Extensions> {
self.0.extensions.borrow()
}
} }
pub(crate) struct AppConfigInner { pub(crate) struct AppConfigInner {
pub(crate) secure: bool, pub(crate) secure: bool,
pub(crate) host: String, pub(crate) host: String,
pub(crate) addr: SocketAddr, pub(crate) addr: SocketAddr,
pub(crate) rmap: ResourceMap,
pub(crate) extensions: RefCell<Extensions>,
} }
impl Default for AppConfigInner { impl Default for AppConfigInner {
@@ -159,23 +164,21 @@ impl Default for AppConfigInner {
secure: false, secure: false,
addr: "127.0.0.1:8080".parse().unwrap(), addr: "127.0.0.1:8080".parse().unwrap(),
host: "localhost:8080".to_owned(), host: "localhost:8080".to_owned(),
rmap: ResourceMap::new(ResourceDef::new("")),
extensions: RefCell::new(Extensions::new()),
} }
} }
} }
/// Router config. It is used for external configuration. /// Service config is used for external configuration.
/// Part of application configuration could be offloaded /// Part of application configuration could be offloaded
/// to set of external methods. This could help with /// to set of external methods. This could help with
/// modularization of big application configuration. /// modularization of big application configuration.
pub struct RouterConfig<P: 'static> { pub struct ServiceConfig {
pub(crate) services: Vec<Box<ServiceFactory<P>>>, pub(crate) services: Vec<Box<ServiceFactory>>,
pub(crate) data: Vec<Box<DataFactory>>, pub(crate) data: Vec<Box<DataFactory>>,
pub(crate) external: Vec<ResourceDef>, pub(crate) external: Vec<ResourceDef>,
} }
impl<P: 'static> RouterConfig<P> { impl ServiceConfig {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
services: Vec::new(), services: Vec::new(),
@@ -188,30 +191,15 @@ impl<P: 'static> RouterConfig<P> {
/// by using `Data<T>` extractor where `T` is data type. /// by using `Data<T>` extractor where `T` is data type.
/// ///
/// This is same as `App::data()` method. /// This is same as `App::data()` method.
pub fn data<S: 'static>(&mut self, data: S) -> &mut Self { pub fn data<S: Into<Data<S>> + 'static>(&mut self, data: S) -> &mut Self {
self.data.push(Box::new(Data::new(data))); self.data.push(Box::new(data.into()));
self
}
/// Set application data factory. This function is
/// similar to `.data()` but it accepts data factory. Data object get
/// constructed asynchronously during application initialization.
///
/// This is same as `App::data_dactory()` method.
pub fn data_factory<F, R>(&mut self, data: F) -> &mut Self
where
F: Fn() -> R + 'static,
R: IntoFuture + 'static,
R::Error: std::fmt::Debug,
{
self.data.push(Box::new(data));
self self
} }
/// Configure route for a specific path. /// Configure route for a specific path.
/// ///
/// This is same as `App::route()` method. /// This is same as `App::route()` method.
pub fn route(&mut self, path: &str, mut route: Route<P>) -> &mut Self { pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self {
self.service( self.service(
Resource::new(path) Resource::new(path)
.add_guards(route.take_guards()) .add_guards(route.take_guards())
@@ -224,7 +212,7 @@ impl<P: 'static> RouterConfig<P> {
/// This is same as `App::service()` method. /// This is same as `App::service()` method.
pub fn service<F>(&mut self, factory: F) -> &mut Self pub fn service<F>(&mut self, factory: F) -> &mut Self
where where
F: HttpServiceFactory<P> + 'static, F: HttpServiceFactory + 'static,
{ {
self.services self.services
.push(Box::new(ServiceFactoryWrapper::new(factory))); .push(Box::new(ServiceFactoryWrapper::new(factory)));
@@ -253,15 +241,16 @@ impl<P: 'static> RouterConfig<P> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_service::Service; use actix_service::Service;
use bytes::Bytes;
use super::*; use super::*;
use crate::http::StatusCode; use crate::http::{Method, StatusCode};
use crate::test::{block_on, init_service, TestRequest}; use crate::test::{block_on, call_service, init_service, read_body, TestRequest};
use crate::{web, App, HttpResponse}; use crate::{web, App, HttpRequest, HttpResponse};
#[test] #[test]
fn test_data() { fn test_data() {
let cfg = |cfg: &mut RouterConfig<_>| { let cfg = |cfg: &mut ServiceConfig| {
cfg.data(10usize); cfg.data(10usize);
}; };
@@ -274,30 +263,84 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
// #[test]
// fn test_data_factory() {
// let cfg = |cfg: &mut ServiceConfig| {
// cfg.data_factory(|| {
// sleep(std::time::Duration::from_millis(50)).then(|_| {
// println!("READY");
// Ok::<_, ()>(10usize)
// })
// });
// };
// let mut srv =
// init_service(App::new().configure(cfg).service(
// web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
// ));
// let req = TestRequest::default().to_request();
// let resp = block_on(srv.call(req)).unwrap();
// assert_eq!(resp.status(), StatusCode::OK);
// let cfg2 = |cfg: &mut ServiceConfig| {
// cfg.data_factory(|| Ok::<_, ()>(10u32));
// };
// let mut srv = init_service(
// App::new()
// .service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()))
// .configure(cfg2),
// );
// let req = TestRequest::default().to_request();
// let resp = block_on(srv.call(req)).unwrap();
// assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
// }
#[test] #[test]
fn test_data_factory() { fn test_external_resource() {
let cfg = |cfg: &mut RouterConfig<_>| {
cfg.data_factory(|| Ok::<_, ()>(10usize));
};
let mut srv =
init_service(App::new().configure(cfg).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let cfg2 = |cfg: &mut RouterConfig<_>| {
cfg.data_factory(|| Ok::<_, ()>(10u32));
};
let mut srv = init_service( let mut srv = init_service(
App::new() App::new()
.service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())) .configure(|cfg| {
.configure(cfg2), cfg.external_resource(
"youtube",
"https://youtube.com/watch/{video_id}",
);
})
.route(
"/test",
web::get().to(|req: HttpRequest| {
HttpResponse::Ok().body(format!(
"{}",
req.url_for("youtube", &["12345"]).unwrap()
))
}),
),
); );
let req = TestRequest::default().to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = block_on(srv.call(req)).unwrap(); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp);
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
}
#[test]
fn test_service() {
let mut srv = init_service(App::new().configure(|cfg| {
cfg.service(
web::resource("/test").route(web::get().to(|| HttpResponse::Created())),
)
.route("/index.html", web::get().to(|| HttpResponse::Ok()));
}));
let req = TestRequest::with_uri("/test")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/index.html")
.method(Method::GET)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
} }
} }

View File

@@ -3,7 +3,6 @@ use std::sync::Arc;
use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::error::{Error, ErrorInternalServerError};
use actix_http::Extensions; use actix_http::Extensions;
use futures::{Async, Future, IntoFuture, Poll};
use crate::dev::Payload; use crate::dev::Payload;
use crate::extract::FromRequest; use crate::extract::FromRequest;
@@ -11,11 +10,7 @@ use crate::request::HttpRequest;
/// Application data factory /// Application data factory
pub(crate) trait DataFactory { pub(crate) trait DataFactory {
fn construct(&self) -> Box<DataFactoryResult>; fn create(&self, extensions: &mut Extensions) -> bool;
}
pub(crate) trait DataFactoryResult {
fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()>;
} }
/// Application data. /// Application data.
@@ -33,38 +28,48 @@ pub(crate) trait DataFactoryResult {
/// instance for each thread, thus application data must be constructed /// instance for each thread, thus application data must be constructed
/// multiple times. If you want to share data between different /// multiple times. If you want to share data between different
/// threads, a shareable object should be used, e.g. `Send + Sync`. Application /// threads, a shareable object should be used, e.g. `Send + Sync`. Application
/// data does not need to be `Send` or `Sync`. Internally `Data` instance /// data does not need to be `Send` or `Sync`. Internally `Data` type
/// uses `Arc`. /// uses `Arc`. if your data implements `Send` + `Sync` traits you can
/// use `web::Data::new()` and avoid double `Arc`.
/// ///
/// If route data is not set for a handler, using `Data<T>` extractor would /// If route data is not set for a handler, using `Data<T>` extractor would
/// cause *Internal Server Error* response. /// cause *Internal Server Error* response.
/// ///
/// ```rust /// ```rust
/// use std::cell::Cell; /// use std::sync::Mutex;
/// use actix_web::{web, App}; /// use actix_web::{web, App};
/// ///
/// struct MyData { /// struct MyData {
/// counter: Cell<usize>, /// counter: usize,
/// } /// }
/// ///
/// /// Use `Data<T>` extractor to access data in handler. /// /// Use `Data<T>` extractor to access data in handler.
/// fn index(data: web::Data<MyData>) { /// fn index(data: web::Data<Mutex<MyData>>) {
/// data.counter.set(data.counter.get() + 1); /// let mut data = data.lock().unwrap();
/// data.counter += 1;
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
///
/// let app = App::new() /// let app = App::new()
/// // Store `MyData` in application storage. /// // Store `MyData` in application storage.
/// .data(MyData{ counter: Cell::new(0) }) /// .data(data.clone())
/// .service( /// .service(
/// web::resource("/index.html").route( /// web::resource("/index.html").route(
/// web::get().to(index))); /// web::get().to(index)));
/// } /// }
/// ``` /// ```
#[derive(Debug)]
pub struct Data<T>(Arc<T>); pub struct Data<T>(Arc<T>);
impl<T> Data<T> { impl<T> Data<T> {
pub(crate) fn new(state: T) -> Data<T> { /// Create new `Data` instance.
///
/// Internally `Data` type uses `Arc`. if your data implements
/// `Send` + `Sync` traits you can use `web::Data::new()` and
/// avoid double `Arc`.
pub fn new(state: T) -> Data<T> {
Data(Arc::new(state)) Data(Arc::new(state))
} }
@@ -88,14 +93,21 @@ impl<T> Clone for Data<T> {
} }
} }
impl<T: 'static, P> FromRequest<P> for Data<T> { impl<T> From<T> for Data<T> {
fn from(data: T) -> Self {
Data::new(data)
}
}
impl<T: 'static> FromRequest for Data<T> {
type Config = ();
type Error = Error; type Error = Error;
type Future = Result<Self, Error>; type Future = Result<Self, Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
if let Some(st) = req.app_config().extensions().get::<Data<T>>() { if let Some(st) = req.get_app_data::<T>() {
Ok(st.clone()) Ok(st)
} else { } else {
log::debug!( log::debug!(
"Failed to construct App-level Data extractor. \ "Failed to construct App-level Data extractor. \
@@ -110,141 +122,12 @@ impl<T: 'static, P> FromRequest<P> for Data<T> {
} }
impl<T: 'static> DataFactory for Data<T> { impl<T: 'static> DataFactory for Data<T> {
fn construct(&self) -> Box<DataFactoryResult> { fn create(&self, extensions: &mut Extensions) -> bool {
Box::new(DataFut { st: self.clone() }) if !extensions.contains::<Data<T>>() {
} let _ = extensions.insert(Data(self.0.clone()));
} true
struct DataFut<T> {
st: Data<T>,
}
impl<T: 'static> DataFactoryResult for DataFut<T> {
fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> {
extensions.insert(self.st.clone());
Ok(Async::Ready(()))
}
}
impl<F, Out> DataFactory for F
where
F: Fn() -> Out + 'static,
Out: IntoFuture + 'static,
Out::Error: std::fmt::Debug,
{
fn construct(&self) -> Box<DataFactoryResult> {
Box::new(DataFactoryFut {
fut: (*self)().into_future(),
})
}
}
struct DataFactoryFut<T, F>
where
F: Future<Item = T>,
F::Error: std::fmt::Debug,
{
fut: F,
}
impl<T: 'static, F> DataFactoryResult for DataFactoryFut<T, F>
where
F: Future<Item = T>,
F::Error: std::fmt::Debug,
{
fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> {
match self.fut.poll() {
Ok(Async::Ready(s)) => {
extensions.insert(Data::new(s));
Ok(Async::Ready(()))
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => {
log::error!("Can not construct application state: {:?}", e);
Err(())
}
}
}
}
/// Route data.
///
/// Route data is an arbitrary data attached to specific route.
/// Route data could be added to route during route configuration process
/// with `Route::data()` method. Route data is also used as an extractor
/// configuration storage. Route data could be accessed in handler
/// via `RouteData<T>` extractor.
///
/// If route data is not set for a handler, using `RouteData` extractor
/// would cause *Internal Server Error* response.
///
/// ```rust
/// # use std::cell::Cell;
/// use actix_web::{web, App};
///
/// struct MyData {
/// counter: Cell<usize>,
/// }
///
/// /// Use `RouteData<T>` extractor to access data in handler.
/// fn index(data: web::RouteData<MyData>) {
/// data.counter.set(data.counter.get() + 1);
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::get()
/// // Store `MyData` in route storage
/// .data(MyData{ counter: Cell::new(0) })
/// // Route data could be used as extractor configuration storage,
/// // limit size of the payload
/// .data(web::PayloadConfig::new(4096))
/// // register handler
/// .to(index)
/// ));
/// }
/// ```
pub struct RouteData<T>(Arc<T>);
impl<T> RouteData<T> {
pub(crate) fn new(state: T) -> RouteData<T> {
RouteData(Arc::new(state))
}
/// Get referecnce to inner data object.
pub fn get_ref(&self) -> &T {
self.0.as_ref()
}
}
impl<T> Deref for RouteData<T> {
type Target = T;
fn deref(&self) -> &T {
self.0.as_ref()
}
}
impl<T> Clone for RouteData<T> {
fn clone(&self) -> RouteData<T> {
RouteData(self.0.clone())
}
}
impl<T: 'static, P> FromRequest<P> for RouteData<T> {
type Error = Error;
type Future = Result<Self, Error>;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
if let Some(st) = req.route_data::<T>() {
Ok(st.clone())
} else { } else {
log::debug!("Failed to construct Route-level Data extractor"); false
Err(ErrorInternalServerError(
"Route data is not configured, to configure use Route::data()",
))
} }
} }
} }
@@ -279,12 +162,13 @@ mod tests {
#[test] #[test]
fn test_route_data_extractor() { fn test_route_data_extractor() {
let mut srv = init_service(App::new().service(web::resource("/").route( let mut srv =
web::get().data(10usize).to(|data: web::RouteData<usize>| { init_service(App::new().service(web::resource("/").data(10usize).route(
let _ = data.clone(); web::get().to(|data: web::Data<usize>| {
HttpResponse::Ok() let _ = data.clone();
}), HttpResponse::Ok()
))); }),
)));
let req = TestRequest::default().to_request(); let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap(); let resp = block_on(srv.call(req)).unwrap();
@@ -293,15 +177,30 @@ mod tests {
// different type // different type
let mut srv = init_service( let mut srv = init_service(
App::new().service( App::new().service(
web::resource("/").route( web::resource("/")
web::get() .data(10u32)
.data(10u32) .route(web::get().to(|_: web::Data<usize>| HttpResponse::Ok())),
.to(|_: web::RouteData<usize>| HttpResponse::Ok()),
),
), ),
); );
let req = TestRequest::default().to_request(); let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap(); let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
#[test]
fn test_override_data() {
let mut srv = init_service(App::new().data(1usize).service(
web::resource("/").data(10usize).route(web::get().to(
|data: web::Data<usize>| {
assert_eq!(*data, 10);
let _ = data.clone();
HttpResponse::Ok()
},
)),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
} }

View File

@@ -10,15 +10,18 @@ use crate::request::HttpRequest;
/// Trait implemented by types that can be extracted from request. /// Trait implemented by types that can be extracted from request.
/// ///
/// Types that implement this trait can be used with `Route` handlers. /// Types that implement this trait can be used with `Route` handlers.
pub trait FromRequest<P>: Sized { pub trait FromRequest: Sized {
/// The associated error which can be returned. /// The associated error which can be returned.
type Error: Into<Error>; type Error: Into<Error>;
/// Future that resolves to a Self /// Future that resolves to a Self
type Future: IntoFuture<Item = Self, Error = Self::Error>; type Future: IntoFuture<Item = Self, Error = Self::Error>;
/// Configuration for this extractor
type Config: Default + 'static;
/// Convert request to a Self /// Convert request to a Self
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future; fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future;
/// Convert request to a Self /// Convert request to a Self
/// ///
@@ -26,6 +29,14 @@ pub trait FromRequest<P>: Sized {
fn extract(req: &HttpRequest) -> Self::Future { fn extract(req: &HttpRequest) -> Self::Future {
Self::from_request(req, &mut Payload::None) Self::from_request(req, &mut Payload::None)
} }
/// Create and configure config instance.
fn configure<F>(f: F) -> Self::Config
where
F: FnOnce(Self::Config) -> Self::Config,
{
f(Self::Config::default())
}
} }
/// Optionally extract a field from the request /// Optionally extract a field from the request
@@ -45,11 +56,12 @@ pub trait FromRequest<P>: Sized {
/// name: String /// name: String
/// } /// }
/// ///
/// impl<P> FromRequest<P> for Thing { /// impl FromRequest for Thing {
/// type Error = Error; /// type Error = Error;
/// type Future = Result<Self, Self::Error>; /// type Future = Result<Self, Self::Error>;
/// type Config = ();
/// ///
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future { /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
/// if rand::random() { /// if rand::random() {
/// Ok(Thing { name: "thingy".into() }) /// Ok(Thing { name: "thingy".into() })
/// } else { /// } else {
@@ -75,16 +87,17 @@ pub trait FromRequest<P>: Sized {
/// ); /// );
/// } /// }
/// ``` /// ```
impl<T: 'static, P> FromRequest<P> for Option<T> impl<T: 'static> FromRequest for Option<T>
where where
T: FromRequest<P>, T: FromRequest,
T::Future: 'static, T::Future: 'static,
{ {
type Config = T::Config;
type Error = Error; type Error = Error;
type Future = Box<Future<Item = Option<T>, Error = Error>>; type Future = Box<Future<Item = Option<T>, Error = Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
Box::new( Box::new(
T::from_request(req, payload) T::from_request(req, payload)
.into_future() .into_future()
@@ -116,11 +129,12 @@ where
/// name: String /// name: String
/// } /// }
/// ///
/// impl<P> FromRequest<P> for Thing { /// impl FromRequest for Thing {
/// type Error = Error; /// type Error = Error;
/// type Future = Result<Thing, Error>; /// type Future = Result<Thing, Error>;
/// type Config = ();
/// ///
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future { /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
/// if rand::random() { /// if rand::random() {
/// Ok(Thing { name: "thingy".into() }) /// Ok(Thing { name: "thingy".into() })
/// } else { /// } else {
@@ -143,17 +157,18 @@ where
/// ); /// );
/// } /// }
/// ``` /// ```
impl<T: 'static, P> FromRequest<P> for Result<T, T::Error> impl<T: 'static> FromRequest for Result<T, T::Error>
where where
T: FromRequest<P>, T: FromRequest,
T::Future: 'static, T::Future: 'static,
T::Error: 'static, T::Error: 'static,
{ {
type Config = T::Config;
type Error = Error; type Error = Error;
type Future = Box<Future<Item = Result<T, T::Error>, Error = Error>>; type Future = Box<Future<Item = Result<T, T::Error>, Error = Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
Box::new( Box::new(
T::from_request(req, payload) T::from_request(req, payload)
.into_future() .into_future()
@@ -166,11 +181,12 @@ where
} }
#[doc(hidden)] #[doc(hidden)]
impl<P> FromRequest<P> for () { impl FromRequest for () {
type Config = ();
type Error = Error; type Error = Error;
type Future = Result<(), Error>; type Future = Result<(), Error>;
fn from_request(_: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
Ok(()) Ok(())
} }
} }
@@ -179,12 +195,13 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
/// FromRequest implementation for tuple /// FromRequest implementation for tuple
#[doc(hidden)] #[doc(hidden)]
impl<P, $($T: FromRequest<P> + 'static),+> FromRequest<P> for ($($T,)+) impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+)
{ {
type Error = Error; type Error = Error;
type Future = $fut_type<P, $($T),+>; type Future = $fut_type<$($T),+>;
type Config = ($($T::Config),+);
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
$fut_type { $fut_type {
items: <($(Option<$T>,)+)>::default(), items: <($(Option<$T>,)+)>::default(),
futs: ($($T::from_request(req, payload).into_future(),)+), futs: ($($T::from_request(req, payload).into_future(),)+),
@@ -193,12 +210,12 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
} }
#[doc(hidden)] #[doc(hidden)]
pub struct $fut_type<P, $($T: FromRequest<P>),+> { pub struct $fut_type<$($T: FromRequest),+> {
items: ($(Option<$T>,)+), items: ($(Option<$T>,)+),
futs: ($(<$T::Future as futures::IntoFuture>::Future,)+), futs: ($(<$T::Future as futures::IntoFuture>::Future,)+),
} }
impl<P, $($T: FromRequest<P>),+> Future for $fut_type<P, $($T),+> impl<$($T: FromRequest),+> Future for $fut_type<$($T),+>
{ {
type Item = ($($T,)+); type Item = ($($T,)+);
type Error = Error; type Error = Error;
@@ -248,13 +265,12 @@ tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5,
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_http::http::header; use actix_http::http::header;
use actix_router::ResourceDef;
use bytes::Bytes; use bytes::Bytes;
use serde_derive::Deserialize; use serde_derive::Deserialize;
use super::*; use super::*;
use crate::test::{block_on, TestRequest}; use crate::test::{block_on, TestRequest};
use crate::types::{Form, FormConfig, Path, Query}; use crate::types::{Form, FormConfig};
#[derive(Deserialize, Debug, PartialEq)] #[derive(Deserialize, Debug, PartialEq)]
struct Info { struct Info {
@@ -267,7 +283,7 @@ mod tests {
header::CONTENT_TYPE, header::CONTENT_TYPE,
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
) )
.route_data(FormConfig::default().limit(4096)) .data(FormConfig::default().limit(4096))
.to_http_parts(); .to_http_parts();
let r = block_on(Option::<Form<Info>>::from_request(&req, &mut pl)).unwrap(); let r = block_on(Option::<Form<Info>>::from_request(&req, &mut pl)).unwrap();
@@ -333,58 +349,4 @@ mod tests {
block_on(Result::<Form<Info>, Error>::from_request(&req, &mut pl)).unwrap(); block_on(Result::<Form<Info>, Error>::from_request(&req, &mut pl)).unwrap();
assert!(r.is_err()); assert!(r.is_err());
} }
#[derive(Deserialize)]
struct MyStruct {
key: String,
value: String,
}
#[derive(Deserialize)]
struct Id {
id: String,
}
#[derive(Deserialize)]
struct Test2 {
key: String,
value: u32,
}
#[test]
fn test_request_extract() {
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let (req, mut pl) = req.into_parts();
let s = Path::<MyStruct>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.key, "name");
assert_eq!(s.value, "user1");
let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, "user1");
let s = Query::<Id>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.id, "test");
let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let (req, mut pl) = req.into_parts();
let s = Path::<Test2>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32);
let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
let res = Path::<Vec<String>>::from_request(&req, &mut pl).unwrap();
assert_eq!(res[0], "name".to_owned());
assert_eq!(res[1], "32".to_owned());
}
} }

View File

@@ -1,8 +1,6 @@
use std::cell::RefCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc;
use actix_http::{Error, Extensions, Payload, Response}; use actix_http::{Error, Payload, Response};
use actix_service::{NewService, Service, Void}; use actix_service::{NewService, Service, Void};
use futures::future::{ok, FutureResult}; use futures::future::{ok, FutureResult};
use futures::{try_ready, Async, Future, IntoFuture, Poll}; use futures::{try_ready, Async, Future, IntoFuture, Poll};
@@ -124,7 +122,7 @@ where
pub trait AsyncFactory<T, R>: Clone + 'static pub trait AsyncFactory<T, R>: Clone + 'static
where where
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
fn call(&self, param: T) -> R; fn call(&self, param: T) -> R;
@@ -134,7 +132,7 @@ impl<F, R> AsyncFactory<(), R> for F
where where
F: Fn() -> R + Clone + 'static, F: Fn() -> R + Clone + 'static,
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
fn call(&self, _: ()) -> R { fn call(&self, _: ()) -> R {
@@ -147,7 +145,7 @@ pub struct AsyncHandler<F, T, R>
where where
F: AsyncFactory<T, R>, F: AsyncFactory<T, R>,
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
hnd: F, hnd: F,
@@ -158,7 +156,7 @@ impl<F, T, R> AsyncHandler<F, T, R>
where where
F: AsyncFactory<T, R>, F: AsyncFactory<T, R>,
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
pub fn new(hnd: F) -> Self { pub fn new(hnd: F) -> Self {
@@ -173,7 +171,7 @@ impl<F, T, R> Clone for AsyncHandler<F, T, R>
where where
F: AsyncFactory<T, R>, F: AsyncFactory<T, R>,
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@@ -188,7 +186,7 @@ impl<F, T, R> Service for AsyncHandler<F, T, R>
where where
F: AsyncFactory<T, R>, F: AsyncFactory<T, R>,
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
type Request = (T, HttpRequest); type Request = (T, HttpRequest);
@@ -203,32 +201,56 @@ where
fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future {
AsyncHandlerServiceResponse { AsyncHandlerServiceResponse {
fut: self.hnd.call(param).into_future(), fut: self.hnd.call(param).into_future(),
fut2: None,
req: Some(req), req: Some(req),
} }
} }
} }
#[doc(hidden)] #[doc(hidden)]
pub struct AsyncHandlerServiceResponse<T> { pub struct AsyncHandlerServiceResponse<T>
where
T: Future,
T::Item: Responder,
{
fut: T, fut: T,
fut2: Option<<<T::Item as Responder>::Future as IntoFuture>::Future>,
req: Option<HttpRequest>, req: Option<HttpRequest>,
} }
impl<T> Future for AsyncHandlerServiceResponse<T> impl<T> Future for AsyncHandlerServiceResponse<T>
where where
T: Future, T: Future,
T::Item: Into<Response>, T::Item: Responder,
T::Error: Into<Error>, T::Error: Into<Error>,
{ {
type Item = ServiceResponse; type Item = ServiceResponse;
type Error = Void; type Error = Void;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut2 {
return match fut.poll() {
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new(
self.req.take().unwrap(),
res,
))),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => {
let res: Response = e.into().into();
Ok(Async::Ready(ServiceResponse::new(
self.req.take().unwrap(),
res,
)))
}
};
}
match self.fut.poll() { match self.fut.poll() {
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new( Ok(Async::Ready(res)) => {
self.req.take().unwrap(), self.fut2 =
res.into(), Some(res.respond_to(self.req.as_ref().unwrap()).into_future());
))), return self.poll();
}
Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => { Err(e) => {
let res: Response = e.into().into(); let res: Response = e.into().into();
@@ -242,66 +264,61 @@ where
} }
/// Extract arguments from request /// Extract arguments from request
pub struct Extract<P, T: FromRequest<P>, S> { pub struct Extract<T: FromRequest, S> {
config: Rc<RefCell<Option<Rc<Extensions>>>>,
service: S, service: S,
_t: PhantomData<(P, T)>, _t: PhantomData<T>,
} }
impl<P, T: FromRequest<P>, S> Extract<P, T, S> { impl<T: FromRequest, S> Extract<T, S> {
pub fn new(config: Rc<RefCell<Option<Rc<Extensions>>>>, service: S) -> Self { pub fn new(service: S) -> Self {
Extract { Extract {
config,
service, service,
_t: PhantomData, _t: PhantomData,
} }
} }
} }
impl<P, T: FromRequest<P>, S> NewService for Extract<P, T, S> impl<T: FromRequest, S> NewService for Extract<T, S>
where where
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void> S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
+ Clone, + Clone,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = (Error, ServiceRequest<P>); type Error = (Error, ServiceRequest);
type InitError = (); type InitError = ();
type Service = ExtractService<P, T, S>; type Service = ExtractService<T, S>;
type Future = FutureResult<Self::Service, ()>; type Future = FutureResult<Self::Service, ()>;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
ok(ExtractService { ok(ExtractService {
_t: PhantomData, _t: PhantomData,
config: self.config.borrow().clone(),
service: self.service.clone(), service: self.service.clone(),
}) })
} }
} }
pub struct ExtractService<P, T: FromRequest<P>, S> { pub struct ExtractService<T: FromRequest, S> {
config: Option<Rc<Extensions>>,
service: S, service: S,
_t: PhantomData<(P, T)>, _t: PhantomData<T>,
} }
impl<P, T: FromRequest<P>, S> Service for ExtractService<P, T, S> impl<T: FromRequest, S> Service for ExtractService<T, S>
where where
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void> S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
+ Clone, + Clone,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = (Error, ServiceRequest<P>); type Error = (Error, ServiceRequest);
type Future = ExtractResponse<P, T, S>; type Future = ExtractResponse<T, S>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(())) Ok(Async::Ready(()))
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let (mut req, mut payload) = req.into_parts(); let (req, mut payload) = req.into_parts();
req.set_route_data(self.config.clone());
let fut = T::from_request(&req, &mut payload).into_future(); let fut = T::from_request(&req, &mut payload).into_future();
ExtractResponse { ExtractResponse {
@@ -313,19 +330,19 @@ where
} }
} }
pub struct ExtractResponse<P, T: FromRequest<P>, S: Service> { pub struct ExtractResponse<T: FromRequest, S: Service> {
req: Option<(HttpRequest, Payload<P>)>, req: Option<(HttpRequest, Payload)>,
service: S, service: S,
fut: <T::Future as IntoFuture>::Future, fut: <T::Future as IntoFuture>::Future,
fut_s: Option<S::Future>, fut_s: Option<S::Future>,
} }
impl<P, T: FromRequest<P>, S> Future for ExtractResponse<P, T, S> impl<T: FromRequest, S> Future for ExtractResponse<T, S>
where where
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>, S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>,
{ {
type Item = ServiceResponse; type Item = ServiceResponse;
type Error = (Error, ServiceRequest<P>); type Error = (Error, ServiceRequest);
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut_s { if let Some(ref mut fut) = self.fut_s {
@@ -357,7 +374,7 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
impl<Func, $($T,)+ Res> AsyncFactory<($($T,)+), Res> for Func impl<Func, $($T,)+ Res> AsyncFactory<($($T,)+), Res> for Func
where Func: Fn($($T,)+) -> Res + Clone + 'static, where Func: Fn($($T,)+) -> Res + Clone + 'static,
Res: IntoFuture, Res: IntoFuture,
Res::Item: Into<Response>, Res::Item: Responder,
Res::Error: Into<Error>, Res::Error: Into<Error>,
{ {
fn call(&self, param: ($($T,)+)) -> Res { fn call(&self, param: ($($T,)+)) -> Res {

View File

@@ -30,7 +30,7 @@ impl ConnectionInfo {
let mut host = None; let mut host = None;
let mut scheme = None; let mut scheme = None;
let mut remote = None; let mut remote = None;
let peer = None; let mut peer = None;
// load forwarded header // load forwarded header
for hdr in req.headers.get_all(&header::FORWARDED) { for hdr in req.headers.get_all(&header::FORWARDED) {
@@ -116,10 +116,10 @@ impl ConnectionInfo {
remote = h.split(',').next().map(|v| v.trim()); remote = h.split(',').next().map(|v| v.trim());
} }
} }
// if remote.is_none() { if remote.is_none() {
// get peeraddr from socketaddr // get peeraddr from socketaddr
// peer = req.peer_addr().map(|addr| format!("{}", addr)); peer = req.peer_addr.map(|addr| format!("{}", addr));
// } }
} }
ConnectionInfo { ConnectionInfo {

View File

@@ -133,16 +133,18 @@ pub mod dev {
//! use actix_web::dev::*; //! use actix_web::dev::*;
//! ``` //! ```
pub use crate::app::AppRouter; pub use crate::config::{AppConfig, AppService};
pub use crate::config::{AppConfig, ServiceConfig};
pub use crate::info::ConnectionInfo; pub use crate::info::ConnectionInfo;
pub use crate::rmap::ResourceMap; pub use crate::rmap::ResourceMap;
pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse}; pub use crate::service::{
HttpServiceFactory, ServiceRequest, ServiceResponse, WebService,
};
pub use crate::types::form::UrlEncoded; pub use crate::types::form::UrlEncoded;
pub use crate::types::json::JsonBody; pub use crate::types::json::JsonBody;
pub use crate::types::readlines::Readlines; pub use crate::types::readlines::Readlines;
pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody}; pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody};
pub use actix_http::encoding::Decoder as Decompress;
pub use actix_http::ResponseBuilder as HttpResponseBuilder; pub use actix_http::ResponseBuilder as HttpResponseBuilder;
pub use actix_http::{ pub use actix_http::{
Extensions, Payload, PayloadStream, RequestHead, ResponseHead, Extensions, Payload, PayloadStream, RequestHead, ResponseHead,

View File

@@ -6,7 +6,7 @@ use std::str::FromStr;
use actix_http::body::MessageBody; use actix_http::body::MessageBody;
use actix_http::encoding::Encoder; use actix_http::encoding::Encoder;
use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING};
use actix_http::{Response, ResponseBuilder}; use actix_http::{Error, Response, ResponseBuilder};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use futures::future::{ok, FutureResult}; use futures::future::{ok, FutureResult};
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
@@ -41,11 +41,11 @@ impl<B> BodyEncoding for Response<B> {
/// To disable compression set encoding to `ContentEncoding::Identity` value. /// To disable compression set encoding to `ContentEncoding::Identity` value.
/// ///
/// ```rust /// ```rust
/// use actix_web::{web, middleware::encoding, App, HttpResponse}; /// use actix_web::{web, middleware, App, HttpResponse};
/// ///
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new()
/// .wrap(encoding::Compress::default()) /// .wrap(middleware::Compress::default())
/// .service( /// .service(
/// web::resource("/test") /// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok())) /// .route(web::get().to(|| HttpResponse::Ok()))
@@ -68,14 +68,14 @@ impl Default for Compress {
} }
} }
impl<S, P, B> Transform<S> for Compress impl<S, B> Transform<S> for Compress
where where
B: MessageBody, B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<Encoder<B>>; type Response = ServiceResponse<Encoder<B>>;
type Error = S::Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = CompressMiddleware<S>; type Transform = CompressMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = FutureResult<Self::Transform, Self::InitError>;
@@ -93,21 +93,21 @@ pub struct CompressMiddleware<S> {
encoding: ContentEncoding, encoding: ContentEncoding,
} }
impl<S, P, B> Service for CompressMiddleware<S> impl<S, B> Service for CompressMiddleware<S>
where where
B: MessageBody, B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<Encoder<B>>; type Response = ServiceResponse<Encoder<B>>;
type Error = S::Error; type Error = Error;
type Future = CompressResponse<S, P, B>; type Future = CompressResponse<S, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
// negotiate content-encoding // negotiate content-encoding
let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) {
if let Ok(enc) = val.to_str() { if let Ok(enc) = val.to_str() {
@@ -128,23 +128,23 @@ where
} }
#[doc(hidden)] #[doc(hidden)]
pub struct CompressResponse<S, P, B> pub struct CompressResponse<S, B>
where where
S: Service, S: Service,
B: MessageBody, B: MessageBody,
{ {
fut: S::Future, fut: S::Future,
encoding: ContentEncoding, encoding: ContentEncoding,
_t: PhantomData<(P, B)>, _t: PhantomData<(B)>,
} }
impl<S, P, B> Future for CompressResponse<S, P, B> impl<S, B> Future for CompressResponse<S, B>
where where
B: MessageBody, B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
type Item = ServiceResponse<Encoder<B>>; type Item = ServiceResponse<Encoder<B>>;
type Error = S::Error; type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let resp = futures::try_ready!(self.fut.poll()); let resp = futures::try_ready!(self.fut.poll());

View File

@@ -47,7 +47,7 @@ use futures::future::{ok, Either, Future, FutureResult};
use futures::Poll; use futures::Poll;
use crate::dev::RequestHead; use crate::dev::RequestHead;
use crate::error::{ResponseError, Result}; use crate::error::{Error, ResponseError, Result};
use crate::http::header::{self, HeaderName, HeaderValue}; use crate::http::header::{self, HeaderName, HeaderValue};
use crate::http::{self, HttpTryFrom, Method, StatusCode, Uri}; use crate::http::{self, HttpTryFrom, Method, StatusCode, Uri};
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
@@ -81,13 +81,6 @@ pub enum CorsError {
fmt = "The request header `Access-Control-Request-Headers` has an invalid value" fmt = "The request header `Access-Control-Request-Headers` has an invalid value"
)] )]
BadRequestHeaders, BadRequestHeaders,
/// The request header `Access-Control-Request-Headers` is required but is
/// missing.
#[display(
fmt = "The request header `Access-Control-Request-Headers` is required but is
missing"
)]
MissingRequestHeaders,
/// Origin is not allowed to make this request /// Origin is not allowed to make this request
#[display(fmt = "Origin is not allowed to make this request")] #[display(fmt = "Origin is not allowed to make this request")]
OriginNotAllowed, OriginNotAllowed,
@@ -475,11 +468,10 @@ fn cors<'a>(
parts.as_mut() parts.as_mut()
} }
impl<S, P, B> IntoTransform<CorsFactory, S> for Cors impl<S, B> IntoTransform<CorsFactory, S> for Cors
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static,
B: 'static, B: 'static,
{ {
fn into_transform(self) -> CorsFactory { fn into_transform(self) -> CorsFactory {
@@ -537,16 +529,15 @@ pub struct CorsFactory {
inner: Rc<Inner>, inner: Rc<Inner>,
} }
impl<S, P, B> Transform<S> for CorsFactory impl<S, B> Transform<S> for CorsFactory
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static,
B: 'static, B: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = CorsMiddleware<S>; type Transform = CorsMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = FutureResult<Self::Transform, Self::InitError>;
@@ -663,41 +654,43 @@ impl Inner {
Err(_) => return Err(CorsError::BadRequestHeaders), Err(_) => return Err(CorsError::BadRequestHeaders),
}; };
} }
// `Access-Control-Request-Headers` must contain 1 or more
if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) { // `field-name`.
return Err(CorsError::HeadersNotAllowed); if !hdrs.is_empty() {
if !hdrs.is_subset(allowed_headers) {
return Err(CorsError::HeadersNotAllowed);
}
return Ok(());
} }
return Ok(());
} }
Err(CorsError::BadRequestHeaders) Err(CorsError::BadRequestHeaders)
} else { } else {
Err(CorsError::MissingRequestHeaders) return Ok(());
} }
} }
} }
} }
} }
impl<S, P, B> Service for CorsMiddleware<S> impl<S, B> Service for CorsMiddleware<S>
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static,
B: 'static, B: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = Error;
type Future = Either< type Future = Either<
FutureResult<Self::Response, Self::Error>, FutureResult<Self::Response, Error>,
Either<S::Future, Box<Future<Item = Self::Response, Error = Self::Error>>>, Either<S::Future, Box<Future<Item = Self::Response, Error = Error>>>,
>; >;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
if self.inner.preflight && Method::OPTIONS == *req.method() { if self.inner.preflight && Method::OPTIONS == *req.method() {
if let Err(e) = self if let Err(e) = self
.inner .inner
@@ -815,16 +808,17 @@ mod tests {
use actix_service::{FnService, Transform}; use actix_service::{FnService, Transform};
use super::*; use super::*;
use crate::dev::PayloadStream;
use crate::test::{self, block_on, TestRequest}; use crate::test::{self, block_on, TestRequest};
impl Cors { impl Cors {
fn finish<S, P, B>(self, srv: S) -> CorsMiddleware<S> fn finish<S, B>(self, srv: S) -> CorsMiddleware<S>
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> S: Service<
+ 'static, Request = ServiceRequest,
Response = ServiceResponse<B>,
Error = Error,
> + 'static,
S::Future: 'static, S::Future: 'static,
S::Error: 'static,
B: 'static, B: 'static,
{ {
block_on( block_on(
@@ -849,7 +843,18 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn default() {
let mut cors =
block_on(Cors::default().new_transform(test::ok_service())).unwrap();
let req = TestRequest::with_header("Origin", "https://www.example.com")
.to_srv_request();
let resp = test::call_service(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
@@ -865,11 +870,12 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.header(header::ACCESS_CONTROL_REQUEST_HEADERS, "X-Not-Allowed")
.to_srv_request(); .to_srv_request();
assert!(cors.inner.validate_allowed_method(req.head()).is_err()); assert!(cors.inner.validate_allowed_method(req.head()).is_err());
assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); assert!(cors.inner.validate_allowed_headers(req.head()).is_err());
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
@@ -878,7 +884,7 @@ mod tests {
.to_srv_request(); .to_srv_request();
assert!(cors.inner.validate_allowed_method(req.head()).is_err()); assert!(cors.inner.validate_allowed_method(req.head()).is_err());
assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); assert!(cors.inner.validate_allowed_headers(req.head()).is_ok());
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
@@ -889,7 +895,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
resp.headers() resp.headers()
@@ -935,7 +941,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
@@ -974,7 +980,7 @@ mod tests {
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
@@ -983,7 +989,7 @@ mod tests {
let mut cors = Cors::new().disable_preflight().finish(test::ok_service()); let mut cors = Cors::new().disable_preflight().finish(test::ok_service());
let req = TestRequest::default().method(Method::GET).to_srv_request(); let req = TestRequest::default().method(Method::GET).to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert!(resp assert!(resp
.headers() .headers()
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
@@ -992,7 +998,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"https://www.example.com"[..], &b"https://www.example.com"[..],
resp.headers() resp.headers()
@@ -1019,7 +1025,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"*"[..], &b"*"[..],
resp.headers() resp.headers()
@@ -1057,7 +1063,7 @@ mod tests {
.allowed_headers(exposed_headers.clone()) .allowed_headers(exposed_headers.clone())
.expose_headers(exposed_headers.clone()) .expose_headers(exposed_headers.clone())
.allowed_header(header::CONTENT_TYPE) .allowed_header(header::CONTENT_TYPE)
.finish(FnService::new(move |req: ServiceRequest<PayloadStream>| { .finish(FnService::new(move |req: ServiceRequest| {
req.into_response( req.into_response(
HttpResponse::Ok().header(header::VARY, "Accept").finish(), HttpResponse::Ok().header(header::VARY, "Accept").finish(),
) )
@@ -1065,7 +1071,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com") let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"Accept, Origin"[..], &b"Accept, Origin"[..],
resp.headers().get(header::VARY).unwrap().as_bytes() resp.headers().get(header::VARY).unwrap().as_bytes()
@@ -1081,7 +1087,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
let origins_str = resp let origins_str = resp
.headers() .headers()
@@ -1105,7 +1111,7 @@ mod tests {
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"https://example.com"[..], &b"https://example.com"[..],
resp.headers() resp.headers()
@@ -1118,7 +1124,7 @@ mod tests {
.method(Method::GET) .method(Method::GET)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"https://example.org"[..], &b"https://example.org"[..],
resp.headers() resp.headers()
@@ -1141,7 +1147,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"https://example.com"[..], &b"https://example.com"[..],
resp.headers() resp.headers()
@@ -1155,7 +1161,7 @@ mod tests {
.method(Method::OPTIONS) .method(Method::OPTIONS)
.to_srv_request(); .to_srv_request();
let resp = test::call_success(&mut cors, req); let resp = test::call_service(&mut cors, req);
assert_eq!( assert_eq!(
&b"https://example.org"[..], &b"https://example.org"[..],
resp.headers() resp.headers()

View File

@@ -1,75 +0,0 @@
//! Chain service for decompressing request payload.
use std::marker::PhantomData;
use actix_http::encoding::Decoder;
use actix_service::{NewService, Service};
use bytes::Bytes;
use futures::future::{ok, FutureResult};
use futures::{Async, Poll, Stream};
use crate::dev::Payload;
use crate::error::{Error, PayloadError};
use crate::service::ServiceRequest;
/// `Middleware` for decompressing request's payload.
/// `Decompress` middleware must be added with `App::chain()` method.
///
/// ```rust
/// use actix_web::{web, middleware::encoding, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .chain(encoding::Decompress::new())
/// .service(
/// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub struct Decompress<P>(PhantomData<P>);
impl<P> Decompress<P>
where
P: Stream<Item = Bytes, Error = PayloadError>,
{
pub fn new() -> Self {
Decompress(PhantomData)
}
}
impl<P> NewService for Decompress<P>
where
P: Stream<Item = Bytes, Error = PayloadError>,
{
type Request = ServiceRequest<P>;
type Response = ServiceRequest<Decoder<Payload<P>>>;
type Error = Error;
type InitError = ();
type Service = Decompress<P>;
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
ok(Decompress(PhantomData))
}
}
impl<P> Service for Decompress<P>
where
P: Stream<Item = Bytes, Error = PayloadError>,
{
type Request = ServiceRequest<P>;
type Response = ServiceRequest<Decoder<Payload<P>>>;
type Error = Error;
type Future = FutureResult<Self::Response, Self::Error>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
let (req, payload) = req.into_parts();
let payload = Decoder::from_headers(payload, req.headers());
ok(ServiceRequest::from_parts(req, Payload::Stream(payload)))
}
}

View File

@@ -8,6 +8,7 @@ use futures::{Future, Poll};
use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
use crate::http::{HeaderMap, HttpTryFrom}; use crate::http::{HeaderMap, HttpTryFrom};
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
use crate::Error;
/// `Middleware` for setting default response headers. /// `Middleware` for setting default response headers.
/// ///
@@ -85,14 +86,14 @@ impl DefaultHeaders {
} }
} }
impl<S, P, B> Transform<S> for DefaultHeaders impl<S, B> Transform<S> for DefaultHeaders
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static, S::Future: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = DefaultHeadersMiddleware<S>; type Transform = DefaultHeadersMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = FutureResult<Self::Transform, Self::InitError>;
@@ -110,21 +111,21 @@ pub struct DefaultHeadersMiddleware<S> {
inner: Rc<Inner>, inner: Rc<Inner>,
} }
impl<S, P, B> Service for DefaultHeadersMiddleware<S> impl<S, B> Service for DefaultHeadersMiddleware<S>
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static, S::Future: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone(); let inner = self.inner.clone();
Box::new(self.service.call(req).map(move |mut res| { Box::new(self.service.call(req).map(move |mut res| {
@@ -171,7 +172,7 @@ mod tests {
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let req = TestRequest::default().to_srv_request(); let req = TestRequest::default().to_srv_request();
let srv = FnService::new(|req: ServiceRequest<_>| { let srv = FnService::new(|req: ServiceRequest| {
req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()) req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())
}); });
let mut mw = block_on( let mut mw = block_on(
@@ -186,7 +187,7 @@ mod tests {
#[test] #[test]
fn test_content_type() { fn test_content_type() {
let srv = FnService::new(|req: ServiceRequest<_>| { let srv = FnService::new(|req: ServiceRequest| {
req.into_response(HttpResponse::Ok().finish()) req.into_response(HttpResponse::Ok().finish())
}); });
let mut mw = let mut mw =

View File

@@ -81,18 +81,13 @@ impl<B> ErrorHandlers<B> {
} }
} }
impl<S, P, B> Transform<S> for ErrorHandlers<B> impl<S, B> Transform<S> for ErrorHandlers<B>
where where
S: Service< S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static,
B: 'static, B: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
@@ -113,18 +108,13 @@ pub struct ErrorHandlersMiddleware<S, B> {
handlers: Rc<HashMap<StatusCode, Box<ErrorHandler<B>>>>, handlers: Rc<HashMap<StatusCode, Box<ErrorHandler<B>>>>,
} }
impl<S, P, B> Service for ErrorHandlersMiddleware<S, B> impl<S, B> Service for ErrorHandlersMiddleware<S, B>
where where
S: Service< S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
>,
S::Future: 'static, S::Future: 'static,
S::Error: 'static,
B: 'static, B: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
@@ -133,7 +123,7 @@ where
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let handlers = self.handlers.clone(); let handlers = self.handlers.clone();
Box::new(self.service.call(req).and_then(move |res| { Box::new(self.service.call(req).and_then(move |res| {
@@ -169,7 +159,7 @@ mod tests {
#[test] #[test]
fn test_handler() { fn test_handler() {
let srv = FnService::new(|req: ServiceRequest<_>| { let srv = FnService::new(|req: ServiceRequest| {
req.into_response(HttpResponse::InternalServerError().finish()) req.into_response(HttpResponse::InternalServerError().finish())
}); });
@@ -180,7 +170,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request()); let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
} }
@@ -195,7 +185,7 @@ mod tests {
#[test] #[test]
fn test_handler_async() { fn test_handler_async() {
let srv = FnService::new(|req: ServiceRequest<_>| { let srv = FnService::new(|req: ServiceRequest| {
req.into_response(HttpResponse::InternalServerError().finish()) req.into_response(HttpResponse::InternalServerError().finish())
}); });
@@ -206,7 +196,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request()); let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
} }
} }

View File

@@ -49,10 +49,12 @@
//! ``` //! ```
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::time::SystemTime;
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use futures::future::{ok, Either, FutureResult}; use futures::future::{ok, Either, FutureResult};
use futures::{Future, IntoFuture, Poll}; use futures::{Future, IntoFuture, Poll};
use serde::{Deserialize, Serialize};
use time::Duration; use time::Duration;
use crate::cookie::{Cookie, CookieJar, Key, SameSite}; use crate::cookie::{Cookie, CookieJar, Key, SameSite};
@@ -140,12 +142,13 @@ struct IdentityItem {
/// } /// }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
impl<P> FromRequest<P> for Identity { impl FromRequest for Identity {
type Config = ();
type Error = Error; type Error = Error;
type Future = Result<Identity, Error>; type Future = Result<Identity, Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
Ok(Identity(req.clone())) Ok(Identity(req.clone()))
} }
} }
@@ -159,7 +162,7 @@ pub trait IdentityPolicy: Sized + 'static {
type ResponseFuture: IntoFuture<Item = (), Error = Error>; type ResponseFuture: IntoFuture<Item = (), Error = Error>;
/// Parse the session from request and load data from a service identity. /// Parse the session from request and load data from a service identity.
fn from_request<P>(&self, request: &mut ServiceRequest<P>) -> Self::Future; fn from_request(&self, request: &mut ServiceRequest) -> Self::Future;
/// Write changes to response /// Write changes to response
fn to_response<B>( fn to_response<B>(
@@ -198,18 +201,17 @@ impl<T> IdentityService<T> {
} }
} }
impl<S, T, P, B> Transform<S> for IdentityService<T> impl<S, T, B> Transform<S> for IdentityService<T>
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>
+ 'static,
S::Future: 'static, S::Future: 'static,
S::Error: 'static,
T: IdentityPolicy, T: IdentityPolicy,
P: 'static,
B: 'static, B: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = IdentityServiceMiddleware<S, T>; type Transform = IdentityServiceMiddleware<S, T>;
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = FutureResult<Self::Transform, Self::InitError>;
@@ -228,25 +230,24 @@ pub struct IdentityServiceMiddleware<S, T> {
service: Rc<RefCell<S>>, service: Rc<RefCell<S>>,
} }
impl<S, T, P, B> Service for IdentityServiceMiddleware<S, T> impl<S, T, B> Service for IdentityServiceMiddleware<S, T>
where where
P: 'static,
B: 'static, B: 'static,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>
+ 'static,
S::Future: 'static, S::Future: 'static,
S::Error: 'static,
T: IdentityPolicy, T: IdentityPolicy,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = S::Error; type Error = Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.borrow_mut().poll_ready() self.service.borrow_mut().poll_ready()
} }
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let srv = self.service.clone(); let srv = self.service.clone();
let backend = self.backend.clone(); let backend = self.backend.clone();
@@ -285,84 +286,149 @@ where
struct CookieIdentityInner { struct CookieIdentityInner {
key: Key, key: Key,
key_v2: Key,
name: String, name: String,
path: String, path: String,
domain: Option<String>, domain: Option<String>,
secure: bool, secure: bool,
max_age: Option<Duration>, max_age: Option<Duration>,
same_site: Option<SameSite>, same_site: Option<SameSite>,
visit_deadline: Option<Duration>,
login_deadline: Option<Duration>,
}
#[derive(Deserialize, Serialize, Debug)]
struct CookieValue {
identity: String,
#[serde(skip_serializing_if = "Option::is_none")]
login_timestamp: Option<SystemTime>,
#[serde(skip_serializing_if = "Option::is_none")]
visit_timestamp: Option<SystemTime>,
}
#[derive(Debug)]
struct CookieIdentityExtention {
login_timestamp: Option<SystemTime>,
} }
impl CookieIdentityInner { impl CookieIdentityInner {
fn new(key: &[u8]) -> CookieIdentityInner { fn new(key: &[u8]) -> CookieIdentityInner {
let key_v2: Vec<u8> =
key.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect();
CookieIdentityInner { CookieIdentityInner {
key: Key::from_master(key), key: Key::from_master(key),
key_v2: Key::from_master(&key_v2),
name: "actix-identity".to_owned(), name: "actix-identity".to_owned(),
path: "/".to_owned(), path: "/".to_owned(),
domain: None, domain: None,
secure: true, secure: true,
max_age: None, max_age: None,
same_site: None, same_site: None,
visit_deadline: None,
login_deadline: None,
} }
} }
fn set_cookie<B>( fn set_cookie<B>(
&self, &self,
resp: &mut ServiceResponse<B>, resp: &mut ServiceResponse<B>,
id: Option<String>, value: Option<CookieValue>,
) -> Result<()> { ) -> Result<()> {
let some = id.is_some(); let add_cookie = value.is_some();
{ let val = value.map(|val| {
let id = id.unwrap_or_else(String::new); if !self.legacy_supported() {
let mut cookie = Cookie::new(self.name.clone(), id); serde_json::to_string(&val)
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(true);
if let Some(ref domain) = self.domain {
cookie.set_domain(domain.clone());
}
if let Some(max_age) = self.max_age {
cookie.set_max_age(max_age);
}
if let Some(same_site) = self.same_site {
cookie.set_same_site(same_site);
}
let mut jar = CookieJar::new();
if some {
jar.private(&self.key).add(cookie);
} else { } else {
jar.add_original(cookie.clone()); Ok(val.identity)
jar.private(&self.key).remove(cookie);
} }
});
let mut cookie =
Cookie::new(self.name.clone(), val.unwrap_or_else(|| Ok(String::new()))?);
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(true);
for cookie in jar.delta() { if let Some(ref domain) = self.domain {
let val = HeaderValue::from_str(&cookie.to_string())?; cookie.set_domain(domain.clone());
resp.headers_mut().append(header::SET_COOKIE, val);
}
} }
if let Some(max_age) = self.max_age {
cookie.set_max_age(max_age);
}
if let Some(same_site) = self.same_site {
cookie.set_same_site(same_site);
}
let mut jar = CookieJar::new();
let key = if self.legacy_supported() {
&self.key
} else {
&self.key_v2
};
if add_cookie {
jar.private(&key).add(cookie);
} else {
jar.add_original(cookie.clone());
jar.private(&key).remove(cookie);
}
for cookie in jar.delta() {
let val = HeaderValue::from_str(&cookie.to_string())?;
resp.headers_mut().append(header::SET_COOKIE, val);
}
Ok(()) Ok(())
} }
fn load<T>(&self, req: &ServiceRequest<T>) -> Option<String> { fn load(&self, req: &ServiceRequest) -> Option<CookieValue> {
if let Ok(cookies) = req.cookies() { let cookie = req.cookie(&self.name)?;
for cookie in cookies.iter() { let mut jar = CookieJar::new();
if cookie.name() == self.name { jar.add_original(cookie.clone());
let mut jar = CookieJar::new(); let res = if self.legacy_supported() {
jar.add_original(cookie.clone()); jar.private(&self.key).get(&self.name).map(|n| CookieValue {
identity: n.value().to_string(),
login_timestamp: None,
visit_timestamp: None,
})
} else {
None
};
res.or_else(|| {
jar.private(&self.key_v2)
.get(&self.name)
.and_then(|c| self.parse(c))
})
}
let cookie_opt = jar.private(&self.key).get(&self.name); fn parse(&self, cookie: Cookie) -> Option<CookieValue> {
if let Some(cookie) = cookie_opt { let value: CookieValue = serde_json::from_str(cookie.value()).ok()?;
return Some(cookie.value().into()); let now = SystemTime::now();
} if let Some(visit_deadline) = self.visit_deadline {
} if now.duration_since(value.visit_timestamp?).ok()?
> visit_deadline.to_std().ok()?
{
return None;
} }
} }
None if let Some(login_deadline) = self.login_deadline {
if now.duration_since(value.login_timestamp?).ok()?
> login_deadline.to_std().ok()?
{
return None;
}
}
Some(value)
}
fn legacy_supported(&self) -> bool {
self.visit_deadline.is_none() && self.login_deadline.is_none()
}
fn always_update_cookie(&self) -> bool {
self.visit_deadline.is_some()
}
fn requires_oob_data(&self) -> bool {
self.login_deadline.is_some()
} }
} }
@@ -428,8 +494,13 @@ impl CookieIdentityPolicy {
self self
} }
/// Sets the `max-age` field in the session cookie being built. /// Sets the `max-age` field in the session cookie being built with given number of seconds.
pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy { pub fn max_age(self, seconds: i64) -> CookieIdentityPolicy {
self.max_age_time(Duration::seconds(seconds))
}
/// Sets the `max-age` field in the session cookie being built with `chrono::Duration`.
pub fn max_age_time(mut self, value: Duration) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self self
} }
@@ -439,14 +510,42 @@ impl CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site); Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site);
self self
} }
/// Accepts only users whose cookie has been seen before the given deadline
///
/// By default visit deadline is disabled.
pub fn visit_deadline(mut self, value: Duration) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().visit_deadline = Some(value);
self
}
/// Accepts only users which has been authenticated before the given deadline
///
/// By default login deadline is disabled.
pub fn login_deadline(mut self, value: Duration) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().login_deadline = Some(value);
self
}
} }
impl IdentityPolicy for CookieIdentityPolicy { impl IdentityPolicy for CookieIdentityPolicy {
type Future = Result<Option<String>, Error>; type Future = Result<Option<String>, Error>;
type ResponseFuture = Result<(), Error>; type ResponseFuture = Result<(), Error>;
fn from_request<P>(&self, req: &mut ServiceRequest<P>) -> Self::Future { fn from_request(&self, req: &mut ServiceRequest) -> Self::Future {
Ok(self.0.load(req)) Ok(self.0.load(req).map(
|CookieValue {
identity,
login_timestamp,
..
}| {
if self.0.requires_oob_data() {
req.extensions_mut()
.insert(CookieIdentityExtention { login_timestamp });
}
identity
},
))
} }
fn to_response<B>( fn to_response<B>(
@@ -455,9 +554,36 @@ impl IdentityPolicy for CookieIdentityPolicy {
changed: bool, changed: bool,
res: &mut ServiceResponse<B>, res: &mut ServiceResponse<B>,
) -> Self::ResponseFuture { ) -> Self::ResponseFuture {
if changed { let _ = if changed {
let _ = self.0.set_cookie(res, id); let login_timestamp = SystemTime::now();
} self.0.set_cookie(
res,
id.map(|identity| CookieValue {
identity,
login_timestamp: self.0.login_deadline.map(|_| login_timestamp),
visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp),
}),
)
} else if self.0.always_update_cookie() && id.is_some() {
let visit_timestamp = SystemTime::now();
let mut login_timestamp = None;
if self.0.requires_oob_data() {
let CookieIdentityExtention {
login_timestamp: lt,
} = res.request().extensions_mut().remove().unwrap();
login_timestamp = lt;
}
self.0.set_cookie(
res,
Some(CookieValue {
identity: id.unwrap(),
login_timestamp,
visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp),
}),
)
} else {
Ok(())
};
Ok(()) Ok(())
} }
} }
@@ -469,14 +595,20 @@ mod tests {
use crate::test::{self, TestRequest}; use crate::test::{self, TestRequest};
use crate::{web, App, HttpResponse}; use crate::{web, App, HttpResponse};
use std::borrow::Borrow;
const COOKIE_KEY_MASTER: [u8; 32] = [0; 32];
const COOKIE_NAME: &'static str = "actix_auth";
const COOKIE_LOGIN: &'static str = "test";
#[test] #[test]
fn test_identity() { fn test_identity() {
let mut srv = test::init_service( let mut srv = test::init_service(
App::new() App::new()
.wrap(IdentityService::new( .wrap(IdentityService::new(
CookieIdentityPolicy::new(&[0; 32]) CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
.domain("www.rust-lang.org") .domain("www.rust-lang.org")
.name("actix_auth") .name(COOKIE_NAME)
.path("/") .path("/")
.secure(true), .secure(true),
)) ))
@@ -488,7 +620,7 @@ mod tests {
} }
})) }))
.service(web::resource("/login").to(|id: Identity| { .service(web::resource("/login").to(|id: Identity| {
id.remember("test".to_string()); id.remember(COOKIE_LOGIN.to_string());
HttpResponse::Ok() HttpResponse::Ok()
})) }))
.service(web::resource("/logout").to(|id: Identity| { .service(web::resource("/logout").to(|id: Identity| {
@@ -501,15 +633,15 @@ mod tests {
})), })),
); );
let resp = let resp =
test::call_success(&mut srv, TestRequest::with_uri("/index").to_request()); test::call_service(&mut srv, TestRequest::with_uri("/index").to_request());
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let resp = let resp =
test::call_success(&mut srv, TestRequest::with_uri("/login").to_request()); test::call_service(&mut srv, TestRequest::with_uri("/login").to_request());
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let c = resp.response().cookies().next().unwrap().to_owned(); let c = resp.response().cookies().next().unwrap().to_owned();
let resp = test::call_success( let resp = test::call_service(
&mut srv, &mut srv,
TestRequest::with_uri("/index") TestRequest::with_uri("/index")
.cookie(c.clone()) .cookie(c.clone())
@@ -517,7 +649,7 @@ mod tests {
); );
assert_eq!(resp.status(), StatusCode::CREATED); assert_eq!(resp.status(), StatusCode::CREATED);
let resp = test::call_success( let resp = test::call_service(
&mut srv, &mut srv,
TestRequest::with_uri("/logout") TestRequest::with_uri("/logout")
.cookie(c.clone()) .cookie(c.clone())
@@ -526,4 +658,380 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert!(resp.headers().contains_key(header::SET_COOKIE)) assert!(resp.headers().contains_key(header::SET_COOKIE))
} }
#[test]
fn test_identity_max_age_time() {
let duration = Duration::days(1);
let mut srv = test::init_service(
App::new()
.wrap(IdentityService::new(
CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
.domain("www.rust-lang.org")
.name(COOKIE_NAME)
.path("/")
.max_age_time(duration)
.secure(true),
))
.service(web::resource("/login").to(|id: Identity| {
id.remember("test".to_string());
HttpResponse::Ok()
})),
);
let resp =
test::call_service(&mut srv, TestRequest::with_uri("/login").to_request());
assert_eq!(resp.status(), StatusCode::OK);
assert!(resp.headers().contains_key(header::SET_COOKIE));
let c = resp.response().cookies().next().unwrap().to_owned();
assert_eq!(duration, c.max_age().unwrap());
}
#[test]
fn test_identity_max_age() {
let seconds = 60;
let mut srv = test::init_service(
App::new()
.wrap(IdentityService::new(
CookieIdentityPolicy::new(&COOKIE_KEY_MASTER)
.domain("www.rust-lang.org")
.name(COOKIE_NAME)
.path("/")
.max_age(seconds)
.secure(true),
))
.service(web::resource("/login").to(|id: Identity| {
id.remember("test".to_string());
HttpResponse::Ok()
})),
);
let resp =
test::call_service(&mut srv, TestRequest::with_uri("/login").to_request());
assert_eq!(resp.status(), StatusCode::OK);
assert!(resp.headers().contains_key(header::SET_COOKIE));
let c = resp.response().cookies().next().unwrap().to_owned();
assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap());
}
fn create_identity_server<
F: Fn(CookieIdentityPolicy) -> CookieIdentityPolicy + Sync + Send + Clone + 'static,
>(
f: F,
) -> impl actix_service::Service<
Request = actix_http::Request,
Response = ServiceResponse<actix_http::body::Body>,
Error = actix_http::Error,
> {
test::init_service(
App::new()
.wrap(IdentityService::new(f(CookieIdentityPolicy::new(
&COOKIE_KEY_MASTER,
)
.secure(false)
.name(COOKIE_NAME))))
.service(web::resource("/").to(|id: Identity| {
let identity = id.identity();
if identity.is_none() {
id.remember(COOKIE_LOGIN.to_string())
}
web::Json(identity)
})),
)
}
fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> {
let mut jar = CookieJar::new();
jar.private(&Key::from_master(&COOKIE_KEY_MASTER))
.add(Cookie::new(COOKIE_NAME, identity));
jar.get(COOKIE_NAME).unwrap().clone()
}
fn login_cookie(
identity: &'static str,
login_timestamp: Option<SystemTime>,
visit_timestamp: Option<SystemTime>,
) -> Cookie<'static> {
let mut jar = CookieJar::new();
let key: Vec<u8> = COOKIE_KEY_MASTER
.iter()
.chain([1, 0, 0, 0].iter())
.map(|e| *e)
.collect();
jar.private(&Key::from_master(&key)).add(Cookie::new(
COOKIE_NAME,
serde_json::to_string(&CookieValue {
identity: identity.to_string(),
login_timestamp,
visit_timestamp,
})
.unwrap(),
));
jar.get(COOKIE_NAME).unwrap().clone()
}
fn assert_logged_in(response: &mut ServiceResponse, identity: Option<&str>) {
use bytes::BytesMut;
use futures::Stream;
let bytes =
test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| {
b.extend(c);
Ok::<_, Error>(b)
}))
.unwrap();
let resp: Option<String> = serde_json::from_slice(&bytes[..]).unwrap();
assert_eq!(resp.as_ref().map(|s| s.borrow()), identity);
}
fn assert_legacy_login_cookie(response: &mut ServiceResponse, identity: &str) {
let mut cookies = CookieJar::new();
for cookie in response.headers().get_all(header::SET_COOKIE) {
cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
}
let cookie = cookies
.private(&Key::from_master(&COOKIE_KEY_MASTER))
.get(COOKIE_NAME)
.unwrap();
assert_eq!(cookie.value(), identity);
}
enum LoginTimestampCheck {
NoTimestamp,
NewTimestamp,
OldTimestamp(SystemTime),
}
enum VisitTimeStampCheck {
NoTimestamp,
NewTimestamp,
}
fn assert_login_cookie(
response: &mut ServiceResponse,
identity: &str,
login_timestamp: LoginTimestampCheck,
visit_timestamp: VisitTimeStampCheck,
) {
let mut cookies = CookieJar::new();
for cookie in response.headers().get_all(header::SET_COOKIE) {
cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
}
let key: Vec<u8> = COOKIE_KEY_MASTER
.iter()
.chain([1, 0, 0, 0].iter())
.map(|e| *e)
.collect();
let cookie = cookies
.private(&Key::from_master(&key))
.get(COOKIE_NAME)
.unwrap();
let cv: CookieValue = serde_json::from_str(cookie.value()).unwrap();
assert_eq!(cv.identity, identity);
let now = SystemTime::now();
let t30sec_ago = now - Duration::seconds(30).to_std().unwrap();
match login_timestamp {
LoginTimestampCheck::NoTimestamp => assert_eq!(cv.login_timestamp, None),
LoginTimestampCheck::NewTimestamp => assert!(
t30sec_ago <= cv.login_timestamp.unwrap()
&& cv.login_timestamp.unwrap() <= now
),
LoginTimestampCheck::OldTimestamp(old_timestamp) => {
assert_eq!(cv.login_timestamp, Some(old_timestamp))
}
}
match visit_timestamp {
VisitTimeStampCheck::NoTimestamp => assert_eq!(cv.visit_timestamp, None),
VisitTimeStampCheck::NewTimestamp => assert!(
t30sec_ago <= cv.visit_timestamp.unwrap()
&& cv.visit_timestamp.unwrap() <= now
),
}
}
fn assert_no_login_cookie(response: &mut ServiceResponse) {
let mut cookies = CookieJar::new();
for cookie in response.headers().get_all(header::SET_COOKIE) {
cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
}
assert!(cookies.get(COOKIE_NAME).is_none());
}
#[test]
fn test_identity_legacy_cookie_is_set() {
let mut srv = create_identity_server(|c| c);
let mut resp =
test::call_service(&mut srv, TestRequest::with_uri("/").to_request());
assert_logged_in(&mut resp, None);
assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN);
}
#[test]
fn test_identity_legacy_cookie_works() {
let mut srv = create_identity_server(|c| c);
let cookie = legacy_login_cookie(COOKIE_LOGIN);
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
);
assert_logged_in(&mut resp, Some(COOKIE_LOGIN));
assert_no_login_cookie(&mut resp);
}
#[test]
fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() {
let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90)));
let cookie = legacy_login_cookie(COOKIE_LOGIN);
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
);
assert_logged_in(&mut resp, None);
assert_login_cookie(
&mut resp,
COOKIE_LOGIN,
LoginTimestampCheck::NoTimestamp,
VisitTimeStampCheck::NewTimestamp,
);
}
#[test]
fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() {
let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90)));
let cookie = legacy_login_cookie(COOKIE_LOGIN);
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
);
assert_logged_in(&mut resp, None);
assert_login_cookie(
&mut resp,
COOKIE_LOGIN,
LoginTimestampCheck::NewTimestamp,
VisitTimeStampCheck::NoTimestamp,
);
}
#[test]
fn test_identity_cookie_rejected_if_login_timestamp_needed() {
let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90)));
let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now()));
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
);
assert_logged_in(&mut resp, None);
assert_login_cookie(
&mut resp,
COOKIE_LOGIN,
LoginTimestampCheck::NewTimestamp,
VisitTimeStampCheck::NoTimestamp,
);
}
#[test]
fn test_identity_cookie_rejected_if_visit_timestamp_needed() {
let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90)));
let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
);
assert_logged_in(&mut resp, None);
assert_login_cookie(
&mut resp,
COOKIE_LOGIN,
LoginTimestampCheck::NoTimestamp,
VisitTimeStampCheck::NewTimestamp,
);
}
#[test]
fn test_identity_cookie_rejected_if_login_timestamp_too_old() {
let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90)));
let cookie = login_cookie(
COOKIE_LOGIN,
Some(SystemTime::now() - Duration::days(180).to_std().unwrap()),
None,
);
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
);
assert_logged_in(&mut resp, None);
assert_login_cookie(
&mut resp,
COOKIE_LOGIN,
LoginTimestampCheck::NewTimestamp,
VisitTimeStampCheck::NoTimestamp,
);
}
#[test]
fn test_identity_cookie_rejected_if_visit_timestamp_too_old() {
let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90)));
let cookie = login_cookie(
COOKIE_LOGIN,
None,
Some(SystemTime::now() - Duration::days(180).to_std().unwrap()),
);
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
);
assert_logged_in(&mut resp, None);
assert_login_cookie(
&mut resp,
COOKIE_LOGIN,
LoginTimestampCheck::NoTimestamp,
VisitTimeStampCheck::NewTimestamp,
);
}
#[test]
fn test_identity_cookie_not_updated_on_login_deadline() {
let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90)));
let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None);
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
);
assert_logged_in(&mut resp, Some(COOKIE_LOGIN));
assert_no_login_cookie(&mut resp);
}
#[test]
fn test_identity_cookie_updated_on_visit_deadline() {
let mut srv = create_identity_server(|c| {
c.visit_deadline(Duration::days(90))
.login_deadline(Duration::days(90))
});
let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap();
let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp));
let mut resp = test::call_service(
&mut srv,
TestRequest::with_uri("/")
.cookie(cookie.clone())
.to_request(),
);
assert_logged_in(&mut resp, Some(COOKIE_LOGIN));
assert_login_cookie(
&mut resp,
COOKIE_LOGIN,
LoginTimestampCheck::OldTimestamp(timestamp),
VisitTimeStampCheck::NewTimestamp,
);
}
} }

View File

@@ -114,14 +114,14 @@ impl Default for Logger {
} }
} }
impl<S, P, B> Transform<S> for Logger impl<S, B> Transform<S> for Logger
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody, B: MessageBody,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<StreamLog<B>>; type Response = ServiceResponse<StreamLog<B>>;
type Error = S::Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = LoggerMiddleware<S>; type Transform = LoggerMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = FutureResult<Self::Transform, Self::InitError>;
@@ -140,21 +140,21 @@ pub struct LoggerMiddleware<S> {
service: S, service: S,
} }
impl<S, P, B> Service for LoggerMiddleware<S> impl<S, B> Service for LoggerMiddleware<S>
where where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody, B: MessageBody,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse<StreamLog<B>>; type Response = ServiceResponse<StreamLog<B>>;
type Error = S::Error; type Error = Error;
type Future = LoggerResponse<S, P, B>; type Future = LoggerResponse<S, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
if self.inner.exclude.contains(req.path()) { if self.inner.exclude.contains(req.path()) {
LoggerResponse { LoggerResponse {
fut: self.service.call(req), fut: self.service.call(req),
@@ -180,7 +180,7 @@ where
} }
#[doc(hidden)] #[doc(hidden)]
pub struct LoggerResponse<S, P, B> pub struct LoggerResponse<S, B>
where where
B: MessageBody, B: MessageBody,
S: Service, S: Service,
@@ -188,16 +188,16 @@ where
fut: S::Future, fut: S::Future,
time: time::Tm, time: time::Tm,
format: Option<Format>, format: Option<Format>,
_t: PhantomData<(P, B)>, _t: PhantomData<(B,)>,
} }
impl<S, P, B> Future for LoggerResponse<S, P, B> impl<S, B> Future for LoggerResponse<S, B>
where where
B: MessageBody, B: MessageBody,
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
type Item = ServiceResponse<StreamLog<B>>; type Item = ServiceResponse<StreamLog<B>>;
type Error = S::Error; type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let res = futures::try_ready!(self.fut.poll()); let res = futures::try_ready!(self.fut.poll());
@@ -363,13 +363,6 @@ impl FormatText {
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0; let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0;
fmt.write_fmt(format_args!("{:.6}", rt)) fmt.write_fmt(format_args!("{:.6}", rt))
} }
// FormatText::RemoteAddr => {
// if let Some(remote) = req.connection_info().remote() {
// return remote.fmt(fmt);
// } else {
// "-".fmt(fmt)
// }
// }
FormatText::EnvironHeader(ref name) => { FormatText::EnvironHeader(ref name) => {
if let Ok(val) = env::var(name) { if let Ok(val) = env::var(name) {
fmt.write_fmt(format_args!("{}", val)) fmt.write_fmt(format_args!("{}", val))
@@ -402,7 +395,7 @@ impl FormatText {
} }
} }
fn render_request<P>(&mut self, now: time::Tm, req: &ServiceRequest<P>) { fn render_request(&mut self, now: time::Tm, req: &ServiceRequest) {
match *self { match *self {
FormatText::RequestLine => { FormatText::RequestLine => {
*self = if req.query_string().is_empty() { *self = if req.query_string().is_empty() {
@@ -441,6 +434,14 @@ impl FormatText {
}; };
*self = FormatText::Str(s.to_string()); *self = FormatText::Str(s.to_string());
} }
FormatText::RemoteAddr => {
let s = if let Some(remote) = req.connection_info().remote() {
FormatText::Str(remote.to_string())
} else {
FormatText::Str("-".to_string())
};
*self = s;
}
_ => (), _ => (),
} }
} }
@@ -464,7 +465,7 @@ mod tests {
#[test] #[test]
fn test_logger() { fn test_logger() {
let srv = FnService::new(|req: ServiceRequest<_>| { let srv = FnService::new(|req: ServiceRequest| {
req.into_response( req.into_response(
HttpResponse::build(StatusCode::OK) HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt") .header("X-Test", "ttt")

View File

@@ -1,22 +1,16 @@
//! Middlewares //! Middlewares
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
mod compress; mod compress;
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] pub use self::compress::{BodyEncoding, Compress};
mod decompress;
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
pub mod encoding {
//! Middlewares for compressing/decompressing payloads.
pub use super::compress::{BodyEncoding, Compress};
pub use super::decompress::Decompress;
}
pub mod cors; pub mod cors;
mod defaultheaders; mod defaultheaders;
pub mod errhandlers; pub mod errhandlers;
mod logger; mod logger;
mod normalize;
pub use self::defaultheaders::DefaultHeaders; pub use self::defaultheaders::DefaultHeaders;
pub use self::logger::Logger; pub use self::logger::Logger;
pub use self::normalize::NormalizePath;
#[cfg(feature = "secure-cookies")] #[cfg(feature = "secure-cookies")]
pub mod identity; pub mod identity;

153
src/middleware/normalize.rs Normal file
View File

@@ -0,0 +1,153 @@
//! `Middleware` to normalize request's URI
use actix_http::http::{HttpTryFrom, PathAndQuery, Uri};
use actix_service::{Service, Transform};
use bytes::Bytes;
use futures::future::{self, FutureResult};
use regex::Regex;
use crate::service::{ServiceRequest, ServiceResponse};
use crate::Error;
#[derive(Default, Clone, Copy)]
/// `Middleware` to normalize request's URI in place
///
/// Performs following:
///
/// - Merges multiple slashes into one.
///
/// ```rust
/// use actix_web::{web, http, middleware, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .wrap(middleware::NormalizePath)
/// .service(
/// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub struct NormalizePath;
impl<S, B> Transform<S> for NormalizePath
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = NormalizePathNormalization<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
future::ok(NormalizePathNormalization {
service,
merge_slash: Regex::new("//+").unwrap(),
})
}
}
pub struct NormalizePathNormalization<S> {
service: S,
merge_slash: Regex,
}
impl<S, B> Service for NormalizePathNormalization<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = S::Future;
fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let head = req.head_mut();
let path = head.uri.path();
let original_len = path.len();
let path = self.merge_slash.replace_all(path, "/");
if original_len != path.len() {
let mut parts = head.uri.clone().into_parts();
let pq = parts.path_and_query.as_ref().unwrap();
let path = if let Some(q) = pq.query() {
Bytes::from(format!("{}?{}", path, q))
} else {
Bytes::from(path.as_ref())
};
parts.path_and_query = Some(PathAndQuery::try_from(path).unwrap());
let uri = Uri::from_parts(parts).unwrap();
req.match_info_mut().get_mut().update(&uri);
req.head_mut().uri = uri;
}
self.service.call(req)
}
}
#[cfg(test)]
mod tests {
use actix_service::FnService;
use super::*;
use crate::dev::ServiceRequest;
use crate::test::{block_on, call_service, init_service, TestRequest};
use crate::{web, App, HttpResponse};
#[test]
fn test_wrap() {
let mut app = init_service(
App::new()
.wrap(NormalizePath::default())
.service(web::resource("/v1/something/").to(|| HttpResponse::Ok())),
);
let req = TestRequest::with_uri("/v1//something////").to_request();
let res = call_service(&mut app, req);
assert!(res.status().is_success());
}
#[test]
fn test_in_place_normalization() {
let srv = FnService::new(|req: ServiceRequest| {
assert_eq!("/v1/something/", req.path());
req.into_response(HttpResponse::Ok().finish())
});
let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap();
let req = TestRequest::with_uri("/v1//something////").to_srv_request();
let res = block_on(normalize.call(req)).unwrap();
assert!(res.status().is_success());
}
#[test]
fn should_normalize_nothing() {
const URI: &str = "/v1/something/";
let srv = FnService::new(|req: ServiceRequest| {
assert_eq!(URI, req.path());
req.into_response(HttpResponse::Ok().finish())
});
let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap();
let req = TestRequest::with_uri(URI).to_srv_request();
let res = block_on(normalize.call(req)).unwrap();
assert!(res.status().is_success());
}
}

View File

@@ -1,13 +1,13 @@
use std::cell::{Ref, RefCell, RefMut}; use std::cell::{Ref, RefCell, RefMut};
use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, net};
use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::http::{HeaderMap, Method, Uri, Version};
use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
use actix_router::{Path, Url}; use actix_router::{Path, Url};
use crate::config::AppConfig; use crate::config::AppConfig;
use crate::data::{Data, RouteData}; use crate::data::Data;
use crate::error::UrlGenerationError; use crate::error::UrlGenerationError;
use crate::extract::FromRequest; use crate::extract::FromRequest;
use crate::info::ConnectionInfo; use crate::info::ConnectionInfo;
@@ -20,9 +20,9 @@ pub struct HttpRequest(pub(crate) Rc<HttpRequestInner>);
pub(crate) struct HttpRequestInner { pub(crate) struct HttpRequestInner {
pub(crate) head: Message<RequestHead>, pub(crate) head: Message<RequestHead>,
pub(crate) path: Path<Url>, pub(crate) path: Path<Url>,
pub(crate) app_data: Rc<Extensions>,
rmap: Rc<ResourceMap>, rmap: Rc<ResourceMap>,
config: AppConfig, config: AppConfig,
route_data: Option<Rc<Extensions>>,
pool: &'static HttpRequestPool, pool: &'static HttpRequestPool,
} }
@@ -33,6 +33,7 @@ impl HttpRequest {
head: Message<RequestHead>, head: Message<RequestHead>,
rmap: Rc<ResourceMap>, rmap: Rc<ResourceMap>,
config: AppConfig, config: AppConfig,
app_data: Rc<Extensions>,
pool: &'static HttpRequestPool, pool: &'static HttpRequestPool,
) -> HttpRequest { ) -> HttpRequest {
HttpRequest(Rc::new(HttpRequestInner { HttpRequest(Rc::new(HttpRequestInner {
@@ -40,8 +41,8 @@ impl HttpRequest {
path, path,
rmap, rmap,
config, config,
app_data,
pool, pool,
route_data: None,
})) }))
} }
} }
@@ -170,6 +171,17 @@ impl HttpRequest {
self.url_for(name, &NO_PARAMS) self.url_for(name, &NO_PARAMS)
} }
/// Peer socket address
///
/// Peer address is actual socket address, if proxy is used in front of
/// actix http server, then peer address would be address of this proxy.
///
/// To get client connection information `.connection_info()` should be used.
#[inline]
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
self.head().peer_addr
}
/// Get *ConnectionInfo* for the current request. /// Get *ConnectionInfo* for the current request.
#[inline] #[inline]
pub fn connection_info(&self) -> Ref<ConnectionInfo> { pub fn connection_info(&self) -> Ref<ConnectionInfo> {
@@ -184,27 +196,23 @@ impl HttpRequest {
/// Get an application data stored with `App::data()` method during /// Get an application data stored with `App::data()` method during
/// application configuration. /// application configuration.
pub fn app_data<T: 'static>(&self) -> Option<Data<T>> { pub fn app_data<T: 'static>(&self) -> Option<&T> {
if let Some(st) = self.0.config.extensions().get::<Data<T>>() { if let Some(st) = self.0.app_data.get::<Data<T>>() {
Some(&st)
} else {
None
}
}
/// Get an application data stored with `App::data()` method during
/// application configuration.
pub fn get_app_data<T: 'static>(&self) -> Option<Data<T>> {
if let Some(st) = self.0.app_data.get::<Data<T>>() {
Some(st.clone()) Some(st.clone())
} else { } else {
None None
} }
} }
/// Load route data. Route data could be set during
/// route configuration with `Route::data()` method.
pub fn route_data<T: 'static>(&self) -> Option<&RouteData<T>> {
if let Some(ref ext) = self.0.route_data {
ext.get::<RouteData<T>>()
} else {
None
}
}
pub(crate) fn set_route_data(&mut self, data: Option<Rc<Extensions>>) {
Rc::get_mut(&mut self.0).unwrap().route_data = data;
}
} }
impl HttpMessage for HttpRequest { impl HttpMessage for HttpRequest {
@@ -265,12 +273,13 @@ impl Drop for HttpRequest {
/// ); /// );
/// } /// }
/// ``` /// ```
impl<P> FromRequest<P> for HttpRequest { impl FromRequest for HttpRequest {
type Config = ();
type Error = Error; type Error = Error;
type Future = Result<Self, Error>; type Future = Result<Self, Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
Ok(req.clone()) Ok(req.clone())
} }
} }
@@ -323,7 +332,7 @@ mod tests {
use super::*; use super::*;
use crate::dev::{ResourceDef, ResourceMap}; use crate::dev::{ResourceDef, ResourceMap};
use crate::http::{header, StatusCode}; use crate::http::{header, StatusCode};
use crate::test::{call_success, init_service, TestRequest}; use crate::test::{call_service, init_service, TestRequest};
use crate::{web, App, HttpResponse}; use crate::{web, App, HttpResponse};
#[test] #[test]
@@ -452,7 +461,7 @@ mod tests {
)); ));
let req = TestRequest::default().to_request(); let req = TestRequest::default().to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let mut srv = init_service(App::new().data(10u32).service( let mut srv = init_service(App::new().data(10u32).service(
@@ -466,7 +475,7 @@ mod tests {
)); ));
let req = TestRequest::default().to_request(); let req = TestRequest::default().to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
} }
} }

View File

@@ -1,7 +1,8 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use actix_http::{Error, Response}; use actix_http::{Error, Extensions, Response};
use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{ use actix_service::{
apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform, apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform,
@@ -9,7 +10,8 @@ use actix_service::{
use futures::future::{ok, Either, FutureResult}; use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll}; use futures::{Async, Future, IntoFuture, Poll};
use crate::dev::{insert_slash, HttpServiceFactory, ResourceDef, ServiceConfig}; use crate::data::Data;
use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef};
use crate::extract::FromRequest; use crate::extract::FromRequest;
use crate::guard::Guard; use crate::guard::Guard;
use crate::handler::{AsyncFactory, Factory}; use crate::handler::{AsyncFactory, Factory};
@@ -17,9 +19,8 @@ use crate::responder::Responder;
use crate::route::{CreateRouteService, Route, RouteService}; use crate::route::{CreateRouteService, Route, RouteService};
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>; type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService<P> = type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
/// *Resource* is an entry in resources table which corresponds to requested URL. /// *Resource* is an entry in resources table which corresponds to requested URL.
/// ///
@@ -43,18 +44,19 @@ type HttpNewService<P> =
/// ///
/// If no matching route could be found, *405* response code get returned. /// If no matching route could be found, *405* response code get returned.
/// Default behavior could be overriden with `default_resource()` method. /// Default behavior could be overriden with `default_resource()` method.
pub struct Resource<P, T = ResourceEndpoint<P>> { pub struct Resource<T = ResourceEndpoint> {
endpoint: T, endpoint: T,
rdef: String, rdef: String,
name: Option<String>, name: Option<String>,
routes: Vec<Route<P>>, routes: Vec<Route>,
data: Option<Extensions>,
guards: Vec<Box<Guard>>, guards: Vec<Box<Guard>>,
default: Rc<RefCell<Option<Rc<HttpNewService<P>>>>>, default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
factory_ref: Rc<RefCell<Option<ResourceFactory<P>>>>, factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
} }
impl<P> Resource<P> { impl Resource {
pub fn new(path: &str) -> Resource<P> { pub fn new(path: &str) -> Resource {
let fref = Rc::new(RefCell::new(None)); let fref = Rc::new(RefCell::new(None));
Resource { Resource {
@@ -64,16 +66,16 @@ impl<P> Resource<P> {
endpoint: ResourceEndpoint::new(fref.clone()), endpoint: ResourceEndpoint::new(fref.clone()),
factory_ref: fref, factory_ref: fref,
guards: Vec::new(), guards: Vec::new(),
data: None,
default: Rc::new(RefCell::new(None)), default: Rc::new(RefCell::new(None)),
} }
} }
} }
impl<P, T> Resource<P, T> impl<T> Resource<T>
where where
P: 'static,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -154,8 +156,43 @@ where
/// # fn post_handler() {} /// # fn post_handler() {}
/// # fn delete_handler() {} /// # fn delete_handler() {}
/// ``` /// ```
pub fn route(mut self, route: Route<P>) -> Self { pub fn route(mut self, route: Route) -> Self {
self.routes.push(route.finish()); self.routes.push(route);
self
}
/// Provide resource specific data. This method allows to add extractor
/// configuration or specific state available via `Data<T>` extractor.
/// Provided data is available for all routes registered for the current resource.
/// Resource data overrides data registered by `App::data()` method.
///
/// ```rust
/// use actix_web::{web, App, FromRequest};
///
/// /// extract text data from request
/// fn index(body: String) -> String {
/// format!("Body {}!", body)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html")
/// // limit size of the payload
/// .data(String::configure(|cfg| {
/// cfg.limit(4096)
/// }))
/// .route(
/// web::get()
/// // register handler
/// .to(index)
/// ));
/// }
/// ```
pub fn data<U: 'static>(mut self, data: U) -> Self {
if self.data.is_none() {
self.data = Some(Extensions::new());
}
self.data.as_mut().unwrap().insert(Data::new(data));
self self
} }
@@ -182,7 +219,7 @@ where
pub fn to<F, I, R>(mut self, handler: F) -> Self pub fn to<F, I, R>(mut self, handler: F) -> Self
where where
F: Factory<I, R> + 'static, F: Factory<I, R> + 'static,
I: FromRequest<P> + 'static, I: FromRequest + 'static,
R: Responder + 'static, R: Responder + 'static,
{ {
self.routes.push(Route::new().to(handler)); self.routes.push(Route::new().to(handler));
@@ -216,9 +253,9 @@ where
pub fn to_async<F, I, R>(mut self, handler: F) -> Self pub fn to_async<F, I, R>(mut self, handler: F) -> Self
where where
F: AsyncFactory<I, R>, F: AsyncFactory<I, R>,
I: FromRequest<P> + 'static, I: FromRequest + 'static,
R: IntoFuture + 'static, R: IntoFuture + 'static,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
self.routes.push(Route::new().to_async(handler)); self.routes.push(Route::new().to_async(handler));
@@ -236,9 +273,8 @@ where
self, self,
mw: F, mw: F,
) -> Resource< ) -> Resource<
P,
impl NewService< impl NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -247,7 +283,7 @@ where
where where
M: Transform< M: Transform<
T::Service, T::Service,
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -262,6 +298,7 @@ where
guards: self.guards, guards: self.guards,
routes: self.routes, routes: self.routes,
default: self.default, default: self.default,
data: self.data,
factory_ref: self.factory_ref, factory_ref: self.factory_ref,
} }
} }
@@ -302,54 +339,54 @@ where
self, self,
mw: F, mw: F,
) -> Resource< ) -> Resource<
P,
impl NewService< impl NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
> >
where where
F: FnMut(ServiceRequest<P>, &mut T::Service) -> R + Clone, F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
R: IntoFuture<Item = ServiceResponse, Error = Error>, R: IntoFuture<Item = ServiceResponse, Error = Error>,
{ {
self.wrap(mw) self.wrap(mw)
} }
/// Default resource to be used if no matching route could be found. /// Default service to be used if no matching route could be found.
/// By default *405* response get returned. Resource does not use /// By default *405* response get returned. Resource does not use
/// default handler from `App` or `Scope`. /// default handler from `App` or `Scope`.
pub fn default_resource<F, R, U>(mut self, f: F) -> Self pub fn default_service<F, U>(mut self, f: F) -> Self
where where
F: FnOnce(Resource<P>) -> R, F: IntoNewService<U>,
R: IntoNewService<U>,
U: NewService< U: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
> + 'static, > + 'static,
U::InitError: fmt::Debug,
{ {
// create and configure default resource // create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service(
f(Resource::new("")).into_new_service().map_init_err(|_| ()), f.into_new_service().map_init_err(|e| {
log::error!("Can not construct default service: {:?}", e)
}),
))))); )))));
self self
} }
} }
impl<P, T> HttpServiceFactory<P> for Resource<P, T> impl<T> HttpServiceFactory for Resource<T>
where where
P: 'static,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
> + 'static, > + 'static,
{ {
fn register(mut self, config: &mut ServiceConfig<P>) { fn register(mut self, config: &mut AppService) {
let guards = if self.guards.is_empty() { let guards = if self.guards.is_empty() {
None None
} else { } else {
@@ -363,14 +400,18 @@ where
if let Some(ref name) = self.name { if let Some(ref name) = self.name {
*rdef.name_mut() = name.clone(); *rdef.name_mut() = name.clone();
} }
// custom app data storage
if let Some(ref mut ext) = self.data {
config.set_route_data(ext);
}
config.register_service(rdef, guards, self, None) config.register_service(rdef, guards, self, None)
} }
} }
impl<P, T> IntoNewService<T> for Resource<P, T> impl<T> IntoNewService<T> for Resource<T>
where where
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -379,6 +420,7 @@ where
fn into_new_service(self) -> T { fn into_new_service(self) -> T {
*self.factory_ref.borrow_mut() = Some(ResourceFactory { *self.factory_ref.borrow_mut() = Some(ResourceFactory {
routes: self.routes, routes: self.routes,
data: self.data.map(|data| Rc::new(data)),
default: self.default, default: self.default,
}); });
@@ -386,18 +428,19 @@ where
} }
} }
pub struct ResourceFactory<P> { pub struct ResourceFactory {
routes: Vec<Route<P>>, routes: Vec<Route>,
default: Rc<RefCell<Option<Rc<HttpNewService<P>>>>>, data: Option<Rc<Extensions>>,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
} }
impl<P: 'static> NewService for ResourceFactory<P> { impl NewService for ResourceFactory {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = ResourceService<P>; type Service = ResourceService;
type Future = CreateResourceService<P>; type Future = CreateResourceService;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
let default_fut = if let Some(ref default) = *self.default.borrow() { let default_fut = if let Some(ref default) = *self.default.borrow() {
@@ -412,25 +455,27 @@ impl<P: 'static> NewService for ResourceFactory<P> {
.iter() .iter()
.map(|route| CreateRouteServiceItem::Future(route.new_service(&()))) .map(|route| CreateRouteServiceItem::Future(route.new_service(&())))
.collect(), .collect(),
data: self.data.clone(),
default: None, default: None,
default_fut, default_fut,
} }
} }
} }
enum CreateRouteServiceItem<P> { enum CreateRouteServiceItem {
Future(CreateRouteService<P>), Future(CreateRouteService),
Service(RouteService<P>), Service(RouteService),
} }
pub struct CreateResourceService<P> { pub struct CreateResourceService {
fut: Vec<CreateRouteServiceItem<P>>, fut: Vec<CreateRouteServiceItem>,
default: Option<HttpService<P>>, data: Option<Rc<Extensions>>,
default_fut: Option<Box<Future<Item = HttpService<P>, Error = ()>>>, default: Option<HttpService>,
default_fut: Option<Box<Future<Item = HttpService, Error = ()>>>,
} }
impl<P> Future for CreateResourceService<P> { impl Future for CreateResourceService {
type Item = ResourceService<P>; type Item = ResourceService;
type Error = (); type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -469,6 +514,7 @@ impl<P> Future for CreateResourceService<P> {
.collect(); .collect();
Ok(Async::Ready(ResourceService { Ok(Async::Ready(ResourceService {
routes, routes,
data: self.data.clone(),
default: self.default.take(), default: self.default.take(),
})) }))
} else { } else {
@@ -477,13 +523,14 @@ impl<P> Future for CreateResourceService<P> {
} }
} }
pub struct ResourceService<P> { pub struct ResourceService {
routes: Vec<RouteService<P>>, routes: Vec<RouteService>,
default: Option<HttpService<P>>, data: Option<Rc<Extensions>>,
default: Option<HttpService>,
} }
impl<P> Service for ResourceService<P> { impl Service for ResourceService {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = Either< type Future = Either<
@@ -495,9 +542,12 @@ impl<P> Service for ResourceService<P> {
Ok(Async::Ready(())) Ok(Async::Ready(()))
} }
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
for route in self.routes.iter_mut() { for route in self.routes.iter_mut() {
if route.check(&mut req) { if route.check(&mut req) {
if let Some(ref data) = self.data {
req.set_data_container(data.clone());
}
return route.call(req); return route.call(req);
} }
} }
@@ -514,23 +564,23 @@ impl<P> Service for ResourceService<P> {
} }
#[doc(hidden)] #[doc(hidden)]
pub struct ResourceEndpoint<P> { pub struct ResourceEndpoint {
factory: Rc<RefCell<Option<ResourceFactory<P>>>>, factory: Rc<RefCell<Option<ResourceFactory>>>,
} }
impl<P> ResourceEndpoint<P> { impl ResourceEndpoint {
fn new(factory: Rc<RefCell<Option<ResourceFactory<P>>>>) -> Self { fn new(factory: Rc<RefCell<Option<ResourceFactory>>>) -> Self {
ResourceEndpoint { factory } ResourceEndpoint { factory }
} }
} }
impl<P: 'static> NewService for ResourceEndpoint<P> { impl NewService for ResourceEndpoint {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = ResourceService<P>; type Service = ResourceService;
type Future = CreateResourceService<P>; type Future = CreateResourceService;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
self.factory.borrow_mut().as_mut().unwrap().new_service(&()) self.factory.borrow_mut().as_mut().unwrap().new_service(&())
@@ -547,16 +597,16 @@ mod tests {
use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
use crate::test::{call_success, init_service, TestRequest}; use crate::test::{call_service, init_service, TestRequest};
use crate::{web, App, Error, HttpResponse}; use crate::{web, App, Error, HttpResponse};
fn md<S, P, B>( fn md<S, B>(
req: ServiceRequest<P>, req: ServiceRequest,
srv: &mut S, srv: &mut S,
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error> ) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
where where
S: Service< S: Service<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
>, >,
@@ -579,7 +629,7 @@ mod tests {
), ),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -605,7 +655,7 @@ mod tests {
), ),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -620,7 +670,7 @@ mod tests {
sleep(Duration::from_millis(100)).then(|_| HttpResponse::Ok()) sleep(Duration::from_millis(100)).then(|_| HttpResponse::Ok())
}))); })));
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
} }
@@ -631,34 +681,38 @@ mod tests {
.service( .service(
web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), web::resource("/test").route(web::get().to(|| HttpResponse::Ok())),
) )
.default_resource(|r| r.to(|| HttpResponse::BadRequest())), .default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::BadRequest())
}),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test")
.method(Method::POST) .method(Method::POST)
.to_request(); .to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let mut srv = init_service( let mut srv = init_service(
App::new().service( App::new().service(
web::resource("/test") web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok())) .route(web::get().to(|| HttpResponse::Ok()))
.default_resource(|r| r.to(|| HttpResponse::BadRequest())), .default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::BadRequest())
}),
), ),
); );
let req = TestRequest::with_uri("/test").to_request(); let req = TestRequest::with_uri("/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test")
.method(Method::POST) .method(Method::POST)
.to_request(); .to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
} }
} }

View File

@@ -1,8 +1,12 @@
use actix_http::error::InternalError; use actix_http::error::InternalError;
use actix_http::{http::StatusCode, Error, Response, ResponseBuilder}; use actix_http::http::{
header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, HttpTryFrom,
StatusCode,
};
use actix_http::{Error, Response, ResponseBuilder};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::future::{err, ok, Either as EitherFuture, FutureResult}; use futures::future::{err, ok, Either as EitherFuture, FutureResult};
use futures::{Future, IntoFuture, Poll}; use futures::{try_ready, Async, Future, IntoFuture, Poll};
use crate::request::HttpRequest; use crate::request::HttpRequest;
@@ -18,6 +22,51 @@ pub trait Responder {
/// Convert itself to `AsyncResult` or `Error`. /// Convert itself to `AsyncResult` or `Error`.
fn respond_to(self, req: &HttpRequest) -> Self::Future; fn respond_to(self, req: &HttpRequest) -> Self::Future;
/// Override a status code for a Responder.
///
/// ```rust
/// use actix_web::{HttpRequest, Responder, http::StatusCode};
///
/// fn index(req: HttpRequest) -> impl Responder {
/// "Welcome!".with_status(StatusCode::OK)
/// }
/// # fn main() {}
/// ```
fn with_status(self, status: StatusCode) -> CustomResponder<Self>
where
Self: Sized,
{
CustomResponder::new(self).with_status(status)
}
/// Add header to the Responder's response.
///
/// ```rust
/// use actix_web::{web, HttpRequest, Responder};
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(req: HttpRequest) -> impl Responder {
/// web::Json(
/// MyObj{name: "Name".to_string()}
/// )
/// .with_header("x-version", "1.2.3")
/// }
/// # fn main() {}
/// ```
fn with_header<K, V>(self, key: K, value: V) -> CustomResponder<Self>
where
Self: Sized,
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
CustomResponder::new(self).with_header(key, value)
}
} }
impl Responder for Response { impl Responder for Response {
@@ -154,6 +203,117 @@ impl Responder for BytesMut {
} }
} }
/// Allows to override status code and headers for a responder.
pub struct CustomResponder<T> {
responder: T,
status: Option<StatusCode>,
headers: Option<HeaderMap>,
error: Option<HttpError>,
}
impl<T: Responder> CustomResponder<T> {
fn new(responder: T) -> Self {
CustomResponder {
responder,
status: None,
headers: None,
error: None,
}
}
/// Override a status code for the Responder's response.
///
/// ```rust
/// use actix_web::{HttpRequest, Responder, http::StatusCode};
///
/// fn index(req: HttpRequest) -> impl Responder {
/// "Welcome!".with_status(StatusCode::OK)
/// }
/// # fn main() {}
/// ```
pub fn with_status(mut self, status: StatusCode) -> Self {
self.status = Some(status);
self
}
/// Add header to the Responder's response.
///
/// ```rust
/// use actix_web::{web, HttpRequest, Responder};
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(req: HttpRequest) -> impl Responder {
/// web::Json(
/// MyObj{name: "Name".to_string()}
/// )
/// .with_header("x-version", "1.2.3")
/// }
/// # fn main() {}
/// ```
pub fn with_header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
if self.headers.is_none() {
self.headers = Some(HeaderMap::new());
}
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
self.headers.as_mut().unwrap().append(key, value);
}
Err(e) => self.error = Some(e.into()),
},
Err(e) => self.error = Some(e.into()),
};
self
}
}
impl<T: Responder> Responder for CustomResponder<T> {
type Error = T::Error;
type Future = CustomResponderFut<T>;
fn respond_to(self, req: &HttpRequest) -> Self::Future {
CustomResponderFut {
fut: self.responder.respond_to(req).into_future(),
status: self.status,
headers: self.headers,
}
}
}
pub struct CustomResponderFut<T: Responder> {
fut: <T::Future as IntoFuture>::Future,
status: Option<StatusCode>,
headers: Option<HeaderMap>,
}
impl<T: Responder> Future for CustomResponderFut<T> {
type Item = Response;
type Error = T::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut res = try_ready!(self.fut.poll());
if let Some(status) = self.status {
*res.status_mut() = status;
}
if let Some(ref headers) = self.headers {
for (k, v) in headers {
res.headers_mut().insert(k.clone(), v.clone());
}
}
Ok(Async::Ready(res))
}
}
/// Combines two different responder types into a single type /// Combines two different responder types into a single type
/// ///
/// ```rust /// ```rust
@@ -307,11 +467,11 @@ pub(crate) mod tests {
); );
let req = TestRequest::with_uri("/none").to_request(); let req = TestRequest::with_uri("/none").to_request();
let resp = TestRequest::block_on(srv.call(req)).unwrap(); let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/some").to_request(); let req = TestRequest::with_uri("/some").to_request();
let resp = TestRequest::block_on(srv.call(req)).unwrap(); let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
match resp.response().body() { match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => { ResponseBody::Body(Body::Bytes(ref b)) => {
@@ -435,4 +595,33 @@ pub(crate) mod tests {
); );
assert!(res.is_err()); assert!(res.is_err());
} }
#[test]
fn test_custom_responder() {
let req = TestRequest::default().to_http_request();
let res = block_on(
"test"
.to_string()
.with_status(StatusCode::BAD_REQUEST)
.respond_to(&req),
)
.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(res.body().bin_ref(), b"test");
let res = block_on(
"test"
.to_string()
.with_header("content-type", "json")
.respond_to(&req),
)
.unwrap();
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.body().bin_ref(), b"test");
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("json")
);
}
} }

View File

@@ -1,13 +1,10 @@
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc; use std::rc::Rc;
use actix_http::{http::Method, Error, Extensions, Response}; use actix_http::{http::Method, Error};
use actix_service::{NewService, Service}; use actix_service::{NewService, Service};
use futures::future::{ok, Either, FutureResult}; use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll}; use futures::{Async, Future, IntoFuture, Poll};
use crate::data::RouteData;
use crate::extract::FromRequest; use crate::extract::FromRequest;
use crate::guard::{self, Guard}; use crate::guard::{self, Guard};
use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler}; use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler};
@@ -42,45 +39,34 @@ type BoxedRouteNewService<Req, Res> = Box<
/// ///
/// Route uses builder-like pattern for configuration. /// Route uses builder-like pattern for configuration.
/// If handler is not explicitly set, default *404 Not Found* handler is used. /// If handler is not explicitly set, default *404 Not Found* handler is used.
pub struct Route<P> { pub struct Route {
service: BoxedRouteNewService<ServiceRequest<P>, ServiceResponse>, service: BoxedRouteNewService<ServiceRequest, ServiceResponse>,
guards: Rc<Vec<Box<Guard>>>, guards: Rc<Vec<Box<Guard>>>,
data: Option<Extensions>,
data_ref: Rc<RefCell<Option<Rc<Extensions>>>>,
} }
impl<P: 'static> Route<P> { impl Route {
/// Create new route which matches any request. /// Create new route which matches any request.
pub fn new() -> Route<P> { pub fn new() -> Route {
let data_ref = Rc::new(RefCell::new(None));
Route { Route {
service: Box::new(RouteNewService::new(Extract::new( service: Box::new(RouteNewService::new(Extract::new(Handler::new(|| {
data_ref.clone(), HttpResponse::NotFound()
Handler::new(|| HttpResponse::NotFound()), })))),
))),
guards: Rc::new(Vec::new()), guards: Rc::new(Vec::new()),
data: None,
data_ref,
} }
} }
pub(crate) fn finish(mut self) -> Self {
*self.data_ref.borrow_mut() = self.data.take().map(Rc::new);
self
}
pub(crate) fn take_guards(&mut self) -> Vec<Box<Guard>> { pub(crate) fn take_guards(&mut self) -> Vec<Box<Guard>> {
std::mem::replace(Rc::get_mut(&mut self.guards).unwrap(), Vec::new()) std::mem::replace(Rc::get_mut(&mut self.guards).unwrap(), Vec::new())
} }
} }
impl<P> NewService for Route<P> { impl NewService for Route {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = RouteService<P>; type Service = RouteService;
type Future = CreateRouteService<P>; type Future = CreateRouteService;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
CreateRouteService { CreateRouteService {
@@ -90,17 +76,16 @@ impl<P> NewService for Route<P> {
} }
} }
type RouteFuture<P> = Box< type RouteFuture =
Future<Item = BoxedRouteService<ServiceRequest<P>, ServiceResponse>, Error = ()>, Box<Future<Item = BoxedRouteService<ServiceRequest, ServiceResponse>, Error = ()>>;
>;
pub struct CreateRouteService<P> { pub struct CreateRouteService {
fut: RouteFuture<P>, fut: RouteFuture,
guards: Rc<Vec<Box<Guard>>>, guards: Rc<Vec<Box<Guard>>>,
} }
impl<P> Future for CreateRouteService<P> { impl Future for CreateRouteService {
type Item = RouteService<P>; type Item = RouteService;
type Error = (); type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -114,13 +99,13 @@ impl<P> Future for CreateRouteService<P> {
} }
} }
pub struct RouteService<P> { pub struct RouteService {
service: BoxedRouteService<ServiceRequest<P>, ServiceResponse>, service: BoxedRouteService<ServiceRequest, ServiceResponse>,
guards: Rc<Vec<Box<Guard>>>, guards: Rc<Vec<Box<Guard>>>,
} }
impl<P> RouteService<P> { impl RouteService {
pub fn check(&self, req: &mut ServiceRequest<P>) -> bool { pub fn check(&self, req: &mut ServiceRequest) -> bool {
for f in self.guards.iter() { for f in self.guards.iter() {
if !f.check(req.head()) { if !f.check(req.head()) {
return false; return false;
@@ -130,8 +115,8 @@ impl<P> RouteService<P> {
} }
} }
impl<P> Service for RouteService<P> { impl Service for RouteService {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = Either< type Future = Either<
@@ -143,12 +128,12 @@ impl<P> Service for RouteService<P> {
self.service.poll_ready() self.service.poll_ready()
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
self.service.call(req) self.service.call(req)
} }
} }
impl<P: 'static> Route<P> { impl Route {
/// Add method guard to the route. /// Add method guard to the route.
/// ///
/// ```rust /// ```rust
@@ -235,16 +220,14 @@ impl<P: 'static> Route<P> {
/// ); /// );
/// } /// }
/// ``` /// ```
pub fn to<F, T, R>(mut self, handler: F) -> Route<P> pub fn to<F, T, R>(mut self, handler: F) -> Route
where where
F: Factory<T, R> + 'static, F: Factory<T, R> + 'static,
T: FromRequest<P> + 'static, T: FromRequest + 'static,
R: Responder + 'static, R: Responder + 'static,
{ {
self.service = Box::new(RouteNewService::new(Extract::new( self.service =
self.data_ref.clone(), Box::new(RouteNewService::new(Extract::new(Handler::new(handler))));
Handler::new(handler),
)));
self self
} }
@@ -278,92 +261,57 @@ impl<P: 'static> Route<P> {
pub fn to_async<F, T, R>(mut self, handler: F) -> Self pub fn to_async<F, T, R>(mut self, handler: F) -> Self
where where
F: AsyncFactory<T, R>, F: AsyncFactory<T, R>,
T: FromRequest<P> + 'static, T: FromRequest + 'static,
R: IntoFuture + 'static, R: IntoFuture + 'static,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
self.service = Box::new(RouteNewService::new(Extract::new( self.service = Box::new(RouteNewService::new(Extract::new(AsyncHandler::new(
self.data_ref.clone(), handler,
AsyncHandler::new(handler), ))));
)));
self
}
/// Provide route specific data. This method allows to add extractor
/// configuration or specific state available via `RouteData<T>` extractor.
///
/// ```rust
/// use actix_web::{web, App};
///
/// /// extract text data from request
/// fn index(body: String) -> String {
/// format!("Body {}!", body)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::get()
/// // limit size of the payload
/// .data(web::PayloadConfig::new(4096))
/// // register handler
/// .to(index)
/// ));
/// }
/// ```
pub fn data<C: 'static>(mut self, data: C) -> Self {
if self.data.is_none() {
self.data = Some(Extensions::new());
}
self.data.as_mut().unwrap().insert(RouteData::new(data));
self self
} }
} }
struct RouteNewService<P, T> struct RouteNewService<T>
where where
T: NewService<Request = ServiceRequest<P>, Error = (Error, ServiceRequest<P>)>, T: NewService<Request = ServiceRequest, Error = (Error, ServiceRequest)>,
{ {
service: T, service: T,
_t: PhantomData<P>,
} }
impl<P: 'static, T> RouteNewService<P, T> impl<T> RouteNewService<T>
where where
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = (Error, ServiceRequest<P>), Error = (Error, ServiceRequest),
>, >,
T::Future: 'static, T::Future: 'static,
T::Service: 'static, T::Service: 'static,
<T::Service as Service>::Future: 'static, <T::Service as Service>::Future: 'static,
{ {
pub fn new(service: T) -> Self { pub fn new(service: T) -> Self {
RouteNewService { RouteNewService { service }
service,
_t: PhantomData,
}
} }
} }
impl<P: 'static, T> NewService for RouteNewService<P, T> impl<T> NewService for RouteNewService<T>
where where
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = (Error, ServiceRequest<P>), Error = (Error, ServiceRequest),
>, >,
T::Future: 'static, T::Future: 'static,
T::Service: 'static, T::Service: 'static,
<T::Service as Service>::Future: 'static, <T::Service as Service>::Future: 'static,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = BoxedRouteService<ServiceRequest<P>, Self::Response>; type Service = BoxedRouteService<ServiceRequest, Self::Response>;
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>; type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
@@ -373,31 +321,27 @@ where
.map_err(|_| ()) .map_err(|_| ())
.and_then(|service| { .and_then(|service| {
let service: BoxedRouteService<_, _> = let service: BoxedRouteService<_, _> =
Box::new(RouteServiceWrapper { Box::new(RouteServiceWrapper { service });
service,
_t: PhantomData,
});
Ok(service) Ok(service)
}), }),
) )
} }
} }
struct RouteServiceWrapper<P, T: Service> { struct RouteServiceWrapper<T: Service> {
service: T, service: T,
_t: PhantomData<P>,
} }
impl<P, T> Service for RouteServiceWrapper<P, T> impl<T> Service for RouteServiceWrapper<T>
where where
T::Future: 'static, T::Future: 'static,
T: Service< T: Service<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = (Error, ServiceRequest<P>), Error = (Error, ServiceRequest),
>, >,
{ {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = Either< type Future = Either<
@@ -409,7 +353,7 @@ where
self.service.poll_ready().map_err(|(e, _)| e) self.service.poll_ready().map_err(|(e, _)| e)
} }
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let mut fut = self.service.call(req); let mut fut = self.service.call(req);
match fut.poll() { match fut.poll() {
Ok(Async::Ready(res)) => Either::A(ok(res)), Ok(Async::Ready(res)) => Either::A(ok(res)),
@@ -426,18 +370,25 @@ where
mod tests { mod tests {
use std::time::Duration; use std::time::Duration;
use bytes::Bytes;
use futures::Future; use futures::Future;
use serde_derive::Serialize;
use tokio_timer::sleep; use tokio_timer::sleep;
use crate::http::{Method, StatusCode}; use crate::http::{Method, StatusCode};
use crate::test::{call_success, init_service, TestRequest}; use crate::test::{call_service, init_service, read_body, TestRequest};
use crate::{error, web, App, HttpResponse}; use crate::{error, web, App, HttpResponse};
#[derive(Serialize, PartialEq, Debug)]
struct MyObject {
name: String,
}
#[test] #[test]
fn test_route() { fn test_route() {
let mut srv = let mut srv = init_service(
init_service( App::new()
App::new().service( .service(
web::resource("/test") web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok())) .route(web::get().to(|| HttpResponse::Ok()))
.route(web::put().to(|| { .route(web::put().to(|| {
@@ -452,37 +403,51 @@ mod tests {
Err::<HttpResponse, _>(error::ErrorBadRequest("err")) Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
}) })
})), })),
), )
); .service(web::resource("/json").route(web::get().to_async(|| {
sleep(Duration::from_millis(25)).then(|_| {
Ok::<_, crate::Error>(web::Json(MyObject {
name: "test".to_string(),
}))
})
}))),
);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test")
.method(Method::GET) .method(Method::GET)
.to_request(); .to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test")
.method(Method::POST) .method(Method::POST)
.to_request(); .to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::CREATED); assert_eq!(resp.status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test")
.method(Method::PUT) .method(Method::PUT)
.to_request(); .to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test")
.method(Method::DELETE) .method(Method::DELETE)
.to_request(); .to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test")
.method(Method::HEAD) .method(Method::HEAD)
.to_request(); .to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let req = TestRequest::with_uri("/json").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp);
assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
} }
} }

View File

@@ -1,4 +1,5 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use actix_http::Response; use actix_http::Response;
@@ -10,7 +11,7 @@ use actix_service::{
use futures::future::{ok, Either, Future, FutureResult}; use futures::future::{ok, Either, Future, FutureResult};
use futures::{Async, IntoFuture, Poll}; use futures::{Async, IntoFuture, Poll};
use crate::dev::{HttpServiceFactory, ServiceConfig}; use crate::dev::{AppService, HttpServiceFactory};
use crate::error::Error; use crate::error::Error;
use crate::guard::Guard; use crate::guard::Guard;
use crate::resource::Resource; use crate::resource::Resource;
@@ -21,9 +22,8 @@ use crate::service::{
}; };
type Guards = Vec<Box<Guard>>; type Guards = Vec<Box<Guard>>;
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>; type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService<P> = type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
type BoxedResponse = Either< type BoxedResponse = Either<
FutureResult<ServiceResponse, Error>, FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>, Box<Future<Item = ServiceResponse, Error = Error>>,
@@ -58,18 +58,18 @@ type BoxedResponse = Either<
/// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path2 - `GET` requests
/// * /{project_id}/path3 - `HEAD` requests /// * /{project_id}/path3 - `HEAD` requests
/// ///
pub struct Scope<P, T = ScopeEndpoint<P>> { pub struct Scope<T = ScopeEndpoint> {
endpoint: T, endpoint: T,
rdef: String, rdef: String,
services: Vec<Box<ServiceFactory<P>>>, services: Vec<Box<ServiceFactory>>,
guards: Vec<Box<Guard>>, guards: Vec<Box<Guard>>,
default: Rc<RefCell<Option<Rc<HttpNewService<P>>>>>, default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
factory_ref: Rc<RefCell<Option<ScopeFactory<P>>>>, factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
} }
impl<P: 'static> Scope<P> { impl Scope {
/// Create a new scope /// Create a new scope
pub fn new(path: &str) -> Scope<P> { pub fn new(path: &str) -> Scope {
let fref = Rc::new(RefCell::new(None)); let fref = Rc::new(RefCell::new(None));
Scope { Scope {
endpoint: ScopeEndpoint::new(fref.clone()), endpoint: ScopeEndpoint::new(fref.clone()),
@@ -82,11 +82,10 @@ impl<P: 'static> Scope<P> {
} }
} }
impl<P, T> Scope<P, T> impl<T> Scope<T>
where where
P: 'static,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -146,7 +145,7 @@ where
/// ``` /// ```
pub fn service<F>(mut self, factory: F) -> Self pub fn service<F>(mut self, factory: F) -> Self
where where
F: HttpServiceFactory<P> + 'static, F: HttpServiceFactory + 'static,
{ {
self.services self.services
.push(Box::new(ServiceFactoryWrapper::new(factory))); .push(Box::new(ServiceFactoryWrapper::new(factory)));
@@ -174,7 +173,7 @@ where
/// ); /// );
/// } /// }
/// ``` /// ```
pub fn route(self, path: &str, mut route: Route<P>) -> Self { pub fn route(self, path: &str, mut route: Route) -> Self {
self.service( self.service(
Resource::new(path) Resource::new(path)
.add_guards(route.take_guards()) .add_guards(route.take_guards())
@@ -182,22 +181,24 @@ where
) )
} }
/// Default resource to be used if no matching route could be found. /// Default service to be used if no matching route could be found.
/// ///
/// If default resource is not registered, app's default resource is being used. /// If default resource is not registered, app's default resource is being used.
pub fn default_resource<F, U>(mut self, f: F) -> Self pub fn default_service<F, U>(mut self, f: F) -> Self
where where
F: FnOnce(Resource<P>) -> Resource<P, U>, F: IntoNewService<U>,
U: NewService< U: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (),
> + 'static, > + 'static,
U::InitError: fmt::Debug,
{ {
// create and configure default resource // create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service(
f(Resource::new("")).into_new_service().map_init_err(|_| ()), f.into_new_service().map_init_err(|e| {
log::error!("Can not construct default service: {:?}", e)
}),
))))); )))));
self self
@@ -216,9 +217,8 @@ where
self, self,
mw: F, mw: F,
) -> Scope< ) -> Scope<
P,
impl NewService< impl NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -227,7 +227,7 @@ where
where where
M: Transform< M: Transform<
T::Service, T::Service,
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
@@ -247,7 +247,7 @@ where
/// Registers middleware, in the form of a closure, that runs during inbound /// Registers middleware, in the form of a closure, that runs during inbound
/// processing in the request lifecycle (request -> response), modifying /// processing in the request lifecycle (request -> response), modifying
/// request as necessary, across all requests managed by the *Scope*. /// request as necessary, across all requests managed by the *Scope*.
/// Scope-level middleware is more limited in what it can modify, relative /// Scope-level middleware is more limited in what it can modify, relative
/// to Route or Application level middleware, in that Scope-level middleware /// to Route or Application level middleware, in that Scope-level middleware
/// can not modify ServiceResponse. /// can not modify ServiceResponse.
@@ -279,33 +279,31 @@ where
self, self,
mw: F, mw: F,
) -> Scope< ) -> Scope<
P,
impl NewService< impl NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, >,
> >
where where
F: FnMut(ServiceRequest<P>, &mut T::Service) -> R + Clone, F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone,
R: IntoFuture<Item = ServiceResponse, Error = Error>, R: IntoFuture<Item = ServiceResponse, Error = Error>,
{ {
self.wrap(mw) self.wrap(mw)
} }
} }
impl<P, T> HttpServiceFactory<P> for Scope<P, T> impl<T> HttpServiceFactory for Scope<T>
where where
P: 'static,
T: NewService< T: NewService<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
> + 'static, > + 'static,
{ {
fn register(self, config: &mut ServiceConfig<P>) { fn register(self, config: &mut AppService) {
// update default resource if needed // update default resource if needed
if self.default.borrow().is_none() { if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service()); *self.default.borrow_mut() = Some(config.default_service());
@@ -324,6 +322,7 @@ where
default: self.default.clone(), default: self.default.clone(),
services: Rc::new( services: Rc::new(
cfg.into_services() cfg.into_services()
.1
.into_iter() .into_iter()
.map(|(mut rdef, srv, guards, nested)| { .map(|(mut rdef, srv, guards, nested)| {
rmap.add(&mut rdef, nested); rmap.add(&mut rdef, nested);
@@ -350,18 +349,18 @@ where
} }
} }
pub struct ScopeFactory<P> { pub struct ScopeFactory {
services: Rc<Vec<(ResourceDef, HttpNewService<P>, RefCell<Option<Guards>>)>>, services: Rc<Vec<(ResourceDef, HttpNewService, RefCell<Option<Guards>>)>>,
default: Rc<RefCell<Option<Rc<HttpNewService<P>>>>>, default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
} }
impl<P: 'static> NewService for ScopeFactory<P> { impl NewService for ScopeFactory {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = ScopeService<P>; type Service = ScopeService;
type Future = ScopeFactoryResponse<P>; type Future = ScopeFactoryResponse;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
let default_fut = if let Some(ref default) = *self.default.borrow() { let default_fut = if let Some(ref default) = *self.default.borrow() {
@@ -390,21 +389,21 @@ impl<P: 'static> NewService for ScopeFactory<P> {
/// Create scope service /// Create scope service
#[doc(hidden)] #[doc(hidden)]
pub struct ScopeFactoryResponse<P> { pub struct ScopeFactoryResponse {
fut: Vec<CreateScopeServiceItem<P>>, fut: Vec<CreateScopeServiceItem>,
default: Option<HttpService<P>>, default: Option<HttpService>,
default_fut: Option<Box<Future<Item = HttpService<P>, Error = ()>>>, default_fut: Option<Box<Future<Item = HttpService, Error = ()>>>,
} }
type HttpServiceFut<P> = Box<Future<Item = HttpService<P>, Error = ()>>; type HttpServiceFut = Box<Future<Item = HttpService, Error = ()>>;
enum CreateScopeServiceItem<P> { enum CreateScopeServiceItem {
Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut<P>), Future(Option<ResourceDef>, Option<Guards>, HttpServiceFut),
Service(ResourceDef, Option<Guards>, HttpService<P>), Service(ResourceDef, Option<Guards>, HttpService),
} }
impl<P> Future for ScopeFactoryResponse<P> { impl Future for ScopeFactoryResponse {
type Item = ScopeService<P>; type Item = ScopeService;
type Error = (); type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -465,14 +464,14 @@ impl<P> Future for ScopeFactoryResponse<P> {
} }
} }
pub struct ScopeService<P> { pub struct ScopeService {
router: Router<HttpService<P>, Vec<Box<Guard>>>, router: Router<HttpService, Vec<Box<Guard>>>,
default: Option<HttpService<P>>, default: Option<HttpService>,
_ready: Option<(ServiceRequest<P>, ResourceInfo)>, _ready: Option<(ServiceRequest, ResourceInfo)>,
} }
impl<P> Service for ScopeService<P> { impl Service for ScopeService {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Future = Either<BoxedResponse, FutureResult<Self::Response, Self::Error>>; type Future = Either<BoxedResponse, FutureResult<Self::Response, Self::Error>>;
@@ -481,7 +480,7 @@ impl<P> Service for ScopeService<P> {
Ok(Async::Ready(())) Ok(Async::Ready(()))
} }
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let res = self.router.recognize_mut_checked(&mut req, |req, guards| { let res = self.router.recognize_mut_checked(&mut req, |req, guards| {
if let Some(ref guards) = guards { if let Some(ref guards) = guards {
for f in guards { for f in guards {
@@ -505,23 +504,23 @@ impl<P> Service for ScopeService<P> {
} }
#[doc(hidden)] #[doc(hidden)]
pub struct ScopeEndpoint<P> { pub struct ScopeEndpoint {
factory: Rc<RefCell<Option<ScopeFactory<P>>>>, factory: Rc<RefCell<Option<ScopeFactory>>>,
} }
impl<P> ScopeEndpoint<P> { impl ScopeEndpoint {
fn new(factory: Rc<RefCell<Option<ScopeFactory<P>>>>) -> Self { fn new(factory: Rc<RefCell<Option<ScopeFactory>>>) -> Self {
ScopeEndpoint { factory } ScopeEndpoint { factory }
} }
} }
impl<P: 'static> NewService for ScopeEndpoint<P> { impl NewService for ScopeEndpoint {
type Request = ServiceRequest<P>; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Service = ScopeService<P>; type Service = ScopeService;
type Future = ScopeFactoryResponse<P>; type Future = ScopeFactoryResponse;
fn new_service(&self, _: &()) -> Self::Future { fn new_service(&self, _: &()) -> Self::Future {
self.factory.borrow_mut().as_mut().unwrap().new_service(&()) self.factory.borrow_mut().as_mut().unwrap().new_service(&())
@@ -537,7 +536,7 @@ mod tests {
use crate::dev::{Body, ResponseBody}; use crate::dev::{Body, ResponseBody};
use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
use crate::test::{block_on, call_success, init_service, TestRequest}; use crate::test::{block_on, call_service, init_service, TestRequest};
use crate::{guard, web, App, Error, HttpRequest, HttpResponse}; use crate::{guard, web, App, Error, HttpRequest, HttpResponse};
#[test] #[test]
@@ -848,7 +847,9 @@ mod tests {
App::new().service( App::new().service(
web::scope("/app") web::scope("/app")
.service(web::resource("/path1").to(|| HttpResponse::Ok())) .service(web::resource("/path1").to(|| HttpResponse::Ok()))
.default_resource(|r| r.to(|| HttpResponse::BadRequest())), .default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::BadRequest())
}),
), ),
); );
@@ -865,12 +866,13 @@ mod tests {
fn test_default_resource_propagation() { fn test_default_resource_propagation() {
let mut srv = init_service( let mut srv = init_service(
App::new() App::new()
.service( .service(web::scope("/app1").default_service(
web::scope("/app1") web::resource("").to(|| HttpResponse::BadRequest()),
.default_resource(|r| r.to(|| HttpResponse::BadRequest())), ))
)
.service(web::scope("/app2")) .service(web::scope("/app2"))
.default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())), .default_service(|r: ServiceRequest| {
r.into_response(HttpResponse::MethodNotAllowed())
}),
); );
let req = TestRequest::with_uri("/non-exist").to_request(); let req = TestRequest::with_uri("/non-exist").to_request();
@@ -886,13 +888,13 @@ mod tests {
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
} }
fn md<S, P, B>( fn md<S, B>(
req: ServiceRequest<P>, req: ServiceRequest,
srv: &mut S, srv: &mut S,
) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error> ) -> impl IntoFuture<Item = ServiceResponse<B>, Error = Error>
where where
S: Service< S: Service<
Request = ServiceRequest<P>, Request = ServiceRequest,
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
>, >,
@@ -911,7 +913,7 @@ mod tests {
web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), web::resource("/test").route(web::get().to(|| HttpResponse::Ok())),
))); )));
let req = TestRequest::with_uri("/app/test").to_request(); let req = TestRequest::with_uri("/app/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),
@@ -937,7 +939,7 @@ mod tests {
), ),
); );
let req = TestRequest::with_uri("/app/test").to_request(); let req = TestRequest::with_uri("/app/test").to_request();
let resp = call_success(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(), resp.headers().get(header::CONTENT_TYPE).unwrap(),

View File

@@ -1,6 +1,6 @@
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
use std::fmt; use std::rc::Rc;
use std::marker::PhantomData; use std::{fmt, net};
use actix_http::body::{Body, MessageBody, ResponseBody}; use actix_http::body::{Body, MessageBody, ResponseBody};
use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version};
@@ -8,66 +8,68 @@ use actix_http::{
Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response,
ResponseHead, ResponseHead,
}; };
use actix_router::{Path, Resource, Url}; use actix_router::{Path, Resource, ResourceDef, Url};
use actix_service::{IntoNewService, NewService};
use futures::future::{ok, FutureResult, IntoFuture}; use futures::future::{ok, FutureResult, IntoFuture};
use crate::config::{AppConfig, ServiceConfig}; use crate::config::{AppConfig, AppService};
use crate::data::Data; use crate::data::Data;
use crate::dev::insert_slash;
use crate::guard::Guard;
use crate::info::ConnectionInfo;
use crate::request::HttpRequest; use crate::request::HttpRequest;
pub trait HttpServiceFactory<P> { pub trait HttpServiceFactory {
fn register(self, config: &mut ServiceConfig<P>); fn register(self, config: &mut AppService);
} }
pub(crate) trait ServiceFactory<P> { pub(crate) trait ServiceFactory {
fn register(&mut self, config: &mut ServiceConfig<P>); fn register(&mut self, config: &mut AppService);
} }
pub(crate) struct ServiceFactoryWrapper<T, P> { pub(crate) struct ServiceFactoryWrapper<T> {
factory: Option<T>, factory: Option<T>,
_t: PhantomData<P>,
} }
impl<T, P> ServiceFactoryWrapper<T, P> { impl<T> ServiceFactoryWrapper<T> {
pub fn new(factory: T) -> Self { pub fn new(factory: T) -> Self {
Self { Self {
factory: Some(factory), factory: Some(factory),
_t: PhantomData,
} }
} }
} }
impl<T, P> ServiceFactory<P> for ServiceFactoryWrapper<T, P> impl<T> ServiceFactory for ServiceFactoryWrapper<T>
where where
T: HttpServiceFactory<P>, T: HttpServiceFactory,
{ {
fn register(&mut self, config: &mut ServiceConfig<P>) { fn register(&mut self, config: &mut AppService) {
if let Some(item) = self.factory.take() { if let Some(item) = self.factory.take() {
item.register(config) item.register(config)
} }
} }
} }
pub struct ServiceRequest<P = PayloadStream> { pub struct ServiceRequest {
req: HttpRequest, req: HttpRequest,
payload: Payload<P>, payload: Payload,
} }
impl<P> ServiceRequest<P> { impl ServiceRequest {
/// Construct service request from parts /// Construct service request from parts
pub fn from_parts(req: HttpRequest, payload: Payload<P>) -> Self { pub(crate) fn from_parts(req: HttpRequest, payload: Payload) -> Self {
ServiceRequest { req, payload } ServiceRequest { req, payload }
} }
/// Deconstruct request into parts /// Deconstruct request into parts
pub fn into_parts(self) -> (HttpRequest, Payload<P>) { pub fn into_parts(self) -> (HttpRequest, Payload) {
(self.req, self.payload) (self.req, self.payload)
} }
/// Create service response /// Create service response
#[inline] #[inline]
pub fn into_response<B>(self, res: Response<B>) -> ServiceResponse<B> { pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
ServiceResponse::new(self.req, res) ServiceResponse::new(self.req, res.into())
} }
/// Create service response for error /// Create service response for error
@@ -137,6 +139,23 @@ impl<P> ServiceRequest<P> {
} }
} }
/// Peer socket address
///
/// Peer address is actual socket address, if proxy is used in front of
/// actix http server, then peer address would be address of this proxy.
///
/// To get client connection information `ConnectionInfo` should be used.
#[inline]
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
self.head().peer_addr
}
/// Get *ConnectionInfo* for the current request.
#[inline]
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
ConnectionInfo::get(self.head(), &*self.app_config())
}
/// Get a reference to the Path parameters. /// Get a reference to the Path parameters.
/// ///
/// Params is a container for url parameters. /// Params is a container for url parameters.
@@ -162,22 +181,28 @@ impl<P> ServiceRequest<P> {
/// Get an application data stored with `App::data()` method during /// Get an application data stored with `App::data()` method during
/// application configuration. /// application configuration.
pub fn app_data<T: 'static>(&self) -> Option<Data<T>> { pub fn app_data<T: 'static>(&self) -> Option<Data<T>> {
if let Some(st) = self.req.app_config().extensions().get::<Data<T>>() { if let Some(st) = self.req.0.app_data.get::<Data<T>>() {
Some(st.clone()) Some(st.clone())
} else { } else {
None None
} }
} }
#[doc(hidden)]
/// Set new app data container
pub fn set_data_container(&mut self, extensions: Rc<Extensions>) {
Rc::get_mut(&mut self.req.0).unwrap().app_data = extensions;
}
} }
impl<P> Resource<Url> for ServiceRequest<P> { impl Resource<Url> for ServiceRequest {
fn resource_path(&mut self) -> &mut Path<Url> { fn resource_path(&mut self) -> &mut Path<Url> {
self.match_info_mut() self.match_info_mut()
} }
} }
impl<P> HttpMessage for ServiceRequest<P> { impl HttpMessage for ServiceRequest {
type Stream = P; type Stream = PayloadStream;
#[inline] #[inline]
/// Returns Request's headers. /// Returns Request's headers.
@@ -203,7 +228,7 @@ impl<P> HttpMessage for ServiceRequest<P> {
} }
} }
impl<P> fmt::Debug for ServiceRequest<P> { impl fmt::Debug for ServiceRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!( writeln!(
f, f,
@@ -365,10 +390,136 @@ impl<B: MessageBody> fmt::Debug for ServiceResponse<B> {
} }
} }
pub struct WebService {
rdef: String,
name: Option<String>,
guards: Vec<Box<Guard>>,
}
impl WebService {
/// Create new `WebService` instance.
pub fn new(path: &str) -> Self {
WebService {
rdef: path.to_string(),
name: None,
guards: Vec::new(),
}
}
/// Set service name.
///
/// Name is used for url generation.
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
/// Add match guard to a web service.
///
/// ```rust
/// use actix_web::{web, guard, dev, App, HttpResponse};
///
/// fn index(req: dev::ServiceRequest) -> dev::ServiceResponse {
/// req.into_response(HttpResponse::Ok().finish())
/// }
///
/// fn main() {
/// let app = App::new()
/// .service(
/// web::service("/app")
/// .guard(guard::Header("content-type", "text/plain"))
/// .finish(index)
/// );
/// }
/// ```
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.guards.push(Box::new(guard));
self
}
/// Set a service factory implementation and generate web service.
pub fn finish<T, F>(self, service: F) -> impl HttpServiceFactory
where
F: IntoNewService<T>,
T: NewService<
Request = ServiceRequest,
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static,
{
WebServiceImpl {
srv: service.into_new_service(),
rdef: self.rdef,
name: self.name,
guards: self.guards,
}
}
}
struct WebServiceImpl<T> {
srv: T,
rdef: String,
name: Option<String>,
guards: Vec<Box<Guard>>,
}
impl<T> HttpServiceFactory for WebServiceImpl<T>
where
T: NewService<
Request = ServiceRequest,
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static,
{
fn register(mut self, config: &mut AppService) {
let guards = if self.guards.is_empty() {
None
} else {
Some(std::mem::replace(&mut self.guards, Vec::new()))
};
let mut rdef = if config.is_root() || !self.rdef.is_empty() {
ResourceDef::new(&insert_slash(&self.rdef))
} else {
ResourceDef::new(&self.rdef)
};
if let Some(ref name) = self.name {
*rdef.name_mut() = name.clone();
}
config.register_service(rdef, guards, self.srv, None)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::test::TestRequest; use super::*;
use crate::HttpResponse; use crate::test::{call_service, init_service, TestRequest};
use crate::{guard, http, web, App, HttpResponse};
#[test]
fn test_service() {
let mut srv = init_service(
App::new().service(web::service("/test").name("test").finish(
|req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()),
)),
);
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), http::StatusCode::OK);
let mut srv = init_service(
App::new().service(web::service("/test").guard(guard::Get()).finish(
|req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()),
)),
);
let req = TestRequest::with_uri("/test")
.method(http::Method::PUT)
.to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), http::StatusCode::NOT_FOUND);
}
#[test] #[test]
fn test_fmt_debug() { fn test_fmt_debug() {

View File

@@ -2,34 +2,50 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use actix_http::cookie::Cookie;
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
use actix_http::http::{HttpTryFrom, Method, StatusCode, Version}; use actix_http::http::{HttpTryFrom, Method, StatusCode, Uri, Version};
use actix_http::test::TestRequest as HttpTestRequest; use actix_http::test::TestRequest as HttpTestRequest;
use actix_http::{Extensions, PayloadStream, Request}; use actix_http::{cookie::Cookie, Extensions, Request};
use actix_router::{Path, ResourceDef, Url}; use actix_router::{Path, ResourceDef, Url};
use actix_rt::Runtime; use actix_rt::Runtime;
use actix_server_config::ServerConfig; use actix_server_config::ServerConfig;
use actix_service::{FnService, IntoNewService, NewService, Service}; use actix_service::{FnService, IntoNewService, NewService, Service};
use bytes::Bytes; use bytes::{Bytes, BytesMut};
use futures::future::{lazy, Future}; use futures::future::{lazy, ok, Future, IntoFuture};
use futures::Stream;
use serde::de::DeserializeOwned;
use serde_json;
pub use actix_http::test::TestBuffer; pub use actix_http::test::TestBuffer;
use crate::config::{AppConfig, AppConfigInner}; use crate::config::{AppConfig, AppConfigInner};
use crate::data::RouteData; use crate::data::Data;
use crate::dev::{Body, Payload}; use crate::dev::{Body, MessageBody, Payload};
use crate::request::HttpRequestPool; use crate::request::HttpRequestPool;
use crate::rmap::ResourceMap; use crate::rmap::ResourceMap;
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
use crate::{Error, HttpRequest, HttpResponse}; use crate::{Error, HttpRequest, HttpResponse};
thread_local! { thread_local! {
static RT: RefCell<Runtime> = { static RT: RefCell<Inner> = {
RefCell::new(Runtime::new().unwrap()) RefCell::new(Inner(Some(Runtime::new().unwrap())))
}; };
} }
struct Inner(Option<Runtime>);
impl Inner {
fn get_mut(&mut self) -> &mut Runtime {
self.0.as_mut().unwrap()
}
}
impl Drop for Inner {
fn drop(&mut self) {
std::mem::forget(self.0.take().unwrap())
}
}
/// Runs the provided future, blocking the current thread until the future /// Runs the provided future, blocking the current thread until the future
/// completes. /// completes.
/// ///
@@ -42,41 +58,59 @@ thread_local! {
/// This function panics on nested call. /// This function panics on nested call.
pub fn block_on<F>(f: F) -> Result<F::Item, F::Error> pub fn block_on<F>(f: F) -> Result<F::Item, F::Error>
where where
F: Future, F: IntoFuture,
{ {
RT.with(move |rt| rt.borrow_mut().block_on(f)) RT.with(move |rt| rt.borrow_mut().get_mut().block_on(f.into_future()))
} }
/// Runs the provided function, blocking the current thread until the resul
/// future completes.
///
/// This function can be used to synchronously block the current thread
/// until the provided `future` has resolved either successfully or with an
/// error. The result of the future is then returned from this function
/// call.
///
/// Note that this function is intended to be used only for testing purpose.
/// This function panics on nested call.
pub fn block_fn<F, R>(f: F) -> Result<R::Item, R::Error>
where
F: FnOnce() -> R,
R: IntoFuture,
{
RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(|| f())))
}
#[doc(hidden)]
/// Runs the provided function, with runtime enabled. /// Runs the provided function, with runtime enabled.
/// ///
/// Note that this function is intended to be used only for testing purpose. /// Note that this function is intended to be used only for testing purpose.
/// This function panics on nested call. /// This function panics on nested call.
pub fn run_on<F, R>(f: F) -> R pub fn run_on<F, R>(f: F) -> R
where where
F: Fn() -> R, F: FnOnce() -> R,
{ {
RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| Ok::<_, ()>(f())))) RT.with(move |rt| {
.unwrap() rt.borrow_mut()
.get_mut()
.block_on(lazy(|| Ok::<_, ()>(f())))
})
.unwrap()
} }
/// Create service that always responds with `HttpResponse::Ok()` /// Create service that always responds with `HttpResponse::Ok()`
pub fn ok_service() -> impl Service< pub fn ok_service(
Request = ServiceRequest<PayloadStream>, ) -> impl Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error>
Response = ServiceResponse<Body>, {
Error = Error,
> {
default_service(StatusCode::OK) default_service(StatusCode::OK)
} }
/// Create service that responds with response with specified status code /// Create service that responds with response with specified status code
pub fn default_service( pub fn default_service(
status_code: StatusCode, status_code: StatusCode,
) -> impl Service< ) -> impl Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error>
Request = ServiceRequest<PayloadStream>, {
Response = ServiceResponse<Body>, FnService::new(move |req: ServiceRequest| {
Error = Error,
> {
FnService::new(move |req: ServiceRequest<PayloadStream>| {
req.into_response(HttpResponse::build(status_code).finish()) req.into_response(HttpResponse::build(status_code).finish())
}) })
} }
@@ -84,11 +118,12 @@ pub fn default_service(
/// This method accepts application builder instance, and constructs /// This method accepts application builder instance, and constructs
/// service. /// service.
/// ///
/// ```rust,ignore /// ```rust
/// use actix_service::Service; /// use actix_service::Service;
/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// use actix_web::{test, web, App, HttpResponse, http::StatusCode};
/// ///
/// fn main() { /// #[test]
/// fn test_init_service() {
/// let mut app = test::init_service( /// let mut app = test::init_service(
/// App::new() /// App::new()
/// .service(web::resource("/test").to(|| HttpResponse::Ok())) /// .service(web::resource("/test").to(|| HttpResponse::Ok()))
@@ -116,16 +151,19 @@ where
S::InitError: std::fmt::Debug, S::InitError: std::fmt::Debug,
{ {
let cfg = ServerConfig::new("127.0.0.1:8080".parse().unwrap()); let cfg = ServerConfig::new("127.0.0.1:8080".parse().unwrap());
block_on(app.into_new_service().new_service(&cfg)).unwrap() let srv = app.into_new_service();
let fut = run_on(move || srv.new_service(&cfg));
block_on(fut).unwrap()
} }
/// Calls service and waits for response future completion. /// Calls service and waits for response future completion.
/// ///
/// ```rust,ignore /// ```rust
/// use actix_web::{test, App, HttpResponse, http::StatusCode}; /// use actix_web::{test, App, HttpResponse, http::StatusCode};
/// use actix_service::Service; /// use actix_service::Service;
/// ///
/// fn main() { /// #[test]
/// fn test_response() {
/// let mut app = test::init_service( /// let mut app = test::init_service(
/// App::new() /// App::new()
/// .service(web::resource("/test").to(|| HttpResponse::Ok())) /// .service(web::resource("/test").to(|| HttpResponse::Ok()))
@@ -135,16 +173,155 @@ where
/// let req = test::TestRequest::with_uri("/test").to_request(); /// let req = test::TestRequest::with_uri("/test").to_request();
/// ///
/// // Call application /// // Call application
/// let resp = test::call_success(&mut app, req); /// let resp = test::call_service(&mut app, req);
/// assert_eq!(resp.status(), StatusCode::OK); /// assert_eq!(resp.status(), StatusCode::OK);
/// } /// }
/// ``` /// ```
pub fn call_success<S, R, B, E>(app: &mut S, req: R) -> S::Response pub fn call_service<S, R, B, E>(app: &mut S, req: R) -> S::Response
where where
S: Service<Request = R, Response = ServiceResponse<B>, Error = E>, S: Service<Request = R, Response = ServiceResponse<B>, Error = E>,
E: std::fmt::Debug, E: std::fmt::Debug,
{ {
block_on(app.call(req)).unwrap() block_on(run_on(move || app.call(req))).unwrap()
}
/// Helper function that returns a response body of a TestRequest
/// This function blocks the current thread until futures complete.
///
/// ```rust
/// use actix_web::{test, web, App, HttpResponse, http::header};
/// use bytes::Bytes;
///
/// #[test]
/// fn test_index() {
/// let mut app = test::init_service(
/// App::new().service(
/// web::resource("/index.html")
/// .route(web::post().to(
/// || HttpResponse::Ok().body("welcome!")))));
///
/// let req = test::TestRequest::post()
/// .uri("/index.html")
/// .header(header::CONTENT_TYPE, "application/json")
/// .to_request();
///
/// let result = test::read_response(&mut app, req);
/// assert_eq!(result, Bytes::from_static(b"welcome!"));
/// }
/// ```
pub fn read_response<S, B>(app: &mut S, req: Request) -> Bytes
where
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody,
{
block_on(run_on(move || {
app.call(req).and_then(|mut resp: ServiceResponse<B>| {
resp.take_body()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, Error>(body)
})
.map(|body: BytesMut| body.freeze())
})
}))
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
}
/// Helper function that returns a response body of a ServiceResponse.
/// This function blocks the current thread until futures complete.
///
/// ```rust
/// use actix_web::{test, web, App, HttpResponse, http::header};
/// use bytes::Bytes;
///
/// #[test]
/// fn test_index() {
/// let mut app = test::init_service(
/// App::new().service(
/// web::resource("/index.html")
/// .route(web::post().to(
/// || HttpResponse::Ok().body("welcome!")))));
///
/// let req = test::TestRequest::post()
/// .uri("/index.html")
/// .header(header::CONTENT_TYPE, "application/json")
/// .to_request();
///
/// let resp = call_service(&mut srv, req);
/// let result = test::read_body(resp);
/// assert_eq!(result, Bytes::from_static(b"welcome!"));
/// }
/// ```
pub fn read_body<B>(mut res: ServiceResponse<B>) -> Bytes
where
B: MessageBody,
{
block_on(run_on(move || {
res.take_body()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, Error>(body)
})
.map(|body: BytesMut| body.freeze())
}))
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
}
/// Helper function that returns a deserialized response body of a TestRequest
/// This function blocks the current thread until futures complete.
///
/// ```rust
/// use actix_web::{App, test, web, HttpResponse, http::header};
/// use serde::{Serialize, Deserialize};
///
/// #[derive(Serialize, Deserialize)]
/// pub struct Person {
/// id: String,
/// name: String
/// }
///
/// #[test]
/// fn test_add_person() {
/// let mut app = test::init_service(
/// App::new().service(
/// web::resource("/people")
/// .route(web::post().to(|person: web::Json<Person>| {
/// HttpResponse::Ok()
/// .json(person.into_inner())})
/// )));
///
/// let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
///
/// let req = test::TestRequest::post()
/// .uri("/people")
/// .header(header::CONTENT_TYPE, "application/json")
/// .set_payload(payload)
/// .to_request();
///
/// let result: Person = test::read_response_json(&mut app, req);
/// }
/// ```
pub fn read_response_json<S, B, T>(app: &mut S, req: Request) -> T
where
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody,
T: DeserializeOwned,
{
block_on(run_on(move || {
app.call(req).and_then(|mut resp: ServiceResponse<B>| {
resp.take_body()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, Error>(body)
})
.and_then(|body: BytesMut| {
ok(serde_json::from_slice(&body).unwrap_or_else(|_| {
panic!("read_response_json failed during deserialization")
}))
})
})
}))
.unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap"))
} }
/// Test `Request` builder. /// Test `Request` builder.
@@ -152,12 +329,11 @@ where
/// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. /// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern.
/// You can generate various types of request via TestRequest's methods: /// You can generate various types of request via TestRequest's methods:
/// * `TestRequest::to_request` creates `actix_http::Request` instance. /// * `TestRequest::to_request` creates `actix_http::Request` instance.
/// * `TestRequest::to_service` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. /// * `TestRequest::to_srv_request` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters.
/// * `TestRequest::to_from` creates `ServiceFromRequest` instance, which is used for testing extractors. /// * `TestRequest::to_srv_response` creates `ServiceResponse` instance.
/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. /// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers.
/// ///
/// ```rust,ignore /// ```rust
/// # use futures::IntoFuture;
/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage};
/// use actix_web::http::{header, StatusCode}; /// use actix_web::http::{header, StatusCode};
/// ///
@@ -169,15 +345,16 @@ where
/// } /// }
/// } /// }
/// ///
/// fn main() { /// #[test]
/// fn test_index() {
/// let req = test::TestRequest::with_header("content-type", "text/plain") /// let req = test::TestRequest::with_header("content-type", "text/plain")
/// .to_http_request(); /// .to_http_request();
/// ///
/// let resp = test::block_on(index(req).into_future()).unwrap(); /// let resp = test::block_on(index(req)).unwrap();
/// assert_eq!(resp.status(), StatusCode::OK); /// assert_eq!(resp.status(), StatusCode::OK);
/// ///
/// let req = test::TestRequest::default().to_http_request(); /// let req = test::TestRequest::default().to_http_request();
/// let resp = test::block_on(index(req).into_future()).unwrap(); /// let resp = test::block_on(index(req)).unwrap();
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
/// } /// }
/// ``` /// ```
@@ -185,7 +362,8 @@ pub struct TestRequest {
req: HttpTestRequest, req: HttpTestRequest,
rmap: ResourceMap, rmap: ResourceMap,
config: AppConfigInner, config: AppConfigInner,
route_data: Extensions, path: Path<Url>,
app_data: Extensions,
} }
impl Default for TestRequest { impl Default for TestRequest {
@@ -194,7 +372,8 @@ impl Default for TestRequest {
req: HttpTestRequest::default(), req: HttpTestRequest::default(),
rmap: ResourceMap::new(ResourceDef::new("")), rmap: ResourceMap::new(ResourceDef::new("")),
config: AppConfigInner::default(), config: AppConfigInner::default(),
route_data: Extensions::new(), path: Path::new(Url::new(Uri::default())),
app_data: Extensions::new(),
} }
} }
} }
@@ -230,6 +409,21 @@ impl TestRequest {
TestRequest::default().method(Method::POST) TestRequest::default().method(Method::POST)
} }
/// Create TestRequest and set method to `Method::PUT`
pub fn put() -> TestRequest {
TestRequest::default().method(Method::PUT)
}
/// Create TestRequest and set method to `Method::PATCH`
pub fn patch() -> TestRequest {
TestRequest::default().method(Method::PATCH)
}
/// Create TestRequest and set method to `Method::DELETE`
pub fn delete() -> TestRequest {
TestRequest::default().method(Method::DELETE)
}
/// Set HTTP version of this request /// Set HTTP version of this request
pub fn version(mut self, ver: Version) -> Self { pub fn version(mut self, ver: Version) -> Self {
self.req.version(ver); self.req.version(ver);
@@ -270,6 +464,12 @@ impl TestRequest {
self 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
}
/// Set request payload /// Set request payload
pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self { pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self {
self.req.set_payload(data); self.req.set_payload(data);
@@ -278,15 +478,8 @@ impl TestRequest {
/// Set application data. This is equivalent of `App::data()` method /// Set application data. This is equivalent of `App::data()` method
/// for testing purpose. /// for testing purpose.
pub fn app_data<T: 'static>(self, data: T) -> Self { pub fn data<T: 'static>(mut self, data: T) -> Self {
self.config.extensions.borrow_mut().insert(data); self.app_data.insert(Data::new(data));
self
}
/// Set route data. This is equivalent of `Route::data()` method
/// for testing purpose.
pub fn route_data<T: 'static>(mut self, data: T) -> Self {
self.route_data.insert(RouteData::new(data));
self self
} }
@@ -298,19 +491,21 @@ impl TestRequest {
} }
/// Complete request creation and generate `Request` instance /// Complete request creation and generate `Request` instance
pub fn to_request(mut self) -> Request<PayloadStream> { pub fn to_request(mut self) -> Request {
self.req.finish() self.req.finish()
} }
/// Complete request creation and generate `ServiceRequest` instance /// Complete request creation and generate `ServiceRequest` instance
pub fn to_srv_request(mut self) -> ServiceRequest<PayloadStream> { pub fn to_srv_request(mut self) -> ServiceRequest {
let (head, payload) = self.req.finish().into_parts(); let (head, payload) = self.req.finish().into_parts();
self.path.get_mut().update(&head.uri);
let req = HttpRequest::new( let req = HttpRequest::new(
Path::new(Url::new(head.uri.clone())), self.path,
head, head,
Rc::new(self.rmap), Rc::new(self.rmap),
AppConfig::new(self.config), AppConfig::new(self.config),
Rc::new(self.app_data),
HttpRequestPool::create(), HttpRequestPool::create(),
); );
@@ -325,47 +520,159 @@ impl TestRequest {
/// Complete request creation and generate `HttpRequest` instance /// Complete request creation and generate `HttpRequest` instance
pub fn to_http_request(mut self) -> HttpRequest { pub fn to_http_request(mut self) -> HttpRequest {
let (head, _) = self.req.finish().into_parts(); let (head, _) = self.req.finish().into_parts();
self.path.get_mut().update(&head.uri);
let mut req = HttpRequest::new( HttpRequest::new(
Path::new(Url::new(head.uri.clone())), self.path,
head, head,
Rc::new(self.rmap), Rc::new(self.rmap),
AppConfig::new(self.config), AppConfig::new(self.config),
Rc::new(self.app_data),
HttpRequestPool::create(), HttpRequestPool::create(),
); )
req.set_route_data(Some(Rc::new(self.route_data)));
req
} }
/// Complete request creation and generate `HttpRequest` and `Payload` instances /// Complete request creation and generate `HttpRequest` and `Payload` instances
pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { pub fn to_http_parts(mut self) -> (HttpRequest, Payload) {
let (head, payload) = self.req.finish().into_parts(); let (head, payload) = self.req.finish().into_parts();
self.path.get_mut().update(&head.uri);
let mut req = HttpRequest::new( let req = HttpRequest::new(
Path::new(Url::new(head.uri.clone())), self.path,
head, head,
Rc::new(self.rmap), Rc::new(self.rmap),
AppConfig::new(self.config), AppConfig::new(self.config),
Rc::new(self.app_data),
HttpRequestPool::create(), HttpRequestPool::create(),
); );
req.set_route_data(Some(Rc::new(self.route_data)));
(req, payload) (req, payload)
} }
}
/// Runs the provided future, blocking the current thread until the future #[cfg(test)]
/// completes. mod tests {
/// use serde::{Deserialize, Serialize};
/// This function can be used to synchronously block the current thread use std::time::SystemTime;
/// until the provided `future` has resolved either successfully or with an
/// error. The result of the future is then returned from this function use super::*;
/// call. use crate::{http::header, web, App, HttpResponse};
///
/// Note that this function is intended to be used only for testing purpose. #[test]
/// This function panics on nested call. fn test_basics() {
pub fn block_on<F>(f: F) -> Result<F::Item, F::Error> let req = TestRequest::with_hdr(header::ContentType::json())
where .version(Version::HTTP_2)
F: Future, .set(header::Date(SystemTime::now().into()))
{ .param("test", "123")
block_on(f) .data(10u32)
.to_http_request();
assert!(req.headers().contains_key(header::CONTENT_TYPE));
assert!(req.headers().contains_key(header::DATE));
assert_eq!(&req.match_info()["test"], "123");
assert_eq!(req.version(), Version::HTTP_2);
let data = req.get_app_data::<u32>().unwrap();
assert!(req.get_app_data::<u64>().is_none());
assert_eq!(*data, 10);
assert_eq!(*data.get_ref(), 10);
assert!(req.app_data::<u64>().is_none());
let data = req.app_data::<u32>().unwrap();
assert_eq!(*data, 10);
}
#[test]
fn test_request_methods() {
let mut app = init_service(
App::new().service(
web::resource("/index.html")
.route(web::put().to(|| HttpResponse::Ok().body("put!")))
.route(web::patch().to(|| HttpResponse::Ok().body("patch!")))
.route(web::delete().to(|| HttpResponse::Ok().body("delete!"))),
),
);
let put_req = TestRequest::put()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.to_request();
let result = read_response(&mut app, put_req);
assert_eq!(result, Bytes::from_static(b"put!"));
let patch_req = TestRequest::patch()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.to_request();
let result = read_response(&mut app, patch_req);
assert_eq!(result, Bytes::from_static(b"patch!"));
let delete_req = TestRequest::delete().uri("/index.html").to_request();
let result = read_response(&mut app, delete_req);
assert_eq!(result, Bytes::from_static(b"delete!"));
}
#[test]
fn test_response() {
let mut app = init_service(
App::new().service(
web::resource("/index.html")
.route(web::post().to(|| HttpResponse::Ok().body("welcome!"))),
),
);
let req = TestRequest::post()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.to_request();
let result = read_response(&mut app, req);
assert_eq!(result, Bytes::from_static(b"welcome!"));
}
#[derive(Serialize, Deserialize)]
pub struct Person {
id: String,
name: String,
}
#[test]
fn test_response_json() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Json<Person>| {
HttpResponse::Ok().json(person.into_inner())
}),
)));
let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
let req = TestRequest::post()
.uri("/people")
.header(header::CONTENT_TYPE, "application/json")
.set_payload(payload)
.to_request();
let result: Person = read_response_json(&mut app, req);
assert_eq!(&result.id, "12345");
}
#[test]
fn test_async_with_block() {
fn async_with_block() -> impl Future<Item = HttpResponse, Error = Error> {
web::block(move || Some(4).ok_or("wrong")).then(|res| match res {
Ok(value) => HttpResponse::Ok()
.content_type("text/plain")
.body(format!("Async with block value: {}", value)),
Err(_) => panic!("Unexpected"),
})
}
let mut app = init_service(
App::new().service(web::resource("/index.html").to_async(async_with_block)),
);
let req = TestRequest::post().uri("/index.html").to_request();
let res = block_fn(|| app.call(req)).unwrap();
assert!(res.status().is_success());
} }
} }

View File

@@ -3,15 +3,15 @@
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, ops}; use std::{fmt, ops};
use actix_http::error::{Error, PayloadError}; use actix_http::{Error, HttpMessage, Payload};
use actix_http::{HttpMessage, Payload}; use bytes::BytesMut;
use bytes::{Bytes, BytesMut};
use encoding::all::UTF_8; use encoding::all::UTF_8;
use encoding::types::{DecoderTrap, Encoding}; use encoding::types::{DecoderTrap, Encoding};
use encoding::EncodingRef; use encoding::EncodingRef;
use futures::{Future, Poll, Stream}; use futures::{Future, Poll, Stream};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use crate::dev::Decompress;
use crate::error::UrlencodedError; use crate::error::UrlencodedError;
use crate::extract::FromRequest; use crate::extract::FromRequest;
use crate::http::header::CONTENT_LENGTH; use crate::http::header::CONTENT_LENGTH;
@@ -69,19 +69,19 @@ impl<T> ops::DerefMut for Form<T> {
} }
} }
impl<T, P> FromRequest<P> for Form<T> impl<T> FromRequest for Form<T>
where where
T: DeserializeOwned + 'static, T: DeserializeOwned + 'static,
P: Stream<Item = Bytes, Error = crate::error::PayloadError> + 'static,
{ {
type Config = FormConfig;
type Error = Error; type Error = Error;
type Future = Box<Future<Item = Self, Error = Error>>; type Future = Box<Future<Item = Self, Error = Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let req2 = req.clone(); let req2 = req.clone();
let (limit, err) = req let (limit, err) = req
.route_data::<FormConfig>() .app_data::<FormConfig>()
.map(|c| (c.limit, c.ehandler.clone())) .map(|c| (c.limit, c.ehandler.clone()))
.unwrap_or((16384, None)); .unwrap_or((16384, None));
@@ -116,7 +116,7 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
/// ///
/// ```rust /// ```rust
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App, Result}; /// use actix_web::{web, App, FromRequest, Result};
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct FormData { /// struct FormData {
@@ -132,10 +132,11 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
/// fn main() { /// fn main() {
/// let app = App::new().service( /// let app = App::new().service(
/// web::resource("/index.html") /// web::resource("/index.html")
/// .route(web::get() /// // change `Form` extractor configuration
/// // change `Form` extractor configuration /// .data(
/// .data(web::FormConfig::default().limit(4097)) /// web::Form::<FormData>::configure(|cfg| cfg.limit(4097))
/// .to(index)) /// )
/// .route(web::get().to(index))
/// ); /// );
/// } /// }
/// ``` /// ```
@@ -182,8 +183,8 @@ impl Default for FormConfig {
/// * content type is not `application/x-www-form-urlencoded` /// * content type is not `application/x-www-form-urlencoded`
/// * content-length is greater than 32k /// * content-length is greater than 32k
/// ///
pub struct UrlEncoded<P, U> { pub struct UrlEncoded<U> {
stream: Payload<P>, stream: Option<Decompress<Payload>>,
limit: usize, limit: usize,
length: Option<usize>, length: Option<usize>,
encoding: EncodingRef, encoding: EncodingRef,
@@ -191,12 +192,9 @@ pub struct UrlEncoded<P, U> {
fut: Option<Box<Future<Item = U, Error = UrlencodedError>>>, fut: Option<Box<Future<Item = U, Error = UrlencodedError>>>,
} }
impl<P, U> UrlEncoded<P, U> impl<U> UrlEncoded<U> {
where
P: Stream<Item = Bytes, Error = PayloadError>,
{
/// Create a new future to URL encode a request /// Create a new future to URL encode a request
pub fn new(req: &HttpRequest, payload: &mut Payload<P>) -> UrlEncoded<P, U> { pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded<U> {
// check content type // check content type
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
return Self::err(UrlencodedError::ContentType); return Self::err(UrlencodedError::ContentType);
@@ -219,9 +217,10 @@ where
} }
}; };
let payload = Decompress::from_headers(payload.take(), req.headers());
UrlEncoded { UrlEncoded {
encoding, encoding,
stream: payload.take(), stream: Some(payload),
limit: 32_768, limit: 32_768,
length: len, length: len,
fut: None, fut: None,
@@ -231,7 +230,7 @@ where
fn err(e: UrlencodedError) -> Self { fn err(e: UrlencodedError) -> Self {
UrlEncoded { UrlEncoded {
stream: Payload::None, stream: None,
limit: 32_768, limit: 32_768,
fut: None, fut: None,
err: Some(e), err: Some(e),
@@ -247,9 +246,8 @@ where
} }
} }
impl<P, U> Future for UrlEncoded<P, U> impl<U> Future for UrlEncoded<U>
where where
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
U: DeserializeOwned + 'static, U: DeserializeOwned + 'static,
{ {
type Item = U; type Item = U;
@@ -274,7 +272,10 @@ where
// future // future
let encoding = self.encoding; let encoding = self.encoding;
let fut = std::mem::replace(&mut self.stream, Payload::None) let fut = self
.stream
.take()
.unwrap()
.from_err() .from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| { .fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit { if (body.len() + chunk.len()) > limit {
@@ -329,10 +330,6 @@ mod tests {
fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { fn eq(err: UrlencodedError, other: UrlencodedError) -> bool {
match err { match err {
UrlencodedError::Chunked => match other {
UrlencodedError::Chunked => true,
_ => false,
},
UrlencodedError::Overflow => match other { UrlencodedError::Overflow => match other {
UrlencodedError::Overflow => true, UrlencodedError::Overflow => true,
_ => false, _ => false,
@@ -355,20 +352,20 @@ mod tests {
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(CONTENT_LENGTH, "xxxx") .header(CONTENT_LENGTH, "xxxx")
.to_http_parts(); .to_http_parts();
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)); let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength)); assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength));
let (req, mut pl) = let (req, mut pl) =
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(CONTENT_LENGTH, "1000000") .header(CONTENT_LENGTH, "1000000")
.to_http_parts(); .to_http_parts();
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)); let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
assert!(eq(info.err().unwrap(), UrlencodedError::Overflow)); assert!(eq(info.err().unwrap(), UrlencodedError::Overflow));
let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain") let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
.header(CONTENT_LENGTH, "10") .header(CONTENT_LENGTH, "10")
.to_http_parts(); .to_http_parts();
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)); let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); assert!(eq(info.err().unwrap(), UrlencodedError::ContentType));
} }
@@ -380,7 +377,7 @@ mod tests {
.set_payload(Bytes::from_static(b"hello=world")) .set_payload(Bytes::from_static(b"hello=world"))
.to_http_parts(); .to_http_parts();
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)).unwrap(); let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl)).unwrap();
assert_eq!( assert_eq!(
info, info,
Info { Info {
@@ -396,7 +393,7 @@ mod tests {
.set_payload(Bytes::from_static(b"hello=world")) .set_payload(Bytes::from_static(b"hello=world"))
.to_http_parts(); .to_http_parts();
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)).unwrap(); let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl)).unwrap();
assert_eq!( assert_eq!(
info, info,
Info { Info {

View File

@@ -3,7 +3,7 @@
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, ops}; use std::{fmt, ops};
use bytes::{Bytes, BytesMut}; use bytes::BytesMut;
use futures::{Future, Poll, Stream}; use futures::{Future, Poll, Stream};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
@@ -12,7 +12,8 @@ use serde_json;
use actix_http::http::{header::CONTENT_LENGTH, StatusCode}; use actix_http::http::{header::CONTENT_LENGTH, StatusCode};
use actix_http::{HttpMessage, Payload, Response}; use actix_http::{HttpMessage, Payload, Response};
use crate::error::{Error, JsonPayloadError, PayloadError}; use crate::dev::Decompress;
use crate::error::{Error, JsonPayloadError};
use crate::extract::FromRequest; use crate::extract::FromRequest;
use crate::request::HttpRequest; use crate::request::HttpRequest;
use crate::responder::Responder; use crate::responder::Responder;
@@ -163,19 +164,19 @@ impl<T: Serialize> Responder for Json<T> {
/// ); /// );
/// } /// }
/// ``` /// ```
impl<T, P> FromRequest<P> for Json<T> impl<T> FromRequest for Json<T>
where where
T: DeserializeOwned + 'static, T: DeserializeOwned + 'static,
P: Stream<Item = Bytes, Error = crate::error::PayloadError> + 'static,
{ {
type Config = JsonConfig;
type Error = Error; type Error = Error;
type Future = Box<Future<Item = Self, Error = Error>>; type Future = Box<Future<Item = Self, Error = Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let req2 = req.clone(); let req2 = req.clone();
let (limit, err) = req let (limit, err) = req
.route_data::<JsonConfig>() .app_data::<JsonConfig>()
.map(|c| (c.limit, c.ehandler.clone())) .map(|c| (c.limit, c.ehandler.clone()))
.unwrap_or((32768, None)); .unwrap_or((32768, None));
@@ -205,7 +206,7 @@ where
/// ///
/// ```rust /// ```rust
/// #[macro_use] extern crate serde_derive; /// #[macro_use] extern crate serde_derive;
/// use actix_web::{error, web, App, HttpResponse}; /// use actix_web::{error, web, App, FromRequest, HttpResponse};
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// struct Info {
@@ -219,15 +220,16 @@ where
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().service( /// let app = App::new().service(
/// web::resource("/index.html").route( /// web::resource("/index.html").data(
/// web::post().data( /// // change json extractor configuration
/// // change json extractor configuration /// web::Json::<Info>::configure(|cfg| {
/// web::JsonConfig::default().limit(4096) /// cfg.limit(4096)
/// .error_handler(|err, req| { // <- create custom error response /// .error_handler(|err, req| { // <- create custom error response
/// error::InternalError::from_response( /// error::InternalError::from_response(
/// err, HttpResponse::Conflict().finish()).into() /// err, HttpResponse::Conflict().finish()).into()
/// })) /// })
/// .to(index)) /// }))
/// .route(web::post().to(index))
/// ); /// );
/// } /// }
/// ``` /// ```
@@ -270,21 +272,20 @@ impl Default for JsonConfig {
/// ///
/// * content type is not `application/json` /// * content type is not `application/json`
/// * content length is greater than 256k /// * content length is greater than 256k
pub struct JsonBody<P, U> { pub struct JsonBody<U> {
limit: usize, limit: usize,
length: Option<usize>, length: Option<usize>,
stream: Payload<P>, stream: Option<Decompress<Payload>>,
err: Option<JsonPayloadError>, err: Option<JsonPayloadError>,
fut: Option<Box<Future<Item = U, Error = JsonPayloadError>>>, fut: Option<Box<Future<Item = U, Error = JsonPayloadError>>>,
} }
impl<P, U> JsonBody<P, U> impl<U> JsonBody<U>
where where
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
U: DeserializeOwned + 'static, U: DeserializeOwned + 'static,
{ {
/// Create `JsonBody` for request. /// Create `JsonBody` for request.
pub fn new(req: &HttpRequest, payload: &mut Payload<P>) -> Self { pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self {
// check content-type // check content-type
let json = if let Ok(Some(mime)) = req.mime_type() { let json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
@@ -295,7 +296,7 @@ where
return JsonBody { return JsonBody {
limit: 262_144, limit: 262_144,
length: None, length: None,
stream: Payload::None, stream: None,
fut: None, fut: None,
err: Some(JsonPayloadError::ContentType), err: Some(JsonPayloadError::ContentType),
}; };
@@ -309,11 +310,12 @@ where
} }
} }
} }
let payload = Decompress::from_headers(payload.take(), req.headers());
JsonBody { JsonBody {
limit: 262_144, limit: 262_144,
length: len, length: len,
stream: payload.take(), stream: Some(payload),
fut: None, fut: None,
err: None, err: None,
} }
@@ -326,9 +328,8 @@ where
} }
} }
impl<P, U> Future for JsonBody<P, U> impl<U> Future for JsonBody<U>
where where
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
U: DeserializeOwned + 'static, U: DeserializeOwned + 'static,
{ {
type Item = U; type Item = U;
@@ -350,7 +351,10 @@ where
} }
} }
let fut = std::mem::replace(&mut self.stream, Payload::None) let fut = self
.stream
.take()
.unwrap()
.from_err() .from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| { .fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit { if (body.len() + chunk.len()) > limit {
@@ -426,7 +430,7 @@ mod tests {
header::HeaderValue::from_static("16"), header::HeaderValue::from_static("16"),
) )
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.route_data(JsonConfig::default().limit(10).error_handler(|err, _| { .data(JsonConfig::default().limit(10).error_handler(|err, _| {
let msg = MyObject { let msg = MyObject {
name: "invalid request".to_string(), name: "invalid request".to_string(),
}; };
@@ -478,7 +482,7 @@ mod tests {
header::HeaderValue::from_static("16"), header::HeaderValue::from_static("16"),
) )
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.route_data(JsonConfig::default().limit(10)) .data(JsonConfig::default().limit(10))
.to_http_parts(); .to_http_parts();
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl)); let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
@@ -495,7 +499,7 @@ mod tests {
header::HeaderValue::from_static("16"), header::HeaderValue::from_static("16"),
) )
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.route_data( .data(
JsonConfig::default() JsonConfig::default()
.limit(10) .limit(10)
.error_handler(|_, _| JsonPayloadError::ContentType.into()), .error_handler(|_, _| JsonPayloadError::ContentType.into()),
@@ -508,7 +512,7 @@ mod tests {
#[test] #[test]
fn test_json_body() { fn test_json_body() {
let (req, mut pl) = TestRequest::default().to_http_parts(); let (req, mut pl) = TestRequest::default().to_http_parts();
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl)); let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl));
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default() let (req, mut pl) = TestRequest::default()
@@ -517,7 +521,7 @@ mod tests {
header::HeaderValue::from_static("application/text"), header::HeaderValue::from_static("application/text"),
) )
.to_http_parts(); .to_http_parts();
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl)); let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl));
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let (req, mut pl) = TestRequest::default() let (req, mut pl) = TestRequest::default()
@@ -531,7 +535,7 @@ mod tests {
) )
.to_http_parts(); .to_http_parts();
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl).limit(100)); let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl).limit(100));
assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow));
let (req, mut pl) = TestRequest::default() let (req, mut pl) = TestRequest::default()
@@ -546,7 +550,7 @@ mod tests {
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.to_http_parts(); .to_http_parts();
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl)); let json = block_on(JsonBody::<MyObject>::new(&req, &mut pl));
assert_eq!( assert_eq!(
json.ok().unwrap(), json.ok().unwrap(),
MyObject { MyObject {

View File

@@ -152,15 +152,16 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
/// ); /// );
/// } /// }
/// ``` /// ```
impl<T, P> FromRequest<P> for Path<T> impl<T> FromRequest for Path<T>
where where
T: de::DeserializeOwned, T: de::DeserializeOwned,
{ {
type Config = ();
type Error = Error; type Error = Error;
type Future = Result<Self, Error>; type Future = Result<Self, Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
.map(|inner| Path { inner }) .map(|inner| Path { inner })
.map_err(ErrorNotFound) .map_err(ErrorNotFound)
@@ -170,10 +171,25 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_router::ResourceDef; use actix_router::ResourceDef;
use derive_more::Display;
use serde_derive::Deserialize;
use super::*; use super::*;
use crate::test::{block_on, TestRequest}; use crate::test::{block_on, TestRequest};
#[derive(Deserialize, Debug, Display)]
#[display(fmt = "MyStruct({}, {})", key, value)]
struct MyStruct {
key: String,
value: String,
}
#[derive(Deserialize)]
struct Test2 {
key: String,
value: u32,
}
#[test] #[test]
fn test_extract_path_single() { fn test_extract_path_single() {
let resource = ResourceDef::new("/{value}/"); let resource = ResourceDef::new("/{value}/");
@@ -183,6 +199,7 @@ mod tests {
let (req, mut pl) = req.into_parts(); let (req, mut pl) = req.into_parts();
assert_eq!(*Path::<i8>::from_request(&req, &mut pl).unwrap(), 32); assert_eq!(*Path::<i8>::from_request(&req, &mut pl).unwrap(), 32);
assert!(Path::<MyStruct>::from_request(&req, &mut pl).is_err());
} }
#[test] #[test]
@@ -212,4 +229,46 @@ mod tests {
let () = <()>::from_request(&req, &mut pl).unwrap(); let () = <()>::from_request(&req, &mut pl).unwrap();
} }
#[test]
fn test_request_extract() {
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let (req, mut pl) = req.into_parts();
let mut s = Path::<MyStruct>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.key, "name");
assert_eq!(s.value, "user1");
s.value = "user2".to_string();
assert_eq!(s.value, "user2");
assert_eq!(
format!("{}, {:?}", s, s),
"MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }"
);
let s = s.into_inner();
assert_eq!(s.value, "user2");
let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, "user1");
let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
let resource = ResourceDef::new("/{key}/{value}/");
resource.match_path(req.match_info_mut());
let (req, mut pl) = req.into_parts();
let s = Path::<Test2>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32);
let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap();
assert_eq!(s.0, "name");
assert_eq!(s.1, 32);
let res = Path::<Vec<String>>::from_request(&req, &mut pl).unwrap();
assert_eq!(res[0], "name".to_owned());
assert_eq!(res[1], "32".to_owned());
}
} }

View File

@@ -44,7 +44,7 @@ use crate::request::HttpRequest;
/// ); /// );
/// } /// }
/// ``` /// ```
pub struct Payload(crate::dev::Payload<Box<Stream<Item = Bytes, Error = PayloadError>>>); pub struct Payload(crate::dev::Payload);
impl Stream for Payload { impl Stream for Payload {
type Item = Bytes; type Item = Bytes;
@@ -85,26 +85,14 @@ impl Stream for Payload {
/// ); /// );
/// } /// }
/// ``` /// ```
impl<P> FromRequest<P> for Payload impl FromRequest for Payload {
where type Config = PayloadConfig;
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Error = Error; type Error = Error;
type Future = Result<Payload, Error>; type Future = Result<Payload, Error>;
#[inline] #[inline]
fn from_request(_: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future { fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let pl = match payload.take() { Ok(Payload(payload.take()))
crate::dev::Payload::Stream(s) => {
let pl: Box<dyn Stream<Item = Bytes, Error = PayloadError>> =
Box::new(s);
crate::dev::Payload::Stream(pl)
}
crate::dev::Payload::None => crate::dev::Payload::None,
crate::dev::Payload::H1(pl) => crate::dev::Payload::H1(pl),
crate::dev::Payload::H2(pl) => crate::dev::Payload::H2(pl),
};
Ok(Payload(pl))
} }
} }
@@ -133,18 +121,16 @@ where
/// ); /// );
/// } /// }
/// ``` /// ```
impl<P> FromRequest<P> for Bytes impl FromRequest for Bytes {
where type Config = PayloadConfig;
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Error = Error; type Error = Error;
type Future = type Future =
Either<Box<Future<Item = Bytes, Error = Error>>, FutureResult<Bytes, Error>>; Either<Box<Future<Item = Bytes, Error = Error>>, FutureResult<Bytes, Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let mut tmp; let mut tmp;
let cfg = if let Some(cfg) = req.route_data::<PayloadConfig>() { let cfg = if let Some(cfg) = req.app_data::<PayloadConfig>() {
cfg cfg
} else { } else {
tmp = PayloadConfig::default(); tmp = PayloadConfig::default();
@@ -172,7 +158,7 @@ where
/// ## Example /// ## Example
/// ///
/// ```rust /// ```rust
/// use actix_web::{web, App}; /// use actix_web::{web, App, FromRequest};
/// ///
/// /// extract text data from request /// /// extract text data from request
/// fn index(text: String) -> String { /// fn index(text: String) -> String {
@@ -181,25 +167,24 @@ where
/// ///
/// fn main() { /// fn main() {
/// let app = App::new().service( /// let app = App::new().service(
/// web::resource("/index.html").route( /// web::resource("/index.html")
/// web::get() /// .data(String::configure(|cfg| { // <- limit size of the payload
/// .data(web::PayloadConfig::new(4096)) // <- limit size of the payload /// cfg.limit(4096)
/// .to(index)) // <- register handler with extractor params /// }))
/// .route(web::get().to(index)) // <- register handler with extractor params
/// ); /// );
/// } /// }
/// ``` /// ```
impl<P> FromRequest<P> for String impl FromRequest for String {
where type Config = PayloadConfig;
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Error = Error; type Error = Error;
type Future = type Future =
Either<Box<Future<Item = String, Error = Error>>, FutureResult<String, Error>>; Either<Box<Future<Item = String, Error = Error>>, FutureResult<String, Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let mut tmp; let mut tmp;
let cfg = if let Some(cfg) = req.route_data::<PayloadConfig>() { let cfg = if let Some(cfg) = req.app_data::<PayloadConfig>() {
cfg cfg
} else { } else {
tmp = PayloadConfig::default(); tmp = PayloadConfig::default();
@@ -247,7 +232,9 @@ pub struct PayloadConfig {
impl PayloadConfig { impl PayloadConfig {
/// Create `PayloadConfig` instance and set max size of payload. /// Create `PayloadConfig` instance and set max size of payload.
pub fn new(limit: usize) -> Self { pub fn new(limit: usize) -> Self {
Self::default().limit(limit) let mut cfg = Self::default();
cfg.limit = limit;
cfg
} }
/// Change max size of payload. By default max size is 256Kb /// Change max size of payload. By default max size is 256Kb
@@ -300,20 +287,17 @@ impl Default for PayloadConfig {
/// By default only 256Kb payload reads to a memory, then /// By default only 256Kb payload reads to a memory, then
/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()` /// `PayloadError::Overflow` get returned. Use `MessageBody::limit()`
/// method to change upper limit. /// method to change upper limit.
pub struct HttpMessageBody<P> { pub struct HttpMessageBody {
limit: usize, limit: usize,
length: Option<usize>, length: Option<usize>,
stream: dev::Payload<P>, stream: Option<dev::Decompress<dev::Payload>>,
err: Option<PayloadError>, err: Option<PayloadError>,
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>, fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
} }
impl<P> HttpMessageBody<P> impl HttpMessageBody {
where
P: Stream<Item = Bytes, Error = PayloadError>,
{
/// Create `MessageBody` for request. /// Create `MessageBody` for request.
pub fn new(req: &HttpRequest, payload: &mut dev::Payload<P>) -> HttpMessageBody<P> { pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody {
let mut len = None; let mut len = None;
if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() { if let Ok(s) = l.to_str() {
@@ -328,7 +312,7 @@ where
} }
HttpMessageBody { HttpMessageBody {
stream: payload.take(), stream: Some(dev::Decompress::from_headers(payload.take(), req.headers())),
limit: 262_144, limit: 262_144,
length: len, length: len,
fut: None, fut: None,
@@ -344,7 +328,7 @@ where
fn err(e: PayloadError) -> Self { fn err(e: PayloadError) -> Self {
HttpMessageBody { HttpMessageBody {
stream: dev::Payload::None, stream: None,
limit: 262_144, limit: 262_144,
fut: None, fut: None,
err: Some(e), err: Some(e),
@@ -353,10 +337,7 @@ where
} }
} }
impl<P> Future for HttpMessageBody<P> impl Future for HttpMessageBody {
where
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Item = Bytes; type Item = Bytes;
type Error = PayloadError; type Error = PayloadError;
@@ -378,7 +359,9 @@ where
// future // future
let limit = self.limit; let limit = self.limit;
self.fut = Some(Box::new( self.fut = Some(Box::new(
std::mem::replace(&mut self.stream, actix_http::Payload::None) self.stream
.take()
.unwrap()
.from_err() .from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| { .fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit { if (body.len() + chunk.len()) > limit {

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