core/homeassistant/components/http/view.py

154 lines
4.4 KiB
Python
Raw Normal View History

"""Support for views."""
import asyncio
import json
import logging
from typing import List, Optional
from aiohttp import web
from aiohttp.web_exceptions import (
2019-07-31 19:25:30 +00:00
HTTPBadRequest,
HTTPInternalServerError,
HTTPUnauthorized,
)
import voluptuous as vol
from homeassistant import exceptions
from homeassistant.const import CONTENT_TYPE_JSON, HTTP_OK
from homeassistant.core import Context, is_callback
from homeassistant.helpers.json import JSONEncoder
from .const import KEY_AUTHENTICATED, KEY_HASS, KEY_REAL_IP
_LOGGER = logging.getLogger(__name__)
# mypy: allow-untyped-defs, no-check-untyped-defs
class HomeAssistantView:
"""Base view for all views."""
url: Optional[str] = None
extra_urls: List[str] = []
# Views inheriting from this class can override this
requires_auth = True
cors_allowed = False
@staticmethod
def context(request):
"""Generate a context from a request."""
2019-07-31 19:25:30 +00:00
user = request.get("hass_user")
if user is None:
return Context()
return Context(user_id=user.id)
@staticmethod
def json(result, status_code=HTTP_OK, headers=None):
"""Return a JSON response."""
try:
msg = json.dumps(
2018-11-28 12:25:23 +00:00
result, sort_keys=True, cls=JSONEncoder, allow_nan=False
2019-07-31 19:25:30 +00:00
).encode("UTF-8")
2018-11-28 12:25:23 +00:00
except (ValueError, TypeError) as err:
2019-07-31 19:25:30 +00:00
_LOGGER.error("Unable to serialize to JSON: %s\n%s", err, result)
raise HTTPInternalServerError
response = web.Response(
2019-07-31 19:25:30 +00:00
body=msg,
content_type=CONTENT_TYPE_JSON,
status=status_code,
headers=headers,
)
response.enable_compression()
return response
def json_message(
self, message, status_code=HTTP_OK, message_code=None, headers=None
):
"""Return a JSON message response."""
2019-07-31 19:25:30 +00:00
data = {"message": message}
if message_code is not None:
2019-07-31 19:25:30 +00:00
data["code"] = message_code
return self.json(data, status_code, headers=headers)
def register(self, app, router):
"""Register the view with a router."""
2019-07-31 19:25:30 +00:00
assert self.url is not None, "No url set for view"
urls = [self.url] + self.extra_urls
routes = []
2019-07-31 19:25:30 +00:00
for method in ("get", "post", "delete", "put", "patch", "head", "options"):
handler = getattr(self, method, None)
if not handler:
continue
handler = request_handler_factory(self, handler)
for url in urls:
routes.append(router.add_route(method, url, handler))
if not self.cors_allowed:
return
for route in routes:
2019-07-31 19:25:30 +00:00
app["allow_cors"](route)
def request_handler_factory(view, handler):
"""Wrap the handler classes."""
2019-07-31 19:25:30 +00:00
assert asyncio.iscoroutinefunction(handler) or is_callback(
handler
), "Handler should be a coroutine or a callback."
async def handle(request):
"""Handle incoming request."""
if not request.app[KEY_HASS].is_running:
return web.Response(status=503)
authenticated = request.get(KEY_AUTHENTICATED, False)
if view.requires_auth and not authenticated:
raise HTTPUnauthorized()
2019-07-31 19:25:30 +00:00
_LOGGER.debug(
"Serving %s to %s (auth: %s)",
request.path,
request.get(KEY_REAL_IP),
authenticated,
)
try:
result = handler(request, **request.match_info)
if asyncio.iscoroutine(result):
result = await result
except vol.Invalid:
raise HTTPBadRequest()
except exceptions.ServiceNotFound:
raise HTTPInternalServerError()
except exceptions.Unauthorized:
raise HTTPUnauthorized()
if isinstance(result, web.StreamResponse):
# The method handler returned a ready-made Response, how nice of it
return result
status_code = HTTP_OK
if isinstance(result, tuple):
result, status_code = result
if isinstance(result, str):
2019-07-31 19:25:30 +00:00
result = result.encode("utf-8")
elif result is None:
2019-07-31 19:25:30 +00:00
result = b""
elif not isinstance(result, bytes):
assert (
False
), f"Result should be None, string, bytes or Response. Got: {result}"
return web.Response(body=result, status=status_code)
return handle