Repository navigation
Expand file tree
/
Copy pathserver.py
More file actions
95 lines (82 loc) · 3.05 KB
/
Copy pathserver.py
File metadata and controls
95 lines (82 loc) · 3.05 KB
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
#!/usr/bin/env python3
"""
calibrat3 development server — static files with HTTP Range support and
no-cache headers (so edits show up on reload).
Usage:
python3 server.py [port] # default 8080
# or simply: python3 -m http.server 8080 (no range requests -> whole-file download)
The app needs Range requests for the sample session videos to stream; the
stdlib SimpleHTTPRequestHandler doesn't implement them, so this adds a
minimal single-range implementation.
"""
import os
import re
import sys
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
RANGE_RE = re.compile(r"bytes=(\d*)-(\d*)")
class Handler(SimpleHTTPRequestHandler):
extensions_map = {
**SimpleHTTPRequestHandler.extensions_map,
".js": "text/javascript",
".mjs": "text/javascript",
".wasm": "application/wasm",
".toml": "text/plain",
".mp4": "video/mp4",
}
def end_headers(self):
self.send_header("Cache-Control", "no-store")
self.send_header("Accept-Ranges", "bytes")
super().end_headers()
def send_head(self):
path = self.translate_path(self.path)
rng = self.headers.get("Range")
if not rng or os.path.isdir(path) or not os.path.exists(path):
return super().send_head()
m = RANGE_RE.match(rng)
if not m:
return super().send_head()
size = os.path.getsize(path)
start = int(m.group(1)) if m.group(1) else 0
end = int(m.group(2)) if m.group(2) else size - 1
end = min(end, size - 1)
if start > end or start >= size:
self.send_response(416)
self.send_header("Content-Range", f"bytes */{size}")
self.end_headers()
return None
f = open(path, "rb")
f.seek(start)
self.send_response(206)
self.send_header("Content-Type", self.guess_type(path))
self.send_header("Content-Range", f"bytes {start}-{end}/{size}")
self.send_header("Content-Length", str(end - start + 1))
self.end_headers()
self._range_len = end - start + 1
return f
def copyfile(self, source, outputfile):
n = getattr(self, "_range_len", None)
if n is None:
return super().copyfile(source, outputfile)
remaining = n
while remaining > 0:
chunk = source.read(min(1 << 16, remaining))
if not chunk:
break
outputfile.write(chunk)
remaining -= len(chunk)
self._range_len = None
def log_message(self, fmt, *args):
if os.environ.get("CALIBRAT3_QUIET"):
return
super().log_message(fmt, *args)
def main():
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
os.chdir(os.path.dirname(os.path.abspath(__file__)))
server = ThreadingHTTPServer(("0.0.0.0", port), Handler)
print(f"calibrat3 dev server on http://localhost:{port}/ (Ctrl-C to stop)")
try:
server.serve_forever()
except KeyboardInterrupt:
server.server_close()
if __name__ == "__main__":
main()