WSGI的全称是Web Server Gateway Interface,翻译过来就是Web服务器网关接口。具体的来说,WSGI是一个规范,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。WSGI一开始是在PEP-0333中定义的,最新版本是在Python的PEP-3333定义的。
那么,它实现了那些功能,用来做什么?
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规定:
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.可调用对象要返回一个值,这个值是可迭代的。
middleware
另外,有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。
middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传给服务器程序。
其实无论是服务器程序,middleware 还是应用程序,都在服务端,为客户端提供服务,之所以把他们抽象成不同层,就是为了控制复杂度,使得每一次都不太复杂,各司其职。
首先结合概念看这张图,
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 = "Hello World!, this is the wsgi response!
"
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服务器!
服务器端启动服务,等到客户端输入 curl -i http://localhost:8000/
命令,摁下回车键,看到终端上的输出,整个过程中,wsgi 的服务器端发生了什么呢?
我们先看一下 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 = "Hello World!, this is the wsgi response!
"
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,需要分别实现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的调用。
参考:
https://www.jianshu.com/p/c66d3adeaaed
https://www.cnblogs.com/wangkun122/articles/8493778.html