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.
This commit is contained in:
parent
43502f82f9
commit
6a611c371c
@ -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,17 +543,20 @@ 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) {
|
||||
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();
|
||||
|
||||
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,
|
||||
@ -567,20 +567,32 @@ impl Service {
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
self.internal_error(
|
||||
format!("serving {domain} via proxy {}: {e}", http_url.original_url).as_str(),
|
||||
format!("serving {domain} via proxy {}: {e}", http_url.original_url)
|
||||
.as_str(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(GetSettingsResult::Interface) => {
|
||||
if https_upgradable {
|
||||
return self.https_redirect(domain, req);
|
||||
}
|
||||
|
||||
if Some(&domain) == self.interface_domain.as_ref() {
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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…
Reference in New Issue
Block a user