//! Types for handling requests //! //! The main type is the [`Handler`], which wraps a more specific type of handler and //! manages delegating responses to it. 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. /// /// A crucial part of the documentation for this is the implementations of [`From`], as /// this is what can be passed to [`Server::add_route()`](crate::Server::add_route()) in /// order to create a new route. /// /// Detailed descriptions on each variant also describe how each kind of handler works, /// and how they can be created pub enum Handler { /// A handler that responds to a request by delegating to an [`Fn`] /// /// Most often created by using the implementation by using the implementation of /// [`From`] /// /// If you're feeling overwhelmed by the function signature, don't panic. Please see /// the [example](#example). /// /// 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()). /// /// [`From`]: #impl-From FnHandler(HandlerInner), /// A handler that always serves an identical response, for any and all request /// /// 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 [`FilesHandler`] by /// [passing a directory](#impl-From). /// /// Most often created by using [`From`] or [`From`] /// /// [`FilesHandler`]: Self::FilesHandler /// [`From`]: #impl-From /// [`From`]: #impl-From<%26'_%20Document> StaticHandler(Response), #[cfg(feature = "serve_dir")] /// A handler that serves a directory, including a directory listing /// /// Most often created with [`From`] /// /// 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`, and automatically infer the MIME type. /// /// This is equivilent to serving files using [`util::serve_dir()`], and as such will /// include directory listings. /// /// Additionally, if the path is only a single file, that file will be served in /// response to *every request*. That is, adding a handler for `/path/to/file.txt` /// to the route `/hello` will mean that `/hello`, `/hello/file.txt`, and /// `/hello/irrele/vant` will all be responded to with the contents of `file.txt`. /// /// [`From`]: #impl-From FilesHandler(PathBuf), } /// Since we can't store train objects, we need to wrap fn handlers in a box type HandlerInner = Box HandlerResponse + Send + Sync>; /// Same with dyn Futures type HandlerResponse = Pin> + 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 From for Handler where H: 'static + Fn(Request) -> R + Send + Sync, R: 'static + Future> + Send, { /// Wrap an [`Fn`] in a [`Handler`] struct, creating an [`FnHandler`] /// /// This automatically boxes both the [`Fn`] and the [`Fn`]'s response. /// /// Don't be overwhelmed by the function signature here. It's honestly way simpler /// than it looks. /// /// # Example /// /// ``` /// # use kochab::*; /// use anyhow::Result; /// /// let handler: Handler = handle_request.into(); /// /// async fn handle_request(request: Request) -> Result { /// // This could be done with a StaticHandler, but for demonstration: /// Ok(Response::success_gemini("Hello world!")) /// } /// ``` /// /// [`FnHandler`]: Self::FnHandler 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 for Handler { /// Serve an unchanging response /// /// ## Panics /// This response type **CANNOT** be created using Responses with [`Reader`] bodies. /// Attempting to do this will cause a panic. Don't. /// /// This will create a [`StaticHandler`] /// /// [`Reader`]: Body::Reader /// [`StaticHandler`]: Self::StaticHandler 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 /// /// 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. /// /// This will create a [`StaticHandler`] /// /// [`StaticHandler`]: Self::StaticHandler fn from(doc: &Document) -> Self { Self::StaticHandler(doc.into()) } } #[cfg(feature = "serve_dir")] impl From for Handler { /// Serve files from a directory /// /// The path to a single file can be passed in order to serve only a single file for /// any and all requests. /// /// This will create a [`FilesHandler`]. /// /// [`util::serve_dir()`]: crate::util::serve_dir() /// [`FilesHandler`]: Handler::FilesHandler 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, } impl HandlerCatchUnwind { fn new(future: AssertUnwindSafe) -> Self { Self { future } } } impl Future for HandlerCatchUnwind { type Output = Result; fn poll( mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context ) -> Poll { 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(""))) } } } }