2016-11-28 00:26:46 +00:00
|
|
|
"""Helper for aiohttp webclient stuff."""
|
|
|
|
import asyncio
|
2017-01-19 17:55:27 +00:00
|
|
|
import sys
|
2019-02-07 21:34:14 +00:00
|
|
|
from ssl import SSLContext # noqa: F401
|
|
|
|
from typing import Any, Awaitable, Optional, cast
|
|
|
|
from typing import Union # noqa: F401
|
2017-01-19 17:55:27 +00:00
|
|
|
|
2016-11-28 00:26:46 +00:00
|
|
|
import aiohttp
|
2017-01-19 17:55:27 +00:00
|
|
|
from aiohttp.hdrs import USER_AGENT, CONTENT_TYPE
|
|
|
|
from aiohttp import web
|
2017-03-30 07:50:53 +00:00
|
|
|
from aiohttp.web_exceptions import HTTPGatewayTimeout, HTTPBadGateway
|
2017-01-19 17:55:27 +00:00
|
|
|
import async_timeout
|
2016-11-28 00:26:46 +00:00
|
|
|
|
2019-02-07 21:34:14 +00:00
|
|
|
from homeassistant.core import callback, Event
|
2017-10-08 15:17:54 +00:00
|
|
|
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__
|
2019-02-07 21:34:14 +00:00
|
|
|
from homeassistant.helpers.typing import HomeAssistantType
|
2017-10-08 15:17:54 +00:00
|
|
|
from homeassistant.loader import bind_hass
|
2018-07-16 08:32:07 +00:00
|
|
|
from homeassistant.util import ssl as ssl_util
|
2016-11-28 00:26:46 +00:00
|
|
|
|
|
|
|
DATA_CONNECTOR = 'aiohttp_connector'
|
|
|
|
DATA_CONNECTOR_NOTVERIFY = 'aiohttp_connector_notverify'
|
|
|
|
DATA_CLIENTSESSION = 'aiohttp_clientsession'
|
|
|
|
DATA_CLIENTSESSION_NOTVERIFY = 'aiohttp_clientsession_notverify'
|
2017-01-04 20:15:50 +00:00
|
|
|
SERVER_SOFTWARE = 'HomeAssistant/{0} aiohttp/{1} Python/{2[0]}.{2[1]}'.format(
|
|
|
|
__version__, aiohttp.__version__, sys.version_info)
|
2016-11-28 00:26:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
@callback
|
2017-10-08 15:17:54 +00:00
|
|
|
@bind_hass
|
2019-02-07 21:34:14 +00:00
|
|
|
def async_get_clientsession(hass: HomeAssistantType,
|
|
|
|
verify_ssl: bool = True) -> aiohttp.ClientSession:
|
2016-11-28 00:26:46 +00:00
|
|
|
"""Return default aiohttp ClientSession.
|
|
|
|
|
|
|
|
This method must be run in the event loop.
|
|
|
|
"""
|
|
|
|
if verify_ssl:
|
|
|
|
key = DATA_CLIENTSESSION
|
|
|
|
else:
|
|
|
|
key = DATA_CLIENTSESSION_NOTVERIFY
|
|
|
|
|
|
|
|
if key not in hass.data:
|
2018-03-04 05:28:04 +00:00
|
|
|
hass.data[key] = async_create_clientsession(hass, verify_ssl)
|
2016-11-28 00:26:46 +00:00
|
|
|
|
2019-02-07 21:34:14 +00:00
|
|
|
return cast(aiohttp.ClientSession, hass.data[key])
|
2016-11-28 00:26:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
@callback
|
2017-10-08 15:17:54 +00:00
|
|
|
@bind_hass
|
2019-02-07 21:34:14 +00:00
|
|
|
def async_create_clientsession(hass: HomeAssistantType,
|
|
|
|
verify_ssl: bool = True,
|
|
|
|
auto_cleanup: bool = True,
|
|
|
|
**kwargs: Any) -> aiohttp.ClientSession:
|
2016-11-28 00:26:46 +00:00
|
|
|
"""Create a new ClientSession with kwargs, i.e. for cookies.
|
|
|
|
|
|
|
|
If auto_cleanup is False, you need to call detach() after the session
|
|
|
|
returned is no longer used. Default is True, the session will be
|
|
|
|
automatically detached on homeassistant_stop.
|
|
|
|
|
|
|
|
This method must be run in the event loop.
|
|
|
|
"""
|
|
|
|
connector = _async_get_connector(hass, verify_ssl)
|
|
|
|
|
|
|
|
clientsession = aiohttp.ClientSession(
|
|
|
|
loop=hass.loop,
|
|
|
|
connector=connector,
|
2017-01-04 20:15:50 +00:00
|
|
|
headers={USER_AGENT: SERVER_SOFTWARE},
|
2016-11-28 00:26:46 +00:00
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
|
|
|
if auto_cleanup:
|
|
|
|
_async_register_clientsession_shutdown(hass, clientsession)
|
|
|
|
|
|
|
|
return clientsession
|
|
|
|
|
|
|
|
|
2017-10-08 15:17:54 +00:00
|
|
|
@bind_hass
|
2019-02-07 21:34:14 +00:00
|
|
|
async def async_aiohttp_proxy_web(
|
|
|
|
hass: HomeAssistantType, request: web.BaseRequest,
|
|
|
|
web_coro: Awaitable[aiohttp.ClientResponse], buffer_size: int = 102400,
|
|
|
|
timeout: int = 10) -> Optional[web.StreamResponse]:
|
2017-01-19 17:55:27 +00:00
|
|
|
"""Stream websession request to aiohttp web response."""
|
|
|
|
try:
|
|
|
|
with async_timeout.timeout(timeout, loop=hass.loop):
|
2018-07-23 12:36:36 +00:00
|
|
|
req = await web_coro
|
2017-03-30 07:50:53 +00:00
|
|
|
|
2017-03-31 09:55:22 +00:00
|
|
|
except asyncio.CancelledError:
|
|
|
|
# The user cancelled the request
|
2019-02-07 21:34:14 +00:00
|
|
|
return None
|
2017-03-31 09:55:22 +00:00
|
|
|
|
2017-03-30 07:50:53 +00:00
|
|
|
except asyncio.TimeoutError as err:
|
2017-03-31 09:55:22 +00:00
|
|
|
# Timeout trying to start the web request
|
2017-03-30 07:50:53 +00:00
|
|
|
raise HTTPGatewayTimeout() from err
|
|
|
|
|
|
|
|
except aiohttp.ClientError as err:
|
2017-03-31 09:55:22 +00:00
|
|
|
# Something went wrong with the connection
|
2017-03-30 07:50:53 +00:00
|
|
|
raise HTTPBadGateway() from err
|
2017-01-19 17:55:27 +00:00
|
|
|
|
2017-08-26 16:56:39 +00:00
|
|
|
try:
|
2018-07-23 12:36:36 +00:00
|
|
|
return await async_aiohttp_proxy_stream(
|
2017-08-26 16:56:39 +00:00
|
|
|
hass,
|
|
|
|
request,
|
|
|
|
req.content,
|
|
|
|
req.headers.get(CONTENT_TYPE)
|
|
|
|
)
|
|
|
|
finally:
|
|
|
|
req.close()
|
2017-01-19 17:55:27 +00:00
|
|
|
|
|
|
|
|
2017-10-08 15:17:54 +00:00
|
|
|
@bind_hass
|
2019-02-07 21:34:14 +00:00
|
|
|
async def async_aiohttp_proxy_stream(hass: HomeAssistantType,
|
|
|
|
request: web.BaseRequest,
|
|
|
|
stream: aiohttp.StreamReader,
|
|
|
|
content_type: str,
|
|
|
|
buffer_size: int = 102400,
|
|
|
|
timeout: int = 10) -> web.StreamResponse:
|
2017-03-30 07:50:53 +00:00
|
|
|
"""Stream a stream to aiohttp web response."""
|
|
|
|
response = web.StreamResponse()
|
|
|
|
response.content_type = content_type
|
2018-02-25 11:38:46 +00:00
|
|
|
await response.prepare(request)
|
2017-03-30 07:50:53 +00:00
|
|
|
|
|
|
|
try:
|
2017-01-19 17:55:27 +00:00
|
|
|
while True:
|
2017-03-30 07:50:53 +00:00
|
|
|
with async_timeout.timeout(timeout, loop=hass.loop):
|
2018-02-25 11:38:46 +00:00
|
|
|
data = await stream.read(buffer_size)
|
2017-03-30 07:50:53 +00:00
|
|
|
|
2017-01-24 19:43:36 +00:00
|
|
|
if not data:
|
|
|
|
break
|
2018-03-05 21:28:41 +00:00
|
|
|
await response.write(data)
|
2017-01-19 17:55:27 +00:00
|
|
|
|
2017-03-30 07:50:53 +00:00
|
|
|
except (asyncio.TimeoutError, aiohttp.ClientError):
|
2018-07-23 12:36:36 +00:00
|
|
|
# Something went wrong fetching data, closed connection
|
2017-03-31 09:55:22 +00:00
|
|
|
pass
|
2017-01-19 17:55:27 +00:00
|
|
|
|
2018-07-23 12:36:36 +00:00
|
|
|
return response
|
|
|
|
|
2017-01-19 17:55:27 +00:00
|
|
|
|
2016-11-28 00:26:46 +00:00
|
|
|
@callback
|
2019-02-07 21:34:14 +00:00
|
|
|
def _async_register_clientsession_shutdown(
|
|
|
|
hass: HomeAssistantType, clientsession: aiohttp.ClientSession) -> None:
|
2017-06-08 13:53:12 +00:00
|
|
|
"""Register ClientSession close on Home Assistant shutdown.
|
2016-11-28 00:26:46 +00:00
|
|
|
|
|
|
|
This method must be run in the event loop.
|
|
|
|
"""
|
|
|
|
@callback
|
2019-02-07 21:34:14 +00:00
|
|
|
def _async_close_websession(event: Event) -> None:
|
2016-11-28 00:26:46 +00:00
|
|
|
"""Close websession."""
|
|
|
|
clientsession.detach()
|
|
|
|
|
|
|
|
hass.bus.async_listen_once(
|
2017-02-13 05:24:07 +00:00
|
|
|
EVENT_HOMEASSISTANT_CLOSE, _async_close_websession)
|
2016-11-28 00:26:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
@callback
|
2019-02-07 21:34:14 +00:00
|
|
|
def _async_get_connector(hass: HomeAssistantType,
|
|
|
|
verify_ssl: bool = True) -> aiohttp.BaseConnector:
|
2016-11-28 00:26:46 +00:00
|
|
|
"""Return the connector pool for aiohttp.
|
|
|
|
|
|
|
|
This method must be run in the event loop.
|
|
|
|
"""
|
2018-03-15 20:49:49 +00:00
|
|
|
key = DATA_CONNECTOR if verify_ssl else DATA_CONNECTOR_NOTVERIFY
|
|
|
|
|
|
|
|
if key in hass.data:
|
2019-02-07 21:34:14 +00:00
|
|
|
return cast(aiohttp.BaseConnector, hass.data[key])
|
2017-02-13 05:24:07 +00:00
|
|
|
|
2016-11-28 00:26:46 +00:00
|
|
|
if verify_ssl:
|
2019-02-07 21:34:14 +00:00
|
|
|
ssl_context = \
|
|
|
|
ssl_util.client_context() # type: Union[bool, SSLContext]
|
2016-11-28 00:26:46 +00:00
|
|
|
else:
|
2018-03-15 20:49:49 +00:00
|
|
|
ssl_context = False
|
|
|
|
|
2019-04-10 21:24:12 +00:00
|
|
|
connector = aiohttp.TCPConnector(loop=hass.loop,
|
|
|
|
enable_cleanup_closed=True,
|
|
|
|
ssl=ssl_context,
|
|
|
|
)
|
2018-03-15 20:49:49 +00:00
|
|
|
hass.data[key] = connector
|
|
|
|
|
2019-02-07 21:34:14 +00:00
|
|
|
async def _async_close_connector(event: Event) -> None:
|
2018-03-15 20:49:49 +00:00
|
|
|
"""Close connector pool."""
|
2019-01-09 05:09:47 +00:00
|
|
|
await connector.close()
|
2018-03-15 20:49:49 +00:00
|
|
|
|
|
|
|
hass.bus.async_listen_once(
|
|
|
|
EVENT_HOMEASSISTANT_CLOSE, _async_close_connector)
|
2016-11-28 00:26:46 +00:00
|
|
|
|
2017-02-13 05:24:07 +00:00
|
|
|
return connector
|