|
|
@@ -0,0 +1,170 @@ |
|
|
|
"""CherryPy-based server for running NILM filters via HTTP""" |
|
|
|
|
|
|
|
import cherrypy |
|
|
|
import sys |
|
|
|
import os |
|
|
|
import socket |
|
|
|
import simplejson as json |
|
|
|
import decorator |
|
|
|
import psutil |
|
|
|
import traceback |
|
|
|
import argparse |
|
|
|
|
|
|
|
import nilmdb |
|
|
|
from nilmdb.utils.printf import * |
|
|
|
from nilmdb.server.serverutil import ( |
|
|
|
chunked_response, |
|
|
|
response_type, |
|
|
|
workaround_cp_bug_1200, |
|
|
|
exception_to_httperror, |
|
|
|
CORS_allow, |
|
|
|
json_to_request_params, |
|
|
|
json_error_page, |
|
|
|
cherrypy_start, |
|
|
|
cherrypy_stop, |
|
|
|
) |
|
|
|
import nilmrun |
|
|
|
|
|
|
|
# Add CORS_allow tool |
|
|
|
cherrypy.tools.CORS_allow = cherrypy.Tool('on_start_resource', CORS_allow) |
|
|
|
|
|
|
|
# CherryPy apps |
|
|
|
class NilmRunApp(object): |
|
|
|
"""Root application for NILM runner""" |
|
|
|
|
|
|
|
def __init__(self): |
|
|
|
pass |
|
|
|
|
|
|
|
# / |
|
|
|
@cherrypy.expose |
|
|
|
def index(self): |
|
|
|
cherrypy.response.headers['Content-Type'] = 'text/plain' |
|
|
|
msg = sprintf("This is NilmRun version %s, running on host %s.\n", |
|
|
|
nilmdb.__version__, socket.getfqdn()) |
|
|
|
return msg |
|
|
|
|
|
|
|
# /favicon.ico |
|
|
|
@cherrypy.expose |
|
|
|
def favicon_ico(self): |
|
|
|
raise cherrypy.NotFound() |
|
|
|
|
|
|
|
# /version |
|
|
|
@cherrypy.expose |
|
|
|
@cherrypy.tools.json_out() |
|
|
|
def version(self): |
|
|
|
return nilmrun.__version__ |
|
|
|
|
|
|
|
class Server(object): |
|
|
|
def __init__(self, host = '127.0.0.1', port = 8080, |
|
|
|
embedded = True, # hide diagnostics and output, etc |
|
|
|
force_traceback = False, # include traceback in all errors |
|
|
|
basepath = '', # base URL path for cherrypy.tree |
|
|
|
): |
|
|
|
self.embedded = embedded |
|
|
|
|
|
|
|
# Build up global server configuration |
|
|
|
cherrypy.config.update({ |
|
|
|
'server.socket_host': host, |
|
|
|
'server.socket_port': port, |
|
|
|
'engine.autoreload_on': False, |
|
|
|
'server.max_request_body_size': 8*1024*1024, |
|
|
|
}) |
|
|
|
if self.embedded: |
|
|
|
cherrypy.config.update({ 'environment': 'embedded' }) |
|
|
|
|
|
|
|
# Build up application specific configuration |
|
|
|
app_config = {} |
|
|
|
app_config.update({ |
|
|
|
'error_page.default': self.json_error_page, |
|
|
|
}) |
|
|
|
|
|
|
|
# Some default headers to just help identify that things are working |
|
|
|
app_config.update({ 'response.headers.X-Jim-Is-Awesome': 'yeah' }) |
|
|
|
|
|
|
|
# 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'] }) |
|
|
|
|
|
|
|
# Configure the 'json_in' tool to also allow other content-types |
|
|
|
# (like x-www-form-urlencoded), and to treat JSON as a dict that |
|
|
|
# fills requests.param. |
|
|
|
app_config.update({ 'tools.json_in.force': False, |
|
|
|
'tools.json_in.processor': json_to_request_params }) |
|
|
|
|
|
|
|
# Send tracebacks in error responses. They're hidden by the |
|
|
|
# error_page function for client errors (code 400-499). |
|
|
|
app_config.update({ 'request.show_tracebacks' : True }) |
|
|
|
self.force_traceback = force_traceback |
|
|
|
|
|
|
|
# Patch CherryPy error handler to never pad out error messages. |
|
|
|
# This isn't necessary, but then again, neither is padding the |
|
|
|
# error messages. |
|
|
|
cherrypy._cperror._ie_friendly_error_sizes = {} |
|
|
|
|
|
|
|
# Build up the application and mount it |
|
|
|
root = NilmRunApp() |
|
|
|
cherrypy.tree.apps = {} |
|
|
|
cherrypy.tree.mount(root, basepath, config = { "/" : app_config }) |
|
|
|
|
|
|
|
# Set up the WSGI application pointer for external programs |
|
|
|
self.wsgi_application = cherrypy.tree |
|
|
|
|
|
|
|
def json_error_page(self, status, message, traceback, version): |
|
|
|
"""Return a custom error page in JSON so the client can parse it""" |
|
|
|
return json_error_page(status, message, traceback, version, |
|
|
|
self.force_traceback) |
|
|
|
|
|
|
|
def start(self, blocking = False, event = None): |
|
|
|
cherrypy_start(blocking, event, self.embedded) |
|
|
|
|
|
|
|
def stop(self): |
|
|
|
cherrypy_stop() |
|
|
|
|
|
|
|
# Multiple processes and threads should be OK here, but we'll still |
|
|
|
# follow the NilmDB approach of having just one globally initialized |
|
|
|
# copy of the server object. |
|
|
|
_wsgi_server = None |
|
|
|
def wsgi_application(basepath): # pragma: no cover |
|
|
|
"""Return a WSGI application object. |
|
|
|
|
|
|
|
'basepath' is the URL path of the application base, which |
|
|
|
is the same as the first argument to Apache's WSGIScriptAlias |
|
|
|
directive. |
|
|
|
""" |
|
|
|
def application(environ, start_response): |
|
|
|
global _wsgi_server |
|
|
|
if _wsgi_server is None: |
|
|
|
# Try to start the server |
|
|
|
try: |
|
|
|
_wsgi_server = nilmrun.server.Server( |
|
|
|
embedded = True, |
|
|
|
basepath = basepath.rstrip('/')) |
|
|
|
except Exception: |
|
|
|
# Build an error message on failure |
|
|
|
import pprint |
|
|
|
err = sprintf("Initializing nilmrun failed:\n\n", |
|
|
|
dbpath) |
|
|
|
err += traceback.format_exc() |
|
|
|
try: |
|
|
|
import pwd |
|
|
|
import grp |
|
|
|
err += sprintf("\nRunning as: uid=%d (%s), gid=%d (%s) " |
|
|
|
"on host %s, pid %d\n", |
|
|
|
os.getuid(), pwd.getpwuid(os.getuid())[0], |
|
|
|
os.getgid(), grp.getgrgid(os.getgid())[0], |
|
|
|
socket.gethostname(), os.getpid()) |
|
|
|
except ImportError: |
|
|
|
pass |
|
|
|
err += sprintf("\nEnvironment:\n%s\n", pprint.pformat(environ)) |
|
|
|
if _wsgi_server is None: |
|
|
|
# Serve up the error with our own mini WSGI app. |
|
|
|
headers = [ ('Content-type', 'text/plain'), |
|
|
|
('Content-length', str(len(err))) ] |
|
|
|
start_response("500 Internal Server Error", headers) |
|
|
|
return [err] |
|
|
|
|
|
|
|
# Call the normal application |
|
|
|
return _wsgi_server.wsgi_application(environ, start_response) |
|
|
|
return application |