From a7bf635158105f54758cf085ec27e380211a7e2a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 27 Feb 2018 15:03:28 -0800 Subject: [PATCH] unify headers and body processing for client response and server request --- src/client/mod.rs | 2 +- src/client/pipeline.rs | 1 + src/client/response.rs | 331 +-------------------- src/helpers.rs | 54 ++-- src/httpmessage.rs | 595 ++++++++++++++++++++++++++++++++++++++ src/httprequest.rs | 600 ++------------------------------------- src/info.rs | 1 + src/json.rs | 28 +- src/lib.rs | 4 +- src/middleware/cors.rs | 1 + src/middleware/logger.rs | 1 + src/pred.rs | 1 + src/server/encoding.rs | 4 +- src/server/h1.rs | 1 + src/server/h1writer.rs | 4 +- src/server/h2.rs | 1 + src/server/h2writer.rs | 4 +- src/server/mod.rs | 4 +- src/server/settings.rs | 4 +- src/ws/client.rs | 1 + src/ws/mod.rs | 1 + 21 files changed, 699 insertions(+), 944 deletions(-) create mode 100644 src/httpmessage.rs diff --git a/src/client/mod.rs b/src/client/mod.rs index f7b735437..f3d8172ba 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -8,7 +8,7 @@ mod writer; pub use self::pipeline::{SendRequest, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::{ClientResponse, ResponseBody, JsonResponse, UrlEncoded}; +pub use self::response::ClientResponse; pub use self::connector::{Connect, Connection, ClientConnector, ClientConnectorError}; pub(crate) use self::writer::HttpClientWriter; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index dfb01bd63..bd35d975b 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -10,6 +10,7 @@ use error::Error; use body::{Body, BodyStream}; use context::{Frame, ActorHttpContext}; use headers::ContentEncoding; +use httpmessage::HttpMessage; use error::PayloadError; use server::WriterState; use server::shared::SharedBytes; diff --git a/src/client/response.rs b/src/client/response.rs index 4bb7c2d66..0f997dcda 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,20 +1,15 @@ use std::{fmt, str}; use std::rc::Rc; use std::cell::UnsafeCell; -use std::collections::HashMap; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use cookie::Cookie; -use futures::{Async, Future, Poll, Stream}; +use futures::{Async, Poll, Stream}; use http::{HeaderMap, StatusCode, Version}; use http::header::{self, HeaderValue}; -use mime::Mime; -use serde_json; -use serde::de::DeserializeOwned; -use url::form_urlencoded; -// use multipart::Multipart; -use error::{CookieParseError, ParseError, PayloadError, JsonPayloadError, UrlencodedError}; +use httpmessage::HttpMessage; +use error::{CookieParseError, PayloadError}; use super::pipeline::Pipeline; @@ -41,6 +36,14 @@ impl Default for ClientMessage { /// An HTTP Client response pub struct ClientResponse(Rc>, Option>); +impl HttpMessage for ClientResponse { + /// Get the headers from the response. + #[inline] + fn headers(&self) -> &HeaderMap { + &self.as_ref().headers + } +} + impl ClientResponse { pub(crate) fn new(msg: ClientMessage) -> ClientResponse { @@ -68,12 +71,6 @@ impl ClientResponse { self.as_ref().version } - /// Get the headers from the response. - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.as_ref().headers - } - /// Get a mutable reference to the headers. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { @@ -120,83 +117,6 @@ impl ClientResponse { } None } - - /// Read the request content type. If request does not contain - /// *Content-Type* header, empty str get returned. - pub fn content_type(&self) -> &str { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return content_type.split(';').next().unwrap().trim() - } - } - "" - } - - /// Convert the request content type to a known mime type. - pub fn mime_type(&self) -> Option { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return match content_type.parse() { - Ok(mt) => Some(mt), - Err(_) => None - }; - } - } - None - } - - /// Check if request has chunked transfer encoding - pub fn chunked(&self) -> Result { - if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { - if let Ok(s) = encodings.to_str() { - Ok(s.to_lowercase().contains("chunked")) - } else { - Err(ParseError::Header) - } - } else { - Ok(false) - } - } - - /// Load request body. - /// - /// By default only 256Kb payload reads to a memory, then connection get dropped - /// and `PayloadError` get returned. Use `ResponseBody::limit()` - /// method to change upper limit. - pub fn body(self) -> ResponseBody { - ResponseBody::new(self) - } - - // /// Return stream to http payload processes as multipart. - // /// - // /// Content-type: multipart/form-data; - // pub fn multipart(mut self) -> Multipart { - // Multipart::from_response(&mut self) - // } - - /// Parse `application/x-www-form-urlencoded` encoded body. - /// Return `UrlEncoded` future. It resolves to a `HashMap` which - /// contains decoded parameters. - /// - /// Returns error: - /// - /// * content type is not `application/x-www-form-urlencoded` - /// * transfer encoding is `chunked`. - /// * content-length is greater than 256k - pub fn urlencoded(self) -> UrlEncoded { - UrlEncoded::new(self) - } - - /// Parse `application/json` encoded body. - /// Return `JsonResponse` future. It resolves to a `T` value. - /// - /// Returns error: - /// - /// * content type is not `application/json` - /// * content length is greater than 256k - pub fn json(self) -> JsonResponse { - JsonResponse::from_response(self) - } } impl fmt::Debug for ClientResponse { @@ -229,230 +149,3 @@ impl Stream for ClientResponse { } } } - -/// Future that resolves to a complete response body. -#[must_use = "ResponseBody does nothing unless polled"] -pub struct ResponseBody { - limit: usize, - resp: Option, - fut: Option>>, -} - -impl ResponseBody { - - /// Create `ResponseBody` for request. - pub fn new(resp: ClientResponse) -> Self { - ResponseBody { - limit: 262_144, - resp: Some(resp), - fut: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for ResponseBody { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll { - if let Some(resp) = self.resp.take() { - if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > self.limit { - return Err(PayloadError::Overflow); - } - } else { - return Err(PayloadError::Overflow); - } - } - } - let limit = self.limit; - let fut = resp.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(PayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .map(|bytes| bytes.freeze()); - self.fut = Some(Box::new(fut)); - } - - self.fut.as_mut().expect("ResponseBody could not be used second time").poll() - } -} - -/// Client response json parser that resolves to a deserialized `T` value. -/// -/// Returns error: -/// -/// * content type is not `application/json` -/// * content length is greater than 256k -#[must_use = "JsonResponse does nothing unless polled"] -pub struct JsonResponse{ - limit: usize, - ct: &'static str, - resp: Option, - fut: Option>>, -} - -impl JsonResponse { - - /// Create `JsonResponse` for request. - pub fn from_response(resp: ClientResponse) -> Self { - JsonResponse{ - limit: 262_144, - resp: Some(resp), - ct: "application/json", - fut: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set allowed content type. - /// - /// By default *application/json* content type is used. Set content type - /// to empty string if you want to disable content type check. - pub fn content_type(mut self, ct: &'static str) -> Self { - self.ct = ct; - self - } -} - -impl Future for JsonResponse { - type Item = T; - type Error = JsonPayloadError; - - fn poll(&mut self) -> Poll { - if let Some(resp) = self.resp.take() { - if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > self.limit { - return Err(JsonPayloadError::Overflow); - } - } else { - return Err(JsonPayloadError::Overflow); - } - } - } - // check content-type - if !self.ct.is_empty() && resp.content_type() != self.ct { - return Err(JsonPayloadError::ContentType) - } - - let limit = self.limit; - let fut = resp.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(JsonPayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(|body| Ok(serde_json::from_slice::(&body)?)); - - self.fut = Some(Box::new(fut)); - } - - self.fut.as_mut().expect("JsonResponse could not be used second time").poll() - } -} - -/// Future that resolves to a parsed urlencoded values. -#[must_use = "UrlEncoded does nothing unless polled"] -pub struct UrlEncoded { - resp: Option, - limit: usize, - fut: Option, Error=UrlencodedError>>>, -} - -impl UrlEncoded { - pub fn new(resp: ClientResponse) -> UrlEncoded { - UrlEncoded{resp: Some(resp), - limit: 262_144, - fut: None} - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for UrlEncoded { - type Item = HashMap; - type Error = UrlencodedError; - - fn poll(&mut self) -> Poll { - if let Some(resp) = self.resp.take() { - if resp.chunked().unwrap_or(false) { - return Err(UrlencodedError::Chunked) - } else if let Some(len) = resp.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > 262_144 { - return Err(UrlencodedError::Overflow); - } - } else { - return Err(UrlencodedError::UnknownLength); - } - } else { - return Err(UrlencodedError::UnknownLength); - } - } - - // check content type - let mut encoding = false; - if let Some(content_type) = resp.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if content_type.to_lowercase() == "application/x-www-form-urlencoded" { - encoding = true; - } - } - } - if !encoding { - return Err(UrlencodedError::ContentType); - } - - // urlencoded body - let limit = self.limit; - let fut = resp.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(UrlencodedError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(|body| { - let mut m = HashMap::new(); - for (k, v) in form_urlencoded::parse(&body) { - m.insert(k.into(), v.into()); - } - Ok(m) - }); - - self.fut = Some(Box::new(fut)); - } - - self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() - } -} diff --git a/src/helpers.rs b/src/helpers.rs index 25e22b8fe..5f54f48f9 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -8,7 +8,7 @@ use time; use bytes::{BufMut, BytesMut}; use http::Version; -use httprequest::HttpMessage; +use httprequest::HttpInnerMessage; // "Sun, 06 Nov 1994 08:49:37 GMT".len() pub(crate) const DATE_VALUE_LENGTH: usize = 29; @@ -67,7 +67,7 @@ impl fmt::Write for CachedDate { } /// Internal use only! unsafe -pub(crate) struct SharedMessagePool(RefCell>>); +pub(crate) struct SharedMessagePool(RefCell>>); impl SharedMessagePool { pub fn new() -> SharedMessagePool { @@ -75,16 +75,16 @@ impl SharedMessagePool { } #[inline] - pub fn get(&self) -> Rc { + pub fn get(&self) -> Rc { if let Some(msg) = self.0.borrow_mut().pop_front() { msg } else { - Rc::new(HttpMessage::default()) + Rc::new(HttpInnerMessage::default()) } } #[inline] - pub fn release(&self, mut msg: Rc) { + pub fn release(&self, mut msg: Rc) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { Rc::get_mut(&mut msg).unwrap().reset(); @@ -93,10 +93,10 @@ impl SharedMessagePool { } } -pub(crate) struct SharedHttpMessage( - Option>, Option>); +pub(crate) struct SharedHttpInnerMessage( + Option>, Option>); -impl Drop for SharedHttpMessage { +impl Drop for SharedHttpInnerMessage { fn drop(&mut self) { if let Some(ref pool) = self.1 { if let Some(msg) = self.0.take() { @@ -108,56 +108,56 @@ impl Drop for SharedHttpMessage { } } -impl Deref for SharedHttpMessage { - type Target = HttpMessage; +impl Deref for SharedHttpInnerMessage { + type Target = HttpInnerMessage; - fn deref(&self) -> &HttpMessage { + fn deref(&self) -> &HttpInnerMessage { self.get_ref() } } -impl DerefMut for SharedHttpMessage { +impl DerefMut for SharedHttpInnerMessage { - fn deref_mut(&mut self) -> &mut HttpMessage { + fn deref_mut(&mut self) -> &mut HttpInnerMessage { self.get_mut() } } -impl Clone for SharedHttpMessage { +impl Clone for SharedHttpInnerMessage { - fn clone(&self) -> SharedHttpMessage { - SharedHttpMessage(self.0.clone(), self.1.clone()) + fn clone(&self) -> SharedHttpInnerMessage { + SharedHttpInnerMessage(self.0.clone(), self.1.clone()) } } -impl Default for SharedHttpMessage { +impl Default for SharedHttpInnerMessage { - fn default() -> SharedHttpMessage { - SharedHttpMessage(Some(Rc::new(HttpMessage::default())), None) + fn default() -> SharedHttpInnerMessage { + SharedHttpInnerMessage(Some(Rc::new(HttpInnerMessage::default())), None) } } -impl SharedHttpMessage { +impl SharedHttpInnerMessage { - pub fn from_message(msg: HttpMessage) -> SharedHttpMessage { - SharedHttpMessage(Some(Rc::new(msg)), None) + pub fn from_message(msg: HttpInnerMessage) -> SharedHttpInnerMessage { + SharedHttpInnerMessage(Some(Rc::new(msg)), None) } - pub fn new(msg: Rc, pool: Rc) -> SharedHttpMessage { - SharedHttpMessage(Some(msg), Some(pool)) + pub fn new(msg: Rc, pool: Rc) -> SharedHttpInnerMessage { + SharedHttpInnerMessage(Some(msg), Some(pool)) } #[inline(always)] #[allow(mutable_transmutes)] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] - pub fn get_mut(&self) -> &mut HttpMessage { - let r: &HttpMessage = self.0.as_ref().unwrap().as_ref(); + pub fn get_mut(&self) -> &mut HttpInnerMessage { + let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref(); unsafe{mem::transmute(r)} } #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] - pub fn get_ref(&self) -> &HttpMessage { + pub fn get_ref(&self) -> &HttpInnerMessage { self.0.as_ref().unwrap() } } diff --git a/src/httpmessage.rs b/src/httpmessage.rs new file mode 100644 index 000000000..14a551ead --- /dev/null +++ b/src/httpmessage.rs @@ -0,0 +1,595 @@ +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 encoding::all::UTF_8; +use encoding::EncodingRef; +use encoding::label::encoding_from_whatwg_label; +use http::{header, HeaderMap}; + +use json::JsonBody; +use multipart::Multipart; +use error::{ParseError, ContentTypeError, + HttpRangeError, PayloadError, UrlencodedError}; + + +pub trait HttpMessage { + + /// Read the Message Headers. + fn headers(&self) -> &HeaderMap; + + /// Read the request content type. If request does not contain + /// *Content-Type* header, empty str get returned. + fn content_type(&self) -> &str { + if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { + if let Ok(content_type) = content_type.to_str() { + return content_type.split(';').next().unwrap().trim() + } + } + "" + } + + /// Get content type encoding + /// + /// UTF-8 is used by default, If request charset is not set. + fn encoding(&self) -> Result { + if let Some(mime_type) = self.mime_type()? { + if let Some(charset) = mime_type.get_param("charset") { + if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) { + Ok(enc) + } else { + Err(ContentTypeError::UnknownEncoding) + } + } else { + Ok(UTF_8) + } + } else { + Ok(UTF_8) + } + } + + /// Convert the request content type to a known mime type. + fn mime_type(&self) -> Result, ContentTypeError> { + if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { + if let Ok(content_type) = content_type.to_str() { + return match content_type.parse() { + Ok(mt) => Ok(Some(mt)), + Err(_) => Err(ContentTypeError::ParseError), + }; + } else { + return Err(ContentTypeError::ParseError) + } + } + Ok(None) + } + + /// Check if request has chunked transfer encoding + fn chunked(&self) -> Result { + if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { + if let Ok(s) = encodings.to_str() { + Ok(s.to_lowercase().contains("chunked")) + } else { + Err(ParseError::Header) + } + } else { + Ok(false) + } + } + + /// Parses Range HTTP header string as per RFC 2616. + /// `size` is full size of response (file). + fn range(&self, size: u64) -> Result, HttpRangeError> { + if let Some(range) = self.headers().get(header::RANGE) { + HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size) + .map_err(|e| e.into()) + } else { + Ok(Vec::new()) + } + } + + /// Load http message body. + /// + /// By default only 256Kb payload reads to a memory, then `PayloadError::Overflow` + /// get returned. Use `MessageBody::limit()` method to change upper limit. + /// + /// ## Server example + /// + /// ```rust + /// # extern crate bytes; + /// # extern crate actix_web; + /// # extern crate futures; + /// # #[macro_use] extern crate serde_derive; + /// use actix_web::*; + /// use bytes::Bytes; + /// use futures::future::Future; + /// + /// fn index(mut req: HttpRequest) -> Box> { + /// req.body() // <- get Body future + /// .limit(1024) // <- change max size of the body to a 1kb + /// .from_err() + /// .and_then(|bytes: Bytes| { // <- complete body + /// println!("==== BODY ==== {:?}", bytes); + /// Ok(httpcodes::HTTPOk.into()) + /// }).responder() + /// } + /// # fn main() {} + /// ``` + fn body(self) -> MessageBody + where Self: Stream + Sized + { + MessageBody::new(self) + } + + /// Parse `application/x-www-form-urlencoded` encoded body. + /// Return `UrlEncoded` future. It resolves to a `HashMap` which + /// contains decoded parameters. + /// + /// Returns error: + /// + /// * content type is not `application/x-www-form-urlencoded` + /// * transfer encoding is `chunked`. + /// * content-length is greater than 256k + /// + /// ## Server example + /// + /// ```rust + /// # extern crate actix_web; + /// # extern crate futures; + /// use actix_web::*; + /// use futures::future::{Future, ok}; + /// + /// fn index(mut req: HttpRequest) -> Box> { + /// req.urlencoded() // <- get UrlEncoded future + /// .from_err() + /// .and_then(|params| { // <- url encoded parameters + /// println!("==== BODY ==== {:?}", params); + /// ok(httpcodes::HTTPOk.into()) + /// }) + /// .responder() + /// } + /// # fn main() {} + /// ``` + fn urlencoded(self) -> UrlEncoded + where Self: Stream + Sized + { + UrlEncoded::new(self) + } + + /// Parse `application/json` encoded body. + /// Return `JsonBody` future. It resolves to a `T` value. + /// + /// Returns error: + /// + /// * content type is not `application/json` + /// * content length is greater than 256k + /// + /// ## Server example + /// + /// ```rust + /// # extern crate actix_web; + /// # extern crate futures; + /// # #[macro_use] extern crate serde_derive; + /// use actix_web::*; + /// use futures::future::{Future, ok}; + /// + /// #[derive(Deserialize, Debug)] + /// struct MyObj { + /// name: String, + /// } + /// + /// fn index(mut req: HttpRequest) -> Box> { + /// req.json() // <- get JsonBody future + /// .from_err() + /// .and_then(|val: MyObj| { // <- deserialized value + /// println!("==== BODY ==== {:?}", val); + /// Ok(httpcodes::HTTPOk.into()) + /// }).responder() + /// } + /// # fn main() {} + /// ``` + fn json(self) -> JsonBody + where Self: Stream + Sized + { + JsonBody::new(self) + } + + /// Return stream to http payload processes as multipart. + /// + /// Content-type: multipart/form-data; + /// + /// ## Server example + /// + /// ```rust + /// # extern crate actix; + /// # extern crate actix_web; + /// # extern crate env_logger; + /// # extern crate futures; + /// # use std::str; + /// # use actix::*; + /// # use actix_web::*; + /// # use futures::{Future, Stream}; + /// # use futures::future::{ok, result, Either}; + /// fn index(mut req: HttpRequest) -> Box> { + /// req.multipart().from_err() // <- get multipart stream for current request + /// .and_then(|item| match item { // <- iterate over multipart items + /// multipart::MultipartItem::Field(field) => { + /// // Field in turn is stream of *Bytes* object + /// Either::A(field.from_err() + /// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c))) + /// .finish()) + /// }, + /// multipart::MultipartItem::Nested(mp) => { + /// // Or item could be nested Multipart stream + /// Either::B(ok(())) + /// } + /// }) + /// .finish() // <- Stream::finish() combinator from actix + /// .map(|_| httpcodes::HTTPOk.into()) + /// .responder() + /// } + /// # fn main() {} + /// ``` + fn multipart(self) -> Multipart + where Self: Stream + Sized + { + let boundary = Multipart::boundary(self.headers()); + Multipart::new(boundary, self) + } +} + +/// Future that resolves to a complete http message body. +pub struct MessageBody { + limit: usize, + req: Option, + fut: Option>>, +} + +impl MessageBody { + + /// Create `RequestBody` for request. + pub fn new(req: T) -> MessageBody { + MessageBody { + limit: 262_144, + req: Some(req), + fut: None, + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } +} + +impl Future for MessageBody + where T: HttpMessage + Stream + 'static +{ + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll { + if let Some(req) = self.req.take() { + if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + if len > self.limit { + return Err(PayloadError::Overflow); + } + } else { + return Err(PayloadError::UnknownLength); + } + } else { + return Err(PayloadError::UnknownLength); + } + } + + // future + let limit = self.limit; + self.fut = Some(Box::new( + req.from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(PayloadError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .map(|body| body.freeze()) + )); + } + + self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() + } +} + +/// Future that resolves to a parsed urlencoded values. +pub struct UrlEncoded { + req: Option, + limit: usize, + fut: Option, Error=UrlencodedError>>>, +} + +impl UrlEncoded { + pub fn new(req: T) -> UrlEncoded { + UrlEncoded { + req: Some(req), + limit: 262_144, + fut: None, + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } +} + +impl Future for UrlEncoded + where T: HttpMessage + Stream + 'static +{ + type Item = HashMap; + type Error = UrlencodedError; + + fn poll(&mut self) -> Poll { + if let Some(req) = self.req.take() { + if req.chunked().unwrap_or(false) { + return Err(UrlencodedError::Chunked) + } else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + if len > 262_144 { + return Err(UrlencodedError::Overflow); + } + } else { + return Err(UrlencodedError::UnknownLength) + } + } else { + return Err(UrlencodedError::UnknownLength) + } + } + + // check content type + if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { + return Err(UrlencodedError::ContentType) + } + let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?; + + // future + let limit = self.limit; + let fut = req.from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(UrlencodedError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .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()); + } + Ok(m) + }); + self.fut = Some(Box::new(fut)); + } + + self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mime; + use encoding::Encoding; + use encoding::all::ISO_8859_2; + use futures::Async; + use http::{Method, Version, Uri}; + use httprequest::HttpRequest; + use std::str::FromStr; + use std::iter::FromIterator; + use test::TestRequest; + + #[test] + fn test_content_type() { + let req = TestRequest::with_header("content-type", "text/plain").finish(); + assert_eq!(req.content_type(), "text/plain"); + let req = TestRequest::with_header( + "content-type", "application/json; charset=utf=8").finish(); + assert_eq!(req.content_type(), "application/json"); + let req = HttpRequest::default(); + assert_eq!(req.content_type(), ""); + } + + #[test] + fn test_mime_type() { + let req = TestRequest::with_header("content-type", "application/json").finish(); + assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); + let req = HttpRequest::default(); + assert_eq!(req.mime_type().unwrap(), None); + let req = TestRequest::with_header( + "content-type", "application/json; charset=utf-8").finish(); + let mt = req.mime_type().unwrap().unwrap(); + assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8)); + assert_eq!(mt.type_(), mime::APPLICATION); + assert_eq!(mt.subtype(), mime::JSON); + } + + #[test] + fn test_mime_type_error() { + let req = TestRequest::with_header( + "content-type", "applicationadfadsfasdflknadsfklnadsfjson").finish(); + assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); + } + + #[test] + fn test_encoding() { + let req = HttpRequest::default(); + assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); + + let req = TestRequest::with_header( + "content-type", "application/json").finish(); + assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); + + let req = TestRequest::with_header( + "content-type", "application/json; charset=ISO-8859-2").finish(); + assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name()); + } + + #[test] + fn test_encoding_error() { + let req = TestRequest::with_header( + "content-type", "applicatjson").finish(); + assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); + + let req = TestRequest::with_header( + "content-type", "application/json; charset=kkkttktk").finish(); + assert_eq!(Some(ContentTypeError::UnknownEncoding), req.encoding().err()); + } + + #[test] + fn test_no_request_range_header() { + let req = HttpRequest::default(); + let ranges = req.range(100).unwrap(); + assert!(ranges.is_empty()); + } + + #[test] + fn test_request_range_header() { + let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish(); + let ranges = req.range(100).unwrap(); + assert_eq!(ranges.len(), 1); + assert_eq!(ranges[0].start, 0); + assert_eq!(ranges[0].length, 5); + } + + #[test] + fn test_chunked() { + let req = HttpRequest::default(); + assert!(!req.chunked().unwrap()); + + let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); + assert!(req.chunked().unwrap()); + + let mut headers = HeaderMap::new(); + let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())}; + + headers.insert(header::TRANSFER_ENCODING, + header::HeaderValue::from_str(s).unwrap()); + let req = HttpRequest::new( + Method::GET, Uri::from_str("/").unwrap(), + Version::HTTP_11, headers, None); + assert!(req.chunked().is_err()); + } + + impl PartialEq for UrlencodedError { + fn eq(&self, other: &UrlencodedError) -> bool { + match *self { + UrlencodedError::Chunked => match *other { + UrlencodedError::Chunked => true, + _ => false, + }, + UrlencodedError::Overflow => match *other { + UrlencodedError::Overflow => true, + _ => false, + }, + UrlencodedError::UnknownLength => match *other { + UrlencodedError::UnknownLength => true, + _ => false, + }, + UrlencodedError::ContentType => match *other { + UrlencodedError::ContentType => true, + _ => false, + }, + _ => false, + } + } + } + + #[test] + fn test_urlencoded_error() { + let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); + 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); + + 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); + + 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); + } + + #[test] + fn test_urlencoded() { + 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")); + + let result = req.urlencoded().poll().ok().unwrap(); + assert_eq!(result, Async::Ready( + HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())]))); + + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8") + .header(header::CONTENT_LENGTH, "11") + .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())]))); + } + + #[test] + fn test_message_body() { + let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + match req.body().poll().err().unwrap() { + PayloadError::UnknownLength => (), + _ => panic!("error"), + } + + let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); + match req.body().poll().err().unwrap() { + PayloadError::Overflow => (), + _ => panic!("error"), + } + + let mut req = HttpRequest::default(); + req.payload_mut().unread_data(Bytes::from_static(b"test")); + match req.body().poll().ok().unwrap() { + Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), + _ => panic!("error"), + } + + let mut req = HttpRequest::default(); + req.payload_mut().unread_data(Bytes::from_static(b"11111111111111")); + match req.body().limit(5).poll().err().unwrap() { + PayloadError::Overflow => (), + _ => panic!("error"), + } + } +} diff --git a/src/httprequest.rs b/src/httprequest.rs index ca70b6ed0..b6a5fdcba 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -2,18 +2,11 @@ use std::{io, cmp, str, fmt, mem}; use std::rc::Rc; use std::net::SocketAddr; -use std::collections::HashMap; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use cookie::Cookie; -use futures::{Async, Future, Stream, Poll}; -use http_range::HttpRange; -use serde::de::DeserializeOwned; -use mime::Mime; +use futures::{Async, Stream, Poll}; use failure; use url::{Url, form_urlencoded}; -use encoding::all::UTF_8; -use encoding::EncodingRef; -use encoding::label::encoding_from_whatwg_label; use http::{header, Uri, Method, Version, HeaderMap, Extensions}; use tokio_io::AsyncRead; @@ -21,14 +14,12 @@ use info::ConnectionInfo; use param::Params; use router::Router; use payload::Payload; -use json::JsonBody; -use multipart::Multipart; -use helpers::SharedHttpMessage; -use error::{ParseError, ContentTypeError, UrlGenerationError, - CookieParseError, HttpRangeError, PayloadError, UrlencodedError}; +use httpmessage::HttpMessage; +use helpers::SharedHttpInnerMessage; +use error::{UrlGenerationError, CookieParseError, PayloadError}; -pub struct HttpMessage { +pub struct HttpInnerMessage { pub version: Version, pub method: Method, pub uri: Uri, @@ -43,10 +34,10 @@ pub struct HttpMessage { pub info: Option>, } -impl Default for HttpMessage { +impl Default for HttpInnerMessage { - fn default() -> HttpMessage { - HttpMessage { + fn default() -> HttpInnerMessage { + HttpInnerMessage { method: Method::GET, uri: Uri::default(), version: Version::HTTP_11, @@ -63,7 +54,7 @@ impl Default for HttpMessage { } } -impl HttpMessage { +impl HttpInnerMessage { /// Checks if a connection should be kept alive. #[inline] @@ -99,7 +90,7 @@ impl HttpMessage { } /// An HTTP Request -pub struct HttpRequest(SharedHttpMessage, Option>, Option); +pub struct HttpRequest(SharedHttpInnerMessage, Option>, Option); impl HttpRequest<()> { /// Construct a new Request. @@ -108,7 +99,7 @@ impl HttpRequest<()> { version: Version, headers: HeaderMap, payload: Option) -> HttpRequest { HttpRequest( - SharedHttpMessage::from_message(HttpMessage { + SharedHttpInnerMessage::from_message(HttpInnerMessage { method, uri, version, @@ -129,7 +120,7 @@ impl HttpRequest<()> { #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] - pub(crate) fn from_message(msg: SharedHttpMessage) -> HttpRequest { + pub(crate) fn from_message(msg: SharedHttpInnerMessage) -> HttpRequest { HttpRequest(msg, None, None) } @@ -146,6 +137,14 @@ impl HttpRequest<()> { } } + +impl HttpMessage for HttpRequest { + #[inline] + fn headers(&self) -> &HeaderMap { + &self.as_ref().headers + } +} + impl HttpRequest { #[inline] @@ -164,18 +163,18 @@ impl HttpRequest { /// mutable reference should not be returned as result for request's method #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] - pub(crate) fn as_mut(&self) -> &mut HttpMessage { + pub(crate) fn as_mut(&self) -> &mut HttpInnerMessage { self.0.get_mut() } #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] - fn as_ref(&self) -> &HttpMessage { + fn as_ref(&self) -> &HttpInnerMessage { self.0.get_ref() } #[inline] - pub(crate) fn get_inner(&mut self) -> &mut HttpMessage { + pub(crate) fn get_inner(&mut self) -> &mut HttpInnerMessage { self.as_mut() } @@ -219,12 +218,6 @@ impl HttpRequest { self.as_ref().version } - /// Read the Request Headers. - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.as_ref().headers - } - #[doc(hidden)] #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { @@ -381,51 +374,6 @@ impl HttpRequest { self.as_ref().keep_alive() } - /// Read the request content type. If request does not contain - /// *Content-Type* header, empty str get returned. - pub fn content_type(&self) -> &str { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return content_type.split(';').next().unwrap().trim() - } - } - "" - } - - /// Get content type encoding - /// - /// UTF-8 is used by default, If request charset is not set. - pub fn encoding(&self) -> Result { - if let Some(mime_type) = self.mime_type()? { - if let Some(charset) = mime_type.get_param("charset") { - if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) { - Ok(enc) - } else { - Err(ContentTypeError::UnknownEncoding) - } - } else { - Ok(UTF_8) - } - } else { - Ok(UTF_8) - } - } - - /// Convert the request content type to a known mime type. - pub fn mime_type(&self) -> Result, ContentTypeError> { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return match content_type.parse() { - Ok(mt) => Ok(Some(mt)), - Err(_) => Err(ContentTypeError::ParseError), - }; - } else { - return Err(ContentTypeError::ParseError) - } - } - Ok(None) - } - /// Check if request requires connection upgrade pub(crate) fn upgrade(&self) -> bool { if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) { @@ -436,164 +384,6 @@ impl HttpRequest { self.as_ref().method == Method::CONNECT } - /// Check if request has chunked transfer encoding - pub fn chunked(&self) -> Result { - if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { - if let Ok(s) = encodings.to_str() { - Ok(s.to_lowercase().contains("chunked")) - } else { - Err(ParseError::Header) - } - } else { - Ok(false) - } - } - - /// Parses Range HTTP header string as per RFC 2616. - /// `size` is full size of response (file). - pub fn range(&self, size: u64) -> Result, HttpRangeError> { - if let Some(range) = self.headers().get(header::RANGE) { - HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size) - .map_err(|e| e.into()) - } else { - Ok(Vec::new()) - } - } - - /// Load request body. - /// - /// By default only 256Kb payload reads to a memory, then `BAD REQUEST` - /// http response get returns to a peer. Use `RequestBody::limit()` - /// method to change upper limit. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// # #[macro_use] extern crate serde_derive; - /// use actix_web::*; - /// use bytes::Bytes; - /// use futures::future::Future; - /// - /// fn index(mut req: HttpRequest) -> Box> { - /// req.body() // <- get Body future - /// .limit(1024) // <- change max size of the body to a 1kb - /// .from_err() - /// .and_then(|bytes: Bytes| { // <- complete body - /// println!("==== BODY ==== {:?}", bytes); - /// Ok(httpcodes::HTTPOk.into()) - /// }).responder() - /// } - /// # fn main() {} - /// ``` - pub fn body(self) -> RequestBody { - RequestBody::new(self.without_state()) - } - - /// Return stream to http payload processes as multipart. - /// - /// Content-type: multipart/form-data; - /// - /// ```rust - /// # extern crate actix; - /// # extern crate actix_web; - /// # extern crate env_logger; - /// # extern crate futures; - /// # use std::str; - /// # use actix::*; - /// # use actix_web::*; - /// # use futures::{Future, Stream}; - /// # use futures::future::{ok, result, Either}; - /// fn index(mut req: HttpRequest) -> Box> { - /// req.multipart().from_err() // <- get multipart stream for current request - /// .and_then(|item| match item { // <- iterate over multipart items - /// multipart::MultipartItem::Field(field) => { - /// // Field in turn is stream of *Bytes* object - /// Either::A(field.from_err() - /// .map(|c| println!("-- CHUNK: \n{:?}", str::from_utf8(&c))) - /// .finish()) - /// }, - /// multipart::MultipartItem::Nested(mp) => { - /// // Or item could be nested Multipart stream - /// Either::B(ok(())) - /// } - /// }) - /// .finish() // <- Stream::finish() combinator from actix - /// .map(|_| httpcodes::HTTPOk.into()) - /// .responder() - /// } - /// # fn main() {} - /// ``` - pub fn multipart(self) -> Multipart> { - let boundary = Multipart::boundary(self.headers()); - Multipart::new(boundary, self) - } - - /// Parse `application/x-www-form-urlencoded` encoded body. - /// Return `UrlEncoded` future. It resolves to a `HashMap` which - /// contains decoded parameters. - /// - /// Returns error: - /// - /// * content type is not `application/x-www-form-urlencoded` - /// * transfer encoding is `chunked`. - /// * content-length is greater than 256k - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// use actix_web::*; - /// use futures::future::{Future, ok}; - /// - /// fn index(mut req: HttpRequest) -> Box> { - /// req.urlencoded() // <- get UrlEncoded future - /// .from_err() - /// .and_then(|params| { // <- url encoded parameters - /// println!("==== BODY ==== {:?}", params); - /// ok(httpcodes::HTTPOk.into()) - /// }) - /// .responder() - /// } - /// # fn main() {} - /// ``` - pub fn urlencoded(self) -> UrlEncoded { - UrlEncoded::new(self.without_state()) - } - - /// Parse `application/json` encoded body. - /// Return `JsonBody` future. It resolves to a `T` value. - /// - /// Returns error: - /// - /// * content type is not `application/json` - /// * content length is greater than 256k - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// # #[macro_use] extern crate serde_derive; - /// use actix_web::*; - /// use futures::future::{Future, ok}; - /// - /// #[derive(Deserialize, Debug)] - /// struct MyObj { - /// name: String, - /// } - /// - /// fn index(mut req: HttpRequest) -> Box> { - /// req.json() // <- get JsonBody future - /// .from_err() - /// .and_then(|val: MyObj| { // <- deserialized value - /// println!("==== BODY ==== {:?}", val); - /// Ok(httpcodes::HTTPOk.into()) - /// }).responder() - /// } - /// # fn main() {} - /// ``` - pub fn json(self) -> JsonBody { - JsonBody::from_request(self) - } - #[cfg(test)] pub(crate) fn payload(&self) -> &Payload { let msg = self.as_mut(); @@ -617,7 +407,7 @@ impl Default for HttpRequest<()> { /// Construct default request fn default() -> HttpRequest { - HttpRequest(SharedHttpMessage::default(), None, None) + HttpRequest(SharedHttpInnerMessage::default(), None, None) } } @@ -700,158 +490,10 @@ impl fmt::Debug for HttpRequest { } } -/// Future that resolves to a parsed urlencoded values. -pub struct UrlEncoded { - req: Option>, - limit: usize, - fut: Option, Error=UrlencodedError>>>, -} - -impl UrlEncoded { - pub fn new(req: HttpRequest) -> UrlEncoded { - UrlEncoded { - req: Some(req), - limit: 262_144, - fut: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for UrlEncoded { - type Item = HashMap; - type Error = UrlencodedError; - - fn poll(&mut self) -> Poll { - if let Some(req) = self.req.take() { - if req.chunked().unwrap_or(false) { - return Err(UrlencodedError::Chunked) - } else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > 262_144 { - return Err(UrlencodedError::Overflow); - } - } else { - return Err(UrlencodedError::UnknownLength) - } - } else { - return Err(UrlencodedError::UnknownLength) - } - } - - // check content type - if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { - return Err(UrlencodedError::ContentType) - } - let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?; - - // future - let limit = self.limit; - let fut = req.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(UrlencodedError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .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()); - } - Ok(m) - }); - self.fut = Some(Box::new(fut)); - } - - self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() - } -} - -/// Future that resolves to a complete request body. -pub struct RequestBody { - limit: usize, - req: Option>, - fut: Option>>, -} - -impl RequestBody { - - /// Create `RequestBody` for request. - pub fn new(req: HttpRequest) -> RequestBody { - RequestBody { - limit: 262_144, - req: Some(req), - fut: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for RequestBody { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll { - if let Some(req) = self.req.take() { - if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > self.limit { - return Err(PayloadError::Overflow); - } - } else { - return Err(PayloadError::UnknownLength); - } - } else { - return Err(PayloadError::UnknownLength); - } - } - - // future - let limit = self.limit; - self.fut = Some(Box::new( - req.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(PayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .map(|body| body.freeze()) - )); - } - - self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() - } -} - #[cfg(test)] mod tests { use super::*; - use mime; - use encoding::Encoding; - use encoding::all::ISO_8859_2; use http::{Uri, HttpTryFrom}; - use std::str::FromStr; - use std::iter::FromIterator; use router::Pattern; use resource::Resource; use test::TestRequest; @@ -864,63 +506,6 @@ mod tests { assert!(dbg.contains("HttpRequest")); } - #[test] - fn test_content_type() { - let req = TestRequest::with_header("content-type", "text/plain").finish(); - assert_eq!(req.content_type(), "text/plain"); - let req = TestRequest::with_header( - "content-type", "application/json; charset=utf=8").finish(); - assert_eq!(req.content_type(), "application/json"); - let req = HttpRequest::default(); - assert_eq!(req.content_type(), ""); - } - - #[test] - fn test_mime_type() { - let req = TestRequest::with_header("content-type", "application/json").finish(); - assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); - let req = HttpRequest::default(); - assert_eq!(req.mime_type().unwrap(), None); - let req = TestRequest::with_header( - "content-type", "application/json; charset=utf-8").finish(); - let mt = req.mime_type().unwrap().unwrap(); - assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8)); - assert_eq!(mt.type_(), mime::APPLICATION); - assert_eq!(mt.subtype(), mime::JSON); - } - - #[test] - fn test_mime_type_error() { - let req = TestRequest::with_header( - "content-type", "applicationadfadsfasdflknadsfklnadsfjson").finish(); - assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); - } - - #[test] - fn test_encoding() { - let req = HttpRequest::default(); - assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - - let req = TestRequest::with_header( - "content-type", "application/json").finish(); - assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - - let req = TestRequest::with_header( - "content-type", "application/json; charset=ISO-8859-2").finish(); - assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name()); - } - - #[test] - fn test_encoding_error() { - let req = TestRequest::with_header( - "content-type", "applicatjson").finish(); - assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); - - let req = TestRequest::with_header( - "content-type", "application/json; charset=kkkttktk").finish(); - assert_eq!(Some(ContentTypeError::UnknownEncoding), req.encoding().err()); - } - #[test] fn test_uri_mut() { let mut req = HttpRequest::default(); @@ -958,22 +543,6 @@ mod tests { assert!(cookie.is_none()); } - #[test] - fn test_no_request_range_header() { - let req = HttpRequest::default(); - let ranges = req.range(100).unwrap(); - assert!(ranges.is_empty()); - } - - #[test] - fn test_request_range_header() { - let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish(); - let ranges = req.range(100).unwrap(); - assert_eq!(ranges.len(), 1); - assert_eq!(ranges[0].start, 0); - assert_eq!(ranges[0].length, 5); - } - #[test] fn test_request_query() { let req = TestRequest::with_uri("/?id=test").finish(); @@ -996,125 +565,6 @@ mod tests { assert_eq!(req.match_info().get("key"), Some("value")); } - #[test] - fn test_chunked() { - let req = HttpRequest::default(); - assert!(!req.chunked().unwrap()); - - let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); - assert!(req.chunked().unwrap()); - - let mut headers = HeaderMap::new(); - let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())}; - - headers.insert(header::TRANSFER_ENCODING, - header::HeaderValue::from_str(s).unwrap()); - let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); - assert!(req.chunked().is_err()); - } - - impl PartialEq for UrlencodedError { - fn eq(&self, other: &UrlencodedError) -> bool { - match *self { - UrlencodedError::Chunked => match *other { - UrlencodedError::Chunked => true, - _ => false, - }, - UrlencodedError::Overflow => match *other { - UrlencodedError::Overflow => true, - _ => false, - }, - UrlencodedError::UnknownLength => match *other { - UrlencodedError::UnknownLength => true, - _ => false, - }, - UrlencodedError::ContentType => match *other { - UrlencodedError::ContentType => true, - _ => false, - }, - _ => false, - } - } - } - - #[test] - fn test_urlencoded_error() { - let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); - 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); - - 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); - - 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); - } - - #[test] - fn test_urlencoded() { - 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")); - - let result = req.urlencoded().poll().ok().unwrap(); - assert_eq!(result, Async::Ready( - HashMap::from_iter(vec![("hello".to_owned(), "world".to_owned())]))); - - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8") - .header(header::CONTENT_LENGTH, "11") - .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())]))); -} - - #[test] - fn test_request_body() { - let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); - match req.body().poll().err().unwrap() { - PayloadError::UnknownLength => (), - _ => panic!("error"), - } - - let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); - match req.body().poll().err().unwrap() { - PayloadError::Overflow => (), - _ => panic!("error"), - } - - let mut req = HttpRequest::default(); - req.payload_mut().unread_data(Bytes::from_static(b"test")); - match req.body().poll().ok().unwrap() { - Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), - _ => panic!("error"), - } - - let mut req = HttpRequest::default(); - req.payload_mut().unread_data(Bytes::from_static(b"11111111111111")); - match req.body().limit(5).poll().err().unwrap() { - PayloadError::Overflow => (), - _ => panic!("error"), - } - } - #[test] fn test_url_for() { let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") diff --git a/src/info.rs b/src/info.rs index 45bd4fe6a..6177cd021 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,5 +1,6 @@ use std::str::FromStr; use http::header::{self, HeaderName}; +use httpmessage::HttpMessage; use httprequest::HttpRequest; const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR"; diff --git a/src/json.rs b/src/json.rs index 341bc32dd..56b2a46aa 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,4 +1,4 @@ -use bytes::BytesMut; +use bytes::{Bytes, BytesMut}; use futures::{Poll, Future, Stream}; use http::header::CONTENT_LENGTH; @@ -6,8 +6,9 @@ use serde_json; use serde::Serialize; use serde::de::DeserializeOwned; -use error::{Error, JsonPayloadError}; +use error::{Error, JsonPayloadError, PayloadError}; use handler::Responder; +use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -54,6 +55,9 @@ impl Responder for Json { /// * content type is not `application/json` /// * content length is greater than 256k /// +/// +/// # Server example +/// /// ```rust /// # extern crate actix_web; /// # extern crate futures; @@ -76,17 +80,17 @@ impl Responder for Json { /// } /// # fn main() {} /// ``` -pub struct JsonBody{ +pub struct JsonBody{ limit: usize, ct: &'static str, - req: Option>, - fut: Option>>, + req: Option, + fut: Option>>, } -impl JsonBody { +impl JsonBody { /// Create `JsonBody` for request. - pub fn from_request(req: HttpRequest) -> Self { + pub fn new(req: T) -> Self { JsonBody{ limit: 262_144, req: Some(req), @@ -111,11 +115,13 @@ impl JsonBody { } } -impl Future for JsonBody { - type Item = T; +impl Future for JsonBody + where T: HttpMessage + Stream + 'static +{ + type Item = U; type Error = JsonPayloadError; - fn poll(&mut self) -> Poll { + fn poll(&mut self) -> Poll { if let Some(req) = self.req.take() { if let Some(len) = req.headers().get(CONTENT_LENGTH) { if let Ok(s) = len.to_str() { @@ -143,7 +149,7 @@ impl Future for JsonBody { Ok(body) } }) - .and_then(|body| Ok(serde_json::from_slice::(&body)?)); + .and_then(|body| Ok(serde_json::from_slice::(&body)?)); self.fut = Some(Box::new(fut)); } diff --git a/src/lib.rs b/src/lib.rs index 8177dd759..05127e1f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,6 +103,7 @@ mod body; mod context; mod handler; mod helpers; +mod httpmessage; mod httprequest; mod httpresponse; mod info; @@ -128,6 +129,7 @@ pub use error::{Error, Result, ResponseError}; pub use body::{Body, Binary}; pub use json::Json; pub use application::Application; +pub use httpmessage::HttpMessage; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use handler::{Reply, Responder, NormalizePath, AsyncResponder}; @@ -191,6 +193,6 @@ pub mod dev { pub use json::JsonBody; pub use router::{Router, Pattern}; pub use param::{FromParam, Params}; - pub use httprequest::{UrlEncoded, RequestBody}; + pub use httpmessage::{UrlEncoded, MessageBody}; pub use httpresponse::HttpResponseBuilder; } diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 748ab1bba..c949bcc49 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -55,6 +55,7 @@ use http::header::{self, HeaderName, HeaderValue}; use error::{Result, ResponseError}; use resource::Resource; +use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use httpcodes::{HTTPOk, HTTPBadRequest}; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 4907b214c..f5f2e270b 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -8,6 +8,7 @@ use time; use regex::Regex; use error::Result; +use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Started, Finished}; diff --git a/src/pred.rs b/src/pred.rs index 47d906fb0..c84325eef 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use http; use http::{header, HttpTryFrom}; +use httpmessage::HttpMessage; use httprequest::HttpRequest; /// Trait defines resource route predicate. diff --git a/src/server/encoding.rs b/src/server/encoding.rs index c666b7232..694d63a1d 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -16,7 +16,7 @@ use bytes::{Bytes, BytesMut, BufMut, Writer}; use headers::ContentEncoding; use body::{Body, Binary}; use error::PayloadError; -use httprequest::HttpMessage; +use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; use payload::{PayloadSender, PayloadWriter}; @@ -371,7 +371,7 @@ impl ContentEncoder { } pub fn for_server(buf: SharedBytes, - req: &HttpMessage, + req: &HttpInnerMessage, resp: &mut HttpResponse, response_encoding: ContentEncoding) -> ContentEncoder { diff --git a/src/server/h1.rs b/src/server/h1.rs index cb24e6d0f..8fb3a9e97 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -860,6 +860,7 @@ mod tests { use http::{Version, Method}; use super::*; + use httpmessage::HttpMessage; use application::HttpApplication; use server::settings::WorkerSettings; use server::IoStream; diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index da60e220c..80d02f292 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -10,7 +10,7 @@ use http::header::{HeaderValue, CONNECTION, DATE}; use helpers; use body::{Body, Binary}; use headers::ContentEncoding; -use httprequest::HttpMessage; +use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use super::shared::SharedBytes; @@ -98,7 +98,7 @@ impl Writer for H1Writer { } fn start(&mut self, - req: &mut HttpMessage, + req: &mut HttpInnerMessage, msg: &mut HttpResponse, encoding: ContentEncoding) -> io::Result { diff --git a/src/server/h2.rs b/src/server/h2.rs index 5dfcb57ad..97974c88e 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -19,6 +19,7 @@ use tokio_core::reactor::Timeout; use pipeline::Pipeline; use error::PayloadError; use httpcodes::HTTPNotFound; +use httpmessage::HttpMessage; use httprequest::HttpRequest; use payload::{Payload, PayloadWriter}; diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 29b534671..095cd78f2 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -11,7 +11,7 @@ use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE, CONTENT_LEN use helpers; use body::{Body, Binary}; use headers::ContentEncoding; -use httprequest::HttpMessage; +use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; use super::encoding::ContentEncoder; use super::shared::SharedBytes; @@ -111,7 +111,7 @@ impl Writer for H2Writer { self.written } - fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse, encoding: ContentEncoding) + fn start(&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, encoding: ContentEncoding) -> io::Result { // prepare response self.flags.insert(Flags::STARTED); diff --git a/src/server/mod.rs b/src/server/mod.rs index 3769e588e..9f644a1e9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -25,7 +25,7 @@ pub use self::settings::ServerSettings; use body::Binary; use error::Error; use headers::ContentEncoding; -use httprequest::{HttpMessage, HttpRequest}; +use httprequest::{HttpInnerMessage, HttpRequest}; use httpresponse::HttpResponse; /// max buffer size 64k @@ -103,7 +103,7 @@ pub enum WriterState { pub trait Writer { fn written(&self) -> u64; - fn start(&mut self, req: &mut HttpMessage, resp: &mut HttpResponse, encoding: ContentEncoding) + fn start(&mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse, encoding: ContentEncoding) -> io::Result; fn write(&mut self, payload: Binary) -> io::Result; diff --git a/src/server/settings.rs b/src/server/settings.rs index 33e6fa8d1..50be1f7e7 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -103,8 +103,8 @@ impl WorkerSettings { SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes)) } - pub fn get_http_message(&self) -> helpers::SharedHttpMessage { - helpers::SharedHttpMessage::new(self.messages.get(), Rc::clone(&self.messages)) + pub fn get_http_message(&self) -> helpers::SharedHttpInnerMessage { + helpers::SharedHttpInnerMessage::new(self.messages.get(), Rc::clone(&self.messages)) } pub fn add_channel(&self) { diff --git a/src/ws/client.rs b/src/ws/client.rs index 7b7a07419..152391465 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -19,6 +19,7 @@ use actix::prelude::*; use body::{Body, Binary}; use error::UrlParseError; use payload::PayloadHelper; +use httpmessage::HttpMessage; use client::{ClientRequest, ClientRequestBuilder, ClientResponse, ClientConnector, SendRequest, SendRequestError, diff --git a/src/ws/mod.rs b/src/ws/mod.rs index b2f0da3c1..90b2558b7 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -52,6 +52,7 @@ use actix::{Actor, AsyncContext, StreamHandler}; use body::Binary; use payload::PayloadHelper; use error::{Error, PayloadError, ResponseError}; +use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed};