mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into dpypin/freeze-client-request
This commit is contained in:
commit
5f0b43da7c
|
@ -1,4 +1,13 @@
|
|||
# Changes
|
||||
## not released yet
|
||||
|
||||
### Added
|
||||
|
||||
* Add `middleware::Conditon` that conditionally enables another middleware
|
||||
|
||||
### Fixed
|
||||
|
||||
* h2 will use error response #1080
|
||||
|
||||
## [1.0.7] - 2019-08-29
|
||||
|
||||
|
|
|
@ -257,8 +257,8 @@ where
|
|||
}
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(_e) => {
|
||||
let res: Response = Response::InternalServerError().finish();
|
||||
Err(e) => {
|
||||
let res: Response = e.into().into();
|
||||
let (res, body) = res.replace_body(());
|
||||
|
||||
let mut send = send.take().unwrap();
|
||||
|
|
|
@ -454,9 +454,9 @@ fn test_h2_service_error() {
|
|||
});
|
||||
|
||||
let response = srv.block_on(srv.sget("/").send()).unwrap();
|
||||
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
|
||||
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
|
||||
|
||||
// read response
|
||||
let bytes = srv.load_body(response).unwrap();
|
||||
assert!(bytes.is_empty());
|
||||
assert_eq!(bytes, Bytes::from_static(b"error"));
|
||||
}
|
||||
|
|
|
@ -449,11 +449,11 @@ fn test_h2_service_error() {
|
|||
});
|
||||
|
||||
let response = srv.block_on(srv.sget("/").send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
// read response
|
||||
let bytes = srv.load_body(response).unwrap();
|
||||
assert!(bytes.is_empty());
|
||||
assert_eq!(bytes, Bytes::from_static(b"error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
# Changes
|
||||
## [0.1.4] - 2019-xx-xx
|
||||
|
||||
* Multipart handling now parses requests which do not end in CRLF #1038
|
||||
|
||||
## [0.1.3] - 2019-08-18
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ impl InnerMultipart {
|
|||
boundary: &str,
|
||||
) -> Result<Option<bool>, MultipartError> {
|
||||
// TODO: need to read epilogue
|
||||
match payload.readline()? {
|
||||
match payload.readline_or_eof()? {
|
||||
None => {
|
||||
if payload.eof {
|
||||
Ok(Some(true))
|
||||
|
@ -176,16 +176,15 @@ impl InnerMultipart {
|
|||
}
|
||||
}
|
||||
Some(chunk) => {
|
||||
if chunk.len() == boundary.len() + 4
|
||||
&& &chunk[..2] == b"--"
|
||||
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
|
||||
{
|
||||
if chunk.len() < boundary.len() + 4
|
||||
|| &chunk[..2] != b"--"
|
||||
|| &chunk[2..boundary.len() + 2] != boundary.as_bytes() {
|
||||
Err(MultipartError::Boundary)
|
||||
} else if &chunk[boundary.len() + 2..] == b"\r\n" {
|
||||
Ok(Some(false))
|
||||
} else if chunk.len() == boundary.len() + 6
|
||||
&& &chunk[..2] == b"--"
|
||||
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
|
||||
&& &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
|
||||
{
|
||||
} else if &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
|
||||
&& (chunk.len() == boundary.len() + 4
|
||||
|| &chunk[boundary.len() + 4..] == b"\r\n") {
|
||||
Ok(Some(true))
|
||||
} else {
|
||||
Err(MultipartError::Boundary)
|
||||
|
@ -779,6 +778,14 @@ impl PayloadBuffer {
|
|||
self.read_until(b"\n")
|
||||
}
|
||||
|
||||
/// Read bytes until new line delimiter or eof
|
||||
pub fn readline_or_eof(&mut self) -> Result<Option<Bytes>, MultipartError> {
|
||||
match self.readline() {
|
||||
Err(MultipartError::Incomplete) if self.eof => Ok(Some(self.buf.take().freeze())),
|
||||
line => line
|
||||
}
|
||||
}
|
||||
|
||||
/// Put unprocessed data back to the buffer
|
||||
pub fn unprocessed(&mut self, data: Bytes) {
|
||||
let buf = BytesMut::from(data);
|
||||
|
@ -849,32 +856,65 @@ mod tests {
|
|||
(tx, rx.map_err(|_| panic!()).and_then(|res| res))
|
||||
}
|
||||
|
||||
fn create_simple_request_with_header() -> (Bytes, HeaderMap) {
|
||||
let bytes = Bytes::from(
|
||||
"testasdadsad\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
|
||||
test\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
|
||||
data\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n"
|
||||
);
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static(
|
||||
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
|
||||
),
|
||||
);
|
||||
(bytes, headers)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multipart_no_end_crlf() {
|
||||
run_on(|| {
|
||||
let (sender, payload) = create_stream();
|
||||
let (bytes, headers) = create_simple_request_with_header();
|
||||
let bytes_stripped = bytes.slice_to(bytes.len()); // strip crlf
|
||||
|
||||
sender.unbounded_send(Ok(bytes_stripped)).unwrap();
|
||||
drop(sender); // eof
|
||||
|
||||
let mut multipart = Multipart::new(&headers, payload);
|
||||
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(Some(_)) => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(Some(_)) => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(None) => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multipart() {
|
||||
run_on(|| {
|
||||
let (sender, payload) = create_stream();
|
||||
let (bytes, headers) = create_simple_request_with_header();
|
||||
|
||||
let bytes = Bytes::from(
|
||||
"testasdadsad\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
|
||||
test\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
|
||||
data\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
|
||||
);
|
||||
sender.unbounded_send(Ok(bytes)).unwrap();
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static(
|
||||
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
|
||||
),
|
||||
);
|
||||
|
||||
let mut multipart = Multipart::new(&headers, payload);
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(Some(mut field)) => {
|
||||
|
@ -925,28 +965,10 @@ mod tests {
|
|||
fn test_stream() {
|
||||
run_on(|| {
|
||||
let (sender, payload) = create_stream();
|
||||
let (bytes, headers) = create_simple_request_with_header();
|
||||
|
||||
let bytes = Bytes::from(
|
||||
"testasdadsad\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\n\r\n\
|
||||
test\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
||||
Content-Type: text/plain; charset=utf-8\r\n\r\n\
|
||||
data\r\n\
|
||||
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
|
||||
);
|
||||
sender.unbounded_send(Ok(bytes)).unwrap();
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static(
|
||||
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
|
||||
),
|
||||
);
|
||||
|
||||
let mut multipart = Multipart::new(&headers, payload);
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(Some(mut field)) => {
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
* Add `FrozenClientRequest` to support retries for sending HTTP requests
|
||||
|
||||
|
||||
## [0.2.5] - 2019-09-06
|
||||
|
||||
### Changed
|
||||
|
||||
* Ensure that the `Host` header is set when initiating a WebSocket client connection.
|
||||
|
||||
|
||||
## [0.2.4] - 2019-08-13
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -233,6 +233,10 @@ impl WebsocketsRequest {
|
|||
return Either::A(err(InvalidUrl::UnknownScheme.into()));
|
||||
}
|
||||
|
||||
if !self.head.headers.contains_key(header::HOST) {
|
||||
self.head.headers.insert(header::HOST, HeaderValue::from_str(uri.host().unwrap()).unwrap());
|
||||
}
|
||||
|
||||
// set cookies
|
||||
if let Some(ref mut jar) = self.cookies {
|
||||
let mut cookie = String::new();
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
//! `Middleware` for conditionally enables another middleware.
|
||||
use actix_service::{Service, Transform};
|
||||
use futures::future::{ok, Either, FutureResult, Map};
|
||||
use futures::{Future, Poll};
|
||||
|
||||
/// `Middleware` for conditionally enables another middleware.
|
||||
/// The controled middleware must not change the `Service` interfaces.
|
||||
/// This means you cannot control such middlewares like `Logger` or `Compress`.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::middleware::{Condition, NormalizePath};
|
||||
/// use actix_web::App;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into());
|
||||
/// let app = App::new()
|
||||
/// .wrap(Condition::new(enable_normalize, NormalizePath));
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Condition<T> {
|
||||
trans: T,
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
impl<T> Condition<T> {
|
||||
pub fn new(enable: bool, trans: T) -> Self {
|
||||
Self { trans, enable }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> Transform<S> for Condition<T>
|
||||
where
|
||||
S: Service,
|
||||
T: Transform<S, Request = S::Request, Response = S::Response, Error = S::Error>,
|
||||
{
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type InitError = T::InitError;
|
||||
type Transform = ConditionMiddleware<T::Transform, S>;
|
||||
type Future = Either<
|
||||
Map<T::Future, fn(T::Transform) -> Self::Transform>,
|
||||
FutureResult<Self::Transform, Self::InitError>,
|
||||
>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
if self.enable {
|
||||
let f = self
|
||||
.trans
|
||||
.new_transform(service)
|
||||
.map(ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform);
|
||||
Either::A(f)
|
||||
} else {
|
||||
Either::B(ok(ConditionMiddleware::Disable(service)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ConditionMiddleware<E, D> {
|
||||
Enable(E),
|
||||
Disable(D),
|
||||
}
|
||||
|
||||
impl<E, D> Service for ConditionMiddleware<E, D>
|
||||
where
|
||||
E: Service,
|
||||
D: Service<Request = E::Request, Response = E::Response, Error = E::Error>,
|
||||
{
|
||||
type Request = E::Request;
|
||||
type Response = E::Response;
|
||||
type Error = E::Error;
|
||||
type Future = Either<E::Future, D::Future>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
use ConditionMiddleware::*;
|
||||
match self {
|
||||
Enable(service) => service.poll_ready(),
|
||||
Disable(service) => service.poll_ready(),
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&mut self, req: E::Request) -> Self::Future {
|
||||
use ConditionMiddleware::*;
|
||||
match self {
|
||||
Enable(service) => Either::A(service.call(req)),
|
||||
Disable(service) => Either::B(service.call(req)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_service::IntoService;
|
||||
|
||||
use super::*;
|
||||
use crate::dev::{ServiceRequest, ServiceResponse};
|
||||
use crate::error::Result;
|
||||
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
|
||||
use crate::middleware::errhandlers::*;
|
||||
use crate::test::{self, TestRequest};
|
||||
use crate::HttpResponse;
|
||||
|
||||
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||
res.response_mut()
|
||||
.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
|
||||
Ok(ErrorHandlerResponse::Response(res))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handler_enabled() {
|
||||
let srv = |req: ServiceRequest| {
|
||||
req.into_response(HttpResponse::InternalServerError().finish())
|
||||
};
|
||||
|
||||
let mw =
|
||||
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
|
||||
|
||||
let mut mw =
|
||||
test::block_on(Condition::new(true, mw).new_transform(srv.into_service()))
|
||||
.unwrap();
|
||||
let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request());
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
}
|
||||
#[test]
|
||||
fn test_handler_disabled() {
|
||||
let srv = |req: ServiceRequest| {
|
||||
req.into_response(HttpResponse::InternalServerError().finish())
|
||||
};
|
||||
|
||||
let mw =
|
||||
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
|
||||
|
||||
let mut mw =
|
||||
test::block_on(Condition::new(false, mw).new_transform(srv.into_service()))
|
||||
.unwrap();
|
||||
|
||||
let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request());
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE), None);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,9 @@ mod defaultheaders;
|
|||
pub mod errhandlers;
|
||||
mod logger;
|
||||
mod normalize;
|
||||
mod condition;
|
||||
|
||||
pub use self::defaultheaders::DefaultHeaders;
|
||||
pub use self::logger::Logger;
|
||||
pub use self::normalize::NormalizePath;
|
||||
pub use self::condition::Condition;
|
||||
|
|
Loading…
Reference in New Issue