Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
http_proxy.py368 linesDownload Raw Back to _sync
1from __future__ import annotations2 3import base644import logging5import ssl6import typing7 8from .._backends.base import SOCKET_OPTION, NetworkBackend9from .._exceptions import ProxyError10from .._models import (11    URL,12    Origin,13    Request,14    Response,15    enforce_bytes,16    enforce_headers,17    enforce_url,18)19from .._ssl import default_ssl_context20from .._synchronization import Lock21from .._trace import Trace22from .connection import HTTPConnection23from .connection_pool import ConnectionPool24from .http11 import HTTP11Connection25from .interfaces import ConnectionInterface26 27ByteOrStr = typing.Union[bytes, str]28HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]29HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]30 31 32logger = logging.getLogger("httpcore.proxy")33 34 35def merge_headers(36    default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,37    override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,38) -> list[tuple[bytes, bytes]]:39    """40    Append default_headers and override_headers, de-duplicating if a key exists41    in both cases.42    """43    default_headers = [] if default_headers is None else list(default_headers)44    override_headers = [] if override_headers is None else list(override_headers)45    has_override = set(key.lower() for key, value in override_headers)46    default_headers = [47        (key, value)48        for key, value in default_headers49        if key.lower() not in has_override50    ]51    return default_headers + override_headers52 53 54class HTTPProxy(ConnectionPool):  # pragma: nocover55    """56    A connection pool that sends requests via an HTTP proxy.57    """58 59    def __init__(60        self,61        proxy_url: URL | bytes | str,62        proxy_auth: tuple[bytes | str, bytes | str] | None = None,63        proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,64        ssl_context: ssl.SSLContext | None = None,65        proxy_ssl_context: ssl.SSLContext | None = None,66        max_connections: int | None = 10,67        max_keepalive_connections: int | None = None,68        keepalive_expiry: float | None = None,69        http1: bool = True,70        http2: bool = False,71        retries: int = 0,72        local_address: str | None = None,73        uds: str | None = None,74        network_backend: NetworkBackend | None = None,75        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,76    ) -> None:77        """78        A connection pool for making HTTP requests.79 80        Parameters:81            proxy_url: The URL to use when connecting to the proxy server.82                For example `"http://127.0.0.1:8080/"`.83            proxy_auth: Any proxy authentication as a two-tuple of84                (username, password). May be either bytes or ascii-only str.85            proxy_headers: Any HTTP headers to use for the proxy requests.86                For example `{"Proxy-Authorization": "Basic <username>:<password>"}`.87            ssl_context: An SSL context to use for verifying connections.88                If not specified, the default `httpcore.default_ssl_context()`89                will be used.90            proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin.91            max_connections: The maximum number of concurrent HTTP connections that92                the pool should allow. Any attempt to send a request on a pool that93                would exceed this amount will block until a connection is available.94            max_keepalive_connections: The maximum number of idle HTTP connections95                that will be maintained in the pool.96            keepalive_expiry: The duration in seconds that an idle HTTP connection97                may be maintained for before being expired from the pool.98            http1: A boolean indicating if HTTP/1.1 requests should be supported99                by the connection pool. Defaults to True.100            http2: A boolean indicating if HTTP/2 requests should be supported by101                the connection pool. Defaults to False.102            retries: The maximum number of retries when trying to establish103                a connection.104            local_address: Local address to connect from. Can also be used to105                connect using a particular address family. Using106                `local_address="0.0.0.0"` will connect using an `AF_INET` address107                (IPv4), while using `local_address="::"` will connect using an108                `AF_INET6` address (IPv6).109            uds: Path to a Unix Domain Socket to use instead of TCP sockets.110            network_backend: A backend instance to use for handling network I/O.111        """112        super().__init__(113            ssl_context=ssl_context,114            max_connections=max_connections,115            max_keepalive_connections=max_keepalive_connections,116            keepalive_expiry=keepalive_expiry,117            http1=http1,118            http2=http2,119            network_backend=network_backend,120            retries=retries,121            local_address=local_address,122            uds=uds,123            socket_options=socket_options,124        )125 126        self._proxy_url = enforce_url(proxy_url, name="proxy_url")127        if (128            self._proxy_url.scheme == b"http" and proxy_ssl_context is not None129        ):  # pragma: no cover130            raise RuntimeError(131                "The `proxy_ssl_context` argument is not allowed for the http scheme"132            )133 134        self._ssl_context = ssl_context135        self._proxy_ssl_context = proxy_ssl_context136        self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")137        if proxy_auth is not None:138            username = enforce_bytes(proxy_auth[0], name="proxy_auth")139            password = enforce_bytes(proxy_auth[1], name="proxy_auth")140            userpass = username + b":" + password141            authorization = b"Basic " + base64.b64encode(userpass)142            self._proxy_headers = [143                (b"Proxy-Authorization", authorization)144            ] + self._proxy_headers145 146    def create_connection(self, origin: Origin) -> ConnectionInterface:147        if origin.scheme == b"http":148            return ForwardHTTPConnection(149                proxy_origin=self._proxy_url.origin,150                proxy_headers=self._proxy_headers,151                remote_origin=origin,152                keepalive_expiry=self._keepalive_expiry,153                network_backend=self._network_backend,154                proxy_ssl_context=self._proxy_ssl_context,155            )156        return TunnelHTTPConnection(157            proxy_origin=self._proxy_url.origin,158            proxy_headers=self._proxy_headers,159            remote_origin=origin,160            ssl_context=self._ssl_context,161            proxy_ssl_context=self._proxy_ssl_context,162            keepalive_expiry=self._keepalive_expiry,163            http1=self._http1,164            http2=self._http2,165            network_backend=self._network_backend,166        )167 168 169class ForwardHTTPConnection(ConnectionInterface):170    def __init__(171        self,172        proxy_origin: Origin,173        remote_origin: Origin,174        proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None,175        keepalive_expiry: float | None = None,176        network_backend: NetworkBackend | None = None,177        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,178        proxy_ssl_context: ssl.SSLContext | None = None,179    ) -> None:180        self._connection = HTTPConnection(181            origin=proxy_origin,182            keepalive_expiry=keepalive_expiry,183            network_backend=network_backend,184            socket_options=socket_options,185            ssl_context=proxy_ssl_context,186        )187        self._proxy_origin = proxy_origin188        self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")189        self._remote_origin = remote_origin190 191    def handle_request(self, request: Request) -> Response:192        headers = merge_headers(self._proxy_headers, request.headers)193        url = URL(194            scheme=self._proxy_origin.scheme,195            host=self._proxy_origin.host,196            port=self._proxy_origin.port,197            target=bytes(request.url),198        )199        proxy_request = Request(200            method=request.method,201            url=url,202            headers=headers,203            content=request.stream,204            extensions=request.extensions,205        )206        return self._connection.handle_request(proxy_request)207 208    def can_handle_request(self, origin: Origin) -> bool:209        return origin == self._remote_origin210 211    def close(self) -> None:212        self._connection.close()213 214    def info(self) -> str:215        return self._connection.info()216 217    def is_available(self) -> bool:218        return self._connection.is_available()219 220    def has_expired(self) -> bool:221        return self._connection.has_expired()222 223    def is_idle(self) -> bool:224        return self._connection.is_idle()225 226    def is_closed(self) -> bool:227        return self._connection.is_closed()228 229    def __repr__(self) -> str:230        return f"<{self.__class__.__name__} [{self.info()}]>"231 232 233class TunnelHTTPConnection(ConnectionInterface):234    def __init__(235        self,236        proxy_origin: Origin,237        remote_origin: Origin,238        ssl_context: ssl.SSLContext | None = None,239        proxy_ssl_context: ssl.SSLContext | None = None,240        proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None,241        keepalive_expiry: float | None = None,242        http1: bool = True,243        http2: bool = False,244        network_backend: NetworkBackend | None = None,245        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,246    ) -> None:247        self._connection: ConnectionInterface = HTTPConnection(248            origin=proxy_origin,249            keepalive_expiry=keepalive_expiry,250            network_backend=network_backend,251            socket_options=socket_options,252            ssl_context=proxy_ssl_context,253        )254        self._proxy_origin = proxy_origin255        self._remote_origin = remote_origin256        self._ssl_context = ssl_context257        self._proxy_ssl_context = proxy_ssl_context258        self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers")259        self._keepalive_expiry = keepalive_expiry260        self._http1 = http1261        self._http2 = http2262        self._connect_lock = Lock()263        self._connected = False264 265    def handle_request(self, request: Request) -> Response:266        timeouts = request.extensions.get("timeout", {})267        timeout = timeouts.get("connect", None)268 269        with self._connect_lock:270            if not self._connected:271                target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port)272 273                connect_url = URL(274                    scheme=self._proxy_origin.scheme,275                    host=self._proxy_origin.host,276                    port=self._proxy_origin.port,277                    target=target,278                )279                connect_headers = merge_headers(280                    [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers281                )282                connect_request = Request(283                    method=b"CONNECT",284                    url=connect_url,285                    headers=connect_headers,286                    extensions=request.extensions,287                )288                connect_response = self._connection.handle_request(289                    connect_request290                )291 292                if connect_response.status < 200 or connect_response.status > 299:293                    reason_bytes = connect_response.extensions.get("reason_phrase", b"")294                    reason_str = reason_bytes.decode("ascii", errors="ignore")295                    msg = "%d %s" % (connect_response.status, reason_str)296                    self._connection.close()297                    raise ProxyError(msg)298 299                stream = connect_response.extensions["network_stream"]300 301                # Upgrade the stream to SSL302                ssl_context = (303                    default_ssl_context()304                    if self._ssl_context is None305                    else self._ssl_context306                )307                alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"]308                ssl_context.set_alpn_protocols(alpn_protocols)309 310                kwargs = {311                    "ssl_context": ssl_context,312                    "server_hostname": self._remote_origin.host.decode("ascii"),313                    "timeout": timeout,314                }315                with Trace("start_tls", logger, request, kwargs) as trace:316                    stream = stream.start_tls(**kwargs)317                    trace.return_value = stream318 319                # Determine if we should be using HTTP/1.1 or HTTP/2320                ssl_object = stream.get_extra_info("ssl_object")321                http2_negotiated = (322                    ssl_object is not None323                    and ssl_object.selected_alpn_protocol() == "h2"324                )325 326                # Create the HTTP/1.1 or HTTP/2 connection327                if http2_negotiated or (self._http2 and not self._http1):328                    from .http2 import HTTP2Connection329 330                    self._connection = HTTP2Connection(331                        origin=self._remote_origin,332                        stream=stream,333                        keepalive_expiry=self._keepalive_expiry,334                    )335                else:336                    self._connection = HTTP11Connection(337                        origin=self._remote_origin,338                        stream=stream,339                        keepalive_expiry=self._keepalive_expiry,340                    )341 342                self._connected = True343        return self._connection.handle_request(request)344 345    def can_handle_request(self, origin: Origin) -> bool:346        return origin == self._remote_origin347 348    def close(self) -> None:349        self._connection.close()350 351    def info(self) -> str:352        return self._connection.info()353 354    def is_available(self) -> bool:355        return self._connection.is_available()356 357    def has_expired(self) -> bool:358        return self._connection.has_expired()359 360    def is_idle(self) -> bool:361        return self._connection.is_idle()362 363    def is_closed(self) -> bool:364        return self._connection.is_closed()365 366    def __repr__(self) -> str:367        return f"<{self.__class__.__name__} [{self.info()}]>"368 
codekingpro/portable-devtools · Team Ai