PEP 3333简介

1. 什么是PEP?

PEP是Python Enhancement Proposals的缩写。一个PEP是一份为Python社区提供各种增强功能的技术规格,也是提交新特性,以便让社区指出问题,精确化技术文档的提案。

2. PEP 3333

PEP3333

本文档指定了Web服务器与Python Web应用程序或框架之间的标准接口,以提高Web应用程序和Web服务器之间的可移植性。

Python当前拥有各种各样的Web应用程序框架,例如Zope,Quixote,Webware,SkunkWeb,PSO和Twisted Web。但是,由于不同的Web应用程序框架可能支持不同的Web服务器,所以对于Python新手而言,在选择Web应用程序框架时可能会遇到一些问题。

相比之下,Java也有许多中Web应用程序框架,但是Java的“Servlet" API 使得任何使用Java Web应用程序框架编写的应用程序都能够在支持Servlet API的Web服务器中运行。

Python也需要这样一个接口,这个PEP提出了Web服务器与Web应用程序/框架之间的简单且通用的接口:Python Web服务器网关接口(WSGI)

仅仅只有一个接口规范是不行的,还需要Web服务器和Web应用程序框架实现WSGI接口才能真正起到作用。

WSGI接口要考虑两个部分:server/gateway端,application/framework端。server端需要调用application所提供的可调用对象。这个可调用对象的提供方式,根据server或gateway的不同而不同,例如:

  1. 通过application编写脚本来创建server或gatewya实例,并为其提供应用程序对象。
  2. 通过使用配置文件来指定应该从何处导入应用程序对象。
WSGI
WSGI
server/gateway
application/framwork

因此,WSGI定义了两种“字符串”:

  • “Native string” 我认为就是原生字符串:用于请求/响应头和元数据(Python3中的str类型,Python2中的unicode类型)
    “Bytestrings”(字节串,我认为是字节类型的字符串,字节的序列)(Python 3中的bytes类型,python2中的str类型),用于请求和响应的主体(例如POST / PUT输入数据和HTML页面输出)。

2.1 Application/Framework端

WSG接口规范中所提到的应用程序对象是一个接受2个参数的可调用对象,可以是函数、方法、类或实现了__call__()方法的实例。例如:

HELLO_WORLD = b"Hello world!\n"

# 这是一个应用程序对象
def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [HELLO_WORLD]

# 这是另外一个应用程序对象
class AppClass:
    """Produce the same output, but using a class

    (Note: 'AppClass' is the "application" here, so calling it
    returns an instance of 'AppClass', which is then the iterable
    return value of the "application callable" as required by
    the spec.

    If we wanted to use *instances* of 'AppClass' as application
    objects instead, we would have to implement a '__call__'
    method, which would be invoked to execute the application,
    and we would need to create an instance for use by the
    server or gateway.
    """

    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

2.2 Server/Gateway端

server/gateway每次接收到从HTTP客户端发送的request时,都会调用一次这个应用程序对象。下面用一个例子来证明,

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()

2.3 Middleware:Components that Play Both Sides

这样一个中间件,既可以扮演server端,也可扮演application端。对于server端而言,中间件扮演application;对于application端而言,中间件扮演server。

  • 在重写environ之后,根据目标UR路由request到不同的应用程序对象。
  • 允许多个应用程序/框架同时运行。
  • 通过网络转发request和response,实现负载均衡和远程处理。
  • perform content postprocessing

一段中间件的示例代码如下:

from piglatin import piglatin

class LatinIter:

    """Transform iterated output to piglatin, if it's okay to do so

    Note that the "okayness" can change until the application yields
    its first non-empty bytestring, so 'transform_ok' has to be a mutable
    truth value.
    """

    def __init__(self, result, transform_ok):
        if hasattr(result, 'close'):
            self.close = result.close
        self._next = iter(result).__next__
        self.transform_ok = transform_ok

    def __iter__(self):
        return self

    def __next__(self):
        if self.transform_ok:
            return piglatin(self._next())   # call must be byte-safe on Py3
        else:
            return self._next()

class Latinator:

    # by default, don't transform output
    transform = False

    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):

        transform_ok = []

        def start_latin(status, response_headers, exc_info=None):

            # Reset ok flag, in case this is a repeat call
            del transform_ok[:]

            for name, value in response_headers:
                if name.lower() == 'content-type' and value == 'text/plain':
                    transform_ok.append(True)
                    # Strip content-length if present, else it'll be wrong
                    response_headers = [(name, value)
                        for name, value in response_headers
                            if name.lower() != 'content-length'
                    ]
                    break

            write = start_response(status, response_headers, exc_info)

            if transform_ok:
                def write_latin(data):
                    write(piglatin(data))   # call must be byte-safe on Py3
                return write_latin
            else:
                return write

        return LatinIter(self.application(environ, start_latin), transform_ok)


# Run foo_app under a Latinator's control, using the example CGI gateway
from foo_app import foo_app
run_with_cgi(Latinator(foo_app))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值