From 32453966822001a8a81b675e4cbe7d481896feef Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Tue, 17 Nov 2020 20:38:10 -0500 Subject: [PATCH 1/3] Add basic timeout with hardcoded durations --- src/lib.rs | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8717bc0..ec4b027 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,18 @@ #[macro_use] extern crate log; -use std::{panic::AssertUnwindSafe, convert::TryFrom, io::BufReader, sync::Arc}; +use std::{ + panic::AssertUnwindSafe, + convert::TryFrom, + io::BufReader, + sync::Arc, + time::Duration, +}; use futures::{future::BoxFuture, FutureExt}; use tokio::{ prelude::*, io::{self, BufStream}, net::{TcpStream, ToSocketAddrs}, + time::timeout, }; use tokio::net::TcpListener; use rustls::ClientCertVerifier; @@ -54,12 +61,22 @@ impl Server { } async fn serve_client(self, stream: TcpStream) -> Result<()> { - let stream = self.tls_acceptor.accept(stream).await - .context("Failed to establish TLS session")?; - let mut stream = BufStream::new(stream); + let fut_accept_request = async { + let stream = self.tls_acceptor.accept(stream).await + .context("Failed to establish TLS session")?; + let mut stream = BufStream::new(stream); + + let request = receive_request(&mut stream).await + .context("Failed to receive request")?; + + Result::<_, anyhow::Error>::Ok((request, stream)) + }; + + // Use a timeout for interacting with the client + let fut_accept_request = timeout(Duration::from_secs(5), fut_accept_request); + let (mut request, mut stream) = fut_accept_request.await + .context("Client timed out while waiting for response")??; - let mut request = receive_request(&mut stream).await - .context("Failed to receive request")?; debug!("Client requested: {}", request.uri()); // Identify the client certificate from the tls stream. This is the first @@ -83,11 +100,18 @@ impl Server { }) .context("Request handler failed")?; - send_response(response, &mut stream).await - .context("Failed to send response")?; + // Use a timeout for sending the response + let fut_send_and_flush = async { + send_response(response, &mut stream).await + .context("Failed to send response")?; - stream.flush().await - .context("Failed to flush response data")?; + stream.flush() + .await + .context("Failed to flush response data") + }; + timeout(Duration::from_millis(1000), fut_send_and_flush) + .await + .context("Client timed out receiving response data")??; Ok(()) } From 71cf4d33f2c568a5595587160b00d5b098c060fb Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Tue, 17 Nov 2020 20:45:57 -0500 Subject: [PATCH 2/3] Added ability to set the timeout in the Server builder --- src/lib.rs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ec4b027..a75d88a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,7 @@ pub struct Server { tls_acceptor: TlsAcceptor, listener: Arc, handler: Handler, + timeout: Duration, } impl Server { @@ -73,7 +74,7 @@ impl Server { }; // Use a timeout for interacting with the client - let fut_accept_request = timeout(Duration::from_secs(5), fut_accept_request); + let fut_accept_request = timeout(self.timeout, fut_accept_request); let (mut request, mut stream) = fut_accept_request.await .context("Client timed out while waiting for response")??; @@ -109,7 +110,7 @@ impl Server { .await .context("Failed to flush response data") }; - timeout(Duration::from_millis(1000), fut_send_and_flush) + timeout(self.timeout, fut_send_and_flush) .await .context("Client timed out receiving response data")??; @@ -119,11 +120,29 @@ impl Server { pub struct Builder { addr: A, + timeout: Duration, } impl Builder { fn bind(addr: A) -> Self { - Self { addr } + Self { addr, timeout: Duration::from_secs(1) } + } + + /// Set the timeout on incoming requests + /// + /// Note that this timeout is applied twice, once for the delivery of the request, and + /// once for sending the client's response. This means that for a 1 second timeout, + /// the client will have 1 second to complete the TLS handshake and deliver a request + /// header, then your API will have as much time as it needs to handle the request, + /// before the client has another second to receive the response. + /// + /// If you would like a timeout for your code itself, please use + /// ['tokio::time::Timeout`] to implement it internally. + /// + /// The default timeout is 1 second. + pub fn set_timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self } pub async fn serve(self, handler: F) -> Result<()> @@ -140,6 +159,7 @@ impl Builder { tls_acceptor: TlsAcceptor::from(config), listener: Arc::new(listener), handler: Arc::new(handler), + timeout: self.timeout, }; server.serve().await From aa2dbbf67ade4d538d8c7893109dc43399e2bc99 Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Tue, 17 Nov 2020 21:01:54 -0500 Subject: [PATCH 3/3] Fixed typo in timeout docs --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a75d88a..2dadbc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,7 +137,7 @@ impl Builder { /// before the client has another second to receive the response. /// /// If you would like a timeout for your code itself, please use - /// ['tokio::time::Timeout`] to implement it internally. + /// [`tokio::time::Timeout`] to implement it internally. /// /// The default timeout is 1 second. pub fn set_timeout(mut self, timeout: Duration) -> Self {