//! Separate opt-in HTTP backend, not a shortcut inside the proxy handler. use axum::{ body::Body, extract::Request, response::{Html, IntoResponse, Response}, routing::{any, get}, Json, Router, }; use bytes::Bytes; use http::HeaderMap; use serde_json::json; use std::{convert::Infallible, time::Duration}; pub fn router() -> Router { Router::new() .route( "/", get(|| async { Html(include_str!("admin/web/demo.html")) }), ) .route("/ws", get(websocket)) .route("/cache-demo", get(cache_demo)) .route("/inspect", any(inspect)) .route("/echo", any(echo)) .route("/stream", get(stream)) .fallback(|| async { Json(json!({"backend":"demo","message":"Request reached the separate demo backend."})) }) } async fn inspect(request: Request) -> Json { let headers = request.headers(); let read = |name: &str| { headers .get(name) .and_then(|v| v.to_str().ok()) .unwrap_or("") .to_owned() }; Json( json!({"backend":"demo", "method":request.method().as_str(), "path":request.uri().path(), "query":request.uri().query(), "http_version":format!("{:?}", request.version()), "host":read("host"), "forwarded_for":read("x-forwarded-for"), "forwarded_proto":read("x-forwarded-proto"), "request_id":read("x-request-id")}), ) } async fn echo(request: Request) -> Response { let content_type = request.headers().get("content-type").cloned(); let mut response = request.into_body().into_response(); if let Some(value) = content_type { response.headers_mut().insert("content-type", value); } response } async fn stream() -> (HeaderMap, Body) { let stream = futures_util::stream::unfold(0u32, |index| async move { if index >= 8 { return None; } tokio::time::sleep(Duration::from_millis(300)).await; Some(( Ok::<_, Infallible>(Bytes::from(format!( "chunk={} backend=demo streaming=true\n", index + 1 ))), index + 1, )) }); let mut headers = HeaderMap::new(); headers.insert("content-type", "text/plain; charset=utf-8".parse().unwrap()); headers.insert("cache-control", "no-store".parse().unwrap()); (headers, Body::from_stream(stream)) } async fn websocket(ws: axum::extract::ws::WebSocketUpgrade) -> Response { use axum::extract::ws::Message; ws.max_message_size(65_536) .max_frame_size(65_536) .on_upgrade(|mut socket| async move { while let Some(Ok(message)) = socket.recv().await { let reply = match message { Message::Text(_) | Message::Binary(_) => message, Message::Ping(data) => Message::Pong(data), Message::Pong(_) => continue, Message::Close(frame) => { let _ = socket.send(Message::Close(frame)).await; break; } }; if socket.send(reply).await.is_err() { break; } } }) } /// Explicitly public demo object for manual cache/compression checks; no request secrets. async fn cache_demo() -> Response { let now = crate::logging::now_ms(); let text = format!( "RustProxy public cache demo. Origin generation time: {now} ms\n{}", "This is an anonymous demo object, fresh for 30 seconds.\n".repeat(100) ); ( [ ("cache-control", "public, max-age=30"), ("content-type", "text/plain; charset=utf-8"), ], text, ) .into_response() }