浅析WSGI

首先,什么是WSGI?

WSGI, 全称为 Web Server Gateway Interface

它不是什么框架,它是一个规范,但是作为一个规范,它实际上并没有官方标准。
这里要说明一下,PEP-0333并不是用来规范WSGI用得,而是PEP-0333提出应该有那么一个东西用来处理web服务器和应用服务器之间的关系,因此产生了WSGI。

其次,为什么PEP-0333觉得需要这个规范呢?

因为,就目前来看,Web服务一般按照如下方式部署:

  1. 先部署一个web服务器(Apache etc.)用于处理协议层的业务,比如,单台物理机多服务(多域名或者多端口)、负载均衡等。

  2. 然后部署一个应用服务器(Django etc.),它用于处理具体业务方面的事情,比如,滴滴打车的应用服务器就会处理打车订单CRUD,处理完成之后呢再返回给web服务器,web服务器收到响应之后再返回给客户端。

WSGI是如何工作的?

通过上文,我们可以了解到WSGI无非做了两件事:

  1. 让Web服务器知道如何调用Python应用程序并把从客户端来的请求拿过来。

  2. 让Python应用程序知道客户端的具体请求是什么,以及如何返回结果给Web服务器并帮助Python应用程序把计算后的结果返回给Web服务器。

也就是说,WSGI是连接Web服务器和应用服务器的桥梁。

目前实现的WSGI中,有两个角色,分别是Server/Gatewayapplication/framework

当请求来临的时候,server调用application,然后application把结果返回给server

那么,server是如何调用application的?

接下来,server需要知道去哪能够找到application。实现这一逻辑,需要在server指定一个Python模块(具体在server的哪个位置保存这一路径,那就得根据具体server类型来选择了,如apache或nginx)该模块必须包含一个名称为application的可调用对象,这个对象的形式如下:

PEP-0333中指出了,一个WSGI的application角色,应该是一个可调用对象:

def application(environ, start_response)

其中,environ是一个包含了关于这次HTTP请求信息的字典,start_response是一个可调用对象,用于在application中执行,用以在返回响应内容前设置响应的状态码和响应头,同时也意味着告诉serverapplication要开始返回http的body了。这两个就是server调用application时候需要传递的所有参数了。

那么,我们还需要再说一下,environstart_response()是需要在server端的生成和定义的

有了这两个参数,application就能知道用户请求的是什么资源,请求中带了什么数据,结果该如何返回给server等等。其中,environ包含了一些符合CGI规范的环境变量和WSGI规范新添加的变量,此外还可能有一些系统变量及Web服务器相关的环境变量。start_response是一个可调用对象,它包含了一个表示HTTP响应状态的字符串和一个HTTP响应headers的列表以及一个用于出错返回的信息,具体参数包含及详情请点这里。

下面是一个完整的application demo,最终返回的body也可以是一个可迭代对象,这样server也可以通过遍历这个对象来拼接成body。

def application(environ, start_response):
    start_response("200 OK", [("Content-type", "text/plain")])
    return ["Hello World!",]

看上去不错,那么我们该如何调用呢?

有几个服务器组件也能够运行的WSGI应用程序,但是对于简单的测试目的,我们可以使用包含在Python的标准库参考实现,就像下面那样:

if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('localhost', 8080, application)
    server.serve_forever()

好了,还记得我们刚才提到的最终返回的body也可以是一个可迭代对象吗?在python中,创建一个迭代器的简单办法就是使用一个生成器,比如,我们有一个用于静态文件的服务,我们可以写一个生成器,让它一次生成一个固定大小的文件块,这样我们就可以随时一次只存储一个文件块了,让我们来试试吧:

def send_file(file_path, size):
    with open(file_path) as f:
        block = f.read(BLOCK_SIZE)
        while block:
            yield block
            block = f.read(BLOCK_SIZE)

对应的 WSGI application 部分如下:

size = os.path.getsize(file_path)
headers = [
    ("Content-type", mimetype),
    ("Content-length", str(size)),
]
start_response("200 OK", headers)
return send_file(file_path, size)

注意,send_file就是上文中所指的可迭代对象。

WSGI中间件

看得出来,WSGI应用结构非常简洁,只需指定一个可调用的识别标记,这使得WSGI应用很容易实现调用其他框架,无非就是修改到来的请求或者修改发出的响应,当然也可以两者都有。那么,接下来看看demo:

class Filter(object):
    def __init__(self, application):
        self.application = application
        
    def __call__(self, environ, start_response):
        # Do something here to modify request
        pass
        
        # Call the wrapped application
        app_iter = self.application(environ, 
                                    self._sr_callback(start_response))
        
        # Do something to modify the response body
        pass
        
        # Return modified response
        return app_iter
        
    def _sr_callback(self, start_response):
        def callback(status, headers, exc_info=None):
            # Do something to modify the response status or headers
            pass
        
            # Call upstream start_response
            start_response(status, headers, exc_info)
        return callback

像这样应用通常被称为Middleware applicationsFilter Filter可以被连接在一起,由此产生的链通常被称为pipeline

最后呢,我想说明一下,WSGI是故意设计成最小的Web服务器实现轻松的应用,以便被更多的人采用。但是,几乎没有人真的喜欢直接操作environ变量,也几乎没有人喜欢用start_response这么诡异的逻辑,虽然WSGI提供的API易于实现,但这不代表它的语义让人满意。也正是因为这一原因,几乎每一个应用程序或者web框架都把environstart_response封装成了语义更完善容错率更高的request和response对象。

Webob就是request和response对象的规范实现之一,它使得WSGI更容易和更满意地被大家使用。

你可以在这里找到Webob的官方文档,当然,下次有机会我也会简单的说明一下Webob到底有多方便。

    原文作者:Moxmi
    原文地址: https://segmentfault.com/a/1190000004116601
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞