[Python]python版本的springMVC
程序员文章站
2022-06-14 17:35:15
...
上一篇中,提到了装饰器在tornado中的封装风格,今天就来实现一下。
首先需要了解tornado本来的风格,从一个最简单的helloworld开始。
首先是定义一个requestHandler子类,里面可以实现get,post等方法,可以看到,回写页面也是靠这个对象的write方法。然后配置对应的URL(上例中默认是所有URL)传入到Application对象中去就OK了。这种风格的写法和javaEE中的servlet一样,实现一个servlet子类,重写doPost,doGet方法,然后在web.xml中配置对应路径,很显然过于麻烦了,特别是在多人合作的时候就显得比较复杂。体会过bottle的人应该对于那种即熟悉又陌生的装饰器风格印象深刻。下面我们就自己实现一个。
构思,上一篇中提到利用装饰器在装饰的时候会调用一次的特点可以收集所有业务函数和它的装饰器参数。难点在于如何构造函数的参数?获取页面参数,回写页面是需要RequestHandler参与的,所以我们必须把这个参数注入到业务函数中,剩下就是URL中截取的参数了,举个栗子:localhost:8888/book/medicine/49875,其中medicine是book的category,49875是书的id。这两个参数也是需要注入的。例如:
好了确定了之后我们可以开始着手写装饰器类了(建立文件route.py)
结尾引入了上一篇中提到的封装的业务函数文件进来。也就是(biz.py):
最后改造一下入口文件serv.py
接下来,我们可以实验一下
看来已经达到效果了,但是目前会有一个问题,就是假设我们的业务是比较耗时的,tornado的特性又是单线程的。就会发生排队!我们可以实验一下,首先将biz中的test方法加上sleep模拟业务耗时的操作
然后同时访问5次,看看所需耗时是多少
足足25秒钟!
tornado的原理和google的V8,nodejs很像,是单进程单线程,主进程会睡在epoll上,注册EPOLLIN事件,由管道对象消费,收集回调函数到队列中,然后尝试写入epoll,EPOLLOUT,从而唤醒主线程,主线程负责执行回调队列中所有函数。这也是为什么它们都比较费CPU但却都并发能力很强,效率很高的原因。所以想要使用他们最好的方式是写异步api,所有的工作全部异步化。
首先这对于一般程序员来说要改变编码习惯,全异步的API很容易造成逻辑的混乱,调试的难度陡然加大,而且tornado的异步api没有nodejs完善。
如果我们想利用tornado超强的并发处理,又想使用传统编程呢?其实也是有办法的,下一章,咱们就使用多线程改造它!
首先需要了解tornado本来的风格,从一个最简单的helloworld开始。
import sys,tornado.ioloop,tornado.web,tornado class Xroute(tornado.web.RequestHandler): def get(self, path): self.write("hello world,get") #main if __name__=="__main__": port=8888 application = tornado.web.Application([(r"/(.*)", Xroute),]) if len(sys.argv)>1: port = int(sys.argv[1]) application.listen(port) tornado.ioloop.IOLoop.instance().start()
首先是定义一个requestHandler子类,里面可以实现get,post等方法,可以看到,回写页面也是靠这个对象的write方法。然后配置对应的URL(上例中默认是所有URL)传入到Application对象中去就OK了。这种风格的写法和javaEE中的servlet一样,实现一个servlet子类,重写doPost,doGet方法,然后在web.xml中配置对应路径,很显然过于麻烦了,特别是在多人合作的时候就显得比较复杂。体会过bottle的人应该对于那种即熟悉又陌生的装饰器风格印象深刻。下面我们就自己实现一个。
构思,上一篇中提到利用装饰器在装饰的时候会调用一次的特点可以收集所有业务函数和它的装饰器参数。难点在于如何构造函数的参数?获取页面参数,回写页面是需要RequestHandler参与的,所以我们必须把这个参数注入到业务函数中,剩下就是URL中截取的参数了,举个栗子:localhost:8888/book/medicine/49875,其中medicine是book的category,49875是书的id。这两个参数也是需要注入的。例如:
@Router.route(url=r"book/([a-z]+)/(\d+)",method=Router._GET|Router._POST) def test3(req, categories, bookid): #http://localhost:8888/book/medicine/49875 return "looking for a " + categories + " book." + "No." + bookid
好了确定了之后我们可以开始着手写装饰器类了(建立文件route.py)
#!/usr/bin/python #coding=utf-8 import re class Router(object): '''dispather and decortor''' _GET = 0x001 _POST = 0x002 _PUT = 0x004 _DELETE = 0x008 #mapper中存放的就是所有的业务方法,key为装饰器上面的url参数 mapper={} #装饰器方法 @classmethod def route(cls, **deco): print Router.mapper def foo(func): #装饰器url如果不存在于mapper中,就将以下结构存入mapper中,例如 # “hello/(\w+)” : { # "call" = func, # "method" = "GET" # } url = deco.get('url') or '/' if url not in Router.mapper: method = deco.get('method') or Router._GET mapper_node = {} mapper_node['method'] = method mapper_node['call'] = func Router.mapper[url] = mapper_node return func return foo #get,post方法需要注入requestHandler实例,所以在此需要得到它 @classmethod def get(cls, path, reqhandler): Router.emit(path, reqhandler, Router._GET) @classmethod def post(cls,path,reqhandler): Router.emit(path, reqhandler, Router._POST) #put,delete,head...可以套这种写法套下去 @classmethod def emit(cls, path, reqhandler, method_flag): mapper = Router.mapper for urlExp in mapper: m = re.match('^'+urlExp+'$',path) #如果用户访问地址能够匹配mapper中url映射规则 if m: #构造注入参数,首先是requestHandler,然后是路径参数 params = (reqhandler,) for items in m.groups(): params+=(items,) mapper_node = mapper.get(urlExp) method = mapper_node.get('method') #如果不匹配装饰器参数中method的规则就报405异常 if method_flag is not method_flag & method: raise tornado.web.HTTPError(405) try: call = mapper_node.get('call') #执行方法回写页面 reqhandler.write(call(*params)) except Exception,e: print e raise tornado.web.HTTPError(500) break else: raise tornado.web.HTTPError(404) #为什么在这里引入业务函数,因为如果装饰器的特性是在装饰的时候调用一次, #如果一开始就引进来,装饰器一调用发现装饰器类都没有定义就会报错,所以 #需要把装饰器类定义出来了之后引人 from biz import *
结尾引入了上一篇中提到的封装的业务函数文件进来。也就是(biz.py):
#!/bin/python #coding=utf-8 import sys,traceback,tornado.web,time from route import Router @Router.route(url=r"hello/([a-z]+)",method=Router._GET|Router._POST) def test(req, who): #http://localhost:8888/hello/billy return "Hi," + who + "\n" @Router.route(url=r"greetings/([a-z]+)",method=Router._GET) def test2(req, who): #http://localhost:8888/greetings/rowland raise Exception("error") @Router.route(url=r"book/([a-z]+)/(\d+)",method=Router._GET|Router._POST) def test3(req, categories,bookid): #http://localhost:8888/book/medicine/49875 return "look for" + categories + " book" + " No." + bookid + "\n"
最后改造一下入口文件serv.py
#!/usr/bin/python #coding=utf-8 import sys,tornado.ioloop,tornado.web,tornado from route import Router class Xroute(tornado.web.RequestHandler): def get(self, path): Router.get(path, self) def post(self, path): Router.post(path, self) #put,head,delete等等可以套这种写法套下去 #main if __name__=="__main__": port=8888 application = tornado.web.Application([(r"^/([^\.|]*)(?!\.\w+)$", Xroute),]) if len(sys.argv)>1: port = int(sys.argv[1]) application.listen(port) tornado.ioloop.IOLoop.instance().start()
接下来,我们可以实验一下
看来已经达到效果了,但是目前会有一个问题,就是假设我们的业务是比较耗时的,tornado的特性又是单线程的。就会发生排队!我们可以实验一下,首先将biz中的test方法加上sleep模拟业务耗时的操作
@Router.route(url=r"hello/([a-z]+)",method=Router._GET|Router._POST) def test(req, who): time.sleep(5) return "Hi," + who + "\n"
然后同时访问5次,看看所需耗时是多少
#!/bin/sh function bb_curl() { for i in {1..5} ;do curl localhost:8888/hello/billy & done } function bb_test() { bb_curl wait } #main time bb_test
足足25秒钟!
tornado的原理和google的V8,nodejs很像,是单进程单线程,主进程会睡在epoll上,注册EPOLLIN事件,由管道对象消费,收集回调函数到队列中,然后尝试写入epoll,EPOLLOUT,从而唤醒主线程,主线程负责执行回调队列中所有函数。这也是为什么它们都比较费CPU但却都并发能力很强,效率很高的原因。所以想要使用他们最好的方式是写异步api,所有的工作全部异步化。
首先这对于一般程序员来说要改变编码习惯,全异步的API很容易造成逻辑的混乱,调试的难度陡然加大,而且tornado的异步api没有nodejs完善。
如果我们想利用tornado超强的并发处理,又想使用传统编程呢?其实也是有办法的,下一章,咱们就使用多线程改造它!
上一篇: [Python]从装饰器说开去
下一篇: clover简介及使用说明