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:
foo:
kind: proxy
url: ok
url: http://ok
bar:
kind: git
url: a

View File

@ -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<util::BoxByteStream, GetFileError> {
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)?;

View File

@ -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");

View File

@ -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
};

View File

@ -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<hyper::Body>,
req_is_https: bool,
) -> unexpected::Result<hyper::Response<hyper::Body>> {
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(),
)),
}
}

View File

@ -165,6 +165,33 @@ impl<'svc> Service {
req: Request<Body>,
req_is_https: bool,
) -> 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 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)