花了两个星期,我终于把 WSGI 整明白了

2020-07-08 00:00:00 函数 对象 请求 调用 路由
本文自个人公众号:Python编程时光

===== 2019.6.29 更新 =====

回答:为什么需要有 WSGI 协议,用 WSGI 协议的地方为何不直接用http?为什么要翻译一次?

======================

在 三百六十行,行行转 IT 的现状下,很多来自各行各业的同学,都选择 Python 这门胶水语言做为踏入互联网大门的块敲门砖,在这些人里,又有相当大比例的同学选择了 Web 开发这个方向(包括我)。而从事 web 开发,绕不过一个知识点,就是 WSGI。

不管你是否是这些如上同学中的一员,都应该好好地学习一下这个知识点。

由于我本人不从事专业的 python web 开发,所以在写这篇文章的时候,借鉴了许多的网络博客,并花了很多的精力阅读了大量的 OpenStack 代码。

为了写这篇文章,零零散散地花了大概两个星期。本来可以拆成多篇文章,写成一个系列的,经过一番思虑,还是准备一篇讲完,这就是本篇文章这么长的原因。

另外,一篇文章是不能吃透一个知识点的,本篇涉及的背景知识也比较多的,若我有讲得不到位的,还请你多多查阅其他人的网络博客进一步学习。

在你往下看之前,我先问你几个问题,你带着这些问题往下看,可能更有目的性,学习可能更有效果。

问1:一个 HTTP 请求到达对应的 application处理函数要经过怎样的过程?

问2:如何不通过流行的 web 框架来写一个简单的web服务?

一个HTTP请求的过程可以分为两个阶段,阶段是从客户端到WSGI Server,第二阶段是从WSGI Server 到WSGI Application



今天主要是讲第二阶段,主要内容有以下几点:

  1. WSGI 是什么,因何而生?
  2. 为什么需要有 WSGI?(补充)
  3. HTTP请求是如何到应用程序的?
  4. 实现一个简单的 WSGI Server
  5. 实现“高并发”的WSGI Server
  6. 次路由:PasteDeploy
  7. PasteDeploy 使用说明
  8. webob.dec.wsgify 装饰器
  9. 第二次路由:中间件 routes 路由

01. WSGI 是什么,因何而生?

WSGI是 Web Server Gateway Interface 的缩写。

它是 Python应用程序(application)或框架(如 Django)和 Web服务器之间的一种接口,已经被广泛接受。

它是一种协议,一种规范,其是在 PEP 333提出的,并在 PEP 3333 进行补充(主要是为了支持 Python3.x)。这个协议旨在解决众多 web 框架和web server软件的兼容问题。有了WSGI,你不用再因为你使用的web 框架而去选择特定的 web server软件。

常见的web应用框架有:Django,Flask等

常用的web服务器软件有:uWSGI,Gunicorn等

那这个 WSGI 协议内容是什么呢?知乎上有人将 PEP 3333 翻译成中文,写得非常好,我将这段协议的内容搬运过来。

WSGI 接口有服务端和应用端两部分,服务端也可以叫网关端,应用端也叫框架端。服务端调用一个由应用端提供的可调用对象。如何提供这个对象,由服务端决定。例如某些服务器或者网关需要应用的部署者写一段脚本,以创建服务器或者网关的实例,并且为这个实例提供一个应用实例。另一些服务器或者网关则可能使用配置文件或其他方法以指定应用实例应该从哪里导入或获取。

WSGI 对于 application 对象有如下三点要求

  1. 必须是一个可调用的对象
  2. 接收两个必选参数environ、start_response。
  3. 返回值必须是可迭代对象,用来表示http body。

02. 为什么要有WSGI?

这是来自评论区的一个问题,我觉得问得很好,所以来答一下,更新在这里。

请教下用wsgi协议的地方为何不直接用http?为什么要翻译一次?

以下是我的回答,个人理解,仅供交流。

web框架(即app)在生产中一般不用于直接接收http请求。

你可能会说,django不就可以直接接收http请求吗,也不需要uwsgi之类的所谓的服务器。

其实不是,django只是在其内部自己实现了一个简易的web服务器,以供开发调试之用。所以初学者往往会误以为,web app框架本身就可以接收http请求。

web 服务器 和 web 框架,分工不同,职责不同(web 服务器专注于接收并解析请求以调用的方式将请求的内容传web框架),缺一不可,可以说它们是两个组件,共同协作才能实现web网页的访问,既然是两个组件,那总要定义一些约定俗成的通讯协议,而这就是WSGI,所以必须有WSGI。

那接下来,就引出另一个问题了:如果它们不分开,而将二者整合在一起,对外只有一个组件,是不是就没有WSGI什么事了?

答案,是的。

但是你也可以发现目前市场上有相当多的大大小小的web开发框架,如果每个框架都去自己实现web服务器,那岂不是重复造轮子?

好的情况应该是,由专业的团队去开发专业的web服务器,而开发出来的web服务器需要具备框架通用性,Django可以用,Flask也可以用,开发者也可以自由选择用哪个web 服务器软件,用哪个web 框架,灵活组合。

03. HTTP请求是如何到应用程序的?

当客户端发出一个 HTTP 请求后,是如何转到我们的应用程序处理并返回的呢?

关于这个过程,细节的点这里没法细讲,只能讲个大概。

我根据其架构组成的不同将这个过程的实现分为两种:



1、两级结构 在这种结构里,uWSGI作为服务器,它用到了HTTP协议以及wsgi协议,flask应用作为application,实现了wsgi协议。当有客户端发来请求,uWSGI接受请求,调用flask app得到相应,之后相应给客户端。 这里说一点,通常来说,Flask等web框架会自己附带一个wsgi服务器(这就是flask应用可以直接启动的原因),但是这只是在开发阶段用到的,在生产环境是不够用的,所以用到了uwsgi这个性能高的wsgi服务器。

2、三级结构 这种结构里,uWSGI作为中间件,它用到了uwsgi协议(与nginx通信),wsgi协议(调用Flask app)。当有客户端发来请求,nginx先做处理(静态资源是nginx的强项),无法处理的请求(uWSGI),后的相应也是nginx回复给客户端的。 多了一层反向代理有什么好处?

提高web server性能(uWSGI处理静态资源不如nginx;nginx会在收到一个完整的http请求后再转发给wWSGI)

nginx可以做负载均衡(前提是有多个服务器),保护了实际的web服务器(客户端是和nginx交互而不是uWSGI)

04. 实现一个简单的 WSGI Server

在上面的架构图里,不知道你发现没有,有个库叫做 wsgiref ,它是 Python 自带的一个 wsgi 服务器模块。

从其名字上就看出,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。

有了 wsgiref 这个模块,你就可以很快速的启动一个wsgi server。

from wsgiref.simple_server import make_server

# 这里的 appclass 暂且不说,后面会讲到
app = appclass()
server = make_server('', 64570, app)
server.serve_forever()

相关文章