Patchwork [in-clowncopter] httpclient: update to 938f2107d6e2 of httpplus

login
register
mail settings
Submitter Augie Fackler
Date Dec. 31, 2015, 6:28 p.m.
Message ID <7683ed1377dad6ece4db.1451586482@imladris.local>
Download mbox | patch
Permalink /patch/12447/
State Accepted
Commit 1ad9da968a2e8a244478fd98730ed4791d6c835d
Headers show

Comments

Augie Fackler - Dec. 31, 2015, 6:28 p.m.
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1451585960 18000
#      Thu Dec 31 13:19:20 2015 -0500
# Node ID 7683ed1377dad6ece4dbe2276a8a166c77599a8d
# Parent  5724832b2aaee5a941372194e9798861643b85d3
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.

Patch

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