Mercurial > hg
changeset 27601:1ad9da968a2e
httpclient: update to 938f2107d6e2 of httpplus
This enhances proxy support in httpclient a little bit, though I don't
know that we used that functionality at all. It also switches httpplus
to using absolute_import.
author | Augie Fackler <augie@google.com> |
---|---|
date | Thu, 31 Dec 2015 13:19:20 -0500 |
parents | cfb26146a8cd |
children | 67aa88e00fc7 |
files | mercurial/httpclient/__init__.py mercurial/httpclient/_readers.py mercurial/httpclient/socketutil.py tests/test-check-py3-compat.t |
diffstat | 4 files changed, 64 insertions(+), 26 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/httpclient/__init__.py Thu Dec 31 15:39:38 2015 +0000 +++ b/mercurial/httpclient/__init__.py Thu Dec 31 13:19:20 2015 -0500 @@ -36,6 +36,7 @@ * notices when the server responds early to a request * implements ssl inline instead of in a different class """ +from __future__ import absolute_import # Many functions in this file have too many arguments. # pylint: disable=R0913 @@ -48,8 +49,10 @@ import select import socket -import _readers -import socketutil +from . import ( + _readers, + socketutil, + ) logger = logging.getLogger(__name__) @@ -124,6 +127,12 @@ # pylint: disable=W0212 self._reader._close() + def getheader(self, header, default=None): + return self.headers.getheader(header, default=default) + + def getheaders(self): + return self.headers.items() + def readline(self): """Read a single line from the response body. @@ -279,6 +288,14 @@ # pylint: disable=W0212 self._load_response = self._reader._load +def _foldheaders(headers): + """Given some headers, rework them so we can safely overwrite values. + + >>> _foldheaders({'Accept-Encoding': 'wat'}) + {'accept-encoding': ('Accept-Encoding', 'wat')} + """ + return dict((k.lower(), (k, v)) for k, v in headers.iteritems()) + class HTTPConnection(object): """Connection to a single http server. @@ -292,7 +309,8 @@ def __init__(self, host, port=None, use_ssl=None, ssl_validator=None, timeout=TIMEOUT_DEFAULT, continue_timeout=TIMEOUT_ASSUME_CONTINUE, - proxy_hostport=None, ssl_wrap_socket=None, **ssl_opts): + proxy_hostport=None, proxy_headers=None, + ssl_wrap_socket=None, **ssl_opts): """Create a new HTTPConnection. Args: @@ -307,6 +325,13 @@ "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE. proxy_hostport: Optional. Tuple of (host, port) to use as an http proxy for the connection. Default is to not use a proxy. + proxy_headers: Optional dict of header keys and values to send to + a proxy when using CONNECT. For compatibility with + httplib, the Proxy-Authorization header may be + specified in headers for request(), which will clobber + any such header specified here if specified. Providing + this option and not proxy_hostport will raise an + ValueError. ssl_wrap_socket: Optional function to use for wrapping sockets. If unspecified, the one from the ssl module will be used if available, or something that's compatible with @@ -330,10 +355,7 @@ elif use_ssl is None: use_ssl = (port == 443) elif port is None: - if use_ssl: - port = 443 - else: - port = 80 + port = (use_ssl and 443 or 80) self.port = port if use_ssl and not socketutil.have_ssl: raise Exception('ssl requested but unavailable on this Python') @@ -346,13 +368,20 @@ self._current_response_taken = False if proxy_hostport is None: self._proxy_host = self._proxy_port = None + if proxy_headers: + raise ValueError( + 'proxy_headers may not be specified unless ' + 'proxy_hostport is also specified.') + else: + self._proxy_headers = {} else: self._proxy_host, self._proxy_port = proxy_hostport + self._proxy_headers = _foldheaders(proxy_headers or {}) self.timeout = timeout self.continue_timeout = continue_timeout - def _connect(self): + def _connect(self, proxy_headers): """Connect to the host and port specified in __init__.""" if self.sock: return @@ -362,10 +391,9 @@ sock = socketutil.create_connection((self._proxy_host, self._proxy_port)) if self.ssl: - # TODO proxy header support data = self._buildheaders('CONNECT', '%s:%d' % (self.host, self.port), - {}, HTTP_VER_1_0) + proxy_headers, HTTP_VER_1_0) sock.send(data) sock.setblocking(0) r = self.response_class(sock, self.timeout, 'CONNECT') @@ -468,10 +496,10 @@ return True return False - def _reconnect(self, where): + def _reconnect(self, where, pheaders): logger.info('reconnecting during %s', where) self.close() - self._connect() + self._connect(pheaders) def request(self, method, path, body=None, headers={}, expect_continue=False): @@ -492,11 +520,20 @@ logger.info('sending %s request for %s to %s on port %s', method, path, self.host, self.port) - hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems()) + hdrs = _foldheaders(headers) if hdrs.get('expect', ('', ''))[1].lower() == '100-continue': expect_continue = True elif expect_continue: hdrs['expect'] = ('Expect', '100-Continue') + # httplib compatibility: if the user specified a + # proxy-authorization header, that's actually intended for a + # proxy CONNECT action, not the real request, but only if + # we're going to use a proxy. + pheaders = dict(self._proxy_headers) + if self._proxy_host and self.ssl: + pa = hdrs.pop('proxy-authorization', None) + if pa is not None: + pheaders['proxy-authorization'] = pa chunked = False if body and HDR_CONTENT_LENGTH not in hdrs: @@ -513,7 +550,7 @@ # conditions where we'll want to retry, so make a note of the # state of self.sock fresh_socket = self.sock is None - self._connect() + self._connect(pheaders) outgoing_headers = self._buildheaders( method, path, hdrs, self.http_version) response = None @@ -588,7 +625,7 @@ logger.info( 'Connection appeared closed in read on first' ' request loop iteration, will retry.') - self._reconnect('read') + self._reconnect('read', pheaders) continue else: # We didn't just send the first data hunk, @@ -645,7 +682,7 @@ elif (e[0] not in (errno.ECONNRESET, errno.EPIPE) and not first): raise - self._reconnect('write') + self._reconnect('write', pheaders) amt = self.sock.send(out) logger.debug('sent %d', amt) first = False @@ -664,8 +701,8 @@ # data at all, and in all probability the socket was # closed before the server even saw our request. Try # the request again on a fresh socket. - logging.debug('response._select() failed during request().' - ' Assuming request needs to be retried.') + logger.debug('response._select() failed during request().' + ' Assuming request needs to be retried.') self.sock = None # Call this method explicitly to re-try the # request. We don't use self.request() because
--- a/mercurial/httpclient/_readers.py Thu Dec 31 15:39:38 2015 +0000 +++ b/mercurial/httpclient/_readers.py Thu Dec 31 13:19:20 2015 -0500 @@ -31,6 +31,7 @@ This module is package-private. It is not expected that these will have any clients outside of httpplus. """ +from __future__ import absolute_import import httplib import logging @@ -98,11 +99,12 @@ return result def readto(self, delimstr, blocks = None): - """return available data chunks up to the first one in which delimstr - occurs. No data will be returned after delimstr -- the chunk in which - it occurs will be split and the remainder pushed back onto the available - data queue. If blocks is supplied chunks will be added to blocks, otherwise - a new list will be allocated. + """return available data chunks up to the first one in which + delimstr occurs. No data will be returned after delimstr -- + the chunk in which it occurs will be split and the remainder + pushed back onto the available data queue. If blocks is + supplied chunks will be added to blocks, otherwise a new list + will be allocated. """ if blocks is None: blocks = []
--- a/mercurial/httpclient/socketutil.py Thu Dec 31 15:39:38 2015 +0000 +++ b/mercurial/httpclient/socketutil.py Thu Dec 31 13:19:20 2015 -0500 @@ -32,6 +32,8 @@ socket.create_connection method, but fall back to the old methods if those are unavailable. """ +from __future__ import absolute_import + import logging import socket
--- a/tests/test-check-py3-compat.t Thu Dec 31 15:39:38 2015 +0000 +++ b/tests/test-check-py3-compat.t Thu Dec 31 13:19:20 2015 -0500 @@ -101,9 +101,6 @@ mercurial/cmdutil.py not using absolute_import mercurial/commands.py not using absolute_import mercurial/dispatch.py requires print_function - mercurial/httpclient/__init__.py not using absolute_import - mercurial/httpclient/_readers.py not using absolute_import - mercurial/httpclient/socketutil.py not using absolute_import mercurial/keepalive.py requires print_function mercurial/lsprof.py requires print_function mercurial/lsprofcalltree.py requires print_function