黑马程序员技术交流社区

标题: 【上海校区】WSGI协议详解 [打印本页]

作者: 梁强    时间: 2019-7-11 11:17
标题: 【上海校区】WSGI协议详解
简介
Python Web开发中,后台服务端程序可以分为两个部分,1.是服务器程序 2.是应用程序.服务器通常是TCP服务器,负责处理来自客户端(一般都是浏览器/APP)请求的整理/分发/响应.2.后者负责具体的逻辑处理.这时候为了方便应用程序的开发,我们把常用的功能封装起来,成为各种Web开发框架,Django/Flask/Tornado.不同的框架有不同的开发方式,但是开发出来的应用程序都需要和服务器程序配合,才能算是完整的后台服务.
如果服务器程序和应用程序之间没有标准的协议,我们在开发一套应用程序的时候是不是要针对某个具体的服务器程序来写,而不能随便来写.这时候我们就需要规定出一套特定协议,将应用程序和服务器程序解耦出来.而 WSGI就是这个作用.Python Web开发中,这套标准协议就是 The Web Server Gateway Interface,即 WSGI,这套标准在官方的 PEP 333中描述.
WSGI具体是什么?
WSGI是服务器程序与应用程序的一个约定,它规定了双方各自需要实现什么接口,提供了什么功能,以便二者能够配合使用.WSGI 不能规定的太复杂,否则对已有的服务器来说,实现起来会困难,不利于WSGI的普及。同时WSGI也不能规定的太多,例如cookie处理就没有在WSGI中规定,这是为了给框架最大的灵活性。要知道WSGI最终的目的是为了方便服务器与应用程序配合使用,而不是成为一个Web框架的标准。

WSGI-应用程序
WSGI规定:
我们看下官方文档给出的定义
[Python] 纯文本查看 复制代码
HELLO_WORLD = b"Hello world!\n"

# callable function
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]

# callable class
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

# callable object
class ApplicationObj(object):
    def __call__(self, environ, start_response):
        return [HELL_WORLD]
翻译过来就是:
同时, WSGI规定
这时候了解Python Web开发的可能就会讲了,平时我们写的应用程序并不是这个样子的,应该是下面这种
[Python] 纯文本查看 复制代码
class Index(View):
        def get(self):
                return 'Hello world'
这是因为框架已经帮我们把WSGI规定的一些东西封装起来,我们平时用框架时看不到,只需要直接实现我们的逻辑,再返回一个值就好了。其它的东西框架帮我们做好了。这也是框架的价值所在,把常用的东西封装起来,让使用者只需要关注最重要的东西。
比如 Django框架的 WSGI Application 就采用的 callable object的形式
[Python] 纯文本查看 复制代码
# lib/python3.7/site-packages/django/core/handlers/wsgi.py 146 line
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super(WSGIHandler, self).__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = '%d %s' % (response.status_code, response.reason_phrase)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
        start_response(force_str(status), response_headers)
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
            response = environ['wsgi.file_wrapper'](response.file_to_stream)
        return response
WSGI-服务器程序
服务器程序会在每次客户端的请求传来时,调用我们写好的应用程序,并将处理好的结果(response)返回客户端.
服务器程序大概是这个样子
[Python] 纯文本查看 复制代码
# 具体见PEP333规范
def run_with_cgi(application):
    environ = {}

    def write(data):
        pass

    def start_response(status, response_headers, exc_info=None):
       pass
        # 调用应用程序
    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()
这里可以看出服务器程序是如何与应用程序配合完成用户请求的。
WSGI规定了应用程序需要一个可调用对象,有两个参数,返回一个可迭代对象。在服务器 程序中,针对这几个规定,做了以下几件事:
WSGI-Middleware
有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。
有点类似于语法糖 装饰器,
[Python] 纯文本查看 复制代码
# URL Routing middleware
def urlrouting(url_app_mapping):
    def midware_app(environ, start_response):
        url = environ['PATH_INFO']
        app = url_app_mapping[url]

        result = app(environ, start_response)

        return result

    return midware_app
'''
中间件作用说明:
服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。
'''
WSGI-详解
应用程序
2个参数名称不是固定的,可以随便起,不过为了表达清楚所以一般都叫 environ和 start_response
服务器程序
[Python] 纯文本查看 复制代码
# server programmed
def run(application):
    environ = {}

    # set environ
    def write(data):
        pass

    def start_response(status, response_headers, exc_info=None):
        return write

    try:
        result = application(environ, start_response)
    finally:
        if hasattr(result, 'close'):
            result.close()

    if hasattr(result, '__len__'):
        # result must be accumulated
        pass


    for data in result:
        write(data)


HELLO_WORLD = b"Hello world!\n"


# callable function
def application(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)

    return [HELLO_WORLD]


environ变量
environ 变量需要包含 CGI 环境变量,它们在The Common Gateway Interface Specification 中定义,下面列出的变量必须包含在 enciron变量中:
[Python] 纯文本查看 复制代码
REQUEST_METHOD = 'GET'
SCRIPT_NAME = ''
PATH_INFO = '/xyz'
QUERY_STRING = 'abc'
CONTENT_TYPE = 'text/plain'
CONTENT_LENGTH = ''
SERVER_NAME = 'minix-ubuntu-desktop'
SERVER_PORT = '8000'
SERVER_PROTOCOL = 'HTTP/1.1'

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_HOST = 'localhost:8000'
HTTP_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36'

Unicode
HTTP 不支持 Unicode, 所有编码/解码都必须由应用程序完成,所有传递给或者来自server的字符串都必须是 str 或者bytes类型,而不是unicode。
注意:WSGI中的 bytestrings在Python3中指 bytes,在Python2中指 str







欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2