WSGI接口简单介绍以及用 WSGI 协议的地方为何不直接用http?

了解了HTTP协议和HTML文档,我们其实就明白了一个Web应用的本质就是:

  1. 浏览器发送一个HTTP请求;

  2. 服务器收到请求,生成一个HTML文档;

  3. 服务器把HTML文档作为HTTP响应的Body发送给浏览器;

  4. 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。

所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。

如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。

正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。

这个接口就是WSGI:Web Server Gateway Interface。

WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello, web!”:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return '<h1>Hello, web!</h1>'

上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

  • environ:一个包含所有HTTP请求信息的dict对象;

  • start_response:一个发送HTTP响应的函数。

application()函数中,调用:

start_response('200 OK', [('Content-Type', 'text/html')])

就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每个Header用一个包含两个strtuple表示。

通常情况下,都应该把Content-Type头发送给浏览器。其他很多常用的HTTP Header也应该发送。

然后,函数的返回值'<h1>Hello, web!</h1>'将作为HTTP响应的Body发送给浏览器。

有了WSGI,我们关心的就是如何从environ这个dict对象拿到HTTP请求信息,然后构造HTML,通过start_response()发送Header,最后返回Body。

整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。

不过,等等,这个application()函数怎么调用?如果我们自己调用,两个参数environstart_response我们没法提供,返回的str也没法发给浏览器。

所以application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。但是现在,我们只想尽快测试一下我们编写的application()函数真的可以把HTML输出到浏览器,所以,要赶紧找一个最简单的WSGI服务器,把我们的Web应用程序跑起来。

好消息是Python内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。

运行WSGI服务

我们先编写hello.py,实现Web应用程序的WSGI处理函数:

# hello.py

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return '<h1>Hello, web!</h1>'

然后,再编写一个server.py,负责启动WSGI服务器,加载application()函数:

# server.py
# 从wsgiref模块导入:
from wsgiref.simple_server import make_server
# 导入我们自己编写的application函数:
from hello import application

# 创建一个服务器,IP地址为空,端口是8000,处理函数是application:
httpd = make_server('', 8000, application)
print "Serving HTTP on port 8000..."
# 开始监听HTTP请求:
httpd.serve_forever()

确保以上两个文件在同一个目录下,然后在命令行输入python server.py来启动WSGI服务器:

注意:如果8000端口已被其他程序占用,启动将失败,请修改成其他端口。

启动成功后,打开浏览器,输入http://localhost:8080/,就可以看到结果了:

# wsgi app

def application(environ, start_response):
    response_body = "<h1>Hello World</h1>"
    header = [('Content-Type', 'text/html')]
    status = "200 OK"
    start_response(status, header)
    print "environ http request method:"+environ['REQUEST_METHOD']
    return [response_body]

if __name__ == "__main__":
    from wsgiref.simple_server import make_server
    httpd = make_server("0.0.0.0", 8080, application)
    print "httpd run on :"+str(httpd.server_port)
    httpd.serve_forever()
wsgi app

小结

无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ获得,HTTP响应的输出都可以通过start_response()加上函数返回值作为Body。

复杂的Web应用程序,光靠一个WSGI函数来处理还是太底层了,我们需要在WSGI之上再抽象出Web框架,进一步简化Web开发。

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

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

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

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

 

 

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

  1. WSGI 是什么,因何而生?
  2. 为什么需要有 WSGI?(补充)
  3. HTTP请求是如何到应用程序的?

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)

https://zhuanlan.zhihu.com/p/68676316

https://www.liaoxuefeng.com/wiki/897692888725344/923057027806560

原文地址:https://www.cnblogs.com/leijiangtao/p/3969704.html