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 resp
200 OK
Content-Type: text/html
Hello World!
请求部分
在普通的WSGI中,请求的信息是environ,这是一个类似CGI的字典形式。而使用webob,需要创建一个request对象,这是对environ的一个包装。如下:
from webob import Request
def 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:8080
This is environ info 192.168.1.11:8080
This is Request info 192.168.1.11:8080
关于返回
通常是函数中先调用start_response,然后函数返回可迭代对象,webob中直接构造Response对象,说白了,Response对象就是一个WSGI应用,如下:
from webob import Request
from webob import Response
def 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:8080
200 OK
Content-Length: 12
Content-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 sys
def 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 Request
from webob import exc
class 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, Response
from webob import exc
def 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的传递了。然后这个装饰器就可以如下使用:
@controller
def index(req):
return 'This is the index'
再来一个复杂一点的:
@controller
def 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)