1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-08-10 00:26:06 +02:00

Compare commits

...

29 Commits

Author SHA1 Message Date
Douman
46db09428c Prepare release 0.7.16 2018-12-11 21:04:05 +03:00
ethanpailes
90eef31cc0 impl ResponseError for SendError when possible (#619) 2018-12-11 19:37:52 +03:00
Akos Vandra
86af02156b add impl FromRequest for Either<A,B> (#618) 2018-12-10 19:02:05 +03:00
Douman
ac9fc662c6 Bump version to 0.7.15 2018-12-05 18:27:06 +03:00
Douman
0745a1a9f8 Remove usage of upcoming keyword async
AsyncResult::async is replaced with AsyncResult::future
2018-12-05 18:23:04 +03:00
silwol
b1635bc0e6 Update some dependencies (#612)
* Update rand to 0.6

* Update parking_lot to 0.7

* Update env_logger to 0.6
2018-12-04 09:58:22 +03:00
Kelly Thomas Kline
08c7743bb8 Add set_mailbox_capacity() function 2018-12-02 08:40:09 +03:00
vemoo
68c5d6e6d6 impl From<Cow<'static, [u8]>> for Binary (#611)
impl `From` for `Cow<'static, [u8]>`  and `From<Cow<'static, str>>` for `Binary`
2018-12-02 08:32:55 +03:00
François
c386353337 decode reserved characters when extracting path with configuration (#577)
* decode reserved characters when extracting path with configuration

* remove useless clone

* add a method to get decoded parameter by name
2018-11-24 16:54:11 +03:00
Douman
9aab382ea8 Allow user to provide addr to custom resolver
We basically swaps Addr with Recipient to enable user to use custom resolver
2018-11-23 15:36:12 +03:00
Douman
389cb13cd6 Export PathConfig and QueryConfig
Closes #597
2018-11-20 23:06:38 +03:00
Huston Bokinsky
6a93178479 Complete error helper functions. 2018-11-20 08:07:46 +03:00
Nikolay Kim
cd9901c928 prepare release 2018-11-14 16:24:01 -08:00
Nikolay Kim
1ef0eed0bd do not stop on keep-alive timer if sink is not completly flushed 2018-11-08 20:46:13 -08:00
Nikolay Kim
61b1030882 Fix websockets connection drop if request contains content-length header #567 2018-11-08 20:35:47 -08:00
Nikolay Kim
7065c540e1 set nodelay on socket #560 2018-11-08 16:29:43 -08:00
Nikolay Kim
aed3933ae8 Merge branch 'master' of github.com:actix/actix-web 2018-11-08 16:15:45 -08:00
Nikolay Kim
5b7740dee3 hide ChunkedReadFile 2018-11-08 16:12:16 -08:00
imaperson
1a0bf32ec7 Fix unnecessary owned string and change htmlescape in favor of askama_escape (#584) 2018-11-08 16:08:06 -08:00
Nikolay Kim
9ab586e24e update actix-net dep 2018-11-08 16:06:23 -08:00
Nikolay Kim
62f1c90c8d update base64 dep 2018-11-07 21:18:40 -08:00
Nikolay Kim
2677d325a7 fix keep-alive timer reset 2018-11-07 21:09:33 -08:00
Julian Tescher
8e354021d4 Add SameSite option to identity middleware cookie (#581) 2018-11-07 23:24:06 +03:00
Stanislav Tkach
3b536ee96c Use old clippy attributes syntax (#562) 2018-11-01 11:14:48 +03:00
Nikolay Kim
cfd9a56ff7 Add async/await ref 2018-10-28 09:24:19 -07:00
Douman
5f91f5eda6 Correct IoStream::set_keepalive for UDS (#564)
Enable uds feature in tests
2018-10-26 10:59:06 +03:00
François
42d5d48e71 add a way to configure error treatment for Query and Path extractors (#550)
* add a way to configure error treatment for Query extractor

* allow error handler to be customized for Path extractor
2018-10-20 06:43:43 +03:00
Douman
960274ada8 Refactoring of server output to not exclude HTTP_10 (#552) 2018-10-19 07:52:10 +03:00
ivan-ochc
f383f618b5 Fix typo in error message (#554) 2018-10-18 21:27:31 +03:00
47 changed files with 1154 additions and 207 deletions

View File

@@ -35,7 +35,7 @@ script:
cargo check --features rust-tls
cargo check --features ssl
cargo check --features tls
cargo test --features="ssl,tls,rust-tls" -- --nocapture
cargo test --features="ssl,tls,rust-tls,uds" -- --nocapture
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then

View File

@@ -1,5 +1,53 @@
# Changes
## [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

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "0.7.13"
version = "0.7.16"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@@ -61,14 +61,14 @@ flate2-rust = ["flate2/rust_backend"]
cell = ["actix-net/cell"]
[dependencies]
actix = "^0.7.5"
actix-net = "^0.1.1"
actix = "0.7.9"
actix-net = "0.2.2"
base64 = "0.9"
askama_escape = "0.1.0"
base64 = "0.10"
bitflags = "1.0"
failure = "^0.1.2"
h2 = "0.1"
htmlescape = "0.3"
http = "^0.1.8"
httparse = "1.3"
log = "0.4"
@@ -76,7 +76,7 @@ mime = "0.3"
mime_guess = "2.0.0-alpha"
num_cpus = "1.0"
percent-encoding = "1.0"
rand = "0.5"
rand = "0.6"
regex = "1.0"
serde = "1.0"
serde_json = "1.0"
@@ -87,7 +87,7 @@ encoding = "0.2"
language-tags = "0.2"
lazy_static = "1.0"
lazycell = "1.0.0"
parking_lot = "0.6"
parking_lot = "0.7"
serde_urlencoded = "^0.5.3"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.11", features=["percent-encode"] }
@@ -127,7 +127,7 @@ webpki-roots = { version = "0.15", optional = true }
tokio-uds = { version="0.2", optional = true }
[dev-dependencies]
env_logger = "0.5"
env_logger = "0.6"
serde_derive = "1.0"
[build-dependencies]

View File

@@ -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
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple

View File

@@ -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/))
* 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)
* Experimental [Async/Await](https://github.com/mehcode/actix-web-async-await) support.
## Documentation & community resources

View File

@@ -1,5 +1,6 @@
use bytes::{Bytes, BytesMut};
use futures::Stream;
use std::borrow::Cow;
use std::sync::Arc;
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 {
fn from(s: String) -> Binary {
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 {
fn from(s: &'a String) -> Binary {
Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s)))
@@ -287,6 +306,16 @@ mod tests {
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]
fn test_static_bytes() {
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");
}
#[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]
fn test_arc_string() {
let b = Arc::new("test".to_owned());

View File

@@ -3,9 +3,9 @@ use std::net::Shutdown;
use std::time::{Duration, Instant};
use std::{fmt, io, mem, time};
use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError};
use actix::{
fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context,
use actix_inner::actors::resolver::{Connect as ResolveConnect, Resolver, ResolverError};
use actix_inner::{
fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context,
ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised,
SystemService, WrapFuture,
};
@@ -220,7 +220,7 @@ pub struct ClientConnector {
acq_tx: mpsc::UnboundedSender<AcquiredConnOperation>,
acq_rx: Option<mpsc::UnboundedReceiver<AcquiredConnOperation>>,
resolver: Option<Addr<Resolver>>,
resolver: Option<Recipient<ResolveConnect>>,
conn_lifetime: Duration,
conn_keep_alive: Duration,
limit: usize,
@@ -239,7 +239,7 @@ impl Actor for ClientConnector {
fn started(&mut self, ctx: &mut Self::Context) {
if self.resolver.is_none() {
self.resolver = Some(Resolver::from_registry())
self.resolver = Some(Resolver::from_registry().recipient())
}
self.collect_periodic(ctx);
ctx.add_stream(self.acq_rx.take().unwrap());
@@ -287,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)
}
}
@@ -503,8 +503,10 @@ impl ClientConnector {
}
/// 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
}
@@ -940,7 +942,7 @@ impl Handler<Connect> for ClientConnector {
}
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 {
host,
port,

View File

@@ -2,11 +2,12 @@
//!
//! ```rust
//! # extern crate actix_web;
//! # extern crate actix;
//! # extern crate futures;
//! # extern crate tokio;
//! # use futures::Future;
//! # use std::process;
//! use actix_web::{actix, client};
//! use actix_web::client;
//!
//! fn main() {
//! actix::run(
@@ -61,12 +62,13 @@ impl ResponseError for SendRequestError {
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate actix;
/// # extern crate futures;
/// # extern crate tokio;
/// # extern crate env_logger;
/// # use futures::Future;
/// # use std::process;
/// use actix_web::{actix, client};
/// use actix_web::client;
///
/// fn main() {
/// actix::run(

View File

@@ -56,7 +56,7 @@ impl HttpResponseParser {
return Ok(Async::Ready(msg));
}
Async::NotReady => {
if buf.capacity() >= MAX_BUFFER_SIZE {
if buf.len() >= MAX_BUFFER_SIZE {
return Err(HttpResponseParserError::Error(
ParseError::TooLarge,
));

View File

@@ -6,7 +6,8 @@ use std::time::{Duration, Instant};
use std::{io, mem};
use tokio_timer::Delay;
use actix::{Addr, Request, SystemService};
use actix_inner::dev::Request;
use actix::{Addr, SystemService};
use super::{
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,

View File

@@ -27,11 +27,12 @@ use httprequest::HttpRequest;
///
/// ```rust
/// # extern crate actix_web;
/// # extern crate actix;
/// # extern crate futures;
/// # extern crate tokio;
/// # use futures::Future;
/// # use std::process;
/// use actix_web::{actix, client};
/// use actix_web::client;
///
/// fn main() {
/// actix::run(
@@ -631,7 +632,7 @@ impl ClientRequestBuilder {
if !parts.headers.contains_key(header::HOST) {
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),
Some(port) => write!(wrt, "{}:{}", host, port),
};

View File

@@ -1,6 +1,6 @@
#![cfg_attr(
feature = "cargo-clippy",
allow(clippy::redundant_field_names)
allow(redundant_field_names)
)]
use std::cell::RefCell;

View File

@@ -1,7 +1,10 @@
use std::rc::Rc;
use serde::de::{self, Deserializer, Error as DeError, Visitor};
use httprequest::HttpRequest;
use param::ParamsIter;
use uri::RESERVED_QUOTER;
macro_rules! unsupported_type {
($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 {
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
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",
self.req.match_info().len()).as_str()))
} else {
let v = self.req.match_info()[0].parse().map_err(
|_| de::value::Error::custom(
format!("can not parse {:?} to a {}",
&self.req.match_info()[0], $tp)))?;
visitor.$visit_fn(v)
let v_parsed = percent_decode_if_needed!(&self.req.match_info()[0], self.decode)
.map_err(|_| de::value::Error::custom(
format!("can not parse {:?} to a {}", &self.req.match_info()[0], $tp)
))?;
visitor.$visit_fn(v_parsed)
}
}
}
@@ -35,11 +52,12 @@ macro_rules! parse_single_value {
pub struct PathDeserializer<'de, S: 'de> {
req: &'de HttpRequest<S>,
decode: bool,
}
impl<'de, S: 'de> PathDeserializer<'de, S> {
pub fn new(req: &'de HttpRequest<S>) -> Self {
PathDeserializer { req }
pub fn new(req: &'de HttpRequest<S>, decode: bool) -> Self {
PathDeserializer { req, decode }
}
}
@@ -53,6 +71,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
visitor.visit_map(ParamsDeserializer {
params: self.req.match_info().iter(),
current: None,
decode: self.decode,
})
}
@@ -107,6 +126,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
} else {
visitor.visit_seq(ParamsSeq {
params: self.req.match_info().iter(),
decode: self.decode,
})
}
}
@@ -128,6 +148,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
} else {
visitor.visit_seq(ParamsSeq {
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"))
}
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>
where
V: Visitor<'de>,
{
visitor.visit_seq(ParamsSeq {
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_f64, visit_f64, "f64");
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_char, visit_char, "char");
}
struct ParamsDeserializer<'de> {
params: ParamsIter<'de>,
current: Option<(&'de str, &'de str)>,
decode: bool,
}
impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
@@ -212,7 +221,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
V: de::DeserializeSeed<'de>,
{
if let Some((_, value)) = self.current.take() {
seed.deserialize(Value { value })
seed.deserialize(Value { value, decode: self.decode })
} else {
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>
where V: Visitor<'de>
{
let v = self.value.parse().map_err(
|_| de::value::Error::custom(
format!("can not parse {:?} to a {}", self.value, $tp)))?;
visitor.$visit_fn(v)
let v_parsed = percent_decode_if_needed!(&self.value, self.decode)
.map_err(|_| de::value::Error::custom(
format!("can not parse {:?} to a {}", &self.value, $tp)
))?;
visitor.$visit_fn(v_parsed)
}
}
}
struct Value<'de> {
value: &'de str,
decode: bool,
}
impl<'de> Deserializer<'de> for Value<'de> {
@@ -377,6 +388,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
struct ParamsSeq<'de> {
params: ParamsIter<'de>,
decode: bool,
}
impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
@@ -387,7 +399,7 @@ impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
T: de::DeserializeSeed<'de>,
{
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),
}
}

View File

@@ -5,7 +5,7 @@ use std::string::FromUtf8Error;
use std::sync::Mutex;
use std::{fmt, io, result};
use actix::MailboxError;
use actix::{MailboxError, SendError};
use cookie;
use failure::{self, Backtrace, Fail};
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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.cause, f)
@@ -759,6 +763,16 @@ where
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*
/// response.
#[allow(non_snake_case)]
@@ -789,6 +803,26 @@ where
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
/// TIMEOUT* response.
#[allow(non_snake_case)]
@@ -819,6 +853,16 @@ where
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
/// *PRECONDITION FAILED* response.
#[allow(non_snake_case)]
@@ -829,6 +873,46 @@ where
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
/// *EXPECTATION FAILED* response.
#[allow(non_snake_case)]
@@ -839,6 +923,106 @@ where
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
/// generate *INTERNAL SERVER ERROR* response.
#[allow(non_snake_case)]
@@ -889,6 +1073,66 @@ where
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)]
mod tests {
use super::*;
@@ -1068,6 +1312,9 @@ mod tests {
let r: HttpResponse = ErrorUnauthorized("err").into();
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();
assert_eq!(r.status(), StatusCode::FORBIDDEN);
@@ -1077,6 +1324,12 @@ mod tests {
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
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();
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
@@ -1086,12 +1339,57 @@ mod tests {
let r: HttpResponse = ErrorGone("err").into();
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();
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();
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();
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
@@ -1106,5 +1404,23 @@ mod tests {
let r: HttpResponse = ErrorGatewayTimeout("err").into();
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);
}
}

View File

@@ -12,13 +12,15 @@ use serde::de::{self, DeserializeOwned};
use serde_urlencoded;
use de::PathDeserializer;
use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError};
use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError, ErrorConflict};
use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest;
use Either;
#[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
///
@@ -111,18 +113,73 @@ impl<T, S> FromRequest<S> for Path<T>
where
T: DeserializeOwned,
{
type Config = ();
type Config = PathConfig<S>;
type Result = Result<Self, Error>;
#[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();
de::Deserialize::deserialize(PathDeserializer::new(&req))
.map_err(ErrorNotFound)
let req2 = req.clone();
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 })
}
}
/// 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> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
@@ -200,17 +257,69 @@ impl<T, S> FromRequest<S> for Query<T>
where
T: de::DeserializeOwned,
{
type Config = ();
type Config = QueryConfig<S>;
type Result = Result<Self, Error>;
#[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())
.map_err(|e| e.into())
.map_err(move |e| (*err)(e, &req2))
.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> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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
///
/// If the FromRequest for T fails, inject Err into handler rather than returning an error response
@@ -766,6 +1022,11 @@ mod tests {
hello: String,
}
#[derive(Deserialize, Debug, PartialEq)]
struct OtherInfo {
bye: String,
}
#[test]
fn test_bytes() {
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]
fn test_result() {
let req = TestRequest::with_header(
@@ -951,15 +1254,15 @@ mod tests {
let info = router.recognize(&req, &(), 0);
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.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.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");
let mut router = Router::<()>::default();
@@ -968,11 +1271,11 @@ mod tests {
let info = router.recognize(&req, &(), 0);
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.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.1, 32);
@@ -989,7 +1292,69 @@ mod tests {
let req = TestRequest::with_uri("/32/").finish();
let info = router.recognize(&req, &(), 0);
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]

View File

@@ -11,10 +11,10 @@ use std::{cmp, io};
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use askama_escape::{escape as escape_html_entity};
use bytes::Bytes;
use futures::{Async, Future, Poll, Stream};
use futures_cpupool::{CpuFuture, CpuPool};
use htmlescape::encode_minimal as escape_html_entity;
use mime;
use mime_guess::{get_mime_type, guess_mime_type};
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
@@ -472,6 +472,7 @@ impl<C: StaticFileConfig> Responder for NamedFile<C> {
}
}
#[doc(hidden)]
/// A helper created from a `std::fs::File` which reads the file
/// chunk-by-chunk on a `CpuPool`.
pub struct ChunkedReadFile {
@@ -561,8 +562,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)
};
}
// " -- &quot; & -- &amp; ' -- &#x27; < -- &lt; > -- &gt; / -- &#x2f;
macro_rules! encode_file_name {
($entry:ident) => {
escape_html_entity(&$entry.file_name().to_string_lossy())
};
}
fn directory_listing<S>(
dir: &Directory, req: &HttpRequest<S>,
dir: &Directory,
req: &HttpRequest<S>,
) -> Result<HttpResponse, io::Error> {
let index_of = format!("Index of {}", req.path());
let mut body = String::new();
@@ -575,11 +591,6 @@ fn directory_listing<S>(
Ok(p) => base.join(p),
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();
// " -- &quot; & -- &amp; ' -- &#x27; < -- &lt; > -- &gt;
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 let Ok(metadata) = entry.metadata() {
@@ -587,13 +598,15 @@ fn directory_listing<S>(
let _ = write!(
body,
"<li><a href=\"{}\">{}/</a></li>",
file_url, file_name
encode_file_url!(p),
encode_file_name!(entry),
);
} else {
let _ = write!(
body,
"<li><a href=\"{}\">{}</a></li>",
file_url, file_name
encode_file_url!(p),
encode_file_name!(entry),
);
}
} else {
@@ -656,7 +669,8 @@ impl<S: 'static> StaticFiles<S> {
/// Create new `StaticFiles` instance for specified base directory and
/// `CpuPool`.
pub fn with_pool<T: Into<PathBuf>>(
dir: T, pool: CpuPool,
dir: T,
pool: CpuPool,
) -> Result<StaticFiles<S>, Error> {
Self::with_config_pool(dir, pool, DefaultConfig)
}
@@ -667,7 +681,8 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
///
/// Identical with `new` but allows to specify configiration to use.
pub fn with_config<T: Into<PathBuf>>(
dir: T, config: C,
dir: T,
config: C,
) -> Result<StaticFiles<S, C>, Error> {
// use default CpuPool
let pool = { DEFAULT_CPUPOOL.lock().clone() };
@@ -678,7 +693,9 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
/// Create new `StaticFiles` instance for specified base directory with config and
/// `CpuPool`.
pub fn with_config_pool<T: Into<PathBuf>>(
dir: T, pool: CpuPool, _: C,
dir: T,
pool: CpuPool,
_: C,
) -> Result<StaticFiles<S, C>, Error> {
let dir = dir.into().canonicalize()?;
@@ -736,7 +753,8 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
}
fn try_handle(
&self, req: &HttpRequest<S>,
&self,
req: &HttpRequest<S>,
) -> Result<AsyncResult<HttpResponse>, Error> {
let tail: String = req.match_info().query("tail")?;
let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?;

View File

@@ -86,7 +86,7 @@ pub trait FromRequest<S>: Sized {
/// # fn is_a_variant() -> bool { true }
/// # fn main() {}
/// ```
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum Either<A, B> {
/// First branch of the type
A(A),
@@ -250,7 +250,7 @@ pub(crate) enum AsyncResultItem<I, E> {
impl<I, E> AsyncResult<I, E> {
/// Create async response
#[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)))
}
@@ -401,7 +401,7 @@ where
},
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)),
}
});
AsyncResult::async(Box::new(fut))
AsyncResult::future(Box::new(fut))
}
}

View File

@@ -213,9 +213,10 @@ pub trait HttpMessage: Sized {
/// # extern crate actix_web;
/// # extern crate env_logger;
/// # extern crate futures;
/// # extern crate actix;
/// # use std::str;
/// # use actix_web::*;
/// # use actix_web::actix::fut::FinishStream;
/// # use actix::FinishStream;
/// # use futures::{Future, Stream};
/// # use futures::future::{ok, result, Either};
/// fn index(mut req: HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {

View File

@@ -694,7 +694,7 @@ impl HttpResponseBuilder {
}
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(clippy::borrowed_box))]
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
fn parts<'a>(
parts: &'a mut Option<Box<InnerHttpResponse>>, err: &Option<HttpError>,
) -> Option<&'a mut Box<InnerHttpResponse>> {

View File

@@ -18,7 +18,7 @@ impl ConnectionInfo {
/// Create *ConnectionInfo* instance for a request.
#[cfg_attr(
feature = "cargo-clippy",
allow(clippy::cyclomatic_complexity)
allow(cyclomatic_complexity)
)]
pub fn update(&mut self, req: &Request) {
let mut host = None;

View File

@@ -100,9 +100,9 @@ extern crate failure;
extern crate lazy_static;
#[macro_use]
extern crate futures;
extern crate askama_escape;
extern crate cookie;
extern crate futures_cpupool;
extern crate htmlescape;
extern crate http as modhttp;
extern crate httparse;
extern crate language_tags;
@@ -217,14 +217,12 @@ pub use server::Request;
pub mod actix {
//! Re-exports [actix's](https://docs.rs/actix/) prelude
extern crate actix;
pub use self::actix::actors::resolver;
pub use self::actix::actors::signal;
pub use self::actix::fut;
pub use self::actix::msgs;
pub use self::actix::prelude::*;
pub use self::actix::{run, spawn};
pub use super::actix_inner::actors::resolver;
pub use super::actix_inner::actors::signal;
pub use super::actix_inner::fut;
pub use super::actix_inner::msgs;
pub use super::actix_inner::prelude::*;
pub use super::actix_inner::{run, spawn};
}
#[cfg(feature = "openssl")]
@@ -255,7 +253,7 @@ pub mod dev {
pub use body::BodyStream;
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 httpmessage::{MessageBody, Readlines, UrlEncoded};
pub use httpresponse::HttpResponseBuilder;

View File

@@ -76,7 +76,7 @@ impl ResponseError for CsrfError {
}
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(format!("{}://{}:{}", scheme, host, port))
}

View File

@@ -48,7 +48,7 @@ impl DefaultHeaders {
/// Set a header.
#[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
where
HeaderName: HttpTryFrom<K>,

View File

@@ -48,7 +48,7 @@
//! ```
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;
use time::Duration;
@@ -237,6 +237,7 @@ struct CookieIdentityInner {
domain: Option<String>,
secure: bool,
max_age: Option<Duration>,
same_site: Option<SameSite>,
}
impl CookieIdentityInner {
@@ -248,6 +249,7 @@ impl CookieIdentityInner {
domain: None,
secure: true,
max_age: None,
same_site: None,
}
}
@@ -268,6 +270,10 @@ impl CookieIdentityInner {
cookie.set_max_age(max_age);
}
if let Some(same_site) = self.same_site {
cookie.set_same_site(same_site);
}
let mut jar = CookieJar::new();
if some {
jar.private(&self.key).add(cookie);
@@ -370,6 +376,12 @@ impl CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
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 {

View File

@@ -33,7 +33,8 @@
//!
//! ```rust
//! # 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};
//!
//! fn index(req: HttpRequest) -> Result<&'static str> {

View File

@@ -8,7 +8,7 @@ use http::StatusCode;
use smallvec::SmallVec;
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
/// 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
pub fn unprocessed(&self) -> &str {
&self.url.path()[(self.tail as usize)..]
@@ -300,4 +311,24 @@ mod tests {
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())
);
}
}

View File

@@ -551,12 +551,12 @@ impl<S: 'static, H> ProcessResponse<S, H> {
if self.resp.as_ref().unwrap().status().is_server_error()
{
error!(
"Error occured during request handling, status: {} {}",
"Error occurred during request handling, status: {} {}",
self.resp.as_ref().unwrap().status(), err
);
} else {
warn!(
"Error occured during request handling: {}",
"Error occurred during request handling: {}",
err
);
}

View File

@@ -57,7 +57,7 @@ impl<S: 'static> Route<S> {
pub(crate) fn compose(
&self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
) -> 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.

View File

@@ -61,7 +61,7 @@ pub struct Scope<S> {
#[cfg_attr(
feature = "cargo-clippy",
allow(clippy::new_without_default_derive)
allow(new_without_default_derive)
)]
impl<S: 'static> Scope<S> {
/// Create a new scope
@@ -356,7 +356,7 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
if self.middlewares.is_empty() {
self.router.handle(&req2)
} else {
AsyncResult::async(Box::new(Compose::new(
AsyncResult::future(Box::new(Compose::new(
req2,
Rc::clone(&self.router),
Rc::clone(&self.middlewares),

View File

@@ -9,14 +9,20 @@ use super::acceptor::{
};
use super::error::AcceptorError;
use super::handler::IntoHttpHandler;
use super::service::HttpService;
use super::service::{HttpService, StreamConfiguration};
use super::settings::{ServerSettings, ServiceConfig};
use super::KeepAlive;
pub(crate) trait ServiceProvider {
fn register(
&self, server: Server, lst: net::TcpListener, host: String,
addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64,
&self,
server: Server,
lst: net::TcpListener,
host: String,
addr: net::SocketAddr,
keep_alive: KeepAlive,
secure: bool,
client_timeout: u64,
client_shutdown: u64,
) -> Server;
}
@@ -43,8 +49,13 @@ where
}
fn finish(
&self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool,
client_timeout: u64, client_shutdown: u64,
&self,
host: String,
addr: net::SocketAddr,
keep_alive: KeepAlive,
secure: bool,
client_timeout: u64,
client_shutdown: u64,
) -> impl ServiceFactory {
let factory = self.factory.clone();
let acceptor = self.acceptor.clone();
@@ -65,6 +76,7 @@ where
acceptor.create(),
)).map_err(|_| ())
.map_init_err(|_| ())
.and_then(StreamConfiguration::new().nodelay(true))
.and_then(
HttpService::new(settings)
.map_init_err(|_| ())
@@ -76,6 +88,7 @@ where
TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service))
.map_err(|_| ())
.map_init_err(|_| ())
.and_then(StreamConfiguration::new().nodelay(true))
.and_then(
HttpService::new(settings)
.map_init_err(|_| ())
@@ -95,8 +108,14 @@ where
H: IntoHttpHandler,
{
fn register(
&self, server: Server, lst: net::TcpListener, host: String,
addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64,
&self,
server: Server,
lst: net::TcpListener,
host: String,
addr: net::SocketAddr,
keep_alive: KeepAlive,
secure: bool,
client_timeout: u64,
client_shutdown: u64,
) -> Server {
server.listen2(

View File

@@ -87,7 +87,10 @@ where
H: HttpHandler + 'static,
{
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>,
) -> Self {
let addr = stream.peer_addr();
@@ -123,8 +126,11 @@ where
}
pub(crate) fn for_error(
settings: ServiceConfig<H>, stream: T, status: StatusCode,
mut keepalive_timer: Option<Delay>, buf: BytesMut,
settings: ServiceConfig<H>,
stream: T,
status: StatusCode,
mut keepalive_timer: Option<Delay>,
buf: BytesMut,
) -> Self {
if let Some(deadline) = settings.client_timer_expire() {
let _ = keepalive_timer.as_mut().map(|delay| delay.reset(deadline));
@@ -280,7 +286,7 @@ where
}
if timer.deadline() >= self.ka_expire {
// 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) {
// timeout on first request (slow request) return 408
trace!("Slow request timeout");
@@ -298,16 +304,19 @@ where
if let Some(deadline) =
self.settings.client_shutdown_timer()
{
timer.reset(deadline)
timer.reset(deadline);
let _ = timer.poll();
} else {
return Ok(());
}
}
} else if let Some(dl) = self.settings.keep_alive_expire() {
timer.reset(dl)
timer.reset(dl);
let _ = timer.poll();
}
} else {
timer.reset(self.ka_expire)
timer.reset(self.ka_expire);
let _ = timer.poll();
}
}
Ok(Async::NotReady) => (),

View File

@@ -43,7 +43,9 @@ impl H1Decoder {
}
pub fn decode<H>(
&mut self, src: &mut BytesMut, settings: &ServiceConfig<H>,
&mut self,
src: &mut BytesMut,
settings: &ServiceConfig<H>,
) -> Result<Option<Message>, DecoderError> {
// read payload
if self.decoder.is_some() {
@@ -80,7 +82,9 @@ impl H1Decoder {
}
fn parse_message<H>(
&self, buf: &mut BytesMut, settings: &ServiceConfig<H>,
&self,
buf: &mut BytesMut,
settings: &ServiceConfig<H>,
) -> Poll<(Request, Option<EncodingDecoder>), ParseError> {
// Parse http message
let mut has_upgrade = false;
@@ -178,6 +182,13 @@ impl H1Decoder {
}
header::UPGRADE => {
has_upgrade = true;
// check content-length, some clients (dart)
// sends "content-length: 0" with websocket upgrade
if let Ok(val) = value.to_str() {
if val == "websocket" {
content_length = None;
}
}
}
_ => (),
}
@@ -221,7 +232,9 @@ pub(crate) struct HeaderIndex {
impl HeaderIndex {
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;
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
@@ -369,7 +382,10 @@ macro_rules! byte (
impl ChunkedState {
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> {
use self::ChunkedState::*;
match *self {
@@ -432,7 +448,8 @@ impl ChunkedState {
}
}
fn read_size_lf(
rdr: &mut BytesMut, size: &mut u64,
rdr: &mut BytesMut,
size: &mut u64,
) -> Poll<ChunkedState, io::Error> {
match byte!(rdr) {
b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)),
@@ -445,7 +462,9 @@ impl ChunkedState {
}
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> {
trace!("Chunked read, remaining={:?}", rem);

View File

@@ -60,7 +60,10 @@ where
H: HttpHandler + 'static,
{
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 {
let addr = io.peer_addr();
let extensions = io.extensions();
@@ -284,10 +287,12 @@ where
if self.tasks.is_empty() {
return Err(HttpDispatchError::ShutdownTimeout);
} else if let Some(dl) = self.settings.keep_alive_expire() {
timer.reset(dl)
timer.reset(dl);
let _ = timer.poll();
}
} else {
timer.reset(self.ka_expire)
timer.reset(self.ka_expire);
let _ = timer.poll();
}
}
Ok(Async::NotReady) => (),
@@ -348,8 +353,11 @@ struct Entry<H: HttpHandler + 'static> {
impl<H: HttpHandler + 'static> Entry<H> {
fn new(
parts: Parts, recv: RecvStream, resp: SendResponse<Bytes>,
addr: Option<SocketAddr>, settings: ServiceConfig<H>,
parts: Parts,
recv: RecvStream,
resp: SendResponse<Bytes>,
addr: Option<SocketAddr>,
settings: ServiceConfig<H>,
extensions: Option<Rc<Extensions>>,
) -> Entry<H>
where

View File

@@ -1,6 +1,6 @@
#![cfg_attr(
feature = "cargo-clippy",
allow(clippy::redundant_field_names)
allow(redundant_field_names)
)]
use std::{cmp, io};

View File

@@ -326,7 +326,7 @@ where
#[doc(hidden)]
#[cfg_attr(
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>
where
@@ -457,7 +457,8 @@ impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
///
/// ```rust
/// extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse};
/// extern crate actix;
/// use actix_web::{server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system

View File

@@ -166,7 +166,8 @@ const HW_BUFFER_SIZE: usize = 32_768;
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{actix, server, App, HttpResponse};
/// # extern crate actix;
/// use actix_web::{server, App, HttpResponse};
///
/// fn main() {
/// let sys = actix::System::new("example"); // <- create Actix system
@@ -334,7 +335,7 @@ impl IoStream for ::tokio_uds::UnixStream {
}
#[inline]
fn set_keepalive(&mut self, _nodelay: bool) -> io::Result<()> {
fn set_keepalive(&mut self, _dur: Option<time::Duration>) -> io::Result<()> {
Ok(())
}
}

View File

@@ -300,10 +300,10 @@ impl Output {
Some(true) => {
// Enable transfer encoding
info.length = ResponseLength::Chunked;
if version == Version::HTTP_11 {
TransferEncoding::chunked(buf)
} else {
if version == Version::HTTP_2 {
TransferEncoding::eof(buf)
} else {
TransferEncoding::chunked(buf)
}
}
Some(false) => TransferEncoding::eof(buf),
@@ -337,10 +337,10 @@ impl Output {
} else {
// Enable transfer encoding
info.length = ResponseLength::Chunked;
if version == Version::HTTP_11 {
TransferEncoding::chunked(buf)
} else {
if version == Version::HTTP_2 {
TransferEncoding::eof(buf)
} else {
TransferEncoding::chunked(buf)
}
}
}
@@ -438,7 +438,7 @@ impl ContentEncoder {
}
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write_eof(&mut self) -> Result<bool, io::Error> {
let encoder =
@@ -480,7 +480,7 @@ impl ContentEncoder {
}
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
match *self {

View File

@@ -4,7 +4,7 @@ use std::str::FromStr;
use std::sync::mpsc;
use std::{net, thread};
use actix_inner::{Actor, Addr, System};
use actix::{Actor, Addr, System};
use cookie::Cookie;
use futures::Future;

View File

@@ -1,25 +1,12 @@
use http::Uri;
use std::rc::Rc;
#[allow(dead_code)]
const GEN_DELIMS: &[u8] = b":/?#[]@";
#[allow(dead_code)]
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
#[allow(dead_code)]
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
#[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";
// https://tools.ietf.org/html/rfc3986#section-2.2
const RESERVED_PLUS_EXTRA: &[u8] = b":/?#[]@!$&'()*,+?;=%^ <>\"\\`{}|";
// https://tools.ietf.org/html/rfc3986#section-2.3
const UNRESERVED: &[u8] =
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-._~";
#[inline]
fn bit_at(array: &[u8], ch: u8) -> bool {
@@ -32,7 +19,8 @@ fn set_bit(array: &mut [u8], ch: u8) {
}
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)]
@@ -43,7 +31,7 @@ pub(crate) struct Url {
impl 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 }
}
@@ -63,36 +51,19 @@ impl Url {
pub(crate) struct Quoter {
safe_table: [u8; 16],
protected_table: [u8; 16],
}
impl Quoter {
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
pub fn new(safe: &[u8]) -> Quoter {
let mut q = Quoter {
safe_table: [0; 16],
protected_table: [0; 16],
};
// 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 {
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
}
@@ -115,19 +86,17 @@ impl Quoter {
if let Some(ch) = restore_ch(pct[1], pct[2]) {
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) {
buf.push(ch);
idx += 1;
continue;
}
buf.extend_from_slice(&pct);
} else {
// Not ASCII, decode it
buf.push(ch);
}
buf.push(ch);
} else {
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> {
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()
);
}
}

View File

@@ -86,7 +86,7 @@ where
match fut.poll() {
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),
}
}
@@ -208,7 +208,7 @@ where
match fut.poll() {
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),
}
}

View File

@@ -231,6 +231,13 @@ where
pub fn handle(&self) -> SpawnHandle {
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>

View File

@@ -46,7 +46,7 @@ impl Frame {
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>(
pl: &mut PayloadBuffer<S>, server: bool, max_size: usize,
) -> Poll<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError>

View File

@@ -1,5 +1,5 @@
//! 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::slice;
@@ -19,7 +19,7 @@ impl<'a> ShortSlice<'a> {
/// Faster version of `apply_mask()` which operates on 8-byte blocks.
#[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) {
// Extend the mask to 64 bits
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.
#[cfg_attr(
feature = "cargo-clippy",
allow(clippy::needless_pass_by_value)
allow(needless_pass_by_value)
)]
fn xor_short(buf: ShortSlice, mask: u64) {
// Unsafe: we know that a `ShortSlice` fits in a u64

View File

@@ -8,7 +8,8 @@
//!
//! ```rust
//! # extern crate actix_web;
//! # use actix_web::actix::*;
//! # extern crate actix;
//! # use actix::prelude::*;
//! # use actix_web::*;
//! use actix_web::{ws, HttpRequest, HttpResponse};
//!

View File

@@ -179,7 +179,7 @@ fn test_client_gzip_encoding_large() {
#[test]
fn test_client_gzip_encoding_large_random() {
let data = rand::thread_rng()
.gen_ascii_chars()
.sample_iter(&rand::distributions::Alphanumeric)
.take(100_000)
.collect::<String>();
@@ -247,7 +247,7 @@ fn test_client_brotli_encoding() {
#[test]
fn test_client_brotli_encoding_large_random() {
let data = rand::thread_rng()
.gen_ascii_chars()
.sample_iter(&rand::distributions::Alphanumeric)
.take(70_000)
.collect::<String>();
@@ -309,7 +309,7 @@ fn test_client_deflate_encoding() {
#[test]
fn test_client_deflate_encoding_large_random() {
let data = rand::thread_rng()
.gen_ascii_chars()
.sample_iter(&rand::distributions::Alphanumeric)
.take(70_000)
.collect::<String>();

View File

@@ -672,6 +672,6 @@ fn test_unsafe_path_route() {
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(
bytes,
Bytes::from_static(b"success: http:%2F%2Fexample.com")
Bytes::from_static(b"success: http%3A%2F%2Fexample.com")
);
}

View File

@@ -385,10 +385,11 @@ fn test_ws_stopped() {
{
let (reader, mut writer) = srv.ws().unwrap();
writer.text("text");
writer.close(None);
let (item, _) = srv.execute(reader.into_future()).unwrap();
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);
}