1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-28 01:32:57 +01:00

fix client timer and add slow request tests

This commit is contained in:
Nikolay Kim 2018-10-02 00:19:28 -07:00
parent f007860a16
commit f3ce6574e4
8 changed files with 144 additions and 54 deletions

View File

@ -8,6 +8,8 @@
* Added slow request timeout setting * Added slow request timeout setting
* Respond with 408 response on slow request timeout #523
### Fixed ### Fixed

View File

@ -86,6 +86,7 @@ language-tags = "0.2"
lazy_static = "1.0" lazy_static = "1.0"
lazycell = "1.0.0" lazycell = "1.0.0"
parking_lot = "0.6" parking_lot = "0.6"
serde_urlencoded = "^0.5.3"
url = { version="1.7", features=["query_encoding"] } url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.11", features=["percent-encode"] } cookie = { version="0.11", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true } brotli2 = { version="^0.3.2", optional = true }
@ -125,7 +126,7 @@ webpki-roots = { version = "0.15", optional = true }
# unix sockets # unix sockets
tokio-uds = { version="0.2", optional = true } tokio-uds = { version="0.2", optional = true }
serde_urlencoded = "^0.5.3" backtrace="*"
[dev-dependencies] [dev-dependencies]
env_logger = "0.5" env_logger = "0.5"

View File

@ -16,7 +16,7 @@ use super::KeepAlive;
pub(crate) trait ServiceProvider { pub(crate) trait ServiceProvider {
fn register( fn register(
&self, server: Server, lst: net::TcpListener, host: String, &self, server: Server, lst: net::TcpListener, host: String,
addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: u64, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64,
client_shutdown: u64, client_shutdown: u64,
) -> Server; ) -> Server;
} }
@ -28,7 +28,6 @@ where
{ {
factory: F, factory: F,
acceptor: A, acceptor: A,
no_client_timer: bool,
} }
impl<F, H, A> HttpServiceBuilder<F, H, A> impl<F, H, A> HttpServiceBuilder<F, H, A>
@ -40,27 +39,13 @@ where
{ {
/// Create http service builder /// Create http service builder
pub fn new(factory: F, acceptor: A) -> Self { pub fn new(factory: F, acceptor: A) -> Self {
Self { Self { factory, acceptor }
factory,
acceptor,
no_client_timer: false,
}
}
pub(crate) fn no_client_timer(mut self) -> Self {
self.no_client_timer = true;
self
} }
fn finish( fn finish(
&self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, &self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool,
client_timeout: u64, client_shutdown: u64, client_timeout: u64, client_shutdown: u64,
) -> impl ServiceFactory { ) -> impl ServiceFactory {
let timeout = if self.no_client_timer {
0
} else {
client_timeout
};
let factory = self.factory.clone(); let factory = self.factory.clone();
let acceptor = self.acceptor.clone(); let acceptor = self.acceptor.clone();
move || { move || {
@ -68,12 +53,12 @@ where
let settings = WorkerSettings::new( let settings = WorkerSettings::new(
app, app,
keep_alive, keep_alive,
timeout as u64, client_timeout,
client_shutdown, client_shutdown,
ServerSettings::new(addr, &host, false), ServerSettings::new(addr, &host, false),
); );
if timeout == 0 { if secure {
Either::A(ServerMessageAcceptor::new( Either::A(ServerMessageAcceptor::new(
settings.clone(), settings.clone(),
TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service))
@ -88,8 +73,10 @@ where
} else { } else {
Either::B(ServerMessageAcceptor::new( Either::B(ServerMessageAcceptor::new(
settings.clone(), settings.clone(),
TcpAcceptor::new(AcceptorTimeout::new(timeout, acceptor.create())) TcpAcceptor::new(AcceptorTimeout::new(
.map_err(|_| ()) client_timeout,
acceptor.create(),
)).map_err(|_| ())
.map_init_err(|_| ()) .map_init_err(|_| ())
.and_then( .and_then(
HttpService::new(settings) HttpService::new(settings)
@ -112,7 +99,6 @@ where
HttpServiceBuilder { HttpServiceBuilder {
factory: self.factory.clone(), factory: self.factory.clone(),
acceptor: self.acceptor.clone(), acceptor: self.acceptor.clone(),
no_client_timer: self.no_client_timer,
} }
} }
} }
@ -126,13 +112,20 @@ where
{ {
fn register( fn register(
&self, server: Server, lst: net::TcpListener, host: String, &self, server: Server, lst: net::TcpListener, host: String,
addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: u64, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64,
client_shutdown: u64, client_shutdown: u64,
) -> Server { ) -> Server {
server.listen2( server.listen2(
"actix-web", "actix-web",
lst, lst,
self.finish(host, addr, keep_alive, client_timeout, client_shutdown), self.finish(
host,
addr,
keep_alive,
secure,
client_timeout,
client_shutdown,
),
) )
} }
} }

View File

@ -9,6 +9,7 @@ use tokio_timer::Delay;
use super::error::HttpDispatchError; use super::error::HttpDispatchError;
use super::settings::WorkerSettings; use super::settings::WorkerSettings;
use super::{h1, h2, HttpHandler, IoStream}; use super::{h1, h2, HttpHandler, IoStream};
use http::StatusCode;
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
@ -42,11 +43,9 @@ where
pub(crate) fn new( pub(crate) fn new(
settings: WorkerSettings<H>, io: T, peer: Option<SocketAddr>, settings: WorkerSettings<H>, io: T, peer: Option<SocketAddr>,
) -> HttpChannel<T, H> { ) -> HttpChannel<T, H> {
let ka_timeout = settings.client_timer();
HttpChannel { HttpChannel {
ka_timeout,
node: None, node: None,
ka_timeout: settings.client_timer(),
proto: Some(HttpProtocol::Unknown( proto: Some(HttpProtocol::Unknown(
settings, settings,
peer, peer,
@ -91,10 +90,23 @@ where
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
// keep-alive timer // keep-alive timer
if let Some(ref mut timer) = self.ka_timeout { if self.ka_timeout.is_some() {
match timer.poll() { match self.ka_timeout.as_mut().unwrap().poll() {
Ok(Async::Ready(_)) => { Ok(Async::Ready(_)) => {
trace!("Slow request timed out, close connection"); trace!("Slow request timed out, close connection");
if let Some(HttpProtocol::Unknown(settings, _, io, buf)) =
self.proto.take()
{
self.proto =
Some(HttpProtocol::H1(h1::Http1Dispatcher::for_error(
settings,
io,
StatusCode::REQUEST_TIMEOUT,
self.ka_timeout.take(),
buf,
)));
return self.poll();
}
return Ok(Async::Ready(())); return Ok(Async::Ready(()));
} }
Ok(Async::NotReady) => (), Ok(Async::NotReady) => (),
@ -121,12 +133,8 @@ where
let mut is_eof = false; let mut is_eof = false;
let kind = match self.proto { let kind = match self.proto {
Some(HttpProtocol::H1(ref mut h1)) => { Some(HttpProtocol::H1(ref mut h1)) => return h1.poll(),
return h1.poll(); Some(HttpProtocol::H2(ref mut h2)) => return h2.poll(),
}
Some(HttpProtocol::H2(ref mut h2)) => {
return h2.poll();
}
Some(HttpProtocol::Unknown(_, _, ref mut io, ref mut buf)) => { Some(HttpProtocol::Unknown(_, _, ref mut io, ref mut buf)) => {
let mut err = None; let mut err = None;
let mut disconnect = false; let mut disconnect = false;

View File

@ -121,6 +121,31 @@ where
} }
} }
pub(crate) fn for_error(
settings: WorkerSettings<H>, stream: T, status: StatusCode,
mut keepalive_timer: Option<Delay>, buf: BytesMut,
) -> Self {
if let Some(deadline) = settings.client_timer_expire() {
let _ = keepalive_timer.as_mut().map(|delay| delay.reset(deadline));
}
let mut disp = Http1Dispatcher {
flags: Flags::STARTED | Flags::READ_DISCONNECTED,
stream: H1Writer::new(stream, settings.clone()),
decoder: H1Decoder::new(),
payload: None,
tasks: VecDeque::new(),
error: None,
addr: None,
ka_timer: keepalive_timer,
ka_expire: settings.now(),
buf,
settings,
};
disp.push_response_entry(status);
disp
}
#[inline] #[inline]
pub fn settings(&self) -> &WorkerSettings<H> { pub fn settings(&self) -> &WorkerSettings<H> {
&self.settings &self.settings
@ -133,7 +158,7 @@ where
#[inline] #[inline]
fn can_read(&self) -> bool { fn can_read(&self) -> bool {
if self.flags.intersects(Flags::READ_DISCONNECTED) { if self.flags.contains(Flags::READ_DISCONNECTED) {
return false; return false;
} }
@ -250,6 +275,15 @@ where
); );
let _ = IoStream::shutdown(io, Shutdown::Both); let _ = IoStream::shutdown(io, Shutdown::Both);
return Err(HttpDispatchError::ShutdownTimeout); return Err(HttpDispatchError::ShutdownTimeout);
} else if !self.flags.contains(Flags::STARTED) {
// timeout on first request (slow request) return 408
trace!("Slow request timeout");
self.flags
.insert(Flags::STARTED | Flags::READ_DISCONNECTED);
self.tasks.push_back(Entry::Error(ServerError::err(
Version::HTTP_11,
StatusCode::REQUEST_TIMEOUT,
)));
} else { } else {
trace!("Keep-alive timeout, close connection"); trace!("Keep-alive timeout, close connection");
self.flags.insert(Flags::SHUTDOWN); self.flags.insert(Flags::SHUTDOWN);

View File

@ -232,10 +232,10 @@ where
lst, lst,
addr, addr,
scheme: "http", scheme: "http",
handler: Box::new( handler: Box::new(HttpServiceBuilder::new(
HttpServiceBuilder::new(self.factory.clone(), DefaultAcceptor) self.factory.clone(),
.no_client_timer(), DefaultAcceptor,
), )),
}); });
self self
@ -498,10 +498,10 @@ impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
.as_ref() .as_ref()
.map(|h| h.to_owned()) .map(|h| h.to_owned())
.unwrap_or_else(|| format!("{}", socket.addr)); .unwrap_or_else(|| format!("{}", socket.addr));
let client_shutdown = if socket.scheme == "https" { let (secure, client_shutdown) = if socket.scheme == "https" {
self.client_shutdown (true, self.client_shutdown)
} else { } else {
0 (false, 0)
}; };
srv = socket.handler.register( srv = socket.handler.register(
srv, srv,
@ -509,6 +509,7 @@ impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
host, host,
socket.addr, socket.addr,
self.keep_alive, self.keep_alive,
secure,
self.client_timeout, self.client_timeout,
client_shutdown, client_shutdown,
); );
@ -550,10 +551,10 @@ impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
.as_ref() .as_ref()
.map(|h| h.to_owned()) .map(|h| h.to_owned())
.unwrap_or_else(|| format!("{}", socket.addr)); .unwrap_or_else(|| format!("{}", socket.addr));
let client_shutdown = if socket.scheme == "https" { let (secure, client_shutdown) = if socket.scheme == "https" {
self.client_shutdown (true, self.client_shutdown)
} else { } else {
0 (false, 0)
}; };
srv = socket.handler.register( srv = socket.handler.register(
srv, srv,
@ -561,6 +562,7 @@ impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
host, host,
socket.addr, socket.addr,
self.keep_alive, self.keep_alive,
secure,
self.client_timeout, self.client_timeout,
client_shutdown, client_shutdown,
); );

View File

@ -232,6 +232,16 @@ impl<H: 'static> WorkerSettings<H> {
} }
} }
/// Client timeout for first request.
pub fn client_timer_expire(&self) -> Option<Instant> {
let delay = self.0.client_timeout;
if delay != 0 {
Some(self.now() + Duration::from_millis(delay))
} else {
None
}
}
/// Client shutdown timer /// Client shutdown timer
pub fn client_shutdown_timer(&self) -> Option<Instant> { pub fn client_shutdown_timer(&self) -> Option<Instant> {
let delay = self.0.client_shutdown; let delay = self.0.client_shutdown;

View File

@ -1054,3 +1054,43 @@ fn test_custom_pipeline() {
assert!(response.status().is_success()); assert!(response.status().is_success());
} }
} }
#[test]
fn test_slow_request() {
use actix::System;
use std::net;
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
let addr = test::TestServer::unused_addr();
thread::spawn(move || {
System::run(move || {
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})]
});
let srv = srv.bind(addr).unwrap();
srv.client_timeout(200).start();
let _ = tx.send(System::current());
});
});
let sys = rx.recv().unwrap();
thread::sleep(time::Duration::from_millis(200));
let mut stream = net::TcpStream::connect(addr).unwrap();
let mut data = String::new();
let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 408 Request Timeou"));
let mut stream = net::TcpStream::connect(addr).unwrap();
let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n");
let mut data = String::new();
let _ = stream.read_to_string(&mut data);
assert!(data.starts_with("HTTP/1.1 408 Request Timeou"));
sys.stop();
}