@@ -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
@@ -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 = []
@@ -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
@@ -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