Flask是一个使用 Python 编写的轻量级 Web 应用框架, 让咱们可使用Python语言快速搭建Web服务, Flask也被称为 "microframework" ,由于它使用简单的核心, 用 extension 增长其余功能python
咱们先来看看python如今比较流行的web框架nginx
Flask: 轻, 组件间松耦合, 自由、灵活,可扩展性强,第三方库的选择面广的同时也增长了组件间兼容问题web
Django: Django至关于一个全家桶, 几乎包括了全部web开发用到的模块(session管理、CSRF防伪造请求、Form表单处理、ORM数据库对象化、模板语言), 可是相对应的会形成一个紧耦合的状况, 对第三方插件不太友好数据库
Tornado: 底层经过eventloop来实现异步处理请求, 处理效率高, 学习难度大, 处理稍有不慎很容易阻塞主进程致使不能正常提供服务, 新版本也支持asyncioflask
Sanic: 一个类Flask框架, 可是底层使用uvloop进行异步处理, 可使用同步的方式编写异步代码, 并且运行效率十分高效.服务器
先来看看维基百科对WSGI的定义session
Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口.app
何为网关, 即从客户端发出的每一个请求(数据包)第一个到达的地方, 而后再根据路由进行转发处理. 而对于服务端发送过来的消息, 老是先经过网关层, 而后再转发至客户端框架
那么可想而知, 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__)
# 摘自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处理一个请求的基本流程,
其中response = self.full_dispatch_request()请求分发的过程咱们须要关注一下
# 摘自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进行了以下操做:
以上的这些是Flask提供给咱们使用的钩子(hook), 能够根据自身需求来定义,
而hook中还有@teardown_request, 是在每次处理请求后执行(不管是否有异常), 因此它是在上下文出栈的时候被调用
若是同时定义了四种钩子(hook), 那么执行顺序应该是
graph LR before_first_request --> before_request before_request --> after_request after_request --> teardown_request
在请求函数和钩子函数之间,通常经过全局变量g实现数据共享
如今的处理流程就变为:
其中3-7就是须要咱们完成的部分.
上面咱们知道, Flask处理请求的步骤, 那么咱们来试试
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 函数去处理呢?咱们先来普及几个知识点:
添加路由的方法:
咱们先来看看@app.route干了什么事情
# 摘自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.
# 摘自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又是什么等.
咱们只须要在须要用到这些信息的时候把它从上下文中取出来便可. 而上下文是有生命周期的, 不是全部时候都能获取到.
上下文生命周期:
那么上下文是在何时建立的呢?咱们又要如何建立上下文: 刚才咱们提到, 在wsgi_app处理请求的时候就会先建立上下文, 那个上下文实际上是请求上下文, 那应用上下文呢?
# 摘自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来建立应用上下文与请求上下文.
那么咱们应该如何使用上下文呢?
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就是请求上下文.
那么随之而来的问题是: 这些上下文的做用域是什么?
线程有个叫作ThreadLocal的类,也就是一般实现线程隔离的类. 而werkzeug本身实现了它的线程隔离类: werkzeug.local.Local. 而LocalStack就是用Local实现的.
这个咱们能够经过globals.py能够看到
# 摘自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经过这样的方式来隔离每一个请求.