1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 21:06:19 +00:00

Hass.io ingress (#22505)

* Fix API stream of snapshot / Add ingress

* fix lint

* Fix stream handling

* Cleanup api handling

* fix typing

* Set proxy header

* Use header constant

* Enable the ingress setup

* fix lint

* Fix name

* Fix tests

* fix lint

* forward params

* Add tests for ingress

* Cleanup cookie handling with aiohttp 3.5

* Add more tests

* Fix tests

* Fix lint

* Fix header handling for steam

* forward header too

* fix lint

* fix flake
This commit is contained in:
Pascal Vizeli
2019-04-01 14:16:16 +02:00
committed by GitHub
parent 42e3e878df
commit 6829ecad9d
8 changed files with 458 additions and 88 deletions

View File

@@ -3,10 +3,11 @@ import asyncio
import logging
import os
import re
from typing import Dict, Union
import aiohttp
from aiohttp import web
from aiohttp.hdrs import CONTENT_TYPE
from aiohttp.hdrs import CONTENT_TYPE, CONTENT_LENGTH
from aiohttp.web_exceptions import HTTPBadGateway
import async_timeout
@@ -20,7 +21,8 @@ _LOGGER = logging.getLogger(__name__)
NO_TIMEOUT = re.compile(
r'^(?:'
r'|homeassistant/update'
r'|host/update'
r'|hassos/update'
r'|hassos/update/cli'
r'|supervisor/update'
r'|addons/[^/]+/(?:update|install|rebuild)'
r'|snapshots/.+/full'
@@ -44,25 +46,26 @@ class HassIOView(HomeAssistantView):
url = "/api/hassio/{path:.+}"
requires_auth = False
def __init__(self, host, websession):
def __init__(self, host: str, websession: aiohttp.ClientSession):
"""Initialize a Hass.io base view."""
self._host = host
self._websession = websession
async def _handle(self, request, path):
async def _handle(
self, request: web.Request, path: str
) -> Union[web.Response, web.StreamResponse]:
"""Route data to Hass.io."""
if _need_auth(path) and not request[KEY_AUTHENTICATED]:
return web.Response(status=401)
client = await self._command_proxy(path, request)
data = await client.read()
return _create_response(client, data)
return await self._command_proxy(path, request)
get = _handle
post = _handle
async def _command_proxy(self, path, request):
async def _command_proxy(
self, path: str, request: web.Request
) -> Union[web.Response, web.StreamResponse]:
"""Return a client request with proxy origin for Hass.io supervisor.
This method is a coroutine.
@@ -71,29 +74,38 @@ class HassIOView(HomeAssistantView):
hass = request.app['hass']
data = None
headers = {
X_HASSIO: os.environ.get('HASSIO_TOKEN', ""),
}
user = request.get('hass_user')
if user is not None:
headers[X_HASS_USER_ID] = request['hass_user'].id
headers[X_HASS_IS_ADMIN] = str(int(request['hass_user'].is_admin))
headers = _init_header(request)
try:
with async_timeout.timeout(10, loop=hass.loop):
data = await request.read()
if data:
headers[CONTENT_TYPE] = request.content_type
else:
data = None
method = getattr(self._websession, request.method.lower())
client = await method(
"http://{}/{}".format(self._host, path), data=data,
headers=headers, timeout=read_timeout
)
print(client.headers)
return client
# Simple request
if int(client.headers.get(CONTENT_LENGTH, 0)) < 4194000:
# Return Response
body = await client.read()
return web.Response(
content_type=client.content_type,
status=client.status,
body=body,
)
# Stream response
response = web.StreamResponse(status=client.status)
response.content_type = client.content_type
await response.prepare(request)
async for data in client.content.iter_chunked(4096):
await response.write(data)
return response
except aiohttp.ClientError as err:
_LOGGER.error("Client error on api %s request %s", path, err)
@@ -104,23 +116,30 @@ class HassIOView(HomeAssistantView):
raise HTTPBadGateway()
def _create_response(client, data):
"""Convert a response from client request."""
return web.Response(
body=data,
status=client.status,
content_type=client.content_type,
)
def _init_header(request: web.Request) -> Dict[str, str]:
"""Create initial header."""
headers = {
X_HASSIO: os.environ.get('HASSIO_TOKEN', ""),
CONTENT_TYPE: request.content_type,
}
# Add user data
user = request.get('hass_user')
if user is not None:
headers[X_HASS_USER_ID] = request['hass_user'].id
headers[X_HASS_IS_ADMIN] = str(int(request['hass_user'].is_admin))
return headers
def _get_timeout(path):
def _get_timeout(path: str) -> int:
"""Return timeout for a URL path."""
if NO_TIMEOUT.match(path):
return 0
return 300
def _need_auth(path):
def _need_auth(path: str) -> bool:
"""Return if a path need authentication."""
if NO_AUTH.match(path):
return False