diff --git a/.dev-config.yml b/.dev-config.yml index 7dc50b6..d779cf3 100644 --- a/.dev-config.yml +++ b/.dev-config.yml @@ -5,7 +5,7 @@ domain: builtins: foo: kind: proxy - url: ok + url: http://ok bar: kind: git url: a diff --git a/src/domain/manager.rs b/src/domain/manager.rs index 0e8ece6..ce74dba 100644 --- a/src/domain/manager.rs +++ b/src/domain/manager.rs @@ -32,9 +32,6 @@ pub enum GetFileError { #[error("file not found")] FileNotFound, - #[error("origin is of kind proxy")] - OriginIsProxy { url: String }, - #[error(transparent)] Unexpected(#[from] unexpected::Error), } @@ -249,8 +246,8 @@ impl Manager for ManagerImpl { ) -> Result { let config = self.domain_store.get(domain)?; - if let origin::Descr::Proxy { url } = config.origin_descr { - return Err(GetFileError::OriginIsProxy { url }); + if let origin::Descr::Proxy { .. } = config.origin_descr { + return Err(unexpected::Error::from("origin is proxy, can't serve file").into()); } let f = self.origin_store.get_file(&config.origin_descr, path)?; diff --git a/src/error/unexpected.rs b/src/error/unexpected.rs index 77420ce..00c5b8c 100644 --- a/src/error/unexpected.rs +++ b/src/error/unexpected.rs @@ -22,7 +22,7 @@ impl Error { let mut w = String::new(); if let Some(prefix) = prefix { - write!(w, "{prefix}").expect("error writing prefix"); + write!(w, "{prefix}: ").expect("error writing prefix"); } write!(w, "{body}").expect("error formatting body"); diff --git a/src/main.rs b/src/main.rs index 58b09a5..04d0c45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,6 +78,14 @@ async fn main() { config.service.dns_records.push(primary_cname); } + for (domain, builtin_domain) in &config.domain.builtins { + if let domani::origin::Descr::Proxy { ref url } = builtin_domain.settings.origin_descr { + if let Err(e) = domani::origin::proxy::validate_proxy_url(url) { + panic!("invalid config for builtin domain {domain}: {e}"); + } + } + } + config }; diff --git a/src/origin/proxy.rs b/src/origin/proxy.rs index ff911e2..6923e36 100644 --- a/src/origin/proxy.rs +++ b/src/origin/proxy.rs @@ -1,4 +1,5 @@ use crate::error::unexpected::{self, Mappable}; +use crate::{domain, origin}; use http::header::HeaderValue; use std::{net, str::FromStr}; @@ -6,27 +7,43 @@ use std::{net, str::FromStr}; // being served on, it can't be abstracted out into a simple "get_file" operation like other // origins. +pub fn validate_proxy_url(proxy_url: &str) -> unexpected::Result<()> { + let parsed_proxy_url = + http::Uri::from_str(proxy_url).or_unexpected_while("parsing proxy url {proxy_url}")?; + + let scheme = parsed_proxy_url.scheme().map_unexpected_while(|| { + format!("expected a scheme of http in the proxy url {proxy_url}") + })?; + + if scheme != "http" { + return Err(unexpected::Error::from( + format!("scheme of proxy url {proxy_url} should be 'http'",).as_str(), + )); + } + + Ok(()) +} + pub async fn serve_http_request( + settings: &domain::Settings, client_ip: net::IpAddr, - proxy_url: &str, mut req: hyper::Request, req_is_https: bool, ) -> unexpected::Result> { - let parsed_proxy_url = - http::Uri::from_str(proxy_url).or_unexpected_while("parsing proxy url")?; + let proxy_url = if let origin::Descr::Proxy { ref url } = settings.origin_descr { + url + } else { + panic!("non-proxy domain settings passed in: {settings:?}") + }; - let scheme = parsed_proxy_url - .scheme() - .or_unexpected_while("expected a scheme of http in the proxy url")?; - if scheme != "http" { - return Err(unexpected::Error::from("proxy url scheme should be 'http")); - } + let parsed_proxy_url = + http::Uri::from_str(proxy_url).or_unexpected_while("parsing proxy url {proxy_url}")?; // figure out what the host header should be, based on the host[:port] of the proxy_url let host = { - let authority = parsed_proxy_url - .authority() - .or_unexpected_while("getting host from proxy url, there is no host")?; + let authority = parsed_proxy_url.authority().or_unexpected_while(format!( + "getting host from proxy url {proxy_url}, there is no host" + ))?; let host_and_port; let mut host = authority.host(); @@ -51,7 +68,7 @@ pub async fn serve_http_request( // ProxyError doesn't actually implement Error :facepalm: so we have to format the error // manually Err(e) => Err(unexpected::Error::from( - format!("error while proxying: {e:?}").as_str(), + format!("error while proxying to {proxy_url}: {e:?}").as_str(), )), } } diff --git a/src/service/http.rs b/src/service/http.rs index 9a09fee..625e3a3 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -165,6 +165,33 @@ impl<'svc> Service { req: Request, req_is_https: bool, ) -> Response { + // first check if the domain is backed by a proxy, and deal with that first + match self.domain_manager.get_settings(&domain) { + Ok(settings) => { + if let origin::Descr::Proxy { .. } = settings.origin_descr { + return origin::proxy::serve_http_request( + &settings, + client_ip, + req, + req_is_https, + ) + .await + .unwrap_or_else(|e| { + self.internal_error(format!("proxying {domain}: {e}").as_str()) + }); + } + // fall out of match + } + Err(domain::manager::GetSettingsError::NotFound) => { + return self.render_error_page(404, "Domain not found"); + } + Err(domain::manager::GetSettingsError::Unexpected(e)) => { + return self.internal_error( + format!("failed to fetch settings for domain {domain}: {e}").as_str(), + ); + } + } + let mut path_owned; let path = req.uri().path(); @@ -185,13 +212,6 @@ impl<'svc> Service { Err(domain::manager::GetFileError::FileNotFound) => { self.render_error_page(404, "File not found") } - Err(domain::manager::GetFileError::OriginIsProxy { url }) => { - origin::proxy::serve_http_request(client_ip, &url, req, req_is_https) - .await - .unwrap_or_else(|e| { - self.internal_error(format!("proxying {domain} to {url}: {e}").as_str()) - }) - } Err(domain::manager::GetFileError::Unexpected(e)) => { self.internal_error(format!("failed to fetch file {path}: {e}").as_str()) } @@ -432,7 +452,7 @@ impl<'svc> Service { } } - // If a managed domain was given then serve that from its origin + // If a managed domain was given then serve that from its origin, which is possibly a proxy if let Some(domain) = maybe_host { return self .serve_origin(client_ip, domain, req, req_is_https)