From 85ec192ac4b000d4e47df6123b65eacbd1fdccfa Mon Sep 17 00:00:00 2001 From: Alexey Namyotkin <62434915+nametkin@users.noreply.github.com> Date: Tue, 16 May 2023 09:20:30 +0300 Subject: gh-69152: add method get_proxy_response_headers to HTTPConnection class (#104248) Add http.client.HTTPConnection method get_proxy_response_headers() - this is a followup to https://github.com/python/cpython/pull/26152 which added it as a non-public attribute. This way we don't pre-compute a headers dictionary that most users will never access. The new method is properly public and documented and triggers full proxy header parsing into a dict only when actually called. --------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Gregory P. Smith --- Doc/library/http.client.rst | 11 +++++++ Lib/http/client.py | 37 +++++++++++++++++----- Lib/test/test_httplib.py | 2 +- .../2021-05-16-14-28-30.bpo-24964.Oa5Ie_.rst | 6 ++-- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/Doc/library/http.client.rst b/Doc/library/http.client.rst index bf1f2e3..46d616a 100644 --- a/Doc/library/http.client.rst +++ b/Doc/library/http.client.rst @@ -394,6 +394,17 @@ HTTPConnection Objects one will be automatically generated and transmitted if not provided in the headers argument. + +.. method:: HTTPConnection.get_proxy_response_headers() + + Returns a dictionary with the headers of the response received from + the proxy server to the CONNECT request. + + If the CONNECT request was not sent, the method returns an empty dictionary. + + .. versionadded:: 3.12 + + .. method:: HTTPConnection.connect() Connect to the server specified when the object was created. By default, diff --git a/Lib/http/client.py b/Lib/http/client.py index 50f2b46..59a9fd7 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -221,8 +221,9 @@ def _read_headers(fp): break return headers -def parse_headers(fp, _class=HTTPMessage): - """Parses only RFC2822 headers from a file pointer. +def _parse_header_lines(header_lines, _class=HTTPMessage): + """ + Parses only RFC2822 headers from header lines. email Parser wants to see strings rather than bytes. But a TextIOWrapper around self.rfile would buffer too many bytes @@ -231,10 +232,15 @@ def parse_headers(fp, _class=HTTPMessage): to parse. """ - headers = _read_headers(fp) - hstring = b''.join(headers).decode('iso-8859-1') + hstring = b''.join(header_lines).decode('iso-8859-1') return email.parser.Parser(_class=_class).parsestr(hstring) +def parse_headers(fp, _class=HTTPMessage): + """Parses only RFC2822 headers from a file pointer.""" + + headers = _read_headers(fp) + return _parse_header_lines(headers, _class) + class HTTPResponse(io.BufferedIOBase): @@ -858,7 +864,7 @@ class HTTPConnection: self._tunnel_host = None self._tunnel_port = None self._tunnel_headers = {} - self._proxy_response_headers = None + self._raw_proxy_headers = None (self.host, self.port) = self._get_hostport(host, port) @@ -945,11 +951,11 @@ class HTTPConnection: try: (version, code, message) = response._read_status() - self._proxy_response_headers = parse_headers(response.fp) + self._raw_proxy_headers = _read_headers(response.fp) if self.debuglevel > 0: - for hdr, val in self._proxy_response_headers.items(): - print("header:", hdr + ":", val) + for header in self._raw_proxy_headers: + print('header:', header.decode()) if code != http.HTTPStatus.OK: self.close() @@ -958,6 +964,21 @@ class HTTPConnection: finally: response.close() + def get_proxy_response_headers(self): + """ + Returns a dictionary with the headers of the response + received from the proxy server to the CONNECT request + sent to set the tunnel. + + If the CONNECT request was not sent, the method returns + an empty dictionary. + """ + return ( + _parse_header_lines(self._raw_proxy_headers) + if self._raw_proxy_headers is not None + else {} + ) + def connect(self): """Connect to the host and port specified in __init__.""" sys.audit("http.client.connect", self, self.host, self.port) diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 4b1d355..8955d45 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -2401,7 +2401,7 @@ class TunnelTests(TestCase): self.conn.set_tunnel('destination.com') self.conn.request('PUT', '/', '') - headers = self.conn._proxy_response_headers + headers = self.conn.get_proxy_response_headers() self.assertIn(expected_header, headers.items()) def test_tunnel_leak(self): diff --git a/Misc/NEWS.d/next/Library/2021-05-16-14-28-30.bpo-24964.Oa5Ie_.rst b/Misc/NEWS.d/next/Library/2021-05-16-14-28-30.bpo-24964.Oa5Ie_.rst index ba11367..0904b16 100644 --- a/Misc/NEWS.d/next/Library/2021-05-16-14-28-30.bpo-24964.Oa5Ie_.rst +++ b/Misc/NEWS.d/next/Library/2021-05-16-14-28-30.bpo-24964.Oa5Ie_.rst @@ -1,3 +1,3 @@ -Added attribute '_proxy_response_headers' to HTTPConnection class. This -attribute contains the headers of the proxy server response to the CONNECT -request. +Added :meth:`http.client.HTTPConnection.get_proxy_response_headers` that +provides access to the HTTP headers on a proxy server response to the +``CONNECT`` request. -- cgit v0.12