-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.py
More file actions
125 lines (102 loc) · 4.18 KB
/
Copy pathserver.py
File metadata and controls
125 lines (102 loc) · 4.18 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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import gzip
import os
import socketserver
from http import HTTPStatus
import http.server
# Paths that compress well via gzip (stay aligned with handlers below).
_GZIP_EXTENSIONS = ('.html', '.css', '.js', '.txt', '.json')
class GzipHandler(http.server.SimpleHTTPRequestHandler):
"""
SimpleHTTPRequestHandler with gzip for text-heavy static files only.
The previous implementation compressed inside end_headers() after stock send_head()
had already emitted Content-Length, producing illegal duplicate headers while
stock do_GET still streamed the uncompressed file. Compression is isolated to
do_GET/do_HEAD with a single header set + one write.
"""
def send_response_only(self, code, message=None):
super().send_response_only(code, message)
tail = self._url_path_without_query()
if tail.endswith(
('.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg')
):
self.send_header('Cache-Control', 'public, max-age=31536000')
elif tail.endswith(('.html', '.json')):
self.send_header('Cache-Control', 'public, max-age=0, must-revalidate')
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('X-Content-Type-Options', 'nosniff')
self.send_header('X-Frame-Options', 'DENY')
self.send_header('X-XSS-Protection', '1; mode=block')
@staticmethod
def _accepts_gzip(value):
if not value:
return False
return 'gzip' in value.lower()
def _url_path_without_query(self):
return self.path.split('?', 1)[0].split('#', 1)[0]
def _should_try_gzip(self):
if not self._accepts_gzip(self.headers.get('Accept-Encoding')):
return False
tail = self._url_path_without_query().lower()
return tail.endswith(_GZIP_EXTENSIONS)
def _defer_to_parent_headers(self):
"""Let stock handler manage Range requests and validators."""
if self.headers.get('Range'):
return True
if self.headers.get('If-Modified-Since'):
return True
if self.headers.get('If-None-Match'):
return True
return False
def _emit_gzip(self, fs_path, write_body=True):
with open(fs_path, 'rb') as fh:
raw = fh.read()
encoded = gzip.compress(raw)
st = os.stat(fs_path)
self.send_response(HTTPStatus.OK)
ctype = self.guess_type(fs_path)
if ctype:
self.send_header('Content-Type', ctype)
self.send_header('Last-Modified', self.date_time_string(st.st_mtime))
self.send_header('Content-Encoding', 'gzip')
self.send_header('Content-Length', str(len(encoded)))
self.send_header('Vary', 'Accept-Encoding')
self.end_headers()
if write_body:
try:
self.wfile.write(encoded)
except BrokenPipeError:
pass
except ConnectionResetError:
pass
def do_GET(self):
if self._defer_to_parent_headers():
super().do_GET()
return
if self._should_try_gzip():
fs_path = self.translate_path(self.path)
if os.path.isfile(fs_path):
try:
self._emit_gzip(fs_path, write_body=True)
except OSError:
self.send_error(HTTPStatus.NOT_FOUND.value, 'File not found')
return
super().do_GET()
def do_HEAD(self):
if self._defer_to_parent_headers():
super().do_HEAD()
return
if self._should_try_gzip():
fs_path = self.translate_path(self.path)
if os.path.isfile(fs_path):
try:
self._emit_gzip(fs_path, write_body=False)
except OSError:
self.send_error(HTTPStatus.NOT_FOUND.value, 'File not found')
return
super().do_HEAD()
if __name__ == '__main__':
PORT = 8000
socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(('', PORT), GzipHandler) as httpd:
print(f'Serving at port {PORT} with Gzip compression and caching enabled')
httpd.serve_forever()