Web 服务器网关接口(WSGI),又叫 Python Web Server Gateway Interface,是用来描述 Web 服务器与 Web 应用程序之间如何通信,以及应用程序如何处理请求的一套规范。它的第一个版本于 2003 年发布,此后 WSGI 逐渐成为 Python Web 应用开发中的重要标准,目前规范版本为 1.0.1。
使用 Python 做过 Web 开发的,对下面的例子应该非常熟悉,运行之后访问网站会显示 Hello, web!:
from wsgiref.simple_server import make_server
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, web!</h1>'
httpd = make_server('', 80, application)
print "Serving HTTP on port 80..."
httpd.serve_forever()
在上面的例子中,几行代码就可以搭建出一个 HTTP 服务器,并没有使用 Apache、Nginx 这样的专用 Web 服务器,而是使用了 wsgiref 中的模块。
对于这个服务来说,application 是用户层代码,而 WSGI 则在 Web 服务器和应用程序之间提供了一套统一的接口。

也就是说,WSGI 本身并不负责真正的业务处理,它更像是一条标准化的管道。最终处理用户请求的,还是用户层的代码。
这里就有两个问题:
- 如何把用户请求交给
application程序来处理? - 如何把用户的请求数据等信息一并交给
application?
WSGI 的处理方式很简单:为每个请求调用一个回调函数,这个回调函数接受两个参数。第一个参数为用户请求和运行环境的数据,第二个参数为 HTTP 响应头的回调函数。
由于这个回调函数就是用户层程序,因此就很容易通过参数的形式获取前端的请求数据等信息:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return "Your IP Addr is %s, Your Request Path is %s" % (
environ['REMOTE_ADDR'],
environ['PATH_INFO'][1:] or '/'
)
from wsgiref.simple_server import make_server
httpd = make_server('', 80, application)
print "Serving HTTP on port 80..."
httpd.serve_forever()
这样一来,网站应用程序就可以通过 WSGI 传入的 environ 参数获取用户输入以及其它请求信息,从而给不同的用户返回不同的内容。
实现一个简单的WSGI服务器
只有真正动手实现一个 WSGI 服务器,才能对它有更深入的认识。废话不多说,直接上代码:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 对WSGI的理解并对其进行实验
# https://www.python.org/dev/peps/pep-3333/
import socket,re,os
httpre=re.compile(r"^(\w*)\s*([^\s]*)\s*HTTP\/([.\d]*)[\r\n]*([\s\S]+)[\r\n]*[\r\n]*([\s\S]?)$")
headerre=re.compile(r"([^\s]*):\s*([^\r\n]*)[\r\n]")
def recvall(link,buflen):
buf = link.recv(buflen)
if len(buf) < buflen:
return buf
else:
return "%s%s"%(buf,recvall(link,buflen))
def headerdic(str):
mydic = {}
matches = headerre.findall(str)
for line in matches:
mydic[line[0]]=line[1]
return mydic
# http request return
# method, uri, httpversion, header, body
def httppareser(request):
try:
raw_re = httpre.findall(request.strip())[0]
return raw_re[0], raw_re[1], raw_re[2], raw_re[3], raw_re[4]
except:
return None
class WSGIServer:
def __init__(self,host="127.0.0.1",port=8080,keepalive=5):
self.host = host
self.port = port
self.keepalive = keepalive
self.environ = os.environ
def run(self,app):
sockt = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sockt.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sockt.bind((self.host,self.port))
sockt.listen(5)
while True:
con,addr = sockt.accept()
raw_request = recvall(con,1024)
method,uri,httpversion,header,body = httppareser(raw_request)
environ = header
global response_header
# start_response
def start_response(status,response_header_list):
global response_header
headers = ""
for header in response_header_list:
headers = headers+"%s:%s\r\n"%(header[0],header[1])
response_header = "HTTP/1.1 %s\r\n%s\r\n"%(status,headers)
response_body = app(environ,start_response)
con.send(response_header+response_body)
con.close()
# This is the application
def app(environ,start_response):
start_response(
"404 Not Found",
[
("Content-Type","text/html"),
("User-Agent","Mybody")
]
)
return """<!DOCTYPE HTML>
<html>
<head lang="zh-cn">
<meta charset="utf-8">
<title>你好世界</title>
<body>
<h1>Hello World</h1>
</body>
</html>"""
srv = WSGIServer()
srv.run(app)
当然,Python 对静态文件的处理效率还是比较低的,所以通常生产环境的做法是在 WSGI 前增加一个反向代理服务器。静态文件由 Web 服务器直接处理,动态请求则交给 Python 来处理:

我们注意到,在应用程序被调用的时候,WSGI 给我们传递了两个参数,一个是组合后的字典类型数据 environ,另一个是可调用的函数 start_response。
应用程序通过调用传入的函数来生成 HTTP 响应头部信息,再通过返回值生成 HTTP Body。
从网络传输的角度来看,HTTP 头部数据和 Body 最终都是需要通过网络发送的数据,只是它们在 HTTP 协议中承担的作用不同。为什么不直接返回头部和 Body 信息,而要额外传递一个函数?
实际上,这正是 WSGI 接口设计的一部分。通过 environ 传递请求环境,通过 start_response 设置响应头,再通过返回值返回响应内容,可以把 Web 服务器和应用程序之间的职责划分清楚。
对此,Python 后来也提出了新的 Web 网关接口方案,相关介绍可以参考 PEP 0444。
整体来看,WSGI 提供的其实就是一套标准接口,让 Web 服务器不需要关心 Python 应用程序内部是如何实现的,Python 应用程序也不需要关心底层 Web 服务器具体是什么。
这也是 WSGI 能够成为 Python Web 开发基础设施的重要原因。
评论0
欢迎分享你的看法,也欢迎补充不同的实践经验。