mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-08 01:36:29 +00:00
Allow event stream over api proxy (#285)
* Allow event stream over api proxy * fix lint * fix lint * cleanup code * fix bug * fix prepare * Fix stream bug * fix api request
This commit is contained in:
parent
63d82ce03e
commit
d7df423deb
@ -137,6 +137,7 @@ class Addon(object):
|
||||
"""Return if auto update is enable."""
|
||||
if ATTR_AUTO_UPDATE in self.data.user.get(self._id, {}):
|
||||
return self.data.user[self._id][ATTR_AUTO_UPDATE]
|
||||
return None
|
||||
|
||||
@auto_update.setter
|
||||
def auto_update(self, value):
|
||||
@ -159,6 +160,7 @@ class Addon(object):
|
||||
"""Return a API token for this add-on."""
|
||||
if self.is_installed:
|
||||
return self.data.user[self._id][ATTR_UUID]
|
||||
return None
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
@ -333,7 +335,7 @@ class Addon(object):
|
||||
def audio_input(self):
|
||||
"""Return ALSA config for input or None."""
|
||||
if not self.with_audio:
|
||||
return
|
||||
return None
|
||||
|
||||
setting = self.config.audio_input
|
||||
if self.is_installed and ATTR_AUDIO_INPUT in self.data.user[self._id]:
|
||||
|
@ -219,6 +219,7 @@ def validate_options(raw_schema):
|
||||
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
# pylint: disable=inconsistent-return-statements
|
||||
def _single_validate(typ, value, key):
|
||||
"""Validate a single element."""
|
||||
# if required argument
|
||||
|
@ -79,6 +79,8 @@ class RestAPI(object):
|
||||
'/homeassistant/api/{path:.+}', api_hass.api)
|
||||
self.webapp.router.add_get(
|
||||
'/homeassistant/api/{path:.+}', api_hass.api)
|
||||
self.webapp.router.add_get(
|
||||
'/homeassistant/api', api_hass.api)
|
||||
|
||||
def register_addons(self, addons):
|
||||
"""Register homeassistant function."""
|
||||
|
@ -57,7 +57,7 @@ class APIAddons(object):
|
||||
"""Return a simplified device list."""
|
||||
dev_list = addon.devices
|
||||
if not dev_list:
|
||||
return
|
||||
return None
|
||||
return [row.split(':')[0] for row in dev_list]
|
||||
|
||||
@api_process
|
||||
|
@ -46,9 +46,9 @@ class APIHomeAssistant(object):
|
||||
self.loop = loop
|
||||
self.homeassistant = homeassistant
|
||||
|
||||
async def homeassistant_proxy(self, path, request):
|
||||
async def homeassistant_proxy(self, path, request, timeout=300):
|
||||
"""Return a client request with proxy origin for Home-Assistant."""
|
||||
url = "{}/api/{}".format(self.homeassistant.api_url, path)
|
||||
url = f"{self.homeassistant.api_url}/api/{path}"
|
||||
|
||||
try:
|
||||
data = None
|
||||
@ -57,7 +57,7 @@ class APIHomeAssistant(object):
|
||||
self.homeassistant.websession, request.method.lower())
|
||||
|
||||
# read data
|
||||
with async_timeout.timeout(10, loop=self.loop):
|
||||
with async_timeout.timeout(30, loop=self.loop):
|
||||
data = await request.read()
|
||||
|
||||
if data:
|
||||
@ -72,7 +72,7 @@ class APIHomeAssistant(object):
|
||||
headers = None
|
||||
|
||||
client = await method(
|
||||
url, data=data, headers=headers, timeout=300
|
||||
url, data=data, headers=headers, timeout=timeout
|
||||
)
|
||||
|
||||
return client
|
||||
@ -172,11 +172,41 @@ class APIHomeAssistant(object):
|
||||
|
||||
async def api(self, request):
|
||||
"""Proxy API request to Home-Assistant."""
|
||||
path = request.match_info.get('path')
|
||||
path = request.match_info.get('path', '')
|
||||
_LOGGER.info("Proxy /api/%s request", path)
|
||||
|
||||
client = await self.homeassistant_proxy(path, request)
|
||||
return web.Response(
|
||||
body=await client.read(),
|
||||
status=client.status,
|
||||
content_type=client.content_type
|
||||
)
|
||||
# API stream
|
||||
if path.startswith("stream"):
|
||||
client = await self.homeassistant_proxy(
|
||||
path, request, timeout=None)
|
||||
|
||||
response = web.StreamResponse()
|
||||
response.content_type = request.headers.get(CONTENT_TYPE)
|
||||
try:
|
||||
await response.prepare(request)
|
||||
while True:
|
||||
data = await client.content.read(10)
|
||||
if not data:
|
||||
await response.write_eof()
|
||||
break
|
||||
response.write(data)
|
||||
|
||||
except aiohttp.ClientError:
|
||||
await response.write_eof()
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
# Normal request
|
||||
else:
|
||||
client = await self.homeassistant_proxy(path, request)
|
||||
|
||||
data = await client.read()
|
||||
return web.Response(
|
||||
body=data,
|
||||
status=client.status,
|
||||
content_type=client.content_type
|
||||
)
|
||||
|
@ -25,6 +25,7 @@ class DockerAddon(DockerInterface):
|
||||
config, loop, api, image=addon.image, timeout=addon.timeout)
|
||||
self.addon = addon
|
||||
|
||||
# pylint: disable=inconsistent-return-statements
|
||||
def process_metadata(self, metadata, force=False):
|
||||
"""Use addon data instead meta data with legacy."""
|
||||
if not self.addon.legacy:
|
||||
@ -50,6 +51,7 @@ class DockerAddon(DockerInterface):
|
||||
"""Return the IPC namespace."""
|
||||
if self.addon.host_ipc:
|
||||
return 'host'
|
||||
return None
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
@ -114,6 +116,7 @@ class DockerAddon(DockerInterface):
|
||||
return [
|
||||
"apparmor:unconfined",
|
||||
]
|
||||
return None
|
||||
|
||||
@property
|
||||
def tmpfs(self):
|
||||
|
@ -27,7 +27,7 @@ class DockerHomeAssistant(DockerInterface):
|
||||
def devices(self):
|
||||
"""Create list of special device to map into docker."""
|
||||
if not self.data.devices:
|
||||
return
|
||||
return None
|
||||
|
||||
devices = []
|
||||
for device in self.data.devices:
|
||||
@ -41,7 +41,7 @@ class DockerHomeAssistant(DockerInterface):
|
||||
Need run inside executor.
|
||||
"""
|
||||
if self._is_running():
|
||||
return
|
||||
return False
|
||||
|
||||
# cleanup
|
||||
self._stop()
|
||||
|
@ -70,7 +70,7 @@ class Hardware(object):
|
||||
devices = devices_file.read()
|
||||
except OSError as err:
|
||||
_LOGGER.error("Can't read asound data -> %s", err)
|
||||
return
|
||||
return None
|
||||
|
||||
audio_list = {}
|
||||
|
||||
@ -110,12 +110,12 @@ class Hardware(object):
|
||||
stats = stat_file.read()
|
||||
except OSError as err:
|
||||
_LOGGER.error("Can't read stat data -> %s", err)
|
||||
return
|
||||
return None
|
||||
|
||||
# parse stat file
|
||||
found = RE_BOOT_TIME.search(stats)
|
||||
if not found:
|
||||
_LOGGER.error("Can't found last boot time!")
|
||||
return
|
||||
return None
|
||||
|
||||
return datetime.utcfromtimestamp(int(found.group(1)))
|
||||
|
@ -29,11 +29,12 @@ def validate_timezone(timezone):
|
||||
return timezone
|
||||
|
||||
|
||||
# pylint: disable=inconsistent-return-statements
|
||||
def convert_to_docker_ports(data):
|
||||
"""Convert data into docker port list."""
|
||||
# dynamic ports
|
||||
if data is None:
|
||||
return
|
||||
return None
|
||||
|
||||
# single port
|
||||
if isinstance(data, int):
|
||||
|
Loading…
x
Reference in New Issue
Block a user