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
#![deny(warnings, missing_docs)]
//! This crate provides utilities for serving 'cargo-web' build output
//! (rust compiled as wasm and associated html/css/etc files) included in
//! native binaries as HTTP responses
//!
//! Designed for use with the [`embed-wasm-build` crate](https://crates.io/crates/embed-wasm-build).
//! See [embed-wasm-example](https://github.com/inanna-malick/embed-wasm-example) for a full example.

use headers::HeaderMapExt;
use hyper::{Body, Response};

/// Enum controlling how requests with path '/' are handled
#[non_exhaustive]
#[derive(PartialEq, Eq)]
pub enum IndexHandling {
    /// map requests to '/' to 'index.html'
    MapEmptyPathToIndex,
    /// apply no special logic to requests to '/'
    NoIndexHandling,
}

/// Wraps a hashmap of static content generated at compile time and provides convenience
/// functions for resolving static content as 'hyper::Response' when given a path.
pub struct StaticLookup {
    /// Enum controlling how requests with path '/' are handled (only public for macro use)
    #[doc(hidden)]
    pub index_mode: IndexHandling,
    /// Reference to the static map containing binary content (only public for macro use)
    #[doc(hidden)]
    pub wasm: &'static phf::Map<&'static str, &'static [u8]>,
}

impl StaticLookup {
    /// Given a path ('/', '/css/tree.css', etc) attempt to construct a 'hyper::Response'
    /// using the static hashmap generated at compile time. Infers MIME type from path.
    ///
    /// path expected as `let x: http::uri::PathAndQuery = ..; x.as_str()`, omiting type to simplify interface
    pub fn get(&self, path: &str) -> Option<Response<Body>> {
        // drop leading '/' from path
        let path = &path[1..];

        let path = if path.len() == 0 && self.index_mode == IndexHandling::MapEmptyPathToIndex {
            "index.html"
        } else {
            path
        };

        match self.wasm.get(path).map(|p| *p) {
            None => None,
            Some(blob) => {
                let body = hyper::Body::from(blob);
                let mut resp = hyper::Response::new(body);

                let mime_type = mime_guess::from_path(path).first_or_octet_stream();
                resp.headers_mut()
                    .typed_insert(headers::ContentType::from(mime_type));
                resp.headers_mut()
                    .typed_insert(headers::AcceptRanges::bytes());
                resp.headers_mut()
                    .typed_insert(headers::ContentLength(blob.len() as u64));

                Some(resp)
            }
        }
    }
}

/// Imports the generated static wasm blobs from the build output directory as
/// generated by 'embed_wasm_build::compile_wasm'
#[macro_export]
macro_rules! include_wasm {
    () => {
        include!(concat!(env!("OUT_DIR"), "/wasm_blobs.rs"));

        // FIXME: will fail if user aliases embed_wasm
        pub static STATIC_LOOKUP: ::embed_wasm::StaticLookup = ::embed_wasm::StaticLookup {
            index_mode: ::embed_wasm::IndexHandling::MapEmptyPathToIndex,
            wasm: &WASM,
        };
    };
}