mirror of
https://github.com/actix/actix-web.git
synced 2024-12-18 06:06:36 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
7f9d2471d1
22 changed files with 937 additions and 506 deletions
|
@ -2,14 +2,16 @@
|
|||
|
||||
## 0.5.0
|
||||
|
||||
* Type-safe path/query parameter handling, using serde #70
|
||||
* Type-safe path/query/form parameter handling, using serde #70
|
||||
|
||||
* HttpResponse builder's methods `.body()`, `.finish()`, `.json()`
|
||||
return `HttpResponse` instead of `Result`
|
||||
|
||||
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
|
||||
|
||||
* Add `HttpRequest::resource()`, returns current matched resource
|
||||
* Added `HttpRequest::resource()`, returns current matched resource
|
||||
|
||||
* Added `ErrorHandlers` middleware
|
||||
|
||||
* Router cannot parse Non-ASCII characters in URL #137
|
||||
|
||||
|
@ -19,6 +21,8 @@
|
|||
|
||||
* Fix client connection pooling
|
||||
|
||||
* Fix logger request duration calculation #152
|
||||
|
||||
|
||||
## 0.4.10 (2018-03-20)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Actix web is a simple, pragmatic, extremely fast, web framework for Rust.
|
||||
|
||||
|
|
|
@ -11,10 +11,10 @@ fn index(_req: HttpRequest) -> &'static str {
|
|||
|
||||
fn main() {
|
||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||
let _ = env_logger::init();
|
||||
let sys = actix::System::new("ws-example");
|
||||
env_logger::init();
|
||||
let sys = actix::System::new("hello-world");
|
||||
|
||||
let _addr = server::new(
|
||||
server::new(
|
||||
|| App::new()
|
||||
// enable logger
|
||||
.middleware(middleware::Logger::default())
|
||||
|
|
|
@ -14,7 +14,7 @@ extern crate env_logger;
|
|||
|
||||
use actix::prelude::*;
|
||||
use actix_web::{
|
||||
middleware, http, server,
|
||||
middleware, http::{self, header::CONTENT_TYPE}, server,
|
||||
App, AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error};
|
||||
use juniper::http::graphiql::graphiql_source;
|
||||
use juniper::http::GraphQLRequest;
|
||||
|
@ -79,7 +79,7 @@ fn graphql(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error
|
|||
.from_err()
|
||||
.and_then(|res| {
|
||||
match res {
|
||||
Ok(user) => Ok(HttpResponse::Ok().body(user)),
|
||||
Ok(user) => Ok(HttpResponse::Ok().header(CONTENT_TYPE, "application/json").body(user)),
|
||||
Err(_) => Ok(HttpResponse::InternalServerError().into())
|
||||
}
|
||||
})
|
||||
|
|
|
@ -140,9 +140,8 @@ use actix_web::{http, middleware, App, HttpResponse};
|
|||
fn main() {
|
||||
let app = App::new()
|
||||
.middleware(
|
||||
middleware::DefaultHeaders::build()
|
||||
.header("X-Version", "0.2")
|
||||
.finish())
|
||||
middleware::DefaultHeaders::new()
|
||||
.header("X-Version", "0.2"))
|
||||
.resource("/test", |r| {
|
||||
r.method(http::Method::GET).f(|req| HttpResponse::Ok());
|
||||
r.method(http::Method::HEAD).f(|req| HttpResponse::MethodNotAllowed());
|
||||
|
@ -210,3 +209,37 @@ fn main() {
|
|||
# let _ = sys.run();
|
||||
}
|
||||
```
|
||||
|
||||
## Error handlers
|
||||
|
||||
`ErrorHandlers` middleware allows to provide custom handlers for responses.
|
||||
|
||||
You can use `ErrorHandlers::handler()` method to register a custom error handler
|
||||
for specific status code. You can modify existing response or create completly new
|
||||
one. Error handler can return response immediately or return future that resolves
|
||||
to a response.
|
||||
|
||||
```rust
|
||||
# extern crate actix_web;
|
||||
use actix_web::{
|
||||
App, HttpRequest, HttpResponse, Result,
|
||||
http, middleware::Response, middleware::ErrorHandlers};
|
||||
|
||||
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
let mut builder = resp.into_builder();
|
||||
builder.header(http::header::CONTENT_TYPE, "application/json");
|
||||
Ok(Response::Done(builder.into()))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new()
|
||||
.middleware(
|
||||
ErrorHandlers::new()
|
||||
.handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500))
|
||||
.resource("/test", |r| {
|
||||
r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
|
||||
})
|
||||
.finish();
|
||||
}
|
||||
```
|
||||
|
|
|
@ -256,26 +256,32 @@ A full example is available in the
|
|||
Actix provides support for *application/x-www-form-urlencoded* encoded bodies.
|
||||
`HttpResponse::urlencoded()` returns a
|
||||
[*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, which resolves
|
||||
into `HashMap<String, String>` which contains decoded parameters.
|
||||
The *UrlEncoded* future can resolve into a error in several cases:
|
||||
to the deserialized instance, the type of the instance must implement the
|
||||
`Deserialize` trait from *serde*. The *UrlEncoded* future can resolve into
|
||||
a error in several cases:
|
||||
|
||||
* content type is not `application/x-www-form-urlencoded`
|
||||
* transfer encoding is `chunked`.
|
||||
* content-length is greater than 256k
|
||||
* payload terminates with error.
|
||||
|
||||
|
||||
```rust
|
||||
# extern crate actix_web;
|
||||
# extern crate futures;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
use actix_web::*;
|
||||
use futures::future::{Future, ok};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FormData {
|
||||
username: String,
|
||||
}
|
||||
|
||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
req.urlencoded() // <- get UrlEncoded future
|
||||
req.urlencoded::<FormData>() // <- get UrlEncoded future
|
||||
.from_err()
|
||||
.and_then(|params| { // <- url encoded parameters
|
||||
println!("==== BODY ==== {:?}", params);
|
||||
.and_then(|data| { // <- deserialized instance
|
||||
println!("USERNAME: {:?}", data.username);
|
||||
ok(HttpResponse::Ok().into())
|
||||
})
|
||||
.responder()
|
||||
|
@ -283,7 +289,6 @@ fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|||
# fn main() {}
|
||||
```
|
||||
|
||||
|
||||
## Streaming request
|
||||
|
||||
*HttpRequest* is a stream of `Bytes` objects. It can be used to read the request
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use handler::Reply;
|
||||
|
@ -9,7 +9,7 @@ use resource::{ResourceHandler};
|
|||
use header::ContentEncoding;
|
||||
use handler::{Handler, RouteHandler, WrapHandler};
|
||||
use httprequest::HttpRequest;
|
||||
use pipeline::{Pipeline, PipelineHandler};
|
||||
use pipeline::{Pipeline, PipelineHandler, HandlerType};
|
||||
use middleware::Middleware;
|
||||
use server::{HttpHandler, IntoHttpHandler, HttpHandlerTask, ServerSettings};
|
||||
|
||||
|
@ -21,7 +21,7 @@ pub struct HttpApplication<S=()> {
|
|||
state: Rc<S>,
|
||||
prefix: String,
|
||||
router: Router,
|
||||
inner: Rc<RefCell<Inner<S>>>,
|
||||
inner: Rc<UnsafeCell<Inner<S>>>,
|
||||
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,6 @@ pub(crate) struct Inner<S> {
|
|||
prefix: usize,
|
||||
default: ResourceHandler<S>,
|
||||
encoding: ContentEncoding,
|
||||
router: Router,
|
||||
resources: Vec<ResourceHandler<S>>,
|
||||
handlers: Vec<(String, Box<RouteHandler<S>>)>,
|
||||
}
|
||||
|
@ -40,39 +39,60 @@ impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
|||
self.encoding
|
||||
}
|
||||
|
||||
fn handle(&mut self, mut req: HttpRequest<S>) -> Reply {
|
||||
if let Some(idx) = self.router.recognize(&mut req) {
|
||||
self.resources[idx].handle(req.clone(), Some(&mut self.default))
|
||||
fn handle(&mut self, req: HttpRequest<S>, htype: HandlerType) -> Reply {
|
||||
match htype {
|
||||
HandlerType::Normal(idx) =>
|
||||
self.resources[idx].handle(req, Some(&mut self.default)),
|
||||
HandlerType::Handler(idx) =>
|
||||
self.handlers[idx].1.handle(req),
|
||||
HandlerType::Default =>
|
||||
self.default.handle(req, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> HttpApplication<S> {
|
||||
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &Inner<S> {
|
||||
unsafe{&*self.inner.get()}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_handler(&self, req: &mut HttpRequest<S>) -> HandlerType {
|
||||
if let Some(idx) = self.router.recognize(req) {
|
||||
HandlerType::Normal(idx)
|
||||
} else {
|
||||
for &mut (ref prefix, ref mut handler) in &mut self.handlers {
|
||||
let inner = self.as_ref();
|
||||
for idx in 0..inner.handlers.len() {
|
||||
let &(ref prefix, _) = &inner.handlers[idx];
|
||||
let m = {
|
||||
let path = &req.path()[self.prefix..];
|
||||
let path = &req.path()[inner.prefix..];
|
||||
path.starts_with(prefix) && (
|
||||
path.len() == prefix.len() ||
|
||||
path.split_at(prefix.len()).1.starts_with('/'))
|
||||
};
|
||||
if m {
|
||||
let path: &'static str = unsafe {
|
||||
mem::transmute(&req.path()[self.prefix+prefix.len()..]) };
|
||||
mem::transmute(&req.path()[inner.prefix+prefix.len()..]) };
|
||||
if path.is_empty() {
|
||||
req.match_info_mut().add("tail", "");
|
||||
} else {
|
||||
req.match_info_mut().add("tail", path.split_at(1).1);
|
||||
}
|
||||
return handler.handle(req)
|
||||
return HandlerType::Handler(idx)
|
||||
}
|
||||
}
|
||||
self.default.handle(req, None)
|
||||
HandlerType::Default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<S: 'static> HttpApplication<S> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn run(&mut self, req: HttpRequest<S>) -> Reply {
|
||||
self.inner.borrow_mut().handle(req)
|
||||
pub(crate) fn run(&mut self, mut req: HttpRequest<S>) -> Reply {
|
||||
let tp = self.get_handler(&mut req);
|
||||
unsafe{&mut *self.inner.get()}.handle(req, tp)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest<S> {
|
||||
req.with_state(Rc::clone(&self.state), self.router.clone())
|
||||
|
@ -89,10 +109,10 @@ impl<S: 'static> HttpHandler for HttpApplication<S> {
|
|||
path.split_at(self.prefix.len()).1.starts_with('/'))
|
||||
};
|
||||
if m {
|
||||
let mut req = req.with_state(Rc::clone(&self.state), self.router.clone());
|
||||
let tp = self.get_handler(&mut req);
|
||||
let inner = Rc::clone(&self.inner);
|
||||
let req = req.with_state(Rc::clone(&self.state), self.router.clone());
|
||||
|
||||
Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), inner)))
|
||||
Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), inner, tp)))
|
||||
} else {
|
||||
Err(req)
|
||||
}
|
||||
|
@ -409,12 +429,11 @@ impl<S> App<S> where S: 'static {
|
|||
|
||||
let (router, resources) = Router::new(prefix, parts.settings, resources);
|
||||
|
||||
let inner = Rc::new(RefCell::new(
|
||||
let inner = Rc::new(UnsafeCell::new(
|
||||
Inner {
|
||||
prefix: prefix.len(),
|
||||
default: parts.default,
|
||||
encoding: parts.encoding,
|
||||
router: router.clone(),
|
||||
handlers: parts.handlers,
|
||||
resources,
|
||||
}
|
||||
|
|
15
src/body.rs
15
src/body.rs
|
@ -6,6 +6,10 @@ use futures::Stream;
|
|||
|
||||
use error::Error;
|
||||
use context::ActorHttpContext;
|
||||
use handler::Responder;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
|
||||
/// Type represent streaming body
|
||||
pub type BodyStream = Box<Stream<Item=Bytes, Error=Error>>;
|
||||
|
@ -247,6 +251,17 @@ impl AsRef<[u8]> for Binary {
|
|||
}
|
||||
}
|
||||
|
||||
impl Responder for Binary {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/octet-stream")
|
||||
.body(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
282
src/de.rs
282
src/de.rs
|
@ -1,177 +1,10 @@
|
|||
use std::slice::Iter;
|
||||
use std::borrow::Cow;
|
||||
use std::convert::AsRef;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use serde::de::{self, Deserializer, Visitor, Error as DeError};
|
||||
|
||||
use serde_urlencoded;
|
||||
use serde::de::{self, Deserializer, DeserializeOwned, Visitor, Error as DeError};
|
||||
use futures::future::{FutureResult, result};
|
||||
|
||||
use error::Error;
|
||||
use handler::FromRequest;
|
||||
use httprequest::HttpRequest;
|
||||
|
||||
/// Extract typed information from the request's path.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Path, Result, http};
|
||||
///
|
||||
/// /// extract path info from "/{username}/{count}/?index.html" url
|
||||
/// /// {username} - deserializes to a String
|
||||
/// /// {count} - - deserializes to a u32
|
||||
/// fn index(info: Path<(String, u32)>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}! {}", info.0, info.1))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/{username}/{count}/?index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It is possible to extract path information to a specific type that implements
|
||||
/// `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Path, Result, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// extract path info using serde
|
||||
/// fn index(info: Path<Info>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", info.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Path<T>{
|
||||
inner: T
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Path<T> {
|
||||
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Path<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Path<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Path<T> {
|
||||
/// Deconstruct to a inner value
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> FromRequest<S> for Path<T>
|
||||
where T: DeserializeOwned, S: 'static
|
||||
{
|
||||
type Result = FutureResult<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>) -> Self::Result {
|
||||
let req = req.clone();
|
||||
result(de::Deserialize::deserialize(PathDeserializer{req: &req})
|
||||
.map_err(|e| e.into())
|
||||
.map(|inner| Path{inner}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract typed information from from the request's query.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Query, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// // use `with` extractor for query info
|
||||
/// // this handler get called only if request's query contains `username` field
|
||||
/// fn index(info: Query<Info>) -> String {
|
||||
/// format!("Welcome {}!", info.username)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/index.html",
|
||||
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Query<T>(T);
|
||||
|
||||
impl<T> Deref for Query<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Query<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Query<T> {
|
||||
/// Deconstruct to a inner value
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> FromRequest<S> for Query<T>
|
||||
where T: de::DeserializeOwned, S: 'static
|
||||
{
|
||||
type Result = FutureResult<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>) -> Self::Result {
|
||||
let req = req.clone();
|
||||
result(serde_urlencoded::from_str::<T>(req.query_string())
|
||||
.map_err(|e| e.into())
|
||||
.map(Query))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! unsupported_type {
|
||||
($trait_fn:ident, $name:expr) => {
|
||||
|
@ -207,6 +40,12 @@ pub struct PathDeserializer<'de, S: 'de> {
|
|||
req: &'de HttpRequest<S>
|
||||
}
|
||||
|
||||
impl<'de, S: 'de> PathDeserializer<'de, S> {
|
||||
pub fn new(req: &'de HttpRequest<S>) -> Self {
|
||||
PathDeserializer{req}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S>
|
||||
{
|
||||
type Error = de::value::Error;
|
||||
|
@ -549,110 +388,3 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
|
|||
Err(de::value::Error::custom("not supported"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures::{Async, Future};
|
||||
use super::*;
|
||||
use router::{Router, Resource};
|
||||
use resource::ResourceHandler;
|
||||
use test::TestRequest;
|
||||
use server::ServerSettings;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MyStruct {
|
||||
key: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Id {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Test2 {
|
||||
key: String,
|
||||
value: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_extract() {
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
match Path::<MyStruct>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, "user1");
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match Path::<(String, String)>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, "user1");
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match Query::<Id>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.id, "test");
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let mut req = TestRequest::with_uri("/name/32/").finish();
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
match Path::<Test2>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.as_ref().key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match Path::<(String, u8)>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match Path::<Vec<String>>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.into_inner(), vec!["name".to_owned(), "32".to_owned()]);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_path_signle() {
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((Resource::new("index", "/{value}/"), Some(resource)));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
|
||||
let mut req = TestRequest::with_uri("/32/").finish();
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
match Path::<i8>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.into_inner(), 32);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,6 +115,13 @@ impl ResponseError for DeError {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return `BAD_REQUEST` for `Utf8Error`
|
||||
impl ResponseError for Utf8Error {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::new(StatusCode::BAD_REQUEST)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `InternalServerError` for `HttpError`,
|
||||
/// Response generation can return `HttpError`, so it is internal error
|
||||
impl ResponseError for HttpError {}
|
||||
|
|
461
src/extractor.rs
Normal file
461
src/extractor.rs
Normal file
|
@ -0,0 +1,461 @@
|
|||
use std::str;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use bytes::Bytes;
|
||||
use serde_urlencoded;
|
||||
use serde::de::{self, DeserializeOwned};
|
||||
use futures::future::{Future, FutureResult, result};
|
||||
use encoding::all::UTF_8;
|
||||
use encoding::types::{Encoding, DecoderTrap};
|
||||
|
||||
use error::{Error, ErrorBadRequest};
|
||||
use handler::{Either, FromRequest};
|
||||
use httprequest::HttpRequest;
|
||||
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
|
||||
use de::PathDeserializer;
|
||||
|
||||
/// Extract typed information from the request's path.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Path, Result, http};
|
||||
///
|
||||
/// /// extract path info from "/{username}/{count}/?index.html" url
|
||||
/// /// {username} - deserializes to a String
|
||||
/// /// {count} - - deserializes to a u32
|
||||
/// fn index(info: Path<(String, u32)>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}! {}", info.0, info.1))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/{username}/{count}/?index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It is possible to extract path information to a specific type that implements
|
||||
/// `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Path, Result, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// extract path info using serde
|
||||
/// fn index(info: Path<Info>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", info.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/{username}/index.html", // <- define path parameters
|
||||
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Path<T>{
|
||||
inner: T
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Path<T> {
|
||||
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Path<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Path<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Path<T> {
|
||||
/// Deconstruct to an inner value
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> FromRequest<S> for Path<T>
|
||||
where T: DeserializeOwned, S: 'static
|
||||
{
|
||||
type Result = FutureResult<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>) -> Self::Result {
|
||||
let req = req.clone();
|
||||
result(de::Deserialize::deserialize(PathDeserializer::new(&req))
|
||||
.map_err(|e| e.into())
|
||||
.map(|inner| Path{inner}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract typed information from from the request's query.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Query, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// // use `with` extractor for query info
|
||||
/// // this handler get called only if request's query contains `username` field
|
||||
/// fn index(info: Query<Info>) -> String {
|
||||
/// format!("Welcome {}!", info.username)
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/index.html",
|
||||
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Query<T>(T);
|
||||
|
||||
impl<T> Deref for Query<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Query<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Query<T> {
|
||||
/// Deconstruct to a inner value
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> FromRequest<S> for Query<T>
|
||||
where T: de::DeserializeOwned, S: 'static
|
||||
{
|
||||
type Result = FutureResult<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>) -> Self::Result {
|
||||
let req = req.clone();
|
||||
result(serde_urlencoded::from_str::<T>(req.query_string())
|
||||
.map_err(|e| e.into())
|
||||
.map(Query))
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract typed information from the request's body.
|
||||
///
|
||||
/// To extract typed information from request's body, the type `T` must implement the
|
||||
/// `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// It is possible to extract path information to a specific type that implements
|
||||
/// `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Form, Result};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct FormData {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// extract form data using serde
|
||||
/// /// this handle get called only if content type is *x-www-form-urlencoded*
|
||||
/// /// and content of the request could be deserialized to a `FormData` struct
|
||||
/// fn index(form: Form<FormData>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", form.username))
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub struct Form<T>(pub T);
|
||||
|
||||
impl<T> Deref for Form<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Form<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> FromRequest<S> for Form<T>
|
||||
where T: DeserializeOwned + 'static, S: 'static
|
||||
{
|
||||
type Result = Box<Future<Item=Self, Error=Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>) -> Self::Result {
|
||||
Box::new(UrlEncoded::new(req.clone()).from_err().map(Form))
|
||||
}
|
||||
}
|
||||
|
||||
/// Request payload extractor.
|
||||
///
|
||||
/// Loads request's payload and construct Bytes instance.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// extern crate bytes;
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{App, Result};
|
||||
///
|
||||
/// /// extract text data from request
|
||||
/// fn index(body: bytes::Bytes) -> Result<String> {
|
||||
/// Ok(format!("Body {:?}!", body))
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
impl<S: 'static> FromRequest<S> for Bytes
|
||||
{
|
||||
type Result = Box<Future<Item=Self, Error=Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>) -> Self::Result {
|
||||
Box::new(MessageBody::new(req.clone()).from_err())
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract text information from the request's body.
|
||||
///
|
||||
/// Text extractor automatically decode body according to the request's charset.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{App, Result};
|
||||
///
|
||||
/// /// extract text data from request
|
||||
/// fn index(body: String) -> Result<String> {
|
||||
/// Ok(format!("Body {}!", body))
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
impl<S: 'static> FromRequest<S> for String
|
||||
{
|
||||
type Result = Either<FutureResult<String, Error>,
|
||||
Box<Future<Item=String, Error=Error>>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>) -> Self::Result {
|
||||
let encoding = match req.encoding() {
|
||||
Err(_) => return Either::A(
|
||||
result(Err(ErrorBadRequest("Unknown request charset")))),
|
||||
Ok(encoding) => encoding,
|
||||
};
|
||||
|
||||
Either::B(Box::new(
|
||||
MessageBody::new(req.clone())
|
||||
.from_err()
|
||||
.and_then(move |body| {
|
||||
let enc: *const Encoding = encoding as *const Encoding;
|
||||
if enc == UTF_8 {
|
||||
Ok(str::from_utf8(body.as_ref())
|
||||
.map_err(|_| ErrorBadRequest("Can not decode body"))?
|
||||
.to_owned())
|
||||
} else {
|
||||
Ok(encoding.decode(&body, DecoderTrap::Strict)
|
||||
.map_err(|_| ErrorBadRequest("Can not decode body"))?)
|
||||
}
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bytes::Bytes;
|
||||
use futures::{Async, Future};
|
||||
use http::header;
|
||||
use router::{Router, Resource};
|
||||
use resource::ResourceHandler;
|
||||
use test::TestRequest;
|
||||
use server::ServerSettings;
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
struct Info {
|
||||
hello: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes() {
|
||||
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish();
|
||||
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
|
||||
|
||||
match Bytes::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s, Bytes::from_static(b"hello=world"));
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string() {
|
||||
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish();
|
||||
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
|
||||
|
||||
match String::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s, "hello=world");
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_form() {
|
||||
let mut req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(header::CONTENT_LENGTH, "11")
|
||||
.finish();
|
||||
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
|
||||
|
||||
match Form::<Info>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.hello, "world");
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MyStruct {
|
||||
key: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Id {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Test2 {
|
||||
key: String,
|
||||
value: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_extract() {
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
|
||||
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
match Path::<MyStruct>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, "user1");
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match Path::<(String, String)>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, "user1");
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match Query::<Id>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.id, "test");
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let mut req = TestRequest::with_uri("/name/32/").finish();
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
match Path::<Test2>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.as_ref().key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match Path::<(String, u8)>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match Path::<Vec<String>>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.into_inner(), vec!["name".to_owned(), "32".to_owned()]);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_path_signle() {
|
||||
let mut resource = ResourceHandler::<()>::default();
|
||||
resource.name("index");
|
||||
let mut routes = Vec::new();
|
||||
routes.push((Resource::new("index", "/{value}/"), Some(resource)));
|
||||
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||
|
||||
let mut req = TestRequest::with_uri("/32/").finish();
|
||||
assert!(router.recognize(&mut req).is_some());
|
||||
|
||||
match Path::<i8>::from_request(&req).poll().unwrap() {
|
||||
Async::Ready(s) => {
|
||||
assert_eq!(s.into_inner(), 32);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use std::ops::Deref;
|
||||
use std::marker::PhantomData;
|
||||
use futures::Poll;
|
||||
use futures::future::{Future, FutureResult, ok, err};
|
||||
|
||||
use error::Error;
|
||||
|
@ -96,6 +97,21 @@ impl<A, B> Responder for Either<A, B>
|
|||
}
|
||||
}
|
||||
|
||||
impl<A, B, I, E> Future for Either<A, B>
|
||||
where A: Future<Item=I, Error=E>,
|
||||
B: Future<Item=I, Error=E>,
|
||||
{
|
||||
type Item = I;
|
||||
type Error = E;
|
||||
|
||||
fn poll(&mut self) -> Poll<I, E> {
|
||||
match *self {
|
||||
Either::A(ref mut fut) => fut.poll(),
|
||||
Either::B(ref mut fut) => fut.poll(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience trait that converts `Future` object to a `Boxed` future
|
||||
///
|
||||
/// For example loading json from request's body is async operation.
|
||||
|
@ -373,7 +389,7 @@ impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
|
|||
}
|
||||
}
|
||||
|
||||
/// Access to an application state
|
||||
/// Access an application state
|
||||
///
|
||||
/// `S` - application state type
|
||||
///
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use std::str;
|
||||
use std::collections::HashMap;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Future, Stream, Poll};
|
||||
use http_range::HttpRange;
|
||||
use serde::de::DeserializeOwned;
|
||||
use mime::Mime;
|
||||
use url::form_urlencoded;
|
||||
use serde_urlencoded;
|
||||
use encoding::all::UTF_8;
|
||||
use encoding::EncodingRef;
|
||||
use encoding::types::{Encoding, DecoderTrap};
|
||||
use encoding::label::encoding_from_whatwg_label;
|
||||
use http::{header, HeaderMap};
|
||||
|
||||
|
@ -137,8 +137,8 @@ pub trait HttpMessage {
|
|||
}
|
||||
|
||||
/// Parse `application/x-www-form-urlencoded` encoded request's body.
|
||||
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>` which
|
||||
/// contains decoded parameters.
|
||||
/// Return `UrlEncoded` future. Form can be deserialized to any type that implements
|
||||
/// `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// Returns error:
|
||||
///
|
||||
|
@ -152,20 +152,21 @@ pub trait HttpMessage {
|
|||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # use futures::Future;
|
||||
/// use actix_web::*;
|
||||
/// # use std::collections::HashMap;
|
||||
/// use actix_web::{HttpMessage, HttpRequest, HttpResponse, FutureResponse};
|
||||
///
|
||||
/// fn index(mut req: HttpRequest) -> FutureResponse<HttpResponse> {
|
||||
/// req.urlencoded() // <- get UrlEncoded future
|
||||
/// .from_err()
|
||||
/// .and_then(|params| { // <- url encoded parameters
|
||||
/// println!("==== BODY ==== {:?}", params);
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// })
|
||||
/// .responder()
|
||||
/// Box::new(
|
||||
/// req.urlencoded::<HashMap<String, String>>() // <- get UrlEncoded future
|
||||
/// .from_err()
|
||||
/// .and_then(|params| { // <- url encoded parameters
|
||||
/// println!("==== BODY ==== {:?}", params);
|
||||
/// Ok(HttpResponse::Ok().into())
|
||||
/// }))
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn urlencoded(self) -> UrlEncoded<Self>
|
||||
fn urlencoded<T: DeserializeOwned>(self) -> UrlEncoded<Self, T>
|
||||
where Self: Stream<Item=Bytes, Error=PayloadError> + Sized
|
||||
{
|
||||
UrlEncoded::new(self)
|
||||
|
@ -321,14 +322,14 @@ impl<T> Future for MessageBody<T>
|
|||
}
|
||||
|
||||
/// Future that resolves to a parsed urlencoded values.
|
||||
pub struct UrlEncoded<T> {
|
||||
pub struct UrlEncoded<T, U> {
|
||||
req: Option<T>,
|
||||
limit: usize,
|
||||
fut: Option<Box<Future<Item=HashMap<String, String>, Error=UrlencodedError>>>,
|
||||
fut: Option<Box<Future<Item=U, Error=UrlencodedError>>>,
|
||||
}
|
||||
|
||||
impl<T> UrlEncoded<T> {
|
||||
pub fn new(req: T) -> UrlEncoded<T> {
|
||||
impl<T, U> UrlEncoded<T, U> {
|
||||
pub fn new(req: T) -> UrlEncoded<T, U> {
|
||||
UrlEncoded {
|
||||
req: Some(req),
|
||||
limit: 262_144,
|
||||
|
@ -343,10 +344,11 @@ impl<T> UrlEncoded<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Future for UrlEncoded<T>
|
||||
where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static
|
||||
impl<T, U> Future for UrlEncoded<T, U>
|
||||
where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static,
|
||||
U: DeserializeOwned + 'static
|
||||
{
|
||||
type Item = HashMap<String, String>;
|
||||
type Item = U;
|
||||
type Error = UrlencodedError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
|
@ -385,13 +387,16 @@ impl<T> Future for UrlEncoded<T>
|
|||
}
|
||||
})
|
||||
.and_then(move |body| {
|
||||
let mut m = HashMap::new();
|
||||
let parsed = form_urlencoded::parse_with_encoding(
|
||||
&body, Some(encoding), false).map_err(|_| UrlencodedError::Parse)?;
|
||||
for (k, v) in parsed {
|
||||
m.insert(k.into(), v.into());
|
||||
let enc: *const Encoding = encoding as *const Encoding;
|
||||
if enc == UTF_8 {
|
||||
serde_urlencoded::from_bytes::<U>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
} else {
|
||||
let body = encoding.decode(&body, DecoderTrap::Strict)
|
||||
.map_err(|_| UrlencodedError::Parse)?;
|
||||
serde_urlencoded::from_str::<U>(&body)
|
||||
.map_err(|_| UrlencodedError::Parse)
|
||||
}
|
||||
Ok(m)
|
||||
});
|
||||
self.fut = Some(Box::new(fut));
|
||||
}
|
||||
|
@ -410,7 +415,6 @@ mod tests {
|
|||
use http::{Method, Version, Uri};
|
||||
use httprequest::HttpRequest;
|
||||
use std::str::FromStr;
|
||||
use std::iter::FromIterator;
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
|
@ -529,28 +533,37 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
struct Info {
|
||||
hello: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_urlencoded_error() {
|
||||
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
|
||||
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked);
|
||||
assert_eq!(req.urlencoded::<Info>()
|
||||
.poll().err().unwrap(), UrlencodedError::Chunked);
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(header::CONTENT_LENGTH, "xxxx")
|
||||
.finish();
|
||||
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength);
|
||||
assert_eq!(req.urlencoded::<Info>()
|
||||
.poll().err().unwrap(), UrlencodedError::UnknownLength);
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(header::CONTENT_LENGTH, "1000000")
|
||||
.finish();
|
||||
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow);
|
||||
assert_eq!(req.urlencoded::<Info>()
|
||||
.poll().err().unwrap(), UrlencodedError::Overflow);
|
||||
|
||||
let req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE, "text/plain")
|
||||
.header(header::CONTENT_LENGTH, "10")
|
||||
.finish();
|
||||
assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType);
|
||||
assert_eq!(req.urlencoded::<Info>()
|
||||
.poll().err().unwrap(), UrlencodedError::ContentType);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -561,9 +574,8 @@ mod tests {
|
|||
.finish();
|
||||
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
|
||||
|
||||
let result = req.urlencoded().poll().ok().unwrap();
|
||||
assert_eq!(result, Async::Ready(
|
||||
HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())])));
|
||||
let result = req.urlencoded::<Info>().poll().ok().unwrap();
|
||||
assert_eq!(result, Async::Ready(Info{hello: "world".to_owned()}));
|
||||
|
||||
let mut req = TestRequest::with_header(
|
||||
header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8")
|
||||
|
@ -572,8 +584,7 @@ mod tests {
|
|||
req.payload_mut().unread_data(Bytes::from_static(b"hello=world"));
|
||||
|
||||
let result = req.urlencoded().poll().ok().unwrap();
|
||||
assert_eq!(result, Async::Ready(
|
||||
HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())])));
|
||||
assert_eq!(result, Async::Ready(Info{hello: "world".to_owned()}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -39,7 +39,13 @@ pub struct HttpInnerMessage {
|
|||
pub addr: Option<SocketAddr>,
|
||||
pub payload: Option<Payload>,
|
||||
pub info: Option<ConnectionInfo<'static>>,
|
||||
pub resource: i16,
|
||||
resource: RouterResource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone,PartialEq)]
|
||||
enum RouterResource {
|
||||
Notset,
|
||||
Normal(u16),
|
||||
}
|
||||
|
||||
impl Default for HttpInnerMessage {
|
||||
|
@ -58,7 +64,7 @@ impl Default for HttpInnerMessage {
|
|||
payload: None,
|
||||
extensions: Extensions::new(),
|
||||
info: None,
|
||||
resource: -1,
|
||||
resource: RouterResource::Notset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,12 +101,12 @@ impl HttpInnerMessage {
|
|||
self.addr = None;
|
||||
self.info = None;
|
||||
self.payload = None;
|
||||
self.resource = -1;
|
||||
self.resource = RouterResource::Notset;
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static!{
|
||||
static ref RESOURCE: Resource = Resource::default();
|
||||
static ref RESOURCE: Resource = Resource::unset();
|
||||
}
|
||||
|
||||
|
||||
|
@ -128,7 +134,7 @@ impl HttpRequest<()> {
|
|||
addr: None,
|
||||
extensions: Extensions::new(),
|
||||
info: None,
|
||||
resource: -1,
|
||||
resource: RouterResource::Notset,
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
|
@ -330,17 +336,16 @@ impl<S> HttpRequest<S> {
|
|||
/// This method returns reference to matched `Resource` object.
|
||||
#[inline]
|
||||
pub fn resource(&self) -> &Resource {
|
||||
let idx = self.as_ref().resource;
|
||||
if idx >= 0 {
|
||||
if let Some(ref router) = self.2 {
|
||||
if let Some(ref router) = self.2 {
|
||||
if let RouterResource::Normal(idx) = self.as_ref().resource {
|
||||
return router.get_resource(idx as usize)
|
||||
}
|
||||
}
|
||||
&*RESOURCE
|
||||
}
|
||||
|
||||
pub(crate) fn set_resource(&mut self, idx: usize) {
|
||||
self.as_mut().resource = idx as i16;
|
||||
pub(crate) fn set_resource(&mut self, res: usize) {
|
||||
self.as_mut().resource = RouterResource::Normal(res as u16);
|
||||
}
|
||||
|
||||
/// Peer socket address
|
||||
|
|
95
src/json.rs
95
src/json.rs
|
@ -19,54 +19,6 @@ use httpresponse::HttpResponse;
|
|||
///
|
||||
/// Json can be used for two different purpose. First is for json response generation
|
||||
/// and second is for extracting typed information from request's payload.
|
||||
///
|
||||
/// The `Json` type allows you to respond with well-formed JSON data: simply
|
||||
/// return a value of type Json<T> where T is the type of a structure
|
||||
/// to serialize into *JSON*. The type `T` must implement the `Serialize`
|
||||
/// trait from *serde*.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # use actix_web::*;
|
||||
/// #
|
||||
/// #[derive(Serialize)]
|
||||
/// struct MyObj {
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<Json<MyObj>> {
|
||||
/// Ok(Json(MyObj{name: req.match_info().query("name")?}))
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// To extract typed information from request's body, the type `T` must implement the
|
||||
/// `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Json, Result, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// extract `Info` using serde
|
||||
/// fn index(info: Json<Info>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", info.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/index.html",
|
||||
/// |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Json<T>(pub T);
|
||||
|
||||
impl<T> Deref for Json<T> {
|
||||
|
@ -95,6 +47,26 @@ impl<T> fmt::Display for Json<T> where T: fmt::Display {
|
|||
}
|
||||
}
|
||||
|
||||
/// The `Json` type allows you to respond with well-formed JSON data: simply
|
||||
/// return a value of type Json<T> where T is the type of a structure
|
||||
/// to serialize into *JSON*. The type `T` must implement the `Serialize`
|
||||
/// trait from *serde*.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # use actix_web::*;
|
||||
/// #
|
||||
/// #[derive(Serialize)]
|
||||
/// struct MyObj {
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> Result<Json<MyObj>> {
|
||||
/// Ok(Json(MyObj{name: req.match_info().query("name")?}))
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
impl<T: Serialize> Responder for Json<T> {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
@ -108,6 +80,32 @@ impl<T: Serialize> Responder for Json<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// To extract typed information from request's body, the type `T` must implement the
|
||||
/// `Deserialize` trait from *serde*.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{App, Json, Result, http};
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Info {
|
||||
/// username: String,
|
||||
/// }
|
||||
///
|
||||
/// /// deserialize `Info` from request's body
|
||||
/// fn index(info: Json<Info>) -> Result<String> {
|
||||
/// Ok(format!("Welcome {}!", info.username))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().resource(
|
||||
/// "/index.html",
|
||||
/// |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor
|
||||
/// }
|
||||
/// ```
|
||||
impl<T, S> FromRequest<S> for Json<T>
|
||||
where T: DeserializeOwned + 'static, S: 'static
|
||||
{
|
||||
|
@ -129,7 +127,6 @@ impl<T, S> FromRequest<S> for Json<T>
|
|||
/// * content type is not `application/json`
|
||||
/// * content length is greater than 256k
|
||||
///
|
||||
///
|
||||
/// # Server example
|
||||
///
|
||||
/// ```rust
|
||||
|
|
|
@ -109,6 +109,7 @@ mod application;
|
|||
mod body;
|
||||
mod context;
|
||||
mod de;
|
||||
mod extractor;
|
||||
mod handler;
|
||||
mod header;
|
||||
mod helpers;
|
||||
|
@ -134,15 +135,15 @@ pub mod middleware;
|
|||
pub mod pred;
|
||||
pub mod test;
|
||||
pub mod server;
|
||||
pub use extractor::{Path, Form, Query};
|
||||
pub use error::{Error, Result, ResponseError};
|
||||
pub use body::{Body, Binary};
|
||||
pub use json::Json;
|
||||
pub use de::{Path, Query};
|
||||
pub use application::App;
|
||||
pub use httpmessage::HttpMessage;
|
||||
pub use httprequest::HttpRequest;
|
||||
pub use httpresponse::HttpResponse;
|
||||
pub use handler::{Either, Responder, AsyncResponder, FutureResponse, State};
|
||||
pub use handler::{Either, Responder, AsyncResponder, FromRequest, FutureResponse, State};
|
||||
pub use context::HttpContext;
|
||||
pub use server::HttpServer;
|
||||
|
||||
|
@ -178,9 +179,9 @@ pub mod dev {
|
|||
pub use context::Drain;
|
||||
pub use json::JsonBody;
|
||||
pub use info::ConnectionInfo;
|
||||
pub use handler::{Handler, Reply, FromRequest};
|
||||
pub use handler::{Handler, Reply};
|
||||
pub use route::Route;
|
||||
pub use router::{Router, Resource};
|
||||
pub use router::{Router, Resource, ResourceType};
|
||||
pub use resource::ResourceHandler;
|
||||
pub use param::{FromParam, Params};
|
||||
pub use httpmessage::{UrlEncoded, MessageBody};
|
||||
|
|
|
@ -18,9 +18,8 @@ use middleware::{Response, Middleware};
|
|||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .middleware(
|
||||
/// middleware::DefaultHeaders::build()
|
||||
/// .header("X-Version", "0.2")
|
||||
/// .finish())
|
||||
/// middleware::DefaultHeaders::new()
|
||||
/// .header("X-Version", "0.2"))
|
||||
/// .resource("/test", |r| {
|
||||
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
|
||||
|
@ -33,9 +32,41 @@ pub struct DefaultHeaders{
|
|||
headers: HeaderMap,
|
||||
}
|
||||
|
||||
impl Default for DefaultHeaders {
|
||||
fn default() -> Self {
|
||||
DefaultHeaders{ct: false, headers: HeaderMap::new()}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultHeaders {
|
||||
pub fn build() -> DefaultHeadersBuilder {
|
||||
DefaultHeadersBuilder{ct: false, headers: Some(HeaderMap::new())}
|
||||
/// Construct `DefaultHeaders` middleware.
|
||||
pub fn new() -> DefaultHeaders {
|
||||
DefaultHeaders::default()
|
||||
}
|
||||
|
||||
/// Set a header.
|
||||
#[inline]
|
||||
#[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>,
|
||||
HeaderValue: HttpTryFrom<V>
|
||||
{
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => {
|
||||
match HeaderValue::try_from(value) {
|
||||
Ok(value) => { self.headers.append(key, value); }
|
||||
Err(_) => panic!("Can not create header value"),
|
||||
}
|
||||
},
|
||||
Err(_) => panic!("Can not create header name"),
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set *CONTENT-TYPE* header if response does not contain this header.
|
||||
pub fn content_type(mut self) -> Self {
|
||||
self.ct = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,49 +87,6 @@ impl<S> Middleware<S> for DefaultHeaders {
|
|||
}
|
||||
}
|
||||
|
||||
/// Structure that follows the builder pattern for building `DefaultHeaders` middleware.
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultHeadersBuilder {
|
||||
ct: bool,
|
||||
headers: Option<HeaderMap>,
|
||||
}
|
||||
|
||||
impl DefaultHeadersBuilder {
|
||||
|
||||
/// Set a header.
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))]
|
||||
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||
where HeaderName: HttpTryFrom<K>,
|
||||
HeaderValue: HttpTryFrom<V>
|
||||
{
|
||||
if let Some(ref mut headers) = self.headers {
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => {
|
||||
match HeaderValue::try_from(value) {
|
||||
Ok(value) => { headers.append(key, value); }
|
||||
Err(_) => panic!("Can not create header value"),
|
||||
}
|
||||
},
|
||||
Err(_) => panic!("Can not create header name"),
|
||||
};
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set *CONTENT-TYPE* header if response does not contain this header.
|
||||
pub fn content_type(&mut self) -> &mut Self {
|
||||
self.ct = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Finishes building and returns the built `DefaultHeaders` middleware.
|
||||
pub fn finish(&mut self) -> DefaultHeaders {
|
||||
let headers = self.headers.take().expect("cannot reuse middleware builder");
|
||||
DefaultHeaders{ ct: self.ct, headers }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -106,9 +94,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_default_headers() {
|
||||
let mw = DefaultHeaders::build()
|
||||
.header(CONTENT_TYPE, "0001")
|
||||
.finish();
|
||||
let mw = DefaultHeaders::new()
|
||||
.header(CONTENT_TYPE, "0001");
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
|
||||
|
|
114
src/middleware/errhandlers.rs
Normal file
114
src/middleware/errhandlers.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use error::Result;
|
||||
use http::StatusCode;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use middleware::{Middleware, Response};
|
||||
|
||||
|
||||
type ErrorHandler<S> = Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response>;
|
||||
|
||||
/// `Middleware` for allowing custom handlers for responses.
|
||||
///
|
||||
/// You can use `ErrorHandlers::handler()` method to register a custom error handler
|
||||
/// for specific status code. You can modify existing response or create completly new
|
||||
/// one.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{http, App, HttpRequest, HttpResponse, Result};
|
||||
/// use actix_web::middleware::{Response, ErrorHandlers};
|
||||
///
|
||||
/// fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
/// let mut builder = resp.into_builder();
|
||||
/// builder.header(http::header::CONTENT_TYPE, "application/json");
|
||||
/// Ok(Response::Done(builder.into()))
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .middleware(
|
||||
/// ErrorHandlers::new()
|
||||
/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500))
|
||||
/// .resource("/test", |r| {
|
||||
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||
/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
|
||||
/// })
|
||||
/// .finish();
|
||||
/// }
|
||||
/// ```
|
||||
pub struct ErrorHandlers<S> {
|
||||
handlers: HashMap<StatusCode, Box<ErrorHandler<S>>>,
|
||||
}
|
||||
|
||||
impl<S> Default for ErrorHandlers<S> {
|
||||
fn default() -> Self {
|
||||
ErrorHandlers {
|
||||
handlers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> ErrorHandlers<S> {
|
||||
|
||||
/// Construct new `ErrorHandlers` instance
|
||||
pub fn new() -> Self {
|
||||
ErrorHandlers::default()
|
||||
}
|
||||
|
||||
/// Register error handler for specified status code
|
||||
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
|
||||
where F: Fn(&mut HttpRequest<S>, HttpResponse) -> Result<Response> + 'static
|
||||
{
|
||||
self.handlers.insert(status, Box::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Middleware<S> for ErrorHandlers<S> {
|
||||
|
||||
fn response(&self, req: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
if let Some(handler) = self.handlers.get(&resp.status()) {
|
||||
handler(req, resp)
|
||||
} else {
|
||||
Ok(Response::Done(resp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::StatusCode;
|
||||
use http::header::CONTENT_TYPE;
|
||||
|
||||
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||
let mut builder = resp.into_builder();
|
||||
builder.header(CONTENT_TYPE, "0001");
|
||||
Ok(Response::Done(builder.into()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handler() {
|
||||
let mw = ErrorHandlers::new()
|
||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
let resp = HttpResponse::InternalServerError().finish();
|
||||
let resp = match mw.response(&mut req, resp) {
|
||||
Ok(Response::Done(resp)) => resp,
|
||||
_ => panic!(),
|
||||
};
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
|
||||
let resp = HttpResponse::Ok().finish();
|
||||
let resp = match mw.response(&mut req, resp) {
|
||||
Ok(Response::Done(resp)) => resp,
|
||||
_ => panic!(),
|
||||
};
|
||||
assert!(!resp.headers().contains_key(CONTENT_TYPE));
|
||||
}
|
||||
}
|
|
@ -231,18 +231,14 @@ impl FormatText {
|
|||
FormatText::ResponseSize => resp.response_size().fmt(fmt),
|
||||
FormatText::Pid => unsafe{libc::getpid().fmt(fmt)},
|
||||
FormatText::Time => {
|
||||
let response_time = time::now() - entry_time;
|
||||
let response_time = response_time.num_seconds() as f64 +
|
||||
(response_time.num_nanoseconds().unwrap_or(0) as f64)/1_000_000_000.0;
|
||||
|
||||
fmt.write_fmt(format_args!("{:.6}", response_time))
|
||||
let rt = time::now() - entry_time;
|
||||
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0;
|
||||
fmt.write_fmt(format_args!("{:.6}", rt))
|
||||
},
|
||||
FormatText::TimeMillis => {
|
||||
let response_time = time::now() - entry_time;
|
||||
let response_time_ms = (response_time.num_seconds() * 1000) as f64 +
|
||||
(response_time.num_nanoseconds().unwrap_or(0) as f64)/1_000_000.0;
|
||||
|
||||
fmt.write_fmt(format_args!("{:.6}", response_time_ms))
|
||||
let rt = time::now() - entry_time;
|
||||
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0;
|
||||
fmt.write_fmt(format_args!("{:.6}", rt))
|
||||
},
|
||||
FormatText::RemoteAddr => {
|
||||
if let Some(remote) = req.connection_info().remote() {
|
||||
|
|
|
@ -10,10 +10,12 @@ mod logger;
|
|||
#[cfg(feature = "session")]
|
||||
mod session;
|
||||
mod defaultheaders;
|
||||
mod errhandlers;
|
||||
pub mod cors;
|
||||
pub mod csrf;
|
||||
pub use self::logger::Logger;
|
||||
pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder};
|
||||
pub use self::errhandlers::ErrorHandlers;
|
||||
pub use self::defaultheaders::DefaultHeaders;
|
||||
|
||||
#[cfg(feature = "session")]
|
||||
pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{io, mem};
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use log::Level::Debug;
|
||||
|
@ -18,11 +18,18 @@ use middleware::{Middleware, Finished, Started, Response};
|
|||
use application::Inner;
|
||||
use server::{Writer, WriterState, HttpHandlerTask};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum HandlerType {
|
||||
Normal(usize),
|
||||
Handler(usize),
|
||||
Default,
|
||||
}
|
||||
|
||||
pub(crate) trait PipelineHandler<S> {
|
||||
|
||||
fn encoding(&self) -> ContentEncoding;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Reply;
|
||||
fn handle(&mut self, req: HttpRequest<S>, htype: HandlerType) -> Reply;
|
||||
}
|
||||
|
||||
pub(crate) struct Pipeline<S, H>(PipelineInfo<S>, PipelineState<S, H>);
|
||||
|
@ -105,7 +112,7 @@ impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
|
|||
|
||||
pub fn new(req: HttpRequest<S>,
|
||||
mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||
handler: Rc<RefCell<H>>) -> Pipeline<S, H>
|
||||
handler: Rc<UnsafeCell<H>>, htype: HandlerType) -> Pipeline<S, H>
|
||||
{
|
||||
let mut info = PipelineInfo {
|
||||
req, mws,
|
||||
|
@ -113,9 +120,9 @@ impl<S: 'static, H: PipelineHandler<S>> Pipeline<S, H> {
|
|||
error: None,
|
||||
context: None,
|
||||
disconnected: None,
|
||||
encoding: handler.borrow().encoding(),
|
||||
encoding: unsafe{&*handler.get()}.encoding(),
|
||||
};
|
||||
let state = StartMiddlewares::init(&mut info, handler);
|
||||
let state = StartMiddlewares::init(&mut info, handler, htype);
|
||||
|
||||
Pipeline(info, state)
|
||||
}
|
||||
|
@ -209,20 +216,23 @@ type Fut = Box<Future<Item=Option<HttpResponse>, Error=Error>>;
|
|||
|
||||
/// Middlewares start executor
|
||||
struct StartMiddlewares<S, H> {
|
||||
hnd: Rc<RefCell<H>>,
|
||||
hnd: Rc<UnsafeCell<H>>,
|
||||
htype: HandlerType,
|
||||
fut: Option<Fut>,
|
||||
_s: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
||||
|
||||
fn init(info: &mut PipelineInfo<S>, handler: Rc<RefCell<H>>) -> PipelineState<S, H> {
|
||||
fn init(info: &mut PipelineInfo<S>, hnd: Rc<UnsafeCell<H>>, htype: HandlerType)
|
||||
-> PipelineState<S, H>
|
||||
{
|
||||
// execute middlewares, we need this stage because middlewares could be non-async
|
||||
// and we can move to next state immediately
|
||||
let len = info.mws.len() as u16;
|
||||
loop {
|
||||
if info.count == len {
|
||||
let reply = handler.borrow_mut().handle(info.req.clone());
|
||||
let reply = unsafe{&mut *hnd.get()}.handle(info.req.clone(), htype);
|
||||
return WaitingResponse::init(info, reply)
|
||||
} else {
|
||||
match info.mws[info.count as usize].start(&mut info.req) {
|
||||
|
@ -234,7 +244,7 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
|||
match fut.poll() {
|
||||
Ok(Async::NotReady) =>
|
||||
return PipelineState::Starting(StartMiddlewares {
|
||||
hnd: handler,
|
||||
hnd, htype,
|
||||
fut: Some(fut),
|
||||
_s: PhantomData}),
|
||||
Ok(Async::Ready(resp)) => {
|
||||
|
@ -264,7 +274,8 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
|
|||
return Some(RunMiddlewares::init(info, resp));
|
||||
}
|
||||
if info.count == len {
|
||||
let reply = (*self.hnd.borrow_mut()).handle(info.req.clone());
|
||||
let reply = unsafe{
|
||||
&mut *self.hnd.get()}.handle(info.req.clone(), self.htype);
|
||||
return Some(WaitingResponse::init(info, reply));
|
||||
} else {
|
||||
loop {
|
||||
|
|
|
@ -12,7 +12,6 @@ use resource::ResourceHandler;
|
|||
use httprequest::HttpRequest;
|
||||
use server::ServerSettings;
|
||||
|
||||
|
||||
/// Interface for application router.
|
||||
pub struct Router(Rc<Inner>);
|
||||
|
||||
|
@ -68,7 +67,7 @@ impl Router {
|
|||
pub(crate) fn get_resource(&self, idx: usize) -> &Resource {
|
||||
&self.0.patterns[idx]
|
||||
}
|
||||
|
||||
|
||||
/// Query for matched resource
|
||||
pub fn recognize<S>(&self, req: &mut HttpRequest<S>) -> Option<usize> {
|
||||
if self.0.prefix_len > req.path().len() {
|
||||
|
@ -127,7 +126,6 @@ impl Clone for Router {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum PatternElement {
|
||||
Str(String),
|
||||
|
@ -140,26 +138,27 @@ enum PatternType {
|
|||
Dynamic(Regex, Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
/// Resource type
|
||||
pub enum ResourceType {
|
||||
/// Normal resource
|
||||
Normal,
|
||||
/// Resource for applicaiton default handler
|
||||
Default,
|
||||
/// External resource
|
||||
External,
|
||||
/// Unknown resource type
|
||||
Unset,
|
||||
}
|
||||
|
||||
/// Reslource type describes an entry in resources table
|
||||
#[derive(Clone)]
|
||||
pub struct Resource {
|
||||
tp: PatternType,
|
||||
rtp: ResourceType,
|
||||
name: String,
|
||||
pattern: String,
|
||||
elements: Vec<PatternElement>,
|
||||
external: bool,
|
||||
}
|
||||
|
||||
impl Default for Resource {
|
||||
fn default() -> Resource {
|
||||
Resource {
|
||||
tp: PatternType::Static("".to_owned()),
|
||||
name: "".to_owned(),
|
||||
pattern: "".to_owned(),
|
||||
elements: Vec::new(),
|
||||
external: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resource {
|
||||
|
@ -175,10 +174,21 @@ impl Resource {
|
|||
/// Panics if path pattern is wrong.
|
||||
pub fn external(name: &str, path: &str) -> Self {
|
||||
let mut resource = Resource::with_prefix(name, path, "/");
|
||||
resource.external = true;
|
||||
resource.rtp = ResourceType::External;
|
||||
resource
|
||||
}
|
||||
|
||||
/// Unset resource type
|
||||
pub(crate) fn unset() -> Resource {
|
||||
Resource {
|
||||
tp: PatternType::Static("".to_owned()),
|
||||
rtp: ResourceType::Unset,
|
||||
name: "".to_owned(),
|
||||
pattern: "".to_owned(),
|
||||
elements: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse path pattern and create new `Resource` instance with custom prefix
|
||||
pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self {
|
||||
let (pattern, elements, is_dynamic) = Resource::parse(path, prefix);
|
||||
|
@ -200,8 +210,8 @@ impl Resource {
|
|||
tp,
|
||||
elements,
|
||||
name: name.into(),
|
||||
rtp: ResourceType::Normal,
|
||||
pattern: path.to_owned(),
|
||||
external: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,6 +220,11 @@ impl Resource {
|
|||
&self.name
|
||||
}
|
||||
|
||||
/// Resource type
|
||||
pub fn rtype(&self) -> ResourceType {
|
||||
self.rtp
|
||||
}
|
||||
|
||||
/// Path pattern of the resource
|
||||
pub fn pattern(&self) -> &str {
|
||||
&self.pattern
|
||||
|
@ -253,7 +268,7 @@ impl Resource {
|
|||
I: AsRef<str>,
|
||||
{
|
||||
let mut iter = elements.into_iter();
|
||||
let mut path = if !self.external {
|
||||
let mut path = if self.rtp != ResourceType::External {
|
||||
format!("{}/", router.prefix())
|
||||
} else {
|
||||
String::new()
|
||||
|
|
Loading…
Reference in a new issue