Flask0.1源码剖析

来源:互联网 发布:淘宝推广大师破解版 编辑:程序博客网 时间:2024/05/18 08:32

前言

平时都是看别人的剖析代码,今天也来尝试剖析一下Flask的源码,加深对Flask的理解。下面的分析,全部基于Flask-0.1,0.1版本的代码加上注释也仅有不到700行,但麻雀虽小,五脏俱全,也足以缕清Flask的脉络。

知识准备

在开始解析之前,读者最好对web server, WSGI和web框架的功能有一个基本的了解。

WSGI

WSGI是一个web应用和服务器通信的协议,web应用可以通过WSGI一起工作。关于WSGI可以参考我的上一篇博客:web server, WSGI和web framework。

Werkzeug

Werkzeug是一个WSGI工具包。

Jinjia2

Jinja2是一个功能齐全的模板引擎。它有完整的unicode支持,一个可选的集成沙箱执行环境,被广泛使用。

Flask与jinja2、Werkzeug

Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架。这里“微”的含义体现在,Flask本身只是Werkzeug和Jinja2的之间的桥梁,前者实现一个合适的WSGI应用,后者处理模板。Flask也绑定了一些通用的标准库包,比如logging。除此之外其它所有一切都交给扩展来实现。

对于Werkzeug,它只是工具包,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理。将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。

python的web框架原理

源码剖析

从Hello, World开始

Flask的使用非常简单,

from flask import Flaskapp = Flask(__name__)@app.route("/")def hello():    return "Hello World!"if __name__ == "__main__":    app.run()

每当我们需要创建一个flask应用时,我们都会创建一个Flask对象:
app = Flask(__name__)。app是Flask一个对象,而run()是该对象的一个方法。我们先简单的认为定义了一个类,然后实例化这个类并调用该类的一个方法。

__init__()

下面看一下Flask对象的init方法,如果不考虑jinjia2相关,核心成员就下面几个:

    def __init__(self, package_name):        self.debug = False        self.package_name = package_name        self.root_path = _get_package_path(self.package_name)        self.view_functions = {}        self.error_handlers = {}        self.before_request_funcs = []        self.after_request_funcs = []        self.url_map = Map()

我们重点关注后面5个成员的作用:
1. view_functions中保存了视图函数,处理用户请求的函数,如上面的hello()
2. error_handlers中保存了错误处理函数
3. before_request_funcs 保存了请求的预处理函数
4. after_request_funcs 保存请求后处理函数。
5. url_map用以保存URI到视图函数的映射,即保存app.route()这个装饰器的信息。

route

view_fuctions

self.view_fuctions是在调用route()装饰器的时候被赋值的,保存了视图函数名到函数体的映射。

    def route(self, rule, **options):        """        def decorator(f):            self.add_url_rule(rule, f.__name__, **options)            self.view_functions[f.__name__] = f            return f        return decorator

url_map

在route()中,还调用add_url_rule()来给self.url_map赋值,用以保存URI到视图函数的名映射。

    def add_url_rule(self, rule, endpoint, **options):        options['endpoint'] = endpoint        options.setdefault('methods', ('GET',))        self.url_map.add(Rule(rule, **options))

利用self.url_mapself.view_functions这两个字典,就可以通过URL找到匹配的处理函数。

run()

上面说到的是初始化部分,下面看一下执行部分。

def run(self, host='localhost', port=5000, **options):    from werkzeug import run_simple    if 'debug' in options:        self.debug = options.pop('debug')    options.setdefault('use_reloader', self.debug)    options.setdefault('use_debugger', self.debug)    return run_simple(host, port, self, **options)

当我们执行app.run()时,调用堆栈如下:

app.run()    run_simple(host, port, self, **options)        __call__(self, environ, start_response)            wsgi_app(self, environ, start_response)

run_simple是Werkzeug中的一个方法,这里调用它来启动了一个WSGI server。

wsgi_app

wsgi_app是flask核心:

def wsgi_app(self, environ, start_response):    with self.request_context(environ):        rv = self.preprocess_request()        if rv is None:            rv = self.dispatch_request()        response = self.make_response(rv)        response = self.process_response(response)        return response(environ, start_response)

代码正常执行的函数调用顺序是:dispatch_request (->match_request) -> make_response -> process_response。

request_context

    def request_context(self, environ):        return _RequestContext(self, environ)

这里参数environ是一个WSGI的环境,返回的是一个_RequestContext对象。_RequestContext这个类的定义如下:

class _RequestContext(object):    def __init__(self, app, environ):        self.app = app        self.url_adapter = app.url_map.bind_to_environ(environ)        self.request = app.request_class(environ)        self.session = app.open_session(self.request)        self.g = _RequestGlobals()        self.flashes = None    def __enter__(self):        _request_ctx_stack.push(self)    def __exit__(self, exc_type, exc_value, tb):        if tb is None or not self.app.debug:            _request_ctx_stack.pop()

RequestContext即请求上下文,内部保存着几个变量,app为当前程序实例,request为请求对象,session为会话对象。RequestContext重写了__enter____exit__函数,将自己放入了全局变量_request_ctx_stack的一个栈中。说明,在Flask对象调用run()之后,每当有请求进入时,都会将请求上下文入栈;在处理完请求后,再出栈。

dispatch_request

先看一下dispatch_request函数的实现,这里有flask的错误处理逻辑:

def dispatch_request(self):    try:        endpoint, values = self.match_request()        return self.view_functions[endpoint](**values)    except HTTPException, e:        handler = self.error_handlers.get(e.code)        if handler is None:            return e        return handler(e)    except Exception, e:        handler = self.error_handlers.get(500)        if self.debug or handler is None:            raise        return handler(e)

如果出现错误,则根据相应的error code,调用不同的错误处理函数。如果正常,dispatch_request返回的self.view_functions[endpoint](**values)就是处理请求的视图函数。

match_request

    def match_request(self):        """Matches the current request against the URL map and also        stores the endpoint and view arguments on the request object        is successful, otherwise the exception is stored.        """        rv = _request_ctx_stack.top.url_adapter.match()        request.endpoint, request.view_args = rv        return rv

_request_ctx_stack是一个全局变量,它的作用后面细说。

make_response

rv就是视图函数的返回值,作为参数传给了make_response。
make_response的作用是将这个返回值转化为一个真正的response对象。

    def make_response(self, rv):        if isinstance(rv, self.response_class):            return rv        if isinstance(rv, basestring):            return self.response_class(rv)        if isinstance(rv, tuple):            return self.response_class(*rv)        return self.response_class.force_type(rv, request.environ)

process_response

在得到response对象后,在发送给WSGI server前,还调用process_response对视图函数返回的response进一步处理。

    def process_response(self, response):        session = _request_ctx_stack.top.session        if session is not None:            self.save_session(session, response)        for handler in self.after_request_funcs:            response = handler(response)        return response

save_session

save_session的作用是保存有更新的会话信息。

    def save_session(self, session, response):        if session is not None:            session.save_cookie(response, self.session_cookie_name)

after_request_funcs

前面已经提到,self.after_request_funcs是一个数组,保存请求后处理函数。在process_response遍历这个数组中的函数,来处理reponse。

_request_ctx_stack

_request_ctx_stack是一个全局变量:_request_ctx_stack = LocalStack()。正如它LocalStack()的名字所暗示的那样,_request_ctx_stack是一个栈。

LocalStack

class LocalStack(object):    def __init__(self):        self._local = Local()    def push(self, obj):        rv = getattr(self._local, 'stack', None)        if rv is None:            self._local.stack = rv = []        rv.append(obj)        return rv    def pop(self):        stack = getattr(self._local, 'stack', None)        if stack is None:            return None        elif len(stack) == 1:            release_local(self._local)            return stack[-1]        else:            return stack.pop()

按照我们的理解,要实现一个栈,那么LocalStack类应该有一个成员变量,是一个list,然后通过 这个list来保存栈的元素。然而,LocalStack并没有一个类型是list的成员变量, LocalStack仅有一个成员变量self._local = Local(),self._local被赋予了一个stack成员。

push

self._local.stack是在push时赋值的,这里stack被初始化一个数组,作为栈来使用。

    def push(self, obj):        """Pushes a new item to the stack"""        self._lock.acquire()        try:            rv = getattr(self._local, 'stack', None)            if rv is None:                self._local.stack = rv = []            rv.append(obj)            return rv        finally:            self._lock.release()

top

@property装饰器,可以将top函数以变量的方式访问,top()返回的就是self._local.stack栈顶元素(即数组中末尾元素)。

    @property    def top(self):        try:            return self._local.stack[-1]        except (AttributeError, IndexError):            return None

Local()

顺藤摸瓜,我们来到了Werkzeug的源码中,到达了Local类的定义处:

class Local(object):    def __init__(self):        object.__setattr__(self, '__storage__', {})        object.__setattr__(self, '__ident_func__', get_ident)    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}

需要注意的是,Local类有两个成员变量,分别是storageident_func,其中,前者 是一个字典,后者是一个函数。这个函数的含义是,获取当前线程的id(或协程的id)。
此外,我们注意到,Local类自定义了__getattr____setattr__这两个方法,也就是说,我们在操作self.local.stack时, 会调用__setattr____getattr__方法。

_request_ctx_stack = LocalStack()    _request_ctx_stack.push(item)            # 注意,这里赋值的时候,会调用__setattr__方法            self._local.stack = rv = [] ==> __setattr__(self, name, value)

__setattr__

而__setattr的定义如下:

def __setattr__(self, name, value):    ident = self.__ident_func__()    storage = self.__storage__    try:        storage[ident][name] = value    except KeyError:        storage[ident] = {name: value}

setattr中,通过ident_func获取到了一个key,然后进行赋值。自此,我们可以知道, LocalStack是一个全局字典,或者说是一个名字空间。这个名字空间是所有线程共享的

__getattr__

当我们访问字典中的某个元素的时候,会通过__getattr__进行访问,__getattr__先通过线程id, 找当前这个线程的数据,然后进行访问。

字段的内容如下:

{'thread_id':{'stack':[]}}{'thread_id1':{'stack':[_RequestContext()]},    'thread_id2':{'stack':[_RequestContext()]}}

最后,我们来看一下其他几个全局变量:

current_app = LocalProxy(lambda: _request_ctx_stack.top.app)request = LocalProxy(lambda: _request_ctx_stack.top.request)session = LocalProxy(lambda: _request_ctx_stack.top.session)g = LocalProxy(lambda: _request_ctx_stack.top.g)

这几个变量都是通过LocalProxy代理获得初值,参数都是_request_ctx_stack.top的同名成员。

LocalProxy

每个传给Flask对象的请求,都在不同的线程中处理,而且同一时刻每个线程只处理一个请求,所以对于每个请求来说,它们完全不用担心自己上下文中的数据被别的请求所修改。

LocalProxy仅仅是一个代理(可以想象设计模式中的代理模式)。通过LocalStack和LocalProxy这样的Python魔法,每个线程访问当前请求中的数据(app,request, session,g)时, 都好像都在访问一个全局变量,但是,互相之间又互不影响。

参考

  1. http://mingxinglai.com/cn/2016/08/flask-source-code/
  2. WSGI接口-廖雪峰
  3. Flask服务启动
0 0