systemprompt_api/services/static_content/static_files/
cache.rs1use axum::http::{HeaderMap, StatusCode, header};
8use axum::response::IntoResponse;
9use std::hash::{Hash, Hasher};
10
11pub const CACHE_STATIC_ASSET: &str = "public, max-age=31536000, immutable";
12pub const CACHE_HTML: &str = "no-cache";
13pub const CACHE_METADATA: &str = "public, max-age=3600";
14
15pub fn compute_etag(content: &[u8]) -> String {
16 let mut hasher = std::collections::hash_map::DefaultHasher::new();
17 content.hash(&mut hasher);
18 format!("\"{}\"", hasher.finish())
19}
20
21pub(super) fn etag_matches(headers: &HeaderMap, etag: &str) -> bool {
22 headers
23 .get(header::IF_NONE_MATCH)
24 .and_then(|v| v.to_str().ok())
25 == Some(etag)
26}
27
28pub(super) fn not_modified_response(
29 etag: &str,
30 cache_control: &'static str,
31) -> axum::response::Response {
32 (
33 StatusCode::NOT_MODIFIED,
34 [
35 (header::ETAG, etag.to_owned()),
36 (header::CACHE_CONTROL, cache_control.to_owned()),
37 ],
38 )
39 .into_response()
40}
41
42fn serve_file_response(
43 content: Vec<u8>,
44 content_type: String,
45 cache_control: &'static str,
46 etag: String,
47) -> axum::response::Response {
48 (
49 StatusCode::OK,
50 [
51 (header::CONTENT_TYPE, content_type),
52 (header::CACHE_CONTROL, cache_control.to_owned()),
53 (header::ETAG, etag),
54 ],
55 content,
56 )
57 .into_response()
58}
59
60pub(super) async fn serve_cached_file(
61 file_path: &std::path::Path,
62 headers: &HeaderMap,
63 content_type: &str,
64 cache_control: &'static str,
65) -> axum::response::Response {
66 match tokio::fs::read(file_path).await {
67 Ok(content) => {
68 let etag = compute_etag(&content);
69 if etag_matches(headers, &etag) {
70 return not_modified_response(&etag, cache_control);
71 }
72 serve_file_response(content, content_type.to_owned(), cache_control, etag)
73 },
74 Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Error reading file").into_response(),
75 }
76}
77
78pub(super) fn resolve_mime_type(path: &std::path::Path) -> &'static str {
79 match path.extension().and_then(|ext| ext.to_str()) {
80 Some("js") => "application/javascript",
81 Some("css") => "text/css",
82 Some("woff" | "woff2") => "font/woff2",
83 Some("ttf") => "font/ttf",
84 Some("png") => "image/png",
85 Some("jpg" | "jpeg") => "image/jpeg",
86 Some("svg") => "image/svg+xml",
87 Some("ico") => "image/x-icon",
88 Some("json") => "application/json",
89 Some("pdf") => "application/pdf",
90 Some("mp4") => "video/mp4",
91 Some("webm") => "video/webm",
92 _ => "application/octet-stream",
93 }
94}