WIP
This commit is contained in:
commit
844f86cda0
99 files changed
+53591
No files matched your search
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Static file server for the rendered Bible site.
|
||||
|
||||
Stdlib only, so the container needs nothing but a Python base image. Its one
|
||||
job beyond serving files is getting cache headers right: the service worker
|
||||
owns the offline copy, and if the worker or the pages that register it are
|
||||
served stale, a new build can never reach a device that already installed the
|
||||
old one.
|
||||
|
||||
Expects to sit behind something that terminates TLS. That is not optional for
|
||||
the offline app: browsers only register a service worker in a secure context,
|
||||
so a device loading this over plain http:// from a LAN address installs the
|
||||
page but never gets an offline copy.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
from functools import partial
|
||||
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
# Revalidate every time: these are what carry an update to an installed app.
|
||||
NO_CACHE_NAMES = {"sw.js"}
|
||||
NO_CACHE_EXTS = {".html", ".webmanifest"}
|
||||
|
||||
|
||||
class Handler(SimpleHTTPRequestHandler):
|
||||
extensions_map = {
|
||||
**SimpleHTTPRequestHandler.extensions_map,
|
||||
".html": "text/html; charset=utf-8",
|
||||
".css": "text/css; charset=utf-8",
|
||||
".js": "text/javascript; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8",
|
||||
".webmanifest": "application/manifest+json; charset=utf-8",
|
||||
".png": "image/png",
|
||||
".svg": "image/svg+xml",
|
||||
}
|
||||
server_version = "bible-static"
|
||||
sys_version = ""
|
||||
|
||||
def end_headers(self):
|
||||
path = self.path.split("?", 1)[0]
|
||||
name = os.path.basename(path)
|
||||
ext = os.path.splitext(name)[1].lower()
|
||||
if name in NO_CACHE_NAMES or ext in NO_CACHE_EXTS or path.endswith("/"):
|
||||
self.send_header("Cache-Control", "no-cache")
|
||||
else:
|
||||
self.send_header("Cache-Control", "public, max-age=604800")
|
||||
# Lets the worker claim the whole origin even if it moves off root.
|
||||
self.send_header("Service-Worker-Allowed", "/")
|
||||
self.send_header("X-Content-Type-Options", "nosniff")
|
||||
super().end_headers()
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
sys.stdout.write("%s %s\n" % (self.address_string(), fmt % args))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("--root", default=os.environ.get("SITE_ROOT", "books"),
|
||||
help="directory to serve (default: books)")
|
||||
ap.add_argument("--host", default=os.environ.get("HOST", "0.0.0.0"))
|
||||
ap.add_argument("--port", type=int, default=int(os.environ.get("PORT", "8080")))
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
root = os.path.abspath(args.root)
|
||||
if not os.path.isdir(root):
|
||||
sys.exit("site root not found: %s" % root)
|
||||
|
||||
httpd = ThreadingHTTPServer((args.host, args.port),
|
||||
partial(Handler, directory=root))
|
||||
httpd.daemon_threads = True
|
||||
|
||||
def stop(_signum, _frame):
|
||||
httpd.shutdown()
|
||||
|
||||
signal.signal(signal.SIGTERM, stop)
|
||||
signal.signal(signal.SIGINT, stop)
|
||||
|
||||
print("serving %s at http://%s:%d" % (root, args.host, args.port))
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
finally:
|
||||
httpd.server_close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user