Merge branch 'master' into master

This commit is contained in:
Nikolay Kim 2019-09-09 12:33:23 +06:00 committed by GitHub
commit a18b272ea2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 315 additions and 9 deletions

View File

@ -1,6 +1,19 @@
# Changes # Changes
## not released yet
## [1.0.6] - 2019-xx-xx ### Added
* Add `middleware::Conditon` that conditionally enables another middleware
## [1.0.7] - 2019-08-29
### Fixed
* Request Extensions leak #1062
## [1.0.6] - 2019-08-28
### Added ### Added
@ -8,10 +21,17 @@
* Form immplements Responder, returning a `application/x-www-form-urlencoded` response * Form immplements Responder, returning a `application/x-www-form-urlencoded` response
* Add `into_inner` to `Data`
* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set
the header in test requests.
### Changed ### Changed
* `Query` payload made `pub`. Allows user to pattern-match the payload. * `Query` payload made `pub`. Allows user to pattern-match the payload.
* Enable `rust-tls` feature for client #1045
* Update serde_urlencoded to 0.6.1 * Update serde_urlencoded to 0.6.1
* Update url to 2.1 * Update url to 2.1

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "1.0.5" version = "1.0.7"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"
@ -66,7 +66,7 @@ fail = ["actix-http/fail"]
ssl = ["openssl", "actix-server/ssl", "awc/ssl"] ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
# rustls # rustls
rust-tls = ["rustls", "actix-server/rust-tls"] rust-tls = ["rustls", "actix-server/rust-tls", "awc/rust-tls"]
# unix domain sockets support # unix domain sockets support
uds = ["actix-server/uds"] uds = ["actix-server/uds"]

View File

@ -1,5 +1,12 @@
# Changes # Changes
## [0.2.10] - 2019-09-xx
### Fixed
* on_connect result isn't added to request extensions for http2 requests #1009
## [0.2.9] - 2019-08-13 ## [0.2.9] - 2019-08-13
### Changed ### Changed

View File

@ -49,7 +49,7 @@ secure-cookies = ["ring"]
[dependencies] [dependencies]
actix-service = "0.4.1" actix-service = "0.4.1"
actix-codec = "0.1.2" actix-codec = "0.1.2"
actix-connect = "0.2.2" actix-connect = "0.2.4"
actix-utils = "0.4.4" actix-utils = "0.4.4"
actix-server-config = "0.1.2" actix-server-config = "0.1.2"
actix-threadpool = "0.1.1" actix-threadpool = "0.1.1"

View File

@ -199,6 +199,7 @@ where
self.client_disconnect, self.client_disconnect,
); );
H2Service::with_config(cfg, service.into_new_service()) H2Service::with_config(cfg, service.into_new_service())
.on_connect(self.on_connect)
} }
/// Finish service configuration and create `HttpService` instance. /// Finish service configuration and create `HttpService` instance.

View File

@ -502,7 +502,7 @@ where
let pl = self.codec.message_type(); let pl = self.codec.message_type();
req.head_mut().peer_addr = self.peer_addr; req.head_mut().peer_addr = self.peer_addr;
// on_connect data // set on_connect data
if let Some(ref on_connect) = self.on_connect { if let Some(ref on_connect) = self.on_connect {
on_connect.set(&mut req.extensions_mut()); on_connect.set(&mut req.extensions_mut());
} }

View File

@ -23,6 +23,7 @@ use crate::cloneable::CloneableService;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError};
use crate::helpers::DataFactory; use crate::helpers::DataFactory;
use crate::httpmessage::HttpMessage;
use crate::message::ResponseHead; use crate::message::ResponseHead;
use crate::payload::Payload; use crate::payload::Payload;
use crate::request::Request; use crate::request::Request;
@ -122,6 +123,12 @@ where
head.version = parts.version; head.version = parts.version;
head.headers = parts.headers.into(); head.headers = parts.headers.into();
head.peer_addr = self.peer_addr; head.peer_addr = self.peer_addr;
// set on_connect data
if let Some(ref on_connect) = self.on_connect {
on_connect.set(&mut req.extensions_mut());
}
tokio_current_thread::spawn(ServiceResponse::<S::Future, B> { tokio_current_thread::spawn(ServiceResponse::<S::Future, B> {
state: ServiceResponseState::ServiceCall( state: ServiceResponseState::ServiceCall(
self.service.call(req), self.service.call(req),

View File

@ -11,6 +11,7 @@ use futures::stream::{once, Stream};
use regex::Regex; use regex::Regex;
use tokio_timer::sleep; use tokio_timer::sleep;
use actix_http::httpmessage::HttpMessage;
use actix_http::{ use actix_http::{
body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response,
}; };
@ -602,3 +603,18 @@ fn test_h1_service_error() {
let bytes = srv.load_body(response).unwrap(); let bytes = srv.load_body(response).unwrap();
assert_eq!(bytes, Bytes::from_static(b"error")); assert_eq!(bytes, Bytes::from_static(b"error"));
} }
#[test]
fn test_h1_on_connect() {
let mut srv = TestServer::new(|| {
HttpService::build()
.on_connect(|_| 10usize)
.h1(|req: Request| {
assert!(req.extensions().contains::<usize>());
future::ok::<_, ()>(Response::Ok().finish())
})
});
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
}

View File

@ -1,9 +1,5 @@
#![cfg(feature = "ssl")] #![cfg(feature = "ssl")]
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::error::{ErrorBadRequest, PayloadError};
use actix_http::http::header::{self, HeaderName, HeaderValue};
use actix_http::http::{Method, StatusCode, Version};
use actix_http::{body, Error, HttpService, Request, Response};
use actix_http_test::TestServer; use actix_http_test::TestServer;
use actix_server::ssl::OpensslAcceptor; use actix_server::ssl::OpensslAcceptor;
use actix_server_config::ServerConfig; use actix_server_config::ServerConfig;
@ -15,6 +11,12 @@ use futures::stream::{once, Stream};
use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod};
use std::io::Result; use std::io::Result;
use actix_http::error::{ErrorBadRequest, PayloadError};
use actix_http::http::header::{self, HeaderName, HeaderValue};
use actix_http::http::{Method, StatusCode, Version};
use actix_http::httpmessage::HttpMessage;
use actix_http::{body, Error, HttpService, Request, Response};
fn load_body<S>(stream: S) -> impl Future<Item = BytesMut, Error = PayloadError> fn load_body<S>(stream: S) -> impl Future<Item = BytesMut, Error = PayloadError>
where where
S: Stream<Item = Bytes, Error = PayloadError>, S: Stream<Item = Bytes, Error = PayloadError>,
@ -453,3 +455,26 @@ fn test_h2_service_error() {
let bytes = srv.load_body(response).unwrap(); let bytes = srv.load_body(response).unwrap();
assert!(bytes.is_empty()); assert!(bytes.is_empty());
} }
#[test]
fn test_h2_on_connect() {
let openssl = ssl_acceptor().unwrap();
let mut srv = TestServer::new(move || {
openssl
.clone()
.map_err(|e| println!("Openssl error: {}", e))
.and_then(
HttpService::build()
.on_connect(|_| 10usize)
.h2(|req: Request| {
assert!(req.extensions().contains::<usize>());
ok::<_, ()>(Response::Ok().finish())
})
.map_err(|_| ()),
)
});
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
}

View File

@ -1,5 +1,11 @@
# Changes # Changes
## [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 ## [0.2.4] - 2019-08-13
### Changed ### Changed

View File

@ -233,6 +233,10 @@ impl WebsocketsRequest {
return Either::A(err(InvalidUrl::UnknownScheme.into())); 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 // set cookies
if let Some(ref mut jar) = self.cookies { if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new(); let mut cookie = String::new();

View File

@ -77,6 +77,11 @@ impl<T> Data<T> {
pub fn get_ref(&self) -> &T { pub fn get_ref(&self) -> &T {
self.0.as_ref() self.0.as_ref()
} }
/// Convert to the internal Arc<T>
pub fn into_inner(self) -> Arc<T> {
self.0
}
} }
impl<T> Deref for Data<T> { impl<T> Deref for Data<T> {

143
src/middleware/condition.rs Normal file
View File

@ -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);
}
}

View File

@ -6,7 +6,9 @@ mod defaultheaders;
pub mod errhandlers; pub mod errhandlers;
mod logger; mod logger;
mod normalize; mod normalize;
mod condition;
pub use self::defaultheaders::DefaultHeaders; pub use self::defaultheaders::DefaultHeaders;
pub use self::logger::Logger; pub use self::logger::Logger;
pub use self::normalize::NormalizePath; pub use self::normalize::NormalizePath;
pub use self::condition::Condition;

View File

@ -259,6 +259,7 @@ impl Drop for HttpRequest {
if Rc::strong_count(&self.0) == 1 { if Rc::strong_count(&self.0) == 1 {
let v = &mut self.0.pool.0.borrow_mut(); let v = &mut self.0.pool.0.borrow_mut();
if v.len() < 128 { if v.len() < 128 {
self.extensions_mut().clear();
v.push(self.0.clone()); v.push(self.0.clone());
} }
} }
@ -494,4 +495,38 @@ mod tests {
let resp = call_service(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
} }
#[test]
fn test_extensions_dropped() {
struct Tracker {
pub dropped: bool,
}
struct Foo {
tracker: Rc<RefCell<Tracker>>,
}
impl Drop for Foo {
fn drop(&mut self) {
self.tracker.borrow_mut().dropped = true;
}
}
let tracker = Rc::new(RefCell::new(Tracker { dropped: false }));
{
let tracker2 = Rc::clone(&tracker);
let mut srv = init_service(App::new().data(10u32).service(
web::resource("/").to(move |req: HttpRequest| {
req.extensions_mut().insert(Foo {
tracker: Rc::clone(&tracker2),
});
HttpResponse::Ok()
}),
));
let req = TestRequest::default().to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
assert!(tracker.borrow().dropped);
}
} }

View File

@ -478,6 +478,16 @@ impl TestRequest {
self self
} }
/// Serialize `data` to a URL encoded form and set it as the request payload. The `Content-Type`
/// header is set to `application/x-www-form-urlencoded`.
pub fn set_form<T: Serialize>(mut self, data: &T) -> Self {
let bytes = serde_urlencoded::to_string(data)
.expect("Failed to serialize test data as a urlencoded form");
self.req.set_payload(bytes);
self.req.set(ContentType::form_url_encoded());
self
}
/// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is /// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is
/// set to `application/json`. /// set to `application/json`.
pub fn set_json<T: Serialize>(mut self, data: &T) -> Self { pub fn set_json<T: Serialize>(mut self, data: &T) -> Self {
@ -670,6 +680,31 @@ mod tests {
assert_eq!(&result.id, "12345"); assert_eq!(&result.id, "12345");
} }
#[test]
fn test_request_response_form() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Form<Person>| {
HttpResponse::Ok().json(person.into_inner())
}),
)));
let payload = Person {
id: "12345".to_string(),
name: "User name".to_string(),
};
let req = TestRequest::post()
.uri("/people")
.set_form(&payload)
.to_request();
assert_eq!(req.content_type(), "application/x-www-form-urlencoded");
let result: Person = read_response_json(&mut app, req);
assert_eq!(&result.id, "12345");
assert_eq!(&result.name, "User name");
}
#[test] #[test]
fn test_request_response_json() { fn test_request_response_json() {
let mut app = init_service(App::new().service(web::resource("/people").route( let mut app = init_service(App::new().service(web::resource("/people").route(