mirror of https://github.com/fafhrd91/actix-web
Updated Changlog
This commit is contained in:
commit
b4ed682b77
|
@ -32,7 +32,10 @@ script:
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then
|
if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then
|
||||||
cargo clean
|
cargo clean
|
||||||
cargo test --features="ssl,tls,rust-tls" -- --nocapture
|
cargo check --features rust-tls
|
||||||
|
cargo check --features ssl
|
||||||
|
cargo check --features tls
|
||||||
|
cargo test --features="ssl,tls,rust-tls,uds" -- --nocapture
|
||||||
fi
|
fi
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
||||||
|
|
108
CHANGES.md
108
CHANGES.md
|
@ -1,8 +1,112 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## [0.7.19] - 2019-04-19
|
## [x.x.xx] - xxxx-xx-xx
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Add `from_file` and `from_file_with_config` to `NamedFile` to allow sending files without a known path. #670
|
||||||
|
|
||||||
|
* Add `insert` and `remove` methods to `HttpResponseBuilder`
|
||||||
|
|
||||||
|
* Add client HTTP Authentication methods `.basic_auth()` and `.bearer_auth()`. #540
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Ignored the `If-Modified-Since` if `If-None-Match` is specified. #680
|
||||||
|
|
||||||
|
## [0.7.18] - 2019-01-10
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Add `with_cookie` for `TestRequest` to allow users to customize request cookie. #647
|
||||||
|
|
||||||
|
* Add `cookie` method for `TestRequest` to allow users to add cookie dynamically.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* StaticFiles decode special characters in request's path
|
||||||
|
|
||||||
|
* Fix test server listener leak #654
|
||||||
|
|
||||||
|
## [0.7.17] - 2018-12-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Support for custom content types in `JsonConfig`. #637
|
||||||
|
|
||||||
|
* Send `HTTP/1.1 100 Continue` if request contains `expect: continue` header #634
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631
|
||||||
|
|
||||||
|
* Access-Control-Allow-Origin header should only a return a single, matching origin. #603
|
||||||
|
|
||||||
|
## [0.7.16] - 2018-12-11
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Implement `FromRequest` extractor for `Either<A,B>`
|
||||||
|
|
||||||
|
* Implement `ResponseError` for `SendError`
|
||||||
|
|
||||||
|
|
||||||
|
## [0.7.15] - 2018-12-05
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* `ClientConnector::resolver` now accepts `Into<Recipient>` instead of `Addr`. It enables user to implement own resolver.
|
||||||
|
|
||||||
|
* `QueryConfig` and `PathConfig` are made public.
|
||||||
|
|
||||||
|
* `AsyncResult::async` is changed to `AsyncResult::future` as `async` is reserved keyword in 2018 edition.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled
|
||||||
|
with `PathConfig::default().disable_decoding()`
|
||||||
|
|
||||||
|
|
||||||
|
## [0.7.14] - 2018-11-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Add method to configure custom error handler to `Query` and `Path` extractors.
|
||||||
|
|
||||||
|
* Add method to configure `SameSite` option in `CookieIdentityPolicy`.
|
||||||
|
|
||||||
|
* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled
|
||||||
|
with `PathConfig::default().disable_decoding()`
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fix websockets connection drop if request contains "content-length" header #567
|
||||||
|
|
||||||
|
* Fix keep-alive timer reset
|
||||||
|
|
||||||
|
* HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549
|
||||||
|
|
||||||
|
* Set nodelay for socket #560
|
||||||
|
|
||||||
|
|
||||||
|
## [0.7.13] - 2018-10-14
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed rustls support
|
||||||
|
|
||||||
|
* HttpServer not sending streamed request body on HTTP/2 requests #544
|
||||||
|
|
||||||
|
|
||||||
|
## [0.7.12] - 2018-10-10
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Set min version for actix
|
||||||
|
|
||||||
|
* Set min version for actix-net
|
||||||
|
|
||||||
* Added client HTTP Authentication methods `.basic_auth()` and `.bearer_auth()`. #540
|
|
||||||
|
|
||||||
## [0.7.11] - 2018-10-09
|
## [0.7.11] - 2018-10-09
|
||||||
|
|
||||||
|
|
23
Cargo.toml
23
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "0.7.11"
|
version = "0.7.18"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -61,21 +61,22 @@ flate2-rust = ["flate2/rust_backend"]
|
||||||
cell = ["actix-net/cell"]
|
cell = ["actix-net/cell"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.7.0"
|
actix = "0.7.9"
|
||||||
actix-net = "0.1.0"
|
actix-net = "0.2.6"
|
||||||
|
|
||||||
base64 = "0.9"
|
v_htmlescape = "0.4"
|
||||||
|
base64 = "0.10"
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
|
failure = "^0.1.2"
|
||||||
h2 = "0.1"
|
h2 = "0.1"
|
||||||
htmlescape = "0.3"
|
http = "^0.1.14"
|
||||||
http = "^0.1.8"
|
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "2.0.0-alpha"
|
mime_guess = "2.0.0-alpha"
|
||||||
num_cpus = "1.0"
|
num_cpus = "1.0"
|
||||||
percent-encoding = "1.0"
|
percent-encoding = "1.0"
|
||||||
rand = "0.5"
|
rand = "0.6"
|
||||||
regex = "1.0"
|
regex = "1.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
@ -86,15 +87,13 @@ encoding = "0.2"
|
||||||
language-tags = "0.2"
|
language-tags = "0.2"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
lazycell = "1.0.0"
|
lazycell = "1.0.0"
|
||||||
parking_lot = "0.6"
|
parking_lot = "0.7"
|
||||||
serde_urlencoded = "^0.5.3"
|
serde_urlencoded = "^0.5.3"
|
||||||
url = { version="1.7", features=["query_encoding"] }
|
url = { version="1.7", features=["query_encoding"] }
|
||||||
cookie = { version="0.11", features=["percent-encode"] }
|
cookie = { version="0.11", features=["percent-encode"] }
|
||||||
brotli2 = { version="^0.3.2", optional = true }
|
brotli2 = { version="^0.3.2", optional = true }
|
||||||
flate2 = { version="^1.0.2", optional = true, default-features = false }
|
flate2 = { version="^1.0.2", optional = true, default-features = false }
|
||||||
|
|
||||||
failure = "^0.1.2"
|
|
||||||
|
|
||||||
# io
|
# io
|
||||||
mio = "^0.6.13"
|
mio = "^0.6.13"
|
||||||
net2 = "0.2"
|
net2 = "0.2"
|
||||||
|
@ -106,7 +105,7 @@ slab = "0.4"
|
||||||
tokio = "0.1"
|
tokio = "0.1"
|
||||||
tokio-io = "0.1"
|
tokio-io = "0.1"
|
||||||
tokio-tcp = "0.1"
|
tokio-tcp = "0.1"
|
||||||
tokio-timer = "0.2"
|
tokio-timer = "0.2.8"
|
||||||
tokio-reactor = "0.1"
|
tokio-reactor = "0.1"
|
||||||
tokio-current-thread = "0.1"
|
tokio-current-thread = "0.1"
|
||||||
|
|
||||||
|
@ -128,7 +127,7 @@ webpki-roots = { version = "0.15", optional = true }
|
||||||
tokio-uds = { version="0.2", optional = true }
|
tokio-uds = { version="0.2", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.5"
|
env_logger = "0.6"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
30
MIGRATION.md
30
MIGRATION.md
|
@ -1,3 +1,33 @@
|
||||||
|
## 0.7.15
|
||||||
|
|
||||||
|
* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
|
||||||
|
your routes, you should use `%20`.
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource("/my index", |r| {
|
||||||
|
r.method(http::Method::GET)
|
||||||
|
.with(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let app = App::new().resource("/my%20index", |r| {
|
||||||
|
r.method(http::Method::GET)
|
||||||
|
.with(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* If you used `AsyncResult::async` you need to replace it with `AsyncResult::future`
|
||||||
|
|
||||||
|
|
||||||
## 0.7.4
|
## 0.7.4
|
||||||
|
|
||||||
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
|
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
|
||||||
|
|
|
@ -14,6 +14,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||||
* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/))
|
* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/))
|
||||||
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
|
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
|
||||||
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||||
|
* Experimental [Async/Await](https://github.com/mehcode/actix-web-async-await) support.
|
||||||
|
|
||||||
## Documentation & community resources
|
## Documentation & community resources
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||||
* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/)
|
* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/)
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||||
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||||
* Minimum supported Rust version: 1.26 or later
|
* Minimum supported Rust version: 1.31 or later
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
|
39
src/body.rs
39
src/body.rs
|
@ -1,5 +1,6 @@
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{fmt, mem};
|
use std::{fmt, mem};
|
||||||
|
|
||||||
|
@ -194,12 +195,30 @@ impl From<Vec<u8>> for Binary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Cow<'static, [u8]>> for Binary {
|
||||||
|
fn from(b: Cow<'static, [u8]>) -> Binary {
|
||||||
|
match b {
|
||||||
|
Cow::Borrowed(s) => Binary::Slice(s),
|
||||||
|
Cow::Owned(vec) => Binary::Bytes(Bytes::from(vec)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<String> for Binary {
|
impl From<String> for Binary {
|
||||||
fn from(s: String) -> Binary {
|
fn from(s: String) -> Binary {
|
||||||
Binary::Bytes(Bytes::from(s))
|
Binary::Bytes(Bytes::from(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Cow<'static, str>> for Binary {
|
||||||
|
fn from(s: Cow<'static, str>) -> Binary {
|
||||||
|
match s {
|
||||||
|
Cow::Borrowed(s) => Binary::Slice(s.as_ref()),
|
||||||
|
Cow::Owned(s) => Binary::Bytes(Bytes::from(s)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a String> for Binary {
|
impl<'a> From<&'a String> for Binary {
|
||||||
fn from(s: &'a String) -> Binary {
|
fn from(s: &'a String) -> Binary {
|
||||||
Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s)))
|
Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s)))
|
||||||
|
@ -287,6 +306,16 @@ mod tests {
|
||||||
assert_eq!(Binary::from("test").as_ref(), b"test");
|
assert_eq!(Binary::from("test").as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cow_str() {
|
||||||
|
let cow: Cow<'static, str> = Cow::Borrowed("test");
|
||||||
|
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||||
|
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||||
|
let cow: Cow<'static, str> = Cow::Owned("test".to_owned());
|
||||||
|
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||||
|
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_static_bytes() {
|
fn test_static_bytes() {
|
||||||
assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
|
assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
|
||||||
|
@ -307,6 +336,16 @@ mod tests {
|
||||||
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
|
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cow_bytes() {
|
||||||
|
let cow: Cow<'static, [u8]> = Cow::Borrowed(b"test");
|
||||||
|
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||||
|
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||||
|
let cow: Cow<'static, [u8]> = Cow::Owned(Vec::from("test"));
|
||||||
|
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||||
|
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_arc_string() {
|
fn test_arc_string() {
|
||||||
let b = Arc::new("test".to_owned());
|
let b = Arc::new("test".to_owned());
|
||||||
|
|
|
@ -3,9 +3,9 @@ use std::net::Shutdown;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::{fmt, io, mem, time};
|
use std::{fmt, io, mem, time};
|
||||||
|
|
||||||
use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError};
|
use actix_inner::actors::resolver::{Connect as ResolveConnect, Resolver, ResolverError};
|
||||||
use actix::{
|
use actix_inner::{
|
||||||
fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context,
|
fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context,
|
||||||
ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised,
|
ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised,
|
||||||
SystemService, WrapFuture,
|
SystemService, WrapFuture,
|
||||||
};
|
};
|
||||||
|
@ -37,15 +37,9 @@ use {
|
||||||
))]
|
))]
|
||||||
use {
|
use {
|
||||||
rustls::ClientConfig, std::io::Error as SslError, std::sync::Arc,
|
rustls::ClientConfig, std::io::Error as SslError, std::sync::Arc,
|
||||||
tokio_rustls::ClientConfigExt, webpki::DNSNameRef, webpki_roots,
|
tokio_rustls::TlsConnector as SslConnector, webpki::DNSNameRef, webpki_roots,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
feature = "rust-tls",
|
|
||||||
not(any(feature = "alpn", feature = "tls", feature = "ssl"))
|
|
||||||
))]
|
|
||||||
type SslConnector = Arc<ClientConfig>;
|
|
||||||
|
|
||||||
#[cfg(not(any(
|
#[cfg(not(any(
|
||||||
feature = "alpn",
|
feature = "alpn",
|
||||||
feature = "ssl",
|
feature = "ssl",
|
||||||
|
@ -226,7 +220,7 @@ pub struct ClientConnector {
|
||||||
acq_tx: mpsc::UnboundedSender<AcquiredConnOperation>,
|
acq_tx: mpsc::UnboundedSender<AcquiredConnOperation>,
|
||||||
acq_rx: Option<mpsc::UnboundedReceiver<AcquiredConnOperation>>,
|
acq_rx: Option<mpsc::UnboundedReceiver<AcquiredConnOperation>>,
|
||||||
|
|
||||||
resolver: Option<Addr<Resolver>>,
|
resolver: Option<Recipient<ResolveConnect>>,
|
||||||
conn_lifetime: Duration,
|
conn_lifetime: Duration,
|
||||||
conn_keep_alive: Duration,
|
conn_keep_alive: Duration,
|
||||||
limit: usize,
|
limit: usize,
|
||||||
|
@ -245,7 +239,7 @@ impl Actor for ClientConnector {
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
if self.resolver.is_none() {
|
if self.resolver.is_none() {
|
||||||
self.resolver = Some(Resolver::from_registry())
|
self.resolver = Some(Resolver::from_registry().recipient())
|
||||||
}
|
}
|
||||||
self.collect_periodic(ctx);
|
self.collect_periodic(ctx);
|
||||||
ctx.add_stream(self.acq_rx.take().unwrap());
|
ctx.add_stream(self.acq_rx.take().unwrap());
|
||||||
|
@ -282,7 +276,7 @@ impl Default for ClientConnector {
|
||||||
config
|
config
|
||||||
.root_store
|
.root_store
|
||||||
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||||
Arc::new(config)
|
SslConnector::from(Arc::new(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
@ -293,7 +287,7 @@ impl Default for ClientConnector {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::let_unit_value))]
|
#[cfg_attr(feature = "cargo-clippy", allow(let_unit_value))]
|
||||||
ClientConnector::with_connector_impl(connector)
|
ClientConnector::with_connector_impl(connector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -373,7 +367,7 @@ impl ClientConnector {
|
||||||
/// config
|
/// config
|
||||||
/// .root_store
|
/// .root_store
|
||||||
/// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
/// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||||
/// let conn = ClientConnector::with_connector(Arc::new(config)).start();
|
/// let conn = ClientConnector::with_connector(config).start();
|
||||||
///
|
///
|
||||||
/// conn.send(
|
/// conn.send(
|
||||||
/// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host
|
/// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host
|
||||||
|
@ -390,7 +384,7 @@ impl ClientConnector {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn with_connector(connector: ClientConfig) -> ClientConnector {
|
pub fn with_connector(connector: ClientConfig) -> ClientConnector {
|
||||||
// keep level of indirection for docstrings matching featureflags
|
// keep level of indirection for docstrings matching featureflags
|
||||||
Self::with_connector_impl(Arc::new(connector))
|
Self::with_connector_impl(SslConnector::from(Arc::new(connector)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
|
@ -509,8 +503,10 @@ impl ClientConnector {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use custom resolver actor
|
/// Use custom resolver actor
|
||||||
pub fn resolver(mut self, addr: Addr<Resolver>) -> Self {
|
///
|
||||||
self.resolver = Some(addr);
|
/// By default actix's Resolver is used.
|
||||||
|
pub fn resolver<A: Into<Recipient<ResolveConnect>>>(mut self, addr: A) -> Self {
|
||||||
|
self.resolver = Some(addr.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -832,7 +828,7 @@ impl ClientConnector {
|
||||||
let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap();
|
let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap();
|
||||||
fut::Either::A(
|
fut::Either::A(
|
||||||
act.connector
|
act.connector
|
||||||
.connect_async(host, stream)
|
.connect(host, stream)
|
||||||
.into_actor(act)
|
.into_actor(act)
|
||||||
.then(move |res, _, _| {
|
.then(move |res, _, _| {
|
||||||
match res {
|
match res {
|
||||||
|
@ -946,7 +942,7 @@ impl Handler<Connect> for ClientConnector {
|
||||||
}
|
}
|
||||||
|
|
||||||
let host = uri.host().unwrap().to_owned();
|
let host = uri.host().unwrap().to_owned();
|
||||||
let port = uri.port().unwrap_or_else(|| proto.port());
|
let port = uri.port_part().map(|port| port.as_u16()).unwrap_or_else(|| proto.port());
|
||||||
let key = Key {
|
let key = Key {
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # extern crate actix_web;
|
//! # extern crate actix_web;
|
||||||
|
//! # extern crate actix;
|
||||||
//! # extern crate futures;
|
//! # extern crate futures;
|
||||||
//! # extern crate tokio;
|
//! # extern crate tokio;
|
||||||
//! # use futures::Future;
|
|
||||||
//! # use std::process;
|
//! # use std::process;
|
||||||
//! use actix_web::{actix, client};
|
//! use actix_web::client;
|
||||||
|
//! use futures::Future;
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() {
|
||||||
//! actix::run(
|
//! actix::run(
|
||||||
|
@ -61,12 +62,13 @@ impl ResponseError for SendRequestError {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate actix;
|
||||||
/// # extern crate futures;
|
/// # extern crate futures;
|
||||||
/// # extern crate tokio;
|
/// # extern crate tokio;
|
||||||
/// # extern crate env_logger;
|
/// # extern crate env_logger;
|
||||||
/// # use futures::Future;
|
|
||||||
/// # use std::process;
|
/// # use std::process;
|
||||||
/// use actix_web::{actix, client};
|
/// use actix_web::client;
|
||||||
|
/// use futures::Future;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// actix::run(
|
/// actix::run(
|
||||||
|
|
|
@ -56,7 +56,7 @@ impl HttpResponseParser {
|
||||||
return Ok(Async::Ready(msg));
|
return Ok(Async::Ready(msg));
|
||||||
}
|
}
|
||||||
Async::NotReady => {
|
Async::NotReady => {
|
||||||
if buf.capacity() >= MAX_BUFFER_SIZE {
|
if buf.len() >= MAX_BUFFER_SIZE {
|
||||||
return Err(HttpResponseParserError::Error(
|
return Err(HttpResponseParserError::Error(
|
||||||
ParseError::TooLarge,
|
ParseError::TooLarge,
|
||||||
));
|
));
|
||||||
|
|
|
@ -6,7 +6,8 @@ use std::time::{Duration, Instant};
|
||||||
use std::{io, mem};
|
use std::{io, mem};
|
||||||
use tokio_timer::Delay;
|
use tokio_timer::Delay;
|
||||||
|
|
||||||
use actix::{Addr, Request, SystemService};
|
use actix_inner::dev::Request;
|
||||||
|
use actix::{Addr, SystemService};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,
|
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,
|
||||||
|
|
|
@ -28,11 +28,12 @@ use httprequest::HttpRequest;
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate actix;
|
||||||
/// # extern crate futures;
|
/// # extern crate futures;
|
||||||
/// # extern crate tokio;
|
/// # extern crate tokio;
|
||||||
/// # use futures::Future;
|
/// # use futures::Future;
|
||||||
/// # use std::process;
|
/// # use std::process;
|
||||||
/// use actix_web::{actix, client};
|
/// use actix_web::client;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// actix::run(
|
/// actix::run(
|
||||||
|
@ -655,7 +656,7 @@ impl ClientRequestBuilder {
|
||||||
if !parts.headers.contains_key(header::HOST) {
|
if !parts.headers.contains_key(header::HOST) {
|
||||||
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
|
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
|
||||||
|
|
||||||
let _ = match parts.uri.port() {
|
let _ = match parts.uri.port_part().map(|port| port.as_u16()) {
|
||||||
None | Some(80) | Some(443) => write!(wrt, "{}", host),
|
None | Some(80) | Some(443) => write!(wrt, "{}", host),
|
||||||
Some(port) => write!(wrt, "{}:{}", host, port),
|
Some(port) => write!(wrt, "{}:{}", host, port),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
feature = "cargo-clippy",
|
feature = "cargo-clippy",
|
||||||
allow(clippy::redundant_field_names)
|
allow(redundant_field_names)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
70
src/de.rs
70
src/de.rs
|
@ -1,7 +1,10 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
||||||
|
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use param::ParamsIter;
|
use param::ParamsIter;
|
||||||
|
use uri::RESERVED_QUOTER;
|
||||||
|
|
||||||
macro_rules! unsupported_type {
|
macro_rules! unsupported_type {
|
||||||
($trait_fn:ident, $name:expr) => {
|
($trait_fn:ident, $name:expr) => {
|
||||||
|
@ -13,6 +16,20 @@ macro_rules! unsupported_type {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! percent_decode_if_needed {
|
||||||
|
($value:expr, $decode:expr) => {
|
||||||
|
if $decode {
|
||||||
|
if let Some(ref mut value) = RESERVED_QUOTER.requote($value.as_bytes()) {
|
||||||
|
Rc::make_mut(value).parse()
|
||||||
|
} else {
|
||||||
|
$value.parse()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$value.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! parse_single_value {
|
macro_rules! parse_single_value {
|
||||||
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
|
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
|
||||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
|
@ -23,11 +40,11 @@ macro_rules! parse_single_value {
|
||||||
format!("wrong number of parameters: {} expected 1",
|
format!("wrong number of parameters: {} expected 1",
|
||||||
self.req.match_info().len()).as_str()))
|
self.req.match_info().len()).as_str()))
|
||||||
} else {
|
} else {
|
||||||
let v = self.req.match_info()[0].parse().map_err(
|
let v_parsed = percent_decode_if_needed!(&self.req.match_info()[0], self.decode)
|
||||||
|_| de::value::Error::custom(
|
.map_err(|_| de::value::Error::custom(
|
||||||
format!("can not parse {:?} to a {}",
|
format!("can not parse {:?} to a {}", &self.req.match_info()[0], $tp)
|
||||||
&self.req.match_info()[0], $tp)))?;
|
))?;
|
||||||
visitor.$visit_fn(v)
|
visitor.$visit_fn(v_parsed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,11 +52,12 @@ macro_rules! parse_single_value {
|
||||||
|
|
||||||
pub struct PathDeserializer<'de, S: 'de> {
|
pub struct PathDeserializer<'de, S: 'de> {
|
||||||
req: &'de HttpRequest<S>,
|
req: &'de HttpRequest<S>,
|
||||||
|
decode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de, S: 'de> PathDeserializer<'de, S> {
|
impl<'de, S: 'de> PathDeserializer<'de, S> {
|
||||||
pub fn new(req: &'de HttpRequest<S>) -> Self {
|
pub fn new(req: &'de HttpRequest<S>, decode: bool) -> Self {
|
||||||
PathDeserializer { req }
|
PathDeserializer { req, decode }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +71,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||||
visitor.visit_map(ParamsDeserializer {
|
visitor.visit_map(ParamsDeserializer {
|
||||||
params: self.req.match_info().iter(),
|
params: self.req.match_info().iter(),
|
||||||
current: None,
|
current: None,
|
||||||
|
decode: self.decode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +126,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||||
} else {
|
} else {
|
||||||
visitor.visit_seq(ParamsSeq {
|
visitor.visit_seq(ParamsSeq {
|
||||||
params: self.req.match_info().iter(),
|
params: self.req.match_info().iter(),
|
||||||
|
decode: self.decode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,6 +148,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||||
} else {
|
} else {
|
||||||
visitor.visit_seq(ParamsSeq {
|
visitor.visit_seq(ParamsSeq {
|
||||||
params: self.req.match_info().iter(),
|
params: self.req.match_info().iter(),
|
||||||
|
decode: self.decode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,28 +162,13 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||||
Err(de::value::Error::custom("unsupported type: enum"))
|
Err(de::value::Error::custom("unsupported type: enum"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
|
||||||
where
|
|
||||||
V: Visitor<'de>,
|
|
||||||
{
|
|
||||||
if self.req.match_info().len() != 1 {
|
|
||||||
Err(de::value::Error::custom(
|
|
||||||
format!(
|
|
||||||
"wrong number of parameters: {} expected 1",
|
|
||||||
self.req.match_info().len()
|
|
||||||
).as_str(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
visitor.visit_str(&self.req.match_info()[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
visitor.visit_seq(ParamsSeq {
|
visitor.visit_seq(ParamsSeq {
|
||||||
params: self.req.match_info().iter(),
|
params: self.req.match_info().iter(),
|
||||||
|
decode: self.decode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,13 +190,16 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||||
parse_single_value!(deserialize_f32, visit_f32, "f32");
|
parse_single_value!(deserialize_f32, visit_f32, "f32");
|
||||||
parse_single_value!(deserialize_f64, visit_f64, "f64");
|
parse_single_value!(deserialize_f64, visit_f64, "f64");
|
||||||
parse_single_value!(deserialize_string, visit_string, "String");
|
parse_single_value!(deserialize_string, visit_string, "String");
|
||||||
|
parse_single_value!(deserialize_str, visit_string, "String");
|
||||||
parse_single_value!(deserialize_byte_buf, visit_string, "String");
|
parse_single_value!(deserialize_byte_buf, visit_string, "String");
|
||||||
parse_single_value!(deserialize_char, visit_char, "char");
|
parse_single_value!(deserialize_char, visit_char, "char");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ParamsDeserializer<'de> {
|
struct ParamsDeserializer<'de> {
|
||||||
params: ParamsIter<'de>,
|
params: ParamsIter<'de>,
|
||||||
current: Option<(&'de str, &'de str)>,
|
current: Option<(&'de str, &'de str)>,
|
||||||
|
decode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
|
impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
|
||||||
|
@ -212,7 +221,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
|
||||||
V: de::DeserializeSeed<'de>,
|
V: de::DeserializeSeed<'de>,
|
||||||
{
|
{
|
||||||
if let Some((_, value)) = self.current.take() {
|
if let Some((_, value)) = self.current.take() {
|
||||||
seed.deserialize(Value { value })
|
seed.deserialize(Value { value, decode: self.decode })
|
||||||
} else {
|
} else {
|
||||||
Err(de::value::Error::custom("unexpected item"))
|
Err(de::value::Error::custom("unexpected item"))
|
||||||
}
|
}
|
||||||
|
@ -252,16 +261,18 @@ macro_rules! parse_value {
|
||||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
where V: Visitor<'de>
|
where V: Visitor<'de>
|
||||||
{
|
{
|
||||||
let v = self.value.parse().map_err(
|
let v_parsed = percent_decode_if_needed!(&self.value, self.decode)
|
||||||
|_| de::value::Error::custom(
|
.map_err(|_| de::value::Error::custom(
|
||||||
format!("can not parse {:?} to a {}", self.value, $tp)))?;
|
format!("can not parse {:?} to a {}", &self.value, $tp)
|
||||||
visitor.$visit_fn(v)
|
))?;
|
||||||
|
visitor.$visit_fn(v_parsed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Value<'de> {
|
struct Value<'de> {
|
||||||
value: &'de str,
|
value: &'de str,
|
||||||
|
decode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserializer<'de> for Value<'de> {
|
impl<'de> Deserializer<'de> for Value<'de> {
|
||||||
|
@ -377,6 +388,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
||||||
|
|
||||||
struct ParamsSeq<'de> {
|
struct ParamsSeq<'de> {
|
||||||
params: ParamsIter<'de>,
|
params: ParamsIter<'de>,
|
||||||
|
decode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
|
impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
|
||||||
|
@ -387,7 +399,7 @@ impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
|
||||||
T: de::DeserializeSeed<'de>,
|
T: de::DeserializeSeed<'de>,
|
||||||
{
|
{
|
||||||
match self.params.next() {
|
match self.params.next() {
|
||||||
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)),
|
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1, decode: self.decode })?)),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
318
src/error.rs
318
src/error.rs
|
@ -5,7 +5,7 @@ use std::string::FromUtf8Error;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::{fmt, io, result};
|
use std::{fmt, io, result};
|
||||||
|
|
||||||
use actix::MailboxError;
|
use actix::{MailboxError, SendError};
|
||||||
use cookie;
|
use cookie;
|
||||||
use failure::{self, Backtrace, Fail};
|
use failure::{self, Backtrace, Fail};
|
||||||
use futures::Canceled;
|
use futures::Canceled;
|
||||||
|
@ -136,6 +136,10 @@ pub trait ResponseError: Fail + InternalResponseErrorAsFail {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> ResponseError for SendError<T>
|
||||||
|
where T: Send + Sync + 'static {
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
fmt::Display::fmt(&self.cause, f)
|
fmt::Display::fmt(&self.cause, f)
|
||||||
|
@ -759,6 +763,16 @@ where
|
||||||
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
|
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *PAYMENT_REQUIRED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorPaymentRequired<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper function that creates wrapper of any error and generate *FORBIDDEN*
|
/// Helper function that creates wrapper of any error and generate *FORBIDDEN*
|
||||||
/// response.
|
/// response.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -789,6 +803,26 @@ where
|
||||||
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
|
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *NOT
|
||||||
|
/// ACCEPTABLE* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorNotAcceptable<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *PROXY
|
||||||
|
/// AUTHENTICATION REQUIRED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorProxyAuthenticationRequired<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper function that creates wrapper of any error and generate *REQUEST
|
/// Helper function that creates wrapper of any error and generate *REQUEST
|
||||||
/// TIMEOUT* response.
|
/// TIMEOUT* response.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -819,6 +853,16 @@ where
|
||||||
InternalError::new(err, StatusCode::GONE).into()
|
InternalError::new(err, StatusCode::GONE).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate *LENGTH
|
||||||
|
/// REQUIRED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorLengthRequired<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::LENGTH_REQUIRED).into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper function that creates wrapper of any error and generate
|
/// Helper function that creates wrapper of any error and generate
|
||||||
/// *PRECONDITION FAILED* response.
|
/// *PRECONDITION FAILED* response.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -829,6 +873,46 @@ where
|
||||||
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
|
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *PAYLOAD TOO LARGE* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorPayloadTooLarge<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *URI TOO LONG* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorUriTooLong<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::URI_TOO_LONG).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *UNSUPPORTED MEDIA TYPE* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorUnsupportedMediaType<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *RANGE NOT SATISFIABLE* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorRangeNotSatisfiable<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper function that creates wrapper of any error and generate
|
/// Helper function that creates wrapper of any error and generate
|
||||||
/// *EXPECTATION FAILED* response.
|
/// *EXPECTATION FAILED* response.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -839,6 +923,106 @@ where
|
||||||
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
|
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *IM A TEAPOT* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorImATeapot<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::IM_A_TEAPOT).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *MISDIRECTED REQUEST* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorMisdirectedRequest<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *UNPROCESSABLE ENTITY* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorUnprocessableEntity<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *LOCKED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorLocked<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::LOCKED).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *FAILED DEPENDENCY* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorFailedDependency<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *UPGRADE REQUIRED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorUpgradeRequired<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *PRECONDITION REQUIRED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorPreconditionRequired<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *TOO MANY REQUESTS* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorTooManyRequests<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *REQUEST HEADER FIELDS TOO LARGE* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorRequestHeaderFieldsTooLarge<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and generate
|
||||||
|
/// *UNAVAILABLE FOR LEGAL REASONS* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorUnavailableForLegalReasons<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper function that creates wrapper of any error and
|
/// Helper function that creates wrapper of any error and
|
||||||
/// generate *INTERNAL SERVER ERROR* response.
|
/// generate *INTERNAL SERVER ERROR* response.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -889,6 +1073,66 @@ where
|
||||||
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
|
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and
|
||||||
|
/// generate *HTTP VERSION NOT SUPPORTED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorHttpVersionNotSupported<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and
|
||||||
|
/// generate *VARIANT ALSO NEGOTIATES* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorVariantAlsoNegotiates<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and
|
||||||
|
/// generate *INSUFFICIENT STORAGE* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorInsufficientStorage<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and
|
||||||
|
/// generate *LOOP DETECTED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorLoopDetected<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::LOOP_DETECTED).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and
|
||||||
|
/// generate *NOT EXTENDED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorNotExtended<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::NOT_EXTENDED).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates wrapper of any error and
|
||||||
|
/// generate *NETWORK AUTHENTICATION REQUIRED* response.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn ErrorNetworkAuthenticationRequired<T>(err: T) -> Error
|
||||||
|
where
|
||||||
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
|
{
|
||||||
|
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1068,6 +1312,9 @@ mod tests {
|
||||||
let r: HttpResponse = ErrorUnauthorized("err").into();
|
let r: HttpResponse = ErrorUnauthorized("err").into();
|
||||||
assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
|
assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorPaymentRequired("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED);
|
||||||
|
|
||||||
let r: HttpResponse = ErrorForbidden("err").into();
|
let r: HttpResponse = ErrorForbidden("err").into();
|
||||||
assert_eq!(r.status(), StatusCode::FORBIDDEN);
|
assert_eq!(r.status(), StatusCode::FORBIDDEN);
|
||||||
|
|
||||||
|
@ -1077,6 +1324,12 @@ mod tests {
|
||||||
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
|
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
|
||||||
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
|
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorNotAcceptable("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorProxyAuthenticationRequired("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED);
|
||||||
|
|
||||||
let r: HttpResponse = ErrorRequestTimeout("err").into();
|
let r: HttpResponse = ErrorRequestTimeout("err").into();
|
||||||
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
|
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
|
||||||
|
|
||||||
|
@ -1086,12 +1339,57 @@ mod tests {
|
||||||
let r: HttpResponse = ErrorGone("err").into();
|
let r: HttpResponse = ErrorGone("err").into();
|
||||||
assert_eq!(r.status(), StatusCode::GONE);
|
assert_eq!(r.status(), StatusCode::GONE);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorLengthRequired("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED);
|
||||||
|
|
||||||
let r: HttpResponse = ErrorPreconditionFailed("err").into();
|
let r: HttpResponse = ErrorPreconditionFailed("err").into();
|
||||||
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
|
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorPayloadTooLarge("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorUriTooLong("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::URI_TOO_LONG);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorUnsupportedMediaType("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorRangeNotSatisfiable("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE);
|
||||||
|
|
||||||
let r: HttpResponse = ErrorExpectationFailed("err").into();
|
let r: HttpResponse = ErrorExpectationFailed("err").into();
|
||||||
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
|
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorImATeapot("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::IM_A_TEAPOT);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorMisdirectedRequest("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorUnprocessableEntity("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorLocked("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::LOCKED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorFailedDependency("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorUpgradeRequired("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorPreconditionRequired("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorTooManyRequests("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorRequestHeaderFieldsTooLarge("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorUnavailableForLegalReasons("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
|
||||||
|
|
||||||
let r: HttpResponse = ErrorInternalServerError("err").into();
|
let r: HttpResponse = ErrorInternalServerError("err").into();
|
||||||
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
|
@ -1106,5 +1404,23 @@ mod tests {
|
||||||
|
|
||||||
let r: HttpResponse = ErrorGatewayTimeout("err").into();
|
let r: HttpResponse = ErrorGatewayTimeout("err").into();
|
||||||
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
|
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorHttpVersionNotSupported("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorVariantAlsoNegotiates("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorInsufficientStorage("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorLoopDetected("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::LOOP_DETECTED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorNotExtended("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::NOT_EXTENDED);
|
||||||
|
|
||||||
|
let r: HttpResponse = ErrorNetworkAuthenticationRequired("err").into();
|
||||||
|
assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
397
src/extractor.rs
397
src/extractor.rs
|
@ -12,13 +12,15 @@ use serde::de::{self, DeserializeOwned};
|
||||||
use serde_urlencoded;
|
use serde_urlencoded;
|
||||||
|
|
||||||
use de::PathDeserializer;
|
use de::PathDeserializer;
|
||||||
use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError};
|
use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError, ErrorConflict};
|
||||||
use handler::{AsyncResult, FromRequest};
|
use handler::{AsyncResult, FromRequest};
|
||||||
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
|
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
|
use Either;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
/// Extract typed information from the request's path.
|
/// Extract typed information from the request's path. Information from the path is
|
||||||
|
/// URL decoded. Decoding of special characters can be disabled through `PathConfig`.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
|
@ -111,18 +113,73 @@ impl<T, S> FromRequest<S> for Path<T>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
{
|
{
|
||||||
type Config = ();
|
type Config = PathConfig<S>;
|
||||||
type Result = Result<Self, Error>;
|
type Result = Result<Self, Error>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
|
||||||
let req = req.clone();
|
let req = req.clone();
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(&req))
|
let req2 = req.clone();
|
||||||
.map_err(ErrorNotFound)
|
let err = Rc::clone(&cfg.ehandler);
|
||||||
|
de::Deserialize::deserialize(PathDeserializer::new(&req, cfg.decode))
|
||||||
|
.map_err(move |e| (*err)(e, &req2))
|
||||||
.map(|inner| Path { inner })
|
.map(|inner| Path { inner })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Path extractor configuration
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::{error, http, App, HttpResponse, Path, Result};
|
||||||
|
///
|
||||||
|
/// /// deserialize `Info` from request's body, max payload size is 4kb
|
||||||
|
/// fn index(info: Path<(u32, String)>) -> Result<String> {
|
||||||
|
/// Ok(format!("Welcome {}!", info.1))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new().resource("/index.html/{id}/{name}", |r| {
|
||||||
|
/// r.method(http::Method::GET).with_config(index, |cfg| {
|
||||||
|
/// cfg.0.error_handler(|err, req| {
|
||||||
|
/// // <- create custom error response
|
||||||
|
/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
|
||||||
|
/// });
|
||||||
|
/// })
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct PathConfig<S> {
|
||||||
|
ehandler: Rc<Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error>,
|
||||||
|
decode: bool,
|
||||||
|
}
|
||||||
|
impl<S> PathConfig<S> {
|
||||||
|
/// Set custom error handler
|
||||||
|
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error + 'static,
|
||||||
|
{
|
||||||
|
self.ehandler = Rc::new(f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable decoding of URL encoded special charaters from the path
|
||||||
|
pub fn disable_decoding(&mut self) -> &mut Self
|
||||||
|
{
|
||||||
|
self.decode = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Default for PathConfig<S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
PathConfig {
|
||||||
|
ehandler: Rc::new(|e, _| ErrorNotFound(e)),
|
||||||
|
decode: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: fmt::Debug> fmt::Debug for Path<T> {
|
impl<T: fmt::Debug> fmt::Debug for Path<T> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
self.inner.fmt(f)
|
self.inner.fmt(f)
|
||||||
|
@ -136,7 +193,7 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
/// Extract typed information from from the request's query.
|
/// Extract typed information from the request's query.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
|
@ -200,17 +257,69 @@ impl<T, S> FromRequest<S> for Query<T>
|
||||||
where
|
where
|
||||||
T: de::DeserializeOwned,
|
T: de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
type Config = ();
|
type Config = QueryConfig<S>;
|
||||||
type Result = Result<Self, Error>;
|
type Result = Result<Self, Error>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
|
||||||
|
let req2 = req.clone();
|
||||||
|
let err = Rc::clone(&cfg.ehandler);
|
||||||
serde_urlencoded::from_str::<T>(req.query_string())
|
serde_urlencoded::from_str::<T>(req.query_string())
|
||||||
.map_err(|e| e.into())
|
.map_err(move |e| (*err)(e, &req2))
|
||||||
.map(Query)
|
.map(Query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Query extractor configuration
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::{error, http, App, HttpResponse, Query, Result};
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct Info {
|
||||||
|
/// username: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /// deserialize `Info` from request's body, max payload size is 4kb
|
||||||
|
/// fn index(info: Query<Info>) -> Result<String> {
|
||||||
|
/// Ok(format!("Welcome {}!", info.username))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new().resource("/index.html", |r| {
|
||||||
|
/// r.method(http::Method::GET).with_config(index, |cfg| {
|
||||||
|
/// cfg.0.error_handler(|err, req| {
|
||||||
|
/// // <- create custom error response
|
||||||
|
/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
|
||||||
|
/// });
|
||||||
|
/// })
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct QueryConfig<S> {
|
||||||
|
ehandler: Rc<Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error>,
|
||||||
|
}
|
||||||
|
impl<S> QueryConfig<S> {
|
||||||
|
/// Set custom error handler
|
||||||
|
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error + 'static,
|
||||||
|
{
|
||||||
|
self.ehandler = Rc::new(f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Default for QueryConfig<S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
QueryConfig {
|
||||||
|
ehandler: Rc::new(|e, _| e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: fmt::Debug> fmt::Debug for Query<T> {
|
impl<T: fmt::Debug> fmt::Debug for Query<T> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
|
@ -526,6 +635,153 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract either one of two fields from the request.
|
||||||
|
///
|
||||||
|
/// If both or none of the fields can be extracted, the default behaviour is to prefer the first
|
||||||
|
/// successful, last that failed. The behaviour can be changed by setting the appropriate
|
||||||
|
/// ```EitherCollisionStrategy```.
|
||||||
|
///
|
||||||
|
/// CAVEAT: Most of the time both extractors will be run. Make sure that the extractors you specify
|
||||||
|
/// can be run one after another (or in parallel). This will always fail for extractors that modify
|
||||||
|
/// the request state (such as the `Form` extractors that read in the body stream).
|
||||||
|
/// So Either<Form<A>, Form<B>> will not work correctly - it will only succeed if it matches the first
|
||||||
|
/// option, but will always fail to match the second (since the body stream will be at the end, and
|
||||||
|
/// appear to be empty).
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// extern crate rand;
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
|
||||||
|
/// use actix_web::error::ErrorBadRequest;
|
||||||
|
/// use actix_web::Either;
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, Deserialize)]
|
||||||
|
/// struct Thing { name: String }
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, Deserialize)]
|
||||||
|
/// struct OtherThing { id: String }
|
||||||
|
///
|
||||||
|
/// impl<S> FromRequest<S> for Thing {
|
||||||
|
/// type Config = ();
|
||||||
|
/// type Result = Result<Thing, Error>;
|
||||||
|
///
|
||||||
|
/// #[inline]
|
||||||
|
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
|
||||||
|
/// if rand::random() {
|
||||||
|
/// Ok(Thing { name: "thingy".into() })
|
||||||
|
/// } else {
|
||||||
|
/// Err(ErrorBadRequest("no luck"))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl<S> FromRequest<S> for OtherThing {
|
||||||
|
/// type Config = ();
|
||||||
|
/// type Result = Result<OtherThing, Error>;
|
||||||
|
///
|
||||||
|
/// #[inline]
|
||||||
|
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
|
||||||
|
/// if rand::random() {
|
||||||
|
/// Ok(OtherThing { id: "otherthingy".into() })
|
||||||
|
/// } else {
|
||||||
|
/// Err(ErrorBadRequest("no luck"))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /// extract text data from request
|
||||||
|
/// fn index(supplied_thing: Either<Thing, OtherThing>) -> Result<String> {
|
||||||
|
/// match supplied_thing {
|
||||||
|
/// Either::A(thing) => Ok(format!("Got something: {:?}", thing)),
|
||||||
|
/// Either::B(other_thing) => Ok(format!("Got anotherthing: {:?}", other_thing))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new().resource("/users/:first", |r| {
|
||||||
|
/// r.method(http::Method::POST).with(index)
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
impl<A: 'static, B: 'static, S: 'static> FromRequest<S> for Either<A,B> where A: FromRequest<S>, B: FromRequest<S> {
|
||||||
|
type Config = EitherConfig<A,B,S>;
|
||||||
|
type Result = AsyncResult<Either<A,B>>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
|
||||||
|
let a = A::from_request(&req.clone(), &cfg.a).into().map(|a| Either::A(a));
|
||||||
|
let b = B::from_request(req, &cfg.b).into().map(|b| Either::B(b));
|
||||||
|
|
||||||
|
match &cfg.collision_strategy {
|
||||||
|
EitherCollisionStrategy::PreferA => AsyncResult::future(Box::new(a.or_else(|_| b))),
|
||||||
|
EitherCollisionStrategy::PreferB => AsyncResult::future(Box::new(b.or_else(|_| a))),
|
||||||
|
EitherCollisionStrategy::FastestSuccessful => AsyncResult::future(Box::new(a.select2(b).then( |r| match r {
|
||||||
|
Ok(future::Either::A((ares, _b))) => AsyncResult::ok(ares),
|
||||||
|
Ok(future::Either::B((bres, _a))) => AsyncResult::ok(bres),
|
||||||
|
Err(future::Either::A((_aerr, b))) => AsyncResult::future(Box::new(b)),
|
||||||
|
Err(future::Either::B((_berr, a))) => AsyncResult::future(Box::new(a))
|
||||||
|
}))),
|
||||||
|
EitherCollisionStrategy::ErrorA => AsyncResult::future(Box::new(b.then(|r| match r {
|
||||||
|
Err(_berr) => AsyncResult::future(Box::new(a)),
|
||||||
|
Ok(b) => AsyncResult::future(Box::new(a.then( |r| match r {
|
||||||
|
Ok(_a) => Err(ErrorConflict("Both wings of either extractor completed")),
|
||||||
|
Err(_arr) => Ok(b)
|
||||||
|
})))
|
||||||
|
}))),
|
||||||
|
EitherCollisionStrategy::ErrorB => AsyncResult::future(Box::new(a.then(|r| match r {
|
||||||
|
Err(_aerr) => AsyncResult::future(Box::new(b)),
|
||||||
|
Ok(a) => AsyncResult::future(Box::new(b.then( |r| match r {
|
||||||
|
Ok(_b) => Err(ErrorConflict("Both wings of either extractor completed")),
|
||||||
|
Err(_berr) => Ok(a)
|
||||||
|
})))
|
||||||
|
}))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the result if neither or both of the extractors supplied to an Either<A,B> extractor succeed.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EitherCollisionStrategy {
|
||||||
|
/// If both are successful, return A, if both fail, return error of B
|
||||||
|
PreferA,
|
||||||
|
/// If both are successful, return B, if both fail, return error of A
|
||||||
|
PreferB,
|
||||||
|
/// Return result of the faster, error of the slower if both fail
|
||||||
|
FastestSuccessful,
|
||||||
|
/// Return error if both succeed, return error of A if both fail
|
||||||
|
ErrorA,
|
||||||
|
/// Return error if both succeed, return error of B if both fail
|
||||||
|
ErrorB
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EitherCollisionStrategy {
|
||||||
|
fn default() -> Self {
|
||||||
|
EitherCollisionStrategy::FastestSuccessful
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///Determines Either extractor configuration
|
||||||
|
///
|
||||||
|
///By default `EitherCollisionStrategy::FastestSuccessful` is used.
|
||||||
|
pub struct EitherConfig<A,B,S> where A: FromRequest<S>, B: FromRequest<S> {
|
||||||
|
a: A::Config,
|
||||||
|
b: B::Config,
|
||||||
|
collision_strategy: EitherCollisionStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A,B,S> Default for EitherConfig<A,B,S> where A: FromRequest<S>, B: FromRequest<S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
EitherConfig {
|
||||||
|
a: A::Config::default(),
|
||||||
|
b: B::Config::default(),
|
||||||
|
collision_strategy: EitherCollisionStrategy::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Optionally extract a field from the request or extract the Error if unsuccessful
|
/// Optionally extract a field from the request or extract the Error if unsuccessful
|
||||||
///
|
///
|
||||||
/// If the FromRequest for T fails, inject Err into handler rather than returning an error response
|
/// If the FromRequest for T fails, inject Err into handler rather than returning an error response
|
||||||
|
@ -766,6 +1022,11 @@ mod tests {
|
||||||
hello: String,
|
hello: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, PartialEq)]
|
||||||
|
struct OtherInfo {
|
||||||
|
bye: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bytes() {
|
fn test_bytes() {
|
||||||
let cfg = PayloadConfig::default();
|
let cfg = PayloadConfig::default();
|
||||||
|
@ -869,6 +1130,48 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_either() {
|
||||||
|
let req = TestRequest::default().finish();
|
||||||
|
let mut cfg: EitherConfig<Query<Info>, Query<OtherInfo>, _> = EitherConfig::default();
|
||||||
|
|
||||||
|
assert!(Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().is_err());
|
||||||
|
|
||||||
|
let req = TestRequest::default().uri("/index?hello=world").finish();
|
||||||
|
|
||||||
|
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
|
||||||
|
Async::Ready(r) => assert_eq!(r, Either::A(Query(Info { hello: "world".into() }))),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = TestRequest::default().uri("/index?bye=world").finish();
|
||||||
|
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
|
||||||
|
Async::Ready(r) => assert_eq!(r, Either::B(Query(OtherInfo { bye: "world".into() }))),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = TestRequest::default().uri("/index?hello=world&bye=world").finish();
|
||||||
|
cfg.collision_strategy = EitherCollisionStrategy::PreferA;
|
||||||
|
|
||||||
|
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
|
||||||
|
Async::Ready(r) => assert_eq!(r, Either::A(Query(Info { hello: "world".into() }))),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.collision_strategy = EitherCollisionStrategy::PreferB;
|
||||||
|
|
||||||
|
match Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().unwrap() {
|
||||||
|
Async::Ready(r) => assert_eq!(r, Either::B(Query(OtherInfo { bye: "world".into() }))),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.collision_strategy = EitherCollisionStrategy::ErrorA;
|
||||||
|
assert!(Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().is_err());
|
||||||
|
|
||||||
|
cfg.collision_strategy = EitherCollisionStrategy::FastestSuccessful;
|
||||||
|
assert!(Either::<Query<Info>, Query<OtherInfo>>::from_request(&req, &cfg).poll().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_result() {
|
fn test_result() {
|
||||||
let req = TestRequest::with_header(
|
let req = TestRequest::with_header(
|
||||||
|
@ -951,15 +1254,15 @@ mod tests {
|
||||||
let info = router.recognize(&req, &(), 0);
|
let info = router.recognize(&req, &(), 0);
|
||||||
let req = req.with_route_info(info);
|
let req = req.with_route_info(info);
|
||||||
|
|
||||||
let s = Path::<MyStruct>::from_request(&req, &()).unwrap();
|
let s = Path::<MyStruct>::from_request(&req, &PathConfig::default()).unwrap();
|
||||||
assert_eq!(s.key, "name");
|
assert_eq!(s.key, "name");
|
||||||
assert_eq!(s.value, "user1");
|
assert_eq!(s.value, "user1");
|
||||||
|
|
||||||
let s = Path::<(String, String)>::from_request(&req, &()).unwrap();
|
let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap();
|
||||||
assert_eq!(s.0, "name");
|
assert_eq!(s.0, "name");
|
||||||
assert_eq!(s.1, "user1");
|
assert_eq!(s.1, "user1");
|
||||||
|
|
||||||
let s = Query::<Id>::from_request(&req, &()).unwrap();
|
let s = Query::<Id>::from_request(&req, &QueryConfig::default()).unwrap();
|
||||||
assert_eq!(s.id, "test");
|
assert_eq!(s.id, "test");
|
||||||
|
|
||||||
let mut router = Router::<()>::default();
|
let mut router = Router::<()>::default();
|
||||||
|
@ -968,11 +1271,11 @@ mod tests {
|
||||||
let info = router.recognize(&req, &(), 0);
|
let info = router.recognize(&req, &(), 0);
|
||||||
let req = req.with_route_info(info);
|
let req = req.with_route_info(info);
|
||||||
|
|
||||||
let s = Path::<Test2>::from_request(&req, &()).unwrap();
|
let s = Path::<Test2>::from_request(&req, &PathConfig::default()).unwrap();
|
||||||
assert_eq!(s.as_ref().key, "name");
|
assert_eq!(s.as_ref().key, "name");
|
||||||
assert_eq!(s.value, 32);
|
assert_eq!(s.value, 32);
|
||||||
|
|
||||||
let s = Path::<(String, u8)>::from_request(&req, &()).unwrap();
|
let s = Path::<(String, u8)>::from_request(&req, &PathConfig::default()).unwrap();
|
||||||
assert_eq!(s.0, "name");
|
assert_eq!(s.0, "name");
|
||||||
assert_eq!(s.1, 32);
|
assert_eq!(s.1, 32);
|
||||||
|
|
||||||
|
@ -989,7 +1292,69 @@ mod tests {
|
||||||
let req = TestRequest::with_uri("/32/").finish();
|
let req = TestRequest::with_uri("/32/").finish();
|
||||||
let info = router.recognize(&req, &(), 0);
|
let info = router.recognize(&req, &(), 0);
|
||||||
let req = req.with_route_info(info);
|
let req = req.with_route_info(info);
|
||||||
assert_eq!(*Path::<i8>::from_request(&req, &()).unwrap(), 32);
|
assert_eq!(*Path::<i8>::from_request(&req, &&PathConfig::default()).unwrap(), 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_path_decode() {
|
||||||
|
let mut router = Router::<()>::default();
|
||||||
|
router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
|
||||||
|
|
||||||
|
macro_rules! test_single_value {
|
||||||
|
($value:expr, $expected:expr) => {
|
||||||
|
{
|
||||||
|
let req = TestRequest::with_uri($value).finish();
|
||||||
|
let info = router.recognize(&req, &(), 0);
|
||||||
|
let req = req.with_route_info(info);
|
||||||
|
assert_eq!(*Path::<String>::from_request(&req, &PathConfig::default()).unwrap(), $expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_single_value!("/%25/", "%");
|
||||||
|
test_single_value!("/%40%C2%A3%24%25%5E%26%2B%3D/", "@£$%^&+=");
|
||||||
|
test_single_value!("/%2B/", "+");
|
||||||
|
test_single_value!("/%252B/", "%2B");
|
||||||
|
test_single_value!("/%2F/", "/");
|
||||||
|
test_single_value!("/%252F/", "%2F");
|
||||||
|
test_single_value!("/http%3A%2F%2Flocalhost%3A80%2Ffoo/", "http://localhost:80/foo");
|
||||||
|
test_single_value!("/%2Fvar%2Flog%2Fsyslog/", "/var/log/syslog");
|
||||||
|
test_single_value!(
|
||||||
|
"/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%252Fvar%252Flog%252Fsyslog/",
|
||||||
|
"http://localhost:80/file/%2Fvar%2Flog%2Fsyslog"
|
||||||
|
);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/%25/7/?id=test").finish();
|
||||||
|
|
||||||
|
let mut router = Router::<()>::default();
|
||||||
|
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
|
||||||
|
let info = router.recognize(&req, &(), 0);
|
||||||
|
let req = req.with_route_info(info);
|
||||||
|
|
||||||
|
let s = Path::<Test2>::from_request(&req, &PathConfig::default()).unwrap();
|
||||||
|
assert_eq!(s.key, "%");
|
||||||
|
assert_eq!(s.value, 7);
|
||||||
|
|
||||||
|
let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap();
|
||||||
|
assert_eq!(s.0, "%");
|
||||||
|
assert_eq!(s.1, "7");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_path_no_decode() {
|
||||||
|
let mut router = Router::<()>::default();
|
||||||
|
router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/%25/").finish();
|
||||||
|
let info = router.recognize(&req, &(), 0);
|
||||||
|
let req = req.with_route_info(info);
|
||||||
|
assert_eq!(
|
||||||
|
*Path::<String>::from_request(
|
||||||
|
&req,
|
||||||
|
&&PathConfig::default().disable_decoding()
|
||||||
|
).unwrap(),
|
||||||
|
"%25"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
297
src/fs.rs
297
src/fs.rs
|
@ -11,10 +11,10 @@ use std::{cmp, io};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
|
use v_htmlescape::escape as escape_html_entity;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::{Async, Future, Poll, Stream};
|
use futures::{Async, Future, Poll, Stream};
|
||||||
use futures_cpupool::{CpuFuture, CpuPool};
|
use futures_cpupool::{CpuFuture, CpuPool};
|
||||||
use htmlescape::encode_minimal as escape_html_entity;
|
|
||||||
use mime;
|
use mime;
|
||||||
use mime_guess::{get_mime_type, guess_mime_type};
|
use mime_guess::{get_mime_type, guess_mime_type};
|
||||||
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
||||||
|
@ -120,6 +120,32 @@ pub struct NamedFile<C = DefaultConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NamedFile {
|
impl NamedFile {
|
||||||
|
/// Creates an instance from a previously opened file.
|
||||||
|
///
|
||||||
|
/// The given `path` need not exist and is only used to determine the `ContentType` and
|
||||||
|
/// `ContentDisposition` headers.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// extern crate actix_web;
|
||||||
|
///
|
||||||
|
/// use actix_web::fs::NamedFile;
|
||||||
|
/// use std::io::{self, Write};
|
||||||
|
/// use std::env;
|
||||||
|
/// use std::fs::File;
|
||||||
|
///
|
||||||
|
/// fn main() -> io::Result<()> {
|
||||||
|
/// let mut file = File::create("foo.txt")?;
|
||||||
|
/// file.write_all(b"Hello, world!")?;
|
||||||
|
/// let named_file = NamedFile::from_file(file, "bar.txt")?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn from_file<P: AsRef<Path>>(file: File, path: P) -> io::Result<NamedFile> {
|
||||||
|
Self::from_file_with_config(file, path, DefaultConfig)
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to open a file in read-only mode.
|
/// Attempts to open a file in read-only mode.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -135,16 +161,29 @@ impl NamedFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: StaticFileConfig> NamedFile<C> {
|
impl<C: StaticFileConfig> NamedFile<C> {
|
||||||
/// Attempts to open a file in read-only mode using provided configiration.
|
/// Creates an instance from a previously opened file using the provided configuration.
|
||||||
|
///
|
||||||
|
/// The given `path` need not exist and is only used to determine the `ContentType` and
|
||||||
|
/// `ContentDisposition` headers.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```no_run
|
||||||
/// use actix_web::fs::{DefaultConfig, NamedFile};
|
/// extern crate actix_web;
|
||||||
///
|
///
|
||||||
/// let file = NamedFile::open_with_config("foo.txt", DefaultConfig);
|
/// use actix_web::fs::{DefaultConfig, NamedFile};
|
||||||
|
/// use std::io::{self, Write};
|
||||||
|
/// use std::env;
|
||||||
|
/// use std::fs::File;
|
||||||
|
///
|
||||||
|
/// fn main() -> io::Result<()> {
|
||||||
|
/// let mut file = File::create("foo.txt")?;
|
||||||
|
/// file.write_all(b"Hello, world!")?;
|
||||||
|
/// let named_file = NamedFile::from_file_with_config(file, "bar.txt", DefaultConfig)?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn open_with_config<P: AsRef<Path>>(path: P, _: C) -> io::Result<NamedFile<C>> {
|
pub fn from_file_with_config<P: AsRef<Path>>(file: File, path: P, _: C) -> io::Result<NamedFile<C>> {
|
||||||
let path = path.as_ref().to_path_buf();
|
let path = path.as_ref().to_path_buf();
|
||||||
|
|
||||||
// Get the name of the file and use it to construct default Content-Type
|
// Get the name of the file and use it to construct default Content-Type
|
||||||
|
@ -169,7 +208,6 @@ impl<C: StaticFileConfig> NamedFile<C> {
|
||||||
(ct, cd)
|
(ct, cd)
|
||||||
};
|
};
|
||||||
|
|
||||||
let file = File::open(&path)?;
|
|
||||||
let md = file.metadata()?;
|
let md = file.metadata()?;
|
||||||
let modified = md.modified().ok();
|
let modified = md.modified().ok();
|
||||||
let cpu_pool = None;
|
let cpu_pool = None;
|
||||||
|
@ -188,6 +226,19 @@ impl<C: StaticFileConfig> NamedFile<C> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to open a file in read-only mode using provided configuration.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use actix_web::fs::{DefaultConfig, NamedFile};
|
||||||
|
///
|
||||||
|
/// let file = NamedFile::open_with_config("foo.txt", DefaultConfig);
|
||||||
|
/// ```
|
||||||
|
pub fn open_with_config<P: AsRef<Path>>(path: P, config: C) -> io::Result<NamedFile<C>> {
|
||||||
|
Self::from_file_with_config(File::open(&path)?, path, config)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns reference to the underlying `File` object.
|
/// Returns reference to the underlying `File` object.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn file(&self) -> &File {
|
pub fn file(&self) -> &File {
|
||||||
|
@ -390,6 +441,8 @@ impl<C: StaticFileConfig> Responder for NamedFile<C> {
|
||||||
// check last modified
|
// check last modified
|
||||||
let not_modified = if !none_match(etag.as_ref(), req) {
|
let not_modified = if !none_match(etag.as_ref(), req) {
|
||||||
true
|
true
|
||||||
|
} else if req.headers().contains_key(header::IF_NONE_MATCH) {
|
||||||
|
false
|
||||||
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
|
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
|
||||||
(last_modified, req.get_header())
|
(last_modified, req.get_header())
|
||||||
{
|
{
|
||||||
|
@ -472,6 +525,7 @@ impl<C: StaticFileConfig> Responder for NamedFile<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
/// A helper created from a `std::fs::File` which reads the file
|
/// A helper created from a `std::fs::File` which reads the file
|
||||||
/// chunk-by-chunk on a `CpuPool`.
|
/// chunk-by-chunk on a `CpuPool`.
|
||||||
pub struct ChunkedReadFile {
|
pub struct ChunkedReadFile {
|
||||||
|
@ -561,8 +615,23 @@ impl Directory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// show file url as relative to static path
|
||||||
|
macro_rules! encode_file_url {
|
||||||
|
($path:ident) => {
|
||||||
|
utf8_percent_encode(&$path.to_string_lossy(), DEFAULT_ENCODE_SET)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// " -- " & -- & ' -- ' < -- < > -- > / -- /
|
||||||
|
macro_rules! encode_file_name {
|
||||||
|
($entry:ident) => {
|
||||||
|
escape_html_entity(&$entry.file_name().to_string_lossy())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn directory_listing<S>(
|
fn directory_listing<S>(
|
||||||
dir: &Directory, req: &HttpRequest<S>,
|
dir: &Directory,
|
||||||
|
req: &HttpRequest<S>,
|
||||||
) -> Result<HttpResponse, io::Error> {
|
) -> Result<HttpResponse, io::Error> {
|
||||||
let index_of = format!("Index of {}", req.path());
|
let index_of = format!("Index of {}", req.path());
|
||||||
let mut body = String::new();
|
let mut body = String::new();
|
||||||
|
@ -575,11 +644,6 @@ fn directory_listing<S>(
|
||||||
Ok(p) => base.join(p),
|
Ok(p) => base.join(p),
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
// show file url as relative to static path
|
|
||||||
let file_url = utf8_percent_encode(&p.to_string_lossy(), DEFAULT_ENCODE_SET)
|
|
||||||
.to_string();
|
|
||||||
// " -- " & -- & ' -- ' < -- < > -- >
|
|
||||||
let file_name = escape_html_entity(&entry.file_name().to_string_lossy());
|
|
||||||
|
|
||||||
// if file is a directory, add '/' to the end of the name
|
// if file is a directory, add '/' to the end of the name
|
||||||
if let Ok(metadata) = entry.metadata() {
|
if let Ok(metadata) = entry.metadata() {
|
||||||
|
@ -587,13 +651,15 @@ fn directory_listing<S>(
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
body,
|
body,
|
||||||
"<li><a href=\"{}\">{}/</a></li>",
|
"<li><a href=\"{}\">{}/</a></li>",
|
||||||
file_url, file_name
|
encode_file_url!(p),
|
||||||
|
encode_file_name!(entry),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
body,
|
body,
|
||||||
"<li><a href=\"{}\">{}</a></li>",
|
"<li><a href=\"{}\">{}</a></li>",
|
||||||
file_url, file_name
|
encode_file_url!(p),
|
||||||
|
encode_file_name!(entry),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -656,7 +722,8 @@ impl<S: 'static> StaticFiles<S> {
|
||||||
/// Create new `StaticFiles` instance for specified base directory and
|
/// Create new `StaticFiles` instance for specified base directory and
|
||||||
/// `CpuPool`.
|
/// `CpuPool`.
|
||||||
pub fn with_pool<T: Into<PathBuf>>(
|
pub fn with_pool<T: Into<PathBuf>>(
|
||||||
dir: T, pool: CpuPool,
|
dir: T,
|
||||||
|
pool: CpuPool,
|
||||||
) -> Result<StaticFiles<S>, Error> {
|
) -> Result<StaticFiles<S>, Error> {
|
||||||
Self::with_config_pool(dir, pool, DefaultConfig)
|
Self::with_config_pool(dir, pool, DefaultConfig)
|
||||||
}
|
}
|
||||||
|
@ -667,7 +734,8 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
|
||||||
///
|
///
|
||||||
/// Identical with `new` but allows to specify configiration to use.
|
/// Identical with `new` but allows to specify configiration to use.
|
||||||
pub fn with_config<T: Into<PathBuf>>(
|
pub fn with_config<T: Into<PathBuf>>(
|
||||||
dir: T, config: C,
|
dir: T,
|
||||||
|
config: C,
|
||||||
) -> Result<StaticFiles<S, C>, Error> {
|
) -> Result<StaticFiles<S, C>, Error> {
|
||||||
// use default CpuPool
|
// use default CpuPool
|
||||||
let pool = { DEFAULT_CPUPOOL.lock().clone() };
|
let pool = { DEFAULT_CPUPOOL.lock().clone() };
|
||||||
|
@ -678,7 +746,9 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
|
||||||
/// Create new `StaticFiles` instance for specified base directory with config and
|
/// Create new `StaticFiles` instance for specified base directory with config and
|
||||||
/// `CpuPool`.
|
/// `CpuPool`.
|
||||||
pub fn with_config_pool<T: Into<PathBuf>>(
|
pub fn with_config_pool<T: Into<PathBuf>>(
|
||||||
dir: T, pool: CpuPool, _: C,
|
dir: T,
|
||||||
|
pool: CpuPool,
|
||||||
|
_: C,
|
||||||
) -> Result<StaticFiles<S, C>, Error> {
|
) -> Result<StaticFiles<S, C>, Error> {
|
||||||
let dir = dir.into().canonicalize()?;
|
let dir = dir.into().canonicalize()?;
|
||||||
|
|
||||||
|
@ -722,7 +792,7 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
|
||||||
|
|
||||||
/// Set index file
|
/// Set index file
|
||||||
///
|
///
|
||||||
/// Redirects to specific index file for directory "/" instead of
|
/// Shows specific index file for directory "/" instead of
|
||||||
/// showing files listing.
|
/// showing files listing.
|
||||||
pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles<S, C> {
|
pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles<S, C> {
|
||||||
self.index = Some(index.into());
|
self.index = Some(index.into());
|
||||||
|
@ -736,9 +806,10 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_handle(
|
fn try_handle(
|
||||||
&self, req: &HttpRequest<S>,
|
&self,
|
||||||
|
req: &HttpRequest<S>,
|
||||||
) -> Result<AsyncResult<HttpResponse>, Error> {
|
) -> Result<AsyncResult<HttpResponse>, Error> {
|
||||||
let tail: String = req.match_info().query("tail")?;
|
let tail: String = req.match_info().get_decoded("tail").unwrap_or_else(|| "".to_string());
|
||||||
let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?;
|
let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?;
|
||||||
|
|
||||||
// full filepath
|
// full filepath
|
||||||
|
@ -746,17 +817,11 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
|
||||||
|
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
if let Some(ref redir_index) = self.index {
|
if let Some(ref redir_index) = self.index {
|
||||||
// TODO: Don't redirect, just return the index content.
|
let path = path.join(redir_index);
|
||||||
// TODO: It'd be nice if there were a good usable URL manipulation
|
|
||||||
// library
|
NamedFile::open_with_config(path, C::default())?
|
||||||
let mut new_path: String = req.path().to_owned();
|
.set_cpu_pool(self.cpu_pool.clone())
|
||||||
if !new_path.ends_with('/') {
|
.respond_to(&req)?
|
||||||
new_path.push('/');
|
|
||||||
}
|
|
||||||
new_path.push_str(redir_index);
|
|
||||||
HttpResponse::Found()
|
|
||||||
.header(header::LOCATION, new_path.as_str())
|
|
||||||
.finish()
|
|
||||||
.respond_to(&req)
|
.respond_to(&req)
|
||||||
} else if self.show_index {
|
} else if self.show_index {
|
||||||
let dir = Directory::new(self.directory.clone(), path);
|
let dir = Directory::new(self.directory.clone(), path);
|
||||||
|
@ -881,6 +946,8 @@ impl HttpRange {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use application::App;
|
use application::App;
|
||||||
|
@ -900,6 +967,43 @@ mod tests {
|
||||||
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
|
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_if_modified_since_without_if_none_match() {
|
||||||
|
let mut file = NamedFile::open("Cargo.toml")
|
||||||
|
.unwrap()
|
||||||
|
.set_cpu_pool(CpuPool::new(1));
|
||||||
|
let since = header::HttpDate::from(
|
||||||
|
SystemTime::now().add(Duration::from_secs(60)));
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.header(header::IF_MODIFIED_SINCE, since)
|
||||||
|
.finish();
|
||||||
|
let resp = file.respond_to(&req).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
resp.status(),
|
||||||
|
StatusCode::NOT_MODIFIED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_if_modified_since_with_if_none_match() {
|
||||||
|
let mut file = NamedFile::open("Cargo.toml")
|
||||||
|
.unwrap()
|
||||||
|
.set_cpu_pool(CpuPool::new(1));
|
||||||
|
let since = header::HttpDate::from(
|
||||||
|
SystemTime::now().add(Duration::from_secs(60)));
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.header(header::IF_NONE_MATCH, "miss_etag")
|
||||||
|
.header(header::IF_MODIFIED_SINCE, since)
|
||||||
|
.finish();
|
||||||
|
let resp = file.respond_to(&req).unwrap();
|
||||||
|
assert_ne!(
|
||||||
|
resp.status(),
|
||||||
|
StatusCode::NOT_MODIFIED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_named_file_text() {
|
fn test_named_file_text() {
|
||||||
assert!(NamedFile::open("test--").is_err());
|
assert!(NamedFile::open("test--").is_err());
|
||||||
|
@ -1280,6 +1384,27 @@ mod tests {
|
||||||
assert_eq!(bytes, data);
|
assert_eq!(bytes, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static_files_with_spaces() {
|
||||||
|
let mut srv = test::TestServer::with_factory(|| {
|
||||||
|
App::new().handler(
|
||||||
|
"/",
|
||||||
|
StaticFiles::new(".").unwrap().index_file("Cargo.toml"),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let request = srv
|
||||||
|
.get()
|
||||||
|
.uri(srv.url("/tests/test%20space.binary"))
|
||||||
|
.finish()
|
||||||
|
.unwrap();
|
||||||
|
let response = srv.execute(request.send()).unwrap();
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let bytes = srv.execute(response.body()).unwrap();
|
||||||
|
let data = Bytes::from(fs::read("tests/test space.binary").unwrap());
|
||||||
|
assert_eq!(bytes, data);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct OnlyMethodHeadConfig;
|
pub struct OnlyMethodHeadConfig;
|
||||||
impl StaticFileConfig for OnlyMethodHeadConfig {
|
impl StaticFileConfig for OnlyMethodHeadConfig {
|
||||||
|
@ -1392,43 +1517,66 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_redirect_to_index() {
|
fn test_serve_index() {
|
||||||
let st = StaticFiles::new(".").unwrap().index_file("index.html");
|
let st = StaticFiles::new(".").unwrap().index_file("test.binary");
|
||||||
let req = TestRequest::default().uri("/tests").finish();
|
let req = TestRequest::default().uri("/tests").finish();
|
||||||
|
|
||||||
let resp = st.handle(&req).respond_to(&req).unwrap();
|
let resp = st.handle(&req).respond_to(&req).unwrap();
|
||||||
let resp = resp.as_msg();
|
let resp = resp.as_msg();
|
||||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::LOCATION).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).expect("content type"),
|
||||||
"/tests/index.html"
|
"application/octet-stream"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get(header::CONTENT_DISPOSITION).expect("content disposition"),
|
||||||
|
"attachment; filename=\"test.binary\""
|
||||||
);
|
);
|
||||||
|
|
||||||
let req = TestRequest::default().uri("/tests/").finish();
|
let req = TestRequest::default().uri("/tests/").finish();
|
||||||
let resp = st.handle(&req).respond_to(&req).unwrap();
|
let resp = st.handle(&req).respond_to(&req).unwrap();
|
||||||
let resp = resp.as_msg();
|
let resp = resp.as_msg();
|
||||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::LOCATION).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"/tests/index.html"
|
"application/octet-stream"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||||
|
"attachment; filename=\"test.binary\""
|
||||||
|
);
|
||||||
|
|
||||||
|
// nonexistent index file
|
||||||
|
let req = TestRequest::default().uri("/tests/unknown").finish();
|
||||||
|
let resp = st.handle(&req).respond_to(&req).unwrap();
|
||||||
|
let resp = resp.as_msg();
|
||||||
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::default().uri("/tests/unknown/").finish();
|
||||||
|
let resp = st.handle(&req).respond_to(&req).unwrap();
|
||||||
|
let resp = resp.as_msg();
|
||||||
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_redirect_to_index_nested() {
|
fn test_serve_index_nested() {
|
||||||
let st = StaticFiles::new(".").unwrap().index_file("mod.rs");
|
let st = StaticFiles::new(".").unwrap().index_file("mod.rs");
|
||||||
let req = TestRequest::default().uri("/src/client").finish();
|
let req = TestRequest::default().uri("/src/client").finish();
|
||||||
let resp = st.handle(&req).respond_to(&req).unwrap();
|
let resp = st.handle(&req).respond_to(&req).unwrap();
|
||||||
let resp = resp.as_msg();
|
let resp = resp.as_msg();
|
||||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resp.headers().get(header::LOCATION).unwrap(),
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
"/src/client/mod.rs"
|
"text/x-rust"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||||
|
"inline; filename=\"mod.rs\""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integration_redirect_to_index_with_prefix() {
|
fn integration_serve_index_with_prefix() {
|
||||||
let mut srv = test::TestServer::with_factory(|| {
|
let mut srv = test::TestServer::with_factory(|| {
|
||||||
App::new()
|
App::new()
|
||||||
.prefix("public")
|
.prefix("public")
|
||||||
|
@ -1437,29 +1585,21 @@ mod tests {
|
||||||
|
|
||||||
let request = srv.get().uri(srv.url("/public")).finish().unwrap();
|
let request = srv.get().uri(srv.url("/public")).finish().unwrap();
|
||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert_eq!(response.status(), StatusCode::FOUND);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let loc = response
|
let bytes = srv.execute(response.body()).unwrap();
|
||||||
.headers()
|
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||||
.get(header::LOCATION)
|
assert_eq!(bytes, data);
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(loc, "/public/Cargo.toml");
|
|
||||||
|
|
||||||
let request = srv.get().uri(srv.url("/public/")).finish().unwrap();
|
let request = srv.get().uri(srv.url("/public/")).finish().unwrap();
|
||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert_eq!(response.status(), StatusCode::FOUND);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let loc = response
|
let bytes = srv.execute(response.body()).unwrap();
|
||||||
.headers()
|
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||||
.get(header::LOCATION)
|
assert_eq!(bytes, data);
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(loc, "/public/Cargo.toml");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integration_redirect_to_index() {
|
fn integration_serve_index() {
|
||||||
let mut srv = test::TestServer::with_factory(|| {
|
let mut srv = test::TestServer::with_factory(|| {
|
||||||
App::new().handler(
|
App::new().handler(
|
||||||
"test",
|
"test",
|
||||||
|
@ -1469,25 +1609,26 @@ mod tests {
|
||||||
|
|
||||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert_eq!(response.status(), StatusCode::FOUND);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let loc = response
|
let bytes = srv.execute(response.body()).unwrap();
|
||||||
.headers()
|
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||||
.get(header::LOCATION)
|
assert_eq!(bytes, data);
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(loc, "/test/Cargo.toml");
|
|
||||||
|
|
||||||
let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
|
let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
|
||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert_eq!(response.status(), StatusCode::FOUND);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let loc = response
|
let bytes = srv.execute(response.body()).unwrap();
|
||||||
.headers()
|
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||||
.get(header::LOCATION)
|
assert_eq!(bytes, data);
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
// nonexistent index file
|
||||||
.unwrap();
|
let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap();
|
||||||
assert_eq!(loc, "/test/Cargo.toml");
|
let response = srv.execute(request.send()).unwrap();
|
||||||
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap();
|
||||||
|
let response = srv.execute(request.send()).unwrap();
|
||||||
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -86,7 +86,7 @@ pub trait FromRequest<S>: Sized {
|
||||||
/// # fn is_a_variant() -> bool { true }
|
/// # fn is_a_variant() -> bool { true }
|
||||||
/// # fn main() {}
|
/// # fn main() {}
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Either<A, B> {
|
pub enum Either<A, B> {
|
||||||
/// First branch of the type
|
/// First branch of the type
|
||||||
A(A),
|
A(A),
|
||||||
|
@ -250,7 +250,7 @@ pub(crate) enum AsyncResultItem<I, E> {
|
||||||
impl<I, E> AsyncResult<I, E> {
|
impl<I, E> AsyncResult<I, E> {
|
||||||
/// Create async response
|
/// Create async response
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn async(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
|
pub fn future(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
|
||||||
AsyncResult(Some(AsyncResultItem::Future(fut)))
|
AsyncResult(Some(AsyncResultItem::Future(fut)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +401,7 @@ where
|
||||||
},
|
},
|
||||||
Err(e) => err(e),
|
Err(e) => err(e),
|
||||||
});
|
});
|
||||||
Ok(AsyncResult::async(Box::new(fut)))
|
Ok(AsyncResult::future(Box::new(fut)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -502,7 +502,7 @@ where
|
||||||
Err(e) => Either::A(err(e)),
|
Err(e) => Either::A(err(e)),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
AsyncResult::async(Box::new(fut))
|
AsyncResult::future(Box::new(fut))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -200,7 +200,7 @@ pub trait HttpMessage: Sized {
|
||||||
/// # fn main() {}
|
/// # fn main() {}
|
||||||
/// ```
|
/// ```
|
||||||
fn json<T: DeserializeOwned>(&self) -> JsonBody<Self, T> {
|
fn json<T: DeserializeOwned>(&self) -> JsonBody<Self, T> {
|
||||||
JsonBody::new(self)
|
JsonBody::new::<()>(self, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return stream to http payload processes as multipart.
|
/// Return stream to http payload processes as multipart.
|
||||||
|
@ -213,9 +213,10 @@ pub trait HttpMessage: Sized {
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
/// # extern crate env_logger;
|
/// # extern crate env_logger;
|
||||||
/// # extern crate futures;
|
/// # extern crate futures;
|
||||||
|
/// # extern crate actix;
|
||||||
/// # use std::str;
|
/// # use std::str;
|
||||||
/// # use actix_web::*;
|
/// # use actix_web::*;
|
||||||
/// # use actix_web::actix::fut::FinishStream;
|
/// # use actix::FinishStream;
|
||||||
/// # use futures::{Future, Stream};
|
/// # use futures::{Future, Stream};
|
||||||
/// # use futures::future::{ok, result, Either};
|
/// # use futures::future::{ok, result, Either};
|
||||||
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
|
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
|
||||||
|
|
|
@ -216,7 +216,7 @@ impl<S> HttpRequest<S> {
|
||||||
self.url_for(name, &NO_PARAMS)
|
self.url_for(name, &NO_PARAMS)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method returns reference to current `RouteInfo` object.
|
/// This method returns reference to current `ResourceInfo` object.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn resource(&self) -> &ResourceInfo {
|
pub fn resource(&self) -> &ResourceInfo {
|
||||||
&self.resource
|
&self.resource
|
||||||
|
|
|
@ -246,7 +246,7 @@ impl HttpResponse {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get body os this response
|
/// Get body of this response
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn body(&self) -> &Body {
|
pub fn body(&self) -> &Body {
|
||||||
&self.get_ref().body
|
&self.get_ref().body
|
||||||
|
@ -366,7 +366,7 @@ impl HttpResponseBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header.
|
/// Append a header.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
|
@ -394,7 +394,7 @@ impl HttpResponseBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a header.
|
/// Append a header.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
|
@ -426,6 +426,65 @@ impl HttpResponseBuilder {
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Set or replace a header with a single value.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::{http, HttpRequest, HttpResponse};
|
||||||
|
///
|
||||||
|
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
/// HttpResponse::Ok()
|
||||||
|
/// .insert("X-TEST", "value")
|
||||||
|
/// .insert(http::header::CONTENT_TYPE, "application/json")
|
||||||
|
/// .finish()
|
||||||
|
/// }
|
||||||
|
/// fn main() {}
|
||||||
|
/// ```
|
||||||
|
pub fn insert<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||||
|
where
|
||||||
|
HeaderName: HttpTryFrom<K>,
|
||||||
|
V: IntoHeaderValue,
|
||||||
|
{
|
||||||
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
|
match HeaderName::try_from(key) {
|
||||||
|
Ok(key) => match value.try_into() {
|
||||||
|
Ok(value) => {
|
||||||
|
parts.headers.insert(key, value);
|
||||||
|
}
|
||||||
|
Err(e) => self.err = Some(e.into()),
|
||||||
|
},
|
||||||
|
Err(e) => self.err = Some(e.into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove all instances of a header already set on this `HttpResponseBuilder`.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::{http, HttpRequest, HttpResponse};
|
||||||
|
///
|
||||||
|
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
/// HttpResponse::Ok()
|
||||||
|
/// .header(http::header::CONTENT_TYPE, "nevermind") // won't be used
|
||||||
|
/// .remove(http::header::CONTENT_TYPE)
|
||||||
|
/// .finish()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn remove<K>(&mut self, key: K) -> &mut Self
|
||||||
|
where HeaderName: HttpTryFrom<K>
|
||||||
|
{
|
||||||
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
|
match HeaderName::try_from(key) {
|
||||||
|
Ok(key) => {
|
||||||
|
parts.headers.remove(key);
|
||||||
|
},
|
||||||
|
Err(e) => self.err = Some(e.into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the custom reason for the response.
|
/// Set the custom reason for the response.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -694,7 +753,7 @@ impl HttpResponseBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::borrowed_box))]
|
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
|
||||||
fn parts<'a>(
|
fn parts<'a>(
|
||||||
parts: &'a mut Option<Box<InnerHttpResponse>>, err: &Option<HttpError>,
|
parts: &'a mut Option<Box<InnerHttpResponse>>, err: &Option<HttpError>,
|
||||||
) -> Option<&'a mut Box<InnerHttpResponse>> {
|
) -> Option<&'a mut Box<InnerHttpResponse>> {
|
||||||
|
@ -1128,6 +1187,40 @@ mod tests {
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_insert() {
|
||||||
|
let resp = HttpResponse::Ok()
|
||||||
|
.insert("deleteme", "old value")
|
||||||
|
.insert("deleteme", "new value")
|
||||||
|
.finish();
|
||||||
|
assert_eq!("new value", resp.headers().get("deleteme").expect("new value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove() {
|
||||||
|
let resp = HttpResponse::Ok()
|
||||||
|
.header("deleteme", "value")
|
||||||
|
.remove("deleteme")
|
||||||
|
.finish();
|
||||||
|
assert!(resp.headers().get("deleteme").is_none())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_replace() {
|
||||||
|
let resp = HttpResponse::Ok()
|
||||||
|
.header("some-header", "old_value1")
|
||||||
|
.header("some-header", "old_value2")
|
||||||
|
.remove("some-header")
|
||||||
|
.header("some-header", "new_value1")
|
||||||
|
.header("some-header", "new_value2")
|
||||||
|
.remove("unrelated-header")
|
||||||
|
.finish();
|
||||||
|
let mut v = resp.headers().get_all("some-header").into_iter();
|
||||||
|
assert_eq!("new_value1", v.next().unwrap());
|
||||||
|
assert_eq!("new_value2", v.next().unwrap());
|
||||||
|
assert_eq!(None, v.next());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_upgrade() {
|
fn test_upgrade() {
|
||||||
let resp = HttpResponse::build(StatusCode::OK).upgrade().finish();
|
let resp = HttpResponse::build(StatusCode::OK).upgrade().finish();
|
||||||
|
|
|
@ -18,7 +18,7 @@ impl ConnectionInfo {
|
||||||
/// Create *ConnectionInfo* instance for a request.
|
/// Create *ConnectionInfo* instance for a request.
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "cargo-clippy",
|
feature = "cargo-clippy",
|
||||||
allow(clippy::cyclomatic_complexity)
|
allow(cyclomatic_complexity)
|
||||||
)]
|
)]
|
||||||
pub fn update(&mut self, req: &Request) {
|
pub fn update(&mut self, req: &Request) {
|
||||||
let mut host = None;
|
let mut host = None;
|
||||||
|
|
82
src/json.rs
82
src/json.rs
|
@ -143,7 +143,7 @@ where
|
||||||
let req2 = req.clone();
|
let req2 = req.clone();
|
||||||
let err = Rc::clone(&cfg.ehandler);
|
let err = Rc::clone(&cfg.ehandler);
|
||||||
Box::new(
|
Box::new(
|
||||||
JsonBody::new(req)
|
JsonBody::new(req, Some(cfg))
|
||||||
.limit(cfg.limit)
|
.limit(cfg.limit)
|
||||||
.map_err(move |e| (*err)(e, &req2))
|
.map_err(move |e| (*err)(e, &req2))
|
||||||
.map(Json),
|
.map(Json),
|
||||||
|
@ -155,6 +155,7 @@ where
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
|
/// extern crate mime;
|
||||||
/// #[macro_use] extern crate serde_derive;
|
/// #[macro_use] extern crate serde_derive;
|
||||||
/// use actix_web::{error, http, App, HttpResponse, Json, Result};
|
/// use actix_web::{error, http, App, HttpResponse, Json, Result};
|
||||||
///
|
///
|
||||||
|
@ -173,6 +174,9 @@ where
|
||||||
/// r.method(http::Method::POST)
|
/// r.method(http::Method::POST)
|
||||||
/// .with_config(index, |cfg| {
|
/// .with_config(index, |cfg| {
|
||||||
/// cfg.0.limit(4096) // <- change json extractor configuration
|
/// cfg.0.limit(4096) // <- change json extractor configuration
|
||||||
|
/// .content_type(|mime| { // <- accept text/plain content type
|
||||||
|
/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||||
|
/// })
|
||||||
/// .error_handler(|err, req| { // <- create custom error response
|
/// .error_handler(|err, req| { // <- create custom error response
|
||||||
/// error::InternalError::from_response(
|
/// error::InternalError::from_response(
|
||||||
/// err, HttpResponse::Conflict().finish()).into()
|
/// err, HttpResponse::Conflict().finish()).into()
|
||||||
|
@ -184,6 +188,7 @@ where
|
||||||
pub struct JsonConfig<S> {
|
pub struct JsonConfig<S> {
|
||||||
limit: usize,
|
limit: usize,
|
||||||
ehandler: Rc<Fn(JsonPayloadError, &HttpRequest<S>) -> Error>,
|
ehandler: Rc<Fn(JsonPayloadError, &HttpRequest<S>) -> Error>,
|
||||||
|
content_type: Option<Box<Fn(mime::Mime) -> bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> JsonConfig<S> {
|
impl<S> JsonConfig<S> {
|
||||||
|
@ -201,6 +206,15 @@ impl<S> JsonConfig<S> {
|
||||||
self.ehandler = Rc::new(f);
|
self.ehandler = Rc::new(f);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set predicate for allowed content types
|
||||||
|
pub fn content_type<F>(&mut self, predicate: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: Fn(mime::Mime) -> bool + 'static,
|
||||||
|
{
|
||||||
|
self.content_type = Some(Box::new(predicate));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Default for JsonConfig<S> {
|
impl<S> Default for JsonConfig<S> {
|
||||||
|
@ -208,6 +222,7 @@ impl<S> Default for JsonConfig<S> {
|
||||||
JsonConfig {
|
JsonConfig {
|
||||||
limit: 262_144,
|
limit: 262_144,
|
||||||
ehandler: Rc::new(|e, _| e.into()),
|
ehandler: Rc::new(|e, _| e.into()),
|
||||||
|
content_type: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,6 +232,7 @@ impl<S> Default for JsonConfig<S> {
|
||||||
/// Returns error:
|
/// Returns error:
|
||||||
///
|
///
|
||||||
/// * content type is not `application/json`
|
/// * content type is not `application/json`
|
||||||
|
/// (unless specified in [`JsonConfig`](struct.JsonConfig.html))
|
||||||
/// * content length is greater than 256k
|
/// * content length is greater than 256k
|
||||||
///
|
///
|
||||||
/// # Server example
|
/// # Server example
|
||||||
|
@ -253,10 +269,13 @@ pub struct JsonBody<T: HttpMessage, U: DeserializeOwned> {
|
||||||
|
|
||||||
impl<T: HttpMessage, U: DeserializeOwned> JsonBody<T, U> {
|
impl<T: HttpMessage, U: DeserializeOwned> JsonBody<T, U> {
|
||||||
/// Create `JsonBody` for request.
|
/// Create `JsonBody` for request.
|
||||||
pub fn new(req: &T) -> Self {
|
pub fn new<S>(req: &T, cfg: Option<&JsonConfig<S>>) -> Self {
|
||||||
// check content-type
|
// check content-type
|
||||||
let json = if let Ok(Some(mime)) = req.mime_type() {
|
let json = if let Ok(Some(mime)) = req.mime_type() {
|
||||||
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
|
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) ||
|
||||||
|
cfg.map_or(false, |cfg| {
|
||||||
|
cfg.content_type.as_ref().map_or(false, |predicate| predicate(mime))
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
@ -440,4 +459,61 @@ mod tests {
|
||||||
.finish();
|
.finish();
|
||||||
assert!(handler.handle(&req).as_err().is_none())
|
assert!(handler.handle(&req).as_err().is_none())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_json_and_bad_content_type() {
|
||||||
|
let mut cfg = JsonConfig::default();
|
||||||
|
cfg.limit(4096);
|
||||||
|
let handler = With::new(|data: Json<MyObject>| data, cfg);
|
||||||
|
|
||||||
|
let req = TestRequest::with_header(
|
||||||
|
header::CONTENT_TYPE,
|
||||||
|
header::HeaderValue::from_static("text/plain"),
|
||||||
|
).header(
|
||||||
|
header::CONTENT_LENGTH,
|
||||||
|
header::HeaderValue::from_static("16"),
|
||||||
|
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
|
.finish();
|
||||||
|
assert!(handler.handle(&req).as_err().is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_json_and_good_custom_content_type() {
|
||||||
|
let mut cfg = JsonConfig::default();
|
||||||
|
cfg.limit(4096);
|
||||||
|
cfg.content_type(|mime: mime::Mime| {
|
||||||
|
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||||
|
});
|
||||||
|
let handler = With::new(|data: Json<MyObject>| data, cfg);
|
||||||
|
|
||||||
|
let req = TestRequest::with_header(
|
||||||
|
header::CONTENT_TYPE,
|
||||||
|
header::HeaderValue::from_static("text/plain"),
|
||||||
|
).header(
|
||||||
|
header::CONTENT_LENGTH,
|
||||||
|
header::HeaderValue::from_static("16"),
|
||||||
|
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
|
.finish();
|
||||||
|
assert!(handler.handle(&req).as_err().is_none())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_json_and_bad_custom_content_type() {
|
||||||
|
let mut cfg = JsonConfig::default();
|
||||||
|
cfg.limit(4096);
|
||||||
|
cfg.content_type(|mime: mime::Mime| {
|
||||||
|
mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
|
||||||
|
});
|
||||||
|
let handler = With::new(|data: Json<MyObject>| data, cfg);
|
||||||
|
|
||||||
|
let req = TestRequest::with_header(
|
||||||
|
header::CONTENT_TYPE,
|
||||||
|
header::HeaderValue::from_static("text/html"),
|
||||||
|
).header(
|
||||||
|
header::CONTENT_LENGTH,
|
||||||
|
header::HeaderValue::from_static("16"),
|
||||||
|
).set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||||
|
.finish();
|
||||||
|
assert!(handler.handle(&req).as_err().is_some())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
18
src/lib.rs
18
src/lib.rs
|
@ -102,7 +102,6 @@ extern crate lazy_static;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate cookie;
|
extern crate cookie;
|
||||||
extern crate futures_cpupool;
|
extern crate futures_cpupool;
|
||||||
extern crate htmlescape;
|
|
||||||
extern crate http as modhttp;
|
extern crate http as modhttp;
|
||||||
extern crate httparse;
|
extern crate httparse;
|
||||||
extern crate language_tags;
|
extern crate language_tags;
|
||||||
|
@ -137,6 +136,7 @@ extern crate serde_urlencoded;
|
||||||
extern crate percent_encoding;
|
extern crate percent_encoding;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate smallvec;
|
extern crate smallvec;
|
||||||
|
extern crate v_htmlescape;
|
||||||
|
|
||||||
extern crate actix_net;
|
extern crate actix_net;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -217,14 +217,12 @@ pub use server::Request;
|
||||||
|
|
||||||
pub mod actix {
|
pub mod actix {
|
||||||
//! Re-exports [actix's](https://docs.rs/actix/) prelude
|
//! Re-exports [actix's](https://docs.rs/actix/) prelude
|
||||||
|
pub use super::actix_inner::actors::resolver;
|
||||||
extern crate actix;
|
pub use super::actix_inner::actors::signal;
|
||||||
pub use self::actix::actors::resolver;
|
pub use super::actix_inner::fut;
|
||||||
pub use self::actix::actors::signal;
|
pub use super::actix_inner::msgs;
|
||||||
pub use self::actix::fut;
|
pub use super::actix_inner::prelude::*;
|
||||||
pub use self::actix::msgs;
|
pub use super::actix_inner::{run, spawn};
|
||||||
pub use self::actix::prelude::*;
|
|
||||||
pub use self::actix::{run, spawn};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
|
@ -255,7 +253,7 @@ pub mod dev {
|
||||||
|
|
||||||
pub use body::BodyStream;
|
pub use body::BodyStream;
|
||||||
pub use context::Drain;
|
pub use context::Drain;
|
||||||
pub use extractor::{FormConfig, PayloadConfig};
|
pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig, EitherConfig, EitherCollisionStrategy};
|
||||||
pub use handler::{AsyncResult, Handler};
|
pub use handler::{AsyncResult, Handler};
|
||||||
pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
|
pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
|
||||||
pub use httpresponse::HttpResponseBuilder;
|
pub use httpresponse::HttpResponseBuilder;
|
||||||
|
|
|
@ -442,11 +442,23 @@ impl<S> Middleware<S> for Cors {
|
||||||
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
|
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AllOrSome::Some(_) => {
|
AllOrSome::Some(ref origins) => {
|
||||||
resp.headers_mut().insert(
|
if let Some(origin) = req.headers().get(header::ORIGIN).filter(|o| {
|
||||||
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
match o.to_str() {
|
||||||
self.inner.origins_str.as_ref().unwrap().clone(),
|
Ok(os) => origins.contains(os),
|
||||||
);
|
_ => false
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
|
origin.clone(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
|
self.inner.origins_str.as_ref().unwrap().clone()
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1134,17 +1146,10 @@ mod tests {
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if origins_str.starts_with("https://www.example.com") {
|
assert_eq!(
|
||||||
assert_eq!(
|
"https://www.example.com",
|
||||||
"https://www.example.com, https://www.google.com",
|
origins_str
|
||||||
origins_str
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
assert_eq!(
|
|
||||||
"https://www.google.com, https://www.example.com",
|
|
||||||
origins_str
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1180,4 +1185,43 @@ mod tests {
|
||||||
let response = srv.execute(request.send()).unwrap();
|
let response = srv.execute(request.send()).unwrap();
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_origins() {
|
||||||
|
let cors = Cors::build()
|
||||||
|
.allowed_origin("https://example.com")
|
||||||
|
.allowed_origin("https://example.org")
|
||||||
|
.allowed_methods(vec![Method::GET])
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
|
||||||
|
let req = TestRequest::with_header("Origin", "https://example.com")
|
||||||
|
.method(Method::GET)
|
||||||
|
.finish();
|
||||||
|
let resp: HttpResponse = HttpResponse::Ok().into();
|
||||||
|
|
||||||
|
let resp = cors.response(&req, resp).unwrap().response();
|
||||||
|
print!("{:?}", resp);
|
||||||
|
assert_eq!(
|
||||||
|
&b"https://example.com"[..],
|
||||||
|
resp.headers()
|
||||||
|
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
||||||
|
.unwrap()
|
||||||
|
.as_bytes()
|
||||||
|
);
|
||||||
|
|
||||||
|
let req = TestRequest::with_header("Origin", "https://example.org")
|
||||||
|
.method(Method::GET)
|
||||||
|
.finish();
|
||||||
|
let resp: HttpResponse = HttpResponse::Ok().into();
|
||||||
|
|
||||||
|
let resp = cors.response(&req, resp).unwrap().response();
|
||||||
|
assert_eq!(
|
||||||
|
&b"https://example.org"[..],
|
||||||
|
resp.headers()
|
||||||
|
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
||||||
|
.unwrap()
|
||||||
|
.as_bytes()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ impl ResponseError for CsrfError {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uri_origin(uri: &Uri) -> Option<String> {
|
fn uri_origin(uri: &Uri) -> Option<String> {
|
||||||
match (uri.scheme_part(), uri.host(), uri.port()) {
|
match (uri.scheme_part(), uri.host(), uri.port_part().map(|port| port.as_u16())) {
|
||||||
(Some(scheme), Some(host), Some(port)) => {
|
(Some(scheme), Some(host), Some(port)) => {
|
||||||
Some(format!("{}://{}:{}", scheme, host, port))
|
Some(format!("{}://{}:{}", scheme, host, port))
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ impl DefaultHeaders {
|
||||||
|
|
||||||
/// Set a header.
|
/// Set a header.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::match_wild_err_arm))]
|
#[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))]
|
||||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||||
where
|
where
|
||||||
HeaderName: HttpTryFrom<K>,
|
HeaderName: HttpTryFrom<K>,
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use cookie::{Cookie, CookieJar, Key};
|
use cookie::{Cookie, CookieJar, Key, SameSite};
|
||||||
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
|
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use time::Duration;
|
use time::Duration;
|
||||||
|
@ -237,6 +237,7 @@ struct CookieIdentityInner {
|
||||||
domain: Option<String>,
|
domain: Option<String>,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
max_age: Option<Duration>,
|
max_age: Option<Duration>,
|
||||||
|
same_site: Option<SameSite>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CookieIdentityInner {
|
impl CookieIdentityInner {
|
||||||
|
@ -248,6 +249,7 @@ impl CookieIdentityInner {
|
||||||
domain: None,
|
domain: None,
|
||||||
secure: true,
|
secure: true,
|
||||||
max_age: None,
|
max_age: None,
|
||||||
|
same_site: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,6 +270,10 @@ impl CookieIdentityInner {
|
||||||
cookie.set_max_age(max_age);
|
cookie.set_max_age(max_age);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(same_site) = self.same_site {
|
||||||
|
cookie.set_same_site(same_site);
|
||||||
|
}
|
||||||
|
|
||||||
let mut jar = CookieJar::new();
|
let mut jar = CookieJar::new();
|
||||||
if some {
|
if some {
|
||||||
jar.private(&self.key).add(cookie);
|
jar.private(&self.key).add(cookie);
|
||||||
|
@ -370,6 +376,12 @@ impl CookieIdentityPolicy {
|
||||||
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
|
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the `same_site` field in the session cookie being built.
|
||||||
|
pub fn same_site(mut self, same_site: SameSite) -> Self {
|
||||||
|
Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
|
impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
|
||||||
|
|
|
@ -33,7 +33,8 @@
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # extern crate actix_web;
|
//! # extern crate actix_web;
|
||||||
//! use actix_web::{actix, server, App, HttpRequest, Result};
|
//! # extern crate actix;
|
||||||
|
//! use actix_web::{server, App, HttpRequest, Result};
|
||||||
//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
|
//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend};
|
||||||
//!
|
//!
|
||||||
//! fn index(req: HttpRequest) -> Result<&'static str> {
|
//! fn index(req: HttpRequest) -> Result<&'static str> {
|
||||||
|
|
33
src/param.rs
33
src/param.rs
|
@ -8,7 +8,7 @@ use http::StatusCode;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use error::{InternalError, ResponseError, UriSegmentError};
|
use error::{InternalError, ResponseError, UriSegmentError};
|
||||||
use uri::Url;
|
use uri::{Url, RESERVED_QUOTER};
|
||||||
|
|
||||||
/// A trait to abstract the idea of creating a new instance of a type from a
|
/// A trait to abstract the idea of creating a new instance of a type from a
|
||||||
/// path parameter.
|
/// path parameter.
|
||||||
|
@ -103,6 +103,17 @@ impl Params {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get URL-decoded matched parameter by name without type conversion
|
||||||
|
pub fn get_decoded(&self, key: &str) -> Option<String> {
|
||||||
|
self.get(key).map(|value| {
|
||||||
|
if let Some(ref mut value) = RESERVED_QUOTER.requote(value.as_bytes()) {
|
||||||
|
Rc::make_mut(value).to_string()
|
||||||
|
} else {
|
||||||
|
value.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Get unprocessed part of path
|
/// Get unprocessed part of path
|
||||||
pub fn unprocessed(&self) -> &str {
|
pub fn unprocessed(&self) -> &str {
|
||||||
&self.url.path()[(self.tail as usize)..]
|
&self.url.path()[(self.tail as usize)..]
|
||||||
|
@ -300,4 +311,24 @@ mod tests {
|
||||||
Ok(PathBuf::from_iter(vec!["seg2"]))
|
Ok(PathBuf::from_iter(vec!["seg2"]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_param_by_name() {
|
||||||
|
let mut params = Params::new();
|
||||||
|
params.add_static("item1", "path");
|
||||||
|
params.add_static("item2", "http%3A%2F%2Flocalhost%3A80%2Ffoo");
|
||||||
|
|
||||||
|
assert_eq!(params.get("item0"), None);
|
||||||
|
assert_eq!(params.get_decoded("item0"), None);
|
||||||
|
assert_eq!(params.get("item1"), Some("path"));
|
||||||
|
assert_eq!(params.get_decoded("item1"), Some("path".to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
params.get("item2"),
|
||||||
|
Some("http%3A%2F%2Flocalhost%3A80%2Ffoo")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
params.get_decoded("item2"),
|
||||||
|
Some("http://localhost:80/foo".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -551,12 +551,12 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||||
if self.resp.as_ref().unwrap().status().is_server_error()
|
if self.resp.as_ref().unwrap().status().is_server_error()
|
||||||
{
|
{
|
||||||
error!(
|
error!(
|
||||||
"Error occured during request handling, status: {} {}",
|
"Error occurred during request handling, status: {} {}",
|
||||||
self.resp.as_ref().unwrap().status(), err
|
self.resp.as_ref().unwrap().status(), err
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
"Error occured during request handling: {}",
|
"Error occurred during request handling: {}",
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl<S: 'static> Route<S> {
|
||||||
pub(crate) fn compose(
|
pub(crate) fn compose(
|
||||||
&self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
&self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
) -> AsyncResult<HttpResponse> {
|
) -> AsyncResult<HttpResponse> {
|
||||||
AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
|
AsyncResult::future(Box::new(Compose::new(req, mws, self.handler.clone())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add match predicate to route.
|
/// Add match predicate to route.
|
||||||
|
|
|
@ -61,7 +61,7 @@ pub struct Scope<S> {
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "cargo-clippy",
|
feature = "cargo-clippy",
|
||||||
allow(clippy::new_without_default_derive)
|
allow(new_without_default_derive)
|
||||||
)]
|
)]
|
||||||
impl<S: 'static> Scope<S> {
|
impl<S: 'static> Scope<S> {
|
||||||
/// Create a new scope
|
/// Create a new scope
|
||||||
|
@ -356,7 +356,7 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
|
||||||
if self.middlewares.is_empty() {
|
if self.middlewares.is_empty() {
|
||||||
self.router.handle(&req2)
|
self.router.handle(&req2)
|
||||||
} else {
|
} else {
|
||||||
AsyncResult::async(Box::new(Compose::new(
|
AsyncResult::future(Box::new(Compose::new(
|
||||||
req2,
|
req2,
|
||||||
Rc::clone(&self.router),
|
Rc::clone(&self.router),
|
||||||
Rc::clone(&self.middlewares),
|
Rc::clone(&self.middlewares),
|
||||||
|
|
|
@ -9,14 +9,20 @@ use super::acceptor::{
|
||||||
};
|
};
|
||||||
use super::error::AcceptorError;
|
use super::error::AcceptorError;
|
||||||
use super::handler::IntoHttpHandler;
|
use super::handler::IntoHttpHandler;
|
||||||
use super::service::HttpService;
|
use super::service::{HttpService, StreamConfiguration};
|
||||||
use super::settings::{ServerSettings, ServiceConfig};
|
use super::settings::{ServerSettings, ServiceConfig};
|
||||||
use super::KeepAlive;
|
use super::KeepAlive;
|
||||||
|
|
||||||
pub(crate) trait ServiceProvider {
|
pub(crate) trait ServiceProvider {
|
||||||
fn register(
|
fn register(
|
||||||
&self, server: Server, lst: net::TcpListener, host: String,
|
&self,
|
||||||
addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64,
|
server: Server,
|
||||||
|
lst: net::TcpListener,
|
||||||
|
host: String,
|
||||||
|
addr: net::SocketAddr,
|
||||||
|
keep_alive: KeepAlive,
|
||||||
|
secure: bool,
|
||||||
|
client_timeout: u64,
|
||||||
client_shutdown: u64,
|
client_shutdown: u64,
|
||||||
) -> Server;
|
) -> Server;
|
||||||
}
|
}
|
||||||
|
@ -43,8 +49,13 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(
|
fn finish(
|
||||||
&self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool,
|
&self,
|
||||||
client_timeout: u64, client_shutdown: u64,
|
host: String,
|
||||||
|
addr: net::SocketAddr,
|
||||||
|
keep_alive: KeepAlive,
|
||||||
|
secure: bool,
|
||||||
|
client_timeout: u64,
|
||||||
|
client_shutdown: u64,
|
||||||
) -> impl ServiceFactory {
|
) -> impl ServiceFactory {
|
||||||
let factory = self.factory.clone();
|
let factory = self.factory.clone();
|
||||||
let acceptor = self.acceptor.clone();
|
let acceptor = self.acceptor.clone();
|
||||||
|
@ -65,6 +76,7 @@ where
|
||||||
acceptor.create(),
|
acceptor.create(),
|
||||||
)).map_err(|_| ())
|
)).map_err(|_| ())
|
||||||
.map_init_err(|_| ())
|
.map_init_err(|_| ())
|
||||||
|
.and_then(StreamConfiguration::new().nodelay(true))
|
||||||
.and_then(
|
.and_then(
|
||||||
HttpService::new(settings)
|
HttpService::new(settings)
|
||||||
.map_init_err(|_| ())
|
.map_init_err(|_| ())
|
||||||
|
@ -76,6 +88,7 @@ where
|
||||||
TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service))
|
TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service))
|
||||||
.map_err(|_| ())
|
.map_err(|_| ())
|
||||||
.map_init_err(|_| ())
|
.map_init_err(|_| ())
|
||||||
|
.and_then(StreamConfiguration::new().nodelay(true))
|
||||||
.and_then(
|
.and_then(
|
||||||
HttpService::new(settings)
|
HttpService::new(settings)
|
||||||
.map_init_err(|_| ())
|
.map_init_err(|_| ())
|
||||||
|
@ -95,8 +108,14 @@ where
|
||||||
H: IntoHttpHandler,
|
H: IntoHttpHandler,
|
||||||
{
|
{
|
||||||
fn register(
|
fn register(
|
||||||
&self, server: Server, lst: net::TcpListener, host: String,
|
&self,
|
||||||
addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64,
|
server: Server,
|
||||||
|
lst: net::TcpListener,
|
||||||
|
host: String,
|
||||||
|
addr: net::SocketAddr,
|
||||||
|
keep_alive: KeepAlive,
|
||||||
|
secure: bool,
|
||||||
|
client_timeout: u64,
|
||||||
client_shutdown: u64,
|
client_shutdown: u64,
|
||||||
) -> Server {
|
) -> Server {
|
||||||
server.listen2(
|
server.listen2(
|
||||||
|
|
102
src/server/h1.rs
102
src/server/h1.rs
|
@ -7,6 +7,7 @@ use futures::{Async, Future, Poll};
|
||||||
use tokio_current_thread::spawn;
|
use tokio_current_thread::spawn;
|
||||||
use tokio_timer::Delay;
|
use tokio_timer::Delay;
|
||||||
|
|
||||||
|
use body::Binary;
|
||||||
use error::{Error, PayloadError};
|
use error::{Error, PayloadError};
|
||||||
use http::{StatusCode, Version};
|
use http::{StatusCode, Version};
|
||||||
use payload::{Payload, PayloadStatus, PayloadWriter};
|
use payload::{Payload, PayloadStatus, PayloadWriter};
|
||||||
|
@ -50,32 +51,40 @@ pub struct Http1Dispatcher<T: IoStream, H: HttpHandler + 'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Entry<H: HttpHandler> {
|
enum Entry<H: HttpHandler> {
|
||||||
Task(H::Task),
|
Task(H::Task, Option<()>),
|
||||||
Error(Box<HttpHandlerTask>),
|
Error(Box<HttpHandlerTask>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H: HttpHandler> Entry<H> {
|
impl<H: HttpHandler> Entry<H> {
|
||||||
fn into_task(self) -> H::Task {
|
fn into_task(self) -> H::Task {
|
||||||
match self {
|
match self {
|
||||||
Entry::Task(task) => task,
|
Entry::Task(task, _) => task,
|
||||||
Entry::Error(_) => panic!(),
|
Entry::Error(_) => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn disconnected(&mut self) {
|
fn disconnected(&mut self) {
|
||||||
match *self {
|
match *self {
|
||||||
Entry::Task(ref mut task) => task.disconnected(),
|
Entry::Task(ref mut task, _) => task.disconnected(),
|
||||||
Entry::Error(ref mut task) => task.disconnected(),
|
Entry::Error(ref mut task) => task.disconnected(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
|
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error> {
|
||||||
match *self {
|
match *self {
|
||||||
Entry::Task(ref mut task) => task.poll_io(io),
|
Entry::Task(ref mut task, ref mut except) => {
|
||||||
|
match except {
|
||||||
|
Some(_) => {
|
||||||
|
let _ = io.write(&Binary::from("HTTP/1.1 100 Continue\r\n\r\n"));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
task.poll_io(io)
|
||||||
|
}
|
||||||
Entry::Error(ref mut task) => task.poll_io(io),
|
Entry::Error(ref mut task) => task.poll_io(io),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn poll_completed(&mut self) -> Poll<(), Error> {
|
fn poll_completed(&mut self) -> Poll<(), Error> {
|
||||||
match *self {
|
match *self {
|
||||||
Entry::Task(ref mut task) => task.poll_completed(),
|
Entry::Task(ref mut task, _) => task.poll_completed(),
|
||||||
Entry::Error(ref mut task) => task.poll_completed(),
|
Entry::Error(ref mut task) => task.poll_completed(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +96,10 @@ where
|
||||||
H: HttpHandler + 'static,
|
H: HttpHandler + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
settings: ServiceConfig<H>, stream: T, buf: BytesMut, is_eof: bool,
|
settings: ServiceConfig<H>,
|
||||||
|
stream: T,
|
||||||
|
buf: BytesMut,
|
||||||
|
is_eof: bool,
|
||||||
keepalive_timer: Option<Delay>,
|
keepalive_timer: Option<Delay>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let addr = stream.peer_addr();
|
let addr = stream.peer_addr();
|
||||||
|
@ -123,8 +135,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn for_error(
|
pub(crate) fn for_error(
|
||||||
settings: ServiceConfig<H>, stream: T, status: StatusCode,
|
settings: ServiceConfig<H>,
|
||||||
mut keepalive_timer: Option<Delay>, buf: BytesMut,
|
stream: T,
|
||||||
|
status: StatusCode,
|
||||||
|
mut keepalive_timer: Option<Delay>,
|
||||||
|
buf: BytesMut,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
if let Some(deadline) = settings.client_timer_expire() {
|
if let Some(deadline) = settings.client_timer_expire() {
|
||||||
let _ = keepalive_timer.as_mut().map(|delay| delay.reset(deadline));
|
let _ = keepalive_timer.as_mut().map(|delay| delay.reset(deadline));
|
||||||
|
@ -280,7 +295,7 @@ where
|
||||||
}
|
}
|
||||||
if timer.deadline() >= self.ka_expire {
|
if timer.deadline() >= self.ka_expire {
|
||||||
// check for any outstanding request handling
|
// check for any outstanding request handling
|
||||||
if self.tasks.is_empty() {
|
if self.tasks.is_empty() && self.flags.contains(Flags::FLUSHED) {
|
||||||
if !self.flags.contains(Flags::STARTED) {
|
if !self.flags.contains(Flags::STARTED) {
|
||||||
// timeout on first request (slow request) return 408
|
// timeout on first request (slow request) return 408
|
||||||
trace!("Slow request timeout");
|
trace!("Slow request timeout");
|
||||||
|
@ -298,16 +313,19 @@ where
|
||||||
if let Some(deadline) =
|
if let Some(deadline) =
|
||||||
self.settings.client_shutdown_timer()
|
self.settings.client_shutdown_timer()
|
||||||
{
|
{
|
||||||
timer.reset(deadline)
|
timer.reset(deadline);
|
||||||
|
let _ = timer.poll();
|
||||||
} else {
|
} else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(dl) = self.settings.keep_alive_expire() {
|
} else if let Some(dl) = self.settings.keep_alive_expire() {
|
||||||
timer.reset(dl)
|
timer.reset(dl);
|
||||||
|
let _ = timer.poll();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
timer.reset(self.ka_expire)
|
timer.reset(self.ka_expire);
|
||||||
|
let _ = timer.poll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Async::NotReady) => (),
|
Ok(Async::NotReady) => (),
|
||||||
|
@ -454,7 +472,11 @@ where
|
||||||
|
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
match self.decoder.decode(&mut self.buf, &self.settings) {
|
match self.decoder.decode(&mut self.buf, &self.settings) {
|
||||||
Ok(Some(Message::Message { mut msg, payload })) => {
|
Ok(Some(Message::Message {
|
||||||
|
mut msg,
|
||||||
|
mut expect,
|
||||||
|
payload,
|
||||||
|
})) => {
|
||||||
updated = true;
|
updated = true;
|
||||||
self.flags.insert(Flags::STARTED);
|
self.flags.insert(Flags::STARTED);
|
||||||
|
|
||||||
|
@ -475,6 +497,12 @@ where
|
||||||
match self.settings.handler().handle(msg) {
|
match self.settings.handler().handle(msg) {
|
||||||
Ok(mut task) => {
|
Ok(mut task) => {
|
||||||
if self.tasks.is_empty() {
|
if self.tasks.is_empty() {
|
||||||
|
if expect {
|
||||||
|
expect = false;
|
||||||
|
let _ = self.stream.write(&Binary::from(
|
||||||
|
"HTTP/1.1 100 Continue\r\n\r\n",
|
||||||
|
));
|
||||||
|
}
|
||||||
match task.poll_io(&mut self.stream) {
|
match task.poll_io(&mut self.stream) {
|
||||||
Ok(Async::Ready(ready)) => {
|
Ok(Async::Ready(ready)) => {
|
||||||
// override keep-alive state
|
// override keep-alive state
|
||||||
|
@ -501,7 +529,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.tasks.push_back(Entry::Task(task));
|
self.tasks.push_back(Entry::Task(
|
||||||
|
task,
|
||||||
|
if expect { Some(()) } else { None },
|
||||||
|
));
|
||||||
continue 'outer;
|
continue 'outer;
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -599,13 +630,13 @@ mod tests {
|
||||||
impl Message {
|
impl Message {
|
||||||
fn message(self) -> Request {
|
fn message(self) -> Request {
|
||||||
match self {
|
match self {
|
||||||
Message::Message { msg, payload: _ } => msg,
|
Message::Message { msg, .. } => msg,
|
||||||
_ => panic!("error"),
|
_ => panic!("error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_payload(&self) -> bool {
|
fn is_payload(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
Message::Message { msg: _, payload } => payload,
|
Message::Message { payload, .. } => payload,
|
||||||
_ => panic!("error"),
|
_ => panic!("error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -865,13 +896,13 @@ mod tests {
|
||||||
let settings = wrk_settings();
|
let settings = wrk_settings();
|
||||||
|
|
||||||
let mut reader = H1Decoder::new();
|
let mut reader = H1Decoder::new();
|
||||||
assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() }
|
assert! { reader.decode(&mut buf, &settings).unwrap().is_none() }
|
||||||
|
|
||||||
buf.extend(b"t");
|
buf.extend(b"t");
|
||||||
assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() }
|
assert! { reader.decode(&mut buf, &settings).unwrap().is_none() }
|
||||||
|
|
||||||
buf.extend(b"es");
|
buf.extend(b"es");
|
||||||
assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() }
|
assert! { reader.decode(&mut buf, &settings).unwrap().is_none() }
|
||||||
|
|
||||||
buf.extend(b"t: value\r\n\r\n");
|
buf.extend(b"t: value\r\n\r\n");
|
||||||
match reader.decode(&mut buf, &settings) {
|
match reader.decode(&mut buf, &settings) {
|
||||||
|
@ -933,6 +964,14 @@ mod tests {
|
||||||
let req = parse_ready!(&mut buf);
|
let req = parse_ready!(&mut buf);
|
||||||
|
|
||||||
assert!(!req.keep_alive());
|
assert!(!req.keep_alive());
|
||||||
|
|
||||||
|
let mut buf = BytesMut::from(
|
||||||
|
"GET /test HTTP/1.1\r\n\
|
||||||
|
connection: Close\r\n\r\n",
|
||||||
|
);
|
||||||
|
let req = parse_ready!(&mut buf);
|
||||||
|
|
||||||
|
assert!(!req.keep_alive());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -944,10 +983,26 @@ mod tests {
|
||||||
let req = parse_ready!(&mut buf);
|
let req = parse_ready!(&mut buf);
|
||||||
|
|
||||||
assert!(!req.keep_alive());
|
assert!(!req.keep_alive());
|
||||||
|
|
||||||
|
let mut buf = BytesMut::from(
|
||||||
|
"GET /test HTTP/1.0\r\n\
|
||||||
|
connection: Close\r\n\r\n",
|
||||||
|
);
|
||||||
|
let req = parse_ready!(&mut buf);
|
||||||
|
|
||||||
|
assert!(!req.keep_alive());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_conn_keep_alive_1_0() {
|
fn test_conn_keep_alive_1_0() {
|
||||||
|
let mut buf = BytesMut::from(
|
||||||
|
"GET /test HTTP/1.0\r\n\
|
||||||
|
connection: Keep-Alive\r\n\r\n",
|
||||||
|
);
|
||||||
|
let req = parse_ready!(&mut buf);
|
||||||
|
|
||||||
|
assert!(req.keep_alive());
|
||||||
|
|
||||||
let mut buf = BytesMut::from(
|
let mut buf = BytesMut::from(
|
||||||
"GET /test HTTP/1.0\r\n\
|
"GET /test HTTP/1.0\r\n\
|
||||||
connection: keep-alive\r\n\r\n",
|
connection: keep-alive\r\n\r\n",
|
||||||
|
@ -1000,6 +1055,15 @@ mod tests {
|
||||||
let req = parse_ready!(&mut buf);
|
let req = parse_ready!(&mut buf);
|
||||||
|
|
||||||
assert!(req.upgrade());
|
assert!(req.upgrade());
|
||||||
|
|
||||||
|
let mut buf = BytesMut::from(
|
||||||
|
"GET /test HTTP/1.1\r\n\
|
||||||
|
upgrade: Websockets\r\n\
|
||||||
|
connection: Upgrade\r\n\r\n",
|
||||||
|
);
|
||||||
|
let req = parse_ready!(&mut buf);
|
||||||
|
|
||||||
|
assert!(req.upgrade());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -20,7 +20,11 @@ pub(crate) struct H1Decoder {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum Message {
|
pub(crate) enum Message {
|
||||||
Message { msg: Request, payload: bool },
|
Message {
|
||||||
|
msg: Request,
|
||||||
|
payload: bool,
|
||||||
|
expect: bool,
|
||||||
|
},
|
||||||
Chunk(Bytes),
|
Chunk(Bytes),
|
||||||
Eof,
|
Eof,
|
||||||
}
|
}
|
||||||
|
@ -43,7 +47,9 @@ impl H1Decoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode<H>(
|
pub fn decode<H>(
|
||||||
&mut self, src: &mut BytesMut, settings: &ServiceConfig<H>,
|
&mut self,
|
||||||
|
src: &mut BytesMut,
|
||||||
|
settings: &ServiceConfig<H>,
|
||||||
) -> Result<Option<Message>, DecoderError> {
|
) -> Result<Option<Message>, DecoderError> {
|
||||||
// read payload
|
// read payload
|
||||||
if self.decoder.is_some() {
|
if self.decoder.is_some() {
|
||||||
|
@ -61,10 +67,11 @@ impl H1Decoder {
|
||||||
.parse_message(src, settings)
|
.parse_message(src, settings)
|
||||||
.map_err(DecoderError::Error)?
|
.map_err(DecoderError::Error)?
|
||||||
{
|
{
|
||||||
Async::Ready((msg, decoder)) => {
|
Async::Ready((msg, expect, decoder)) => {
|
||||||
self.decoder = decoder;
|
self.decoder = decoder;
|
||||||
Ok(Some(Message::Message {
|
Ok(Some(Message::Message {
|
||||||
msg,
|
msg,
|
||||||
|
expect,
|
||||||
payload: self.decoder.is_some(),
|
payload: self.decoder.is_some(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -80,12 +87,15 @@ impl H1Decoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_message<H>(
|
fn parse_message<H>(
|
||||||
&self, buf: &mut BytesMut, settings: &ServiceConfig<H>,
|
&self,
|
||||||
) -> Poll<(Request, Option<EncodingDecoder>), ParseError> {
|
buf: &mut BytesMut,
|
||||||
|
settings: &ServiceConfig<H>,
|
||||||
|
) -> Poll<(Request, bool, Option<EncodingDecoder>), ParseError> {
|
||||||
// Parse http message
|
// Parse http message
|
||||||
let mut has_upgrade = false;
|
let mut has_upgrade = false;
|
||||||
let mut chunked = false;
|
let mut chunked = false;
|
||||||
let mut content_length = None;
|
let mut content_length = None;
|
||||||
|
let mut expect_continue = false;
|
||||||
|
|
||||||
let msg = {
|
let msg = {
|
||||||
// Unsafe: we read only this data only after httparse parses headers into.
|
// Unsafe: we read only this data only after httparse parses headers into.
|
||||||
|
@ -153,23 +163,25 @@ impl H1Decoder {
|
||||||
}
|
}
|
||||||
// transfer-encoding
|
// transfer-encoding
|
||||||
header::TRANSFER_ENCODING => {
|
header::TRANSFER_ENCODING => {
|
||||||
if let Ok(s) = value.to_str() {
|
if let Ok(s) = value.to_str().map(|s| s.trim()) {
|
||||||
chunked = s.to_lowercase().contains("chunked");
|
chunked = s.eq_ignore_ascii_case("chunked");
|
||||||
} else {
|
} else {
|
||||||
return Err(ParseError::Header);
|
return Err(ParseError::Header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// connection keep-alive state
|
// connection keep-alive state
|
||||||
header::CONNECTION => {
|
header::CONNECTION => {
|
||||||
let ka = if let Ok(conn) = value.to_str() {
|
let ka = if let Ok(conn) =
|
||||||
|
value.to_str().map(|conn| conn.trim())
|
||||||
|
{
|
||||||
if version == Version::HTTP_10
|
if version == Version::HTTP_10
|
||||||
&& conn.contains("keep-alive")
|
&& conn.eq_ignore_ascii_case("keep-alive")
|
||||||
{
|
{
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
version == Version::HTTP_11 && !(conn
|
version == Version::HTTP_11
|
||||||
.contains("close")
|
&& !(conn.eq_ignore_ascii_case("close")
|
||||||
|| conn.contains("upgrade"))
|
|| conn.eq_ignore_ascii_case("upgrade"))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -178,6 +190,18 @@ impl H1Decoder {
|
||||||
}
|
}
|
||||||
header::UPGRADE => {
|
header::UPGRADE => {
|
||||||
has_upgrade = true;
|
has_upgrade = true;
|
||||||
|
// check content-length, some clients (dart)
|
||||||
|
// sends "content-length: 0" with websocket upgrade
|
||||||
|
if let Ok(val) = value.to_str().map(|val| val.trim()) {
|
||||||
|
if val.eq_ignore_ascii_case("websocket") {
|
||||||
|
content_length = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header::EXPECT => {
|
||||||
|
if value == "100-continue" {
|
||||||
|
expect_continue = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -209,7 +233,7 @@ impl H1Decoder {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Async::Ready((msg, decoder)))
|
Ok(Async::Ready((msg, expect_continue, decoder)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +245,9 @@ pub(crate) struct HeaderIndex {
|
||||||
|
|
||||||
impl HeaderIndex {
|
impl HeaderIndex {
|
||||||
pub(crate) fn record(
|
pub(crate) fn record(
|
||||||
bytes: &[u8], headers: &[httparse::Header], indices: &mut [HeaderIndex],
|
bytes: &[u8],
|
||||||
|
headers: &[httparse::Header],
|
||||||
|
indices: &mut [HeaderIndex],
|
||||||
) {
|
) {
|
||||||
let bytes_ptr = bytes.as_ptr() as usize;
|
let bytes_ptr = bytes.as_ptr() as usize;
|
||||||
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
|
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
|
||||||
|
@ -369,7 +395,10 @@ macro_rules! byte (
|
||||||
|
|
||||||
impl ChunkedState {
|
impl ChunkedState {
|
||||||
fn step(
|
fn step(
|
||||||
&self, body: &mut BytesMut, size: &mut u64, buf: &mut Option<Bytes>,
|
&self,
|
||||||
|
body: &mut BytesMut,
|
||||||
|
size: &mut u64,
|
||||||
|
buf: &mut Option<Bytes>,
|
||||||
) -> Poll<ChunkedState, io::Error> {
|
) -> Poll<ChunkedState, io::Error> {
|
||||||
use self::ChunkedState::*;
|
use self::ChunkedState::*;
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -432,7 +461,8 @@ impl ChunkedState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn read_size_lf(
|
fn read_size_lf(
|
||||||
rdr: &mut BytesMut, size: &mut u64,
|
rdr: &mut BytesMut,
|
||||||
|
size: &mut u64,
|
||||||
) -> Poll<ChunkedState, io::Error> {
|
) -> Poll<ChunkedState, io::Error> {
|
||||||
match byte!(rdr) {
|
match byte!(rdr) {
|
||||||
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)),
|
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)),
|
||||||
|
@ -445,7 +475,9 @@ impl ChunkedState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_body(
|
fn read_body(
|
||||||
rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option<Bytes>,
|
rdr: &mut BytesMut,
|
||||||
|
rem: &mut u64,
|
||||||
|
buf: &mut Option<Bytes>,
|
||||||
) -> Poll<ChunkedState, io::Error> {
|
) -> Poll<ChunkedState, io::Error> {
|
||||||
trace!("Chunked read, remaining={:?}", rem);
|
trace!("Chunked read, remaining={:?}", rem);
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,10 @@ where
|
||||||
H: HttpHandler + 'static,
|
H: HttpHandler + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
settings: ServiceConfig<H>, io: T, buf: Bytes, keepalive_timer: Option<Delay>,
|
settings: ServiceConfig<H>,
|
||||||
|
io: T,
|
||||||
|
buf: Bytes,
|
||||||
|
keepalive_timer: Option<Delay>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let addr = io.peer_addr();
|
let addr = io.peer_addr();
|
||||||
let extensions = io.extensions();
|
let extensions = io.extensions();
|
||||||
|
@ -284,10 +287,12 @@ where
|
||||||
if self.tasks.is_empty() {
|
if self.tasks.is_empty() {
|
||||||
return Err(HttpDispatchError::ShutdownTimeout);
|
return Err(HttpDispatchError::ShutdownTimeout);
|
||||||
} else if let Some(dl) = self.settings.keep_alive_expire() {
|
} else if let Some(dl) = self.settings.keep_alive_expire() {
|
||||||
timer.reset(dl)
|
timer.reset(dl);
|
||||||
|
let _ = timer.poll();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
timer.reset(self.ka_expire)
|
timer.reset(self.ka_expire);
|
||||||
|
let _ = timer.poll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Async::NotReady) => (),
|
Ok(Async::NotReady) => (),
|
||||||
|
@ -348,8 +353,11 @@ struct Entry<H: HttpHandler + 'static> {
|
||||||
|
|
||||||
impl<H: HttpHandler + 'static> Entry<H> {
|
impl<H: HttpHandler + 'static> Entry<H> {
|
||||||
fn new(
|
fn new(
|
||||||
parts: Parts, recv: RecvStream, resp: SendResponse<Bytes>,
|
parts: Parts,
|
||||||
addr: Option<SocketAddr>, settings: ServiceConfig<H>,
|
recv: RecvStream,
|
||||||
|
resp: SendResponse<Bytes>,
|
||||||
|
addr: Option<SocketAddr>,
|
||||||
|
settings: ServiceConfig<H>,
|
||||||
extensions: Option<Rc<Extensions>>,
|
extensions: Option<Rc<Extensions>>,
|
||||||
) -> Entry<H>
|
) -> Entry<H>
|
||||||
where
|
where
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
feature = "cargo-clippy",
|
feature = "cargo-clippy",
|
||||||
allow(clippy::redundant_field_names)
|
allow(redundant_field_names)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use std::{cmp, io};
|
use std::{cmp, io};
|
||||||
|
|
|
@ -326,7 +326,7 @@ where
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "cargo-clippy",
|
feature = "cargo-clippy",
|
||||||
allow(clippy::needless_pass_by_value)
|
allow(needless_pass_by_value)
|
||||||
)]
|
)]
|
||||||
pub fn bind_with<S, A>(mut self, addr: S, acceptor: A) -> io::Result<Self>
|
pub fn bind_with<S, A>(mut self, addr: S, acceptor: A) -> io::Result<Self>
|
||||||
where
|
where
|
||||||
|
@ -451,13 +451,13 @@ impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
|
||||||
/// For each address this method starts separate thread which does
|
/// For each address this method starts separate thread which does
|
||||||
/// `accept()` in a loop.
|
/// `accept()` in a loop.
|
||||||
///
|
///
|
||||||
/// This methods panics if no socket addresses get bound.
|
/// This methods panics if no socket address can be bound or an `Actix` system is not yet
|
||||||
///
|
/// configured.
|
||||||
/// This method requires to run within properly configured `Actix` system.
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// extern crate actix_web;
|
/// extern crate actix_web;
|
||||||
/// use actix_web::{actix, server, App, HttpResponse};
|
/// extern crate actix;
|
||||||
|
/// use actix_web::{server, App, HttpResponse};
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let sys = actix::System::new("example"); // <- create Actix system
|
/// let sys = actix::System::new("example"); // <- create Actix system
|
||||||
|
|
|
@ -166,7 +166,8 @@ const HW_BUFFER_SIZE: usize = 32_768;
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
/// use actix_web::{actix, server, App, HttpResponse};
|
/// # extern crate actix;
|
||||||
|
/// use actix_web::{server, App, HttpResponse};
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let sys = actix::System::new("example"); // <- create Actix system
|
/// let sys = actix::System::new("example"); // <- create Actix system
|
||||||
|
@ -302,6 +303,8 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static {
|
||||||
} else {
|
} else {
|
||||||
Ok(Async::NotReady)
|
Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
|
} else if e.kind() == io::ErrorKind::ConnectionReset && read_some {
|
||||||
|
Ok(Async::Ready((read_some, true)))
|
||||||
} else {
|
} else {
|
||||||
Err(e)
|
Err(e)
|
||||||
};
|
};
|
||||||
|
@ -334,7 +337,7 @@ impl IoStream for ::tokio_uds::UnixStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn set_keepalive(&mut self, _nodelay: bool) -> io::Result<()> {
|
fn set_keepalive(&mut self, _dur: Option<time::Duration>) -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,11 +299,10 @@ impl Output {
|
||||||
match resp.chunked() {
|
match resp.chunked() {
|
||||||
Some(true) => {
|
Some(true) => {
|
||||||
// Enable transfer encoding
|
// Enable transfer encoding
|
||||||
|
info.length = ResponseLength::Chunked;
|
||||||
if version == Version::HTTP_2 {
|
if version == Version::HTTP_2 {
|
||||||
info.length = ResponseLength::None;
|
|
||||||
TransferEncoding::eof(buf)
|
TransferEncoding::eof(buf)
|
||||||
} else {
|
} else {
|
||||||
info.length = ResponseLength::Chunked;
|
|
||||||
TransferEncoding::chunked(buf)
|
TransferEncoding::chunked(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,15 +336,11 @@ impl Output {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Enable transfer encoding
|
// Enable transfer encoding
|
||||||
match version {
|
info.length = ResponseLength::Chunked;
|
||||||
Version::HTTP_11 => {
|
if version == Version::HTTP_2 {
|
||||||
info.length = ResponseLength::Chunked;
|
TransferEncoding::eof(buf)
|
||||||
TransferEncoding::chunked(buf)
|
} else {
|
||||||
}
|
TransferEncoding::chunked(buf)
|
||||||
_ => {
|
|
||||||
info.length = ResponseLength::None;
|
|
||||||
TransferEncoding::eof(buf)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -443,7 +438,7 @@ impl ContentEncoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))]
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
|
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
|
||||||
let encoder =
|
let encoder =
|
||||||
|
@ -485,7 +480,7 @@ impl ContentEncoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))]
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
|
|
48
src/test.rs
48
src/test.rs
|
@ -4,8 +4,10 @@ use std::str::FromStr;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::{net, thread};
|
use std::{net, thread};
|
||||||
|
|
||||||
use actix_inner::{Actor, Addr, System};
|
use actix::{Actor, Addr, System};
|
||||||
|
use actix::actors::signal;
|
||||||
|
|
||||||
|
use actix_net::server::Server;
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use http::header::HeaderName;
|
use http::header::HeaderName;
|
||||||
|
@ -66,6 +68,7 @@ pub struct TestServer {
|
||||||
ssl: bool,
|
ssl: bool,
|
||||||
conn: Addr<ClientConnector>,
|
conn: Addr<ClientConnector>,
|
||||||
rt: Runtime,
|
rt: Runtime,
|
||||||
|
backend: Addr<Server>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestServer {
|
impl TestServer {
|
||||||
|
@ -112,24 +115,25 @@ impl TestServer {
|
||||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
let local_addr = tcp.local_addr().unwrap();
|
let local_addr = tcp.local_addr().unwrap();
|
||||||
|
|
||||||
let _ = HttpServer::new(factory)
|
let srv = HttpServer::new(factory)
|
||||||
.disable_signals()
|
.disable_signals()
|
||||||
.listen(tcp)
|
.listen(tcp)
|
||||||
.keep_alive(5)
|
.keep_alive(5)
|
||||||
.start();
|
.start();
|
||||||
|
|
||||||
tx.send((System::current(), local_addr, TestServer::get_conn()))
|
tx.send((System::current(), local_addr, TestServer::get_conn(), srv))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
sys.run();
|
sys.run();
|
||||||
});
|
});
|
||||||
|
|
||||||
let (system, addr, conn) = rx.recv().unwrap();
|
let (system, addr, conn, backend) = rx.recv().unwrap();
|
||||||
System::set_current(system);
|
System::set_current(system);
|
||||||
TestServer {
|
TestServer {
|
||||||
addr,
|
addr,
|
||||||
conn,
|
conn,
|
||||||
ssl: false,
|
ssl: false,
|
||||||
rt: Runtime::new().unwrap(),
|
rt: Runtime::new().unwrap(),
|
||||||
|
backend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,6 +201,7 @@ impl TestServer {
|
||||||
|
|
||||||
/// Stop http server
|
/// Stop http server
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
|
let _ = self.backend.send(signal::Signal(signal::SignalType::Term)).wait();
|
||||||
System::current().stop();
|
System::current().stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,8 +338,7 @@ where
|
||||||
.keep_alive(5)
|
.keep_alive(5)
|
||||||
.disable_signals();
|
.disable_signals();
|
||||||
|
|
||||||
tx.send((System::current(), addr, TestServer::get_conn()))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
#[cfg(any(feature = "alpn", feature = "ssl"))]
|
||||||
{
|
{
|
||||||
|
@ -356,18 +360,22 @@ where
|
||||||
let tcp = net::TcpListener::bind(addr).unwrap();
|
let tcp = net::TcpListener::bind(addr).unwrap();
|
||||||
srv = srv.listen(tcp);
|
srv = srv.listen(tcp);
|
||||||
}
|
}
|
||||||
srv.start();
|
let backend = srv.start();
|
||||||
|
|
||||||
|
tx.send((System::current(), addr, TestServer::get_conn(), backend))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
sys.run();
|
sys.run();
|
||||||
});
|
});
|
||||||
|
|
||||||
let (system, addr, conn) = rx.recv().unwrap();
|
let (system, addr, conn, backend) = rx.recv().unwrap();
|
||||||
System::set_current(system);
|
System::set_current(system);
|
||||||
TestServer {
|
TestServer {
|
||||||
addr,
|
addr,
|
||||||
conn,
|
conn,
|
||||||
ssl: has_ssl,
|
ssl: has_ssl,
|
||||||
rt: Runtime::new().unwrap(),
|
rt: Runtime::new().unwrap(),
|
||||||
|
backend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -507,6 +515,11 @@ impl TestRequest<()> {
|
||||||
{
|
{
|
||||||
TestRequest::default().header(key, value)
|
TestRequest::default().header(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create TestRequest and set request cookie
|
||||||
|
pub fn with_cookie(cookie: Cookie<'static>) -> TestRequest<()> {
|
||||||
|
TestRequest::default().cookie(cookie)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> TestRequest<S> {
|
impl<S: 'static> TestRequest<S> {
|
||||||
|
@ -543,6 +556,25 @@ impl<S: 'static> TestRequest<S> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// set cookie of this request
|
||||||
|
pub fn cookie(mut self, cookie: Cookie<'static>) -> Self {
|
||||||
|
if self.cookies.is_some() {
|
||||||
|
let mut should_insert = true;
|
||||||
|
let old_cookies = self.cookies.as_mut().unwrap();
|
||||||
|
for old_cookie in old_cookies.iter() {
|
||||||
|
if old_cookie == &cookie {
|
||||||
|
should_insert = false
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if should_insert {
|
||||||
|
old_cookies.push(cookie);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
self.cookies = Some(vec![cookie]);
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set a header
|
/// Set a header
|
||||||
pub fn set<H: Header>(mut self, hdr: H) -> Self {
|
pub fn set<H: Header>(mut self, hdr: H) -> Self {
|
||||||
if let Ok(value) = hdr.try_into() {
|
if let Ok(value) = hdr.try_into() {
|
||||||
|
|
95
src/uri.rs
95
src/uri.rs
|
@ -1,25 +1,12 @@
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
// https://tools.ietf.org/html/rfc3986#section-2.2
|
||||||
const GEN_DELIMS: &[u8] = b":/?#[]@";
|
const RESERVED_PLUS_EXTRA: &[u8] = b":/?#[]@!$&'()*,+?;=%^ <>\"\\`{}|";
|
||||||
#[allow(dead_code)]
|
|
||||||
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
|
// https://tools.ietf.org/html/rfc3986#section-2.3
|
||||||
#[allow(dead_code)]
|
const UNRESERVED: &[u8] =
|
||||||
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
|
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-._~";
|
||||||
#[allow(dead_code)]
|
|
||||||
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
1234567890
|
|
||||||
-._~";
|
|
||||||
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
1234567890
|
|
||||||
-._~
|
|
||||||
!$'()*,";
|
|
||||||
const QS: &[u8] = b"+&=;b";
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn bit_at(array: &[u8], ch: u8) -> bool {
|
fn bit_at(array: &[u8], ch: u8) -> bool {
|
||||||
|
@ -32,7 +19,8 @@ fn set_bit(array: &mut [u8], ch: u8) {
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") };
|
static ref UNRESERVED_QUOTER: Quoter = { Quoter::new(UNRESERVED) };
|
||||||
|
pub(crate) static ref RESERVED_QUOTER: Quoter = { Quoter::new(RESERVED_PLUS_EXTRA) };
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
|
@ -43,7 +31,7 @@ pub(crate) struct Url {
|
||||||
|
|
||||||
impl Url {
|
impl Url {
|
||||||
pub fn new(uri: Uri) -> Url {
|
pub fn new(uri: Uri) -> Url {
|
||||||
let path = DEFAULT_QUOTER.requote(uri.path().as_bytes());
|
let path = UNRESERVED_QUOTER.requote(uri.path().as_bytes());
|
||||||
|
|
||||||
Url { uri, path }
|
Url { uri, path }
|
||||||
}
|
}
|
||||||
|
@ -63,36 +51,19 @@ impl Url {
|
||||||
|
|
||||||
pub(crate) struct Quoter {
|
pub(crate) struct Quoter {
|
||||||
safe_table: [u8; 16],
|
safe_table: [u8; 16],
|
||||||
protected_table: [u8; 16],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Quoter {
|
impl Quoter {
|
||||||
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
|
pub fn new(safe: &[u8]) -> Quoter {
|
||||||
let mut q = Quoter {
|
let mut q = Quoter {
|
||||||
safe_table: [0; 16],
|
safe_table: [0; 16],
|
||||||
protected_table: [0; 16],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// prepare safe table
|
// prepare safe table
|
||||||
for i in 0..128 {
|
|
||||||
if ALLOWED.contains(&i) {
|
|
||||||
set_bit(&mut q.safe_table, i);
|
|
||||||
}
|
|
||||||
if QS.contains(&i) {
|
|
||||||
set_bit(&mut q.safe_table, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ch in safe {
|
for ch in safe {
|
||||||
set_bit(&mut q.safe_table, *ch)
|
set_bit(&mut q.safe_table, *ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare protected table
|
|
||||||
for ch in protected {
|
|
||||||
set_bit(&mut q.safe_table, *ch);
|
|
||||||
set_bit(&mut q.protected_table, *ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
q
|
q
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,19 +86,17 @@ impl Quoter {
|
||||||
|
|
||||||
if let Some(ch) = restore_ch(pct[1], pct[2]) {
|
if let Some(ch) = restore_ch(pct[1], pct[2]) {
|
||||||
if ch < 128 {
|
if ch < 128 {
|
||||||
if bit_at(&self.protected_table, ch) {
|
|
||||||
buf.extend_from_slice(&pct);
|
|
||||||
idx += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if bit_at(&self.safe_table, ch) {
|
if bit_at(&self.safe_table, ch) {
|
||||||
buf.push(ch);
|
buf.push(ch);
|
||||||
idx += 1;
|
idx += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf.extend_from_slice(&pct);
|
||||||
|
} else {
|
||||||
|
// Not ASCII, decode it
|
||||||
|
buf.push(ch);
|
||||||
}
|
}
|
||||||
buf.push(ch);
|
|
||||||
} else {
|
} else {
|
||||||
buf.extend_from_slice(&pct[..]);
|
buf.extend_from_slice(&pct[..]);
|
||||||
}
|
}
|
||||||
|
@ -172,3 +141,37 @@ fn from_hex(v: u8) -> Option<u8> {
|
||||||
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
|
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
|
||||||
from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2)))
|
from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_path() {
|
||||||
|
assert_eq!(UNRESERVED_QUOTER.requote(b"https://localhost:80/foo"), None);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
|
||||||
|
b"https://localhost:80/foo%25"
|
||||||
|
).unwrap()).unwrap(),
|
||||||
|
"https://localhost:80/foo%25".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
|
||||||
|
b"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo"
|
||||||
|
).unwrap()).unwrap(),
|
||||||
|
"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
|
||||||
|
b"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A"
|
||||||
|
).unwrap()).unwrap(),
|
||||||
|
"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,7 @@ where
|
||||||
|
|
||||||
match fut.poll() {
|
match fut.poll() {
|
||||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
Ok(Async::NotReady) => AsyncResult::future(Box::new(fut)),
|
||||||
Err(e) => AsyncResult::err(e),
|
Err(e) => AsyncResult::err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ where
|
||||||
|
|
||||||
match fut.poll() {
|
match fut.poll() {
|
||||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
Ok(Async::NotReady) => AsyncResult::future(Box::new(fut)),
|
||||||
Err(e) => AsyncResult::err(e),
|
Err(e) => AsyncResult::err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,6 +231,13 @@ where
|
||||||
pub fn handle(&self) -> SpawnHandle {
|
pub fn handle(&self) -> SpawnHandle {
|
||||||
self.inner.curr_handle()
|
self.inner.curr_handle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set mailbox capacity
|
||||||
|
///
|
||||||
|
/// By default mailbox capacity is 16 messages.
|
||||||
|
pub fn set_mailbox_capacity(&mut self, cap: usize) {
|
||||||
|
self.inner.set_mailbox_capacity(cap)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, S> WsWriter for WebsocketContext<A, S>
|
impl<A, S> WsWriter for WebsocketContext<A, S>
|
||||||
|
|
|
@ -46,7 +46,7 @@ impl Frame {
|
||||||
Frame::message(payload, OpCode::Close, true, genmask)
|
Frame::message(payload, OpCode::Close, true, genmask)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
|
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
|
||||||
fn read_copy_md<S>(
|
fn read_copy_md<S>(
|
||||||
pl: &mut PayloadBuffer<S>, server: bool, max_size: usize,
|
pl: &mut PayloadBuffer<S>, server: bool, max_size: usize,
|
||||||
) -> Poll<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError>
|
) -> Poll<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs)
|
//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs)
|
||||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
|
#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))]
|
||||||
use std::ptr::copy_nonoverlapping;
|
use std::ptr::copy_nonoverlapping;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ impl<'a> ShortSlice<'a> {
|
||||||
|
|
||||||
/// Faster version of `apply_mask()` which operates on 8-byte blocks.
|
/// Faster version of `apply_mask()` which operates on 8-byte blocks.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))]
|
#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))]
|
||||||
pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
|
pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
|
||||||
// Extend the mask to 64 bits
|
// Extend the mask to 64 bits
|
||||||
let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64);
|
let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64);
|
||||||
|
@ -52,7 +52,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) {
|
||||||
// a `ShortSlice` must be smaller than a u64.
|
// a `ShortSlice` must be smaller than a u64.
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "cargo-clippy",
|
feature = "cargo-clippy",
|
||||||
allow(clippy::needless_pass_by_value)
|
allow(needless_pass_by_value)
|
||||||
)]
|
)]
|
||||||
fn xor_short(buf: ShortSlice, mask: u64) {
|
fn xor_short(buf: ShortSlice, mask: u64) {
|
||||||
// Unsafe: we know that a `ShortSlice` fits in a u64
|
// Unsafe: we know that a `ShortSlice` fits in a u64
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # extern crate actix_web;
|
//! # extern crate actix_web;
|
||||||
//! # use actix_web::actix::*;
|
//! # extern crate actix;
|
||||||
|
//! # use actix::prelude::*;
|
||||||
//! # use actix_web::*;
|
//! # use actix_web::*;
|
||||||
//! use actix_web::{ws, HttpRequest, HttpResponse};
|
//! use actix_web::{ws, HttpRequest, HttpResponse};
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
ÂTǑɂVù2þvI ª–\ÇRË™–ˆæeÞ<04>vDØ:è—½¬RVÖYpíÿ;ÍÏGñùp!2÷CŒ.–<0C>û®õpA!ûߦÙx j+Uc÷±©X”c%Û;ï"yìAI
|
|
@ -179,7 +179,7 @@ fn test_client_gzip_encoding_large() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_client_gzip_encoding_large_random() {
|
fn test_client_gzip_encoding_large_random() {
|
||||||
let data = rand::thread_rng()
|
let data = rand::thread_rng()
|
||||||
.gen_ascii_chars()
|
.sample_iter(&rand::distributions::Alphanumeric)
|
||||||
.take(100_000)
|
.take(100_000)
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ fn test_client_brotli_encoding() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_client_brotli_encoding_large_random() {
|
fn test_client_brotli_encoding_large_random() {
|
||||||
let data = rand::thread_rng()
|
let data = rand::thread_rng()
|
||||||
.gen_ascii_chars()
|
.sample_iter(&rand::distributions::Alphanumeric)
|
||||||
.take(70_000)
|
.take(70_000)
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
|
@ -309,7 +309,7 @@ fn test_client_deflate_encoding() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_client_deflate_encoding_large_random() {
|
fn test_client_deflate_encoding_large_random() {
|
||||||
let data = rand::thread_rng()
|
let data = rand::thread_rng()
|
||||||
.gen_ascii_chars()
|
.sample_iter(&rand::distributions::Alphanumeric)
|
||||||
.take(70_000)
|
.take(70_000)
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
|
|
|
@ -672,6 +672,6 @@ fn test_unsafe_path_route() {
|
||||||
let bytes = srv.execute(response.body()).unwrap();
|
let bytes = srv.execute(response.body()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bytes,
|
bytes,
|
||||||
Bytes::from_static(b"success: http:%2F%2Fexample.com")
|
Bytes::from_static(b"success: http%3A%2F%2Fexample.com")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -385,10 +385,11 @@ fn test_ws_stopped() {
|
||||||
{
|
{
|
||||||
let (reader, mut writer) = srv.ws().unwrap();
|
let (reader, mut writer) = srv.ws().unwrap();
|
||||||
writer.text("text");
|
writer.text("text");
|
||||||
|
writer.close(None);
|
||||||
let (item, _) = srv.execute(reader.into_future()).unwrap();
|
let (item, _) = srv.execute(reader.into_future()).unwrap();
|
||||||
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
|
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
|
||||||
}
|
}
|
||||||
thread::sleep(time::Duration::from_millis(1000));
|
thread::sleep(time::Duration::from_millis(100));
|
||||||
|
|
||||||
assert_eq!(num.load(Ordering::Relaxed), 1);
|
assert_eq!(num.load(Ordering::Relaxed), 1);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue