Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
client.py1647 linesDownload Raw Back to aiohttp
1"""HTTP Client for asyncio."""
2
3import asyncio
4import base64
5import hashlib
6import json
7import os
8import sys
9import traceback
10import warnings
11from contextlib import suppress
12from types import TracebackType
13from typing import (
14    TYPE_CHECKING,
15    Any,
16    Awaitable,
17    Callable,
18    Coroutine,
19    Final,
20    FrozenSet,
21    Generator,
22    Generic,
23    Iterable,
24    List,
25    Mapping,
26    Optional,
27    Sequence,
28    Set,
29    Tuple,
30    Type,
31    TypedDict,
32    TypeVar,
33    Union,
34)
35
36import attr
37from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr
38from yarl import URL
39
40from . import hdrs, http, payload
41from ._websocket.reader import WebSocketDataQueue
42from .abc import AbstractCookieJar
43from .client_exceptions import (
44    ClientConnectionError,
45    ClientConnectionResetError,
46    ClientConnectorCertificateError,
47    ClientConnectorDNSError,
48    ClientConnectorError,
49    ClientConnectorSSLError,
50    ClientError,
51    ClientHttpProxyError,
52    ClientOSError,
53    ClientPayloadError,
54    ClientProxyConnectionError,
55    ClientResponseError,
56    ClientSSLError,
57    ConnectionTimeoutError,
58    ContentTypeError,
59    InvalidURL,
60    InvalidUrlClientError,
61    InvalidUrlRedirectClientError,
62    NonHttpUrlClientError,
63    NonHttpUrlRedirectClientError,
64    RedirectClientError,
65    ServerConnectionError,
66    ServerDisconnectedError,
67    ServerFingerprintMismatch,
68    ServerTimeoutError,
69    SocketTimeoutError,
70    TooManyRedirects,
71    WSMessageTypeError,
72    WSServerHandshakeError,
73)
74from .client_middlewares import ClientMiddlewareType, build_client_middlewares
75from .client_reqrep import (
76    ClientRequest as ClientRequest,
77    ClientResponse as ClientResponse,
78    Fingerprint as Fingerprint,
79    RequestInfo as RequestInfo,
80    _merge_ssl_params,
81)
82from .client_ws import (
83    DEFAULT_WS_CLIENT_TIMEOUT,
84    ClientWebSocketResponse as ClientWebSocketResponse,
85    ClientWSTimeout as ClientWSTimeout,
86)
87from .connector import (
88    HTTP_AND_EMPTY_SCHEMA_SET,
89    BaseConnector as BaseConnector,
90    NamedPipeConnector as NamedPipeConnector,
91    TCPConnector as TCPConnector,
92    UnixConnector as UnixConnector,
93)
94from .cookiejar import CookieJar
95from .helpers import (
96    _SENTINEL,
97    DEBUG,
98    EMPTY_BODY_METHODS,
99    BasicAuth,
100    TimeoutHandle,
101    basicauth_from_netrc,
102    get_env_proxy_for_url,
103    netrc_from_env,
104    sentinel,
105    strip_auth_from_url,
106)
107from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter
108from .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse
109from .tracing import Trace, TraceConfig
110from .typedefs import JSONEncoder, LooseCookies, LooseHeaders, Query, StrOrURL
111
112__all__ = (
113    # client_exceptions
114    "ClientConnectionError",
115    "ClientConnectionResetError",
116    "ClientConnectorCertificateError",
117    "ClientConnectorDNSError",
118    "ClientConnectorError",
119    "ClientConnectorSSLError",
120    "ClientError",
121    "ClientHttpProxyError",
122    "ClientOSError",
123    "ClientPayloadError",
124    "ClientProxyConnectionError",
125    "ClientResponseError",
126    "ClientSSLError",
127    "ConnectionTimeoutError",
128    "ContentTypeError",
129    "InvalidURL",
130    "InvalidUrlClientError",
131    "RedirectClientError",
132    "NonHttpUrlClientError",
133    "InvalidUrlRedirectClientError",
134    "NonHttpUrlRedirectClientError",
135    "ServerConnectionError",
136    "ServerDisconnectedError",
137    "ServerFingerprintMismatch",
138    "ServerTimeoutError",
139    "SocketTimeoutError",
140    "TooManyRedirects",
141    "WSServerHandshakeError",
142    # client_reqrep
143    "ClientRequest",
144    "ClientResponse",
145    "Fingerprint",
146    "RequestInfo",
147    # connector
148    "BaseConnector",
149    "TCPConnector",
150    "UnixConnector",
151    "NamedPipeConnector",
152    # client_ws
153    "ClientWebSocketResponse",
154    # client
155    "ClientSession",
156    "ClientTimeout",
157    "ClientWSTimeout",
158    "request",
159    "WSMessageTypeError",
160)
161
162
163if TYPE_CHECKING:
164    from ssl import SSLContext
165else:
166    SSLContext = None
167
168if sys.version_info >= (3, 11) and TYPE_CHECKING:
169    from typing import Unpack
170
171
172class _RequestOptions(TypedDict, total=False):
173    params: Query
174    data: Any
175    json: Any
176    cookies: Union[LooseCookies, None]
177    headers: Union[LooseHeaders, None]
178    skip_auto_headers: Union[Iterable[str], None]
179    auth: Union[BasicAuth, None]
180    allow_redirects: bool
181    max_redirects: int
182    compress: Union[str, bool, None]
183    chunked: Union[bool, None]
184    expect100: bool
185    raise_for_status: Union[None, bool, Callable[[ClientResponse], Awaitable[None]]]
186    read_until_eof: bool
187    proxy: Union[StrOrURL, None]
188    proxy_auth: Union[BasicAuth, None]
189    timeout: "Union[ClientTimeout, _SENTINEL, None]"
190    ssl: Union[SSLContext, bool, Fingerprint]
191    server_hostname: Union[str, None]
192    proxy_headers: Union[LooseHeaders, None]
193    trace_request_ctx: Union[Mapping[str, Any], None]
194    read_bufsize: Union[int, None]
195    auto_decompress: Union[bool, None]
196    max_line_size: Union[int, None]
197    max_field_size: Union[int, None]
198    max_headers: Union[int, None]
199    middlewares: Optional[Sequence[ClientMiddlewareType]]
200
201
202@attr.s(auto_attribs=True, frozen=True, slots=True)
203class ClientTimeout:
204    total: Optional[float] = None
205    connect: Optional[float] = None
206    sock_read: Optional[float] = None
207    sock_connect: Optional[float] = None
208    ceil_threshold: float = 5
209
210    # pool_queue_timeout: Optional[float] = None
211    # dns_resolution_timeout: Optional[float] = None
212    # socket_connect_timeout: Optional[float] = None
213    # connection_acquiring_timeout: Optional[float] = None
214    # new_connection_timeout: Optional[float] = None
215    # http_header_timeout: Optional[float] = None
216    # response_body_timeout: Optional[float] = None
217
218    # to create a timeout specific for a single request, either
219    # - create a completely new one to overwrite the default
220    # - or use http://www.attrs.org/en/stable/api.html#attr.evolve
221    # to overwrite the defaults
222
223
224# 5 Minute default read timeout
225DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60, sock_connect=30)
226
227# https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2
228IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE"})
229
230_RetType = TypeVar("_RetType", ClientResponse, ClientWebSocketResponse)
231_CharsetResolver = Callable[[ClientResponse, bytes], str]
232
233
234class ClientSession:
235    """First-class interface for making HTTP requests."""
236
237    ATTRS = frozenset(
238        [
239            "_base_url",
240            "_base_url_origin",
241            "_source_traceback",
242            "_connector",
243            "_loop",
244            "_cookie_jar",
245            "_connector_owner",
246            "_default_auth",
247            "_version",
248            "_json_serialize",
249            "_requote_redirect_url",
250            "_timeout",
251            "_raise_for_status",
252            "_auto_decompress",
253            "_trust_env",
254            "_default_headers",
255            "_skip_auto_headers",
256            "_request_class",
257            "_response_class",
258            "_ws_response_class",
259            "_trace_configs",
260            "_read_bufsize",
261            "_max_line_size",
262            "_max_field_size",
263            "_max_headers",
264            "_resolve_charset",
265            "_default_proxy",
266            "_default_proxy_auth",
267            "_retry_connection",
268            "_middlewares",
269            "requote_redirect_url",
270        ]
271    )
272
273    _source_traceback: Optional[traceback.StackSummary] = None
274    _connector: Optional[BaseConnector] = None
275
276    def __init__(
277        self,
278        base_url: Optional[StrOrURL] = None,
279        *,
280        connector: Optional[BaseConnector] = None,
281        loop: Optional[asyncio.AbstractEventLoop] = None,
282        cookies: Optional[LooseCookies] = None,
283        headers: Optional[LooseHeaders] = None,
284        proxy: Optional[StrOrURL] = None,
285        proxy_auth: Optional[BasicAuth] = None,
286        skip_auto_headers: Optional[Iterable[str]] = None,
287        auth: Optional[BasicAuth] = None,
288        json_serialize: JSONEncoder = json.dumps,
289        request_class: Type[ClientRequest] = ClientRequest,
290        response_class: Type[ClientResponse] = ClientResponse,
291        ws_response_class: Type[ClientWebSocketResponse] = ClientWebSocketResponse,
292        version: HttpVersion = http.HttpVersion11,
293        cookie_jar: Optional[AbstractCookieJar] = None,
294        connector_owner: bool = True,
295        raise_for_status: Union[
296            bool, Callable[[ClientResponse], Awaitable[None]]
297        ] = False,
298        read_timeout: Union[float, _SENTINEL] = sentinel,
299        conn_timeout: Optional[float] = None,
300        timeout: Union[object, ClientTimeout] = sentinel,
301        auto_decompress: bool = True,
302        trust_env: bool = False,
303        requote_redirect_url: bool = True,
304        trace_configs: Optional[List[TraceConfig]] = None,
305        read_bufsize: int = 2**16,
306        max_line_size: int = 8190,
307        max_field_size: int = 8190,
308        max_headers: int = 128,
309        fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8",
310        middlewares: Sequence[ClientMiddlewareType] = (),
311        ssl_shutdown_timeout: Union[_SENTINEL, None, float] = sentinel,
312    ) -> None:
313        # We initialise _connector to None immediately, as it's referenced in __del__()
314        # and could cause issues if an exception occurs during initialisation.
315        self._connector: Optional[BaseConnector] = None
316
317        if loop is None:
318            if connector is not None:
319                loop = connector._loop
320
321        loop = loop or asyncio.get_running_loop()
322
323        if base_url is None or isinstance(base_url, URL):
324            self._base_url: Optional[URL] = base_url
325            self._base_url_origin = None if base_url is None else base_url.origin()
326        else:
327            self._base_url = URL(base_url)
328            self._base_url_origin = self._base_url.origin()
329            assert self._base_url.absolute, "Only absolute URLs are supported"
330        if self._base_url is not None and not self._base_url.path.endswith("/"):
331            raise ValueError("base_url must have a trailing '/'")
332
333        if timeout is sentinel or timeout is None:
334            self._timeout = DEFAULT_TIMEOUT
335            if read_timeout is not sentinel:
336                warnings.warn(
337                    "read_timeout is deprecated, use timeout argument instead",
338                    DeprecationWarning,
339                    stacklevel=2,
340                )
341                self._timeout = attr.evolve(self._timeout, total=read_timeout)
342            if conn_timeout is not None:
343                self._timeout = attr.evolve(self._timeout, connect=conn_timeout)
344                warnings.warn(
345                    "conn_timeout is deprecated, use timeout argument instead",
346                    DeprecationWarning,
347                    stacklevel=2,
348                )
349        else:
350            if not isinstance(timeout, ClientTimeout):
351                raise ValueError(
352                    f"timeout parameter cannot be of {type(timeout)} type, "
353                    "please use 'timeout=ClientTimeout(...)'",
354                )
355            self._timeout = timeout
356            if read_timeout is not sentinel:
357                raise ValueError(
358                    "read_timeout and timeout parameters "
359                    "conflict, please setup "
360                    "timeout.read"
361                )
362            if conn_timeout is not None:
363                raise ValueError(
364                    "conn_timeout and timeout parameters "
365                    "conflict, please setup "
366                    "timeout.connect"
367                )
368
369        if ssl_shutdown_timeout is not sentinel:
370            warnings.warn(
371                "The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0",
372                DeprecationWarning,
373                stacklevel=2,
374            )
375
376        if connector is None:
377            connector = TCPConnector(
378                loop=loop, ssl_shutdown_timeout=ssl_shutdown_timeout
379            )
380
381        if connector._loop is not loop:
382            raise RuntimeError("Session and connector has to use same event loop")
383
384        self._loop = loop
385
386        if loop.get_debug():
387            self._source_traceback = traceback.extract_stack(sys._getframe(1))
388
389        if cookie_jar is None:
390            cookie_jar = CookieJar(loop=loop)
391        self._cookie_jar = cookie_jar
392
393        if cookies:
394            self._cookie_jar.update_cookies(cookies)
395
396        self._connector = connector
397        self._connector_owner = connector_owner
398        self._default_auth = auth
399        self._version = version
400        self._json_serialize = json_serialize
401        self._raise_for_status = raise_for_status
402        self._auto_decompress = auto_decompress
403        self._trust_env = trust_env
404        self._requote_redirect_url = requote_redirect_url
405        self._read_bufsize = read_bufsize
406        self._max_line_size = max_line_size
407        self._max_field_size = max_field_size
408        self._max_headers = max_headers
409
410        # Convert to list of tuples
411        if headers:
412            real_headers: CIMultiDict[str] = CIMultiDict(headers)
413        else:
414            real_headers = CIMultiDict()
415        self._default_headers: CIMultiDict[str] = real_headers
416        if skip_auto_headers is not None:
417            self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers)
418        else:
419            self._skip_auto_headers = frozenset()
420
421        self._request_class = request_class
422        self._response_class = response_class
423        self._ws_response_class = ws_response_class
424
425        self._trace_configs = trace_configs or []
426        for trace_config in self._trace_configs:
427            trace_config.freeze()
428
429        self._resolve_charset = fallback_charset_resolver
430
431        self._default_proxy = proxy
432        self._default_proxy_auth = proxy_auth
433        self._retry_connection: bool = True
434        self._middlewares = middlewares
435
436    def __init_subclass__(cls: Type["ClientSession"]) -> None:
437        warnings.warn(
438            "Inheritance class {} from ClientSession "
439            "is discouraged".format(cls.__name__),
440            DeprecationWarning,
441            stacklevel=2,
442        )
443
444    if DEBUG:
445
446        def __setattr__(self, name: str, val: Any) -> None:
447            if name not in self.ATTRS:
448                warnings.warn(
449                    "Setting custom ClientSession.{} attribute "
450                    "is discouraged".format(name),
451                    DeprecationWarning,
452                    stacklevel=2,
453                )
454            super().__setattr__(name, val)
455
456    def __del__(self, _warnings: Any = warnings) -> None:
457        if not self.closed:
458            kwargs = {"source": self}
459            _warnings.warn(
460                f"Unclosed client session {self!r}", ResourceWarning, **kwargs
461            )
462            context = {"client_session": self, "message": "Unclosed client session"}
463            if self._source_traceback is not None:
464                context["source_traceback"] = self._source_traceback
465            self._loop.call_exception_handler(context)
466
467    if sys.version_info >= (3, 11) and TYPE_CHECKING:
468
469        def request(
470            self,
471            method: str,
472            url: StrOrURL,
473            **kwargs: Unpack[_RequestOptions],
474        ) -> "_RequestContextManager": ...
475
476    else:
477
478        def request(
479            self, method: str, url: StrOrURL, **kwargs: Any
480        ) -> "_RequestContextManager":
481            """Perform HTTP request."""
482            return _RequestContextManager(self._request(method, url, **kwargs))
483
484    def _build_url(self, str_or_url: StrOrURL) -> URL:
485        url = URL(str_or_url)
486        if self._base_url and not url.absolute:
487            return self._base_url.join(url)
488        return url
489
490    async def _request(
491        self,
492        method: str,
493        str_or_url: StrOrURL,
494        *,
495        params: Query = None,
496        data: Any = None,
497        json: Any = None,
498        cookies: Optional[LooseCookies] = None,
499        headers: Optional[LooseHeaders] = None,
500        skip_auto_headers: Optional[Iterable[str]] = None,
501        auth: Optional[BasicAuth] = None,
502        allow_redirects: bool = True,
503        max_redirects: int = 10,
504        compress: Union[str, bool, None] = None,
505        chunked: Optional[bool] = None,
506        expect100: bool = False,
507        raise_for_status: Union[
508            None, bool, Callable[[ClientResponse], Awaitable[None]]
509        ] = None,
510        read_until_eof: bool = True,
511        proxy: Optional[StrOrURL] = None,
512        proxy_auth: Optional[BasicAuth] = None,
513        timeout: Union[ClientTimeout, _SENTINEL] = sentinel,
514        verify_ssl: Optional[bool] = None,
515        fingerprint: Optional[bytes] = None,
516        ssl_context: Optional[SSLContext] = None,
517        ssl: Union[SSLContext, bool, Fingerprint] = True,
518        server_hostname: Optional[str] = None,
519        proxy_headers: Optional[LooseHeaders] = None,
520        trace_request_ctx: Optional[Mapping[str, Any]] = None,
521        read_bufsize: Optional[int] = None,
522        auto_decompress: Optional[bool] = None,
523        max_line_size: Optional[int] = None,
524        max_field_size: Optional[int] = None,
525        max_headers: Optional[int] = None,
526        middlewares: Optional[Sequence[ClientMiddlewareType]] = None,
527    ) -> ClientResponse:
528
529        # NOTE: timeout clamps existing connect and read timeouts.  We cannot
530        # set the default to None because we need to detect if the user wants
531        # to use the existing timeouts by setting timeout to None.
532
533        if self.closed:
534            raise RuntimeError("Session is closed")
535
536        ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
537
538        if data is not None and json is not None:
539            raise ValueError(
540                "data and json parameters can not be used at the same time"
541            )
542        elif json is not None:
543            data = payload.JsonPayload(json, dumps=self._json_serialize)
544
545        if not isinstance(chunked, bool) and chunked is not None:
546            warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)
547
548        redirects = 0
549        history: List[ClientResponse] = []
550        version = self._version
551        params = params or {}
552
553        # Merge with default headers and transform to CIMultiDict
554        headers = self._prepare_headers(headers)
555
556        try:
557            url = self._build_url(str_or_url)
558        except ValueError as e:
559            raise InvalidUrlClientError(str_or_url) from e
560
561        assert self._connector is not None
562        if url.scheme not in self._connector.allowed_protocol_schema_set:
563            raise NonHttpUrlClientError(url)
564
565        skip_headers: Optional[Iterable[istr]]
566        if skip_auto_headers is not None:
567            skip_headers = {
568                istr(i) for i in skip_auto_headers
569            } | self._skip_auto_headers
570        elif self._skip_auto_headers:
571            skip_headers = self._skip_auto_headers
572        else:
573            skip_headers = None
574
575        if proxy is None:
576            proxy = self._default_proxy
577        if proxy_auth is None:
578            proxy_auth = self._default_proxy_auth
579
580        if proxy is None:
581            proxy_headers = None
582        else:
583            proxy_headers = self._prepare_headers(proxy_headers)
584            try:
585                proxy = URL(proxy)
586            except ValueError as e:
587                raise InvalidURL(proxy) from e
588
589        if timeout is sentinel:
590            real_timeout: ClientTimeout = self._timeout
591        else:
592            if not isinstance(timeout, ClientTimeout):
593                real_timeout = ClientTimeout(total=timeout)
594            else:
595                real_timeout = timeout
596        # timeout is cumulative for all request operations
597        # (request, redirects, responses, data consuming)
598        tm = TimeoutHandle(
599            self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold
600        )
601        handle = tm.start()
602
603        if read_bufsize is None:
604            read_bufsize = self._read_bufsize
605
606        if auto_decompress is None:
607            auto_decompress = self._auto_decompress
608
609        if max_line_size is None:
610            max_line_size = self._max_line_size
611
612        if max_field_size is None:
613            max_field_size = self._max_field_size
614
615        if max_headers is None:
616            max_headers = self._max_headers
617
618        traces = [
619            Trace(
620                self,
621                trace_config,
622                trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
623            )
624            for trace_config in self._trace_configs
625        ]
626
627        for trace in traces:
628            await trace.send_request_start(method, url.update_query(params), headers)
629
630        timer = tm.timer()
631        try:
632            with timer:
633                # https://www.rfc-editor.org/rfc/rfc9112.html#name-retrying-requests
634                retry_persistent_connection = (
635                    self._retry_connection and method in IDEMPOTENT_METHODS
636                )
637                while True:
638                    url, auth_from_url = strip_auth_from_url(url)
639                    if not url.raw_host:
640                        # NOTE: Bail early, otherwise, causes `InvalidURL` through
641                        # NOTE: `self._request_class()` below.
642                        err_exc_cls = (
643                            InvalidUrlRedirectClientError
644                            if redirects
645                            else InvalidUrlClientError
646                        )
647                        raise err_exc_cls(url)
648                    # If `auth` was passed for an already authenticated URL,
649                    # disallow only if this is the initial URL; this is to avoid issues
650                    # with sketchy redirects that are not the caller's responsibility
651                    if not history and (auth and auth_from_url):
652                        raise ValueError(
653                            "Cannot combine AUTH argument with "
654                            "credentials encoded in URL"
655                        )
656
657                    # Override the auth with the one from the URL only if we
658                    # have no auth, or if we got an auth from a redirect URL
659                    if auth is None or (history and auth_from_url is not None):
660                        auth = auth_from_url
661
662                    if (
663                        auth is None
664                        and self._default_auth
665                        and (
666                            not self._base_url or self._base_url_origin == url.origin()
667                        )
668                    ):
669                        auth = self._default_auth
670
671                    # Try netrc if auth is still None and trust_env is enabled.
672                    if auth is None and self._trust_env and url.host is not None:
673                        auth = await self._loop.run_in_executor(
674                            None, self._get_netrc_auth, url.host
675                        )
676
677                    # It would be confusing if we support explicit
678                    # Authorization header with auth argument
679                    if (
680                        headers is not None
681                        and auth is not None
682                        and hdrs.AUTHORIZATION in headers
683                    ):
684                        raise ValueError(
685                            "Cannot combine AUTHORIZATION header "
686                            "with AUTH argument or credentials "
687                            "encoded in URL"
688                        )
689
690                    all_cookies = self._cookie_jar.filter_cookies(url)
691
692                    if cookies is not None:
693                        tmp_cookie_jar = CookieJar(
694                            quote_cookie=self._cookie_jar.quote_cookie
695                        )
696                        tmp_cookie_jar.update_cookies(cookies)
697                        req_cookies = tmp_cookie_jar.filter_cookies(url)
698                        if req_cookies:
699                            all_cookies.load(req_cookies)
700
701                    proxy_: Optional[URL] = None
702                    if proxy is not None:
703                        proxy_ = URL(proxy)
704                    elif self._trust_env:
705                        with suppress(LookupError):
706                            proxy_, proxy_auth = await asyncio.to_thread(
707                                get_env_proxy_for_url, url
708                            )
709
710                    req = self._request_class(
711                        method,
712                        url,
713                        params=params,
714                        headers=headers,
715                        skip_auto_headers=skip_headers,
716                        data=data,
717                        cookies=all_cookies,
718                        auth=auth,
719                        version=version,
720                        compress=compress,
721                        chunked=chunked,
722                        expect100=expect100,
723                        loop=self._loop,
724                        response_class=self._response_class,
725                        proxy=proxy_,
726                        proxy_auth=proxy_auth,
727                        timer=timer,
728                        session=self,
729                        ssl=ssl if ssl is not None else True,
730                        server_hostname=server_hostname,
731                        proxy_headers=proxy_headers,
732                        traces=traces,
733                        trust_env=self.trust_env,
734                    )
735
736                    async def _connect_and_send_request(
737                        req: ClientRequest,
738                    ) -> ClientResponse:
739                        # connection timeout
740                        assert self._connector is not None
741                        try:
742                            conn = await self._connector.connect(
743                                req, traces=traces, timeout=real_timeout
744                            )
745                        except asyncio.TimeoutError as exc:
746                            raise ConnectionTimeoutError(
747                                f"Connection timeout to host {req.url}"
748                            ) from exc
749
750                        assert conn.protocol is not None
751                        conn.protocol.set_response_params(
752                            timer=timer,
753                            skip_payload=req.method in EMPTY_BODY_METHODS,
754                            read_until_eof=read_until_eof,
755                            auto_decompress=auto_decompress,
756                            read_timeout=real_timeout.sock_read,
757                            read_bufsize=read_bufsize,
758                            timeout_ceil_threshold=self._connector._timeout_ceil_threshold,
759                            max_line_size=max_line_size,
760                            max_field_size=max_field_size,
761                            max_headers=max_headers,
762                        )
763                        try:
764                            resp = await req.send(conn)
765                            try:
766                                await resp.start(conn)
767                            except BaseException:
768                                resp.close()
769                                raise
770                        except BaseException:
771                            conn.close()
772                            raise
773                        return resp
774
775                    # Apply middleware (if any) - per-request middleware overrides session middleware
776                    effective_middlewares = (
777                        self._middlewares if middlewares is None else middlewares
778                    )
779
780                    if effective_middlewares:
781                        handler = build_client_middlewares(
782                            _connect_and_send_request, effective_middlewares
783                        )
784                    else:
785                        handler = _connect_and_send_request
786
787                    try:
788                        resp = await handler(req)
789                    # Client connector errors should not be retried
790                    except (
791                        ConnectionTimeoutError,
792                        ClientConnectorError,
793                        ClientConnectorCertificateError,
794                        ClientConnectorSSLError,
795                    ):
796                        raise
797                    except (ClientOSError, ServerDisconnectedError):
798                        if retry_persistent_connection:
799                            retry_persistent_connection = False
800                            continue
801                        raise
802                    except ClientError:
803                        raise
804                    except OSError as exc:
805                        if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
806                            raise
807                        raise ClientOSError(*exc.args) from exc
808
809                    # Update cookies from raw headers to preserve duplicates
810                    if resp._raw_cookie_headers:
811                        self._cookie_jar.update_cookies_from_headers(
812                            resp._raw_cookie_headers, resp.url
813                        )
814
815                    # redirects
816                    if resp.status in (301, 302, 303, 307, 308) and allow_redirects:
817
818                        for trace in traces:
819                            await trace.send_request_redirect(
820                                method, url.update_query(params), headers, resp
821                            )
822
823                        redirects += 1
824                        history.append(resp)
825                        if max_redirects and redirects >= max_redirects:
826                            if req._body is not None:
827                                await req._body.close()
828                            resp.close()
829                            raise TooManyRedirects(
830                                history[0].request_info, tuple(history)
831                            )
832
833                        # For 301 and 302, mimic IE, now changed in RFC
834                        # https://github.com/kennethreitz/requests/pull/269
835                        if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (
836                            resp.status in (301, 302) and resp.method == hdrs.METH_POST
837                        ):
838                            method = hdrs.METH_GET
839                            data = None
840                            if headers.get(hdrs.CONTENT_LENGTH):
841                                headers.pop(hdrs.CONTENT_LENGTH)
842                        else:
843                            # For 307/308, always preserve the request body
844                            # For 301/302 with non-POST methods, preserve the request body
845                            # https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1
846                            # Use the existing payload to avoid recreating it from a potentially consumed file
847                            data = req._body
848
849                        r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(
850                            hdrs.URI
851                        )
852                        if r_url is None:
853                            # see github.com/aio-libs/aiohttp/issues/2022
854                            break
855                        else:
856                            # reading from correct redirection
857                            # response is forbidden
858                            resp.release()
859
860                        try:
861                            parsed_redirect_url = URL(
862                                r_url, encoded=not self._requote_redirect_url
863                            )
864                        except ValueError as e:
865                            if req._body is not None:
866                                await req._body.close()
867                            resp.close()
868                            raise InvalidUrlRedirectClientError(
869                                r_url,
870                                "Server attempted redirecting to a location that does not look like a URL",
871                            ) from e
872
873                        scheme = parsed_redirect_url.scheme
874                        if scheme not in HTTP_AND_EMPTY_SCHEMA_SET:
875                            if req._body is not None:
876                                await req._body.close()
877                            resp.close()
878                            raise NonHttpUrlRedirectClientError(r_url)
879                        elif not scheme:
880                            parsed_redirect_url = url.join(parsed_redirect_url)
881
882                        try:
883                            redirect_origin = parsed_redirect_url.origin()
884                        except ValueError as origin_val_err:
885                            if req._body is not None:
886                                await req._body.close()
887                            resp.close()
888                            raise InvalidUrlRedirectClientError(
889                                parsed_redirect_url,
890                                "Invalid redirect URL origin",
891                            ) from origin_val_err
892
893                        if url.origin() != redirect_origin:
894                            auth = None
895                            headers.pop(hdrs.AUTHORIZATION, None)
896                            headers.pop(hdrs.COOKIE, None)
897                            headers.pop(hdrs.PROXY_AUTHORIZATION, None)
898
899                        url = parsed_redirect_url
900                        params = {}
901                        resp.release()
902                        continue
903
904                    break
905
906            if req._body is not None:
907                await req._body.close()
908            # check response status
909            if raise_for_status is None:
910                raise_for_status = self._raise_for_status
911
912            if raise_for_status is None:
913                pass
914            elif callable(raise_for_status):
915                await raise_for_status(resp)
916            elif raise_for_status:
917                resp.raise_for_status()
918
919            # register connection
920            if handle is not None:
921                if resp.connection is not None:
922                    resp.connection.add_callback(handle.cancel)
923                else:
924                    handle.cancel()
925
926            resp._history = tuple(history)
927
928            for trace in traces:
929                await trace.send_request_end(
930                    method, url.update_query(params), headers, resp
931                )
932            return resp
933
934        except BaseException as e:
935            # cleanup timer
936            tm.close()
937            if handle:
938                handle.cancel()
939                handle = None
940
941            for trace in traces:
942                await trace.send_request_exception(
943                    method, url.update_query(params), headers, e
944                )
945            raise
946
947    def ws_connect(
948        self,
949        url: StrOrURL,
950        *,
951        method: str = hdrs.METH_GET,
952        protocols: Iterable[str] = (),
953        timeout: Union[ClientWSTimeout, _SENTINEL] = sentinel,
954        receive_timeout: Optional[float] = None,
955        autoclose: bool = True,
956        autoping: bool = True,
957        heartbeat: Optional[float] = None,
958        auth: Optional[BasicAuth] = None,
959        origin: Optional[str] = None,
960        params: Query = None,
961        headers: Optional[LooseHeaders] = None,
962        proxy: Optional[StrOrURL] = None,
963        proxy_auth: Optional[BasicAuth] = None,
964        ssl: Union[SSLContext, bool, Fingerprint] = True,
965        verify_ssl: Optional[bool] = None,
966        fingerprint: Optional[bytes] = None,
967        ssl_context: Optional[SSLContext] = None,
968        server_hostname: Optional[str] = None,
969        proxy_headers: Optional[LooseHeaders] = None,
970        compress: int = 0,
971        max_msg_size: int = 4 * 1024 * 1024,
972    ) -> "_WSRequestContextManager":
973        """Initiate websocket connection."""
974        return _WSRequestContextManager(
975            self._ws_connect(
976                url,
977                method=method,
978                protocols=protocols,
979                timeout=timeout,
980                receive_timeout=receive_timeout,
981                autoclose=autoclose,
982                autoping=autoping,
983                heartbeat=heartbeat,
984                auth=auth,
985                origin=origin,
986                params=params,
987                headers=headers,
988                proxy=proxy,
989                proxy_auth=proxy_auth,
990                ssl=ssl,
991                verify_ssl=verify_ssl,
992                fingerprint=fingerprint,
993                ssl_context=ssl_context,
994                server_hostname=server_hostname,
995                proxy_headers=proxy_headers,
996                compress=compress,
997                max_msg_size=max_msg_size,
998            )
999        )
1000
1001    async def _ws_connect(
1002        self,
1003        url: StrOrURL,
1004        *,
1005        method: str = hdrs.METH_GET,
1006        protocols: Iterable[str] = (),
1007        timeout: Union[ClientWSTimeout, _SENTINEL] = sentinel,
1008        receive_timeout: Optional[float] = None,
1009        autoclose: bool = True,
1010        autoping: bool = True,
1011        heartbeat: Optional[float] = None,
1012        auth: Optional[BasicAuth] = None,
1013        origin: Optional[str] = None,
1014        params: Query = None,
1015        headers: Optional[LooseHeaders] = None,
1016        proxy: Optional[StrOrURL] = None,
1017        proxy_auth: Optional[BasicAuth] = None,
1018        ssl: Union[SSLContext, bool, Fingerprint] = True,
1019        verify_ssl: Optional[bool] = None,
1020        fingerprint: Optional[bytes] = None,
1021        ssl_context: Optional[SSLContext] = None,
1022        server_hostname: Optional[str] = None,
1023        proxy_headers: Optional[LooseHeaders] = None,
1024        compress: int = 0,
1025        max_msg_size: int = 4 * 1024 * 1024,
1026    ) -> ClientWebSocketResponse:
1027        if timeout is not sentinel:
1028            if isinstance(timeout, ClientWSTimeout):
1029                ws_timeout = timeout
1030            else:
1031                warnings.warn(
1032                    "parameter 'timeout' of type 'float' "
1033                    "is deprecated, please use "
1034                    "'timeout=ClientWSTimeout(ws_close=...)'",
1035                    DeprecationWarning,
1036                    stacklevel=2,
1037                )
1038                ws_timeout = ClientWSTimeout(ws_close=timeout)
1039        else:
1040            ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT
1041        if receive_timeout is not None:
1042            warnings.warn(
1043                "float parameter 'receive_timeout' "
1044                "is deprecated, please use parameter "
1045                "'timeout=ClientWSTimeout(ws_receive=...)'",
1046                DeprecationWarning,
1047                stacklevel=2,
1048            )
1049            ws_timeout = attr.evolve(ws_timeout, ws_receive=receive_timeout)
1050
1051        if headers is None:
1052            real_headers: CIMultiDict[str] = CIMultiDict()
1053        else:
1054            real_headers = CIMultiDict(headers)
1055
1056        default_headers = {
1057            hdrs.UPGRADE: "websocket",
1058            hdrs.CONNECTION: "Upgrade",
1059            hdrs.SEC_WEBSOCKET_VERSION: "13",
1060        }
1061
1062        for key, value in default_headers.items():
1063            real_headers.setdefault(key, value)
1064
1065        sec_key = base64.b64encode(os.urandom(16))
1066        real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode()
1067
1068        if protocols:
1069            real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols)
1070        if origin is not None:
1071            real_headers[hdrs.ORIGIN] = origin
1072        if compress:
1073            extstr = ws_ext_gen(compress=compress)
1074            real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr
1075
1076        # For the sake of backward compatibility, if user passes in None, convert it to True
1077        if ssl is None:
1078            warnings.warn(
1079                "ssl=None is deprecated, please use ssl=True",
1080                DeprecationWarning,
1081                stacklevel=2,
1082            )
1083            ssl = True
1084        ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
1085
1086        # send request
1087        resp = await self.request(
1088            method,
1089            url,
1090            params=params,
1091            headers=real_headers,
1092            read_until_eof=False,
1093            auth=auth,
1094            proxy=proxy,
1095            proxy_auth=proxy_auth,
1096            ssl=ssl,
1097            server_hostname=server_hostname,
1098            proxy_headers=proxy_headers,
1099        )
1100
1101        try:
1102            # check handshake
1103            if resp.status != 101:
1104                raise WSServerHandshakeError(
1105                    resp.request_info,
1106                    resp.history,
1107                    message="Invalid response status",
1108                    status=resp.status,
1109                    headers=resp.headers,
1110                )
1111
1112            if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket":
1113                raise WSServerHandshakeError(
1114                    resp.request_info,
1115                    resp.history,
1116                    message="Invalid upgrade header",
1117                    status=resp.status,
1118                    headers=resp.headers,
1119                )
1120
1121            if resp.headers.get(hdrs.CONNECTION, "").lower() != "upgrade":
1122                raise WSServerHandshakeError(
1123                    resp.request_info,
1124                    resp.history,
1125                    message="Invalid connection header",
1126                    status=resp.status,
1127                    headers=resp.headers,
1128                )
1129
1130            # key calculation
1131            r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "")
1132            match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()
1133            if r_key != match:
1134                raise WSServerHandshakeError(
1135                    resp.request_info,
1136                    resp.history,
1137                    message="Invalid challenge response",
1138                    status=resp.status,
1139                    headers=resp.headers,
1140                )
1141
1142            # websocket protocol
1143            protocol = None
1144            if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers:
1145                resp_protocols = [
1146                    proto.strip()
1147                    for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
1148                ]
1149
1150                for proto in resp_protocols:
1151                    if proto in protocols:
1152                        protocol = proto
1153                        break
1154
1155            # websocket compress
1156            notakeover = False
1157            if compress:
1158                compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)
1159                if compress_hdrs:
1160                    try:
1161                        compress, notakeover = ws_ext_parse(compress_hdrs)
1162                    except WSHandshakeError as exc:
1163                        raise WSServerHandshakeError(
1164                            resp.request_info,
1165                            resp.history,
1166                            message=exc.args[0],
1167                            status=resp.status,
1168                            headers=resp.headers,
1169                        ) from exc
1170                else:
1171                    compress = 0
1172                    notakeover = False
1173
1174            conn = resp.connection
1175            assert conn is not None
1176            conn_proto = conn.protocol
1177            assert conn_proto is not None
1178
1179            # For WS connection the read_timeout must be either receive_timeout or greater
1180            # None == no timeout, i.e. infinite timeout, so None is the max timeout possible
1181            if ws_timeout.ws_receive is None:
1182                # Reset regardless
1183                conn_proto.read_timeout = None
1184            elif conn_proto.read_timeout is not None:
1185                conn_proto.read_timeout = max(
1186                    ws_timeout.ws_receive, conn_proto.read_timeout
1187                )
1188
1189            transport = conn.transport
1190            assert transport is not None
1191            reader = WebSocketDataQueue(conn_proto, 2**16, loop=self._loop)
1192            conn_proto.set_parser(WebSocketReader(reader, max_msg_size), reader)
1193            writer = WebSocketWriter(
1194                conn_proto,
1195                transport,
1196                use_mask=True,
1197                compress=compress,
1198                notakeover=notakeover,
1199            )
1200        except BaseException:

Showing the first 1,200 of 1647 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai