Flask
Flask是什么"microframework" ,因为它使用简单的核心, 用 extension 增加其他功能
为什么选择Flask"color: #ff0000">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__)
# 摘自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()请求分发的过程我们需要关注一下
# 摘自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处理请求的步骤, 那么我们来试试
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干了什么事情
# 摘自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
上下文管理
下面我们再来看看之前一直忽略的上下文,什么是上下文呢"htmlcode">
# 摘自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就是请求上下文.
- current_app 表示当前运行程序文件的程序实例
- g: 处理请求时用作临时存储的对象. 每次请求都会重设这个变量 生命周期同RequestContext
- request 代表的是当前的请求
那么随之而来的问题是: 这些上下文的作用域是什么"htmlcode">
# 摘自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通过这样的方式来隔离每个请求.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
稳了!魔兽国服回归的3条重磅消息!官宣时间再确认!
昨天有一位朋友在大神群里分享,自己亚服账号被封号之后居然弹出了国服的封号信息对话框。
这里面让他访问的是一个国服的战网网址,com.cn和后面的zh都非常明白地表明这就是国服战网。
而他在复制这个网址并且进行登录之后,确实是网易的网址,也就是我们熟悉的停服之后国服发布的暴雪游戏产品运营到期开放退款的说明。这是一件比较奇怪的事情,因为以前都没有出现这样的情况,现在突然提示跳转到国服战网的网址,是不是说明了简体中文客户端已经开始进行更新了呢?
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]