async def test_retries_exceeded(server: Server) -> None: """ When retries are enabled and connecting failures more than the configured number of retries, connect exceptions are raised. """ backend = AsyncMockBackend() retries = 1 async with httpcore.AsyncConnectionPool( retries=retries, max_keepalive_connections=0, backend=backend ) as http: response = await http.handle_async_request( method=b"GET", url=(b"http", *server.netloc, b"/"), headers=[server.host_header], stream=httpcore.ByteStream(b""), extensions={}, ) status_code, _, stream, _ = response assert status_code == 200 await read_body(stream) # First failure is retried on, second one isn't. backend.push(httpcore.ConnectError(), httpcore.ConnectTimeout()) with pytest.raises(httpcore.ConnectTimeout): await http.handle_async_request( method=b"GET", url=(b"http", *server.netloc, b"/"), headers=[server.host_header], stream=httpcore.ByteStream(b""), extensions={}, )
async def test_connection_pool_get_connection_info( http2: bool, keepalive_expiry: float, expected_during_active: dict, expected_during_idle: dict, ) -> None: async with httpcore.AsyncConnectionPool( http2=http2, keepalive_expiry=keepalive_expiry ) as http: method = b"GET" url = (b"https", b"example.org", 443, b"/") headers = [(b"host", b"example.org")] _, _, _, _, stream_1 = await http.request(method, url, headers) _, _, _, _, stream_2 = await http.request(method, url, headers) try: stats = await http.get_connection_info() assert stats == expected_during_active finally: await read_body(stream_1) await read_body(stream_2) stats = await http.get_connection_info() assert stats == expected_during_idle stats = await http.get_connection_info() assert stats == {}
async def test_request_unsupported_protocol(backend: str) -> None: async with httpcore.AsyncConnectionPool(backend=backend) as http: method = b"GET" url = (b"ftp", b"example.org", 443, b"/") headers = [(b"host", b"example.org")] with pytest.raises(httpcore.UnsupportedProtocol): await http.arequest(method, url, headers)
async def client(): transport = httpcore.AsyncConnectionPool(uds=SOCKPATH) for _ in range(40): async with httpx.AsyncClient(transport=transport) as client: r = await client.get("http://localhost/sleep/0.1") assert r.status_code == 200 assert r.text == f"Slept 0.1 seconds.\n"
async def test_max_keepalive_connections_handled_correctly( max_keepalive: int, connections_number: int, backend: str, server: Server) -> None: async with httpcore.AsyncConnectionPool( max_keepalive_connections=max_keepalive, keepalive_expiry=60, backend=backend) as http: method = b"GET" url = (b"http", *server.netloc, b"/") headers = [server.host_header] connections_streams = [] for _ in range(connections_number): _, _, stream, _ = await http.arequest(method, url, headers) connections_streams.append(stream) try: for i in range(len(connections_streams)): await read_body(connections_streams[i]) finally: stats = await http.get_connection_info() connections_in_pool = next(iter(stats.values())) assert len(connections_in_pool) == min(connections_number, max_keepalive)
def init_transport( self, verify: VerifyTypes = True, cert: CertTypes = None, http2: bool = False, pool_limits: PoolLimits = DEFAULT_POOL_LIMITS, transport: httpcore.AsyncHTTPTransport = None, app: typing.Callable = None, trust_env: bool = True, ) -> httpcore.AsyncHTTPTransport: if transport is not None: return transport if app is not None: return ASGITransport(app=app) ssl_context = SSLConfig(verify=verify, cert=cert, trust_env=trust_env).ssl_context return httpcore.AsyncConnectionPool( ssl_context=ssl_context, max_keepalive=pool_limits.max_keepalive, max_connections=pool_limits.max_connections, http2=http2, )
async def test_http_request_reuse_connection() -> None: async with httpcore.AsyncConnectionPool() as http: method = b"GET" url = (b"http", b"example.org", 80, b"/") headers = [(b"host", b"example.org")] http_version, status_code, reason, headers, stream = await http.request( method, url, headers) body = await read_body(stream) assert http_version == b"HTTP/1.1" assert status_code == 200 assert reason == b"OK" assert len(http._connections[url[:3]]) == 1 # type: ignore method = b"GET" url = (b"http", b"example.org", 80, b"/") headers = [(b"host", b"example.org")] http_version, status_code, reason, headers, stream = await http.request( method, url, headers) body = await read_body(stream) assert http_version == b"HTTP/1.1" assert status_code == 200 assert reason == b"OK" assert len(http._connections[url[:3]]) == 1 # type: ignore
async def test_https_request_reuse_connection(backend: str, https_server: Server) -> None: async with httpcore.AsyncConnectionPool(backend=backend) as http: method = b"GET" url = (b"https", *https_server.netloc, b"/") headers = [https_server.host_header] status_code, headers, stream, ext = await http.arequest( method, url, headers) await read_body(stream) assert status_code == 200 reason = "OK" if https_server.sends_reason else "" assert ext == {"http_version": "HTTP/1.1", "reason": reason} assert len(http._connections[url[:3]]) == 1 # type: ignore method = b"GET" url = (b"https", *https_server.netloc, b"/") headers = [https_server.host_header] status_code, headers, stream, ext = await http.arequest( method, url, headers) await read_body(stream) assert status_code == 200 reason = "OK" if https_server.sends_reason else "" assert ext == {"http_version": "HTTP/1.1", "reason": reason} assert len(http._connections[url[:3]]) == 1 # type: ignore
async def test_http_request_cannot_reuse_dropped_connection( backend: str, server: Server ) -> None: async with httpcore.AsyncConnectionPool(backend=backend) as http: method = b"GET" url = (b"http", *server.netloc, b"/") headers = [server.host_header] status_code, headers, stream, ext = await http.arequest(method, url, headers) await read_body(stream) assert status_code == 200 assert ext == {"http_version": "HTTP/1.1", "reason": "OK"} assert len(http._connections[url[:3]]) == 1 # type: ignore # Mock the connection as having been dropped. connection = list(http._connections[url[:3]])[0] # type: ignore connection.is_connection_dropped = lambda: True # type: ignore method = b"GET" url = (b"http", *server.netloc, b"/") headers = [server.host_header] status_code, headers, stream, ext = await http.arequest(method, url, headers) await read_body(stream) assert status_code == 200 assert ext == {"http_version": "HTTP/1.1", "reason": "OK"} assert len(http._connections[url[:3]]) == 1 # type: ignore
async def test_http_request_cannot_reuse_dropped_connection() -> None: async with httpcore.AsyncConnectionPool() as http: method = b"GET" url = (b"http", b"example.org", 80, b"/") headers = [(b"host", b"example.org")] http_version, status_code, reason, headers, stream = await http.request( method, url, headers) body = await read_body(stream) assert http_version == b"HTTP/1.1" assert status_code == 200 assert reason == b"OK" assert len(http._connections[url[:3]]) == 1 # type: ignore # Mock the connection as having been dropped. connection = list(http._connections[url[:3]])[0] # type: ignore connection.is_connection_dropped = lambda: True method = b"GET" url = (b"http", b"example.org", 80, b"/") headers = [(b"host", b"example.org")] http_version, status_code, reason, headers, stream = await http.request( method, url, headers) body = await read_body(stream) assert http_version == b"HTTP/1.1" assert status_code == 200 assert reason == b"OK" assert len(http._connections[url[:3]]) == 1 # type: ignore
async def test_broken_socket_detection_many_open_files(backend: str, server: Server) -> None: """ Regression test for: https://github.com/encode/httpcore/issues/182 """ async with httpcore.AsyncConnectionPool(backend=backend) as http: # * First attempt will be successful because it will grab the last # available fd before what select() supports on the platform. # * Second attempt would have failed without a fix, due to a "filedescriptor # out of range in select()" exception. for _ in range(2): ( status_code, response_headers, stream, extensions, ) = await http.handle_async_request( method=b"GET", url=(b"http", *server.netloc, b"/"), headers=[server.host_header], stream=httpcore.ByteStream(b""), extensions={}, ) await read_body(stream) assert status_code == 200 reason_phrase = b"OK" if server.sends_reason else b"" assert extensions == { "http_version": b"HTTP/1.1", "reason_phrase": reason_phrase, } origin = (b"http", *server.netloc) assert len(http._connections[origin]) == 1 # type: ignore
async def test_httpcore_request(): async with MockTransport() as transport: transport.add("GET", "https://foo.bar/", content="foobar") with httpcore.SyncConnectionPool() as http: (http_version, status_code, reason_phrase, headers, stream,) = http.request( method=b"GET", url=(b"https", b"foo.bar", 443, b"/"), ) body = b"".join([chunk for chunk in stream]) stream.close() assert body == b"foobar" async with httpcore.AsyncConnectionPool() as http: ( http_version, status_code, reason_phrase, headers, stream, ) = await http.request( method=b"GET", url=(b"https", b"foo.bar", 443, b"/"), ) body = b"".join([chunk async for chunk in stream]) await stream.aclose() assert body == b"foobar"
async def test_connection_pool_get_connection_info( http2: bool, keepalive_expiry: float, expected_during_active: dict, expected_during_idle: dict, backend: str, https_server: Server, ) -> None: async with httpcore.AsyncConnectionPool( http2=http2, keepalive_expiry=keepalive_expiry, backend=backend ) as http: method = b"GET" url = (b"https", *https_server.netloc, b"/") headers = [https_server.host_header] _, _, stream_1, _ = await http.arequest(method, url, headers) _, _, stream_2, _ = await http.arequest(method, url, headers) try: stats = await http.get_connection_info() assert stats == expected_during_active finally: await read_body(stream_1) await read_body(stream_2) stats = await http.get_connection_info() assert stats == expected_during_idle stats = await http.get_connection_info() assert stats == {}
def _init_transport( self, verify: VerifyTypes = True, cert: CertTypes = None, http2: bool = False, limits: Limits = DEFAULT_LIMITS, transport: httpcore.AsyncHTTPTransport = None, app: typing.Callable = None, trust_env: bool = True, ) -> httpcore.AsyncHTTPTransport: if transport is not None: return transport if app is not None: return ASGITransport(app=app) ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env) return httpcore.AsyncConnectionPool( ssl_context=ssl_context, max_connections=limits.max_connections, max_keepalive_connections=limits.max_keepalive_connections, keepalive_expiry=KEEPALIVE_EXPIRY, http2=http2, )
async def test_cannot_connect_tcp(backend: str, url) -> None: """ A properly wrapped error is raised when connecting to the server fails. """ async with httpcore.AsyncConnectionPool(backend=backend) as http: method = b"GET" with pytest.raises(httpcore.ConnectError): await http.arequest(method, url)
async def client(app, loop): transport = httpcore.AsyncConnectionPool(uds=SOCKPATH) try: async with httpx.AsyncClient(transport=transport) as client: r = await client.get("http://myhost.invalid/") assert r.status_code == 200 assert r.text == os.path.abspath(SOCKPATH) finally: app.stop()
async def test_connection_pool_get_connection_info(http2, expected) -> None: async with httpcore.AsyncConnectionPool(http2=http2) as http: method = b"GET" url = (b"https", b"example.org", 443, b"/") headers = [(b"host", b"example.org")] for _ in range(2): _ = await http.request(method, url, headers) stats = http.get_connection_info() assert stats == {"https://example.org": expected}
async def test_cannot_connect_uds(backend: str) -> None: """ A properly wrapped error is raised when connecting to the UDS server fails. """ uds = "/tmp/doesnotexist.sock" method = b"GET" url = (b"http", b"localhost", None, b"/") async with httpcore.AsyncConnectionPool(backend=backend, uds=uds) as http: with pytest.raises(httpcore.ConnectError): await http.arequest(method, url)
async def test_request_unsupported_protocol(backend: str) -> None: async with httpcore.AsyncConnectionPool(backend=backend) as http: with pytest.raises(httpcore.UnsupportedProtocol): await http.handle_async_request( method=b"GET", url=(b"ftp", b"example.org", 443, b"/"), headers=[(b"host", b"example.org")], stream=httpcore.ByteStream(b""), extensions={}, )
async def main(scenario: model.Scenario, sslconfig: model.SslConfig) -> None: ssl_context = (ssl.create_default_context( cafile=str(sslconfig.local_ca_file)) if scenario.local_ca else None) async with httpcore.AsyncConnectionPool( ssl_context=ssl_context) as transport: async with async_record_measure(): tasks = [] for step in scenario.steps: tasks += await handlers[step.__class__](step, transport) await asyncio.gather(*tasks)
async def test_closing_http_request(backend: str, server: Server) -> None: async with httpcore.AsyncConnectionPool(backend=backend) as http: method = b"GET" url = (b"http", *server.netloc, b"/") headers = [server.host_header, (b"connection", b"close")] status_code, headers, stream, ext = await http.arequest(method, url, headers) await read_body(stream) assert status_code == 200 assert ext == {"http_version": "HTTP/1.1", "reason": "OK"} assert url[:3] not in http._connections # type: ignore
async def test_retries_enabled(server: Server) -> None: """ When retries are enabled, connection failures are retried on with a fixed exponential backoff. """ method = b"GET" url = (b"http", *server.netloc, b"/") headers = [server.host_header] backend = AsyncMockBackend() retries = 10 # Large enough to not run out of retries within this test. async with httpcore.AsyncConnectionPool(retries=retries, max_keepalive_connections=0, backend=backend) as http: # Standard case, no failures. response = await http.arequest(method, url, headers) assert backend.pop_open_tcp_stream_intervals() == [] status_code, _, stream, _ = response assert status_code == 200 await read_body(stream) # One failure, then success. backend.push(httpcore.ConnectError(), None) response = await http.arequest(method, url, headers) assert backend.pop_open_tcp_stream_intervals() == [ pytest.approx(0, abs=1e-3), # Retry immediately. ] status_code, _, stream, _ = response assert status_code == 200 await read_body(stream) # Three failures, then success. backend.push( httpcore.ConnectError(), httpcore.ConnectTimeout(), httpcore.ConnectTimeout(), None, ) response = await http.arequest(method, url, headers) assert backend.pop_open_tcp_stream_intervals() == [ pytest.approx(0, abs=1e-3), # Retry immediately. pytest.approx(0.5, rel=0.1), # First backoff. pytest.approx(1.0, rel=0.1), # Second (increased) backoff. ] status_code, _, stream, _ = response assert status_code == 200 await read_body(stream) # Non-connect exceptions are not retried on. backend.push(httpcore.ReadTimeout(), httpcore.NetworkError()) with pytest.raises(httpcore.ReadTimeout): await http.arequest(method, url, headers) with pytest.raises(httpcore.NetworkError): await http.arequest(method, url, headers)
async def test_connection_timeout_tcp(backend: str, server: Server) -> None: # we try to access http server using https. It caused some SSL timeouts # in TLSStream.wrap inside inside AnyIOBackend.open_tcp_stream method = b"GET" url = (b"https", *server.netloc, b"/") headers = [server.host_header] ext = {"timeout": {"connect": 0.1}} async with httpcore.AsyncConnectionPool(backend=backend) as http: with pytest.raises(httpcore.ConnectTimeout): await http.arequest(method, url, headers, ext=ext)
async def test_http2_request(backend: str, https_server: Server) -> None: async with httpcore.AsyncConnectionPool(backend=backend, http2=True) as http: method = b"GET" url = (b"https", *https_server.netloc, b"/") headers = [https_server.host_header] status_code, headers, stream, ext = await http.arequest(method, url, headers) await read_body(stream) assert status_code == 200 assert ext == {"http_version": "HTTP/2"} assert len(http._connections[url[:3]]) == 1 # type: ignore
async def test_explicit_backend_name(server: Server) -> None: async with httpcore.AsyncConnectionPool(backend=lookup_async_backend()) as http: method = b"GET" url = (b"http", *server.netloc, b"/") headers = [server.host_header] status_code, headers, stream, ext = await http.arequest(method, url, headers) await read_body(stream) assert status_code == 200 assert ext == {"http_version": "HTTP/1.1", "reason": "OK"} assert len(http._connections[url[:3]]) == 1 # type: ignore
async def test_https_request(backend: str) -> None: async with httpcore.AsyncConnectionPool(backend=backend) as http: method = b"GET" url = (b"https", b"example.org", 443, b"/") headers = [(b"host", b"example.org")] status_code, headers, stream, ext = await http.arequest( method, url, headers) await read_body(stream) assert status_code == 200 assert ext == {"http_version": "HTTP/1.1", "reason": "OK"} assert len(http._connections[url[:3]]) == 1 # type: ignore
async def test_get_request_with_connection_keepalive() -> None: backend = MockBackend( http_buffer=[ b"HTTP/1.1 200 OK\r\n", b"Date: Sat, 06 Oct 2049 12:34:56 GMT\r\n", b"Server: Apache\r\n", b"Content-Length: 13\r\n", b"Content-Type: text/plain\r\n", b"\r\n", b"Hello, world.", b"HTTP/1.1 200 OK\r\n", b"Date: Sat, 06 Oct 2049 12:34:56 GMT\r\n", b"Server: Apache\r\n", b"Content-Length: 13\r\n", b"Content-Type: text/plain\r\n", b"\r\n", b"Hello, world.", ] ) async with httpcore.AsyncConnectionPool(backend=backend) as http: # We're sending a request with a standard keep-alive connection, so # it will remain in the pool once we've sent the request. response = await http.handle_async_request( method=b"GET", url=(b"http", b"example.org", None, b"/"), headers=[(b"Host", b"example.org")], stream=httpcore.ByteStream(b""), extensions={}, ) status_code, headers, stream, extensions = response body = await stream.aread() assert status_code == 200 assert body == b"Hello, world." assert await http.get_connection_info() == { "http://example.org": ["HTTP/1.1, IDLE"] } # This second request will go out over the same connection. response = await http.handle_async_request( method=b"GET", url=(b"http", b"example.org", None, b"/"), headers=[(b"Host", b"example.org")], stream=httpcore.ByteStream(b""), extensions={}, ) status_code, headers, stream, extensions = response body = await stream.aread() assert status_code == 200 assert body == b"Hello, world." assert await http.get_connection_info() == { "http://example.org": ["HTTP/1.1, IDLE"] }
async def test_connection_timeout_uds(backend: str, uds_server: Server, uds: str) -> None: # we try to access http server using https. It caused some SSL timeouts # in TLSStream.wrap inside AnyIOBackend.open_uds_stream method = b"GET" url = (b"https", b"localhost", None, b"/") headers = [(b"host", b"localhost")] ext = {"timeout": {"connect": 0.1}} async with httpcore.AsyncConnectionPool(uds=uds, backend=backend) as http: with pytest.raises(httpcore.ConnectTimeout): await http.arequest(method, url, headers, ext=ext)
async def main(scenario: model.Scenario, sslconfig: model.SslConfig, http2: str = 'http1') -> None: ssl_context = (ssl.create_default_context( cafile=str(sslconfig.local_ca_file)) if scenario.local_ca else None) http2 = http2 == 'http2' async with httpcore.AsyncConnectionPool( http2=http2, ssl_context=ssl_context) as transport: async with async_record_measure(): async with curio.TaskGroup() as g: for step in scenario.steps: await handlers[step.__class__](g, step, transport)
async def test_cannot_connect_tcp(backend: str, url) -> None: """ A properly wrapped error is raised when connecting to the server fails. """ async with httpcore.AsyncConnectionPool(backend=backend) as http: with pytest.raises(httpcore.ConnectError): await http.handle_async_request( method=b"GET", url=url, headers=[], stream=httpcore.ByteStream(b""), extensions={}, )