Quantum/src/handlers/static_response.rs
RTSDA fd12f35e6c Implement core Caddy replacement functionality
- Add comprehensive HTTP/1.1 and HTTP/2 server support
- Implement reverse proxy with load balancing
- Add static file serving with proper MIME types
- Create multi-port server management
- Add TLS/HTTPS support with SNI and ACME
- Implement authentication middleware framework
- Add advanced routing and matchers system
- Create file sync service foundation
- Add metrics collection and health monitoring
- Implement simple configuration format
- Successfully tested with production-equivalent config

Core features working:
- Reverse proxy to localhost:3000 ✓
- Static file serving ✓
- Multi-port configuration ✓
- CORS headers and security ✓
- Simple config format detection ✓

Ready for production testing as Caddy replacement.
2025-08-17 20:02:04 -04:00

150 lines
4.8 KiB
Rust

use anyhow::Result;
use http::{Request, Response, StatusCode, HeaderValue};
use http_body_util::Full;
use hyper::body::Bytes;
use std::collections::HashMap;
use tracing::debug;
/// Handler for static responses (redirects, custom responses, etc.)
#[derive(Debug, Clone)]
pub struct StaticResponseHandler {
pub status_code: u16,
pub body: Option<String>,
pub headers: HashMap<String, String>,
}
impl StaticResponseHandler {
/// Create a redirect response
pub fn redirect(location: String, status_code: Option<u16>) -> Self {
let mut headers = HashMap::new();
headers.insert("location".to_string(), location);
Self {
status_code: status_code.unwrap_or(301),
body: None,
headers,
}
}
/// Create a custom response with body
pub fn with_body(status_code: u16, body: String) -> Self {
Self {
status_code,
body: Some(body),
headers: HashMap::new(),
}
}
/// Add a header to the response
pub fn with_header(mut self, name: String, value: String) -> Self {
self.headers.insert(name, value);
self
}
/// Add multiple headers
pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
self.headers.extend(headers);
self
}
/// Generate the HTTP response
pub fn generate_response(&self) -> Result<Response<Full<Bytes>>> {
let status = StatusCode::from_u16(self.status_code)
.map_err(|e| anyhow::anyhow!("Invalid status code {}: {}", self.status_code, e))?;
let mut response_builder = Response::builder().status(status);
// Add headers
for (name, value) in &self.headers {
let header_name = name.to_lowercase();
let header_value = HeaderValue::from_str(value)
.map_err(|e| anyhow::anyhow!("Invalid header value for '{}': {}", name, e))?;
response_builder = response_builder.header(header_name, header_value);
}
// Set body
let body = self.body.as_deref().unwrap_or("");
response_builder
.body(Full::new(Bytes::from(body.to_string())))
.map_err(|e| anyhow::anyhow!("Failed to build response: {}", e))
}
/// Handle a request with this static response
pub async fn handle_request<T>(&self, _req: Request<T>) -> Result<Response<Full<Bytes>>> {
debug!("Serving static response: {} {}", self.status_code,
self.body.as_deref().unwrap_or("<no body>"));
self.generate_response()
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::Method;
fn create_test_request<T>(method: Method, path: &str, body: T) -> Request<T> {
Request::builder()
.method(method)
.uri(path)
.body(body)
.unwrap()
}
#[test]
fn test_redirect_handler() {
let handler = StaticResponseHandler::redirect(
"https://example.com".to_string(),
Some(302)
);
assert_eq!(handler.status_code, 302);
assert_eq!(handler.headers.get("location").unwrap(), "https://example.com");
assert!(handler.body.is_none());
}
#[test]
fn test_custom_response_handler() {
let handler = StaticResponseHandler::with_body(
410,
"Service has been migrated".to_string()
);
assert_eq!(handler.status_code, 410);
assert_eq!(handler.body.as_ref().unwrap(), "Service has been migrated");
}
#[test]
fn test_response_with_headers() {
let handler = StaticResponseHandler::with_body(200, "OK".to_string())
.with_header("content-type".to_string(), "application/json".to_string())
.with_header("cache-control".to_string(), "no-cache".to_string());
assert_eq!(handler.headers.len(), 2);
assert_eq!(handler.headers.get("content-type").unwrap(), "application/json");
assert_eq!(handler.headers.get("cache-control").unwrap(), "no-cache");
}
#[tokio::test]
async fn test_generate_response() {
let handler = StaticResponseHandler::redirect(
"https://example.com".to_string(),
Some(301)
);
let response = handler.generate_response().unwrap();
assert_eq!(response.status(), StatusCode::MOVED_PERMANENTLY);
assert_eq!(
response.headers().get("location").unwrap(),
"https://example.com"
);
}
#[tokio::test]
async fn test_handle_request() {
let handler = StaticResponseHandler::with_body(200, "Hello World".to_string());
let req = create_test_request(Method::GET, "/test", "body");
let response = handler.handle_request(req).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
}