1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-24 07:53:00 +01:00

Merge branch 'master' of github.com:actix/actix-web

This commit is contained in:
Nikolay Kim 2018-06-14 11:42:27 +02:00
commit 566b16c1f7
4 changed files with 215 additions and 23 deletions

View File

@ -4,6 +4,8 @@
### Added ### Added
* Add `HttpMessage::readlines()` for reading line by line.
* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests. * Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests.
* Add method to configure custom error handler to Form extractor. * Add method to configure custom error handler to Form extractor.

View File

@ -192,20 +192,7 @@ impl App<()> {
/// Create application with empty state. Application can /// Create application with empty state. Application can
/// be configured with a builder-like pattern. /// be configured with a builder-like pattern.
pub fn new() -> App<()> { pub fn new() -> App<()> {
App { App::with_state(())
parts: Some(ApplicationParts {
state: (),
prefix: "/".to_owned(),
settings: ServerSettings::default(),
default: ResourceHandler::default_not_found(),
resources: Vec::new(),
handlers: Vec::new(),
external: HashMap::new(),
encoding: ContentEncoding::Auto,
filters: Vec::new(),
middlewares: Vec::new(),
}),
}
} }
} }
@ -737,7 +724,7 @@ impl<S: 'static> Iterator for App<S> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use body::{Body, Binary}; use body::{Binary, Body};
use http::StatusCode; use http::StatusCode;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -811,7 +798,9 @@ mod tests {
#[test] #[test]
fn test_handler() { fn test_handler() {
let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish(); let mut app = App::new()
.handler("/test", |_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req); let resp = app.run(req);
@ -836,7 +825,9 @@ mod tests {
#[test] #[test]
fn test_handler2() { fn test_handler2() {
let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish(); let mut app = App::new()
.handler("test", |_| HttpResponse::Ok())
.finish();
let req = TestRequest::with_uri("/test").finish(); let req = TestRequest::with_uri("/test").finish();
let resp = app.run(req); let resp = app.run(req);
@ -890,21 +881,29 @@ mod tests {
#[test] #[test]
fn test_route() { fn test_route() {
let mut app = App::new() let mut app = App::new()
.route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) .route("/test", Method::GET, |_: HttpRequest| {
HttpResponse::Ok()
})
.route("/test", Method::POST, |_: HttpRequest| { .route("/test", Method::POST, |_: HttpRequest| {
HttpResponse::Created() HttpResponse::Created()
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/test").method(Method::GET).finish(); let req = TestRequest::with_uri("/test")
.method(Method::GET)
.finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
let req = TestRequest::with_uri("/test").method(Method::POST).finish(); let req = TestRequest::with_uri("/test")
.method(Method::POST)
.finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
let req = TestRequest::with_uri("/test").method(Method::HEAD).finish(); let req = TestRequest::with_uri("/test")
.method(Method::HEAD)
.finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
} }
@ -973,6 +972,9 @@ mod tests {
let req = TestRequest::with_uri("/some").finish(); let req = TestRequest::with_uri("/some").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); assert_eq!(
resp.as_msg().body(),
&Body::Binary(Binary::Slice(b"some"))
);
} }
} }

View File

@ -590,6 +590,30 @@ impl From<JsonError> for JsonPayloadError {
} }
} }
/// Error type returned when reading body as lines.
pub enum ReadlinesError {
/// Error when decoding a line.
EncodingError,
/// Payload error.
PayloadError(PayloadError),
/// Line limit exceeded.
LimitOverflow,
/// ContentType error.
ContentTypeError(ContentTypeError),
}
impl From<PayloadError> for ReadlinesError {
fn from(err: PayloadError) -> Self {
ReadlinesError::PayloadError(err)
}
}
impl From<ContentTypeError> for ReadlinesError {
fn from(err: ContentTypeError) -> Self {
ReadlinesError::ContentTypeError(err)
}
}
/// Errors which can occur when attempting to interpret a segment string as a /// Errors which can occur when attempting to interpret a segment string as a
/// valid path segment. /// valid path segment.
#[derive(Fail, Debug, PartialEq)] #[derive(Fail, Debug, PartialEq)]

View File

@ -3,7 +3,7 @@ use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label; use encoding::label::encoding_from_whatwg_label;
use encoding::types::{DecoderTrap, Encoding}; use encoding::types::{DecoderTrap, Encoding};
use encoding::EncodingRef; use encoding::EncodingRef;
use futures::{Future, Poll, Stream}; use futures::{Future, Poll, Stream, Async};
use http::{header, HeaderMap}; use http::{header, HeaderMap};
use http_range::HttpRange; use http_range::HttpRange;
use mime::Mime; use mime::Mime;
@ -13,6 +13,7 @@ use std::str;
use error::{ use error::{
ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError, ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError,
ReadlinesError
}; };
use header::Header; use header::Header;
use json::JsonBody; use json::JsonBody;
@ -260,6 +261,143 @@ pub trait HttpMessage {
let boundary = Multipart::boundary(self.headers()); let boundary = Multipart::boundary(self.headers());
Multipart::new(boundary, self) Multipart::new(boundary, self)
} }
/// Return stream of lines.
fn readlines(self) -> Readlines<Self>
where
Self: Stream<Item = Bytes, Error = PayloadError> + Sized,
{
Readlines::new(self)
}
}
/// Stream to read request line by line.
pub struct Readlines<T>
where
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
{
req: T,
buff: BytesMut,
limit: usize,
checked_buff: bool,
}
impl<T> Readlines<T>
where
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
{
/// Create a new stream to read request line by line.
fn new(req: T) -> Self {
Readlines {
req,
buff: BytesMut::with_capacity(262_144),
limit: 262_144,
checked_buff: true,
}
}
/// Change max line size. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
impl<T> Stream for Readlines<T>
where
T: HttpMessage + Stream<Item = Bytes, Error = PayloadError> + 'static,
{
type Item = String;
type Error = ReadlinesError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
let encoding = self.req.encoding()?;
// check if there is a newline in the buffer
if !self.checked_buff {
let mut found: Option<usize> = None;
for (ind, b) in self.buff.iter().enumerate() {
if *b == '\n' as u8 {
found = Some(ind);
break;
}
}
if let Some(ind) = found {
// check if line is longer than limit
if ind+1 > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&self.buff.split_to(ind+1))
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
encoding
.decode(&self.buff.split_to(ind+1), DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
return Ok(Async::Ready(Some(line)));
}
self.checked_buff = true;
}
// poll req for more bytes
match self.req.poll() {
Ok(Async::Ready(Some(mut bytes))) => {
// check if there is a newline in bytes
let mut found: Option<usize> = None;
for (ind, b) in bytes.iter().enumerate() {
if *b == '\n' as u8 {
found = Some(ind);
break;
}
}
if let Some(ind) = found {
// check if line is longer than limit
if ind+1 > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&bytes.split_to(ind+1))
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
encoding
.decode(&bytes.split_to(ind+1), DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
// extend buffer with rest of the bytes;
self.buff.extend_from_slice(&bytes);
self.checked_buff = false;
return Ok(Async::Ready(Some(line)));
}
self.buff.extend_from_slice(&bytes);
Ok(Async::NotReady)
},
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
if self.buff.len() == 0 {
return Ok(Async::Ready(None));
}
if self.buff.len() > self.limit {
return Err(ReadlinesError::LimitOverflow);
}
let enc: *const Encoding = encoding as *const Encoding;
let line = if enc == UTF_8 {
str::from_utf8(&self.buff)
.map_err(|_| ReadlinesError::EncodingError)?
.to_owned()
} else {
encoding
.decode(&self.buff, DecoderTrap::Strict)
.map_err(|_| ReadlinesError::EncodingError)?
};
self.buff.clear();
return Ok(Async::Ready(Some(line)))
},
Err(e) => Err(ReadlinesError::from(e)),
}
}
} }
/// Future that resolves to a complete http message body. /// Future that resolves to a complete http message body.
@ -667,4 +805,30 @@ mod tests {
_ => unreachable!("error"), _ => unreachable!("error"),
} }
} }
#[test]
fn test_readlines() {
let mut req = HttpRequest::default();
req.payload_mut().unread_data(Bytes::from_static(
b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\
industry. Lorem Ipsum has been the industry's standard dummy\n\
Contrary to popular belief, Lorem Ipsum is not simply random text."
));
let mut r = Readlines::new(req);
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(s,
"Lorem Ipsum is simply dummy text of the printing and typesetting\n"),
_ => unreachable!("error"),
}
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(s,
"industry. Lorem Ipsum has been the industry's standard dummy\n"),
_ => unreachable!("error"),
}
match r.poll().ok().unwrap() {
Async::Ready(Some(s)) => assert_eq!(s,
"Contrary to popular belief, Lorem Ipsum is not simply random text."),
_ => unreachable!("error"),
}
}
} }