|
@@ -71,6 +71,52 @@ def exception_to_httperror(*expected): |
|
|
# care of that. |
|
|
# care of that. |
|
|
return decorator.decorator(wrapper) |
|
|
return decorator.decorator(wrapper) |
|
|
|
|
|
|
|
|
|
|
|
# Custom CherryPy tools |
|
|
|
|
|
|
|
|
|
|
|
def CORS_allow(methods): |
|
|
|
|
|
"""This does several things: |
|
|
|
|
|
|
|
|
|
|
|
Handles CORS preflight requests. |
|
|
|
|
|
Adds Allow: header to all requests. |
|
|
|
|
|
Raise 405 if request.method not in method. |
|
|
|
|
|
|
|
|
|
|
|
It is similar to cherrypy.tools.allow, with the CORS stuff added. |
|
|
|
|
|
""" |
|
|
|
|
|
request = cherrypy.request.headers |
|
|
|
|
|
response = cherrypy.response.headers |
|
|
|
|
|
|
|
|
|
|
|
if not isinstance(methods, (tuple, list)): # pragma: no cover |
|
|
|
|
|
methods = [ methods ] |
|
|
|
|
|
methods = [ m.upper() for m in methods if m ] |
|
|
|
|
|
if not methods: # pragma: no cover |
|
|
|
|
|
methods = [ 'GET', 'HEAD' ] |
|
|
|
|
|
elif 'GET' in methods and 'HEAD' not in methods: # pragma: no cover |
|
|
|
|
|
methods.append('HEAD') |
|
|
|
|
|
response['Allow'] = ', '.join(methods) |
|
|
|
|
|
|
|
|
|
|
|
# Allow all origins |
|
|
|
|
|
if 'Origin' in request: |
|
|
|
|
|
response['Access-Control-Allow-Origin'] = request['Origin'] |
|
|
|
|
|
|
|
|
|
|
|
# If it's a CORS request, send response. |
|
|
|
|
|
request_method = request.get("Access-Control-Request-Method", None) |
|
|
|
|
|
request_headers = request.get("Access-Control-Request-Headers", None) |
|
|
|
|
|
if (cherrypy.request.method == "OPTIONS" and |
|
|
|
|
|
request_method and request_headers): |
|
|
|
|
|
response['Access-Control-Allow-Headers'] = request_headers |
|
|
|
|
|
response['Access-Control-Allow-Methods'] = ', '.join(methods) |
|
|
|
|
|
# Try to stop further processing and return a 200 OK |
|
|
|
|
|
cherrypy.response.status = "200 OK" |
|
|
|
|
|
cherrypy.response.body = "" |
|
|
|
|
|
cherrypy.request.handler = lambda: "" |
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
# Reject methods that were not explicitly allowed |
|
|
|
|
|
if cherrypy.request.method not in methods: |
|
|
|
|
|
raise cherrypy.HTTPError(405) |
|
|
|
|
|
|
|
|
|
|
|
cherrypy.tools.CORS_allow = cherrypy.Tool('on_start_resource', CORS_allow) |
|
|
|
|
|
|
|
|
# CherryPy apps |
|
|
# CherryPy apps |
|
|
class Root(NilmApp): |
|
|
class Root(NilmApp): |
|
|
"""Root application for NILM database""" |
|
|
"""Root application for NILM database""" |
|
@@ -131,7 +177,7 @@ class Stream(NilmApp): |
|
|
@cherrypy.expose |
|
|
@cherrypy.expose |
|
|
@cherrypy.tools.json_out() |
|
|
@cherrypy.tools.json_out() |
|
|
@exception_to_httperror(NilmDBError, ValueError) |
|
|
@exception_to_httperror(NilmDBError, ValueError) |
|
|
@cherrypy.tools.allow(methods = ["POST"]) |
|
|
|
|
|
|
|
|
@cherrypy.tools.CORS_allow(methods = ["POST"]) |
|
|
def create(self, path, layout): |
|
|
def create(self, path, layout): |
|
|
"""Create a new stream in the database. Provide path |
|
|
"""Create a new stream in the database. Provide path |
|
|
and one of the nilmdb.layout.layouts keys. |
|
|
and one of the nilmdb.layout.layouts keys. |
|
@@ -142,7 +188,7 @@ class Stream(NilmApp): |
|
|
@cherrypy.expose |
|
|
@cherrypy.expose |
|
|
@cherrypy.tools.json_out() |
|
|
@cherrypy.tools.json_out() |
|
|
@exception_to_httperror(NilmDBError) |
|
|
@exception_to_httperror(NilmDBError) |
|
|
@cherrypy.tools.allow(methods = ["POST"]) |
|
|
|
|
|
|
|
|
@cherrypy.tools.CORS_allow(methods = ["POST"]) |
|
|
def destroy(self, path): |
|
|
def destroy(self, path): |
|
|
"""Delete a stream and its associated data.""" |
|
|
"""Delete a stream and its associated data.""" |
|
|
return self.db.stream_destroy(path) |
|
|
return self.db.stream_destroy(path) |
|
@@ -175,7 +221,7 @@ class Stream(NilmApp): |
|
|
@cherrypy.expose |
|
|
@cherrypy.expose |
|
|
@cherrypy.tools.json_out() |
|
|
@cherrypy.tools.json_out() |
|
|
@exception_to_httperror(NilmDBError, LookupError, TypeError) |
|
|
@exception_to_httperror(NilmDBError, LookupError, TypeError) |
|
|
@cherrypy.tools.allow(methods = ["POST"]) |
|
|
|
|
|
|
|
|
@cherrypy.tools.CORS_allow(methods = ["POST"]) |
|
|
def set_metadata(self, path, data): |
|
|
def set_metadata(self, path, data): |
|
|
"""Set metadata for the named stream, replacing any |
|
|
"""Set metadata for the named stream, replacing any |
|
|
existing metadata. Data should be a json-encoded |
|
|
existing metadata. Data should be a json-encoded |
|
@@ -187,7 +233,7 @@ class Stream(NilmApp): |
|
|
@cherrypy.expose |
|
|
@cherrypy.expose |
|
|
@cherrypy.tools.json_out() |
|
|
@cherrypy.tools.json_out() |
|
|
@exception_to_httperror(NilmDBError, LookupError, TypeError) |
|
|
@exception_to_httperror(NilmDBError, LookupError, TypeError) |
|
|
@cherrypy.tools.allow(methods = ["POST"]) |
|
|
|
|
|
|
|
|
@cherrypy.tools.CORS_allow(methods = ["POST"]) |
|
|
def update_metadata(self, path, data): |
|
|
def update_metadata(self, path, data): |
|
|
"""Update metadata for the named stream. Data |
|
|
"""Update metadata for the named stream. Data |
|
|
should be a json-encoded dictionary""" |
|
|
should be a json-encoded dictionary""" |
|
@@ -197,7 +243,7 @@ class Stream(NilmApp): |
|
|
# /stream/insert?path=/newton/prep |
|
|
# /stream/insert?path=/newton/prep |
|
|
@cherrypy.expose |
|
|
@cherrypy.expose |
|
|
@cherrypy.tools.json_out() |
|
|
@cherrypy.tools.json_out() |
|
|
@cherrypy.tools.allow(methods = ["PUT"]) |
|
|
|
|
|
|
|
|
@cherrypy.tools.CORS_allow(methods = ["PUT"]) |
|
|
def insert(self, path, start, end): |
|
|
def insert(self, path, start, end): |
|
|
""" |
|
|
""" |
|
|
Insert new data into the database. Provide textual data |
|
|
Insert new data into the database. Provide textual data |
|
@@ -253,7 +299,7 @@ class Stream(NilmApp): |
|
|
@cherrypy.expose |
|
|
@cherrypy.expose |
|
|
@cherrypy.tools.json_out() |
|
|
@cherrypy.tools.json_out() |
|
|
@exception_to_httperror(NilmDBError) |
|
|
@exception_to_httperror(NilmDBError) |
|
|
@cherrypy.tools.allow(methods = ["POST"]) |
|
|
|
|
|
|
|
|
@cherrypy.tools.CORS_allow(methods = ["POST"]) |
|
|
def remove(self, path, start = None, end = None): |
|
|
def remove(self, path, start = None, end = None): |
|
|
""" |
|
|
""" |
|
|
Remove data from the backend database. Removes all data in |
|
|
Remove data from the backend database. Removes all data in |
|
@@ -408,16 +454,14 @@ class Server(object): |
|
|
'error_page.default': self.json_error_page, |
|
|
'error_page.default': self.json_error_page, |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
# Send a permissive Access-Control-Allow-Origin (CORS) header |
|
|
|
|
|
# with all responses so that browsers can send cross-domain |
|
|
|
|
|
# requests to this server. |
|
|
|
|
|
app_config.update({ 'response.headers.Access-Control-Allow-Origin': |
|
|
|
|
|
'*' }) |
|
|
|
|
|
|
|
|
# Some default headers to just help identify that things are working |
|
|
|
|
|
app_config.update({ 'response.headers.X-Jim-Is-Awesome': 'yeah' }) |
|
|
|
|
|
|
|
|
# Only allow GET and HEAD by default. Individual handlers |
|
|
|
|
|
# can override. |
|
|
|
|
|
app_config.update({ 'tools.allow.on': True, |
|
|
|
|
|
'tools.allow.methods': ['GET', 'HEAD'] }) |
|
|
|
|
|
|
|
|
# Set up Cross-Origin Resource Sharing (CORS) handler so we |
|
|
|
|
|
# can correctly respond to browsers' CORS preflight requests. |
|
|
|
|
|
# This also limits verbs to GET and HEAD by default. |
|
|
|
|
|
app_config.update({ 'tools.CORS_allow.on': True, |
|
|
|
|
|
'tools.CORS_allow.methods': ['GET', 'HEAD'] }) |
|
|
|
|
|
|
|
|
# Send tracebacks in error responses. They're hidden by the |
|
|
# Send tracebacks in error responses. They're hidden by the |
|
|
# error_page function for client errors (code 400-499). |
|
|
# error_page function for client errors (code 400-499). |
|
|