1
0
Fork 0
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:
Rhys 2018-04-03 14:14:19 +01:00 committed by GitHub
commit 7f9d2471d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 937 additions and 506 deletions

View file

@ -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)

View file

@ -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.

View file

@ -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())

View file

@ -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())
}
})

View file

@ -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();
}
```

View file

@ -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

View file

@ -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,
}

View file

@ -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
View file

@ -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!(),
}
}
}

View file

@ -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
View 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!(),
}
}
}

View file

@ -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
///

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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};

View file

@ -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();

View 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));
}
}

View file

@ -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() {

View file

@ -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,

View file

@ -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 {

View file

@ -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()