导航
导航
文章目录
  1. 1. 知识准备
    1. 1.1 WSGI
    2. 1.2 Werkzeug
  2. 2. Flask 工作流程剖析
    1. 2.1 示例代码
    2. 2.2 Flask 代码结构
    3. 2.3 流程分析
    4. 2.4 请求上下文

Flask 源码剖析之工作流程

Flask 是一个 Python 实现的 Web 微框架。代码简洁,易于学习。

这篇文章主要讲解了 Flask 是如何处理来自客户端的 HTTP 请求,并将结果返回给客户端。本文基于 Flask 0.3 版本来分析 Flask 的工作流程。

1. 知识准备

1.1 WSGI

在 Web 部署上,有一个方案被广泛使用:

  • 首先,部署一个 Web 服务器用于接收来自客户端的 HTTP 请求。
  • 然后,部署一个应用程序 (各种语言编写的,Java,PHP,Python等),这个应用程序会从 Web 服务器上接收 HTTP 请求,然后处理之后再返回给 Web 服务器,最后 Web 服务器返回给客户端。

那么 Web 服务器和应用程序之间如何交互呢?在早期,是通过 CGI 交互的。开发者可以编写 CGI 脚本。每次收到 HTTP 请求就会触发 CGI 脚本。这种方式每次接收到请求之后都会开启新的进程。大大影响了服务器的性能。所以各种语言都开始实现自己的 HTTP 服务器。Python 在标准库里加入了 http.server 模块。使用该模块时,开发者只需编写自己的子类继承BaseHTTPRequestHandler,添加do_GET()和do_POST()方法即可。

还有一些其他的方式,如在 Apache 上使用mod_python模块来与 Web 服务器进行交互。但是使用mod_python的应用程序与使用 CGI 或者 http.server 编写的程序几乎没有任何相似之处。

Python 可以使用上述不同的方式编写应用程序,但是在设计与 Web 服务器交互的接口时,各自又采用了特定的机制。使用 CGI 或者 http.server 编写的程序都要经过修改才能在 Apache 下运行。同样使用 CGI 编写的程序也要重写一部分代码才能应用 http.server. 这使得应用程序的可移植性很差。

为了解决这一个问题,Python 社区在 PEP 333 中提出了 Web 服务器网关接口 (WSGI, Web Server Gateway Interface). David Wheeler 有句名言:“计算机科学中的任何问题,都可以通过加上另一层间接的中间层来解决。” WSGI 就是这样的一层中间层。通过这一层,用 Python 编写的应用程序就能与任何的 Web 服务器进行交互。WSGI 标准制定了一个调用惯例,如果所有主流的 Web 服务器的实现都遵循这一规则,那么应用程序以及 Web 框架不用修改代码就能够跑在服务器上。

WSGI 定义了一套标准。根据定义,WSGI 应用程序是可被调用的。其规范示例如下:

1
2
3
def app(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
yield "Hello world!\n"

1.2 Werkzeug

Werkzeug 是一个 WSGI 的工具包,它可以作为 Web 框架的底层库。Flask 正是基于 Werkzeug 编写的 Web 框架。

2. Flask 工作流程剖析

2.1 示例代码

以 Flask 0.3 中提供的 flaskr 作为例子:

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
# flaskr.py
from flask import Flask, render_template, g

app = Flask(__name__)


@app.before_request
def before_request():
g.db = connect_db() #: 连接数据库


@app.after_request
def after_request(response):
g.db.close() #: 关闭数据库
return response


@app.route('/', methods=['GET'])
def index():
data = g.db.select("select * from table")
res = [dict(title=row[0], text=row[1], for row in data.fetchall()]
return render_template('index.html', res=res)


app.run()

2.2 Flask 代码结构

以下整理了与请求处理流程相关的源代码:

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
class Flask(object):
request_class = Request
response_class = Response

def __init__(self, import_name):
self.view_functions = {}
self.error_handlers = {}
self.before_request_funcs = {}
self.after_request_funcs = {}
self.url_map = Map()

def run(self, host="localhost", port=5000, **options):

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):

def route(self, rule, **options):

def before_request(self, f):

def after_request(self, f):

def dispatch_request(self, rv):

def make_response(self, rv):

def preprocess_request(self):

def process_response(self, response):

def wsgi_app(self, environ, start_response):

def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)


class _RequestContext(object):


_request_ctx_stack = LocalStack()
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)

view_functions存放编写的视图函数,如上述例子中的index函数

error_handlers存放编写的错误处理函数,如上述例子的page_not_found函数

before_request_funcs存放预处理函数,如上述例子的before_request函数,这类函数都是在视图函数之前先执行处理。

after_request_funcs存放后处理函数,如上述例子的after_request函数,同理,这类函数都是在视图函数之后执行。

url_map存放URI到视图函数的映射,即保存了app.route这个装饰器的信息。

2.3 流程分析

当我们输入python flaskr.py之后,实例化app,将相应的视图函数、预处理函数、后处理函数、错误处理函数存放到相应的变量当中,然后执行app.run()

1
2
3
def run(self, host="localhost", port=5000, **options):
from werkzeug.serving import run_simple
return run_simple(host, port, self, **options)

run()方法中调用__call__()wsgi应用传给服务器,这样开启了Web服务。

当我们在浏览器输入http://localhost:5000/回车之后,wsgi_app()方法接受到来自客户端的请求,开始处理。

1
2
3
4
5
6
7
8
9
10
11
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
try:
rv = self.preprocess_request() #: 执行预处理函数
if rv is None:
rv = self.dispatch_request() #: 执行视图函数
response = self.make_response(rv)
response = self.process_response(response) #: 执行后处理函数
except Exception, e:
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)

wsgi_app()可以说是Flask运行机制的核心代码。可以看到,通过上下文处理器withcurrent_app, request, session, g变量存入Flask的上下文实例变量当中。然后调用preprocess_request()开启数据库连接,接着执行dispatch_request()找到URL路由并且执行index视图函数,将视图函数返回的值经过make_response()处理之后,执行process_response()关闭数据库连接。然后将最后的Response实例传给WSGI服务器。

2.4 请求上下文

Flask 应用在处理每个 HTTP 请求的时候,都会构造一个上下文对象。对于该请求的处理过程,都会在这个上下文对象中进行。

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
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)
if self.session is None:
self.session = _NullSession()
self.g = _RequestGlobals()
self.flashes = None

try:
self.request.endpoint, self.request.view_args = \
self.url_adapter.match()
except HTTPException, e:
print e.code, e.description
self.request.routing_exception = e

def push(self):
_request_ctx_stack.push(self)

def pop(self):
_request_ctx_stack.pop()

def __enter__(self):
self.push()
return self

def __exit__(self, exc_type, exc_value, tb):
if tb is None or not self.app.debug:
self.pop()

上下文对象主要存放了以下属性:

  • app: 当前请求的Flask应用
  • request: 当前请求的Request实例
  • session: 当前请求的会话信息
  • g: 此属性可以存储一些全局变量

当进入这个上下文对象之后,会触发_request_ctx_stack.push(self),将上述定义的属性压入栈中。_request_ctx_stack是使用werkzeug定义的一种LocalStack数据结构。LocalStack是一个栈结构,它可以将Greenlet或者thread推入、弹出,也可以快速拿到栈顶对象。这些修改对其他线程隔离,不会影响其他线程。(具体请看Flask 的 Context 机制, 深入 Flask 源码理解 Context)。正式基于这个特性,我们可以在Flask应用中访问app,request,session,g属性。定义如下:

1
2
3
4
5
_request_ctx_stack = LocalStack()
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)