109 lines
3.7 KiB
Rust
109 lines
3.7 KiB
Rust
//! 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<serde_json::Value> {
|
|
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()
|
|
}
|