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:
Pascal Vizeli 2017-12-24 15:04:16 +01:00 committed by GitHub
parent 63d82ce03e
commit d7df423deb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 58 additions and 19 deletions

View File

@ -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]:

View File

@ -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

View File

@ -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."""

View File

@ -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

View File

@ -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
)

View File

@ -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):

View File

@ -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()

View File

@ -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)))

View File

@ -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):