//! The pages: every request this server does not answer itself, handed to //! the SvelteKit app that draws them, which reads back under `/_data`. use axum::Router; use axum::body::Body; use axum::extract::Request; use axum::http::{HeaderMap, HeaderValue, StatusCode, header}; use axum::response::{IntoResponse, Response}; use std::sync::LazyLock; /// The largest request body handed on: the pages take none to speak of. const BODY_LIMIT: usize = 1 << 20; /// Headers about one hop of a connection, which are not handed on. const HOP: [header::HeaderName; 6] = [ header::CONNECTION, header::HOST, header::TRANSFER_ENCODING, header::CONTENT_LENGTH, header::UPGRADE, header::TE, ]; static CLIENT: LazyLock = LazyLock::new(|| { reqwest::Client::builder() .redirect(reqwest::redirect::Policy::none()) .build() .expect("a client builds") }); /// A router that hands every request to the app at `web`. pub fn pages(web: String) -> Router { let web: &'static str = web.trim_end_matches('/').to_string().leak(); Router::new().fallback(move |req: Request| page(web, req)) } async fn page(web: &str, req: Request) -> Response { match handed(web, req).await { Ok(res) => res, Err(e) => ( StatusCode::BAD_GATEWAY, format!("The pages are not being drawn: {e:#}"), ) .into_response(), } } /// `req`, as the app at `web` answers it. The app sees the host it was /// asked for, as `x-forwarded-host`. async fn handed(web: &str, req: Request) -> anyhow::Result { let (parts, body) = req.into_parts(); let at = parts.uri.path_and_query().map_or("/", |p| p.as_str()); let mut headers = kept(&parts.headers); if let Some(host) = parts.headers.get(header::HOST) { headers.insert("x-forwarded-host", host.clone()); } headers .entry("x-forwarded-proto") .or_insert(HeaderValue::from_static("http")); let body = axum::body::to_bytes(body, BODY_LIMIT).await?; let answer = CLIENT .request(parts.method, format!("{web}{at}")) .headers(headers) .body(body) .send() .await?; let mut res = Response::builder().status(answer.status()); let mut headers = kept(answer.headers()); for (name, value) in [ (header::X_CONTENT_TYPE_OPTIONS, "nosniff"), (header::REFERRER_POLICY, "same-origin"), ] { headers .entry(name) .or_insert(HeaderValue::from_static(value)); } *res.headers_mut().unwrap() = headers; Ok(res.body(Body::from(answer.bytes().await?))?) } /// `headers`, but for those about one hop. fn kept(headers: &HeaderMap) -> HeaderMap { let mut out = headers.clone(); for name in HOP { out.remove(name); } out }