aboutsummaryrefslogtreecommitdiffstats
path: root/web/demo/serve
blob: 07df7c3078d04c58f0daee28e0a03f312aa45289 (plain) (blame)
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
#!/usr/bin/env python3
"""Serve the built demo out of web/demo/build/site.

    web/demo/serve                     http://localhost:8000/
    web/demo/serve --port 9000
    web/demo/serve --dir build/site

Not `python3 -m http.server`, for two reasons that both look like the emulator
being broken. Older mimetypes databases do not know .wasm, so the module
arrives as application/octet-stream and instantiation refuses it; and the
default handler caches, so a rebuilt root filesystem serves the old blocks
until someone thinks to hard-refresh.
"""

import argparse
import functools
import http.server
import os
import sys

TYPES = {
    ".wasm": "application/wasm",
    ".bin": "application/octet-stream",  # bios, kernel, rootfs blocks
    ".cfg": "text/plain; charset=utf-8",  # the VM config, read by the emulator
    ".txt": "text/plain; charset=utf-8",  # the rootfs block index
    ".html": "text/html; charset=utf-8",
    ".js": "text/javascript; charset=utf-8",
    ".css": "text/css; charset=utf-8",
}


class Handler(http.server.SimpleHTTPRequestHandler):
    def guess_type(self, path):
        return TYPES.get(os.path.splitext(path)[1].lower()) or super().guess_type(path)

    def send_head(self):
        """Serve <path>.gz with Content-Encoding: gzip when the client allows.

        The build writes a .gz twin beside every block and every big asset;
        XHR and the browser decode transparently, so the emulator sees the
        same bytes off a wire carrying about half as many. This is what any
        competent static host does on its own -- this server is just not one.
        """
        path = self.translate_path(self.path)
        gz = path + ".gz"
        if (
            not path.endswith(".gz")
            and os.path.isfile(gz)
            and "gzip" in self.headers.get("Accept-Encoding", "")
        ):
            try:
                f = open(gz, "rb")
            except OSError:
                return super().send_head()
            self.send_response(200)
            self.send_header("Content-Type", self.guess_type(path))
            self.send_header("Content-Encoding", "gzip")
            self.send_header("Content-Length", str(os.fstat(f.fileno()).st_size))
            self.end_headers()
            return f
        return super().send_head()

    def end_headers(self):
        self.send_header("Cache-Control", "no-store")
        super().end_headers()

    def log_message(self, fmt, *args):
        sys.stderr.write("  %s\n" % (fmt % args))


def main():
    here = os.path.dirname(os.path.abspath(__file__))
    ap = argparse.ArgumentParser(description="serve the slosh web demo")
    ap.add_argument("--port", type=int, default=8000)
    ap.add_argument("--dir", default=os.path.join(here, "build", "site"))
    args = ap.parse_args()

    if not os.path.isdir(args.dir):
        sys.exit("%s does not exist -- run `make` in %s first" % (args.dir, here))

    http.server.ThreadingHTTPServer.allow_reuse_address = True
    handler = functools.partial(Handler, directory=args.dir)
    with http.server.ThreadingHTTPServer(("", args.port), handler) as httpd:
        print("serving %s at http://localhost:%d/" % (args.dir, args.port))
        try:
            httpd.serve_forever()
        except KeyboardInterrupt:
            print()


if __name__ == "__main__":
    main()