@@ -24,6 +24,9 @@ from nilmdb.server.serverutil import ( | |||||
exception_to_httperror, | exception_to_httperror, | ||||
CORS_allow, | CORS_allow, | ||||
json_to_request_params, | json_to_request_params, | ||||
json_error_page, | |||||
cherrypy_start, | |||||
cherrypy_stop, | |||||
) | ) | ||||
# Add CORS_allow tool | # Add CORS_allow tool | ||||
@@ -469,70 +472,14 @@ class Server(object): | |||||
def json_error_page(self, status, message, traceback, version): | def json_error_page(self, status, message, traceback, version): | ||||
"""Return a custom error page in JSON so the client can parse it""" | """Return a custom error page in JSON so the client can parse it""" | ||||
errordata = { "status" : status, | |||||
"message" : message, | |||||
"traceback" : traceback } | |||||
# Don't send a traceback if the error was 400-499 (client's fault) | |||||
try: | |||||
code = int(status.split()[0]) | |||||
if not self.force_traceback: | |||||
if code >= 400 and code <= 499: | |||||
errordata["traceback"] = "" | |||||
except Exception: # pragma: no cover | |||||
pass | |||||
# Override the response type, which was previously set to text/html | |||||
cherrypy.serving.response.headers['Content-Type'] = ( | |||||
"application/json;charset=utf-8" ) | |||||
# Undo the HTML escaping that cherrypy's get_error_page function applies | |||||
# (cherrypy issue 1135) | |||||
for k, v in errordata.iteritems(): | |||||
v = v.replace("<","<") | |||||
v = v.replace(">",">") | |||||
v = v.replace("&","&") | |||||
errordata[k] = v | |||||
return json.dumps(errordata, separators=(',',':')) | |||||
return json_error_page(status, message, traceback, version, | |||||
self.force_traceback) | |||||
def start(self, blocking = False, event = None): | def start(self, blocking = False, event = None): | ||||
if not self.embedded: # pragma: no cover | |||||
# Handle signals nicely | |||||
if hasattr(cherrypy.engine, "signal_handler"): | |||||
cherrypy.engine.signal_handler.subscribe() | |||||
if hasattr(cherrypy.engine, "console_control_handler"): | |||||
cherrypy.engine.console_control_handler.subscribe() | |||||
# Cherrypy stupidly calls os._exit(70) when it can't bind the | |||||
# port. At least try to print a reasonable error and continue | |||||
# in this case, rather than just dying silently (as we would | |||||
# otherwise do in embedded mode) | |||||
real_exit = os._exit | |||||
def fake_exit(code): # pragma: no cover | |||||
if code == os.EX_SOFTWARE: | |||||
fprintf(sys.stderr, "error: CherryPy called os._exit!\n") | |||||
else: | |||||
real_exit(code) | |||||
os._exit = fake_exit | |||||
cherrypy.engine.start() | |||||
os._exit = real_exit | |||||
# Signal that the engine has started successfully | |||||
if event is not None: | |||||
event.set() | |||||
if blocking: | |||||
try: | |||||
cherrypy.engine.wait(cherrypy.engine.states.EXITING, | |||||
interval = 0.1, channel = 'main') | |||||
except (KeyboardInterrupt, IOError): # pragma: no cover | |||||
cherrypy.engine.log('Keyboard Interrupt: shutting down bus') | |||||
cherrypy.engine.exit() | |||||
except SystemExit: # pragma: no cover | |||||
cherrypy.engine.log('SystemExit raised: shutting down bus') | |||||
cherrypy.engine.exit() | |||||
raise | |||||
cherrypy_start(blocking, event, self.embedded) | |||||
def stop(self): | def stop(self): | ||||
cherrypy.engine.exit() | |||||
cherrypy_stop() | |||||
# Use a single global nilmdb.server.NilmDB and nilmdb.server.Server | # Use a single global nilmdb.server.NilmDB and nilmdb.server.Server | ||||
# instance since the database can only be opened once. For this to | # instance since the database can only be opened once. For this to | ||||
@@ -1,8 +1,11 @@ | |||||
"""Decorators and other helpers for a CherryPy server""" | |||||
"""Miscellaneous decorators and other helpers for running a CherryPy | |||||
server""" | |||||
import cherrypy | import cherrypy | ||||
import sys | import sys | ||||
import os | |||||
import decorator | import decorator | ||||
import simplejson as json | |||||
# Decorators | # Decorators | ||||
def chunked_response(func): | def chunked_response(func): | ||||
@@ -122,3 +125,75 @@ def json_to_request_params(body): | |||||
raise cherrypy.HTTPError(415) | raise cherrypy.HTTPError(415) | ||||
cherrypy.request.params.update(cherrypy.request.json) | cherrypy.request.params.update(cherrypy.request.json) | ||||
# Used as an "error_page.default" handler | |||||
def json_error_page(status, message, traceback, version, | |||||
force_traceback = False): | |||||
"""Return a custom error page in JSON so the client can parse it""" | |||||
errordata = { "status" : status, | |||||
"message" : message, | |||||
"traceback" : traceback } | |||||
# Don't send a traceback if the error was 400-499 (client's fault) | |||||
try: | |||||
code = int(status.split()[0]) | |||||
if not force_traceback: | |||||
if code >= 400 and code <= 499: | |||||
errordata["traceback"] = "" | |||||
except Exception: # pragma: no cover | |||||
pass | |||||
# Override the response type, which was previously set to text/html | |||||
cherrypy.serving.response.headers['Content-Type'] = ( | |||||
"application/json;charset=utf-8" ) | |||||
# Undo the HTML escaping that cherrypy's get_error_page function applies | |||||
# (cherrypy issue 1135) | |||||
for k, v in errordata.iteritems(): | |||||
v = v.replace("<","<") | |||||
v = v.replace(">",">") | |||||
v = v.replace("&","&") | |||||
errordata[k] = v | |||||
return json.dumps(errordata, separators=(',',':')) | |||||
# Start/stop CherryPy standalone server | |||||
def cherrypy_start(blocking = False, event = False, embedded = False): | |||||
"""Start the CherryPy server, handling errors and signals | |||||
somewhat gracefully.""" | |||||
if not embedded: # pragma: no cover | |||||
# Handle signals nicely | |||||
if hasattr(cherrypy.engine, "signal_handler"): | |||||
cherrypy.engine.signal_handler.subscribe() | |||||
if hasattr(cherrypy.engine, "console_control_handler"): | |||||
cherrypy.engine.console_control_handler.subscribe() | |||||
# Cherrypy stupidly calls os._exit(70) when it can't bind the | |||||
# port. At least try to print a reasonable error and continue | |||||
# in this case, rather than just dying silently (as we would | |||||
# otherwise do in embedded mode) | |||||
real_exit = os._exit | |||||
def fake_exit(code): # pragma: no cover | |||||
if code == os.EX_SOFTWARE: | |||||
fprintf(sys.stderr, "error: CherryPy called os._exit!\n") | |||||
else: | |||||
real_exit(code) | |||||
os._exit = fake_exit | |||||
cherrypy.engine.start() | |||||
os._exit = real_exit | |||||
# Signal that the engine has started successfully | |||||
if event is not None: | |||||
event.set() | |||||
if blocking: | |||||
try: | |||||
cherrypy.engine.wait(cherrypy.engine.states.EXITING, | |||||
interval = 0.1, channel = 'main') | |||||
except (KeyboardInterrupt, IOError): # pragma: no cover | |||||
cherrypy.engine.log('Keyboard Interrupt: shutting down bus') | |||||
cherrypy.engine.exit() | |||||
except SystemExit: # pragma: no cover | |||||
cherrypy.engine.log('SystemExit raised: shutting down bus') | |||||
cherrypy.engine.exit() | |||||
raise | |||||
# Stop CherryPy server | |||||
def cherrypy_stop(): | |||||
cherrypy.engine.exit() |