|
- """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
|