11import base64
22from contextlib import closing
3+ import gzip
34from http .server import BaseHTTPRequestHandler
45import os
56import socket
@@ -93,32 +94,39 @@ def redirect_request(self, req, fp, code, msg, headers, newurl):
9394 return new_request
9495
9596
96- def _bake_output (registry , accept_header , params ):
97+ def _bake_output (registry , accept_header , accept_encoding_header , params , disable_compression ):
9798 """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 )
99101 if 'name[]' in params :
100102 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
103110
104111
105- def make_wsgi_app (registry : CollectorRegistry = REGISTRY ) -> Callable :
112+ def make_wsgi_app (registry : CollectorRegistry = REGISTRY , disable_compression : bool = False ) -> Callable :
106113 """Create a WSGI app which serves the metrics from a registry."""
107114
108115 def prometheus_app (environ , start_response ):
109116 # Prepare parameters
110117 accept_header = environ .get ('HTTP_ACCEPT' )
118+ accept_encoding_header = environ .get ('HTTP_ACCEPT_ENCODING' )
111119 params = parse_qs (environ .get ('QUERY_STRING' , '' ))
112120 if environ ['PATH_INFO' ] == '/favicon.ico' :
113121 # Serve empty response for browsers
114122 status = '200 OK'
115- header = ('' , '' )
123+ headers = [ ('' , '' )]
116124 output = b''
117125 else :
118126 # 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 )
120128 # Return output
121- start_response (status , [ header ] )
129+ start_response (status , headers )
122130 return [output ]
123131
124132 return prometheus_app
@@ -152,8 +160,10 @@ def _get_best_family(address, port):
152160
153161def start_wsgi_server (port : int , addr : str = '0.0.0.0' , registry : CollectorRegistry = REGISTRY ) -> None :
154162 """Starts a WSGI server for prometheus metrics as a daemon thread."""
163+
155164 class TmpServer (ThreadingWSGIServer ):
156165 """Copy of ThreadingWSGIServer to update address_family locally"""
166+
157167 TmpServer .address_family , addr = _get_best_family (addr , port )
158168 app = make_wsgi_app (registry )
159169 httpd = make_server (addr , port , app , TmpServer , handler_class = _SilentHandler )
@@ -227,7 +237,7 @@ def sample_line(line):
227237 return '' .join (output ).encode ('utf-8' )
228238
229239
230- def choose_encoder (accept_header : str ) -> Tuple [Callable [[CollectorRegistry ], bytes ], str ]:
240+ def choose_formatter (accept_header : str ) -> Tuple [Callable [[CollectorRegistry ], bytes ], str ]:
231241 accept_header = accept_header or ''
232242 for accepted in accept_header .split (',' ):
233243 if accepted .split (';' )[0 ].strip () == 'application/openmetrics-text' :
@@ -236,6 +246,14 @@ def choose_encoder(accept_header: str) -> Tuple[Callable[[CollectorRegistry], by
236246 return generate_latest , CONTENT_TYPE_LATEST
237247
238248
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+
239257class MetricsHandler (BaseHTTPRequestHandler ):
240258 """HTTP handler that gives metrics from ``REGISTRY``."""
241259 registry : CollectorRegistry = REGISTRY
@@ -244,12 +262,14 @@ def do_GET(self) -> None:
244262 # Prepare parameters
245263 registry = self .registry
246264 accept_header = self .headers .get ('Accept' )
265+ accept_encoding_header = self .headers .get ('Accept-Encoding' )
247266 params = parse_qs (urlparse (self .path ).query )
248267 # 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 )
250269 # Return output
251270 self .send_response (int (status .split (' ' )[0 ]))
252- self .send_header (* header )
271+ for header in headers :
272+ self .send_header (* header )
253273 self .end_headers ()
254274 self .wfile .write (output )
255275
@@ -289,14 +309,13 @@ def write_to_textfile(path: str, registry: CollectorRegistry) -> None:
289309
290310
291311def _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 ,
298318) -> Callable [[], None ]:
299-
300319 def handle () -> None :
301320 request = Request (url , data = data )
302321 request .get_method = lambda : method # type: ignore
@@ -310,11 +329,11 @@ def handle() -> None:
310329
311330
312331def 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 ,
318337) -> Callable [[], None ]:
319338 """Default handler that implements HTTP/HTTPS connections.
320339
@@ -324,11 +343,11 @@ def default_handler(
324343
325344
326345def 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 ,
332351) -> Callable [[], None ]:
333352 """
334353 Handler that automatically trusts redirect responses for all HTTP methods.
@@ -344,13 +363,13 @@ def passthrough_redirect_handler(
344363
345364
346365def 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 ,
354373) -> Callable [[], None ]:
355374 """Handler that implements HTTP/HTTPS connections with Basic Auth.
356375
@@ -371,12 +390,12 @@ def handle():
371390
372391
373392def 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 ,
380399) -> None :
381400 """Push metrics to the given pushgateway.
382401
@@ -420,12 +439,12 @@ def push_to_gateway(
420439
421440
422441def 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 ,
429448) -> None :
430449 """PushAdd metrics to the given pushgateway.
431450
@@ -451,11 +470,11 @@ def pushadd_to_gateway(
451470
452471
453472def 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 ,
459478) -> None :
460479 """Delete metrics from the given pushgateway.
461480
@@ -480,13 +499,13 @@ def delete_from_gateway(
480499
481500
482501def _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 ,
490509) -> None :
491510 gateway_url = urlparse (gateway )
492511 # See https://bugs.python.org/issue27657 for details on urlparse in py>=3.7.6.
0 commit comments