1
1
import base64
2
2
from contextlib import closing
3
+ import gzip
3
4
from http .server import BaseHTTPRequestHandler
4
5
import os
5
6
import socket
@@ -93,32 +94,39 @@ def redirect_request(self, req, fp, code, msg, headers, newurl):
93
94
return new_request
94
95
95
96
96
- def _bake_output (registry , accept_header , params ):
97
+ def _bake_output (registry , accept_header , accept_encoding_header , params , disable_compression ):
97
98
"""Bake output for metrics output."""
98
- encoder , content_type = choose_encoder (accept_header )
99
+ # Choose the correct plain text format of the output.
100
+ formatter , content_type = choose_formatter (accept_header )
99
101
if 'name[]' in params :
100
102
registry = registry .restricted_registry (params ['name[]' ])
101
- output = encoder (registry )
102
- return '200 OK' , ('Content-Type' , content_type ), output
103
+ output = formatter (registry )
104
+ headers = [('Content-Type' , content_type )]
105
+ # If gzip encoding required, gzip the output.
106
+ if not disable_compression and gzip_accepted (accept_encoding_header ):
107
+ output = gzip .compress (output )
108
+ headers .append (('Content-Encoding' , 'gzip' ))
109
+ return '200 OK' , headers , output
103
110
104
111
105
- def make_wsgi_app (registry : CollectorRegistry = REGISTRY ) -> Callable :
112
+ def make_wsgi_app (registry : CollectorRegistry = REGISTRY , disable_compression : bool = False ) -> Callable :
106
113
"""Create a WSGI app which serves the metrics from a registry."""
107
114
108
115
def prometheus_app (environ , start_response ):
109
116
# Prepare parameters
110
117
accept_header = environ .get ('HTTP_ACCEPT' )
118
+ accept_encoding_header = environ .get ('HTTP_ACCEPT_ENCODING' )
111
119
params = parse_qs (environ .get ('QUERY_STRING' , '' ))
112
120
if environ ['PATH_INFO' ] == '/favicon.ico' :
113
121
# Serve empty response for browsers
114
122
status = '200 OK'
115
- header = ('' , '' )
123
+ headers = [ ('' , '' )]
116
124
output = b''
117
125
else :
118
126
# Bake output
119
- status , header , output = _bake_output (registry , accept_header , params )
127
+ status , headers , output = _bake_output (registry , accept_header , accept_encoding_header , params , disable_compression )
120
128
# Return output
121
- start_response (status , [ header ] )
129
+ start_response (status , headers )
122
130
return [output ]
123
131
124
132
return prometheus_app
@@ -152,8 +160,10 @@ def _get_best_family(address, port):
152
160
153
161
def start_wsgi_server (port : int , addr : str = '0.0.0.0' , registry : CollectorRegistry = REGISTRY ) -> None :
154
162
"""Starts a WSGI server for prometheus metrics as a daemon thread."""
163
+
155
164
class TmpServer (ThreadingWSGIServer ):
156
165
"""Copy of ThreadingWSGIServer to update address_family locally"""
166
+
157
167
TmpServer .address_family , addr = _get_best_family (addr , port )
158
168
app = make_wsgi_app (registry )
159
169
httpd = make_server (addr , port , app , TmpServer , handler_class = _SilentHandler )
@@ -227,7 +237,7 @@ def sample_line(line):
227
237
return '' .join (output ).encode ('utf-8' )
228
238
229
239
230
- def choose_encoder (accept_header : str ) -> Tuple [Callable [[CollectorRegistry ], bytes ], str ]:
240
+ def choose_formatter (accept_header : str ) -> Tuple [Callable [[CollectorRegistry ], bytes ], str ]:
231
241
accept_header = accept_header or ''
232
242
for accepted in accept_header .split (',' ):
233
243
if accepted .split (';' )[0 ].strip () == 'application/openmetrics-text' :
@@ -236,6 +246,14 @@ def choose_encoder(accept_header: str) -> Tuple[Callable[[CollectorRegistry], by
236
246
return generate_latest , CONTENT_TYPE_LATEST
237
247
238
248
249
+ def gzip_accepted (accept_encoding_header : str ) -> bool :
250
+ accept_encoding_header = accept_encoding_header or ''
251
+ for accepted in accept_encoding_header .split (',' ):
252
+ if accepted .split (';' )[0 ].strip ().lower () == 'gzip' :
253
+ return True
254
+ return False
255
+
256
+
239
257
class MetricsHandler (BaseHTTPRequestHandler ):
240
258
"""HTTP handler that gives metrics from ``REGISTRY``."""
241
259
registry : CollectorRegistry = REGISTRY
@@ -244,12 +262,14 @@ def do_GET(self) -> None:
244
262
# Prepare parameters
245
263
registry = self .registry
246
264
accept_header = self .headers .get ('Accept' )
265
+ accept_encoding_header = self .headers .get ('Accept-Encoding' )
247
266
params = parse_qs (urlparse (self .path ).query )
248
267
# Bake output
249
- status , header , output = _bake_output (registry , accept_header , params )
268
+ status , headers , output = _bake_output (registry , accept_header , accept_encoding_header , params , False )
250
269
# Return output
251
270
self .send_response (int (status .split (' ' )[0 ]))
252
- self .send_header (* header )
271
+ for header in headers :
272
+ self .send_header (* header )
253
273
self .end_headers ()
254
274
self .wfile .write (output )
255
275
@@ -289,14 +309,13 @@ def write_to_textfile(path: str, registry: CollectorRegistry) -> None:
289
309
290
310
291
311
def _make_handler (
292
- url : str ,
293
- method : str ,
294
- timeout : Optional [float ],
295
- headers : Sequence [Tuple [str , str ]],
296
- data : bytes ,
297
- base_handler : type ,
312
+ url : str ,
313
+ method : str ,
314
+ timeout : Optional [float ],
315
+ headers : Sequence [Tuple [str , str ]],
316
+ data : bytes ,
317
+ base_handler : type ,
298
318
) -> Callable [[], None ]:
299
-
300
319
def handle () -> None :
301
320
request = Request (url , data = data )
302
321
request .get_method = lambda : method # type: ignore
@@ -310,11 +329,11 @@ def handle() -> None:
310
329
311
330
312
331
def default_handler (
313
- url : str ,
314
- method : str ,
315
- timeout : Optional [float ],
316
- headers : List [Tuple [str , str ]],
317
- data : bytes ,
332
+ url : str ,
333
+ method : str ,
334
+ timeout : Optional [float ],
335
+ headers : List [Tuple [str , str ]],
336
+ data : bytes ,
318
337
) -> Callable [[], None ]:
319
338
"""Default handler that implements HTTP/HTTPS connections.
320
339
@@ -324,11 +343,11 @@ def default_handler(
324
343
325
344
326
345
def passthrough_redirect_handler (
327
- url : str ,
328
- method : str ,
329
- timeout : Optional [float ],
330
- headers : List [Tuple [str , str ]],
331
- data : bytes ,
346
+ url : str ,
347
+ method : str ,
348
+ timeout : Optional [float ],
349
+ headers : List [Tuple [str , str ]],
350
+ data : bytes ,
332
351
) -> Callable [[], None ]:
333
352
"""
334
353
Handler that automatically trusts redirect responses for all HTTP methods.
@@ -344,13 +363,13 @@ def passthrough_redirect_handler(
344
363
345
364
346
365
def basic_auth_handler (
347
- url : str ,
348
- method : str ,
349
- timeout : Optional [float ],
350
- headers : List [Tuple [str , str ]],
351
- data : bytes ,
352
- username : str = None ,
353
- password : str = None ,
366
+ url : str ,
367
+ method : str ,
368
+ timeout : Optional [float ],
369
+ headers : List [Tuple [str , str ]],
370
+ data : bytes ,
371
+ username : str = None ,
372
+ password : str = None ,
354
373
) -> Callable [[], None ]:
355
374
"""Handler that implements HTTP/HTTPS connections with Basic Auth.
356
375
@@ -371,12 +390,12 @@ def handle():
371
390
372
391
373
392
def push_to_gateway (
374
- gateway : str ,
375
- job : str ,
376
- registry : CollectorRegistry ,
377
- grouping_key : Optional [Dict [str , Any ]] = None ,
378
- timeout : Optional [float ] = 30 ,
379
- handler : Callable = default_handler ,
393
+ gateway : str ,
394
+ job : str ,
395
+ registry : CollectorRegistry ,
396
+ grouping_key : Optional [Dict [str , Any ]] = None ,
397
+ timeout : Optional [float ] = 30 ,
398
+ handler : Callable = default_handler ,
380
399
) -> None :
381
400
"""Push metrics to the given pushgateway.
382
401
@@ -420,12 +439,12 @@ def push_to_gateway(
420
439
421
440
422
441
def pushadd_to_gateway (
423
- gateway : str ,
424
- job : str ,
425
- registry : Optional [CollectorRegistry ],
426
- grouping_key : Optional [Dict [str , Any ]] = None ,
427
- timeout : Optional [float ] = 30 ,
428
- handler : Callable = default_handler ,
442
+ gateway : str ,
443
+ job : str ,
444
+ registry : Optional [CollectorRegistry ],
445
+ grouping_key : Optional [Dict [str , Any ]] = None ,
446
+ timeout : Optional [float ] = 30 ,
447
+ handler : Callable = default_handler ,
429
448
) -> None :
430
449
"""PushAdd metrics to the given pushgateway.
431
450
@@ -451,11 +470,11 @@ def pushadd_to_gateway(
451
470
452
471
453
472
def delete_from_gateway (
454
- gateway : str ,
455
- job : str ,
456
- grouping_key : Optional [Dict [str , Any ]] = None ,
457
- timeout : Optional [float ] = 30 ,
458
- handler : Callable = default_handler ,
473
+ gateway : str ,
474
+ job : str ,
475
+ grouping_key : Optional [Dict [str , Any ]] = None ,
476
+ timeout : Optional [float ] = 30 ,
477
+ handler : Callable = default_handler ,
459
478
) -> None :
460
479
"""Delete metrics from the given pushgateway.
461
480
@@ -480,13 +499,13 @@ def delete_from_gateway(
480
499
481
500
482
501
def _use_gateway (
483
- method : str ,
484
- gateway : str ,
485
- job : str ,
486
- registry : Optional [CollectorRegistry ],
487
- grouping_key : Optional [Dict [str , Any ]],
488
- timeout : Optional [float ],
489
- handler : Callable ,
502
+ method : str ,
503
+ gateway : str ,
504
+ job : str ,
505
+ registry : Optional [CollectorRegistry ],
506
+ grouping_key : Optional [Dict [str , Any ]],
507
+ timeout : Optional [float ],
508
+ handler : Callable ,
490
509
) -> None :
491
510
gateway_url = urlparse (gateway )
492
511
# See https://bugs.python.org/issue27657 for details on urlparse in py>=3.7.6.
0 commit comments