improve error messages
This commit is contained in:
parent
a70c5ddf16
commit
bfb6282a9b
|
@ -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`
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
65
src/lib.rs
65
src/lib.rs
|
@ -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");
|
||||
|
||||
|
|
10
src/types.rs
10
src/types.rs
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
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> {
|
||||
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,
|
||||
|
|
Loading…
Reference in a new issue