From 220cbe40e56341edc49223bf262fbacd7ef2621c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Fro=C5=82ow?= Date: Mon, 2 Apr 2018 19:10:33 +0200 Subject: [PATCH 01/13] Add header for juniper example --- examples/juniper/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs index 26f63de95..97319afea 100644 --- a/examples/juniper/src/main.rs +++ b/examples/juniper/src/main.rs @@ -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) -> Box Ok(HttpResponse::Ok().body(user)), + Ok(user) => Ok(HttpResponse::Ok().header(CONTENT_TYPE, "application/json").body(user)), Err(_) => Ok(HttpResponse::InternalServerError().into()) } }) From 6c906b08e1df597fed8decbdd63ebd1cd8b8511e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 10:27:37 -0700 Subject: [PATCH 02/13] match resource path before executing middlewares --- src/application.rs | 63 ++++++++++++++++++++++++++++++---------------- src/httprequest.rs | 25 ++++++++++-------- src/pipeline.rs | 31 +++++++++++++++-------- src/router.rs | 53 ++++++++++++++++++++++++-------------- 4 files changed, 111 insertions(+), 61 deletions(-) diff --git a/src/application.rs b/src/application.rs index cf58cc971..38886efc5 100644 --- a/src/application.rs +++ b/src/application.rs @@ -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 { state: Rc, prefix: String, router: Router, - inner: Rc>>, + inner: Rc>>, middlewares: Rc>>>, } @@ -29,7 +29,6 @@ pub(crate) struct Inner { prefix: usize, default: ResourceHandler, encoding: ContentEncoding, - router: Router, resources: Vec>, handlers: Vec<(String, Box>)>, } @@ -40,39 +39,60 @@ impl PipelineHandler for Inner { self.encoding } - fn handle(&mut self, mut req: HttpRequest) -> 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, 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 HttpApplication { + + #[inline] + fn as_ref(&self) -> &Inner { + unsafe{&*self.inner.get()} + } + + #[inline] + fn get_handler(&self, req: &mut HttpRequest) -> 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 HttpApplication { #[cfg(test)] - pub(crate) fn run(&mut self, req: HttpRequest) -> Reply { - self.inner.borrow_mut().handle(req) + pub(crate) fn run(&mut self, mut req: HttpRequest) -> 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 { req.with_state(Rc::clone(&self.state), self.router.clone()) @@ -89,10 +109,10 @@ impl HttpHandler for HttpApplication { 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) } @@ -392,12 +412,11 @@ impl App 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, } diff --git a/src/httprequest.rs b/src/httprequest.rs index 16dea0c8b..00aacb810 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -39,7 +39,13 @@ pub struct HttpInnerMessage { pub addr: Option, pub payload: Option, pub info: Option>, - 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 HttpRequest { /// 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 diff --git a/src/pipeline.rs b/src/pipeline.rs index d8a5dcfb2..842d519ab 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -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 { fn encoding(&self) -> ContentEncoding; - fn handle(&mut self, req: HttpRequest) -> Reply; + fn handle(&mut self, req: HttpRequest, htype: HandlerType) -> Reply; } pub(crate) struct Pipeline(PipelineInfo, PipelineState); @@ -105,7 +112,7 @@ impl> Pipeline { pub fn new(req: HttpRequest, mws: Rc>>>, - handler: Rc>) -> Pipeline + handler: Rc>, htype: HandlerType) -> Pipeline { let mut info = PipelineInfo { req, mws, @@ -113,9 +120,9 @@ impl> Pipeline { 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, Error=Error>>; /// Middlewares start executor struct StartMiddlewares { - hnd: Rc>, + hnd: Rc>, + htype: HandlerType, fut: Option, _s: PhantomData, } impl> StartMiddlewares { - fn init(info: &mut PipelineInfo, handler: Rc>) -> PipelineState { + fn init(info: &mut PipelineInfo, hnd: Rc>, htype: HandlerType) + -> PipelineState + { // 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> StartMiddlewares { 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> StartMiddlewares { 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 { diff --git a/src/router.rs b/src/router.rs index 050520b20..b8e6baf00 100644 --- a/src/router.rs +++ b/src/router.rs @@ -12,7 +12,6 @@ use resource::ResourceHandler; use httprequest::HttpRequest; use server::ServerSettings; - /// Interface for application router. pub struct Router(Rc); @@ -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(&self, req: &mut HttpRequest) -> Option { 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), } +#[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, - 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, { 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() From 8219a7aebe364166645cebe384356b98a49e1540 Mon Sep 17 00:00:00 2001 From: Daniel Holbert Date: Mon, 2 Apr 2018 10:44:46 -0700 Subject: [PATCH 03/13] Use https (not http) url for meritbadge Right now this readme file uses an HTTP url to reference a meritbadge image, which ends up producing "broken https" UI on the crates.io page https://crates.io/crates/actix-web. This patch just upgrades this to an HTTPS url (which still works), to avoid that problem. (Literally a 1-character change, changing "http" to "https" in "http://meritbadge.herokuapp.com/actix-web") --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ccd49e9fc..46f589d6f 100644 --- a/README.md +++ b/README.md @@ -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. From 83bf8521926fdbb5f29622e5784b9d6596f739aa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 11:09:24 -0700 Subject: [PATCH 04/13] Fix logger request duration calculation --- CHANGES.md | 2 ++ examples/hello-world/src/main.rs | 6 +++--- src/middleware/logger.rs | 16 ++++++---------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 80a945f66..267d7a4b3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,8 @@ * Fix client connection pooling +* Fix logger request duration calculation #152 + ## 0.4.10 (2018-03-20) diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index 137be494e..2af478947 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -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()) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 173abd2f8..48a8d3db9 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -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() { From 280c8d87f8f1db713050324b5afd35556a71483b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 11:18:31 -0700 Subject: [PATCH 05/13] expose ResourceType --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 6f202ebbd..6d544d822 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,7 +180,7 @@ pub mod dev { pub use info::ConnectionInfo; pub use handler::{Handler, Reply, FromRequest}; 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}; From cbf4c61eb5da29e9a7d6cad763fa325502748cb4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 14:00:18 -0700 Subject: [PATCH 06/13] add urlencoded body extractor --- CHANGES.md | 2 +- src/de.rs | 2 +- src/handler.rs | 2 +- src/httpmessage.rs | 159 ++++++++++++++++++++++++++++++++++----------- src/json.rs | 3 +- src/lib.rs | 2 +- 6 files changed, 127 insertions(+), 43 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 267d7a4b3..29cc1ae6f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ ## 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` diff --git a/src/de.rs b/src/de.rs index a72d6b5b4..637385a32 100644 --- a/src/de.rs +++ b/src/de.rs @@ -88,7 +88,7 @@ impl DerefMut for Path { } impl Path { - /// Deconstruct to a inner value + /// Deconstruct to an inner value pub fn into_inner(self) -> T { self.inner } diff --git a/src/handler.rs b/src/handler.rs index 855df5353..08e49797b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -373,7 +373,7 @@ impl RouteHandler for AsyncHandler } } -/// Access to an application state +/// Access an application state /// /// `S` - application state type /// diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 47b42dc91..32ccb39f6 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,20 +1,23 @@ use std::str; -use std::collections::HashMap; +use std::ops::{Deref, DerefMut}; 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}; use json::JsonBody; use header::Header; +use handler::FromRequest; use multipart::Multipart; -use error::{ParseError, ContentTypeError, +use httprequest::HttpRequest; +use error::{Error, ParseError, ContentTypeError, HttpRangeError, PayloadError, UrlencodedError}; @@ -137,8 +140,8 @@ pub trait HttpMessage { } /// Parse `application/x-www-form-urlencoded` encoded request's body. - /// Return `UrlEncoded` future. It resolves to a `HashMap` which - /// contains decoded parameters. + /// Return `UrlEncoded` future. Form can be deserialized to any type that implements + /// `Deserialize` trait from *serde*. /// /// Returns error: /// @@ -152,20 +155,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 { - /// 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::>() // <- get UrlEncoded future + /// .from_err() + /// .and_then(|params| { // <- url encoded parameters + /// println!("==== BODY ==== {:?}", params); + /// Ok(HttpResponse::Ok().into()) + /// })) /// } /// # fn main() {} /// ``` - fn urlencoded(self) -> UrlEncoded + fn urlencoded(self) -> UrlEncoded where Self: Stream + Sized { UrlEncoded::new(self) @@ -321,14 +325,14 @@ impl Future for MessageBody } /// Future that resolves to a parsed urlencoded values. -pub struct UrlEncoded { +pub struct UrlEncoded { req: Option, limit: usize, - fut: Option, Error=UrlencodedError>>>, + fut: Option>>, } -impl UrlEncoded { - pub fn new(req: T) -> UrlEncoded { +impl UrlEncoded { + pub fn new(req: T) -> UrlEncoded { UrlEncoded { req: Some(req), limit: 262_144, @@ -343,10 +347,11 @@ impl UrlEncoded { } } -impl Future for UrlEncoded - where T: HttpMessage + Stream + 'static +impl Future for UrlEncoded + where T: HttpMessage + Stream + 'static, + U: DeserializeOwned + 'static { - type Item = HashMap; + type Item = U; type Error = UrlencodedError; fn poll(&mut self) -> Poll { @@ -385,13 +390,16 @@ impl Future for UrlEncoded } }) .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::(&body) + .map_err(|_| UrlencodedError::Parse) + } else { + let body = encoding.decode(&body, DecoderTrap::Strict) + .map_err(|_| UrlencodedError::Parse)?; + serde_urlencoded::from_str::(&body) + .map_err(|_| UrlencodedError::Parse) } - Ok(m) }); self.fut = Some(Box::new(fut)); } @@ -400,6 +408,61 @@ impl Future for UrlEncoded } } +/// 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) -> Result { +/// Ok(format!("Welcome {}!", form.username)) +/// } +/// # fn main() {} +/// ``` +pub struct Form(pub T); + +impl Deref for Form { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for Form { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl FromRequest for Form + where T: DeserializeOwned + 'static, S: 'static +{ + type Result = Box>; + + #[inline] + fn from_request(req: &HttpRequest) -> Self::Result { + Box::new(UrlEncoded::new(req.clone()).from_err().map(Form)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -410,7 +473,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 +591,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::() + .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::() + .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::() + .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::() + .poll().err().unwrap(), UrlencodedError::ContentType); } #[test] @@ -561,9 +632,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::().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 +642,23 @@ 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] + fn test_urlencoded_extractor() { + 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::::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.hello, "world"); + }, + _ => unreachable!(), + } } #[test] diff --git a/src/json.rs b/src/json.rs index ee2c1b80e..63e58fea8 100644 --- a/src/json.rs +++ b/src/json.rs @@ -56,7 +56,7 @@ use httpresponse::HttpResponse; /// username: String, /// } /// -/// /// extract `Info` using serde +/// /// deserialize `Info` from request's body /// fn index(info: Json) -> Result { /// Ok(format!("Welcome {}!", info.username)) /// } @@ -129,7 +129,6 @@ impl FromRequest for Json /// * content type is not `application/json` /// * content length is greater than 256k /// -/// /// # Server example /// /// ```rust diff --git a/src/lib.rs b/src/lib.rs index 6d544d822..70a61d747 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,7 +139,7 @@ pub use body::{Body, Binary}; pub use json::Json; pub use de::{Path, Query}; pub use application::App; -pub use httpmessage::HttpMessage; +pub use httpmessage::{HttpMessage, Form}; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use handler::{Either, Responder, AsyncResponder, FutureResponse, State}; From a6cbdde43f358d82372c87250ebd4bd359e506c4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 14:55:42 -0700 Subject: [PATCH 07/13] add extractor for Binary type; move all extractors to separate module --- src/body.rs | 15 ++ src/de.rs | 282 +-------------------------------- src/extractor.rs | 387 +++++++++++++++++++++++++++++++++++++++++++++ src/httpmessage.rs | 76 +-------- src/lib.rs | 5 +- 5 files changed, 413 insertions(+), 352 deletions(-) create mode 100644 src/extractor.rs diff --git a/src/body.rs b/src/body.rs index 57df35287..97b8850c8 100644 --- a/src/body.rs +++ b/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>; @@ -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 { + Ok(HttpResponse::Ok() + .content_type("application/octet-stream") + .body(self)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/de.rs b/src/de.rs index 637385a32..659dc10a6 100644 --- a/src/de.rs +++ b/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 { -/// 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) -> Result { -/// 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{ - inner: T -} - -impl AsRef for Path { - - fn as_ref(&self) -> &T { - &self.inner - } -} - -impl Deref for Path { - type Target = T; - - fn deref(&self) -> &T { - &self.inner - } -} - -impl DerefMut for Path { - fn deref_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Path { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.inner - } -} - -impl FromRequest for Path - where T: DeserializeOwned, S: 'static -{ - type Result = FutureResult; - - #[inline] - fn from_request(req: &HttpRequest) -> 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) -> 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); - -impl Deref for Query { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Query { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl Query { - /// Deconstruct to a inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl FromRequest for Query - where T: de::DeserializeOwned, S: 'static -{ - type Result = FutureResult; - - #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { - let req = req.clone(); - result(serde_urlencoded::from_str::(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 } +impl<'de, S: 'de> PathDeserializer<'de, S> { + pub fn new(req: &'de HttpRequest) -> 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::::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::::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::::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::>::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::::from_request(&req).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.into_inner(), 32); - }, - _ => unreachable!(), - } - } -} diff --git a/src/extractor.rs b/src/extractor.rs new file mode 100644 index 000000000..dd4511ede --- /dev/null +++ b/src/extractor.rs @@ -0,0 +1,387 @@ +use std::ops::{Deref, DerefMut}; + +use serde_urlencoded; +use serde::de::{self, DeserializeOwned}; +use futures::future::{Future, FutureResult, result}; + +use body::Binary; +use error::Error; +use handler::FromRequest; +use httprequest::HttpRequest; +use 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 { +/// 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) -> Result { +/// 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{ + inner: T +} + +impl AsRef for Path { + + fn as_ref(&self) -> &T { + &self.inner + } +} + +impl Deref for Path { + type Target = T; + + fn deref(&self) -> &T { + &self.inner + } +} + +impl DerefMut for Path { + fn deref_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl Path { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.inner + } +} + +impl FromRequest for Path + where T: DeserializeOwned, S: 'static +{ + type Result = FutureResult; + + #[inline] + fn from_request(req: &HttpRequest) -> 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) -> 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); + +impl Deref for Query { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for Query { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl Query { + /// Deconstruct to a inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl FromRequest for Query + where T: de::DeserializeOwned, S: 'static +{ + type Result = FutureResult; + + #[inline] + fn from_request(req: &HttpRequest) -> Self::Result { + let req = req.clone(); + result(serde_urlencoded::from_str::(req.query_string()) + .map_err(|e| e.into()) + .map(Query)) + } +} + +/// Request payload extractor. +/// +/// Loads request's payload and construct Binary instance. +impl FromRequest for Binary +{ + type Result = Box>; + + #[inline] + fn from_request(req: &HttpRequest) -> Self::Result { + Box::new( + MessageBody::new(req.clone()).from_err().map(|b| b.into())) + } +} + +/// 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) -> Result { +/// Ok(format!("Welcome {}!", form.username)) +/// } +/// # fn main() {} +/// ``` +pub struct Form(pub T); + +impl Deref for Form { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for Form { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl FromRequest for Form + where T: DeserializeOwned + 'static, S: 'static +{ + type Result = Box>; + + #[inline] + fn from_request(req: &HttpRequest) -> Self::Result { + Box::new(UrlEncoded::new(req.clone()).from_err().map(Form)) + } +} + +#[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_binary() { + let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + + match Binary::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s, Binary::from(Bytes::from_static(b"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::::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::::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::::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::::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::>::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::::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.into_inner(), 32); + }, + _ => unreachable!(), + } + } +} diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 32ccb39f6..11d1d087b 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,5 +1,4 @@ use std::str; -use std::ops::{Deref, DerefMut}; use bytes::{Bytes, BytesMut}; use futures::{Future, Stream, Poll}; use http_range::HttpRange; @@ -14,10 +13,8 @@ use http::{header, HeaderMap}; use json::JsonBody; use header::Header; -use handler::FromRequest; use multipart::Multipart; -use httprequest::HttpRequest; -use error::{Error, ParseError, ContentTypeError, +use error::{ParseError, ContentTypeError, HttpRangeError, PayloadError, UrlencodedError}; @@ -408,61 +405,6 @@ impl Future for UrlEncoded } } -/// 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) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// # fn main() {} -/// ``` -pub struct Form(pub T); - -impl Deref for Form { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Form { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl FromRequest for Form - where T: DeserializeOwned + 'static, S: 'static -{ - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { - Box::new(UrlEncoded::new(req.clone()).from_err().map(Form)) - } -} - #[cfg(test)] mod tests { use super::*; @@ -645,22 +587,6 @@ mod tests { assert_eq!(result, Async::Ready(Info{hello: "world".to_owned()})); } - #[test] - fn test_urlencoded_extractor() { - 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::::from_request(&req).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.hello, "world"); - }, - _ => unreachable!(), - } - } - #[test] fn test_message_body() { let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); diff --git a/src/lib.rs b/src/lib.rs index 70a61d747..11f1c00a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,6 +109,7 @@ mod application; mod body; mod context; mod de; +mod extractor; mod handler; mod header; mod helpers; @@ -134,12 +135,12 @@ 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, Form}; +pub use httpmessage::HttpMessage; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use handler::{Either, Responder, AsyncResponder, FutureResponse, State}; From ef6f31006053736e1ebe71bd0bddbfd776b4ad30 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 15:08:49 -0700 Subject: [PATCH 08/13] update urlencoded example in guide --- guide/src/qs_7.md | 18 ++++++++++++------ src/extractor.rs | 1 - 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 886d8b27c..04e6d4263 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -256,8 +256,9 @@ 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` 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`. @@ -268,14 +269,20 @@ The *UrlEncoded* future can resolve into a error in several cases: ```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> { - req.urlencoded() // <- get UrlEncoded future + req.urlencoded::() // <- 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 +290,6 @@ fn index(mut req: HttpRequest) -> Box> { # fn main() {} ``` - ## Streaming request *HttpRequest* is a stream of `Bytes` objects. It can be used to read the request diff --git a/src/extractor.rs b/src/extractor.rs index dd4511ede..0b264589f 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -287,7 +287,6 @@ mod tests { } } - #[derive(Deserialize)] struct MyStruct { key: String, From d292c5023fc4b0391d10ec758d2df47191cbd376 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 16:19:18 -0700 Subject: [PATCH 09/13] add String and Bytes extractor --- guide/src/qs_7.md | 1 - src/error.rs | 7 +++ src/extractor.rs | 117 +++++++++++++++++++++++++++++++++++++--------- src/handler.rs | 16 +++++++ src/json.rs | 94 ++++++++++++++++++------------------- src/lib.rs | 4 +- 6 files changed, 167 insertions(+), 72 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 04e6d4263..fab21a34b 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -265,7 +265,6 @@ a error in several cases: * content-length is greater than 256k * payload terminates with error. - ```rust # extern crate actix_web; # extern crate futures; diff --git a/src/error.rs b/src/error.rs index d709ce590..dc4ae78ec 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 {} diff --git a/src/extractor.rs b/src/extractor.rs index 0b264589f..2346365bc 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,14 +1,17 @@ +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 body::Binary; -use error::Error; -use handler::FromRequest; +use error::{Error, ErrorBadRequest}; +use handler::{Either, FromRequest}; use httprequest::HttpRequest; -use httpmessage::{MessageBody, UrlEncoded}; +use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use de::PathDeserializer; /// Extract typed information from the request's path. @@ -173,20 +176,6 @@ impl FromRequest for Query } } -/// Request payload extractor. -/// -/// Loads request's payload and construct Binary instance. -impl FromRequest for Binary -{ - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { - Box::new( - MessageBody::new(req.clone()).from_err().map(|b| b.into())) - } -} - /// Extract typed information from the request's body. /// /// To extract typed information from request's body, the type `T` must implement the @@ -242,6 +231,79 @@ impl FromRequest for 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 { +/// Ok(format!("Body {:?}!", body)) +/// } +/// # fn main() {} +/// ``` +impl FromRequest for Bytes +{ + type Result = Box>; + + #[inline] + fn from_request(req: &HttpRequest) -> 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 { +/// Ok(format!("Body {}!", body)) +/// } +/// # fn main() {} +/// ``` +impl FromRequest for String +{ + type Result = Either, + Box>>; + + #[inline] + fn from_request(req: &HttpRequest) -> 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::*; @@ -259,13 +321,26 @@ mod tests { } #[test] - fn test_binary() { + 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 Binary::from_request(&req).poll().unwrap() { + match Bytes::from_request(&req).poll().unwrap() { Async::Ready(s) => { - assert_eq!(s, Binary::from(Bytes::from_static(b"hello=world"))); + 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!(), } diff --git a/src/handler.rs b/src/handler.rs index 08e49797b..6041dc288 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -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 Responder for Either } } +impl Future for Either + where A: Future, + B: Future, +{ + type Item = I; + type Error = E; + + fn poll(&mut self) -> Poll { + 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. diff --git a/src/json.rs b/src/json.rs index 63e58fea8..3c8f81e8d 100644 --- a/src/json.rs +++ b/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 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> { -/// 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, -/// } -/// -/// /// deserialize `Info` from request's body -/// fn index(info: Json) -> Result { -/// 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(pub T); impl Deref for Json { @@ -95,6 +47,26 @@ impl fmt::Display for Json where T: fmt::Display { } } +/// The `Json` type allows you to respond with well-formed JSON data: simply +/// return a value of type Json 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> { +/// Ok(Json(MyObj{name: req.match_info().query("name")?})) +/// } +/// # fn main() {} +/// ``` impl Responder for Json { type Item = HttpResponse; type Error = Error; @@ -108,6 +80,32 @@ impl Responder for Json { } } +/// 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) -> Result { +/// 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 FromRequest for Json where T: DeserializeOwned + 'static, S: 'static { diff --git a/src/lib.rs b/src/lib.rs index 11f1c00a3..3e134c441 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,7 +143,7 @@ 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; @@ -179,7 +179,7 @@ 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, ResourceType}; pub use resource::ResourceHandler; From 3b93bff602239ed6a947183292ed35e126df9900 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 21:37:00 -0700 Subject: [PATCH 10/13] add ErrorHandlers middleware --- guide/src/qs_10.md | 34 ++++++++++ src/middleware/errhandlers.rs | 115 ++++++++++++++++++++++++++++++++++ src/middleware/mod.rs | 2 + 3 files changed, 151 insertions(+) create mode 100644 src/middleware/errhandlers.rs diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index c69d26f68..9a4addba1 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -210,3 +210,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(_: &mut HttpRequest, resp: HttpResponse) -> Result { + 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(); +} +``` diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs new file mode 100644 index 000000000..2906e39ed --- /dev/null +++ b/src/middleware/errhandlers.rs @@ -0,0 +1,115 @@ +use std::collections::HashMap; + +use error::Result; +use http::StatusCode; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use middleware::{Middleware, Response}; + + +type ErrorHandler = Fn(&mut HttpRequest, HttpResponse) -> Result; + +/// `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::{ +/// App, HttpRequest, HttpResponse, Result, +/// http, middleware::Response, middleware::ErrorHandlers}; +/// +/// fn render_500(_: &mut HttpRequest, resp: HttpResponse) -> Result { +/// 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 { + handlers: HashMap>>, +} + +impl Default for ErrorHandlers { + fn default() -> Self { + ErrorHandlers { + handlers: HashMap::new(), + } + } +} + +impl ErrorHandlers { + + /// Construct new `ErrorHandlers` instance + pub fn new() -> Self { + ErrorHandlers::default() + } + + /// Register error handler for specified status code + pub fn handler(mut self, status: StatusCode, handler: F) -> Self + where F: Fn(&mut HttpRequest, HttpResponse) -> Result + 'static + { + self.handlers.insert(status, Box::new(handler)); + self + } +} + +impl Middleware for ErrorHandlers { + + fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Result { + 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(_: &mut HttpRequest, resp: HttpResponse) -> Result { + 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)); + } +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 6f85dfeb5..97f3fa2b0 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -10,9 +10,11 @@ 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::errhandlers::ErrorHandlers; pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder}; #[cfg(feature = "session")] From 476b1fb36a40f6a2339c6b170d96e0131f2091d2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 21:43:50 -0700 Subject: [PATCH 11/13] simplify DefaultHeaders middleware --- guide/src/qs_10.md | 5 +- src/middleware/defaultheaders.rs | 89 ++++++++++++++------------------ src/middleware/mod.rs | 2 +- 3 files changed, 41 insertions(+), 55 deletions(-) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 9a4addba1..aaff39ae1 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -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()); diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 068002cad..5399b29d4 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -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(mut self, key: K, value: V) -> Self + where HeaderName: HttpTryFrom, + HeaderValue: HttpTryFrom + { + 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 Middleware for DefaultHeaders { } } -/// Structure that follows the builder pattern for building `DefaultHeaders` middleware. -#[derive(Debug)] -pub struct DefaultHeadersBuilder { - ct: bool, - headers: Option, -} - -impl DefaultHeadersBuilder { - - /// Set a header. - #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))] - pub fn header(&mut self, key: K, value: V) -> &mut Self - where HeaderName: HttpTryFrom, - HeaderValue: HttpTryFrom - { - 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(); diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 97f3fa2b0..8b0503925 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -15,7 +15,7 @@ pub mod cors; pub mod csrf; pub use self::logger::Logger; pub use self::errhandlers::ErrorHandlers; -pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder}; +pub use self::defaultheaders::DefaultHeaders; #[cfg(feature = "session")] pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage, From fee30d6f47840ecbf982f94acbe7f76561a3d6ce Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 22:01:20 -0700 Subject: [PATCH 12/13] fix doc test compatibility --- src/middleware/errhandlers.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 2906e39ed..db3c70f34 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -19,9 +19,8 @@ type ErrorHandler = Fn(&mut HttpRequest, HttpResponse) -> Result /// /// ```rust /// # extern crate actix_web; -/// use actix_web::{ -/// App, HttpRequest, HttpResponse, Result, -/// http, middleware::Response, middleware::ErrorHandlers}; +/// use actix_web::{http, App, HttpRequest, HttpResponse, Result}; +/// use actix_web::middleware::{Response, ErrorHandlers}; /// /// fn render_500(_: &mut HttpRequest, resp: HttpResponse) -> Result { /// let mut builder = resp.into_builder(); From 2a269f111188c28a687eb2146e1b60e1fa33329d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 22:08:04 -0700 Subject: [PATCH 13/13] update changes --- CHANGES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 29cc1ae6f..03f5b5e94 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,7 +9,9 @@ * 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