深入理解WSGI

WSGI(Web Server Gateway Interface)是Web服务器与Python Web应用或框架之间的标准接口,用于促进跨Web服务器的Web应用的可移植性。根据PEP 3333提案说明,WSGI只是一种协议,而不是一个框架。WSGI力求简洁和通用,所以仅规范了三种组件:Application,Server和Middleware。

  • 组件Application

Application可以是一个函数、方法、类或者实现了__call__方法的实例。

    • 如果是一个函数

def application(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [b"hello world!\n"]

该函数的参数必须是占位参数,而不能是命名参数,参数的名称不必是environ和start_response,只要名称合法就行。environ参数类型必须是Python内建字典(不能是dict的子类)。start_response必须返回一个可回调的仅有一个参数的write方法。application返回值必须是一个可迭代对象。

    • 如果是一个类

class AppClass:
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response
    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "hello world"

AppClass(environ,start_response)与application相同,也是返回一个可迭代的对象。

    • 如果是一个实例

class Instance():
    def __call__(self, environ, start_response):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        start_response(status, response_headers)
        return ["hello world"]

实例需要实现一个__call__方法,参数与application相同。

  • 组件Server

Server主要功能是实现信息转换,即使用HTTP协议把网络请求中信息进行解析,然后按照WSGI规范进行重组,并把这些信息传递给application处理,最后使用HTTP协议把application返回的内容打包返回给客户端。

Server响应客户端请求步骤:

  1. 使用HTTP协议解析请求内容,构建environ字典

  2. 提供一个start_response方法,用于生成响应头部

  3. 调用application对象,处理environ数据

  4. 接收application返回的结果

  5. 把application返回的结果以HTTP协议格式发送到客户端

PEP 3333提案的Server示例:

import os, sys
enc, esc = sys.getfilesystemencoding(), 'surrogateescape'
​
def unicode_to_wsgi(u):
    # Convert an environment variable to a WSGI "bytes-as-unicode" string
    return u.encode(enc, esc).decode('iso-8859-1')
​
def wsgi_to_bytes(s):
    return s.encode('iso-8859-1')
​
def run_with_cgi(application):
    environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()}
    environ['wsgi.input']        = sys.stdin.buffer
    environ['wsgi.errors']       = sys.stderr
    environ['wsgi.version']      = (1, 0)
    environ['wsgi.multithread']  = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once']     = True
​
    if environ.get('HTTPS', 'off') in ('on', '1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'
​
    headers_set = []
    headers_sent = []
​
    def write(data):
        out = sys.stdout.buffer
​
        if not headers_set:
             raise AssertionError("write() before start_response()")
​
        elif not headers_sent:
             # Before the first output, send the stored headers
             status, response_headers = headers_sent[:] = headers_set
             out.write(wsgi_to_bytes('Status: %s\r\n' % status))
             for header in response_headers:
                 out.write(wsgi_to_bytes('%s: %s\r\n' % header))
             out.write(wsgi_to_bytes('\r\n'))
​
        out.write(data)
        out.flush()
​
    def start_response(status, response_headers, exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[1].with_traceback(exc_info[2])
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")
​
        headers_set[:] = [status, response_headers]
​
        # Note: error checking on the headers should happen here,
        # *after* the headers are set.  That way, if an error
        # occurs, start_response can only be re-called with
        # exc_info set.
​
        return write
​
    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result, 'close'):
            result.close()

run_with_cgi方法可以认为是一个简化版的Server。

  • 组件Middleware

顾名思义,中间件是介于Server和application之间的组件。从Server角度看,它是一个application,从application看,它又是一个Server,所以它既有application的结构,又有Server的功能。下面是一个日志中间件的示例:

class LogMiddleware():
    def __init__(self, application):
        self.application = application
    def __call__(self, environ, start_response):
        response = self.application(environ, start_response)
        log.info(response)
        return response

从上可以看出,中间件对象同样实现了__call__方法,所以可以被Server当做application进行调用。但是,它的构造方法的参数又是一个application(也可以是另一个中间件对象,从而构成中间件栈),可以在application调用之前或之后实现一些自己的操作。这种编码方式属于典型的装饰器模式。

一个简单的application示例

import eventlet
import eventlet.wsgi
​
def application(evn,start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return '<h1>app</h1>'
​
class AppClass:
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response
    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "hello world"
​
class Instance():
    def __call__(self, environ, start_response):
        status = '200 OK'
            response_headers = [('Content-type', 'text/plain')]
            start_response(status, response_headers)
        return ["hello world"]
​
#eventlet.wsgi.server(eventlet.listen(('',8080)),application)
eventlet.wsgi.server(eventlet.listen(('',8080)),AppClass)
#eventlet.wsgi.server(eventlet.listen(('',8080)),Instance())

上述是三种application的测试脚本。

小结

WSGI作为Python Web的标准,对Python Web的发展至关重要。多种WSGI服务器的实现为开发者提供了丰富的选择。

 

如果对云计算感兴趣,可以关注我的微信公众号:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值