在之前的文章中我写了有关于如何使用PasteDeploy生成WSGI的Application。在Openstack的源码中,除了 PasteDeploy外,还有一个和WSGI密切相关的工具包WebOb。这篇文章就来讲讲这个WebOb。官网在 这:http://webob.org/
简单的说,WebOb是一个用于对WSGI request环境进行包装(也就是变得易用)以及用于创建WSGI response的一个包。
1.Request
webob.Request是WebOb中的一个重要对象。其会的对WSGI的environ(就是传递给WSGI APP的那个参数)参数进行封装。
一个简单的例子:
1
2
3
4
5
|
from webob import Request req = Request.blank( '/article?id=1' ) from pprint import pprint pprint(req.environ) |
输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
[root@OS_DEV dev]# python webobtest.py {'HTTP_HOST': 'localhost:80', 'PATH_INFO': '/article', 'QUERY_STRING': 'id=1', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80', 'SERVER_PROTOCOL': 'HTTP/1.0', 'wsgi.errors': <open file '<stderr>', mode 'w' at 0x7f83c59d21e0>, 'wsgi.input': <io.BytesIO object at 0x7f83c592b590>, 'wsgi.multiprocess': False, 'wsgi.multithread': False, 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.version': (1, 0)} |
既然是request,那么必然有个body,并且也有request的方法(GET?POST?DELETE?),所以文档里有这么个例子:
1
2
3
4
5
6
7
8
9
10
|
>>> hasattr(req.body_file, 'read') True >>> req.body '' >>> req.method = 'PUT' >>> req.body = 'test' >>> hasattr(req.body_file, 'read') True >>> req.body 'test' |
对request请求头部的操作如下:
1
2
3
4
5
|
>>> req.headers['Content-Type'] = 'application/x-www-urlencoded' >>> sorted(req.headers.items()) [('Content-Length', '4'), ('Content-Type', 'application/x-www-urlencoded'), ('Host', 'localhost:80')] >>> req.environ['CONTENT_TYPE'] 'application/x-www-urlencoded' |
对请求参数的处理如下:
1
2
3
4
5
6
7
8
9
|
>>> req = Request.blank('/test?check=a&check=b&name=Bob') >>> req.GET MultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')]) >>> req.GET['check'] u'b' >>> req.GET.getall('check') [u'a', u'b'] >>> req.GET.items() [(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')] |
下面这个是比较常见的查看参数的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
>>> req.params NestedMultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob'), (u'name', u'Joe'), (u'email', u'joe@example.com')]) >>> req.params['name'] u'Bob' >>> req.params.getall('name') [u'Bob', u'Joe'] >>> for name, value in req.params.items(): ... print '%s: %r' % (name, value) check: u'a' check: u'b' name: u'Bob' name: u'Joe' email: u'joe@example.com' |
一个把request传递给WSGI应用的例子:
1
2
3
4
5
6
7
8
9
|
from webob import Request req = Request.blank( '/' ) def wsgi_app(environ, start_response): start_response( '200 OK' , [( 'Content-type' , 'text/plain' )]) return [ 'Hi!' ] print req.call_application(wsgi_app) |
输出:
1
2
|
[root@OS_DEV dev]# python webobtest.py ('200 OK', [('Content-type', 'text/plain')], ['Hi!']) |
2.Response
webob.Response包含了标准WSGI response的所有要素。其本身也可以看成是一个WSGI的application。你可以通过req.call_application(res)对其调用。
最简单的例子如下:
1
2
3
4
5
6
7
8
|
>>> from webob import Response >>> res = Response() >>> res.status '200 OK' >>> res.headerlist [('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')] >>> res.body '' |
如何写入body:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
>>> res = Response(content_type='text/plain', charset=None) >>> f = res.body_file >>> f.write('hey') >>> f.write(u'test') Traceback (most recent call last): . . . TypeError: You can only write unicode to Response if charset has been set >>> f.encoding >>> res.charset = 'utf8' >>> f.encoding 'utf8' >>> f.write(u'test') >>> res.app_iter ['', 'hey', 'test'] >>> res.body 'heytest' |
注意下这个例子,这个例子把普通的WSGI的应用通过Request和Response做了一个简单的包装,虽然没有太大的修改,但对于之后使用装饰器的情况来说,是个不错的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
>>> def my_app(environ, start_response): ... req = Request(environ) ... res = Response() ... res.content_type = 'text/plain' ... parts = [] ... for name, value in sorted (req.environ.items()): ... parts.append( '%s: %r' % (name, value)) ... res.body = 'n' .join(parts) ... return res(environ, start_response) >>> req = Request.blank( '/' ) >>> res = req.get_response(my_app) >>> print res 200 OK Content - Type : text / plain; charset = UTF - 8 Content - Length: ... HTTP_HOST: 'localhost:80' PATH_INFO: '/' QUERY_STRING: '' REQUEST_METHOD: 'GET' SCRIPT_NAME: '' SERVER_NAME: 'localhost' SERVER_PORT: '80' SERVER_PROTOCOL: 'HTTP/1.0' wsgi.errors: < open file '<stderr>' , mode 'w' at ...> wsgi. input : <...IO... object at ...> wsgi.multiprocess: False wsgi.multithread: False wsgi.run_once: False wsgi.url_scheme: 'http' wsgi.version: ( 1 , 0 ) |
3.Exceptions
其实就是对HTTP错误代码的一个封装。也可以看成是一个WSGI的应用。
一个简单的例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
>>> from webob.exc import * >>> exc = HTTPTemporaryRedirect(location = 'foo' ) >>> req = Request.blank( '/path/to/something' ) >>> print str (req.get_response(exc)).strip() 307 Temporary Redirect Location: http: / / localhost / path / to / foo Content - Length: 126 Content - Type : text / plain; charset = UTF - 8 307 Temporary Redirect The resource has been moved to http: / / localhost / path / to / foo; you should be redirected automatically. |
4. WSGIfy decorator
结合上面的例子,既然WebOb可以让WSGI的请求变得更加简单、强大,那么能不能不用原始的那种WSGI的参数和返回格式,而全部用WebOb替代?可以的,通过WSGIfy decorator这个装饰器。
比如这个最简单的例子:
1
2
3
|
@wsgify def myfunc(req): return webob.Response( 'hey there' ) |
调用的时候有两个选择:
1
|
app_iter = myfunc(environ, start_response) |
或:
1
|
resp = myfunc(req) |
第一种选择就是最原始和标准的的WSGI格式,第二种选择则是WebOb封装过后的格式。说实话后者看上去更加符合逻辑(给你个请求,给我个响应)。
如果myfanc直接返回一个Exception,那么就会的相当于直接调用Exception这个WebOb的WSGI Application,可以很容易的返回异常页面。
另外也可以对Request进行继承,修改其内容,对真正的Request做一些判断(个人感觉像是在过滤),比如:
1
2
3
4
5
6
7
8
9
10
|
class MyRequest(webob.Request): @property def is_local( self ): return self .remote_addr = = '127.0.0.1' @wsgify (RequestClass = MyRequest) def myfunc(req): if req.is_local: return Response( 'hi!' ) else : raise webob.exc.HTTPForbidden |
需要记住一点:被@wsgify修饰过后的那些func,其Return的是个对Response的调用。
5.总结
总结一下WebOb。既然有人用WebOb,那它必然有它的好用的地方,好用的地方在哪里呢?我个人觉得有两个:一是兼容性好,二是使用简单。
首先先说兼容性吧。之前写过文章介绍过PasteDeploy,后者可以通过标准的配置文件生成WSGI应用。那么通过WebOb写出的WSGI应用是否
可以用在这里呢?答案是可以的,上面的装饰器的例子已经介绍了,经过装饰器装饰后的func可以通过标准的WSGI方法去调用。
然后说说使用上的感觉。简单就是好用。其把WSGI的几个参数、返回的方法都封装成了Reqeust、Response这两个对象,同时还提供了一个好用的Exception对象,就这三个对象,记起来也不难,读起来也一看就知道是啥意思,所以说用起来方便。
个人觉得最有代表的例子是这个,一目了然,使人一读就懂。最神奇的是可以通过WSGI标准对其进行调用,写的时候完全可以忘了WSGI标准是啥,同时还能写出兼容WSGI工具(比如PasteDeploy)的代码,真是不错。
1
2
3
|
@wsgify def myfunc(req): return webob.Response( 'hey there' ) |