Merge remote-tracking branch 'upstream/master' into timeout-override
This commit is contained in:
commit
b50a5e3244
|
@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `redirect_temporary_lossy` for `Response` and `ResponseHeader`
|
- `redirect_temporary_lossy` for `Response` and `ResponseHeader`
|
||||||
- `bad_request_lossy` for `Response` and `ResponseHeader`
|
- `bad_request_lossy` for `Response` and `ResponseHeader`
|
||||||
- support for a lot more mime-types in `guess_mime_from_path`, backed by the `mime_guess` crate
|
- support for a lot more mime-types in `guess_mime_from_path`, backed by the `mime_guess` crate
|
||||||
|
- customizable TLS cert & key paths by [@Alch-Emi](https://github.com/Alch-Emi)
|
||||||
|
- `server_dir` default feature for serve_dir utils [@Alch-Emi](https://github.com/Alch-Emi)
|
||||||
|
### Improved
|
||||||
|
- build time and size by [@Alch-Emi](https://github.com/Alch-Emi)
|
||||||
|
|
||||||
## [0.3.0] - 2020-11-14
|
## [0.3.0] - 2020-11-14
|
||||||
### Added
|
### Added
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -8,20 +8,25 @@ description = "Gemini server implementation"
|
||||||
repository = "https://github.com/panicbit/northstar"
|
repository = "https://github.com/panicbit/northstar"
|
||||||
documentation = "https://docs.rs/northstar"
|
documentation = "https://docs.rs/northstar"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["serve_dir"]
|
||||||
|
serve_dir = ["mime_guess", "tokio/fs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.33"
|
anyhow = "1.0.33"
|
||||||
rustls = { version = "0.18.1", features = ["dangerous_configuration"] }
|
rustls = { version = "0.18.1", features = ["dangerous_configuration"] }
|
||||||
tokio-rustls = "0.20.0"
|
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"
|
mime = "0.3.16"
|
||||||
uriparse = "0.6.3"
|
uriparse = "0.6.3"
|
||||||
percent-encoding = "2.1.0"
|
percent-encoding = "2.1.0"
|
||||||
futures = "0.3.7"
|
futures-core = "0.3.7"
|
||||||
itertools = "0.9.0"
|
|
||||||
log = "0.4.11"
|
log = "0.4.11"
|
||||||
webpki = "0.21.0"
|
webpki = "0.21.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
mime_guess = "2.0.3"
|
mime_guess = { version = "2.0.3", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.8.1"
|
env_logger = "0.8.1"
|
||||||
|
futures-util = "0.3.7"
|
||||||
|
tokio = { version = "0.3.1", features = ["macros", "rt-multi-thread", "sync"] }
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
use futures::{future::BoxFuture, FutureExt};
|
use futures_core::future::BoxFuture;
|
||||||
|
use futures_util::FutureExt;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use northstar::{Certificate, GEMINI_MIME, GEMINI_PORT, Request, Response, Server};
|
use northstar::{Certificate, GEMINI_MIME, GEMINI_PORT, Request, Response, Server};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
use futures::{future::BoxFuture, FutureExt};
|
use futures_core::future::BoxFuture;
|
||||||
|
use futures_util::FutureExt;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use northstar::{Server, Request, Response, GEMINI_PORT, Document};
|
use northstar::{Server, Request, Response, GEMINI_PORT, Document};
|
||||||
use northstar::document::HeadingLevel::*;
|
use northstar::document::HeadingLevel::*;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
use futures::{future::BoxFuture, FutureExt};
|
use futures_core::future::BoxFuture;
|
||||||
|
use futures_util::FutureExt;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use northstar::{Server, Request, Response, GEMINI_PORT};
|
use northstar::{Server, Request, Response, GEMINI_PORT};
|
||||||
|
|
||||||
|
|
73
src/lib.rs
73
src/lib.rs
|
@ -5,9 +5,10 @@ use std::{
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
io::BufReader,
|
io::BufReader,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
path::PathBuf,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use futures::{future::BoxFuture, FutureExt};
|
use futures_core::future::BoxFuture;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
io::{self, BufStream},
|
io::{self, BufStream},
|
||||||
|
@ -32,7 +33,7 @@ pub const REQUEST_URI_MAX_LEN: usize = 1024;
|
||||||
pub const GEMINI_PORT: u16 = 1965;
|
pub const GEMINI_PORT: u16 = 1965;
|
||||||
|
|
||||||
type Handler = Arc<dyn Fn(Request) -> HandlerResponse + Send + Sync>;
|
type Handler = Arc<dyn Fn(Request) -> HandlerResponse + Send + Sync>;
|
||||||
type HandlerResponse = BoxFuture<'static, Result<Response>>;
|
pub (crate) type HandlerResponse = BoxFuture<'static, Result<Response>>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
|
@ -94,7 +95,7 @@ impl Server {
|
||||||
let handler = (self.handler)(request);
|
let handler = (self.handler)(request);
|
||||||
let handler = AssertUnwindSafe(handler);
|
let handler = AssertUnwindSafe(handler);
|
||||||
|
|
||||||
let response = handler.catch_unwind().await
|
let response = util::HandlerCatchUnwind::new(handler).await
|
||||||
.unwrap_or_else(|_| Response::server_error(""))
|
.unwrap_or_else(|_| Response::server_error(""))
|
||||||
.or_else(|err| {
|
.or_else(|err| {
|
||||||
error!("Handler failed: {:?}", err);
|
error!("Handler failed: {:?}", err);
|
||||||
|
@ -202,6 +203,8 @@ impl Server {
|
||||||
|
|
||||||
pub struct Builder<A> {
|
pub struct Builder<A> {
|
||||||
addr: A,
|
addr: A,
|
||||||
|
cert_path: PathBuf,
|
||||||
|
key_path: PathBuf,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
complex_body_timeout_override: Option<Duration>,
|
complex_body_timeout_override: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
@ -212,9 +215,51 @@ impl<A: ToSocketAddrs> Builder<A> {
|
||||||
addr,
|
addr,
|
||||||
timeout: Duration::from_secs(1),
|
timeout: Duration::from_secs(1),
|
||||||
complex_body_timeout_override: Some(Duration::from_secs(30)),
|
complex_body_timeout_override: Some(Duration::from_secs(30)),
|
||||||
|
cert_path: PathBuf::from("cert/cert.pem"),
|
||||||
|
key_path: PathBuf::from("cert/key.pem"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the directory that northstar should look for TLS certs and keys into
|
||||||
|
///
|
||||||
|
/// Northstar will look for files called `cert.pem` and `key.pem` in the provided
|
||||||
|
/// directory.
|
||||||
|
///
|
||||||
|
/// This does not need to be set if both [`set_cert()`](Self::set_cert()) and
|
||||||
|
/// [`set_key()`](Self::set_key()) have been called.
|
||||||
|
///
|
||||||
|
/// If not set, the default is `cert/`
|
||||||
|
pub fn set_tls_dir(self, dir: impl Into<PathBuf>) -> Self {
|
||||||
|
let dir = dir.into();
|
||||||
|
self.set_cert(dir.join("cert.pem"))
|
||||||
|
.set_key(dir.join("key.pem"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the path to the TLS certificate northstar will use
|
||||||
|
///
|
||||||
|
/// This defaults to `cert/cert.pem`.
|
||||||
|
///
|
||||||
|
/// This does not need to be called it [`set_tls_dir()`](Self::set_tls_dir()) has been
|
||||||
|
/// called.
|
||||||
|
pub fn set_cert(mut self, cert_path: impl Into<PathBuf>) -> Self {
|
||||||
|
self.cert_path = cert_path.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the path to the ertificate key northstar will use
|
||||||
|
///
|
||||||
|
/// This defaults to `cert/key.pem`.
|
||||||
|
///
|
||||||
|
/// This does not need to be called it [`set_tls_dir()`](Self::set_tls_dir()) has been
|
||||||
|
/// called.
|
||||||
|
///
|
||||||
|
/// This should of course correspond to the key set in
|
||||||
|
/// [`set_cert()`](Self::set_cert())
|
||||||
|
pub fn set_key(mut self, key_path: impl Into<PathBuf>) -> Self {
|
||||||
|
self.key_path = key_path.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the timeout on incoming requests
|
/// Set the timeout on incoming requests
|
||||||
///
|
///
|
||||||
/// Note that this timeout is applied twice, once for the delivery of the request, and
|
/// Note that this timeout is applied twice, once for the delivery of the request, and
|
||||||
|
@ -278,7 +323,7 @@ impl<A: ToSocketAddrs> Builder<A> {
|
||||||
where
|
where
|
||||||
F: Fn(Request) -> HandlerResponse + Send + Sync + 'static,
|
F: Fn(Request) -> HandlerResponse + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let config = tls_config()
|
let config = tls_config(&self.cert_path, &self.key_path)
|
||||||
.context("Failed to create TLS config")?;
|
.context("Failed to create TLS config")?;
|
||||||
|
|
||||||
let listener = TcpListener::bind(self.addr).await
|
let listener = TcpListener::bind(self.addr).await
|
||||||
|
@ -345,12 +390,12 @@ async fn send_response_body(body: Body, stream: &mut (impl AsyncWrite + Unpin))
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tls_config() -> Result<Arc<ServerConfig>> {
|
fn tls_config(cert_path: &PathBuf, key_path: &PathBuf) -> Result<Arc<ServerConfig>> {
|
||||||
let mut config = ServerConfig::new(AllowAnonOrSelfsignedClient::new());
|
let mut config = ServerConfig::new(AllowAnonOrSelfsignedClient::new());
|
||||||
|
|
||||||
let cert_chain = load_cert_chain()
|
let cert_chain = load_cert_chain(cert_path)
|
||||||
.context("Failed to load TLS certificate")?;
|
.context("Failed to load TLS certificate")?;
|
||||||
let key = load_key()
|
let key = load_key(key_path)
|
||||||
.context("Failed to load TLS key")?;
|
.context("Failed to load TLS key")?;
|
||||||
config.set_single_cert(cert_chain, key)
|
config.set_single_cert(cert_chain, key)
|
||||||
.context("Failed to use loaded TLS certificate")?;
|
.context("Failed to use loaded TLS certificate")?;
|
||||||
|
@ -358,24 +403,22 @@ fn tls_config() -> Result<Arc<ServerConfig>> {
|
||||||
Ok(config.into())
|
Ok(config.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_cert_chain() -> Result<Vec<Certificate>> {
|
fn load_cert_chain(cert_path: &PathBuf) -> Result<Vec<Certificate>> {
|
||||||
let cert_path = "cert/cert.pem";
|
|
||||||
let certs = std::fs::File::open(cert_path)
|
let certs = std::fs::File::open(cert_path)
|
||||||
.with_context(|| format!("Failed to open `{}`", cert_path))?;
|
.with_context(|| format!("Failed to open `{:?}`", cert_path))?;
|
||||||
let mut certs = BufReader::new(certs);
|
let mut certs = BufReader::new(certs);
|
||||||
let certs = rustls::internal::pemfile::certs(&mut certs)
|
let certs = rustls::internal::pemfile::certs(&mut certs)
|
||||||
.map_err(|_| anyhow!("failed to load certs `{}`", cert_path))?;
|
.map_err(|_| anyhow!("failed to load certs `{:?}`", cert_path))?;
|
||||||
|
|
||||||
Ok(certs)
|
Ok(certs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_key() -> Result<PrivateKey> {
|
fn load_key(key_path: &PathBuf) -> Result<PrivateKey> {
|
||||||
let key_path = "cert/key.pem";
|
|
||||||
let keys = std::fs::File::open(key_path)
|
let keys = std::fs::File::open(key_path)
|
||||||
.with_context(|| format!("Failed to open `{}`", key_path))?;
|
.with_context(|| format!("Failed to open `{:?}`", key_path))?;
|
||||||
let mut keys = BufReader::new(keys);
|
let mut keys = BufReader::new(keys);
|
||||||
let mut keys = rustls::internal::pemfile::pkcs8_private_keys(&mut keys)
|
let mut keys = rustls::internal::pemfile::pkcs8_private_keys(&mut keys)
|
||||||
.map_err(|_| anyhow!("failed to load key `{}`", key_path))?;
|
.map_err(|_| anyhow!("failed to load key `{:?}`", key_path))?;
|
||||||
|
|
||||||
ensure!(!keys.is_empty(), "no key found");
|
ensure!(!keys.is_empty(), "no key found");
|
||||||
|
|
||||||
|
|
|
@ -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;
|
use crate::types::Document;
|
||||||
|
|
||||||
|
@ -37,6 +39,7 @@ impl<'a> From<&'a str> for Body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="serve_dir")]
|
||||||
impl From<File> for Body {
|
impl From<File> for Body {
|
||||||
fn from(file: File) -> Self {
|
fn from(file: File) -> Self {
|
||||||
Self::Reader(Box::new(file))
|
Self::Reader(Box::new(file))
|
||||||
|
|
|
@ -39,7 +39,6 @@
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use itertools::Itertools;
|
|
||||||
use crate::types::URIReference;
|
use crate::types::URIReference;
|
||||||
use crate::util::Cowy;
|
use crate::util::Cowy;
|
||||||
|
|
||||||
|
@ -550,5 +549,6 @@ fn strip_newlines(text: impl Cowy<str>) -> String {
|
||||||
text.as_ref()
|
text.as_ref()
|
||||||
.lines()
|
.lines()
|
||||||
.filter(|part| !part.is_empty())
|
.filter(|part| !part.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
.join(" ")
|
.join(" ")
|
||||||
}
|
}
|
||||||
|
|
50
src/util.rs
50
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 mime::Mime;
|
||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
|
#[cfg(feature="serve_dir")]
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
io,
|
io,
|
||||||
};
|
};
|
||||||
use crate::types::{Response, Document, document::HeadingLevel::*};
|
#[cfg(feature="serve_dir")]
|
||||||
use itertools::Itertools;
|
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<P: AsRef<Path>>(path: P, mime: &Mime) -> Result<Response> {
|
pub async fn serve_file<P: AsRef<Path>>(path: P, mime: &Mime) -> Result<Response> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
@ -22,6 +30,7 @@ pub async fn serve_file<P: AsRef<Path>>(path: P, mime: &Mime) -> Result<Response
|
||||||
Ok(Response::success_with_body(mime, file))
|
Ok(Response::success_with_body(mime, file))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="serve_dir")]
|
||||||
pub async fn serve_dir<D: AsRef<Path>, P: AsRef<Path>>(dir: D, virtual_path: &[P]) -> Result<Response> {
|
pub async fn serve_dir<D: AsRef<Path>, P: AsRef<Path>>(dir: D, virtual_path: &[P]) -> Result<Response> {
|
||||||
debug!("Dir: {}", dir.as_ref().display());
|
debug!("Dir: {}", dir.as_ref().display());
|
||||||
let dir = dir.as_ref().canonicalize()
|
let dir = dir.as_ref().canonicalize()
|
||||||
|
@ -47,6 +56,7 @@ pub async fn serve_dir<D: AsRef<Path>, P: AsRef<Path>>(dir: D, virtual_path: &[P
|
||||||
serve_dir_listing(path, virtual_path).await
|
serve_dir_listing(path, virtual_path).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="serve_dir")]
|
||||||
async fn serve_dir_listing<P: AsRef<Path>, B: AsRef<Path>>(path: P, virtual_path: &[B]) -> Result<Response> {
|
async fn serve_dir_listing<P: AsRef<Path>, B: AsRef<Path>>(path: P, virtual_path: &[B]) -> Result<Response> {
|
||||||
let mut dir = match fs::read_dir(path).await {
|
let mut dir = match fs::read_dir(path).await {
|
||||||
Ok(dir) => dir,
|
Ok(dir) => dir,
|
||||||
|
@ -56,10 +66,10 @@ async fn serve_dir_listing<P: AsRef<Path>, B: AsRef<Path>>(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();
|
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();
|
document.add_blank_line();
|
||||||
|
|
||||||
if virtual_path.get(0).map(<_>::as_ref) != Some(Path::new("")) {
|
if virtual_path.get(0).map(<_>::as_ref) != Some(Path::new("")) {
|
||||||
|
@ -85,6 +95,7 @@ async fn serve_dir_listing<P: AsRef<Path>, B: AsRef<Path>>(path: P, virtual_path
|
||||||
Ok(Response::document(document))
|
Ok(Response::document(document))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="serve_dir")]
|
||||||
pub fn guess_mime_from_path<P: AsRef<Path>>(path: P) -> Mime {
|
pub fn guess_mime_from_path<P: AsRef<Path>>(path: P) -> Mime {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let extension = path.extension().and_then(|s| s.to_str());
|
let extension = path.extension().and_then(|s| s.to_str());
|
||||||
|
@ -115,3 +126,32 @@ where
|
||||||
C: AsRef<T> + Into<T::Owned>,
|
C: AsRef<T> + Into<T::Owned>,
|
||||||
T: ToOwned + ?Sized,
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue