diff --git a/Cargo.toml b/Cargo.toml index dd12927..9ad991d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,20 +8,25 @@ description = "Gemini server implementation" repository = "https://github.com/panicbit/northstar" documentation = "https://docs.rs/northstar" +[features] +default = ["serve_dir"] +serve_dir = ["mime_guess", "tokio/fs"] + [dependencies] anyhow = "1.0.33" rustls = { version = "0.18.1", features = ["dangerous_configuration"] } tokio-rustls = "0.20.0" -tokio = { version = "0.3.1", features = ["full"] } +tokio = { version = "0.3.1", features = ["io-util","net","time", "rt"] } mime = "0.3.16" uriparse = "0.6.3" percent-encoding = "2.1.0" -futures = "0.3.7" -itertools = "0.9.0" +futures-core = "0.3.7" log = "0.4.11" webpki = "0.21.0" lazy_static = "1.4.0" -mime_guess = "2.0.3" +mime_guess = { version = "2.0.3", optional = true } [dev-dependencies] env_logger = "0.8.1" +futures-util = "0.3.7" +tokio = { version = "0.3.1", features = ["macros", "rt-multi-thread", "sync"] } diff --git a/examples/certificates.rs b/examples/certificates.rs index f0f98b3..541fbe5 100644 --- a/examples/certificates.rs +++ b/examples/certificates.rs @@ -1,5 +1,6 @@ use anyhow::*; -use futures::{future::BoxFuture, FutureExt}; +use futures_core::future::BoxFuture; +use futures_util::FutureExt; use log::LevelFilter; use tokio::sync::RwLock; use northstar::{Certificate, GEMINI_MIME, GEMINI_PORT, Request, Response, Server}; diff --git a/examples/document.rs b/examples/document.rs index 5986896..8ff6bbb 100644 --- a/examples/document.rs +++ b/examples/document.rs @@ -1,5 +1,6 @@ use anyhow::*; -use futures::{future::BoxFuture, FutureExt}; +use futures_core::future::BoxFuture; +use futures_util::FutureExt; use log::LevelFilter; use northstar::{Server, Request, Response, GEMINI_PORT, Document}; use northstar::document::HeadingLevel::*; diff --git a/examples/serve_dir.rs b/examples/serve_dir.rs index 47145e1..fd26ac4 100644 --- a/examples/serve_dir.rs +++ b/examples/serve_dir.rs @@ -1,5 +1,6 @@ use anyhow::*; -use futures::{future::BoxFuture, FutureExt}; +use futures_core::future::BoxFuture; +use futures_util::FutureExt; use log::LevelFilter; use northstar::{Server, Request, Response, GEMINI_PORT}; diff --git a/src/lib.rs b/src/lib.rs index b8d9f81..0d113a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ use std::{ path::PathBuf, time::Duration, }; -use futures::{future::BoxFuture, FutureExt}; +use futures_core::future::BoxFuture; use tokio::{ prelude::*, io::{self, BufStream}, @@ -33,7 +33,7 @@ pub const REQUEST_URI_MAX_LEN: usize = 1024; pub const GEMINI_PORT: u16 = 1965; type Handler = Arc HandlerResponse + Send + Sync>; -type HandlerResponse = BoxFuture<'static, Result>; +pub (crate) type HandlerResponse = BoxFuture<'static, Result>; #[derive(Clone)] pub struct Server { @@ -94,7 +94,7 @@ impl Server { let handler = (self.handler)(request); let handler = AssertUnwindSafe(handler); - let response = handler.catch_unwind().await + let response = util::HandlerCatchUnwind::new(handler).await .unwrap_or_else(|_| Response::server_error("")) .or_else(|err| { error!("Handler failed: {:?}", err); diff --git a/src/types/body.rs b/src/types/body.rs index dfeb8ca..d1356cc 100644 --- a/src/types/body.rs +++ b/src/types/body.rs @@ -1,4 +1,6 @@ -use tokio::{io::AsyncRead, fs::File}; +use tokio::io::AsyncRead; +#[cfg(feature="serve_dir")] +use tokio::fs::File; use crate::types::Document; @@ -37,6 +39,7 @@ impl<'a> From<&'a str> for Body { } } +#[cfg(feature="serve_dir")] impl From for Body { fn from(file: File) -> Self { Self::Reader(Box::new(file)) diff --git a/src/types/document.rs b/src/types/document.rs index f6fc5aa..e322357 100644 --- a/src/types/document.rs +++ b/src/types/document.rs @@ -39,7 +39,6 @@ use std::convert::TryInto; use std::fmt; -use itertools::Itertools; use crate::types::URIReference; use crate::util::Cowy; @@ -550,5 +549,6 @@ fn strip_newlines(text: impl Cowy) -> String { text.as_ref() .lines() .filter(|part| !part.is_empty()) + .collect::>() .join(" ") } diff --git a/src/util.rs b/src/util.rs index 1ba11af..c48b8bf 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,13 +1,21 @@ -use std::path::Path; +#[cfg(feature="serve_dir")] +use std::path::{Path, PathBuf}; +#[cfg(feature="serve_dir")] use mime::Mime; use anyhow::*; +#[cfg(feature="serve_dir")] use tokio::{ fs::{self, File}, io, }; -use crate::types::{Response, Document, document::HeadingLevel::*}; -use itertools::Itertools; +#[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 futures_core::future::Future; +#[cfg(feature="serve_dir")] pub async fn serve_file>(path: P, mime: &Mime) -> Result { let path = path.as_ref(); @@ -22,6 +30,7 @@ pub async fn serve_file>(path: P, mime: &Mime) -> Result, P: AsRef>(dir: D, virtual_path: &[P]) -> Result { debug!("Dir: {}", dir.as_ref().display()); let dir = dir.as_ref().canonicalize() @@ -47,6 +56,7 @@ pub async fn serve_dir, P: AsRef>(dir: D, virtual_path: &[P serve_dir_listing(path, virtual_path).await } +#[cfg(feature="serve_dir")] async fn serve_dir_listing, B: AsRef>(path: P, virtual_path: &[B]) -> Result { let mut dir = match fs::read_dir(path).await { Ok(dir) => dir, @@ -56,10 +66,10 @@ async fn serve_dir_listing, B: AsRef>(path: P, virtual_path } }; - let breadcrumbs = virtual_path.iter().map(|segment| segment.as_ref().display()).join("/"); + let breadcrumbs: PathBuf = virtual_path.iter().collect(); let mut document = Document::new(); - document.add_heading(H1, format!("Index of /{}", breadcrumbs)); + document.add_heading(H1, format!("Index of /{}", breadcrumbs.display())); document.add_blank_line(); if virtual_path.get(0).map(<_>::as_ref) != Some(Path::new("")) { @@ -85,6 +95,7 @@ async fn serve_dir_listing, B: AsRef>(path: P, virtual_path Ok(Response::document(document)) } +#[cfg(feature="serve_dir")] pub fn guess_mime_from_path>(path: P) -> Mime { let path = path.as_ref(); let extension = path.extension().and_then(|s| s.to_str()); @@ -115,3 +126,32 @@ where C: AsRef + Into, 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)) + } + } +}