1
from __future__ import annotations
3
import http.client as httplib
6
from http.client import HTTPException
8
from socket import error as SocketError
9
from ssl import SSLError as BaseSSLError
10
from test import SHORT_TIMEOUT
11
from unittest.mock import Mock, patch
15
from dummyserver.socketserver import DEFAULT_CA
16
from urllib3 import Retry
17
from urllib3.connection import HTTPConnection
18
from urllib3.connectionpool import (
24
from urllib3.exceptions import (
36
from urllib3.response import HTTPResponse
37
from urllib3.util.ssl_match_hostname import CertificateError
38
from urllib3.util.timeout import _DEFAULT_TIMEOUT, Timeout
40
from .test_response import MockChunkedEncodingResponse, MockSock
43
class HTTPUnixConnection(HTTPConnection):
44
def __init__(self, host: str, timeout: int = 60, **kwargs: typing.Any) -> None:
45
super().__init__("localhost")
46
self.unix_socket = host
47
self.timeout = timeout
51
class HTTPUnixConnectionPool(HTTPConnectionPool):
53
ConnectionCls = HTTPUnixConnection
56
class TestConnectionPool:
58
Tests in this suite should exercise the ConnectionPool functionality
59
without actually making any network requests or connections.
62
@pytest.mark.parametrize(
65
("http://google.com/", "/"),
66
("http://google.com/", "http://google.com/"),
67
("http://google.com/", "http://google.com"),
68
("http://google.com/", "http://google.com/abra/cadabra"),
69
("http://google.com:42/", "http://google.com:42/abracadabra"),
71
("http://google.com:80/", "http://google.com/abracadabra"),
72
("http://google.com/", "http://google.com:80/abracadabra"),
73
("https://google.com:443/", "https://google.com/abracadabra"),
74
("https://google.com/", "https://google.com:443/abracadabra"),
76
"http://[2607:f8b0:4005:805::200e%25eth0]/",
77
"http://[2607:f8b0:4005:805::200e%eth0]/",
80
"https://[2607:f8b0:4005:805::200e%25eth0]:443/",
81
"https://[2607:f8b0:4005:805::200e%eth0]:443/",
83
("http://[::1]/", "http://[::1]"),
85
"http://[2001:558:fc00:200:f816:3eff:fef9:b954%lo]/",
86
"http://[2001:558:fc00:200:f816:3eff:fef9:b954%25lo]",
90
def test_same_host(self, a: str, b: str) -> None:
91
with connection_from_url(a) as c:
92
assert c.is_same_host(b)
94
@pytest.mark.parametrize(
97
("https://google.com/", "http://google.com/"),
98
("http://google.com/", "https://google.com/"),
99
("http://yahoo.com/", "http://google.com/"),
100
("http://google.com:42", "https://google.com/abracadabra"),
101
("http://google.com", "https://google.net/"),
103
("http://google.com:42", "http://google.com"),
104
("https://google.com:42", "https://google.com"),
105
("http://google.com:443", "http://google.com"),
106
("https://google.com:80", "https://google.com"),
107
("http://google.com:443", "https://google.com"),
108
("https://google.com:80", "http://google.com"),
109
("https://google.com:443", "http://google.com"),
110
("http://google.com:80", "https://google.com"),
113
("http://[dead::beef]", "https://[dead::beef%en5]/"),
116
def test_not_same_host(self, a: str, b: str) -> None:
117
with connection_from_url(a) as c:
118
assert not c.is_same_host(b)
120
with connection_from_url(b) as c:
121
assert not c.is_same_host(a)
123
@pytest.mark.parametrize(
127
("google.com", "http://google.com/"),
128
("google.com", "http://google.com"),
129
("google.com", "http://google.com/abra/cadabra"),
131
("google.com", "http://google.com:80/abracadabra"),
134
def test_same_host_no_port_http(self, a: str, b: str) -> None:
137
with HTTPConnectionPool(a) as c:
138
assert c.is_same_host(b)
140
@pytest.mark.parametrize(
144
("google.com", "https://google.com/"),
145
("google.com", "https://google.com"),
146
("google.com", "https://google.com/abra/cadabra"),
148
("google.com", "https://google.com:443/abracadabra"),
151
def test_same_host_no_port_https(self, a: str, b: str) -> None:
154
with HTTPSConnectionPool(a) as c:
155
assert c.is_same_host(b)
157
@pytest.mark.parametrize(
160
("google.com", "https://google.com/"),
161
("yahoo.com", "http://google.com/"),
162
("google.com", "https://google.net/"),
163
("google.com", "http://google.com./"),
166
def test_not_same_host_no_port_http(self, a: str, b: str) -> None:
167
with HTTPConnectionPool(a) as c:
168
assert not c.is_same_host(b)
170
with HTTPConnectionPool(b) as c:
171
assert not c.is_same_host(a)
173
@pytest.mark.parametrize(
176
("google.com", "http://google.com/"),
177
("yahoo.com", "https://google.com/"),
178
("google.com", "https://google.net/"),
179
("google.com", "https://google.com./"),
182
def test_not_same_host_no_port_https(self, a: str, b: str) -> None:
183
with HTTPSConnectionPool(a) as c:
184
assert not c.is_same_host(b)
186
with HTTPSConnectionPool(b) as c:
187
assert not c.is_same_host(a)
189
@pytest.mark.parametrize(
192
("%2Fvar%2Frun%2Fdocker.sock", "http+unix://%2Fvar%2Frun%2Fdocker.sock"),
193
("%2Fvar%2Frun%2Fdocker.sock", "http+unix://%2Fvar%2Frun%2Fdocker.sock/"),
195
"%2Fvar%2Frun%2Fdocker.sock",
196
"http+unix://%2Fvar%2Frun%2Fdocker.sock/abracadabra",
198
("%2Ftmp%2FTEST.sock", "http+unix://%2Ftmp%2FTEST.sock"),
199
("%2Ftmp%2FTEST.sock", "http+unix://%2Ftmp%2FTEST.sock/"),
200
("%2Ftmp%2FTEST.sock", "http+unix://%2Ftmp%2FTEST.sock/abracadabra"),
203
def test_same_host_custom_protocol(self, a: str, b: str) -> None:
204
with HTTPUnixConnectionPool(a) as c:
205
assert c.is_same_host(b)
207
@pytest.mark.parametrize(
210
("%2Ftmp%2Ftest.sock", "http+unix://%2Ftmp%2FTEST.sock"),
211
("%2Ftmp%2Ftest.sock", "http+unix://%2Ftmp%2FTEST.sock/"),
212
("%2Ftmp%2Ftest.sock", "http+unix://%2Ftmp%2FTEST.sock/abracadabra"),
213
("%2Fvar%2Frun%2Fdocker.sock", "http+unix://%2Ftmp%2FTEST.sock"),
216
def test_not_same_host_custom_protocol(self, a: str, b: str) -> None:
217
with HTTPUnixConnectionPool(a) as c:
218
assert not c.is_same_host(b)
220
def test_max_connections(self) -> None:
221
with HTTPConnectionPool(host="localhost", maxsize=1, block=True) as pool:
222
pool._get_conn(timeout=SHORT_TIMEOUT)
224
with pytest.raises(EmptyPoolError):
225
pool._get_conn(timeout=SHORT_TIMEOUT)
227
with pytest.raises(EmptyPoolError):
228
pool.request("GET", "/", pool_timeout=SHORT_TIMEOUT)
230
assert pool.num_connections == 1
232
def test_put_conn_when_pool_is_full_nonblocking(
233
self, caplog: pytest.LogCaptureFixture
236
If maxsize = n and we _put_conn n + 1 conns, the n + 1th conn will
237
get closed and will not get added to the pool.
239
with HTTPConnectionPool(host="localhost", maxsize=1, block=False) as pool:
240
conn1 = pool._get_conn()
243
conn2 = pool._get_conn()
245
with patch.object(conn1, "close") as conn1_close:
246
with patch.object(conn2, "close") as conn2_close:
247
pool._put_conn(conn1)
248
pool._put_conn(conn2)
250
assert conn1_close.called is False
251
assert conn2_close.called is True
253
assert conn1 == pool._get_conn()
254
assert conn2 != pool._get_conn()
256
assert pool.num_connections == 3
257
assert "Connection pool is full, discarding connection" in caplog.text
258
assert "Connection pool size: 1" in caplog.text
260
def test_put_conn_when_pool_is_full_blocking(self) -> None:
262
If maxsize = n and we _put_conn n + 1 conns, the n + 1th conn will
263
cause a FullPoolError.
265
with HTTPConnectionPool(host="localhost", maxsize=1, block=True) as pool:
266
conn1 = pool._get_conn()
267
conn2 = pool._new_conn()
269
with patch.object(conn1, "close") as conn1_close:
270
with patch.object(conn2, "close") as conn2_close:
271
pool._put_conn(conn1)
272
with pytest.raises(FullPoolError):
273
pool._put_conn(conn2)
275
assert conn1_close.called is False
276
assert conn2_close.called is True
278
assert conn1 == pool._get_conn()
280
def test_put_conn_closed_pool(self) -> None:
281
with HTTPConnectionPool(host="localhost", maxsize=1, block=True) as pool:
282
conn1 = pool._get_conn()
283
with patch.object(conn1, "close") as conn1_close:
286
assert pool.pool is None
290
pool._put_conn(conn1)
292
assert conn1_close.called is True
294
def test_exception_str(self) -> None:
296
str(EmptyPoolError(HTTPConnectionPool(host="localhost"), "Test."))
297
== "HTTPConnectionPool(host='localhost', port=None): Test."
300
def test_retry_exception_str(self) -> None:
302
str(MaxRetryError(HTTPConnectionPool(host="localhost"), "Test.", None))
303
== "HTTPConnectionPool(host='localhost', port=None): "
304
"Max retries exceeded with url: Test. (Caused by None)"
307
err = SocketError("Test")
312
str(MaxRetryError(HTTPConnectionPool(host="localhost"), "Test.", err))
313
== "HTTPConnectionPool(host='localhost', port=None): "
314
"Max retries exceeded with url: Test. "
315
"(Caused by %r)" % err
318
def test_pool_size(self) -> None:
320
with HTTPConnectionPool(
321
host="localhost", maxsize=POOL_SIZE, block=True
325
exception: type[BaseException],
326
expect: type[BaseException],
327
reason: type[BaseException] | None = None,
329
with patch.object(pool, "_make_request", side_effect=exception()):
330
with pytest.raises(expect) as excinfo:
331
pool.request("GET", "/")
332
if reason is not None:
333
assert isinstance(excinfo.value.reason, reason)
334
assert pool.pool is not None
335
assert pool.pool.qsize() == POOL_SIZE
339
_test(BaseSSLError, MaxRetryError, SSLError)
340
_test(CertificateError, MaxRetryError, SSLError)
346
with patch.object(pool, "_make_request", side_effect=HTTPException()):
347
with pytest.raises(MaxRetryError):
348
pool.request("GET", "/", retries=1, pool_timeout=SHORT_TIMEOUT)
349
assert pool.pool is not None
350
assert pool.pool.qsize() == POOL_SIZE
352
def test_empty_does_not_put_conn(self) -> None:
353
"""Do not put None back in the pool if the pool was empty"""
355
with HTTPConnectionPool(host="localhost", maxsize=1, block=True) as pool:
357
pool, "_get_conn", side_effect=EmptyPoolError(pool, "Pool is empty")
362
side_effect=AssertionError("Unexpected _put_conn"),
364
with pytest.raises(EmptyPoolError):
365
pool.request("GET", "/")
367
def test_assert_same_host(self) -> None:
368
with connection_from_url("http://google.com:80") as c:
369
with pytest.raises(HostChangedError):
370
c.request("GET", "http://yahoo.com:80", assert_same_host=True)
372
def test_pool_close(self) -> None:
373
pool = connection_from_url("http://google.com:80")
376
conn1 = pool._get_conn()
377
conn2 = pool._get_conn()
378
conn3 = pool._get_conn()
379
pool._put_conn(conn1)
380
pool._put_conn(conn2)
382
old_pool_queue = pool.pool
385
assert pool.pool is None
387
with pytest.raises(ClosedPoolError):
390
pool._put_conn(conn3)
392
with pytest.raises(ClosedPoolError):
395
with pytest.raises(Empty):
396
assert old_pool_queue is not None
397
old_pool_queue.get(block=False)
399
def test_pool_close_twice(self) -> None:
400
pool = connection_from_url("http://google.com:80")
403
conn1 = pool._get_conn()
404
conn2 = pool._get_conn()
405
pool._put_conn(conn1)
406
pool._put_conn(conn2)
409
assert pool.pool is None
413
except AttributeError:
414
pytest.fail("Pool of the ConnectionPool is None and has no attribute get.")
416
def test_pool_timeouts(self) -> None:
417
with HTTPConnectionPool(host="localhost") as pool:
418
conn = pool._new_conn()
419
assert conn.__class__ == HTTPConnection
420
assert pool.timeout.__class__ == Timeout
421
assert pool.timeout._read == _DEFAULT_TIMEOUT
422
assert pool.timeout._connect == _DEFAULT_TIMEOUT
423
assert pool.timeout.total is None
425
pool = HTTPConnectionPool(host="localhost", timeout=SHORT_TIMEOUT)
426
assert pool.timeout._read == SHORT_TIMEOUT
427
assert pool.timeout._connect == SHORT_TIMEOUT
428
assert pool.timeout.total is None
430
def test_no_host(self) -> None:
431
with pytest.raises(LocationValueError):
432
HTTPConnectionPool(None)
434
def test_contextmanager(self) -> None:
435
with connection_from_url("http://google.com:80") as pool:
437
conn1 = pool._get_conn()
438
conn2 = pool._get_conn()
439
conn3 = pool._get_conn()
440
pool._put_conn(conn1)
441
pool._put_conn(conn2)
443
old_pool_queue = pool.pool
445
assert pool.pool is None
446
with pytest.raises(ClosedPoolError):
449
pool._put_conn(conn3)
450
with pytest.raises(ClosedPoolError):
452
with pytest.raises(Empty):
453
assert old_pool_queue is not None
454
old_pool_queue.get(block=False)
456
def test_url_from_pool(self) -> None:
457
with connection_from_url("http://google.com:80") as pool:
458
path = "path?query=foo"
459
assert f"http://google.com:80/{path}" == _url_from_pool(pool, path)
461
def test_ca_certs_default_cert_required(self) -> None:
462
with connection_from_url("https://google.com:80", ca_certs=DEFAULT_CA) as pool:
463
conn = pool._get_conn()
464
assert conn.cert_reqs == ssl.CERT_REQUIRED
466
def test_cleanup_on_extreme_connection_error(self) -> None:
468
This test validates that we clean up properly even on exceptions that
469
we'd not otherwise catch, i.e. those that inherit from BaseException
470
like KeyboardInterrupt or gevent.Timeout. See #805 for more details.
473
class RealBad(BaseException):
476
def kaboom(*args: typing.Any, **kwargs: typing.Any) -> None:
479
with connection_from_url("http://localhost:80") as c:
480
with patch.object(c, "_make_request", kaboom):
481
assert c.pool is not None
482
initial_pool_size = c.pool.qsize()
487
c.urlopen("GET", "/", release_conn=False)
491
new_pool_size = c.pool.qsize()
492
assert initial_pool_size == new_pool_size
494
def test_release_conn_param_is_respected_after_http_error_retry(self) -> None:
495
"""For successful ```urlopen(release_conn=False)```,
496
the connection isn't released, even after a retry.
498
This is a regression test for issue #651 [1], where the connection
499
would be released if the initial request failed, even if a retry
502
[1] <https://github.com/urllib3/urllib3/issues/651>
505
class _raise_once_make_request_function:
506
"""Callable that can mimic `_make_request()`.
508
Raises the given exception on its first call, but returns a
509
successful response on subsequent calls.
513
self, ex: type[BaseException], pool: HTTPConnectionPool
516
self._ex: type[BaseException] | None = ex
521
conn: HTTPConnection,
526
**kwargs: typing.Any,
529
ex, self._ex = self._ex, None
531
httplib_response = httplib.HTTPResponse(MockSock)
532
httplib_response.fp = MockChunkedEncodingResponse([b"f", b"o", b"o"])
533
httplib_response.headers = httplib_response.msg = httplib.HTTPMessage()
535
response_conn: HTTPConnection | None = kwargs.get("response_conn")
537
response = HTTPResponse(
538
body=httplib_response,
539
headers=httplib_response.headers,
540
status=httplib_response.status,
541
version=httplib_response.version,
542
reason=httplib_response.reason,
543
original_response=httplib_response,
545
request_method=method,
547
preload_content=False,
548
connection=response_conn,
553
def _test(exception: type[BaseException]) -> None:
554
with HTTPConnectionPool(host="localhost", maxsize=1, block=True) as pool:
561
_raise_once_make_request_function(exception, pool),
563
response = pool.urlopen(
568
preload_content=False,
571
assert pool.pool is not None
572
assert pool.pool.qsize() == 0
573
assert pool.num_connections == 2
574
assert response.connection is not None
576
response.release_conn()
577
assert pool.pool.qsize() == 1
578
assert response.connection is None
586
def test_read_timeout_0_does_not_raise_bad_status_line_error(self) -> None:
587
with HTTPConnectionPool(host="localhost", maxsize=1) as pool:
588
conn = Mock(spec=HTTPConnection)
590
conn.is_closed = False
591
with patch.object(Timeout, "read_timeout", 0):
592
timeout = Timeout(1, 1, 1)
593
with pytest.raises(ReadTimeoutError):
594
pool._make_request(conn, "", "", timeout=timeout)