浅谈flask源码之请求过程

 

 更新时间:2018年07月26日 09:51:36   做者:Dear、   我要评论python

 
这篇文章主要介绍了浅谈flask源码之请求过程,小编以为挺不错的,如今分享给你们,也给你们作个参考。一块儿跟随小编过来看看吧
 

Flasknginx

Flask是什么?web

Flask是一个使用 Python 编写的轻量级 Web 应用框架, 让咱们可使用Python语言快速搭建Web服务, Flask也被称为 "microframework" ,由于它使用简单的核心, 用 extension 增长其余功能数据库

为何选择Flask?flask

咱们先来看看python如今比较流行的web框架服务器

  • Flask
  • Django
  • Tornado
  • Sanic

Flask: 轻, 组件间松耦合, 自由、灵活,可扩展性强,第三方库的选择面广的同时也增长了组件间兼容问题session

Django: Django至关于一个全家桶, 几乎包括了全部web开发用到的模块(session管理、CSRF防伪造请求、Form表单处理、ORM数据库对象化、模板语言), 可是相对应的会形成一个紧耦合的状况, 对第三方插件不太友好app

Tornado: 底层经过eventloop来实现异步处理请求, 处理效率高, 学习难度大, 处理稍有不慎很容易阻塞主进程致使不能正常提供服务, 新版本也支持asyncio框架

Sanic: 一个类Flask框架, 可是底层使用uvloop进行异步处理, 可使用同步的方式编写异步代码, 并且运行效率十分高效.异步

WSGI

先来看看维基百科对WSGI的定义

Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口.

何为网关, 即从客户端发出的每一个请求(数据包)第一个到达的地方, 而后再根据路由进行转发处理. 而对于服务端发送过来的消息, 老是先经过网关层, 而后再转发至客户端

那么可想而知, WSGI实际上是做为一个网关接口, 来接受Server传递过来的信息, 而后经过这个接口调用后台app里的view function进行响应.

先看一段有趣的对话:

Nginx:Hey, WSGI, 我刚收到了一个请求,我须要你做些准备, 而后由Flask来处理这个请求.
WSGI:OK, Nginx. 我会设置好环境变量, 而后将这个请求传递给Flask处理.
Flask:Thanks. WSGI给我一些时间,我将会把请求的响应返回给你.
WSGI:Alright, 那我等你.
Flask:Okay, 我完成了, 这里是请求的响应结果, 请求把结果传递给Nginx.
WSGI:Good job! Nginx, 这里是响应结果, 已经按照要求给你传递回来了.
Nginx:Cool, 我收到了, 我把响应结果返回给客户端.你们合做愉快~

对话里面能够清晰了解到WSGI、nginx、Flask三者的关系

下面来看看Flask中的wsgi接口(注意:每一个进入Flask的请求都会调用Flask.__call__)

?
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
# 摘自Flask源码 app.py
class Flask(_PackageBoundObject):
   # 中间省略
   def __call__( self , environ, start_response):
     return self .wsgi_app(environ, start_response)
       
   def wsgi_app( self , environ, start_response):
     # environ: 一个包含所有HTTP请求信息的字典, 由WSGI Server解包HTTP请求生成
     # start_response: WSGI Server提供的函数, 调用能够发送响应的状态码和HTTP报文头,
     # 函数在返回前必须调用一次.
     :param environ: A WSGI environment.
     :param start_response: A callable accepting a status code,
       a list of headers, and an optional exception context to
       start the 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)

wsgi_app中定义的就是Flask处理一个请求的基本流程,
1.建立上下文
2.把上下文入栈
3.分发请求
4.上下文出栈
5.返回结果

其中response = self.full_dispatch_request()请求分发的过程咱们须要关注一下

?
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
# 摘自Flask源码 app.py
class Flask(_PackageBoundObject):
   # 中间省略
   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 dispatch_request( self ):
     req = _request_ctx_stack.top.request
     if req.routing_exception is not None :
       self .raise_routing_exception(req)
     rule = req.url_rule
     if getattr (rule, 'provide_automatic_options' , False ) \
       and req.method = = 'OPTIONS' :
       return self .make_default_options_response()
     return self .view_functions[rule.endpoint]( * * req.view_args)
 
   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

咱们能够看到, 请求分发的操做实际上是由dispatch_request来完成的, 而在请求进行分发的先后咱们能够看到Flask进行了以下操做:
1.try_trigger_before_first_request_functions, 首次处理请求前的操做,经过@before_first_request定义,能够进行数据库链接
2.preprocess_request, 每次处理请求前进行的操做, 经过@before_request来定义, 能够拦截请求
3.process_response, 每次正常处理请求后进行的操做, 经过@after_request来定义, 能够统计接口访问成功的数量
4.finalize_request, 把视图函数的返回值转换成一个真正的响应对象

以上的这些是Flask提供给咱们使用的钩子(hook), 能够根据自身需求来定义,
而hook中还有@teardown_request, 是在每次处理请求后执行(不管是否有异常), 因此它是在上下文出栈的时候被调用

若是同时定义了四种钩子(hook), 那么执行顺序应该是

graph LR
before_first_request --> before_request
before_request --> after_request
after_request --> teardown_request

在请求函数和钩子函数之间,通常经过全局变量g实现数据共享

如今的处理流程就变为:

1.建立上下文
2.上下文入栈
3.执行before_first_request操做(若是是第一次处理请求)
4.执行before_request操做
5.分发请求
6.执行after_request操做
7.执行teardown_request操做
8.上下文出栈
9.返回结果

其中3-7就是须要咱们完成的部分.

如何使用Flask

上面咱们知道, 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
from flask import Flask
app = Flask(__name__)
 
 
@app .before_first_request
def before_first_request():
   print ( 'before_first_request run' )
 
 
@app .before_request
def before_request():
   print ( 'before_request run' )
 
 
@app .after_request
def after_request(param):
   print ( 'after_request run' )
   return param
 
@app .teardown_request
def teardown_request(param):
   print ( 'teardown_request run' )
 
 
@app .route( '/' )
def hello_world():
   return 'Hello World!'
 
 
if __name__ = = '__main__' :
   app.run()

当运行flask进程时, 访问127.0.0.1:5000, 程序输出, 正好认证了咱们以前说的执行顺序.

before_first_request run
before_request run
after_request run
teardown_request run
127.0.0.1 - - [03/May/2018 18:42:52] "GET / HTTP/1.1" 200 -

路由分发

看了上面的代码, 咱们可能仍是会有疑问, 为何咱们的请求就会跑到hello world 函数去处理呢?咱们先来普及几个知识点:

  • url: 客户端访问的网址
  • view_func: 即咱们写的视图函数
  • rule: 定义的匹配路由的地址
  • url_map: 存放着rule与endpoint的映射关系
  • endpoint: 能够看做为每一个view_func的ID
  • view_functions: 一个字典, 以endpoint为key, view_func 为value

添加路由的方法:

1.@app.route
2.add_url_rule

咱们先来看看@app.route干了什么事情

?
1
2
3
4
5
6
7
8
9
# 摘自Flask源码 app.py
class Flask(_PackageBoundObject):
   # 中间省略
   def route( self , rule, * * options):
     def decorator(f):
       endpoint = options.pop( 'endpoint' , None )
       self .add_url_rule(rule, endpoint, f, * * options)
       return f
     return decorator

咱们能够看到, route函数是一个装饰器, 它在执行时会先获取endpoint, 而后再经过调用add_url_rule来添加路由, 也就是说全部添加路由的操做其实都是经过add_url_rule来完成的. 下面咱们再来看看add_url_rule.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 摘自Flask源码 app.py
class Flask(_PackageBoundObject):
   # 中间省略
   # 定义view_functions
   self .view_functions = {}
   # 定义url_map
   self .url_map = Map ()
   
   def add_url_rule( self , rule, endpoint = None , view_func = None ,
            provide_automatic_options = None , * * options):
     # 建立rule
     rule = self .url_rule_class(rule, methods = methods, * * options)
     rule.provide_automatic_options = provide_automatic_options
     # 把rule添加到url_map
     self .url_map.add(rule)
     if view_func is not None :
       old_func = self .view_functions.get(endpoint)
       if old_func is not None and old_func ! = view_func:
         raise AssertionError( 'View function mapping is overwriting an '
                    'existing endpoint function: %s' % endpoint)
       # 把view_func 添加到view_functions字典
       self .view_functions[endpoint] = view_func

能够看到, 当咱们添加路由时, 会生成一个rule, 并把它存放到url_map里头, 而后把view_func与其对应的endpoint存到字典.

当一个请求进入时, Flask会先根据用户访问的Url到url_map里边根据rule来获取到endpoint, 而后再利用view_functions获取endpoint在里边所对应的视图函数

graph LR
url1 -->url_map
url2 -->url_map
url3 -->url_map
urln -->url_map
url_map --> endpoint
endpoint --> view_functions

上下文管理

下面咱们再来看看以前一直忽略的上下文,什么是上下文呢?

上下文即语境、语意,是一句话中的语境,也就是语言环境. 一句莫名其妙的话出现会让人不理解什么意思, 若是有语言环境的说明, 则会更好, 这就是语境对语意的影响. 而对应到程序里每每就是程序中须要共享的信息,保存着程序运行或交互中须要保持或传递的信息.

Flask中有两种上下文分别为:应用上下文(AppContext)和请求上下文(RequestContext). 按照上面提到的咱们很容易就联想到:应用上下文就是保存着应用运行或交互中须要保持或传递的信息, 如当前应用的应用名, 当前应用注册了什么路由, 又有什么视图函数等. 而请求上下文就保存着处理请求过程当中须要保持或传递的信息, 如此次请求的url是什么, 参数又是什么, 请求的method又是什么等.

 

咱们只须要在须要用到这些信息的时候把它从上下文中取出来便可. 而上下文是有生命周期的, 不是全部时候都能获取到.

上下文生命周期:

  • RequestContext: 生命周期在处理一次请求期间, 请求处理完成后生命周期也就结束了.
  • AppContext: 生命周期最长, 只要当前应用还在运行, 就一直存在. (应用未运行前并不存在)

那么上下文是在何时建立的呢?咱们又要如何建立上下文: 刚才咱们提到, 在wsgi_app处理请求的时候就会先建立上下文, 那个上下文实际上是请求上下文, 那应用上下文呢?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 摘自Flask源码 ctx.py
class RequestContext( object ):
   # 中间省略
   def push( self ):
     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 )

咱们知道当有请求进入时, Flask会自动帮咱们来建立请求上下文. 而经过上述代码咱们能够看到,在建立请求上下文时会有一个判断操做, 若是应用上下文为空或与当前应用不匹配, 那么会从新建立一个应用上下文. 因此说通常状况下并不须要咱们手动去建立, 固然若是须要, 你也能够显式调用app_context与request_context来建立应用上下文与请求上下文.

那么咱们应该如何使用上下文呢?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask, request, g, current_app
app = Flask(__name__)
 
@app .before_request
def before_request():
   print 'before_request run'
   g.name = "Tom"
   
@app .after_request
def after_request(response):
   print 'after_request run'
   print (g.name)
   return response
 
@app .route( '/' )
def index():
   print (request.url)
   g.name = 'Cat'
   print (current_app.name)
   
if __name__ = = '__main__' :
   app.run()

访问127.0.0.1:5000时程序输出

before_request run
http://127.0.0.1:5000/
flask_run
after_request run
Cat
127.0.0.1 - - [04/May/2018 18:05:13] "GET / HTTP/1.1" 200 -

代码里边应用到的current_app和g都属于应用上下文对象, 而request就是请求上下文.

  • current_app 表示当前运行程序文件的程序实例
  • g: 处理请求时用做临时存储的对象. 每次请求都会重设这个变量 生命周期同RequestContext
  • request 表明的是当前的请求

那么随之而来的问题是: 这些上下文的做用域是什么?

线程有个叫作ThreadLocal的类,也就是一般实现线程隔离的类. 而werkzeug本身实现了它的线程隔离类: werkzeug.local.Local. 而LocalStack就是用Local实现的.

这个咱们能够经过globals.py能够看到

?
1
2
3
4
5
6
7
8
9
10
11
# 摘自Flask源码 globals.py
from functools import partial
from werkzeug.local import LocalStack, LocalProxy
 
 
_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' ))

_lookup_app_object思就是说, 对于不一样的线程, 它们访问这两个对象看到的结果是不同的、彻底隔离的. Flask经过这样的方式来隔离每一个请求.

以上就是本文的所有内容,但愿对你们的学习有所帮助,也但愿你们多多支持脚本之家。

相关文章
相关标签/搜索