Retrieve domain settings within each service, rather than in domain manager

This allows for fewer dependencies when initializing the services, and
more precise handling of the different cases.
main
Brian Picciano 4 months ago
parent 43502f82f9
commit 6a611c371c
  1. 63
      src/domain/manager.rs
  2. 3
      src/main.rs
  3. 61
      src/service/gemini.rs
  4. 106
      src/service/http.rs
  5. 16
      src/service/http/tasks.rs

@ -30,38 +30,7 @@ impl From<store::GetError> for GetSettingsError {
}
}
#[derive(thiserror::Error, Debug)]
pub enum GetFileError {
#[error("domain not found")]
DomainNotFound,
#[error("file not found")]
FileNotFound,
#[error(transparent)]
Unexpected(#[from] unexpected::Error),
}
impl From<GetSettingsError> for GetFileError {
fn from(e: GetSettingsError) -> Self {
match e {
GetSettingsError::NotFound => Self::DomainNotFound,
GetSettingsError::Unexpected(e) => Self::Unexpected(e),
}
}
}
impl From<origin::GetFileError> for GetFileError {
fn from(e: origin::GetFileError) -> Self {
match e {
origin::GetFileError::DescrNotSynced => {
Self::Unexpected(unexpected::Error::from("origin descr not synced"))
}
origin::GetFileError::FileNotFound => Self::FileNotFound,
origin::GetFileError::Unexpected(e) => Self::Unexpected(e),
}
}
}
pub type GetFileError = origin::GetFileError;
#[derive(thiserror::Error, Debug)]
pub enum SyncWithSettingsError {
@ -125,7 +94,7 @@ pub trait Manager: Sync + Send {
fn get_file(
&self,
domain: &domain::Name,
settings: &domain::Settings,
path: &str,
) -> Result<util::BoxByteStream, GetFileError>;
@ -425,34 +394,12 @@ impl Manager for ManagerImpl {
fn get_file(
&self,
domain: &domain::Name,
settings: &domain::Settings,
path: &str,
) -> Result<util::BoxByteStream, GetFileError> {
let settings = match self.get_settings(domain)? {
GetSettingsResult::Stored(settings) => settings,
GetSettingsResult::Builtin(config) => config.settings,
GetSettingsResult::Proxied(_) => {
return Err(
unexpected::Error::from("can't call get_file on proxied domain").into(),
);
}
GetSettingsResult::Interface => {
return Err(
unexpected::Error::from("can't call get_file on interface domain").into(),
);
}
GetSettingsResult::External(_) => {
return Err(GetFileError::DomainNotFound);
}
};
let path = settings.process_path(path);
let f = self
.origin_store
.get_file(&settings.origin_descr, path.as_ref())?;
Ok(f)
self.origin_store
.get_file(&settings.origin_descr, path.as_ref())
}
fn sync_with_settings(

@ -176,8 +176,6 @@ async fn main() {
domain_manager.clone(),
domani::domain::manager::HttpsCertResolver::from(domain_manager.clone()),
config.service.clone(),
config.domain.proxied_domains.clone(),
config.domain.interface_domain.clone(),
);
if gemini_enabled {
@ -186,7 +184,6 @@ async fn main() {
domain_manager.clone(),
domani::domain::manager::GeminiCertResolver::from(domain_manager.clone()),
config.service.gemini.clone(),
config.domain.proxied_domains.clone(),
);
}

@ -6,14 +6,13 @@ pub use config::*;
use crate::error::unexpected::{self, Mappable};
use crate::{domain, service, task_stack, util};
use std::{collections, sync};
use std::sync;
use tokio_util::sync::CancellationToken;
pub struct Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
config: Config,
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
}
#[derive(thiserror::Error, Debug)]
@ -31,7 +30,6 @@ impl Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: CertResolver,
config: Config,
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
) -> sync::Arc<Service>
where
CertResolver: rustls::server::ResolvesServerCert + 'static,
@ -40,7 +38,6 @@ impl Service {
domain_manager,
cert_resolver: sync::Arc::from(cert_resolver),
config,
proxied,
});
task_stack.push_spawn(|canceller| listen(service.clone(), canceller));
service
@ -74,7 +71,11 @@ impl Service {
Ok(())
}
async fn serve_conn<IO>(&self, domain: &domain::Name, conn: IO) -> Result<(), HandleConnError>
async fn serve_conn<IO>(
&self,
settings: &domain::Settings,
conn: IO,
) -> Result<(), HandleConnError>
where
IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
{
@ -96,15 +97,23 @@ impl Service {
let path = service::append_index_to_path(req.path(), "index.gmi");
let f = match self.domain_manager.get_file(domain, &path) {
use domain::manager::GetFileError;
let f = match self.domain_manager.get_file(settings, &path) {
Ok(f) => f,
Err(domain::manager::GetFileError::DomainNotFound) => {
return Err(unexpected::Error::from("domain not found when serving file").into())
}
Err(domain::manager::GetFileError::FileNotFound) => {
Err(GetFileError::FileNotFound) => {
return Ok(self.respond_conn(w, "51", "File not found", None).await?)
}
Err(domain::manager::GetFileError::Unexpected(e)) => return Err(e.into()),
Err(GetFileError::DescrNotSynced) => {
return Err(unexpected::Error::from(
format!(
"Backend for {:?} has not yet been synced",
settings.origin_descr
)
.as_str(),
)
.into())
}
Err(GetFileError::Unexpected(e)) => return Err(e.into()),
};
let content_type = service::guess_mime(&path);
@ -156,8 +165,14 @@ impl Service {
))
})?;
// If the domain should be proxied, then proxy it
if let Some(config) = self.proxied.get(&domain) {
use domain::manager::{GetSettingsError, GetSettingsResult};
let get_settings_res = self.domain_manager.get_settings(&domain);
// If the domain should be proxied, then proxy it. This case gets checked before
// any of the others because the others require terminating the TLS stream to be
// handled.
if let Ok(GetSettingsResult::Proxied(ref config)) = get_settings_res {
if let Some(ref gemini_url) = config.gemini_url {
let prefixed_conn = proxy::teed_io_to_prefixed(start.into_inner());
self.proxy_conn(gemini_url.addr.as_str(), prefixed_conn)
@ -166,8 +181,26 @@ impl Service {
}
}
// Terminate TLS stream
let conn = start.into_stream(tls_config).await.or_unexpected()?;
self.serve_conn(&domain, conn).await
let settings = match get_settings_res {
Ok(GetSettingsResult::Stored(settings)) => settings,
Ok(GetSettingsResult::Builtin(config)) => config.settings,
Ok(GetSettingsResult::Proxied(_)) => {
panic!("proxied case already handled")
}
Ok(GetSettingsResult::Interface)
| Ok(GetSettingsResult::External(_))
| Err(GetSettingsError::NotFound) => {
return Ok(self
.respond_conn(conn, "51", "File not found", None)
.await?)
}
Err(GetSettingsError::Unexpected(e)) => return Err(e.into()),
};
self.serve_conn(&settings, conn).await
}
Err(err) => {
return Err(unexpected::Error::from(

@ -10,7 +10,7 @@ use http::request::Parts;
use hyper::{Body, Method, Request, Response};
use serde::{Deserialize, Serialize};
use std::{collections, future, net, sync};
use std::{future, net, sync};
use crate::error::unexpected::{self, Mappable};
use crate::{domain, service, task_stack};
@ -50,8 +50,6 @@ pub struct Service {
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
handlebars: handlebars::Handlebars<'static>,
config: service::Config,
proxied_domains: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
interface_domain: Option<domain::Name>,
}
impl Service {
@ -60,8 +58,6 @@ impl Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: CertResolver,
config: service::Config,
proxied_domains: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
interface_domain: Option<domain::Name>,
) -> sync::Arc<Service>
where
CertResolver: rustls::server::ResolvesServerCert + 'static,
@ -71,8 +67,6 @@ impl Service {
cert_resolver: sync::Arc::from(cert_resolver),
handlebars: tpl::get(),
config,
proxied_domains,
interface_domain,
});
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
@ -208,18 +202,21 @@ impl Service {
)
}
async fn serve_origin(&self, domain: domain::Name, req: Request<Body>) -> Response<Body> {
async fn serve_origin(&self, settings: domain::Settings, req: Request<Body>) -> Response<Body> {
let path = service::append_index_to_path(req.uri().path(), "index.html");
match self.domain_manager.get_file(&domain, &path) {
use domain::manager::GetFileError;
match self.domain_manager.get_file(&settings, &path) {
Ok(f) => self.serve(200, &path, Body::wrap_stream(f)),
Err(domain::manager::GetFileError::DomainNotFound) => {
self.render_error_page(404, "Unknown domain name")
}
Err(domain::manager::GetFileError::FileNotFound) => {
self.render_error_page(404, "File not found")
}
Err(domain::manager::GetFileError::Unexpected(e)) => {
Err(GetFileError::FileNotFound) => self.render_error_page(404, "File not found"),
Err(GetFileError::DescrNotSynced) => self.internal_error(
format!(
"Backend for {:?} has not yet been synced",
settings.origin_descr
)
.as_str(),
),
Err(GetFileError::Unexpected(e)) => {
self.internal_error(format!("failed to fetch file {path}: {e}").as_str())
}
}
@ -546,41 +543,56 @@ impl Service {
// everything else must use https if possible.
let https_upgradable = self.https_enabled() && !req_is_https;
if let Some(config) = self.proxied_domains.get(&domain) {
if config.http_url.is_none() {
return self.render_error_page(404, "Domain not found");
}
let settings = {
use domain::manager::{GetSettingsError, GetSettingsResult};
match self.domain_manager.get_settings(&domain) {
Ok(GetSettingsResult::Stored(settings)) => settings,
Ok(GetSettingsResult::Builtin(config)) => config.settings,
Ok(GetSettingsResult::Proxied(config)) => {
if config.http_url.is_none() {
return self.render_error_page(404, "Domain not found");
} else if https_upgradable && !config.https_disabled {
return self.https_redirect(domain, req);
}
let http_url = config.http_url.as_ref().unwrap();
let http_url = config.http_url.as_ref().unwrap();
if https_upgradable && !config.https_disabled {
return self.https_redirect(domain, req);
return service::http::proxy::serve_http_request(
http_url.original_url.as_str(),
&config.http_request_headers.0,
client_ip,
req,
req_is_https,
)
.await
.unwrap_or_else(|e| {
self.internal_error(
format!("serving {domain} via proxy {}: {e}", http_url.original_url)
.as_str(),
)
});
}
Ok(GetSettingsResult::Interface) => {
if https_upgradable {
return self.https_redirect(domain, req);
}
return self.serve_interface(req).await;
}
Ok(GetSettingsResult::External(_)) => {
return self.render_error_page(404, "Unknown domain name")
}
Err(GetSettingsError::NotFound) => {
return self.render_error_page(404, "Unknown domain name")
}
Err(GetSettingsError::Unexpected(e)) => {
return self.internal_error(
format!("failed to fetch settings for domain {domain}: {e}").as_str(),
)
}
}
};
return service::http::proxy::serve_http_request(
http_url.original_url.as_str(),
&config.http_request_headers.0,
client_ip,
req,
req_is_https,
)
.await
.unwrap_or_else(|e| {
self.internal_error(
format!("serving {domain} via proxy {}: {e}", http_url.original_url).as_str(),
)
});
}
if https_upgradable {
return self.https_redirect(domain, req);
}
if Some(&domain) == self.interface_domain.as_ref() {
return self.serve_interface(req).await;
}
self.serve_origin(domain, req).await
self.serve_origin(settings, req).await
}
}

@ -14,12 +14,6 @@ pub async fn listen_http(
) -> Result<(), unexpected::Error> {
let addr = service.config.http.http_addr;
// only used for logging
let listen_host = service
.interface_domain
.clone()
.map_or(addr.ip().to_string(), |ref d| d.as_str().to_string());
let make_service = hyper::service::make_service_fn(move |conn: &AddrStream| {
let service = service.clone();
let client_ip = conn.remote_addr().ip();
@ -36,7 +30,7 @@ pub async fn listen_http(
async move { Ok::<_, convert::Infallible>(hyper_service) }
});
log::info!("Listening on http://{}:{}", listen_host, addr.port(),);
log::info!("Listening on http://{}", &addr);
let server = hyper::Server::bind(&addr).serve(make_service);
let graceful = server.with_graceful_shutdown(async {
@ -53,12 +47,6 @@ pub async fn listen_https(
let cert_resolver = service.cert_resolver.clone();
let addr = service.config.http.https_addr.unwrap();
// only used for logging
let listen_host = service
.interface_domain
.clone()
.map_or(addr.ip().to_string(), |ref d| d.as_str().to_string());
let make_service = hyper::service::make_service_fn(move |conn: &TlsStream<AddrStream>| {
let service = service.clone();
let client_ip = conn.get_ref().0.remote_addr().ip();
@ -97,7 +85,7 @@ pub async fn listen_https(
let incoming = hyper::server::accept::from_stream(incoming);
log::info!("Listening on https://{}:{}", listen_host, addr.port());
log::info!("Listening on https://{}", addr);
let server = hyper::Server::builder(incoming).serve(make_service);

Loading…
Cancel
Save