kochab/src/handling.rs

210 lines
7.9 KiB
Rust

//! Types for handling requests
//!
//! The main type is the [`Handler`], which wraps a more specific type of handler and
//! manages delegating responses to it.
//!
//! For most purposes, you should never have to manually create any of these structs
//! yourself, though it may be useful to look at the implementations of [`From`] on
//! [`Handler`], as these are the things that can be used as handlers for routes.
use anyhow::Result;
use std::{
pin::Pin,
future::Future,
task::Poll,
panic::{catch_unwind, AssertUnwindSafe},
};
#[cfg(feature = "serve_dir")]
use std::path::PathBuf;
use crate::{Document, types::{Body, Response, Request}};
/// A struct representing something capable of handling a request.
///
/// In the future, this may have multiple varieties, but at the minute, it just wraps an
/// [`Fn`](std::ops::Fn).
///
/// The most useful part of the documentation for this is the implementations of [`From`]
/// on it, as this is what can be passed to
/// [`Server::add_route()`](crate::Server::add_route()) in order to create a new route.
/// Each implementation has bespoke docs that describe how the type is used, and what
/// response is produced.
pub enum Handler {
/// A handler that responds to a request by delegating to an [`Fn`]
FnHandler(HandlerInner),
/// A handler that always serves an identical response, for any and all request
StaticHandler(Response),
#[cfg(feature = "serve_dir")]
/// A handler that serves a directory, including a directory listing
FilesHandler(PathBuf),
}
/// Since we can't store train objects, we need to wrap fn handlers in a box
type HandlerInner = Box<dyn Fn(Request) -> HandlerResponse + Send + Sync>;
/// Same with dyn Futures
type HandlerResponse = Pin<Box<dyn Future<Output = Result<Response>> + Send>>;
impl Handler {
/// Handle an incoming request
///
/// This delegates to the request to the appropriate method of handling it, whether
/// that's fetching a file or directory listing, cloning a static response, or handing
/// the request to a wrapped handler function.
///
/// Any unexpected errors that occur will be printed to the log and potentially
/// reported to the user, depending on the handler type.
pub async fn handle(&self, request: Request) -> Response {
match self {
Self::FnHandler(inner) => {
let fut_handle = (inner)(request);
let fut_handle = AssertUnwindSafe(fut_handle);
HandlerCatchUnwind::new(fut_handle).await
.unwrap_or_else(|err| {
error!("Handler failed: {:?}", err);
Response::temporary_failure("")
})
},
Self::StaticHandler(response) => {
match &response.body {
None => Response::new(response.status, &response.meta),
Some(Body::Bytes(bytes)) => Response::success(&response.meta, bytes.clone()),
_ => {
error!(concat!(
"Cannot construct a static handler with a reader-based body! ",
" We're sending a response so that the client doesn't crash, but",
" given that this is a release build you should really fix this."
));
Response::permanent_failure(
"Very bad server error, go tell the sysadmin to look at the logs."
)
}
}
},
#[cfg(feature = "serve_dir")]
Self::FilesHandler(path) => {
if path.is_dir() {
crate::util::serve_dir(path, request.trailing_segments()).await
} else {
let mime = crate::util::guess_mime_from_path(&path);
crate::util::serve_file(path, &mime).await
}
},
}
}
}
impl<H, R> From<H> for Handler
where
H: 'static + Fn(Request) -> R + Send + Sync,
R: 'static + Future<Output = Result<Response>> + Send,
{
/// Wrap an [`Fn`] in a [`Handler`] struct
///
/// This automatically boxes both the [`Fn`] and the [`Fn`]'s response.
///
/// Any requests passed to the handler will be directly handed down to the handler,
/// with the request as the first argument. The response provided will be sent to the
/// requester. If the handler panics or returns an [`Err`], this will be logged, and
/// the requester will be sent a [`TEMPORARY FAILURE`](Response::temporary_failure()).
fn from(handler: H) -> Self {
Self::FnHandler(
Box::new(move|req| Box::pin((handler)(req)) as HandlerResponse)
)
}
}
// We tolerate a fallible `impl From` because this is *really* not the kind of thing the
// user should be catching in runtime.
#[allow(clippy::fallible_impl_from)]
impl From<Response> for Handler {
/// Serve an unchanging response
///
/// Any and all requests to this handler will be responded to with the same response,
/// no matter what. This is good for static content that is provided by your app.
/// For serving files & directories, try looking at creating a handler from a path
///
/// ## Panics
/// This response type **CANNOT** be created using Responses with [`Reader`] bodies.
/// Attempting to do this will cause a panic. Don't.
///
/// [`Reader`]: Body::Reader
fn from(response: Response) -> Self {
#[cfg(debug_assertions)] {
// We have another check once the handler is actually called that is not
// disabled for release builds
if let Some(Body::Reader(_)) = response.as_ref() {
panic!("Cannot construct a static handler with a reader-based body");
}
}
Self::StaticHandler(response)
}
}
impl From<&Document> for Handler {
/// Serve an unchanging response, shorthand for From<Response>
///
/// This document will be sent in response to any requests that arrive at this
/// handler. As with all documents, this will be a successful response with a
/// `text/gemini` MIME.
fn from(doc: &Document) -> Self {
Self::StaticHandler(doc.into())
}
}
#[cfg(feature = "serve_dir")]
impl From<PathBuf> for Handler {
/// Serve files from a directory
///
/// Any requests directed to this handler will be served from this path. For example,
/// if a handler serving files from the path `./public/` and bound to `/serve`
/// receives a request for `/serve/file.txt`, it will respond with the contents of the
/// file at `./public/file.txt`.
///
/// This is equivilent to serving files using [`util::serve_dir()`], and as such will
/// include directory listings.
///
/// The path to a single file can be passed in order to serve only a single file for
/// any and all requests.
///
/// [`util::serve_dir()`]: crate::util::serve_dir()
fn from(path: PathBuf) -> Self {
Self::FilesHandler(path)
}
}
/// A utility for catching unwinds on Futures.
///
/// This is adapted from the futures-rs CatchUnwind, in an effort to reduce the large
/// amount of dependencies tied into the feature that provides this simple struct.
#[must_use = "futures do nothing unless polled"]
struct HandlerCatchUnwind {
future: AssertUnwindSafe<HandlerResponse>,
}
impl HandlerCatchUnwind {
fn new(future: AssertUnwindSafe<HandlerResponse>) -> Self {
Self { future }
}
}
impl Future for HandlerCatchUnwind {
type Output = Result<Response>;
fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context
) -> Poll<Self::Output> {
match catch_unwind(AssertUnwindSafe(|| self.future.as_mut().poll(cx))) {
Ok(res) => res,
Err(e) => {
error!("Handler panic! {:?}", e);
Poll::Ready(Ok(Response::temporary_failure("")))
}
}
}
}