feat: error handling

This commit is contained in:
hexlocation 2025-07-29 20:38:43 +02:00
parent 3c4f3b7533
commit 17c802bab8
6 changed files with 145 additions and 59 deletions

View file

@ -65,7 +65,7 @@ impl Endpoint {
&mut self,
db: &mut BoxyDatabase,
hostname: String,
) -> Result<(), Box<dyn Error>> {
) -> Result<(), tokio_postgres::Error> {
let tx = db.client.transaction().await?;
let endpoint_id: i32 = tx

View file

@ -68,11 +68,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let database_shared = Arc::new(Mutex::new(Box::leak(database)));
let api_svc = ApiService {
database: database_shared.clone(),
config: config.clone(),
_address: None,
};
let api_svc = ApiService::new(database_shared.clone(), config.clone()).await;
let svc = ControllerService {
database: database_shared,

View file

@ -2,10 +2,7 @@ use std::{any::type_name_of_val, error::Error};
use http_body_util::{Either, Full};
use hyper::{
Request, Response,
body::{Body, Bytes, Incoming},
server::conn::http1,
service::{HttpService, Service},
body::{Body, Bytes, Incoming}, server::conn::http1, service::{HttpService, Service}, Request, Response, StatusCode
};
use hyper_util::rt::TokioIo;
use log::{error, info};
@ -28,6 +25,22 @@ pub trait TcpIntercept {
fn stream(&mut self, stream: &TcpStream);
}
pub async fn default_response() -> GeneralResponse {
Response::builder()
.status(404)
.body(GeneralBody::Right(Full::from(Bytes::from(
"That route doesn't exist.",
))))
.unwrap()
}
pub async fn custom_resp(e: StatusCode, m: &'static str) -> GeneralResponse {
Response::builder()
.status(e)
.body(GeneralBody::Right(Full::from(Bytes::from(m))))
.unwrap()
}
impl<S> Server<S>
where
S: TcpIntercept,

View file

@ -7,13 +7,13 @@ use hyper::{
body::{Bytes, Incoming},
service::Service,
};
use log::{debug, warn};
use log::{debug, error, warn};
use tokio::{net::TcpStream, sync::Mutex};
use crate::{
config::{Client, Config},
db::{BoxyDatabase, Endpoint},
server::{GeneralBody, GeneralResponse, TcpIntercept},
server::{custom_resp, default_response, GeneralBody, GeneralResponse, TcpIntercept},
};
#[derive(Debug, Clone)]
@ -23,21 +23,6 @@ pub struct ApiService {
pub _address: Option<IpAddr>,
}
async fn default_response() -> GeneralResponse {
Response::builder()
.status(404)
.body(GeneralBody::Right(Full::from(Bytes::from(
"That route doesn't exist.",
))))
.unwrap()
}
async fn custom_resp(e: StatusCode, m: &'static str) -> GeneralResponse {
Response::builder()
.status(e)
.body(GeneralBody::Right(Full::from(Bytes::from(m))))
.unwrap()
}
impl TcpIntercept for ApiService {
fn stream(&mut self, stream: &TcpStream) {
@ -45,6 +30,16 @@ impl TcpIntercept for ApiService {
}
}
impl ApiService {
pub async fn new(database: Arc<Mutex<&'static mut BoxyDatabase>>, config: Config) -> Self {
Self {
database,
config,
_address: None,
}
}
}
impl Service<Request<Incoming>> for ApiService {
type Response = GeneralResponse;
type Error = hyper::Error;
@ -53,7 +48,7 @@ impl Service<Request<Incoming>> for ApiService {
fn call(&self, req: Request<Incoming>) -> Self::Future {
let database = self.database.clone();
let config = self.config.clone();
let address = self._address.clone().unwrap();
let address = self._address.unwrap();
Box::pin(async move {
match *req.method() {
@ -61,19 +56,55 @@ impl Service<Request<Incoming>> for ApiService {
"/register" => {
debug!("new api register request from {}", address);
let encoded_header = req
.headers()
.get(hyper::header::AUTHORIZATION)
.unwrap()
let encoded_header =
match req.headers().get(hyper::header::AUTHORIZATION) {
None => {
error!(
"Authorization header not given for request from {address}",
);
return Ok(custom_resp(
StatusCode::BAD_REQUEST,
"Invalid credentials.",
)
.await);
}
Some(x) => x,
}
.to_str()
.unwrap();
debug!("authorization header: {}", encoded_header);
let auth_string = String::from_utf8(
BASE64_STANDARD.decode(&encoded_header[6..]).unwrap(),
)
.unwrap();
let auth_bytes = match BASE64_STANDARD.decode(&encoded_header[6..]) {
Ok(x) => x,
Err(e) => {
error!(
"Error while decoding authorization header from {address}: {e}",
);
return Ok(custom_resp(
StatusCode::BAD_REQUEST,
"Invalid base64 string given.",
)
.await);
}
};
let auth_string = match String::from_utf8(auth_bytes) {
Ok(x) => x,
Err(e) => {
error!(
"Error while decoding authorization header from {address}: {e}",
);
return Ok(custom_resp(
StatusCode::BAD_REQUEST,
"Invalid UTF-8 in authentication string.",
)
.await);
}
};
debug!("decoded auth string: {}", auth_string);
@ -90,7 +121,7 @@ impl Service<Request<Incoming>> for ApiService {
.await);
}
let body = String::from_utf8(
let body = match String::from_utf8(
req.collect()
.await
.unwrap()
@ -98,16 +129,40 @@ impl Service<Request<Incoming>> for ApiService {
.iter()
.cloned()
.collect::<Vec<u8>>(),
)
.unwrap();
let json = json::parse(body.as_str()).unwrap();
) {
Ok(x) => x,
Err(e) => {
error!(
"Error while inferring UTF-8 string from {address}'s request body: {e}",
);
return Ok(custom_resp(
StatusCode::BAD_REQUEST,
"Invalid UTF-8 in body.",
)
.await);
}
};
let json = match json::parse(body.as_str()) {
Ok(x) => x,
Err(e) => {
error!("Error while parsing JSON body from {address}: {e}",);
return Ok(custom_resp(
StatusCode::BAD_REQUEST,
"Invalid JSON in body.",
)
.await);
}
};
debug!("body: {}", body);
let mut endpoint = Endpoint::new(
None,
address,
json["port"].as_u16().unwrap(),
json["port"].as_u16().unwrap_or(8080),
json["callback"].as_str().unwrap_or("/").to_string(),
)
.await;
@ -120,7 +175,7 @@ impl Service<Request<Incoming>> for ApiService {
.await
.unwrap();
Ok(custom_resp(StatusCode::OK, "").await)
Ok(custom_resp(StatusCode::OK, "Success").await)
}
_ => Ok(default_response().await),
},

View file

@ -1,15 +1,12 @@
use std::{pin::Pin, sync::Arc};
use hyper::{
Request,
body::Incoming,
service::Service,
};
use hyper::{Request, StatusCode, body::Incoming, service::Service};
use log::error;
use tokio::sync::Mutex;
use crate::{
db::{BoxyDatabase, Endpoint},
server::{GeneralResponse, TcpIntercept},
server::{GeneralResponse, TcpIntercept, custom_resp},
};
use super::proxy::ProxyService;
@ -31,19 +28,32 @@ impl Service<Request<Incoming>> for ControllerService {
fn call(&self, req: Request<Incoming>) -> Self::Future {
let database = self.database.clone();
Box::pin(async move {
let hostname = req
.headers()
.get(hyper::header::HOST)
.unwrap()
.to_str()
.unwrap()
.to_string();
let hostname = match req.headers().get(hyper::header::HOST) {
Some(x) => x,
None => {
error!("No host header given.");
return Ok(custom_resp(StatusCode::BAD_REQUEST, "No host header given.").await);
}
}
.to_str()
.unwrap()
.to_string();
let endpoints = Endpoint::get_by_hostname(*database.lock().await, hostname.clone())
.await
.unwrap();
let endpoint = endpoints.first().unwrap();
let endpoint = match endpoints.first() {
Some(x) => x,
None => {
error!("No endpoint found for request.");
return Ok(
custom_resp(StatusCode::NOT_FOUND, "No endpoint found for host.").await,
);
}
};
let proxy = ProxyService {
address: format!("{}:{}", endpoint.address.clone(), endpoint.port),

View file

@ -1,11 +1,11 @@
use std::pin::Pin;
use hyper::{Request, body::Incoming, service::Service};
use hyper::{Request, StatusCode, body::Incoming, service::Service};
use hyper_util::rt::TokioIo;
use log::error;
use tokio::net::TcpStream;
use crate::server::{GeneralResponse, to_general_response};
use crate::server::{GeneralResponse, custom_resp, to_general_response};
#[derive(Debug, Clone)]
pub struct ProxyService {
@ -22,7 +22,19 @@ impl Service<Request<Incoming>> for ProxyService {
let address = self.address.clone();
let hostname = self.hostname.clone();
Box::pin(async move {
let stream = TcpStream::connect(address).await.unwrap();
let stream = match TcpStream::connect(address).await {
Ok(x) => x,
Err(e) => {
error!("Could not open connection to endpoint: {e}");
return Ok(custom_resp(
StatusCode::BAD_GATEWAY,
"Unable to open connection to endpoint.",
)
.await);
}
};
let io = TokioIo::new(stream);
let (mut sender, conn) = hyper::client::conn::http1::Builder::new()