Skip to main content

systemprompt_api/services/static_content/static_files/
cache.rs

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