2019-02-14 15:01:46 +00:00
|
|
|
"""Support to serve the Home Assistant API as WSGI application."""
|
2021-03-18 08:25:40 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2020-08-09 12:07:31 +00:00
|
|
|
from contextvars import ContextVar
|
2017-11-04 19:04:05 +00:00
|
|
|
from ipaddress import ip_network
|
2016-11-25 21:04:06 +00:00
|
|
|
import logging
|
2017-11-04 19:04:05 +00:00
|
|
|
import os
|
2016-11-25 21:04:06 +00:00
|
|
|
import ssl
|
2021-05-10 21:30:47 +00:00
|
|
|
from typing import Any, Final, Optional, TypedDict, cast
|
2016-11-25 21:04:06 +00:00
|
|
|
|
|
|
|
from aiohttp import web
|
2021-05-10 21:30:47 +00:00
|
|
|
from aiohttp.typedefs import StrOrURL
|
|
|
|
from aiohttp.web_exceptions import HTTPMovedPermanently, HTTPRedirection
|
2017-11-04 19:04:05 +00:00
|
|
|
import voluptuous as vol
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2021-04-13 21:10:58 +00:00
|
|
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, SERVER_PORT
|
2020-06-02 18:54:11 +00:00
|
|
|
from homeassistant.core import Event, HomeAssistant
|
2020-01-14 21:03:02 +00:00
|
|
|
from homeassistant.helpers import storage
|
2016-11-25 21:04:06 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
2021-05-10 21:30:47 +00:00
|
|
|
from homeassistant.helpers.typing import ConfigType
|
2020-01-14 21:03:02 +00:00
|
|
|
from homeassistant.loader import bind_hass
|
2021-04-13 21:10:58 +00:00
|
|
|
from homeassistant.setup import async_start_setup, async_when_setup_or_start
|
2016-12-18 20:56:07 +00:00
|
|
|
import homeassistant.util as hass_util
|
2018-07-16 08:32:07 +00:00
|
|
|
from homeassistant.util import ssl as ssl_util
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2018-02-15 21:06:14 +00:00
|
|
|
from .auth import setup_auth
|
|
|
|
from .ban import setup_bans
|
2020-08-11 20:57:50 +00:00
|
|
|
from .const import KEY_AUTHENTICATED, KEY_HASS, KEY_HASS_USER # noqa: F401
|
2018-02-15 21:06:14 +00:00
|
|
|
from .cors import setup_cors
|
2020-08-11 20:57:50 +00:00
|
|
|
from .forwarded import async_setup_forwarded
|
2020-08-09 12:07:31 +00:00
|
|
|
from .request_context import setup_request_context
|
2021-01-14 19:02:01 +00:00
|
|
|
from .security_filter import setup_security_filter
|
2019-02-15 17:31:54 +00:00
|
|
|
from .static import CACHE_HEADERS, CachingStaticResource
|
2021-05-10 21:30:47 +00:00
|
|
|
from .view import HomeAssistantView
|
2020-08-04 13:34:23 +00:00
|
|
|
from .web_runner import HomeAssistantTCPSite
|
2018-03-09 01:51:49 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
DOMAIN: Final = "http"
|
2019-08-12 03:38:18 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
CONF_SERVER_HOST: Final = "server_host"
|
|
|
|
CONF_SERVER_PORT: Final = "server_port"
|
|
|
|
CONF_BASE_URL: Final = "base_url"
|
|
|
|
CONF_SSL_CERTIFICATE: Final = "ssl_certificate"
|
|
|
|
CONF_SSL_PEER_CERTIFICATE: Final = "ssl_peer_certificate"
|
|
|
|
CONF_SSL_KEY: Final = "ssl_key"
|
|
|
|
CONF_CORS_ORIGINS: Final = "cors_allowed_origins"
|
|
|
|
CONF_USE_X_FORWARDED_FOR: Final = "use_x_forwarded_for"
|
|
|
|
CONF_TRUSTED_PROXIES: Final = "trusted_proxies"
|
|
|
|
CONF_LOGIN_ATTEMPTS_THRESHOLD: Final = "login_attempts_threshold"
|
|
|
|
CONF_IP_BAN_ENABLED: Final = "ip_ban_enabled"
|
|
|
|
CONF_SSL_PROFILE: Final = "ssl_profile"
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
SSL_MODERN: Final = "modern"
|
|
|
|
SSL_INTERMEDIATE: Final = "intermediate"
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
_LOGGER: Final = logging.getLogger(__name__)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
DEFAULT_DEVELOPMENT: Final = "0"
|
2021-02-08 13:44:46 +00:00
|
|
|
# Cast to be able to load custom cards.
|
|
|
|
# My to be able to check url and version info.
|
2021-05-10 21:30:47 +00:00
|
|
|
DEFAULT_CORS: Final[list[str]] = ["https://cast.home-assistant.io"]
|
|
|
|
NO_LOGIN_ATTEMPT_THRESHOLD: Final = -1
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
MAX_CLIENT_SIZE: Final = 1024 ** 2 * 16
|
2020-01-09 10:09:34 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
STORAGE_KEY: Final = DOMAIN
|
|
|
|
STORAGE_VERSION: Final = 1
|
|
|
|
SAVE_DELAY: Final = 180
|
2019-02-26 22:42:48 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
HTTP_SCHEMA: Final = vol.All(
|
2020-05-08 00:29:47 +00:00
|
|
|
cv.deprecated(CONF_BASE_URL),
|
|
|
|
vol.Schema(
|
|
|
|
{
|
2020-08-04 13:34:23 +00:00
|
|
|
vol.Optional(CONF_SERVER_HOST): vol.All(
|
|
|
|
cv.ensure_list, vol.Length(min=1), [cv.string]
|
|
|
|
),
|
2020-05-08 00:29:47 +00:00
|
|
|
vol.Optional(CONF_SERVER_PORT, default=SERVER_PORT): cv.port,
|
|
|
|
vol.Optional(CONF_BASE_URL): cv.string,
|
|
|
|
vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile,
|
|
|
|
vol.Optional(CONF_SSL_PEER_CERTIFICATE): cv.isfile,
|
|
|
|
vol.Optional(CONF_SSL_KEY): cv.isfile,
|
2021-02-08 13:44:46 +00:00
|
|
|
vol.Optional(CONF_CORS_ORIGINS, default=DEFAULT_CORS): vol.All(
|
2020-05-08 00:29:47 +00:00
|
|
|
cv.ensure_list, [cv.string]
|
|
|
|
),
|
|
|
|
vol.Inclusive(CONF_USE_X_FORWARDED_FOR, "proxy"): cv.boolean,
|
|
|
|
vol.Inclusive(CONF_TRUSTED_PROXIES, "proxy"): vol.All(
|
|
|
|
cv.ensure_list, [ip_network]
|
|
|
|
),
|
|
|
|
vol.Optional(
|
|
|
|
CONF_LOGIN_ATTEMPTS_THRESHOLD, default=NO_LOGIN_ATTEMPT_THRESHOLD
|
|
|
|
): vol.Any(cv.positive_int, NO_LOGIN_ATTEMPT_THRESHOLD),
|
|
|
|
vol.Optional(CONF_IP_BAN_ENABLED, default=True): cv.boolean,
|
|
|
|
vol.Optional(CONF_SSL_PROFILE, default=SSL_MODERN): vol.In(
|
|
|
|
[SSL_INTERMEDIATE, SSL_MODERN]
|
|
|
|
),
|
|
|
|
}
|
|
|
|
),
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
CONFIG_SCHEMA: Final = vol.Schema({DOMAIN: HTTP_SCHEMA}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
|
|
|
|
|
|
|
class ConfData(TypedDict, total=False):
|
|
|
|
"""Typed dict for config data."""
|
|
|
|
|
|
|
|
server_host: list[str]
|
|
|
|
server_port: int
|
|
|
|
base_url: str
|
|
|
|
ssl_certificate: str
|
|
|
|
ssl_peer_certificate: str
|
|
|
|
ssl_key: str
|
|
|
|
cors_allowed_origins: list[str]
|
|
|
|
use_x_forwarded_for: bool
|
|
|
|
trusted_proxies: list[str]
|
|
|
|
login_attempts_threshold: int
|
|
|
|
ip_ban_enabled: bool
|
|
|
|
ssl_profile: str
|
2016-11-25 21:04:06 +00:00
|
|
|
|
|
|
|
|
2020-01-14 21:03:02 +00:00
|
|
|
@bind_hass
|
2021-03-18 08:25:40 +00:00
|
|
|
async def async_get_last_config(hass: HomeAssistant) -> dict | None:
|
2020-01-14 21:03:02 +00:00
|
|
|
"""Return the last known working config."""
|
|
|
|
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
|
|
|
|
return cast(Optional[dict], await store.async_load())
|
|
|
|
|
|
|
|
|
2018-08-13 07:26:20 +00:00
|
|
|
class ApiConfig:
|
|
|
|
"""Configuration settings for API server."""
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def __init__(
|
2020-05-08 00:29:47 +00:00
|
|
|
self,
|
|
|
|
local_ip: str,
|
|
|
|
host: str,
|
2021-05-10 21:30:47 +00:00
|
|
|
port: int,
|
|
|
|
use_ssl: bool,
|
2019-07-31 19:25:30 +00:00
|
|
|
) -> None:
|
2018-08-13 07:26:20 +00:00
|
|
|
"""Initialize a new API config object."""
|
2020-05-08 00:29:47 +00:00
|
|
|
self.local_ip = local_ip
|
2018-08-13 07:26:20 +00:00
|
|
|
self.host = host
|
|
|
|
self.port = port
|
2019-10-13 21:16:27 +00:00
|
|
|
self.use_ssl = use_ssl
|
2018-08-13 07:26:20 +00:00
|
|
|
|
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
2016-11-25 21:04:06 +00:00
|
|
|
"""Set up the HTTP API and debug interface."""
|
2021-05-10 21:30:47 +00:00
|
|
|
conf: ConfData | None = config.get(DOMAIN)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
|
|
|
if conf is None:
|
2021-05-10 21:30:47 +00:00
|
|
|
conf = cast(ConfData, HTTP_SCHEMA({}))
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2020-08-04 13:34:23 +00:00
|
|
|
server_host = conf.get(CONF_SERVER_HOST)
|
2016-11-25 21:04:06 +00:00
|
|
|
server_port = conf[CONF_SERVER_PORT]
|
2018-02-17 09:29:14 +00:00
|
|
|
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
|
2018-06-26 15:44:08 +00:00
|
|
|
ssl_peer_certificate = conf.get(CONF_SSL_PEER_CERTIFICATE)
|
2018-02-17 09:29:14 +00:00
|
|
|
ssl_key = conf.get(CONF_SSL_KEY)
|
2016-11-25 21:04:06 +00:00
|
|
|
cors_origins = conf[CONF_CORS_ORIGINS]
|
2018-08-03 11:52:34 +00:00
|
|
|
use_x_forwarded_for = conf.get(CONF_USE_X_FORWARDED_FOR, False)
|
2021-05-10 21:30:47 +00:00
|
|
|
trusted_proxies = conf.get(CONF_TRUSTED_PROXIES) or []
|
2016-11-25 21:04:06 +00:00
|
|
|
is_ban_enabled = conf[CONF_IP_BAN_ENABLED]
|
|
|
|
login_threshold = conf[CONF_LOGIN_ATTEMPTS_THRESHOLD]
|
2018-08-14 06:20:17 +00:00
|
|
|
ssl_profile = conf[CONF_SSL_PROFILE]
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2018-02-15 21:06:14 +00:00
|
|
|
server = HomeAssistantHTTP(
|
2016-11-25 21:04:06 +00:00
|
|
|
hass,
|
|
|
|
server_host=server_host,
|
|
|
|
server_port=server_port,
|
|
|
|
ssl_certificate=ssl_certificate,
|
2018-06-26 15:44:08 +00:00
|
|
|
ssl_peer_certificate=ssl_peer_certificate,
|
2016-11-25 21:04:06 +00:00
|
|
|
ssl_key=ssl_key,
|
|
|
|
cors_origins=cors_origins,
|
|
|
|
use_x_forwarded_for=use_x_forwarded_for,
|
2018-06-29 20:27:06 +00:00
|
|
|
trusted_proxies=trusted_proxies,
|
2016-11-25 21:04:06 +00:00
|
|
|
login_threshold=login_threshold,
|
2018-08-14 06:20:17 +00:00
|
|
|
is_ban_enabled=is_ban_enabled,
|
|
|
|
ssl_profile=ssl_profile,
|
2016-11-25 21:04:06 +00:00
|
|
|
)
|
|
|
|
|
2020-06-02 18:54:11 +00:00
|
|
|
async def stop_server(event: Event) -> None:
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Stop the server."""
|
2018-03-09 01:51:49 +00:00
|
|
|
await server.stop()
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2021-04-13 21:10:58 +00:00
|
|
|
async def start_server(*_: Any) -> None:
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Start the server."""
|
2021-04-13 21:10:58 +00:00
|
|
|
with async_start_setup(hass, ["http"]):
|
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server)
|
2021-05-10 21:30:47 +00:00
|
|
|
# We already checked it's not None.
|
|
|
|
assert conf is not None
|
2021-04-13 21:10:58 +00:00
|
|
|
await start_http_server_and_save_config(hass, dict(conf), server)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2021-04-13 21:10:58 +00:00
|
|
|
async_when_setup_or_start(hass, "frontend", start_server)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
|
|
|
hass.http = server
|
2016-12-18 20:56:07 +00:00
|
|
|
|
2020-05-08 00:29:47 +00:00
|
|
|
local_ip = await hass.async_add_executor_job(hass_util.get_local_ip)
|
2016-12-18 20:56:07 +00:00
|
|
|
|
2021-02-10 13:31:11 +00:00
|
|
|
host = local_ip
|
|
|
|
if server_host is not None:
|
2020-08-04 13:34:23 +00:00
|
|
|
# Assume the first server host name provided as API host
|
|
|
|
host = server_host[0]
|
2016-12-18 20:56:07 +00:00
|
|
|
|
2021-02-10 13:31:11 +00:00
|
|
|
hass.config.api = ApiConfig(
|
|
|
|
local_ip, host, server_port, ssl_certificate is not None
|
|
|
|
)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class HomeAssistantHTTP:
|
2018-02-15 21:06:14 +00:00
|
|
|
"""HTTP server for Home Assistant."""
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
2021-05-10 21:30:47 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
ssl_certificate: str | None,
|
|
|
|
ssl_peer_certificate: str | None,
|
|
|
|
ssl_key: str | None,
|
|
|
|
server_host: list[str] | None,
|
|
|
|
server_port: int,
|
|
|
|
cors_origins: list[str],
|
|
|
|
use_x_forwarded_for: bool,
|
|
|
|
trusted_proxies: list[str],
|
|
|
|
login_threshold: int,
|
|
|
|
is_ban_enabled: bool,
|
|
|
|
ssl_profile: str,
|
|
|
|
) -> None:
|
2018-02-15 21:06:14 +00:00
|
|
|
"""Initialize the HTTP Home Assistant server."""
|
2020-01-09 10:09:34 +00:00
|
|
|
app = self.app = web.Application(
|
|
|
|
middlewares=[], client_max_size=MAX_CLIENT_SIZE
|
|
|
|
)
|
2019-03-11 02:55:36 +00:00
|
|
|
app[KEY_HASS] = hass
|
2018-02-15 21:06:14 +00:00
|
|
|
|
2021-01-14 19:02:01 +00:00
|
|
|
# Order matters, security filters middle ware needs to go first,
|
|
|
|
# forwarded middleware needs to go second.
|
|
|
|
setup_security_filter(app)
|
|
|
|
|
2020-08-11 20:57:50 +00:00
|
|
|
# Only register middleware if `use_x_forwarded_for` is enabled
|
|
|
|
# and trusted proxies are provided
|
|
|
|
if use_x_forwarded_for and trusted_proxies:
|
|
|
|
async_setup_forwarded(app, trusted_proxies)
|
|
|
|
|
2020-08-09 12:07:31 +00:00
|
|
|
setup_request_context(app, current_request)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
|
|
|
if is_ban_enabled:
|
2018-02-15 21:06:14 +00:00
|
|
|
setup_bans(hass, app, login_threshold)
|
|
|
|
|
2019-03-11 02:55:36 +00:00
|
|
|
setup_auth(hass, app)
|
2018-02-15 21:06:14 +00:00
|
|
|
|
2018-07-19 06:37:00 +00:00
|
|
|
setup_cors(app, cors_origins)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
|
|
|
self.hass = hass
|
|
|
|
self.ssl_certificate = ssl_certificate
|
2018-06-26 15:44:08 +00:00
|
|
|
self.ssl_peer_certificate = ssl_peer_certificate
|
2016-11-25 21:04:06 +00:00
|
|
|
self.ssl_key = ssl_key
|
|
|
|
self.server_host = server_host
|
|
|
|
self.server_port = server_port
|
2021-06-01 10:51:44 +00:00
|
|
|
self.use_x_forwarded_for = use_x_forwarded_for
|
2019-06-08 06:08:55 +00:00
|
|
|
self.trusted_proxies = trusted_proxies
|
2018-02-15 21:06:14 +00:00
|
|
|
self.is_ban_enabled = is_ban_enabled
|
2018-08-14 06:20:17 +00:00
|
|
|
self.ssl_profile = ssl_profile
|
2016-11-25 21:04:06 +00:00
|
|
|
self._handler = None
|
2021-05-10 21:30:47 +00:00
|
|
|
self.runner: web.AppRunner | None = None
|
|
|
|
self.site: HomeAssistantTCPSite | None = None
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
def register_view(self, view: HomeAssistantView) -> None:
|
2016-11-25 21:04:06 +00:00
|
|
|
"""Register a view with the WSGI server.
|
|
|
|
|
|
|
|
The view argument must be a class that inherits from HomeAssistantView.
|
|
|
|
It is optional to instantiate it before registering; this method will
|
|
|
|
handle it either way.
|
|
|
|
"""
|
|
|
|
if isinstance(view, type):
|
|
|
|
# Instantiate the view, if needed
|
|
|
|
view = view()
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if not hasattr(view, "url"):
|
2016-11-25 21:04:06 +00:00
|
|
|
class_name = view.__class__.__name__
|
2019-08-23 16:53:33 +00:00
|
|
|
raise AttributeError(f'{class_name} missing required attribute "url"')
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if not hasattr(view, "name"):
|
2016-11-25 21:04:06 +00:00
|
|
|
class_name = view.__class__.__name__
|
2019-08-23 16:53:33 +00:00
|
|
|
raise AttributeError(f'{class_name} missing required attribute "name"')
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2018-07-19 06:37:00 +00:00
|
|
|
view.register(self.app, self.app.router)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
def register_redirect(
|
|
|
|
self,
|
|
|
|
url: str,
|
|
|
|
redirect_to: StrOrURL,
|
|
|
|
*,
|
|
|
|
redirect_exc: type[HTTPRedirection] = HTTPMovedPermanently,
|
|
|
|
) -> None:
|
2016-11-25 21:04:06 +00:00
|
|
|
"""Register a redirect with the server.
|
|
|
|
|
|
|
|
If given this must be either a string or callable. In case of a
|
|
|
|
callable it's called with the url adapter that triggered the match and
|
|
|
|
the values of the URL as keyword arguments and has to return the target
|
|
|
|
for the redirect, otherwise it has to be a string with placeholders in
|
|
|
|
rule syntax.
|
|
|
|
"""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
async def redirect(request: web.Request) -> web.StreamResponse:
|
2016-11-25 21:04:06 +00:00
|
|
|
"""Redirect to location."""
|
2021-05-10 21:30:47 +00:00
|
|
|
# Should be instance of aiohttp.web_exceptions._HTTPMove.
|
|
|
|
raise redirect_exc(redirect_to) # type: ignore[arg-type,misc]
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
self.app.router.add_route("GET", url, redirect)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
def register_static_path(
|
|
|
|
self, url_path: str, path: str, cache_headers: bool = True
|
|
|
|
) -> web.FileResponse | None:
|
2017-03-30 07:50:53 +00:00
|
|
|
"""Register a folder or file to serve as a static path."""
|
2016-11-25 21:04:06 +00:00
|
|
|
if os.path.isdir(path):
|
2017-03-30 07:50:53 +00:00
|
|
|
if cache_headers:
|
2021-05-10 21:30:47 +00:00
|
|
|
resource: type[
|
|
|
|
CachingStaticResource | web.StaticResource
|
|
|
|
] = CachingStaticResource
|
2017-03-30 07:50:53 +00:00
|
|
|
else:
|
|
|
|
resource = web.StaticResource
|
|
|
|
self.app.router.register_resource(resource(url_path, path))
|
2021-05-10 21:30:47 +00:00
|
|
|
return None
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
async def serve_file(request: web.Request) -> web.FileResponse:
|
|
|
|
"""Serve file from disk."""
|
|
|
|
if cache_headers:
|
2019-02-15 17:31:54 +00:00
|
|
|
return web.FileResponse(path, headers=CACHE_HEADERS)
|
2021-05-10 21:30:47 +00:00
|
|
|
return web.FileResponse(path)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
self.app.router.add_route("GET", url_path, serve_file)
|
2021-05-10 21:30:47 +00:00
|
|
|
return None
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
async def start(self) -> None:
|
2018-08-20 12:03:35 +00:00
|
|
|
"""Start the aiohttp server."""
|
2021-05-10 21:30:47 +00:00
|
|
|
context: ssl.SSLContext | None
|
2016-11-25 21:04:06 +00:00
|
|
|
if self.ssl_certificate:
|
2016-12-13 06:02:24 +00:00
|
|
|
try:
|
2018-08-14 06:20:17 +00:00
|
|
|
if self.ssl_profile == SSL_INTERMEDIATE:
|
|
|
|
context = ssl_util.server_context_intermediate()
|
|
|
|
else:
|
|
|
|
context = ssl_util.server_context_modern()
|
2018-08-20 12:03:35 +00:00
|
|
|
await self.hass.async_add_executor_job(
|
2019-07-31 19:25:30 +00:00
|
|
|
context.load_cert_chain, self.ssl_certificate, self.ssl_key
|
|
|
|
)
|
2016-12-13 06:02:24 +00:00
|
|
|
except OSError as error:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error(
|
|
|
|
"Could not read SSL certificate from %s: %s",
|
|
|
|
self.ssl_certificate,
|
|
|
|
error,
|
|
|
|
)
|
2016-12-13 06:02:24 +00:00
|
|
|
return
|
2018-06-26 15:44:08 +00:00
|
|
|
|
|
|
|
if self.ssl_peer_certificate:
|
|
|
|
context.verify_mode = ssl.CERT_REQUIRED
|
2018-08-20 12:03:35 +00:00
|
|
|
await self.hass.async_add_executor_job(
|
2019-07-31 19:25:30 +00:00
|
|
|
context.load_verify_locations, self.ssl_peer_certificate
|
|
|
|
)
|
2018-06-26 15:44:08 +00:00
|
|
|
|
2016-11-25 21:04:06 +00:00
|
|
|
else:
|
|
|
|
context = None
|
|
|
|
|
2016-11-27 22:01:12 +00:00
|
|
|
# Aiohttp freezes apps after start so that no changes can be made.
|
|
|
|
# However in Home Assistant components can be discovered after boot.
|
|
|
|
# This will now raise a RunTimeError.
|
2018-03-05 21:28:41 +00:00
|
|
|
# To work around this we now prevent the router from getting frozen
|
2018-11-21 19:55:21 +00:00
|
|
|
# pylint: disable=protected-access
|
2021-05-10 21:30:47 +00:00
|
|
|
self.app._router.freeze = lambda: None # type: ignore[assignment]
|
2016-11-27 22:01:12 +00:00
|
|
|
|
2018-08-20 12:03:35 +00:00
|
|
|
self.runner = web.AppRunner(self.app)
|
|
|
|
await self.runner.setup()
|
2020-08-04 13:34:23 +00:00
|
|
|
|
|
|
|
self.site = HomeAssistantTCPSite(
|
2019-07-31 19:25:30 +00:00
|
|
|
self.runner, self.server_host, self.server_port, ssl_context=context
|
|
|
|
)
|
2016-12-13 06:02:24 +00:00
|
|
|
try:
|
2018-08-20 12:03:35 +00:00
|
|
|
await self.site.start()
|
2016-12-13 06:02:24 +00:00
|
|
|
except OSError as error:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error(
|
|
|
|
"Failed to create HTTP server at port %d: %s", self.server_port, error
|
|
|
|
)
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2020-11-13 13:39:50 +00:00
|
|
|
_LOGGER.info("Now listening on port %d", self.server_port)
|
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
async def stop(self) -> None:
|
2018-08-20 12:03:35 +00:00
|
|
|
"""Stop the aiohttp server."""
|
2021-05-10 21:30:47 +00:00
|
|
|
if self.site is not None:
|
|
|
|
await self.site.stop()
|
|
|
|
if self.runner is not None:
|
|
|
|
await self.runner.cleanup()
|
2020-06-02 18:54:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def start_http_server_and_save_config(
|
2021-03-18 08:25:40 +00:00
|
|
|
hass: HomeAssistant, conf: dict, server: HomeAssistantHTTP
|
2020-06-02 18:54:11 +00:00
|
|
|
) -> None:
|
|
|
|
"""Startup the http server and save the config."""
|
2021-05-10 21:30:47 +00:00
|
|
|
await server.start()
|
2020-06-02 18:54:11 +00:00
|
|
|
|
|
|
|
# If we are set up successful, we store the HTTP settings for safe mode.
|
|
|
|
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
|
|
|
|
|
|
|
|
if CONF_TRUSTED_PROXIES in conf:
|
|
|
|
conf[CONF_TRUSTED_PROXIES] = [
|
|
|
|
str(ip.network_address) for ip in conf[CONF_TRUSTED_PROXIES]
|
|
|
|
]
|
|
|
|
|
2021-05-03 05:48:49 +00:00
|
|
|
store.async_delay_save(lambda: conf, SAVE_DELAY)
|
2020-08-09 12:07:31 +00:00
|
|
|
|
|
|
|
|
2021-03-18 08:25:40 +00:00
|
|
|
current_request: ContextVar[web.Request | None] = ContextVar(
|
2020-08-09 12:07:31 +00:00
|
|
|
"current_request", default=None
|
|
|
|
)
|