使用tornado,我们可以做什么?
以下介绍都是建立在python2.x的基础上面,tornado使用任意版本皆可。
如果我们需要对外提供一个http server(web api)/websocket server时,我们都可以使用tornado,以下是一个基于tornado的rest的应用简介。
当我们下载好了tornado以后,可以按照tornado的文档demo,复制一份监听代码过来,代码如下:
import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") application = tornado.web.Application([ (r"/", MainHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
这一段代码在运行时就已经可以开始监听并返回信息了,然后开始按照rest的规范开始改写,我们首先需要对url的path进行定位,根据path执行相对应的代码,则有了以下代码:
import os.path import tornado.escape import tornado.ioloop import tornado.options import tornado.web import tornado.websocket import tornado.httpserver import tools import applogic from config import config class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/.*?", WebHandler), ] settings = dict( cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__", template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), xsrf_cookies=False ) tornado.web.Application.__init__(self, handlers, **settings) class WebHandler(tornado.web.RequestHandler): def get(self): path = self.request.path command = path[path.rfind('/') +1:] result = applogic.execute(self, command, self.get_argument) if result: to_message = tools.dumps(result) self.write(to_message) def post(self): path = self.request.path command = path[path.rfind('/') +1:] obj = tools.loads(self.request.body) result = applogic.execute(self, command, obj) if result: to_message = tools.dumps(result) self.write(to_message) def main(): app = Application() http_server = tornado.httpserver.HTTPServer(app) http_server.bind(config.default['main_port'], config.default['host']) tornado.ioloop.IOLoop.instance().start()
我们对path做了最后一级目录的截取,由于客观原因,多级目录的则需要看客们自行实现。
applogic是我们的具体的逻辑代码门面类,因为是脚本语言的关系,所以这里使用一个相对巧妙的ioc,代码如下:
def execute(handler, command, obj): if main_map.has_key(command): result = main_map[command](handler, obj) if result: response.send(handler, result) else: handler.send_error(404) def create(handler, obj): flag = papercache.push(obj) result = {} if flag: result = { 'code':0 } else: result = { 'code':1, 'errorMsg':'param is error' } return result def grab(handler, obj): id = obj.has_key('id') and obj['id'] or None result = { 'code':1, 'errorMsg':'redpaper is empty', 'money':0 } if id: money = papercache.pop_queue(id) if money: result = { 'money' : money, 'code': 0 } return result main_map = { 'create':create, 'grab':grab }
我们将具体的执行逻辑,放在定义好的function里面,然后将function的引用放在一个字典里面,然后根据command(最后一级目录对应的字符串),来执行具体的代码。
我们将返回的数据做了一个封装,因为实际应用里面的数据格式,可能采用的是json,或者是其它自定义的协议,所以我们有一个response的封装,代码如下:
def send(handler, obj): if type(handler) is not None: obj = tools.dumps(obj) handler.write(obj)
tools的代码如下:
import json def dumps(obj): obj = toUnicode(obj) if obj: obj = json.dumps(obj) obj = str(obj) return obj def loads(obj): if obj: obj = json.loads(str(obj)) obj = toUtf8(obj) return obj
因为可能存在中文的关系,所以加了一个Utf8的转换,,基于websocket的也是同理,则在监听的时候,使用WebSocketHandle,代码如下:
import os.path import tornado.escape import tornado.ioloop import tornado.options import tornado.web import tornado.websocket import tornado.httpserver import tools import applogic from config import config class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/web", MainHandler) (r"/.*?", WebHandler), ] settings = dict( cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__", template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), xsrf_cookies=False ) tornado.web.Application.__init__(self, handlers, **settings) class MainHandler(tornado.websocket.WebSocketHandler): def allow_draft76(self): return True def open(self): token = self.get_cookie('token') if not token: print "long not token" self.close() else: print('connect') def on_close(self): print('close'); def on_message(self, message): obj = tools.loads(message) command = obj.has_key('command') and obj['command'] or None body = obj.has_key('body') and obj['body'] or None result = applogic.execute(self, command, body) if result: to_message = tools.dumps(result) self.write(to_message) return class WebHandler(tornado.web.RequestHandler): def get(self): path = self.request.path command = path[path.rfind('/') +1:] result = applogic.execute(self, command, self.get_argument) if result: to_message = tools.dumps(result) self.write(to_message) def post(self): path = self.request.path command = path[path.rfind('/') +1:] obj = tools.loads(self.request.body) result = applogic.execute(self, command, obj) if result: to_message = tools.dumps(result) self.write(to_message) def main(): app = Application() http_server = tornado.httpserver.HTTPServer(app) http_server.bind(config.default['main_port'], config.default['host']) tornado.ioloop.IOLoop.instance().start()
可能你们会觉得怎么多了个command和body出来了,因为我的通讯协议是假定{"command":"", "body":""},这样的json格式。
那一个简易的基于python的rest服务和websocket通讯服务器到这里就结束了