Simplify the http service a bunch, better error handling

This commit is contained in:
Brian Picciano 2023-07-08 16:04:33 +02:00
parent 82290d8b0b
commit a3c823c7b2
2 changed files with 31 additions and 27 deletions

View File

@ -4,15 +4,12 @@ mod tpl;
use hyper::{Body, Method, Request, Response}; use hyper::{Body, Method, Request, Response};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::Infallible;
use std::str::FromStr; use std::str::FromStr;
use std::{future, net, sync}; use std::{future, net, sync};
use crate::error::unexpected; use crate::error::unexpected;
use crate::{domain, service, util}; use crate::{domain, service, util};
type SvcResponse = Result<Response<Body>, String>;
pub struct Service { pub struct Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>, domain_manager: sync::Arc<dyn domain::manager::Manager>,
target_a: net::Ipv4Addr, target_a: net::Ipv4Addr,
@ -94,7 +91,7 @@ struct DomainSyncArgs {
} }
impl<'svc> Service { impl<'svc> Service {
fn serve(&self, status_code: u16, path: &'_ str, body: Body) -> SvcResponse { fn serve(&self, status_code: u16, path: &'_ str, body: Body) -> Response<Body> {
let content_type = mime_guess::from_path(path) let content_type = mime_guess::from_path(path)
.first_or_octet_stream() .first_or_octet_stream()
.to_string(); .to_string();
@ -104,12 +101,22 @@ impl<'svc> Service {
.header("Content-Type", content_type) .header("Content-Type", content_type)
.body(body) .body(body)
{ {
Ok(res) => Ok(res), Ok(res) => res,
Err(err) => Err(format!("failed to build {}: {}", path, err)), Err(err) => {
if status_code == 500 {
panic!("failed to build {}: {}", path, err);
}
self.serve(
500,
"error.txt",
format!("failed to build {}: {}", path, err).into(),
)
}
} }
} }
fn render<T>(&self, status_code: u16, name: &'_ str, value: T) -> SvcResponse fn render<T>(&self, status_code: u16, name: &'_ str, value: T) -> Response<Body>
where where
T: Serialize, T: Serialize,
{ {
@ -127,7 +134,7 @@ impl<'svc> Service {
self.serve(status_code, name, rendered.into()) self.serve(status_code, name, rendered.into())
} }
fn render_error_page(&'svc self, status_code: u16, e: &'_ str) -> SvcResponse { fn render_error_page(&self, status_code: u16, e: &'_ str) -> Response<Body> {
#[derive(Serialize)] #[derive(Serialize)]
struct Response<'a> { struct Response<'a> {
error_msg: &'a str, error_msg: &'a str,
@ -143,7 +150,7 @@ impl<'svc> Service {
) )
} }
fn render_page<T>(&self, name: &'_ str, data: T) -> SvcResponse fn render_page<T>(&self, name: &'_ str, data: T) -> Response<Body>
where where
T: Serialize, T: Serialize,
{ {
@ -157,7 +164,7 @@ impl<'svc> Service {
) )
} }
fn serve_origin(&self, domain: domain::Name, path: &'_ str) -> SvcResponse { fn serve_origin(&self, domain: domain::Name, path: &'_ str) -> Response<Body> {
let mut path_owned; let mut path_owned;
let path = match path.ends_with('/') { let path = match path.ends_with('/') {
@ -183,20 +190,24 @@ impl<'svc> Service {
} }
} }
async fn with_query_req<'a, F, In, Out>(&self, req: &'a Request<Body>, f: F) -> SvcResponse async fn with_query_req<'a, F, In, Out>(&self, req: &'a Request<Body>, f: F) -> Response<Body>
where where
In: Deserialize<'a>, In: Deserialize<'a>,
F: FnOnce(In) -> Out, F: FnOnce(In) -> Out,
Out: future::Future<Output = SvcResponse>, Out: future::Future<Output = Response<Body>>,
{ {
let query = req.uri().query().unwrap_or(""); let query = req.uri().query().unwrap_or("");
match serde_urlencoded::from_str::<In>(query) { match serde_urlencoded::from_str::<In>(query) {
Ok(args) => f(args).await, Ok(args) => f(args).await,
Err(err) => Err(format!("failed to parse query args: {}", err)), Err(err) => self.serve(
400,
"error.txt",
format!("failed to parse query args: {}", err).into(),
),
} }
} }
fn domain_get(&self, args: DomainGetArgs) -> SvcResponse { fn domain_get(&self, args: DomainGetArgs) -> Response<Body> {
#[derive(Serialize)] #[derive(Serialize)]
struct Response { struct Response {
domain: domain::Name, domain: domain::Name,
@ -225,7 +236,7 @@ impl<'svc> Service {
&self, &self,
args: DomainInitArgs, args: DomainInitArgs,
domain_config: service::util::FlatConfig, domain_config: service::util::FlatConfig,
) -> SvcResponse { ) -> Response<Body> {
#[derive(Serialize)] #[derive(Serialize)]
struct Response { struct Response {
domain: domain::Name, domain: domain::Name,
@ -265,7 +276,7 @@ impl<'svc> Service {
&self, &self,
args: DomainSyncArgs, args: DomainSyncArgs,
domain_config: service::util::FlatConfig, domain_config: service::util::FlatConfig,
) -> SvcResponse { ) -> Response<Body> {
if args.passphrase != self.passphrase.as_str() { if args.passphrase != self.passphrase.as_str() {
return self.render_error_page(401, "Incorrect passphrase"); return self.render_error_page(401, "Incorrect passphrase");
} }
@ -307,7 +318,7 @@ impl<'svc> Service {
self.render_page("/domain_sync.html", response) self.render_page("/domain_sync.html", response)
} }
fn domains(&self) -> SvcResponse { fn domains(&self) -> Response<Body> {
#[derive(Serialize)] #[derive(Serialize)]
struct Response { struct Response {
domains: Vec<String>, domains: Vec<String>,
@ -330,7 +341,7 @@ impl<'svc> Service {
self.render_page("/domains.html", Response { domains }) self.render_page("/domains.html", Response { domains })
} }
async fn handle_request_inner(&self, req: Request<Body>) -> SvcResponse { async fn handle_request(&self, req: Request<Body>) -> Response<Body> {
let maybe_host = match ( let maybe_host = match (
req.headers() req.headers()
.get("Host") .get("Host")
@ -398,13 +409,6 @@ impl<'svc> Service {
_ => self.render_error_page(404, "Page not found!"), _ => self.render_error_page(404, "Page not found!"),
} }
} }
pub async fn handle_request(&self, req: Request<Body>) -> Result<Response<Body>, Infallible> {
match self.handle_request_inner(req).await {
Ok(res) => Ok(res),
Err(err) => panic!("unexpected error {err}"),
}
}
} }
fn strip_port(host: &str) -> &str { fn strip_port(host: &str) -> &str {

View File

@ -18,7 +18,7 @@ pub async fn listen_http(
// Create a `Service` for responding to the request. // Create a `Service` for responding to the request.
let hyper_service = hyper::service::service_fn(move |req| { let hyper_service = hyper::service::service_fn(move |req| {
let service = service.clone(); let service = service.clone();
async move { service.handle_request(req).await } async move { Ok::<_, convert::Infallible>(service.handle_request(req).await) }
}); });
// Return the service to hyper. // Return the service to hyper.
@ -48,7 +48,7 @@ pub async fn listen_https(
// Create a `Service` for responding to the request. // Create a `Service` for responding to the request.
let hyper_service = hyper::service::service_fn(move |req| { let hyper_service = hyper::service::service_fn(move |req| {
let service = service.clone(); let service = service.clone();
async move { service.handle_request(req).await } async move { Ok::<_, convert::Infallible>(service.handle_request(req).await) }
}); });
// Return the service to hyper. // Return the service to hyper.