PEP 0333 – Python Web Server Gateway Interface 是一种 web server or gateway 和 python web application or framework 之间简单通用的接口,符合这种接口的 application 可运行在所有符合该接口的 server 上。通俗的讲,WSGI 规范了一种简z单的接口,解耦了 server 和 application,使得双边的开发者更加专注自身特性的开发。
Web server/gateway: 即 HTTP Server,处理 HTTP 协议,接受用户 HTTP 请求和提供并发,调用 web application 处理业务逻辑。通常采用 C/C++ 编写,代表:apache, nginx 和 IIS。
Python Web application/framework: 专注业务逻辑的 python 应用或者框架。
The Application/Framework Side
Application/framework 端必须定义一个 callable object,callable object 可以是以下三者之一:
function, method
class
instance with a __call__ method
Callable object 必须满足以下两个条件:
接受两个参数:字典(environ),回调函数(start_response,返回 HTTP status,headers 给 web server)
返回一个可迭代的值
基于 callable function 的 application/framework 样例如下:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['This is a python application!']
基于 callable class 的 application/framework 样例如下:
class ApplicationClass(object):
def __init__(self, environ, start_response):
self.environ = environ
self.start_response = start_response
def __iter__(self):
self.start_response('200 OK', [('Content-type', 'text/plain')])
yield "Hello world!n"
The Server/Gateway Side
Server/gateway 端主要专注 HTTP 层面的业务,重点是接收 HTTP 请求和提供并发。每当收到 HTTP 请求,server/gateway 必须调用 callable object:
接收 HTTP 请求,但是不关心 HTTP url, HTTP method 等
为 environ 提供必要的参数,实现一个回调函数 start_response,并传给 callable object
调用 callable object
我们直接使用支持 WSGI 框架的 wsgiref 库,编写一个样例:
# application/framework side
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['This is a python application!']
# server/gateway side
if __name__ == '__main__':
from wsgiref.simple_server import make_server
server = make_server('0.0.0.0', 8080, application)
server.serve_forever()
运行后,对于任意的 url 和 method,本例的返回值均为 ‘This is a python application!’
$curl 127.0.0.1:8080
This is a python application!
$ curl 127.0.0.1:8080/test
This is a python application!
Middleware: Components that Play Both Sides
Middleware 处于 server/gateway 和 application/framework 之间,对 server/gateway 来说,它相当于 application/framework;对 application/framework 来说,它相当于 server/gateway。每个 middleware 实现不同的功能,我们通常根据需求选择相应的 middleware 并组合起来,实现所需的功能。比如,可在 middleware 中实现以下功能:
1.根据 url 把用户请求调度到不同的 application 中。
2.负载均衡,转发用户请求
3.预处理 XSL 等相关数据
44限制请求速率,设置白名单
本例实现了一个 IPBlacklist 的 middleware:
class IPBlacklistMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
ip_addr = environ.get('HTTP_HOST').split(':')[0]
if ip_addr not in ('127.0.0.1'):
return forbidden(start_response)
return self.app(environ, start_response)
def forbidden(start_response):
start_response('403 Forbidden', [('Content-Type', 'text/plain')])
return ['Forbidden']
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['This is a python application!']
if __name__ == '__main__':
from wsgiref.simple_server import make_server
application = IPBlacklistMiddleware(application)
server = make_server('0.0.0.0', 8080, application)
server.serve_forever()
# 从本机测试
$ curl 127.0.0.1:8080/test
This is a python application!
# 从其它主机测测试
$ curl 10.10.10.2:8080/test
Forbidden
Path Dispatching
至此样例的一个不足之处是对于任意的 url 和 method,程序的返回值均为 ‘This is a python application!’,所以我们需要增加 path dispatch 功能。由于 WSGI 框架下的 server/gateway 不处理 url 和 method,所以 url mapping 需由 application/framework 端完成。注意到参数 environ,它包含以下变量:
1.REQUEST_METHOD: 即 HTTP method
2.PATH_INFO: 即 HTTP url
所以 application/framework 可以根据 environ 的 REQUEST_METHOD 和 PATH_INFO 实现 path dispatch,样例如下:
class IPBlacklistMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
ip_addr = environ.get('HTTP_HOST').split(':')[0]
if ip_addr not in ('127.0.0.1'):
return forbidden(start_response)
return self.app(environ, start_response)
def dog(start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['This is dog!']
def cat(start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['This is cat!']
def not_found(start_response):
start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
return ['Not Found']
def forbidden(start_response):
start_response('403 Forbidden', [('Content-Type', 'text/plain')])
return ['Forbidden']
def application(environ, start_response):
path = environ.get('PATH_INFO', '').lstrip('/')
mapping = {'dog': dog, 'cat': cat}
call_back = mapping[path] if path in mapping else not_found
return call_back(start_response)
if __name__ == '__main__':
from wsgiref.simple_server import make_server
application = IPBlacklistMiddleware(application)
server = make_server('0.0.0.0', 8080, application)
server.serve_forever()