详解 Python 程序与服务器连接的 WSGI 接口

文章为转载,原文见这里,侵删

这篇文章主要介绍了 Python 程序与服务器连接的 WSGI 接口,是 Python 网络编程学习当中的重要内容,需要的朋友可以参考下

了解了 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!”:

1
2
3
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 用一个包含两个 str 的 tuple 表示。

通常情况下,都应该把 Content-Type 头发送给浏览器。其他很多常用的 HTTP Header 也应该发送。然后,函数的返回值 Hello, web! 将作为 HTTP 响应的 Body 发送给浏览器。

有了 WSGI,我们关心的就是如何从 environ 这个 dict 对象拿到 HTTP 请求信息,然后构造 HTML,通过 start_response () 发送 Header,最后返回 Body。整个 application () 函数本身没有涉及到任何解析 HTTP 的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。

不过,等等,这个 application () 函数怎么调用?如果我们自己调用,两个参数 environ 和 start_response 我们没法提供,返回的 str 也没法发给浏览器。
所以 application () 函数必须由 WSGI 服务器来调用。有很多符合 WSGI 规范的服务器,我们可以挑选一个来用。但是现在,我们只想尽快测试一下我们编写的 application () 函数真的可以把 HTML 输出到浏览器,所以,要赶紧找一个最简单的 WSGI 服务器,把我们的 Web 应用程序跑起来。

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

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

1
2
3
4
5
#hello.py  
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, web!</h1>'

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

1
2
3
4
5
6
7
8
9
10
11
# 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 服务

启动server

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

浏览器观察

再看看刚刚打开的 cmd 窗口,会输出请求的如下所示

请求情况

可以看到总共有两个请求源,其中 LC-PC 是通过电脑浏览器输入 localhost 访问,而 192.168.1.109 是通过手机浏览器访问的(电脑和手机在同一局域网下);输出的内容包括请求的 ip,时间和 http 请求头。

无论多么复杂的 Web 应用程序,入口都是一个 WSGI 处理函数。HTTP 请求的所有输入信息都可以通过 environ 获得,HTTP 响应的输出都可以通过 start_response () 加上函数返回值作为 Body。
复杂的 Web 应用程序,光靠一个 WSGI 函数来处理还是太底层了,我们需要在 WSGI 之上再抽象出 Web 框架,进一步简化 Web 开发。