def handle_message(self, message): """ Given a message dictionary, perform the relevant RPC actions and send out the response. This function is called from a pool of background threads """ before = time.time() duration, result = None, None threadlocal.reset(websocket=self, message=message, headers=self.header_fields, **self.session_fields) action, callback, client, method = message.get('action'), message.get('callback'), message.get('client'), message.get('method') try: with self.client_lock(client): self.internal_action(action, client, callback) if method: self.clear_cached_response(client, callback) func = self.get_method(method) args, kwargs = get_params(message.get('params')) result = self.NO_RESPONSE try: result = func(*args, **kwargs) duration = (time.time() - before) if config['debug'] else None finally: trigger_delayed_notifications() self.update_triggers(client, callback, func, args, kwargs, result, duration) except: log.error('unexpected websocket dispatch error', exc_info=True) exc_class, exc, tb = sys.exc_info() str_content = str(exc) or 'Unexpected Error.' message = (str_content + '\n' + traceback.format_exc()) if config['debug'] else str_content self.send(error=message, callback=callback, client=client) else: if callback is not None and result is not self.NO_RESPONSE: self.send(data=result, callback=callback, client=client, _time=duration)
def handle_message(self, message): before = time.time() duration, result = None, None threadlocal.reset(websocket=self, message=message, username=self.username) action, callback, client, method = message.get('action'), message.get('callback'), message.get('client'), message.get('method') try: with self.client_lock(client): self.internal_action(action, client, callback) if method: func = self.get_method(method) args, kwargs = get_params(message.get('params')) result = self.NO_RESPONSE try: result = func(*args, **kwargs) duration = (time.time() - before) if config['debug'] else None finally: self.update_triggers(client, callback, func, args, kwargs, result, duration) except: log.error('unexpected websocket dispatch error', exc_info=True) exc_class, exc, tb = sys.exc_info() str_content = str(exc) or 'Unexpected Error.' message = (str_content + '\n' + traceback.format_exc()) if config['debug'] else str_content self.send(error=message, callback=callback, client=client) else: if callback is not None and result is not self.NO_RESPONSE: self.send(data=result, callback=callback, client=client, _time=duration)
def run(self): while not self.stopped.is_set(): try: self.func() except: log.error('unexpected error', exc_info=True) if self.interval: self.stopped.wait(self.interval)
def received_message(self, message): s = str(message) log.debug('received {!r}', s) try: parsed = json.loads(s) except: log.error('failed to parse incoming message', exc_info=True) else: self.dispatcher.defer(parsed)
def run(self): while not self.stopped.is_set(): try: self.func() except: log.error('unexpected error', exc_info=True) interval = config['thread_wait_interval'] if self.interval is None else self.interval if interval: self.stopped.wait(interval)
def upgrade(self, **kwargs): try: kwargs['handler_cls'].check_authentication() except WebSocketAuthError: raise cherrypy.HTTPError(401, 'You must be logged in to establish a websocket connection.') except: log.error('unexpected websocket authentication error', exc_info=True) raise cherrypy.HTTPError(401, 'unexpected authentication error') else: return WebSocketTool.upgrade(self, **kwargs)
def _poll(self): with self._lock: assert self.ws and self.ws.connected, 'cannot poll while websocket is not connected' try: self.call(self.poll_method) except: log.error('no poll response received from {!r}, closing connection, will attempt to reconnect', self.url, exc_info=True) self.ws.close() else: self._last_poll = datetime.now()
def _send(self, **kwargs): log.debug('sending {}', kwargs) with self._lock: assert self.connected, 'tried to send data on closed websocket {!r}'.format(self.url) try: return self.ws.send(kwargs) except: log.error('failed to send {!r} on {!r}, closing websocket and will attempt to reconnect', kwargs, self.url) self.ws.close() raise
def received_message(self, message): try: fields = json.loads(str(message.data)) assert isinstance(fields, dict) except: message = 'incoming websocket message was not a json object: {}'.format(message.data) log.error(message) self.send(error=message) else: log.debug('received {}', fields) responder.defer(self, fields)
def _check_sometimes_required_options(): missing = [] for optname in ['subscription_host', 'ldap.url', 'ldap.basedn']: val = config[optname] if not val or isinstance(val, (list, tuple)) and not filter(bool, val): missing.append(optname) if missing: message = 'missing configuration options: {}'.format(missing) log.error(message) raise ConfigurationError(message)
def check_authentication(cls): host, origin = cherrypy.request.headers['host'], cherrypy.request.headers['origin'] if ('//' + host) not in origin: log.error('Javascript websocket connections must follow same-origin policy; origin {!r} does not match host {!r}', origin, host) raise ValueError('Origin and Host headers do not match') if config['ws.auth_required'] and 'username' not in cherrypy.session: log.warning('websocket connections to this address must have a valid session') raise ValueError('you are not logged in') return cherrypy.session.get('username', '<UNAUTHENTICATED>')
def received_message(self, message): try: data = message.data if isinstance(message.data, six.text_type) else message.data.decode('utf-8') fields = json.loads(data) assert isinstance(fields, dict) except: message = 'incoming websocket message was not a json object: {}'.format(message.data) log.error(message) self.send(error=message) else: log.debug('received {}', fields) responder.defer(self, fields)
def fallback(self, message): """ Handler method which is called for incoming websocket messages which aren't valid responses to an outstanding call or subscription. By default this just logs an error message. You can override this by subclassing this class, or just by assigning a hander method, e.g. >>> ws = WebSocket() >>> ws.fallback = some_handler_function >>> ws.connect() """ _, exc, _ = sys.exc_info() log.error('no callback registered for message {!r}, message ignored: {}', message, exc)
def local_broadcast(channels, trigger=None, originating_client=None): """Triggers callbacks registered via @locally_subscribes""" triggered = set() for channel in sideboard.lib.listify(channels): for callback in local_subscriptions[channel]: triggered.add(callback) for callback in triggered: threadlocal.reset(trigger=trigger, originating_client=originating_client) try: callback() except: log.error('unexpected error on local broadcast callback', exc_info=True)
def upgrade(self, **kwargs): try: kwargs['handler_cls'].check_authentication() except WebSocketAuthError: raise cherrypy.HTTPError( 401, 'You must be logged in to establish a websocket connection.') except: log.error('unexpected websocket authentication error', exc_info=True) raise cherrypy.HTTPError(401, 'unexpected authentication error') else: return WebSocketTool.upgrade(self, **kwargs)
def mainloop_daemon(): log.info('starting Sideboard daemon process') args = parser.parse_args() if os.fork() == 0: pid = os.fork() if pid == 0: mainloop() else: log.debug('writing pid ({}) to pidfile ({})', pid, args.pidfile) try: with open(args.pidfile, 'w') as f: f.write('{}'.format(pid)) except: log.error('unexpected error writing pid ({}) to pidfile ({})', pid, args.pidfile, exc_info=True)
def received_message(self, message): try: data = message.data if isinstance( message.data, six.text_type) else message.data.decode('utf-8') fields = json.loads(data) assert isinstance(fields, dict) except: message = 'incoming websocket message was not a json object: {}'.format( message.data) log.error(message) self.send(error=message) else: log.debug('received {}', fields) responder.defer(self, fields)
def ldap_auth(username, password): if not username or not password: return False try: ssl_material = ( config['ldap.cacert'], config['ldap.cert'], config['ldap.key'] ) server_kwargs = {} tls_kwargs = {} if config['ldap.url'].startswith('ldaps') or any(ssl_material): server_kwargs['use_ssl'] = True else: server_kwargs['use_ssl'] = False server_kwargs['host'] = config['ldap.url'] if config['ldap.cacert']: tls_kwargs['ca_certs_file'] = config['ldap.cacert'] # if we specify a CA certs file, assume we want to validate it tls_kwargs['validate'] = ssl.CERT_REQUIRED if tls_kwargs: server_kwargs['tls'] = ldap3.Tls(**tls_kwargs) server = ldap3.Server(**server_kwargs) except: log.error('Error initializing LDAP server', exc_info=True) raise # attempt to bind on each base DN that was configured for basedn in listify(config['ldap.basedn']): dn = '{}={},{}'.format(config['ldap.userattr'], username, basedn) log.debug('attempting to bind with dn {}', dn) try: connection = ldap3.Connection(server, user=dn, password=password) connection.start_tls() is_bound = connection.bind() except: log.warning("Error binding to LDAP server with dn", exc_info=True) raise if is_bound: return True # we couldn't auth on anything return False
def check_authentication(cls): host, origin = cherrypy.request.headers[ 'host'], cherrypy.request.headers['origin'] if ('//' + host) not in origin: log.error( 'Javascript websocket connections must follow same-origin policy; origin {!r} does not match host {!r}', origin, host) raise ValueError('Origin and Host headers do not match') if config['ws.auth_required'] and 'username' not in cherrypy.session: log.warning( 'websocket connections to this address must have a valid session' ) raise ValueError('you are not logged in') return cherrypy.session.get('username', '<UNAUTHENTICATED>')
def _make_jsonrpc_handler(services, debug=config['debug'], precall=lambda body: None, errback=lambda err, message: log.error(message, exc_info=True)): @cherrypy.expose @cherrypy.tools.force_json_in() @cherrypy.tools.json_out(handler=json_handler) def jsonrpc_handler(self): id = None def error(code, message): body = {'jsonrpc': '2.0', 'id': id, 'error': {'code': code, 'message': message}} log.warn('returning error message: {!r}', body) return body body = cherrypy.request.json if not isinstance(body, dict): return error(ERR_INVALID_JSON, 'invalid json input {!r}'.format( cherrypy.request.body)) log.debug('jsonrpc request body: {!r}', body) id, params = body.get('id'), body.get('params', []) if 'method' not in body: return error(ERR_INVALID_RPC, '"method" field required for jsonrpc request') method = body['method'] if method.count('.') != 1: return error(ERR_MISSING_FUNC, 'invalid method ' + method) module, function = method.split('.') if module not in services: return error(ERR_MISSING_FUNC, 'no module ' + module) service = services[module] if not hasattr(service, function): return error(ERR_MISSING_FUNC, 'no function ' + method) if not isinstance(params, (list, dict)): return error(ERR_INVALID_PARAMS, 'invalid parameter list: {!r}'.format(params)) args, kwargs = (params, {}) if isinstance(params, list) else ([], params) precall(body) try: response = {'jsonrpc': '2.0', 'id': id, 'result': getattr(service, function)(*args, **kwargs)} log.debug('returning success message: {!r}', response) return response except Exception as e: errback(e, 'unexpected jsonrpc error calling ' + method) message = 'unexpected error' if debug: message += ': ' + traceback.format_exc() return error(ERR_FUNC_EXCEPTION, message) return jsonrpc_handler
def received_message(self, message): """ This overrides the default ws4py event handler to parse the incoming message and pass it off to our pool of background threads, which call this class' handle_message function to perform the relevant RPC actions. """ try: data = message.data if isinstance(message.data, six.text_type) else message.data.decode('utf-8') fields = json.loads(data) assert isinstance(fields, dict) except: message = 'incoming websocket message was not a json object: {}'.format(message.data) log.error(message) self.send(error=message) else: log.debug('received {}', fields) responder.defer(self, fields)
def check_authentication(cls): host, origin = cherrypy.request.headers[ 'host'], cherrypy.request.headers['origin'] if ('//' + host.split(':')[0]) not in origin: log.error( 'Javascript websocket connections must follow same-origin policy; origin {!r} does not match host {!r}', origin, host) raise WebSocketAuthError('Origin and Host headers do not match') if config['ws.auth_required'] and not cherrypy.session.get( config['ws.auth_field']): log.warning( 'websocket connections to this address must have a valid session' ) raise WebSocketAuthError('You are not logged in') return WebSocketDispatcher.check_authentication()
def subscribe(self, callback, method, *args, **kwargs): """ Send a websocket request which you expect to subscribe you to a channel with a callback which will be called every time there is new data, and return the client id which uniquely identifies this subscription. Callback may be either a function or a dictionary in the form { 'callback': <function>, 'errback': <function>, # optional 'paramback: <function>, # optional 'client': <string> # optional } Both callback and errback take a single argument; for callback, this is the return value of the method, for errback it is the error message returning. If no errback is specified, we will log errors at the ERROR level and do nothing further. The paramback function exists for subscriptions where we might want to pass different parameters every time we reconnect. This might be used for e.g. time-based parameters. This function takes no arguments and returns the parameters which should be passed every time we connect and fire (or re-fire) all of our subscriptions. The client id is automatically generated if omitted, and you should not set this yourself unless you really know what you're doing. The positional and keyword arguments passed to this function will be used as the arguments to the remote method, unless paramback is passed, in which case that will be used to generate the params, and args/kwargs will be ignored. """ client = self._next_id('client') if isinstance(callback, Mapping): assert 'callback' in callback, 'callback is required' client = callback.setdefault('client', client) self._callbacks[client] = callback else: self._callbacks[client] = {'client': client, 'callback': callback} paramback = self._callbacks[client].get('paramback') params = self.preprocess( method, paramback() if paramback else (args or kwargs)) self._callbacks[client].setdefault( 'errback', lambda result: log.error('{}(*{}, **{}) returned an error: {!r}', method, args, kwargs, result)) self._callbacks[client].update({'method': method, 'params': params}) try: self._send(method=method, params=params, client=client) except: log.warn( 'initial subscription to {} at {!r} failed, will retry on reconnect', method, self.url) return client
def subscribe(self, callback, method, *args, **kwargs): """ Send a websocket request which you expect to subscribe you to a channel with a callback which will be called every time there is new data, and return the client id which uniquely identifies this subscription. Callback may be either a function or a dictionary in the form { 'callback': <function>, 'errback': <function>, # optional 'paramback: <function>, # optional 'client': <string> # optional } Both callback and errback take a single argument; for callback, this is the return value of the method, for errback it is the error message returning. If no errback is specified, we will log errors at the ERROR level and do nothing further. The paramback function exists for subscriptions where we might want to pass different parameters every time we reconnect. This might be used for e.g. time-based parameters. This function takes no arguments and returns the parameters which should be passed every time we connect and fire (or re-fire) all of our subscriptions. The client id is automatically generated if omitted, and you should not set this yourself unless you really know what you're doing. The positional and keyword arguments passed to this function will be used as the arguments to the remote method, unless paramback is passed, in which case that will be used to generate the params, and args/kwargs will be ignored. """ client = self._next_id('client') if isinstance(callback, Mapping): assert 'callback' in callback, 'callback is required' client = callback.setdefault('client', client) self._callbacks[client] = callback else: self._callbacks[client] = { 'client': client, 'callback': callback } paramback = self._callbacks[client].get('paramback') params = self.preprocess(method, paramback() if paramback else (args or kwargs)) self._callbacks[client].setdefault('errback', lambda result: log.error('{}(*{}, **{}) returned an error: {!r}', method, args, kwargs, result)) self._callbacks[client].update({ 'method': method, 'params': params }) try: self._send(method=method, params=params, client=client) except: log.warn('initial subscription to {} at {!r} failed, will retry on reconnect', method, self.url) return client
def _make_jsonrpc_handler( services, debug=config["debug"], precall=lambda body: None, errback=lambda err, message: log.error(message, exc_info=True), ): @cherrypy.expose @cherrypy.tools.force_json_in() @cherrypy.tools.json_out(handler=json_handler) def jsonrpc_handler(self): id = None def error(code, message): body = {"jsonrpc": "2.0", "id": id, "error": {"code": code, "message": message}} log.warn("returning error message: {!r}", body) return body body = cherrypy.request.json if not isinstance(body, dict): return error(ERR_INVALID_JSON, "invalid json input {!r}".format(cherrypy.request.body)) log.debug("jsonrpc request body: {!r}", body) id, params = body.get("id"), body.get("params", []) if "method" not in body: return error(ERR_INVALID_RPC, '"method" field required for jsonrpc request') method = body["method"] if method.count(".") != 1: return error(ERR_MISSING_FUNC, "invalid method " + method) module, function = method.split(".") if module not in services: return error(ERR_MISSING_FUNC, "no module " + module) service = services[module] if not hasattr(service, function): return error(ERR_MISSING_FUNC, "no function " + method) if not isinstance(params, (list, dict)): return error(ERR_INVALID_PARAMS, "invalid parameter list: {!r}".format(params)) args, kwargs = (params, {}) if isinstance(params, list) else ([], params) precall(body) try: response = {"jsonrpc": "2.0", "id": id, "result": getattr(service, function)(*args, **kwargs)} log.debug("returning success message: {!r}", response) return response except Exception as e: errback(e, "unexpected jsonrpc error calling " + method) message = "unexpected error" if debug: message += ": " + traceback.format_exc() return error(ERR_FUNC_EXCEPTION, message) return jsonrpc_handler
def handle_message(self, message): """ Given a message dictionary, perform the relevant RPC actions and send out the response. This function is called from a pool of background threads """ before = time.time() duration, result = None, None threadlocal.reset(websocket=self, message=message, headers=self.header_fields, **self.session_fields) action, callback, client, method = message.get('action'), message.get( 'callback'), message.get('client'), message.get('method') try: with self.client_lock(client): self.internal_action(action, client, callback) if method: self.clear_cached_response(client, callback) func = self.get_method(method) args, kwargs = get_params(message.get('params')) result = self.NO_RESPONSE try: result = func(*args, **kwargs) duration = (time.time() - before) if config['debug'] else None finally: trigger_delayed_notifications() self.update_triggers(client, callback, func, args, kwargs, result, duration) except: log.error('unexpected websocket dispatch error', exc_info=True) exc_class, exc, tb = sys.exc_info() str_content = str(exc) or 'Unexpected Error.' message = ( str_content + '\n' + traceback.format_exc()) if config['debug'] else str_content self.send(error=message, callback=callback, client=client) else: if callback is not None and result is not self.NO_RESPONSE: self.send(data=result, callback=callback, client=client, _time=duration)
def differences(cls, instance): diff = {} for attr, column in instance.__table__.columns.items(): new_val = getattr(instance, attr) old_val = instance.orig_value_of(attr) if old_val != new_val: """ Important note: here we try and show the old vs new value for something that has been changed so that we can report it in the tracking page. Sometimes, however, if we changed the type of the value in the database (via a database migration) the old value might not be able to be shown as the new type (i.e. it used to be a string, now it's int). In that case, we won't be able to show a representation of the old value and instead we'll log it as '<ERROR>'. In theory the database migration SHOULD be the thing handling this, but if it doesn't, it becomes our problem to deal with. We are overly paranoid with exception handling here because the tracking code should be made to never, ever, ever crash, even if it encounters insane/old data that really shouldn't be our problem. """ try: old_val_repr = cls.repr(column, old_val) except Exception as e: log.error( 'Tracking repr({}) failed on old value'.format(attr), exc_info=True) old_val_repr = '<ERROR>' try: new_val_repr = cls.repr(column, new_val) except Exception as e: log.error( 'Tracking repr({}) failed on new value'.format(attr), exc_info=True) new_val_repr = '<ERROR>' diff[attr] = "'{} -> {}'".format(old_val_repr, new_val_repr) return diff
def received_message(self, message): """ This overrides the default ws4py event handler to parse the incoming message and pass it off to our pool of background threads, which call this class' handle_message function to perform the relevant RPC actions. """ try: data = message.data if isinstance( message.data, six.text_type) else message.data.decode('utf-8') fields = json.loads(data) assert isinstance(fields, dict) except: message = 'incoming websocket message was not a json object: {}'.format( message.data) log.error(message) self.send(error=message) else: log.debug('received {}', fields) responder.defer(self, fields)
def _status_adjustments(self): if self.badge_status == c.NEW_STATUS and self.banned: self.badge_status = c.WATCHED_STATUS try: send_email(c.SECURITY_EMAIL, [c.REGDESK_EMAIL, c.SECURITY_EMAIL], c.EVENT_NAME + ' WatchList Notification', render('emails/reg_workflow/attendee_watchlist.txt', {'attendee': self}), model='n/a') except: log.error('unable to send banned email about {}', self) elif self.badge_status == c.NEW_STATUS and not self.placeholder and \ self.first_name and ( self.paid in [c.HAS_PAID, c.NEED_NOT_PAY] or self.paid == c.PAID_BY_GROUP and self.group_id and not self.group.is_unpaid): self.badge_status = c.COMPLETED_STATUS
def ldap_auth(username, password): if not username or not password: return False try: conn = ldap.initialize(config['ldap.url']) force_start_tls = False if config['ldap.cacert']: ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, config['ldap.cacert']) force_start_tls = True if config['ldap.cert']: ldap.set_option(ldap.OPT_X_TLS_CERTFILE, config['ldap.cert']) force_start_tls = True if config['ldap.key']: ldap.set_option(ldap.OPT_X_TLS_KEYFILE, config['ldap.key']) force_start_tls = True if force_start_tls: conn.start_tls_s() else: conn.set_option(ldap.OPT_X_TLS_DEMAND, config['ldap.start_tls']) except: log.error('Error initializing LDAP connection', exc_info=True) raise for basedn in listify(config['ldap.basedn']): dn = '{}={},{}'.format(config['ldap.userattr'], username, basedn) log.debug('attempting to bind with dn {}', dn) try: conn.simple_bind_s(dn, password) except ldap.INVALID_CREDENTIALS as x: continue except: log.warning("Error binding to LDAP server with dn", exc_info=True) raise else: return True
def handle_message(self, message): before = time.time() duration, result = None, None threadlocal.reset(websocket=self, message=message, username=self.username) action, callback, client, method = message.get('action'), message.get( 'callback'), message.get('client'), message.get('method') try: with self.client_lock(client): self.internal_action(action, client, callback) if method: func = self.get_method(method) args, kwargs = get_params(message.get('params')) result = self.NO_RESPONSE try: result = func(*args, **kwargs) duration = (time.time() - before) if config['debug'] else None finally: self.update_triggers(client, callback, func, args, kwargs, result, duration) except: log.error('unexpected websocket dispatch error', exc_info=True) exc_class, exc, tb = sys.exc_info() str_content = str(exc) or 'Unexpected Error.' message = ( str_content + '\n' + traceback.format_exc()) if config['debug'] else str_content self.send(error=message, callback=callback, client=client) else: if callback is not None and result is not self.NO_RESPONSE: self.send(data=result, callback=callback, client=client, _time=duration)
def subscribe(self, callback, method, *args, **kwargs): """ Send a websocket request which you expect to subscribe you to a channel with a callback which will be called every time there is new data, and return the client id which uniquely identifies this subscription. Callback may be either a function or a dictionary in the form { 'callback': <function>, 'errback': <function> } Both callback and errback take a single argument; for callback, this is the return value of the method, for errback it is the error message returning. If no errback is specified, we will log errors at the ERROR level and do nothing further. The positional and keyword arguments passed to this function will be used as the arguments to the remote method. """ client = self._next_id('client') if isinstance(callback, dict): assert 'callback' in callback and 'errback' in callback, 'callback and errback are required' client = callback.setdefault('client', client) self._callbacks[client] = callback else: self._callbacks[client] = { 'client': client, 'callback': callback, 'errback': lambda result: log.error('{}(*{}, **{}) returned an error: {!r}', method, args, kwargs, result) } self._callbacks[client].update({ 'method': method, 'params': args or kwargs }) try: self._send(method=method, params=args or kwargs, client=client) except: log.warn('initial subscription to {} at {!r} failed, will retry on reconnect', method, self.url) return client
def subscribe(self, callback, method, *args, **kwargs): """ Send a websocket request which you expect to subscribe you to a channel with a callback which will be called every time there is new data, and return the client id which uniquely identifies this subscription. Callback may be either a function or a dictionary in the form { 'callback': <function>, 'errback': <function> } Both callback and errback take a single argument; for callback, this is the return value of the method, for errback it is the error message returning. If no errback is specified, we will log errors at the ERROR level and do nothing further. The positional and keyword arguments passed to this function will be used as the arguments to the remote method. """ client = self._next_id('client') if isinstance(callback, Mapping): assert 'callback' in callback and 'errback' in callback, 'callback and errback are required' client = callback.setdefault('client', client) self._callbacks[client] = callback else: self._callbacks[client] = { 'client': client, 'callback': callback, 'errback': lambda result: log.error('{}(*{}, **{}) returned an error: {!r}', method, args, kwargs, result) } self._callbacks[client].update({ 'method': method, 'params': args or kwargs }) try: self._send(method=method, params=args or kwargs, client=client) except: log.warn('initial subscription to {} at {!r} failed, will retry on reconnect', method, self.url) return client
def __del__(self): if self.session.transaction._connections: log.error('SessionManager went out of scope without underlying connection being closed; did you forget to use it as a context manager?') self.session.close()
def __del__(self): if self.session.transaction._connections: log.error( 'SessionManager went out of scope without underlying connection being closed; did you forget to use it as a context manager?' ) self.session.close()
def _make_jsonrpc_handler( services, debug=config['debug'], precall=lambda body: None, errback=lambda err, message: log.error(message, exc_info=True)): @cherrypy.expose @cherrypy.tools.force_json_in() @cherrypy.tools.json_out(handler=json_handler) def jsonrpc_handler(self=None): id = None def error(code, message): body = { 'jsonrpc': '2.0', 'id': id, 'error': { 'code': code, 'message': message } } log.warn('returning error message: {!r}', body) return body body = cherrypy.request.json if not isinstance(body, dict): return error( ERR_INVALID_JSON, 'invalid json input {!r}'.format(cherrypy.request.body)) log.debug('jsonrpc request body: {!r}', body) id, params = body.get('id'), body.get('params', []) if 'method' not in body: return error(ERR_INVALID_RPC, '"method" field required for jsonrpc request') method = body['method'] if method.count('.') != 1: return error(ERR_MISSING_FUNC, 'invalid method ' + method) module, function = method.split('.') if module not in services: return error(ERR_MISSING_FUNC, 'no module ' + module) service = services[module] if not hasattr(service, function): return error(ERR_MISSING_FUNC, 'no function ' + method) if not isinstance(params, (list, dict)): return error(ERR_INVALID_PARAMS, 'invalid parameter list: {!r}'.format(params)) args, kwargs = (params, {}) if isinstance(params, list) else ([], params) precall(body) try: response = { 'jsonrpc': '2.0', 'id': id, 'result': getattr(service, function)(*args, **kwargs) } log.debug('returning success message: {!r}', response) return response except Exception as e: errback(e, 'unexpected jsonrpc error calling ' + method) message = 'unexpected error' if debug: message += ': ' + traceback.format_exc() return error(ERR_FUNC_EXCEPTION, message) finally: trigger_delayed_notifications() return jsonrpc_handler