Repository: actix/actix-web
Pull request #590 (2018-11-16 03:26:07)

Add set_mailbox_capacity() function


Patch:
diff --git a/CHANGES.md b/CHANGES.md
index efeaadf0..902a84f6 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,19 @@
 # Changes
 
+## [0.7.15] - 2018-xx-xx
+
+## Changed
+
+* `ClientConnector::resolver` now accepts `Into<Recipient>` instead of `Addr`. It enables user to implement own resolver.
+
+* `QueryConfig` and `PathConfig` are made public.
+
+### 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
@@ -8,6 +22,9 @@
 
 * 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
 
diff --git a/Cargo.toml b/Cargo.toml
index 41f2e667..e3fbd4e3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,7 +61,7 @@ flate2-rust = ["flate2/rust_backend"]
 cell = ["actix-net/cell"]
 
 [dependencies]
-actix = "0.7.6"
+actix = "0.7.7"
 actix-net = "0.2.2"
 
 askama_escape = "0.1.0"
diff --git a/MIGRATION.md b/MIGRATION.md
index 3c0bdd94..26a31424 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -1,3 +1,31 @@
+## 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);
+         });
+    }
+    ```
+
+
 ## 0.7.4
 
 * `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
diff --git a/src/body.rs b/src/body.rs
index a93db1e9..5487dbba 100644
--- a/src/body.rs
+++ b/src/body.rs
@@ -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());
diff --git a/src/client/connector.rs b/src/client/connector.rs
index 3990c955..72132bc6 100644
--- a/src/client/connector.rs
+++ b/src/client/connector.rs
@@ -5,7 +5,7 @@ use std::{fmt, io, mem, time};
 
 use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError};
 use actix::{
-    fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context,
+    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());
@@ -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
     }
 
diff --git a/src/de.rs b/src/de.rs
index 59ab79ba..05f8914f 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -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),
         }
     }
diff --git a/src/error.rs b/src/error.rs
index 76c8e79e..1766c152 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -759,6 +759,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 +799,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 +849,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 +869,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 +919,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 +1069,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 +1308,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 +1320,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 +1335,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 +1400,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);
     }
 }
diff --git a/src/extractor.rs b/src/extractor.rs
index 45e29ace..717e0f6c 100644
--- a/src/extractor.rs
+++ b/src/extractor.rs
@@ -18,7 +18,8 @@ use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
 use httprequest::HttpRequest;
 
 #[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
 ///
@@ -119,7 +120,7 @@ where
         let req = req.clone();
         let req2 = req.clone();
         let err = Rc::clone(&cfg.ehandler);
-        de::Deserialize::deserialize(PathDeserializer::new(&req))
+        de::Deserialize::deserialize(PathDeserializer::new(&req, cfg.decode))
             .map_err(move |e| (*err)(e, &req2))
             .map(|inner| Path { inner })
     }
@@ -149,6 +150,7 @@ where
 /// ```
 pub struct PathConfig<S> {
     ehandler: Rc<Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error>,
+    decode: bool,
 }
 impl<S> PathConfig<S> {
     /// Set custom error handler
@@ -159,12 +161,20 @@ impl<S> PathConfig<S> {
         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,
         }
     }
 }
@@ -1090,6 +1100,68 @@ mod tests {
         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]
     fn test_tuple_extract() {
         let mut router = Router::<()>::default();
diff --git a/src/lib.rs b/src/lib.rs
index 738153fa..f8326886 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -255,7 +255,7 @@ pub mod dev {
 
     pub use body::BodyStream;
     pub use context::Drain;
-    pub use extractor::{FormConfig, PayloadConfig};
+    pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig};
     pub use handler::{AsyncResult, Handler};
     pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
     pub use httpresponse::HttpResponseBuilder;
diff --git a/src/param.rs b/src/param.rs
index d0664df9..a3f60259 100644
--- a/src/param.rs
+++ b/src/param.rs
@@ -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())
+        );
+    }
 }
diff --git a/src/uri.rs b/src/uri.rs
index 881cf20a..c87cb3d5 100644
--- a/src/uri.rs
+++ b/src/uri.rs
@@ -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()
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/ws/context.rs b/src/ws/context.rs
index 4db83df5..5e207d43 100644
--- a/src/ws/context.rs
+++ b/src/ws/context.rs
@@ -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>


Test patch:
diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs
index 3ea709c9..debc1626 100644
--- a/tests/test_handlers.rs
+++ b/tests/test_handlers.rs
@@ -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")
     );
 }