Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/aiohttp/client.py: 54%
491 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:40 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:40 +0000
1"""HTTP Client for asyncio."""
3import asyncio
4import base64
5import hashlib
6import json
7import os
8import sys
9import traceback
10import warnings
11from contextlib import suppress
12from types import SimpleNamespace, 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 Literal,
26 Mapping,
27 Optional,
28 Set,
29 Tuple,
30 Type,
31 TypeVar,
32 Union,
33)
35import attr
36from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr
37from yarl import URL
39from . import hdrs, http, payload
40from .abc import AbstractCookieJar
41from .client_exceptions import (
42 ClientConnectionError as ClientConnectionError,
43 ClientConnectorCertificateError as ClientConnectorCertificateError,
44 ClientConnectorError as ClientConnectorError,
45 ClientConnectorSSLError as ClientConnectorSSLError,
46 ClientError as ClientError,
47 ClientHttpProxyError as ClientHttpProxyError,
48 ClientOSError as ClientOSError,
49 ClientPayloadError as ClientPayloadError,
50 ClientProxyConnectionError as ClientProxyConnectionError,
51 ClientResponseError as ClientResponseError,
52 ClientSSLError as ClientSSLError,
53 ContentTypeError as ContentTypeError,
54 InvalidURL as InvalidURL,
55 ServerConnectionError as ServerConnectionError,
56 ServerDisconnectedError as ServerDisconnectedError,
57 ServerFingerprintMismatch as ServerFingerprintMismatch,
58 ServerTimeoutError as ServerTimeoutError,
59 TooManyRedirects as TooManyRedirects,
60 WSServerHandshakeError as WSServerHandshakeError,
61)
62from .client_reqrep import (
63 ClientRequest as ClientRequest,
64 ClientResponse as ClientResponse,
65 Fingerprint as Fingerprint,
66 RequestInfo as RequestInfo,
67 _merge_ssl_params,
68)
69from .client_ws import ClientWebSocketResponse as ClientWebSocketResponse
70from .connector import (
71 BaseConnector as BaseConnector,
72 NamedPipeConnector as NamedPipeConnector,
73 TCPConnector as TCPConnector,
74 UnixConnector as UnixConnector,
75)
76from .cookiejar import CookieJar
77from .helpers import (
78 _SENTINEL,
79 DEBUG,
80 BasicAuth,
81 TimeoutHandle,
82 ceil_timeout,
83 get_env_proxy_for_url,
84 get_running_loop,
85 method_must_be_empty_body,
86 sentinel,
87 strip_auth_from_url,
88)
89from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter
90from .http_websocket import WSHandshakeError, WSMessage, ws_ext_gen, ws_ext_parse
91from .streams import FlowControlDataQueue
92from .tracing import Trace, TraceConfig
93from .typedefs import JSONEncoder, LooseCookies, LooseHeaders, StrOrURL
95__all__ = (
96 # client_exceptions
97 "ClientConnectionError",
98 "ClientConnectorCertificateError",
99 "ClientConnectorError",
100 "ClientConnectorSSLError",
101 "ClientError",
102 "ClientHttpProxyError",
103 "ClientOSError",
104 "ClientPayloadError",
105 "ClientProxyConnectionError",
106 "ClientResponseError",
107 "ClientSSLError",
108 "ContentTypeError",
109 "InvalidURL",
110 "ServerConnectionError",
111 "ServerDisconnectedError",
112 "ServerFingerprintMismatch",
113 "ServerTimeoutError",
114 "TooManyRedirects",
115 "WSServerHandshakeError",
116 # client_reqrep
117 "ClientRequest",
118 "ClientResponse",
119 "Fingerprint",
120 "RequestInfo",
121 # connector
122 "BaseConnector",
123 "TCPConnector",
124 "UnixConnector",
125 "NamedPipeConnector",
126 # client_ws
127 "ClientWebSocketResponse",
128 # client
129 "ClientSession",
130 "ClientTimeout",
131 "request",
132)
135if TYPE_CHECKING:
136 from ssl import SSLContext
137else:
138 SSLContext = None
141@attr.s(auto_attribs=True, frozen=True, slots=True)
142class ClientTimeout:
143 total: Optional[float] = None
144 connect: Optional[float] = None
145 sock_read: Optional[float] = None
146 sock_connect: Optional[float] = None
147 ceil_threshold: float = 5
149 # pool_queue_timeout: Optional[float] = None
150 # dns_resolution_timeout: Optional[float] = None
151 # socket_connect_timeout: Optional[float] = None
152 # connection_acquiring_timeout: Optional[float] = None
153 # new_connection_timeout: Optional[float] = None
154 # http_header_timeout: Optional[float] = None
155 # response_body_timeout: Optional[float] = None
157 # to create a timeout specific for a single request, either
158 # - create a completely new one to overwrite the default
159 # - or use http://www.attrs.org/en/stable/api.html#attr.evolve
160 # to overwrite the defaults
163# 5 Minute default read timeout
164DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60)
166_RetType = TypeVar("_RetType")
167_CharsetResolver = Callable[[ClientResponse, bytes], str]
170class ClientSession:
171 """First-class interface for making HTTP requests."""
173 ATTRS = frozenset(
174 [
175 "_base_url",
176 "_source_traceback",
177 "_connector",
178 "requote_redirect_url",
179 "_loop",
180 "_cookie_jar",
181 "_connector_owner",
182 "_default_auth",
183 "_version",
184 "_json_serialize",
185 "_requote_redirect_url",
186 "_timeout",
187 "_raise_for_status",
188 "_auto_decompress",
189 "_trust_env",
190 "_default_headers",
191 "_skip_auto_headers",
192 "_request_class",
193 "_response_class",
194 "_ws_response_class",
195 "_trace_configs",
196 "_read_bufsize",
197 "_max_line_size",
198 "_max_field_size",
199 "_resolve_charset",
200 ]
201 )
203 _source_traceback: Optional[traceback.StackSummary] = None
204 _connector: Optional[BaseConnector] = None
206 def __init__(
207 self,
208 base_url: Optional[StrOrURL] = None,
209 *,
210 connector: Optional[BaseConnector] = None,
211 loop: Optional[asyncio.AbstractEventLoop] = None,
212 cookies: Optional[LooseCookies] = None,
213 headers: Optional[LooseHeaders] = None,
214 skip_auto_headers: Optional[Iterable[str]] = None,
215 auth: Optional[BasicAuth] = None,
216 json_serialize: JSONEncoder = json.dumps,
217 request_class: Type[ClientRequest] = ClientRequest,
218 response_class: Type[ClientResponse] = ClientResponse,
219 ws_response_class: Type[ClientWebSocketResponse] = ClientWebSocketResponse,
220 version: HttpVersion = http.HttpVersion11,
221 cookie_jar: Optional[AbstractCookieJar] = None,
222 connector_owner: bool = True,
223 raise_for_status: Union[
224 bool, Callable[[ClientResponse], Awaitable[None]]
225 ] = False,
226 read_timeout: Union[float, _SENTINEL] = sentinel,
227 conn_timeout: Optional[float] = None,
228 timeout: Union[object, ClientTimeout] = sentinel,
229 auto_decompress: bool = True,
230 trust_env: bool = False,
231 requote_redirect_url: bool = True,
232 trace_configs: Optional[List[TraceConfig]] = None,
233 read_bufsize: int = 2**16,
234 max_line_size: int = 8190,
235 max_field_size: int = 8190,
236 fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8",
237 ) -> None:
238 if loop is None:
239 if connector is not None:
240 loop = connector._loop
242 loop = get_running_loop(loop)
244 if base_url is None or isinstance(base_url, URL):
245 self._base_url: Optional[URL] = base_url
246 else:
247 self._base_url = URL(base_url)
248 assert (
249 self._base_url.origin() == self._base_url
250 ), "Only absolute URLs without path part are supported"
252 if connector is None:
253 connector = TCPConnector(loop=loop)
255 if connector._loop is not loop:
256 raise RuntimeError("Session and connector has to use same event loop")
258 self._loop = loop
260 if loop.get_debug():
261 self._source_traceback = traceback.extract_stack(sys._getframe(1))
263 if cookie_jar is None:
264 cookie_jar = CookieJar(loop=loop)
265 self._cookie_jar = cookie_jar
267 if cookies is not None:
268 self._cookie_jar.update_cookies(cookies)
270 self._connector = connector
271 self._connector_owner = connector_owner
272 self._default_auth = auth
273 self._version = version
274 self._json_serialize = json_serialize
275 if timeout is sentinel:
276 self._timeout = DEFAULT_TIMEOUT
277 if read_timeout is not sentinel:
278 warnings.warn(
279 "read_timeout is deprecated, " "use timeout argument instead",
280 DeprecationWarning,
281 stacklevel=2,
282 )
283 self._timeout = attr.evolve(self._timeout, total=read_timeout)
284 if conn_timeout is not None:
285 self._timeout = attr.evolve(self._timeout, connect=conn_timeout)
286 warnings.warn(
287 "conn_timeout is deprecated, " "use timeout argument instead",
288 DeprecationWarning,
289 stacklevel=2,
290 )
291 else:
292 self._timeout = timeout # type: ignore[assignment]
293 if read_timeout is not sentinel:
294 raise ValueError(
295 "read_timeout and timeout parameters "
296 "conflict, please setup "
297 "timeout.read"
298 )
299 if conn_timeout is not None:
300 raise ValueError(
301 "conn_timeout and timeout parameters "
302 "conflict, please setup "
303 "timeout.connect"
304 )
305 self._raise_for_status = raise_for_status
306 self._auto_decompress = auto_decompress
307 self._trust_env = trust_env
308 self._requote_redirect_url = requote_redirect_url
309 self._read_bufsize = read_bufsize
310 self._max_line_size = max_line_size
311 self._max_field_size = max_field_size
313 # Convert to list of tuples
314 if headers:
315 real_headers: CIMultiDict[str] = CIMultiDict(headers)
316 else:
317 real_headers = CIMultiDict()
318 self._default_headers: CIMultiDict[str] = real_headers
319 if skip_auto_headers is not None:
320 self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers)
321 else:
322 self._skip_auto_headers = frozenset()
324 self._request_class = request_class
325 self._response_class = response_class
326 self._ws_response_class = ws_response_class
328 self._trace_configs = trace_configs or []
329 for trace_config in self._trace_configs:
330 trace_config.freeze()
332 self._resolve_charset = fallback_charset_resolver
334 def __init_subclass__(cls: Type["ClientSession"]) -> None:
335 warnings.warn(
336 "Inheritance class {} from ClientSession "
337 "is discouraged".format(cls.__name__),
338 DeprecationWarning,
339 stacklevel=2,
340 )
342 if DEBUG:
344 def __setattr__(self, name: str, val: Any) -> None:
345 if name not in self.ATTRS:
346 warnings.warn(
347 "Setting custom ClientSession.{} attribute "
348 "is discouraged".format(name),
349 DeprecationWarning,
350 stacklevel=2,
351 )
352 super().__setattr__(name, val)
354 def __del__(self, _warnings: Any = warnings) -> None:
355 if not self.closed:
356 kwargs = {"source": self}
357 _warnings.warn(
358 f"Unclosed client session {self!r}", ResourceWarning, **kwargs
359 )
360 context = {"client_session": self, "message": "Unclosed client session"}
361 if self._source_traceback is not None:
362 context["source_traceback"] = self._source_traceback
363 self._loop.call_exception_handler(context)
365 def request(
366 self, method: str, url: StrOrURL, **kwargs: Any
367 ) -> "_RequestContextManager":
368 """Perform HTTP request."""
369 return _RequestContextManager(self._request(method, url, **kwargs))
371 def _build_url(self, str_or_url: StrOrURL) -> URL:
372 url = URL(str_or_url)
373 if self._base_url is None:
374 return url
375 else:
376 assert not url.is_absolute() and url.path.startswith("/")
377 return self._base_url.join(url)
379 async def _request(
380 self,
381 method: str,
382 str_or_url: StrOrURL,
383 *,
384 params: Optional[Mapping[str, str]] = None,
385 data: Any = None,
386 json: Any = None,
387 cookies: Optional[LooseCookies] = None,
388 headers: Optional[LooseHeaders] = None,
389 skip_auto_headers: Optional[Iterable[str]] = None,
390 auth: Optional[BasicAuth] = None,
391 allow_redirects: bool = True,
392 max_redirects: int = 10,
393 compress: Optional[str] = None,
394 chunked: Optional[bool] = None,
395 expect100: bool = False,
396 raise_for_status: Union[
397 None, bool, Callable[[ClientResponse], Awaitable[None]]
398 ] = None,
399 read_until_eof: bool = True,
400 proxy: Optional[StrOrURL] = None,
401 proxy_auth: Optional[BasicAuth] = None,
402 timeout: Union[ClientTimeout, _SENTINEL] = sentinel,
403 verify_ssl: Optional[bool] = None,
404 fingerprint: Optional[bytes] = None,
405 ssl_context: Optional[SSLContext] = None,
406 ssl: Optional[Union[SSLContext, Literal[False], Fingerprint]] = None,
407 server_hostname: Optional[str] = None,
408 proxy_headers: Optional[LooseHeaders] = None,
409 trace_request_ctx: Optional[SimpleNamespace] = None,
410 read_bufsize: Optional[int] = None,
411 auto_decompress: Optional[bool] = None,
412 max_line_size: Optional[int] = None,
413 max_field_size: Optional[int] = None,
414 ) -> ClientResponse:
416 # NOTE: timeout clamps existing connect and read timeouts. We cannot
417 # set the default to None because we need to detect if the user wants
418 # to use the existing timeouts by setting timeout to None.
420 if self.closed:
421 raise RuntimeError("Session is closed")
423 ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
425 if data is not None and json is not None:
426 raise ValueError(
427 "data and json parameters can not be used at the same time"
428 )
429 elif json is not None:
430 data = payload.JsonPayload(json, dumps=self._json_serialize)
432 if not isinstance(chunked, bool) and chunked is not None:
433 warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)
435 redirects = 0
436 history = []
437 version = self._version
438 params = params or {}
440 # Merge with default headers and transform to CIMultiDict
441 headers = self._prepare_headers(headers)
442 proxy_headers = self._prepare_headers(proxy_headers)
444 try:
445 url = self._build_url(str_or_url)
446 except ValueError as e:
447 raise InvalidURL(str_or_url) from e
449 skip_headers = set(self._skip_auto_headers)
450 if skip_auto_headers is not None:
451 for i in skip_auto_headers:
452 skip_headers.add(istr(i))
454 if proxy is not None:
455 try:
456 proxy = URL(proxy)
457 except ValueError as e:
458 raise InvalidURL(proxy) from e
460 if timeout is sentinel:
461 real_timeout: ClientTimeout = self._timeout
462 else:
463 if not isinstance(timeout, ClientTimeout):
464 real_timeout = ClientTimeout(total=timeout)
465 else:
466 real_timeout = timeout
467 # timeout is cumulative for all request operations
468 # (request, redirects, responses, data consuming)
469 tm = TimeoutHandle(
470 self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold
471 )
472 handle = tm.start()
474 if read_bufsize is None:
475 read_bufsize = self._read_bufsize
477 if auto_decompress is None:
478 auto_decompress = self._auto_decompress
480 if max_line_size is None:
481 max_line_size = self._max_line_size
483 if max_field_size is None:
484 max_field_size = self._max_field_size
486 traces = [
487 Trace(
488 self,
489 trace_config,
490 trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
491 )
492 for trace_config in self._trace_configs
493 ]
495 for trace in traces:
496 await trace.send_request_start(method, url.update_query(params), headers)
498 timer = tm.timer()
499 try:
500 with timer:
501 while True:
502 url, auth_from_url = strip_auth_from_url(url)
503 if auth and auth_from_url:
504 raise ValueError(
505 "Cannot combine AUTH argument with "
506 "credentials encoded in URL"
507 )
509 if auth is None:
510 auth = auth_from_url
511 if auth is None:
512 auth = self._default_auth
513 # It would be confusing if we support explicit
514 # Authorization header with auth argument
515 if (
516 headers is not None
517 and auth is not None
518 and hdrs.AUTHORIZATION in headers
519 ):
520 raise ValueError(
521 "Cannot combine AUTHORIZATION header "
522 "with AUTH argument or credentials "
523 "encoded in URL"
524 )
526 all_cookies = self._cookie_jar.filter_cookies(url)
528 if cookies is not None:
529 tmp_cookie_jar = CookieJar()
530 tmp_cookie_jar.update_cookies(cookies)
531 req_cookies = tmp_cookie_jar.filter_cookies(url)
532 if req_cookies:
533 all_cookies.load(req_cookies)
535 if proxy is not None:
536 proxy = URL(proxy)
537 elif self._trust_env:
538 with suppress(LookupError):
539 proxy, proxy_auth = get_env_proxy_for_url(url)
541 req = self._request_class(
542 method,
543 url,
544 params=params,
545 headers=headers,
546 skip_auto_headers=skip_headers,
547 data=data,
548 cookies=all_cookies,
549 auth=auth,
550 version=version,
551 compress=compress,
552 chunked=chunked,
553 expect100=expect100,
554 loop=self._loop,
555 response_class=self._response_class,
556 proxy=proxy,
557 proxy_auth=proxy_auth,
558 timer=timer,
559 session=self,
560 ssl=ssl,
561 server_hostname=server_hostname,
562 proxy_headers=proxy_headers,
563 traces=traces,
564 trust_env=self.trust_env,
565 )
567 # connection timeout
568 try:
569 async with ceil_timeout(
570 real_timeout.connect,
571 ceil_threshold=real_timeout.ceil_threshold,
572 ):
573 assert self._connector is not None
574 conn = await self._connector.connect(
575 req, traces=traces, timeout=real_timeout
576 )
577 except asyncio.TimeoutError as exc:
578 raise ServerTimeoutError(
579 "Connection timeout " "to host {}".format(url)
580 ) from exc
582 assert conn.transport is not None
584 assert conn.protocol is not None
585 conn.protocol.set_response_params(
586 timer=timer,
587 skip_payload=method_must_be_empty_body(method),
588 read_until_eof=read_until_eof,
589 auto_decompress=auto_decompress,
590 read_timeout=real_timeout.sock_read,
591 read_bufsize=read_bufsize,
592 timeout_ceil_threshold=self._connector._timeout_ceil_threshold,
593 max_line_size=max_line_size,
594 max_field_size=max_field_size,
595 )
597 try:
598 try:
599 resp = await req.send(conn)
600 try:
601 await resp.start(conn)
602 except BaseException:
603 resp.close()
604 raise
605 except BaseException:
606 conn.close()
607 raise
608 except ClientError:
609 raise
610 except OSError as exc:
611 if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
612 raise
613 raise ClientOSError(*exc.args) from exc
615 self._cookie_jar.update_cookies(resp.cookies, resp.url)
617 # redirects
618 if resp.status in (301, 302, 303, 307, 308) and allow_redirects:
620 for trace in traces:
621 await trace.send_request_redirect(
622 method, url.update_query(params), headers, resp
623 )
625 redirects += 1
626 history.append(resp)
627 if max_redirects and redirects >= max_redirects:
628 resp.close()
629 raise TooManyRedirects(
630 history[0].request_info, tuple(history)
631 )
633 # For 301 and 302, mimic IE, now changed in RFC
634 # https://github.com/kennethreitz/requests/pull/269
635 if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (
636 resp.status in (301, 302) and resp.method == hdrs.METH_POST
637 ):
638 method = hdrs.METH_GET
639 data = None
640 if headers.get(hdrs.CONTENT_LENGTH):
641 headers.pop(hdrs.CONTENT_LENGTH)
643 r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(
644 hdrs.URI
645 )
646 if r_url is None:
647 # see github.com/aio-libs/aiohttp/issues/2022
648 break
649 else:
650 # reading from correct redirection
651 # response is forbidden
652 resp.release()
654 try:
655 parsed_url = URL(
656 r_url, encoded=not self._requote_redirect_url
657 )
659 except ValueError as e:
660 raise InvalidURL(r_url) from e
662 scheme = parsed_url.scheme
663 if scheme not in ("http", "https", ""):
664 resp.close()
665 raise ValueError("Can redirect only to http or https")
666 elif not scheme:
667 parsed_url = url.join(parsed_url)
669 if url.origin() != parsed_url.origin():
670 auth = None
671 headers.pop(hdrs.AUTHORIZATION, None)
673 url = parsed_url
674 params = {}
675 resp.release()
676 continue
678 break
680 # check response status
681 if raise_for_status is None:
682 raise_for_status = self._raise_for_status
684 if raise_for_status is None:
685 pass
686 elif callable(raise_for_status):
687 await raise_for_status(resp)
688 elif raise_for_status:
689 resp.raise_for_status()
691 # register connection
692 if handle is not None:
693 if resp.connection is not None:
694 resp.connection.add_callback(handle.cancel)
695 else:
696 handle.cancel()
698 resp._history = tuple(history)
700 for trace in traces:
701 await trace.send_request_end(
702 method, url.update_query(params), headers, resp
703 )
704 return resp
706 except BaseException as e:
707 # cleanup timer
708 tm.close()
709 if handle:
710 handle.cancel()
711 handle = None
713 for trace in traces:
714 await trace.send_request_exception(
715 method, url.update_query(params), headers, e
716 )
717 raise
719 def ws_connect(
720 self,
721 url: StrOrURL,
722 *,
723 method: str = hdrs.METH_GET,
724 protocols: Iterable[str] = (),
725 timeout: float = 10.0,
726 receive_timeout: Optional[float] = None,
727 autoclose: bool = True,
728 autoping: bool = True,
729 heartbeat: Optional[float] = None,
730 auth: Optional[BasicAuth] = None,
731 origin: Optional[str] = None,
732 params: Optional[Mapping[str, str]] = None,
733 headers: Optional[LooseHeaders] = None,
734 proxy: Optional[StrOrURL] = None,
735 proxy_auth: Optional[BasicAuth] = None,
736 ssl: Union[SSLContext, Literal[False], None, Fingerprint] = None,
737 verify_ssl: Optional[bool] = None,
738 fingerprint: Optional[bytes] = None,
739 ssl_context: Optional[SSLContext] = None,
740 proxy_headers: Optional[LooseHeaders] = None,
741 compress: int = 0,
742 max_msg_size: int = 4 * 1024 * 1024,
743 ) -> "_WSRequestContextManager":
744 """Initiate websocket connection."""
745 return _WSRequestContextManager(
746 self._ws_connect(
747 url,
748 method=method,
749 protocols=protocols,
750 timeout=timeout,
751 receive_timeout=receive_timeout,
752 autoclose=autoclose,
753 autoping=autoping,
754 heartbeat=heartbeat,
755 auth=auth,
756 origin=origin,
757 params=params,
758 headers=headers,
759 proxy=proxy,
760 proxy_auth=proxy_auth,
761 ssl=ssl,
762 verify_ssl=verify_ssl,
763 fingerprint=fingerprint,
764 ssl_context=ssl_context,
765 proxy_headers=proxy_headers,
766 compress=compress,
767 max_msg_size=max_msg_size,
768 )
769 )
771 async def _ws_connect(
772 self,
773 url: StrOrURL,
774 *,
775 method: str = hdrs.METH_GET,
776 protocols: Iterable[str] = (),
777 timeout: float = 10.0,
778 receive_timeout: Optional[float] = None,
779 autoclose: bool = True,
780 autoping: bool = True,
781 heartbeat: Optional[float] = None,
782 auth: Optional[BasicAuth] = None,
783 origin: Optional[str] = None,
784 params: Optional[Mapping[str, str]] = None,
785 headers: Optional[LooseHeaders] = None,
786 proxy: Optional[StrOrURL] = None,
787 proxy_auth: Optional[BasicAuth] = None,
788 ssl: Union[SSLContext, Literal[False], None, Fingerprint] = None,
789 verify_ssl: Optional[bool] = None,
790 fingerprint: Optional[bytes] = None,
791 ssl_context: Optional[SSLContext] = None,
792 proxy_headers: Optional[LooseHeaders] = None,
793 compress: int = 0,
794 max_msg_size: int = 4 * 1024 * 1024,
795 ) -> ClientWebSocketResponse:
797 if headers is None:
798 real_headers: CIMultiDict[str] = CIMultiDict()
799 else:
800 real_headers = CIMultiDict(headers)
802 default_headers = {
803 hdrs.UPGRADE: "websocket",
804 hdrs.CONNECTION: "Upgrade",
805 hdrs.SEC_WEBSOCKET_VERSION: "13",
806 }
808 for key, value in default_headers.items():
809 real_headers.setdefault(key, value)
811 sec_key = base64.b64encode(os.urandom(16))
812 real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode()
814 if protocols:
815 real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols)
816 if origin is not None:
817 real_headers[hdrs.ORIGIN] = origin
818 if compress:
819 extstr = ws_ext_gen(compress=compress)
820 real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr
822 ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
824 # send request
825 resp = await self.request(
826 method,
827 url,
828 params=params,
829 headers=real_headers,
830 read_until_eof=False,
831 auth=auth,
832 proxy=proxy,
833 proxy_auth=proxy_auth,
834 ssl=ssl,
835 proxy_headers=proxy_headers,
836 )
838 try:
839 # check handshake
840 if resp.status != 101:
841 raise WSServerHandshakeError(
842 resp.request_info,
843 resp.history,
844 message="Invalid response status",
845 status=resp.status,
846 headers=resp.headers,
847 )
849 if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket":
850 raise WSServerHandshakeError(
851 resp.request_info,
852 resp.history,
853 message="Invalid upgrade header",
854 status=resp.status,
855 headers=resp.headers,
856 )
858 if resp.headers.get(hdrs.CONNECTION, "").lower() != "upgrade":
859 raise WSServerHandshakeError(
860 resp.request_info,
861 resp.history,
862 message="Invalid connection header",
863 status=resp.status,
864 headers=resp.headers,
865 )
867 # key calculation
868 r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "")
869 match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()
870 if r_key != match:
871 raise WSServerHandshakeError(
872 resp.request_info,
873 resp.history,
874 message="Invalid challenge response",
875 status=resp.status,
876 headers=resp.headers,
877 )
879 # websocket protocol
880 protocol = None
881 if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers:
882 resp_protocols = [
883 proto.strip()
884 for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
885 ]
887 for proto in resp_protocols:
888 if proto in protocols:
889 protocol = proto
890 break
892 # websocket compress
893 notakeover = False
894 if compress:
895 compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)
896 if compress_hdrs:
897 try:
898 compress, notakeover = ws_ext_parse(compress_hdrs)
899 except WSHandshakeError as exc:
900 raise WSServerHandshakeError(
901 resp.request_info,
902 resp.history,
903 message=exc.args[0],
904 status=resp.status,
905 headers=resp.headers,
906 ) from exc
907 else:
908 compress = 0
909 notakeover = False
911 conn = resp.connection
912 assert conn is not None
913 conn_proto = conn.protocol
914 assert conn_proto is not None
915 transport = conn.transport
916 assert transport is not None
917 reader: FlowControlDataQueue[WSMessage] = FlowControlDataQueue(
918 conn_proto, 2**16, loop=self._loop
919 )
920 conn_proto.set_parser(WebSocketReader(reader, max_msg_size), reader)
921 writer = WebSocketWriter(
922 conn_proto,
923 transport,
924 use_mask=True,
925 compress=compress,
926 notakeover=notakeover,
927 )
928 except BaseException:
929 resp.close()
930 raise
931 else:
932 return self._ws_response_class(
933 reader,
934 writer,
935 protocol,
936 resp,
937 timeout,
938 autoclose,
939 autoping,
940 self._loop,
941 receive_timeout=receive_timeout,
942 heartbeat=heartbeat,
943 compress=compress,
944 client_notakeover=notakeover,
945 )
947 def _prepare_headers(self, headers: Optional[LooseHeaders]) -> "CIMultiDict[str]":
948 """Add default headers and transform it to CIMultiDict"""
949 # Convert headers to MultiDict
950 result = CIMultiDict(self._default_headers)
951 if headers:
952 if not isinstance(headers, (MultiDictProxy, MultiDict)):
953 headers = CIMultiDict(headers)
954 added_names: Set[str] = set()
955 for key, value in headers.items():
956 if key in added_names:
957 result.add(key, value)
958 else:
959 result[key] = value
960 added_names.add(key)
961 return result
963 def get(
964 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
965 ) -> "_RequestContextManager":
966 """Perform HTTP GET request."""
967 return _RequestContextManager(
968 self._request(hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs)
969 )
971 def options(
972 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
973 ) -> "_RequestContextManager":
974 """Perform HTTP OPTIONS request."""
975 return _RequestContextManager(
976 self._request(
977 hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs
978 )
979 )
981 def head(
982 self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any
983 ) -> "_RequestContextManager":
984 """Perform HTTP HEAD request."""
985 return _RequestContextManager(
986 self._request(
987 hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs
988 )
989 )
991 def post(
992 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
993 ) -> "_RequestContextManager":
994 """Perform HTTP POST request."""
995 return _RequestContextManager(
996 self._request(hdrs.METH_POST, url, data=data, **kwargs)
997 )
999 def put(
1000 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1001 ) -> "_RequestContextManager":
1002 """Perform HTTP PUT request."""
1003 return _RequestContextManager(
1004 self._request(hdrs.METH_PUT, url, data=data, **kwargs)
1005 )
1007 def patch(
1008 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
1009 ) -> "_RequestContextManager":
1010 """Perform HTTP PATCH request."""
1011 return _RequestContextManager(
1012 self._request(hdrs.METH_PATCH, url, data=data, **kwargs)
1013 )
1015 def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager":
1016 """Perform HTTP DELETE request."""
1017 return _RequestContextManager(self._request(hdrs.METH_DELETE, url, **kwargs))
1019 async def close(self) -> None:
1020 """Close underlying connector.
1022 Release all acquired resources.
1023 """
1024 if not self.closed:
1025 if self._connector is not None and self._connector_owner:
1026 await self._connector.close()
1027 self._connector = None
1029 @property
1030 def closed(self) -> bool:
1031 """Is client session closed.
1033 A readonly property.
1034 """
1035 return self._connector is None or self._connector.closed
1037 @property
1038 def connector(self) -> Optional[BaseConnector]:
1039 """Connector instance used for the session."""
1040 return self._connector
1042 @property
1043 def cookie_jar(self) -> AbstractCookieJar:
1044 """The session cookies."""
1045 return self._cookie_jar
1047 @property
1048 def version(self) -> Tuple[int, int]:
1049 """The session HTTP protocol version."""
1050 return self._version
1052 @property
1053 def requote_redirect_url(self) -> bool:
1054 """Do URL requoting on redirection handling."""
1055 return self._requote_redirect_url
1057 @requote_redirect_url.setter
1058 def requote_redirect_url(self, val: bool) -> None:
1059 """Do URL requoting on redirection handling."""
1060 warnings.warn(
1061 "session.requote_redirect_url modification " "is deprecated #2778",
1062 DeprecationWarning,
1063 stacklevel=2,
1064 )
1065 self._requote_redirect_url = val
1067 @property
1068 def loop(self) -> asyncio.AbstractEventLoop:
1069 """Session's loop."""
1070 warnings.warn(
1071 "client.loop property is deprecated", DeprecationWarning, stacklevel=2
1072 )
1073 return self._loop
1075 @property
1076 def timeout(self) -> ClientTimeout:
1077 """Timeout for the session."""
1078 return self._timeout
1080 @property
1081 def headers(self) -> "CIMultiDict[str]":
1082 """The default headers of the client session."""
1083 return self._default_headers
1085 @property
1086 def skip_auto_headers(self) -> FrozenSet[istr]:
1087 """Headers for which autogeneration should be skipped"""
1088 return self._skip_auto_headers
1090 @property
1091 def auth(self) -> Optional[BasicAuth]:
1092 """An object that represents HTTP Basic Authorization"""
1093 return self._default_auth
1095 @property
1096 def json_serialize(self) -> JSONEncoder:
1097 """Json serializer callable"""
1098 return self._json_serialize
1100 @property
1101 def connector_owner(self) -> bool:
1102 """Should connector be closed on session closing"""
1103 return self._connector_owner
1105 @property
1106 def raise_for_status(
1107 self,
1108 ) -> Union[bool, Callable[[ClientResponse], Awaitable[None]]]:
1109 """Should `ClientResponse.raise_for_status()` be called for each response."""
1110 return self._raise_for_status
1112 @property
1113 def auto_decompress(self) -> bool:
1114 """Should the body response be automatically decompressed."""
1115 return self._auto_decompress
1117 @property
1118 def trust_env(self) -> bool:
1119 """
1120 Should proxies information from environment or netrc be trusted.
1122 Information is from HTTP_PROXY / HTTPS_PROXY environment variables
1123 or ~/.netrc file if present.
1124 """
1125 return self._trust_env
1127 @property
1128 def trace_configs(self) -> List[TraceConfig]:
1129 """A list of TraceConfig instances used for client tracing"""
1130 return self._trace_configs
1132 def detach(self) -> None:
1133 """Detach connector from session without closing the former.
1135 Session is switched to closed state anyway.
1136 """
1137 self._connector = None
1139 def __enter__(self) -> None:
1140 raise TypeError("Use async with instead")
1142 def __exit__(
1143 self,
1144 exc_type: Optional[Type[BaseException]],
1145 exc_val: Optional[BaseException],
1146 exc_tb: Optional[TracebackType],
1147 ) -> None:
1148 # __exit__ should exist in pair with __enter__ but never executed
1149 pass # pragma: no cover
1151 async def __aenter__(self) -> "ClientSession":
1152 return self
1154 async def __aexit__(
1155 self,
1156 exc_type: Optional[Type[BaseException]],
1157 exc_val: Optional[BaseException],
1158 exc_tb: Optional[TracebackType],
1159 ) -> None:
1160 await self.close()
1163class _BaseRequestContextManager(Coroutine[Any, Any, _RetType], Generic[_RetType]):
1165 __slots__ = ("_coro", "_resp")
1167 def __init__(self, coro: Coroutine["asyncio.Future[Any]", None, _RetType]) -> None:
1168 self._coro = coro
1170 def send(self, arg: None) -> "asyncio.Future[Any]":
1171 return self._coro.send(arg)
1173 def throw(self, *args: Any, **kwargs: Any) -> "asyncio.Future[Any]":
1174 return self._coro.throw(*args, **kwargs)
1176 def close(self) -> None:
1177 return self._coro.close()
1179 def __await__(self) -> Generator[Any, None, _RetType]:
1180 ret = self._coro.__await__()
1181 return ret
1183 def __iter__(self) -> Generator[Any, None, _RetType]:
1184 return self.__await__()
1186 async def __aenter__(self) -> _RetType:
1187 self._resp = await self._coro
1188 return self._resp
1191class _RequestContextManager(_BaseRequestContextManager[ClientResponse]):
1192 __slots__ = ()
1194 async def __aexit__(
1195 self,
1196 exc_type: Optional[Type[BaseException]],
1197 exc: Optional[BaseException],
1198 tb: Optional[TracebackType],
1199 ) -> None:
1200 # We're basing behavior on the exception as it can be caused by
1201 # user code unrelated to the status of the connection. If you
1202 # would like to close a connection you must do that
1203 # explicitly. Otherwise connection error handling should kick in
1204 # and close/recycle the connection as required.
1205 self._resp.release()
1206 await self._resp.wait_for_close()
1209class _WSRequestContextManager(_BaseRequestContextManager[ClientWebSocketResponse]):
1210 __slots__ = ()
1212 async def __aexit__(
1213 self,
1214 exc_type: Optional[Type[BaseException]],
1215 exc: Optional[BaseException],
1216 tb: Optional[TracebackType],
1217 ) -> None:
1218 await self._resp.close()
1221class _SessionRequestContextManager:
1223 __slots__ = ("_coro", "_resp", "_session")
1225 def __init__(
1226 self,
1227 coro: Coroutine["asyncio.Future[Any]", None, ClientResponse],
1228 session: ClientSession,
1229 ) -> None:
1230 self._coro = coro
1231 self._resp: Optional[ClientResponse] = None
1232 self._session = session
1234 async def __aenter__(self) -> ClientResponse:
1235 try:
1236 self._resp = await self._coro
1237 except BaseException:
1238 await self._session.close()
1239 raise
1240 else:
1241 return self._resp
1243 async def __aexit__(
1244 self,
1245 exc_type: Optional[Type[BaseException]],
1246 exc: Optional[BaseException],
1247 tb: Optional[TracebackType],
1248 ) -> None:
1249 assert self._resp is not None
1250 self._resp.close()
1251 await self._session.close()
1254def request(
1255 method: str,
1256 url: StrOrURL,
1257 *,
1258 params: Optional[Mapping[str, str]] = None,
1259 data: Any = None,
1260 json: Any = None,
1261 headers: Optional[LooseHeaders] = None,
1262 skip_auto_headers: Optional[Iterable[str]] = None,
1263 auth: Optional[BasicAuth] = None,
1264 allow_redirects: bool = True,
1265 max_redirects: int = 10,
1266 compress: Optional[str] = None,
1267 chunked: Optional[bool] = None,
1268 expect100: bool = False,
1269 raise_for_status: Optional[bool] = None,
1270 read_until_eof: bool = True,
1271 proxy: Optional[StrOrURL] = None,
1272 proxy_auth: Optional[BasicAuth] = None,
1273 timeout: Union[ClientTimeout, object] = sentinel,
1274 cookies: Optional[LooseCookies] = None,
1275 version: HttpVersion = http.HttpVersion11,
1276 connector: Optional[BaseConnector] = None,
1277 read_bufsize: Optional[int] = None,
1278 loop: Optional[asyncio.AbstractEventLoop] = None,
1279 max_line_size: int = 8190,
1280 max_field_size: int = 8190,
1281) -> _SessionRequestContextManager:
1282 """Constructs and sends a request.
1284 Returns response object.
1285 method - HTTP method
1286 url - request url
1287 params - (optional) Dictionary or bytes to be sent in the query
1288 string of the new request
1289 data - (optional) Dictionary, bytes, or file-like object to
1290 send in the body of the request
1291 json - (optional) Any json compatible python object
1292 headers - (optional) Dictionary of HTTP Headers to send with
1293 the request
1294 cookies - (optional) Dict object to send with the request
1295 auth - (optional) BasicAuth named tuple represent HTTP Basic Auth
1296 auth - aiohttp.helpers.BasicAuth
1297 allow_redirects - (optional) If set to False, do not follow
1298 redirects
1299 version - Request HTTP version.
1300 compress - Set to True if request has to be compressed
1301 with deflate encoding.
1302 chunked - Set to chunk size for chunked transfer encoding.
1303 expect100 - Expect 100-continue response from server.
1304 connector - BaseConnector sub-class instance to support
1305 connection pooling.
1306 read_until_eof - Read response until eof if response
1307 does not have Content-Length header.
1308 loop - Optional event loop.
1309 timeout - Optional ClientTimeout settings structure, 5min
1310 total timeout by default.
1311 Usage::
1312 >>> import aiohttp
1313 >>> resp = await aiohttp.request('GET', 'http://python.org/')
1314 >>> resp
1315 <ClientResponse(python.org/) [200]>
1316 >>> data = await resp.read()
1317 """
1318 connector_owner = False
1319 if connector is None:
1320 connector_owner = True
1321 connector = TCPConnector(loop=loop, force_close=True)
1323 session = ClientSession(
1324 loop=loop,
1325 cookies=cookies,
1326 version=version,
1327 timeout=timeout,
1328 connector=connector,
1329 connector_owner=connector_owner,
1330 )
1332 return _SessionRequestContextManager(
1333 session._request(
1334 method,
1335 url,
1336 params=params,
1337 data=data,
1338 json=json,
1339 headers=headers,
1340 skip_auto_headers=skip_auto_headers,
1341 auth=auth,
1342 allow_redirects=allow_redirects,
1343 max_redirects=max_redirects,
1344 compress=compress,
1345 chunked=chunked,
1346 expect100=expect100,
1347 raise_for_status=raise_for_status,
1348 read_until_eof=read_until_eof,
1349 proxy=proxy,
1350 proxy_auth=proxy_auth,
1351 read_bufsize=read_bufsize,
1352 max_line_size=max_line_size,
1353 max_field_size=max_field_size,
1354 ),
1355 session,
1356 )