Merge pull request #10 from panicbit/2_better_errors
improve error messages
This commit is contained in:
commit
2528ce21b8
|
@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Changed
|
### Changed
|
||||||
- `Meta::new` rejects strings exceeding `Meta::MAX_LEN` (`1024`)
|
- `Meta::new` rejects strings exceeding `Meta::MAX_LEN` (`1024`)
|
||||||
- Some `Response` and `Status` constructors are now infallible
|
- Some `Response` and `Status` constructors are now infallible
|
||||||
|
- Improve error messages
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
- Instead of `gemini_mime()` use `GEMINI_MIME`
|
- Instead of `gemini_mime()` use `GEMINI_MIME`
|
||||||
|
|
|
@ -19,3 +19,6 @@ 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"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "0.8.1"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
use futures::{future::BoxFuture, FutureExt};
|
use futures::{future::BoxFuture, FutureExt};
|
||||||
|
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};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -10,6 +11,10 @@ type CertBytes = Vec<u8>;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
env_logger::builder()
|
||||||
|
.filter_module("northstar", LevelFilter::Debug)
|
||||||
|
.init();
|
||||||
|
|
||||||
let users = Arc::<RwLock::<HashMap<CertBytes, String>>>::default();
|
let users = Arc::<RwLock::<HashMap<CertBytes, String>>>::default();
|
||||||
|
|
||||||
Server::bind(("0.0.0.0", GEMINI_PORT))
|
Server::bind(("0.0.0.0", GEMINI_PORT))
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
use futures::{future::BoxFuture, FutureExt};
|
use futures::{future::BoxFuture, FutureExt};
|
||||||
|
use log::LevelFilter;
|
||||||
use northstar::{Server, Request, Response, GEMINI_PORT};
|
use northstar::{Server, Request, Response, GEMINI_PORT};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
env_logger::builder()
|
||||||
|
.filter_module("northstar", LevelFilter::Debug)
|
||||||
|
.init();
|
||||||
|
|
||||||
Server::bind(("localhost", GEMINI_PORT))
|
Server::bind(("localhost", GEMINI_PORT))
|
||||||
.serve(handle_request)
|
.serve(handle_request)
|
||||||
.await
|
.await
|
||||||
|
|
65
src/lib.rs
65
src/lib.rs
|
@ -44,22 +44,25 @@ impl Server {
|
||||||
|
|
||||||
async fn serve(self) -> Result<()> {
|
async fn serve(self) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
let (stream, _addr) = self.listener.accept().await?;
|
let (stream, _addr) = self.listener.accept().await
|
||||||
|
.context("Failed to accept client")?;
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(err) = this.serve_client(stream).await {
|
if let Err(err) = this.serve_client(stream).await {
|
||||||
error!("{}", err);
|
error!("{:?}", err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_client(self, stream: TcpStream) -> Result<()> {
|
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 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());
|
debug!("Client requested: {}", request.uri());
|
||||||
|
|
||||||
// Identify the client certificate from the tls stream. This is the first
|
// Identify the client certificate from the tls stream. This is the first
|
||||||
|
@ -78,13 +81,16 @@ impl Server {
|
||||||
let response = handler.catch_unwind().await
|
let response = handler.catch_unwind().await
|
||||||
.unwrap_or_else(|_| Response::server_error(""))
|
.unwrap_or_else(|_| Response::server_error(""))
|
||||||
.or_else(|err| {
|
.or_else(|err| {
|
||||||
error!("Handler: {}", err);
|
error!("Handler failed: {:?}", err);
|
||||||
Response::server_error("")
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -103,11 +109,15 @@ 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()
|
||||||
|
.context("Failed to create TLS config")?;
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(self.addr).await
|
||||||
|
.context("Failed to create socket")?;
|
||||||
|
|
||||||
let server = Server {
|
let server = Server {
|
||||||
tls_acceptor: TlsAcceptor::from(config),
|
tls_acceptor: TlsAcceptor::from(config),
|
||||||
listener: Arc::new(TcpListener::bind(self.addr).await?),
|
listener: Arc::new(listener),
|
||||||
handler: Arc::new(handler),
|
handler: Arc::new(handler),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -134,17 +144,22 @@ async fn receive_request(stream: &mut (impl AsyncBufRead + Unpin)) -> Result<Req
|
||||||
uri.pop();
|
uri.pop();
|
||||||
uri.pop();
|
uri.pop();
|
||||||
|
|
||||||
let uri = URIReference::try_from(&*uri)?.into_owned();
|
let uri = URIReference::try_from(&*uri)
|
||||||
let request = Request::from_uri(uri)?;
|
.context("Request URI is invalid")?
|
||||||
|
.into_owned();
|
||||||
|
let request = Request::from_uri(uri)
|
||||||
|
.context("Failed to create request from URI")?;
|
||||||
|
|
||||||
Ok(request)
|
Ok(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_response(mut response: Response, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> {
|
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() {
|
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(())
|
Ok(())
|
||||||
|
@ -174,26 +189,34 @@ async fn send_response_body(body: Body, stream: &mut (impl AsyncWrite + Unpin))
|
||||||
fn tls_config() -> Result<Arc<ServerConfig>> {
|
fn tls_config() -> 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()
|
||||||
let key = load_key()?;
|
.context("Failed to load TLS certificate")?;
|
||||||
config.set_single_cert(cert_chain, key)?;
|
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())
|
Ok(config.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_cert_chain() -> Result<Vec<Certificate>> {
|
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 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"))?;
|
.map_err(|_| anyhow!("failed to load certs `{}`", cert_path))?;
|
||||||
|
|
||||||
Ok(certs)
|
Ok(certs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_key() -> Result<PrivateKey> {
|
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)
|
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");
|
ensure!(!keys.is_empty(), "no key found");
|
||||||
|
|
||||||
|
|
10
src/types.rs
10
src/types.rs
|
@ -27,7 +27,8 @@ impl Request {
|
||||||
None => None,
|
None => None,
|
||||||
Some(query) => {
|
Some(query) => {
|
||||||
let input = percent_decode_str(query.as_str())
|
let input = percent_decode_str(query.as_str())
|
||||||
.decode_utf8()?
|
.decode_utf8()
|
||||||
|
.context("Request URI query contains invalid UTF-8")?
|
||||||
.into_owned();
|
.into_owned();
|
||||||
Some(input)
|
Some(input)
|
||||||
}
|
}
|
||||||
|
@ -84,7 +85,7 @@ impl ResponseHeader {
|
||||||
pub fn input(prompt: impl AsRef<str> + Into<String>) -> Result<Self> {
|
pub fn input(prompt: impl AsRef<str> + Into<String>) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
status: Status::INPUT,
|
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> {
|
pub fn server_error(reason: impl AsRef<str> + Into<String>) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
status: Status::PERMANENT_FAILURE,
|
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> {
|
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)
|
Ok(mime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
src/util.rs
12
src/util.rs
|
@ -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> {
|
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()
|
||||||
|
.context("Failed to canonicalize directory")?;
|
||||||
let mut path = dir.to_path_buf();
|
let mut path = dir.to_path_buf();
|
||||||
|
|
||||||
for segment in virtual_path {
|
for segment in virtual_path {
|
||||||
path.push(segment);
|
path.push(segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = path.canonicalize()?;
|
let path = path.canonicalize()
|
||||||
|
.context("Failed to canonicalize path")?;
|
||||||
|
|
||||||
if !path.starts_with(&dir) {
|
if !path.starts_with(&dir) {
|
||||||
return Ok(Response::not_found());
|
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, "=> .. 📁 ../")?;
|
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 = entry.file_name();
|
||||||
let file_name = file_name.to_string_lossy();
|
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!(
|
writeln!(
|
||||||
listing,
|
listing,
|
||||||
|
|
Loading…
Reference in a new issue