个人博客

flask运行流程原理

一、WSGI网关接口

WSGI的主要作用是在Web服务器和Web应用程序承担“翻译官”的角色。用于Web服务器和Web应用解析对方给与的信息。主要的作用如下:

  • Web服务器的责任在于监听和接收请求。在处理请求的时候调用WSGI提供的标准化接口,将请求的信息转给WSGI;
  • WSGI的责任在于“中转”请求和响应信息。WSGI接收到Web服务器提供的请求信息后可以做一些处理,之后通过标准化接口调用Web应用,并将请求信息传递给Web应用。同时,WSGI还将会处理Web应用返回的响应信息,并通过服务器返回给客户端;
  • Web应用的责任在于接收请求信息,并且生成响应。

Flask框架也是基于WSGI网关进行的开发,所以这个应用也可以叫WSGI应用,依赖于底层库werkzeug。

服务器、Web应用的设计应该遵循网关接口的一些规范。对于WSGI网关,要求Web应用实现一个函数或者一个可调用对象webapp(environ, start_response)。服务器或网关中要定义start_response函数并且调用Web应用。

二、Werkzeug库

Werkzeug是一个Python写成的WSGI工具集。它遵循WSGI规范,对服务器和Web应用之间的“中间层”进行了开发,衍生出一系列非常有用的Web服务底层模块。关于Werkzeug功能的最简单的一个例子如下:

from werkzeug.wrappers import Request, Response

def application(environ, start_response):
    request = Request(environ)
    response = Response("Hello %s!" % request.args.get('name', 'World!'))
    return response(environ, start_response)

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, application)

运行上面的例子,当在浏览器输入http://127.0.0.1:4000/就会向本地的服务器发出一个请求。在请求的过程中,werkzeug主要做了下面几件事情:

  • 根据服务器和WSGI服务器产生的environ环境信息,封装一个Request实例,这个实例包含请求的所有信息;
  • Web应用根据封装的Request实例信息,产生一个Response实例(上述例子只是输出一段字符串)。这个Response实例是一个可调用的WSGI应用;
  • 上一步骤产生的可调用应用对象response调用response(environ, start_response)生成响应信息并发回客户端。调用函数是由WSGI规范规定的。

以上过程很好地将服务器和web应用分离开来:服务器不用考虑请求信息怎么被解析给web应用,以及后续怎么和web应用通信;web应用也不用考虑怎么将响应信息返回给服务器。服务器要做的只是提供web应用所需的请求信息,web应用提供的也只是响应信息,中间的处理过程werkzeug可以帮助完成。

三、flask应用的要点或特性

使用app = Flask(name),可以实例化一个Flask应用。运行起来后的Flask应用有一些要点或特性:

1,对于请求和响应的处理,Flask使用werkzeug库中的Request类和Response类。

Werkzeug库中的wrappers模块主要对request和response进行封装。request包含了客户端发往服务器的所有请求信息,response包含了web应用返回给客户端的所有信息。wrappers模块对请求和响应的封装简化了客户端、服务器和web应用通信的流程。

2,对于URL模式的处理,Flask应用使用werkzeug库中的Map类和Rule类,每一个URL模式对应一个Rule实例,这些Rule实例最终会作为参数传递给Map类构造包含所有URL模式的一个“地图”。

Werkzeug库的routing模块的主要功能在于URL解析。对于WSGI应用来讲,不同的URL对应不同的视图函数,routing模块则会对请求信息的URL进行解析并匹配,触发URL对应的视图函数,以此生成一个响应信息。routing模块的解析和匹配功能主要体现在三个类上:Rule、Map和MapAdapter。

3,当实例化一个Flask应用app之后,Flask采取装饰器的方法,将URL规则和视图函数结合在一起写,其中主要的函数是route,如:

@app.route('/')
 def index():
     pass

这样写视图函数,会将'/'这条URL规则和视图函数index()联系起来,并且会形成一个Rule实例,再添加进Map实例中去。当访问'/'时,会执行index()。

4,实例化Flask应用时,会创造一个Jinja环境,这是Flask自带的一种模板引擎。

5,实例化的Flask应用是一个可调用对象要遵循WSGI规范,就要实现一个函数或者一个可调用对象webapp(environ, start_response),以方便服务器或网关调用。Flask应用通过__call__(environ, start_response)方法可以让它被服务器或网关调用。

def __call__(self, environ, start_response):
     """Shortcut for :attr:`wsgi_app`"""
     return self.wsgi_app(environ, start_response)

四、flask应用运行和使用

当实例化完一个Flask应用之后,可以通过调用app.run()方法运行这个应用。

Flask应用的run()方法会调用werkzeug.serving模块中的run_simple方法。这个方法会创建一个本地的测试服务器,并且在这个服务器中运行Flask应用。

def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
    # 省略其他代码
    _host = "127.0.0.1"
    _port = 5000
    server_name = self.config.get("SERVER_NAME")
    sn_host, sn_port = None, None

    if server_name:
        sn_host, _, sn_port = server_name.partition(":")

    host = host or sn_host or _host
    # pick the first value that's not None (0 is allowed)
    port = int(next((p for p in (port, sn_port) if p is not None), _port))

    options.setdefault("use_reloader", self.debug)
    options.setdefault("use_debugger", self.debug)
    options.setdefault("threaded", True)

    cli.show_server_banner(self.env, self.debug, self.name, False)

    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

当服务器开始调用Flask应用后,便会触发Flask应用的__call__(environ, start_response)方法。其中environ由服务器产生,start_response在服务器中定义。

    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)

从上面的代码看,Flask应用被调用时会执行wsgi_app(environ, start_response)方法。可以看出,wsgi_app是真正被调用的WSGI应用,wsgi_app可以被一些“中间件”装饰,以便先行处理一些操作。

当Flask应用真正处理请求时,wsgi_app(environ, start_response)被调用,这个方法会构造一个上下文对象,所有的请求处理过程,都会在这个上下文对象中进行。这个上下文对象是RequestContext类的实例。

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:  # noqa: B001
            error = sys.exc_info()[1]
            raise
        return response(environ, start_response)
    finally:
        if self.should_ignore_error(error):
            error = None
        ctx.auto_pop(error)
-------------------------------------
class RequestContext(object):
    def __init__(self, app, environ, request=None, session=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = None
        try:
            self.url_adapter = app.create_url_adapter(self.request)
        except HTTPException as e:
            self.request.routing_exception = e
        self.flashes = None
        self.session = session
        self._implicit_app_ctx_stack = []
        self.preserved = False
        self._preserved_exc = None
        self._after_request_functions = []

    def push(self):
    """Binds the request context to the current context."""
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

        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)

        if self.session is None:
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(self.app, self.request)

            if self.session is None:
                self.session = session_interface.make_null_session(self.app)

        if self.url_adapter is not None:
            self.match_request()

根据RequestContext对象的构造方法,可以发现一些和flask应用相关的属性:

  • app:上下文对象的app属性是当前的Flask应用;
  • url_adapter:上下文对象的url_adapter属性是通过Flask应用中的Map实例构造成一个MapAdapter实例,主要功能是将请求中的URL和Map实例中的URL规则进行匹配;
  • request:上下文对象的request属性是通过Request类构造的实例,反映请求的信息;
  • session:上下文对象的session属性存储请求的会话信息;
  • flashes:消息闪现的信息。

处理请求的过程定义在wsgi_app方法中,主要实现看full_dispatch_request方法:

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:  # noqa: B001
            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 full_dispatch_request(self):
    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)

def finalize_request(self, rv, from_error_handler=False):
    response = self.make_response(rv)
    try:
        response = self.process_response(response)
        request_finished.send(self, response=response)
    except Exception:
        if not from_error_handler:
            raise
        self.logger.exception(
            "Request finalizing failed with an error while handling an error"
        )
    return response

主要分为下面的步骤:

  • 1,在请求正式被处理之前的一些操作,调用preprocess_request()方法,例如打开一个数据库连接等操作;

  • 2,正式处理请求。这个过程调用dispatch_request()方法,这个方法会根据URL匹配的情况调用相关的视图函数;

  • 3,将从视图函数返回的值转变为一个Response对象;

  • 4,在响应被发送到WSGI服务器之前,调用process_response(response)做一些后续处理过程;

  • 5,调用response(environ, start_response)方法将响应发送回WSGI服务器。

相关标签
回到顶部