| from __future__ import absolute_import |
| |
| import time |
| |
| # The default socket timeout, used by httplib to indicate that no timeout was |
| # specified by the user |
| from socket import _GLOBAL_DEFAULT_TIMEOUT |
| |
| from ..exceptions import TimeoutStateError |
| |
| # A sentinel value to indicate that no timeout was specified by the user in |
| # urllib3 |
| _Default = object() |
| |
| |
| # Use time.monotonic if available. |
| current_time = getattr(time, "monotonic", time.time) |
| |
| |
| class Timeout(object): |
| """Timeout configuration. |
| |
| Timeouts can be defined as a default for a pool: |
| |
| .. code-block:: python |
| |
| timeout = Timeout(connect=2.0, read=7.0) |
| http = PoolManager(timeout=timeout) |
| response = http.request('GET', 'http://example.com/') |
| |
| Or per-request (which overrides the default for the pool): |
| |
| .. code-block:: python |
| |
| response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) |
| |
| Timeouts can be disabled by setting all the parameters to ``None``: |
| |
| .. code-block:: python |
| |
| no_timeout = Timeout(connect=None, read=None) |
| response = http.request('GET', 'http://example.com/, timeout=no_timeout) |
| |
| |
| :param total: |
| This combines the connect and read timeouts into one; the read timeout |
| will be set to the time leftover from the connect attempt. In the |
| event that both a connect timeout and a total are specified, or a read |
| timeout and a total are specified, the shorter timeout will be applied. |
| |
| Defaults to None. |
| |
| :type total: int, float, or None |
| |
| :param connect: |
| The maximum amount of time (in seconds) to wait for a connection |
| attempt to a server to succeed. Omitting the parameter will default the |
| connect timeout to the system default, probably `the global default |
| timeout in socket.py |
| <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. |
| None will set an infinite timeout for connection attempts. |
| |
| :type connect: int, float, or None |
| |
| :param read: |
| The maximum amount of time (in seconds) to wait between consecutive |
| read operations for a response from the server. Omitting the parameter |
| will default the read timeout to the system default, probably `the |
| global default timeout in socket.py |
| <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. |
| None will set an infinite timeout. |
| |
| :type read: int, float, or None |
| |
| .. note:: |
| |
| Many factors can affect the total amount of time for urllib3 to return |
| an HTTP response. |
| |
| For example, Python's DNS resolver does not obey the timeout specified |
| on the socket. Other factors that can affect total request time include |
| high CPU load, high swap, the program running at a low priority level, |
| or other behaviors. |
| |
| In addition, the read and total timeouts only measure the time between |
| read operations on the socket connecting the client and the server, |
| not the total amount of time for the request to return a complete |
| response. For most requests, the timeout is raised because the server |
| has not sent the first byte in the specified time. This is not always |
| the case; if a server streams one byte every fifteen seconds, a timeout |
| of 20 seconds will not trigger, even though the request will take |
| several minutes to complete. |
| |
| If your goal is to cut off any request after a set amount of wall clock |
| time, consider having a second "watcher" thread to cut off a slow |
| request. |
| """ |
| |
| #: A sentinel object representing the default timeout value |
| DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT |
| |
| def __init__(self, total=None, connect=_Default, read=_Default): |
| self._connect = self._validate_timeout(connect, "connect") |
| self._read = self._validate_timeout(read, "read") |
| self.total = self._validate_timeout(total, "total") |
| self._start_connect = None |
| |
| def __repr__(self): |
| return "%s(connect=%r, read=%r, total=%r)" % ( |
| type(self).__name__, |
| self._connect, |
| self._read, |
| self.total, |
| ) |
| |
| # __str__ provided for backwards compatibility |
| __str__ = __repr__ |
| |
| @classmethod |
| def _validate_timeout(cls, value, name): |
| """Check that a timeout attribute is valid. |
| |
| :param value: The timeout value to validate |
| :param name: The name of the timeout attribute to validate. This is |
| used to specify in error messages. |
| :return: The validated and casted version of the given value. |
| :raises ValueError: If it is a numeric value less than or equal to |
| zero, or the type is not an integer, float, or None. |
| """ |
| if value is _Default: |
| return cls.DEFAULT_TIMEOUT |
| |
| if value is None or value is cls.DEFAULT_TIMEOUT: |
| return value |
| |
| if isinstance(value, bool): |
| raise ValueError( |
| "Timeout cannot be a boolean value. It must " |
| "be an int, float or None." |
| ) |
| try: |
| float(value) |
| except (TypeError, ValueError): |
| raise ValueError( |
| "Timeout value %s was %s, but it must be an " |
| "int, float or None." % (name, value) |
| ) |
| |
| try: |
| if value <= 0: |
| raise ValueError( |
| "Attempted to set %s timeout to %s, but the " |
| "timeout cannot be set to a value less " |
| "than or equal to 0." % (name, value) |
| ) |
| except TypeError: |
| # Python 3 |
| raise ValueError( |
| "Timeout value %s was %s, but it must be an " |
| "int, float or None." % (name, value) |
| ) |
| |
| return value |
| |
| @classmethod |
| def from_float(cls, timeout): |
| """Create a new Timeout from a legacy timeout value. |
| |
| The timeout value used by httplib.py sets the same timeout on the |
| connect(), and recv() socket requests. This creates a :class:`Timeout` |
| object that sets the individual timeouts to the ``timeout`` value |
| passed to this function. |
| |
| :param timeout: The legacy timeout value. |
| :type timeout: integer, float, sentinel default object, or None |
| :return: Timeout object |
| :rtype: :class:`Timeout` |
| """ |
| return Timeout(read=timeout, connect=timeout) |
| |
| def clone(self): |
| """Create a copy of the timeout object |
| |
| Timeout properties are stored per-pool but each request needs a fresh |
| Timeout object to ensure each one has its own start/stop configured. |
| |
| :return: a copy of the timeout object |
| :rtype: :class:`Timeout` |
| """ |
| # We can't use copy.deepcopy because that will also create a new object |
| # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to |
| # detect the user default. |
| return Timeout(connect=self._connect, read=self._read, total=self.total) |
| |
| def start_connect(self): |
| """Start the timeout clock, used during a connect() attempt |
| |
| :raises urllib3.exceptions.TimeoutStateError: if you attempt |
| to start a timer that has been started already. |
| """ |
| if self._start_connect is not None: |
| raise TimeoutStateError("Timeout timer has already been started.") |
| self._start_connect = current_time() |
| return self._start_connect |
| |
| def get_connect_duration(self): |
| """Gets the time elapsed since the call to :meth:`start_connect`. |
| |
| :return: Elapsed time in seconds. |
| :rtype: float |
| :raises urllib3.exceptions.TimeoutStateError: if you attempt |
| to get duration for a timer that hasn't been started. |
| """ |
| if self._start_connect is None: |
| raise TimeoutStateError( |
| "Can't get connect duration for timer that has not started." |
| ) |
| return current_time() - self._start_connect |
| |
| @property |
| def connect_timeout(self): |
| """Get the value to use when setting a connection timeout. |
| |
| This will be a positive float or integer, the value None |
| (never timeout), or the default system timeout. |
| |
| :return: Connect timeout. |
| :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None |
| """ |
| if self.total is None: |
| return self._connect |
| |
| if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: |
| return self.total |
| |
| return min(self._connect, self.total) |
| |
| @property |
| def read_timeout(self): |
| """Get the value for the read timeout. |
| |
| This assumes some time has elapsed in the connection timeout and |
| computes the read timeout appropriately. |
| |
| If self.total is set, the read timeout is dependent on the amount of |
| time taken by the connect timeout. If the connection time has not been |
| established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be |
| raised. |
| |
| :return: Value to use for the read timeout. |
| :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None |
| :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` |
| has not yet been called on this object. |
| """ |
| if ( |
| self.total is not None |
| and self.total is not self.DEFAULT_TIMEOUT |
| and self._read is not None |
| and self._read is not self.DEFAULT_TIMEOUT |
| ): |
| # In case the connect timeout has not yet been established. |
| if self._start_connect is None: |
| return self._read |
| return max(0, min(self.total - self.get_connect_duration(), self._read)) |
| elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: |
| return max(0, self.total - self.get_connect_duration()) |
| else: |
| return self._read |