Added complex timeout override option to builder
This commit is contained in:
parent
a778774189
commit
94d7a5ab4f
160
src/lib.rs
160
src/lib.rs
|
@ -40,6 +40,7 @@ pub struct Server {
|
|||
listener: Arc<TcpListener>,
|
||||
handler: Handler,
|
||||
timeout: Duration,
|
||||
complex_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
|
@ -101,31 +102,116 @@ impl Server {
|
|||
})
|
||||
.context("Request handler failed")?;
|
||||
|
||||
// Use a timeout for sending the response
|
||||
let fut_send_and_flush = async {
|
||||
send_response(response, &mut stream).await
|
||||
self.send_response(response, &mut stream).await
|
||||
.context("Failed to send response")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_response(&self, mut response: Response, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> {
|
||||
let maybe_body = response.take_body();
|
||||
let header = response.header();
|
||||
|
||||
// Okay, I know this method looks really complicated, but I promise it's not.
|
||||
// There's really only three things this method does:
|
||||
//
|
||||
// * Send the response header
|
||||
// * Send the response body
|
||||
// * Flush the stream
|
||||
//
|
||||
// All the other code is doing one of two things. Either it's
|
||||
//
|
||||
// * code to add and handle timeouts (that's what all the async blocks and calls
|
||||
// to tokio::time::timeout are), or
|
||||
// * logic to decide whether to use the special case timeout handling (seperate
|
||||
// timeouts for the header and the body) vs the normal timeout handling (header,
|
||||
// body, and flush all as one timeout)
|
||||
//
|
||||
// The split between the two cases happens at this very first if block.
|
||||
// Everything in this deep chain of if's and if-let's is for the special case. If
|
||||
// any one of the ifs fails, the code after the big if block is run, and that's
|
||||
// the normal case.
|
||||
//
|
||||
// Hope this helps! Emi <3
|
||||
|
||||
if header.status == Status::SUCCESS && maybe_body.is_some() {
|
||||
// aaaa let me have if-let chaining ;_;
|
||||
if let "text/plain"|"text/gemini" = header.meta.as_str() {
|
||||
if let Some(cplx_timeout) = self.complex_timeout {
|
||||
|
||||
|
||||
////////////// Use the special case timeout override /////////////////////////////
|
||||
|
||||
// Send the header & flush
|
||||
let fut_send_header = async {
|
||||
send_response_header(response.header(), stream).await
|
||||
.context("Failed to write response header")?;
|
||||
|
||||
stream.flush()
|
||||
.await
|
||||
.context("Failed to flush response header")
|
||||
};
|
||||
tokio::time::timeout(self.timeout, fut_send_header)
|
||||
.await
|
||||
.context("Timed out while sending response header")??;
|
||||
|
||||
// Send the body & flush
|
||||
let fut_send_body = async {
|
||||
send_response_body(maybe_body.unwrap(), stream).await
|
||||
.context("Failed to write response body")?;
|
||||
|
||||
stream.flush()
|
||||
.await
|
||||
.context("Failed to flush response body")
|
||||
};
|
||||
tokio::time::timeout(cplx_timeout, fut_send_body)
|
||||
.await
|
||||
.context("Timed out while sending response body")??;
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////// Use the normal timeout /////////////////////////////////////////////
|
||||
|
||||
let fut_send_response = async {
|
||||
send_response_header(response.header(), stream).await
|
||||
.context("Failed to write response header")?;
|
||||
|
||||
if let Some(body) = maybe_body {
|
||||
send_response_body(body, stream).await
|
||||
.context("Failed to write response body")?;
|
||||
}
|
||||
|
||||
stream.flush()
|
||||
.await
|
||||
.context("Failed to flush response data")
|
||||
};
|
||||
timeout(self.timeout, fut_send_and_flush)
|
||||
tokio::time::timeout(self.timeout, fut_send_response)
|
||||
.await
|
||||
.context("Client timed out receiving response data")??;
|
||||
.context("Timed out while sending response data")??;
|
||||
|
||||
Ok(())
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Builder<A> {
|
||||
addr: A,
|
||||
timeout: Duration,
|
||||
complex_body_timeout_override: Option<Duration>,
|
||||
}
|
||||
|
||||
impl<A: ToSocketAddrs> Builder<A> {
|
||||
fn bind(addr: A) -> Self {
|
||||
Self { addr, timeout: Duration::from_secs(30) }
|
||||
Self {
|
||||
addr,
|
||||
timeout: Duration::from_secs(1),
|
||||
complex_body_timeout_override: Some(Duration::from_secs(30)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the timeout on incoming requests
|
||||
|
@ -139,17 +225,54 @@ impl<A: ToSocketAddrs> Builder<A> {
|
|||
/// If you would like a timeout for your code itself, please use
|
||||
/// [`tokio::time::Timeout`] to implement it internally.
|
||||
///
|
||||
/// **The default timeout is 30 seconds.** If you are considering changing this, keep
|
||||
/// in mind that some clients, when recieving a file type not supported for display,
|
||||
/// will prompt the user how they would like to proceed. While this occurs, the
|
||||
/// request hangs open. Setting a short timeout may close the prompt before user has
|
||||
/// a chance to respond. If you are only serving `text/plain` and `text/gemini`, this
|
||||
/// should not be a problem.
|
||||
/// **The default timeout is 1 second.** As somewhat of a workaround for
|
||||
/// shortcomings of the specification, this timeout, and any timeout set using this
|
||||
/// method, is overridden in special cases, specifically for MIME types outside of
|
||||
/// `text/plain` and `text/gemini`, to be 30 seconds. If you would like to change or
|
||||
/// prevent this, please see
|
||||
/// [`override_complex_body_timeout`](Self::override_complex_body_timeout()).
|
||||
pub fn set_timeout(mut self, timeout: Duration) -> Self {
|
||||
self.timeout = timeout;
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the timeout for complex body types
|
||||
///
|
||||
/// Many clients choose to handle body types which cannot be displayed by prompting
|
||||
/// the user if they would like to download or open the request body. However, since
|
||||
/// this prompt occurs in the middle of receiving a request, often the connection
|
||||
/// times out before the end user is able to respond to the prompt.
|
||||
///
|
||||
/// As a workaround, it is possible to set an override on the request timeout in
|
||||
/// specific conditions:
|
||||
///
|
||||
/// 1. **Only override the timeout for receiving the body of the request.** This will
|
||||
/// not override the timeout on sending the request header, nor on receiving the
|
||||
/// response header.
|
||||
/// 2. **Only override the timeout for successful responses.** The only bodies which
|
||||
/// have bodies are successful ones. In all other cases, there's no body to
|
||||
/// timeout for
|
||||
/// 3. **Only override the timeout for complex body types.** Almost all clients are
|
||||
/// able to display `text/plain` and `text/gemini` responses, and will not prompt
|
||||
/// the user for these response types. This means that there is no reason to
|
||||
/// expect a client to have a human-length response time for these MIME types.
|
||||
/// Because of this, responses of this type will not be overridden.
|
||||
///
|
||||
/// This method is used to override the timeout for responses meeting these specific
|
||||
/// criteria. All other stages of the connection will use the timeout specified in
|
||||
/// [`set_timeout()`](Self::set_timeout()).
|
||||
///
|
||||
/// If this is set to [`None`], then the client will have the default amount of time
|
||||
/// to both receive the header and the body. If this is set to [`Some`], the client
|
||||
/// will have the default amount of time to recieve the header, and an *additional*
|
||||
/// alotment of time to recieve the body.
|
||||
///
|
||||
/// The default timeout for this is 30 seconds.
|
||||
pub fn override_complex_body_timeout(mut self, timeout: Option<Duration>) -> Self {
|
||||
self.complex_body_timeout_override = timeout;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn serve<F>(self, handler: F) -> Result<()>
|
||||
where
|
||||
F: Fn(Request) -> HandlerResponse + Send + Sync + 'static,
|
||||
|
@ -165,6 +288,7 @@ impl<A: ToSocketAddrs> Builder<A> {
|
|||
listener: Arc::new(listener),
|
||||
handler: Arc::new(handler),
|
||||
timeout: self.timeout,
|
||||
complex_timeout: self.complex_body_timeout_override,
|
||||
};
|
||||
|
||||
server.serve().await
|
||||
|
@ -199,18 +323,6 @@ async fn receive_request(stream: &mut (impl AsyncBufRead + Unpin)) -> Result<Req
|
|||
Ok(request)
|
||||
}
|
||||
|
||||
async fn send_response(mut response: Response, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> {
|
||||
send_response_header(response.header(), stream).await
|
||||
.context("Failed to send response header")?;
|
||||
|
||||
if let Some(body) = response.take_body() {
|
||||
send_response_body(body, stream).await
|
||||
.context("Failed to send response body")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_response_header(header: &ResponseHeader, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> {
|
||||
let header = format!(
|
||||
"{status} {meta}\r\n",
|
||||
|
|
Loading…
Reference in a new issue