mirror of
https://github.com/Xevion/xevion.dev.git
synced 2026-01-31 04:26:43 -06:00
Extract reqwest client creation into dedicated HttpClient abstraction that handles both TCP and Unix socket connections transparently. Simplifies proxy logic by removing duplicate URL construction and client selection throughout the codebase.
130 lines
4.5 KiB
Rust
130 lines
4.5 KiB
Rust
use axum::{
|
|
http::{HeaderMap, HeaderValue, StatusCode, header},
|
|
response::{IntoResponse, Response},
|
|
};
|
|
|
|
use crate::assets;
|
|
|
|
/// Check if a path represents a static asset
|
|
pub fn is_static_asset(path: &str) -> bool {
|
|
path.starts_with("/node_modules/")
|
|
|| path.starts_with("/@") // Vite internals like /@vite/client, /@fs/, /@id/
|
|
|| path.starts_with("/.svelte-kit/")
|
|
|| path.starts_with("/.well-known/")
|
|
|| path.ends_with(".woff2")
|
|
|| path.ends_with(".woff")
|
|
|| path.ends_with(".ttf")
|
|
|| path.ends_with(".ico")
|
|
|| path.ends_with(".png")
|
|
|| path.ends_with(".jpg")
|
|
|| path.ends_with(".svg")
|
|
|| path.ends_with(".webp")
|
|
|| path.ends_with(".css")
|
|
|| path.ends_with(".js")
|
|
|| path.ends_with(".map")
|
|
}
|
|
|
|
/// Check if a path represents a page route (not an asset)
|
|
pub fn is_page_route(path: &str) -> bool {
|
|
!path.starts_with("/node_modules/")
|
|
&& !path.starts_with("/@")
|
|
&& !path.starts_with("/.svelte-kit/")
|
|
&& !path.contains('.')
|
|
}
|
|
|
|
/// Check if the request accepts HTML responses
|
|
pub fn accepts_html(headers: &HeaderMap) -> bool {
|
|
if let Some(accept) = headers.get(header::ACCEPT) {
|
|
if let Ok(accept_str) = accept.to_str() {
|
|
return accept_str.contains("text/html") || accept_str.contains("*/*");
|
|
}
|
|
}
|
|
// Default to true for requests without Accept header (browsers typically send it)
|
|
true
|
|
}
|
|
|
|
/// Determines if request prefers raw content (CLI tools) over HTML
|
|
pub fn prefers_raw_content(headers: &HeaderMap) -> bool {
|
|
// Check User-Agent for known CLI tools first (most reliable)
|
|
if let Some(ua) = headers.get(header::USER_AGENT) {
|
|
if let Ok(ua_str) = ua.to_str() {
|
|
let ua_lower = ua_str.to_lowercase();
|
|
if ua_lower.starts_with("curl/")
|
|
|| ua_lower.starts_with("wget/")
|
|
|| ua_lower.starts_with("httpie/")
|
|
|| ua_lower.contains("curlie")
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check Accept header - if it explicitly prefers text/html, serve HTML
|
|
if let Some(accept) = headers.get(header::ACCEPT) {
|
|
if let Ok(accept_str) = accept.to_str() {
|
|
// If text/html appears before */* in the list, they prefer HTML
|
|
if let Some(html_pos) = accept_str.find("text/html") {
|
|
if let Some(wildcard_pos) = accept_str.find("*/*") {
|
|
return html_pos > wildcard_pos;
|
|
}
|
|
// Has text/html but no */* → prefers HTML
|
|
return false;
|
|
}
|
|
// Has */* but no text/html → probably a CLI tool
|
|
if accept_str.contains("*/*") && !accept_str.contains("text/html") {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No Accept header → assume browser (safer default)
|
|
false
|
|
}
|
|
|
|
/// Serve a prerendered error page for the given status code
|
|
pub fn serve_error_page(status: StatusCode) -> Response {
|
|
let status_code = status.as_u16();
|
|
|
|
if let Some(html) = assets::get_error_page(status_code) {
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert(
|
|
header::CONTENT_TYPE,
|
|
HeaderValue::from_static("text/html; charset=utf-8"),
|
|
);
|
|
headers.insert(
|
|
header::CACHE_CONTROL,
|
|
HeaderValue::from_static("no-cache, no-store, must-revalidate"),
|
|
);
|
|
|
|
(status, headers, html).into_response()
|
|
} else {
|
|
// Fallback for undefined error codes (500 generic page)
|
|
tracing::warn!(
|
|
status_code,
|
|
"No prerendered error page found for status code - using fallback"
|
|
);
|
|
|
|
if let Some(fallback_html) = assets::get_error_page(500) {
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert(
|
|
header::CONTENT_TYPE,
|
|
HeaderValue::from_static("text/html; charset=utf-8"),
|
|
);
|
|
headers.insert(
|
|
header::CACHE_CONTROL,
|
|
HeaderValue::from_static("no-cache, no-store, must-revalidate"),
|
|
);
|
|
|
|
(status, headers, fallback_html).into_response()
|
|
} else {
|
|
// Last resort: plaintext (should never happen if 500.html exists)
|
|
(status, format!("Error {}", status_code)).into_response()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Validate hex color format (6 characters, no hash, no alpha)
|
|
pub fn validate_hex_color(color: &str) -> bool {
|
|
color.len() == 6 && color.chars().all(|c| c.is_ascii_hexdigit())
|
|
}
|