10. webob库简单分析

首先来看一个例子。

 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
44
45
46
47
48
#coding:utf-8
from webob import Request
from webob import Response
import webob.dec

"""
经测试,使用wsgify包装的wsgi app, 可以返回另一个wsgi app,
然后req请求交给返回的app处理!返回的wsgi app可以是通过原
始的方式定义的(接收env, start_response参数)。也可以是使用
wsgify包装的!

如该例子中,test_app 通过wsgify包装,成为一个wsgi app,然后他
返回另一个wsgi app:simple_app_2,webob库保证接下来会调用
simple_app_2,并把它返回的结果发送给web server!这样的好处
是对于一个请求,能通过链式的方式一次让每一个app依次处理。

这种链式处理的方式,在OpenStack REST-API中使用得非常频繁。

通过(env, start_response)参数定义的wsgi app不具备该功能。
"""

@webob.dec.wsgify
def test_app(req):
    print "call test_app"
    print "----:%s"%simple_app_2
    return simple_app_2
    # 返回simple_app也是可以的。webob会把simple_app的结果发送给web_server!
    #return simple_app

#test_app = wsgify(test_app)
#test_app(evn, start_response)

@webob.dec.wsgify
def simple_app_2(req):
    print "call app2"
    return webob.Response("hello, app2!\n")

#simple_app_2 = wsgify(simple_app_2)

# 通过标准方式定义的wsgi app
def simple_app(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n', "return test\n"]

httpd = make_server('0.0.0.0', 9999, test_app)
httpd.serve_forever()

利用wsgiref作为web server, test_app 作为wsgi app启动服务。 服务器会收到请求后,会调用test_app.

res = test_app(evn, start_response)

test_app实际上此时是一个wsgify对象,因此会执行wsgify.__call__函数,现在来分析 该核心函数!

wsgify核心代码分析:

  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
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
class wsgify(object):
    """Turns a request-taking, response-returning function into a WSGI
    app
    You can use this like::
        @wsgify
        def myfunc(req):
            return webob.Response('hey there')
    With that ``myfunc`` will be a WSGI application, callable like
    ``app_iter = myfunc(environ, start_response)``.  You can also call
    it like normal, e.g., ``resp = myfunc(req)``.  (You can also wrap
    methods, like ``def myfunc(self, req)``.)
    If you raise exceptions from :mod:`webob.exc` they will be turned
    into WSGI responses.
    There are also several parameters you can use to customize the
    decorator.  Most notably, you can use a :class:`webob.Request`
    subclass, like::
        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
    Another customization you can add is to add `args` (positional
    arguments) or `kwargs` (of course, keyword arguments).  While
    generally not that useful, you can use this to create multiple
    WSGI apps from one function, like::
        import simplejson
        def serve_json(req, json_obj):
            return Response(json.dumps(json_obj),
                            content_type='application/json')
        serve_ob1 = wsgify(serve_json, args=(ob1,))
        serve_ob2 = wsgify(serve_json, args=(ob2,))
    You can return several things from a function:
    * A :class:`webob.Response` object (or subclass)
    * *Any* WSGI application
    * None, and then ``req.response`` will be used (a pre-instantiated
      Response object)
    * A string, which will be written to ``req.response`` and then that
      response will be used.
    * Raise an exception from :mod:`webob.exc`
    Also see :func:`wsgify.middleware` for a way to make middleware.
    You can also subclass this decorator; the most useful things to do
    in a subclass would be to change `RequestClass` or override
    `call_func` (e.g., to add ``req.urlvars`` as keyword arguments to
    the function).
    """

    RequestClass = Request

    def __init__(self, func=None, RequestClass=None,
                 args=(), kwargs=None, middleware_wraps=None):
        # self.func 赋值为 test_app
        self.func = func
        if (RequestClass is not None
            and RequestClass is not self.RequestClass):
            self.RequestClass = RequestClass
        self.args = tuple(args)
        if kwargs is None:
            kwargs = {}
        self.kwargs = kwargs
        self.middleware_wraps = middleware_wraps

    def __repr__(self):
        return '<%s at %s wrapping %r>' % (self.__class__.__name__,
                                           id(self), self.func)

    def __get__(self, obj, type=None):
        # This handles wrapping methods
        if hasattr(self.func, '__get__'):
            return self.clone(self.func.__get__(obj, type))
        else:
            return self

    def __call__(self, req, *args, **kw):
        """Call this as a WSGI application or with a request"""
        # test_app(evn, start_response)
        # 因此req = env, args = (start_response, ), kw = {}
        func = self.func
        if func is None:
            if args or kw:
                raise TypeError(
                    "Unbound %s can only be called with the function it "
                    "will wrap" % self.__class__.__name__)
            func = req
            return self.clone(func)
        if isinstance(req, dict):
            if len(args) != 1 or kw:
                raise TypeError(
                    "Calling %r as a WSGI app with the wrong signature" %
                    self.func)
            environ = req
            start_response = args[0]
            # 根据env 生产req 对象
            req = self.RequestClass(environ)
            req.response = req.ResponseClass()
            try:
                args = self.args
                if self.middleware_wraps:
                    args = (self.middleware_wraps,) + args
                # 调用实际的包装函数,这里为test_app
                # test_app(req, *(), **{})
                # 返回simple_app_2, resp 为simple_app_2
                resp = self.call_func(req, *args, **self.kwargs)
            except HTTPException as exc:
                resp = exc
            if resp is None:
                ## FIXME: I'm not sure what this should be?
                resp = req.response
            if isinstance(resp, text_type):
                resp = bytes_(resp, req.charset)
            if isinstance(resp, bytes):
                body = resp
                resp = req.response
                resp.write(body)
            if resp is not req.response:
                resp = req.response.merge_cookies(resp)
            # 调用simple_app_2(environ, start_response);
            # simple_app_2也是一个wsgify 对象,从而引起simple_app_2.__call__函数调用。
            # 从而实现链式处理!
            return resp(environ, start_response)
        else:
            if self.middleware_wraps:
                args = (self.middleware_wraps,) + args
            return self.func(req, *args, **kw)

web server将http请求转发给test_app(evn, start_response), 会引起test_app.__call__函数调用。

  • 82行:记录包装的函数;
  • 90行:初次请求,req 实际上env, 是一个dict;
  • 96行:获取web server 提供的start_response 参数;
  • 107行:调用包装的函数,就是原始的test_app,执行完成后,resp = simple_app_2;
  • 124行:继续调用simple_app_2 对象!又会引起下一轮simple_app_2.__call__调用。

在simple_app_2.__call__调用中: 在107行处获取最终的结果:webob.Response 对象。 最后还是会执行第24行,Response class定义了__call__函数! 待后面分析。