mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-05 10:25:21 +02:00
Compare commits
25 Commits
codegen-v0
...
web-v3.2.0
Author | SHA1 | Date | |
---|---|---|---|
42f51eb962 | |||
156c97cef2 | |||
798d744eef | |||
4cb833616a | |||
9963a5ef54 | |||
4519db36b2 | |||
7030bf5fe8 | |||
20078fe603 | |||
06e5042b94 | |||
41e7cec72f | |||
d45a1aa6b6 | |||
98243db9f1 | |||
f92742bdac | |||
e563025b16 | |||
cfd5b381f1 | |||
2f84914146 | |||
d765e9099d | |||
34b23f31c9 | |||
26c1a901d9 | |||
c2c71cc626 | |||
aa11231ee5 | |||
b5812b15f0 | |||
b4e02fe29a | |||
37c76a39ab | |||
60e7e52276 |
43
CHANGES.md
43
CHANGES.md
@ -3,6 +3,43 @@
|
|||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.2.0 - 2020-10-30
|
||||||
|
### Added
|
||||||
|
* Implement `exclude_regex` for Logger middleware. [#1723]
|
||||||
|
* Add request-local data extractor `web::ReqData`. [#1748]
|
||||||
|
* Add ability to register closure for request middleware logging. [#1749]
|
||||||
|
* Add `app_data` to `ServiceConfig`. [#1757]
|
||||||
|
* Expose `on_connect` for access to the connection stream before request is handled. [#1754]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro.
|
||||||
|
* Print non-configured `Data<T>` type when attempting extraction. [#1743]
|
||||||
|
* Re-export bytes::Buf{Mut} in web module. [#1750]
|
||||||
|
* Upgrade `pin-project` to `1.0`.
|
||||||
|
|
||||||
|
[#1723]: https://github.com/actix/actix-web/pull/1723
|
||||||
|
[#1743]: https://github.com/actix/actix-web/pull/1743
|
||||||
|
[#1748]: https://github.com/actix/actix-web/pull/1748
|
||||||
|
[#1750]: https://github.com/actix/actix-web/pull/1750
|
||||||
|
[#1754]: https://github.com/actix/actix-web/pull/1754
|
||||||
|
[#1749]: https://github.com/actix/actix-web/pull/1749
|
||||||
|
|
||||||
|
|
||||||
|
## 3.1.0 - 2020-09-29
|
||||||
|
### Changed
|
||||||
|
* Add `TrailingSlash::MergeOnly` behaviour to `NormalizePath`, which allows `NormalizePath`
|
||||||
|
to retain any trailing slashes. [#1695]
|
||||||
|
* Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc<dyn Trait>`
|
||||||
|
via `web::Data::from` [#1710]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* `ResourceMap` debug printing is no longer infinitely recursive. [#1708]
|
||||||
|
|
||||||
|
[#1695]: https://github.com/actix/actix-web/pull/1695
|
||||||
|
[#1708]: https://github.com/actix/actix-web/pull/1708
|
||||||
|
[#1710]: https://github.com/actix/actix-web/pull/1710
|
||||||
|
|
||||||
|
|
||||||
## 3.0.2 - 2020-09-15
|
## 3.0.2 - 2020-09-15
|
||||||
### Fixed
|
### Fixed
|
||||||
* `NormalizePath` when used with `TrailingSlash::Trim` no longer trims the root path "/". [#1678]
|
* `NormalizePath` when used with `TrailingSlash::Trim` no longer trims the root path "/". [#1678]
|
||||||
@ -171,7 +208,7 @@
|
|||||||
|
|
||||||
### Deleted
|
### Deleted
|
||||||
|
|
||||||
* Delete HttpServer::run(), it is not useful witht async/await
|
* Delete HttpServer::run(), it is not useful with async/await
|
||||||
|
|
||||||
## [2.0.0-alpha.3] - 2019-12-07
|
## [2.0.0-alpha.3] - 2019-12-07
|
||||||
|
|
||||||
@ -216,7 +253,7 @@
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Make UrlEncodedError::Overflow more informativve
|
* Make UrlEncodedError::Overflow more informative
|
||||||
|
|
||||||
* Use actix-testing for testing utils
|
* Use actix-testing for testing utils
|
||||||
|
|
||||||
@ -234,7 +271,7 @@
|
|||||||
|
|
||||||
* Re-implement Host predicate (#989)
|
* Re-implement Host predicate (#989)
|
||||||
|
|
||||||
* Form immplements Responder, returning a `application/x-www-form-urlencoded` response
|
* Form implements Responder, returning a `application/x-www-form-urlencoded` response
|
||||||
|
|
||||||
* Add `into_inner` to `Data`
|
* Add `into_inner` to `Data`
|
||||||
|
|
||||||
|
28
Cargo.toml
28
Cargo.toml
@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "3.0.2"
|
version = "3.2.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust."
|
description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["actix", "http", "web", "framework", "async"]
|
keywords = ["actix", "http", "web", "framework", "async"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
@ -64,6 +64,14 @@ required-features = ["compress"]
|
|||||||
name = "test_server"
|
name = "test_server"
|
||||||
required-features = ["compress"]
|
required-features = ["compress"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "on_connect"
|
||||||
|
required-features = []
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "client"
|
||||||
|
required-features = ["rustls"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.3.0"
|
actix-codec = "0.3.0"
|
||||||
actix-service = "1.0.6"
|
actix-service = "1.0.6"
|
||||||
@ -76,8 +84,8 @@ actix-macros = "0.1.0"
|
|||||||
actix-threadpool = "0.3.1"
|
actix-threadpool = "0.3.1"
|
||||||
actix-tls = "2.0.0"
|
actix-tls = "2.0.0"
|
||||||
|
|
||||||
actix-web-codegen = "0.3.0"
|
actix-web-codegen = "0.4.0"
|
||||||
actix-http = "2.0.0"
|
actix-http = "2.1.0"
|
||||||
awc = { version = "2.0.0", default-features = false }
|
awc = { version = "2.0.0", default-features = false }
|
||||||
|
|
||||||
bytes = "0.5.3"
|
bytes = "0.5.3"
|
||||||
@ -90,8 +98,8 @@ fxhash = "0.2.1"
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
socket2 = "0.3"
|
socket2 = "0.3"
|
||||||
pin-project = "0.4.17"
|
pin-project = "1.0.0"
|
||||||
regex = "1.3"
|
regex = "1.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_urlencoded = "0.6.1"
|
serde_urlencoded = "0.6.1"
|
||||||
@ -103,9 +111,9 @@ tinyvec = { version = "1", features = ["alloc"] }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix = "0.10.0"
|
actix = "0.10.0"
|
||||||
actix-http = { version = "2.0.0", features = ["actors"] }
|
actix-http = { version = "2.1.0", features = ["actors"] }
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
env_logger = "0.7"
|
env_logger = "0.8"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
brotli2 = "0.3.2"
|
brotli2 = "0.3.2"
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
@ -125,10 +133,6 @@ actix-files = { path = "actix-files" }
|
|||||||
actix-multipart = { path = "actix-multipart" }
|
actix-multipart = { path = "actix-multipart" }
|
||||||
awc = { path = "awc" }
|
awc = { path = "awc" }
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "client"
|
|
||||||
required-features = ["rustls"]
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "server"
|
name = "server"
|
||||||
harness = false
|
harness = false
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
|
|
||||||
* `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one.
|
* `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one.
|
||||||
It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`,
|
It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`,
|
||||||
or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::default())`.
|
or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`.
|
||||||
|
|
||||||
* `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`.
|
* `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`.
|
||||||
|
|
||||||
|
@ -5,15 +5,17 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://docs.rs/actix-web)
|
[](https://docs.rs/actix-web/3.2.0)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||||

|

|
||||||
|
[](https://deps.rs/crate/actix-web/2.2.0)
|
||||||
<br />
|
<br />
|
||||||
[](https://travis-ci.org/actix/actix-web)
|
[](https://travis-ci.org/actix/actix-web)
|
||||||
[](https://codecov.io/gh/actix/actix-web)
|
[](https://codecov.io/gh/actix/actix-web)
|
||||||
[](https://crates.io/crates/actix-web)
|
[](https://crates.io/crates/actix-web)
|
||||||
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,24 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## [Unreleased] - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
## [0.3.0-beta.1] - 2020-07-15
|
|
||||||
|
## 0.4.0 - 2020-10-06
|
||||||
|
* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714]
|
||||||
|
|
||||||
|
[#1714]: https://github.com/actix/actix-web/pull/1714
|
||||||
|
|
||||||
|
|
||||||
|
## 0.3.0 - 2020-09-11
|
||||||
|
* No significant changes from 0.3.0-beta.1.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.3.0-beta.1 - 2020-07-15
|
||||||
* Update `v_htmlescape` to 0.10
|
* Update `v_htmlescape` to 0.10
|
||||||
* Update `actix-web` and `actix-http` dependencies to beta.1
|
* Update `actix-web` and `actix-http` dependencies to beta.1
|
||||||
|
|
||||||
## [0.3.0-alpha.1] - 2020-05-23
|
|
||||||
|
## 0.3.0-alpha.1 - 2020-05-23
|
||||||
* Update `actix-web` and `actix-http` dependencies to alpha
|
* Update `actix-web` and `actix-http` dependencies to alpha
|
||||||
* Fix some typos in the docs
|
* Fix some typos in the docs
|
||||||
* Bump minimum supported Rust version to 1.40
|
* Bump minimum supported Rust version to 1.40
|
||||||
@ -14,77 +26,73 @@
|
|||||||
|
|
||||||
[#1384]: https://github.com/actix/actix-web/pull/1384
|
[#1384]: https://github.com/actix/actix-web/pull/1384
|
||||||
|
|
||||||
## [0.2.1] - 2019-12-22
|
|
||||||
|
|
||||||
|
## 0.2.1 - 2019-12-22
|
||||||
* Use the same format for file URLs regardless of platforms
|
* Use the same format for file URLs regardless of platforms
|
||||||
|
|
||||||
## [0.2.0] - 2019-12-20
|
|
||||||
|
|
||||||
|
## 0.2.0 - 2019-12-20
|
||||||
* Fix BodyEncoding trait import #1220
|
* Fix BodyEncoding trait import #1220
|
||||||
|
|
||||||
## [0.2.0-alpha.1] - 2019-12-07
|
|
||||||
|
|
||||||
|
## 0.2.0-alpha.1 - 2019-12-07
|
||||||
* Migrate to `std::future`
|
* Migrate to `std::future`
|
||||||
|
|
||||||
## [0.1.7] - 2019-11-06
|
|
||||||
|
|
||||||
* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
|
## 0.1.7 - 2019-11-06
|
||||||
|
* Add an additional `filename*` param in the `Content-Disposition` header of
|
||||||
## [0.1.6] - 2019-10-14
|
`actix_files::NamedFile` to be more compatible. (#1151)
|
||||||
|
|
||||||
|
## 0.1.6 - 2019-10-14
|
||||||
* Add option to redirect to a slash-ended path `Files` #1132
|
* Add option to redirect to a slash-ended path `Files` #1132
|
||||||
|
|
||||||
## [0.1.5] - 2019-10-08
|
|
||||||
|
|
||||||
|
## 0.1.5 - 2019-10-08
|
||||||
* Bump up `mime_guess` crate version to 2.0.1
|
* Bump up `mime_guess` crate version to 2.0.1
|
||||||
|
|
||||||
* Bump up `percent-encoding` crate version to 2.1
|
* Bump up `percent-encoding` crate version to 2.1
|
||||||
|
|
||||||
* Allow user defined request guards for `Files` #1113
|
* Allow user defined request guards for `Files` #1113
|
||||||
|
|
||||||
## [0.1.4] - 2019-07-20
|
|
||||||
|
|
||||||
|
## 0.1.4 - 2019-07-20
|
||||||
* Allow to disable `Content-Disposition` header #686
|
* Allow to disable `Content-Disposition` header #686
|
||||||
|
|
||||||
## [0.1.3] - 2019-06-28
|
|
||||||
|
|
||||||
|
## 0.1.3 - 2019-06-28
|
||||||
* Do not set `Content-Length` header, let actix-http set it #930
|
* Do not set `Content-Length` header, let actix-http set it #930
|
||||||
|
|
||||||
## [0.1.2] - 2019-06-13
|
|
||||||
|
|
||||||
|
## 0.1.2 - 2019-06-13
|
||||||
* Content-Length is 0 for NamedFile HEAD request #914
|
* Content-Length is 0 for NamedFile HEAD request #914
|
||||||
|
|
||||||
* Fix ring dependency from actix-web default features for #741
|
* Fix ring dependency from actix-web default features for #741
|
||||||
|
|
||||||
## [0.1.1] - 2019-06-01
|
|
||||||
|
|
||||||
|
## 0.1.1 - 2019-06-01
|
||||||
* Static files are incorrectly served as both chunked and with length #812
|
* Static files are incorrectly served as both chunked and with length #812
|
||||||
|
|
||||||
## [0.1.0] - 2019-05-25
|
|
||||||
|
|
||||||
* NamedFile last-modified check always fails due to nano-seconds
|
## 0.1.0 - 2019-05-25
|
||||||
in file modified date #820
|
* NamedFile last-modified check always fails due to nano-seconds in file modified date #820
|
||||||
|
|
||||||
## [0.1.0-beta.4] - 2019-05-12
|
|
||||||
|
|
||||||
|
## 0.1.0-beta.4 - 2019-05-12
|
||||||
* Update actix-web to beta.4
|
* Update actix-web to beta.4
|
||||||
|
|
||||||
## [0.1.0-beta.1] - 2019-04-20
|
|
||||||
|
|
||||||
|
## 0.1.0-beta.1 - 2019-04-20
|
||||||
* Update actix-web to beta.1
|
* Update actix-web to beta.1
|
||||||
|
|
||||||
## [0.1.0-alpha.6] - 2019-04-14
|
|
||||||
|
|
||||||
|
## 0.1.0-alpha.6 - 2019-04-14
|
||||||
* Update actix-web to alpha6
|
* 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
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Static files support for actix web."
|
description = "Static file serving for Actix Web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["actix", "http", "async", "futures"]
|
keywords = ["actix", "http", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
# Static files support for actix web [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-files) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
# actix-files
|
||||||
|
|
||||||
## Documentation & community resources
|
> Static file serving for Actix Web
|
||||||
|
|
||||||
* [User Guide](https://actix.rs/docs/)
|
[](https://crates.io/crates/actix-files)
|
||||||
* [API Documentation](https://docs.rs/actix-files/)
|
[](https://docs.rs/actix-files)
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||||
* Cargo package: [actix-files](https://crates.io/crates/actix-files)
|

|
||||||
* Minimum supported Rust version: 1.40 or later
|
<br />
|
||||||
|
[](https://deps.rs/crate/actix-files/0.4.0)
|
||||||
|
[](https://crates.io/crates/actix-files)
|
||||||
|
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
## Documentation & Resources
|
||||||
|
|
||||||
|
- [API Documentation](https://docs.rs/actix-files/)
|
||||||
|
- [Example Project](https://github.com/actix/examples/tree/master/static_index)
|
||||||
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
|
- Minimum supported Rust version: 1.42 or later
|
||||||
|
52
actix-files/src/encoding.rs
Normal file
52
actix-files/src/encoding.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use mime::Mime;
|
||||||
|
|
||||||
|
/// Transforms MIME `text/*` types into their UTF-8 equivalent, if supported.
|
||||||
|
///
|
||||||
|
/// MIME types that are converted
|
||||||
|
/// - application/javascript
|
||||||
|
/// - text/html
|
||||||
|
/// - text/css
|
||||||
|
/// - text/plain
|
||||||
|
/// - text/csv
|
||||||
|
/// - text/tab-separated-values
|
||||||
|
pub(crate) fn equiv_utf8_text(ct: Mime) -> Mime {
|
||||||
|
// use (roughly) order of file-type popularity for a web server
|
||||||
|
|
||||||
|
if ct == mime::APPLICATION_JAVASCRIPT {
|
||||||
|
return mime::APPLICATION_JAVASCRIPT_UTF_8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ct == mime::TEXT_HTML {
|
||||||
|
return mime::TEXT_HTML_UTF_8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ct == mime::TEXT_CSS {
|
||||||
|
return mime::TEXT_CSS_UTF_8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ct == mime::TEXT_PLAIN {
|
||||||
|
return mime::TEXT_PLAIN_UTF_8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ct == mime::TEXT_CSV {
|
||||||
|
return mime::TEXT_CSV_UTF_8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ct == mime::TEXT_TAB_SEPARATED_VALUES {
|
||||||
|
return mime::TEXT_TAB_SEPARATED_VALUES_UTF_8;
|
||||||
|
}
|
||||||
|
|
||||||
|
ct
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_equiv_utf8_text() {
|
||||||
|
assert_eq!(equiv_utf8_text(mime::TEXT_PLAIN), mime::TEXT_PLAIN_UTF_8);
|
||||||
|
assert_eq!(equiv_utf8_text(mime::TEXT_XML), mime::TEXT_XML);
|
||||||
|
assert_eq!(equiv_utf8_text(mime::IMAGE_PNG), mime::IMAGE_PNG);
|
||||||
|
}
|
||||||
|
}
|
@ -138,24 +138,33 @@ impl Files {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Specifies whether to use ETag or not.
|
/// Specifies whether to use ETag or not.
|
||||||
///
|
///
|
||||||
/// Default is true.
|
/// Default is true.
|
||||||
|
#[inline]
|
||||||
pub fn use_etag(mut self, value: bool) -> Self {
|
pub fn use_etag(mut self, value: bool) -> Self {
|
||||||
self.file_flags.set(named::Flags::ETAG, value);
|
self.file_flags.set(named::Flags::ETAG, value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// Specifies whether to use Last-Modified or not.
|
/// Specifies whether to use Last-Modified or not.
|
||||||
///
|
///
|
||||||
/// Default is true.
|
/// Default is true.
|
||||||
|
#[inline]
|
||||||
pub fn use_last_modified(mut self, value: bool) -> Self {
|
pub fn use_last_modified(mut self, value: bool) -> Self {
|
||||||
self.file_flags.set(named::Flags::LAST_MD, value);
|
self.file_flags.set(named::Flags::LAST_MD, value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specifies whether text responses should signal a UTF-8 encoding.
|
||||||
|
///
|
||||||
|
/// Default is false (but will default to true in a future version).
|
||||||
|
#[inline]
|
||||||
|
pub fn prefer_utf8(mut self, value: bool) -> Self {
|
||||||
|
self.file_flags.set(named::Flags::PREFER_UTF8, value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Specifies custom guards to use for directory listings and files.
|
/// Specifies custom guards to use for directory listings and files.
|
||||||
///
|
///
|
||||||
/// Default behaviour allows GET and HEAD.
|
/// Default behaviour allows GET and HEAD.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Static files support for Actix Web.
|
//! Static file serving for Actix Web.
|
||||||
//!
|
//!
|
||||||
//! Provides a non-blocking service for serving static files from disk.
|
//! Provides a non-blocking service for serving static files from disk.
|
||||||
//!
|
//!
|
||||||
@ -8,12 +8,8 @@
|
|||||||
//! use actix_files::Files;
|
//! use actix_files::Files;
|
||||||
//!
|
//!
|
||||||
//! let app = App::new()
|
//! let app = App::new()
|
||||||
//! .service(Files::new("/static", "."));
|
//! .service(Files::new("/static", ".").prefer_utf8(true));
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
|
||||||
//! # Implementation Quirks
|
|
||||||
//! - If a filename contains non-ascii characters, that file will be served with the `charset=utf-8`
|
|
||||||
//! extension on the Content-Type header.
|
|
||||||
|
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
#![warn(missing_docs, missing_debug_implementations)]
|
#![warn(missing_docs, missing_debug_implementations)]
|
||||||
@ -30,6 +26,7 @@ use mime_guess::from_ext;
|
|||||||
|
|
||||||
mod chunked;
|
mod chunked;
|
||||||
mod directory;
|
mod directory;
|
||||||
|
mod encoding;
|
||||||
mod error;
|
mod error;
|
||||||
mod files;
|
mod files;
|
||||||
mod named;
|
mod named;
|
||||||
@ -93,6 +90,9 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_file_extension_to_mime() {
|
async fn test_file_extension_to_mime() {
|
||||||
|
let m = file_extension_to_mime("");
|
||||||
|
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
|
||||||
|
|
||||||
let m = file_extension_to_mime("jpg");
|
let m = file_extension_to_mime("jpg");
|
||||||
assert_eq!(m, mime::IMAGE_JPEG);
|
assert_eq!(m, mime::IMAGE_JPEG);
|
||||||
|
|
||||||
|
@ -22,20 +22,21 @@ use bitflags::bitflags;
|
|||||||
use futures_util::future::{ready, Ready};
|
use futures_util::future::{ready, Ready};
|
||||||
use mime_guess::from_path;
|
use mime_guess::from_path;
|
||||||
|
|
||||||
use crate::range::HttpRange;
|
|
||||||
use crate::ChunkedReadFile;
|
use crate::ChunkedReadFile;
|
||||||
|
use crate::{encoding::equiv_utf8_text, range::HttpRange};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub(crate) struct Flags: u8 {
|
pub(crate) struct Flags: u8 {
|
||||||
const ETAG = 0b0000_0001;
|
const ETAG = 0b0000_0001;
|
||||||
const LAST_MD = 0b0000_0010;
|
const LAST_MD = 0b0000_0010;
|
||||||
const CONTENT_DISPOSITION = 0b0000_0100;
|
const CONTENT_DISPOSITION = 0b0000_0100;
|
||||||
|
const PREFER_UTF8 = 0b0000_1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Flags {
|
impl Default for Flags {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Flags::all()
|
Flags::from_bits_truncate(0b0000_0111)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +93,7 @@ impl NamedFile {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let ct = from_path(&path).first_or_octet_stream();
|
let ct = from_path(&path).first_or_octet_stream();
|
||||||
|
|
||||||
let disposition = match ct.type_() {
|
let disposition = match ct.type_() {
|
||||||
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
|
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
|
||||||
_ => DispositionType::Attachment,
|
_ => DispositionType::Attachment,
|
||||||
@ -215,24 +217,33 @@ impl NamedFile {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Specifies whether to use ETag or not.
|
||||||
///Specifies whether to use ETag or not.
|
|
||||||
///
|
///
|
||||||
///Default is true.
|
/// Default is true.
|
||||||
|
#[inline]
|
||||||
pub fn use_etag(mut self, value: bool) -> Self {
|
pub fn use_etag(mut self, value: bool) -> Self {
|
||||||
self.flags.set(Flags::ETAG, value);
|
self.flags.set(Flags::ETAG, value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Specifies whether to use Last-Modified or not.
|
||||||
///Specifies whether to use Last-Modified or not.
|
|
||||||
///
|
///
|
||||||
///Default is true.
|
/// Default is true.
|
||||||
|
#[inline]
|
||||||
pub fn use_last_modified(mut self, value: bool) -> Self {
|
pub fn use_last_modified(mut self, value: bool) -> Self {
|
||||||
self.flags.set(Flags::LAST_MD, value);
|
self.flags.set(Flags::LAST_MD, value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specifies whether text responses should signal a UTF-8 encoding.
|
||||||
|
///
|
||||||
|
/// Default is false (but will default to true in a future version).
|
||||||
|
#[inline]
|
||||||
|
pub fn prefer_utf8(mut self, value: bool) -> Self {
|
||||||
|
self.flags.set(Flags::PREFER_UTF8, value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn etag(&self) -> Option<header::EntityTag> {
|
pub(crate) fn etag(&self) -> Option<header::EntityTag> {
|
||||||
// This etag format is similar to Apache's.
|
// This etag format is similar to Apache's.
|
||||||
self.modified.as_ref().map(|mtime| {
|
self.modified.as_ref().map(|mtime| {
|
||||||
@ -268,18 +279,24 @@ impl NamedFile {
|
|||||||
/// Creates an `HttpResponse` with file as a streaming body.
|
/// Creates an `HttpResponse` with file as a streaming body.
|
||||||
pub fn into_response(self, req: &HttpRequest) -> Result<HttpResponse, Error> {
|
pub fn into_response(self, req: &HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
if self.status_code != StatusCode::OK {
|
if self.status_code != StatusCode::OK {
|
||||||
let mut resp = HttpResponse::build(self.status_code);
|
let mut res = HttpResponse::build(self.status_code);
|
||||||
|
|
||||||
resp.set(header::ContentType(self.content_type.clone()))
|
if self.flags.contains(Flags::PREFER_UTF8) {
|
||||||
.if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| {
|
let ct = equiv_utf8_text(self.content_type.clone());
|
||||||
res.header(
|
res.header(header::CONTENT_TYPE, ct.to_string());
|
||||||
header::CONTENT_DISPOSITION,
|
} else {
|
||||||
self.content_disposition.to_string(),
|
res.header(header::CONTENT_TYPE, self.content_type.to_string());
|
||||||
);
|
}
|
||||||
});
|
|
||||||
|
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
||||||
|
res.header(
|
||||||
|
header::CONTENT_DISPOSITION,
|
||||||
|
self.content_disposition.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(current_encoding) = self.encoding {
|
if let Some(current_encoding) = self.encoding {
|
||||||
resp.encoding(current_encoding);
|
res.encoding(current_encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
let reader = ChunkedReadFile {
|
let reader = ChunkedReadFile {
|
||||||
@ -290,7 +307,7 @@ impl NamedFile {
|
|||||||
counter: 0,
|
counter: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(resp.streaming(reader));
|
return Ok(res.streaming(reader));
|
||||||
}
|
}
|
||||||
|
|
||||||
let etag = if self.flags.contains(Flags::ETAG) {
|
let etag = if self.flags.contains(Flags::ETAG) {
|
||||||
@ -342,25 +359,33 @@ impl NamedFile {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut resp = HttpResponse::build(self.status_code);
|
let mut resp = HttpResponse::build(self.status_code);
|
||||||
resp.set(header::ContentType(self.content_type.clone()))
|
|
||||||
.if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| {
|
if self.flags.contains(Flags::PREFER_UTF8) {
|
||||||
res.header(
|
let ct = equiv_utf8_text(self.content_type.clone());
|
||||||
header::CONTENT_DISPOSITION,
|
resp.header(header::CONTENT_TYPE, ct.to_string());
|
||||||
self.content_disposition.to_string(),
|
} else {
|
||||||
);
|
resp.header(header::CONTENT_TYPE, self.content_type.to_string());
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
||||||
|
resp.header(
|
||||||
|
header::CONTENT_DISPOSITION,
|
||||||
|
self.content_disposition.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// default compressing
|
// default compressing
|
||||||
if let Some(current_encoding) = self.encoding {
|
if let Some(current_encoding) = self.encoding {
|
||||||
resp.encoding(current_encoding);
|
resp.encoding(current_encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.if_some(last_modified, |lm, resp| {
|
if let Some(lm) = last_modified {
|
||||||
resp.set(header::LastModified(lm));
|
resp.header(header::LAST_MODIFIED, lm.to_string());
|
||||||
})
|
}
|
||||||
.if_some(etag, |etag, resp| {
|
|
||||||
resp.set(header::ETag(etag));
|
if let Some(etag) = etag {
|
||||||
});
|
resp.header(header::ETAG, etag.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
resp.header(header::ACCEPT_RANGES, "bytes");
|
resp.header(header::ACCEPT_RANGES, "bytes");
|
||||||
|
|
||||||
|
40
actix-files/tests/encoding.rs
Normal file
40
actix-files/tests/encoding.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use actix_files::Files;
|
||||||
|
use actix_web::{
|
||||||
|
http::{
|
||||||
|
header::{self, HeaderValue},
|
||||||
|
StatusCode,
|
||||||
|
},
|
||||||
|
test::{self, TestRequest},
|
||||||
|
App,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_utf8_file_contents() {
|
||||||
|
// use default ISO-8859-1 encoding
|
||||||
|
let mut srv =
|
||||||
|
test::init_service(App::new().service(Files::new("/", "./tests"))).await;
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
||||||
|
let res = test::call_service(&mut srv, req).await;
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get(header::CONTENT_TYPE),
|
||||||
|
Some(&HeaderValue::from_static("text/plain")),
|
||||||
|
);
|
||||||
|
|
||||||
|
// prefer UTF-8 encoding
|
||||||
|
let mut srv = test::init_service(
|
||||||
|
App::new().service(Files::new("/", "./tests").prefer_utf8(true)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/utf8.txt").to_request();
|
||||||
|
let res = test::call_service(&mut srv, req).await;
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
res.headers().get(header::CONTENT_TYPE),
|
||||||
|
Some(&HeaderValue::from_static("text/plain; charset=utf-8")),
|
||||||
|
);
|
||||||
|
}
|
3
actix-files/tests/utf8.txt
Normal file
3
actix-files/tests/utf8.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
中文内容显示正确。
|
||||||
|
|
||||||
|
English is OK.
|
@ -3,6 +3,21 @@
|
|||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 2.1.0 - 2020-10-30
|
||||||
|
### Added
|
||||||
|
* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Upgrade `base64` to `0.13`. [#1744]
|
||||||
|
* Upgrade `pin-project` to `1.0`. [#1733]
|
||||||
|
* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760]
|
||||||
|
|
||||||
|
[#1760]: https://github.com/actix/actix-web/pull/1760
|
||||||
|
[#1754]: https://github.com/actix/actix-web/pull/1754
|
||||||
|
[#1733]: https://github.com/actix/actix-web/pull/1733
|
||||||
|
[#1744]: https://github.com/actix/actix-web/pull/1744
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0 - 2020-09-11
|
## 2.0.0 - 2020-09-11
|
||||||
* No significant changes from `2.0.0-beta.4`.
|
* No significant changes from `2.0.0-beta.4`.
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "2.0.0"
|
version = "2.1.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix HTTP primitives"
|
description = "HTTP primitives for the Actix ecosystem"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
@ -49,7 +49,7 @@ actix-threadpool = "0.3.1"
|
|||||||
actix-tls = { version = "2.0.0", optional = true }
|
actix-tls = { version = "2.0.0", optional = true }
|
||||||
actix = { version = "0.10.0", optional = true }
|
actix = { version = "0.10.0", optional = true }
|
||||||
|
|
||||||
base64 = "0.12"
|
base64 = "0.13"
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
bytes = "0.5.3"
|
bytes = "0.5.3"
|
||||||
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
||||||
@ -71,7 +71,7 @@ language-tags = "0.2"
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
pin-project = "0.4.17"
|
pin-project = "1.0.0"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
# Actix http [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-http) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
# actix-http
|
||||||
|
|
||||||
Actix http
|
> HTTP primitives for the Actix ecosystem.
|
||||||
|
|
||||||
## Documentation & community resources
|
[](https://crates.io/crates/actix-http)
|
||||||
|
[](https://docs.rs/actix-http/2.1.0)
|
||||||
|

|
||||||
|
[](https://deps.rs/crate/actix-http/2.1.0)
|
||||||
|
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
* [User Guide](https://actix.rs/docs/)
|
## Documentation & Resources
|
||||||
* [API Documentation](https://docs.rs/actix-http/)
|
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
- [API Documentation](https://docs.rs/actix-http/2.1.0)
|
||||||
* Cargo package: [actix-http](https://crates.io/crates/actix-http)
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
* Minimum supported Rust version: 1.40 or later
|
- Minimum Supported Rust Version (MSRV): 1.42.0
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
@ -14,10 +14,11 @@ use crate::helpers::{Data, DataFactory};
|
|||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::service::HttpService;
|
use crate::service::HttpService;
|
||||||
|
use crate::{ConnectCallback, Extensions};
|
||||||
|
|
||||||
/// A http service builder
|
/// A HTTP service builder
|
||||||
///
|
///
|
||||||
/// This type can be used to construct an instance of `http service` through a
|
/// This type can be used to construct an instance of [`HttpService`] through a
|
||||||
/// builder-like pattern.
|
/// builder-like pattern.
|
||||||
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
|
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
|
||||||
keep_alive: KeepAlive,
|
keep_alive: KeepAlive,
|
||||||
@ -27,7 +28,9 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
|
|||||||
local_addr: Option<net::SocketAddr>,
|
local_addr: Option<net::SocketAddr>,
|
||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
|
// DEPRECATED: in favor of on_connect_ext
|
||||||
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_t: PhantomData<(T, S)>,
|
_t: PhantomData<(T, S)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +52,7 @@ where
|
|||||||
expect: ExpectHandler,
|
expect: ExpectHandler,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
on_connect: None,
|
on_connect: None,
|
||||||
|
on_connect_ext: None,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,6 +142,7 @@ where
|
|||||||
expect: expect.into_factory(),
|
expect: expect.into_factory(),
|
||||||
upgrade: self.upgrade,
|
upgrade: self.upgrade,
|
||||||
on_connect: self.on_connect,
|
on_connect: self.on_connect,
|
||||||
|
on_connect_ext: self.on_connect_ext,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,14 +172,16 @@ where
|
|||||||
expect: self.expect,
|
expect: self.expect,
|
||||||
upgrade: Some(upgrade.into_factory()),
|
upgrade: Some(upgrade.into_factory()),
|
||||||
on_connect: self.on_connect,
|
on_connect: self.on_connect,
|
||||||
|
on_connect_ext: self.on_connect_ext,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set on-connect callback.
|
/// Set on-connect callback.
|
||||||
///
|
///
|
||||||
/// It get called once per connection and result of the call
|
/// Called once per connection. Return value of the call is stored in request extensions.
|
||||||
/// get stored to the request's extensions.
|
///
|
||||||
|
/// *SOFT DEPRECATED*: Prefer the `on_connect_ext` style callback.
|
||||||
pub fn on_connect<F, I>(mut self, f: F) -> Self
|
pub fn on_connect<F, I>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&T) -> I + 'static,
|
F: Fn(&T) -> I + 'static,
|
||||||
@ -184,7 +191,20 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish service configuration and create *http service* for HTTP/1 protocol.
|
/// Sets the callback to be run on connection establishment.
|
||||||
|
///
|
||||||
|
/// Has mutable access to a data container that will be merged into request extensions.
|
||||||
|
/// This enables transport layer data (like client certificates) to be accessed in middleware
|
||||||
|
/// and handlers.
|
||||||
|
pub fn on_connect_ext<F>(mut self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(&T, &mut Extensions) + 'static,
|
||||||
|
{
|
||||||
|
self.on_connect_ext = Some(Rc::new(f));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish service configuration and create a HTTP Service for HTTP/1 protocol.
|
||||||
pub fn h1<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
|
pub fn h1<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
|
||||||
where
|
where
|
||||||
B: MessageBody,
|
B: MessageBody,
|
||||||
@ -200,13 +220,15 @@ where
|
|||||||
self.secure,
|
self.secure,
|
||||||
self.local_addr,
|
self.local_addr,
|
||||||
);
|
);
|
||||||
|
|
||||||
H1Service::with_config(cfg, service.into_factory())
|
H1Service::with_config(cfg, service.into_factory())
|
||||||
.expect(self.expect)
|
.expect(self.expect)
|
||||||
.upgrade(self.upgrade)
|
.upgrade(self.upgrade)
|
||||||
.on_connect(self.on_connect)
|
.on_connect(self.on_connect)
|
||||||
|
.on_connect_ext(self.on_connect_ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish service configuration and create *http service* for HTTP/2 protocol.
|
/// Finish service configuration and create a HTTP service for HTTP/2 protocol.
|
||||||
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
|
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
|
||||||
where
|
where
|
||||||
B: MessageBody + 'static,
|
B: MessageBody + 'static,
|
||||||
@ -223,7 +245,10 @@ where
|
|||||||
self.secure,
|
self.secure,
|
||||||
self.local_addr,
|
self.local_addr,
|
||||||
);
|
);
|
||||||
H2Service::with_config(cfg, service.into_factory()).on_connect(self.on_connect)
|
|
||||||
|
H2Service::with_config(cfg, service.into_factory())
|
||||||
|
.on_connect(self.on_connect)
|
||||||
|
.on_connect_ext(self.on_connect_ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish service configuration and create `HttpService` instance.
|
/// Finish service configuration and create `HttpService` instance.
|
||||||
@ -243,9 +268,11 @@ where
|
|||||||
self.secure,
|
self.secure,
|
||||||
self.local_addr,
|
self.local_addr,
|
||||||
);
|
);
|
||||||
|
|
||||||
HttpService::with_config(cfg, service.into_factory())
|
HttpService::with_config(cfg, service.into_factory())
|
||||||
.expect(self.expect)
|
.expect(self.expect)
|
||||||
.upgrade(self.upgrade)
|
.upgrade(self.upgrade)
|
||||||
.on_connect(self.on_connect)
|
.on_connect(self.on_connect)
|
||||||
|
.on_connect_ext(self.on_connect_ext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use std::fmt;
|
use std::{fmt, mem};
|
||||||
|
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
|
|
||||||
@ -61,6 +61,16 @@ impl Extensions {
|
|||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.map.clear();
|
self.map.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extends self with the items from another `Extensions`.
|
||||||
|
pub fn extend(&mut self, other: Extensions) {
|
||||||
|
self.map.extend(other.map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets (or overrides) items from `other` into this map.
|
||||||
|
pub(crate) fn drain_from(&mut self, other: &mut Self) {
|
||||||
|
self.map.extend(mem::take(&mut other.map));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Extensions {
|
impl fmt::Debug for Extensions {
|
||||||
@ -178,4 +188,57 @@ mod tests {
|
|||||||
assert_eq!(extensions.get::<bool>(), None);
|
assert_eq!(extensions.get::<bool>(), None);
|
||||||
assert_eq!(extensions.get(), Some(&MyType(10)));
|
assert_eq!(extensions.get(), Some(&MyType(10)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extend() {
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct MyType(i32);
|
||||||
|
|
||||||
|
let mut extensions = Extensions::new();
|
||||||
|
|
||||||
|
extensions.insert(5i32);
|
||||||
|
extensions.insert(MyType(10));
|
||||||
|
|
||||||
|
let mut other = Extensions::new();
|
||||||
|
|
||||||
|
other.insert(15i32);
|
||||||
|
other.insert(20u8);
|
||||||
|
|
||||||
|
extensions.extend(other);
|
||||||
|
|
||||||
|
assert_eq!(extensions.get(), Some(&15i32));
|
||||||
|
assert_eq!(extensions.get_mut(), Some(&mut 15i32));
|
||||||
|
|
||||||
|
assert_eq!(extensions.remove::<i32>(), Some(15i32));
|
||||||
|
assert!(extensions.get::<i32>().is_none());
|
||||||
|
|
||||||
|
assert_eq!(extensions.get::<bool>(), None);
|
||||||
|
assert_eq!(extensions.get(), Some(&MyType(10)));
|
||||||
|
|
||||||
|
assert_eq!(extensions.get(), Some(&20u8));
|
||||||
|
assert_eq!(extensions.get_mut(), Some(&mut 20u8));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_drain_from() {
|
||||||
|
let mut ext = Extensions::new();
|
||||||
|
ext.insert(2isize);
|
||||||
|
|
||||||
|
let mut more_ext = Extensions::new();
|
||||||
|
|
||||||
|
more_ext.insert(5isize);
|
||||||
|
more_ext.insert(5usize);
|
||||||
|
|
||||||
|
assert_eq!(ext.get::<isize>(), Some(&2isize));
|
||||||
|
assert_eq!(ext.get::<usize>(), None);
|
||||||
|
assert_eq!(more_ext.get::<isize>(), Some(&5isize));
|
||||||
|
assert_eq!(more_ext.get::<usize>(), Some(&5usize));
|
||||||
|
|
||||||
|
ext.drain_from(&mut more_ext);
|
||||||
|
|
||||||
|
assert_eq!(ext.get::<isize>(), Some(&5isize));
|
||||||
|
assert_eq!(ext.get::<usize>(), Some(&5usize));
|
||||||
|
assert_eq!(more_ext.get::<isize>(), None);
|
||||||
|
assert_eq!(more_ext.get::<usize>(), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ use bytes::{Buf, BytesMut};
|
|||||||
use log::{error, trace};
|
use log::{error, trace};
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
|
|
||||||
use crate::cloneable::CloneableService;
|
use crate::cloneable::CloneableService;
|
||||||
use crate::config::ServiceConfig;
|
use crate::config::ServiceConfig;
|
||||||
use crate::error::{DispatchError, Error};
|
use crate::error::{DispatchError, Error};
|
||||||
@ -21,6 +20,10 @@ use crate::helpers::DataFactory;
|
|||||||
use crate::httpmessage::HttpMessage;
|
use crate::httpmessage::HttpMessage;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
|
use crate::{
|
||||||
|
body::{Body, BodySize, MessageBody, ResponseBody},
|
||||||
|
Extensions,
|
||||||
|
};
|
||||||
|
|
||||||
use super::codec::Codec;
|
use super::codec::Codec;
|
||||||
use super::payload::{Payload, PayloadSender, PayloadStatus};
|
use super::payload::{Payload, PayloadSender, PayloadStatus};
|
||||||
@ -88,6 +91,7 @@ where
|
|||||||
expect: CloneableService<X>,
|
expect: CloneableService<X>,
|
||||||
upgrade: Option<CloneableService<U>>,
|
upgrade: Option<CloneableService<U>>,
|
||||||
on_connect: Option<Box<dyn DataFactory>>,
|
on_connect: Option<Box<dyn DataFactory>>,
|
||||||
|
on_connect_data: Extensions,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
error: Option<DispatchError>,
|
error: Option<DispatchError>,
|
||||||
@ -167,7 +171,7 @@ where
|
|||||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||||
U::Error: fmt::Display,
|
U::Error: fmt::Display,
|
||||||
{
|
{
|
||||||
/// Create http/1 dispatcher.
|
/// Create HTTP/1 dispatcher.
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
stream: T,
|
stream: T,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
@ -175,6 +179,7 @@ where
|
|||||||
expect: CloneableService<X>,
|
expect: CloneableService<X>,
|
||||||
upgrade: Option<CloneableService<U>>,
|
upgrade: Option<CloneableService<U>>,
|
||||||
on_connect: Option<Box<dyn DataFactory>>,
|
on_connect: Option<Box<dyn DataFactory>>,
|
||||||
|
on_connect_data: Extensions,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Dispatcher::with_timeout(
|
Dispatcher::with_timeout(
|
||||||
@ -187,6 +192,7 @@ where
|
|||||||
expect,
|
expect,
|
||||||
upgrade,
|
upgrade,
|
||||||
on_connect,
|
on_connect,
|
||||||
|
on_connect_data,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -202,6 +208,7 @@ where
|
|||||||
expect: CloneableService<X>,
|
expect: CloneableService<X>,
|
||||||
upgrade: Option<CloneableService<U>>,
|
upgrade: Option<CloneableService<U>>,
|
||||||
on_connect: Option<Box<dyn DataFactory>>,
|
on_connect: Option<Box<dyn DataFactory>>,
|
||||||
|
on_connect_data: Extensions,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let keepalive = config.keep_alive_enabled();
|
let keepalive = config.keep_alive_enabled();
|
||||||
@ -234,6 +241,7 @@ where
|
|||||||
expect,
|
expect,
|
||||||
upgrade,
|
upgrade,
|
||||||
on_connect,
|
on_connect,
|
||||||
|
on_connect_data,
|
||||||
flags,
|
flags,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
ka_expire,
|
ka_expire,
|
||||||
@ -526,11 +534,15 @@ where
|
|||||||
let pl = this.codec.message_type();
|
let pl = this.codec.message_type();
|
||||||
req.head_mut().peer_addr = *this.peer_addr;
|
req.head_mut().peer_addr = *this.peer_addr;
|
||||||
|
|
||||||
|
// DEPRECATED
|
||||||
// set on_connect data
|
// set on_connect data
|
||||||
if let Some(ref on_connect) = this.on_connect {
|
if let Some(ref on_connect) = this.on_connect {
|
||||||
on_connect.set(&mut req.extensions_mut());
|
on_connect.set(&mut req.extensions_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// merge on_connect_ext data into request extensions
|
||||||
|
req.extensions_mut().drain_from(this.on_connect_data);
|
||||||
|
|
||||||
if pl == MessageType::Stream && this.upgrade.is_some() {
|
if pl == MessageType::Stream && this.upgrade.is_some() {
|
||||||
this.messages.push_back(DispatcherMessage::Upgrade(req));
|
this.messages.push_back(DispatcherMessage::Upgrade(req));
|
||||||
break;
|
break;
|
||||||
@ -927,8 +939,10 @@ mod tests {
|
|||||||
CloneableService::new(ExpectHandler),
|
CloneableService::new(ExpectHandler),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
Extensions::new(),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
match Pin::new(&mut h1).poll(cx) {
|
match Pin::new(&mut h1).poll(cx) {
|
||||||
Poll::Pending => panic!(),
|
Poll::Pending => panic!(),
|
||||||
Poll::Ready(res) => assert!(res.is_err()),
|
Poll::Ready(res) => assert!(res.is_err()),
|
||||||
|
@ -18,6 +18,7 @@ use crate::error::{DispatchError, Error, ParseError};
|
|||||||
use crate::helpers::DataFactory;
|
use crate::helpers::DataFactory;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
|
use crate::{ConnectCallback, Extensions};
|
||||||
|
|
||||||
use super::codec::Codec;
|
use super::codec::Codec;
|
||||||
use super::dispatcher::Dispatcher;
|
use super::dispatcher::Dispatcher;
|
||||||
@ -30,6 +31,7 @@ pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler<T>> {
|
|||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_t: PhantomData<(T, B)>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ where
|
|||||||
expect: ExpectHandler,
|
expect: ExpectHandler,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
on_connect: None,
|
on_connect: None,
|
||||||
|
on_connect_ext: None,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,6 +216,7 @@ where
|
|||||||
srv: self.srv,
|
srv: self.srv,
|
||||||
upgrade: self.upgrade,
|
upgrade: self.upgrade,
|
||||||
on_connect: self.on_connect,
|
on_connect: self.on_connect,
|
||||||
|
on_connect_ext: self.on_connect_ext,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -229,6 +233,7 @@ where
|
|||||||
srv: self.srv,
|
srv: self.srv,
|
||||||
expect: self.expect,
|
expect: self.expect,
|
||||||
on_connect: self.on_connect,
|
on_connect: self.on_connect,
|
||||||
|
on_connect_ext: self.on_connect_ext,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,6 +246,12 @@ where
|
|||||||
self.on_connect = f;
|
self.on_connect = f;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set on connect callback.
|
||||||
|
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
|
||||||
|
self.on_connect_ext = f;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B, X, U> ServiceFactory for H1Service<T, S, B, X, U>
|
impl<T, S, B, X, U> ServiceFactory for H1Service<T, S, B, X, U>
|
||||||
@ -274,6 +285,7 @@ where
|
|||||||
expect: None,
|
expect: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
on_connect: self.on_connect.clone(),
|
on_connect: self.on_connect.clone(),
|
||||||
|
on_connect_ext: self.on_connect_ext.clone(),
|
||||||
cfg: Some(self.cfg.clone()),
|
cfg: Some(self.cfg.clone()),
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
@ -303,6 +315,7 @@ where
|
|||||||
expect: Option<X::Service>,
|
expect: Option<X::Service>,
|
||||||
upgrade: Option<U::Service>,
|
upgrade: Option<U::Service>,
|
||||||
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
cfg: Option<ServiceConfig>,
|
cfg: Option<ServiceConfig>,
|
||||||
_t: PhantomData<(T, B)>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
@ -352,23 +365,26 @@ where
|
|||||||
|
|
||||||
Poll::Ready(result.map(|service| {
|
Poll::Ready(result.map(|service| {
|
||||||
let this = self.as_mut().project();
|
let this = self.as_mut().project();
|
||||||
|
|
||||||
H1ServiceHandler::new(
|
H1ServiceHandler::new(
|
||||||
this.cfg.take().unwrap(),
|
this.cfg.take().unwrap(),
|
||||||
service,
|
service,
|
||||||
this.expect.take().unwrap(),
|
this.expect.take().unwrap(),
|
||||||
this.upgrade.take(),
|
this.upgrade.take(),
|
||||||
this.on_connect.clone(),
|
this.on_connect.clone(),
|
||||||
|
this.on_connect_ext.clone(),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Service` implementation for HTTP1 transport
|
/// `Service` implementation for HTTP/1 transport
|
||||||
pub struct H1ServiceHandler<T, S: Service, B, X: Service, U: Service> {
|
pub struct H1ServiceHandler<T, S: Service, B, X: Service, U: Service> {
|
||||||
srv: CloneableService<S>,
|
srv: CloneableService<S>,
|
||||||
expect: CloneableService<X>,
|
expect: CloneableService<X>,
|
||||||
upgrade: Option<CloneableService<U>>,
|
upgrade: Option<CloneableService<U>>,
|
||||||
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
_t: PhantomData<(T, B)>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
@ -390,6 +406,7 @@ where
|
|||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
) -> H1ServiceHandler<T, S, B, X, U> {
|
) -> H1ServiceHandler<T, S, B, X, U> {
|
||||||
H1ServiceHandler {
|
H1ServiceHandler {
|
||||||
srv: CloneableService::new(srv),
|
srv: CloneableService::new(srv),
|
||||||
@ -397,6 +414,7 @@ where
|
|||||||
upgrade: upgrade.map(CloneableService::new),
|
upgrade: upgrade.map(CloneableService::new),
|
||||||
cfg,
|
cfg,
|
||||||
on_connect,
|
on_connect,
|
||||||
|
on_connect_ext,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -462,11 +480,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
|
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
|
||||||
let on_connect = if let Some(ref on_connect) = self.on_connect {
|
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
||||||
Some(on_connect(&io))
|
|
||||||
} else {
|
let mut connect_extensions = Extensions::new();
|
||||||
None
|
if let Some(ref handler) = self.on_connect_ext {
|
||||||
};
|
// run on_connect_ext callback, populating connect extensions
|
||||||
|
handler(&io, &mut connect_extensions);
|
||||||
|
}
|
||||||
|
|
||||||
Dispatcher::new(
|
Dispatcher::new(
|
||||||
io,
|
io,
|
||||||
@ -474,7 +494,8 @@ where
|
|||||||
self.srv.clone(),
|
self.srv.clone(),
|
||||||
self.expect.clone(),
|
self.expect.clone(),
|
||||||
self.upgrade.clone(),
|
self.upgrade.clone(),
|
||||||
on_connect,
|
deprecated_on_connect,
|
||||||
|
connect_extensions,
|
||||||
addr,
|
addr,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ use crate::message::ResponseHead;
|
|||||||
use crate::payload::Payload;
|
use crate::payload::Payload;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
|
use crate::Extensions;
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 16_384;
|
const CHUNK_SIZE: usize = 16_384;
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ where
|
|||||||
service: CloneableService<S>,
|
service: CloneableService<S>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect: Option<Box<dyn DataFactory>>,
|
on_connect: Option<Box<dyn DataFactory>>,
|
||||||
|
on_connect_data: Extensions,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
ka_expire: Instant,
|
ka_expire: Instant,
|
||||||
@ -56,6 +58,7 @@ where
|
|||||||
service: CloneableService<S>,
|
service: CloneableService<S>,
|
||||||
connection: Connection<T, Bytes>,
|
connection: Connection<T, Bytes>,
|
||||||
on_connect: Option<Box<dyn DataFactory>>,
|
on_connect: Option<Box<dyn DataFactory>>,
|
||||||
|
on_connect_data: Extensions,
|
||||||
config: ServiceConfig,
|
config: ServiceConfig,
|
||||||
timeout: Option<Delay>,
|
timeout: Option<Delay>,
|
||||||
peer_addr: Option<net::SocketAddr>,
|
peer_addr: Option<net::SocketAddr>,
|
||||||
@ -82,6 +85,7 @@ where
|
|||||||
peer_addr,
|
peer_addr,
|
||||||
connection,
|
connection,
|
||||||
on_connect,
|
on_connect,
|
||||||
|
on_connect_data,
|
||||||
ka_expire,
|
ka_expire,
|
||||||
ka_timer,
|
ka_timer,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
@ -130,11 +134,15 @@ where
|
|||||||
head.headers = parts.headers.into();
|
head.headers = parts.headers.into();
|
||||||
head.peer_addr = this.peer_addr;
|
head.peer_addr = this.peer_addr;
|
||||||
|
|
||||||
|
// DEPRECATED
|
||||||
// set on_connect data
|
// set on_connect data
|
||||||
if let Some(ref on_connect) = this.on_connect {
|
if let Some(ref on_connect) = this.on_connect {
|
||||||
on_connect.set(&mut req.extensions_mut());
|
on_connect.set(&mut req.extensions_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// merge on_connect_ext data into request extensions
|
||||||
|
req.extensions_mut().drain_from(&mut this.on_connect_data);
|
||||||
|
|
||||||
actix_rt::spawn(ServiceResponse::<
|
actix_rt::spawn(ServiceResponse::<
|
||||||
S::Future,
|
S::Future,
|
||||||
S::Response,
|
S::Response,
|
||||||
|
@ -2,7 +2,7 @@ use std::future::Future;
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{net, rc};
|
use std::{net, rc::Rc};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite};
|
use actix_codec::{AsyncRead, AsyncWrite};
|
||||||
use actix_rt::net::TcpStream;
|
use actix_rt::net::TcpStream;
|
||||||
@ -23,6 +23,7 @@ use crate::error::{DispatchError, Error};
|
|||||||
use crate::helpers::DataFactory;
|
use crate::helpers::DataFactory;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
|
use crate::{ConnectCallback, Extensions};
|
||||||
|
|
||||||
use super::dispatcher::Dispatcher;
|
use super::dispatcher::Dispatcher;
|
||||||
|
|
||||||
@ -30,7 +31,8 @@ use super::dispatcher::Dispatcher;
|
|||||||
pub struct H2Service<T, S, B> {
|
pub struct H2Service<T, S, B> {
|
||||||
srv: S,
|
srv: S,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_t: PhantomData<(T, B)>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,19 +52,27 @@ where
|
|||||||
H2Service {
|
H2Service {
|
||||||
cfg,
|
cfg,
|
||||||
on_connect: None,
|
on_connect: None,
|
||||||
|
on_connect_ext: None,
|
||||||
srv: service.into_factory(),
|
srv: service.into_factory(),
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set on connect callback.
|
/// Set on connect callback.
|
||||||
|
|
||||||
pub(crate) fn on_connect(
|
pub(crate) fn on_connect(
|
||||||
mut self,
|
mut self,
|
||||||
f: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
f: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.on_connect = f;
|
self.on_connect = f;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set on connect callback.
|
||||||
|
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
|
||||||
|
self.on_connect_ext = f;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B> H2Service<TcpStream, S, B>
|
impl<S, B> H2Service<TcpStream, S, B>
|
||||||
@ -203,6 +213,7 @@ where
|
|||||||
fut: self.srv.new_service(()),
|
fut: self.srv.new_service(()),
|
||||||
cfg: Some(self.cfg.clone()),
|
cfg: Some(self.cfg.clone()),
|
||||||
on_connect: self.on_connect.clone(),
|
on_connect: self.on_connect.clone(),
|
||||||
|
on_connect_ext: self.on_connect_ext.clone(),
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,7 +225,8 @@ pub struct H2ServiceResponse<T, S: ServiceFactory, B> {
|
|||||||
#[pin]
|
#[pin]
|
||||||
fut: S::Future,
|
fut: S::Future,
|
||||||
cfg: Option<ServiceConfig>,
|
cfg: Option<ServiceConfig>,
|
||||||
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_t: PhantomData<(T, B)>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,6 +249,7 @@ where
|
|||||||
H2ServiceHandler::new(
|
H2ServiceHandler::new(
|
||||||
this.cfg.take().unwrap(),
|
this.cfg.take().unwrap(),
|
||||||
this.on_connect.clone(),
|
this.on_connect.clone(),
|
||||||
|
this.on_connect_ext.clone(),
|
||||||
service,
|
service,
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
@ -247,7 +260,8 @@ where
|
|||||||
pub struct H2ServiceHandler<T, S: Service, B> {
|
pub struct H2ServiceHandler<T, S: Service, B> {
|
||||||
srv: CloneableService<S>,
|
srv: CloneableService<S>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_t: PhantomData<(T, B)>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,12 +275,14 @@ where
|
|||||||
{
|
{
|
||||||
fn new(
|
fn new(
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
srv: S,
|
srv: S,
|
||||||
) -> H2ServiceHandler<T, S, B> {
|
) -> H2ServiceHandler<T, S, B> {
|
||||||
H2ServiceHandler {
|
H2ServiceHandler {
|
||||||
cfg,
|
cfg,
|
||||||
on_connect,
|
on_connect,
|
||||||
|
on_connect_ext,
|
||||||
srv: CloneableService::new(srv),
|
srv: CloneableService::new(srv),
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
@ -296,18 +312,21 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
|
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
|
||||||
let on_connect = if let Some(ref on_connect) = self.on_connect {
|
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
||||||
Some(on_connect(&io))
|
|
||||||
} else {
|
let mut connect_extensions = Extensions::new();
|
||||||
None
|
if let Some(ref handler) = self.on_connect_ext {
|
||||||
};
|
// run on_connect_ext callback, populating connect extensions
|
||||||
|
handler(&io, &mut connect_extensions);
|
||||||
|
}
|
||||||
|
|
||||||
H2ServiceHandlerResponse {
|
H2ServiceHandlerResponse {
|
||||||
state: State::Handshake(
|
state: State::Handshake(
|
||||||
Some(self.srv.clone()),
|
Some(self.srv.clone()),
|
||||||
Some(self.cfg.clone()),
|
Some(self.cfg.clone()),
|
||||||
addr,
|
addr,
|
||||||
on_connect,
|
deprecated_on_connect,
|
||||||
|
Some(connect_extensions),
|
||||||
server::handshake(io),
|
server::handshake(io),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -325,6 +344,7 @@ where
|
|||||||
Option<ServiceConfig>,
|
Option<ServiceConfig>,
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
Option<Box<dyn DataFactory>>,
|
Option<Box<dyn DataFactory>>,
|
||||||
|
Option<Extensions>,
|
||||||
Handshake<T, Bytes>,
|
Handshake<T, Bytes>,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -360,6 +380,7 @@ where
|
|||||||
ref mut config,
|
ref mut config,
|
||||||
ref peer_addr,
|
ref peer_addr,
|
||||||
ref mut on_connect,
|
ref mut on_connect,
|
||||||
|
ref mut on_connect_data,
|
||||||
ref mut handshake,
|
ref mut handshake,
|
||||||
) => match Pin::new(handshake).poll(cx) {
|
) => match Pin::new(handshake).poll(cx) {
|
||||||
Poll::Ready(Ok(conn)) => {
|
Poll::Ready(Ok(conn)) => {
|
||||||
@ -367,6 +388,7 @@ where
|
|||||||
srv.take().unwrap(),
|
srv.take().unwrap(),
|
||||||
conn,
|
conn,
|
||||||
on_connect.take(),
|
on_connect.take(),
|
||||||
|
on_connect_data.take().unwrap(),
|
||||||
config.take().unwrap(),
|
config.take().unwrap(),
|
||||||
None,
|
None,
|
||||||
*peer_addr,
|
*peer_addr,
|
||||||
|
@ -50,6 +50,7 @@ impl<'a> io::Write for Writer<'a> {
|
|||||||
self.0.extend_from_slice(buf);
|
self.0.extend_from_slice(buf);
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Basic http primitives for actix-net framework.
|
//! HTTP primitives for the Actix ecosystem.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
#![allow(
|
#![allow(
|
||||||
@ -7,6 +7,9 @@
|
|||||||
clippy::new_without_default,
|
clippy::new_without_default,
|
||||||
clippy::borrow_interior_mutable_const
|
clippy::borrow_interior_mutable_const
|
||||||
)]
|
)]
|
||||||
|
#![allow(clippy::manual_strip)] // Allow this to keep MSRV(1.42).
|
||||||
|
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||||
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
@ -77,3 +80,5 @@ pub enum Protocol {
|
|||||||
Http1,
|
Http1,
|
||||||
Http2,
|
Http2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
|
||||||
|
@ -554,8 +554,9 @@ impl ResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method calls provided closure with builder reference if value is
|
/// This method calls provided closure with builder reference if value is `true`.
|
||||||
/// true.
|
#[doc(hidden)]
|
||||||
|
#[deprecated = "Use an if statement."]
|
||||||
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
|
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut ResponseBuilder),
|
F: FnOnce(&mut ResponseBuilder),
|
||||||
@ -566,8 +567,9 @@ impl ResponseBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method calls provided closure with builder reference if value is
|
/// This method calls provided closure with builder reference if value is `Some`.
|
||||||
/// Some.
|
#[doc(hidden)]
|
||||||
|
#[deprecated = "Use an if-let construction."]
|
||||||
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
|
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
|
||||||
where
|
where
|
||||||
F: FnOnce(T, &mut ResponseBuilder),
|
F: FnOnce(T, &mut ResponseBuilder),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{fmt, net, rc};
|
use std::{fmt, net, rc::Rc};
|
||||||
|
|
||||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||||
use actix_rt::net::TcpStream;
|
use actix_rt::net::TcpStream;
|
||||||
@ -20,15 +20,17 @@ use crate::error::{DispatchError, Error};
|
|||||||
use crate::helpers::DataFactory;
|
use crate::helpers::DataFactory;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::Response;
|
use crate::response::Response;
|
||||||
use crate::{h1, h2::Dispatcher, Protocol};
|
use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol};
|
||||||
|
|
||||||
/// `ServiceFactory` HTTP1.1/HTTP2 transport implementation
|
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
|
||||||
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler<T>> {
|
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler<T>> {
|
||||||
srv: S,
|
srv: S,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
// DEPRECATED: in favor of on_connect_ext
|
||||||
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_t: PhantomData<(T, B)>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +68,7 @@ where
|
|||||||
expect: h1::ExpectHandler,
|
expect: h1::ExpectHandler,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
on_connect: None,
|
on_connect: None,
|
||||||
|
on_connect_ext: None,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,6 +84,7 @@ where
|
|||||||
expect: h1::ExpectHandler,
|
expect: h1::ExpectHandler,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
on_connect: None,
|
on_connect: None,
|
||||||
|
on_connect_ext: None,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,6 +117,7 @@ where
|
|||||||
srv: self.srv,
|
srv: self.srv,
|
||||||
upgrade: self.upgrade,
|
upgrade: self.upgrade,
|
||||||
on_connect: self.on_connect,
|
on_connect: self.on_connect,
|
||||||
|
on_connect_ext: self.on_connect_ext,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,6 +143,7 @@ where
|
|||||||
srv: self.srv,
|
srv: self.srv,
|
||||||
expect: self.expect,
|
expect: self.expect,
|
||||||
on_connect: self.on_connect,
|
on_connect: self.on_connect,
|
||||||
|
on_connect_ext: self.on_connect_ext,
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,11 +151,17 @@ where
|
|||||||
/// Set on connect callback.
|
/// Set on connect callback.
|
||||||
pub(crate) fn on_connect(
|
pub(crate) fn on_connect(
|
||||||
mut self,
|
mut self,
|
||||||
f: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
f: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.on_connect = f;
|
self.on_connect = f;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set connect callback with mutable access to request data container.
|
||||||
|
pub(crate) fn on_connect_ext(mut self, f: Option<Rc<ConnectCallback<T>>>) -> Self {
|
||||||
|
self.on_connect_ext = f;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
|
impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
|
||||||
@ -355,6 +367,7 @@ where
|
|||||||
expect: None,
|
expect: None,
|
||||||
upgrade: None,
|
upgrade: None,
|
||||||
on_connect: self.on_connect.clone(),
|
on_connect: self.on_connect.clone(),
|
||||||
|
on_connect_ext: self.on_connect_ext.clone(),
|
||||||
cfg: self.cfg.clone(),
|
cfg: self.cfg.clone(),
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
@ -378,7 +391,8 @@ pub struct HttpServiceResponse<
|
|||||||
fut_upg: Option<U::Future>,
|
fut_upg: Option<U::Future>,
|
||||||
expect: Option<X::Service>,
|
expect: Option<X::Service>,
|
||||||
upgrade: Option<U::Service>,
|
upgrade: Option<U::Service>,
|
||||||
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
_t: PhantomData<(T, B)>,
|
_t: PhantomData<(T, B)>,
|
||||||
}
|
}
|
||||||
@ -429,6 +443,7 @@ where
|
|||||||
.fut
|
.fut
|
||||||
.poll(cx)
|
.poll(cx)
|
||||||
.map_err(|e| log::error!("Init http service error: {:?}", e)));
|
.map_err(|e| log::error!("Init http service error: {:?}", e)));
|
||||||
|
|
||||||
Poll::Ready(result.map(|service| {
|
Poll::Ready(result.map(|service| {
|
||||||
let this = self.as_mut().project();
|
let this = self.as_mut().project();
|
||||||
HttpServiceHandler::new(
|
HttpServiceHandler::new(
|
||||||
@ -437,6 +452,7 @@ where
|
|||||||
this.expect.take().unwrap(),
|
this.expect.take().unwrap(),
|
||||||
this.upgrade.take(),
|
this.upgrade.take(),
|
||||||
this.on_connect.clone(),
|
this.on_connect.clone(),
|
||||||
|
this.on_connect_ext.clone(),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -448,7 +464,8 @@ pub struct HttpServiceHandler<T, S: Service, B, X: Service, U: Service> {
|
|||||||
expect: CloneableService<X>,
|
expect: CloneableService<X>,
|
||||||
upgrade: Option<CloneableService<U>>,
|
upgrade: Option<CloneableService<U>>,
|
||||||
cfg: ServiceConfig,
|
cfg: ServiceConfig,
|
||||||
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
_t: PhantomData<(T, B, X)>,
|
_t: PhantomData<(T, B, X)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,11 +486,13 @@ where
|
|||||||
srv: S,
|
srv: S,
|
||||||
expect: X,
|
expect: X,
|
||||||
upgrade: Option<U>,
|
upgrade: Option<U>,
|
||||||
on_connect: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||||
|
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||||
) -> HttpServiceHandler<T, S, B, X, U> {
|
) -> HttpServiceHandler<T, S, B, X, U> {
|
||||||
HttpServiceHandler {
|
HttpServiceHandler {
|
||||||
cfg,
|
cfg,
|
||||||
on_connect,
|
on_connect,
|
||||||
|
on_connect_ext,
|
||||||
srv: CloneableService::new(srv),
|
srv: CloneableService::new(srv),
|
||||||
expect: CloneableService::new(expect),
|
expect: CloneableService::new(expect),
|
||||||
upgrade: upgrade.map(CloneableService::new),
|
upgrade: upgrade.map(CloneableService::new),
|
||||||
@ -543,11 +562,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future {
|
fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future {
|
||||||
let on_connect = if let Some(ref on_connect) = self.on_connect {
|
let mut connect_extensions = Extensions::new();
|
||||||
Some(on_connect(&io))
|
|
||||||
} else {
|
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
||||||
None
|
if let Some(ref handler) = self.on_connect_ext {
|
||||||
};
|
handler(&io, &mut connect_extensions);
|
||||||
|
}
|
||||||
|
|
||||||
match proto {
|
match proto {
|
||||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||||
@ -555,10 +575,12 @@ where
|
|||||||
server::handshake(io),
|
server::handshake(io),
|
||||||
self.cfg.clone(),
|
self.cfg.clone(),
|
||||||
self.srv.clone(),
|
self.srv.clone(),
|
||||||
on_connect,
|
deprecated_on_connect,
|
||||||
|
connect_extensions,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
))),
|
))),
|
||||||
},
|
},
|
||||||
|
|
||||||
Protocol::Http1 => HttpServiceHandlerResponse {
|
Protocol::Http1 => HttpServiceHandlerResponse {
|
||||||
state: State::H1(h1::Dispatcher::new(
|
state: State::H1(h1::Dispatcher::new(
|
||||||
io,
|
io,
|
||||||
@ -566,7 +588,8 @@ where
|
|||||||
self.srv.clone(),
|
self.srv.clone(),
|
||||||
self.expect.clone(),
|
self.expect.clone(),
|
||||||
self.upgrade.clone(),
|
self.upgrade.clone(),
|
||||||
on_connect,
|
deprecated_on_connect,
|
||||||
|
connect_extensions,
|
||||||
peer_addr,
|
peer_addr,
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
@ -595,6 +618,7 @@ where
|
|||||||
ServiceConfig,
|
ServiceConfig,
|
||||||
CloneableService<S>,
|
CloneableService<S>,
|
||||||
Option<Box<dyn DataFactory>>,
|
Option<Box<dyn DataFactory>>,
|
||||||
|
Extensions,
|
||||||
Option<net::SocketAddr>,
|
Option<net::SocketAddr>,
|
||||||
)>,
|
)>,
|
||||||
),
|
),
|
||||||
@ -670,9 +694,16 @@ where
|
|||||||
} else {
|
} else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
let (_, cfg, srv, on_connect, peer_addr) = data.take().unwrap();
|
let (_, cfg, srv, on_connect, on_connect_data, peer_addr) =
|
||||||
|
data.take().unwrap();
|
||||||
self.set(State::H2(Dispatcher::new(
|
self.set(State::H2(Dispatcher::new(
|
||||||
srv, conn, on_connect, cfg, None, peer_addr,
|
srv,
|
||||||
|
conn,
|
||||||
|
on_connect,
|
||||||
|
on_connect_data,
|
||||||
|
cfg,
|
||||||
|
None,
|
||||||
|
peer_addr,
|
||||||
)));
|
)));
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ use std::ptr::copy_nonoverlapping;
|
|||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
// Holds a slice guaranteed to be shorter than 8 bytes
|
// Holds a slice guaranteed to be shorter than 8 bytes
|
||||||
struct ShortSlice<'a>(&'a mut [u8]);
|
struct ShortSlice<'a> {
|
||||||
|
inner: &'a mut [u8],
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ShortSlice<'a> {
|
impl<'a> ShortSlice<'a> {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
@ -12,10 +14,11 @@ impl<'a> ShortSlice<'a> {
|
|||||||
unsafe fn new(slice: &'a mut [u8]) -> Self {
|
unsafe fn new(slice: &'a mut [u8]) -> Self {
|
||||||
// Sanity check for debug builds
|
// Sanity check for debug builds
|
||||||
debug_assert!(slice.len() < 8);
|
debug_assert!(slice.len() < 8);
|
||||||
ShortSlice(slice)
|
ShortSlice { inner: slice }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
self.0.len()
|
self.inner.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +59,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
|
|||||||
fn xor_short(buf: ShortSlice<'_>, mask: u64) {
|
fn xor_short(buf: ShortSlice<'_>, mask: u64) {
|
||||||
// SAFETY: we know that a `ShortSlice` fits in a u64
|
// SAFETY: we know that a `ShortSlice` fits in a u64
|
||||||
unsafe {
|
unsafe {
|
||||||
let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len());
|
let (ptr, len) = (buf.inner.as_mut_ptr(), buf.len());
|
||||||
let mut b: u64 = 0;
|
let mut b: u64 = 0;
|
||||||
#[allow(trivial_casts)]
|
#[allow(trivial_casts)]
|
||||||
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
|
copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len);
|
||||||
@ -96,7 +99,13 @@ fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) {
|
|||||||
|
|
||||||
// SAFETY: we know the middle section is correctly aligned, and the outer
|
// SAFETY: we know the middle section is correctly aligned, and the outer
|
||||||
// sections are smaller than 8 bytes
|
// sections are smaller than 8 bytes
|
||||||
unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) }
|
unsafe {
|
||||||
|
(
|
||||||
|
ShortSlice::new(head),
|
||||||
|
cast_slice(mid),
|
||||||
|
ShortSlice::new(tail),
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// We didn't cross even one aligned boundary!
|
// We didn't cross even one aligned boundary!
|
||||||
|
|
||||||
|
@ -411,8 +411,10 @@ async fn test_h2_on_connect() {
|
|||||||
let srv = test_server(move || {
|
let srv = test_server(move || {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.on_connect(|_| 10usize)
|
.on_connect(|_| 10usize)
|
||||||
|
.on_connect_ext(|_, data| data.insert(20isize))
|
||||||
.h2(|req: Request| {
|
.h2(|req: Request| {
|
||||||
assert!(req.extensions().contains::<usize>());
|
assert!(req.extensions().contains::<usize>());
|
||||||
|
assert!(req.extensions().contains::<isize>());
|
||||||
ok::<_, ()>(Response::Ok().finish())
|
ok::<_, ()>(Response::Ok().finish())
|
||||||
})
|
})
|
||||||
.openssl(ssl_acceptor())
|
.openssl(ssl_acceptor())
|
||||||
|
@ -663,8 +663,10 @@ async fn test_h1_on_connect() {
|
|||||||
let srv = test_server(|| {
|
let srv = test_server(|| {
|
||||||
HttpService::build()
|
HttpService::build()
|
||||||
.on_connect(|_| 10usize)
|
.on_connect(|_| 10usize)
|
||||||
|
.on_connect_ext(|_, data| data.insert(20isize))
|
||||||
.h1(|req: Request| {
|
.h1(|req: Request| {
|
||||||
assert!(req.extensions().contains::<usize>());
|
assert!(req.extensions().contains::<usize>());
|
||||||
|
assert!(req.extensions().contains::<isize>());
|
||||||
future::ok::<_, ()>(Response::Ok().finish())
|
future::ok::<_, ()>(Response::Ok().finish())
|
||||||
})
|
})
|
||||||
.tcp()
|
.tcp()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
* Fix multipart consuming payload before header checks #1513
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0 - 2020-09-11
|
## 3.0.0 - 2020-09-11
|
||||||
|
@ -36,6 +36,9 @@ impl FromRequest for Multipart {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
ok(Multipart::new(req.headers(), payload.take()))
|
ok(match Multipart::boundary(req.headers()) {
|
||||||
|
Ok(boundary) => Multipart::from_boundary(boundary, payload.take()),
|
||||||
|
Err(err) => Multipart::from_error(err),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,26 +64,13 @@ impl Multipart {
|
|||||||
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin + 'static,
|
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin + 'static,
|
||||||
{
|
{
|
||||||
match Self::boundary(headers) {
|
match Self::boundary(headers) {
|
||||||
Ok(boundary) => Multipart {
|
Ok(boundary) => Multipart::from_boundary(boundary, stream),
|
||||||
error: None,
|
Err(err) => Multipart::from_error(err),
|
||||||
safety: Safety::new(),
|
|
||||||
inner: Some(Rc::new(RefCell::new(InnerMultipart {
|
|
||||||
boundary,
|
|
||||||
payload: PayloadRef::new(PayloadBuffer::new(Box::new(stream))),
|
|
||||||
state: InnerState::FirstBoundary,
|
|
||||||
item: InnerMultipartItem::None,
|
|
||||||
}))),
|
|
||||||
},
|
|
||||||
Err(err) => Multipart {
|
|
||||||
error: Some(err),
|
|
||||||
safety: Safety::new(),
|
|
||||||
inner: None,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract boundary info from headers.
|
/// Extract boundary info from headers.
|
||||||
fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> {
|
pub(crate) fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> {
|
||||||
if let Some(content_type) = headers.get(&header::CONTENT_TYPE) {
|
if let Some(content_type) = headers.get(&header::CONTENT_TYPE) {
|
||||||
if let Ok(content_type) = content_type.to_str() {
|
if let Ok(content_type) = content_type.to_str() {
|
||||||
if let Ok(ct) = content_type.parse::<mime::Mime>() {
|
if let Ok(ct) = content_type.parse::<mime::Mime>() {
|
||||||
@ -102,6 +89,32 @@ impl Multipart {
|
|||||||
Err(MultipartError::NoContentType)
|
Err(MultipartError::NoContentType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create multipart instance for given boundary and stream
|
||||||
|
pub(crate) fn from_boundary<S>(boundary: String, stream: S) -> Multipart
|
||||||
|
where
|
||||||
|
S: Stream<Item = Result<Bytes, PayloadError>> + Unpin + 'static,
|
||||||
|
{
|
||||||
|
Multipart {
|
||||||
|
error: None,
|
||||||
|
safety: Safety::new(),
|
||||||
|
inner: Some(Rc::new(RefCell::new(InnerMultipart {
|
||||||
|
boundary,
|
||||||
|
payload: PayloadRef::new(PayloadBuffer::new(Box::new(stream))),
|
||||||
|
state: InnerState::FirstBoundary,
|
||||||
|
item: InnerMultipartItem::None,
|
||||||
|
}))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create Multipart instance from MultipartError
|
||||||
|
pub(crate) fn from_error(err: MultipartError) -> Multipart {
|
||||||
|
Multipart {
|
||||||
|
error: Some(err),
|
||||||
|
safety: Safety::new(),
|
||||||
|
inner: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for Multipart {
|
impl Stream for Multipart {
|
||||||
@ -815,6 +828,8 @@ mod tests {
|
|||||||
use actix_http::h1::Payload;
|
use actix_http::h1::Payload;
|
||||||
use actix_utils::mpsc;
|
use actix_utils::mpsc;
|
||||||
use actix_web::http::header::{DispositionParam, DispositionType};
|
use actix_web::http::header::{DispositionParam, DispositionType};
|
||||||
|
use actix_web::test::TestRequest;
|
||||||
|
use actix_web::FromRequest;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::lazy;
|
use futures_util::future::lazy;
|
||||||
|
|
||||||
@ -1151,4 +1166,38 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(payload.buf.len(), 0);
|
assert_eq!(payload.buf.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_multipart_from_error() {
|
||||||
|
let err = MultipartError::NoContentType;
|
||||||
|
let mut multipart = Multipart::from_error(err);
|
||||||
|
assert!(multipart.next().await.unwrap().is_err())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_multipart_from_boundary() {
|
||||||
|
let (_, payload) = create_stream();
|
||||||
|
let (_, headers) = create_simple_request_with_header();
|
||||||
|
let boundary = Multipart::boundary(&headers);
|
||||||
|
assert!(boundary.is_ok());
|
||||||
|
let _ = Multipart::from_boundary(boundary.unwrap(), payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_multipart_payload_consumption() {
|
||||||
|
// with sample payload and HttpRequest with no headers
|
||||||
|
let (_, inner_payload) = Payload::create(false);
|
||||||
|
let mut payload = actix_web::dev::Payload::from(inner_payload);
|
||||||
|
let req = TestRequest::default().to_http_request();
|
||||||
|
|
||||||
|
// multipart should generate an error
|
||||||
|
let mut mp = Multipart::from_request(&req, &mut payload).await.unwrap();
|
||||||
|
assert!(mp.next().await.unwrap().is_err());
|
||||||
|
|
||||||
|
// and should not consume the payload
|
||||||
|
match payload {
|
||||||
|
actix_web::dev::Payload::H1(_) => {} //expected
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
* Upgrade `pin-project` to `1.0`.
|
||||||
|
|
||||||
## 3.0.0 - 2020-09-11
|
## 3.0.0 - 2020-09-11
|
||||||
* No significant changes from `3.0.0-beta.2`.
|
* No significant changes from `3.0.0-beta.2`.
|
||||||
|
@ -23,7 +23,7 @@ actix-codec = "0.3.0"
|
|||||||
bytes = "0.5.2"
|
bytes = "0.5.2"
|
||||||
futures-channel = { version = "0.3.5", default-features = false }
|
futures-channel = { version = "0.3.5", default-features = false }
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
futures-core = { version = "0.3.5", default-features = false }
|
||||||
pin-project = "0.4.17"
|
pin-project = "1.0.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "1.1.1"
|
actix-rt = "1.1.1"
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
## 0.4.0 - 2020-09-20
|
## 0.4.0 - 2020-09-20
|
||||||
* Added compile success and failure testing. [#1677]
|
* Added compile success and failure testing. [#1677]
|
||||||
* Add `route` macro for supporting multiple HTTP methods guards.
|
* Add `route` macro for supporting multiple HTTP methods guards. [#1674]
|
||||||
|
|
||||||
[#1677]: https://github.com/actix/actix-web/pull/1677
|
[#1677]: https://github.com/actix/actix-web/pull/1677
|
||||||
[#1674]: https://github.com/actix/actix-web/pull/1674
|
[#1674]: https://github.com/actix/actix-web/pull/1674
|
||||||
@ -21,47 +21,48 @@
|
|||||||
[#1559]: https://github.com/actix/actix-web/pull/1559
|
[#1559]: https://github.com/actix/actix-web/pull/1559
|
||||||
|
|
||||||
|
|
||||||
## [0.2.2] - 2020-05-23
|
## 0.2.2 - 2020-05-23
|
||||||
* Add resource middleware on actix-web-codegen [#1467]
|
* Add resource middleware on actix-web-codegen [#1467]
|
||||||
|
|
||||||
[#1467]: https://github.com/actix/actix-web/pull/1467
|
[#1467]: https://github.com/actix/actix-web/pull/1467
|
||||||
|
|
||||||
## [0.2.1] - 2020-02-25
|
|
||||||
|
## 0.2.1 - 2020-02-25
|
||||||
* Add `#[allow(missing_docs)]` attribute to generated structs [#1368]
|
* Add `#[allow(missing_docs)]` attribute to generated structs [#1368]
|
||||||
* Allow the handler function to be named as `config` [#1290]
|
* Allow the handler function to be named as `config` [#1290]
|
||||||
|
|
||||||
[#1368]: https://github.com/actix/actix-web/issues/1368
|
[#1368]: https://github.com/actix/actix-web/issues/1368
|
||||||
[#1290]: https://github.com/actix/actix-web/issues/1290
|
[#1290]: https://github.com/actix/actix-web/issues/1290
|
||||||
|
|
||||||
## [0.2.0] - 2019-12-13
|
|
||||||
|
|
||||||
|
## 0.2.0 - 2019-12-13
|
||||||
* Generate code for actix-web 2.0
|
* Generate code for actix-web 2.0
|
||||||
|
|
||||||
## [0.1.3] - 2019-10-14
|
|
||||||
|
|
||||||
|
## 0.1.3 - 2019-10-14
|
||||||
* Bump up `syn` & `quote` to 1.0
|
* Bump up `syn` & `quote` to 1.0
|
||||||
* Provide better error message
|
* Provide better error message
|
||||||
|
|
||||||
## [0.1.2] - 2019-06-04
|
|
||||||
|
|
||||||
|
## 0.1.2 - 2019-06-04
|
||||||
* Add macros for head, options, trace, connect and patch http methods
|
* Add macros for head, options, trace, connect and patch http methods
|
||||||
|
|
||||||
## [0.1.1] - 2019-06-01
|
|
||||||
|
|
||||||
|
## 0.1.1 - 2019-06-01
|
||||||
* Add syn "extra-traits" feature
|
* Add syn "extra-traits" feature
|
||||||
|
|
||||||
## [0.1.0] - 2019-05-18
|
|
||||||
|
|
||||||
|
## 0.1.0 - 2019-05-18
|
||||||
* Release
|
* Release
|
||||||
|
|
||||||
## [0.1.0-beta.1] - 2019-04-20
|
|
||||||
|
|
||||||
|
## 0.1.0-beta.1 - 2019-04-20
|
||||||
* Gen code for actix-web 1.0.0-beta.1
|
* Gen code for actix-web 1.0.0-beta.1
|
||||||
|
|
||||||
## [0.1.0-alpha.6] - 2019-04-14
|
|
||||||
|
|
||||||
|
## 0.1.0-alpha.6 - 2019-04-14
|
||||||
* Gen code for actix-web 1.0.0-alpha.6
|
* 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
|
||||||
|
@ -6,9 +6,8 @@ fn compile_macros() {
|
|||||||
t.compile_fail("tests/trybuild/simple-fail.rs");
|
t.compile_fail("tests/trybuild/simple-fail.rs");
|
||||||
|
|
||||||
t.pass("tests/trybuild/route-ok.rs");
|
t.pass("tests/trybuild/route-ok.rs");
|
||||||
t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
|
|
||||||
t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs");
|
|
||||||
|
|
||||||
|
test_route_duplicate_unexpected_method(&t);
|
||||||
test_route_missing_method(&t)
|
test_route_missing_method(&t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,3 +24,13 @@ fn test_route_missing_method(t: &trybuild::TestCases) {
|
|||||||
|
|
||||||
#[rustversion::nightly]
|
#[rustversion::nightly]
|
||||||
fn test_route_missing_method(_t: &trybuild::TestCases) {}
|
fn test_route_missing_method(_t: &trybuild::TestCases) {}
|
||||||
|
|
||||||
|
// FIXME: Re-test them on nightly once rust-lang/rust#77993 is fixed.
|
||||||
|
#[rustversion::not(nightly)]
|
||||||
|
fn test_route_duplicate_unexpected_method(t: &trybuild::TestCases) {
|
||||||
|
t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
|
||||||
|
t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustversion::nightly]
|
||||||
|
fn test_route_duplicate_unexpected_method(_t: &trybuild::TestCases) {}
|
||||||
|
@ -3,6 +3,20 @@
|
|||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.1 - 2020-10-30
|
||||||
|
### Changed
|
||||||
|
* Upgrade `base64` to `0.13`. [#1744]
|
||||||
|
* Deprecate `ClientRequest::{if_some, if_true}`. [#1760]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature
|
||||||
|
is enabled [#1737]
|
||||||
|
|
||||||
|
[#1737]: https://github.com/actix/actix-web/pull/1737
|
||||||
|
[#1760]: https://github.com/actix/actix-web/pull/1760
|
||||||
|
[#1744]: https://github.com/actix/actix-web/pull/1744
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0 - 2020-09-11
|
## 2.0.0 - 2020-09-11
|
||||||
### Changed
|
### Changed
|
||||||
* `Client::build` was renamed to `Client::builder`.
|
* `Client::build` was renamed to `Client::builder`.
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "awc"
|
name = "awc"
|
||||||
version = "2.0.0"
|
version = "2.0.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Async HTTP client library that uses the Actix runtime."
|
description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["actix", "http", "framework", "async", "web"]
|
keywords = ["actix", "http", "framework", "async", "web"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
@ -42,8 +42,9 @@ actix-service = "1.0.6"
|
|||||||
actix-http = "2.0.0"
|
actix-http = "2.0.0"
|
||||||
actix-rt = "1.0.0"
|
actix-rt = "1.0.0"
|
||||||
|
|
||||||
base64 = "0.12"
|
base64 = "0.13"
|
||||||
bytes = "0.5.3"
|
bytes = "0.5.3"
|
||||||
|
cfg-if = "1.0"
|
||||||
derive_more = "0.99.2"
|
derive_more = "0.99.2"
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
futures-core = { version = "0.3.5", default-features = false }
|
||||||
log =" 0.4"
|
log =" 0.4"
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
# Actix http client [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/awc) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
# awc (Actix Web Client)
|
||||||
|
|
||||||
An HTTP Client
|
> Async HTTP and WebSocket client library.
|
||||||
|
|
||||||
## Documentation & community resources
|
[](https://crates.io/crates/awc)
|
||||||
|
[](https://docs.rs/awc/2.0.1)
|
||||||
|

|
||||||
|
[](https://deps.rs/crate/awc/2.0.1)
|
||||||
|
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
* [User Guide](https://actix.rs/docs/)
|
## Documentation & Resources
|
||||||
* [API Documentation](https://docs.rs/awc/)
|
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
- [API Documentation](https://docs.rs/awc/2.0.1)
|
||||||
* Cargo package: [awc](https://crates.io/crates/awc)
|
- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https)
|
||||||
* Minimum supported Rust version: 1.40 or later
|
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||||
|
- Minimum Supported Rust Version (MSRV): 1.42.0
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
#![deny(rust_2018_idioms)]
|
//! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem.
|
||||||
#![allow(
|
|
||||||
clippy::type_complexity,
|
|
||||||
clippy::borrow_interior_mutable_const,
|
|
||||||
clippy::needless_doctest_main
|
|
||||||
)]
|
|
||||||
|
|
||||||
//! `awc` is a HTTP and WebSocket client library built using the Actix ecosystem.
|
|
||||||
//!
|
//!
|
||||||
//! ## Making a GET request
|
//! ## Making a GET request
|
||||||
//!
|
//!
|
||||||
@ -91,6 +84,15 @@
|
|||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
#![deny(rust_2018_idioms)]
|
||||||
|
#![allow(
|
||||||
|
clippy::type_complexity,
|
||||||
|
clippy::borrow_interior_mutable_const,
|
||||||
|
clippy::needless_doctest_main
|
||||||
|
)]
|
||||||
|
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||||
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@ -21,10 +21,15 @@ use crate::frozen::FrozenClientRequest;
|
|||||||
use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest};
|
use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest};
|
||||||
use crate::ClientConfig;
|
use crate::ClientConfig;
|
||||||
|
|
||||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
cfg_if::cfg_if! {
|
||||||
const HTTPS_ENCODING: &str = "br, gzip, deflate";
|
if #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] {
|
||||||
#[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))]
|
const HTTPS_ENCODING: &str = "br, gzip, deflate";
|
||||||
const HTTPS_ENCODING: &str = "br";
|
} else if #[cfg(feature = "compress")] {
|
||||||
|
const HTTPS_ENCODING: &str = "br";
|
||||||
|
} else {
|
||||||
|
const HTTPS_ENCODING: &str = "identity";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An HTTP Client request builder
|
/// An HTTP Client request builder
|
||||||
///
|
///
|
||||||
@ -349,8 +354,9 @@ impl ClientRequest {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method calls provided closure with builder reference if
|
/// This method calls provided closure with builder reference if value is `true`.
|
||||||
/// value is `true`.
|
#[doc(hidden)]
|
||||||
|
#[deprecated = "Use an if statement."]
|
||||||
pub fn if_true<F>(self, value: bool, f: F) -> Self
|
pub fn if_true<F>(self, value: bool, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: FnOnce(ClientRequest) -> ClientRequest,
|
F: FnOnce(ClientRequest) -> ClientRequest,
|
||||||
@ -362,8 +368,9 @@ impl ClientRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method calls provided closure with builder reference if
|
/// This method calls provided closure with builder reference if value is `Some`.
|
||||||
/// value is `Some`.
|
#[doc(hidden)]
|
||||||
|
#[deprecated = "Use an if-let construction."]
|
||||||
pub fn if_some<T, F>(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, ClientRequest) -> ClientRequest,
|
F: FnOnce(T, ClientRequest) -> ClientRequest,
|
||||||
@ -596,20 +603,27 @@ mod tests {
|
|||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_basics() {
|
async fn test_basics() {
|
||||||
let mut req = Client::new()
|
let req = Client::new()
|
||||||
.put("/")
|
.put("/")
|
||||||
.version(Version::HTTP_2)
|
.version(Version::HTTP_2)
|
||||||
.set(header::Date(SystemTime::now().into()))
|
.set(header::Date(SystemTime::now().into()))
|
||||||
.content_type("plain/text")
|
.content_type("plain/text")
|
||||||
.if_true(true, |req| req.header(header::SERVER, "awc"))
|
.header(header::SERVER, "awc");
|
||||||
.if_true(false, |req| req.header(header::EXPECT, "awc"))
|
|
||||||
.if_some(Some("server"), |val, req| {
|
let req = if let Some(val) = Some("server") {
|
||||||
req.header(header::USER_AGENT, val)
|
req.header(header::USER_AGENT, val)
|
||||||
})
|
} else {
|
||||||
.if_some(Option::<&str>::None, |_, req| {
|
req
|
||||||
req.header(header::ALLOW, "1")
|
};
|
||||||
})
|
|
||||||
.content_length(100);
|
let req = if let Some(_val) = Option::<&str>::None {
|
||||||
|
req.header(header::ALLOW, "1")
|
||||||
|
} else {
|
||||||
|
req
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut req = req.content_length(100);
|
||||||
|
|
||||||
assert!(req.headers().contains_key(header::CONTENT_TYPE));
|
assert!(req.headers().contains_key(header::CONTENT_TYPE));
|
||||||
assert!(req.headers().contains_key(header::DATE));
|
assert!(req.headers().contains_key(header::DATE));
|
||||||
assert!(req.headers().contains_key(header::SERVER));
|
assert!(req.headers().contains_key(header::SERVER));
|
||||||
@ -617,6 +631,7 @@ mod tests {
|
|||||||
assert!(!req.headers().contains_key(header::ALLOW));
|
assert!(!req.headers().contains_key(header::ALLOW));
|
||||||
assert!(!req.headers().contains_key(header::EXPECT));
|
assert!(!req.headers().contains_key(header::EXPECT));
|
||||||
assert_eq!(req.head.version, Version::HTTP_2);
|
assert_eq!(req.head.version, Version::HTTP_2);
|
||||||
|
|
||||||
let _ = req.headers_mut();
|
let _ = req.headers_mut();
|
||||||
let _ = req.send_body("");
|
let _ = req.send_body("");
|
||||||
}
|
}
|
||||||
|
51
examples/on_connect.rs
Normal file
51
examples/on_connect.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//! This example shows how to use `actix_web::HttpServer::on_connect` to access a lower-level socket
|
||||||
|
//! properties and pass them to a handler through request-local data.
|
||||||
|
//!
|
||||||
|
//! For an example of extracting a client TLS certificate, see:
|
||||||
|
//! <https://github.com/actix/examples/tree/HEAD/rustls-client-cert>
|
||||||
|
|
||||||
|
use std::{any::Any, env, io, net::SocketAddr};
|
||||||
|
|
||||||
|
use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ConnectionInfo {
|
||||||
|
bind: SocketAddr,
|
||||||
|
peer: SocketAddr,
|
||||||
|
ttl: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn route_whoami(conn_info: web::ReqData<ConnectionInfo>) -> String {
|
||||||
|
format!(
|
||||||
|
"Here is some info about your connection:\n\n{:#?}",
|
||||||
|
conn_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_conn_info(connection: &dyn Any, data: &mut Extensions) {
|
||||||
|
if let Some(sock) = connection.downcast_ref::<TcpStream>() {
|
||||||
|
data.insert(ConnectionInfo {
|
||||||
|
bind: sock.local_addr().unwrap(),
|
||||||
|
peer: sock.peer_addr().unwrap(),
|
||||||
|
ttl: sock.ttl().ok(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
unreachable!("connection should only be plaintext since no TLS is set up");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> io::Result<()> {
|
||||||
|
if env::var("RUST_LOG").is_err() {
|
||||||
|
env::set_var("RUST_LOG", "info");
|
||||||
|
}
|
||||||
|
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
HttpServer::new(|| App::new().default_service(web::to(route_whoami)))
|
||||||
|
.on_connect(get_conn_info)
|
||||||
|
.bind(("127.0.0.1", 8080))?
|
||||||
|
.workers(1)
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
1.42.0
|
|
@ -183,6 +183,7 @@ where
|
|||||||
self.data.extend(cfg.data);
|
self.data.extend(cfg.data);
|
||||||
self.services.extend(cfg.services);
|
self.services.extend(cfg.services);
|
||||||
self.external.extend(cfg.external);
|
self.external.extend(cfg.external);
|
||||||
|
self.extensions.extend(cfg.extensions);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,8 +460,8 @@ where
|
|||||||
{
|
{
|
||||||
fn into_factory(self) -> AppInit<T, B> {
|
fn into_factory(self) -> AppInit<T, B> {
|
||||||
AppInit {
|
AppInit {
|
||||||
data: Rc::new(self.data),
|
data: self.data.into_boxed_slice().into(),
|
||||||
data_factories: Rc::new(self.data_factories),
|
data_factories: self.data_factories.into_boxed_slice().into(),
|
||||||
endpoint: self.endpoint,
|
endpoint: self.endpoint,
|
||||||
services: Rc::new(RefCell::new(self.services)),
|
services: Rc::new(RefCell::new(self.services)),
|
||||||
external: RefCell::new(self.external),
|
external: RefCell::new(self.external),
|
||||||
|
@ -39,8 +39,8 @@ where
|
|||||||
{
|
{
|
||||||
pub(crate) endpoint: T,
|
pub(crate) endpoint: T,
|
||||||
pub(crate) extensions: RefCell<Option<Extensions>>,
|
pub(crate) extensions: RefCell<Option<Extensions>>,
|
||||||
pub(crate) data: Rc<Vec<Box<dyn DataFactory>>>,
|
pub(crate) data: Rc<[Box<dyn DataFactory>]>,
|
||||||
pub(crate) data_factories: Rc<Vec<FnDataFactory>>,
|
pub(crate) data_factories: Rc<[FnDataFactory]>,
|
||||||
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
||||||
pub(crate) default: Option<Rc<HttpNewService>>,
|
pub(crate) default: Option<Rc<HttpNewService>>,
|
||||||
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||||
@ -88,15 +88,15 @@ where
|
|||||||
// 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: services
|
||||||
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);
|
(rdef, srv, RefCell::new(guards))
|
||||||
(rdef, srv, RefCell::new(guards))
|
})
|
||||||
})
|
.collect::<Vec<_>>()
|
||||||
.collect(),
|
.into_boxed_slice()
|
||||||
),
|
.into(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// external resources
|
// external resources
|
||||||
@ -147,7 +147,7 @@ where
|
|||||||
|
|
||||||
rmap: Rc<ResourceMap>,
|
rmap: Rc<ResourceMap>,
|
||||||
config: AppConfig,
|
config: AppConfig,
|
||||||
data: Rc<Vec<Box<dyn DataFactory>>>,
|
data: Rc<[Box<dyn DataFactory>]>,
|
||||||
extensions: Option<Extensions>,
|
extensions: Option<Extensions>,
|
||||||
|
|
||||||
_t: PhantomData<B>,
|
_t: PhantomData<B>,
|
||||||
@ -273,7 +273,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppRoutingFactory {
|
pub struct AppRoutingFactory {
|
||||||
services: Rc<Vec<(ResourceDef, HttpNewService, RefCell<Option<Guards>>)>>,
|
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
|
||||||
default: Rc<HttpNewService>,
|
default: Rc<HttpNewService>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ pub struct AppService {
|
|||||||
Option<Guards>,
|
Option<Guards>,
|
||||||
Option<Rc<ResourceMap>>,
|
Option<Rc<ResourceMap>>,
|
||||||
)>,
|
)>,
|
||||||
service_data: Rc<Vec<Box<dyn DataFactory>>>,
|
service_data: Rc<[Box<dyn DataFactory>]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppService {
|
impl AppService {
|
||||||
@ -39,7 +39,7 @@ impl AppService {
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
config: AppConfig,
|
config: AppConfig,
|
||||||
default: Rc<HttpNewService>,
|
default: Rc<HttpNewService>,
|
||||||
service_data: Rc<Vec<Box<dyn DataFactory>>>,
|
service_data: Rc<[Box<dyn DataFactory>]>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
AppService {
|
AppService {
|
||||||
config,
|
config,
|
||||||
@ -178,6 +178,7 @@ pub struct ServiceConfig {
|
|||||||
pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
|
pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
|
||||||
pub(crate) data: Vec<Box<dyn DataFactory>>,
|
pub(crate) data: Vec<Box<dyn DataFactory>>,
|
||||||
pub(crate) external: Vec<ResourceDef>,
|
pub(crate) external: Vec<ResourceDef>,
|
||||||
|
pub(crate) extensions: Extensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceConfig {
|
impl ServiceConfig {
|
||||||
@ -186,6 +187,7 @@ impl ServiceConfig {
|
|||||||
services: Vec::new(),
|
services: Vec::new(),
|
||||||
data: Vec::new(),
|
data: Vec::new(),
|
||||||
external: Vec::new(),
|
external: Vec::new(),
|
||||||
|
extensions: Extensions::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,6 +200,14 @@ impl ServiceConfig {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set arbitrary data item.
|
||||||
|
///
|
||||||
|
/// This is same as `App::data()` method.
|
||||||
|
pub fn app_data<U: 'static>(&mut self, ext: U) -> &mut Self {
|
||||||
|
self.extensions.insert(ext);
|
||||||
|
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.
|
||||||
@ -254,13 +264,16 @@ mod tests {
|
|||||||
async fn test_data() {
|
async fn test_data() {
|
||||||
let cfg = |cfg: &mut ServiceConfig| {
|
let cfg = |cfg: &mut ServiceConfig| {
|
||||||
cfg.data(10usize);
|
cfg.data(10usize);
|
||||||
|
cfg.app_data(15u8);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut srv =
|
let mut srv = init_service(App::new().configure(cfg).service(
|
||||||
init_service(App::new().configure(cfg).service(
|
web::resource("/").to(|_: web::Data<usize>, req: HttpRequest| {
|
||||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
assert_eq!(*req.app_data::<u8>().unwrap(), 15u8);
|
||||||
))
|
HttpResponse::Ok()
|
||||||
.await;
|
}),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
let req = TestRequest::default().to_request();
|
let req = TestRequest::default().to_request();
|
||||||
let resp = srv.call(req).await.unwrap();
|
let resp = srv.call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
71
src/data.rs
71
src/data.rs
@ -1,3 +1,4 @@
|
|||||||
|
use std::any::type_name;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -19,25 +20,20 @@ pub(crate) type FnDataFactory =
|
|||||||
|
|
||||||
/// Application data.
|
/// Application data.
|
||||||
///
|
///
|
||||||
/// Application data is an arbitrary data attached to the app.
|
/// Application level data is a piece of arbitrary data attached to the app, scope, or resource.
|
||||||
/// Application data is available to all routes and could be added
|
/// Application data is available to all routes and can be added during the application
|
||||||
/// during application configuration process
|
/// configuration process via `App::data()`.
|
||||||
/// with `App::data()` method.
|
|
||||||
///
|
///
|
||||||
/// Application data could be accessed by using `Data<T>`
|
/// Application data can be accessed by using `Data<T>` extractor where `T` is data type.
|
||||||
/// extractor where `T` is data type.
|
|
||||||
///
|
///
|
||||||
/// **Note**: http server accepts an application factory rather than
|
/// **Note**: http server accepts an application factory rather than an application instance. HTTP
|
||||||
/// an application instance. Http server constructs an application
|
/// server constructs an application instance for each thread, thus application data must be
|
||||||
/// instance for each thread, thus application data must be constructed
|
/// constructed multiple times. If you want to share data between different threads, a shareable
|
||||||
/// multiple times. If you want to share data between different
|
/// object should be used, e.g. `Send + Sync`. Application data does not need to be `Send`
|
||||||
/// threads, a shareable object should be used, e.g. `Send + Sync`. Application
|
/// or `Sync`. Internally `Data` uses `Arc`.
|
||||||
/// data does not need to be `Send` or `Sync`. Internally `Data` type
|
|
||||||
/// 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
|
||||||
/// cause *Internal Server Error* response.
|
/// Server Error* response.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use std::sync::Mutex;
|
/// use std::sync::Mutex;
|
||||||
@ -47,7 +43,7 @@ pub(crate) type FnDataFactory =
|
|||||||
/// counter: usize,
|
/// counter: usize,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// /// Use `Data<T>` extractor to access data in handler.
|
/// /// Use the `Data<T>` extractor to access data in a handler.
|
||||||
/// async fn index(data: web::Data<Mutex<MyData>>) -> impl Responder {
|
/// async fn index(data: web::Data<Mutex<MyData>>) -> impl Responder {
|
||||||
/// let mut data = data.lock().unwrap();
|
/// let mut data = data.lock().unwrap();
|
||||||
/// data.counter += 1;
|
/// data.counter += 1;
|
||||||
@ -66,14 +62,10 @@ pub(crate) type FnDataFactory =
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Data<T>(Arc<T>);
|
pub struct Data<T: ?Sized>(Arc<T>);
|
||||||
|
|
||||||
impl<T> Data<T> {
|
impl<T> Data<T> {
|
||||||
/// Create new `Data` instance.
|
/// 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> {
|
pub fn new(state: T) -> Data<T> {
|
||||||
Data(Arc::new(state))
|
Data(Arc::new(state))
|
||||||
}
|
}
|
||||||
@ -89,7 +81,7 @@ impl<T> Data<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref for Data<T> {
|
impl<T: ?Sized> Deref for Data<T> {
|
||||||
type Target = Arc<T>;
|
type Target = Arc<T>;
|
||||||
|
|
||||||
fn deref(&self) -> &Arc<T> {
|
fn deref(&self) -> &Arc<T> {
|
||||||
@ -97,19 +89,19 @@ impl<T> Deref for Data<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Clone for Data<T> {
|
impl<T: ?Sized> Clone for Data<T> {
|
||||||
fn clone(&self) -> Data<T> {
|
fn clone(&self) -> Data<T> {
|
||||||
Data(self.0.clone())
|
Data(self.0.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<Arc<T>> for Data<T> {
|
impl<T: ?Sized> From<Arc<T>> for Data<T> {
|
||||||
fn from(arc: Arc<T>) -> Self {
|
fn from(arc: Arc<T>) -> Self {
|
||||||
Data(arc)
|
Data(arc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> FromRequest for Data<T> {
|
impl<T: ?Sized + 'static> FromRequest for Data<T> {
|
||||||
type Config = ();
|
type Config = ();
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<Self, Error>>;
|
type Future = Ready<Result<Self, Error>>;
|
||||||
@ -121,8 +113,9 @@ impl<T: 'static> FromRequest for Data<T> {
|
|||||||
} else {
|
} else {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Failed to construct App-level Data extractor. \
|
"Failed to construct App-level Data extractor. \
|
||||||
Request path: {:?}",
|
Request path: {:?} (type: {})",
|
||||||
req.path()
|
req.path(),
|
||||||
|
type_name::<T>(),
|
||||||
);
|
);
|
||||||
err(ErrorInternalServerError(
|
err(ErrorInternalServerError(
|
||||||
"App data is not configured, to configure use App::data()",
|
"App data is not configured, to configure use App::data()",
|
||||||
@ -131,7 +124,7 @@ impl<T: 'static> FromRequest for Data<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> DataFactory for Data<T> {
|
impl<T: ?Sized + 'static> DataFactory for Data<T> {
|
||||||
fn create(&self, extensions: &mut Extensions) -> bool {
|
fn create(&self, extensions: &mut Extensions) -> bool {
|
||||||
if !extensions.contains::<Data<T>>() {
|
if !extensions.contains::<Data<T>>() {
|
||||||
extensions.insert(Data(self.0.clone()));
|
extensions.insert(Data(self.0.clone()));
|
||||||
@ -293,4 +286,24 @@ mod tests {
|
|||||||
let data_from_arc = Data::from(Arc::new(String::from("test-123")));
|
let data_from_arc = Data::from(Arc::new(String::from("test-123")));
|
||||||
assert_eq!(data_new.0, data_from_arc.0)
|
assert_eq!(data_new.0, data_from_arc.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_data_from_dyn_arc() {
|
||||||
|
trait TestTrait {
|
||||||
|
fn get_num(&self) -> i32;
|
||||||
|
}
|
||||||
|
struct A {}
|
||||||
|
impl TestTrait for A {
|
||||||
|
fn get_num(&self) -> i32 {
|
||||||
|
42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This works when Sized is required
|
||||||
|
let dyn_arc_box: Arc<Box<dyn TestTrait>> = Arc::new(Box::new(A {}));
|
||||||
|
let data_arc_box = Data::from(dyn_arc_box);
|
||||||
|
// This works when Data Sized Bound is removed
|
||||||
|
let dyn_arc: Arc<dyn TestTrait> = Arc::new(A {});
|
||||||
|
let data_arc = Data::from(dyn_arc);
|
||||||
|
assert_eq!(data_arc_box.get_num(), data_arc.get_num())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ mod handler;
|
|||||||
mod info;
|
mod info;
|
||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
mod request;
|
mod request;
|
||||||
|
mod request_data;
|
||||||
mod resource;
|
mod resource;
|
||||||
mod responder;
|
mod responder;
|
||||||
mod rmap;
|
mod rmap;
|
||||||
|
@ -13,7 +13,7 @@ use actix_service::{Service, Transform};
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::{ok, Ready};
|
use futures_util::future::{ok, Ready};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use regex::Regex;
|
use regex::{Regex, RegexSet};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::dev::{BodySize, MessageBody, ResponseBody};
|
use crate::dev::{BodySize, MessageBody, ResponseBody};
|
||||||
@ -34,21 +34,19 @@ use crate::HttpResponse;
|
|||||||
/// Default `Logger` could be created with `default` method, it uses the
|
/// Default `Logger` could be created with `default` method, it uses the
|
||||||
/// default format:
|
/// default format:
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```plain
|
||||||
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::middleware::Logger;
|
/// use actix_web::{middleware::Logger, App};
|
||||||
/// use actix_web::App;
|
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
/// std::env::set_var("RUST_LOG", "actix_web=info");
|
/// env_logger::init();
|
||||||
/// env_logger::init();
|
|
||||||
///
|
///
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .wrap(Logger::default())
|
/// .wrap(Logger::default())
|
||||||
/// .wrap(Logger::new("%a %{User-Agent}i"));
|
/// .wrap(Logger::new("%a %{User-Agent}i"));
|
||||||
/// }
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Format
|
/// ## Format
|
||||||
@ -80,6 +78,8 @@ use crate::HttpResponse;
|
|||||||
///
|
///
|
||||||
/// `%{FOO}e` os.environ['FOO']
|
/// `%{FOO}e` os.environ['FOO']
|
||||||
///
|
///
|
||||||
|
/// `%{FOO}xi` [custom request replacement](Logger::custom_request_replace) labelled "FOO"
|
||||||
|
///
|
||||||
/// # Security
|
/// # Security
|
||||||
/// **\*** It is calculated using
|
/// **\*** It is calculated using
|
||||||
/// [`ConnectionInfo::realip_remote_addr()`](../dev/struct.ConnectionInfo.html#method.realip_remote_addr)
|
/// [`ConnectionInfo::realip_remote_addr()`](../dev/struct.ConnectionInfo.html#method.realip_remote_addr)
|
||||||
@ -92,6 +92,7 @@ pub struct Logger(Rc<Inner>);
|
|||||||
struct Inner {
|
struct Inner {
|
||||||
format: Format,
|
format: Format,
|
||||||
exclude: HashSet<String>,
|
exclude: HashSet<String>,
|
||||||
|
exclude_regex: RegexSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
@ -100,6 +101,7 @@ impl Logger {
|
|||||||
Logger(Rc::new(Inner {
|
Logger(Rc::new(Inner {
|
||||||
format: Format::new(format),
|
format: Format::new(format),
|
||||||
exclude: HashSet::new(),
|
exclude: HashSet::new(),
|
||||||
|
exclude_regex: RegexSet::empty(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,18 +113,69 @@ impl Logger {
|
|||||||
.insert(path.into());
|
.insert(path.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ignore and do not log access info for paths that match regex
|
||||||
|
pub fn exclude_regex<T: Into<String>>(mut self, path: T) -> Self {
|
||||||
|
let inner = Rc::get_mut(&mut self.0).unwrap();
|
||||||
|
let mut patterns = inner.exclude_regex.patterns().to_vec();
|
||||||
|
patterns.push(path.into());
|
||||||
|
let regex_set = RegexSet::new(patterns).unwrap();
|
||||||
|
inner.exclude_regex = regex_set;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a function that receives a ServiceRequest and returns a String for use in the
|
||||||
|
/// log line. The label passed as the first argument should match a replacement substring in
|
||||||
|
/// the logger format like `%{label}xi`.
|
||||||
|
///
|
||||||
|
/// It is convention to print "-" to indicate no output instead of an empty string.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// # use actix_web::{http::HeaderValue, middleware::Logger};
|
||||||
|
/// # fn parse_jwt_id (_req: Option<&HeaderValue>) -> String { "jwt_uid".to_owned() }
|
||||||
|
/// Logger::new("example %{JWT_ID}xi")
|
||||||
|
/// .custom_request_replace("JWT_ID", |req| parse_jwt_id(req.headers().get("Authorization")));
|
||||||
|
/// ```
|
||||||
|
pub fn custom_request_replace(
|
||||||
|
mut self,
|
||||||
|
label: &str,
|
||||||
|
f: impl Fn(&ServiceRequest) -> String + 'static,
|
||||||
|
) -> Self {
|
||||||
|
let inner = Rc::get_mut(&mut self.0).unwrap();
|
||||||
|
|
||||||
|
let ft = inner.format.0.iter_mut().find(|ft| {
|
||||||
|
matches!(ft, FormatText::CustomRequest(unit_label, _) if label == unit_label)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(FormatText::CustomRequest(_, request_fn)) = ft {
|
||||||
|
// replace into None or previously registered fn using same label
|
||||||
|
request_fn.replace(CustomRequestFn {
|
||||||
|
inner_fn: Rc::new(f),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// non-printed request replacement function diagnostic
|
||||||
|
debug!(
|
||||||
|
"Attempted to register custom request logging function for nonexistent label: {}",
|
||||||
|
label
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Logger {
|
impl Default for Logger {
|
||||||
/// Create `Logger` middleware with format:
|
/// Create `Logger` middleware with format:
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```plain
|
||||||
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||||
/// ```
|
/// ```
|
||||||
fn default() -> Logger {
|
fn default() -> Logger {
|
||||||
Logger(Rc::new(Inner {
|
Logger(Rc::new(Inner {
|
||||||
format: Format::default(),
|
format: Format::default(),
|
||||||
exclude: HashSet::new(),
|
exclude: HashSet::new(),
|
||||||
|
exclude_regex: RegexSet::empty(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,6 +193,17 @@ where
|
|||||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
fn new_transform(&self, service: S) -> Self::Future {
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
|
for unit in &self.0.format.0 {
|
||||||
|
// missing request replacement function diagnostic
|
||||||
|
if let FormatText::CustomRequest(label, None) = unit {
|
||||||
|
debug!(
|
||||||
|
"No custom request replacement function was registered for label {} in\
|
||||||
|
logger format.",
|
||||||
|
label
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ok(LoggerMiddleware {
|
ok(LoggerMiddleware {
|
||||||
service,
|
service,
|
||||||
inner: self.0.clone(),
|
inner: self.0.clone(),
|
||||||
@ -168,7 +232,9 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
||||||
if self.inner.exclude.contains(req.path()) {
|
if self.inner.exclude.contains(req.path())
|
||||||
|
|| self.inner.exclude_regex.is_match(req.path())
|
||||||
|
{
|
||||||
LoggerResponse {
|
LoggerResponse {
|
||||||
fut: self.service.call(req),
|
fut: self.service.call(req),
|
||||||
format: None,
|
format: None,
|
||||||
@ -296,7 +362,6 @@ impl<B: MessageBody> MessageBody for StreamLog<B> {
|
|||||||
/// A formatting style for the `Logger`, consisting of multiple
|
/// A formatting style for the `Logger`, consisting of multiple
|
||||||
/// `FormatText`s concatenated into one line.
|
/// `FormatText`s concatenated into one line.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[doc(hidden)]
|
|
||||||
struct Format(Vec<FormatText>);
|
struct Format(Vec<FormatText>);
|
||||||
|
|
||||||
impl Default for Format {
|
impl Default for Format {
|
||||||
@ -312,7 +377,8 @@ impl Format {
|
|||||||
/// Returns `None` if the format string syntax is incorrect.
|
/// Returns `None` if the format string syntax is incorrect.
|
||||||
pub fn new(s: &str) -> Format {
|
pub fn new(s: &str) -> Format {
|
||||||
log::trace!("Access log format: {}", s);
|
log::trace!("Access log format: {}", s);
|
||||||
let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe])|[atPrUsbTD]?)").unwrap();
|
let fmt =
|
||||||
|
Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe]|xi)|[atPrUsbTD]?)").unwrap();
|
||||||
|
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
@ -340,6 +406,7 @@ impl Format {
|
|||||||
HeaderName::try_from(key.as_str()).unwrap(),
|
HeaderName::try_from(key.as_str()).unwrap(),
|
||||||
),
|
),
|
||||||
"e" => FormatText::EnvironHeader(key.as_str().to_owned()),
|
"e" => FormatText::EnvironHeader(key.as_str().to_owned()),
|
||||||
|
"xi" => FormatText::CustomRequest(key.as_str().to_owned(), None),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -369,7 +436,9 @@ impl Format {
|
|||||||
/// A string of text to be logged. This is either one of the data
|
/// A string of text to be logged. This is either one of the data
|
||||||
/// fields supported by the `Logger`, or a custom `String`.
|
/// fields supported by the `Logger`, or a custom `String`.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
// TODO: remove pub on next breaking change
|
||||||
pub enum FormatText {
|
pub enum FormatText {
|
||||||
Str(String),
|
Str(String),
|
||||||
Percent,
|
Percent,
|
||||||
@ -385,6 +454,26 @@ pub enum FormatText {
|
|||||||
RequestHeader(HeaderName),
|
RequestHeader(HeaderName),
|
||||||
ResponseHeader(HeaderName),
|
ResponseHeader(HeaderName),
|
||||||
EnvironHeader(String),
|
EnvironHeader(String),
|
||||||
|
CustomRequest(String, Option<CustomRequestFn>),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove pub on next breaking change
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CustomRequestFn {
|
||||||
|
inner_fn: Rc<dyn Fn(&ServiceRequest) -> String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomRequestFn {
|
||||||
|
fn call(&self, req: &ServiceRequest) -> String {
|
||||||
|
(self.inner_fn)(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for CustomRequestFn {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("custom_request_fn")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormatText {
|
impl FormatText {
|
||||||
@ -441,7 +530,7 @@ impl FormatText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) {
|
fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) {
|
||||||
match *self {
|
match &*self {
|
||||||
FormatText::RequestLine => {
|
FormatText::RequestLine => {
|
||||||
*self = if req.query_string().is_empty() {
|
*self = if req.query_string().is_empty() {
|
||||||
FormatText::Str(format!(
|
FormatText::Str(format!(
|
||||||
@ -493,11 +582,20 @@ impl FormatText {
|
|||||||
};
|
};
|
||||||
*self = s;
|
*self = s;
|
||||||
}
|
}
|
||||||
|
FormatText::CustomRequest(_, request_fn) => {
|
||||||
|
let s = match request_fn {
|
||||||
|
Some(f) => FormatText::Str(f.call(req)),
|
||||||
|
None => FormatText::Str("-".to_owned()),
|
||||||
|
};
|
||||||
|
|
||||||
|
*self = s;
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converter to get a String from something that writes to a Formatter.
|
||||||
pub(crate) struct FormatDisplay<'a>(
|
pub(crate) struct FormatDisplay<'a>(
|
||||||
&'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>,
|
&'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>,
|
||||||
);
|
);
|
||||||
@ -515,7 +613,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::{header, StatusCode};
|
use crate::http::{header, StatusCode};
|
||||||
use crate::test::TestRequest;
|
use crate::test::{self, TestRequest};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_logger() {
|
async fn test_logger() {
|
||||||
@ -538,6 +636,28 @@ mod tests {
|
|||||||
let _res = srv.call(req).await;
|
let _res = srv.call(req).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_logger_exclude_regex() {
|
||||||
|
let srv = |req: ServiceRequest| {
|
||||||
|
ok(req.into_response(
|
||||||
|
HttpResponse::build(StatusCode::OK)
|
||||||
|
.header("X-Test", "ttt")
|
||||||
|
.finish(),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test")
|
||||||
|
.exclude_regex("\\w");
|
||||||
|
|
||||||
|
let mut srv = logger.new_transform(srv.into_service()).await.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::with_header(
|
||||||
|
header::USER_AGENT,
|
||||||
|
header::HeaderValue::from_static("ACTIX-WEB"),
|
||||||
|
)
|
||||||
|
.to_srv_request();
|
||||||
|
let _res = srv.call(req).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_url_path() {
|
async fn test_url_path() {
|
||||||
let mut format = Format::new("%T %U");
|
let mut format = Format::new("%T %U");
|
||||||
@ -662,4 +782,45 @@ mod tests {
|
|||||||
println!("{}", s);
|
println!("{}", s);
|
||||||
assert!(s.contains("192.0.2.60"));
|
assert!(s.contains("192.0.2.60"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_custom_closure_log() {
|
||||||
|
let mut logger = Logger::new("test %{CUSTOM}xi")
|
||||||
|
.custom_request_replace("CUSTOM", |_req: &ServiceRequest| -> String {
|
||||||
|
String::from("custom_log")
|
||||||
|
});
|
||||||
|
let mut unit = Rc::get_mut(&mut logger.0).unwrap().format.0[1].clone();
|
||||||
|
|
||||||
|
let label = match &unit {
|
||||||
|
FormatText::CustomRequest(label, _) => label,
|
||||||
|
ft => panic!("expected CustomRequest, found {:?}", ft),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(label, "CUSTOM");
|
||||||
|
|
||||||
|
let req = TestRequest::default().to_srv_request();
|
||||||
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
unit.render_request(now, &req);
|
||||||
|
|
||||||
|
let render = |fmt: &mut Formatter<'_>| unit.render(fmt, 1024, now);
|
||||||
|
|
||||||
|
let log_output = FormatDisplay(&render).to_string();
|
||||||
|
assert_eq!(log_output, "custom_log");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_closure_logger_in_middleware() {
|
||||||
|
let captured = "custom log replacement";
|
||||||
|
|
||||||
|
let logger = Logger::new("%{CUSTOM}xi")
|
||||||
|
.custom_request_replace("CUSTOM", move |_req: &ServiceRequest| -> String {
|
||||||
|
captured.to_owned()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut srv = logger.new_transform(test::ok_service()).await.unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::default().to_srv_request();
|
||||||
|
srv.call(req).await.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,10 @@ pub enum TrailingSlash {
|
|||||||
/// Always add a trailing slash to the end of the path.
|
/// Always add a trailing slash to the end of the path.
|
||||||
/// This will require all routes to end in a trailing slash for them to be accessible.
|
/// This will require all routes to end in a trailing slash for them to be accessible.
|
||||||
Always,
|
Always,
|
||||||
|
/// Only merge any present multiple trailing slashes.
|
||||||
|
///
|
||||||
|
/// Note: This option provides the best compatibility with the v2 version of this middlware.
|
||||||
|
MergeOnly,
|
||||||
/// Trim trailing slashes from the end of the path.
|
/// Trim trailing slashes from the end of the path.
|
||||||
Trim,
|
Trim,
|
||||||
}
|
}
|
||||||
@ -33,7 +37,8 @@ impl Default for TrailingSlash {
|
|||||||
/// Performs following:
|
/// Performs following:
|
||||||
///
|
///
|
||||||
/// - Merges multiple slashes into one.
|
/// - Merges multiple slashes into one.
|
||||||
/// - Appends a trailing slash if one is not present, or removes one if present, depending on the supplied `TrailingSlash`.
|
/// - Appends a trailing slash if one is not present, removes one if present, or keeps trailing
|
||||||
|
/// slashes as-is, depending on the supplied `TrailingSlash` variant.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web::{web, http, middleware, App, HttpResponse};
|
/// use actix_web::{web, http, middleware, App, HttpResponse};
|
||||||
@ -108,6 +113,7 @@ where
|
|||||||
// Either adds a string to the end (duplicates will be removed anyways) or trims all slashes from the end
|
// Either adds a string to the end (duplicates will be removed anyways) or trims all slashes from the end
|
||||||
let path = match self.trailing_slash_behavior {
|
let path = match self.trailing_slash_behavior {
|
||||||
TrailingSlash::Always => original_path.to_string() + "/",
|
TrailingSlash::Always => original_path.to_string() + "/",
|
||||||
|
TrailingSlash::MergeOnly => original_path.to_string(),
|
||||||
TrailingSlash::Trim => original_path.trim_end_matches('/').to_string(),
|
TrailingSlash::Trim => original_path.trim_end_matches('/').to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -237,6 +243,38 @@ mod tests {
|
|||||||
assert!(res4.status().is_success());
|
assert!(res4.status().is_success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn keep_trailing_slash_unchange() {
|
||||||
|
let mut app = init_service(
|
||||||
|
App::new()
|
||||||
|
.wrap(NormalizePath(TrailingSlash::MergeOnly))
|
||||||
|
.service(web::resource("/").to(HttpResponse::Ok))
|
||||||
|
.service(web::resource("/v1/something").to(HttpResponse::Ok))
|
||||||
|
.service(web::resource("/v1/").to(HttpResponse::Ok)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let tests = vec![
|
||||||
|
("/", true), // root paths should still work
|
||||||
|
("/?query=test", true),
|
||||||
|
("///", true),
|
||||||
|
("/v1/something////", false),
|
||||||
|
("/v1/something/", false),
|
||||||
|
("//v1//something", true),
|
||||||
|
("/v1/", true),
|
||||||
|
("/v1", false),
|
||||||
|
("/v1////", true),
|
||||||
|
("//v1//", true),
|
||||||
|
("///v1", false),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (path, success) in tests {
|
||||||
|
let req = TestRequest::with_uri(path).to_request();
|
||||||
|
let res = call_service(&mut app, req).await;
|
||||||
|
assert_eq!(res.status().is_success(), success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_in_place_normalization() {
|
async fn test_in_place_normalization() {
|
||||||
let srv = |req: ServiceRequest| {
|
let srv = |req: ServiceRequest| {
|
||||||
|
175
src/request_data.rs
Normal file
175
src/request_data.rs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
use std::{any::type_name, ops::Deref};
|
||||||
|
|
||||||
|
use actix_http::error::{Error, ErrorInternalServerError};
|
||||||
|
use futures_util::future;
|
||||||
|
|
||||||
|
use crate::{dev::Payload, FromRequest, HttpRequest};
|
||||||
|
|
||||||
|
/// Request-local data extractor.
|
||||||
|
///
|
||||||
|
/// Request-local data is arbitrary data attached to an individual request, usually
|
||||||
|
/// by middleware. It can be set via `extensions_mut` on [`HttpRequest`][htr_ext_mut]
|
||||||
|
/// or [`ServiceRequest`][srv_ext_mut].
|
||||||
|
///
|
||||||
|
/// Unlike app data, request data is dropped when the request has finished processing. This makes it
|
||||||
|
/// useful as a kind of messaging system between middleware and request handlers. It uses the same
|
||||||
|
/// types-as-keys storage system as app data.
|
||||||
|
///
|
||||||
|
/// # Mutating Request Data
|
||||||
|
/// Note that since extractors must output owned data, only types that `impl Clone` can use this
|
||||||
|
/// extractor. A clone is taken of the required request data and can, therefore, not be directly
|
||||||
|
/// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or
|
||||||
|
/// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not
|
||||||
|
/// provided to make this potential foot-gun more obvious.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use actix_web::{web, HttpResponse, HttpRequest, Responder};
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, Clone, PartialEq)]
|
||||||
|
/// struct FlagFromMiddleware(String);
|
||||||
|
///
|
||||||
|
/// /// Use the `ReqData<T>` extractor to access request data in a handler.
|
||||||
|
/// async fn handler(
|
||||||
|
/// req: HttpRequest,
|
||||||
|
/// opt_flag: Option<web::ReqData<FlagFromMiddleware>>,
|
||||||
|
/// ) -> impl Responder {
|
||||||
|
/// // use an optional extractor if the middleware is
|
||||||
|
/// // not guaranteed to add this type of requests data
|
||||||
|
/// if let Some(flag) = opt_flag {
|
||||||
|
/// assert_eq!(&flag.into_inner(), req.extensions().get::<FlagFromMiddleware>().unwrap());
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// HttpResponse::Ok()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [htr_ext_mut]: crate::HttpRequest::extensions_mut
|
||||||
|
/// [srv_ext_mut]: crate::dev::ServiceRequest::extensions_mut
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ReqData<T: Clone + 'static>(T);
|
||||||
|
|
||||||
|
impl<T: Clone + 'static> ReqData<T> {
|
||||||
|
/// Consumes the `ReqData`, returning it's wrapped data.
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + 'static> Deref for ReqData<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + 'static> FromRequest for ReqData<T> {
|
||||||
|
type Config = ();
|
||||||
|
type Error = Error;
|
||||||
|
type Future = future::Ready<Result<Self, Error>>;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
|
if let Some(st) = req.extensions().get::<T>() {
|
||||||
|
future::ok(ReqData(st.clone()))
|
||||||
|
} else {
|
||||||
|
log::debug!(
|
||||||
|
"Failed to construct App-level ReqData extractor. \
|
||||||
|
Request path: {:?} (type: {})",
|
||||||
|
req.path(),
|
||||||
|
type_name::<T>(),
|
||||||
|
);
|
||||||
|
future::err(ErrorInternalServerError(
|
||||||
|
"Missing expected request extension data",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use futures_util::TryFutureExt as _;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
dev::Service,
|
||||||
|
http::{Method, StatusCode},
|
||||||
|
test::{init_service, TestRequest},
|
||||||
|
web, App, HttpMessage, HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn req_data_extractor() {
|
||||||
|
let mut srv = init_service(
|
||||||
|
App::new()
|
||||||
|
.wrap_fn(|req, srv| {
|
||||||
|
if req.method() == Method::POST {
|
||||||
|
req.extensions_mut().insert(42u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.call(req)
|
||||||
|
})
|
||||||
|
.service(web::resource("/test").to(
|
||||||
|
|req: HttpRequest, data: Option<ReqData<u32>>| {
|
||||||
|
if req.method() != Method::POST {
|
||||||
|
assert!(data.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(data) = data {
|
||||||
|
assert_eq!(*data, 42);
|
||||||
|
assert_eq!(
|
||||||
|
Some(data.into_inner()),
|
||||||
|
req.extensions().get::<u32>().copied()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::get().uri("/test").to_request();
|
||||||
|
let resp = srv.call(req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::post().uri("/test").to_request();
|
||||||
|
let resp = srv.call(req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn req_data_internal_mutability() {
|
||||||
|
let mut srv = init_service(
|
||||||
|
App::new()
|
||||||
|
.wrap_fn(|req, srv| {
|
||||||
|
let data_before = Rc::new(RefCell::new(42u32));
|
||||||
|
req.extensions_mut().insert(data_before);
|
||||||
|
|
||||||
|
srv.call(req).map_ok(|res| {
|
||||||
|
{
|
||||||
|
let ext = res.request().extensions();
|
||||||
|
let data_after = ext.get::<Rc<RefCell<u32>>>().unwrap();
|
||||||
|
assert_eq!(*data_after.borrow(), 53u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.default_service(web::to(|data: ReqData<Rc<RefCell<u32>>>| {
|
||||||
|
assert_eq!(*data.borrow(), 42);
|
||||||
|
*data.borrow_mut() += 11;
|
||||||
|
assert_eq!(*data.borrow(), 53);
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = TestRequest::get().uri("/test").to_request();
|
||||||
|
let resp = srv.call(req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
}
|
50
src/rmap.rs
50
src/rmap.rs
@ -1,5 +1,5 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
use actix_router::ResourceDef;
|
use actix_router::ResourceDef;
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
@ -11,7 +11,7 @@ use crate::request::HttpRequest;
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ResourceMap {
|
pub struct ResourceMap {
|
||||||
root: ResourceDef,
|
root: ResourceDef,
|
||||||
parent: RefCell<Option<Rc<ResourceMap>>>,
|
parent: RefCell<Weak<ResourceMap>>,
|
||||||
named: FxHashMap<String, ResourceDef>,
|
named: FxHashMap<String, ResourceDef>,
|
||||||
patterns: Vec<(ResourceDef, Option<Rc<ResourceMap>>)>,
|
patterns: Vec<(ResourceDef, Option<Rc<ResourceMap>>)>,
|
||||||
}
|
}
|
||||||
@ -20,7 +20,7 @@ impl ResourceMap {
|
|||||||
pub fn new(root: ResourceDef) -> Self {
|
pub fn new(root: ResourceDef) -> Self {
|
||||||
ResourceMap {
|
ResourceMap {
|
||||||
root,
|
root,
|
||||||
parent: RefCell::new(None),
|
parent: RefCell::new(Weak::new()),
|
||||||
named: FxHashMap::default(),
|
named: FxHashMap::default(),
|
||||||
patterns: Vec::new(),
|
patterns: Vec::new(),
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ impl ResourceMap {
|
|||||||
pub(crate) fn finish(&self, current: Rc<ResourceMap>) {
|
pub(crate) fn finish(&self, current: Rc<ResourceMap>) {
|
||||||
for (_, nested) in &self.patterns {
|
for (_, nested) in &self.patterns {
|
||||||
if let Some(ref nested) = nested {
|
if let Some(ref nested) = nested {
|
||||||
*nested.parent.borrow_mut() = Some(current.clone());
|
*nested.parent.borrow_mut() = Rc::downgrade(¤t);
|
||||||
nested.finish(nested.clone());
|
nested.finish(nested.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,7 +210,7 @@ impl ResourceMap {
|
|||||||
U: Iterator<Item = I>,
|
U: Iterator<Item = I>,
|
||||||
I: AsRef<str>,
|
I: AsRef<str>,
|
||||||
{
|
{
|
||||||
if let Some(ref parent) = *self.parent.borrow() {
|
if let Some(ref parent) = self.parent.borrow().upgrade() {
|
||||||
parent.fill_root(path, elements)?;
|
parent.fill_root(path, elements)?;
|
||||||
}
|
}
|
||||||
if self.root.resource_path(path, elements) {
|
if self.root.resource_path(path, elements) {
|
||||||
@ -230,7 +230,7 @@ impl ResourceMap {
|
|||||||
U: Iterator<Item = I>,
|
U: Iterator<Item = I>,
|
||||||
I: AsRef<str>,
|
I: AsRef<str>,
|
||||||
{
|
{
|
||||||
if let Some(ref parent) = *self.parent.borrow() {
|
if let Some(ref parent) = self.parent.borrow().upgrade() {
|
||||||
if let Some(pattern) = parent.named.get(name) {
|
if let Some(pattern) = parent.named.get(name) {
|
||||||
self.fill_root(path, elements)?;
|
self.fill_root(path, elements)?;
|
||||||
if pattern.resource_path(path, elements) {
|
if pattern.resource_path(path, elements) {
|
||||||
@ -367,4 +367,42 @@ mod tests {
|
|||||||
assert_eq!(root.match_name("/user/22/"), None);
|
assert_eq!(root.match_name("/user/22/"), None);
|
||||||
assert_eq!(root.match_name("/user/22/post/55"), Some("user_post"));
|
assert_eq!(root.match_name("/user/22/post/55"), Some("user_post"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bug_fix_issue_1582_debug_print_exits() {
|
||||||
|
// ref: https://github.com/actix/actix-web/issues/1582
|
||||||
|
let mut root = ResourceMap::new(ResourceDef::root_prefix(""));
|
||||||
|
|
||||||
|
let mut user_map = ResourceMap::new(ResourceDef::root_prefix(""));
|
||||||
|
user_map.add(&mut ResourceDef::new("/"), None);
|
||||||
|
user_map.add(&mut ResourceDef::new("/profile"), None);
|
||||||
|
user_map.add(&mut ResourceDef::new("/article/{id}"), None);
|
||||||
|
user_map.add(&mut ResourceDef::new("/post/{post_id}"), None);
|
||||||
|
user_map.add(
|
||||||
|
&mut ResourceDef::new("/post/{post_id}/comment/{comment_id}"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
root.add(
|
||||||
|
&mut ResourceDef::root_prefix("/user/{id}"),
|
||||||
|
Some(Rc::new(user_map)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let root = Rc::new(root);
|
||||||
|
root.finish(Rc::clone(&root));
|
||||||
|
|
||||||
|
// check root has no parent
|
||||||
|
assert!(root.parent.borrow().upgrade().is_none());
|
||||||
|
// check child has parent reference
|
||||||
|
assert!(root.patterns[0].1.is_some());
|
||||||
|
// check child's parent root id matches root's root id
|
||||||
|
assert_eq!(
|
||||||
|
root.patterns[0].1.as_ref().unwrap().root.id(),
|
||||||
|
root.root.id()
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = format!("{:?}", root);
|
||||||
|
assert!(output.starts_with("ResourceMap {"));
|
||||||
|
assert!(output.ends_with(" }"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#![allow(clippy::rc_buffer)] // inner value is mutated before being shared (`Rc::get_mut`)
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
27
src/scope.rs
27
src/scope.rs
@ -58,7 +58,6 @@ type BoxedResponse = LocalBoxFuture<'static, Result<ServiceResponse, Error>>;
|
|||||||
/// * /{project_id}/path1 - responds to all http method
|
/// * /{project_id}/path1 - responds to all http method
|
||||||
/// * /{project_id}/path2 - `GET` requests
|
/// * /{project_id}/path2 - `GET` requests
|
||||||
/// * /{project_id}/path3 - `HEAD` requests
|
/// * /{project_id}/path3 - `HEAD` requests
|
||||||
///
|
|
||||||
pub struct Scope<T = ScopeEndpoint> {
|
pub struct Scope<T = ScopeEndpoint> {
|
||||||
endpoint: T,
|
endpoint: T,
|
||||||
rdef: String,
|
rdef: String,
|
||||||
@ -210,6 +209,9 @@ where
|
|||||||
|
|
||||||
self.data = Some(data);
|
self.data = Some(data);
|
||||||
}
|
}
|
||||||
|
self.data
|
||||||
|
.get_or_insert_with(Extensions::new)
|
||||||
|
.extend(cfg.extensions);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -443,16 +445,17 @@ where
|
|||||||
*self.factory_ref.borrow_mut() = Some(ScopeFactory {
|
*self.factory_ref.borrow_mut() = Some(ScopeFactory {
|
||||||
data: self.data.take().map(Rc::new),
|
data: self.data.take().map(Rc::new),
|
||||||
default: self.default.clone(),
|
default: self.default.clone(),
|
||||||
services: Rc::new(
|
services: cfg
|
||||||
cfg.into_services()
|
.into_services()
|
||||||
.1
|
.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);
|
||||||
(rdef, srv, RefCell::new(guards))
|
(rdef, srv, RefCell::new(guards))
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect::<Vec<_>>()
|
||||||
),
|
.into_boxed_slice()
|
||||||
|
.into(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// get guards
|
// get guards
|
||||||
@ -474,7 +477,7 @@ where
|
|||||||
|
|
||||||
pub struct ScopeFactory {
|
pub struct ScopeFactory {
|
||||||
data: Option<Rc<Extensions>>,
|
data: Option<Rc<Extensions>>,
|
||||||
services: Rc<Vec<(ResourceDef, HttpNewService, RefCell<Option<Guards>>)>>,
|
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
|
||||||
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
116
src/server.rs
116
src/server.rs
@ -1,8 +1,14 @@
|
|||||||
use std::marker::PhantomData;
|
use std::{
|
||||||
use std::sync::{Arc, Mutex};
|
any::Any,
|
||||||
use std::{fmt, io, net};
|
fmt, io,
|
||||||
|
marker::PhantomData,
|
||||||
|
net,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_http::{body::MessageBody, Error, HttpService, KeepAlive, Request, Response};
|
use actix_http::{
|
||||||
|
body::MessageBody, Error, Extensions, HttpService, KeepAlive, Request, Response,
|
||||||
|
};
|
||||||
use actix_server::{Server, ServerBuilder};
|
use actix_server::{Server, ServerBuilder};
|
||||||
use actix_service::{map_config, IntoServiceFactory, Service, ServiceFactory};
|
use actix_service::{map_config, IntoServiceFactory, Service, ServiceFactory};
|
||||||
|
|
||||||
@ -64,6 +70,7 @@ where
|
|||||||
backlog: i32,
|
backlog: i32,
|
||||||
sockets: Vec<Socket>,
|
sockets: Vec<Socket>,
|
||||||
builder: ServerBuilder,
|
builder: ServerBuilder,
|
||||||
|
on_connect_fn: Option<Arc<dyn Fn(&dyn Any, &mut Extensions) + Send + Sync>>,
|
||||||
_t: PhantomData<(S, B)>,
|
_t: PhantomData<(S, B)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +98,32 @@ where
|
|||||||
backlog: 1024,
|
backlog: 1024,
|
||||||
sockets: Vec::new(),
|
sockets: Vec::new(),
|
||||||
builder: ServerBuilder::default(),
|
builder: ServerBuilder::default(),
|
||||||
|
on_connect_fn: None,
|
||||||
|
_t: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets function that will be called once before each connection is handled.
|
||||||
|
/// It will receive a `&std::any::Any`, which contains underlying connection type and an
|
||||||
|
/// [Extensions] container so that request-local data can be passed to middleware and handlers.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// - `actix_tls::openssl::SslStream<actix_web::rt::net::TcpStream>` when using openssl.
|
||||||
|
/// - `actix_tls::rustls::TlsStream<actix_web::rt::net::TcpStream>` when using rustls.
|
||||||
|
/// - `actix_web::rt::net::TcpStream` when no encryption is used.
|
||||||
|
///
|
||||||
|
/// See `on_connect` example for additional details.
|
||||||
|
pub fn on_connect<CB>(self, f: CB) -> HttpServer<F, I, S, B>
|
||||||
|
where
|
||||||
|
CB: Fn(&dyn Any, &mut Extensions) + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
HttpServer {
|
||||||
|
factory: self.factory,
|
||||||
|
config: self.config,
|
||||||
|
backlog: self.backlog,
|
||||||
|
sockets: self.sockets,
|
||||||
|
builder: self.builder,
|
||||||
|
on_connect_fn: Some(Arc::new(f)),
|
||||||
_t: PhantomData,
|
_t: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,6 +273,7 @@ where
|
|||||||
addr,
|
addr,
|
||||||
scheme: "http",
|
scheme: "http",
|
||||||
});
|
});
|
||||||
|
let on_connect_fn = self.on_connect_fn.clone();
|
||||||
|
|
||||||
self.builder = self.builder.listen(
|
self.builder = self.builder.listen(
|
||||||
format!("actix-web-service-{}", addr),
|
format!("actix-web-service-{}", addr),
|
||||||
@ -252,11 +286,20 @@ where
|
|||||||
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
||||||
);
|
);
|
||||||
|
|
||||||
HttpService::build()
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
.client_timeout(c.client_timeout)
|
.client_timeout(c.client_timeout)
|
||||||
.local_addr(addr)
|
.local_addr(addr);
|
||||||
.finish(map_config(factory(), move |_| cfg.clone()))
|
|
||||||
|
let svc = if let Some(handler) = on_connect_fn.clone() {
|
||||||
|
svc.on_connect_ext(move |io: &_, ext: _| {
|
||||||
|
(handler)(io as &dyn Any, ext)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
svc
|
||||||
|
};
|
||||||
|
|
||||||
|
svc.finish(map_config(factory(), move |_| cfg.clone()))
|
||||||
.tcp()
|
.tcp()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
@ -289,6 +332,8 @@ where
|
|||||||
scheme: "https",
|
scheme: "https",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let on_connect_fn = self.on_connect_fn.clone();
|
||||||
|
|
||||||
self.builder = self.builder.listen(
|
self.builder = self.builder.listen(
|
||||||
format!("actix-web-service-{}", addr),
|
format!("actix-web-service-{}", addr),
|
||||||
lst,
|
lst,
|
||||||
@ -299,11 +344,21 @@ where
|
|||||||
addr,
|
addr,
|
||||||
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
||||||
);
|
);
|
||||||
HttpService::build()
|
|
||||||
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
.client_timeout(c.client_timeout)
|
.client_timeout(c.client_timeout)
|
||||||
.client_disconnect(c.client_shutdown)
|
.client_disconnect(c.client_shutdown);
|
||||||
.finish(map_config(factory(), move |_| cfg.clone()))
|
|
||||||
|
let svc = if let Some(handler) = on_connect_fn.clone() {
|
||||||
|
svc.on_connect_ext(move |io: &_, ext: _| {
|
||||||
|
(&*handler)(io as &dyn Any, ext)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
svc
|
||||||
|
};
|
||||||
|
|
||||||
|
svc.finish(map_config(factory(), move |_| cfg.clone()))
|
||||||
.openssl(acceptor.clone())
|
.openssl(acceptor.clone())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
@ -336,6 +391,8 @@ where
|
|||||||
scheme: "https",
|
scheme: "https",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let on_connect_fn = self.on_connect_fn.clone();
|
||||||
|
|
||||||
self.builder = self.builder.listen(
|
self.builder = self.builder.listen(
|
||||||
format!("actix-web-service-{}", addr),
|
format!("actix-web-service-{}", addr),
|
||||||
lst,
|
lst,
|
||||||
@ -346,11 +403,21 @@ where
|
|||||||
addr,
|
addr,
|
||||||
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
||||||
);
|
);
|
||||||
HttpService::build()
|
|
||||||
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
.client_timeout(c.client_timeout)
|
.client_timeout(c.client_timeout)
|
||||||
.client_disconnect(c.client_shutdown)
|
.client_disconnect(c.client_shutdown);
|
||||||
.finish(map_config(factory(), move |_| cfg.clone()))
|
|
||||||
|
let svc = if let Some(handler) = on_connect_fn.clone() {
|
||||||
|
svc.on_connect_ext(move |io: &_, ext: _| {
|
||||||
|
(handler)(io as &dyn Any, ext)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
svc
|
||||||
|
};
|
||||||
|
|
||||||
|
svc.finish(map_config(factory(), move |_| cfg.clone()))
|
||||||
.rustls(config.clone())
|
.rustls(config.clone())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
@ -441,7 +508,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
/// Start listening for unix domain connections on existing listener.
|
/// Start listening for unix domain (UDS) connections on existing listener.
|
||||||
pub fn listen_uds(
|
pub fn listen_uds(
|
||||||
mut self,
|
mut self,
|
||||||
lst: std::os::unix::net::UnixListener,
|
lst: std::os::unix::net::UnixListener,
|
||||||
@ -460,6 +527,7 @@ where
|
|||||||
});
|
});
|
||||||
|
|
||||||
let addr = format!("actix-web-service-{:?}", lst.local_addr()?);
|
let addr = format!("actix-web-service-{:?}", lst.local_addr()?);
|
||||||
|
let on_connect_fn = self.on_connect_fn.clone();
|
||||||
|
|
||||||
self.builder = self.builder.listen_uds(addr, lst, move || {
|
self.builder = self.builder.listen_uds(addr, lst, move || {
|
||||||
let c = cfg.lock().unwrap();
|
let c = cfg.lock().unwrap();
|
||||||
@ -468,11 +536,23 @@ where
|
|||||||
socket_addr,
|
socket_addr,
|
||||||
c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)),
|
c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)),
|
||||||
);
|
);
|
||||||
|
|
||||||
pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then(
|
pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then(
|
||||||
HttpService::build()
|
{
|
||||||
.keep_alive(c.keep_alive)
|
let svc = HttpService::build()
|
||||||
.client_timeout(c.client_timeout)
|
.keep_alive(c.keep_alive)
|
||||||
.finish(map_config(factory(), move |_| config.clone())),
|
.client_timeout(c.client_timeout);
|
||||||
|
|
||||||
|
let svc = if let Some(handler) = on_connect_fn.clone() {
|
||||||
|
svc.on_connect_ext(move |io: &_, ext: _| {
|
||||||
|
(&*handler)(io as &dyn Any, ext)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
svc
|
||||||
|
};
|
||||||
|
|
||||||
|
svc.finish(map_config(factory(), move |_| config.clone()))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
|
@ -283,7 +283,7 @@ impl JsonConfig {
|
|||||||
fn from_req(req: &HttpRequest) -> &Self {
|
fn from_req(req: &HttpRequest) -> &Self {
|
||||||
req.app_data::<Self>()
|
req.app_data::<Self>()
|
||||||
.or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
|
.or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
|
||||||
.unwrap_or_else(|| &DEFAULT_CONFIG)
|
.unwrap_or(&DEFAULT_CONFIG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,7 +284,7 @@ impl PayloadConfig {
|
|||||||
fn from_req(req: &HttpRequest) -> &Self {
|
fn from_req(req: &HttpRequest) -> &Self {
|
||||||
req.app_data::<Self>()
|
req.app_data::<Self>()
|
||||||
.or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
|
.or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
|
||||||
.unwrap_or_else(|| &DEFAULT_CONFIG)
|
.unwrap_or(&DEFAULT_CONFIG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ use actix_router::IntoPattern;
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
pub use actix_http::Response as HttpResponse;
|
pub use actix_http::Response as HttpResponse;
|
||||||
pub use bytes::{Bytes, BytesMut};
|
pub use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||||
pub use futures_channel::oneshot::Canceled;
|
pub use futures_channel::oneshot::Canceled;
|
||||||
|
|
||||||
use crate::error::BlockingError;
|
use crate::error::BlockingError;
|
||||||
@ -19,6 +19,7 @@ use crate::service::WebService;
|
|||||||
pub use crate::config::ServiceConfig;
|
pub use crate::config::ServiceConfig;
|
||||||
pub use crate::data::Data;
|
pub use crate::data::Data;
|
||||||
pub use crate::request::HttpRequest;
|
pub use crate::request::HttpRequest;
|
||||||
|
pub use crate::request_data::ReqData;
|
||||||
pub use crate::types::*;
|
pub use crate::types::*;
|
||||||
|
|
||||||
/// Create resource for a specific path.
|
/// Create resource for a specific path.
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
## Unreleased - 2020-xx-xx
|
## Unreleased - 2020-xx-xx
|
||||||
|
|
||||||
* add ability to set address for `TestServer` [#1645]
|
* add ability to set address for `TestServer` [#1645]
|
||||||
|
* Upgrade `base64` to `0.13`.
|
||||||
|
|
||||||
[#1645]: https://github.com/actix/actix-web/pull/1645
|
[#1645]: https://github.com/actix/actix-web/pull/1645
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ actix-server = "1.0.0"
|
|||||||
actix-testing = "1.0.0"
|
actix-testing = "1.0.0"
|
||||||
awc = "2.0.0"
|
awc = "2.0.0"
|
||||||
|
|
||||||
base64 = "0.12"
|
base64 = "0.13"
|
||||||
bytes = "0.5.3"
|
bytes = "0.5.3"
|
||||||
futures-core = { version = "0.3.5", default-features = false }
|
futures-core = { version = "0.3.5", default-features = false }
|
||||||
http = "0.2.0"
|
http = "0.2.0"
|
||||||
|
Reference in New Issue
Block a user