Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
connection.py223 linesDownload Raw Back to _sync
1from __future__ import annotations2 3import itertools4import logging5import ssl6import types7import typing8 9from .._backends.sync import SyncBackend10from .._backends.base import SOCKET_OPTION, NetworkBackend, NetworkStream11from .._exceptions import ConnectError, ConnectTimeout12from .._models import Origin, Request, Response13from .._ssl import default_ssl_context14from .._synchronization import Lock15from .._trace import Trace16from .http11 import HTTP11Connection17from .interfaces import ConnectionInterface18 19RETRIES_BACKOFF_FACTOR = 0.5  # 0s, 0.5s, 1s, 2s, 4s, etc.20 21 22logger = logging.getLogger("httpcore.connection")23 24 25def 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 034    for n in itertools.count():35        yield factor * 2**n36 37 38class HTTPConnection(ConnectionInterface):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: NetworkBackend | None = None,50        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,51    ) -> None:52        self._origin = origin53        self._ssl_context = ssl_context54        self._keepalive_expiry = keepalive_expiry55        self._http1 = http156        self._http2 = http257        self._retries = retries58        self._local_address = local_address59        self._uds = uds60 61        self._network_backend: NetworkBackend = (62            SyncBackend() if network_backend is None else network_backend63        )64        self._connection: ConnectionInterface | None = None65        self._connect_failed: bool = False66        self._request_lock = Lock()67        self._socket_options = socket_options68 69    def handle_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            with self._request_lock:77                if self._connection is None:78                    stream = self._connect(request)79 80                    ssl_object = stream.get_extra_info("ssl_object")81                    http2_negotiated = (82                        ssl_object is not None83                        and ssl_object.selected_alpn_protocol() == "h2"84                    )85                    if http2_negotiated or (self._http2 and not self._http1):86                        from .http2 import HTTP2Connection87 88                        self._connection = HTTP2Connection(89                            origin=self._origin,90                            stream=stream,91                            keepalive_expiry=self._keepalive_expiry,92                        )93                    else:94                        self._connection = HTTP11Connection(95                            origin=self._origin,96                            stream=stream,97                            keepalive_expiry=self._keepalive_expiry,98                        )99        except BaseException as exc:100            self._connect_failed = True101            raise exc102 103        return self._connection.handle_request(request)104 105    def _connect(self, request: Request) -> NetworkStream: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._retries111        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                    with Trace("connect_tcp", logger, request, kwargs) as trace:124                        stream = self._network_backend.connect_tcp(**kwargs)125                        trace.return_value = stream126                else:127                    kwargs = {128                        "path": self._uds,129                        "timeout": timeout,130                        "socket_options": self._socket_options,131                    }132                    with Trace(133                        "connect_unix_socket", logger, request, kwargs134                    ) as trace:135                        stream = self._network_backend.connect_unix_socket(136                            **kwargs137                        )138                        trace.return_value = stream139 140                if self._origin.scheme in (b"https", b"wss"):141                    ssl_context = (142                        default_ssl_context()143                        if self._ssl_context is None144                        else self._ssl_context145                    )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_hostname152                        or self._origin.host.decode("ascii"),153                        "timeout": timeout,154                    }155                    with Trace("start_tls", logger, request, kwargs) as trace:156                        stream = stream.start_tls(**kwargs)157                        trace.return_value = stream158                return stream159            except (ConnectError, ConnectTimeout):160                if retries_left <= 0:161                    raise162                retries_left -= 1163                delay = next(delays)164                with Trace("retry", logger, request, kwargs) as trace:165                    self._network_backend.sleep(delay)166 167    def can_handle_request(self, origin: Origin) -> bool:168        return origin == self._origin169 170    def close(self) -> None:171        if self._connection is not None:172            with Trace("close", logger, None, {}):173                self._connection.close()174 175    def is_available(self) -> bool:176        if self._connection is None:177            # If HTTP/2 support is enabled, and the resulting connection could178            # end up as HTTP/2 then we should indicate the connection as being179            # available to service multiple requests.180            return (181                self._http2182                and (self._origin.scheme == b"https" or not self._http1)183                and not self._connect_failed184            )185        return self._connection.is_available()186 187    def has_expired(self) -> bool:188        if self._connection is None:189            return self._connect_failed190        return self._connection.has_expired()191 192    def is_idle(self) -> bool:193        if self._connection is None:194            return self._connect_failed195        return self._connection.is_idle()196 197    def is_closed(self) -> bool:198        if self._connection is None:199            return self._connect_failed200        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 are211    # useful for testing or working with connection instances directly.212 213    def __enter__(self) -> HTTPConnection:214        return self215 216    def __exit__(217        self,218        exc_type: type[BaseException] | None = None,219        exc_value: BaseException | None = None,220        traceback: types.TracebackType | None = None,221    ) -> None:222        self.close()223 
codekingpro/portable-devtools · Team Ai