Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions httpcore/_async/connection.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from ssl import SSLContext
from typing import Optional, Tuple, cast

from .._backends.auto import AsyncBackend, AsyncLock, AsyncSocketStream, AutoBackend
from typing import Optional, Tuple, Union, cast

from .._backends.auto import (
AsyncBackend,
AsyncDatagramSocket,
AsyncLock,
AsyncSocketStream,
AutoBackend,
)
from .._exceptions import ConnectError, ConnectTimeout
from .._types import URL, Headers, Origin, TimeoutDict
from .._utils import exponential_backoff, get_logger, url_to_origin
Expand All @@ -24,15 +30,17 @@ def __init__(
self,
origin: Origin,
http2: bool = False,
http3: bool = False,
uds: str = None,
ssl_context: SSLContext = None,
socket: AsyncSocketStream = None,
socket: Union[AsyncDatagramSocket, AsyncSocketStream] = None,
local_address: str = None,
retries: int = 0,
backend: AsyncBackend = None,
):
self.origin = origin
self.http2 = http2
self.http3 = http3
self.uds = uds
self.ssl_context = SSLContext() if ssl_context is None else ssl_context
self.socket = socket
Expand All @@ -45,6 +53,7 @@ def __init__(
self.connection: Optional[AsyncBaseHTTPConnection] = None
self.is_http11 = False
self.is_http2 = False
self.is_http3 = False
self.connect_failed = False
self.expires_at: Optional[float] = None
self.backend = AutoBackend() if backend is None else backend
Expand All @@ -55,6 +64,8 @@ def __repr__(self) -> str:
http_version = "HTTP/1.1"
elif self.is_http2:
http_version = "HTTP/2"
elif self.is_http3:
http_version = "HTTP/3"
return f"<AsyncHTTPConnection http_version={http_version} state={self.state}>"

def info(self) -> str:
Expand Down Expand Up @@ -105,7 +116,9 @@ async def arequest(
)
return await self.connection.arequest(method, url, headers, stream, ext)

async def _open_socket(self, timeout: TimeoutDict = None) -> AsyncSocketStream:
async def _open_socket(
self, timeout: TimeoutDict = None
) -> Union[AsyncDatagramSocket, AsyncSocketStream]:
scheme, hostname, port = self.origin
timeout = {} if timeout is None else timeout
ssl_context = self.ssl_context if scheme == b"https" else None
Expand All @@ -115,7 +128,14 @@ async def _open_socket(self, timeout: TimeoutDict = None) -> AsyncSocketStream:

while True:
try:
if self.uds is None:
if self.http3:
return await self.backend.open_udp_socket(
hostname,
port,
timeout,
local_address=self.local_address,
)
elif self.uds is None:
return await self.backend.open_tcp_stream(
hostname,
port,
Expand All @@ -138,22 +158,35 @@ async def _open_socket(self, timeout: TimeoutDict = None) -> AsyncSocketStream:
self.connect_failed = True
raise

def _create_connection(self, socket: AsyncSocketStream) -> None:
def _create_connection(
self, socket: Union[AsyncDatagramSocket, AsyncSocketStream]
) -> None:
http_version = socket.get_http_version()
logger.trace(
"create_connection socket=%r http_version=%r", socket, http_version
)
if http_version == "HTTP/2":
if http_version == "HTTP/3":
from .http3 import AsyncHTTP3Connection

self.is_http3 = True
self.connection = AsyncHTTP3Connection(
socket=cast(AsyncDatagramSocket, socket),
backend=self.backend,
ssl_context=self.ssl_context,
)
elif http_version == "HTTP/2":
from .http2 import AsyncHTTP2Connection

self.is_http2 = True
self.connection = AsyncHTTP2Connection(
socket=socket, backend=self.backend, ssl_context=self.ssl_context
socket=cast(AsyncSocketStream, socket),
backend=self.backend,
ssl_context=self.ssl_context,
)
else:
self.is_http11 = True
self.connection = AsyncHTTP11Connection(
socket=socket, ssl_context=self.ssl_context
socket=cast(AsyncSocketStream, socket), ssl_context=self.ssl_context
)

@property
Expand Down
13 changes: 13 additions & 0 deletions httpcore/_async/connection_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class AsyncConnectionPool(AsyncHTTPTransport):
* **keepalive_expiry** - `Optional[float]` - The maximum time to allow
before closing a keep-alive connection.
* **http2** - `bool` - Enable HTTP/2 support.
* **http3** - `bool` - Enable HTTP/3 support.
* **uds** - `str` - Path to a Unix Domain Socket to use instead of TCP sockets.
* **local_address** - `Optional[str]` - Local address to connect from. Can
also be used to connect using a particular address family. Using
Expand All @@ -106,6 +107,7 @@ def __init__(
max_keepalive_connections: int = None,
keepalive_expiry: float = None,
http2: bool = False,
http3: bool = False,
uds: str = None,
local_address: str = None,
retries: int = 0,
Expand All @@ -127,6 +129,7 @@ def __init__(
self._max_keepalive_connections = max_keepalive_connections
self._keepalive_expiry = keepalive_expiry
self._http2 = http2
self._http3 = http3
self._uds = uds
self._local_address = local_address
self._retries = retries
Expand All @@ -144,6 +147,15 @@ def __init__(
"package is not installed. Use 'pip install httpcore[http2]'."
)

if http3:
try:
import aioquic # noqa: F401
except ImportError:
raise ImportError(
"Attempted to use http3=True, but the 'aioquic' "
"package is not installed. Use 'pip install httpcore[http3]'."
)

@property
def _connection_semaphore(self) -> AsyncSemaphore:
# We do this lazily, to make sure backend autodetection always
Expand Down Expand Up @@ -171,6 +183,7 @@ def _create_connection(
return AsyncHTTPConnection(
origin=origin,
http2=self._http2,
http3=self._http3,
uds=self._uds,
ssl_context=self._ssl_context,
local_address=self._local_address,
Expand Down
Loading