#!/usr/bin/env python import inspect import json import logging import schema import jsonschema from concurrent.futures import Future import tornado import tornado.gen import tornado.web from tornado.web import RequestHandler, StaticFileHandler import errors class BaseHandler(RequestHandler): def data_received(self, chunk): raise NotImplementedError() class IndexPageHandler(BaseHandler): def get(self): self.render(MAIN_INDEX, status="Ready", info="Retrieving data ...") class StaticTextFileHandler(StaticFileHandler): def set_default_headers(self): self.set_header('Content-Type', 'text/plain') class DevStaticFileHandler(StaticFileHandler): def set_extra_headers(self, path): # Disable cache self.set_header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) class CommandHandler(tornado.web.RequestHandler): """REST Command Handler Routes json command in the form {'name' : 'apiHandler.function', 'parameters' : {}} to the right apiHandler instance function (see https://developers.google.com/streetview/open-spherical-camera/ for an overview of the protocol) This handler supports synchronous and asyncronous calls. Asynchronous API methods must be decorated with @run_on_executor and the enclosing class must provide an 'executor' ThreadPoolExecutor class instance. Example::: class MyAPIHandler(APIHandler): executor = ThreadPoolExecutor(1) @run_on_executor def asynchronousAPI (self, parameters) : return def synchronousAPI (self, parameters) : return """ def initialize(self, apiHandlers, extra): self.apiHandlers = apiHandlers self.extra = extra self.verbose = extra["verbose"] def set_default_headers(self): self.set_header("Access-Control-Allow-Origin", "*") self.set_header("Access-Control-Allow-Headers", "x-requested-with") self.set_header('Access-Control-Allow-Methods', 'POST') @tornado.gen.coroutine def post(self): try: data = json.loads(self.request.body) except ValueError: raise tornado.web.HTTPError(400, "Ill-formed message command") # Split command name command_name = data.get("name") if command_name is None: raise tornado.web.HTTPError(400, "No command name") if command_name.count(".") != 1: raise tornado.web.HTTPError(400, "Invalid command name") (class_name, function_name) = command_name.split(".") # Create a handler instance class_instance = self.apiHandlers.get(class_name) if class_instance is None: raise tornado.web.HTTPError(400, "Unknown handler " + class_name) instance = class_instance(self.extra) # Validate parameters validate = data.get("validate", True) log = data.get("log", True) try: parameters = data.get("parameters") if self.verbose and log: logger.info( "> " + class_name + "." + function_name + "(" + \ json.dumps(parameters) + ")") if validate: schema.api.validate_parameters(command_name, parameters) except jsonschema.ValidationError as ve: e = errors.InvalidParameter(str(ve)).payload m = {'error': e} logger.error(command_name + ", invalid parameter:\n" + str(ve)) else: # Call instance method if not hasattr(instance, function_name): raise tornado.web.HTTPError( 400, "Unknown function " + command_name) # call method function = getattr(instance, function_name) future = function(parameters) try: if isinstance(future, Future) or isinstance( future, tornado.concurrent.Future): result = yield future else: f = Future() f.set_result(future) result = yield f except errors.VSError as e: m = {'error': e.payload} else: # Validate result try: if validate: schema.api.validate_result(command_name, result) except jsonschema.ValidationError as ve: e = errors.InvalidReturnValue(str(ve)).payload m = {'error': e} logger.error(command_name + ", invalid return value:\n" + str(ve)) else: m = {'results': result} if self.verbose and log: logger.info("< " + json.dumps(m)) self.write(m) self.finish() class APIHandler(object): """Base class for APIHandlers """ def __init__(self, extra): pass def get_methods(self): """Get the list of exposed methods for the API Returns: A list of methods available. """ members = [] for member in inspect.getmembers(self, predicate=inspect.ismethod): if member[0] not in ["__init__", "ok", "error"]: members.append(member[0]) return members