Pass the full domain::Settings into the proxy code

This commit is contained in:
Brian Picciano 2023-07-17 16:19:25 +02:00
parent 8f74757f23
commit 651f3f4bb7
6 changed files with 70 additions and 28 deletions

View File

@ -5,7 +5,7 @@ domain:
builtins: builtins:
foo: foo:
kind: proxy kind: proxy
url: ok url: http://ok
bar: bar:
kind: git kind: git
url: a url: a

View File

@ -32,9 +32,6 @@ pub enum GetFileError {
#[error("file not found")] #[error("file not found")]
FileNotFound, FileNotFound,
#[error("origin is of kind proxy")]
OriginIsProxy { url: String },
#[error(transparent)] #[error(transparent)]
Unexpected(#[from] unexpected::Error), Unexpected(#[from] unexpected::Error),
} }
@ -249,8 +246,8 @@ impl Manager for ManagerImpl {
) -> Result<util::BoxByteStream, GetFileError> { ) -> Result<util::BoxByteStream, GetFileError> {
let config = self.domain_store.get(domain)?; let config = self.domain_store.get(domain)?;
if let origin::Descr::Proxy { url } = config.origin_descr { if let origin::Descr::Proxy { .. } = config.origin_descr {
return Err(GetFileError::OriginIsProxy { url }); return Err(unexpected::Error::from("origin is proxy, can't serve file").into());
} }
let f = self.origin_store.get_file(&config.origin_descr, path)?; let f = self.origin_store.get_file(&config.origin_descr, path)?;

View File

@ -22,7 +22,7 @@ impl Error {
let mut w = String::new(); let mut w = String::new();
if let Some(prefix) = prefix { 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"); write!(w, "{body}").expect("error formatting body");

View File

@ -78,6 +78,14 @@ async fn main() {
config.service.dns_records.push(primary_cname); 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 config
}; };

View File

@ -1,4 +1,5 @@
use crate::error::unexpected::{self, Mappable}; use crate::error::unexpected::{self, Mappable};
use crate::{domain, origin};
use http::header::HeaderValue; use http::header::HeaderValue;
use std::{net, str::FromStr}; 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 // being served on, it can't be abstracted out into a simple "get_file" operation like other
// origins. // 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( pub async fn serve_http_request(
settings: &domain::Settings,
client_ip: net::IpAddr, client_ip: net::IpAddr,
proxy_url: &str,
mut req: hyper::Request<hyper::Body>, mut req: hyper::Request<hyper::Body>,
req_is_https: bool, req_is_https: bool,
) -> unexpected::Result<hyper::Response<hyper::Body>> { ) -> unexpected::Result<hyper::Response<hyper::Body>> {
let parsed_proxy_url = let proxy_url = if let origin::Descr::Proxy { ref url } = settings.origin_descr {
http::Uri::from_str(proxy_url).or_unexpected_while("parsing proxy url")?; url
} else {
panic!("non-proxy domain settings passed in: {settings:?}")
};
let scheme = parsed_proxy_url let parsed_proxy_url =
.scheme() http::Uri::from_str(proxy_url).or_unexpected_while("parsing proxy url {proxy_url}")?;
.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"));
}
// figure out what the host header should be, based on the host[:port] of the proxy_url // figure out what the host header should be, based on the host[:port] of the proxy_url
let host = { let host = {
let authority = parsed_proxy_url let authority = parsed_proxy_url.authority().or_unexpected_while(format!(
.authority() "getting host from proxy url {proxy_url}, there is no host"
.or_unexpected_while("getting host from proxy url, there is no host")?; ))?;
let host_and_port; let host_and_port;
let mut host = authority.host(); 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 // ProxyError doesn't actually implement Error :facepalm: so we have to format the error
// manually // manually
Err(e) => Err(unexpected::Error::from( Err(e) => Err(unexpected::Error::from(
format!("error while proxying: {e:?}").as_str(), format!("error while proxying to {proxy_url}: {e:?}").as_str(),
)), )),
} }
} }

View File

@ -165,6 +165,33 @@ impl<'svc> Service {
req: Request<Body>, req: Request<Body>,
req_is_https: bool, req_is_https: bool,
) -> Response<Body> { ) -> Response<Body> {
// 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 mut path_owned;
let path = req.uri().path(); let path = req.uri().path();
@ -185,13 +212,6 @@ impl<'svc> Service {
Err(domain::manager::GetFileError::FileNotFound) => { Err(domain::manager::GetFileError::FileNotFound) => {
self.render_error_page(404, "File not found") 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)) => { Err(domain::manager::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())
} }
@ -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 { if let Some(domain) = maybe_host {
return self return self
.serve_origin(client_ip, domain, req, req_is_https) .serve_origin(client_ip, domain, req, req_is_https)