ars_server/
embedded_asset.rs

1use std::sync::Arc;
2use ars_package::Asset;
3use axum::{http::HeaderValue, http::StatusCode, http::header, response::IntoResponse};
4use bytes::Bytes;
5
6use std::io::Write;
7use flate2::write::GzEncoder;
8use flate2::Compression;
9
10fn gzip_encode_raw(base_raw: &[u8]) -> Vec<u8> {
11    let mut e = GzEncoder::new(Vec::new(), Compression::default());
12    e.write_all(base_raw).unwrap();
13    let compressed_bytes = e.finish();
14    compressed_bytes.unwrap()
15}
16
17#[derive(Clone)]
18pub struct EmbeddedAsset {
19    body: Bytes,
20    mime: Arc<str>,
21    status: u16,
22    cache: bool,
23}
24
25impl EmbeddedAsset {
26    pub fn new(asset: &Asset, cache: bool, target_url: &str, public_url: &str) -> anyhow::Result<Self> {
27        let is_empty = target_url == "/";
28        Ok(match asset.mime.as_ref() {
29            "application/javascript" | "text/css" | "text/html; charset=utf-8" => {
30                let body = if !is_empty {
31                    let next_asset = std::str::from_utf8(asset.bytes.as_ref())?
32                        .replace(target_url, public_url);
33                        Bytes::from(gzip_encode_raw(next_asset.as_bytes()))
34                } else {
35                    Bytes::from(gzip_encode_raw(&asset.bytes))
36                };
37                Self {
38                    body,
39                    mime: asset.mime.clone(),
40                    status: 200,
41                    cache,
42                }
43            }
44            _ => {
45                let body = Bytes::from(gzip_encode_raw(asset.bytes.as_ref()));
46                Self {
47                    body,
48                    mime: asset.mime.clone(),
49                    status: 200,
50                    cache,
51                }
52            }
53        })
54    }
55
56    pub fn json<T>(body: &T) -> anyhow::Result<Self> where T: serde::ser::Serialize {
57        let t = serde_json::to_vec(body)?;
58        let body = Bytes::from(gzip_encode_raw(&t));
59        Ok(Self {
60            status: 200,
61            body,
62            mime: Arc::from("application/json; charset=utf-8"),
63            cache: true,
64        })
65    }
66
67    pub fn not_found(body: Bytes, mime: Arc<str>) -> Self {
68        Self {
69            status: 404,
70            body,
71            mime,
72            cache: false,
73        }
74    }
75}
76
77impl IntoResponse for EmbeddedAsset {
78    fn into_response(self) -> axum::response::Response {
79        let mut response = self.body.into_response();
80        *response.status_mut() = StatusCode::from_u16(self.status).unwrap();
81        if self.status == 200 {
82            response.headers_mut().insert(
83                header::CONTENT_ENCODING,
84                HeaderValue::from_static("gzip"),
85            );
86        }
87        if self.cache {
88            response.headers_mut().insert(
89                header::CACHE_CONTROL,
90                HeaderValue::from_static("public, max-age=604800"),
91            );
92        } else {
93            response.headers_mut().insert(
94                header::CACHE_CONTROL,
95                HeaderValue::from_static(
96                    "no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate",
97                ),
98            );
99        }
100        response.headers_mut().insert(
101            header::CONTENT_TYPE,
102            HeaderValue::from_bytes(self.mime.as_bytes()).unwrap(),
103        );
104        response
105    }
106}