1 """Helper for aiohttp webclient stuff."""
3 from __future__
import annotations
6 from collections.abc
import Awaitable, Callable
7 from contextlib
import suppress
9 from ssl
import SSLContext
11 from types
import MappingProxyType
12 from typing
import TYPE_CHECKING, Any
15 from aiohttp
import web
16 from aiohttp.hdrs
import CONTENT_TYPE, USER_AGENT
17 from aiohttp.resolver
import AsyncResolver
18 from aiohttp.web_exceptions
import HTTPBadGateway, HTTPGatewayTimeout
20 from homeassistant
import config_entries
28 from .frame
import warn_use
29 from .json
import json_dumps
32 from aiohttp.typedefs
import JSONDecoder
35 DATA_CONNECTOR: HassKey[dict[tuple[bool, int, str], aiohttp.BaseConnector]] =
HassKey(
38 DATA_CLIENTSESSION: HassKey[dict[tuple[bool, int, str], aiohttp.ClientSession]] = (
39 HassKey(
"aiohttp_clientsession")
43 f
"{APPLICATION_NAME}/{__version__} "
44 f
"aiohttp/{aiohttp.__version__} Python/{sys.version_info[0]}.{sys.version_info[1]}"
47 ENABLE_CLEANUP_CLOSED = (3, 13, 0) <= sys.version_info < (
51 )
or sys.version_info < (3, 12, 7)
55 WARN_CLOSE_MSG =
"closes the Home Assistant aiohttp session"
68 MAXIMUM_CONNECTIONS = 4096
69 MAXIMUM_CONNECTIONS_PER_HOST = 100
73 """aiohttp.ClientResponse with a json method that uses json_loads by default."""
78 loads: JSONDecoder = json_loads,
81 """Send a json request and parse the json response."""
82 return await super().
json(*args, loads=loads, **kwargs)
89 verify_ssl: bool =
True,
90 family: socket.AddressFamily = socket.AF_UNSPEC,
91 ssl_cipher: ssl_util.SSLCipherList = ssl_util.SSLCipherList.PYTHON_DEFAULT,
92 ) -> aiohttp.ClientSession:
93 """Return default aiohttp ClientSession.
95 This method must be run in the event loop.
97 session_key =
_make_key(verify_ssl, family, ssl_cipher)
98 sessions = hass.data.setdefault(DATA_CLIENTSESSION, {})
100 if session_key
not in sessions:
104 auto_cleanup_method=_async_register_default_clientsession_shutdown,
106 ssl_cipher=ssl_cipher,
108 sessions[session_key] = session
110 session = sessions[session_key]
119 verify_ssl: bool =
True,
120 auto_cleanup: bool =
True,
121 family: socket.AddressFamily = socket.AF_UNSPEC,
122 ssl_cipher: ssl_util.SSLCipherList = ssl_util.SSLCipherList.PYTHON_DEFAULT,
124 ) -> aiohttp.ClientSession:
125 """Create a new ClientSession with kwargs, i.e. for cookies.
127 If auto_cleanup is False, you need to call detach() after the session
128 returned is no longer used. Default is True, the session will be
129 automatically detached on homeassistant_stop or when being created
130 in config entry setup, the config entry is unloaded.
132 This method must be run in the event loop.
134 auto_cleanup_method =
None
136 auto_cleanup_method = _async_register_clientsession_shutdown
141 auto_cleanup_method=auto_cleanup_method,
143 ssl_cipher=ssl_cipher,
151 verify_ssl: bool =
True,
152 auto_cleanup_method: Callable[[HomeAssistant, aiohttp.ClientSession],
None]
154 family: socket.AddressFamily = socket.AF_UNSPEC,
155 ssl_cipher: ssl_util.SSLCipherList = ssl_util.SSLCipherList.PYTHON_DEFAULT,
157 ) -> aiohttp.ClientSession:
158 """Create a new ClientSession with kwargs, i.e. for cookies."""
159 clientsession = aiohttp.ClientSession(
161 json_serialize=json_dumps,
162 response_class=HassClientResponse,
169 clientsession._default_headers = MappingProxyType(
170 {USER_AGENT: SERVER_SOFTWARE},
173 clientsession.close = warn_use(
178 if auto_cleanup_method:
179 auto_cleanup_method(hass, clientsession)
187 request: web.BaseRequest,
188 web_coro: Awaitable[aiohttp.ClientResponse],
189 buffer_size: int = 102400,
191 ) -> web.StreamResponse |
None:
192 """Stream websession request to aiohttp web response."""
194 async
with asyncio.timeout(timeout):
197 except asyncio.CancelledError:
201 except TimeoutError
as err:
203 raise HTTPGatewayTimeout
from err
205 except aiohttp.ClientError
as err:
207 raise HTTPBadGateway
from err
211 hass, request, req.content, req.headers.get(CONTENT_TYPE)
220 request: web.BaseRequest,
221 stream: aiohttp.StreamReader,
222 content_type: str |
None,
223 buffer_size: int = 102400,
225 ) -> web.StreamResponse:
226 """Stream a stream to aiohttp web response."""
227 response = web.StreamResponse()
228 if content_type
is not None:
229 response.content_type = content_type
230 await response.prepare(request)
233 with suppress(TimeoutError, aiohttp.ClientError):
234 while hass.is_running:
235 async
with asyncio.timeout(timeout):
236 data = await stream.read(buffer_size)
240 await response.write(data)
247 hass: HomeAssistant, clientsession: aiohttp.ClientSession
249 """Register ClientSession close on Home Assistant shutdown or config entry unload.
251 This method must be run in the event loop.
255 def _async_close_websession(*_: Any) ->
None:
256 """Close websession."""
257 clientsession.detach()
259 unsub = hass.bus.async_listen_once(
260 EVENT_HOMEASSISTANT_CLOSE, _async_close_websession
263 if not (config_entry := config_entries.current_entry.get()):
266 config_entry.async_on_unload(unsub)
267 config_entry.async_on_unload(_async_close_websession)
272 hass: HomeAssistant, clientsession: aiohttp.ClientSession
274 """Register default ClientSession close on Home Assistant shutdown.
276 This method must be run in the event loop.
280 def _async_close_websession(event: Event) ->
None:
281 """Close websession."""
282 clientsession.detach()
284 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, _async_close_websession)
289 verify_ssl: bool =
True,
290 family: socket.AddressFamily = socket.AF_UNSPEC,
291 ssl_cipher: ssl_util.SSLCipherList = ssl_util.SSLCipherList.PYTHON_DEFAULT,
292 ) -> tuple[bool, socket.AddressFamily, ssl_util.SSLCipherList]:
293 """Make a key for connector or session pool."""
294 return (verify_ssl, family, ssl_cipher)
298 """Home Assistant TCP Connector.
300 Same as aiohttp.TCPConnector but with a longer cleanup_closed timeout.
302 By default the cleanup_closed timeout is 2 seconds. This is too short
303 for Home Assistant since we churn through a lot of connections. We set
304 it to 60 seconds to reduce the overhead of aborting TLS connections
305 that are likely already closed.
309 _cleanup_closed_period = 60.0
315 verify_ssl: bool =
True,
316 family: socket.AddressFamily = socket.AF_UNSPEC,
317 ssl_cipher: ssl_util.SSLCipherList = ssl_util.SSLCipherList.PYTHON_DEFAULT,
318 ) -> aiohttp.BaseConnector:
319 """Return the connector pool for aiohttp.
321 This method must be run in the event loop.
323 connector_key =
_make_key(verify_ssl, family, ssl_cipher)
324 connectors = hass.data.setdefault(DATA_CONNECTOR, {})
326 if connector_key
in connectors:
327 return connectors[connector_key]
330 ssl_context: SSLContext = ssl_util.client_context(ssl_cipher)
332 ssl_context = ssl_util.client_context_no_verify(ssl_cipher)
336 enable_cleanup_closed=ENABLE_CLEANUP_CLOSED,
338 limit=MAXIMUM_CONNECTIONS,
339 limit_per_host=MAXIMUM_CONNECTIONS_PER_HOST,
340 resolver=AsyncResolver(),
342 connectors[connector_key] = connector
344 async
def _async_close_connector(event: Event) ->
None:
345 """Close connector pool."""
346 await connector.close()
348 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, _async_close_connector)
Any json(self, *Any args, JSONDecoder loads=json_loads, **Any kwargs)
None _async_register_clientsession_shutdown(HomeAssistant hass, aiohttp.ClientSession clientsession)
web.StreamResponse async_aiohttp_proxy_stream(HomeAssistant hass, web.BaseRequest request, aiohttp.StreamReader stream, str|None content_type, int buffer_size=102400, int timeout=10)
aiohttp.ClientSession async_create_clientsession(HomeAssistant hass, bool verify_ssl=True, bool auto_cleanup=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT, **Any kwargs)
aiohttp.BaseConnector _async_get_connector(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
aiohttp.ClientSession _async_create_clientsession(HomeAssistant hass, bool verify_ssl=True, Callable[[HomeAssistant, aiohttp.ClientSession], None]|None auto_cleanup_method=None, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT, **Any kwargs)
tuple[bool, socket.AddressFamily, ssl_util.SSLCipherList] _make_key(bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
web.StreamResponse|None async_aiohttp_proxy_web(HomeAssistant hass, web.BaseRequest request, Awaitable[aiohttp.ClientResponse] web_coro, int buffer_size=102400, int timeout=10)
None _async_register_default_clientsession_shutdown(HomeAssistant hass, aiohttp.ClientSession clientsession)