# HG changeset patch # User Augie Fackler # Date 2015-12-31 18:19:20 # Node ID 1ad9da968a2e8a244478fd98730ed4791d6c835d # Parent cfb26146a8cd5ceaa7efe86ee206e48114d2729b 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. diff --git a/mercurial/httpclient/__init__.py b/mercurial/httpclient/__init__.py --- a/mercurial/httpclient/__init__.py +++ b/mercurial/httpclient/__init__.py @@ -36,6 +36,7 @@ httplib, but has several additional feat * 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 rfc822 import select import socket -import _readers -import socketutil +from . import ( + _readers, + socketutil, + ) logger = logging.getLogger(__name__) @@ -124,6 +127,12 @@ class HTTPResponse(object): # 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 @@ class HTTPResponse(object): # 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 @@ class HTTPConnection(object): 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 @@ class HTTPConnection(object): "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 @@ class HTTPConnection(object): 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 @@ class HTTPConnection(object): 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 @@ class HTTPConnection(object): 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 @@ class HTTPConnection(object): 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 @@ class HTTPConnection(object): 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 @@ class HTTPConnection(object): # 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 @@ class HTTPConnection(object): 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 @@ class HTTPConnection(object): 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 @@ class HTTPConnection(object): # 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 diff --git a/mercurial/httpclient/_readers.py b/mercurial/httpclient/_readers.py --- a/mercurial/httpclient/_readers.py +++ b/mercurial/httpclient/_readers.py @@ -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 @@ class AbstractReader(object): 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 = [] diff --git a/mercurial/httpclient/socketutil.py b/mercurial/httpclient/socketutil.py --- a/mercurial/httpclient/socketutil.py +++ b/mercurial/httpclient/socketutil.py @@ -32,6 +32,8 @@ This will attempt to use the ssl module socket.create_connection method, but fall back to the old methods if those are unavailable. """ +from __future__ import absolute_import + import logging import socket diff --git a/tests/test-check-py3-compat.t b/tests/test-check-py3-compat.t --- a/tests/test-check-py3-compat.t +++ b/tests/test-check-py3-compat.t @@ -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