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.
This commit is contained in:
parent
f572209dfa
commit
7990739884
114
src/handling.rs
Normal file
114
src/handling.rs
Normal file
|
@ -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<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::server_error("").unwrap()
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(Response::server_error(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
src/lib.rs
29
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<dyn Fn(Request) -> HandlerResponse + Send + Sync>;
|
||||
pub (crate) type HandlerResponse = Pin<Box<dyn Future<Output = Result<Response>> + 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<A: ToSocketAddrs> Builder<A> {
|
|||
/// "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<F, H>(mut self, path: &'static str, handler: H) -> Self
|
||||
where
|
||||
H: Send + Sync + 'static + Fn(Request) -> F,
|
||||
F: Send + Sync + 'static + Future<Output = Result<Response>>
|
||||
{
|
||||
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<Handler>) -> Self {
|
||||
self.routes.add_route(path, handler.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
31
src/util.rs
31
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<crate::HandlerResponse>,
|
||||
}
|
||||
|
||||
impl HandlerCatchUnwind {
|
||||
pub(super) fn new(future: AssertUnwindSafe<crate::HandlerResponse>) -> Self {
|
||||
Self { future }
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for HandlerCatchUnwind {
|
||||
type Output = Result<Result<Response>, Box<dyn std::any::Any + Send>>;
|
||||
|
||||
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.map(Ok),
|
||||
Err(e) => Poll::Ready(Err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn opt_timeout<T>(duration: Option<time::Duration>, future: impl Future<Output = T>) -> Result<T, time::error::Elapsed> {
|
||||
match duration {
|
||||
Some(duration) => time::timeout(duration, future).await,
|
||||
|
|
Loading…
Reference in a new issue