| 1 | # SPDX-License-Identifier: MIT |
| 2 | from __future__ import absolute_import |
| 3 | # The default socket timeout, used by httplib to indicate that no timeout was |
| 4 | # specified by the user |
| 5 | from socket import _GLOBAL_DEFAULT_TIMEOUT |
| 6 | import time |
| 7 | |
| 8 | from ..exceptions import TimeoutStateError |
| 9 | |
| 10 | # A sentinel value to indicate that no timeout was specified by the user in |
| 11 | # urllib3 |
| 12 | _Default = object() |
| 13 | |
| 14 | |
| 15 | # Use time.monotonic if available. |
| 16 | current_time = getattr(time, "monotonic", time.time) |
| 17 | |
| 18 | |
| 19 | class Timeout(object): |
| 20 | """ Timeout configuration. |
| 21 | |
| 22 | Timeouts can be defined as a default for a pool:: |
| 23 | |
| 24 | timeout = Timeout(connect=2.0, read=7.0) |
| 25 | http = PoolManager(timeout=timeout) |
| 26 | response = http.request('GET', 'http://example.com/') |
| 27 | |
| 28 | Or per-request (which overrides the default for the pool):: |
| 29 | |
| 30 | response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) |
| 31 | |
| 32 | Timeouts can be disabled by setting all the parameters to ``None``:: |
| 33 | |
| 34 | no_timeout = Timeout(connect=None, read=None) |
| 35 | response = http.request('GET', 'http://example.com/, timeout=no_timeout) |
| 36 | |
| 37 | |
| 38 | :param total: |
| 39 | This combines the connect and read timeouts into one; the read timeout |
| 40 | will be set to the time leftover from the connect attempt. In the |
| 41 | event that both a connect timeout and a total are specified, or a read |
| 42 | timeout and a total are specified, the shorter timeout will be applied. |
| 43 | |
| 44 | Defaults to None. |
| 45 | |
| 46 | :type total: integer, float, or None |
| 47 | |
| 48 | :param connect: |
| 49 | The maximum amount of time to wait for a connection attempt to a server |
| 50 | to succeed. Omitting the parameter will default the connect timeout to |
| 51 | the system default, probably `the global default timeout in socket.py |
| 52 | <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. |
| 53 | None will set an infinite timeout for connection attempts. |
| 54 | |
| 55 | :type connect: integer, float, or None |
| 56 | |
| 57 | :param read: |
| 58 | The maximum amount of time to wait between consecutive |
| 59 | read operations for a response from the server. Omitting |
| 60 | the parameter will default the read timeout to the system |
| 61 | default, probably `the global default timeout in socket.py |
| 62 | <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. |
| 63 | None will set an infinite timeout. |
| 64 | |
| 65 | :type read: integer, float, or None |
| 66 | |
| 67 | .. note:: |
| 68 | |
| 69 | Many factors can affect the total amount of time for urllib3 to return |
| 70 | an HTTP response. |
| 71 | |
| 72 | For example, Python's DNS resolver does not obey the timeout specified |
| 73 | on the socket. Other factors that can affect total request time include |
| 74 | high CPU load, high swap, the program running at a low priority level, |
| 75 | or other behaviors. |
| 76 | |
| 77 | In addition, the read and total timeouts only measure the time between |
| 78 | read operations on the socket connecting the client and the server, |
| 79 | not the total amount of time for the request to return a complete |
| 80 | response. For most requests, the timeout is raised because the server |
| 81 | has not sent the first byte in the specified time. This is not always |
| 82 | the case; if a server streams one byte every fifteen seconds, a timeout |
| 83 | of 20 seconds will not trigger, even though the request will take |
| 84 | several minutes to complete. |
| 85 | |
| 86 | If your goal is to cut off any request after a set amount of wall clock |
| 87 | time, consider having a second "watcher" thread to cut off a slow |
| 88 | request. |
| 89 | """ |
| 90 | |
| 91 | #: A sentinel object representing the default timeout value |
| 92 | DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT |
| 93 | |
| 94 | def __init__(self, total=None, connect=_Default, read=_Default): |
| 95 | self._connect = self._validate_timeout(connect, 'connect') |
| 96 | self._read = self._validate_timeout(read, 'read') |
| 97 | self.total = self._validate_timeout(total, 'total') |
| 98 | self._start_connect = None |
| 99 | |
| 100 | def __str__(self): |
| 101 | return '%s(connect=%r, read=%r, total=%r)' % ( |
| 102 | type(self).__name__, self._connect, self._read, self.total) |
| 103 | |
| 104 | @classmethod |
| 105 | def _validate_timeout(cls, value, name): |
| 106 | """ Check that a timeout attribute is valid. |
| 107 | |
| 108 | :param value: The timeout value to validate |
| 109 | :param name: The name of the timeout attribute to validate. This is |
| 110 | used to specify in error messages. |
| 111 | :return: The validated and casted version of the given value. |
| 112 | :raises ValueError: If it is a numeric value less than or equal to |
| 113 | zero, or the type is not an integer, float, or None. |
| 114 | """ |
| 115 | if value is _Default: |
| 116 | return cls.DEFAULT_TIMEOUT |
| 117 | |
| 118 | if value is None or value is cls.DEFAULT_TIMEOUT: |
| 119 | return value |
| 120 | |
| 121 | if isinstance(value, bool): |
| 122 | raise ValueError("Timeout cannot be a boolean value. It must " |
| 123 | "be an int, float or None.") |
| 124 | try: |
| 125 | float(value) |
| 126 | except (TypeError, ValueError): |
| 127 | raise ValueError("Timeout value %s was %s, but it must be an " |
| 128 | "int, float or None." % (name, value)) |
| 129 | |
| 130 | try: |
| 131 | if value <= 0: |
| 132 | raise ValueError("Attempted to set %s timeout to %s, but the " |
| 133 | "timeout cannot be set to a value less " |
| 134 | "than or equal to 0." % (name, value)) |
| 135 | except TypeError: # Python 3 |
| 136 | raise ValueError("Timeout value %s was %s, but it must be an " |
| 137 | "int, float or None." % (name, value)) |
| 138 | |
| 139 | return value |
| 140 | |
| 141 | @classmethod |
| 142 | def from_float(cls, timeout): |
| 143 | """ Create a new Timeout from a legacy timeout value. |
| 144 | |
| 145 | The timeout value used by httplib.py sets the same timeout on the |
| 146 | connect(), and recv() socket requests. This creates a :class:`Timeout` |
| 147 | object that sets the individual timeouts to the ``timeout`` value |
| 148 | passed to this function. |
| 149 | |
| 150 | :param timeout: The legacy timeout value. |
| 151 | :type timeout: integer, float, sentinel default object, or None |
| 152 | :return: Timeout object |
| 153 | :rtype: :class:`Timeout` |
| 154 | """ |
| 155 | return Timeout(read=timeout, connect=timeout) |
| 156 | |
| 157 | def clone(self): |
| 158 | """ Create a copy of the timeout object |
| 159 | |
| 160 | Timeout properties are stored per-pool but each request needs a fresh |
| 161 | Timeout object to ensure each one has its own start/stop configured. |
| 162 | |
| 163 | :return: a copy of the timeout object |
| 164 | :rtype: :class:`Timeout` |
| 165 | """ |
| 166 | # We can't use copy.deepcopy because that will also create a new object |
| 167 | # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to |
| 168 | # detect the user default. |
| 169 | return Timeout(connect=self._connect, read=self._read, |
| 170 | total=self.total) |
| 171 | |
| 172 | def start_connect(self): |
| 173 | """ Start the timeout clock, used during a connect() attempt |
| 174 | |
| 175 | :raises urllib3.exceptions.TimeoutStateError: if you attempt |
| 176 | to start a timer that has been started already. |
| 177 | """ |
| 178 | if self._start_connect is not None: |
| 179 | raise TimeoutStateError("Timeout timer has already been started.") |
| 180 | self._start_connect = current_time() |
| 181 | return self._start_connect |
| 182 | |
| 183 | def get_connect_duration(self): |
| 184 | """ Gets the time elapsed since the call to :meth:`start_connect`. |
| 185 | |
| 186 | :return: Elapsed time. |
| 187 | :rtype: float |
| 188 | :raises urllib3.exceptions.TimeoutStateError: if you attempt |
| 189 | to get duration for a timer that hasn't been started. |
| 190 | """ |
| 191 | if self._start_connect is None: |
| 192 | raise TimeoutStateError("Can't get connect duration for timer " |
| 193 | "that has not started.") |
| 194 | return current_time() - self._start_connect |
| 195 | |
| 196 | @property |
| 197 | def connect_timeout(self): |
| 198 | """ Get the value to use when setting a connection timeout. |
| 199 | |
| 200 | This will be a positive float or integer, the value None |
| 201 | (never timeout), or the default system timeout. |
| 202 | |
| 203 | :return: Connect timeout. |
| 204 | :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None |
| 205 | """ |
| 206 | if self.total is None: |
| 207 | return self._connect |
| 208 | |
| 209 | if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: |
| 210 | return self.total |
| 211 | |
| 212 | return min(self._connect, self.total) |
| 213 | |
| 214 | @property |
| 215 | def read_timeout(self): |
| 216 | """ Get the value for the read timeout. |
| 217 | |
| 218 | This assumes some time has elapsed in the connection timeout and |
| 219 | computes the read timeout appropriately. |
| 220 | |
| 221 | If self.total is set, the read timeout is dependent on the amount of |
| 222 | time taken by the connect timeout. If the connection time has not been |
| 223 | established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be |
| 224 | raised. |
| 225 | |
| 226 | :return: Value to use for the read timeout. |
| 227 | :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None |
| 228 | :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` |
| 229 | has not yet been called on this object. |
| 230 | """ |
| 231 | if (self.total is not None and |
| 232 | self.total is not self.DEFAULT_TIMEOUT and |
| 233 | self._read is not None and |
| 234 | self._read is not self.DEFAULT_TIMEOUT): |
| 235 | # In case the connect timeout has not yet been established. |
| 236 | if self._start_connect is None: |
| 237 | return self._read |
| 238 | return max(0, min(self.total - self.get_connect_duration(), |
| 239 | self._read)) |
| 240 | elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: |
| 241 | return max(0, self.total - self.get_connect_duration()) |
| 242 | else: |
| 243 | return self._read |