💾 Archived View for alchemi.dev › en › projects › kochab › files › src › handling.rs captured on 2023-09-28 at 15:57:40.
⬅️ Previous capture (2022-07-16)
-=-=-=-=-=-=-
//! 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 { FnHandler(HandlerInner), StaticHandler(Response), #[cfg(feature = "serve_dir")] 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 [`SERVER_ERROR`](Response::server_error()). 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(""))) } } } }