Coverage for /pythoncovmergedfiles/medio/medio/src/aiohttp/aiohttp/client.py: 31%
468 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-27 06:09 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-27 06:09 +0000
1"""HTTP Client for asyncio."""
3import asyncio
4import base64
5import dataclasses
6import hashlib
7import json
8import os
9import sys
10import traceback
11import warnings
12from contextlib import suppress
13from types import SimpleNamespace, TracebackType
14from typing import (
15 TYPE_CHECKING,
16 Any,
17 Awaitable,
18 Callable,
19 Collection,
20 Coroutine,
21 Final,
22 FrozenSet,
23 Generator,
24 Generic,
25 Iterable,
26 List,
27 Literal,
28 Mapping,
29 Optional,
30 Set,
31 Tuple,
32 Type,
33 TypeVar,
34 Union,
35 final,
36)
38from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr
39from yarl import URL
41from . import hdrs, http, payload
42from .abc import AbstractCookieJar
43from .client_exceptions import (
44 ClientConnectionError,
45 ClientConnectorCertificateError,
46 ClientConnectorError,
47 ClientConnectorSSLError,
48 ClientError,
49 ClientHttpProxyError,
50 ClientOSError,
51 ClientPayloadError,
52 ClientProxyConnectionError,
53 ClientResponseError,
54 ClientSSLError,
55 ContentTypeError,
56 InvalidURL,
57 ServerConnectionError,
58 ServerDisconnectedError,
59 ServerFingerprintMismatch,
60 ServerTimeoutError,
61 TooManyRedirects,
62 WSServerHandshakeError,
63)
64from .client_reqrep import (
65 SSL_ALLOWED_TYPES,
66 ClientRequest,
67 ClientResponse,
68 Fingerprint,
69 RequestInfo,
70)
71from .client_ws import (
72 DEFAULT_WS_CLIENT_TIMEOUT,
73 ClientWebSocketResponse,
74 ClientWSTimeout,
75)
76from .connector import BaseConnector, NamedPipeConnector, TCPConnector, UnixConnector
77from .cookiejar import CookieJar
78from .helpers import (
79 _SENTINEL,
80 BasicAuth,
81 TimeoutHandle,
82 ceil_timeout,
83 get_env_proxy_for_url,
84 sentinel,
85 strip_auth_from_url,
86)
87from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter
88from .http_websocket import WSHandshakeError, WSMessage, ws_ext_gen, ws_ext_parse
89from .streams import FlowControlDataQueue
90from .tracing import Trace, TraceConfig
91from .typedefs import JSONEncoder, LooseCookies, LooseHeaders, StrOrURL
93__all__ = (
94 # client_exceptions
95 "ClientConnectionError",
96 "ClientConnectorCertificateError",
97 "ClientConnectorError",
98 "ClientConnectorSSLError",
99 "ClientError",
100 "ClientHttpProxyError",
101 "ClientOSError",
102 "ClientPayloadError",
103 "ClientProxyConnectionError",
104 "ClientResponseError",
105 "ClientSSLError",
106 "ContentTypeError",
107 "InvalidURL",
108 "ServerConnectionError",
109 "ServerDisconnectedError",
110 "ServerFingerprintMismatch",
111 "ServerTimeoutError",
112 "TooManyRedirects",
113 "WSServerHandshakeError",
114 # client_reqrep
115 "ClientRequest",
116 "ClientResponse",
117 "Fingerprint",
118 "RequestInfo",
119 # connector
120 "BaseConnector",
121 "TCPConnector",
122 "UnixConnector",
123 "NamedPipeConnector",
124 # client_ws
125 "ClientWebSocketResponse",
126 # client
127 "ClientSession",
128 "ClientTimeout",
129 "request",
130)
133if TYPE_CHECKING:
134 from ssl import SSLContext
135else:
136 SSLContext = None
139@dataclasses.dataclass(frozen=True)
140class ClientTimeout:
141 total: Optional[float] = None
142 connect: Optional[float] = None
143 sock_read: Optional[float] = None
144 sock_connect: Optional[float] = None
145 ceil_threshold: float = 5
147 # pool_queue_timeout: Optional[float] = None
148 # dns_resolution_timeout: Optional[float] = None
149 # socket_connect_timeout: Optional[float] = None
150 # connection_acquiring_timeout: Optional[float] = None
151 # new_connection_timeout: Optional[float] = None
152 # http_header_timeout: Optional[float] = None
153 # response_body_timeout: Optional[float] = None
155 # to create a timeout specific for a single request, either
156 # - create a completely new one to overwrite the default
157 # - or use https://docs.python.org/3/library/dataclasses.html#dataclasses.replace
158 # to overwrite the defaults
161# 5 Minute default read timeout
162DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60)
164_RetType = TypeVar("_RetType")
165_CharsetResolver = Callable[[ClientResponse, bytes], str]
168@final
169class ClientSession:
170 """First-class interface for making HTTP requests."""
172 __slots__ = (
173 "_base_url",
174 "_source_traceback",
175 "_connector",
176 "_loop",
177 "_cookie_jar",
178 "_connector_owner",
179 "_default_auth",
180 "_version",
181 "_json_serialize",
182 "_requote_redirect_url",
183 "_timeout",
184 "_raise_for_status",
185 "_auto_decompress",
186 "_trust_env",
187 "_default_headers",
188 "_skip_auto_headers",
189 "_request_class",
190 "_response_class",
191 "_ws_response_class",
192 "_trace_configs",
193 "_read_bufsize",
194 "_max_line_size",
195 "_max_field_size",
196 "_resolve_charset",
197 )
199 def __init__(
200 self,
201 base_url: Optional[StrOrURL] = None,
202 *,
203 connector: Optional[BaseConnector] = None,
204 cookies: Optional[LooseCookies] = None,
205 headers: Optional[LooseHeaders] = None,
206 skip_auto_headers: Optional[Iterable[str]] = None,
207 auth: Optional[BasicAuth] = None,
208 json_serialize: JSONEncoder = json.dumps,
209 request_class: Type[ClientRequest] = ClientRequest,
210 response_class: Type[ClientResponse] = ClientResponse,
211 ws_response_class: Type[ClientWebSocketResponse] = ClientWebSocketResponse,
212 version: HttpVersion = http.HttpVersion11,
213 cookie_jar: Optional[AbstractCookieJar] = None,
214 connector_owner: bool = True,
215 raise_for_status: Union[
216 bool, Callable[[ClientResponse], Awaitable[None]]
217 ] = False,
218 timeout: Union[_SENTINEL, ClientTimeout, None] = sentinel,
219 auto_decompress: bool = True,
220 trust_env: bool = False,
221 requote_redirect_url: bool = True,
222 trace_configs: Optional[List[TraceConfig]] = None,
223 read_bufsize: int = 2**16,
224 max_line_size: int = 8190,
225 max_field_size: int = 8190,
226 fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8",
227 ) -> None:
228 if base_url is None or isinstance(base_url, URL):
229 self._base_url: Optional[URL] = base_url
230 else:
231 self._base_url = URL(base_url)
232 assert (
233 self._base_url.origin() == self._base_url
234 ), "Only absolute URLs without path part are supported"
236 loop = asyncio.get_running_loop()
238 if connector is None:
239 connector = TCPConnector()
241 # Initialize these three attrs before raising any exception,
242 # they are used in __del__
243 self._connector: Optional[BaseConnector] = connector
244 self._loop = loop
245 if loop.get_debug():
246 self._source_traceback: Optional[
247 traceback.StackSummary
248 ] = traceback.extract_stack(sys._getframe(1))
249 else:
250 self._source_traceback = None
252 if connector._loop is not loop:
253 raise RuntimeError("Session and connector have to use same event loop")
255 if cookie_jar is None:
256 cookie_jar = CookieJar()
257 self._cookie_jar = cookie_jar
259 if cookies is not None:
260 self._cookie_jar.update_cookies(cookies)
262 self._connector_owner = connector_owner
263 self._default_auth = auth
264 self._version = version
265 self._json_serialize = json_serialize
266 if timeout is sentinel or timeout is None:
267 self._timeout = DEFAULT_TIMEOUT
268 else:
269 self._timeout = timeout
270 self._raise_for_status = raise_for_status
271 self._auto_decompress = auto_decompress
272 self._trust_env = trust_env
273 self._requote_redirect_url = requote_redirect_url
274 self._read_bufsize = read_bufsize
275 self._max_line_size = max_line_size
276 self._max_field_size = max_field_size
278 # Convert to list of tuples
279 if headers:
280 real_headers: CIMultiDict[str] = CIMultiDict(headers)
281 else:
282 real_headers = CIMultiDict()
283 self._default_headers: CIMultiDict[str] = real_headers
284 if skip_auto_headers is not None:
285 self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers)
286 else:
287 self._skip_auto_headers = frozenset()
289 self._request_class = request_class
290 self._response_class = response_class
291 self._ws_response_class = ws_response_class
293 self._trace_configs = trace_configs or []
294 for trace_config in self._trace_configs:
295 trace_config.freeze()
297 self._resolve_charset = fallback_charset_resolver
299 def __init_subclass__(cls: Type["ClientSession"]) -> None:
300 raise TypeError(
301 "Inheritance class {} from ClientSession "
302 "is forbidden".format(cls.__name__)
303 )
305 def __del__(self, _warnings: Any = warnings) -> None:
306 if not self.closed:
307 _warnings.warn(
308 f"Unclosed client session {self!r}",
309 ResourceWarning,
310 source=self,
311 )
312 context = {"client_session": self, "message": "Unclosed client session"}
313 if self._source_traceback is not None:
314 context["source_traceback"] = self._source_traceback
315 self._loop.call_exception_handler(context)
317 def request(
318 self, method: str, url: StrOrURL, **kwargs: Any
319 ) -> "_RequestContextManager":
320 """Perform HTTP request."""
321 return _RequestContextManager(self._request(method, url, **kwargs))
323 def _build_url(self, str_or_url: StrOrURL) -> URL:
324 url = URL(str_or_url)
325 if self._base_url is None:
326 return url
327 else:
328 assert not url.is_absolute() and url.path.startswith("/")
329 return self._base_url.join(url)
331 async def _request(
332 self,
333 method: str,
334 str_or_url: StrOrURL,
335 *,
336 params: Optional[Mapping[str, str]] = None,
337 data: Any = None,
338 json: Any = None,
339 cookies: Optional[LooseCookies] = None,
340 headers: Optional[LooseHeaders] = None,
341 skip_auto_headers: Optional[Iterable[str]] = None,
342 auth: Optional[BasicAuth] = None,
343 allow_redirects: bool = True,
344 max_redirects: int = 10,
345 compress: Optional[str] = None,
346 chunked: Optional[bool] = None,
347 expect100: bool = False,
348 raise_for_status: Union[
349 None, bool, Callable[[ClientResponse], Awaitable[None]]
350 ] = None,
351 read_until_eof: bool = True,
352 proxy: Optional[StrOrURL] = None,
353 proxy_auth: Optional[BasicAuth] = None,
354 timeout: Union[ClientTimeout, _SENTINEL, None] = sentinel,
355 ssl: Optional[Union[SSLContext, Literal[False], Fingerprint]] = None,
356 server_hostname: Optional[str] = None,
357 proxy_headers: Optional[LooseHeaders] = None,
358 trace_request_ctx: Optional[SimpleNamespace] = None,
359 read_bufsize: Optional[int] = None,
360 auto_decompress: Optional[bool] = None,
361 max_line_size: Optional[int] = None,
362 max_field_size: Optional[int] = None,
363 ) -> ClientResponse:
364 # NOTE: timeout clamps existing connect and read timeouts. We cannot
365 # set the default to None because we need to detect if the user wants
366 # to use the existing timeouts by setting timeout to None.
368 if self.closed:
369 raise RuntimeError("Session is closed")
371 if not isinstance(ssl, SSL_ALLOWED_TYPES):
372 raise TypeError(
373 "ssl should be SSLContext, bool, Fingerprint, "
374 "or None, got {!r} instead.".format(ssl)
375 )
377 if data is not None and json is not None:
378 raise ValueError(
379 "data and json parameters can not be used at the same time"
380 )
381 elif json is not None:
382 data = payload.JsonPayload(json, dumps=self._json_serialize)
384 redirects = 0
385 history = []
386 version = self._version
387 params = params or {}
389 # Merge with default headers and transform to CIMultiDict
390 headers = self._prepare_headers(headers)
391 proxy_headers = self._prepare_headers(proxy_headers)
393 try:
394 url = self._build_url(str_or_url)
395 except ValueError as e:
396 raise InvalidURL(str_or_url) from e
398 skip_headers = set(self._skip_auto_headers)
399 if skip_auto_headers is not None:
400 for i in skip_auto_headers:
401 skip_headers.add(istr(i))
403 if proxy is not None:
404 try:
405 proxy = URL(proxy)
406 except ValueError as e:
407 raise InvalidURL(proxy) from e
409 if timeout is sentinel or timeout is None:
410 real_timeout: ClientTimeout = self._timeout
411 else:
412 real_timeout = timeout
413 # timeout is cumulative for all request operations
414 # (request, redirects, responses, data consuming)
415 tm = TimeoutHandle(
416 self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold
417 )
418 handle = tm.start()
420 if read_bufsize is None:
421 read_bufsize = self._read_bufsize
423 if auto_decompress is None:
424 auto_decompress = self._auto_decompress
426 if max_line_size is None:
427 max_line_size = self._max_line_size
429 if max_field_size is None:
430 max_field_size = self._max_field_size
432 traces = [
433 Trace(
434 self,
435 trace_config,
436 trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
437 )
438 for trace_config in self._trace_configs
439 ]
441 for trace in traces:
442 await trace.send_request_start(method, url.update_query(params), headers)
444 timer = tm.timer()
445 try:
446 with timer:
447 while True:
448 url, auth_from_url = strip_auth_from_url(url)
449 if auth and auth_from_url:
450 raise ValueError(
451 "Cannot combine AUTH argument with "
452 "credentials encoded in URL"
453 )
455 if auth is None:
456 auth = auth_from_url
457 if auth is None:
458 auth = self._default_auth
459 # It would be confusing if we support explicit
460 # Authorization header with auth argument
461 if auth is not None and hdrs.AUTHORIZATION in headers:
462 raise ValueError(
463 "Cannot combine AUTHORIZATION header "
464 "with AUTH argument or credentials "
465 "encoded in URL"
466 )
468 all_cookies = self._cookie_jar.filter_cookies(url)
470 if cookies is not None:
471 tmp_cookie_jar = CookieJar()
472 tmp_cookie_jar.update_cookies(cookies)
473 req_cookies = tmp_cookie_jar.filter_cookies(url)
474 if req_cookies:
475 all_cookies.load(req_cookies)
477 if proxy is not None:
478 proxy = URL(proxy)
479 elif self._trust_env:
480 with suppress(LookupError):
481 proxy, proxy_auth = get_env_proxy_for_url(url)
483 req = self._request_class(
484 method,
485 url,
486 params=params,
487 headers=headers,
488 skip_auto_headers=skip_headers,
489 data=data,
490 cookies=all_cookies,
491 auth=auth,
492 version=version,
493 compress=compress,
494 chunked=chunked,
495 expect100=expect100,
496 loop=self._loop,
497 response_class=self._response_class,
498 proxy=proxy,
499 proxy_auth=proxy_auth,
500 timer=timer,
501 session=self,
502 ssl=ssl,
503 server_hostname=server_hostname,
504 proxy_headers=proxy_headers,
505 traces=traces,
506 trust_env=self.trust_env,
507 )
509 # connection timeout
510 try:
511 async with ceil_timeout(
512 real_timeout.connect,
513 ceil_threshold=real_timeout.ceil_threshold,
514 ):
515 assert self._connector is not None
516 conn = await self._connector.connect(
517 req, traces=traces, timeout=real_timeout
518 )
519 except asyncio.TimeoutError as exc:
520 raise ServerTimeoutError(
521 f"Connection timeout to host {url}"
522 ) from exc
524 assert conn.transport is not None
526 assert conn.protocol is not None
527 conn.protocol.set_response_params(
528 timer=timer,
529 skip_payload=method.upper() == "HEAD",
530 read_until_eof=read_until_eof,
531 auto_decompress=auto_decompress,
532 read_timeout=real_timeout.sock_read,
533 read_bufsize=read_bufsize,
534 timeout_ceil_threshold=self._connector._timeout_ceil_threshold,
535 max_line_size=max_line_size,
536 max_field_size=max_field_size,
537 )
539 try:
540 try:
541 resp = await req.send(conn)
542 try:
543 await resp.start(conn)
544 except BaseException:
545 resp.close()
546 raise
547 except BaseException:
548 conn.close()
549 raise
550 except ClientError:
551 raise
552 except OSError as exc:
553 if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
554 raise
555 raise ClientOSError(*exc.args) from exc
557 self._cookie_jar.update_cookies(resp.cookies, resp.url)
559 # redirects
560 if resp.status in (301, 302, 303, 307, 308) and allow_redirects:
561 for trace in traces:
562 await trace.send_request_redirect(
563 method, url.update_query(params), headers, resp
564 )
566 redirects += 1
567 history.append(resp)
568 if max_redirects and redirects >= max_redirects:
569 resp.close()
570 raise TooManyRedirects(
571 history[0].request_info, tuple(history)
572 )
574 # For 301 and 302, mimic IE, now changed in RFC
575 # https://github.com/kennethreitz/requests/pull/269
576 if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or (
577 resp.status in (301, 302) and resp.method == hdrs.METH_POST
578 ):
579 method = hdrs.METH_GET
580 data = None
581 if headers.get(hdrs.CONTENT_LENGTH):
582 headers.pop(hdrs.CONTENT_LENGTH)
584 r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(
585 hdrs.URI
586 )
587 if r_url is None:
588 # see github.com/aio-libs/aiohttp/issues/2022
589 break
590 else:
591 # reading from correct redirection
592 # response is forbidden
593 resp.release()
595 try:
596 parsed_url = URL(
597 r_url, encoded=not self._requote_redirect_url
598 )
600 except ValueError as e:
601 raise InvalidURL(r_url) from e
603 scheme = parsed_url.scheme
604 if scheme not in ("http", "https", ""):
605 resp.close()
606 raise ValueError("Can redirect only to http or https")
607 elif not scheme:
608 parsed_url = url.join(parsed_url)
610 is_same_host_https_redirect = (
611 url.host == parsed_url.host
612 and parsed_url.scheme == "https"
613 and url.scheme == "http"
614 )
616 if (
617 url.origin() != parsed_url.origin()
618 and not is_same_host_https_redirect
619 ):
620 auth = None
621 headers.pop(hdrs.AUTHORIZATION, None)
623 url = parsed_url
624 params = {}
625 resp.release()
626 continue
628 break
630 # check response status
631 if raise_for_status is None:
632 raise_for_status = self._raise_for_status
634 if raise_for_status is None:
635 pass
636 elif callable(raise_for_status):
637 await raise_for_status(resp)
638 elif raise_for_status:
639 resp.raise_for_status()
641 # register connection
642 if handle is not None:
643 if resp.connection is not None:
644 resp.connection.add_callback(handle.cancel)
645 else:
646 handle.cancel()
648 resp._history = tuple(history)
650 for trace in traces:
651 await trace.send_request_end(
652 method, url.update_query(params), headers, resp
653 )
654 return resp
656 except BaseException as e:
657 # cleanup timer
658 tm.close()
659 if handle:
660 handle.cancel()
661 handle = None
663 for trace in traces:
664 await trace.send_request_exception(
665 method, url.update_query(params), headers, e
666 )
667 raise
669 def ws_connect(
670 self,
671 url: StrOrURL,
672 *,
673 method: str = hdrs.METH_GET,
674 protocols: Collection[str] = (),
675 timeout: Union[ClientWSTimeout, float, _SENTINEL, None] = sentinel,
676 receive_timeout: Optional[float] = None,
677 autoclose: bool = True,
678 autoping: bool = True,
679 heartbeat: Optional[float] = None,
680 auth: Optional[BasicAuth] = None,
681 origin: Optional[str] = None,
682 params: Optional[Mapping[str, str]] = None,
683 headers: Optional[LooseHeaders] = None,
684 proxy: Optional[StrOrURL] = None,
685 proxy_auth: Optional[BasicAuth] = None,
686 ssl: Union[SSLContext, Literal[False], None, Fingerprint] = None,
687 proxy_headers: Optional[LooseHeaders] = None,
688 compress: int = 0,
689 max_msg_size: int = 4 * 1024 * 1024,
690 ) -> "_WSRequestContextManager":
691 """Initiate websocket connection."""
692 return _WSRequestContextManager(
693 self._ws_connect(
694 url,
695 method=method,
696 protocols=protocols,
697 timeout=timeout,
698 receive_timeout=receive_timeout,
699 autoclose=autoclose,
700 autoping=autoping,
701 heartbeat=heartbeat,
702 auth=auth,
703 origin=origin,
704 params=params,
705 headers=headers,
706 proxy=proxy,
707 proxy_auth=proxy_auth,
708 ssl=ssl,
709 proxy_headers=proxy_headers,
710 compress=compress,
711 max_msg_size=max_msg_size,
712 )
713 )
715 async def _ws_connect(
716 self,
717 url: StrOrURL,
718 *,
719 method: str = hdrs.METH_GET,
720 protocols: Collection[str] = (),
721 timeout: Union[ClientWSTimeout, float, _SENTINEL, None] = sentinel,
722 receive_timeout: Optional[float] = None,
723 autoclose: bool = True,
724 autoping: bool = True,
725 heartbeat: Optional[float] = None,
726 auth: Optional[BasicAuth] = None,
727 origin: Optional[str] = None,
728 params: Optional[Mapping[str, str]] = None,
729 headers: Optional[LooseHeaders] = None,
730 proxy: Optional[StrOrURL] = None,
731 proxy_auth: Optional[BasicAuth] = None,
732 ssl: Union[SSLContext, Literal[False], None, Fingerprint] = None,
733 proxy_headers: Optional[LooseHeaders] = None,
734 compress: int = 0,
735 max_msg_size: int = 4 * 1024 * 1024,
736 ) -> ClientWebSocketResponse:
737 if timeout is sentinel or timeout is None:
738 ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT
739 else:
740 if isinstance(timeout, ClientWSTimeout):
741 ws_timeout = timeout
742 else:
743 warnings.warn(
744 "parameter 'timeout' of type 'float' "
745 "is deprecated, please use "
746 "'timeout=ClientWSTimeout(ws_close=...)'",
747 DeprecationWarning,
748 stacklevel=2,
749 )
750 ws_timeout = ClientWSTimeout(ws_close=timeout)
752 if receive_timeout is not None:
753 warnings.warn(
754 "float parameter 'receive_timeout' "
755 "is deprecated, please use parameter "
756 "'timeout=ClientWSTimeout(ws_receive=...)'",
757 DeprecationWarning,
758 stacklevel=2,
759 )
760 ws_timeout = dataclasses.replace(ws_timeout, ws_receive=receive_timeout)
762 if headers is None:
763 real_headers: CIMultiDict[str] = CIMultiDict()
764 else:
765 real_headers = CIMultiDict(headers)
767 default_headers = {
768 hdrs.UPGRADE: "websocket",
769 hdrs.CONNECTION: "Upgrade",
770 hdrs.SEC_WEBSOCKET_VERSION: "13",
771 }
773 for key, value in default_headers.items():
774 real_headers.setdefault(key, value)
776 sec_key = base64.b64encode(os.urandom(16))
777 real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode()
779 if protocols:
780 real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols)
781 if origin is not None:
782 real_headers[hdrs.ORIGIN] = origin
783 if compress:
784 extstr = ws_ext_gen(compress=compress)
785 real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr
787 if not isinstance(ssl, SSL_ALLOWED_TYPES):
788 raise TypeError(
789 "ssl should be SSLContext, bool, Fingerprint, "
790 "or None, got {!r} instead.".format(ssl)
791 )
793 # send request
794 resp = await self.request(
795 method,
796 url,
797 params=params,
798 headers=real_headers,
799 read_until_eof=False,
800 auth=auth,
801 proxy=proxy,
802 proxy_auth=proxy_auth,
803 ssl=ssl,
804 proxy_headers=proxy_headers,
805 )
807 try:
808 # check handshake
809 if resp.status != 101:
810 raise WSServerHandshakeError(
811 resp.request_info,
812 resp.history,
813 message="Invalid response status",
814 status=resp.status,
815 headers=resp.headers,
816 )
818 if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket":
819 raise WSServerHandshakeError(
820 resp.request_info,
821 resp.history,
822 message="Invalid upgrade header",
823 status=resp.status,
824 headers=resp.headers,
825 )
827 if resp.headers.get(hdrs.CONNECTION, "").lower() != "upgrade":
828 raise WSServerHandshakeError(
829 resp.request_info,
830 resp.history,
831 message="Invalid connection header",
832 status=resp.status,
833 headers=resp.headers,
834 )
836 # key calculation
837 r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "")
838 match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode()
839 if r_key != match:
840 raise WSServerHandshakeError(
841 resp.request_info,
842 resp.history,
843 message="Invalid challenge response",
844 status=resp.status,
845 headers=resp.headers,
846 )
848 # websocket protocol
849 protocol = None
850 if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers:
851 resp_protocols = [
852 proto.strip()
853 for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
854 ]
856 for proto in resp_protocols:
857 if proto in protocols:
858 protocol = proto
859 break
861 # websocket compress
862 notakeover = False
863 if compress:
864 compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS)
865 if compress_hdrs:
866 try:
867 compress, notakeover = ws_ext_parse(compress_hdrs)
868 except WSHandshakeError as exc:
869 raise WSServerHandshakeError(
870 resp.request_info,
871 resp.history,
872 message=exc.args[0],
873 status=resp.status,
874 headers=resp.headers,
875 ) from exc
876 else:
877 compress = 0
878 notakeover = False
880 conn = resp.connection
881 assert conn is not None
882 conn_proto = conn.protocol
883 assert conn_proto is not None
884 transport = conn.transport
885 assert transport is not None
886 reader: FlowControlDataQueue[WSMessage] = FlowControlDataQueue(
887 conn_proto, 2**16, loop=self._loop
888 )
889 conn_proto.set_parser(WebSocketReader(reader, max_msg_size), reader)
890 writer = WebSocketWriter(
891 conn_proto,
892 transport,
893 use_mask=True,
894 compress=compress,
895 notakeover=notakeover,
896 )
897 except BaseException:
898 resp.close()
899 raise
900 else:
901 return self._ws_response_class(
902 reader,
903 writer,
904 protocol,
905 resp,
906 ws_timeout,
907 autoclose,
908 autoping,
909 self._loop,
910 heartbeat=heartbeat,
911 compress=compress,
912 client_notakeover=notakeover,
913 )
915 def _prepare_headers(self, headers: Optional[LooseHeaders]) -> "CIMultiDict[str]":
916 """Add default headers and transform it to CIMultiDict"""
917 # Convert headers to MultiDict
918 result = CIMultiDict(self._default_headers)
919 if headers:
920 if not isinstance(headers, (MultiDictProxy, MultiDict)):
921 headers = CIMultiDict(headers)
922 added_names: Set[str] = set()
923 for key, value in headers.items():
924 if key in added_names:
925 result.add(key, value)
926 else:
927 result[key] = value
928 added_names.add(key)
929 return result
931 def get(
932 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
933 ) -> "_RequestContextManager":
934 """Perform HTTP GET request."""
935 return _RequestContextManager(
936 self._request(hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs)
937 )
939 def options(
940 self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any
941 ) -> "_RequestContextManager":
942 """Perform HTTP OPTIONS request."""
943 return _RequestContextManager(
944 self._request(
945 hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs
946 )
947 )
949 def head(
950 self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any
951 ) -> "_RequestContextManager":
952 """Perform HTTP HEAD request."""
953 return _RequestContextManager(
954 self._request(
955 hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs
956 )
957 )
959 def post(
960 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
961 ) -> "_RequestContextManager":
962 """Perform HTTP POST request."""
963 return _RequestContextManager(
964 self._request(hdrs.METH_POST, url, data=data, **kwargs)
965 )
967 def put(
968 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
969 ) -> "_RequestContextManager":
970 """Perform HTTP PUT request."""
971 return _RequestContextManager(
972 self._request(hdrs.METH_PUT, url, data=data, **kwargs)
973 )
975 def patch(
976 self, url: StrOrURL, *, data: Any = None, **kwargs: Any
977 ) -> "_RequestContextManager":
978 """Perform HTTP PATCH request."""
979 return _RequestContextManager(
980 self._request(hdrs.METH_PATCH, url, data=data, **kwargs)
981 )
983 def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager":
984 """Perform HTTP DELETE request."""
985 return _RequestContextManager(self._request(hdrs.METH_DELETE, url, **kwargs))
987 async def close(self) -> None:
988 """Close underlying connector.
990 Release all acquired resources.
991 """
992 if not self.closed:
993 if self._connector is not None and self._connector_owner:
994 await self._connector.close()
995 self._connector = None
997 @property
998 def closed(self) -> bool:
999 """Is client session closed.
1001 A readonly property.
1002 """
1003 return self._connector is None or self._connector.closed
1005 @property
1006 def connector(self) -> Optional[BaseConnector]:
1007 """Connector instance used for the session."""
1008 return self._connector
1010 @property
1011 def cookie_jar(self) -> AbstractCookieJar:
1012 """The session cookies."""
1013 return self._cookie_jar
1015 @property
1016 def version(self) -> Tuple[int, int]:
1017 """The session HTTP protocol version."""
1018 return self._version
1020 @property
1021 def requote_redirect_url(self) -> bool:
1022 """Do URL requoting on redirection handling."""
1023 return self._requote_redirect_url
1025 @property
1026 def timeout(self) -> ClientTimeout:
1027 """Timeout for the session."""
1028 return self._timeout
1030 @property
1031 def headers(self) -> "CIMultiDict[str]":
1032 """The default headers of the client session."""
1033 return self._default_headers
1035 @property
1036 def skip_auto_headers(self) -> FrozenSet[istr]:
1037 """Headers for which autogeneration should be skipped"""
1038 return self._skip_auto_headers
1040 @property
1041 def auth(self) -> Optional[BasicAuth]:
1042 """An object that represents HTTP Basic Authorization"""
1043 return self._default_auth
1045 @property
1046 def json_serialize(self) -> JSONEncoder:
1047 """Json serializer callable"""
1048 return self._json_serialize
1050 @property
1051 def connector_owner(self) -> bool:
1052 """Should connector be closed on session closing"""
1053 return self._connector_owner
1055 @property
1056 def raise_for_status(
1057 self,
1058 ) -> Union[bool, Callable[[ClientResponse], Awaitable[None]]]:
1059 """Should `ClientResponse.raise_for_status()` be called for each response."""
1060 return self._raise_for_status
1062 @property
1063 def auto_decompress(self) -> bool:
1064 """Should the body response be automatically decompressed."""
1065 return self._auto_decompress
1067 @property
1068 def trust_env(self) -> bool:
1069 """
1070 Should proxies information from environment or netrc be trusted.
1072 Information is from HTTP_PROXY / HTTPS_PROXY environment variables
1073 or ~/.netrc file if present.
1074 """
1075 return self._trust_env
1077 @property
1078 def trace_configs(self) -> List[TraceConfig]:
1079 """A list of TraceConfig instances used for client tracing"""
1080 return self._trace_configs
1082 def detach(self) -> None:
1083 """Detach connector from session without closing the former.
1085 Session is switched to closed state anyway.
1086 """
1087 self._connector = None
1089 async def __aenter__(self) -> "ClientSession":
1090 return self
1092 async def __aexit__(
1093 self,
1094 exc_type: Optional[Type[BaseException]],
1095 exc_val: Optional[BaseException],
1096 exc_tb: Optional[TracebackType],
1097 ) -> None:
1098 await self.close()
1101class _BaseRequestContextManager(Coroutine[Any, Any, _RetType], Generic[_RetType]):
1102 __slots__ = ("_coro", "_resp")
1104 def __init__(self, coro: Coroutine["asyncio.Future[Any]", None, _RetType]) -> None:
1105 self._coro = coro
1107 def send(self, arg: None) -> "asyncio.Future[Any]":
1108 return self._coro.send(arg)
1110 def throw(self, arg: BaseException) -> None: # type: ignore[override]
1111 self._coro.throw(arg) # type: ignore[unused-awaitable]
1113 def close(self) -> None:
1114 return self._coro.close()
1116 def __await__(self) -> Generator[Any, None, _RetType]:
1117 ret = self._coro.__await__()
1118 return ret
1120 def __iter__(self) -> Generator[Any, None, _RetType]:
1121 return self.__await__()
1123 async def __aenter__(self) -> _RetType:
1124 self._resp = await self._coro
1125 return self._resp
1128class _RequestContextManager(_BaseRequestContextManager[ClientResponse]):
1129 __slots__ = ()
1131 async def __aexit__(
1132 self,
1133 exc_type: Optional[Type[BaseException]],
1134 exc: Optional[BaseException],
1135 tb: Optional[TracebackType],
1136 ) -> None:
1137 # We're basing behavior on the exception as it can be caused by
1138 # user code unrelated to the status of the connection. If you
1139 # would like to close a connection you must do that
1140 # explicitly. Otherwise connection error handling should kick in
1141 # and close/recycle the connection as required.
1142 self._resp.release()
1145class _WSRequestContextManager(_BaseRequestContextManager[ClientWebSocketResponse]):
1146 __slots__ = ()
1148 async def __aexit__(
1149 self,
1150 exc_type: Optional[Type[BaseException]],
1151 exc: Optional[BaseException],
1152 tb: Optional[TracebackType],
1153 ) -> None:
1154 await self._resp.close()
1157class _SessionRequestContextManager:
1158 __slots__ = ("_coro", "_resp", "_session")
1160 def __init__(
1161 self,
1162 coro: Coroutine["asyncio.Future[Any]", None, ClientResponse],
1163 session: ClientSession,
1164 ) -> None:
1165 self._coro = coro
1166 self._resp: Optional[ClientResponse] = None
1167 self._session = session
1169 async def __aenter__(self) -> ClientResponse:
1170 try:
1171 self._resp = await self._coro
1172 except BaseException:
1173 await self._session.close()
1174 raise
1175 else:
1176 return self._resp
1178 async def __aexit__(
1179 self,
1180 exc_type: Optional[Type[BaseException]],
1181 exc: Optional[BaseException],
1182 tb: Optional[TracebackType],
1183 ) -> None:
1184 assert self._resp is not None
1185 self._resp.close()
1186 await self._session.close()
1189def request(
1190 method: str,
1191 url: StrOrURL,
1192 *,
1193 params: Optional[Mapping[str, str]] = None,
1194 data: Any = None,
1195 json: Any = None,
1196 headers: Optional[LooseHeaders] = None,
1197 skip_auto_headers: Optional[Iterable[str]] = None,
1198 auth: Optional[BasicAuth] = None,
1199 allow_redirects: bool = True,
1200 max_redirects: int = 10,
1201 compress: Optional[str] = None,
1202 chunked: Optional[bool] = None,
1203 expect100: bool = False,
1204 raise_for_status: Optional[bool] = None,
1205 read_until_eof: bool = True,
1206 proxy: Optional[StrOrURL] = None,
1207 proxy_auth: Optional[BasicAuth] = None,
1208 timeout: Union[ClientTimeout, _SENTINEL] = sentinel,
1209 cookies: Optional[LooseCookies] = None,
1210 version: HttpVersion = http.HttpVersion11,
1211 connector: Optional[BaseConnector] = None,
1212 read_bufsize: Optional[int] = None,
1213 max_line_size: int = 8190,
1214 max_field_size: int = 8190,
1215) -> _SessionRequestContextManager:
1216 """Constructs and sends a request.
1218 Returns response object.
1219 method - HTTP method
1220 url - request url
1221 params - (optional) Dictionary or bytes to be sent in the query
1222 string of the new request
1223 data - (optional) Dictionary, bytes, or file-like object to
1224 send in the body of the request
1225 json - (optional) Any json compatible python object
1226 headers - (optional) Dictionary of HTTP Headers to send with
1227 the request
1228 cookies - (optional) Dict object to send with the request
1229 auth - (optional) BasicAuth named tuple represent HTTP Basic Auth
1230 auth - aiohttp.helpers.BasicAuth
1231 allow_redirects - (optional) If set to False, do not follow
1232 redirects
1233 version - Request HTTP version.
1234 compress - Set to True if request has to be compressed
1235 with deflate encoding.
1236 chunked - Set to chunk size for chunked transfer encoding.
1237 expect100 - Expect 100-continue response from server.
1238 connector - BaseConnector sub-class instance to support
1239 connection pooling.
1240 read_until_eof - Read response until eof if response
1241 does not have Content-Length header.
1242 loop - Optional event loop.
1243 timeout - Optional ClientTimeout settings structure, 5min
1244 total timeout by default.
1245 Usage::
1246 >>> import aiohttp
1247 >>> async with aiohttp.request('GET', 'http://python.org/') as resp:
1248 ... print(resp)
1249 ... data = await resp.read()
1250 <ClientResponse(https://www.python.org/) [200 OK]>
1251 """
1252 connector_owner = False
1253 if connector is None:
1254 connector_owner = True
1255 connector = TCPConnector(force_close=True)
1257 session = ClientSession(
1258 cookies=cookies,
1259 version=version,
1260 timeout=timeout,
1261 connector=connector,
1262 connector_owner=connector_owner,
1263 )
1265 return _SessionRequestContextManager(
1266 session._request(
1267 method,
1268 url,
1269 params=params,
1270 data=data,
1271 json=json,
1272 headers=headers,
1273 skip_auto_headers=skip_auto_headers,
1274 auth=auth,
1275 allow_redirects=allow_redirects,
1276 max_redirects=max_redirects,
1277 compress=compress,
1278 chunked=chunked,
1279 expect100=expect100,
1280 raise_for_status=raise_for_status,
1281 read_until_eof=read_until_eof,
1282 proxy=proxy,
1283 proxy_auth=proxy_auth,
1284 read_bufsize=read_bufsize,
1285 max_line_size=max_line_size,
1286 max_field_size=max_field_size,
1287 ),
1288 session,
1289 )