转载请注明出处: https://blog.csdn.net/jinixin/article/details/84677104

WSGI是什么? 为什么涉及部署时就会提到这个概念? WSGI的作用是什么? 没了它, 服务是不是照样没问题? 每当编写Python Web项目的时候, 这些问题就开始困扰我. 经过一番阅读和讨论后, 想在此简单谈谈.

CGI

在谈WSGI前, 我们需要先了解下CGI是什么.

互联网初步开放于上世纪90年代, 一开始主要用于浏览资料之类的, 而对Web服务器的要求, 也就是接收用户请求, 并返回(响应)存储在其上的静态文件. 在此过程中, Web服务器主要负责编解HTTP请求. 但随着互联网深入人们的生活, 出现了表单(form)这种东西, 就开始要求Web服务器能接收用户提交的数据, 在进行一些处理后, 再将生成的内容返给用户, 这就是动态页面技术.

显然进行一些处理的操作并不适合集成在Web服务器里, 试想下A公司需接收购物表单, B公司要接收注册表单, 两者肯定是不同的处理逻辑, 不可能都去修改Web服务器代码, 所以处理的工作就由Web服务器交给扩展程序去做. 因此整个流程就变为, 用户(浏览器)向Web服务器发起请求, 扩展程序接收Web服务器解析好的HTTP请求, 做一些业务处理后, 再将生成内容返给Web服务器, 最后由Web服务器按HTTP协议编码后回传给用户(浏览器), 而这个扩展程序一般被称为应用服务.

Web服务器一般由C编写, 应用服务则由更高级的C++/PHP/Python/GO来编写, Web服务器与应用服务的交互规则就被称为通用网关接口, 即CGI(Common Gateway Interface). 更进一步说, CGI协议描述了Web服务器如何将解析后的请求头传递给应用服务, 以及应用服务如何从标准输入中读取请求内容, 如何将响应内容通过标准输出回传给Web服务器.

CGI的出现使应用服务与Web服务器间的交互成为可能, 但CGI要求对每个Web请求单独创建一个应用服务进程. 因此面对大量请求, 大量进程的创建和消亡会使操作系统性能大大降低. 此外, 由于地址空间无法共享, 这也限制了资源重用, 所以CGI因效率问题已逐渐被淘汰.

虽然CGI已逐渐不被使用, 但他的出现为之后Web服务器与应用服务间的交互奠定了良好基础, 下面即将提到的WSGI就是对CGI的良好继承. 此外, CGI也出现了改良版FastCGI(快速通用网关接口), FastCGI不再为每个请求都创建新的应用服务进程, 而是使用持续的进程来处理一连串请求. 当进来一个请求时, Web服务器会把解析后的请求通过socket(当应用服务位于本地时)或TCP连接(当应用服务位于远端时)传递给应用服务进程.

WSGI的定义

有了上面对CGI的介绍, WSGI就很好理解了. WSGI是类似CGI的概念, 但其只用于Python, 即Python的Web服务器网关接口(Python Web Server Gateway Interface). WSGI作为一种接口协议, 连接Web服务器和Python应用服务这两部分, 使得两者解耦, 互不影响, 也使得Python应用服务可以平滑在不同种类的Web服务器间迁移.

你可能会有疑问: 你说的Web服务器和Python应用服务具体是指什么?

你可以先认为Web服务器就是能解析或代理HTTP请求的程序, 常见有Nginx, Apache等; 应用服务就是我们用Flask, Tornado编写的Web应用.

WSGI在PEP-0333中被首次提出, 之后在PEP-3333中增加了对Python3的支持.

WSGI是一个同步协议, 其主要规定了两个角色: WSGI服务端(server/gateway), WSGI应用端(application/framework). 而这两部分应该如何实现呢? 下面马上介绍.

WSGI的实现

通过上面我们知道, 支持WSGI协议, 就需要实现WSGI服务端和WSGI应用端两部分.

WSGI应用端需实现一个可调用对象application, 该对象接收两个参数environ和start_response, 并返回一个可迭代对象. 每当WSGI服务端接收到请求后, 就会调用WSGI应用端的application, 并传入environ和start_response, 即调用application(environ, start_response). application由WSGI应用端定义, environ与start_response由WSGI服务端定义.

1. 参数介绍

environ是一个Python字典, 表示解析后的请求头, 其由WSGI服务端定义并传给WSGI应用端的application.

start_response是一个可调用对象, 表示开启响应, 向Web服务器发送响应头. 其由WSGI服务端定义并传给WSGI应用端的application, start_response按序接收三个参数: status(必要参数, 表示响应状态), response_headers(必要参数, 表示响应头), exec_info(可选参数, 表示响应的异常信息), 这些参数均由WSGI应用端传入:

  • status是字符串, 内容为"响应状态码 响应状态信息", 比如"200 OK"或"404 NOT FOUND";
  • response_headers是由(header_key, header_val)组成的列表, WSGI服务端可以修改, 如果响应头里有缺省的key, WSGI服务端会补充;
  • exec_info是sys.exc_info的返回值, 表示WSGI应用端捕获并打算回传给WSGI服务端的错误.

实现application时, 需保证其向WSGI服务端返回第一块响应内容前会调用start_response, 以使得响应头提前于响应内容被发送. 实际上start_response在被调用后, 不会立即向Web服务器发送响应头, 相反其会先将响应头存储起来, 直到application被迭代过一次后, 即准备向用户返回响应内容了, 响应头才会被发出, 此规则唯一例外是响应头中明确指定Content-Length为0. 有必要解释下, 延迟传输响应头是为了到最后一刻都可以用异常输出替换原来的预期输出. 例如, 在缓冲区生成响应正文时发生错误, 则响应状态需从"200 OK"改为"500 Internal Error". 如果响应头还没发出去, 那直接返回500即可, 但如果已经发送了响应头, start_response就必须立即报错, 直接"raise exc_info[1].with_traceback(exc_info[2])".

此外, 如果application返回的可迭代对象有close方法, 则无论请求是否正常完成, WSGI服务端都会调用该迭代对象的close方法, 以帮助WSGI应用端释放资源.

2. 大体实现

下面就是依据WSGI协议对两部分的大体实现:

# WSGI应用端
def application(environ, start_response):start_response('200', [('content-type', 'text/html')])  # 开启响应, 向Web服务器返回响应头while True:yield '...'  # 返回响应内容
# WSGI服务端
def response():environ = {}def start_response():pass  # 开启响应(返回响应头)body = ''result = application(environ, start_response)  # 每收到一个HTTP请求, 便会调用一次applicationfor temp in result:body += temp  # 拼接响应内容return body  # 返回响应内容

WSGI的应用

Enmmm...WSGI也许的确如你上面所说, 但是该怎么用呢? 他是必须的吗?

1. WSGI该怎么用?

在工作中我们不需要自己编写WSGI, 而是可以依靠现有的第三方库. 比如WSGI应用端有Werkzeug, WSGI服务端有uWSGI, gunicorn等.  WSGI服务端的这些库一般都自带有Web服务器(HTTP服务器), 即Web服务器已经连接WSGI服务端了. 常见的应用框架(Flask, Tornado等)也已经为我们集成了WSGI应用端. 所以我们要做的仅仅是连接WSGI服务端与WSGI应用端, 这个一般不需要编码, 往往通过一条命令即可完成, 具体命令要看所使用的WSGI应用端. 下面让我们看看各个框架是怎样将本身的Web应用变为WSGI应用的.

首先Flask的简单应用:

from flask import Flaskapp = Flask(__name__)@app.route('/')
def index():return 'Hello Kitty'if __name__ == '__main__':app.run()

其中Flask的定义如下, 当调用"app()"时, 其会被转成一个WSGI应用.

class Flask(_PackageBoundObject):...def __call__(self, environ, start_response):"""The WSGI server calls the Flask application object as theWSGI application. This calls :meth:`wsgi_app` which can bewrapped to applying middleware."""return self.wsgi_app(environ, start_response)

再比如Tornado的简单应用:

# coding=utf-8import tornado.web
import tornado.wsgi
import tornado.httpserver
import tornado.ioloopclass IndexHandler(tornado.web.RequestHandler):def get(self):self.write("Hello Kitty")def main():application = tornado.web.Application([(r"/", IndexHandler),])wsgi_app = tornado.wsgi.WSGIAdapter(application)  # 将Tornado的Web应用转为WSGI应用端container = tornado.wsgi.WSGIContainer(wsgi_app)  # WSGI服务端http_server = tornado.httpserver.HTTPServer(container)  # 定义HTTP服务器http_server.listen(8888)tornado.ioloop.IOLoop.current().start()if __name__ == "__main__":main()

其中Tornado的WSGIAdapter定义如下, 该类会把一个Tornado的Web应用转变为WSGI应用. 但副作用也很明显, Tornado本身是支持异步请求的, 而WSGI是一个同步协议, 所以经过WSGIAdapter转换后, 整个应用将不再支持异步.

class WSGIAdapter(object):def __init__(self, application):if isinstance(application, WSGIApplication):self.application = lambda request: web.Application.__call__(application, request)else:self.application = applicationdef __call__(self, environ, start_response):pass

下面是Tornado中WSGIContainer定义, 可以看出该类的__call__方法会主动调用WSGI应用端的application对象, 并会传入environ和start_response, 很明显该类实现了WSGI服务端. 事实上WSGIContainer可以接收任何Python框架(Tornado, Flask, Django等)所实现的WSGI应用端, 并使得这些应用可以跑在Tornado的HTTP服务器与IO循环上.

class WSGIContainer(object):def __init__(self, wsgi_application):self.wsgi_application = wsgi_applicationdef __call__(self, request):data = {}response = []def start_response(status, response_headers, exc_info=None):  # 定义了start_responsedata["status"] = statusdata["headers"] = response_headersreturn response.appendapp_response = self.wsgi_application(WSGIContainer.environ(request), start_response)  # 调用WSGI应用端的application, 并传入environ与start_response

其实Tornado的简单应用还有另种写法:

# coding=utf-8import tornado.ioloop
import tornado.webclass IndexHandler(tornado.web.RequestHandler):def get(self):self.write('Hello Kitty')def main():app = tornado.web.Application([  # Tornado的Web应用(r'/', IndexHandler),])app.listen(8888)tornado.ioloop.IOLoop.current().start()if __name__ == '__main__':main()

所以我们可以看出Tornado是一个比较有意思的框架, 一般框架也就实现个WSGI应用端, 但Tornado另外还实现了可用于生产的HTTP服务器和WSGI服务端. 因此目前由Tornado开发的应用服务, 不仅可以像其他Python Web应用那样通过"Web服务器 --> WSGI实现 --> Web应用"的模式做到线上部署, 还可通过自身"自带的HTTP服务器 --> Tornado应用"模式实现一步到位.

但好景不长啊, WSGIAdapter将在Tornado 6.0中被移除, 那之后Tornado应用将如何变为WSGI应用呢, 我们拭目以待, 或者Tornado根本就不关心.

2. WSGI是必须的吗?

结论是只要Web应用涉及线上服务, 即会和用户打交道, WSGI基本上就是必须的(排除Tornado哈). 因为没有WSGI, 常见的应用框架(Django, Flask)是没有办法将HTTP请求解析成Python的request对象的.

你也许会质疑, 我在调试这些框架编写的应用时, 直接就可以运行了, 我也没装Web服务器和WSGI服务端呀?

这主要是因为各大框架为了方便调试, 都内置了测试服务器, 但如果需要用于线上环境, 还是需要正式服务器的支持. 当然如果这些应用仅仅用于离线服务, 那么内置的测试服务器大抵也是可以的.

整个流程

1. 用户(浏览器)发起请求, Web服务器接收HTTP请求, 并将解析后的请求转发给WSGI服务端.

2. WSGI服务端收到请求后立即调用WSGI应用端的application对象, 并将生成的environ与事先定义好的start_response传给该对象.

3. application对象会解析environ, 根据其中的URI调用应用服务中对应的处理方法. 处理完成后, application对象会先调用start_response开启响应, 再将应用服务方法的响应内容迭代返回给WSGI服务端.

4. 最后, WSGI服务端将响应发送给Web服务器, 再由Web服务器返回给用户(浏览器).

此外, 目前比较流行在Web服务器前再加一个Web服务器(比如Nginx)用以代理用户的HTTP请求, 当然其并不会去解析请求. 具体作用嘛, 可能是负载均衡, 控制访问峰值等.

文中如有不当之处, 还望包容和指出, 感谢.

「WSGI」WSGI概述相关推荐

  1. 云原生系列「0」容器概述

    一.Docker概述 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的Linux或Windows 机器上,也可以实现虚拟化. 1. ...

  2. 必看!Spark 进阶之路之「SparkSQL」入门概述 | 博文精选

    作者 | Alice菌 责编 | Carol 来源 | CSDN 博客 封图 | CSDN付费下载于视觉中国 在之前的文章中,我们已经完成了对于Spark核心SparkCore的详细介绍.而今天想为为 ...

  3. 计算机网络「五」 运输层

    前言:本文为计算机网络系列第五章笔记,陆续会更新余下内容.文章参了:计算机网络微课堂.<王道考研计算机网络考研复习指导>.<计算机网络( 第7版 )>-- 谢希仁 .本文仅供学 ...

  4. 计算机网络「四」 网络层

    本文为计算机网络系列第四章笔记,陆续会更新余下内容.文章参考:计算机网络微课堂 系列文章: 计算机网络「一」计算机网络概述 计算机网络「二」物理层 计算机网络「三」 数据链路层   需要说明:文章中图 ...

  5. 计算机网络「二」—— 物理层(多图详解)

    本文自学计算机网络时所写笔记,网课为B站湖科大教书匠的 计算机网络微课堂.(强烈安利这个课程,讲课思路条理清晰,PPT美轮美奂.通俗易懂) 本文为第二章笔记,陆续会更新余下内容 计算机网络「一」计算机 ...

  6. 「Arm Arch」 ISA 概述

    本文源自<书香度年华>「ARM 架构专栏」,是一系列由浅入深.循序渐进的文章,文章之间有一定的前后关联性,所以按顺序阅读,建议收藏专栏. 一.定义 ISA是计算机硬件与系统软件之间的接口, ...

  7. 图像、视频生成大一统!MSRA+北大全华班「女娲」模型怒刷8项SOTA,完虐OpenAI DALL-E...

      视学算法报道   编辑:好困 小咸鱼 LRS [新智元导读]微软亚洲研究院.北京大学强强联合提出了一个可以同时覆盖语言.图像和视频的统一多模态预训练模型--NÜWA(女娲),直接包揽8项SOTA. ...

  8. 「天才少年」稚晖君调戏机械臂!加上AI视觉,2小时学会抓螺母【文末送5本书】...

      视学算法报道   编辑:桃子 小咸鱼 文末包邮送5本价值百元的高质量机器学习技术书籍 [新智元导读]还记得上次那个全栈自研给葡萄缝针的钢铁侠机械臂Dummy吗?这次,华为「天才少年」稚晖君用机械臂 ...

  9. 一个模型通杀8大视觉任务,图像、视频生成大一统!MSRA+北大全华班「女娲」模型...

    来源:新智元 太卷了,太卷了!微软亚洲研究院.北京大学强强联合提出了一个可以同时覆盖语言.图像和视频的统一多模态预训练模型--NÜWA(女娲),包揽8项SOTA,完虐OpenAI DALL-E! 照着 ...

最新文章

  1. 做了7年软件工程师,从500多场技术面试中学到了什么?
  2. NSURLProtectionSpace 证书认证的上下文
  3. 让fedora18桌面显示图标
  4. 实现权限控制_Spring自定义注解+AOP实现权限控制
  5. Tuomas Pirinen:创造游戏人物的8个方法
  6. 围观神龙架构首次开箱,现场直播暴力拆机
  7. 深度学习模型可解释性初探
  8. FreeMarker MyEclipse IDE
  9. (单层)感知机学习规则
  10. php tr td,php-基于tr计数的Td / th的XPath
  11. axure html图标 图片大小,Axure 图标解决方案_html/css_WEB-ITnose
  12. spyder python_spyder python2.7下载
  13. DOS窗口打开本地应用,打开chrome浏览器
  14. OpenKG开源系列 | 面向知识的推理问答编程语言KoPL(清华大学)
  15. python网站数据监测_python 网站数据监控
  16. Hashtable(哈希表)
  17. 二十四小时不插电生活方案
  18. 全国大学生计算机等级考试计算机二级python真题
  19. android开发 问卷调查案例_安卓 问卷调查Demo 原生代码
  20. 在 Excel 启动时运行宏

热门文章

  1. Mysql4_常见函数
  2. CSS 实用工具: Google Fonts API 引入免费字体库
  3. JS 如何动态获取本地文件夹中的所有图片
  4. html设计登黄鹤楼怎么搞,崔颢的《黄鹤楼》和李白的《登金陵凤凰台》
  5. 消除痘印的方法还你光洁肌肤
  6. [转载]Python SMTP发送邮件-smtplib模块
  7. win10重命名文件夹找不到指定文件
  8. c语言 until,for,while,until
  9. ZJOI2017 NGU!
  10. PolarDB-X 1.0和RDS性能对比之吞吐量对比(三)