improve error messages

gemtext
panicbit 2020-11-14 03:56:50 +01:00
parent a70c5ddf16
commit bfb6282a9b
7 changed files with 72 additions and 29 deletions

View File

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- `Meta::new` rejects strings exceeding `Meta::MAX_LEN` (`1024`)
- Some `Response` and `Status` constructors are now infallible
- Improve error messages
### Deprecated
- Instead of `gemini_mime()` use `GEMINI_MIME`

View File

@ -19,3 +19,6 @@ itertools = "0.9.0"
log = "0.4.11"
webpki = "0.21.0"
lazy_static = "1.4.0"
[dev-dependencies]
env_logger = "0.8.1"

View File

@ -1,5 +1,6 @@
use anyhow::*;
use futures::{future::BoxFuture, FutureExt};
use log::LevelFilter;
use tokio::sync::RwLock;
use northstar::{Certificate, GEMINI_MIME, GEMINI_PORT, Request, Response, Server};
use std::collections::HashMap;
@ -10,6 +11,10 @@ type CertBytes = Vec<u8>;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::builder()
.filter_module("northstar", LevelFilter::Debug)
.init();
let users = Arc::<RwLock::<HashMap<CertBytes, String>>>::default();
Server::bind(("0.0.0.0", GEMINI_PORT))

View File

@ -1,9 +1,14 @@
use anyhow::*;
use futures::{future::BoxFuture, FutureExt};
use log::LevelFilter;
use northstar::{Server, Request, Response, GEMINI_PORT};
#[tokio::main]
async fn main() -> Result<()> {
env_logger::builder()
.filter_module("northstar", LevelFilter::Debug)
.init();
Server::bind(("localhost", GEMINI_PORT))
.serve(handle_request)
.await

View File

@ -44,22 +44,25 @@ impl Server {
async fn serve(self) -> Result<()> {
loop {
let (stream, _addr) = self.listener.accept().await?;
let (stream, _addr) = self.listener.accept().await
.context("Failed to accept client")?;
let this = self.clone();
tokio::spawn(async move {
if let Err(err) = this.serve_client(stream).await {
error!("{}", err);
error!("{:?}", err);
}
});
}
}
async fn serve_client(self, stream: TcpStream) -> Result<()> {
let stream = self.tls_acceptor.accept(stream).await?;
let stream = self.tls_acceptor.accept(stream).await
.context("Failed to establish TLS session")?;
let mut stream = BufStream::new(stream);
let mut request = receive_request(&mut stream).await?;
let mut request = receive_request(&mut stream).await
.context("Failed to receive request")?;
debug!("Client requested: {}", request.uri());
// Identify the client certificate from the tls stream. This is the first
@ -78,13 +81,16 @@ impl Server {
let response = handler.catch_unwind().await
.unwrap_or_else(|_| Response::server_error(""))
.or_else(|err| {
error!("Handler: {}", err);
error!("Handler failed: {:?}", err);
Response::server_error("")
})?;
})
.context("Request handler failed")?;
send_response(response, &mut stream).await?;
send_response(response, &mut stream).await
.context("Failed to send response")?;
stream.flush().await?;
stream.flush().await
.context("Failed to flush response data")?;
Ok(())
}
@ -103,11 +109,15 @@ impl<A: ToSocketAddrs> Builder<A> {
where
F: Fn(Request) -> HandlerResponse + Send + Sync + 'static,
{
let config = tls_config()?;
let config = tls_config()
.context("Failed to create TLS config")?;
let listener = TcpListener::bind(self.addr).await
.context("Failed to create socket")?;
let server = Server {
tls_acceptor: TlsAcceptor::from(config),
listener: Arc::new(TcpListener::bind(self.addr).await?),
listener: Arc::new(listener),
handler: Arc::new(handler),
};
@ -134,17 +144,22 @@ async fn receive_request(stream: &mut (impl AsyncBufRead + Unpin)) -> Result<Req
uri.pop();
uri.pop();
let uri = URIReference::try_from(&*uri)?.into_owned();
let request = Request::from_uri(uri)?;
let uri = URIReference::try_from(&*uri)
.context("Request URI is invalid")?
.into_owned();
let request = Request::from_uri(uri)
.context("Failed to create request from URI")?;
Ok(request)
}
async fn send_response(mut response: Response, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> {
send_response_header(response.header(), stream).await?;
send_response_header(response.header(), stream).await
.context("Failed to send response header")?;
if let Some(body) = response.take_body() {
send_response_body(body, stream).await?;
send_response_body(body, stream).await
.context("Failed to send response body")?;
}
Ok(())
@ -174,26 +189,34 @@ async fn send_response_body(body: Body, stream: &mut (impl AsyncWrite + Unpin))
fn tls_config() -> Result<Arc<ServerConfig>> {
let mut config = ServerConfig::new(AllowAnonOrSelfsignedClient::new());
let cert_chain = load_cert_chain()?;
let key = load_key()?;
config.set_single_cert(cert_chain, key)?;
let cert_chain = load_cert_chain()
.context("Failed to load TLS certificate")?;
let key = load_key()
.context("Failed to load TLS key")?;
config.set_single_cert(cert_chain, key)
.context("Failed to use loaded TLS certificate")?;
Ok(config.into())
}
fn load_cert_chain() -> Result<Vec<Certificate>> {
let certs = std::fs::File::open("cert/cert.pem")?;
let cert_path = "cert/cert.pem";
let certs = std::fs::File::open(cert_path)
.with_context(|| format!("Failed to open `{}`", cert_path))?;
let mut certs = BufReader::new(certs);
let certs = rustls::internal::pemfile::certs(&mut certs)
.map_err(|_| anyhow!("failed to load certs"))?;
.map_err(|_| anyhow!("failed to load certs `{}`", cert_path))?;
Ok(certs)
}
fn load_key() -> Result<PrivateKey> {
let mut keys = BufReader::new(std::fs::File::open("cert/key.pem")?);
let key_path = "cert/key.pem";
let keys = std::fs::File::open(key_path)
.with_context(|| format!("Failed to open `{}`", key_path))?;
let mut keys = BufReader::new(keys);
let mut keys = rustls::internal::pemfile::pkcs8_private_keys(&mut keys)
.map_err(|_| anyhow!("failed to load key"))?;
.map_err(|_| anyhow!("failed to load key `{}`", key_path))?;
ensure!(!keys.is_empty(), "no key found");

View File

@ -27,7 +27,8 @@ impl Request {
None => None,
Some(query) => {
let input = percent_decode_str(query.as_str())
.decode_utf8()?
.decode_utf8()
.context("Request URI query contains invalid UTF-8")?
.into_owned();
Some(input)
}
@ -84,7 +85,7 @@ impl ResponseHeader {
pub fn input(prompt: impl AsRef<str> + Into<String>) -> Result<Self> {
Ok(Self {
status: Status::INPUT,
meta: Meta::new(prompt)?,
meta: Meta::new(prompt).context("Invalid input prompt")?,
})
}
@ -105,7 +106,7 @@ impl ResponseHeader {
pub fn server_error(reason: impl AsRef<str> + Into<String>) -> Result<Self> {
Ok(Self {
status: Status::PERMANENT_FAILURE,
meta: Meta::new(reason)?,
meta: Meta::new(reason).context("Invalid server error reason")?,
})
}
@ -269,7 +270,8 @@ impl Meta {
}
pub fn to_mime(&self) -> Result<Mime> {
let mime = self.as_str().parse::<Mime>()?;
let mime = self.as_str().parse::<Mime>()
.context("Meta is not a valid MIME")?;
Ok(mime)
}
}

View File

@ -25,14 +25,16 @@ pub async fn serve_file<P: AsRef<Path>>(path: P, mime: &Mime) -> 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());
let dir = dir.as_ref().canonicalize()?;
let dir = dir.as_ref().canonicalize()
.context("Failed to canonicalize directory")?;
let mut path = dir.to_path_buf();
for segment in virtual_path {
path.push(segment);
}
let path = path.canonicalize()?;
let path = path.canonicalize()
.context("Failed to canonicalize path")?;
if !path.starts_with(&dir) {
return Ok(Response::not_found());
@ -67,10 +69,12 @@ async fn serve_dir_listing<P: AsRef<Path>, B: AsRef<Path>>(path: P, virtual_path
writeln!(listing, "=> .. 📁 ../")?;
}
while let Some(entry) = dir.next_entry().await? {
while let Some(entry) = dir.next_entry().await.context("Failed to list directory")? {
let file_name = entry.file_name();
let file_name = file_name.to_string_lossy();
let is_dir = entry.file_type().await?.is_dir();
let is_dir = entry.file_type().await
.with_context(|| format!("Failed to get file type of `{}`", entry.path().display()))?
.is_dir();
writeln!(
listing,