前言
起源
Python的Web开发框架太多了,设计、开发、部署Web应用的方式也是层出不穷,这样的情况自然会带来组件的开发效率低下等问题。
WSGI是一个标准,它提供了一套相对简单又很容易理解、可以满足Web服务器和Web框架之间近乎所有的的交互动作的一套接口。其最主要的目的就是为了提升整个Python Web开发生态环境中,组件的可重用性。
另一个目的是为了支持“中间层,middleware”的组件,
架构
WSGI定义了3个角色:底层的web server,上层的web application / framework,中间的WSGI middleware。这三个角色各自承担的任务分别是:
Web server side
服务器端需要提供两样东西:一个名为 environ
的字典,一个名为 start_response
的函数。
- environ: 环境变量相关的一些参数
- start_response: 两个参数
- status: '200 OK', '404 Not Found', ...
- response_headers: [('Content-type', 'text/plain')]
start_response(status, response_headers)
Web framework / app side
Web框架/应用这边需要有一个class、对象或者一个函数。其参数就是上面的 environ
和 start_response
Web框架/应用在返回数据之前必须要调用 start_response 函数,而且应当返回一个可遍历的变量,例如数组。
Middleware
中间件则要同时遵守Web server端和Web Framework/app端的规则,且要尽可能做到透明。
中间件可以有多层,是可插拔式的。
中间件的作用包括:
- 错误处理
- Session
- 登录认证
- 压缩数据
- 往environ里增加key
- 改变状态 - status
- 拦截错误
- 改变headers、改变response
- 出现错误时,发送报警邮件
- 把请求转发给其他应用
- 缓存页面
- 等
It watches the state of requests, responses, and the WSGI environment in order to add some particular features.
代码示例
a simple WSGI application:
def application(environ, start_response):
start_response('200 OK', [('Content-type', 'text/html')])
return ['<html><body>Hello world!</body></html>']
test the application as a CGI script:
def application(environ, start_response):
start_response('200 OK',[('Content-type','text/html')])
return ['<html><body>Hello World!</body></html>']
from wsgiref.handlers import CGIHandler
CGIHandler().run(application)
output:
Status: 200 OK
Content-type: text/html
Content-Length: 38
<html><body>Hello world!</body></html>
the environ dictionary
The environ dictionary contains all the information about the environment and the request that the application needs.
def show_environ(environ, start_response):
start_response('200 OK',[('Content-type','text/html')])
sorted_keys = environ.keys()
sorted_keys.sort()
return [
'<html><body><h1>Keys in <tt>environ</tt></h1><p>',
'<br />'.join(sorted_keys),
'</p></body></html>',
]
from wsgiref import simple_server
httpd = simple_server.WSGIServer(
('',8000),
simple_server.WSGIRequestHandler,
)
httpd.set_app(show_environ)
httpd.serve_forever()
这时候浏览器中访问 127.0.0.1:8000 即可得到一个environ的key的列表(101个):
COLORFGBG | COLORTERM | COMMAND_MODE | CONTENT_LENGTH |
---|---|---|---|
CONTENT_TYPE | GATEWAY_INTERFACE | GEM_HOME | GEM_PATH |
GOPATH | HISTCONTROL | HISTTIMEFORMAT | HOME |
HTTP_ACCEPT | HTTP_ACCEPT_ENCODING | HTTP_ACCEPT_LANGUAGE | HTTP_CONNECTION |
HTTP_COOKIE | HTTP_HOST | HTTP_UPGRADE_INSECURE_REQUESTS | HTTP_USER_AGENT |
IRBRC | ITERM_PROFILE | ITERM_SESSION_ID | LC_CTYPE |
LESS | LOGNAME | LSCOLORS | MY_RUBY_HOME |
OLDPWD | PAGER | PATH | PATH_INFO |
PS1 | PWD | PYSPARK_DRIVER_PYTHON | QUERY_STRING |
REMOTE_ADDR | REMOTE_HOST | REQUEST_METHOD | RUBY_VERSION |
SCRIPT_NAME | SECURITYSESSIONID | SERVER_NAME | SERVER_PORT |
SERVER_PROTOCOL | SERVER_SOFTWARE | SHELL | SHLVL |
SSH_AUTH_SOCK | TERM | TERM_PROGRAM | TERM_PROGRAM_VERSION |
TERM_SESSION_ID | TMPDIR | USER | VERSIONER_PYTHON_PREFER_32_BIT |
VERSIONER_PYTHON_VERSION | VIRTUAL_ENV | XPC_FLAGS | XPC_SERVICE_NAME |
ZSH | _ | __CF_USER_TEXT_ENCODING | _system_arch |
_system_name | _system_type | _system_version | rvm_alias_expanded |
rvm_bin_flag | rvm_bin_path | rvm_docs_type | rvm_gemstone_package_file |
rvm_gemstone_url | rvm_hook | rvm_niceness | rvm_nightly_flag |
rvm_only_path_flag | rvm_path | rvm_prefix | rvm_pretty_print_flag |
rvm_proxy | rvm_quiet_flag | rvm_ruby_bits | rvm_ruby_file |
rvm_ruby_make | rvm_ruby_make_install | rvm_ruby_mode | rvm_script_name |
rvm_sdk | rvm_silent_flag | rvm_use_flag | rvm_version |
rvm_wrapper_name | wsgi.errors | wsgi.file_wrapper | wsgi.input |
wsgi.multiprocess | wsgi.multithread | wsgi.run_once | wsgi.url_scheme |
wsgi.version |
REQUEST_METHOD=‘GET’
SCRIPT_NAME=''
SERVER_NAME='localhost'
SERVER_PORT=‘5000’
PATH_INFO='/aaa'
QUERY_STRING='666'
SERVER_PROTOCOL='HTTP/1.1'
CONTENT_TYPE='text/plain'
CONTEN_LENGTH=''
HTTP_HOST = 'localhost:8000'
HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
HTTP_ACCEPT_ENCODING = 'gzip,deflate,sdch'
HTTP_ACCEPT_LANGUAGE = 'en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2'
HTTP_CONNECTION = 'keep-alive'
HTTP_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36'
middleware 样例
# coding: utf-8
# middleware.py
from __future__ import unicode_literals
class TestMiddle(object):
def __init__(self, application):
self.application = application
def __call__(self, environ, start_response):
if 'postman' in environ.get('USER_AGENT'):
start_response('403 Not Allowed', [])
return ['not allowed!']
return self.application(environ, start_response)
调用时:
from middleware import TestMiddle
...
server.set_application(TestMiddle(application))
缺点
Paste
Tornado为啥不支持WSGI?
WSGI是同步的,而Tornado最大的特点是能够异步处理请求。所以,将Tornado应用作为WSGI应用时异步接口全部不可用。
但是严格来说,Tornado是支持WSGI的,只是异步的特性使用不了,但还是可以结合其他WSGI框架使用的,例如:
- Tornado web framework 可以结合WSGI容器使用;
- Tornado HTTP server 可以用作其他WSGI框架的容器。
Tornado的web framework + HTTP Server可以算作是 WSGI 的一整套替代方案。
Tornado入门请参考另一篇介绍文档。
总结
WSGI就是一套用于Web开发的标准。如果你正在开发一套Web框架,或者开发一些组件,那么你需要了解它,否则的话,对于一般的Web应用开发者来说,使用Web框架即可,不需要了解WSGI的详细架构。
参考文档
推荐最后这个参考文档,把Django、Tornado如何结合WSGI介绍了一遍,我就不誊抄了。