上次遗留了两个问题,先说一下自己的看法
问题:
1.明明一个线程只能处理一个请求,那么栈里的元素永远是在栈顶,那为什么需要用栈这个结构?用普通变量不行吗.
2._request_ctx_stack和_app_ctx_stack都是线程隔离的,那么为什么要分开?
我认为在web runtime的情况下是可以不需要栈这个结构的,即使是单线程下也不需要,原本我以为在单线程下,当前一个请求阻塞后,后一个请求还会被推入栈中,结果并不是这样,这也就说明了,栈的结构和是不是单线程没关系,为了验证这点,我写了个简单的接口验证这点:

from flask import Flask,_request_ctx_stackapp = Flask(__name__)@app.route('/')
def index():print(_request_ctx_stack._local.__storage__)time.sleep(1)return '<h1>hello</h1>'app.run(port=3000)

def wsgi_app(self, environ, start_response):

ctx = self.request_context(environ)
ctx.push()
print(_request_ctx_stack._local.__storage__)

我在Flask类中的wsgi_app()方法中加了这一句print(_request_ctx_stack._local.__storage__),wsgi_app()是后端接收应用服务器发来的包装好的WSGI请求的函数,后面会讲到,由于一个线程只能处理一个请求,所以结果应该是栈中永远只有一个请求对象,在路由接口中我延时了1秒,假设成阻塞,看一下结果:

 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
{139851542578944: {'stack': []}}
127.0.0.1 - - [14/Apr/2018 14:31:17] "GET / HTTP/1.1" 200 -
{139851542578944: {'stack': []}}
127.0.0.1 - - [14/Apr/2018 14:31:18] "GET / HTTP/1.1" 200 -
{139851542578944: {'stack': []}}
127.0.0.1 - - [14/Apr/2018 14:31:19] "GET / HTTP/1.1" 200 -

每次栈中只有一个请求对象,这也就说明了栈这个结构和web runtime下的单线程无关,那么就剩下非web runtime的情况了,最常见的是离线测试:

from flask import Flask,_request_ctx_stack,_app_ctx_stackapp = Flask(__name__)
app2 = Flask(__name__)def offline_test():with app.app_context():print(_app_ctx_stack._local.__storage__)with app2.app_context():print(_app_ctx_stack._local.__storage__)with app.app_context():with app.test_request_context():print(_request_ctx_stack._local.__storage__)with app.test_request_context():print(_request_ctx_stack._local.__storage__)

离线测试是单线程的,通过这个例子也能得到第二的问题的答案,为什么要将请求和应用分开,一个原因是flask支持多个app共存,这需要用到中间件,另一个原因是离线测试时,有可能只需要用到应用上下文,所以需要将两者分开,在离线测试时如果进行了嵌套则栈结构的特点就发挥了出来,看一下运行的结果:

{140402410657536: {'stack': []}}
{140402410657536: {'stack': [, ]}}
{140402410657536: {'stack': []}}
{140402410657536: {'stack': [, ]}}

结果显而易见
总结一下:栈结构和分离请求和应用是为了离线测试更加灵活
web应用服务器 WSGI 后端之间的关系
web应用服务器的作用是监听端口,当接收到客户端的请求后将请求转化成WSGI格式(environ)然后传给后端框架
应用服务器<----WSGI协议---->后端框架
WSGI是应用服务器和后端框架之间的桥梁,使得服务器和后端框架分离,各司其职,程序员也能专注于自己的逻辑
在WSGI中规定了每个python web应用都需要是一个可调用的对象,即实现了__call__这个特殊方法,Flask就是一个可调用对象
web应用服务器从哪里将包装好的请求发送给后端
在flask中使用了werkzeug这个工具包,在werkzeug.serving中有一个类,class WSGIRequestHandler(BaseHTTPRequestHandler, object)
这个类提供了environ字典对象,定义了start_response()和run_wsgi()方法,在run_wsgi()中有一个execute(),看一下源码:

def execute(app):application_iter = app(environ, start_response)  #从这里发送到后端try:for data in application_iter:write(data)if not headers_sent:write(b'')finally:if hasattr(application_iter, 'close'):application_iter.close()application_iter = None

第一句application_iter = app(environ, start_response)就调用了Flask.__call__(),并将environ, start_response传入,而Flask.__call__()就return了self.wsgi_app(),
这个wsgi_app(environ, start_response)是一个标准的请求处理函数,所以它就是整个后端处理请求的入口函数,environ是一个包含所有HTTP请求信息的字典对象,start_response是一个发送HTTP响应的函数,environ是从应用服务器传过来的,start_response是定义好的,这些都不需要后端开发人员关心
总结一下:
1.WSGI规定了后端处理函数的格式,即需要接受environ,start_response这两个参数,这两个参数从应用服务器传给后端框架
2.python web应用对象需要是可调用的,即实现了__call__方法,返回WSGI规定格式的后端处理函数来处理请求及返回响应
3.应用服务器会使用werkzeug.serving中的WSGIRequestHandler类中的相应方法,将http请求转化成WSGI格式,所以说werkzeug是一个遵循WSGI协议的工具包,提供给应用服务器使用
后端处理请求返回响应整个流程
之前说到,后端处理请求的入口函数是wsgi_app(self,environ,start_response),先看下源码:

    def wsgi_app(self, environ, start_response):ctx = self.request_context(environ)  #1ctx.push()  #2error = Nonetry:try:response = self.full_dispatch_request()  #3except Exception as e:error = eresponse = self.handle_exception(e)except:error = sys.exc_info()[1]raisereturn response(environ, start_response)finally:if self.should_ignore_error(error):error = Nonectx.auto_pop(error)

其中有三句比较关键,我写了序号
第一句:self.request_context(environ),看下request_context这个方法:

    def request_context(self, environ):return RequestContext(self, environ)

简而言之,传入environ,初始化一个请求上下文对象并返回
第二句:ctx.push(),看下源码:

    def push(self):top = _request_ctx_stack.topif top is not None and top.preserved:top.pop(top._preserved_exc)app_ctx = _app_ctx_stack.topif 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)self.session = self.app.open_session(self.request)if self.session is None:self.session = self.app.make_null_session()

简而言之,推入应用上下文和请求上下文,如果设置了secret_key则开启一个session,关于flask的session放到后面说
第三句:self.full_dispatch_request(),是处理请求的关键函数,看下源码:

    def full_dispatch_request(self):"""Dispatches the request and on top of that performs requestpre and postprocessing as well as HTTP exception catching anderror handling... versionadded:: 0.7"""self.try_trigger_before_first_request_functions()try:request_started.send(self)rv = self.preprocess_request()  #function1if rv is None:rv = self.dispatch_request()  #function2except Exception as e:rv = self.handle_user_exception(e)return self.finalize_request(rv)  #function3

这个函数中嵌套了另外三个函数,预处理函数preprocess_request(),主处理函数dispatch_request()和最终处理函数finalize_request(rv)
1.preprocess_request()是处理被before_request装饰器装饰的函数
2.dispatch_request()匹配请求的URL,并返回视图函数的返回值rv
3.finalize_request(rv)接受视图函数的返回值,并生成响应,这里有make_response和process_response这两个函数,make_response生成响应对象,process_response对响应做一些处理,比如后面要讲到的session
响应生成后,在wsgi_app中return response,最后调用ctx.auto_pop()将请求和应用上下文推出栈,return的response会通过start_response发送到应用服务器,并由其发送到客户端,这样一次请求就结束了.
最后说说session
flask中的session是client side session,说白了就是session会封装在cookie中在最终响应时会发送给客户端,而在服务器本地不会存储,所以叫作client side session,要使用session需要设置secret_key这个配置,通过app.secret_key来设置,用来验证签名,等到下次客户端发来带有cookie的请求时,后端就能从生成对应的session中解析出带有的信息,写个简单的应用来看下session怎么用:

from flask import Flask,sessionapp = flask.Flask(__name__)
app.secret_key = 'gjx'@app.route('/')
def index():if 'name' in session:print(session['name'])else:print('stranger')return '<h1>/</h1>'@app.route('/')
def test(name):session['name'] = nameprint('session set successful')return '<h1>test</h1>'app.run(port=3000)

跑起来后,在浏览器输入127.0.0.1:3000/,会打印出stranger,
然后访问127.0.0.1:3000/jx后,后端打印出session set successful,并且浏览器会收到服务器发来的cookie,
Set-Cookie:session=eyJuYW1lIjoiangifQ.DbNHuQ.MPZLWzoLdga2SPMg0plMYmKlJMc; HttpOnly; Path=/ 这是我测试时收到的,有三个字段,第一个是session的内容,第二个是时间戳,第三个是验证信息
这时已经设置好了session,并且得到了cookie,再次访问127.0.0.1:3000/,后端打印出了jx,就是之前设置的值
如果对session内的值更改,则返回的cookie也会更改,那么在那保存,在那创建session呢?
之前在分析后端请求流程是提到了,在RequestContext的push方法最后:

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

如果设置了secret_key则会执行open_session开启一个session,那如果更改了在哪里保存呢?
在finalize_request执行的self.process_response中:

    def process_response(self, response):ctx = _request_ctx_stack.topbp = ctx.request.blueprintfuncs = ctx._after_request_functionsif bp is not None and bp in self.after_request_funcs:funcs = chain(funcs, reversed(self.after_request_funcs[bp]))if None in self.after_request_funcs:funcs = chain(funcs, reversed(self.after_request_funcs[None]))for handler in funcs:response = handler(response)if not self.session_interface.is_null_session(ctx.session):self.save_session(ctx.session, response)return response

在最后判断如果session不是null session的话会执行self.save_session来保存更新session,在self.save_session中会调用response.set_cookie,flask中的session大概就是这样
总结一下:
1.分析了应用服务器封装好的environ从哪发送给后端
2.分析了应用服务器 WSGI 后端之间的关系以及WSGI协议对接口的标准定义,使得后端人员只需要关心自己的逻辑
3.分析了后端接收到应用服务器发来的WSGI请求之后的一系列处理流程,主要函数是wsgi_app(environ,start_response)
4.最后简单分析了flask中的session机制,它是client side session的.

应用服务器与WSGI协议以及flask后端框架总结(后端接收请求返回响应的整个流程)...相关推荐

  1. iris-v12 框架搭建三 接收与返回方式大全

    iris-v12 框架搭建三 接收与返回方式大全 URL上的传值 表单传值multipart/form-data 有文件 无文件 表单传值application/x-www-form-urlencod ...

  2. SpringMVC框架 学习DAY_02 : 接收请求参数/向模板页面转发数据/重定向与转发 /Session

    1. 接收客户端提交的请求参数 1.1. 使用HttpServletRequest接收请求参数 在处理请求的方法的参数列表中,添加HttpServletRequest类型的参数,在处理请求的过程中,调 ...

  3. djano框架和要点4.请求与响应

    请求 利用http协议向服务器传参有几种途径? 1.提取url特定部分,如/weath/beijing/2018,可以在服务器端的路由中用正则表达式截取 2.查询字符串(query string),形 ...

  4. Laravel框架中,Post请求返回419或者500,因为默认有csrf验证

    解决:打开文件:app\Http\Middleware\VerifyCsrfToken.php, 全部关闭 use Closure; class VerifyCsrfToken extends Mid ...

  5. [转载] Python Web开发最难懂的WSGI协议,到底包含哪些内容? WSGI服务器种类和性能对比

    参考链接: 在Python中创建代理Web服务器 1 http://python.jobbole.com/88653/ 我想大部分Python开发者最先接触到的方向是WEB方向(因为总是有开发者希望马 ...

  6. 2022年最好用的后端框架有哪些?做开发的你一定要知道

    四个最佳后端框架推荐 ​ 本文最初发布于 Ace Infoway 博客. 在这个瞬息万变的世界中,网站和 Web 应用程序已经成为公司拥有卓越线上版图的关键.稳固的线上版图,特别是网站和应用程序,可以 ...

  7. 后端框架有哪些?8个流行的后端框架推荐

    后端框架有哪些?8个流行的后端框架推荐 后端框架 在选择要使用的后端框架时,有许多选项可用.虽然每个后端框架都有自己的优点和缺点,但在做出最终决定之前,还有一些其他因素需要考虑.在本指南中,我们将仔细 ...

  8. API接口开发其实特简单,Python Flask Web 框架教程来了

    大家好,日常工作中,无论你是数据工程师.数据挖掘工程师,甚至数据分析人员,都不可避免的与他人进行数据交互,API接口提供数据是最常见的形式. 今天我给大家分享 Python Flask Web 框架教 ...

  9. 2022年移动应用程序开发的最佳后端框架

    技术的进步带来了移动应用程序开发过程的巨大变化.公司的目标是为客户提供有价值的产品--一种可以帮助用户满足他们需求的产品. 该框架被认为是移动应用程序开发过程的重要组成部分.框架是整个项目的依赖所依赖 ...

最新文章

  1. 使用B::Deparse模块对perl代码反汇编
  2. Java进程占用内存过高,排查解决方法
  3. Java高并发编程:HandlerThread
  4. 引领三消新模式 《梦幻花园》商业化分析
  5. hp服务器raid一直显示同步,HP Netserver NetRAID 一致性检查常见问题解答(FAQ)
  6. 目标检测技术演化:从R-CNN到Faster R-CNN
  7. 七代处理器装win7_为啥新电脑没法装windows7系统?解决方法总会有的
  8. python网络-多进程(21)
  9. jquery替换节点
  10. paip.java 调用c++ dll so总结
  11. pgAgent couldn't get a connection to the database!
  12. Linux内核块设备层介绍之bio层
  13. VMWARE ESXI 6.5U1离线补丁包升级教程
  14. Oracle中的next_day(date,char)
  15. 《关键对话》思维导图
  16. 智能过滤系统 西门子200smart与昆仑通态触摸屏做的自动过滤系统
  17. 3DS MAX 学习笔记 一(对齐工具)
  18. 基于Protobuf的通讯库--Poppy简介
  19. 云服务器修改虚拟内存,云服务器可以设置虚拟内存
  20. 如何在不安装应用程序的情况下旋转iPhone视频

热门文章

  1. javascript 自动按按钮
  2. 十三、进程互斥的软件实现方法
  3. 【建站系列教程】2.2、fiddler手机抓包教程
  4. 网络模型--Densely Connected Convolutional Networks
  5. Java常见面试题(持续更新)
  6. shell中的执行流控制
  7. mybatis mysql方言_MyBatis 方言支持 - Mysql to 华为高斯数据库(gaussdb)
  8. php中perl配置,配置Apache及运行perl,php
  9. 堆叠柱形图上自定义数字_柱形图的类别标签太长太宽,倾斜起来了怎么办?
  10. Centos7上yum安装mongodb(安装epel中的版本可能会比较老)