Implement serving of origins
This commit is contained in:
parent
c1b6d982ef
commit
8f1c0cce22
1
.env.dev
1
.env.dev
@ -1,3 +1,4 @@
|
|||||||
|
export DOMIPLY_HTTP_DOMAIN=localhost
|
||||||
export DOMIPLY_PASSPHRASE=foobar
|
export DOMIPLY_PASSPHRASE=foobar
|
||||||
export DOMIPLY_ORIGIN_STORE_GIT_DIR_PATH=/tmp/domiply_dev_env/origin/git
|
export DOMIPLY_ORIGIN_STORE_GIT_DIR_PATH=/tmp/domiply_dev_env/origin/git
|
||||||
export DOMIPLY_DOMAIN_CHECKER_TARGET_AAAA=::1
|
export DOMIPLY_DOMAIN_CHECKER_TARGET_AAAA=::1
|
||||||
|
@ -17,6 +17,9 @@ struct Cli {
|
|||||||
#[arg(long, default_value_t = SocketAddr::from_str("[::]:3030").unwrap(), env = "DOMIPLY_HTTP_LISTEN_ADDR")]
|
#[arg(long, default_value_t = SocketAddr::from_str("[::]:3030").unwrap(), env = "DOMIPLY_HTTP_LISTEN_ADDR")]
|
||||||
http_listen_addr: SocketAddr,
|
http_listen_addr: SocketAddr,
|
||||||
|
|
||||||
|
#[arg(long, required = true, env = "DOMIPLY_HTTP_DOMAIN")]
|
||||||
|
http_domain: String,
|
||||||
|
|
||||||
#[arg(long, required = true, env = "DOMIPLY_PASSPHRASE")]
|
#[arg(long, required = true, env = "DOMIPLY_PASSPHRASE")]
|
||||||
passphrase: String,
|
passphrase: String,
|
||||||
|
|
||||||
@ -82,6 +85,7 @@ fn main() {
|
|||||||
manager,
|
manager,
|
||||||
config.domain_checker_target_aaaa,
|
config.domain_checker_target_aaaa,
|
||||||
config.passphrase,
|
config.passphrase,
|
||||||
|
config.http_domain,
|
||||||
);
|
);
|
||||||
|
|
||||||
let service = sync::Arc::new(service);
|
let service = sync::Arc::new(service);
|
||||||
|
101
src/service.rs
101
src/service.rs
@ -1,24 +1,25 @@
|
|||||||
use http::status::StatusCode;
|
|
||||||
use hyper::{Body, Method, Request, Response};
|
use hyper::{Body, Method, Request, Response};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::net;
|
use std::net;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync;
|
use std::sync;
|
||||||
|
|
||||||
use crate::domain;
|
use crate::{domain, origin};
|
||||||
|
|
||||||
pub mod http_tpl;
|
pub mod http_tpl;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
type SvcResponse = Result<Response<String>, String>;
|
type SvcResponse = Result<Response<hyper::body::Body>, String>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Service<'svc> {
|
pub struct Service<'svc> {
|
||||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
||||||
target_aaaa: net::Ipv6Addr,
|
target_aaaa: net::Ipv6Addr,
|
||||||
passphrase: String,
|
passphrase: String,
|
||||||
|
http_domain: String,
|
||||||
handlebars: handlebars::Handlebars<'svc>,
|
handlebars: handlebars::Handlebars<'svc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,11 +27,13 @@ pub fn new<'svc, 'mgr>(
|
|||||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
||||||
target_aaaa: net::Ipv6Addr,
|
target_aaaa: net::Ipv6Addr,
|
||||||
passphrase: String,
|
passphrase: String,
|
||||||
|
http_domain: String,
|
||||||
) -> Service<'svc> {
|
) -> Service<'svc> {
|
||||||
Service {
|
Service {
|
||||||
domain_manager,
|
domain_manager,
|
||||||
target_aaaa,
|
target_aaaa,
|
||||||
passphrase,
|
passphrase,
|
||||||
|
http_domain,
|
||||||
handlebars: self::http_tpl::get().expect("Retrieved Handlebars templates"),
|
handlebars: self::http_tpl::get().expect("Retrieved Handlebars templates"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,6 +61,21 @@ struct DomainSyncArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'svc> Service<'svc> {
|
impl<'svc> Service<'svc> {
|
||||||
|
fn serve_string(&self, status_code: u16, path: &'_ str, body: Vec<u8>) -> SvcResponse {
|
||||||
|
let content_type = mime_guess::from_path(path)
|
||||||
|
.first_or_octet_stream()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
match Response::builder()
|
||||||
|
.status(status_code)
|
||||||
|
.header("Content-Type", content_type)
|
||||||
|
.body(body.into())
|
||||||
|
{
|
||||||
|
Ok(res) => Ok(res),
|
||||||
|
Err(err) => Err(format!("failed to build {}: {}", path, err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//// TODO make this use an io::Write, rather than SvcResponse
|
//// TODO make this use an io::Write, rather than SvcResponse
|
||||||
fn render<T>(&self, status_code: u16, name: &'_ str, value: T) -> SvcResponse
|
fn render<T>(&self, status_code: u16, name: &'_ str, value: T) -> SvcResponse
|
||||||
where
|
where
|
||||||
@ -74,18 +92,7 @@ impl<'svc> Service<'svc> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let content_type = mime_guess::from_path(name)
|
self.serve_string(status_code, name, rendered.into())
|
||||||
.first_or_octet_stream()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
match Response::builder()
|
|
||||||
.status(status_code)
|
|
||||||
.header("Content-Type", content_type)
|
|
||||||
.body(rendered)
|
|
||||||
{
|
|
||||||
Ok(res) => Ok(res),
|
|
||||||
Err(err) => Err(format!("failed to build {}: {}", name, err)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_error_page(&'svc self, status_code: u16, e: &'_ str) -> SvcResponse {
|
fn render_error_page(&'svc self, status_code: u16, e: &'_ str) -> SvcResponse {
|
||||||
@ -118,6 +125,40 @@ impl<'svc> Service<'svc> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serve_origin(&self, domain: domain::Name, path: &'_ str) -> SvcResponse {
|
||||||
|
let mut path_owned;
|
||||||
|
|
||||||
|
let path = match path.ends_with("/") {
|
||||||
|
true => {
|
||||||
|
path_owned = String::from(path);
|
||||||
|
path_owned.push_str("index.html");
|
||||||
|
path_owned.as_str()
|
||||||
|
}
|
||||||
|
false => path,
|
||||||
|
};
|
||||||
|
|
||||||
|
let origin = match self.domain_manager.get_origin(&domain) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(domain::manager::GetOriginError::NotFound) => {
|
||||||
|
return self.render_error_page(404, "Domain not found")
|
||||||
|
}
|
||||||
|
Err(domain::manager::GetOriginError::Unexpected(e)) => {
|
||||||
|
return self.render_error_page(500, format!("failed to fetch origin: {e}").as_str())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buf = Vec::<u8>::new();
|
||||||
|
match origin.read_file_into(&path, &mut buf) {
|
||||||
|
Ok(_) => self.serve_string(200, &path, buf),
|
||||||
|
Err(origin::ReadFileIntoError::FileNotFound) => {
|
||||||
|
self.render_error_page(404, "File not found")
|
||||||
|
}
|
||||||
|
Err(origin::ReadFileIntoError::Unexpected(e)) => {
|
||||||
|
self.render_error_page(500, format!("failed to fetch file {path}: {e}").as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn with_query_req<'a, F, In, Out>(&self, req: &'a Request<Body>, f: F) -> SvcResponse
|
async fn with_query_req<'a, F, In, Out>(&self, req: &'a Request<Body>, f: F) -> SvcResponse
|
||||||
where
|
where
|
||||||
In: Deserialize<'a>,
|
In: Deserialize<'a>,
|
||||||
@ -239,21 +280,41 @@ impl<'svc> Service<'svc> {
|
|||||||
pub async fn handle_request<'svc>(
|
pub async fn handle_request<'svc>(
|
||||||
svc: sync::Arc<Service<'svc>>,
|
svc: sync::Arc<Service<'svc>>,
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
) -> Result<Response<String>, Infallible> {
|
) -> Result<Response<Body>, Infallible> {
|
||||||
match handle_request_inner(svc, req).await {
|
match handle_request_inner(svc, req).await {
|
||||||
Ok(res) => Ok(res),
|
Ok(res) => Ok(res),
|
||||||
Err(err) => {
|
Err(err) => panic!("unexpected error {err}"),
|
||||||
let mut res = Response::new(format!("failed to serve request: {}", err));
|
|
||||||
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn strip_port(host: &str) -> &str {
|
||||||
|
match host.rfind(":") {
|
||||||
|
None => host,
|
||||||
|
Some(i) => &host[..i],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_request_inner<'svc>(
|
pub async fn handle_request_inner<'svc>(
|
||||||
svc: sync::Arc<Service<'svc>>,
|
svc: sync::Arc<Service<'svc>>,
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
) -> SvcResponse {
|
) -> SvcResponse {
|
||||||
|
let maybe_host = match (
|
||||||
|
req.headers()
|
||||||
|
.get("Host")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.map(strip_port),
|
||||||
|
req.uri().host().map(strip_port),
|
||||||
|
) {
|
||||||
|
(Some(h), _) if h != svc.http_domain => Some(h),
|
||||||
|
(_, Some(h)) if h != svc.http_domain => Some(h),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
.and_then(|h| domain::Name::from_str(h).ok());
|
||||||
|
|
||||||
|
if let Some(domain) = maybe_host {
|
||||||
|
return svc.serve_origin(domain, req.uri().path());
|
||||||
|
}
|
||||||
|
|
||||||
let method = req.method();
|
let method = req.method();
|
||||||
let path = req.uri().path();
|
let path = req.uri().path();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user