httpcorexyz 1.0.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- httpcorexyz/__init__.py +154 -0
- httpcorexyz/_api.py +94 -0
- httpcorexyz/_async/__init__.py +39 -0
- httpcorexyz/_async/connection.py +222 -0
- httpcorexyz/_async/connection_pool.py +420 -0
- httpcorexyz/_async/http11.py +379 -0
- httpcorexyz/_async/http2.py +592 -0
- httpcorexyz/_async/http_proxy.py +367 -0
- httpcorexyz/_async/interfaces.py +137 -0
- httpcorexyz/_async/socks_proxy.py +341 -0
- httpcorexyz/_backends/__init__.py +0 -0
- httpcorexyz/_backends/anyio.py +146 -0
- httpcorexyz/_backends/auto.py +52 -0
- httpcorexyz/_backends/base.py +101 -0
- httpcorexyz/_backends/mock.py +143 -0
- httpcorexyz/_backends/sync.py +241 -0
- httpcorexyz/_backends/trio.py +159 -0
- httpcorexyz/_exceptions.py +81 -0
- httpcorexyz/_models.py +516 -0
- httpcorexyz/_ssl.py +9 -0
- httpcorexyz/_sync/__init__.py +39 -0
- httpcorexyz/_sync/connection.py +222 -0
- httpcorexyz/_sync/connection_pool.py +420 -0
- httpcorexyz/_sync/http11.py +379 -0
- httpcorexyz/_sync/http2.py +592 -0
- httpcorexyz/_sync/http_proxy.py +367 -0
- httpcorexyz/_sync/interfaces.py +137 -0
- httpcorexyz/_sync/socks_proxy.py +341 -0
- httpcorexyz/_synchronization.py +318 -0
- httpcorexyz/_trace.py +107 -0
- httpcorexyz/_utils.py +37 -0
- httpcorexyz/py.typed +0 -0
- httpcorexyz-1.0.10.dist-info/METADATA +634 -0
- httpcorexyz-1.0.10.dist-info/RECORD +36 -0
- httpcorexyz-1.0.10.dist-info/WHEEL +4 -0
- httpcorexyz-1.0.10.dist-info/licenses/LICENSE.md +27 -0
httpcorexyz/__init__.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from ._api import request, stream
|
|
2
|
+
from ._async import (
|
|
3
|
+
AsyncConnectionInterface,
|
|
4
|
+
AsyncConnectionPool,
|
|
5
|
+
AsyncHTTP2Connection,
|
|
6
|
+
AsyncHTTP11Connection,
|
|
7
|
+
AsyncHTTPConnection,
|
|
8
|
+
AsyncHTTPProxy,
|
|
9
|
+
AsyncSOCKSProxy,
|
|
10
|
+
)
|
|
11
|
+
from ._backends.base import (
|
|
12
|
+
SOCKET_OPTION,
|
|
13
|
+
AsyncNetworkBackend,
|
|
14
|
+
AsyncNetworkStream,
|
|
15
|
+
NetworkBackend,
|
|
16
|
+
NetworkStream,
|
|
17
|
+
)
|
|
18
|
+
from ._backends.mock import AsyncMockBackend, AsyncMockStream, MockBackend, MockStream
|
|
19
|
+
from ._backends.sync import SyncBackend
|
|
20
|
+
from ._exceptions import (
|
|
21
|
+
ConnectError,
|
|
22
|
+
ConnectionNotAvailable,
|
|
23
|
+
ConnectTimeout,
|
|
24
|
+
LocalProtocolError,
|
|
25
|
+
NetworkError,
|
|
26
|
+
PoolTimeout,
|
|
27
|
+
ProtocolError,
|
|
28
|
+
ProxyError,
|
|
29
|
+
ReadError,
|
|
30
|
+
ReadTimeout,
|
|
31
|
+
RemoteProtocolError,
|
|
32
|
+
TimeoutException,
|
|
33
|
+
UnsupportedProtocol,
|
|
34
|
+
WriteError,
|
|
35
|
+
WriteTimeout,
|
|
36
|
+
)
|
|
37
|
+
from ._models import URL, Origin, Proxy, Request, Response
|
|
38
|
+
from ._ssl import default_ssl_context
|
|
39
|
+
from ._sync import (
|
|
40
|
+
ConnectionInterface,
|
|
41
|
+
ConnectionPool,
|
|
42
|
+
HTTP2Connection,
|
|
43
|
+
HTTP11Connection,
|
|
44
|
+
HTTPConnection,
|
|
45
|
+
HTTPProxy,
|
|
46
|
+
SOCKSProxy,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# The 'httpcorexyz.AnyIOBackend' class is conditional on 'anyio' being installed.
|
|
50
|
+
try:
|
|
51
|
+
from ._backends.anyio import AnyIOBackend
|
|
52
|
+
except ImportError: # pragma: nocover
|
|
53
|
+
|
|
54
|
+
class AnyIOBackend: # type: ignore
|
|
55
|
+
def __init__(self, *args, **kwargs): # type: ignore
|
|
56
|
+
msg = "Attempted to use 'httpcorexyz.AnyIOBackend' but 'anyio' is not installed."
|
|
57
|
+
raise RuntimeError(msg)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# The 'httpcorexyz.TrioBackend' class is conditional on 'trio' being installed.
|
|
61
|
+
try:
|
|
62
|
+
from ._backends.trio import TrioBackend
|
|
63
|
+
except ImportError: # pragma: nocover
|
|
64
|
+
|
|
65
|
+
class TrioBackend: # type: ignore
|
|
66
|
+
def __init__(self, *args, **kwargs): # type: ignore
|
|
67
|
+
msg = "Attempted to use 'httpcorexyz.TrioBackend' but 'trio' is not installed."
|
|
68
|
+
raise RuntimeError(msg)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
__all__ = [
|
|
72
|
+
# top-level requests
|
|
73
|
+
"request",
|
|
74
|
+
"stream",
|
|
75
|
+
# models
|
|
76
|
+
"Origin",
|
|
77
|
+
"URL",
|
|
78
|
+
"Request",
|
|
79
|
+
"Response",
|
|
80
|
+
"Proxy",
|
|
81
|
+
# async
|
|
82
|
+
"AsyncHTTPConnection",
|
|
83
|
+
"AsyncConnectionPool",
|
|
84
|
+
"AsyncHTTPProxy",
|
|
85
|
+
"AsyncHTTP11Connection",
|
|
86
|
+
"AsyncHTTP2Connection",
|
|
87
|
+
"AsyncConnectionInterface",
|
|
88
|
+
"AsyncSOCKSProxy",
|
|
89
|
+
# sync
|
|
90
|
+
"HTTPConnection",
|
|
91
|
+
"ConnectionPool",
|
|
92
|
+
"HTTPProxy",
|
|
93
|
+
"HTTP11Connection",
|
|
94
|
+
"HTTP2Connection",
|
|
95
|
+
"ConnectionInterface",
|
|
96
|
+
"SOCKSProxy",
|
|
97
|
+
# network backends, implementations
|
|
98
|
+
"SyncBackend",
|
|
99
|
+
"AnyIOBackend",
|
|
100
|
+
"TrioBackend",
|
|
101
|
+
# network backends, mock implementations
|
|
102
|
+
"AsyncMockBackend",
|
|
103
|
+
"AsyncMockStream",
|
|
104
|
+
"MockBackend",
|
|
105
|
+
"MockStream",
|
|
106
|
+
# network backends, interface
|
|
107
|
+
"AsyncNetworkStream",
|
|
108
|
+
"AsyncNetworkBackend",
|
|
109
|
+
"NetworkStream",
|
|
110
|
+
"NetworkBackend",
|
|
111
|
+
# util
|
|
112
|
+
"default_ssl_context",
|
|
113
|
+
"SOCKET_OPTION",
|
|
114
|
+
# exceptions
|
|
115
|
+
"ConnectionNotAvailable",
|
|
116
|
+
"ProxyError",
|
|
117
|
+
"ProtocolError",
|
|
118
|
+
"LocalProtocolError",
|
|
119
|
+
"RemoteProtocolError",
|
|
120
|
+
"UnsupportedProtocol",
|
|
121
|
+
"TimeoutException",
|
|
122
|
+
"PoolTimeout",
|
|
123
|
+
"ConnectTimeout",
|
|
124
|
+
"ReadTimeout",
|
|
125
|
+
"WriteTimeout",
|
|
126
|
+
"NetworkError",
|
|
127
|
+
"ConnectError",
|
|
128
|
+
"ReadError",
|
|
129
|
+
"WriteError",
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
__version__ = "1.0.10"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
__locals = locals()
|
|
136
|
+
for __name in __all__:
|
|
137
|
+
# Exclude SOCKET_OPTION, it causes AttributeError on Python 3.14
|
|
138
|
+
if not __name.startswith(("__", "SOCKET_OPTION")):
|
|
139
|
+
setattr(__locals[__name], "__module__", "httpcorexyz") # noqa
|
|
140
|
+
|
|
141
|
+
import logging as _logging
|
|
142
|
+
import sys as _sys
|
|
143
|
+
|
|
144
|
+
if "httpcore" not in _sys.modules:
|
|
145
|
+
_sys.modules["httpcore"] = _sys.modules[__name__]
|
|
146
|
+
_logging.getLogger("httpcorexyz").debug(
|
|
147
|
+
"httpcore not found in sys.modules — aliased to httpcorexyz"
|
|
148
|
+
)
|
|
149
|
+
else: # pragma: no cover
|
|
150
|
+
_logging.getLogger("httpcorexyz").debug(
|
|
151
|
+
"httpcore already present in sys.modules — httpcorexyz alias not applied"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
del _logging, _sys
|
httpcorexyz/_api.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from ._models import URL, Extensions, HeaderTypes, Response
|
|
7
|
+
from ._sync.connection_pool import ConnectionPool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def request(
|
|
11
|
+
method: bytes | str,
|
|
12
|
+
url: URL | bytes | str,
|
|
13
|
+
*,
|
|
14
|
+
headers: HeaderTypes = None,
|
|
15
|
+
content: bytes | typing.Iterator[bytes] | None = None,
|
|
16
|
+
extensions: Extensions | None = None,
|
|
17
|
+
) -> Response:
|
|
18
|
+
"""
|
|
19
|
+
Sends an HTTP request, returning the response.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
response = httpcore.request("GET", "https://www.example.com/")
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Arguments:
|
|
26
|
+
method: The HTTP method for the request. Typically one of `"GET"`,
|
|
27
|
+
`"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`.
|
|
28
|
+
url: The URL of the HTTP request. Either as an instance of `httpcore.URL`,
|
|
29
|
+
or as str/bytes.
|
|
30
|
+
headers: The HTTP request headers. Either as a dictionary of str/bytes,
|
|
31
|
+
or as a list of two-tuples of str/bytes.
|
|
32
|
+
content: The content of the request body. Either as bytes,
|
|
33
|
+
or as a bytes iterator.
|
|
34
|
+
extensions: A dictionary of optional extra information included on the request.
|
|
35
|
+
Possible keys include `"timeout"`.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
An instance of `httpcore.Response`.
|
|
39
|
+
"""
|
|
40
|
+
with ConnectionPool() as pool:
|
|
41
|
+
return pool.request(
|
|
42
|
+
method=method,
|
|
43
|
+
url=url,
|
|
44
|
+
headers=headers,
|
|
45
|
+
content=content,
|
|
46
|
+
extensions=extensions,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@contextlib.contextmanager
|
|
51
|
+
def stream(
|
|
52
|
+
method: bytes | str,
|
|
53
|
+
url: URL | bytes | str,
|
|
54
|
+
*,
|
|
55
|
+
headers: HeaderTypes = None,
|
|
56
|
+
content: bytes | typing.Iterator[bytes] | None = None,
|
|
57
|
+
extensions: Extensions | None = None,
|
|
58
|
+
) -> typing.Iterator[Response]:
|
|
59
|
+
"""
|
|
60
|
+
Sends an HTTP request, returning the response within a content manager.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
with httpcore.stream("GET", "https://www.example.com/") as response:
|
|
64
|
+
...
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
When using the `stream()` function, the body of the response will not be
|
|
68
|
+
automatically read. If you want to access the response body you should
|
|
69
|
+
either use `content = response.read()`, or `for chunk in response.iter_content()`.
|
|
70
|
+
|
|
71
|
+
Arguments:
|
|
72
|
+
method: The HTTP method for the request. Typically one of `"GET"`,
|
|
73
|
+
`"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`.
|
|
74
|
+
url: The URL of the HTTP request. Either as an instance of `httpcore.URL`,
|
|
75
|
+
or as str/bytes.
|
|
76
|
+
headers: The HTTP request headers. Either as a dictionary of str/bytes,
|
|
77
|
+
or as a list of two-tuples of str/bytes.
|
|
78
|
+
content: The content of the request body. Either as bytes,
|
|
79
|
+
or as a bytes iterator.
|
|
80
|
+
extensions: A dictionary of optional extra information included on the request.
|
|
81
|
+
Possible keys include `"timeout"`.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
An instance of `httpcore.Response`.
|
|
85
|
+
"""
|
|
86
|
+
with ConnectionPool() as pool:
|
|
87
|
+
with pool.stream(
|
|
88
|
+
method=method,
|
|
89
|
+
url=url,
|
|
90
|
+
headers=headers,
|
|
91
|
+
content=content,
|
|
92
|
+
extensions=extensions,
|
|
93
|
+
) as response:
|
|
94
|
+
yield response
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from .connection import AsyncHTTPConnection
|
|
2
|
+
from .connection_pool import AsyncConnectionPool
|
|
3
|
+
from .http11 import AsyncHTTP11Connection
|
|
4
|
+
from .http_proxy import AsyncHTTPProxy
|
|
5
|
+
from .interfaces import AsyncConnectionInterface
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from .http2 import AsyncHTTP2Connection
|
|
9
|
+
except ImportError: # pragma: nocover
|
|
10
|
+
|
|
11
|
+
class AsyncHTTP2Connection: # type: ignore
|
|
12
|
+
def __init__(self, *args, **kwargs) -> None: # type: ignore
|
|
13
|
+
raise RuntimeError(
|
|
14
|
+
"Attempted to use http2 support, but the `h2` package is not "
|
|
15
|
+
"installed. Use 'pip install httpcore[http2]'."
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from .socks_proxy import AsyncSOCKSProxy
|
|
21
|
+
except ImportError: # pragma: nocover
|
|
22
|
+
|
|
23
|
+
class AsyncSOCKSProxy: # type: ignore
|
|
24
|
+
def __init__(self, *args, **kwargs) -> None: # type: ignore
|
|
25
|
+
raise RuntimeError(
|
|
26
|
+
"Attempted to use SOCKS support, but the `socksio` package is not "
|
|
27
|
+
"installed. Use 'pip install httpcore[socks]'."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"AsyncHTTPConnection",
|
|
33
|
+
"AsyncConnectionPool",
|
|
34
|
+
"AsyncHTTPProxy",
|
|
35
|
+
"AsyncHTTP11Connection",
|
|
36
|
+
"AsyncHTTP2Connection",
|
|
37
|
+
"AsyncConnectionInterface",
|
|
38
|
+
"AsyncSOCKSProxy",
|
|
39
|
+
]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import logging
|
|
5
|
+
import ssl
|
|
6
|
+
import types
|
|
7
|
+
import typing
|
|
8
|
+
|
|
9
|
+
from .._backends.auto import AutoBackend
|
|
10
|
+
from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
|
|
11
|
+
from .._exceptions import ConnectError, ConnectTimeout
|
|
12
|
+
from .._models import Origin, Request, Response
|
|
13
|
+
from .._ssl import default_ssl_context
|
|
14
|
+
from .._synchronization import AsyncLock
|
|
15
|
+
from .._trace import Trace
|
|
16
|
+
from .http11 import AsyncHTTP11Connection
|
|
17
|
+
from .interfaces import AsyncConnectionInterface
|
|
18
|
+
|
|
19
|
+
RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("httpcorexyz.connection")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def exponential_backoff(factor: float) -> typing.Iterator[float]:
|
|
26
|
+
"""
|
|
27
|
+
Generate a geometric sequence that has a ratio of 2 and starts with 0.
|
|
28
|
+
|
|
29
|
+
For example:
|
|
30
|
+
- `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...`
|
|
31
|
+
- `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...`
|
|
32
|
+
"""
|
|
33
|
+
yield 0
|
|
34
|
+
for n in itertools.count():
|
|
35
|
+
yield factor * 2**n
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AsyncHTTPConnection(AsyncConnectionInterface):
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
origin: Origin,
|
|
42
|
+
ssl_context: ssl.SSLContext | None = None,
|
|
43
|
+
keepalive_expiry: float | None = None,
|
|
44
|
+
http1: bool = True,
|
|
45
|
+
http2: bool = False,
|
|
46
|
+
retries: int = 0,
|
|
47
|
+
local_address: str | None = None,
|
|
48
|
+
uds: str | None = None,
|
|
49
|
+
network_backend: AsyncNetworkBackend | None = None,
|
|
50
|
+
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
self._origin = origin
|
|
53
|
+
self._ssl_context = ssl_context
|
|
54
|
+
self._keepalive_expiry = keepalive_expiry
|
|
55
|
+
self._http1 = http1
|
|
56
|
+
self._http2 = http2
|
|
57
|
+
self._retries = retries
|
|
58
|
+
self._local_address = local_address
|
|
59
|
+
self._uds = uds
|
|
60
|
+
|
|
61
|
+
self._network_backend: AsyncNetworkBackend = (
|
|
62
|
+
AutoBackend() if network_backend is None else network_backend
|
|
63
|
+
)
|
|
64
|
+
self._connection: AsyncConnectionInterface | None = None
|
|
65
|
+
self._connect_failed: bool = False
|
|
66
|
+
self._request_lock = AsyncLock()
|
|
67
|
+
self._socket_options = socket_options
|
|
68
|
+
|
|
69
|
+
async def handle_async_request(self, request: Request) -> Response:
|
|
70
|
+
if not self.can_handle_request(request.url.origin):
|
|
71
|
+
raise RuntimeError(
|
|
72
|
+
f"Attempted to send request to {request.url.origin} on connection to {self._origin}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
async with self._request_lock:
|
|
77
|
+
if self._connection is None:
|
|
78
|
+
stream = await self._connect(request)
|
|
79
|
+
|
|
80
|
+
ssl_object = stream.get_extra_info("ssl_object")
|
|
81
|
+
http2_negotiated = (
|
|
82
|
+
ssl_object is not None
|
|
83
|
+
and ssl_object.selected_alpn_protocol() == "h2"
|
|
84
|
+
)
|
|
85
|
+
if http2_negotiated or (self._http2 and not self._http1):
|
|
86
|
+
from .http2 import AsyncHTTP2Connection
|
|
87
|
+
|
|
88
|
+
self._connection = AsyncHTTP2Connection(
|
|
89
|
+
origin=self._origin,
|
|
90
|
+
stream=stream,
|
|
91
|
+
keepalive_expiry=self._keepalive_expiry,
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
self._connection = AsyncHTTP11Connection(
|
|
95
|
+
origin=self._origin,
|
|
96
|
+
stream=stream,
|
|
97
|
+
keepalive_expiry=self._keepalive_expiry,
|
|
98
|
+
)
|
|
99
|
+
except BaseException as exc:
|
|
100
|
+
self._connect_failed = True
|
|
101
|
+
raise exc
|
|
102
|
+
|
|
103
|
+
return await self._connection.handle_async_request(request)
|
|
104
|
+
|
|
105
|
+
async def _connect(self, request: Request) -> AsyncNetworkStream:
|
|
106
|
+
timeouts = request.extensions.get("timeout", {})
|
|
107
|
+
sni_hostname = request.extensions.get("sni_hostname", None)
|
|
108
|
+
timeout = timeouts.get("connect", None)
|
|
109
|
+
|
|
110
|
+
retries_left = self._retries
|
|
111
|
+
delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR)
|
|
112
|
+
|
|
113
|
+
while True:
|
|
114
|
+
try:
|
|
115
|
+
if self._uds is None:
|
|
116
|
+
kwargs = {
|
|
117
|
+
"host": self._origin.host.decode("ascii"),
|
|
118
|
+
"port": self._origin.port,
|
|
119
|
+
"local_address": self._local_address,
|
|
120
|
+
"timeout": timeout,
|
|
121
|
+
"socket_options": self._socket_options,
|
|
122
|
+
}
|
|
123
|
+
async with Trace("connect_tcp", logger, request, kwargs) as trace:
|
|
124
|
+
stream = await self._network_backend.connect_tcp(**kwargs)
|
|
125
|
+
trace.return_value = stream
|
|
126
|
+
else:
|
|
127
|
+
kwargs = {
|
|
128
|
+
"path": self._uds,
|
|
129
|
+
"timeout": timeout,
|
|
130
|
+
"socket_options": self._socket_options,
|
|
131
|
+
}
|
|
132
|
+
async with Trace(
|
|
133
|
+
"connect_unix_socket", logger, request, kwargs
|
|
134
|
+
) as trace:
|
|
135
|
+
stream = await self._network_backend.connect_unix_socket(
|
|
136
|
+
**kwargs
|
|
137
|
+
)
|
|
138
|
+
trace.return_value = stream
|
|
139
|
+
|
|
140
|
+
if self._origin.scheme in (b"https", b"wss"):
|
|
141
|
+
ssl_context = (
|
|
142
|
+
default_ssl_context()
|
|
143
|
+
if self._ssl_context is None
|
|
144
|
+
else self._ssl_context
|
|
145
|
+
)
|
|
146
|
+
alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]
|
|
147
|
+
ssl_context.set_alpn_protocols(alpn_protocols)
|
|
148
|
+
|
|
149
|
+
kwargs = {
|
|
150
|
+
"ssl_context": ssl_context,
|
|
151
|
+
"server_hostname": sni_hostname
|
|
152
|
+
or self._origin.host.decode("ascii"),
|
|
153
|
+
"timeout": timeout,
|
|
154
|
+
}
|
|
155
|
+
async with Trace("start_tls", logger, request, kwargs) as trace:
|
|
156
|
+
stream = await stream.start_tls(**kwargs)
|
|
157
|
+
trace.return_value = stream
|
|
158
|
+
return stream
|
|
159
|
+
except (ConnectError, ConnectTimeout):
|
|
160
|
+
if retries_left <= 0:
|
|
161
|
+
raise
|
|
162
|
+
retries_left -= 1
|
|
163
|
+
delay = next(delays)
|
|
164
|
+
async with Trace("retry", logger, request, kwargs) as trace:
|
|
165
|
+
await self._network_backend.sleep(delay)
|
|
166
|
+
|
|
167
|
+
def can_handle_request(self, origin: Origin) -> bool:
|
|
168
|
+
return origin == self._origin
|
|
169
|
+
|
|
170
|
+
async def aclose(self) -> None:
|
|
171
|
+
if self._connection is not None:
|
|
172
|
+
async with Trace("close", logger, None, {}):
|
|
173
|
+
await self._connection.aclose()
|
|
174
|
+
|
|
175
|
+
def is_available(self) -> bool:
|
|
176
|
+
if self._connection is None:
|
|
177
|
+
# If HTTP/2 support is enabled, and the resulting connection could
|
|
178
|
+
# end up as HTTP/2 then we should indicate the connection as being
|
|
179
|
+
# available to service multiple requests.
|
|
180
|
+
return (
|
|
181
|
+
self._http2
|
|
182
|
+
and (self._origin.scheme == b"https" or not self._http1)
|
|
183
|
+
and not self._connect_failed
|
|
184
|
+
)
|
|
185
|
+
return self._connection.is_available()
|
|
186
|
+
|
|
187
|
+
def has_expired(self) -> bool:
|
|
188
|
+
if self._connection is None:
|
|
189
|
+
return self._connect_failed
|
|
190
|
+
return self._connection.has_expired()
|
|
191
|
+
|
|
192
|
+
def is_idle(self) -> bool:
|
|
193
|
+
if self._connection is None:
|
|
194
|
+
return self._connect_failed
|
|
195
|
+
return self._connection.is_idle()
|
|
196
|
+
|
|
197
|
+
def is_closed(self) -> bool:
|
|
198
|
+
if self._connection is None:
|
|
199
|
+
return self._connect_failed
|
|
200
|
+
return self._connection.is_closed()
|
|
201
|
+
|
|
202
|
+
def info(self) -> str:
|
|
203
|
+
if self._connection is None:
|
|
204
|
+
return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
|
|
205
|
+
return self._connection.info()
|
|
206
|
+
|
|
207
|
+
def __repr__(self) -> str:
|
|
208
|
+
return f"<{self.__class__.__name__} [{self.info()}]>"
|
|
209
|
+
|
|
210
|
+
# These context managers are not used in the standard flow, but are
|
|
211
|
+
# useful for testing or working with connection instances directly.
|
|
212
|
+
|
|
213
|
+
async def __aenter__(self) -> AsyncHTTPConnection:
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
async def __aexit__(
|
|
217
|
+
self,
|
|
218
|
+
exc_type: type[BaseException] | None = None,
|
|
219
|
+
exc_value: BaseException | None = None,
|
|
220
|
+
traceback: types.TracebackType | None = None,
|
|
221
|
+
) -> None:
|
|
222
|
+
await self.aclose()
|