Add CORS (Cross Origin Resource Sharing) support to HTTP
parent
16adc30210
commit
ab294d12f7
|
@ -16,18 +16,23 @@ from http import cookies
|
||||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||||
from socketserver import ThreadingMixIn
|
from socketserver import ThreadingMixIn
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.bootstrap as bootstrap
|
import homeassistant.bootstrap as bootstrap
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.remote as rem
|
import homeassistant.remote as rem
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.util.dt as date_util
|
import homeassistant.util.dt as date_util
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN, HTTP_HEADER_ACCEPT_ENCODING,
|
CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN, HTTP_HEADER_ACCEPT_ENCODING,
|
||||||
HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_CONTENT_ENCODING,
|
HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_CONTENT_ENCODING,
|
||||||
HTTP_HEADER_CONTENT_LENGTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_EXPIRES,
|
HTTP_HEADER_CONTENT_LENGTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_EXPIRES,
|
||||||
HTTP_HEADER_HA_AUTH, HTTP_HEADER_VARY, HTTP_METHOD_NOT_ALLOWED,
|
HTTP_HEADER_HA_AUTH, HTTP_HEADER_VARY,
|
||||||
|
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
|
HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, HTTP_METHOD_NOT_ALLOWED,
|
||||||
HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAUTHORIZED, HTTP_UNPROCESSABLE_ENTITY,
|
HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAUTHORIZED, HTTP_UNPROCESSABLE_ENTITY,
|
||||||
|
ALLOWED_CORS_HEADERS,
|
||||||
SERVER_PORT, URL_ROOT, URL_API_EVENT_FORWARD)
|
SERVER_PORT, URL_ROOT, URL_API_EVENT_FORWARD)
|
||||||
|
|
||||||
DOMAIN = "http"
|
DOMAIN = "http"
|
||||||
|
@ -38,6 +43,7 @@ CONF_SERVER_PORT = "server_port"
|
||||||
CONF_DEVELOPMENT = "development"
|
CONF_DEVELOPMENT = "development"
|
||||||
CONF_SSL_CERTIFICATE = 'ssl_certificate'
|
CONF_SSL_CERTIFICATE = 'ssl_certificate'
|
||||||
CONF_SSL_KEY = 'ssl_key'
|
CONF_SSL_KEY = 'ssl_key'
|
||||||
|
CONF_CORS_ORIGINS = 'cors_allowed_origins'
|
||||||
|
|
||||||
DATA_API_PASSWORD = 'api_password'
|
DATA_API_PASSWORD = 'api_password'
|
||||||
|
|
||||||
|
@ -48,6 +54,19 @@ SESSION_KEY = 'sessionId'
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
DOMAIN: vol.Schema({
|
||||||
|
vol.Optional(CONF_API_PASSWORD): cv.string,
|
||||||
|
vol.Optional(CONF_SERVER_HOST): cv.string,
|
||||||
|
vol.Optional(CONF_SERVER_PORT, default=SERVER_PORT):
|
||||||
|
vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)),
|
||||||
|
vol.Optional(CONF_DEVELOPMENT): cv.string,
|
||||||
|
vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile,
|
||||||
|
vol.Optional(CONF_SSL_KEY): cv.isfile,
|
||||||
|
vol.Optional(CONF_CORS_ORIGINS): cv.ensure_list
|
||||||
|
}),
|
||||||
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Set up the HTTP API and debug interface."""
|
"""Set up the HTTP API and debug interface."""
|
||||||
|
@ -61,11 +80,12 @@ def setup(hass, config):
|
||||||
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
|
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
|
||||||
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
|
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
|
||||||
ssl_key = conf.get(CONF_SSL_KEY)
|
ssl_key = conf.get(CONF_SSL_KEY)
|
||||||
|
cors_origins = conf.get(CONF_CORS_ORIGINS, [])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server = HomeAssistantHTTPServer(
|
server = HomeAssistantHTTPServer(
|
||||||
(server_host, server_port), RequestHandler, hass, api_password,
|
(server_host, server_port), RequestHandler, hass, api_password,
|
||||||
development, ssl_certificate, ssl_key)
|
development, ssl_certificate, ssl_key, cors_origins)
|
||||||
except OSError:
|
except OSError:
|
||||||
# If address already in use
|
# If address already in use
|
||||||
_LOGGER.exception("Error setting up HTTP server")
|
_LOGGER.exception("Error setting up HTTP server")
|
||||||
|
@ -96,7 +116,8 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def __init__(self, server_address, request_handler_class,
|
def __init__(self, server_address, request_handler_class,
|
||||||
hass, api_password, development, ssl_certificate, ssl_key):
|
hass, api_password, development, ssl_certificate, ssl_key,
|
||||||
|
cors_origins):
|
||||||
"""Initialize the server."""
|
"""Initialize the server."""
|
||||||
super().__init__(server_address, request_handler_class)
|
super().__init__(server_address, request_handler_class)
|
||||||
|
|
||||||
|
@ -107,6 +128,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
||||||
self.paths = []
|
self.paths = []
|
||||||
self.sessions = SessionStore()
|
self.sessions = SessionStore()
|
||||||
self.use_ssl = ssl_certificate is not None
|
self.use_ssl = ssl_certificate is not None
|
||||||
|
self.cors_origins = cors_origins
|
||||||
|
|
||||||
# We will lazy init this one if needed
|
# We will lazy init this one if needed
|
||||||
self.event_forwarder = None
|
self.event_forwarder = None
|
||||||
|
@ -351,6 +373,16 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
||||||
self.send_header(HTTP_HEADER_VARY, HTTP_HEADER_ACCEPT_ENCODING)
|
self.send_header(HTTP_HEADER_VARY, HTTP_HEADER_ACCEPT_ENCODING)
|
||||||
|
|
||||||
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(content)))
|
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(content)))
|
||||||
|
|
||||||
|
cors_check = (self.headers.get("Origin") in self.server.cors_origins)
|
||||||
|
|
||||||
|
cors_headers = ", ".join(ALLOWED_CORS_HEADERS)
|
||||||
|
|
||||||
|
if self.server.cors_origins and cors_check:
|
||||||
|
self.send_header(HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||||
|
self.headers.get("Origin"))
|
||||||
|
self.send_header(HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
|
||||||
|
cors_headers)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
if self.command == 'HEAD':
|
if self.command == 'HEAD':
|
||||||
|
|
|
@ -229,6 +229,15 @@ HTTP_HEADER_VARY = "Vary"
|
||||||
HTTP_HEADER_CONTENT_LENGTH = "Content-Length"
|
HTTP_HEADER_CONTENT_LENGTH = "Content-Length"
|
||||||
HTTP_HEADER_CACHE_CONTROL = "Cache-Control"
|
HTTP_HEADER_CACHE_CONTROL = "Cache-Control"
|
||||||
HTTP_HEADER_EXPIRES = "Expires"
|
HTTP_HEADER_EXPIRES = "Expires"
|
||||||
|
HTTP_HEADER_ORIGIN = "Origin"
|
||||||
|
HTTP_HEADER_X_REQUESTED_WITH = "X-Requested-With"
|
||||||
|
HTTP_HEADER_ACCEPT = "Accept"
|
||||||
|
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"
|
||||||
|
HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"
|
||||||
|
|
||||||
|
ALLOWED_CORS_HEADERS = [HTTP_HEADER_ORIGIN, HTTP_HEADER_ACCEPT,
|
||||||
|
HTTP_HEADER_X_REQUESTED_WITH, HTTP_HEADER_CONTENT_TYPE,
|
||||||
|
HTTP_HEADER_HA_AUTH]
|
||||||
|
|
||||||
CONTENT_TYPE_JSON = "application/json"
|
CONTENT_TYPE_JSON = "application/json"
|
||||||
CONTENT_TYPE_MULTIPART = 'multipart/x-mixed-replace; boundary={}'
|
CONTENT_TYPE_MULTIPART = 'multipart/x-mixed-replace; boundary={}'
|
||||||
|
|
Loading…
Reference in New Issue