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