以简洁分离的方式向简单RPC服务器添加方法
我创建了一个简单的 RPC 服务器,用来执行我们团队常用的一些任务,这些任务是从不同的网络调用的。服务器的代码大致是这样的(为了简洁,我没有包括错误处理的部分):
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor
import json
class MyProtocol(Protocol):
def dataReceived(self, data):
req = json.loads(data) # create a dictionary from JSON string
method = getattr(self, req['method']) # get the method
method(req['params']) # call the method
def add(self, params):
result = {} # initialize a dictionary to convert later to JSON
result['result'] = sum(params)
result['error'] = None
result['id'] = 1
self.transport.write(json.dumps(result)) # return a JSON string
self.transport.loseConnection() # close connection
factory = Factory()
factory.protocol = MyProtocol
reactor.listenTCP(8080, factory)
reactor.run()
这个过程非常简单:服务器接收到来自客户端的 JSON RPC 请求,查找对应的方法,然后调用这个方法并传入参数。这个方法会返回 JSON RPC 的响应。对于不太熟悉的人来说,JSON RPC 大概是这样的:
request:
{"method":"my_method", "params":[1,2,3], "id":"my_id"}
response:
{"result":"my_result", "error":null, "id":"my_id"}
我现在的 RPC 服务器非常符合我的需求(可以想象,我的任务非常简单)。但随着任务复杂度的增加,我需要不断添加方法。
我不想每次都打开主文件,添加一个新的 def method3(...)
,然后过两周再加 def method4(...)
,这样下去代码会迅速膨胀,维护起来会越来越困难。
所以,我的问题是:我该如何设计一个架构,让我可以在服务器中 注册 方法。如果能有一个单独的文件夹,每个方法对应一个文件,那就更好了,这样可以方便共享和维护。这种“架构”还可以让我把某些方法的维护工作交给别人,而不管他们对 Twisted 的了解程度。
我不在乎每次注册新方法时是否需要重启服务器,但如果能不重启,那当然更好 :).
谢谢。
1 个回答
这可能有点复杂,但我给你一些初步的步骤(这里的例子是简化过的,具体细节省略了):
# your twisted imports...
import json
class MyProtocol(object): # Would be Protocol instead of object in real code
def dataReceived(self, data):
req = json.loads(data) # create a dictionary from JSON string
modname, funcname = req['method'].split('.')
m = __import__(modname)
method = getattr(m, funcname) # get the method
method(self, req['params']) # call the method
假设你尝试像这样执行:
mp = MyProtocol()
mp.dataReceived('{"method":"somemod.add", "params":[1,2,3]}')
你会在和这个例子同一个文件夹里有一个叫 somemod.py
的模块,里面的内容如下(和你上面提到的 .add()
方法相对应):
import json
def add(proto, params):
result = {} # initialize a dictionary to convert later to JSON
result['result'] = sum(params)
result['error'] = None
result['id'] = 1
proto.transport.write(json.dumps(result)) # return a JSON string
proto.transport.loseConnection() # close connection
这样你就可以为每个需要的功能准备一个模块。上面的 method(..
调用会始终把你的 MyProtocol
实例传递给处理请求的函数。(如果你真的想用实例方法,这里有关于如何在 Python 中添加方法的说明:http://irrepupavel.com/documents/python/instancemethod/)
你需要添加很多错误处理。例如,在 dataReceived()
的第2行的 split()
调用上,你需要进行很多错误检查。
这样你就可以为每个需要支持的方法准备一个只包含一个函数的文件。虽然这不是一个完整的例子,但可能会帮助你入门,因为你要找的东西相当复杂。
为了更正式的注册,我建议在 MyProtocol
中使用一个 dict
,里面存放你支持的方法名称,类似于:
# in MyProtocol's __init__() method:
self.methods = {}
还有一个注册方法..
def register(self, name, callable):
self.methods[name] = callable
..修改 dataReceived()
..
def dataReceived(self, data):
# ...
modname, funcname = self.methods.get(req['method'], False)
# ..continue along the lines of the dataReceived() method above
简单总结一下这篇太长的帖子:__import__
函数(http://docs.python.org/library/functions.html)肯定会是你解决方案的关键部分。