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
//! Macros for creating static HTTP responders of varying media types from the
//! `frontend` directory. Used to serve the testing frontend, which is all
//! static files.

/// Http responder that returns the file at $path (relative to the "frontend"
/// directory) as the body, using $content_type as the Content-Type header. Also
/// serves ETags and responds appropriately, using hashes of the file contents,
/// which are evaluated once on first access. The files are included using
/// [`include_bytes`], so they're static throughout the program's lifetime, and
/// built into the server executable.
macro_rules! response {
    ($path:literal, $content_type:expr) => {{
        use actix_web::http::header::{ContentType, HeaderValue};
        use actix_web::{HttpRequest, HttpResponse};
        use once_cell::sync::Lazy;
        use ring::digest;

        |req: HttpRequest| async move {
            static BYTES: &[u8] = include_bytes!(concat!("../frontend/", $path));
            static ETAG: Lazy<String> = Lazy::new(|| {
                let digest = digest::digest(&digest::SHA256, BYTES);
                let mut digest = data_encoding::BASE64URL.encode(digest.as_ref());
                digest.reserve(2);
                digest.insert(0, '"');
                digest.push('"');
                digest
            });
            let header = req.headers().get("If-None-Match");
            if header.map(HeaderValue::as_bytes) == Some(ETAG.as_bytes()) {
                HttpResponse::NotModified().finish()
            } else {
                HttpResponse::Ok()
                    .content_type($content_type)
                    .append_header(("ETag", ETAG.as_str()))
                    .body(BYTES)
            }
        }
    }};
}

/// Wrapper for [`response`] using `"text/html; charset=utf-8"` as the Content-Type.
macro_rules! html {
    ($path:literal) => {
        crate::frontend::response!($path, ContentType::html())
    };
}

/// Wrapper for [`response`] using `"image/png"` as the Content-Type.
macro_rules! png {
    ($path:literal) => {
        crate::frontend::response!($path, ContentType::png())
    };
}

pub(crate) use {html, png, response};