flask源码分析

WSGI、uWSGI、uwsgi三者是什么

  • WSGI:python应用程序与web服务器之间进行交互的一种协议
  • uWSGI:uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议
  • uwsgi:也是一种协议,规定了怎么把请求转发给应用程序和返回; ,在此常用于在uWSGI服务器与其他网络服务器的数据通信。

flask 简介

flask是一个python语言编写的web轻量级框架。其 WSGI工具箱采用 Werkzeug ,模板引擎则使用 Jinja2。这两个库是flask的核心依赖,werkzeug 负责核心的逻辑模块,比如路由、requestresponse 封装、WSGI 相关的函数等;jinja2 负责模板的渲染。

一个简单的例子

from flask import Flask
app = Flask(__name__)
 
@app.route('/')
def hello_world():
    return 'Hello, World!'
 
if __name__ == '__main__':
    app.run()

调用app.run后,这个web服务就启动了,可在浏览器访问之。
当调用run方法时,主要是调用了Werkzeug 的run_simple,监听指定端口

        from werkzeug.serving import run_simple

        try:
            run_simple(host, port, self, **options)
        finally:
            # reset the first request information if the development server
            # reset normally.  This makes it possible to restart the server
            # without reloader and that stuff from an interactive shell.
            self._got_first_request = False

之后每次请求过来都会调用对对象app进行调用,也就是app()

执行流程

  1. 每当请求过来,会调用app对象的的__call__()方法
    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

    def __call__(self, environ, start_response):
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
        return self.wsgi_app(environ, start_response)
  1. 调用wsgi_app方法,而这个方法如上源码所示:
  • 关于ctx = self.request_context(environ)请求上下文,我们稍后单独分析,现在先把整体整个流程梳理下来。
  • response = self.full_dispatch_request()主要通过这个进行消息的分发处理,full_dispatch_request源码如下:
    def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.

        .. versionadded:: 0.7
        """
        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)
  • full_dispatch_request方法中,实现了一个请求,请求前、请求过程、请求结束需要调用的处理方法,通过方法dispatch_request进行路由,找到对应的视图函数。
    def dispatch_request(self):
        """Does the request dispatching.  Matches the URL and returns the
        return value of the view or error handler.  This does not have to
        be a response object.  In order to convert the return value to a
        proper response object, call :func:`make_response`.

        .. versionchanged:: 0.7
           This no longer does the exception handling, this code was
           moved to the new :meth:`full_dispatch_request`.
        """
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        rule = req.url_rule
        # if we provide automatic options for this URL and the
        # request came with the OPTIONS method, reply automatically
        if getattr(rule, 'provide_automatic_options', False) \
           and req.method == 'OPTIONS':
            return self.make_default_options_response()
        # otherwise dispatch to the handler for that endpoint
        return self.view_functions[rule.endpoint](**req.view_args)

完整流程

当一个请求到来时,这个请求经封装放到一个线程隔离的栈_request_ctx_stack中,current_app对象也会放入到另一个线程隔离的栈_app_ctx_stack中,请求结束后,会弹出。因此只在请求过程中才能使用request对象和current_app对象。

请求上下文

我们使用flask写我们的应用时,经常会使用到from flask import request,那么这个request对象是什么呢,每次请求的request对象为什么都不同呢,下面我们就来分析一下。这个request定义在flask的globals.py下。

_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'))

线程隔离

我们先了解下线程隔离,在多线程编程中有一种线程隔离技术local,也就是在一个对象中,每个线程有单独的数据,对自己的数据操作不会影响到其他线程,实现也很简单,定义一个字典用于保存数据,字典的key是线程id。而在flask中,同样有线程隔离。参考werkzeug的local.py文件,源码:

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)

这个local对象与多线程库的local不同之处在于,这个local不光是线程隔离,也是协程隔离的。LocalStack是基于Local的,实现了基于栈的线程隔离,这个对象也就是我们前面说的用于存储当前应用与当前请求的。
因此,每次请求的request的对象都是栈_request_ctx_stack的最上面元素,并且实现了线程隔离,每次请求的request都不是同一个。current_app同理,属于应用上下文,应用上下文除了current_app还有g。而请求上下文除了request还有session。另外这几个全局变量都是通过代理模式实现的,看下LocalProxy关键部分源码:

class LocalProxy(object):
    __slots__ = ("__local", "__dict__", "__name__", "__wrapped__")

    def __init__(self, local, name=None):
        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)

    def _get_current_object(self):
        """Return the current object.  This is useful if you want the real
        object behind the proxy at a time for performance reasons or because
        you want to pass the object into a different context.
        """
        if not hasattr(self.__local, "__release_local__"):
            return self.__local()
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError("no object bound to %s" % self.__name__)

    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError("__dict__")

  • 主要看方法_get_current_object,返回当前对象,里面几乎所有的魔术方法都实现了对对象的代理操作。其实这个代理类也实现了request和current_app的动态获取,因为这两个对象都是有上下文的,请求过程才存在,每次都可能不同,因此我们对这些对象进行操作时,每次都会通过_get_current_object重新获取最新的request和current_app对象

钩子函数

在看源码的过程中,发现一些钩子函数

# 服务器被第一次访问执行的钩子函数
@app.before_first_request
def first_request():
    print("Hello World")


# 服务器被第一次访问执行的钩子函数
@app.before_first_request
def first_request():
    print("Hello World")
等等

被装饰的函数会在对应的时候调用

信号

Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为。
内置信号:

request_started = _signals.signal('request-started')                # 请求到来前执行
request_finished = _signals.signal('request-finished')              # 请求结束后执行
  
before_render_template = _signals.signal('before-render-template')  # 模板渲染前执行
template_rendered = _signals.signal('template-rendered')            # 模板渲染后执行
  
got_request_exception = _signals.signal('got-request-exception')    # 请求执行出现异常时执行
  
request_tearing_down = _signals.signal('request-tearing-down')      # 请求执行完毕后自动执行(无论成功与否)
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 请求上下文执行完毕后自动执行(无论成功与否)
  
appcontext_pushed = _signals.signal('appcontext-pushed')            # 请求上下文push时执行
appcontext_popped = _signals.signal('appcontext-popped')            # 请求上下文pop时执行
message_flashed = _signals.signal('message-flashed')                # 调用flask在其中添加数据时,自动触发

也可以自定义信号

# 自定义信号
xxxxx = _signals.signal('xxxxx')
  
def func(sender, *args, **kwargs):
    print(sender)
  
# 自定义信号中注册函数
xxxxx.connect(func)
  
@app.route("/x")
def index():
    # 触发信号
    xxxxx.send('1', k1='v1')
    return '123'

你可能感兴趣的:(flask源码分析)