1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use axum::{
    http::{
        header::{CONTENT_ENCODING, CONTENT_TYPE, ETAG, IF_NONE_MATCH},
        HeaderMap, HeaderName, HeaderValue, StatusCode,
    },
    response::IntoResponse,
};

use crate::{
    util::{content_length, supports_encoding},
    ServeOptions,
};

const BROTLI_ENCODING: &str = "br";
#[allow(clippy::declare_interior_mutable_const)]
const BROTLI_HEADER: (HeaderName, HeaderValue) =
    (CONTENT_ENCODING, HeaderValue::from_static(BROTLI_ENCODING));
const GZIP_ENCODING: &str = "gzip";
#[allow(clippy::declare_interior_mutable_const)]
const GZIP_HEADER: (HeaderName, HeaderValue) =
    (CONTENT_ENCODING, HeaderValue::from_static(GZIP_ENCODING));

#[derive(Debug)]
pub struct Asset {
    pub route: &'static str,
    pub etag: &'static str,
    pub content_type: &'static str,
    pub bytes: &'static [u8],
    pub brotli_bytes: &'static [u8],
}

impl Asset {
    pub(super) fn handler(
        &self,
        headers: &HeaderMap,
        status: StatusCode,
        bytes: &'static [u8],
        gzip_bytes: &'static [u8],
        options: &ServeOptions,
    ) -> impl IntoResponse {
        let content_type = HeaderValue::from_static(self.content_type);
        let etag = HeaderValue::from_static(self.etag);
        let cache_control = match self.content_type {
            "text/html" => options.html_cache_control.as_header(),
            _ => options.cache_control.as_header(),
        };

        if let Some(if_none_match) = headers.get(IF_NONE_MATCH) {
            if if_none_match == self.etag {
                return (
                    StatusCode::NOT_MODIFIED,
                    [(CONTENT_TYPE, content_type), cache_control, (ETAG, etag)],
                )
                    .into_response();
            }
        }

        if options.enable_brotli
            && !self.brotli_bytes.is_empty()
            && supports_encoding(headers, BROTLI_ENCODING)
        {
            (
                status,
                [
                    content_length(self.brotli_bytes.len()),
                    BROTLI_HEADER,
                    (CONTENT_TYPE, content_type),
                    cache_control,
                    (ETAG, etag),
                ],
                self.brotli_bytes,
            )
                .into_response()
        } else if options.enable_gzip
            && !gzip_bytes.is_empty()
            && supports_encoding(headers, GZIP_ENCODING)
        {
            (
                status,
                [
                    content_length(gzip_bytes.len()),
                    GZIP_HEADER,
                    (CONTENT_TYPE, content_type),
                    cache_control,
                    (ETAG, etag),
                ],
                gzip_bytes,
            )
                .into_response()
        } else {
            (
                status,
                [
                    content_length(bytes.len()),
                    (CONTENT_TYPE, content_type),
                    cache_control,
                    (ETAG, etag),
                ],
                bytes,
            )
                .into_response()
        }
    }
}