Move proxy config into domain (bigger change than it sounds like)
This commit is contained in:
parent
b44fd575a9
commit
7a1a2297d4
@ -1,13 +1,13 @@
|
||||
pub mod acme;
|
||||
pub mod checker;
|
||||
mod config;
|
||||
pub mod gemini;
|
||||
pub mod manager;
|
||||
mod name;
|
||||
mod settings;
|
||||
pub mod store;
|
||||
mod tls;
|
||||
|
||||
mod config;
|
||||
mod name;
|
||||
mod settings;
|
||||
pub use config::*;
|
||||
pub use name::*;
|
||||
pub use settings::*;
|
||||
|
@ -1,8 +1,9 @@
|
||||
use std::{collections, net, path, str::FromStr};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
mod proxied_domain;
|
||||
|
||||
use crate::domain;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, TryFromInto};
|
||||
use std::{collections, net, path, str::FromStr};
|
||||
|
||||
fn default_resolver_addr() -> net::SocketAddr {
|
||||
net::SocketAddr::from_str("1.1.1.1:53").unwrap()
|
||||
@ -27,7 +28,7 @@ pub struct ConfigACME {
|
||||
pub contact_email: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct ConfigBuiltinDomain {
|
||||
#[serde(flatten)]
|
||||
pub settings: domain::Settings,
|
||||
@ -36,12 +37,32 @@ pub struct ConfigBuiltinDomain {
|
||||
pub public: bool,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct ConfigProxiedDomain {
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "Option<TryFromInto<String>>")]
|
||||
pub gemini_url: Option<proxied_domain::GeminiUrl>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "Option<TryFromInto<String>>")]
|
||||
pub http_url: Option<proxied_domain::HttpUrl>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "TryFromInto<Vec<proxied_domain::HttpRequestHeader>>")]
|
||||
pub http_request_headers: proxied_domain::HttpRequestHeaders,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub store_dir_path: path::PathBuf,
|
||||
#[serde(default)]
|
||||
pub dns: ConfigDNS,
|
||||
pub acme: Option<ConfigACME>,
|
||||
|
||||
#[serde(default)]
|
||||
pub builtins: collections::HashMap<domain::Name, ConfigBuiltinDomain>,
|
||||
|
||||
#[serde(default)]
|
||||
pub proxied: collections::HashMap<domain::Name, ConfigProxiedDomain>,
|
||||
}
|
||||
|
131
src/domain/config/proxied_domain.rs
Normal file
131
src/domain/config/proxied_domain.rs
Normal file
@ -0,0 +1,131 @@
|
||||
use crate::error::unexpected::{self, Mappable};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn addr_from_url(
|
||||
url: &str,
|
||||
expected_scheme: &str,
|
||||
default_port: u16,
|
||||
) -> unexpected::Result<String> {
|
||||
let parsed: http::Uri = url
|
||||
.parse()
|
||||
.map_unexpected_while(|| format!("could not parse as url"))?;
|
||||
|
||||
let scheme = parsed
|
||||
.scheme()
|
||||
.map_unexpected_while(|| format!("scheme is missing"))?;
|
||||
|
||||
if scheme != expected_scheme {
|
||||
return Err(unexpected::Error::from(
|
||||
format!("scheme should be {expected_scheme}").as_str(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(path_and_query) = parsed.path_and_query() {
|
||||
let path_and_query = path_and_query.as_str();
|
||||
if path_and_query != "" && path_and_query != "/" {
|
||||
return Err(unexpected::Error::from(
|
||||
format!("path must be empty").as_str(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
match parsed.authority() {
|
||||
None => Err(unexpected::Error::from(format!("host is missing").as_str())),
|
||||
Some(authority) => {
|
||||
let port = authority.port().map(|p| p.as_u16()).unwrap_or(default_port);
|
||||
Ok(format!("{}:{port}", authority.host()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct GeminiUrl {
|
||||
pub original_url: String,
|
||||
pub addr: String,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for GeminiUrl {
|
||||
type Error = unexpected::Error;
|
||||
fn try_from(url: String) -> Result<Self, Self::Error> {
|
||||
let addr = addr_from_url(&url, "gemini", 1965)?;
|
||||
Ok(Self {
|
||||
original_url: url,
|
||||
addr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GeminiUrl> for String {
|
||||
fn from(u: GeminiUrl) -> Self {
|
||||
u.original_url
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct HttpUrl {
|
||||
pub original_url: String,
|
||||
pub addr: String,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for HttpUrl {
|
||||
type Error = unexpected::Error;
|
||||
fn try_from(url: String) -> Result<Self, Self::Error> {
|
||||
let addr = addr_from_url(&url, "http", 80)?;
|
||||
Ok(Self {
|
||||
original_url: url,
|
||||
addr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpUrl> for String {
|
||||
fn from(u: HttpUrl) -> Self {
|
||||
u.original_url
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct HttpRequestHeader {
|
||||
name: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HttpRequestHeaders(pub http::header::HeaderMap);
|
||||
|
||||
impl Default for HttpRequestHeaders {
|
||||
fn default() -> Self {
|
||||
Self(http::header::HeaderMap::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<HttpRequestHeaders> for Vec<HttpRequestHeader> {
|
||||
type Error = http::header::ToStrError;
|
||||
|
||||
fn try_from(h: HttpRequestHeaders) -> Result<Self, Self::Error> {
|
||||
let mut v = vec![];
|
||||
for (name, value) in &h.0 {
|
||||
v.push(HttpRequestHeader {
|
||||
name: name.to_string(),
|
||||
value: value.to_str()?.to_string(),
|
||||
})
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<HttpRequestHeader>> for HttpRequestHeaders {
|
||||
type Error = unexpected::Error;
|
||||
|
||||
fn try_from(v: Vec<HttpRequestHeader>) -> Result<Self, Self::Error> {
|
||||
use http::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
|
||||
let mut h = HeaderMap::new();
|
||||
for pair in v {
|
||||
let name: HeaderName = pair.name.parse().or_unexpected()?;
|
||||
let value: HeaderValue = pair.value.parse().or_unexpected()?;
|
||||
h.insert(name, value);
|
||||
}
|
||||
Ok(Self(h))
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
use crate::domain::{self, acme, checker, gemini, store, tls};
|
||||
use crate::domain::{self, acme, checker, config, gemini, store, tls};
|
||||
use crate::error::unexpected::{self, Mappable};
|
||||
use crate::{origin, task_stack, util};
|
||||
|
||||
use std::sync;
|
||||
use std::{collections, sync};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub type GetSettingsResult = domain::store::GetResult;
|
||||
pub enum GetSettingsResult {
|
||||
Stored(domain::Settings),
|
||||
Builtin(domain::config::ConfigBuiltinDomain),
|
||||
Proxied(domain::config::ConfigProxiedDomain),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum GetSettingsError {
|
||||
@ -37,11 +41,11 @@ pub enum GetFileError {
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
impl From<store::GetError> for GetFileError {
|
||||
fn from(e: store::GetError) -> Self {
|
||||
impl From<GetSettingsError> for GetFileError {
|
||||
fn from(e: GetSettingsError) -> Self {
|
||||
match e {
|
||||
store::GetError::NotFound => Self::DomainNotFound,
|
||||
store::GetError::Unexpected(e) => Self::Unexpected(e),
|
||||
GetSettingsError::NotFound => Self::DomainNotFound,
|
||||
GetSettingsError::Unexpected(e) => Self::Unexpected(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,31 +62,10 @@ impl From<origin::GetFileError> for GetFileError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum SyncError {
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
|
||||
#[error("already in progress")]
|
||||
AlreadyInProgress,
|
||||
|
||||
#[error(transparent)]
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
impl From<store::GetError> for SyncError {
|
||||
fn from(e: store::GetError) -> SyncError {
|
||||
match e {
|
||||
store::GetError::NotFound => SyncError::NotFound,
|
||||
store::GetError::Unexpected(e) => SyncError::Unexpected(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum SyncWithSettingsError {
|
||||
#[error("cannot call SyncWithSettings on builtin domain")]
|
||||
BuiltinDomain,
|
||||
#[error("domain's settings cannot be modified")]
|
||||
NotModifiable,
|
||||
|
||||
#[error("invalid url")]
|
||||
InvalidURL,
|
||||
@ -128,18 +111,12 @@ impl From<checker::CheckDomainError> for SyncWithSettingsError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<store::SetError> for SyncWithSettingsError {
|
||||
fn from(e: store::SetError) -> SyncWithSettingsError {
|
||||
match e {
|
||||
store::SetError::BuiltinDomain => SyncWithSettingsError::BuiltinDomain,
|
||||
store::SetError::Unexpected(e) => SyncWithSettingsError::Unexpected(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError;
|
||||
|
||||
pub type StoredDomain = domain::store::StoredDomain;
|
||||
pub struct ManagedDomain {
|
||||
pub domain: domain::Name,
|
||||
pub public: bool,
|
||||
}
|
||||
|
||||
#[mockall::automock]
|
||||
pub trait Manager: Sync + Send {
|
||||
@ -167,7 +144,7 @@ pub trait Manager: Sync + Send {
|
||||
domain: &domain::Name,
|
||||
) -> unexpected::Result<Option<String>>;
|
||||
|
||||
fn all_domains(&self) -> Result<Vec<StoredDomain>, unexpected::Error>;
|
||||
fn all_domains(&self) -> Result<Vec<ManagedDomain>, unexpected::Error>;
|
||||
}
|
||||
|
||||
pub struct ManagerImpl {
|
||||
@ -176,6 +153,8 @@ pub struct ManagerImpl {
|
||||
domain_checker: checker::DNSChecker,
|
||||
acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>,
|
||||
gemini_store: Option<Box<dyn gemini::Store + Send + Sync>>,
|
||||
builtins: collections::HashMap<domain::Name, config::ConfigBuiltinDomain>,
|
||||
proxied: collections::HashMap<domain::Name, config::ConfigProxiedDomain>,
|
||||
}
|
||||
|
||||
impl ManagerImpl {
|
||||
@ -191,6 +170,8 @@ impl ManagerImpl {
|
||||
domain_checker: checker::DNSChecker,
|
||||
acme_manager: Option<AcmeManager>,
|
||||
gemini_store: Option<GeminiStore>,
|
||||
builtins: collections::HashMap<domain::Name, config::ConfigBuiltinDomain>,
|
||||
proxied: collections::HashMap<domain::Name, config::ConfigProxiedDomain>,
|
||||
) -> sync::Arc<Self> {
|
||||
let manager = sync::Arc::new(ManagerImpl {
|
||||
origin_store: Box::from(origin_store),
|
||||
@ -199,6 +180,8 @@ impl ManagerImpl {
|
||||
acme_manager: acme_manager
|
||||
.map(|m| Box::new(m) as Box<dyn acme::manager::Manager + Send + Sync>),
|
||||
gemini_store: gemini_store.map(|m| Box::new(m) as Box<dyn gemini::Store + Send + Sync>),
|
||||
builtins,
|
||||
proxied,
|
||||
});
|
||||
|
||||
task_stack.push_spawn(|canceller| {
|
||||
@ -212,14 +195,18 @@ impl ManagerImpl {
|
||||
manager
|
||||
}
|
||||
|
||||
async fn sync_domain_certs_and_origin(
|
||||
fn sync_domain_origin(
|
||||
&self,
|
||||
domain: &domain::Name,
|
||||
settings: &domain::Settings,
|
||||
) -> Result<(), SyncWithSettingsError> {
|
||||
self.origin_store.sync(&settings.origin_descr)?;
|
||||
origin_descr: &origin::Descr,
|
||||
) -> Result<(), origin::SyncError> {
|
||||
log::info!("Syncing origin {:?} for domain {domain}", origin_descr,);
|
||||
self.origin_store.sync(origin_descr)
|
||||
}
|
||||
|
||||
async fn sync_domain_certs(&self, domain: &domain::Name) -> unexpected::Result<()> {
|
||||
if let Some(ref gemini_store) = self.gemini_store {
|
||||
log::info!("Syncing gemini certificate for domain {domain}");
|
||||
if let Some(_) = gemini_store.get_certificate(domain).or_unexpected()? {
|
||||
return Ok(());
|
||||
}
|
||||
@ -233,39 +220,45 @@ impl ManagerImpl {
|
||||
}
|
||||
|
||||
if let Some(ref acme_manager) = self.acme_manager {
|
||||
log::info!("Syncing HTTPS certificate for domain {domain}");
|
||||
acme_manager.sync_domain(domain.clone()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn sync_all_domains_once(&self) {
|
||||
let domains = match self.domain_store.all_domains() {
|
||||
Ok(domains) => domains,
|
||||
Err(err) => {
|
||||
log::error!("Error fetching all domains: {err}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
async fn sync_all_domains_once(&self) -> unexpected::Result<()> {
|
||||
let domains = self
|
||||
.all_domains()
|
||||
.or_unexpected_while("fetching all domains")?
|
||||
.into_iter();
|
||||
|
||||
for StoredDomain { domain, .. } in domains {
|
||||
log::info!("Syncing domain {}", &domain);
|
||||
|
||||
let get_res = match self.domain_store.get(&domain) {
|
||||
Ok(get_res) => get_res,
|
||||
Err(err) => {
|
||||
log::error!("Failed to fetch settings for domain {domain}: {err}");
|
||||
return;
|
||||
}
|
||||
for ManagedDomain { domain, .. } in domains {
|
||||
let settings = match self
|
||||
.get_settings(&domain)
|
||||
.map_unexpected_while(|| format!("fetching settings for {domain}"))?
|
||||
{
|
||||
GetSettingsResult::Stored(settings) => Some(settings),
|
||||
GetSettingsResult::Builtin(config) => Some(config.settings),
|
||||
GetSettingsResult::Proxied(_) => None,
|
||||
};
|
||||
|
||||
let settings = get_res.settings;
|
||||
if let Some(settings) = settings {
|
||||
self.sync_domain_origin(&domain, &settings.origin_descr)
|
||||
.map_unexpected_while(|| {
|
||||
format!(
|
||||
"syncing origin {:?} for domain {domain}",
|
||||
&settings.origin_descr,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Err(err) = self.sync_domain_certs_and_origin(&domain, &settings).await {
|
||||
log::error!("Failed to sync settings for {domain}, settings:{settings:?}: {err}",)
|
||||
}
|
||||
self.sync_domain_certs(&domain)
|
||||
.await
|
||||
.map_unexpected_while(|| format!("syncing certs for domain {domain}",))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn sync_all_domains(&self, canceller: CancellationToken) {
|
||||
@ -273,7 +266,9 @@ impl ManagerImpl {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = canceller.cancelled() => return,
|
||||
_ = interval.tick() => self.sync_all_domains_once().await,
|
||||
_ = interval.tick() => if let Err(err) = self.sync_all_domains_once().await {
|
||||
log::error!("Failed to sync all domains: {err}")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -281,7 +276,15 @@ impl ManagerImpl {
|
||||
|
||||
impl Manager for ManagerImpl {
|
||||
fn get_settings(&self, domain: &domain::Name) -> Result<GetSettingsResult, GetSettingsError> {
|
||||
Ok(self.domain_store.get(domain)?)
|
||||
if let Some(config) = self.builtins.get(domain) {
|
||||
return Ok(GetSettingsResult::Builtin(config.clone()));
|
||||
}
|
||||
|
||||
if let Some(config) = self.proxied.get(domain) {
|
||||
return Ok(GetSettingsResult::Proxied(config.clone()));
|
||||
}
|
||||
|
||||
Ok(GetSettingsResult::Stored(self.domain_store.get(domain)?))
|
||||
}
|
||||
|
||||
fn get_file<'store>(
|
||||
@ -289,7 +292,15 @@ impl Manager for ManagerImpl {
|
||||
domain: &domain::Name,
|
||||
path: &str,
|
||||
) -> Result<util::BoxByteStream, GetFileError> {
|
||||
let settings = self.domain_store.get(domain)?.settings;
|
||||
let settings = match self.get_settings(domain)? {
|
||||
GetSettingsResult::Stored(settings) => settings,
|
||||
GetSettingsResult::Builtin(config) => config.settings,
|
||||
GetSettingsResult::Proxied(_) => {
|
||||
return Err(
|
||||
unexpected::Error::from("can't call get_file on proxied domain").into(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let path = settings.process_path(path);
|
||||
|
||||
@ -306,14 +317,21 @@ impl Manager for ManagerImpl {
|
||||
settings: domain::Settings,
|
||||
) -> util::BoxFuture<'mgr, Result<(), SyncWithSettingsError>> {
|
||||
Box::pin(async move {
|
||||
if self.builtins.contains_key(&domain) || self.proxied.contains_key(&domain) {
|
||||
return Err(SyncWithSettingsError::NotModifiable);
|
||||
}
|
||||
|
||||
let hash = settings
|
||||
.hash()
|
||||
.or_unexpected_while("calculating config hash")?;
|
||||
|
||||
self.domain_checker.check_domain(&domain, &hash).await?;
|
||||
self.sync_domain_certs_and_origin(&domain, &settings)
|
||||
.await?;
|
||||
self.sync_domain_origin(&domain, &settings.origin_descr)?;
|
||||
self.sync_domain_certs(&domain)
|
||||
.await
|
||||
.or_unexpected_while("syncing domain certs")?;
|
||||
self.domain_store.set(&domain, &settings)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@ -336,8 +354,34 @@ impl Manager for ManagerImpl {
|
||||
self.domain_checker.get_challenge_token(domain)
|
||||
}
|
||||
|
||||
fn all_domains(&self) -> Result<Vec<StoredDomain>, unexpected::Error> {
|
||||
self.domain_store.all_domains()
|
||||
fn all_domains(&self) -> Result<Vec<ManagedDomain>, unexpected::Error> {
|
||||
let mut res: Vec<ManagedDomain> = self
|
||||
.domain_store
|
||||
.all_domains()?
|
||||
.into_iter()
|
||||
.map(|domain| ManagedDomain {
|
||||
domain,
|
||||
public: true,
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.builtins
|
||||
.iter()
|
||||
.map(|(domain, config)| ManagedDomain {
|
||||
domain: domain.clone(),
|
||||
public: config.public,
|
||||
})
|
||||
.collect_into(&mut res);
|
||||
|
||||
self.proxied
|
||||
.iter()
|
||||
.map(|(domain, _)| ManagedDomain {
|
||||
domain: domain.clone(),
|
||||
public: false,
|
||||
})
|
||||
.collect_into(&mut res);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,21 +1,8 @@
|
||||
use std::{collections, fs, io, path};
|
||||
use std::{fs, io, path};
|
||||
|
||||
use crate::domain;
|
||||
use crate::error::unexpected::{self, Intoable, Mappable};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// Extra information about a domain which is related to how its stored.
|
||||
pub struct Metadata {
|
||||
pub builtin: bool,
|
||||
pub public: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct GetResult {
|
||||
pub settings: domain::Settings,
|
||||
pub metadata: Metadata,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum GetError {
|
||||
#[error("not found")]
|
||||
@ -25,25 +12,11 @@ pub enum GetError {
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum SetError {
|
||||
#[error("cannot call set on builtin domain")]
|
||||
BuiltinDomain,
|
||||
|
||||
#[error(transparent)]
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
pub struct StoredDomain {
|
||||
pub domain: domain::Name,
|
||||
pub metadata: Metadata,
|
||||
}
|
||||
|
||||
#[mockall::automock]
|
||||
pub trait Store {
|
||||
fn get(&self, domain: &domain::Name) -> Result<GetResult, GetError>;
|
||||
fn set(&self, domain: &domain::Name, settings: &domain::Settings) -> Result<(), SetError>;
|
||||
fn all_domains(&self) -> Result<Vec<StoredDomain>, unexpected::Error>;
|
||||
fn get(&self, domain: &domain::Name) -> Result<domain::Settings, GetError>;
|
||||
fn set(&self, domain: &domain::Name, settings: &domain::Settings) -> unexpected::Result<()>;
|
||||
fn all_domains(&self) -> unexpected::Result<Vec<domain::Name>>;
|
||||
}
|
||||
|
||||
pub struct FSStore {
|
||||
@ -68,7 +41,7 @@ impl FSStore {
|
||||
}
|
||||
|
||||
impl Store for FSStore {
|
||||
fn get(&self, domain: &domain::Name) -> Result<GetResult, GetError> {
|
||||
fn get(&self, domain: &domain::Name) -> Result<domain::Settings, GetError> {
|
||||
let path = self.settings_file_path(domain);
|
||||
let settings_file = fs::File::open(path.as_path()).map_err(|e| match e.kind() {
|
||||
io::ErrorKind::NotFound => GetError::NotFound,
|
||||
@ -80,16 +53,10 @@ impl Store for FSStore {
|
||||
let settings = serde_json::from_reader(settings_file)
|
||||
.map_unexpected_while(|| format!("json parsing {}", path.display()))?;
|
||||
|
||||
Ok(GetResult {
|
||||
settings,
|
||||
metadata: Metadata {
|
||||
public: true,
|
||||
builtin: false,
|
||||
},
|
||||
})
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn set(&self, domain: &domain::Name, settings: &domain::Settings) -> Result<(), SetError> {
|
||||
fn set(&self, domain: &domain::Name, settings: &domain::Settings) -> unexpected::Result<()> {
|
||||
let dir_path = self.settings_dir_path(domain);
|
||||
fs::create_dir_all(dir_path.as_path())
|
||||
.map_unexpected_while(|| format!("creating dir {}", dir_path.display()))?;
|
||||
@ -104,85 +71,25 @@ impl Store for FSStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn all_domains(&self) -> Result<Vec<StoredDomain>, unexpected::Error> {
|
||||
fn all_domains(&self) -> unexpected::Result<Vec<domain::Name>> {
|
||||
fs::read_dir(&self.dir_path)
|
||||
.or_unexpected()?
|
||||
.map(
|
||||
|dir_entry_res: io::Result<fs::DirEntry>| -> Result<StoredDomain, unexpected::Error> {
|
||||
|dir_entry_res: io::Result<fs::DirEntry>| -> unexpected::Result<domain::Name> {
|
||||
let domain = dir_entry_res.or_unexpected()?.file_name();
|
||||
let domain = domain.to_str().ok_or(unexpected::Error::from(
|
||||
"couldn't convert os string to &str",
|
||||
))?;
|
||||
|
||||
Ok(StoredDomain{
|
||||
domain: domain.parse().map_unexpected_while(|| format!("parsing {domain} as domain name"))?,
|
||||
metadata: Metadata{
|
||||
public: true,
|
||||
builtin: false,
|
||||
},
|
||||
})
|
||||
Ok(domain
|
||||
.parse()
|
||||
.map_unexpected_while(|| format!("parsing {domain} as domain name"))?)
|
||||
},
|
||||
)
|
||||
.try_collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StoreWithBuiltin<S: Store> {
|
||||
inner: S,
|
||||
domains: collections::HashMap<domain::Name, domain::config::ConfigBuiltinDomain>,
|
||||
}
|
||||
|
||||
impl<S: Store> StoreWithBuiltin<S> {
|
||||
pub fn new(
|
||||
inner: S,
|
||||
builtin_domains: collections::HashMap<domain::Name, domain::config::ConfigBuiltinDomain>,
|
||||
) -> StoreWithBuiltin<S> {
|
||||
StoreWithBuiltin {
|
||||
inner,
|
||||
domains: builtin_domains,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Store> Store for StoreWithBuiltin<S> {
|
||||
fn get(&self, domain: &domain::Name) -> Result<GetResult, GetError> {
|
||||
if let Some(domain) = self.domains.get(domain) {
|
||||
return Ok(GetResult {
|
||||
settings: domain.settings.clone(),
|
||||
metadata: Metadata {
|
||||
public: domain.public,
|
||||
builtin: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
self.inner.get(domain)
|
||||
}
|
||||
|
||||
fn set(&self, domain: &domain::Name, settings: &domain::Settings) -> Result<(), SetError> {
|
||||
if self.domains.get(domain).is_some() {
|
||||
return Err(SetError::BuiltinDomain);
|
||||
}
|
||||
self.inner.set(domain, settings)
|
||||
}
|
||||
|
||||
fn all_domains(&self) -> Result<Vec<StoredDomain>, unexpected::Error> {
|
||||
let inner_domains = self.inner.all_domains()?;
|
||||
let mut domains: Vec<StoredDomain> = self
|
||||
.domains
|
||||
.iter()
|
||||
.map(|(domain, v)| StoredDomain {
|
||||
domain: domain.clone(),
|
||||
metadata: Metadata {
|
||||
public: v.public,
|
||||
builtin: true,
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
domains.extend(inner_domains);
|
||||
Ok(domains)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Store, *};
|
||||
@ -211,20 +118,11 @@ mod tests {
|
||||
|
||||
assert!(matches!(
|
||||
store.get(&domain),
|
||||
Err::<GetResult, GetError>(GetError::NotFound)
|
||||
Err::<domain::Settings, GetError>(GetError::NotFound)
|
||||
));
|
||||
|
||||
store.set(&domain, &settings).expect("set");
|
||||
assert_eq!(
|
||||
GetResult {
|
||||
settings,
|
||||
metadata: Metadata {
|
||||
public: true,
|
||||
builtin: false,
|
||||
},
|
||||
},
|
||||
store.get(&domain).expect("settings retrieved")
|
||||
);
|
||||
assert_eq!(settings, store.get(&domain).expect("settings retrieved"));
|
||||
|
||||
let new_settings = domain::Settings {
|
||||
origin_descr: Descr::Git {
|
||||
@ -236,13 +134,7 @@ mod tests {
|
||||
|
||||
store.set(&domain, &new_settings).expect("set");
|
||||
assert_eq!(
|
||||
GetResult {
|
||||
settings: new_settings,
|
||||
metadata: Metadata {
|
||||
public: true,
|
||||
builtin: false,
|
||||
},
|
||||
},
|
||||
new_settings,
|
||||
store.get(&domain).expect("settings retrieved")
|
||||
);
|
||||
}
|
||||
|
@ -102,9 +102,6 @@ async fn main() {
|
||||
domani::domain::store::FSStore::new(&config.domain.store_dir_path.join("domains"))
|
||||
.expect("domain config store initialization failed");
|
||||
|
||||
let domain_store =
|
||||
domani::domain::store::StoreWithBuiltin::new(domain_store, config.domain.builtins);
|
||||
|
||||
let domain_acme_manager = if config.service.http.https_addr.is_some() {
|
||||
let acme_config = config
|
||||
.domain
|
||||
@ -146,6 +143,8 @@ async fn main() {
|
||||
domain_checker,
|
||||
domain_acme_manager,
|
||||
domain_gemini_store,
|
||||
config.domain.builtins.clone(),
|
||||
config.domain.proxied.clone(),
|
||||
);
|
||||
|
||||
let _ = domani::service::http::Service::new(
|
||||
@ -153,6 +152,7 @@ async fn main() {
|
||||
domain_manager.clone(),
|
||||
domani::domain::manager::HttpsCertResolver::from(domain_manager.clone()),
|
||||
config.service.clone(),
|
||||
config.domain.proxied.clone(),
|
||||
);
|
||||
|
||||
if gemini_enabled {
|
||||
@ -160,7 +160,8 @@ async fn main() {
|
||||
&mut task_stack,
|
||||
domain_manager.clone(),
|
||||
domani::domain::manager::GeminiCertResolver::from(domain_manager.clone()),
|
||||
config.service,
|
||||
config.service.gemini.clone(),
|
||||
config.domain.proxied.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -6,13 +6,14 @@ pub use config::*;
|
||||
use crate::error::unexpected::{self, Mappable};
|
||||
use crate::{domain, service, task_stack, util};
|
||||
|
||||
use std::sync;
|
||||
use std::{collections, sync};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub struct Service {
|
||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
||||
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
|
||||
config: service::Config,
|
||||
config: Config,
|
||||
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
@ -29,7 +30,8 @@ impl Service {
|
||||
task_stack: &mut task_stack::TaskStack<unexpected::Error>,
|
||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
||||
cert_resolver: CertResolver,
|
||||
config: service::Config,
|
||||
config: Config,
|
||||
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
|
||||
) -> sync::Arc<Service>
|
||||
where
|
||||
CertResolver: rustls::server::ResolvesServerCert + 'static,
|
||||
@ -38,6 +40,7 @@ impl Service {
|
||||
domain_manager,
|
||||
cert_resolver: sync::Arc::from(cert_resolver),
|
||||
config,
|
||||
proxied,
|
||||
});
|
||||
task_stack.push_spawn(|canceller| listen(service.clone(), canceller));
|
||||
service
|
||||
@ -111,19 +114,13 @@ impl Service {
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn proxy_conn<IO>(
|
||||
&self,
|
||||
proxied_domain: &ConfigProxiedDomain,
|
||||
mut conn: IO,
|
||||
) -> unexpected::Result<()>
|
||||
async fn proxy_conn<IO>(&self, proxy_addr: &str, mut conn: IO) -> unexpected::Result<()>
|
||||
where
|
||||
IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
|
||||
{
|
||||
let mut proxy_conn = tokio::net::TcpStream::connect(&proxied_domain.url.addr)
|
||||
let mut proxy_conn = tokio::net::TcpStream::connect(proxy_addr)
|
||||
.await
|
||||
.map_unexpected_while(|| {
|
||||
format!("failed to connect to proxy {}", proxied_domain.url.url,)
|
||||
})?;
|
||||
.map_unexpected_while(|| format!("failed to connect to proxy {proxy_addr}"))?;
|
||||
|
||||
_ = tokio::io::copy_bidirectional(&mut conn, &mut proxy_conn).await;
|
||||
|
||||
@ -160,11 +157,14 @@ impl Service {
|
||||
})?;
|
||||
|
||||
// If the domain should be proxied, then proxy it
|
||||
if let Some(proxied_domain) = self.config.gemini.proxied_domains.get(&domain) {
|
||||
if let Some(config) = self.proxied.get(&domain) {
|
||||
if let Some(ref gemini_url) = config.gemini_url {
|
||||
let prefixed_conn = proxy::teed_io_to_prefixed(start.into_inner());
|
||||
self.proxy_conn(proxied_domain, prefixed_conn).await?;
|
||||
self.proxy_conn(gemini_url.addr.as_str(), prefixed_conn)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let conn = start.into_stream(tls_config).await.or_unexpected()?;
|
||||
self.serve_conn(&domain, conn).await
|
||||
@ -185,7 +185,6 @@ async fn listen(
|
||||
) -> unexpected::Result<()> {
|
||||
let addr = &service
|
||||
.config
|
||||
.gemini
|
||||
.gemini_addr
|
||||
.expect("listen called with gemini_addr not set");
|
||||
|
||||
|
@ -1,79 +1,20 @@
|
||||
use crate::domain;
|
||||
use crate::error::unexpected::{self, Mappable};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, TryFromInto};
|
||||
|
||||
use std::{collections, net, str::FromStr};
|
||||
use std::net;
|
||||
|
||||
fn default_gemini_addr() -> Option<net::SocketAddr> {
|
||||
Some(net::SocketAddr::from_str("[::]:3965").unwrap())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct ConfigProxiedDomainUrl {
|
||||
pub url: String,
|
||||
pub addr: String,
|
||||
}
|
||||
|
||||
impl From<ConfigProxiedDomainUrl> for String {
|
||||
fn from(url: ConfigProxiedDomainUrl) -> Self {
|
||||
url.url
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ConfigProxiedDomainUrl {
|
||||
type Error = unexpected::Error;
|
||||
|
||||
fn try_from(url: String) -> Result<Self, Self::Error> {
|
||||
// use http's implementation, should be the same
|
||||
let parsed = http::Uri::from_str(url.as_str())
|
||||
.map_unexpected_while(|| format!("parsing proxy url {url}"))?;
|
||||
|
||||
let scheme = parsed.scheme().map_unexpected_while(|| {
|
||||
format!("expected a scheme of gemini in the proxy url {url}")
|
||||
})?;
|
||||
|
||||
if scheme != "gemini" {
|
||||
return Err(unexpected::Error::from(
|
||||
format!("scheme of proxy url {url} should be 'gemini'",).as_str(),
|
||||
));
|
||||
}
|
||||
|
||||
match parsed.authority() {
|
||||
None => Err(unexpected::Error::from(
|
||||
format!("proxy url {url} should have a host",).as_str(),
|
||||
)),
|
||||
Some(authority) => {
|
||||
let port = authority.port().map(|p| p.as_u16()).unwrap_or(1965);
|
||||
Ok(ConfigProxiedDomainUrl {
|
||||
url: url,
|
||||
addr: format!("{}:{port}", authority.host()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct ConfigProxiedDomain {
|
||||
#[serde_as(as = "TryFromInto<String>")]
|
||||
pub url: ConfigProxiedDomainUrl,
|
||||
Some("[::]:3965".parse().unwrap())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct Config {
|
||||
#[serde(default = "default_gemini_addr")]
|
||||
pub gemini_addr: Option<net::SocketAddr>,
|
||||
pub proxied_domains: collections::HashMap<domain::Name, ConfigProxiedDomain>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
gemini_addr: default_gemini_addr(),
|
||||
proxied_domains: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use http::request::Parts;
|
||||
use hyper::{Body, Method, Request, Response};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::{future, net, sync};
|
||||
use std::{collections, future, net, sync};
|
||||
|
||||
use crate::error::unexpected;
|
||||
use crate::{domain, service, task_stack};
|
||||
@ -20,6 +20,7 @@ pub struct Service {
|
||||
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
|
||||
handlebars: handlebars::Handlebars<'static>,
|
||||
config: service::Config,
|
||||
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@ -57,6 +58,7 @@ impl Service {
|
||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
||||
cert_resolver: CertResolver,
|
||||
config: service::Config,
|
||||
proxied: collections::HashMap<domain::Name, domain::ConfigProxiedDomain>,
|
||||
) -> sync::Arc<Service>
|
||||
where
|
||||
CertResolver: rustls::server::ResolvesServerCert + 'static,
|
||||
@ -68,6 +70,7 @@ impl Service {
|
||||
cert_resolver: sync::Arc::from(cert_resolver),
|
||||
handlebars: tpl::get(),
|
||||
config,
|
||||
proxied,
|
||||
});
|
||||
|
||||
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
|
||||
@ -209,8 +212,6 @@ impl Service {
|
||||
}
|
||||
|
||||
fn domain(&self, args: DomainArgs) -> Response<Body> {
|
||||
use domain::store::Metadata;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Data {
|
||||
domain: domain::Name,
|
||||
@ -218,42 +219,11 @@ impl Service {
|
||||
}
|
||||
|
||||
let settings = match self.domain_manager.get_settings(&args.domain) {
|
||||
Ok(domain::manager::GetSettingsResult {
|
||||
metadata:
|
||||
Metadata {
|
||||
public: false,
|
||||
builtin: _,
|
||||
},
|
||||
..
|
||||
}) => None,
|
||||
|
||||
Ok(domain::manager::GetSettingsResult {
|
||||
settings,
|
||||
metadata:
|
||||
Metadata {
|
||||
public: true,
|
||||
builtin: false,
|
||||
},
|
||||
}) => Some(settings),
|
||||
|
||||
Ok(domain::manager::GetSettingsResult {
|
||||
metadata:
|
||||
Metadata {
|
||||
public: true,
|
||||
builtin: true,
|
||||
},
|
||||
..
|
||||
}) => {
|
||||
return self.render_error_page(
|
||||
403,
|
||||
format!(
|
||||
"Settings for domain {} cannot be viewed or modified",
|
||||
args.domain
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
Ok(domain::manager::GetSettingsResult::Stored(settings)) => Some(settings),
|
||||
Ok(domain::manager::GetSettingsResult::Builtin(config)) => {
|
||||
config.public.then(|| config.settings)
|
||||
}
|
||||
|
||||
Ok(domain::manager::GetSettingsResult::Proxied(_)) => None,
|
||||
Err(domain::manager::GetSettingsError::NotFound) => None,
|
||||
Err(domain::manager::GetSettingsError::Unexpected(e)) => {
|
||||
return self.internal_error(
|
||||
@ -355,7 +325,7 @@ impl Service {
|
||||
|
||||
let error_msg = match sync_result {
|
||||
Ok(_) => None,
|
||||
Err(domain::manager::SyncWithSettingsError::BuiltinDomain) => Some("This domain is not able to be configured, please contact the server administrator.".to_string()),
|
||||
Err(domain::manager::SyncWithSettingsError::NotModifiable) => Some("This domain is not able to be configured, please contact the server administrator.".to_string()),
|
||||
Err(domain::manager::SyncWithSettingsError::InvalidURL) => Some("Fetching the git repository failed, please double check that you input the correct URL.".to_string()),
|
||||
Err(domain::manager::SyncWithSettingsError::InvalidBranchName) => Some("The git repository does not have a branch of the given name, please double check that you input the correct name.".to_string()),
|
||||
Err(domain::manager::SyncWithSettingsError::AlreadyInProgress) => Some("The configuration of your domain is still in progress, please refresh in a few minutes.".to_string()),
|
||||
@ -386,7 +356,7 @@ impl Service {
|
||||
|
||||
let mut domains: Vec<String> = domains
|
||||
.into_iter()
|
||||
.filter(|d| d.metadata.public)
|
||||
.filter(|d| d.public)
|
||||
.map(|d| d.domain.as_str().to_string())
|
||||
.collect();
|
||||
|
||||
@ -487,9 +457,11 @@ impl Service {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(proxied_domain_config) = self.config.http.proxied_domains.get(&domain) {
|
||||
if let Some(config) = self.proxied.get(&domain) {
|
||||
if let Some(ref http_url) = config.http_url {
|
||||
return service::http::proxy::serve_http_request(
|
||||
proxied_domain_config,
|
||||
http_url.original_url.as_str(),
|
||||
&config.http_request_headers.0,
|
||||
client_ip,
|
||||
req,
|
||||
req_is_https,
|
||||
@ -497,14 +469,12 @@ impl Service {
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
self.internal_error(
|
||||
format!(
|
||||
"serving {domain} via proxy {}: {e}",
|
||||
proxied_domain_config.url.as_ref()
|
||||
)
|
||||
format!("serving {domain} via proxy {}: {e}", http_url.original_url)
|
||||
.as_str(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if Some(&domain) == self.config.interface_domain.as_ref() {
|
||||
return self.serve_interface(req).await;
|
||||
|
@ -1,13 +1,8 @@
|
||||
use crate::domain;
|
||||
use crate::error::unexpected::{self, Mappable};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, TryFromInto};
|
||||
|
||||
use std::{collections, net, str::FromStr};
|
||||
use std::net;
|
||||
|
||||
fn default_http_addr() -> net::SocketAddr {
|
||||
net::SocketAddr::from_str("[::]:3080").unwrap()
|
||||
"[::]:3080".parse().unwrap()
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
@ -40,105 +35,6 @@ impl AsRef<hyper::Method> for ConfigFormMethod {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigProxiedDomainUrl(String);
|
||||
|
||||
impl AsRef<str> for ConfigProxiedDomainUrl {
|
||||
fn as_ref(&self) -> &str {
|
||||
return &self.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigProxiedDomainUrl> for String {
|
||||
fn from(url: ConfigProxiedDomainUrl) -> Self {
|
||||
url.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ConfigProxiedDomainUrl {
|
||||
type Error = unexpected::Error;
|
||||
|
||||
fn try_from(url: String) -> Result<Self, Self::Error> {
|
||||
let parsed = http::Uri::from_str(url.as_str())
|
||||
.map_unexpected_while(|| format!("parsing proxy url {url}"))?;
|
||||
|
||||
let scheme = parsed
|
||||
.scheme()
|
||||
.map_unexpected_while(|| format!("expected a scheme of http in the proxy url {url}"))?;
|
||||
|
||||
if scheme != "http" {
|
||||
return Err(unexpected::Error::from(
|
||||
format!("scheme of proxy url {url} should be 'http'",).as_str(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ConfigProxiedDomainUrl(url))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct ConfigProxiedDomainRequestHeader {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigProxiedDomainRequestHeaders(http::header::HeaderMap);
|
||||
|
||||
impl AsRef<http::header::HeaderMap> for ConfigProxiedDomainRequestHeaders {
|
||||
fn as_ref(&self) -> &http::header::HeaderMap {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConfigProxiedDomainRequestHeaders {
|
||||
fn default() -> Self {
|
||||
ConfigProxiedDomainRequestHeaders(http::header::HeaderMap::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ConfigProxiedDomainRequestHeaders> for Vec<ConfigProxiedDomainRequestHeader> {
|
||||
type Error = http::header::ToStrError;
|
||||
|
||||
fn try_from(h: ConfigProxiedDomainRequestHeaders) -> Result<Self, Self::Error> {
|
||||
let mut v = vec![];
|
||||
for (name, value) in &h.0 {
|
||||
v.push(ConfigProxiedDomainRequestHeader {
|
||||
name: name.to_string(),
|
||||
value: value.to_str()?.to_string(),
|
||||
})
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<ConfigProxiedDomainRequestHeader>> for ConfigProxiedDomainRequestHeaders {
|
||||
type Error = unexpected::Error;
|
||||
|
||||
fn try_from(v: Vec<ConfigProxiedDomainRequestHeader>) -> Result<Self, Self::Error> {
|
||||
use http::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
|
||||
let mut h = HeaderMap::new();
|
||||
for pair in v {
|
||||
let name: HeaderName = pair.name.parse().or_unexpected()?;
|
||||
let value: HeaderValue = pair.value.parse().or_unexpected()?;
|
||||
h.insert(name, value);
|
||||
}
|
||||
Ok(ConfigProxiedDomainRequestHeaders(h))
|
||||
}
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct ConfigProxiedDomain {
|
||||
#[serde_as(as = "TryFromInto<String>")]
|
||||
pub url: ConfigProxiedDomainUrl,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "TryFromInto<Vec<ConfigProxiedDomainRequestHeader>>")]
|
||||
pub request_headers: ConfigProxiedDomainRequestHeaders,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct Config {
|
||||
#[serde(default = "default_http_addr")]
|
||||
@ -147,9 +43,6 @@ pub struct Config {
|
||||
|
||||
#[serde(default)]
|
||||
pub form_method: ConfigFormMethod,
|
||||
|
||||
#[serde(default)]
|
||||
pub proxied_domains: collections::HashMap<domain::Name, ConfigProxiedDomain>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -158,7 +51,6 @@ impl Default for Config {
|
||||
http_addr: default_http_addr(),
|
||||
https_addr: None,
|
||||
form_method: Default::default(),
|
||||
proxied_domains: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
use crate::error::unexpected::{self};
|
||||
use crate::service;
|
||||
use http::header::HeaderValue;
|
||||
use crate::error::unexpected;
|
||||
use std::net;
|
||||
|
||||
pub async fn serve_http_request(
|
||||
config: &service::http::ConfigProxiedDomain,
|
||||
proxy_addr: &str,
|
||||
headers: &http::header::HeaderMap,
|
||||
client_ip: net::IpAddr,
|
||||
mut req: hyper::Request<hyper::Body>,
|
||||
req_is_https: bool,
|
||||
) -> unexpected::Result<hyper::Response<hyper::Body>> {
|
||||
for (name, value) in config.request_headers.as_ref() {
|
||||
for (name, value) in headers {
|
||||
if value == "" {
|
||||
req.headers_mut().remove(name);
|
||||
continue;
|
||||
@ -19,18 +18,18 @@ pub async fn serve_http_request(
|
||||
}
|
||||
|
||||
if req_is_https {
|
||||
req.headers_mut()
|
||||
.insert("x-forwarded-proto", HeaderValue::from_static("https"));
|
||||
req.headers_mut().insert(
|
||||
"x-forwarded-proto",
|
||||
http::header::HeaderValue::from_static("https"),
|
||||
);
|
||||
}
|
||||
|
||||
let url = config.url.as_ref();
|
||||
|
||||
match hyper_reverse_proxy::call(client_ip, url, req).await {
|
||||
match hyper_reverse_proxy::call(client_ip, proxy_addr, req).await {
|
||||
Ok(res) => Ok(res),
|
||||
// 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 to {url}: {e:?}").as_str(),
|
||||
format!("error while proxying to {proxy_addr}: {e:?}").as_str(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user