温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

WSGI是什么

发布时间:2022-04-20 13:53:37 来源:亿速云 阅读:124 作者:iii 栏目:开发技术

这篇文章主要讲解了“WSGI是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“WSGI是什么”吧!

    为什么是 WSGI?

    写过网页应用的各位亲,应该对 CGI 有了解,我们知道,CGI 的全程是“Common Gateway Interface”,即 “通用 Gateway Interface“。没错,这里的 WSGI,就是只针对 Python的网页应用接口“Python Web Server Gateway Interface”。通过这样的类比,想必大家对他的地位就有所了解了。

    它只是一个接口定义:它不负责服务器的实现,也不负责网页应用的实现,它只是一个两边接口方式的约定。所以,它并不是另一个 WEB 应用框架。通常意义上的 WEB 应用框架,也只相当于 WSGI 网页应用端的一种实现。

    这样做的好处是?PEP 0333 中的解释是,为了实现一个类似于 Java Servelet 的 API,使得遵循该接口的应用拥有更广泛的适用性。是的,有了该接口,你就不用去考虑,服务器对 Python 的支持到底是如何实现——无论是“ 直接用 Python 实现的服务器”,还是“服务器嵌入 Python”,或者是 “ 通过网关接口(CGI, Fastcgi...)”——应用程序都有很好的适用性。就像是今天故事的开始,我们遇到了云平台,它提供了对 WSGI 接口的支持,那么,只要应用是基于 WSGI 的,那么应用就可以直接跑起来。

    此外,WSGI 的设计,也提供了另外一种可能性,那就是中间件(middleware)。或者说,我们可以写一些对 server 和 application 都兼容的模块,我们可以把他们部署在 Server 端,也可以部署在 Application 端,完成比如缓存、字符编码转换、根据 url 做应用 routing 等功能。这种设计模式,是 WSGI 降低了 server 和 application 耦合度之后的产物,同时,它从另一个角度大大提升了设计的灵活性。

    WSGI 实施概略

    上一小节,简要对 WSGI 做了介绍。这里从 application、server、middleware 三个角度对 WSGI 稍微进行深入,使我们对它有一个更具体的印象。

    1)Application 端

    WSGI 要求,应用端必须提供一个可被调用的实体(PEP 0333 使用的是 Object,文档还特别解释这有别于Object instance),该实体可以是:一个函数(function)、一个方法(method)、一个类(class)、或者是有__call__方法的对象(Object instance)。

    这里有两个网页应用端的实现示例,一个是 function object,一个 class object:

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

    上面的 function 只是直接对请求直接做了 “200 ok” 回应,并没有处理传进来的参数 environ——里面是由 WSGI Server 端提供的各种 HTTP 请求参数。需要特别注意的是,这个函数在最后,返回的一个 list(用“[]”包含在内)以保证结果的 iterable。下面的 class 类似。

    在下面例子中,AppClass 作为应用实体。当调用发生时,其实是对 class 进行了例化( python 固有特性,可以参考后面 server 端的实现代码进一步理解),正如我们看到,这次调用(call)的返回值也是可迭代的——虽然只迭代一次(yield)。

    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!\n"
            """ In fact, the interator ‘ends‘ here because of no more yield field"""

    与上面两种情形不同,使用 object instance 作为应用实体时,需要为类定义添加 __call__ 方法,同时,参考上面使用 function 作为实体时情形,__call__ 方法的返回值需为 iterable(比如 return [ something ])。

    最后,不管我们的 app 是 function 还是 class, application 都需要处理两个参数,而且是两个位置相关的参数(不是命名参数),分别是:一个存放了 CGI 环境变量的 dictionary object,和一个可调用实体(需要给它三个位置相关的参数,两个必须,一个可选)。

    其中,可调用实体(前例中的 start_response)必须调用一次,两个必须的参数分别为“ HTTP Response的状态(str 类型)“ 和 “HTTP Response Header(list of tuples)“;

    一个可选的参数exc_info,必须是 Python sys.exc_info() tuple,只有在出错需要显示错误信息时使用。完整调用:start_response(status, response_headers,exc_info).

    2)Server 端

    下面是从 PEP 0333 拿来的一个简单的 WSGI 容器,适用于 Python 作为某 WEB Server 上 CGI 时的应用情形。

    import os, sys
    def run_with_cgi(application):
        environ = dict(os.environ.items())
        environ['wsgi.input']        = sys.stdin
        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):
            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
                 sys.stdout.write('Status: %s\r\n' % status)
                 for header in response_headers:
                     sys.stdout.write('%s: %s\r\n' % header)
                 sys.stdout.write('\r\n')
            sys.stdout.write(data)
            sys.stdout.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[0], exc_info[1], exc_info[2]
                finally:
                    exc_info = None     # avoid dangling circular ref
            elif headers_set:
                raise AssertionError("Headers already set!")
            headers_set[:] = [status, response_headers]
            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()

    上面的容器,大概实现了:

    • a)将 CGI 环境变量放入 dictionary object (environ)中,供 Application 实体使用;

    • b)定义了 start_response 方法,供 Application 实体调用;

    • c)调用 application 实体,对 web 请求进行处理;

    • d)将 application 的返回结果,以及通过 start_response 设置的 HTTP Response HEADER,写到 stdout ——像其他 CGI 一样,实际上是被发往网页。

    3) 作为 middleware

    因为 WSGI 的宽松耦合的特性,我们可以轻松的在 Application 和 Server 之前插入任何的中间插件,在不需要改动 Server 和 Application 的前提下,实现一些特殊功能。但是,这种放在 Server 和 Application “中间”的模块,并不是这里要讲的 middleware ;或者,这只能算是一种特殊的 middleware,因为它仅仅是实现了 PEP 0333 中 middleware 定义的 Application 侧的功能。这种仅实施在一侧的 middleware,需要在发布时,特别的声明。

    PEP 0333 中约定,中间件是一些即可以在 Server 端实施,又可以在 Application 端实施的模块。所以,在设计的时候,对两边的特性都要做适当考虑。幸好,WSGI 接口设计的足够简单。

    class Router():
        def __init__(self):
            self.path_info = {}
        def route(self, environ, start_response):
            application = self.path_info[environ['PATH_INFO']]
            return application(environ, start_response)
        def __call__(self, path):
            def wrapper(application):
                self.path_info[path] = application
            return wrapper
    """ The above is the middleware"""
    router = Router()
    @router('/world')
    def world(environ, start_response):
        status = '200 OK'
        output = 'World!'start_response(status, response_headers)  
        return [output] 
    @router('/hello') 
    def hello(environ, start_response):
        status = '200 OK'
        output = 'Hello'
        response_headers = [('Content-type', 'text/plain'), ('Content-Length', str(len(output)))]
        start_response(status, response_headers)  
        return [output]

    简单解释一下:

    - 作为 Application 时,我们用 Router 实例化一个对象。然后对 “ PATH-APP “ 进行注册,根据不同的 PATH,我们要进一步选择哪个 App。接着,就是把 router.route() 喂给 Server ,作为 Application 侧的可调用实体。有请求到来时,根据已经注册的 “PATH-APP” 对选择应用并执行。

    - Server 端类似,我们要先实例化并完成注册。然后,比如,拿我们上一小节实现的 WSGI 容器为例,我们需要修改 result = router.route(environ, start_response),同样完成了router的功能。

    下面是另外一个,实现了 postprocessor 的一个例子,在 Application 返回的 HTTP Header 里面再加一个 Header。

    def myapp(environ, start_response):
        response_headers = [('content-type', 'text/plain')]
        start_response('200 OK', response_headers)
        return ['Check the headers!']
    class Middleware:
        def __init__(self, app):
            self.wrapped_app = app
        def __call__(self, environ, start_response):
            def custom_start_response(status, headers, exc_info=None):
                headers.append(('X-A-SIMPLE-TOKEN', "1234567890"))
                return start_response(status, headers, exc_info)
            return self.wrapped_app(environ, custom_start_response)
    app = Middleware(myapp)

    这里通过改写传递给 Application 的实体,实现了 postprocess 的目的。

    感谢各位的阅读,以上就是“WSGI是什么”的内容了,经过本文的学习后,相信大家对WSGI是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI