mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-03 17:41:30 +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
|
||||
|
||||
|
||||
## 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
|
||||
### Fixed
|
||||
* `NormalizePath` when used with `TrailingSlash::Trim` no longer trims the root path "/". [#1678]
|
||||
@ -171,7 +208,7 @@
|
||||
|
||||
### 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
|
||||
|
||||
@ -216,7 +253,7 @@
|
||||
|
||||
### Changed
|
||||
|
||||
* Make UrlEncodedError::Overflow more informativve
|
||||
* Make UrlEncodedError::Overflow more informative
|
||||
|
||||
* Use actix-testing for testing utils
|
||||
|
||||
@ -234,7 +271,7 @@
|
||||
|
||||
* 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`
|
||||
|
||||
|
28
Cargo.toml
28
Cargo.toml
@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "3.0.2"
|
||||
version = "3.2.0"
|
||||
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"
|
||||
keywords = ["actix", "http", "web", "framework", "async"]
|
||||
homepage = "https://actix.rs"
|
||||
@ -64,6 +64,14 @@ required-features = ["compress"]
|
||||
name = "test_server"
|
||||
required-features = ["compress"]
|
||||
|
||||
[[example]]
|
||||
name = "on_connect"
|
||||
required-features = []
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
required-features = ["rustls"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.3.0"
|
||||
actix-service = "1.0.6"
|
||||
@ -76,8 +84,8 @@ actix-macros = "0.1.0"
|
||||
actix-threadpool = "0.3.1"
|
||||
actix-tls = "2.0.0"
|
||||
|
||||
actix-web-codegen = "0.3.0"
|
||||
actix-http = "2.0.0"
|
||||
actix-web-codegen = "0.4.0"
|
||||
actix-http = "2.1.0"
|
||||
awc = { version = "2.0.0", default-features = false }
|
||||
|
||||
bytes = "0.5.3"
|
||||
@ -90,8 +98,8 @@ fxhash = "0.2.1"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
socket2 = "0.3"
|
||||
pin-project = "0.4.17"
|
||||
regex = "1.3"
|
||||
pin-project = "1.0.0"
|
||||
regex = "1.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.6.1"
|
||||
@ -103,9 +111,9 @@ tinyvec = { version = "1", features = ["alloc"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix = "0.10.0"
|
||||
actix-http = { version = "2.0.0", features = ["actors"] }
|
||||
actix-http = { version = "2.1.0", features = ["actors"] }
|
||||
rand = "0.7"
|
||||
env_logger = "0.7"
|
||||
env_logger = "0.8"
|
||||
serde_derive = "1.0"
|
||||
brotli2 = "0.3.2"
|
||||
flate2 = "1.0.13"
|
||||
@ -125,10 +133,6 @@ actix-files = { path = "actix-files" }
|
||||
actix-multipart = { path = "actix-multipart" }
|
||||
awc = { path = "awc" }
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
required-features = ["rustls"]
|
||||
|
||||
[[bench]]
|
||||
name = "server"
|
||||
harness = false
|
||||
|
@ -48,7 +48,7 @@
|
||||
|
||||
* `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one.
|
||||
It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`,
|
||||
or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::default())`.
|
||||
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`.
|
||||
|
||||
|
@ -5,15 +5,17 @@
|
||||
</p>
|
||||
<p>
|
||||
|
||||
[](https://crates.io/crates/actix-web)
|
||||
[](https://docs.rs/actix-web)
|
||||
[](https://crates.io/crates/actix-web)
|
||||
[](https://docs.rs/actix-web/3.2.0)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||

|
||||
[](https://deps.rs/crate/actix-web/2.2.0)
|
||||
<br />
|
||||
[](https://travis-ci.org/actix/actix-web)
|
||||
[](https://codecov.io/gh/actix/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://discord.gg/NWpN5mmg3x)
|
||||
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,12 +1,24 @@
|
||||
# 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 `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
|
||||
* Fix some typos in the docs
|
||||
* Bump minimum supported Rust version to 1.40
|
||||
@ -14,77 +26,73 @@
|
||||
|
||||
[#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
|
||||
|
||||
## [0.2.0] - 2019-12-20
|
||||
|
||||
## 0.2.0 - 2019-12-20
|
||||
* 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`
|
||||
|
||||
## [0.1.7] - 2019-11-06
|
||||
|
||||
* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
|
||||
|
||||
## [0.1.6] - 2019-10-14
|
||||
## 0.1.7 - 2019-11-06
|
||||
* Add an additional `filename*` param in the `Content-Disposition` header of
|
||||
`actix_files::NamedFile` to be more compatible. (#1151)
|
||||
|
||||
## 0.1.6 - 2019-10-14
|
||||
* Add option to redirect to a slash-ended path `Files` #1132
|
||||
|
||||
## [0.1.5] - 2019-10-08
|
||||
|
||||
## 0.1.5 - 2019-10-08
|
||||
* Bump up `mime_guess` crate version to 2.0.1
|
||||
|
||||
* Bump up `percent-encoding` crate version to 2.1
|
||||
|
||||
* Allow user defined request guards for `Files` #1113
|
||||
|
||||
## [0.1.4] - 2019-07-20
|
||||
|
||||
## 0.1.4 - 2019-07-20
|
||||
* Allow to disable `Content-Disposition` header #686
|
||||
|
||||
## [0.1.3] - 2019-06-28
|
||||
|
||||
## 0.1.3 - 2019-06-28
|
||||
* 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
|
||||
|
||||
* 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
|
||||
|
||||
## [0.1.0] - 2019-05-25
|
||||
|
||||
* NamedFile last-modified check always fails due to nano-seconds
|
||||
in file modified date #820
|
||||
## 0.1.0 - 2019-05-25
|
||||
* 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
|
||||
|
||||
## [0.1.0-beta.1] - 2019-04-20
|
||||
|
||||
## 0.1.0-beta.1 - 2019-04-20
|
||||
* 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
|
||||
|
||||
## [0.1.0-alpha.4] - 2019-04-08
|
||||
|
||||
## 0.1.0-alpha.4 - 2019-04-08
|
||||
* 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
|
||||
|
||||
## [0.1.0-alpha.1] - 2019-03-28
|
||||
|
||||
## 0.1.0-alpha.1 - 2019-03-28
|
||||
* Initial impl
|
||||
|
@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Static files support for actix web."
|
||||
description = "Static file serving for Actix Web"
|
||||
readme = "README.md"
|
||||
keywords = ["actix", "http", "async", "futures"]
|
||||
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/)
|
||||
* [API Documentation](https://docs.rs/actix-files/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [actix-files](https://crates.io/crates/actix-files)
|
||||
* Minimum supported Rust version: 1.40 or later
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://docs.rs/actix-files)
|
||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||

|
||||
<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
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Specifies whether to use ETag or not.
|
||||
///
|
||||
/// Default is true.
|
||||
#[inline]
|
||||
pub fn use_etag(mut self, value: bool) -> Self {
|
||||
self.file_flags.set(named::Flags::ETAG, value);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Specifies whether to use Last-Modified or not.
|
||||
///
|
||||
/// Default is true.
|
||||
#[inline]
|
||||
pub fn use_last_modified(mut self, value: bool) -> Self {
|
||||
self.file_flags.set(named::Flags::LAST_MD, value);
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
//!
|
||||
@ -8,12 +8,8 @@
|
||||
//! use actix_files::Files;
|
||||
//!
|
||||
//! 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)]
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
@ -30,6 +26,7 @@ use mime_guess::from_ext;
|
||||
|
||||
mod chunked;
|
||||
mod directory;
|
||||
mod encoding;
|
||||
mod error;
|
||||
mod files;
|
||||
mod named;
|
||||
@ -93,6 +90,9 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
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");
|
||||
assert_eq!(m, mime::IMAGE_JPEG);
|
||||
|
||||
|
@ -22,20 +22,21 @@ use bitflags::bitflags;
|
||||
use futures_util::future::{ready, Ready};
|
||||
use mime_guess::from_path;
|
||||
|
||||
use crate::range::HttpRange;
|
||||
use crate::ChunkedReadFile;
|
||||
use crate::{encoding::equiv_utf8_text, range::HttpRange};
|
||||
|
||||
bitflags! {
|
||||
pub(crate) struct Flags: u8 {
|
||||
const ETAG = 0b0000_0001;
|
||||
const LAST_MD = 0b0000_0010;
|
||||
const ETAG = 0b0000_0001;
|
||||
const LAST_MD = 0b0000_0010;
|
||||
const CONTENT_DISPOSITION = 0b0000_0100;
|
||||
const PREFER_UTF8 = 0b0000_1000;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Flags {
|
||||
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 disposition = match ct.type_() {
|
||||
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
|
||||
_ => DispositionType::Attachment,
|
||||
@ -215,24 +217,33 @@ impl NamedFile {
|
||||
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 {
|
||||
self.flags.set(Flags::ETAG, value);
|
||||
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 {
|
||||
self.flags.set(Flags::LAST_MD, value);
|
||||
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> {
|
||||
// This etag format is similar to Apache's.
|
||||
self.modified.as_ref().map(|mtime| {
|
||||
@ -268,18 +279,24 @@ impl NamedFile {
|
||||
/// Creates an `HttpResponse` with file as a streaming body.
|
||||
pub fn into_response(self, req: &HttpRequest) -> Result<HttpResponse, Error> {
|
||||
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_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| {
|
||||
res.header(
|
||||
header::CONTENT_DISPOSITION,
|
||||
self.content_disposition.to_string(),
|
||||
);
|
||||
});
|
||||
if self.flags.contains(Flags::PREFER_UTF8) {
|
||||
let ct = equiv_utf8_text(self.content_type.clone());
|
||||
res.header(header::CONTENT_TYPE, ct.to_string());
|
||||
} else {
|
||||
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 {
|
||||
resp.encoding(current_encoding);
|
||||
res.encoding(current_encoding);
|
||||
}
|
||||
|
||||
let reader = ChunkedReadFile {
|
||||
@ -290,7 +307,7 @@ impl NamedFile {
|
||||
counter: 0,
|
||||
};
|
||||
|
||||
return Ok(resp.streaming(reader));
|
||||
return Ok(res.streaming(reader));
|
||||
}
|
||||
|
||||
let etag = if self.flags.contains(Flags::ETAG) {
|
||||
@ -342,25 +359,33 @@ impl NamedFile {
|
||||
};
|
||||
|
||||
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| {
|
||||
res.header(
|
||||
header::CONTENT_DISPOSITION,
|
||||
self.content_disposition.to_string(),
|
||||
);
|
||||
});
|
||||
|
||||
if self.flags.contains(Flags::PREFER_UTF8) {
|
||||
let ct = equiv_utf8_text(self.content_type.clone());
|
||||
resp.header(header::CONTENT_TYPE, ct.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
|
||||
if let Some(current_encoding) = self.encoding {
|
||||
resp.encoding(current_encoding);
|
||||
}
|
||||
|
||||
resp.if_some(last_modified, |lm, resp| {
|
||||
resp.set(header::LastModified(lm));
|
||||
})
|
||||
.if_some(etag, |etag, resp| {
|
||||
resp.set(header::ETag(etag));
|
||||
});
|
||||
if let Some(lm) = last_modified {
|
||||
resp.header(header::LAST_MODIFIED, lm.to_string());
|
||||
}
|
||||
|
||||
if let Some(etag) = etag {
|
||||
resp.header(header::ETAG, etag.to_string());
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
## 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
|
||||
* No significant changes from `2.0.0-beta.4`.
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix HTTP primitives"
|
||||
description = "HTTP primitives for the Actix ecosystem"
|
||||
readme = "README.md"
|
||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
@ -49,7 +49,7 @@ actix-threadpool = "0.3.1"
|
||||
actix-tls = { version = "2.0.0", optional = true }
|
||||
actix = { version = "0.10.0", optional = true }
|
||||
|
||||
base64 = "0.12"
|
||||
base64 = "0.13"
|
||||
bitflags = "1.2"
|
||||
bytes = "0.5.3"
|
||||
cookie = { version = "0.14.1", features = ["percent-encode"] }
|
||||
@ -71,7 +71,7 @@ language-tags = "0.2"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
percent-encoding = "2.1"
|
||||
pin-project = "0.4.17"
|
||||
pin-project = "1.0.0"
|
||||
rand = "0.7"
|
||||
regex = "1.3"
|
||||
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/)
|
||||
* [API Documentation](https://docs.rs/actix-http/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [actix-http](https://crates.io/crates/actix-http)
|
||||
* Minimum supported Rust version: 1.40 or later
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/actix-http/2.1.0)
|
||||
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||
- Minimum Supported Rust Version (MSRV): 1.42.0
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -14,10 +14,11 @@ use crate::helpers::{Data, DataFactory};
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
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.
|
||||
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
|
||||
keep_alive: KeepAlive,
|
||||
@ -27,7 +28,9 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
|
||||
local_addr: Option<net::SocketAddr>,
|
||||
expect: X,
|
||||
upgrade: Option<U>,
|
||||
// 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, S)>,
|
||||
}
|
||||
|
||||
@ -49,6 +52,7 @@ where
|
||||
expect: ExpectHandler,
|
||||
upgrade: None,
|
||||
on_connect: None,
|
||||
on_connect_ext: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -138,6 +142,7 @@ where
|
||||
expect: expect.into_factory(),
|
||||
upgrade: self.upgrade,
|
||||
on_connect: self.on_connect,
|
||||
on_connect_ext: self.on_connect_ext,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -167,14 +172,16 @@ where
|
||||
expect: self.expect,
|
||||
upgrade: Some(upgrade.into_factory()),
|
||||
on_connect: self.on_connect,
|
||||
on_connect_ext: self.on_connect_ext,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set on-connect callback.
|
||||
///
|
||||
/// It get called once per connection and result of the call
|
||||
/// get stored to the request's extensions.
|
||||
/// Called once per connection. Return value of the call is stored in request extensions.
|
||||
///
|
||||
/// *SOFT DEPRECATED*: Prefer the `on_connect_ext` style callback.
|
||||
pub fn on_connect<F, I>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&T) -> I + 'static,
|
||||
@ -184,7 +191,20 @@ where
|
||||
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>
|
||||
where
|
||||
B: MessageBody,
|
||||
@ -200,13 +220,15 @@ where
|
||||
self.secure,
|
||||
self.local_addr,
|
||||
);
|
||||
|
||||
H1Service::with_config(cfg, service.into_factory())
|
||||
.expect(self.expect)
|
||||
.upgrade(self.upgrade)
|
||||
.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>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
@ -223,7 +245,10 @@ where
|
||||
self.secure,
|
||||
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.
|
||||
@ -243,9 +268,11 @@ where
|
||||
self.secure,
|
||||
self.local_addr,
|
||||
);
|
||||
|
||||
HttpService::with_config(cfg, service.into_factory())
|
||||
.expect(self.expect)
|
||||
.upgrade(self.upgrade)
|
||||
.on_connect(self.on_connect)
|
||||
.on_connect_ext(self.on_connect_ext)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
|
||||
@ -61,6 +61,16 @@ impl Extensions {
|
||||
pub fn clear(&mut self) {
|
||||
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 {
|
||||
@ -178,4 +188,57 @@ mod tests {
|
||||
assert_eq!(extensions.get::<bool>(), None);
|
||||
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 pin_project::pin_project;
|
||||
|
||||
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
|
||||
use crate::cloneable::CloneableService;
|
||||
use crate::config::ServiceConfig;
|
||||
use crate::error::{DispatchError, Error};
|
||||
@ -21,6 +20,10 @@ use crate::helpers::DataFactory;
|
||||
use crate::httpmessage::HttpMessage;
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
use crate::{
|
||||
body::{Body, BodySize, MessageBody, ResponseBody},
|
||||
Extensions,
|
||||
};
|
||||
|
||||
use super::codec::Codec;
|
||||
use super::payload::{Payload, PayloadSender, PayloadStatus};
|
||||
@ -88,6 +91,7 @@ where
|
||||
expect: CloneableService<X>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
on_connect: Option<Box<dyn DataFactory>>,
|
||||
on_connect_data: Extensions,
|
||||
flags: Flags,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
error: Option<DispatchError>,
|
||||
@ -167,7 +171,7 @@ where
|
||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
/// Create http/1 dispatcher.
|
||||
/// Create HTTP/1 dispatcher.
|
||||
pub(crate) fn new(
|
||||
stream: T,
|
||||
config: ServiceConfig,
|
||||
@ -175,6 +179,7 @@ where
|
||||
expect: CloneableService<X>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
on_connect: Option<Box<dyn DataFactory>>,
|
||||
on_connect_data: Extensions,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
) -> Self {
|
||||
Dispatcher::with_timeout(
|
||||
@ -187,6 +192,7 @@ where
|
||||
expect,
|
||||
upgrade,
|
||||
on_connect,
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
)
|
||||
}
|
||||
@ -202,6 +208,7 @@ where
|
||||
expect: CloneableService<X>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
on_connect: Option<Box<dyn DataFactory>>,
|
||||
on_connect_data: Extensions,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
) -> Self {
|
||||
let keepalive = config.keep_alive_enabled();
|
||||
@ -234,6 +241,7 @@ where
|
||||
expect,
|
||||
upgrade,
|
||||
on_connect,
|
||||
on_connect_data,
|
||||
flags,
|
||||
peer_addr,
|
||||
ka_expire,
|
||||
@ -526,11 +534,15 @@ where
|
||||
let pl = this.codec.message_type();
|
||||
req.head_mut().peer_addr = *this.peer_addr;
|
||||
|
||||
// DEPRECATED
|
||||
// set on_connect data
|
||||
if let Some(ref on_connect) = this.on_connect {
|
||||
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() {
|
||||
this.messages.push_back(DispatcherMessage::Upgrade(req));
|
||||
break;
|
||||
@ -927,8 +939,10 @@ mod tests {
|
||||
CloneableService::new(ExpectHandler),
|
||||
None,
|
||||
None,
|
||||
Extensions::new(),
|
||||
None,
|
||||
);
|
||||
|
||||
match Pin::new(&mut h1).poll(cx) {
|
||||
Poll::Pending => panic!(),
|
||||
Poll::Ready(res) => assert!(res.is_err()),
|
||||
|
@ -18,6 +18,7 @@ use crate::error::{DispatchError, Error, ParseError};
|
||||
use crate::helpers::DataFactory;
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
use crate::{ConnectCallback, Extensions};
|
||||
|
||||
use super::codec::Codec;
|
||||
use super::dispatcher::Dispatcher;
|
||||
@ -30,6 +31,7 @@ pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler<T>> {
|
||||
expect: X,
|
||||
upgrade: Option<U>,
|
||||
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||
_t: PhantomData<(T, B)>,
|
||||
}
|
||||
|
||||
@ -52,6 +54,7 @@ where
|
||||
expect: ExpectHandler,
|
||||
upgrade: None,
|
||||
on_connect: None,
|
||||
on_connect_ext: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -213,6 +216,7 @@ where
|
||||
srv: self.srv,
|
||||
upgrade: self.upgrade,
|
||||
on_connect: self.on_connect,
|
||||
on_connect_ext: self.on_connect_ext,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -229,6 +233,7 @@ where
|
||||
srv: self.srv,
|
||||
expect: self.expect,
|
||||
on_connect: self.on_connect,
|
||||
on_connect_ext: self.on_connect_ext,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -241,6 +246,12 @@ where
|
||||
self.on_connect = f;
|
||||
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>
|
||||
@ -274,6 +285,7 @@ where
|
||||
expect: None,
|
||||
upgrade: None,
|
||||
on_connect: self.on_connect.clone(),
|
||||
on_connect_ext: self.on_connect_ext.clone(),
|
||||
cfg: Some(self.cfg.clone()),
|
||||
_t: PhantomData,
|
||||
}
|
||||
@ -303,6 +315,7 @@ where
|
||||
expect: Option<X::Service>,
|
||||
upgrade: Option<U::Service>,
|
||||
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||
cfg: Option<ServiceConfig>,
|
||||
_t: PhantomData<(T, B)>,
|
||||
}
|
||||
@ -352,23 +365,26 @@ where
|
||||
|
||||
Poll::Ready(result.map(|service| {
|
||||
let this = self.as_mut().project();
|
||||
|
||||
H1ServiceHandler::new(
|
||||
this.cfg.take().unwrap(),
|
||||
service,
|
||||
this.expect.take().unwrap(),
|
||||
this.upgrade.take(),
|
||||
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> {
|
||||
srv: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||
cfg: ServiceConfig,
|
||||
_t: PhantomData<(T, B)>,
|
||||
}
|
||||
@ -390,6 +406,7 @@ where
|
||||
expect: X,
|
||||
upgrade: Option<U>,
|
||||
on_connect: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||
on_connect_ext: Option<Rc<ConnectCallback<T>>>,
|
||||
) -> H1ServiceHandler<T, S, B, X, U> {
|
||||
H1ServiceHandler {
|
||||
srv: CloneableService::new(srv),
|
||||
@ -397,6 +414,7 @@ where
|
||||
upgrade: upgrade.map(CloneableService::new),
|
||||
cfg,
|
||||
on_connect,
|
||||
on_connect_ext,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -462,11 +480,13 @@ where
|
||||
}
|
||||
|
||||
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
|
||||
let on_connect = if let Some(ref on_connect) = self.on_connect {
|
||||
Some(on_connect(&io))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
||||
|
||||
let mut connect_extensions = Extensions::new();
|
||||
if let Some(ref handler) = self.on_connect_ext {
|
||||
// run on_connect_ext callback, populating connect extensions
|
||||
handler(&io, &mut connect_extensions);
|
||||
}
|
||||
|
||||
Dispatcher::new(
|
||||
io,
|
||||
@ -474,7 +494,8 @@ where
|
||||
self.srv.clone(),
|
||||
self.expect.clone(),
|
||||
self.upgrade.clone(),
|
||||
on_connect,
|
||||
deprecated_on_connect,
|
||||
connect_extensions,
|
||||
addr,
|
||||
)
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ use crate::message::ResponseHead;
|
||||
use crate::payload::Payload;
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
use crate::Extensions;
|
||||
|
||||
const CHUNK_SIZE: usize = 16_384;
|
||||
|
||||
@ -36,6 +37,7 @@ where
|
||||
service: CloneableService<S>,
|
||||
connection: Connection<T, Bytes>,
|
||||
on_connect: Option<Box<dyn DataFactory>>,
|
||||
on_connect_data: Extensions,
|
||||
config: ServiceConfig,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
ka_expire: Instant,
|
||||
@ -56,6 +58,7 @@ where
|
||||
service: CloneableService<S>,
|
||||
connection: Connection<T, Bytes>,
|
||||
on_connect: Option<Box<dyn DataFactory>>,
|
||||
on_connect_data: Extensions,
|
||||
config: ServiceConfig,
|
||||
timeout: Option<Delay>,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
@ -82,6 +85,7 @@ where
|
||||
peer_addr,
|
||||
connection,
|
||||
on_connect,
|
||||
on_connect_data,
|
||||
ka_expire,
|
||||
ka_timer,
|
||||
_t: PhantomData,
|
||||
@ -130,11 +134,15 @@ where
|
||||
head.headers = parts.headers.into();
|
||||
head.peer_addr = this.peer_addr;
|
||||
|
||||
// DEPRECATED
|
||||
// set on_connect data
|
||||
if let Some(ref on_connect) = this.on_connect {
|
||||
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::<
|
||||
S::Future,
|
||||
S::Response,
|
||||
|
@ -2,7 +2,7 @@ use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{net, rc};
|
||||
use std::{net, rc::Rc};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_rt::net::TcpStream;
|
||||
@ -23,6 +23,7 @@ use crate::error::{DispatchError, Error};
|
||||
use crate::helpers::DataFactory;
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
use crate::{ConnectCallback, Extensions};
|
||||
|
||||
use super::dispatcher::Dispatcher;
|
||||
|
||||
@ -30,7 +31,8 @@ use super::dispatcher::Dispatcher;
|
||||
pub struct H2Service<T, S, B> {
|
||||
srv: S,
|
||||
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)>,
|
||||
}
|
||||
|
||||
@ -50,19 +52,27 @@ where
|
||||
H2Service {
|
||||
cfg,
|
||||
on_connect: None,
|
||||
on_connect_ext: None,
|
||||
srv: service.into_factory(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set on connect callback.
|
||||
|
||||
pub(crate) fn on_connect(
|
||||
mut self,
|
||||
f: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||
f: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||
) -> Self {
|
||||
self.on_connect = f;
|
||||
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>
|
||||
@ -203,6 +213,7 @@ where
|
||||
fut: self.srv.new_service(()),
|
||||
cfg: Some(self.cfg.clone()),
|
||||
on_connect: self.on_connect.clone(),
|
||||
on_connect_ext: self.on_connect_ext.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -214,7 +225,8 @@ pub struct H2ServiceResponse<T, S: ServiceFactory, B> {
|
||||
#[pin]
|
||||
fut: S::Future,
|
||||
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)>,
|
||||
}
|
||||
|
||||
@ -237,6 +249,7 @@ where
|
||||
H2ServiceHandler::new(
|
||||
this.cfg.take().unwrap(),
|
||||
this.on_connect.clone(),
|
||||
this.on_connect_ext.clone(),
|
||||
service,
|
||||
)
|
||||
}))
|
||||
@ -247,7 +260,8 @@ where
|
||||
pub struct H2ServiceHandler<T, S: Service, B> {
|
||||
srv: CloneableService<S>,
|
||||
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)>,
|
||||
}
|
||||
|
||||
@ -261,12 +275,14 @@ where
|
||||
{
|
||||
fn new(
|
||||
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,
|
||||
) -> H2ServiceHandler<T, S, B> {
|
||||
H2ServiceHandler {
|
||||
cfg,
|
||||
on_connect,
|
||||
on_connect_ext,
|
||||
srv: CloneableService::new(srv),
|
||||
_t: PhantomData,
|
||||
}
|
||||
@ -296,18 +312,21 @@ where
|
||||
}
|
||||
|
||||
fn call(&mut self, (io, addr): Self::Request) -> Self::Future {
|
||||
let on_connect = if let Some(ref on_connect) = self.on_connect {
|
||||
Some(on_connect(&io))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
||||
|
||||
let mut connect_extensions = Extensions::new();
|
||||
if let Some(ref handler) = self.on_connect_ext {
|
||||
// run on_connect_ext callback, populating connect extensions
|
||||
handler(&io, &mut connect_extensions);
|
||||
}
|
||||
|
||||
H2ServiceHandlerResponse {
|
||||
state: State::Handshake(
|
||||
Some(self.srv.clone()),
|
||||
Some(self.cfg.clone()),
|
||||
addr,
|
||||
on_connect,
|
||||
deprecated_on_connect,
|
||||
Some(connect_extensions),
|
||||
server::handshake(io),
|
||||
),
|
||||
}
|
||||
@ -325,6 +344,7 @@ where
|
||||
Option<ServiceConfig>,
|
||||
Option<net::SocketAddr>,
|
||||
Option<Box<dyn DataFactory>>,
|
||||
Option<Extensions>,
|
||||
Handshake<T, Bytes>,
|
||||
),
|
||||
}
|
||||
@ -360,6 +380,7 @@ where
|
||||
ref mut config,
|
||||
ref peer_addr,
|
||||
ref mut on_connect,
|
||||
ref mut on_connect_data,
|
||||
ref mut handshake,
|
||||
) => match Pin::new(handshake).poll(cx) {
|
||||
Poll::Ready(Ok(conn)) => {
|
||||
@ -367,6 +388,7 @@ where
|
||||
srv.take().unwrap(),
|
||||
conn,
|
||||
on_connect.take(),
|
||||
on_connect_data.take().unwrap(),
|
||||
config.take().unwrap(),
|
||||
None,
|
||||
*peer_addr,
|
||||
|
@ -50,6 +50,7 @@ impl<'a> io::Write for Writer<'a> {
|
||||
self.0.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! Basic http primitives for actix-net framework.
|
||||
//! HTTP primitives for the Actix ecosystem.
|
||||
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![allow(
|
||||
@ -7,6 +7,9 @@
|
||||
clippy::new_without_default,
|
||||
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]
|
||||
extern crate log;
|
||||
@ -77,3 +80,5 @@ pub enum Protocol {
|
||||
Http1,
|
||||
Http2,
|
||||
}
|
||||
|
||||
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
|
||||
|
@ -554,8 +554,9 @@ impl ResponseBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// This method calls provided closure with builder reference if value is
|
||||
/// true.
|
||||
/// This method calls provided closure with builder reference if value is `true`.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "Use an if statement."]
|
||||
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce(&mut ResponseBuilder),
|
||||
@ -566,8 +567,9 @@ impl ResponseBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// This method calls provided closure with builder reference if value is
|
||||
/// Some.
|
||||
/// This method calls provided closure with builder reference if value is `Some`.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "Use an if-let construction."]
|
||||
pub fn if_some<T, F>(&mut self, value: Option<T>, f: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce(T, &mut ResponseBuilder),
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, net, rc};
|
||||
use std::{fmt, net, rc::Rc};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_rt::net::TcpStream;
|
||||
@ -20,15 +20,17 @@ use crate::error::{DispatchError, Error};
|
||||
use crate::helpers::DataFactory;
|
||||
use crate::request::Request;
|
||||
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>> {
|
||||
srv: S,
|
||||
cfg: ServiceConfig,
|
||||
expect: X,
|
||||
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)>,
|
||||
}
|
||||
|
||||
@ -66,6 +68,7 @@ where
|
||||
expect: h1::ExpectHandler,
|
||||
upgrade: None,
|
||||
on_connect: None,
|
||||
on_connect_ext: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -81,6 +84,7 @@ where
|
||||
expect: h1::ExpectHandler,
|
||||
upgrade: None,
|
||||
on_connect: None,
|
||||
on_connect_ext: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -113,6 +117,7 @@ where
|
||||
srv: self.srv,
|
||||
upgrade: self.upgrade,
|
||||
on_connect: self.on_connect,
|
||||
on_connect_ext: self.on_connect_ext,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -138,6 +143,7 @@ where
|
||||
srv: self.srv,
|
||||
expect: self.expect,
|
||||
on_connect: self.on_connect,
|
||||
on_connect_ext: self.on_connect_ext,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -145,11 +151,17 @@ where
|
||||
/// Set on connect callback.
|
||||
pub(crate) fn on_connect(
|
||||
mut self,
|
||||
f: Option<rc::Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||
f: Option<Rc<dyn Fn(&T) -> Box<dyn DataFactory>>>,
|
||||
) -> Self {
|
||||
self.on_connect = f;
|
||||
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>
|
||||
@ -355,6 +367,7 @@ where
|
||||
expect: None,
|
||||
upgrade: None,
|
||||
on_connect: self.on_connect.clone(),
|
||||
on_connect_ext: self.on_connect_ext.clone(),
|
||||
cfg: self.cfg.clone(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
@ -378,7 +391,8 @@ pub struct HttpServiceResponse<
|
||||
fut_upg: Option<U::Future>,
|
||||
expect: Option<X::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,
|
||||
_t: PhantomData<(T, B)>,
|
||||
}
|
||||
@ -429,6 +443,7 @@ where
|
||||
.fut
|
||||
.poll(cx)
|
||||
.map_err(|e| log::error!("Init http service error: {:?}", e)));
|
||||
|
||||
Poll::Ready(result.map(|service| {
|
||||
let this = self.as_mut().project();
|
||||
HttpServiceHandler::new(
|
||||
@ -437,6 +452,7 @@ where
|
||||
this.expect.take().unwrap(),
|
||||
this.upgrade.take(),
|
||||
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>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
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)>,
|
||||
}
|
||||
|
||||
@ -469,11 +486,13 @@ where
|
||||
srv: S,
|
||||
expect: X,
|
||||
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 {
|
||||
cfg,
|
||||
on_connect,
|
||||
on_connect_ext,
|
||||
srv: CloneableService::new(srv),
|
||||
expect: CloneableService::new(expect),
|
||||
upgrade: upgrade.map(CloneableService::new),
|
||||
@ -543,11 +562,12 @@ where
|
||||
}
|
||||
|
||||
fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future {
|
||||
let on_connect = if let Some(ref on_connect) = self.on_connect {
|
||||
Some(on_connect(&io))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut connect_extensions = Extensions::new();
|
||||
|
||||
let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io));
|
||||
if let Some(ref handler) = self.on_connect_ext {
|
||||
handler(&io, &mut connect_extensions);
|
||||
}
|
||||
|
||||
match proto {
|
||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||
@ -555,10 +575,12 @@ where
|
||||
server::handshake(io),
|
||||
self.cfg.clone(),
|
||||
self.srv.clone(),
|
||||
on_connect,
|
||||
deprecated_on_connect,
|
||||
connect_extensions,
|
||||
peer_addr,
|
||||
))),
|
||||
},
|
||||
|
||||
Protocol::Http1 => HttpServiceHandlerResponse {
|
||||
state: State::H1(h1::Dispatcher::new(
|
||||
io,
|
||||
@ -566,7 +588,8 @@ where
|
||||
self.srv.clone(),
|
||||
self.expect.clone(),
|
||||
self.upgrade.clone(),
|
||||
on_connect,
|
||||
deprecated_on_connect,
|
||||
connect_extensions,
|
||||
peer_addr,
|
||||
)),
|
||||
},
|
||||
@ -595,6 +618,7 @@ where
|
||||
ServiceConfig,
|
||||
CloneableService<S>,
|
||||
Option<Box<dyn DataFactory>>,
|
||||
Extensions,
|
||||
Option<net::SocketAddr>,
|
||||
)>,
|
||||
),
|
||||
@ -670,9 +694,16 @@ where
|
||||
} else {
|
||||
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(
|
||||
srv, conn, on_connect, cfg, None, peer_addr,
|
||||
srv,
|
||||
conn,
|
||||
on_connect,
|
||||
on_connect_data,
|
||||
cfg,
|
||||
None,
|
||||
peer_addr,
|
||||
)));
|
||||
self.poll(cx)
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ use std::ptr::copy_nonoverlapping;
|
||||
use std::slice;
|
||||
|
||||
// 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> {
|
||||
/// # Safety
|
||||
@ -12,10 +14,11 @@ impl<'a> ShortSlice<'a> {
|
||||
unsafe fn new(slice: &'a mut [u8]) -> Self {
|
||||
// Sanity check for debug builds
|
||||
debug_assert!(slice.len() < 8);
|
||||
ShortSlice(slice)
|
||||
ShortSlice { inner: slice }
|
||||
}
|
||||
|
||||
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) {
|
||||
// SAFETY: we know that a `ShortSlice` fits in a u64
|
||||
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;
|
||||
#[allow(trivial_casts)]
|
||||
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
|
||||
// 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 {
|
||||
// We didn't cross even one aligned boundary!
|
||||
|
||||
|
@ -411,8 +411,10 @@ async fn test_h2_on_connect() {
|
||||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.on_connect(|_| 10usize)
|
||||
.on_connect_ext(|_, data| data.insert(20isize))
|
||||
.h2(|req: Request| {
|
||||
assert!(req.extensions().contains::<usize>());
|
||||
assert!(req.extensions().contains::<isize>());
|
||||
ok::<_, ()>(Response::Ok().finish())
|
||||
})
|
||||
.openssl(ssl_acceptor())
|
||||
|
@ -663,8 +663,10 @@ async fn test_h1_on_connect() {
|
||||
let srv = test_server(|| {
|
||||
HttpService::build()
|
||||
.on_connect(|_| 10usize)
|
||||
.on_connect_ext(|_, data| data.insert(20isize))
|
||||
.h1(|req: Request| {
|
||||
assert!(req.extensions().contains::<usize>());
|
||||
assert!(req.extensions().contains::<isize>());
|
||||
future::ok::<_, ()>(Response::Ok().finish())
|
||||
})
|
||||
.tcp()
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2020-xx-xx
|
||||
* Fix multipart consuming payload before header checks #1513
|
||||
|
||||
|
||||
## 3.0.0 - 2020-09-11
|
||||
|
@ -36,6 +36,9 @@ impl FromRequest for Multipart {
|
||||
|
||||
#[inline]
|
||||
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,
|
||||
{
|
||||
match Self::boundary(headers) {
|
||||
Ok(boundary) => 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,
|
||||
}))),
|
||||
},
|
||||
Err(err) => Multipart {
|
||||
error: Some(err),
|
||||
safety: Safety::new(),
|
||||
inner: None,
|
||||
},
|
||||
Ok(boundary) => Multipart::from_boundary(boundary, stream),
|
||||
Err(err) => Multipart::from_error(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 Ok(content_type) = content_type.to_str() {
|
||||
if let Ok(ct) = content_type.parse::<mime::Mime>() {
|
||||
@ -102,6 +89,32 @@ impl Multipart {
|
||||
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 {
|
||||
@ -815,6 +828,8 @@ mod tests {
|
||||
use actix_http::h1::Payload;
|
||||
use actix_utils::mpsc;
|
||||
use actix_web::http::header::{DispositionParam, DispositionType};
|
||||
use actix_web::test::TestRequest;
|
||||
use actix_web::FromRequest;
|
||||
use bytes::Bytes;
|
||||
use futures_util::future::lazy;
|
||||
|
||||
@ -1151,4 +1166,38 @@ mod tests {
|
||||
);
|
||||
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
|
||||
|
||||
## Unreleased - 2020-xx-xx
|
||||
|
||||
* Upgrade `pin-project` to `1.0`.
|
||||
|
||||
## 3.0.0 - 2020-09-11
|
||||
* No significant changes from `3.0.0-beta.2`.
|
||||
|
@ -23,7 +23,7 @@ actix-codec = "0.3.0"
|
||||
bytes = "0.5.2"
|
||||
futures-channel = { 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]
|
||||
actix-rt = "1.1.1"
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
## 0.4.0 - 2020-09-20
|
||||
* 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
|
||||
[#1674]: https://github.com/actix/actix-web/pull/1674
|
||||
@ -21,47 +21,48 @@
|
||||
[#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]
|
||||
|
||||
[#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]
|
||||
* Allow the handler function to be named as `config` [#1290]
|
||||
|
||||
[#1368]: https://github.com/actix/actix-web/issues/1368
|
||||
[#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
|
||||
|
||||
## [0.1.3] - 2019-10-14
|
||||
|
||||
## 0.1.3 - 2019-10-14
|
||||
* Bump up `syn` & `quote` to 1.0
|
||||
* 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
|
||||
|
||||
## [0.1.1] - 2019-06-01
|
||||
|
||||
## 0.1.1 - 2019-06-01
|
||||
* Add syn "extra-traits" feature
|
||||
|
||||
## [0.1.0] - 2019-05-18
|
||||
|
||||
## 0.1.0 - 2019-05-18
|
||||
* 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
|
||||
|
||||
## [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
|
||||
|
||||
## [0.1.0-alpha.1] - 2019-03-28
|
||||
|
||||
## 0.1.0-alpha.1 - 2019-03-28
|
||||
* Initial impl
|
||||
|
@ -6,9 +6,8 @@ fn compile_macros() {
|
||||
t.compile_fail("tests/trybuild/simple-fail.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)
|
||||
}
|
||||
|
||||
@ -25,3 +24,13 @@ fn test_route_missing_method(t: &trybuild::TestCases) {
|
||||
|
||||
#[rustversion::nightly]
|
||||
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
|
||||
|
||||
|
||||
## 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
|
||||
### Changed
|
||||
* `Client::build` was renamed to `Client::builder`.
|
||||
|
@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
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"
|
||||
keywords = ["actix", "http", "framework", "async", "web"]
|
||||
homepage = "https://actix.rs"
|
||||
@ -42,8 +42,9 @@ actix-service = "1.0.6"
|
||||
actix-http = "2.0.0"
|
||||
actix-rt = "1.0.0"
|
||||
|
||||
base64 = "0.12"
|
||||
base64 = "0.13"
|
||||
bytes = "0.5.3"
|
||||
cfg-if = "1.0"
|
||||
derive_more = "0.99.2"
|
||||
futures-core = { version = "0.3.5", default-features = false }
|
||||
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/)
|
||||
* [API Documentation](https://docs.rs/awc/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [awc](https://crates.io/crates/awc)
|
||||
* Minimum supported Rust version: 1.40 or later
|
||||
## Documentation & Resources
|
||||
|
||||
- [API Documentation](https://docs.rs/awc/2.0.1)
|
||||
- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https)
|
||||
- [Chat on Gitter](https://gitter.im/actix/actix-web)
|
||||
- Minimum Supported Rust Version (MSRV): 1.42.0
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -1,11 +1,4 @@
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![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.
|
||||
//! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem.
|
||||
//!
|
||||
//! ## 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::convert::TryFrom;
|
||||
use std::rc::Rc;
|
||||
|
@ -21,10 +21,15 @@ use crate::frozen::FrozenClientRequest;
|
||||
use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest};
|
||||
use crate::ClientConfig;
|
||||
|
||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
const HTTPS_ENCODING: &str = "br, gzip, deflate";
|
||||
#[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))]
|
||||
const HTTPS_ENCODING: &str = "br";
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] {
|
||||
const HTTPS_ENCODING: &str = "br, gzip, deflate";
|
||||
} else if #[cfg(feature = "compress")] {
|
||||
const HTTPS_ENCODING: &str = "br";
|
||||
} else {
|
||||
const HTTPS_ENCODING: &str = "identity";
|
||||
}
|
||||
}
|
||||
|
||||
/// An HTTP Client request builder
|
||||
///
|
||||
@ -349,8 +354,9 @@ impl ClientRequest {
|
||||
self
|
||||
}
|
||||
|
||||
/// This method calls provided closure with builder reference if
|
||||
/// value is `true`.
|
||||
/// This method calls provided closure with builder reference if value is `true`.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "Use an if statement."]
|
||||
pub fn if_true<F>(self, value: bool, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(ClientRequest) -> ClientRequest,
|
||||
@ -362,8 +368,9 @@ impl ClientRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// This method calls provided closure with builder reference if
|
||||
/// value is `Some`.
|
||||
/// This method calls provided closure with builder reference if value is `Some`.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "Use an if-let construction."]
|
||||
pub fn if_some<T, F>(self, value: Option<T>, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(T, ClientRequest) -> ClientRequest,
|
||||
@ -596,20 +603,27 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_basics() {
|
||||
let mut req = Client::new()
|
||||
let req = Client::new()
|
||||
.put("/")
|
||||
.version(Version::HTTP_2)
|
||||
.set(header::Date(SystemTime::now().into()))
|
||||
.content_type("plain/text")
|
||||
.if_true(true, |req| req.header(header::SERVER, "awc"))
|
||||
.if_true(false, |req| req.header(header::EXPECT, "awc"))
|
||||
.if_some(Some("server"), |val, req| {
|
||||
req.header(header::USER_AGENT, val)
|
||||
})
|
||||
.if_some(Option::<&str>::None, |_, req| {
|
||||
req.header(header::ALLOW, "1")
|
||||
})
|
||||
.content_length(100);
|
||||
.header(header::SERVER, "awc");
|
||||
|
||||
let req = if let Some(val) = Some("server") {
|
||||
req.header(header::USER_AGENT, val)
|
||||
} else {
|
||||
req
|
||||
};
|
||||
|
||||
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::DATE));
|
||||
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::EXPECT));
|
||||
assert_eq!(req.head.version, Version::HTTP_2);
|
||||
|
||||
let _ = req.headers_mut();
|
||||
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.services.extend(cfg.services);
|
||||
self.external.extend(cfg.external);
|
||||
self.extensions.extend(cfg.extensions);
|
||||
self
|
||||
}
|
||||
|
||||
@ -459,8 +460,8 @@ where
|
||||
{
|
||||
fn into_factory(self) -> AppInit<T, B> {
|
||||
AppInit {
|
||||
data: Rc::new(self.data),
|
||||
data_factories: Rc::new(self.data_factories),
|
||||
data: self.data.into_boxed_slice().into(),
|
||||
data_factories: self.data_factories.into_boxed_slice().into(),
|
||||
endpoint: self.endpoint,
|
||||
services: Rc::new(RefCell::new(self.services)),
|
||||
external: RefCell::new(self.external),
|
||||
|
@ -39,8 +39,8 @@ where
|
||||
{
|
||||
pub(crate) endpoint: T,
|
||||
pub(crate) extensions: RefCell<Option<Extensions>>,
|
||||
pub(crate) data: Rc<Vec<Box<dyn DataFactory>>>,
|
||||
pub(crate) data_factories: Rc<Vec<FnDataFactory>>,
|
||||
pub(crate) data: Rc<[Box<dyn DataFactory>]>,
|
||||
pub(crate) data_factories: Rc<[FnDataFactory]>,
|
||||
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
|
||||
pub(crate) default: Option<Rc<HttpNewService>>,
|
||||
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||
@ -88,15 +88,15 @@ where
|
||||
// complete pipeline creation
|
||||
*self.factory_ref.borrow_mut() = Some(AppRoutingFactory {
|
||||
default,
|
||||
services: Rc::new(
|
||||
services
|
||||
.into_iter()
|
||||
.map(|(mut rdef, srv, guards, nested)| {
|
||||
rmap.add(&mut rdef, nested);
|
||||
(rdef, srv, RefCell::new(guards))
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
services: services
|
||||
.into_iter()
|
||||
.map(|(mut rdef, srv, guards, nested)| {
|
||||
rmap.add(&mut rdef, nested);
|
||||
(rdef, srv, RefCell::new(guards))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice()
|
||||
.into(),
|
||||
});
|
||||
|
||||
// external resources
|
||||
@ -147,7 +147,7 @@ where
|
||||
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
data: Rc<Vec<Box<dyn DataFactory>>>,
|
||||
data: Rc<[Box<dyn DataFactory>]>,
|
||||
extensions: Option<Extensions>,
|
||||
|
||||
_t: PhantomData<B>,
|
||||
@ -273,7 +273,7 @@ where
|
||||
}
|
||||
|
||||
pub struct AppRoutingFactory {
|
||||
services: Rc<Vec<(ResourceDef, HttpNewService, RefCell<Option<Guards>>)>>,
|
||||
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
|
||||
default: Rc<HttpNewService>,
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ pub struct AppService {
|
||||
Option<Guards>,
|
||||
Option<Rc<ResourceMap>>,
|
||||
)>,
|
||||
service_data: Rc<Vec<Box<dyn DataFactory>>>,
|
||||
service_data: Rc<[Box<dyn DataFactory>]>,
|
||||
}
|
||||
|
||||
impl AppService {
|
||||
@ -39,7 +39,7 @@ impl AppService {
|
||||
pub(crate) fn new(
|
||||
config: AppConfig,
|
||||
default: Rc<HttpNewService>,
|
||||
service_data: Rc<Vec<Box<dyn DataFactory>>>,
|
||||
service_data: Rc<[Box<dyn DataFactory>]>,
|
||||
) -> Self {
|
||||
AppService {
|
||||
config,
|
||||
@ -178,6 +178,7 @@ pub struct ServiceConfig {
|
||||
pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
|
||||
pub(crate) data: Vec<Box<dyn DataFactory>>,
|
||||
pub(crate) external: Vec<ResourceDef>,
|
||||
pub(crate) extensions: Extensions,
|
||||
}
|
||||
|
||||
impl ServiceConfig {
|
||||
@ -186,6 +187,7 @@ impl ServiceConfig {
|
||||
services: Vec::new(),
|
||||
data: Vec::new(),
|
||||
external: Vec::new(),
|
||||
extensions: Extensions::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +200,14 @@ impl ServiceConfig {
|
||||
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.
|
||||
///
|
||||
/// This is same as `App::route()` method.
|
||||
@ -254,13 +264,16 @@ mod tests {
|
||||
async fn test_data() {
|
||||
let cfg = |cfg: &mut ServiceConfig| {
|
||||
cfg.data(10usize);
|
||||
cfg.app_data(15u8);
|
||||
};
|
||||
|
||||
let mut srv =
|
||||
init_service(App::new().configure(cfg).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
))
|
||||
.await;
|
||||
let mut srv = init_service(App::new().configure(cfg).service(
|
||||
web::resource("/").to(|_: web::Data<usize>, req: HttpRequest| {
|
||||
assert_eq!(*req.app_data::<u8>().unwrap(), 15u8);
|
||||
HttpResponse::Ok()
|
||||
}),
|
||||
))
|
||||
.await;
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = srv.call(req).await.unwrap();
|
||||
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::sync::Arc;
|
||||
|
||||
@ -19,25 +20,20 @@ pub(crate) type FnDataFactory =
|
||||
|
||||
/// Application data.
|
||||
///
|
||||
/// Application data is an arbitrary data attached to the app.
|
||||
/// Application data is available to all routes and could be added
|
||||
/// during application configuration process
|
||||
/// with `App::data()` method.
|
||||
/// Application level data is a piece of arbitrary data attached to the app, scope, or resource.
|
||||
/// Application data is available to all routes and can be added during the application
|
||||
/// configuration process via `App::data()`.
|
||||
///
|
||||
/// Application data could be accessed by using `Data<T>`
|
||||
/// extractor where `T` is data type.
|
||||
/// Application data can be accessed by using `Data<T>` extractor where `T` is data type.
|
||||
///
|
||||
/// **Note**: http server accepts an application factory rather than
|
||||
/// an application instance. Http server constructs an application
|
||||
/// instance for each thread, thus application data must be constructed
|
||||
/// multiple times. If you want to share data between different
|
||||
/// threads, a shareable object should be used, e.g. `Send + Sync`. Application
|
||||
/// 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`.
|
||||
/// **Note**: http server accepts an application factory rather than an application instance. HTTP
|
||||
/// server constructs an application instance for each thread, thus application data must be
|
||||
/// constructed multiple times. If you want to share data between different threads, a shareable
|
||||
/// object should be used, e.g. `Send + Sync`. Application data does not need to be `Send`
|
||||
/// or `Sync`. Internally `Data` uses `Arc`.
|
||||
///
|
||||
/// If route data is not set for a handler, using `Data<T>` extractor would
|
||||
/// cause *Internal Server Error* response.
|
||||
/// If route data is not set for a handler, using `Data<T>` extractor would cause *Internal
|
||||
/// Server Error* response.
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::sync::Mutex;
|
||||
@ -47,7 +43,7 @@ pub(crate) type FnDataFactory =
|
||||
/// 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 {
|
||||
/// let mut data = data.lock().unwrap();
|
||||
/// data.counter += 1;
|
||||
@ -66,14 +62,10 @@ pub(crate) type FnDataFactory =
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Data<T>(Arc<T>);
|
||||
pub struct Data<T: ?Sized>(Arc<T>);
|
||||
|
||||
impl<T> Data<T> {
|
||||
/// Create new `Data` instance.
|
||||
///
|
||||
/// Internally `Data` type uses `Arc`. if your data implements
|
||||
/// `Send` + `Sync` traits you can use `web::Data::new()` and
|
||||
/// avoid double `Arc`.
|
||||
pub fn new(state: T) -> Data<T> {
|
||||
Data(Arc::new(state))
|
||||
}
|
||||
@ -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>;
|
||||
|
||||
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> {
|
||||
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 {
|
||||
Data(arc)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> FromRequest for Data<T> {
|
||||
impl<T: ?Sized + 'static> FromRequest for Data<T> {
|
||||
type Config = ();
|
||||
type Error = Error;
|
||||
type Future = Ready<Result<Self, Error>>;
|
||||
@ -121,8 +113,9 @@ impl<T: 'static> FromRequest for Data<T> {
|
||||
} else {
|
||||
log::debug!(
|
||||
"Failed to construct App-level Data extractor. \
|
||||
Request path: {:?}",
|
||||
req.path()
|
||||
Request path: {:?} (type: {})",
|
||||
req.path(),
|
||||
type_name::<T>(),
|
||||
);
|
||||
err(ErrorInternalServerError(
|
||||
"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 {
|
||||
if !extensions.contains::<Data<T>>() {
|
||||
extensions.insert(Data(self.0.clone()));
|
||||
@ -293,4 +286,24 @@ mod tests {
|
||||
let data_from_arc = Data::from(Arc::new(String::from("test-123")));
|
||||
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;
|
||||
pub mod middleware;
|
||||
mod request;
|
||||
mod request_data;
|
||||
mod resource;
|
||||
mod responder;
|
||||
mod rmap;
|
||||
|
@ -13,7 +13,7 @@ use actix_service::{Service, Transform};
|
||||
use bytes::Bytes;
|
||||
use futures_util::future::{ok, Ready};
|
||||
use log::debug;
|
||||
use regex::Regex;
|
||||
use regex::{Regex, RegexSet};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
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 format:
|
||||
///
|
||||
/// ```ignore
|
||||
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||
/// ```plain
|
||||
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::middleware::Logger;
|
||||
/// use actix_web::App;
|
||||
/// use actix_web::{middleware::Logger, App};
|
||||
///
|
||||
/// fn main() {
|
||||
/// std::env::set_var("RUST_LOG", "actix_web=info");
|
||||
/// env_logger::init();
|
||||
/// std::env::set_var("RUST_LOG", "actix_web=info");
|
||||
/// env_logger::init();
|
||||
///
|
||||
/// let app = App::new()
|
||||
/// .wrap(Logger::default())
|
||||
/// .wrap(Logger::new("%a %{User-Agent}i"));
|
||||
/// }
|
||||
/// let app = App::new()
|
||||
/// .wrap(Logger::default())
|
||||
/// .wrap(Logger::new("%a %{User-Agent}i"));
|
||||
/// ```
|
||||
///
|
||||
/// ## Format
|
||||
@ -80,6 +78,8 @@ use crate::HttpResponse;
|
||||
///
|
||||
/// `%{FOO}e` os.environ['FOO']
|
||||
///
|
||||
/// `%{FOO}xi` [custom request replacement](Logger::custom_request_replace) labelled "FOO"
|
||||
///
|
||||
/// # Security
|
||||
/// **\*** It is calculated using
|
||||
/// [`ConnectionInfo::realip_remote_addr()`](../dev/struct.ConnectionInfo.html#method.realip_remote_addr)
|
||||
@ -92,6 +92,7 @@ pub struct Logger(Rc<Inner>);
|
||||
struct Inner {
|
||||
format: Format,
|
||||
exclude: HashSet<String>,
|
||||
exclude_regex: RegexSet,
|
||||
}
|
||||
|
||||
impl Logger {
|
||||
@ -100,6 +101,7 @@ impl Logger {
|
||||
Logger(Rc::new(Inner {
|
||||
format: Format::new(format),
|
||||
exclude: HashSet::new(),
|
||||
exclude_regex: RegexSet::empty(),
|
||||
}))
|
||||
}
|
||||
|
||||
@ -111,18 +113,69 @@ impl Logger {
|
||||
.insert(path.into());
|
||||
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 {
|
||||
/// Create `Logger` middleware with format:
|
||||
///
|
||||
/// ```ignore
|
||||
/// ```plain
|
||||
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||
/// ```
|
||||
fn default() -> Logger {
|
||||
Logger(Rc::new(Inner {
|
||||
format: Format::default(),
|
||||
exclude: HashSet::new(),
|
||||
exclude_regex: RegexSet::empty(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -140,6 +193,17 @@ where
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
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 {
|
||||
service,
|
||||
inner: self.0.clone(),
|
||||
@ -168,7 +232,9 @@ where
|
||||
}
|
||||
|
||||
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 {
|
||||
fut: self.service.call(req),
|
||||
format: None,
|
||||
@ -296,7 +362,6 @@ impl<B: MessageBody> MessageBody for StreamLog<B> {
|
||||
/// A formatting style for the `Logger`, consisting of multiple
|
||||
/// `FormatText`s concatenated into one line.
|
||||
#[derive(Clone)]
|
||||
#[doc(hidden)]
|
||||
struct Format(Vec<FormatText>);
|
||||
|
||||
impl Default for Format {
|
||||
@ -312,7 +377,8 @@ impl Format {
|
||||
/// Returns `None` if the format string syntax is incorrect.
|
||||
pub fn new(s: &str) -> Format {
|
||||
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 results = Vec::new();
|
||||
@ -340,6 +406,7 @@ impl Format {
|
||||
HeaderName::try_from(key.as_str()).unwrap(),
|
||||
),
|
||||
"e" => FormatText::EnvironHeader(key.as_str().to_owned()),
|
||||
"xi" => FormatText::CustomRequest(key.as_str().to_owned(), None),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
} else {
|
||||
@ -369,7 +436,9 @@ impl Format {
|
||||
/// A string of text to be logged. This is either one of the data
|
||||
/// fields supported by the `Logger`, or a custom `String`.
|
||||
#[doc(hidden)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
// TODO: remove pub on next breaking change
|
||||
pub enum FormatText {
|
||||
Str(String),
|
||||
Percent,
|
||||
@ -385,6 +454,26 @@ pub enum FormatText {
|
||||
RequestHeader(HeaderName),
|
||||
ResponseHeader(HeaderName),
|
||||
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 {
|
||||
@ -441,7 +530,7 @@ impl FormatText {
|
||||
}
|
||||
|
||||
fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) {
|
||||
match *self {
|
||||
match &*self {
|
||||
FormatText::RequestLine => {
|
||||
*self = if req.query_string().is_empty() {
|
||||
FormatText::Str(format!(
|
||||
@ -493,11 +582,20 @@ impl FormatText {
|
||||
};
|
||||
*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>(
|
||||
&'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>,
|
||||
);
|
||||
@ -515,7 +613,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::http::{header, StatusCode};
|
||||
use crate::test::TestRequest;
|
||||
use crate::test::{self, TestRequest};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_logger() {
|
||||
@ -538,6 +636,28 @@ mod tests {
|
||||
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]
|
||||
async fn test_url_path() {
|
||||
let mut format = Format::new("%T %U");
|
||||
@ -662,4 +782,45 @@ mod tests {
|
||||
println!("{}", s);
|
||||
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.
|
||||
/// This will require all routes to end in a trailing slash for them to be accessible.
|
||||
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,
|
||||
}
|
||||
@ -33,7 +37,8 @@ impl Default for TrailingSlash {
|
||||
/// Performs following:
|
||||
///
|
||||
/// - 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
|
||||
/// 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
|
||||
let path = match self.trailing_slash_behavior {
|
||||
TrailingSlash::Always => original_path.to_string() + "/",
|
||||
TrailingSlash::MergeOnly => original_path.to_string(),
|
||||
TrailingSlash::Trim => original_path.trim_end_matches('/').to_string(),
|
||||
};
|
||||
|
||||
@ -237,6 +243,38 @@ mod tests {
|
||||
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]
|
||||
async fn test_in_place_normalization() {
|
||||
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::rc::Rc;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use actix_router::ResourceDef;
|
||||
use fxhash::FxHashMap;
|
||||
@ -11,7 +11,7 @@ use crate::request::HttpRequest;
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResourceMap {
|
||||
root: ResourceDef,
|
||||
parent: RefCell<Option<Rc<ResourceMap>>>,
|
||||
parent: RefCell<Weak<ResourceMap>>,
|
||||
named: FxHashMap<String, ResourceDef>,
|
||||
patterns: Vec<(ResourceDef, Option<Rc<ResourceMap>>)>,
|
||||
}
|
||||
@ -20,7 +20,7 @@ impl ResourceMap {
|
||||
pub fn new(root: ResourceDef) -> Self {
|
||||
ResourceMap {
|
||||
root,
|
||||
parent: RefCell::new(None),
|
||||
parent: RefCell::new(Weak::new()),
|
||||
named: FxHashMap::default(),
|
||||
patterns: Vec::new(),
|
||||
}
|
||||
@ -38,7 +38,7 @@ impl ResourceMap {
|
||||
pub(crate) fn finish(&self, current: Rc<ResourceMap>) {
|
||||
for (_, nested) in &self.patterns {
|
||||
if let Some(ref nested) = nested {
|
||||
*nested.parent.borrow_mut() = Some(current.clone());
|
||||
*nested.parent.borrow_mut() = Rc::downgrade(¤t);
|
||||
nested.finish(nested.clone());
|
||||
}
|
||||
}
|
||||
@ -210,7 +210,7 @@ impl ResourceMap {
|
||||
U: Iterator<Item = I>,
|
||||
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)?;
|
||||
}
|
||||
if self.root.resource_path(path, elements) {
|
||||
@ -230,7 +230,7 @@ impl ResourceMap {
|
||||
U: Iterator<Item = I>,
|
||||
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) {
|
||||
self.fill_root(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/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::pin::Pin;
|
||||
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}/path2 - `GET` requests
|
||||
/// * /{project_id}/path3 - `HEAD` requests
|
||||
///
|
||||
pub struct Scope<T = ScopeEndpoint> {
|
||||
endpoint: T,
|
||||
rdef: String,
|
||||
@ -210,6 +209,9 @@ where
|
||||
|
||||
self.data = Some(data);
|
||||
}
|
||||
self.data
|
||||
.get_or_insert_with(Extensions::new)
|
||||
.extend(cfg.extensions);
|
||||
self
|
||||
}
|
||||
|
||||
@ -443,16 +445,17 @@ where
|
||||
*self.factory_ref.borrow_mut() = Some(ScopeFactory {
|
||||
data: self.data.take().map(Rc::new),
|
||||
default: self.default.clone(),
|
||||
services: Rc::new(
|
||||
cfg.into_services()
|
||||
.1
|
||||
.into_iter()
|
||||
.map(|(mut rdef, srv, guards, nested)| {
|
||||
rmap.add(&mut rdef, nested);
|
||||
(rdef, srv, RefCell::new(guards))
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
services: cfg
|
||||
.into_services()
|
||||
.1
|
||||
.into_iter()
|
||||
.map(|(mut rdef, srv, guards, nested)| {
|
||||
rmap.add(&mut rdef, nested);
|
||||
(rdef, srv, RefCell::new(guards))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice()
|
||||
.into(),
|
||||
});
|
||||
|
||||
// get guards
|
||||
@ -474,7 +477,7 @@ where
|
||||
|
||||
pub struct ScopeFactory {
|
||||
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>>>>,
|
||||
}
|
||||
|
||||
|
116
src/server.rs
116
src/server.rs
@ -1,8 +1,14 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{fmt, io, net};
|
||||
use std::{
|
||||
any::Any,
|
||||
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_service::{map_config, IntoServiceFactory, Service, ServiceFactory};
|
||||
|
||||
@ -64,6 +70,7 @@ where
|
||||
backlog: i32,
|
||||
sockets: Vec<Socket>,
|
||||
builder: ServerBuilder,
|
||||
on_connect_fn: Option<Arc<dyn Fn(&dyn Any, &mut Extensions) + Send + Sync>>,
|
||||
_t: PhantomData<(S, B)>,
|
||||
}
|
||||
|
||||
@ -91,6 +98,32 @@ where
|
||||
backlog: 1024,
|
||||
sockets: Vec::new(),
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -240,6 +273,7 @@ where
|
||||
addr,
|
||||
scheme: "http",
|
||||
});
|
||||
let on_connect_fn = self.on_connect_fn.clone();
|
||||
|
||||
self.builder = self.builder.listen(
|
||||
format!("actix-web-service-{}", addr),
|
||||
@ -252,11 +286,20 @@ where
|
||||
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
||||
);
|
||||
|
||||
HttpService::build()
|
||||
let svc = HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_timeout(c.client_timeout)
|
||||
.local_addr(addr)
|
||||
.finish(map_config(factory(), move |_| cfg.clone()))
|
||||
.local_addr(addr);
|
||||
|
||||
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()
|
||||
},
|
||||
)?;
|
||||
@ -289,6 +332,8 @@ where
|
||||
scheme: "https",
|
||||
});
|
||||
|
||||
let on_connect_fn = self.on_connect_fn.clone();
|
||||
|
||||
self.builder = self.builder.listen(
|
||||
format!("actix-web-service-{}", addr),
|
||||
lst,
|
||||
@ -299,11 +344,21 @@ where
|
||||
addr,
|
||||
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
||||
);
|
||||
HttpService::build()
|
||||
|
||||
let svc = HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_timeout(c.client_timeout)
|
||||
.client_disconnect(c.client_shutdown)
|
||||
.finish(map_config(factory(), move |_| cfg.clone()))
|
||||
.client_disconnect(c.client_shutdown);
|
||||
|
||||
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())
|
||||
},
|
||||
)?;
|
||||
@ -336,6 +391,8 @@ where
|
||||
scheme: "https",
|
||||
});
|
||||
|
||||
let on_connect_fn = self.on_connect_fn.clone();
|
||||
|
||||
self.builder = self.builder.listen(
|
||||
format!("actix-web-service-{}", addr),
|
||||
lst,
|
||||
@ -346,11 +403,21 @@ where
|
||||
addr,
|
||||
c.host.clone().unwrap_or_else(|| format!("{}", addr)),
|
||||
);
|
||||
HttpService::build()
|
||||
|
||||
let svc = HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_timeout(c.client_timeout)
|
||||
.client_disconnect(c.client_shutdown)
|
||||
.finish(map_config(factory(), move |_| cfg.clone()))
|
||||
.client_disconnect(c.client_shutdown);
|
||||
|
||||
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())
|
||||
},
|
||||
)?;
|
||||
@ -441,7 +508,7 @@ where
|
||||
}
|
||||
|
||||
#[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(
|
||||
mut self,
|
||||
lst: std::os::unix::net::UnixListener,
|
||||
@ -460,6 +527,7 @@ where
|
||||
});
|
||||
|
||||
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 || {
|
||||
let c = cfg.lock().unwrap();
|
||||
@ -468,11 +536,23 @@ where
|
||||
socket_addr,
|
||||
c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)),
|
||||
);
|
||||
|
||||
pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then(
|
||||
HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_timeout(c.client_timeout)
|
||||
.finish(map_config(factory(), move |_| config.clone())),
|
||||
{
|
||||
let svc = HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.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)
|
||||
|
@ -283,7 +283,7 @@ impl JsonConfig {
|
||||
fn from_req(req: &HttpRequest) -> &Self {
|
||||
req.app_data::<Self>()
|
||||
.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 {
|
||||
req.app_data::<Self>()
|
||||
.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;
|
||||
|
||||
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;
|
||||
|
||||
use crate::error::BlockingError;
|
||||
@ -19,6 +19,7 @@ use crate::service::WebService;
|
||||
pub use crate::config::ServiceConfig;
|
||||
pub use crate::data::Data;
|
||||
pub use crate::request::HttpRequest;
|
||||
pub use crate::request_data::ReqData;
|
||||
pub use crate::types::*;
|
||||
|
||||
/// Create resource for a specific path.
|
||||
|
@ -3,6 +3,7 @@
|
||||
## Unreleased - 2020-xx-xx
|
||||
|
||||
* add ability to set address for `TestServer` [#1645]
|
||||
* Upgrade `base64` to `0.13`.
|
||||
|
||||
[#1645]: https://github.com/actix/actix-web/pull/1645
|
||||
|
||||
|
@ -38,7 +38,7 @@ actix-server = "1.0.0"
|
||||
actix-testing = "1.0.0"
|
||||
awc = "2.0.0"
|
||||
|
||||
base64 = "0.12"
|
||||
base64 = "0.13"
|
||||
bytes = "0.5.3"
|
||||
futures-core = { version = "0.3.5", default-features = false }
|
||||
http = "0.2.0"
|
||||
|
Reference in New Issue
Block a user