19
19
20
20
from ...common .constants import DEFAULT_STATIC_SERVER_DIR , PROXY_AGENT_HEADER_VALUE
21
21
from ...common .constants import DEFAULT_ENABLE_STATIC_SERVER , DEFAULT_ENABLE_WEB_SERVER
22
- from ...common .constants import DEFAULT_MIN_COMPRESSION_LIMIT
22
+ from ...common .constants import DEFAULT_MIN_COMPRESSION_LIMIT , DEFAULT_WEB_ACCESS_LOG_FORMAT
23
23
from ...common .utils import bytes_ , text_ , build_http_response , build_websocket_handshake_response
24
24
from ...common .types import Readables , Writables
25
25
from ...common .flag import flags
@@ -79,6 +79,7 @@ class HttpWebServerPlugin(HttpProtocolHandlerPlugin):
79
79
reason = b'NOT FOUND' ,
80
80
headers = {
81
81
b'Server' : PROXY_AGENT_HEADER_VALUE ,
82
+ b'Content-Length' : b'0' ,
82
83
b'Connection' : b'close' ,
83
84
},
84
85
),
@@ -90,6 +91,7 @@ class HttpWebServerPlugin(HttpProtocolHandlerPlugin):
90
91
reason = b'NOT IMPLEMENTED' ,
91
92
headers = {
92
93
b'Server' : PROXY_AGENT_HEADER_VALUE ,
94
+ b'Content-Length' : b'0' ,
93
95
b'Connection' : b'close' ,
94
96
},
95
97
),
@@ -129,29 +131,32 @@ def encryption_enabled(self) -> bool:
129
131
130
132
@staticmethod
131
133
def read_and_build_static_file_response (path : str , min_compression_limit : int ) -> memoryview :
132
- with open (path , 'rb' ) as f :
133
- content = f .read ()
134
- content_type = mimetypes .guess_type (path )[0 ]
135
- if content_type is None :
136
- content_type = 'text/plain'
137
- headers = {
138
- b'Content-Type' : bytes_ (content_type ),
139
- b'Cache-Control' : b'max-age=86400' ,
140
- b'Connection' : b'close' ,
141
- }
142
- do_compress = len (content ) > min_compression_limit
143
- if do_compress :
144
- headers .update ({
145
- b'Content-Encoding' : b'gzip' ,
146
- })
147
- return memoryview (
148
- build_http_response (
149
- httpStatusCodes .OK ,
150
- reason = b'OK' ,
151
- headers = headers ,
152
- body = gzip .compress (content ) if do_compress else content ,
153
- ),
154
- )
134
+ try :
135
+ with open (path , 'rb' ) as f :
136
+ content = f .read ()
137
+ content_type = mimetypes .guess_type (path )[0 ]
138
+ if content_type is None :
139
+ content_type = 'text/plain'
140
+ headers = {
141
+ b'Content-Type' : bytes_ (content_type ),
142
+ b'Cache-Control' : b'max-age=86400' ,
143
+ b'Connection' : b'close' ,
144
+ }
145
+ do_compress = len (content ) > min_compression_limit
146
+ if do_compress :
147
+ headers .update ({
148
+ b'Content-Encoding' : b'gzip' ,
149
+ })
150
+ return memoryview (
151
+ build_http_response (
152
+ httpStatusCodes .OK ,
153
+ reason = b'OK' ,
154
+ headers = headers ,
155
+ body = gzip .compress (content ) if do_compress else content ,
156
+ ),
157
+ )
158
+ except FileNotFoundError :
159
+ return HttpWebServerPlugin .DEFAULT_404_RESPONSE
155
160
156
161
def try_upgrade (self ) -> bool :
157
162
if self .request .has_header (b'connection' ) and \
@@ -215,16 +220,13 @@ def on_request_complete(self) -> Union[socket.socket, bool]:
215
220
# No-route found, try static serving if enabled
216
221
if self .flags .enable_static_server :
217
222
path = text_ (path ).split ('?' )[0 ]
218
- try :
219
- self .client .queue (
220
- self .read_and_build_static_file_response (
221
- self .flags .static_server_dir + path ,
222
- self .flags .min_compression_limit ,
223
- ),
224
- )
225
- return True
226
- except FileNotFoundError :
227
- pass
223
+ self .client .queue (
224
+ self .read_and_build_static_file_response (
225
+ self .flags .static_server_dir + path ,
226
+ self .flags .min_compression_limit ,
227
+ ),
228
+ )
229
+ return True
228
230
229
231
# Catch all unhandled web server requests, return 404
230
232
self .client .queue (self .DEFAULT_404_RESPONSE )
@@ -301,19 +303,26 @@ def on_response_chunk(self, chunk: List[memoryview]) -> List[memoryview]:
301
303
def on_client_connection_close (self ) -> None :
302
304
if self .request .has_host ():
303
305
return
306
+ context = {
307
+ 'client_addr' : self .client .address ,
308
+ 'request_method' : text_ (self .request .method ),
309
+ 'request_path' : text_ (self .request .path ),
310
+ 'connection_time_ms' : '%.2f' % ((time .time () - self .start_time ) * 1000 ),
311
+ }
312
+ log_handled = False
304
313
if self .route :
314
+ # May be merge on_client_connection_close and on_access_log???
315
+ # probably by simply deprecating on_client_connection_close in future.
305
316
self .route .on_client_connection_close ()
306
- self .access_log ()
317
+ ctx = self .route .on_access_log (context )
318
+ if ctx is None :
319
+ log_handled = True
320
+ else :
321
+ context = ctx
322
+ if not log_handled :
323
+ self .access_log (context )
307
324
308
325
# TODO: Allow plugins to customize access_log, similar
309
326
# to how proxy server plugins are able to do it.
310
- def access_log (self ) -> None :
311
- logger .info (
312
- '%s - %s %s - %.2f ms' %
313
- (
314
- self .client .address ,
315
- text_ (self .request .method ),
316
- text_ (self .request .path ),
317
- (time .time () - self .start_time ) * 1000 ,
318
- ),
319
- )
327
+ def access_log (self , context : Dict [str , Any ]) -> None :
328
+ logger .info (DEFAULT_WEB_ACCESS_LOG_FORMAT .format_map (context ))
0 commit comments