WebOb和通用标准实现WSGI框架的比较

来源:互联网 发布:python exit code 9 编辑:程序博客网 时间:2024/05/16 07:07

http://tumblr.wachang.net/post/38298360375/webob-wsgi-framework-diff


作者都是Ian Bicking,两个框架分别是遵循WSGI标准的WSGI框架(以下以1方法表示)以及使用Webob来实现WSGI框架(以下一2方法表示),两篇文章的地址分别是:http://pythonpaste.org/do-it-yourself-framework.html,http://docs.webob.org/en/latest/do-it-yourself.html,这里对两个文章做一个总结比较。

基本概念

controller:就是WSGI应用,2方法中格式为module_name:function_name routes:

webob是一个创建请求和回复对象的库,webob对于请求和回应的封装能力,提供了一种简单测试WSGI应用的方法,如下:

>>> from webob import Request>>> req = Request.blank('http://localhost/test')>>> resp = req.get_response(application)>>> print resp200 OKContent-Type: text/htmlHello World!

请求部分

在普通的WSGI中,请求的信息是environ,这是一个类似CGI的字典形式。而使用webob,需要创建一个request对象,这是对environ的一个包装。如下:

from webob import Requestdef app(environ, start_response):    print 'This is environ info',environ['HTTP_HOST']    start_response('200 OK', [('content-type', 'text/html')])    req = Request(environ)    print 'This is Request info',req.environ['HTTP_HOST']    return ['Hello world!']root@Node1:~/python# python wsgi.py serving on http://192.168.1.11:8080This is environ info 192.168.1.11:8080This is Request info 192.168.1.11:8080

关于返回

通常是函数中先调用start_response,然后函数返回可迭代对象,webob中直接构造Response对象,说白了,Response对象就是一个WSGI应用,如下:

from webob import Requestfrom webob import Responsedef app(environ,start_response):    resp = Response(body='Hello World!')    resp.content_type='text/html'    print resp    return resp(environ,start_response)httpserver.serve(app, host='192.168.1.11', port='8080')root@Node1:~/python# python wsgi.py serving on http://192.168.1.11:8080200 OKContent-Length: 12Content-Type: text/html; charset=UTF-8

关于WSGI服务器

官方有两个参考,都可以:

from paste import httpserver    httpserver.serve(app, host='127.0.0.1', port=8080)from wsgiref.simple_server import make_server    server = make_server('127.0.0.1', 8080, app)    server.serve_forever()

Webob实现的WSGI框架

所谓的routes,router就是根据HTTP请求的PATH的层次调度到不同的WSGI应用上面去。2中使用了Router这个类来实现,如下:

app = Router() app.add_route(‘/’, controller=’controllers:index’) app.add_route(‘/post’, controller=’controllers:post’)

有了router以后,我们就要看看如何载入这个controller了,根据controller的格式,我们需要载入一个模块,然后执行函数,所以写了一个此功能的函数:

import sysdef load_controller(string):   module_name, func_name = string.split(':', 1) #分割出module和func的名字   __import__(module_name) #buildin函数,载入模块   module = sys.modules[module_name] #import的返回不好处理,所以这里返回Model名字   func = getattr(module, func_name)   return func #返回函数对象

Router有add_route方法可以加入路由香,并且Router实例有call方法,着同样,ROuter实例就可以当做一个WSGI应用来使用了。所以当一个请求到来的时候,它会根据PATH_INFO(req.path_info)作为匹配,并传递到相应的controller(WSGI应用),Router的代码如下:

from webob import Requestfrom webob import excclass Router(object):    def __init__(self):       self.routes = [] #里面是元组,每个元组包含了匹配规则,相应的应用    def add_route(self, template, controller, **vars):         if isinstance(controller, basestring):                controller = load_controller(controller)         self.routes.append((re.compile(template_to_regex(template)),                              controller,                               vars))    def __call__(self, environ, start_response):       req = Request(environ)       for regex, controller, vars in self.routes:            match = regex.match(req.path_info)            if match:                req.urlvars = match.groupdict()                req.urlvars.update(vars)                return controller(environ, start_response)        return exc.HTTPNotFound()(environ, start_response)

我们详细看看这个函数:

  • self.routes = [],是一个匹配表,表中内容为(regex, controller, vars)

  • add_route会判断controller应用是字符串或者是对象,都可以处理,如果是对象,需要实现call方法。

  • __call__方法使你可以像函数一样调用一个对象。

  • 对于请求,产生了一个request object对象,controller可以选择以request对象作为参数(在最后返回response(environ,start_response)),或者直接处理(environ,start_response)参数。

  • req.urlvars变量实际上是environ[‘wsgiorg.routing_args’]的一个映射,environ[‘wsgiorg.routing_args’]是经过match以后,WSGI应用对请求信息的修改,加入了这个wsgiorg.routing_args,值就为匹配的一些参数。

  • webob.exc.HTTPNotFound()是一个 WSGI application 用于返回404回应(注意还是要以environ和start_response参数调用).也可以加入自定义信息webob.exc.HTTPNotFound(‘No route matched’)(environ,start_response)

基本流程清楚以后,就是来看看controller端了,controller就是一个WSGI应用,但是为了简单的写应用,一般框架都会提供一个装饰器(把一个函数装饰warp成另外一个函数),利用这个装饰器,可以简化controller的开发,如下一个装饰器:

from webob import Request, Responsefrom webob import excdef controller(func):  #func是自己写的应用    def replacement(environ, start_response):        req = Request(environ) #首先封装environ环境        try:             resp = func(req, **req.urlvars) #将请求和附加参数传给应用处理。返回resp是一个字符串或者一个Response对象。        except exc.HTTPException, e:             resp = e        if isinstance(resp, basestring):#如果应用返回一个字符串,那么就封装为Response对象             resp = Response(body=resp)        return resp(environ, start_response)#Response对象是一个WSGI应用,如此调用的话就成功返回。返回的是自己,webob特色!    return replacement#函数定义中调用另外一个函数,用这种方式。

经过如上装饰以后,自己写的WSGI应用就只需要两个参数controller_func(req, **urlvars)了,确实简化了,不用考虑一直保持environ,start_response的传递了。然后这个装饰器就可以如下使用:

@controllerdef index(req):    return 'This is the index'

再来一个复杂一点的:

@controllerdef hello(req):    if req.method == 'POST':        return 'Hello %s!' % req.params['name']    elif req.method == 'GET':        return '''<form method="POST">            You're name: <input type="text" name="name">            <input type="submit">            </form>'''hello_world = Router()hello_world.add_route('/', controller=hello)

上面一个WSGI应用实际上是一个函数,前面说到,一个WSGI应用也可以是一个类。这样的话,在写controller装饰器的时候,就要注意一点用法:

def rest_controller(cls):    def replacement(environ, start_response):        req = Request(environ)        try:             instance = cls(req, **req.urlvars)             method = getattr(instance, action)             resp = method()             resp = Response(body=resp)             return resp(environ, start_response)    return replacement

action是req中的方法,method是类中的的方法,method()就是一个相应的执行。

class Hello(object):    def __init__(self, req):        self.request = req    def get(self):        return '''<form method="POST">            You're name: <input type="text" name="name">            <input type="submit">            </form>'''    def post(self):        return 'Hello %s!' % self.request.params['name']hello = rest_controller(Hello)