1.WHAT?(什么是wsgi,它用来做什么?)
WSGI的全称是Web Server Gateway Interface,翻译过来就是Web服务器网关接口。具体的来说,WSGI是一个规范,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。WSGI一开始是在PEP-0333中定义的,最新版本是在Python的PEP-3333定义的。
那么,它实现了那些功能,用来做什么?
- 操作 wsgi 的环境变量
- 应答头部的处理
- 实现简单的 HTTP server
- 简单的对程序端和服务器端校验函数
WSGI 是服务器程序与应用程序的一个约定,它规定了双方各自需要实现什么接口,提供什么功能,以便二者能够配合使用。
WSGI 不能规定的太复杂,否则对已有的服务器来说,实现起来会困难,不利于WSGI的普及。同时WSGI也不能规定的太多,例如cookie处理就没有在WSGI中规定,这是为了给框架最大的灵活性。要知道WSGI最终的目的是为了方便服务器与应用程序配合使用,而不是成为一个Web框架的标准。
另一方面,WSGI需要使得middleware(是中间件么?)易于实现。middleware处于服务器程序与应用程序之间,对服务器程序来说,它相当于应用程序,对应用程序来说,它相当于服务器程序。这样,对用户请求的处理,可以变成多个 middleware 叠加在一起,每个middleware实现不同的功能。请求从服务器来的时候,依次通过middleware,响应从应用程序返回的时候,反向通过层层middleware。我们可以方便地添加,替换middleware,以便对用户请求作出不同的处理。
WSGI协议分为两部分,分别为WSGI Server和WSGI Application,WSGI Server负责接受客户端请求、解析请求、并按照协议规范将请求转发给WSGI Application,同时负责接受WSGI Application的响应并发送给客户端;WSGI Application负责接受由WSGI Server发送过来的请求,实现业务处理逻辑,并将标准的响应发回给WSGI Server:

具体来说,WSGI Server解析客户端由socket发送过来的http数据包,将请求的http version、method、host、path等包装成environ参数,并提供start_response回调函数,并将environ和
start_response函数作为参数传递给由WSGI Application提供的callable对象,获取callable对象的返回结果,处理后依照http协议传递给客户端,完成一次请求。
它更像是一个客户端(浏览器)和服务器(服务器后端,具体处理请求,返回用户请求数据)之间的桥梁,在这个桥梁使用的过程中可以给他加一些其他自己需要的基础设施(插件)
WSGI 内容概要
WSGI主要是对应用程序与服务器端的一些规定,所以,它的主要内容就分为两个部分。
应用程序
WSGI规定:
1. 应用程序需要是一个可调用的对象
在Python中:
- 可以是函数
- 可以是一个实例,它的类实现了
__call__方法 - 可以是一个类,这时候,用这个类生成实例的过程就相当于调用这个类
同时,WSGI规定:
2. 可调用对象接收两个参数
这样,如果这个对象是函数的话,它看起来要是这个样子:
# callable function
def application(environ, start_response):
pass
如果这个对象是一个类的话,它看起来是这个样子:
# callable class
class Application:
def __init__(self, environ, start_response):
pass
如果这个对象是一个类的实例,那么,这个类看起来是这个样子:
# callable object
class ApplicationObj:
def __call__(self, environ, start_response):
pass
最后,WSGI还规定:
3.可调用对象要返回一个值,这个值是可迭代的。
另外,有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。
middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传给服务器程序。
其实无论是服务器程序,middleware 还是应用程序,都在服务端,为客户端提供服务,之所以把他们抽象成不同层,就是为了控制复杂度,使得每一次都不太复杂,各司其职。
2.HOW?(它是怎么实现所提供的功能的,我们在项目中怎么用?)
首先结合概念看这张图,

详细分析:
wsgi组成及作用:

wsgiref
|-- handlers.py # 核心代码,负责 wsgi 程序的处理
|-- headers.py # 头部处理的代码
|-- __init__.py #
|-- simple_server.py # 简单的 wsgi HTTP 服务器实现
|-- util.py # 帮助函数
`-- validate.py # wsgi 格式检查和校验
如何使用:
之前说到wsgi是对socket的封装,那么下面我们写一个最小的web服务器来看下wsgi怎么用,
首先我们不用wsgi,而是直接使用socket编程来实现web请求的处理
import socket
def req(client):
“”“
用户请求处理,也就是我们常说的application(应用)
”“”
buf = client.recv(1024)
print(buf)
msg = "HTTP/1.1 200 OK\r\n\r\n"
client.send(('%s' % msg).encode())
msg = "Hello, World! xxx this is socket with no wsgi!!!"
client.send(('%s' % msg).encode())
def main():
“”“
web服务器,实际上是socket bind本地回环等待用户发送请求到8000端口处理
”“”
ip_port = ("localhost", 8000)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(ip_port)
sock.listen(5)
while True:
conn, addr = sock.accept()
req(conn)
conn.close()
if __name__ == "__main__":
main()
运行如上代码,然后在浏览器输入 localhost:8000或者127.0.0.1:8000就会得到如下打印:
Hello, World! xxx this is socket with no wsgi!!!
再来看看wsgi的代码,对比socket会发现,过程一模一样!
from wsgiref.simple_server import make_server
def wsgireq(env, res):
print(env)
res("200 OK",[("Content-Type","text/html")])
body = "<h1>Hello World!, this is the wsgi response!</h1>"
return [body.encode("utf-8")]
if __name__ == "__main__":
httpd = make_server("127.0.0.1", 8000, wsgireq)
print("Serving http on port 8000")
httpd.serve_forever()
一样的方法,浏览器请求 localhost:8000或者127.0.0.1:8000就会得到如下打印:
Hello World!, this is the wsgi response!
区别在于:
wsgi将socket固定服务器等待请求的代码封装到make_server()方法中,代码整体看起来更简洁明了。将单条通信的msg封装成为整体更适用于web编程的body体,body体可以返回整体的HTML静态网页,当我们实现了所有请求处理后就会得到一个网站的web服务器!
一条 HTTP 请求的旅程:
服务器端启动服务,等到客户端输入 curl -i http://localhost:8000/ 命令,摁下回车键,看到终端上的输出,整个过程中,wsgi 的服务器端发生了什么呢?
- 服务器程序创建 socket,并监听在特定的端口,等待客户端的连接
- 客户端发送 http 请求
- socket server 读取请求的数据,交给 http server
- http server 根据 http 的规范解析请求,然后把请求交给 WSGIServer
- WSGIServer 把客户端的信息存放在 environ 变量里,然后交给绑定的 handler 处理请求
- HTTPHandler 解析请求,把 method、path 等放在 environ,然后 WSGIRequestHandler 把服务器端的信息也放到 environ 里
- WSGIRequestHandler 调用绑定的 wsgi ServerHandler,把上面包含了服务器信息,客户端信息,本次请求信息得 environ 传递过去
- wsgi ServerHandler 调用注册的 wsgi app,把 environ 和 start_response 传递过去
- wsgi app 将reponse header、status、body 回传给 wsgi handler
- 然后 handler 逐层传递,最后把这些信息通过 socket 发送到客户端
- 客户端的程序接到应答,解析应答,并把结果打印出来。
结合源码分析wsgi实现桥梁作用的原理:
simple_server.py
我们先看一下 make_server 是怎么启动一个 wsgi 服务器的:
def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler):
server = server_class((host, port), handler_class)
server.set_app(app)
return server
这个函数做的事情就是:监听在本地的端口上,接受来自客户端的请求,
通过 WSGIServer 和 WSGIRequestHandler 处理后,把请求交给程序的
可调用对象 app,然后返回 app 的结果给客户端。
对照我们写的服务器代码:
from wsgiref.simple_server import make_server
def wsgireq(env, res):
print(env)
res("200 OK",[("Content-Type","text/html")])
body = "<h1>Hello World!, this is the wsgi response!</h1>"
return [body.encode("utf-8")]
if __name__ == "__main__":
httpd = make_server("127.0.0.1", 8000, wsgireq)
httpd.serve_forever()
在这里我们传入的参数是ip port 和我们写好的app wsgireq,然后使用默认的
server_class=WSGIServer, handler_class=WSGIRequestHandler类来启动
我们的服务器。然后等待处理客户端的请求。
为了深入了解,我们后面分析下这两个默认类做了什么!
WSGIServer 和 WSGIRequestHandler。下面分析一下它们的代码和执行的功能:
class WSGIServer(HTTPServer):
"""BaseHTTPServer that implements the Python WSGI protocol"""
application = None
def server_bind(self):
"""Override server_bind to store the server name."""
HTTPServer.server_bind(self)
self.setup_environ()
def setup_environ(self):
# Set up base environment
env = self.base_environ = {}
env['SERVER_NAME'] = self.server_name
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
env['SERVER_PORT'] = str(self.server_port)
env['REMOTE_HOST']=''
env['CONTENT_LENGTH']=''
env['SCRIPT_NAME'] = ''
def get_app(self):
return self.application
def set_app(self,application):
self.application = application
WSGIServer 在原来的 HTTPServer 上面封装了一层,在原来的 HTTPServer
的基础上又额外做了下面的事情:
覆写原来的 server_bind 函数,添加初始化 environ 变量的动作
environ如下:
{'ALLUSERSPROFILE': 'C:\\ProgramData',
'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming',
'COMMONPROGRAMFILES': 'C:\\Program Files (x86)\\Common Files',
'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files',
'DRIVERDATA': 'C:\\Windows\\System32\\Drivers\\DriverData',等等 很长的......
environ参数是一个Python的字典,里面存放了所有和客户端相关的信息,这样application
对象就能知道客户端请求的资源是什么,请求中带了什么数据等。environ字典包含了一些CGI规范
要求的数据,以及WSGI规范新增的数据,还可能包含一些操作系统的环境变量以及Web服务器相关的
环境变量。我们来看一些environ中常用的成员:
首先是CGI规范中要求的变量:
REQUEST_METHOD: 请求方法,是个字符串,'GET', 'POST'等
SCRIPT_NAME: HTTP请求的path中的用于查找到application对象的部分,比如Web服务器可以根据
path的一部分来决定请求由哪个virtual host处理
PATH_INFO: HTTP请求的path中剩余的部分,也就是application要处理的部分
QUERY_STRING: HTTP请求中的查询字符串,URL中?后面的内容
CONTENT_TYPE: HTTP headers中的content-type内容
CONTENT_LENGTH: HTTP headers中的content-length内容
SERVER_NAME和SERVER_PORT: 服务器名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起
来可以得到完整的URL路径
SERVER_PROTOCOL: HTTP协议版本,HTTP/1.0或者HTTP/1.1
HTTP_: 和HTTP请求中的headers对应。
WSGI规范中还要求environ包含下列成员:
wsgi.version:表示WSGI版本,一个元组(1, 0),表示版本1.0
wsgi.url_scheme:http或者https
wsgi.input:一个类文件的输入流,application可以通过这个获取HTTP request body
wsgi.errors:一个输出流,当应用程序出错时,可以将错误信息写入这里
wsgi.multithread:当application对象可能被多个线程同时调用时,这个值需要为True
wsgi.multiprocess:当application对象可能被多个进程同时调用时,这个值需要为True
wsgi.run_once:当server期望application对象在进程的生命周期内只被调用一次时,该值为True
上面列出的这些内容已经包括了客户端请求的所有数据,足够application对象处理客户端请求了。
添加了处理满足 wsgi 的 app 函数:set_app 和 get_app
然后看另外一个类 WSGIRequestHandler:
class WSGIRequestHandler(BaseHTTPRequestHandler):
server_version = "WSGIServer/" + __version__
def get_environ(self):
env = self.server.base_environ.copy()
env['SERVER_PROTOCOL'] = self.request_version
env['REQUEST_METHOD'] = self.command
if '?' in self.path:
path,query = self.path.split('?',1)
else:
path,query = self.path,''
env['PATH_INFO'] = urllib.unquote(path)
env['QUERY_STRING'] = query
host = self.address_string()
if host != self.client_address[0]:
env['REMOTE_HOST'] = host
env['REMOTE_ADDR'] = self.client_address[0]
if self.headers.typeheader is None:
env['CONTENT_TYPE'] = self.headers.type
else:
env['CONTENT_TYPE'] = self.headers.typeheader
length = self.headers.getheader('content-length')
if length:
env['CONTENT_LENGTH'] = length
for h in self.headers.headers:
k,v = h.split(':',1)
k=k.replace('-','_').upper(); v=v.strip()
if k in env:
continue # skip content length, type,etc.
if 'HTTP_'+k in env:
env['HTTP_'+k] += ','+v # comma-separate multiple headers
else:
env['HTTP_'+k] = v
return env
def get_stderr(self):
return sys.stderr
def handle(self):
"""Handle a single HTTP request"""
self.raw_requestline = self.rfile.readline()
if not self.parse_request(): # An error code has been sent, just exit
return
handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
)
handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app())
这个类从名字就能知道它的功能——处理客户端的 HTTP 请求,它也是在原来处理 http 请求的
BaseHTTPRequestHandler 类上添加了 wsgi 规范相关的内容。
get_environ: 解析 environ 变量
handle: 处理请求,把封装的环境变量交给 ServerHandler,然后由 ServerHandler 调用
wsgi app,ServerHandler 类会在下面介绍。
handler.py
这个文件主要是 wsgi server 的处理过程,定义 start_response、调用 wsgi app 、处理
content-length 等等。
可以参考这篇文章里的 wsgi server 的简单实现。
WSGI的实现和部署
要使用WSGI,需要分别实现server角色和application角色。
Application端的实现一般是由Python的各种框架来实现的,比如Django, web.py等,一般开发者不需要关心WSGI的实现,框架会会提供接口让开发者获取HTTP请求的内容以及发送HTTP响应。
Server端的实现会比较复杂一点,这个主要是因为软件架构的原因。一般常用的Web服务器,如Apache和nginx,都不会内置WSGI的支持,而是通过扩展来完成。比如Apache服务器,会通过扩展模块mod_wsgi来支持WSGI。Apache和mod_wsgi之间通过程序内部接口传递信息,mod_wsgi会实现WSGI的server端、进程管理以及对application的调用。Nginx上一般是用proxy的方式,用nginx的协议将请求封装好,发送给应用服务器,比如uWSGI,应用服务器会实现WSGI的服务端、进程管理以及对application的调用。
参考:
本文介绍了WSGI(Web Server Gateway Interface),一个定义了Web服务器与Python应用程序交互规范的协议。内容包括WSGI的作用、功能、如何在项目中使用,以及详细分析了WSGI的实现原理和在HTTP请求中的角色。WSGI规定了服务器如何与应用程序对接,允许中间件的插入以处理特定功能,简化服务器和应用程序之间的通信。文中还探讨了WSGI的组成部分、使用方法,以及与直接使用socket编程的区别。
1104

被折叠的 条评论
为什么被折叠?



