| from __future__ import absolute_import |
| |
| import errno |
| import logging |
| import re |
| import socket |
| import sys |
| import warnings |
| from socket import error as SocketError |
| from socket import timeout as SocketTimeout |
| |
| from .connection import ( |
| BaseSSLError, |
| BrokenPipeError, |
| DummyConnection, |
| HTTPConnection, |
| HTTPException, |
| HTTPSConnection, |
| VerifiedHTTPSConnection, |
| port_by_scheme, |
| ) |
| from .exceptions import ( |
| ClosedPoolError, |
| EmptyPoolError, |
| HeaderParsingError, |
| HostChangedError, |
| InsecureRequestWarning, |
| LocationValueError, |
| MaxRetryError, |
| NewConnectionError, |
| ProtocolError, |
| ProxyError, |
| ReadTimeoutError, |
| SSLError, |
| TimeoutError, |
| ) |
| from .packages import six |
| from .packages.six.moves import queue |
| from .request import RequestMethods |
| from .response import HTTPResponse |
| from .util.connection import is_connection_dropped |
| from .util.proxy import connection_requires_http_tunnel |
| from .util.queue import LifoQueue |
| from .util.request import set_file_position |
| from .util.response import assert_header_parsing |
| from .util.retry import Retry |
| from .util.ssl_match_hostname import CertificateError |
| from .util.timeout import Timeout |
| from .util.url import Url, _encode_target |
| from .util.url import _normalize_host as normalize_host |
| from .util.url import get_host, parse_url |
| |
| xrange = six.moves.xrange |
| |
| log = logging.getLogger(__name__) |
| |
| _Default = object() |
| |
| |
| # Pool objects |
| class ConnectionPool(object): |
| """ |
| Base class for all connection pools, such as |
| :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. |
| |
| .. note:: |
| ConnectionPool.urlopen() does not normalize or percent-encode target URIs |
| which is useful if your target server doesn't support percent-encoded |
| target URIs. |
| """ |
| |
| scheme = None |
| QueueCls = LifoQueue |
| |
| def __init__(self, host, port=None): |
| if not host: |
| raise LocationValueError("No host specified.") |
| |
| self.host = _normalize_host(host, scheme=self.scheme) |
| self._proxy_host = host.lower() |
| self.port = port |
| |
| def __str__(self): |
| return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port) |
| |
| def __enter__(self): |
| return self |
| |
| def __exit__(self, exc_type, exc_val, exc_tb): |
| self.close() |
| # Return False to re-raise any potential exceptions |
| return False |
| |
| def close(self): |
| """ |
| Close all pooled connections and disable the pool. |
| """ |
| pass |
| |
| |
| # This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 |
| _blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} |
| |
| |
| class HTTPConnectionPool(ConnectionPool, RequestMethods): |
| """ |
| Thread-safe connection pool for one host. |
| |
| :param host: |
| Host used for this HTTP Connection (e.g. "localhost"), passed into |
| :class:`http.client.HTTPConnection`. |
| |
| :param port: |
| Port used for this HTTP Connection (None is equivalent to 80), passed |
| into :class:`http.client.HTTPConnection`. |
| |
| :param strict: |
| Causes BadStatusLine to be raised if the status line can't be parsed |
| as a valid HTTP/1.0 or 1.1 status line, passed into |
| :class:`http.client.HTTPConnection`. |
| |
| .. note:: |
| Only works in Python 2. This parameter is ignored in Python 3. |
| |
| :param timeout: |
| Socket timeout in seconds for each individual connection. This can |
| be a float or integer, which sets the timeout for the HTTP request, |
| or an instance of :class:`urllib3.util.Timeout` which gives you more |
| fine-grained control over request timeouts. After the constructor has |
| been parsed, this is always a `urllib3.util.Timeout` object. |
| |
| :param maxsize: |
| Number of connections to save that can be reused. More than 1 is useful |
| in multithreaded situations. If ``block`` is set to False, more |
| connections will be created but they will not be saved once they've |
| been used. |
| |
| :param block: |
| If set to True, no more than ``maxsize`` connections will be used at |
| a time. When no free connections are available, the call will block |
| until a connection has been released. This is a useful side effect for |
| particular multithreaded situations where one does not want to use more |
| than maxsize connections per host to prevent flooding. |
| |
| :param headers: |
| Headers to include with all requests, unless other headers are given |
| explicitly. |
| |
| :param retries: |
| Retry configuration to use by default with requests in this pool. |
| |
| :param _proxy: |
| Parsed proxy URL, should not be used directly, instead, see |
| :class:`urllib3.ProxyManager` |
| |
| :param _proxy_headers: |
| A dictionary with proxy headers, should not be used directly, |
| instead, see :class:`urllib3.ProxyManager` |
| |
| :param \\**conn_kw: |
| Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, |
| :class:`urllib3.connection.HTTPSConnection` instances. |
| """ |
| |
| scheme = "http" |
| ConnectionCls = HTTPConnection |
| ResponseCls = HTTPResponse |
| |
| def __init__( |
| self, |
| host, |
| port=None, |
| strict=False, |
| timeout=Timeout.DEFAULT_TIMEOUT, |
| maxsize=1, |
| block=False, |
| headers=None, |
| retries=None, |
| _proxy=None, |
| _proxy_headers=None, |
| _proxy_config=None, |
| **conn_kw |
| ): |
| ConnectionPool.__init__(self, host, port) |
| RequestMethods.__init__(self, headers) |
| |
| self.strict = strict |
| |
| if not isinstance(timeout, Timeout): |
| timeout = Timeout.from_float(timeout) |
| |
| if retries is None: |
| retries = Retry.DEFAULT |
| |
| self.timeout = timeout |
| self.retries = retries |
| |
| self.pool = self.QueueCls(maxsize) |
| self.block = block |
| |
| self.proxy = _proxy |
| self.proxy_headers = _proxy_headers or {} |
| self.proxy_config = _proxy_config |
| |
| # Fill the queue up so that doing get() on it will block properly |
| for _ in xrange(maxsize): |
| self.pool.put(None) |
| |
| # These are mostly for testing and debugging purposes. |
| self.num_connections = 0 |
| self.num_requests = 0 |
| self.conn_kw = conn_kw |
| |
| if self.proxy: |
| # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. |
| # We cannot know if the user has added default socket options, so we cannot replace the |
| # list. |
| self.conn_kw.setdefault("socket_options", []) |
| |
| self.conn_kw["proxy"] = self.proxy |
| self.conn_kw["proxy_config"] = self.proxy_config |
| |
| def _new_conn(self): |
| """ |
| Return a fresh :class:`HTTPConnection`. |
| """ |
| self.num_connections += 1 |
| log.debug( |
| "Starting new HTTP connection (%d): %s:%s", |
| self.num_connections, |
| self.host, |
| self.port or "80", |
| ) |
| |
| conn = self.ConnectionCls( |
| host=self.host, |
| port=self.port, |
| timeout=self.timeout.connect_timeout, |
| strict=self.strict, |
| **self.conn_kw |
| ) |
| return conn |
| |
| def _get_conn(self, timeout=None): |
| """ |
| Get a connection. Will return a pooled connection if one is available. |
| |
| If no connections are available and :prop:`.block` is ``False``, then a |
| fresh connection is returned. |
| |
| :param timeout: |
| Seconds to wait before giving up and raising |
| :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and |
| :prop:`.block` is ``True``. |
| """ |
| conn = None |
| try: |
| conn = self.pool.get(block=self.block, timeout=timeout) |
| |
| except AttributeError: # self.pool is None |
| raise ClosedPoolError(self, "Pool is closed.") |
| |
| except queue.Empty: |
| if self.block: |
| raise EmptyPoolError( |
| self, |
| "Pool reached maximum size and no more connections are allowed.", |
| ) |
| pass # Oh well, we'll create a new connection then |
| |
| # If this is a persistent connection, check if it got disconnected |
| if conn and is_connection_dropped(conn): |
| log.debug("Resetting dropped connection: %s", self.host) |
| conn.close() |
| if getattr(conn, "auto_open", 1) == 0: |
| # This is a proxied connection that has been mutated by |
| # http.client._tunnel() and cannot be reused (since it would |
| # attempt to bypass the proxy) |
| conn = None |
| |
| return conn or self._new_conn() |
| |
| def _put_conn(self, conn): |
| """ |
| Put a connection back into the pool. |
| |
| :param conn: |
| Connection object for the current host and port as returned by |
| :meth:`._new_conn` or :meth:`._get_conn`. |
| |
| If the pool is already full, the connection is closed and discarded |
| because we exceeded maxsize. If connections are discarded frequently, |
| then maxsize should be increased. |
| |
| If the pool is closed, then the connection will be closed and discarded. |
| """ |
| try: |
| self.pool.put(conn, block=False) |
| return # Everything is dandy, done. |
| except AttributeError: |
| # self.pool is None. |
| pass |
| except queue.Full: |
| # This should never happen if self.block == True |
| log.warning( |
| "Connection pool is full, discarding connection: %s. Connection pool size: %s", |
| self.host, |
| self.pool.qsize(), |
| ) |
| # Connection never got put back into the pool, close it. |
| if conn: |
| conn.close() |
| |
| def _validate_conn(self, conn): |
| """ |
| Called right before a request is made, after the socket is created. |
| """ |
| pass |
| |
| def _prepare_proxy(self, conn): |
| # Nothing to do for HTTP connections. |
| pass |
| |
| def _get_timeout(self, timeout): |
| """Helper that always returns a :class:`urllib3.util.Timeout`""" |
| if timeout is _Default: |
| return self.timeout.clone() |
| |
| if isinstance(timeout, Timeout): |
| return timeout.clone() |
| else: |
| # User passed us an int/float. This is for backwards compatibility, |
| # can be removed later |
| return Timeout.from_float(timeout) |
| |
| def _raise_timeout(self, err, url, timeout_value): |
| """Is the error actually a timeout? Will raise a ReadTimeout or pass""" |
| |
| if isinstance(err, SocketTimeout): |
| raise ReadTimeoutError( |
| self, url, "Read timed out. (read timeout=%s)" % timeout_value |
| ) |
| |
| # See the above comment about EAGAIN in Python 3. In Python 2 we have |
| # to specifically catch it and throw the timeout error |
| if hasattr(err, "errno") and err.errno in _blocking_errnos: |
| raise ReadTimeoutError( |
| self, url, "Read timed out. (read timeout=%s)" % timeout_value |
| ) |
| |
| # Catch possible read timeouts thrown as SSL errors. If not the |
| # case, rethrow the original. We need to do this because of: |
| # http://bugs.python.org/issue10272 |
| if "timed out" in str(err) or "did not complete (read)" in str( |
| err |
| ): # Python < 2.7.4 |
| raise ReadTimeoutError( |
| self, url, "Read timed out. (read timeout=%s)" % timeout_value |
| ) |
| |
| def _make_request( |
| self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw |
| ): |
| """ |
| Perform a request on a given urllib connection object taken from our |
| pool. |
| |
| :param conn: |
| a connection from one of our connection pools |
| |
| :param timeout: |
| Socket timeout in seconds for the request. This can be a |
| float or integer, which will set the same timeout value for |
| the socket connect and the socket read, or an instance of |
| :class:`urllib3.util.Timeout`, which gives you more fine-grained |
| control over your timeouts. |
| """ |
| self.num_requests += 1 |
| |
| timeout_obj = self._get_timeout(timeout) |
| timeout_obj.start_connect() |
| conn.timeout = timeout_obj.connect_timeout |
| |
| # Trigger any extra validation we need to do. |
| try: |
| self._validate_conn(conn) |
| except (SocketTimeout, BaseSSLError) as e: |
| # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. |
| self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) |
| raise |
| |
| # conn.request() calls http.client.*.request, not the method in |
| # urllib3.request. It also calls makefile (recv) on the socket. |
| try: |
| if chunked: |
| conn.request_chunked(method, url, **httplib_request_kw) |
| else: |
| conn.request(method, url, **httplib_request_kw) |
| |
| # We are swallowing BrokenPipeError (errno.EPIPE) since the server is |
| # legitimately able to close the connection after sending a valid response. |
| # With this behaviour, the received response is still readable. |
| except BrokenPipeError: |
| # Python 3 |
| pass |
| except IOError as e: |
| # Python 2 and macOS/Linux |
| # EPIPE and ESHUTDOWN are BrokenPipeError on Python 2, and EPROTOTYPE is needed on macOS |
| # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ |
| if e.errno not in { |
| errno.EPIPE, |
| errno.ESHUTDOWN, |
| errno.EPROTOTYPE, |
| }: |
| raise |
| |
| # Reset the timeout for the recv() on the socket |
| read_timeout = timeout_obj.read_timeout |
| |
| # App Engine doesn't have a sock attr |
| if getattr(conn, "sock", None): |
| # In Python 3 socket.py will catch EAGAIN and return None when you |
| # try and read into the file pointer created by http.client, which |
| # instead raises a BadStatusLine exception. Instead of catching |
| # the exception and assuming all BadStatusLine exceptions are read |
| # timeouts, check for a zero timeout before making the request. |
| if read_timeout == 0: |
| raise ReadTimeoutError( |
| self, url, "Read timed out. (read timeout=%s)" % read_timeout |
| ) |
| if read_timeout is Timeout.DEFAULT_TIMEOUT: |
| conn.sock.settimeout(socket.getdefaulttimeout()) |
| else: # None or a value |
| conn.sock.settimeout(read_timeout) |
| |
| # Receive the response from the server |
| try: |
| try: |
| # Python 2.7, use buffering of HTTP responses |
| httplib_response = conn.getresponse(buffering=True) |
| except TypeError: |
| # Python 3 |
| try: |
| httplib_response = conn.getresponse() |
| except BaseException as e: |
| # Remove the TypeError from the exception chain in |
| # Python 3 (including for exceptions like SystemExit). |
| # Otherwise it looks like a bug in the code. |
| six.raise_from(e, None) |
| except (SocketTimeout, BaseSSLError, SocketError) as e: |
| self._raise_timeout(err=e, url=url, timeout_value=read_timeout) |
| raise |
| |
| # AppEngine doesn't have a version attr. |
| http_version = getattr(conn, "_http_vsn_str", "HTTP/?") |
| log.debug( |
| '%s://%s:%s "%s %s %s" %s %s', |
| self.scheme, |
| self.host, |
| self.port, |
| method, |
| url, |
| http_version, |
| httplib_response.status, |
| httplib_response.length, |
| ) |
| |
| try: |
| assert_header_parsing(httplib_response.msg) |
| except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 |
| log.warning( |
| "Failed to parse headers (url=%s): %s", |
| self._absolute_url(url), |
| hpe, |
| exc_info=True, |
| ) |
| |
| return httplib_response |
| |
| def _absolute_url(self, path): |
| return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url |
| |
| def close(self): |
| """ |
| Close all pooled connections and disable the pool. |
| """ |
| if self.pool is None: |
| return |
| # Disable access to the pool |
| old_pool, self.pool = self.pool, None |
| |
| try: |
| while True: |
| conn = old_pool.get(block=False) |
| if conn: |
| conn.close() |
| |
| except queue.Empty: |
| pass # Done. |
| |
| def is_same_host(self, url): |
| """ |
| Check if the given ``url`` is a member of the same host as this |
| connection pool. |
| """ |
| if url.startswith("/"): |
| return True |
| |
| # TODO: Add optional support for socket.gethostbyname checking. |
| scheme, host, port = get_host(url) |
| if host is not None: |
| host = _normalize_host(host, scheme=scheme) |
| |
| # Use explicit default port for comparison when none is given |
| if self.port and not port: |
| port = port_by_scheme.get(scheme) |
| elif not self.port and port == port_by_scheme.get(scheme): |
| port = None |
| |
| return (scheme, host, port) == (self.scheme, self.host, self.port) |
| |
| def urlopen( |
| self, |
| method, |
| url, |
| body=None, |
| headers=None, |
| retries=None, |
| redirect=True, |
| assert_same_host=True, |
| timeout=_Default, |
| pool_timeout=None, |
| release_conn=None, |
| chunked=False, |
| body_pos=None, |
| **response_kw |
| ): |
| """ |
| Get a connection from the pool and perform an HTTP request. This is the |
| lowest level call for making a request, so you'll need to specify all |
| the raw details. |
| |
| .. note:: |
| |
| More commonly, it's appropriate to use a convenience method provided |
| by :class:`.RequestMethods`, such as :meth:`request`. |
| |
| .. note:: |
| |
| `release_conn` will only behave as expected if |
| `preload_content=False` because we want to make |
| `preload_content=False` the default behaviour someday soon without |
| breaking backwards compatibility. |
| |
| :param method: |
| HTTP request method (such as GET, POST, PUT, etc.) |
| |
| :param url: |
| The URL to perform the request on. |
| |
| :param body: |
| Data to send in the request body, either :class:`str`, :class:`bytes`, |
| an iterable of :class:`str`/:class:`bytes`, or a file-like object. |
| |
| :param headers: |
| Dictionary of custom headers to send, such as User-Agent, |
| If-None-Match, etc. If None, pool headers are used. If provided, |
| these headers completely replace any pool-specific headers. |
| |
| :param retries: |
| Configure the number of retries to allow before raising a |
| :class:`~urllib3.exceptions.MaxRetryError` exception. |
| |
| Pass ``None`` to retry until you receive a response. Pass a |
| :class:`~urllib3.util.retry.Retry` object for fine-grained control |
| over different types of retries. |
| Pass an integer number to retry connection errors that many times, |
| but no other types of errors. Pass zero to never retry. |
| |
| If ``False``, then retries are disabled and any exception is raised |
| immediately. Also, instead of raising a MaxRetryError on redirects, |
| the redirect response will be returned. |
| |
| :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. |
| |
| :param redirect: |
| If True, automatically handle redirects (status codes 301, 302, |
| 303, 307, 308). Each redirect counts as a retry. Disabling retries |
| will disable redirect, too. |
| |
| :param assert_same_host: |
| If ``True``, will make sure that the host of the pool requests is |
| consistent else will raise HostChangedError. When ``False``, you can |
| use the pool on an HTTP proxy and request foreign hosts. |
| |
| :param timeout: |
| If specified, overrides the default timeout for this one |
| request. It may be a float (in seconds) or an instance of |
| :class:`urllib3.util.Timeout`. |
| |
| :param pool_timeout: |
| If set and the pool is set to block=True, then this method will |
| block for ``pool_timeout`` seconds and raise EmptyPoolError if no |
| connection is available within the time period. |
| |
| :param release_conn: |
| If False, then the urlopen call will not release the connection |
| back into the pool once a response is received (but will release if |
| you read the entire contents of the response such as when |
| `preload_content=True`). This is useful if you're not preloading |
| the response's content immediately. You will need to call |
| ``r.release_conn()`` on the response ``r`` to return the connection |
| back into the pool. If None, it takes the value of |
| ``response_kw.get('preload_content', True)``. |
| |
| :param chunked: |
| If True, urllib3 will send the body using chunked transfer |
| encoding. Otherwise, urllib3 will send the body using the standard |
| content-length form. Defaults to False. |
| |
| :param int body_pos: |
| Position to seek to in file-like body in the event of a retry or |
| redirect. Typically this won't need to be set because urllib3 will |
| auto-populate the value when needed. |
| |
| :param \\**response_kw: |
| Additional parameters are passed to |
| :meth:`urllib3.response.HTTPResponse.from_httplib` |
| """ |
| |
| parsed_url = parse_url(url) |
| destination_scheme = parsed_url.scheme |
| |
| if headers is None: |
| headers = self.headers |
| |
| if not isinstance(retries, Retry): |
| retries = Retry.from_int(retries, redirect=redirect, default=self.retries) |
| |
| if release_conn is None: |
| release_conn = response_kw.get("preload_content", True) |
| |
| # Check host |
| if assert_same_host and not self.is_same_host(url): |
| raise HostChangedError(self, url, retries) |
| |
| # Ensure that the URL we're connecting to is properly encoded |
| if url.startswith("/"): |
| url = six.ensure_str(_encode_target(url)) |
| else: |
| url = six.ensure_str(parsed_url.url) |
| |
| conn = None |
| |
| # Track whether `conn` needs to be released before |
| # returning/raising/recursing. Update this variable if necessary, and |
| # leave `release_conn` constant throughout the function. That way, if |
| # the function recurses, the original value of `release_conn` will be |
| # passed down into the recursive call, and its value will be respected. |
| # |
| # See issue #651 [1] for details. |
| # |
| # [1] <https://github.com/urllib3/urllib3/issues/651> |
| release_this_conn = release_conn |
| |
| http_tunnel_required = connection_requires_http_tunnel( |
| self.proxy, self.proxy_config, destination_scheme |
| ) |
| |
| # Merge the proxy headers. Only done when not using HTTP CONNECT. We |
| # have to copy the headers dict so we can safely change it without those |
| # changes being reflected in anyone else's copy. |
| if not http_tunnel_required: |
| headers = headers.copy() |
| headers.update(self.proxy_headers) |
| |
| # Must keep the exception bound to a separate variable or else Python 3 |
| # complains about UnboundLocalError. |
| err = None |
| |
| # Keep track of whether we cleanly exited the except block. This |
| # ensures we do proper cleanup in finally. |
| clean_exit = False |
| |
| # Rewind body position, if needed. Record current position |
| # for future rewinds in the event of a redirect/retry. |
| body_pos = set_file_position(body, body_pos) |
| |
| try: |
| # Request a connection from the queue. |
| timeout_obj = self._get_timeout(timeout) |
| conn = self._get_conn(timeout=pool_timeout) |
| |
| conn.timeout = timeout_obj.connect_timeout |
| |
| is_new_proxy_conn = self.proxy is not None and not getattr( |
| conn, "sock", None |
| ) |
| if is_new_proxy_conn and http_tunnel_required: |
| self._prepare_proxy(conn) |
| |
| # Make the request on the httplib connection object. |
| httplib_response = self._make_request( |
| conn, |
| method, |
| url, |
| timeout=timeout_obj, |
| body=body, |
| headers=headers, |
| chunked=chunked, |
| ) |
| |
| # If we're going to release the connection in ``finally:``, then |
| # the response doesn't need to know about the connection. Otherwise |
| # it will also try to release it and we'll have a double-release |
| # mess. |
| response_conn = conn if not release_conn else None |
| |
| # Pass method to Response for length checking |
| response_kw["request_method"] = method |
| |
| # Import httplib's response into our own wrapper object |
| response = self.ResponseCls.from_httplib( |
| httplib_response, |
| pool=self, |
| connection=response_conn, |
| retries=retries, |
| **response_kw |
| ) |
| |
| # Everything went great! |
| clean_exit = True |
| |
| except EmptyPoolError: |
| # Didn't get a connection from the pool, no need to clean up |
| clean_exit = True |
| release_this_conn = False |
| raise |
| |
| except ( |
| TimeoutError, |
| HTTPException, |
| SocketError, |
| ProtocolError, |
| BaseSSLError, |
| SSLError, |
| CertificateError, |
| ) as e: |
| # Discard the connection for these exceptions. It will be |
| # replaced during the next _get_conn() call. |
| clean_exit = False |
| |
| def _is_ssl_error_message_from_http_proxy(ssl_error): |
| # We're trying to detect the message 'WRONG_VERSION_NUMBER' but |
| # SSLErrors are kinda all over the place when it comes to the message, |
| # so we try to cover our bases here! |
| message = " ".join(re.split("[^a-z]", str(ssl_error).lower())) |
| return ( |
| "wrong version number" in message or "unknown protocol" in message |
| ) |
| |
| # Try to detect a common user error with proxies which is to |
| # set an HTTP proxy to be HTTPS when it should be 'http://' |
| # (ie {'http': 'http://proxy', 'https': 'https://proxy'}) |
| # Instead we add a nice error message and point to a URL. |
| if ( |
| isinstance(e, BaseSSLError) |
| and self.proxy |
| and _is_ssl_error_message_from_http_proxy(e) |
| and conn.proxy |
| and conn.proxy.scheme == "https" |
| ): |
| e = ProxyError( |
| "Your proxy appears to only use HTTP and not HTTPS, " |
| "try changing your proxy URL to be HTTP. See: " |
| "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" |
| "#https-proxy-error-http-proxy", |
| SSLError(e), |
| ) |
| elif isinstance(e, (BaseSSLError, CertificateError)): |
| e = SSLError(e) |
| elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: |
| e = ProxyError("Cannot connect to proxy.", e) |
| elif isinstance(e, (SocketError, HTTPException)): |
| e = ProtocolError("Connection aborted.", e) |
| |
| retries = retries.increment( |
| method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] |
| ) |
| retries.sleep() |
| |
| # Keep track of the error for the retry warning. |
| err = e |
| |
| finally: |
| if not clean_exit: |
| # We hit some kind of exception, handled or otherwise. We need |
| # to throw the connection away unless explicitly told not to. |
| # Close the connection, set the variable to None, and make sure |
| # we put the None back in the pool to avoid leaking it. |
| conn = conn and conn.close() |
| release_this_conn = True |
| |
| if release_this_conn: |
| # Put the connection back to be reused. If the connection is |
| # expired then it will be None, which will get replaced with a |
| # fresh connection during _get_conn. |
| self._put_conn(conn) |
| |
| if not conn: |
| # Try again |
| log.warning( |
| "Retrying (%r) after connection broken by '%r': %s", retries, err, url |
| ) |
| return self.urlopen( |
| method, |
| url, |
| body, |
| headers, |
| retries, |
| redirect, |
| assert_same_host, |
| timeout=timeout, |
| pool_timeout=pool_timeout, |
| release_conn=release_conn, |
| chunked=chunked, |
| body_pos=body_pos, |
| **response_kw |
| ) |
| |
| # Handle redirect? |
| redirect_location = redirect and response.get_redirect_location() |
| if redirect_location: |
| if response.status == 303: |
| method = "GET" |
| |
| try: |
| retries = retries.increment(method, url, response=response, _pool=self) |
| except MaxRetryError: |
| if retries.raise_on_redirect: |
| response.drain_conn() |
| raise |
| return response |
| |
| response.drain_conn() |
| retries.sleep_for_retry(response) |
| log.debug("Redirecting %s -> %s", url, redirect_location) |
| return self.urlopen( |
| method, |
| redirect_location, |
| body, |
| headers, |
| retries=retries, |
| redirect=redirect, |
| assert_same_host=assert_same_host, |
| timeout=timeout, |
| pool_timeout=pool_timeout, |
| release_conn=release_conn, |
| chunked=chunked, |
| body_pos=body_pos, |
| **response_kw |
| ) |
| |
| # Check if we should retry the HTTP response. |
| has_retry_after = bool(response.getheader("Retry-After")) |
| if retries.is_retry(method, response.status, has_retry_after): |
| try: |
| retries = retries.increment(method, url, response=response, _pool=self) |
| except MaxRetryError: |
| if retries.raise_on_status: |
| response.drain_conn() |
| raise |
| return response |
| |
| response.drain_conn() |
| retries.sleep(response) |
| log.debug("Retry: %s", url) |
| return self.urlopen( |
| method, |
| url, |
| body, |
| headers, |
| retries=retries, |
| redirect=redirect, |
| assert_same_host=assert_same_host, |
| timeout=timeout, |
| pool_timeout=pool_timeout, |
| release_conn=release_conn, |
| chunked=chunked, |
| body_pos=body_pos, |
| **response_kw |
| ) |
| |
| return response |
| |
| |
| class HTTPSConnectionPool(HTTPConnectionPool): |
| """ |
| Same as :class:`.HTTPConnectionPool`, but HTTPS. |
| |
| :class:`.HTTPSConnection` uses one of ``assert_fingerprint``, |
| ``assert_hostname`` and ``host`` in this order to verify connections. |
| If ``assert_hostname`` is False, no verification is done. |
| |
| The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, |
| ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` |
| is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade |
| the connection socket into an SSL socket. |
| """ |
| |
| scheme = "https" |
| ConnectionCls = HTTPSConnection |
| |
| def __init__( |
| self, |
| host, |
| port=None, |
| strict=False, |
| timeout=Timeout.DEFAULT_TIMEOUT, |
| maxsize=1, |
| block=False, |
| headers=None, |
| retries=None, |
| _proxy=None, |
| _proxy_headers=None, |
| key_file=None, |
| cert_file=None, |
| cert_reqs=None, |
| key_password=None, |
| ca_certs=None, |
| ssl_version=None, |
| assert_hostname=None, |
| assert_fingerprint=None, |
| ca_cert_dir=None, |
| **conn_kw |
| ): |
| |
| HTTPConnectionPool.__init__( |
| self, |
| host, |
| port, |
| strict, |
| timeout, |
| maxsize, |
| block, |
| headers, |
| retries, |
| _proxy, |
| _proxy_headers, |
| **conn_kw |
| ) |
| |
| self.key_file = key_file |
| self.cert_file = cert_file |
| self.cert_reqs = cert_reqs |
| self.key_password = key_password |
| self.ca_certs = ca_certs |
| self.ca_cert_dir = ca_cert_dir |
| self.ssl_version = ssl_version |
| self.assert_hostname = assert_hostname |
| self.assert_fingerprint = assert_fingerprint |
| |
| def _prepare_conn(self, conn): |
| """ |
| Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` |
| and establish the tunnel if proxy is used. |
| """ |
| |
| if isinstance(conn, VerifiedHTTPSConnection): |
| conn.set_cert( |
| key_file=self.key_file, |
| key_password=self.key_password, |
| cert_file=self.cert_file, |
| cert_reqs=self.cert_reqs, |
| ca_certs=self.ca_certs, |
| ca_cert_dir=self.ca_cert_dir, |
| assert_hostname=self.assert_hostname, |
| assert_fingerprint=self.assert_fingerprint, |
| ) |
| conn.ssl_version = self.ssl_version |
| return conn |
| |
| def _prepare_proxy(self, conn): |
| """ |
| Establishes a tunnel connection through HTTP CONNECT. |
| |
| Tunnel connection is established early because otherwise httplib would |
| improperly set Host: header to proxy's IP:port. |
| """ |
| |
| conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) |
| |
| if self.proxy.scheme == "https": |
| conn.tls_in_tls_required = True |
| |
| conn.connect() |
| |
| def _new_conn(self): |
| """ |
| Return a fresh :class:`http.client.HTTPSConnection`. |
| """ |
| self.num_connections += 1 |
| log.debug( |
| "Starting new HTTPS connection (%d): %s:%s", |
| self.num_connections, |
| self.host, |
| self.port or "443", |
| ) |
| |
| if not self.ConnectionCls or self.ConnectionCls is DummyConnection: |
| raise SSLError( |
| "Can't connect to HTTPS URL because the SSL module is not available." |
| ) |
| |
| actual_host = self.host |
| actual_port = self.port |
| if self.proxy is not None: |
| actual_host = self.proxy.host |
| actual_port = self.proxy.port |
| |
| conn = self.ConnectionCls( |
| host=actual_host, |
| port=actual_port, |
| timeout=self.timeout.connect_timeout, |
| strict=self.strict, |
| cert_file=self.cert_file, |
| key_file=self.key_file, |
| key_password=self.key_password, |
| **self.conn_kw |
| ) |
| |
| return self._prepare_conn(conn) |
| |
| def _validate_conn(self, conn): |
| """ |
| Called right before a request is made, after the socket is created. |
| """ |
| super(HTTPSConnectionPool, self)._validate_conn(conn) |
| |
| # Force connect early to allow us to validate the connection. |
| if not getattr(conn, "sock", None): # AppEngine might not have `.sock` |
| conn.connect() |
| |
| if not conn.is_verified: |
| warnings.warn( |
| ( |
| "Unverified HTTPS request is being made to host '%s'. " |
| "Adding certificate verification is strongly advised. See: " |
| "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" |
| "#ssl-warnings" % conn.host |
| ), |
| InsecureRequestWarning, |
| ) |
| |
| if getattr(conn, "proxy_is_verified", None) is False: |
| warnings.warn( |
| ( |
| "Unverified HTTPS connection done to an HTTPS proxy. " |
| "Adding certificate verification is strongly advised. See: " |
| "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" |
| "#ssl-warnings" |
| ), |
| InsecureRequestWarning, |
| ) |
| |
| |
| def connection_from_url(url, **kw): |
| """ |
| Given a url, return an :class:`.ConnectionPool` instance of its host. |
| |
| This is a shortcut for not having to parse out the scheme, host, and port |
| of the url before creating an :class:`.ConnectionPool` instance. |
| |
| :param url: |
| Absolute URL string that must include the scheme. Port is optional. |
| |
| :param \\**kw: |
| Passes additional parameters to the constructor of the appropriate |
| :class:`.ConnectionPool`. Useful for specifying things like |
| timeout, maxsize, headers, etc. |
| |
| Example:: |
| |
| >>> conn = connection_from_url('http://google.com/') |
| >>> r = conn.request('GET', '/') |
| """ |
| scheme, host, port = get_host(url) |
| port = port or port_by_scheme.get(scheme, 80) |
| if scheme == "https": |
| return HTTPSConnectionPool(host, port=port, **kw) |
| else: |
| return HTTPConnectionPool(host, port=port, **kw) |
| |
| |
| def _normalize_host(host, scheme): |
| """ |
| Normalize hosts for comparisons and use with sockets. |
| """ |
| |
| host = normalize_host(host, scheme) |
| |
| # httplib doesn't like it when we include brackets in IPv6 addresses |
| # Specifically, if we include brackets but also pass the port then |
| # httplib crazily doubles up the square brackets on the Host header. |
| # Instead, we need to make sure we never pass ``None`` as the port. |
| # However, for backward compatibility reasons we can't actually |
| # *assert* that. See http://bugs.python.org/issue28539 |
| if host.startswith("[") and host.endswith("]"): |
| host = host[1:-1] |
| return host |