mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-24 16:02:59 +01:00
Merge branch 'master' of github.com:actix/actix-web
This commit is contained in:
commit
566b16c1f7
@ -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.
|
||||||
|
@ -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"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
src/error.rs
24
src/error.rs
@ -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)]
|
||||||
|
@ -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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user