您的当前位置:首页正文

2018-03-21 WSGI - Web Server Gat

来源:华拓网

前言

起源

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、对象或者一个函数。其参数就是上面的 environstart_response
Web框架/应用在返回数据之前必须要调用 start_response 函数,而且应当返回一个可遍历的变量,例如数组。

Middleware

中间件则要同时遵守Web server端和Web Framework/app端的规则,且要尽可能做到透明。
中间件可以有多层,是可插拔式的。
中间件的作用包括:

  1. 错误处理
  2. Session
  3. 登录认证
  4. 压缩数据
  5. 往environ里增加key
  6. 改变状态 - status
  7. 拦截错误
  8. 改变headers、改变response
  9. 出现错误时,发送报警邮件
  10. 把请求转发给其他应用
  11. 缓存页面

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框架使用的,例如:

  1. Tornado web framework 可以结合WSGI容器使用;
  2. Tornado HTTP server 可以用作其他WSGI框架的容器。

Tornado的web framework + HTTP Server可以算作是 WSGI 的一整套替代方案。
Tornado入门请参考另一篇介绍文档

总结

WSGI就是一套用于Web开发的标准。如果你正在开发一套Web框架,或者开发一些组件,那么你需要了解它,否则的话,对于一般的Web应用开发者来说,使用Web框架即可,不需要了解WSGI的详细架构。

参考文档

推荐最后这个参考文档,把Django、Tornado如何结合WSGI介绍了一遍,我就不誊抄了。