|
17 | 17 |
|
18 | 18 | from BaseHTTPServer import BaseHTTPRequestHandler
|
19 | 19 | from SocketServer import ThreadingMixIn
|
20 |
| - from urllib2 import build_opener, HTTPHandler, Request |
| 20 | + from urllib2 import ( |
| 21 | + build_opener, HTTPError, HTTPHandler, HTTPRedirectHandler, Request, |
| 22 | + ) |
21 | 23 | from urlparse import parse_qs, urlparse
|
22 | 24 | except ImportError:
|
23 | 25 | # Python 3
|
24 | 26 | from http.server import BaseHTTPRequestHandler
|
25 | 27 | from socketserver import ThreadingMixIn
|
| 28 | + from urllib.error import HTTPError |
26 | 29 | from urllib.parse import parse_qs, quote_plus, urlparse
|
27 |
| - from urllib.request import build_opener, HTTPHandler, Request |
| 30 | + from urllib.request import ( |
| 31 | + build_opener, HTTPHandler, HTTPRedirectHandler, Request, |
| 32 | + ) |
28 | 33 |
|
29 | 34 | CONTENT_TYPE_LATEST = str('text/plain; version=0.0.4; charset=utf-8')
|
30 | 35 | """Content type of the latest text format"""
|
31 |
| - |
| 36 | +PYTHON27_OR_OLDER = sys.version_info < (3, ) |
32 | 37 | PYTHON26_OR_OLDER = sys.version_info < (2, 7)
|
33 | 38 | PYTHON376_OR_NEWER = sys.version_info > (3, 7, 5)
|
34 | 39 |
|
35 | 40 |
|
| 41 | +class _PrometheusRedirectHandler(HTTPRedirectHandler): |
| 42 | + """ |
| 43 | + Allow additional methods (e.g. PUT) and data forwarding in redirects. |
| 44 | +
|
| 45 | + Use of this class constitute a user's explicit agreement to the |
| 46 | + redirect responses the Prometheus client will receive when using it. |
| 47 | + You should only use this class if you control or otherwise trust the |
| 48 | + redirect behavior involved and are certain it is safe to full transfer |
| 49 | + the original request (method and data) to the redirected URL. For |
| 50 | + example, if you know there is a cosmetic URL redirect in front of a |
| 51 | + local deployment of a Prometheus server, and all redirects are safe, |
| 52 | + this is the class to use to handle redirects in that case. |
| 53 | +
|
| 54 | + The standard HTTPRedirectHandler does not forward request data nor |
| 55 | + does it allow redirected PUT requests (which Prometheus uses for some |
| 56 | + operations, for example `push_to_gateway`) because these cannot |
| 57 | + generically guarantee no violations of HTTP RFC 2616 requirements for |
| 58 | + the user to explicitly confirm redirects that could have unexpected |
| 59 | + side effects (such as rendering a PUT request non-idempotent or |
| 60 | + creating multiple resources not named in the original request). |
| 61 | + """ |
| 62 | + |
| 63 | + def redirect_request(self, req, fp, code, msg, headers, newurl): |
| 64 | + """ |
| 65 | + Apply redirect logic to a request. |
| 66 | +
|
| 67 | + See parent HTTPRedirectHandler.redirect_request for parameter info. |
| 68 | +
|
| 69 | + If the redirect is disallowed, this raises the corresponding HTTP error. |
| 70 | + If the redirect can't be determined, return None to allow other handlers |
| 71 | + to try. If the redirect is allowed, return the new request. |
| 72 | +
|
| 73 | + This method specialized for the case when (a) the user knows that the |
| 74 | + redirect will not cause unacceptable side effects for any request method, |
| 75 | + and (b) the user knows that any request data should be passed through to |
| 76 | + the redirect. If either condition is not met, this should not be used. |
| 77 | + """ |
| 78 | + # note that requests being provided by a handler will use get_method to |
| 79 | + # indicate the method, by monkeypatching this, instead of setting the |
| 80 | + # Request object's method attribute. |
| 81 | + m = getattr(req, "method", req.get_method()) |
| 82 | + if not (code in (301, 302, 303, 307) and m in ("GET", "HEAD") |
| 83 | + or code in (301, 302, 303) and m in ("POST", "PUT")): |
| 84 | + raise HTTPError(req.full_url, code, msg, headers, fp) |
| 85 | + new_request = Request( |
| 86 | + newurl.replace(' ', '%20'), # space escaping in new url if needed. |
| 87 | + headers=req.headers, |
| 88 | + origin_req_host=req.origin_req_host, |
| 89 | + unverifiable=True, |
| 90 | + data=req.data, |
| 91 | + ) |
| 92 | + if PYTHON27_OR_OLDER: |
| 93 | + # the `method` attribute did not exist for Request in Python 2.7. |
| 94 | + new_request.get_method = lambda: m |
| 95 | + else: |
| 96 | + new_request.method = m |
| 97 | + return new_request |
| 98 | + |
| 99 | + |
36 | 100 | def _bake_output(registry, accept_header, params):
|
37 | 101 | """Bake output for metrics output."""
|
38 | 102 | encoder, content_type = choose_encoder(accept_header)
|
@@ -141,7 +205,7 @@ def sample_line(line):
|
141 | 205 | raise
|
142 | 206 |
|
143 | 207 | for suffix, lines in sorted(om_samples.items()):
|
144 |
| - output.append('# HELP {0}{1} {2}\n'.format(metric.name, suffix, |
| 208 | + output.append('# HELP {0}{1} {2}\n'.format(metric.name, suffix, |
145 | 209 | metric.documentation.replace('\\', r'\\').replace('\n', r'\n')))
|
146 | 210 | output.append('# TYPE {0}{1} gauge\n'.format(metric.name, suffix))
|
147 | 211 | output.extend(lines)
|
@@ -205,24 +269,43 @@ def write_to_textfile(path, registry):
|
205 | 269 | os.rename(tmppath, path)
|
206 | 270 |
|
207 | 271 |
|
208 |
| -def default_handler(url, method, timeout, headers, data): |
209 |
| - """Default handler that implements HTTP/HTTPS connections. |
210 |
| -
|
211 |
| - Used by the push_to_gateway functions. Can be re-used by other handlers.""" |
| 272 | +def _make_handler(url, method, timeout, headers, data, base_handler): |
212 | 273 |
|
213 | 274 | def handle():
|
214 | 275 | request = Request(url, data=data)
|
215 | 276 | request.get_method = lambda: method
|
216 | 277 | for k, v in headers:
|
217 | 278 | request.add_header(k, v)
|
218 |
| - resp = build_opener(HTTPHandler).open(request, timeout=timeout) |
| 279 | + resp = build_opener(base_handler).open(request, timeout=timeout) |
219 | 280 | if resp.code >= 400:
|
220 | 281 | raise IOError("error talking to pushgateway: {0} {1}".format(
|
221 | 282 | resp.code, resp.msg))
|
222 | 283 |
|
223 | 284 | return handle
|
224 | 285 |
|
225 | 286 |
|
| 287 | +def default_handler(url, method, timeout, headers, data): |
| 288 | + """Default handler that implements HTTP/HTTPS connections. |
| 289 | +
|
| 290 | + Used by the push_to_gateway functions. Can be re-used by other handlers.""" |
| 291 | + |
| 292 | + return _make_handler(url, method, timeout, headers, data, HTTPHandler) |
| 293 | + |
| 294 | + |
| 295 | +def passthrough_redirect_handler(url, method, timeout, headers, data): |
| 296 | + """ |
| 297 | + Handler that automatically trusts redirect responses for all HTTP methods. |
| 298 | +
|
| 299 | + Augments standard HTTPRedirectHandler capability by permitting PUT requests, |
| 300 | + preserving the method upon redirect, and passing through all headers and |
| 301 | + data from the original request. Only use this handler if you control or |
| 302 | + trust the source of redirect responses you encounter when making requests |
| 303 | + via the Prometheus client. This handler will simply repeat the identical |
| 304 | + request, including same method and data, to the new redirect URL.""" |
| 305 | + |
| 306 | + return _make_handler(url, method, timeout, headers, data, _PrometheusRedirectHandler) |
| 307 | + |
| 308 | + |
226 | 309 | def basic_auth_handler(url, method, timeout, headers, data, username=None, password=None):
|
227 | 310 | """Handler that implements HTTP/HTTPS connections with Basic Auth.
|
228 | 311 |
|
|
0 commit comments