class DelugeRPCProtocol(DelugeTransferProtocol): def message_received(self, request): """ This method is called whenever a message is received from a client. The only message that a client sends to the server is a RPC Request message. If the RPC Request message is valid, then the method is called in :meth:`dispatch`. :param request: the request from the client. :type data: tuple """ if type(request) is not tuple: log.debug("Received invalid message: type is not tuple") return if len(request) < 1: log.debug("Received invalid message: there are no items") return for call in request: if len(call) != 4: log.debug( "Received invalid rpc request: number of items " "in request is %s", len(call)) continue #log.debug("RPCRequest: %s", format_request(call)) reactor.callLater(0, self.dispatch, *call) def sendData(self, data): """ Sends the data to the client. :param data: the object that is to be sent to the client. This should be one of the RPC message types. :type data: object """ self.transfer_message(data) def connectionMade(self): """ This method is called when a new client connects. """ peer = self.transport.getPeer() log.info("Deluge Client connection made from: %s:%s", peer.host, peer.port) # Set the initial auth level of this session to AUTH_LEVEL_NONE self.factory.authorized_sessions[ self.transport.sessionno] = AUTH_LEVEL_NONE def connectionLost(self, reason): """ This method is called when the client is disconnected. :param reason: the reason the client disconnected. :type reason: str """ # We need to remove this session from various dicts del self.factory.authorized_sessions[self.transport.sessionno] if self.transport.sessionno in self.factory.session_protocols: del self.factory.session_protocols[self.transport.sessionno] if self.transport.sessionno in self.factory.interested_events: del self.factory.interested_events[self.transport.sessionno] log.info("Deluge client disconnected: %s", reason.value) def valid_session(self): return self.transport.sessionno in self.factory.authorized_sessions def dispatch(self, request_id, method, args, kwargs): """ This method is run when a RPC Request is made. It will run the local method and will send either a RPC Response or RPC Error back to the client. :param request_id: the request_id from the client (sent in the RPC Request) :type request_id: int :param method: the local method to call. It must be registered with the :class:`RPCServer`. :type method: str :param args: the arguments to pass to `method` :type args: list :param kwargs: the keyword-arguments to pass to `method` :type kwargs: dict """ def sendError(): """ Sends an error response with the contents of the exception that was raised. """ exceptionType, exceptionValue, exceptionTraceback = sys.exc_info() formated_tb = "".join(traceback.format_tb(exceptionTraceback)) try: self.sendData((RPC_ERROR, request_id, exceptionType.__name__, exceptionValue._args, exceptionValue._kwargs, formated_tb)) except Exception, err: # This most likely not a deluge exception, let's wrap it log.error( "An exception occurred while sending RPC_ERROR to " "client. Wrapping it and resending. Error to " "send(causing exception goes next):\n%s", formated_tb) log.exception(err) try: raise WrappedException(str(exceptionValue), exceptionType.__name__, formated_tb) except: sendError() if method == "daemon.info": # This is a special case and used in the initial connection process self.sendData( (RPC_RESPONSE, request_id, deluge.common.get_version())) return elif method == "daemon.login": # This is a special case and used in the initial connection process # We need to authenticate the user here log.debug("RPC dispatch daemon.login") try: client_version = kwargs.pop('client_version', None) if client_version is None: raise IncompatibleClient(deluge.common.get_version()) ret = component.get("AuthManager").authorize(*args, **kwargs) if ret: self.factory.authorized_sessions[ self.transport.sessionno] = (ret, args[0]) self.factory.session_protocols[ self.transport.sessionno] = self except Exception, e: sendError() if not isinstance(e, _ClientSideRecreateError): log.exception(e) else: self.sendData((RPC_RESPONSE, request_id, (ret))) if not ret: self.transport.loseConnection() finally:
def dispatch(self, request_id, method, args, kwargs): """ This method is run when a RPC Request is made. It will run the local method and will send either a RPC Response or RPC Error back to the client. :param request_id: the request_id from the client (sent in the RPC Request) :type request_id: int :param method: the local method to call. It must be registered with the :class:`RPCServer`. :type method: str :param args: the arguments to pass to `method` :type args: list :param kwargs: the keyword-arguments to pass to `method` :type kwargs: dict """ def send_error(): """ Sends an error response with the contents of the exception that was raised. """ exc_type, exc_value, dummy_exc_trace = sys.exc_info() formated_tb = traceback.format_exc() try: self.sendData( ( RPC_ERROR, request_id, exc_type.__name__, exc_value._args, exc_value._kwargs, formated_tb, ) ) except AttributeError: # This is not a deluge exception (object has no attribute '_args), let's wrap it log.warning( 'An exception occurred while sending RPC_ERROR to ' 'client. Wrapping it and resending. Error to ' 'send(causing exception goes next):\n%s', formated_tb, ) try: raise WrappedException( str(exc_value), exc_type.__name__, formated_tb ) except WrappedException: send_error() except Exception as ex: log.error( 'An exception occurred while sending RPC_ERROR to client: %s', ex ) if method == 'daemon.info': # This is a special case and used in the initial connection process self.sendData((RPC_RESPONSE, request_id, deluge.common.get_version())) return elif method == 'daemon.login': # This is a special case and used in the initial connection process # We need to authenticate the user here log.debug('RPC dispatch daemon.login') try: client_version = kwargs.pop('client_version', None) if client_version is None: raise IncompatibleClient(deluge.common.get_version()) ret = component.get('AuthManager').authorize(*args, **kwargs) if ret: self.factory.authorized_sessions[ self.transport.sessionno ] = self.AuthLevel(ret, args[0]) self.factory.session_protocols[self.transport.sessionno] = self except Exception as ex: send_error() if not isinstance(ex, _ClientSideRecreateError): log.exception(ex) else: self.sendData((RPC_RESPONSE, request_id, (ret))) if not ret: self.transport.loseConnection() return # Anything below requires a valid session if not self.valid_session(): return if method == 'daemon.set_event_interest': log.debug('RPC dispatch daemon.set_event_interest') # This special case is to allow clients to set which events they are # interested in receiving. # We are expecting a sequence from the client. try: if self.transport.sessionno not in self.factory.interested_events: self.factory.interested_events[self.transport.sessionno] = [] self.factory.interested_events[self.transport.sessionno].extend(args[0]) except Exception: send_error() else: self.sendData((RPC_RESPONSE, request_id, (True))) return if method not in self.factory.methods: try: # Raise exception to be sent back to client raise AttributeError('RPC call on invalid function: %s' % method) except AttributeError: send_error() return log.debug('RPC dispatch %s', method) try: method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level auth_level = self.factory.authorized_sessions[ self.transport.sessionno ].auth_level if auth_level < method_auth_requirement: # This session is not allowed to call this method log.debug( 'Session %s is attempting an unauthorized method call!', self.transport.sessionno, ) raise NotAuthorizedError(auth_level, method_auth_requirement) # Set the session_id in the factory so that methods can know # which session is calling it. self.factory.session_id = self.transport.sessionno ret = self.factory.methods[method](*args, **kwargs) except Exception as ex: send_error() # Don't bother printing out DelugeErrors, because they are just # for the client if not isinstance(ex, DelugeError): log.exception('Exception calling RPC request: %s', ex) else: # Check if the return value is a deferred, since we'll need to # wait for it to fire before sending the RPC_RESPONSE if isinstance(ret, defer.Deferred): def on_success(result): try: self.sendData((RPC_RESPONSE, request_id, result)) except Exception: send_error() return result def on_fail(failure): try: failure.raiseException() except Exception: send_error() return failure ret.addCallbacks(on_success, on_fail) else: self.sendData((RPC_RESPONSE, request_id, ret))
except: sendError() if method == "daemon.info": # This is a special case and used in the initial connection process self.sendData( (RPC_RESPONSE, request_id, deluge.common.get_version())) return elif method == "daemon.login": # This is a special case and used in the initial connection process # We need to authenticate the user here log.debug("RPC dispatch daemon.login") try: client_version = kwargs.pop('client_version', None) if client_version is None: raise IncompatibleClient(deluge.common.get_version()) ret = component.get("AuthManager").authorize(*args, **kwargs) if ret: self.factory.authorized_sessions[ self.transport.sessionno] = (ret, args[0]) self.factory.session_protocols[ self.transport.sessionno] = self except Exception, e: sendError() if not isinstance(e, _ClientSideRecreateError): log.exception(e) else: self.sendData((RPC_RESPONSE, request_id, (ret))) if not ret: self.transport.loseConnection() finally: