From 79907398847ad8fa067f67bc0916718a6bf012c8 Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Mon, 23 Nov 2020 11:55:40 -0500 Subject: [PATCH] Move the handler type to it's own mod, change to an enum The new enum can be converted to from anything that could previously be passed to add_route, so this is not a breaking change. If fact, from the end user's perspective, nothing changed, but internally, this gives us a lot of potential as far as having multiple types of routes. --- src/handling.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 29 +++--------- src/util.rs | 31 ------------- 3 files changed, 119 insertions(+), 55 deletions(-) create mode 100644 src/handling.rs diff --git a/src/handling.rs b/src/handling.rs new file mode 100644 index 0000000..244efa0 --- /dev/null +++ b/src/handling.rs @@ -0,0 +1,114 @@ +//! 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}, +}; + +use crate::types::{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 +/// [`Builder::add_route`](crate::Builder::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), +} + +/// 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::server_error("").unwrap() + }) + }, + } + } +} + +impl From for Handler +where + H: 'static + Fn(Request) -> R + Send + Sync, + R: 'static + Future> + 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) + ) + } +} + +/// 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(Response::server_error("")) + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 1926bfe..9ef0aad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,12 @@ #[macro_use] extern crate log; use std::{ - panic::AssertUnwindSafe, convert::TryFrom, io::BufReader, sync::Arc, path::PathBuf, time::Duration, - pin::Pin, }; -use std::future::Future; use tokio::{ prelude::*, io::{self, BufStream}, @@ -29,6 +26,7 @@ use routing::RoutingNode; pub mod types; pub mod util; pub mod routing; +pub mod handling; pub use mime; pub use uriparse as uri; @@ -37,8 +35,7 @@ pub use types::*; pub const REQUEST_URI_MAX_LEN: usize = 1024; pub const GEMINI_PORT: u16 = 1965; -type Handler = Box HandlerResponse + Send + Sync>; -pub (crate) type HandlerResponse = Pin> + Send>>; +use handling::Handler; #[derive(Clone)] pub struct Server { @@ -97,19 +94,8 @@ impl Server { request.set_cert(client_cert); let response = if let Some((trailing, handler)) = self.routes.match_request(&request) { - request.set_trailing(trailing); - - let handler = (handler)(request); - let handler = AssertUnwindSafe(handler); - - util::HandlerCatchUnwind::new(handler).await - .unwrap_or_else(|_| Response::server_error("")) - .or_else(|err| { - error!("Handler failed: {:?}", err); - Response::server_error("") - }) - .context("Request handler failed")? + handler.handle(request).await } else { Response::not_found() }; @@ -293,13 +279,8 @@ impl Builder { /// "endpoint". Entering a relative or malformed path will result in a panic. /// /// For more information about routing mechanics, see the docs for [`RoutingNode`]. - pub fn add_route(mut self, path: &'static str, handler: H) -> Self - where - H: Send + Sync + 'static + Fn(Request) -> F, - F: Send + Sync + 'static + Future> - { - let wrapped = Box::new(move|req| Box::pin((handler)(req)) as HandlerResponse); - self.routes.add_route(path, wrapped); + pub fn add_route(mut self, path: &'static str, handler: impl Into) -> Self { + self.routes.add_route(path, handler.into()); self } diff --git a/src/util.rs b/src/util.rs index 33ca6d6..945e2f7 100644 --- a/src/util.rs +++ b/src/util.rs @@ -11,8 +11,6 @@ use tokio::{ #[cfg(feature="serve_dir")] use crate::types::{Document, document::HeadingLevel::*}; use crate::types::Response; -use std::panic::{catch_unwind, AssertUnwindSafe}; -use std::task::Poll; use std::future::Future; use tokio::time; @@ -128,35 +126,6 @@ where T: ToOwned + ?Sized, {} -/// 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"] -pub (crate) struct HandlerCatchUnwind { - future: AssertUnwindSafe, -} - -impl HandlerCatchUnwind { - pub(super) fn new(future: AssertUnwindSafe) -> Self { - Self { future } - } -} - -impl Future for HandlerCatchUnwind { - type Output = Result, Box>; - - 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.map(Ok), - Err(e) => Poll::Ready(Err(e)) - } - } -} - pub(crate) async fn opt_timeout(duration: Option, future: impl Future) -> Result { match duration { Some(duration) => time::timeout(duration, future).await,