Got proxy origin working, more or less
This commit is contained in:
parent
9beeffcdcf
commit
a917f32f04
@ -32,6 +32,9 @@ 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),
|
||||||
}
|
}
|
||||||
@ -245,6 +248,11 @@ impl Manager for ManagerImpl {
|
|||||||
path: &str,
|
path: &str,
|
||||||
) -> 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 {
|
||||||
|
return Err(GetFileError::OriginIsProxy { url });
|
||||||
|
}
|
||||||
|
|
||||||
let f = self.origin_store.get_file(&config.origin_descr, path)?;
|
let f = self.origin_store.get_file(&config.origin_descr, path)?;
|
||||||
Ok(f)
|
Ok(f)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::error::unexpected;
|
use crate::error::unexpected::{self, Mappable};
|
||||||
use std::net;
|
use std::{net, str::FromStr};
|
||||||
|
|
||||||
// proxy is a special case because it is so tied to the underlying protocol that a request is
|
// proxy is a special case because it is so tied to the underlying protocol that a request is
|
||||||
// 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
|
||||||
@ -8,8 +8,37 @@ use std::net;
|
|||||||
pub async fn serve_http_request(
|
pub async fn serve_http_request(
|
||||||
client_ip: net::IpAddr,
|
client_ip: net::IpAddr,
|
||||||
proxy_url: &str,
|
proxy_url: &str,
|
||||||
req: hyper::Request<hyper::Body>,
|
mut req: hyper::Request<hyper::Body>,
|
||||||
) -> unexpected::Result<hyper::Response<hyper::Body>> {
|
) -> unexpected::Result<hyper::Response<hyper::Body>> {
|
||||||
|
let parsed_proxy_url =
|
||||||
|
http::Uri::from_str(proxy_url).or_unexpected_while("parsing proxy url")?;
|
||||||
|
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 host_and_port;
|
||||||
|
let mut host = authority.host();
|
||||||
|
|
||||||
|
if let Some(port) = authority.port() {
|
||||||
|
host_and_port = format!("{host}:{port}");
|
||||||
|
host = host_and_port.as_str();
|
||||||
|
};
|
||||||
|
|
||||||
|
http::header::HeaderValue::from_str(host).or_unexpected()?
|
||||||
|
};
|
||||||
|
|
||||||
|
req.headers_mut().insert("host", host);
|
||||||
|
|
||||||
match hyper_reverse_proxy::call(client_ip, proxy_url, req).await {
|
match hyper_reverse_proxy::call(client_ip, proxy_url, req).await {
|
||||||
Ok(res) => Ok(res),
|
Ok(res) => Ok(res),
|
||||||
// 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
|
||||||
|
@ -9,10 +9,10 @@ use hyper::{Body, Method, Request, Response};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{future, sync};
|
use std::{future, net, sync};
|
||||||
|
|
||||||
use crate::error::unexpected;
|
use crate::error::unexpected;
|
||||||
use crate::{domain, service, util};
|
use crate::{domain, origin, service, util};
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
||||||
@ -158,8 +158,14 @@ impl<'svc> Service {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_origin(&self, domain: domain::Name, path: &str) -> Response<Body> {
|
async fn serve_origin(
|
||||||
|
&self,
|
||||||
|
client_ip: net::IpAddr,
|
||||||
|
domain: domain::Name,
|
||||||
|
req: Request<Body>,
|
||||||
|
) -> Response<Body> {
|
||||||
let mut path_owned;
|
let mut path_owned;
|
||||||
|
let path = req.uri().path();
|
||||||
|
|
||||||
let path = match path.ends_with('/') {
|
let path = match path.ends_with('/') {
|
||||||
true => {
|
true => {
|
||||||
@ -178,6 +184,13 @@ 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)
|
||||||
|
.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())
|
||||||
}
|
}
|
||||||
@ -366,15 +379,13 @@ impl<'svc> Service {
|
|||||||
self.render_page("/domains.html", Response { domains })
|
self.render_page("/domains.html", Response { domains })
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_request(&self, req: Request<Body>) -> Response<Body> {
|
async fn handle_request(&self, client_ip: net::IpAddr, req: Request<Body>) -> Response<Body> {
|
||||||
let (req, body) = req.into_parts();
|
|
||||||
|
|
||||||
let maybe_host = match (
|
let maybe_host = match (
|
||||||
req.headers
|
req.headers()
|
||||||
.get("Host")
|
.get("Host")
|
||||||
.and_then(|v| v.to_str().ok())
|
.and_then(|v| v.to_str().ok())
|
||||||
.map(strip_port),
|
.map(strip_port),
|
||||||
req.uri.host().map(strip_port),
|
req.uri().host().map(strip_port),
|
||||||
) {
|
) {
|
||||||
(Some(h), _) if h != self.config.primary_domain.as_str() => Some(h),
|
(Some(h), _) if h != self.config.primary_domain.as_str() => Some(h),
|
||||||
(_, Some(h)) if h != self.config.primary_domain.as_str() => Some(h),
|
(_, Some(h)) if h != self.config.primary_domain.as_str() => Some(h),
|
||||||
@ -382,32 +393,34 @@ impl<'svc> Service {
|
|||||||
}
|
}
|
||||||
.and_then(|h| domain::Name::from_str(h).ok());
|
.and_then(|h| domain::Name::from_str(h).ok());
|
||||||
|
|
||||||
let path = req.uri.path();
|
{
|
||||||
|
let path = req.uri().path();
|
||||||
|
|
||||||
// Serving acme challenges always takes priority. We serve them from the same store no
|
// Serving acme challenges always takes priority. We serve them from the same store no
|
||||||
// matter the domain, presumably they are cryptographically random enough that it doesn't
|
// matter the domain, presumably they are cryptographically random enough that it doesn't
|
||||||
// matter.
|
// matter.
|
||||||
if req.method == Method::GET && path.starts_with("/.well-known/acme-challenge/") {
|
if req.method() == Method::GET && path.starts_with("/.well-known/acme-challenge/") {
|
||||||
let token = path.trim_start_matches("/.well-known/acme-challenge/");
|
let token = path.trim_start_matches("/.well-known/acme-challenge/");
|
||||||
|
|
||||||
if let Ok(key) = self.domain_manager.get_acme_http01_challenge_key(token) {
|
if let Ok(key) = self.domain_manager.get_acme_http01_challenge_key(token) {
|
||||||
return self.serve(200, "token.txt", key.into());
|
return self.serve(200, "token.txt", key.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Serving domani challenges similarly takes priority.
|
// Serving domani challenges similarly takes priority.
|
||||||
if req.method == Method::GET && path == "/.well-known/domani-challenge" {
|
if req.method() == Method::GET && path == "/.well-known/domani-challenge" {
|
||||||
if let Some(ref domain) = maybe_host {
|
if let Some(ref domain) = maybe_host {
|
||||||
match self
|
match self
|
||||||
.domain_manager
|
.domain_manager
|
||||||
.get_domain_checker_challenge_token(domain)
|
.get_domain_checker_challenge_token(domain)
|
||||||
{
|
{
|
||||||
Ok(Some(token)) => return self.serve(200, "token.txt", token.into()),
|
Ok(Some(token)) => return self.serve(200, "token.txt", token.into()),
|
||||||
Ok(None) => return self.render_error_page(404, "Token not found"),
|
Ok(None) => return self.render_error_page(404, "Token not found"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return self.internal_error(
|
return self.internal_error(
|
||||||
format!("failed to get token for domain {}: {e}", domain).as_str(),
|
format!("failed to get token for domain {}: {e}", domain).as_str(),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -415,10 +428,12 @@ 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
|
||||||
if let Some(domain) = maybe_host {
|
if let Some(domain) = maybe_host {
|
||||||
return self.serve_origin(domain, req.uri.path());
|
return self.serve_origin(client_ip, domain, req).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve main domani site
|
// Serve main domani site
|
||||||
|
let (req, body) = req.into_parts();
|
||||||
|
let path = req.uri.path();
|
||||||
|
|
||||||
if req.method == Method::GET && path.starts_with("/static/") {
|
if req.method == Method::GET && path.starts_with("/static/") {
|
||||||
return self.render(200, path, ());
|
return self.render(200, path, ());
|
||||||
|
@ -4,6 +4,8 @@ use crate::service;
|
|||||||
use std::{convert, future, sync};
|
use std::{convert, future, sync};
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use hyper::server::conn::AddrStream;
|
||||||
|
use tokio_rustls::server::TlsStream;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
pub async fn listen_http(
|
pub async fn listen_http(
|
||||||
@ -13,13 +15,14 @@ pub async fn listen_http(
|
|||||||
let addr = service.config.http.http_addr.clone();
|
let addr = service.config.http.http_addr.clone();
|
||||||
let primary_domain = service.config.primary_domain.clone();
|
let primary_domain = service.config.primary_domain.clone();
|
||||||
|
|
||||||
let make_service = hyper::service::make_service_fn(move |_| {
|
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();
|
||||||
|
|
||||||
// Create a `Service` for responding to the request.
|
// Create a `Service` for responding to the request.
|
||||||
let hyper_service = hyper::service::service_fn(move |req| {
|
let hyper_service = hyper::service::service_fn(move |req| {
|
||||||
let service = service.clone();
|
let service = service.clone();
|
||||||
async move { Ok::<_, convert::Infallible>(service.handle_request(req).await) }
|
async move { Ok::<_, convert::Infallible>(service.handle_request(client_ip, req).await) }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return the service to hyper.
|
// Return the service to hyper.
|
||||||
@ -48,13 +51,14 @@ pub async fn listen_https(
|
|||||||
let addr = service.config.http.https_addr.unwrap().clone();
|
let addr = service.config.http.https_addr.unwrap().clone();
|
||||||
let primary_domain = service.config.primary_domain.clone();
|
let primary_domain = service.config.primary_domain.clone();
|
||||||
|
|
||||||
let make_service = hyper::service::make_service_fn(move |_| {
|
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();
|
||||||
|
|
||||||
// Create a `Service` for responding to the request.
|
// Create a `Service` for responding to the request.
|
||||||
let hyper_service = hyper::service::service_fn(move |req| {
|
let hyper_service = hyper::service::service_fn(move |req| {
|
||||||
let service = service.clone();
|
let service = service.clone();
|
||||||
async move { Ok::<_, convert::Infallible>(service.handle_request(req).await) }
|
async move { Ok::<_, convert::Infallible>(service.handle_request(client_ip, req).await) }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return the service to hyper.
|
// Return the service to hyper.
|
||||||
|
Loading…
Reference in New Issue
Block a user