在上一篇文章 WSGI 中我们提到的例子都是自己处理请求和响应、提供应用程序入口给服务端调用等。这样对我们学习 WSGI 本身是很好的,但如果我们每次在写应用逻辑的时候还要处理这些东西,就显得没有必要了,而且我们的处理方式不一定正确。这时 Webob 这类包的出现就很有意义了——让我们专注应用逻辑。
Webob 也是 OpenStack 里边用的比较多的包。本文主要介绍其 Request、Response、异常(webob.exc)及 WSGIfy 装饰器(webob.dec.wsgify)。
基本对象
Request
基本概念
官方对 Request 有一个简要的定义:
The request object is a wrapper around the WSGI environ dictionary.
也即,Request 是对 WSGI 中环境变量 environ 的一个封装。我们可以通过 req.environ 查看这些环境变量:
1 | from webob import Request |
除此之外,它还有几个比较有用的成员变量:
- req.method:如 GET、POST 等。
- req.GET:包含查询字符串(query string)中的所有变量。注意要和 req.method 中的 GET 区分开来。
- req.POST:包含请求主体(body)中的所有变量。这里只有当请求是 POST 并且是表单提交时才有值。
- req.params:包含 req.GET 和 req.POST 的所有内容。
- req.body:请求的主体(string)。这对请求是 POST 但不是表单提交,或当请求是 PUT 时,是比较有用的。也可以通过 req.body_file 获得一个类似文件的对象。
- req.cookies:Cookies。
- req.headers:请求头部,注意这里大小写敏感。
- req.urlvars 和 req.urlargs:req.urlvars 是请求 url 中的关键字参数,req.urlargs 是位置参数(positional parameters)。它们由 Routes 和 Selector 这类工具来设定。
传递给 WSGI 应用
下边的例子摘自官方样例:
1 | '/') req = Request.blank( |
Response
基本概念
Response 跟 Request 很相似,虽然有一些不同点。Request 是对 environ 的封装;Response 则有 3 个根本的部分(基于 WSGI):
response.status
:对应 WSGI 中 start_response 函数中的 status 参数response.headerlist
:对应 WSGI 中 start_response 函数中的 response_headers 参数response.app_iter
:对应应用程序返回值(body),需为可迭代
对象。可通过 response.body、response.unicode_body 或 response.body_file 获取。
我们看下例子:
1 | from webob import Response |
Response 也是一个 WSGI 应用
Response 本身也是一个 WSGI 应用,通过源码就可以看得出来:
1 | class Response(object): |
所以我们用 Request 来直接调用 Response 对象:
1 | req = Request.blank('/') |
官方提供了一个样例如下:
1 | def my_app(environ, start_response): |
异常
Webob 异常(web.exc.WSGIHTTPException)本身既是一个 WSGI 应用也是一个 Response 对象,源码如下:
1 | class WSGIHTTPException(Response, HTTPException): |
常见的异常如下:
1 | Exception |
WSGIfy 装饰器
WSGIfy
相比较前文中的 Request、Response,个人感觉 wsgify 的作用显得更大,它让我们不用再多考虑 WSGI 接口,而是专注应用逻辑。例如,现在我们写一个 WSGI 应用就很简单了:
1 | # -*- coding: utf-8 -*- |
另外,我们还可以对 wsgify 装饰器进行定制,如官网提供的样例:
1 | class MyRequest(webob.Request): |
实现原理
对于装饰器基础,可参考之前博文【译】 Python 基础知识:装饰器(Decorator)。这里我们主要来看一下 wsgify 这个类是如何装饰函数的。直接看代码吧:
1 | # webob/dec.py |
上边的 self.__call__ 函数是核心,除了表明该类 wsgify 可调用,还包装调用了真正的应用(第 35~43 行)。上边代码中的 resp 变量本身是一个 Response 对象,同时也是一个应用,所以最后传入 environ 和 start_response 向服务器返回结果。
wsgify.middleware
wsgify 除了作为一个装饰器,它还提供了中间件装饰器。如官方提供的一个样例(简单改造过):
1 | import webob |
对于实现原理,理解起来有点绕。我们先来看一下 middleware 这个函数:
1 | # webob/dec.py |
其中,_MiddlewareFactory 定义如下:
1 | # webob/dec.py |
这里,我们本小结开头的中间件样例为例,来说明 wsgify.middleware 如何工作。
- 初始化 restrict_ip 函数:restrict_ip = wsgify.middleware(restrict_ip),返回一个 _MiddlewareFactory 对象。
- 初始化 app 函数:app = wsgify(app),返回一个
应用 wsgify 对象
。 - wrapped = restrict_ip(app, ips=[‘127.0.0.1’]):调用 _MiddlewareFactory 对象(__call__),将 app 和 ips 传入该对象 __call__(self, app=None, **config) 函数,因为此时 _MiddlewareFactory 对象中的 self.wrapper_class 就是 wsgify,所以最终重新调用 wsgify.middleware。需要注意的是,此时传入该函数的参数跟之前的不一样,param:middle_func 还是 restrict_ip,但 param:app 和 param:kw 已经不为 None,分别为 app 和 ips。
- 最后 wsgify.middleware 返回 cls(middle_func, middleware_wraps=app, kwargs=kw),也即
中间件 wsgify 对象
,但需要注意的是此时 middleware_wraps 和 kwargs 已经不为 None。 - 当有请求到达的时候,服务端开始调用(详细过程请参考之前博文 WSGIRef)该
中间件 wsgify 对象
,进入该对象的 __call__(self, req, *args, **kw) 函数,此时重点的地方在于以下: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
32
33
34
35
36
37
38
39
40
41
42
43# webob/dec.py
class wsgify(object):
......
def __call__(self, req, *args, **kw):
"""Call this as a WSGI application or with a request"""
......
if isinstance(req, dict):
......
try:
args = self.args
if self.middleware_wraps:
args = (self.middleware_wraps,) + args # 此时 args = (app,),self.kwargs = ips
resp = self.call_func(req, *args, **self.kwargs)
|
|
def call_func(self, req, *args, **kwargs):
"""Call the wrapped function; override this in a subclass to
change how the function is called."""
return self.func(req, *args, **kwargs)
|
|
# 中间件函数(不是应用,单纯地执行函数)
def restrict_ip(req, app, ips):
if req.remote_addr not in ips:
raise webob.exc.HTTPForbidden('Bad IP: %s' % req.remote_addr)
return app
|
|
# app 应用(wsgify 对象,注意跟中间件 wsgify 对象区分开来)
def app(req):
return 'hi'
# 接下来的执行就比较简单了,调用 wsgify 对象的 __call__ 函数,最终返回 app 处理结果。注意,此时的 wsgify 对象的 self.middleware_wraps 为 None,因为 app 已经是最后一个应用。