2020-11-23 16:55:40 +00:00
|
|
|
//! 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},
|
|
|
|
};
|
2020-11-24 22:22:47 +00:00
|
|
|
#[cfg(feature = "serve_dir")]
|
|
|
|
use std::path::PathBuf;
|
2020-11-23 16:55:40 +00:00
|
|
|
|
2020-12-07 22:29:34 +00:00
|
|
|
use crate::{Body, Response, Request};
|
|
|
|
#[cfg(feature = "gemtext")]
|
|
|
|
use crate::Gemtext;
|
2020-11-23 16:55:40 +00:00
|
|
|
|
|
|
|
/// A struct representing something capable of handling a request.
|
|
|
|
///
|
2020-12-06 15:24:54 +00:00
|
|
|
/// 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.
|
2020-11-23 16:55:40 +00:00
|
|
|
///
|
2020-12-06 15:24:54 +00:00
|
|
|
/// Detailed descriptions on each variant also describe how each kind of handler works,
|
|
|
|
/// and how they can be created
|
2020-11-23 16:55:40 +00:00
|
|
|
pub enum Handler {
|
2020-12-05 19:09:16 +00:00
|
|
|
|
|
|
|
/// A handler that responds to a request by delegating to an [`Fn`]
|
2020-12-06 15:24:54 +00:00
|
|
|
///
|
|
|
|
/// 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<H>
|
2020-11-23 16:55:40 +00:00
|
|
|
FnHandler(HandlerInner),
|
2020-12-05 19:09:16 +00:00
|
|
|
|
|
|
|
/// A handler that always serves an identical response, for any and all request
|
2020-12-06 15:24:54 +00:00
|
|
|
///
|
|
|
|
/// 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<PathBuf>).
|
|
|
|
///
|
2020-12-07 22:29:34 +00:00
|
|
|
/// Most often created by using [`From<Response>`] or [`From<Gemtext>`]
|
2020-12-06 15:24:54 +00:00
|
|
|
///
|
|
|
|
/// [`FilesHandler`]: Self::FilesHandler
|
|
|
|
/// [`From<Response>`]: #impl-From<Response>
|
2020-12-07 22:29:34 +00:00
|
|
|
/// [`From<Gemtext>`]: #impl-From<Gemtext>
|
2020-11-24 04:30:59 +00:00
|
|
|
StaticHandler(Response),
|
2020-12-05 19:09:16 +00:00
|
|
|
|
2020-11-24 22:22:47 +00:00
|
|
|
#[cfg(feature = "serve_dir")]
|
2020-12-05 19:09:16 +00:00
|
|
|
/// A handler that serves a directory, including a directory listing
|
2020-12-06 15:24:54 +00:00
|
|
|
///
|
|
|
|
/// Most often created with [`From<PathBuf>`]
|
|
|
|
///
|
|
|
|
/// 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<PathBuf>`]: #impl-From<PathBuf>
|
2020-11-24 22:22:47 +00:00
|
|
|
FilesHandler(PathBuf),
|
2020-11-23 16:55:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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);
|
2020-12-01 21:36:29 +00:00
|
|
|
Response::temporary_failure("")
|
2020-11-23 16:55:40 +00:00
|
|
|
})
|
|
|
|
},
|
2020-11-24 04:30:59 +00:00
|
|
|
Self::StaticHandler(response) => {
|
2020-12-01 21:36:29 +00:00
|
|
|
match &response.body {
|
|
|
|
None => Response::new(response.status, &response.meta),
|
|
|
|
Some(Body::Bytes(bytes)) => Response::success(&response.meta, bytes.clone()),
|
2020-11-24 04:30:59 +00:00
|
|
|
_ => {
|
|
|
|
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."
|
|
|
|
));
|
2020-12-01 21:36:29 +00:00
|
|
|
Response::permanent_failure(
|
2020-11-24 04:30:59 +00:00
|
|
|
"Very bad server error, go tell the sysadmin to look at the logs."
|
2020-12-01 21:36:29 +00:00
|
|
|
)
|
2020-11-24 04:30:59 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-24 22:22:47 +00:00
|
|
|
},
|
|
|
|
#[cfg(feature = "serve_dir")]
|
|
|
|
Self::FilesHandler(path) => {
|
2020-12-01 21:36:29 +00:00
|
|
|
if path.is_dir() {
|
2020-11-24 22:28:54 +00:00
|
|
|
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
|
2020-12-01 21:36:29 +00:00
|
|
|
}
|
2020-11-24 22:22:47 +00:00
|
|
|
},
|
2020-11-23 16:55:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<H, R> From<H> for Handler
|
|
|
|
where
|
|
|
|
H: 'static + Fn(Request) -> R + Send + Sync,
|
|
|
|
R: 'static + Future<Output = Result<Response>> + Send,
|
|
|
|
{
|
2020-12-06 15:24:54 +00:00
|
|
|
/// Wrap an [`Fn`] in a [`Handler`] struct, creating an [`FnHandler`]
|
2020-11-23 16:55:40 +00:00
|
|
|
///
|
|
|
|
/// This automatically boxes both the [`Fn`] and the [`Fn`]'s response.
|
|
|
|
///
|
2020-12-06 15:24:54 +00:00
|
|
|
/// 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<Response> {
|
|
|
|
/// // This could be done with a StaticHandler, but for demonstration:
|
|
|
|
/// Ok(Response::success_gemini("Hello world!"))
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// [`FnHandler`]: Self::FnHandler
|
2020-11-23 16:55:40 +00:00
|
|
|
fn from(handler: H) -> Self {
|
|
|
|
Self::FnHandler(
|
|
|
|
Box::new(move|req| Box::pin((handler)(req)) as HandlerResponse)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-24 04:30:59 +00:00
|
|
|
// 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
|
|
|
|
///
|
|
|
|
/// ## Panics
|
|
|
|
/// This response type **CANNOT** be created using Responses with [`Reader`] bodies.
|
|
|
|
/// Attempting to do this will cause a panic. Don't.
|
|
|
|
///
|
2020-12-06 15:24:54 +00:00
|
|
|
/// This will create a [`StaticHandler`]
|
|
|
|
///
|
2020-11-24 04:30:59 +00:00
|
|
|
/// [`Reader`]: Body::Reader
|
2020-12-06 15:24:54 +00:00
|
|
|
/// [`StaticHandler`]: Self::StaticHandler
|
2020-11-24 04:30:59 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-07 22:29:34 +00:00
|
|
|
#[cfg(feature = "gemtext")]
|
|
|
|
impl From<Gemtext> for Handler {
|
2020-11-24 04:30:59 +00:00
|
|
|
/// 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.
|
2020-12-06 15:24:54 +00:00
|
|
|
///
|
|
|
|
/// This will create a [`StaticHandler`]
|
|
|
|
///
|
|
|
|
/// [`StaticHandler`]: Self::StaticHandler
|
2020-12-07 22:29:34 +00:00
|
|
|
fn from(doc: Gemtext) -> Self {
|
2020-11-24 04:30:59 +00:00
|
|
|
Self::StaticHandler(doc.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-24 22:22:47 +00:00
|
|
|
#[cfg(feature = "serve_dir")]
|
|
|
|
impl From<PathBuf> for Handler {
|
|
|
|
/// Serve files from a directory
|
|
|
|
///
|
2020-11-24 22:28:54 +00:00
|
|
|
/// The path to a single file can be passed in order to serve only a single file for
|
|
|
|
/// any and all requests.
|
|
|
|
///
|
2020-12-06 15:24:54 +00:00
|
|
|
/// This will create a [`FilesHandler`].
|
|
|
|
///
|
2020-11-24 22:22:47 +00:00
|
|
|
/// [`util::serve_dir()`]: crate::util::serve_dir()
|
2020-12-06 15:24:54 +00:00
|
|
|
/// [`FilesHandler`]: Handler::FilesHandler
|
2020-11-24 22:22:47 +00:00
|
|
|
fn from(path: PathBuf) -> Self {
|
|
|
|
Self::FilesHandler(path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 16:55:40 +00:00
|
|
|
/// 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);
|
2020-12-01 21:36:29 +00:00
|
|
|
Poll::Ready(Ok(Response::temporary_failure("")))
|
2020-11-23 16:55:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|