flask源码分析篇

来源:互联网 发布:神话软件怎么用 编辑:程序博客网 时间:2024/05/21 00:19
* flask 爬坑** 、flask 的的post方法中, 目前发现3种 得到参数的方法:<span style="white-space:pre"></span>1.1 request.data , 发送的参数采用的是 json数据格式,<span style="white-space:pre"></span>但是request.data是一个json格式的字符串, 需要json.loads之后就便成了<span style="white-space:pre"></span>object。<span style="white-space:pre"></span>1.2 requet.form, 发送的参数是form-encoded data。<span style="white-space:pre"></span>1.2.1 requests 库的post方法,如果直接是data={'':''}这种参数,那么req<span style="white-space:pre"></span>uests是当做form-encoded 来处理的。<span style="white-space:pre"></span>1.2.2 requests 支持的 post下的json参数 有两种方法,<span style="white-space:pre"></span>    其一是 : data = json.dumps({'':''})<span style="white-space:pre"></span>其二是 : json = {'': ''}<span style="white-space:pre"></span>1.2.3 post文件上传<span style="white-space:pre"></span>。。。。。。<span style="white-space:pre"></span>1.3*** flask 分析1) flask 中的MethodVIew flask的MethodView 中的as_view中的endpoint在flask-restful中,如果没有传入, 那么就会使用func.__name__值来作为默认值2) flask View模块分析   flask的用户使用的view一般有两个,一个是基本的View类,这个类是flask中最基本的   视图类,不管是flask_restful中的Resource还是扩展类blueprint 和method 都是继    承这个View来实现的。   View类中实现了两个方法,第一个是dispatch_request方法,该方法需要子类去实现。   第二个是类方法,as_view静态方法必须要传入 name字段也就是endpoint, 而且不能出  现重复.   #+ATTR_HTML: textarea t :width 40   #+BEGIN_EXAMPLE       def as_view(cls, name, *class_args, **class_kwargs):        """Converts the class into an actual view function that can be used        with the routing system.  Internally this generates a function on the        fly which will instantiate the :class:`View` on each request and call        the :meth:`dispatch_request` method on it.        The arguments passed to :meth:`as_view` are forwarded to the        constructor of the class.        """        def view(*args, **kwargs):            self = view.view_class(*class_args, **class_kwargs)            return self.dispatch_request(*args, **kwargs)        if cls.decorators:            view.__name__ = name            view.__module__ = cls.__module__            for decorator in cls.decorators:                view = decorator(view)        # We attach the view class to the view function for two reasons:        # first of all it allows us to easily figure out what class-based        # view this thing came from, secondly it's also used for instantiating        # the view class so you can actually replace it with something else        # for testing purposes and debugging.        view.view_class = cls        view.__name__ = name        view.__doc__ = cls.__doc__        view.__module__ = cls.__module__        view.methods = cls.methods        return view   #+END_EXAMPLE   as_view中的代码如上,定义函数view,其中的view_class 被定义为类自身,每一次一个请求h自行时,调用view函数执行,  实例化一个view_class, 去执行相应的功能。在这里还有一点需要注意的是,view.view_class(*args, **kw)执行时,  在我们平时的应用中args 和 kw 其实都是没有值传入的,在flask-restful中,  通过http://flask-restful-cn.readthedocs.io/en/0.3.5/intermediate-usage.html#full-parameter-parsing-example  我们可以看到class_kwargs是通过重载__init__来实现的,并且参数被传入到**class_kwargs中,所以view_class能正常运行。  否则的话,如果没有重写__init__方法,view.view_class(*args, **kw)是没法正常运行的。  + MethodView代码分析。    首先第一点要理解的便是with_metaclass()函数的用法,这是兼容python2和python3的一种高级  实现。    #+ATTR_HTML: textarea t :width 40    #+BEGIN_EXAMPLE       def with_metaclass(meta, *bases):    """Create a base class with a metaclass."""    # This requires a bit of explanation: the basic idea is to make a    # dummy metaclass for one level of class instantiation that replaces    # itself with the actual metaclass.    class metaclass(type):        def __new__(cls, name, this_bases, d):            return meta(name, bases, d)    return type.__new__(metaclass, 'temporary_class', (), {})     网上还有一种流传的写法,说是代码更好:    def with_metaclass(meta, *bases):    """Create a base class with a metaclass."""    # This requires a bit of explanation: the basic idea is to make a    # dummy metaclass for one level of class instantiation that replaces    # itself with the actual metaclass.    class metaclass(type):        __call__ = type.__call__        __init__ = type.__init__        def __new__(cls, name, this_bases, d):            if this_base is None:                return type('temporary_class', None, {})            return meta(name, bases, d)    return metaclass('temporary_class', None, {})    #+END_EXAMPLE  本人倒是觉得第二种代码多执行了几条语句,在本质上没有什么区别,这里有一点让我折腾了  不少日子。type('tmp_class', (), {}) 实例化一个对象,然而这个对象和其他的对象不一样  它是一个类,我们观察它的属性,发现这个对象的__new__是一个<built-in method __new__ of type object at 0x8fb760>,    这个字符串非常难以让人理解,是一个内置的method, 但是党我们打印type.__new__时,这两个值也是不一样的。    #+ATTR_HTML: textarea t :width 40    #+BEGIN_EXAMPLE    class A(type):    def __new__(cls, *args, **kw):        return type.__new__(cls, *args, **kw)    @classmethod    def opp(cls):        print('aaaa')    a = A('tmpclass', (), {})    a.opp()  print(a.__new__)    print(dir(a))    print(a.__dict__)    #+END_EXAMPLE    党我们使用这段小代码去测试的时候,我们可以发现,在a的属性中没法找到opp的值,可是使用a.opp()去访问的时候  却可以正常访问,说明了python中。类属性的查找满足我在python笔记中中 《python 对象模型中》 中记录的属性查找原则。     回归正题,继续回到MethodView类,通过使用元类来更新cls.methods的值,在我们平常的应用中,  我们也应该多重载methods方法。methods = sorted(['GET', 'POST', 'DELETE'])。 至于为什么要排序。 目前还不知道原因。**** flask.app analyze     1) app模块中包含了flask的比较重要的模块,比如今天研究的Flask。也是整个flask的发动机。目前我们使用比较多的可能        是add_url_rule function。整个函数中比较重要的就是endpoint, view_function, methods。这三个关键的member.        当我们使用methodview时,我们在元类构造时候发现,会将endpoint作为__name__属性, 这也使得我们在methodview的构造中,        需要去设置__name__的属性。而这也是as_view的作用。     2) wsgi_app 核心function        在我们第五章的response 和request的分析中,我们实验了一个代码, werkzeug.run_simple服务接受的其实就是一个 可以处理environ, start_response参数的        可执行的对象,在flask中我们常常使用app =Flask()作为application参数传入。也就是说callable(app) == True。然后我们分析Flask类的__call__方法。        #+ATTR_HTML :textarea :width 40            def __call__(self, environ, start_response):                """Shortcut for :attr:`wsgi_app`."""                return self.wsgi_app(environ, start_response)        #+END_EXAMPLE        核心代码落入self.wsgi_app的分析上,首先是第一行代码self.request_context(environ)。可以说现在我们现在终于进入flask的世界。        第一步request_context func 构造了一个所谓的请求上下文的object,可以看出每一次请求都会产生一个新的请求上下文。        让我们来好好看看这个所谓的请求上下文是个什么鬼吧:        #+ATTR_HTML :textarea :width 40        class RequestContext(object):            """The request context contains all request relevant information.  It is            created at the beginning of the request and pushed to the            `_request_ctx_stack` and removed at the end of it.  It will create the            URL adapter and request object for the WSGI environment provided.                    Do not attempt to use this class directly, instead use            :meth:`~flask.Flask.test_request_context` and            :meth:`~flask.Flask.request_context` to create this object.                    When the request context is popped, it will evaluate all the            functions registered on the application for teardown execution            (:meth:`~flask.Flask.teardown_request`).                    The request context is automatically popped at the end of the request            for you.  In debug mode the request context is kept around if            exceptions happen so that interactive debuggers have a chance to            introspect the data.  With 0.4 this can also be forced for requests            that did not fail and outside of ``DEBUG`` mode.  By setting            ``'flask._preserve_context'`` to ``True`` on the WSGI environment the            context will not pop itself at the end of the request.  This is used by            the :meth:`~flask.Flask.test_client` for example to implement the            deferred cleanup functionality.                    You might find this helpful for unittests where you need the            information from the context local around for a little longer.  Make            sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in            that situation, otherwise your unittests will leak memory.            """                    def __init__(self, app, environ, request=None):                self.app = app                if request is None:                    request = app.request_class(environ)                self.request = request                self.url_adapter = app.create_url_adapter(self.request)                self.flashes = None                self.session = None                        # Request contexts can be pushed multiple times and interleaved with                # other request contexts.  Now only if the last level is popped we                # get rid of them.  Additionally if an application context is missing                # one is created implicitly so for each level we add this information                self._implicit_app_ctx_stack = []                        # indicator if the context was preserved.  Next time another context                # is pushed the preserved context is popped.                self.preserved = False                        # remembers the exception for pop if there is one in case the context                # preservation kicks in.                self._preserved_exc = None                        # Functions that should be executed after the request on the response                # object.  These will be called before the regular "after_request"                # functions.                self._after_request_functions = []                        self.match_request()                    def _get_g(self):                return _app_ctx_stack.top.g            def _set_g(self, value):                _app_ctx_stack.top.g = value            g = property(_get_g, _set_g)            del _get_g, _set_g                    def copy(self):                """Creates a copy of this request context with the same request object.                This can be used to move a request context to a different greenlet.                Because the actual request object is the same this cannot be used to                move a request context to a different thread unless access to the                request object is locked.                        .. versionadded:: 0.10                """                return self.__class__(self.app,                    environ=self.request.environ,                    request=self.request                )                    def match_request(self):                """Can be overridden by a subclass to hook into the matching                of the request.                """                try:                    url_rule, self.request.view_args = \                        self.url_adapter.match(return_rule=True)                    self.request.url_rule = url_rule                except HTTPException as e:                    self.request.routing_exception = e                    def push(self):                """Binds the request context to the current context."""                # If an exception occurs in debug mode or if context preservation is                # activated under exception situations exactly one context stays                # on the stack.  The rationale is that you want to access that                # information under debug situations.  However if someone forgets to                # pop that context again we want to make sure that on the next push                # it's invalidated, otherwise we run at risk that something leaks                # memory.  This is usually only a problem in test suite since this                # functionality is not active in production environments.                top = _request_ctx_stack.top                if top is not None and top.preserved:                    top.pop(top._preserved_exc)                        # Before we push the request context we have to ensure that there                # is an application context.                app_ctx = _app_ctx_stack.top                if app_ctx is None or app_ctx.app != self.app:                    app_ctx = self.app.app_context()                    app_ctx.push()                    self._implicit_app_ctx_stack.append(app_ctx)                else:                    self._implicit_app_ctx_stack.append(None)                        if hasattr(sys, 'exc_clear'):                    sys.exc_clear()                        _request_ctx_stack.push(self)                        # Open the session at the moment that the request context is                # available. This allows a custom open_session method to use the                # request context (e.g. code that access database information                # stored on `g` instead of the appcontext).                self.session = self.app.open_session(self.request)                if self.session is None:                    self.session = self.app.make_null_session()                    def pop(self, exc=_sentinel):                """Pops the request context and unbinds it by doing that.  This will                also trigger the execution of functions registered by the                :meth:`~flask.Flask.teardown_request` decorator.                        .. versionchanged:: 0.9                   Added the `exc` argument.                """                app_ctx = self._implicit_app_ctx_stack.pop()                        try:                    clear_request = False                    if not self._implicit_app_ctx_stack:                        self.preserved = False                        self._preserved_exc = None                        if exc is _sentinel:                            exc = sys.exc_info()[1]                        self.app.do_teardown_request(exc)                                # If this interpreter supports clearing the exception information                        # we do that now.  This will only go into effect on Python 2.x,                        # on 3.x it disappears automatically at the end of the exception                        # stack.                        if hasattr(sys, 'exc_clear'):                            sys.exc_clear()                                request_close = getattr(self.request, 'close', None)                        if request_close is not None:                            request_close()                        clear_request = True                finally:                    rv = _request_ctx_stack.pop()                            # get rid of circular dependencies at the end of the request                    # so that we don't require the GC to be active.                    if clear_request:                        rv.request.environ['werkzeug.request'] = None                            # Get rid of the app as well if necessary.                    if app_ctx is not None:                        app_ctx.pop(exc)                            assert rv is self, 'Popped wrong request context.  ' \                        '(%r instead of %r)' % (rv, self)                    def auto_pop(self, exc):                if self.request.environ.get('flask._preserve_context') or \                   (exc is not None and self.app.preserve_context_on_exception):                    self.preserved = True                    self._preserved_exc = exc                else:                    self.pop(exc)                    def __enter__(self):                self.push()                return self                    def __exit__(self, exc_type, exc_value, tb):                # do not pop the request stack if we are in debug mode and an                # exception happened.  This will allow the debugger to still                # access the request object in the interactive shell.  Furthermore                # the context can be force kept alive for the test client.                # See flask.testing for how this works.                self.auto_pop(exc_value)                        if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:                    reraise(exc_type, exc_value, tb)                    def __repr__(self):                return '<%s \'%s\' [%s] of %s>' % (                    self.__class__.__name__,                    self.request.url,                    self.request.method,                    self.app.name,                )                #+END_EXAMPLE        首先来看一下requestContext类的request member。会发现这是由app.request_class构造。        在第六节的代码中,我们知道werkzeug的response对象可以处理environ, 再去观察flask.wrappers中的request类,        发现继承自werkzeug.wrappers.request。继续回到wsai_app func上来, ctx.push(),这个方法中使用了非常重要的        结构, _request_ctx_stack 这个全局的 请求上下文栈。使用werkzeug实现的LocalStack。        为了理解这个LocalStack的作用以及具体细节,我写了如下代码进行测试        #+ATTR_HTML :textarea :width 40            @app.route('/tt')            def view_func(*agrs, **kw):                print 'get into view'                print _request_ctx_stack._local.stack                print _request_ctx_stack._local.__storage__                # print len(_request_ctx_stack._local.stack)                # print _request_ctx_stack._local                time.sleep(10)                return jsonify({'code': 10000})        #+END_EXAMPLE        我们在不同的浏览器中去打印len(_request_ctx_stack._local.stack)的时候,我们会发现所有的值都是1,        同时我们发现在一个应用中,_request_ctx_stack是一个在所有线程或者协程之间共享的全局变量,        但是它的属性_local.stack打印出来的却不一样,这是一段 magical code !重点在与self._loacl 是        werkzeug.Local实现的,werkzeug.Local 重载了__setattr__ 和 __getattr__ 方法,所有对self._local的        操作都会转发到werkzeug.Local的__getattr__中, werkzeug.Local是线程隔离的,所以我们会看到如何下结果         {<greenlet.greenlet object at 0x7fe3f703bb90>: {'stack': [<RequestContext 'http://127.0.0.1:5000/tt' [GET] of app>]},         <greenlet.greenlet object at 0x7fe3f703ba50>: {'stack': [<RequestContext 'http://127.0.0.1:5000/tt' [GET] of app>]}} =========>>>>至于请求上下文为什么要使用栈结构,没有找到原因!***** flask.globals analyze  1) 该模块主要的属性        _request_ctx_stack = LocalStack()        _app_ctx_stack = LocalStack()        current_app = LocalProxy(_find_app)        request = LocalProxy(partial(_lookup_req_object, 'request'))        session = LocalProxy(partial(_lookup_req_object, 'session'))        g = LocalProxy(partial(_lookup_app_object, 'g'))        flask的request_context在request开始的时候push,然后在结束的时候pop,        这样如果出现错误信息,那么我们就没法进行调试,获取到重要的错误信息,        flask中有这样一个配置, PRESERVE_CONTEXT_ON_EXCEPTION 。文档描述如下:            Starting with Flask 0.7 you have finer control over that behavior by setting the        PRESERVE_CONTEXT_ON_EXCEPTION configuration variable. By default it’s linked to the setting of DEBUG.        If the application is in debug mode the context is preserved, in production mode it’s not.            Do not force activate PRESERVE_CONTEXT_ON_EXCEPTION in production mode as it will cause your application to        leak memory on exceptions. However it can be useful during development to get the same error preserving        behavior as in development mode when attempting to debug an error that only occurs under production settings.  2) LocalStack 和 LocalProxy 的分析     首先这里有一片干货文章。     http://www.15yan.com/story/j7BfM4NHEI9/     通过分析werkzeug.local代码,可以看出werkzueg支持greenlet 和多线程, 如果安装了greenlet,     则优先使用greenlet。首先来看核心Loal类     #+ATTR_HTML: textarea :width 40     class Local(object):         __slots__ = ('__storage__', '__ident_func__')         def __init__(self):             object.__setattr__(self, '__storage__', {})             object.__setattr__(self, '__ident_func__', get_ident)         def __iter__(self):             return iter(self.__storage__.items())         def __call__(self, proxy):         """Create a proxy for a name."""             return LocalProxy(self, proxy)         def __release_local__(self):             self.__storage__.pop(self.__ident_func__(), None)         def __getattr__(self, name):             try:                 return self.__storage__[self.__ident_func__()][name]             except KeyError:                 raise AttributeError(name)         def __setattr__(self, name, value):             ident = self.__ident_func__()             storage = self.__storage__             try:                storage[ident][name] = value              except KeyError:                 storage[ident] = {name: value}         def __delattr__(self, name):         try:         del self.__storage__[self.__ident_func__()][name]         except KeyError:         raise AttributeError(name)     #+END_EXAMPLE     首先可以学习的地方是object.__setattr__来增加属性,应该比直接使用setattr要规范好一点。     多使用__slots__可以极大的优化实例的大小,具体可参考python cookbook。首先__storage__是一个字典,     根据__getattr__可以看出key是协程或者线程的id, 根据__setattr__可以看出value就是{name:value},     每一个协程对应的只有一个属性。意思就是len(self.__storage__[ident].key) == 1 => True。      在werkzeug代码中,我们会看到很多下面这样的代码:     #+ATTR_HTML: textarea :width 40    def __init__(self, local, name=None):     def __init__(self):        object.__setattr__(self, '_LocalProxy__local', local)        object.__setattr__(self, '__name__', name)        if callable(local) and not hasattr(local, '__release_local__'):            # "local" is a callable that is not an instance of Local or            # LocalManager: mark it as a wrapped function.            object.__setattr__(self, '__wrapped__', local)     #+END_EXAMPLE     #+ATTR_HTML: textarea :width 40    def __init__(self, local, name=None):     __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)     #+END_EXAMPLE     上面写的object.__setattr__只是规范一点的说话很傻逼, 真是的原因是,当我们在一个类中已经实现了__setattr__的时候     而且我们使用了self.k = v 这种玩意的时候,如果我们在__init__方法中使用setattr的时候,就是出错,因为自己调用了自己,     形成了一个死递归调用。用原生的object.__setattr__就避免这个问题.     ****** werkzeug中的Request 和Response模块的学习     首先还是先上代码:     #+ATTR_HTML :textarea :width 40     from werkzeug.wrappers import Request, Response     from werkzeug.serving import run_simple     def call(env, start_response):         req = Request(env)         print dir(env)         print env.values(), '\n'         print env.viewvalues()         return Response('hello')(env, start_response)     run_simple('localhost', 5000, call,                    use_reloader=True, use_debugger=True, use_evalex=True)     #+END_EXAMPLE    通过一些打印和输出信息,和源码分析,我们可以看出werkzeug的Resquest和Response都接受env来    作为构造参数,env里面包含了大量的浏览器请求信息,比如env.viewvalues列举了所有的浏览器中显示的    request——headers信息。    
0 0
原创粉丝点击