#[cfg(feature = "rustls-tls")]
#[cfg(feature = "native-tls")]
compile_error!("You can only enable one TLS backend");
pub extern crate mime;
pub extern crate url;
mod http_client;
mod middleware;
mod mime_util;
mod routes;
use crate::http_client::{build_http_client, BuildHttpClientArgs};
use anyhow::Result;
use axum::{middleware as axum_middleware, routing::get, Router};
use http_client::HttpClient;
use mime::Mime;
use routes::{HEALTH_ENDPOINT, INDEX_ENDPOINT, PROXY_ENDPOINT};
use std::{net::SocketAddr, time::Duration};
use tokio::net::TcpListener;
use tower_http::{
catch_panic::CatchPanicLayer,
normalize_path::NormalizePathLayer,
timeout::TimeoutLayer,
trace::{self, TraceLayer},
};
use tracing::{info, Level};
use url::Url;
#[derive(Debug, Clone)]
pub struct AigisServer {
router_inner: Router,
}
#[derive(Debug, Clone)]
pub struct AigisServerSettings {
pub request_timeout: u64,
pub upstream_settings: UpstreamSettings,
pub proxy_settings: ProxySettings,
}
#[derive(Debug, Clone)]
pub struct ProxySettings {
pub allowed_mimetypes: Vec<Mime>,
pub max_size: u64,
pub allowed_domains: Option<Vec<Url>>,
pub max_content_rescale_resolution: u32,
}
#[derive(Debug, Clone)]
pub struct UpstreamSettings {
pub pass_headers: Option<Vec<String>>,
pub allow_invalid_certs: bool,
pub request_timeout: u64,
pub max_redirects: usize,
pub use_received_cache_headers: bool,
}
impl Default for AigisServerSettings {
fn default() -> Self {
Self {
request_timeout: 10,
proxy_settings: ProxySettings::default(),
upstream_settings: UpstreamSettings::default(),
}
}
}
impl Default for ProxySettings {
fn default() -> Self {
Self {
allowed_mimetypes: vec![mime::IMAGE_STAR],
allowed_domains: None,
max_size: 100000000,
max_content_rescale_resolution: 1024,
}
}
}
impl Default for UpstreamSettings {
fn default() -> Self {
Self {
allow_invalid_certs: false,
max_redirects: 10,
pass_headers: None,
request_timeout: 30,
use_received_cache_headers: true,
}
}
}
#[derive(Debug, Clone)]
struct AppState {
client: HttpClient,
settings: AigisServerSettings,
}
impl AigisServer {
pub fn new(settings: AigisServerSettings) -> Result<Self> {
let router = Router::new()
.route(PROXY_ENDPOINT, get(routes::proxy_handler))
.route(INDEX_ENDPOINT, get(routes::index_handler))
.route(HEALTH_ENDPOINT, get(routes::health_handler))
.layer(
TraceLayer::new_for_http()
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
)
.layer(TimeoutLayer::new(Duration::from_secs(
settings.request_timeout,
)))
.layer(NormalizePathLayer::trim_trailing_slash())
.layer(CatchPanicLayer::new())
.layer(axum_middleware::from_fn(middleware::header_middleware))
.with_state(AppState {
client: build_http_client(BuildHttpClientArgs {
allow_invalid_certs: settings.upstream_settings.allow_invalid_certs,
max_redirects: settings.upstream_settings.max_redirects,
request_timeout: Duration::from_secs(
settings.upstream_settings.request_timeout,
),
})?,
settings,
});
Ok(Self {
router_inner: router,
})
}
pub async fn start(self, address: &SocketAddr) -> Result<()> {
let tcp_listener = TcpListener::bind(&address).await?;
info!("Listening on http://{}", tcp_listener.local_addr()?);
axum::serve(tcp_listener, self.router_inner)
.with_graceful_shutdown(async {
tokio::signal::ctrl_c()
.await
.expect("failed to listen for ctrl-c");
})
.await?;
Ok(())
}
}