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 type GetFileError = origin::GetFileError;
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum SyncWithSettingsError {
|
pub enum SyncWithSettingsError {
|
||||||
@ -125,7 +94,7 @@ pub trait Manager: Sync + Send {
|
|||||||
|
|
||||||
fn get_file(
|
fn get_file(
|
||||||
&self,
|
&self,
|
||||||
domain: &domain::Name,
|
settings: &domain::Settings,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<util::BoxByteStream, GetFileError>;
|
) -> Result<util::BoxByteStream, GetFileError>;
|
||||||
|
|
||||||
@ -425,34 +394,12 @@ impl Manager for ManagerImpl {
|
|||||||
|
|
||||||
fn get_file(
|
fn get_file(
|
||||||
&self,
|
&self,
|
||||||
domain: &domain::Name,
|
settings: &domain::Settings,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<util::BoxByteStream, GetFileError> {
|
) -> 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 path = settings.process_path(path);
|
||||||
|
self.origin_store
|
||||||
let f = self
|
.get_file(&settings.origin_descr, path.as_ref())
|
||||||
.origin_store
|
|
||||||
.get_file(&settings.origin_descr, path.as_ref())?;
|
|
||||||
|
|
||||||
Ok(f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_with_settings(
|
fn sync_with_settings(
|
||||||
|
@ -176,8 +176,6 @@ async fn main() {
|
|||||||
domain_manager.clone(),
|
domain_manager.clone(),
|
||||||
domani::domain::manager::HttpsCertResolver::from(domain_manager.clone()),
|
domani::domain::manager::HttpsCertResolver::from(domain_manager.clone()),
|
||||||
config.service.clone(),
|
config.service.clone(),
|
||||||
config.domain.proxied_domains.clone(),
|
|
||||||
config.domain.interface_domain.clone(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if gemini_enabled {
|
if gemini_enabled {
|
||||||
@ -186,7 +184,6 @@ async fn main() {
|
|||||||
domain_manager.clone(),
|
domain_manager.clone(),
|
||||||
domani::domain::manager::GeminiCertResolver::from(domain_manager.clone()),
|
domani::domain::manager::GeminiCertResolver::from(domain_manager.clone()),
|
||||||
config.service.gemini.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::error::unexpected::{self, Mappable};
|
||||||
use crate::{domain, service, task_stack, util};
|
use crate::{domain, service, task_stack, util};
|
||||||
|
|
||||||
use std::{collections, sync};
|
use std::sync;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
||||||
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
|
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
|
||||||
config: Config,
|
config: Config,
|
||||||
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@ -31,7 +30,6 @@ impl Service {
|
|||||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
||||||
cert_resolver: CertResolver,
|
cert_resolver: CertResolver,
|
||||||
config: Config,
|
config: Config,
|
||||||
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
|
|
||||||
) -> sync::Arc<Service>
|
) -> sync::Arc<Service>
|
||||||
where
|
where
|
||||||
CertResolver: rustls::server::ResolvesServerCert + 'static,
|
CertResolver: rustls::server::ResolvesServerCert + 'static,
|
||||||
@ -40,7 +38,6 @@ impl Service {
|
|||||||
domain_manager,
|
domain_manager,
|
||||||
cert_resolver: sync::Arc::from(cert_resolver),
|
cert_resolver: sync::Arc::from(cert_resolver),
|
||||||
config,
|
config,
|
||||||
proxied,
|
|
||||||
});
|
});
|
||||||
task_stack.push_spawn(|canceller| listen(service.clone(), canceller));
|
task_stack.push_spawn(|canceller| listen(service.clone(), canceller));
|
||||||
service
|
service
|
||||||
@ -74,7 +71,11 @@ impl Service {
|
|||||||
Ok(())
|
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
|
where
|
||||||
IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
|
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 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,
|
Ok(f) => f,
|
||||||
Err(domain::manager::GetFileError::DomainNotFound) => {
|
Err(GetFileError::FileNotFound) => {
|
||||||
return Err(unexpected::Error::from("domain not found when serving file").into())
|
|
||||||
}
|
|
||||||
Err(domain::manager::GetFileError::FileNotFound) => {
|
|
||||||
return Ok(self.respond_conn(w, "51", "File not found", None).await?)
|
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);
|
let content_type = service::guess_mime(&path);
|
||||||
@ -156,8 +165,14 @@ impl Service {
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// If the domain should be proxied, then proxy it
|
use domain::manager::{GetSettingsError, GetSettingsResult};
|
||||||
if let Some(config) = self.proxied.get(&domain) {
|
|
||||||
|
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 {
|
if let Some(ref gemini_url) = config.gemini_url {
|
||||||
let prefixed_conn = proxy::teed_io_to_prefixed(start.into_inner());
|
let prefixed_conn = proxy::teed_io_to_prefixed(start.into_inner());
|
||||||
self.proxy_conn(gemini_url.addr.as_str(), prefixed_conn)
|
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()?;
|
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) => {
|
Err(err) => {
|
||||||
return Err(unexpected::Error::from(
|
return Err(unexpected::Error::from(
|
||||||
|
@ -10,7 +10,7 @@ use http::request::Parts;
|
|||||||
use hyper::{Body, Method, Request, Response};
|
use hyper::{Body, Method, Request, Response};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::{collections, future, net, sync};
|
use std::{future, net, sync};
|
||||||
|
|
||||||
use crate::error::unexpected::{self, Mappable};
|
use crate::error::unexpected::{self, Mappable};
|
||||||
use crate::{domain, service, task_stack};
|
use crate::{domain, service, task_stack};
|
||||||
@ -50,8 +50,6 @@ pub struct Service {
|
|||||||
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
|
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
|
||||||
handlebars: handlebars::Handlebars<'static>,
|
handlebars: handlebars::Handlebars<'static>,
|
||||||
config: service::Config,
|
config: service::Config,
|
||||||
proxied_domains: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
|
|
||||||
interface_domain: Option<domain::Name>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
@ -60,8 +58,6 @@ impl Service {
|
|||||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
||||||
cert_resolver: CertResolver,
|
cert_resolver: CertResolver,
|
||||||
config: service::Config,
|
config: service::Config,
|
||||||
proxied_domains: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
|
|
||||||
interface_domain: Option<domain::Name>,
|
|
||||||
) -> sync::Arc<Service>
|
) -> sync::Arc<Service>
|
||||||
where
|
where
|
||||||
CertResolver: rustls::server::ResolvesServerCert + 'static,
|
CertResolver: rustls::server::ResolvesServerCert + 'static,
|
||||||
@ -71,8 +67,6 @@ impl Service {
|
|||||||
cert_resolver: sync::Arc::from(cert_resolver),
|
cert_resolver: sync::Arc::from(cert_resolver),
|
||||||
handlebars: tpl::get(),
|
handlebars: tpl::get(),
|
||||||
config,
|
config,
|
||||||
proxied_domains,
|
|
||||||
interface_domain,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
|
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");
|
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)),
|
Ok(f) => self.serve(200, &path, Body::wrap_stream(f)),
|
||||||
Err(domain::manager::GetFileError::DomainNotFound) => {
|
Err(GetFileError::FileNotFound) => self.render_error_page(404, "File not found"),
|
||||||
self.render_error_page(404, "Unknown domain name")
|
Err(GetFileError::DescrNotSynced) => self.internal_error(
|
||||||
}
|
format!(
|
||||||
Err(domain::manager::GetFileError::FileNotFound) => {
|
"Backend for {:?} has not yet been synced",
|
||||||
self.render_error_page(404, "File not found")
|
settings.origin_descr
|
||||||
}
|
)
|
||||||
Err(domain::manager::GetFileError::Unexpected(e)) => {
|
.as_str(),
|
||||||
|
),
|
||||||
|
Err(GetFileError::Unexpected(e)) => {
|
||||||
self.internal_error(format!("failed to fetch file {path}: {e}").as_str())
|
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.
|
// everything else must use https if possible.
|
||||||
let https_upgradable = self.https_enabled() && !req_is_https;
|
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() {
|
if config.http_url.is_none() {
|
||||||
return self.render_error_page(404, "Domain not found");
|
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(
|
return service::http::proxy::serve_http_request(
|
||||||
http_url.original_url.as_str(),
|
http_url.original_url.as_str(),
|
||||||
&config.http_request_headers.0,
|
&config.http_request_headers.0,
|
||||||
@ -567,20 +567,32 @@ impl Service {
|
|||||||
.await
|
.await
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
self.internal_error(
|
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 {
|
if https_upgradable {
|
||||||
return self.https_redirect(domain, req);
|
return self.https_redirect(domain, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
if Some(&domain) == self.interface_domain.as_ref() {
|
|
||||||
return self.serve_interface(req).await;
|
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> {
|
) -> Result<(), unexpected::Error> {
|
||||||
let addr = service.config.http.http_addr;
|
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 make_service = hyper::service::make_service_fn(move |conn: &AddrStream| {
|
||||||
let service = service.clone();
|
let service = service.clone();
|
||||||
let client_ip = conn.remote_addr().ip();
|
let client_ip = conn.remote_addr().ip();
|
||||||
@ -36,7 +30,7 @@ pub async fn listen_http(
|
|||||||
async move { Ok::<_, convert::Infallible>(hyper_service) }
|
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 server = hyper::Server::bind(&addr).serve(make_service);
|
||||||
|
|
||||||
let graceful = server.with_graceful_shutdown(async {
|
let graceful = server.with_graceful_shutdown(async {
|
||||||
@ -53,12 +47,6 @@ pub async fn listen_https(
|
|||||||
let cert_resolver = service.cert_resolver.clone();
|
let cert_resolver = service.cert_resolver.clone();
|
||||||
let addr = service.config.http.https_addr.unwrap();
|
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 make_service = hyper::service::make_service_fn(move |conn: &TlsStream<AddrStream>| {
|
||||||
let service = service.clone();
|
let service = service.clone();
|
||||||
let client_ip = conn.get_ref().0.remote_addr().ip();
|
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);
|
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);
|
let server = hyper::Server::builder(incoming).serve(make_service);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user