Browse Source

Bump required nilmdb version; use nilmdb.server.serverutil stuff

tags/nilmrun-0.1^0
Jim Paris 9 years ago
parent
commit
ce2be06845
5 changed files with 236 additions and 5 deletions
  1. +1
    -2
      README.txt
  2. +1
    -0
      scripts/__init__.py
  3. +58
    -0
      scripts/nilmrun_server.py
  4. +6
    -3
      setup.py
  5. +170
    -0
      src/server.py

+ 1
- 2
README.txt View File

@@ -7,10 +7,9 @@ Prerequisites:
sudo apt-get install python2.7 python-setuptools

# Base dependencies
sudo apt-get install python-cherrypy3 python-simplejson
sudo apt-get install python-numpy python-scipy

nilmdb (1.6.3+)
nilmdb (1.8.0+)

Install:



+ 1
- 0
scripts/__init__.py View File

@@ -0,0 +1 @@
# Command line scripts

+ 58
- 0
scripts/nilmrun_server.py View File

@@ -0,0 +1,58 @@
#!/usr/bin/python

import nilmrun.server
import argparse
import os
import socket

def main():
"""Main entry point for the 'nilmrun-server' command line script"""

parser = argparse.ArgumentParser(
description = 'Run the NilmRun server',
formatter_class = argparse.ArgumentDefaultsHelpFormatter)

parser.add_argument("-V", "--version", action="version",
version = nilmrun.__version__)

group = parser.add_argument_group("Standard options")
group.add_argument('-a', '--address',
help = 'Only listen on the given address',
default = '0.0.0.0')
group.add_argument('-p', '--port', help = 'Listen on the given port',
type = int, default = 12380)
group.add_argument('-q', '--quiet', help = 'Silence output',
action = 'store_true')
group.add_argument('-t', '--traceback',
help = 'Provide tracebacks in client errors',
action = 'store_true', default = False)

args = parser.parse_args()

# Configure the server
if args.quiet:
embedded = True
else:
embedded = False
server = nilmrun.server.Server(host = args.address,
port = args.port,
embedded = embedded,
force_traceback = args.traceback)

# Print info
if not args.quiet:
print "Version: %s" % nilmrun.__version__
if args.address == '0.0.0.0' or args.address == '::':
host = socket.getfqdn()
else:
host = args.address
print "NilmRun Server URL: http://%s:%d/" % ( host, args.port)
print "----"

server.start(blocking = True)

if not args.quiet:
print "Shutting down"

if __name__ == "__main__":
main()

+ 6
- 3
setup.py View File

@@ -61,17 +61,20 @@ setup(name='nilmrun',
long_description = "NILM Database Filter Runner",
license = "Proprietary",
author_email = 'jim@jtan.com',
install_requires = [ 'nilmdb >= 1.6.3',
install_requires = [ 'nilmdb >= 1.8.0',
'nilmtools >= 1.2.2',
'numpy',
'scipy',
],
packages = [ 'nilmrun',
'nilmrun.scripts',
],
package_dir = { 'nilmrun': 'src' },
package_dir = { 'nilmrun': 'src',
'nilmrun.scripts': 'scripts',
},
entry_points = {
'console_scripts': [
'nilmrun-server = nilmrun.server:main',
'nilmrun-server = nilmrun.scripts.nilmrun_server:main',
],
},
zip_safe = False,


+ 170
- 0
src/server.py View File

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

Loading…
Cancel
Save