comparison mercurial/httpclient/__init__.py @ 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 328739ea70c3
children 86db5cb55d46
comparison
equal deleted inserted replaced
27600:cfb26146a8cd 27601:1ad9da968a2e
34 * supports keepalives natively 34 * supports keepalives natively
35 * uses select() to block for incoming data 35 * uses select() to block for incoming data
36 * notices when the server responds early to a request 36 * notices when the server responds early to a request
37 * implements ssl inline instead of in a different class 37 * implements ssl inline instead of in a different class
38 """ 38 """
39 from __future__ import absolute_import
39 40
40 # Many functions in this file have too many arguments. 41 # Many functions in this file have too many arguments.
41 # pylint: disable=R0913 42 # pylint: disable=R0913
42 43
43 import cStringIO 44 import cStringIO
46 import logging 47 import logging
47 import rfc822 48 import rfc822
48 import select 49 import select
49 import socket 50 import socket
50 51
51 import _readers 52 from . import (
52 import socketutil 53 _readers,
54 socketutil,
55 )
53 56
54 logger = logging.getLogger(__name__) 57 logger = logging.getLogger(__name__)
55 58
56 __all__ = ['HTTPConnection', 'HTTPResponse'] 59 __all__ = ['HTTPConnection', 'HTTPResponse']
57 60
121 def _close(self): 124 def _close(self):
122 if self._reader is not None: 125 if self._reader is not None:
123 # We're a friend of the reader class here. 126 # We're a friend of the reader class here.
124 # pylint: disable=W0212 127 # pylint: disable=W0212
125 self._reader._close() 128 self._reader._close()
129
130 def getheader(self, header, default=None):
131 return self.headers.getheader(header, default=default)
132
133 def getheaders(self):
134 return self.headers.items()
126 135
127 def readline(self): 136 def readline(self):
128 """Read a single line from the response body. 137 """Read a single line from the response body.
129 138
130 This may block until either a line ending is found or the 139 This may block until either a line ending is found or the
277 self.headers = headers 286 self.headers = headers
278 # We're a friend of the reader class here. 287 # We're a friend of the reader class here.
279 # pylint: disable=W0212 288 # pylint: disable=W0212
280 self._load_response = self._reader._load 289 self._load_response = self._reader._load
281 290
291 def _foldheaders(headers):
292 """Given some headers, rework them so we can safely overwrite values.
293
294 >>> _foldheaders({'Accept-Encoding': 'wat'})
295 {'accept-encoding': ('Accept-Encoding', 'wat')}
296 """
297 return dict((k.lower(), (k, v)) for k, v in headers.iteritems())
298
282 299
283 class HTTPConnection(object): 300 class HTTPConnection(object):
284 """Connection to a single http server. 301 """Connection to a single http server.
285 302
286 Supports 100-continue and keepalives natively. Uses select() for 303 Supports 100-continue and keepalives natively. Uses select() for
290 response_class = HTTPResponse 307 response_class = HTTPResponse
291 308
292 def __init__(self, host, port=None, use_ssl=None, ssl_validator=None, 309 def __init__(self, host, port=None, use_ssl=None, ssl_validator=None,
293 timeout=TIMEOUT_DEFAULT, 310 timeout=TIMEOUT_DEFAULT,
294 continue_timeout=TIMEOUT_ASSUME_CONTINUE, 311 continue_timeout=TIMEOUT_ASSUME_CONTINUE,
295 proxy_hostport=None, ssl_wrap_socket=None, **ssl_opts): 312 proxy_hostport=None, proxy_headers=None,
313 ssl_wrap_socket=None, **ssl_opts):
296 """Create a new HTTPConnection. 314 """Create a new HTTPConnection.
297 315
298 Args: 316 Args:
299 host: The host to which we'll connect. 317 host: The host to which we'll connect.
300 port: Optional. The port over which we'll connect. Default 80 for 318 port: Optional. The port over which we'll connect. Default 80 for
305 timeout: Optional. Connection timeout, default is TIMEOUT_DEFAULT. 323 timeout: Optional. Connection timeout, default is TIMEOUT_DEFAULT.
306 continue_timeout: Optional. Timeout for waiting on an expected 324 continue_timeout: Optional. Timeout for waiting on an expected
307 "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE. 325 "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE.
308 proxy_hostport: Optional. Tuple of (host, port) to use as an http 326 proxy_hostport: Optional. Tuple of (host, port) to use as an http
309 proxy for the connection. Default is to not use a proxy. 327 proxy for the connection. Default is to not use a proxy.
328 proxy_headers: Optional dict of header keys and values to send to
329 a proxy when using CONNECT. For compatibility with
330 httplib, the Proxy-Authorization header may be
331 specified in headers for request(), which will clobber
332 any such header specified here if specified. Providing
333 this option and not proxy_hostport will raise an
334 ValueError.
310 ssl_wrap_socket: Optional function to use for wrapping 335 ssl_wrap_socket: Optional function to use for wrapping
311 sockets. If unspecified, the one from the ssl module will 336 sockets. If unspecified, the one from the ssl module will
312 be used if available, or something that's compatible with 337 be used if available, or something that's compatible with
313 it if on a Python older than 2.6. 338 it if on a Python older than 2.6.
314 339
328 use_ssl = False 353 use_ssl = False
329 port = 80 354 port = 80
330 elif use_ssl is None: 355 elif use_ssl is None:
331 use_ssl = (port == 443) 356 use_ssl = (port == 443)
332 elif port is None: 357 elif port is None:
333 if use_ssl: 358 port = (use_ssl and 443 or 80)
334 port = 443
335 else:
336 port = 80
337 self.port = port 359 self.port = port
338 if use_ssl and not socketutil.have_ssl: 360 if use_ssl and not socketutil.have_ssl:
339 raise Exception('ssl requested but unavailable on this Python') 361 raise Exception('ssl requested but unavailable on this Python')
340 self.ssl = use_ssl 362 self.ssl = use_ssl
341 self.ssl_opts = ssl_opts 363 self.ssl_opts = ssl_opts
344 self.sock = None 366 self.sock = None
345 self._current_response = None 367 self._current_response = None
346 self._current_response_taken = False 368 self._current_response_taken = False
347 if proxy_hostport is None: 369 if proxy_hostport is None:
348 self._proxy_host = self._proxy_port = None 370 self._proxy_host = self._proxy_port = None
371 if proxy_headers:
372 raise ValueError(
373 'proxy_headers may not be specified unless '
374 'proxy_hostport is also specified.')
375 else:
376 self._proxy_headers = {}
349 else: 377 else:
350 self._proxy_host, self._proxy_port = proxy_hostport 378 self._proxy_host, self._proxy_port = proxy_hostport
379 self._proxy_headers = _foldheaders(proxy_headers or {})
351 380
352 self.timeout = timeout 381 self.timeout = timeout
353 self.continue_timeout = continue_timeout 382 self.continue_timeout = continue_timeout
354 383
355 def _connect(self): 384 def _connect(self, proxy_headers):
356 """Connect to the host and port specified in __init__.""" 385 """Connect to the host and port specified in __init__."""
357 if self.sock: 386 if self.sock:
358 return 387 return
359 if self._proxy_host is not None: 388 if self._proxy_host is not None:
360 logger.info('Connecting to http proxy %s:%s', 389 logger.info('Connecting to http proxy %s:%s',
361 self._proxy_host, self._proxy_port) 390 self._proxy_host, self._proxy_port)
362 sock = socketutil.create_connection((self._proxy_host, 391 sock = socketutil.create_connection((self._proxy_host,
363 self._proxy_port)) 392 self._proxy_port))
364 if self.ssl: 393 if self.ssl:
365 # TODO proxy header support
366 data = self._buildheaders('CONNECT', '%s:%d' % (self.host, 394 data = self._buildheaders('CONNECT', '%s:%d' % (self.host,
367 self.port), 395 self.port),
368 {}, HTTP_VER_1_0) 396 proxy_headers, HTTP_VER_1_0)
369 sock.send(data) 397 sock.send(data)
370 sock.setblocking(0) 398 sock.setblocking(0)
371 r = self.response_class(sock, self.timeout, 'CONNECT') 399 r = self.response_class(sock, self.timeout, 'CONNECT')
372 timeout_exc = HTTPTimeoutException( 400 timeout_exc = HTTPTimeoutException(
373 'Timed out waiting for CONNECT response from proxy') 401 'Timed out waiting for CONNECT response from proxy')
466 self._current_response = None 494 self._current_response = None
467 return False 495 return False
468 return True 496 return True
469 return False 497 return False
470 498
471 def _reconnect(self, where): 499 def _reconnect(self, where, pheaders):
472 logger.info('reconnecting during %s', where) 500 logger.info('reconnecting during %s', where)
473 self.close() 501 self.close()
474 self._connect() 502 self._connect(pheaders)
475 503
476 def request(self, method, path, body=None, headers={}, 504 def request(self, method, path, body=None, headers={},
477 expect_continue=False): 505 expect_continue=False):
478 """Send a request to the server. 506 """Send a request to the server.
479 507
490 'current response is read!') 518 'current response is read!')
491 self._current_response_taken = False 519 self._current_response_taken = False
492 520
493 logger.info('sending %s request for %s to %s on port %s', 521 logger.info('sending %s request for %s to %s on port %s',
494 method, path, self.host, self.port) 522 method, path, self.host, self.port)
495 hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems()) 523 hdrs = _foldheaders(headers)
496 if hdrs.get('expect', ('', ''))[1].lower() == '100-continue': 524 if hdrs.get('expect', ('', ''))[1].lower() == '100-continue':
497 expect_continue = True 525 expect_continue = True
498 elif expect_continue: 526 elif expect_continue:
499 hdrs['expect'] = ('Expect', '100-Continue') 527 hdrs['expect'] = ('Expect', '100-Continue')
528 # httplib compatibility: if the user specified a
529 # proxy-authorization header, that's actually intended for a
530 # proxy CONNECT action, not the real request, but only if
531 # we're going to use a proxy.
532 pheaders = dict(self._proxy_headers)
533 if self._proxy_host and self.ssl:
534 pa = hdrs.pop('proxy-authorization', None)
535 if pa is not None:
536 pheaders['proxy-authorization'] = pa
500 537
501 chunked = False 538 chunked = False
502 if body and HDR_CONTENT_LENGTH not in hdrs: 539 if body and HDR_CONTENT_LENGTH not in hdrs:
503 if getattr(body, '__len__', False): 540 if getattr(body, '__len__', False):
504 hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body)) 541 hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body))
511 548
512 # If we're reusing the underlying socket, there are some 549 # If we're reusing the underlying socket, there are some
513 # conditions where we'll want to retry, so make a note of the 550 # conditions where we'll want to retry, so make a note of the
514 # state of self.sock 551 # state of self.sock
515 fresh_socket = self.sock is None 552 fresh_socket = self.sock is None
516 self._connect() 553 self._connect(pheaders)
517 outgoing_headers = self._buildheaders( 554 outgoing_headers = self._buildheaders(
518 method, path, hdrs, self.http_version) 555 method, path, hdrs, self.http_version)
519 response = None 556 response = None
520 first = True 557 first = True
521 558
586 # after getting a really large response 623 # after getting a really large response
587 # from the server. 624 # from the server.
588 logger.info( 625 logger.info(
589 'Connection appeared closed in read on first' 626 'Connection appeared closed in read on first'
590 ' request loop iteration, will retry.') 627 ' request loop iteration, will retry.')
591 self._reconnect('read') 628 self._reconnect('read', pheaders)
592 continue 629 continue
593 else: 630 else:
594 # We didn't just send the first data hunk, 631 # We didn't just send the first data hunk,
595 # and either have a partial response or no 632 # and either have a partial response or no
596 # response at all. There's really nothing 633 # response at all. There's really nothing
643 if e[0] == errno.EWOULDBLOCK or e[0] == errno.EAGAIN: 680 if e[0] == errno.EWOULDBLOCK or e[0] == errno.EAGAIN:
644 continue 681 continue
645 elif (e[0] not in (errno.ECONNRESET, errno.EPIPE) 682 elif (e[0] not in (errno.ECONNRESET, errno.EPIPE)
646 and not first): 683 and not first):
647 raise 684 raise
648 self._reconnect('write') 685 self._reconnect('write', pheaders)
649 amt = self.sock.send(out) 686 amt = self.sock.send(out)
650 logger.debug('sent %d', amt) 687 logger.debug('sent %d', amt)
651 first = False 688 first = False
652 if out is body: 689 if out is body:
653 body = out[amt:] 690 body = out[amt:]
662 if not response._select(): 699 if not response._select():
663 # This means the response failed to get any response 700 # This means the response failed to get any response
664 # data at all, and in all probability the socket was 701 # data at all, and in all probability the socket was
665 # closed before the server even saw our request. Try 702 # closed before the server even saw our request. Try
666 # the request again on a fresh socket. 703 # the request again on a fresh socket.
667 logging.debug('response._select() failed during request().' 704 logger.debug('response._select() failed during request().'
668 ' Assuming request needs to be retried.') 705 ' Assuming request needs to be retried.')
669 self.sock = None 706 self.sock = None
670 # Call this method explicitly to re-try the 707 # Call this method explicitly to re-try the
671 # request. We don't use self.request() because 708 # request. We don't use self.request() because
672 # some tools (notably Mercurial) expect to be able 709 # some tools (notably Mercurial) expect to be able
673 # to subclass and redefine request(), and they 710 # to subclass and redefine request(), and they