TOC

WSGI简介

    WSGI(Web Server Gateway InterFace),即Web服务器网关接口,说到WSGI,首先我们需要知道什么是CGI(Common Gateway Interface),即公共网关接口,它是Web服务器运行时外部程序的规范,而WSGI是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口;

WSGI Server

    WSGI实际上主要就是用来解析HTTP请求的一个应用程序,同时它也是一个HTTP Server,它主要的作用就是接受用的请求并解析这个请求内容,但是它最多只能到解析这一步,至于处理这个请求,WSGI是做不到的,所以,除了WSGI应用程序之外,我们还需要提供一个应用来负责处理这个请求,而这个应用程序在WSGI里面就称之为WSGI App;
    所以,到现在来看,当一个HTTP请求发送过来之后,这个Request请求会发送到WSGI应用程序,WSGI Server负责将这个Request请求进行解包,解包完成之后会封装成一个字典,这个字典的名字很特殊,叫environ,即环境变量,然后由WSGI Server应用程序,将这个environ数据交给WSGI App进行处理,这个WSGI App程序接受两个参数,第一个参数为start_reponse函数本身,另一个则是这个environ字典数据;
    当WSGI App收到这两个参数之后,开始处理请求的数据,当处理完成之后,WSGI App会首先将start_response这个函数先调用一次,解决响应头的问题,调用start_response这个函数时会传入一些参数,比如contenct_type、charset、status_code等,调用完成之后就得到了一个封装好的响应头对象,然后将这个响应头对象和处理完成之后的数据一起发送给WSGI Server,随后由WSGI Server将响应头对象和处理结果,一起构建Response报文,然后回应给客户端,这就是整个WEB开发在服务端的处理流程,其他语言也是一样的,如下图;

    WSGI主要是定义,支持了WSGI的Server与WSGI App之间的调用规范,对于Python的WSGI Server即支持了HTTP协议的解析,也提供了WSGI Server与WSGI App之间调用的接口,所以Python的WSGI Server实际上是有这两个功能的;

Web Server

    一般情况下,在WSGI之上,应该有一个Web Server,如nginx,它就属于一个Web Server,它也支持CGI,即FastCGI,如下图,就是一个主流Web Server的框架图,当浏览器发起一个请求时,如果此时访问的是一个静态资源,那么实际上,它并不会调用CGI程序,而是直接在文件系统之上,找到对应的资源,然后将其封装成Response报文,直接返回给客户端浏览器;
    但如果访问的是一个动态资源,就不一样了,当一个动态请求到达Web Server时,Web Server会调用CGI模块,然后CGI模块会调用后面的CGI App,在调用之前,Web Server需要将客户端浏览器的Request转换成一个environ这样的一个字典,然后还需要提供一个start_response函数,将这个字典和这个函数,以参数的形式传递给CGI App,CGI App数据处理完成之后,组装Response Header对象,然后将处理完成之后的数据,和Hedader对象一起交给Web Server,由Web Server将这两者封装成Response报文,然后返回给客户端;

WSGI Server

    在Python里面有一个wsgiref模块,它是一个WSGI协议的参考模块,仅参考使用,其实它和我们的SocketServer有点类似,如下,ThreadingTCPServer方法负责TCP请求的接收与发送,Handler类负责处理接收到的请求;
import socketserver

class Handler(socketserver.BaseRequestHandler):
    def handle(self):
        super().handle()
        print(self.request.recv(1024))

server=socketserver.ThreadingTCPServer(('127.0.0.1',8080),Handler)
server.serve_forever()
    从本质上来讲,HTTP协议就是基于TCP协议来实现的,所以对于wsgiref模块内部的实现手段就是socketserver,内部提供了几个方法,即make_server和demo_app,两个主要的方法,make_server主要是实现了WSGI Server,而demo_app则是我们的WCGI调用的外部程序;
from wsgiref.simple_server import make_server, demo_app

ws = make_server('127.0.0.1', 8080, demo_app)
ws.serve_forever()

WSGI App

    如下就是demo_app的源码,其实很好理解,首先,创建一个基于内存的文件对象,然后使用print将"Hello Word!"写入到这个文件对象里面,继而将environ.items(),写入到这个文件对象里面,然后调用一个start_response构建响应头部,最后将数据以"utf-8"形式返回;
def demo_app(environ, start_response):
    from io import StringIO
    stdout = StringIO()
    print("Hello world!", file=stdout)
    print(file=stdout)
    h = sorted(environ.items())
    for k, v in h:
        print(k, '=', repr(v), file=stdout)
    start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
    return [stdout.getvalue().encode("utf-8")]
    所以,从这里来看就是,当浏览器发送一个请求达到我们的WSGI Server,WSGI Server会将这个Request请求报文封装成environ字典,然后交给demo_app进行处理,demo_app在处理完成之后,首先需要调用start_response函数构建响应报文头部,然后在再将处理好的数据返回至WSGI Server,WSGI Server随即构建响应报文,然后响应客户端;
    App在我们简单的看来,可以是一个函数,但是其实更严谨的来说,是一个可调用对象,在Python中的可调用对象主要有三种,第一种是函数,第二种是类,第三种是实现了__call__方法的类的实例,所以对于WSGI App就可以写成这三种形式,如下;
from wsgiref.simple_server import make_server

def app(environ, start_response):
    start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
    return ["200 OK".encode("utf-8")]

class App:
    def __init__(self, environ, start_response):
        start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
    def __iter__(self):
        yield from ["200 OK".encode("utf-8")]

class Application:
    def __call__(self, environ, start_response):
        start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
        return ["200 OK".encode("utf-8")]

ws = make_server('127.0.0.1', 8080, Application())
ws.serve_forever()

environ

    environ就是一个包含了HTTP请求信息的DICT字典对象,它是由WSGI Server封装的,因WSGI Server不负责处理请求,所以就直接将这个请求的具体内容封装成了一个environ字典,交由后端的WSGI App进行处理;
    environ里面有几个比较常用的键值对,如下;
REQUEST_METHOD:客户端发来的请求方法,如GET、POST、DELETE等;
PATH_INFO:URL中的路径部分;
QUERY_STRING:URL中的查询字符串;
SERVER_NAME:服务器名;
SERVER_PORT:服务器端口;
HTTP_HOST:地址和端口的字符串;
SERVER_PROTOCOL:协议类型;
HTTP_USER_AGENT:UserAgent信息;

start_response

    start_response是一个可调用对象,它主要的作用就是构建Response的Header部分,它应该在返回Response Body之前调用,因为在返回Response Body之前,会将Response Body和Response Header进行封装,封装成一个完整的Response报文;
    start_response接受三个参数,如下;
status:状态码和状态描述,如 200 OK;
response_headers:一个二元祖列表,主要是对Response Body的一个描述,如[('Content-Type', 'text/plain; charset=utf-8')];
exc_info:在错误处理时调用;

路由

    上面已经启动了一个HTTP Server,但是它做得非常得简单,正常的Web框架都是一个URL对应一个资源,但是我们经过测试,不管访问任何URL都是这么一个资源,因为不管我们发送的什么URL,都被封装成了environ,任何一个URL请求来了都仅只做了封装environ,并没有对URL做任何区别对待,所以组织的内容都是一样的;
    那么如果我们期望实现不同的URL对应不同的页面,我们就应该加入路径判断的逻辑,根据不同路径去调用不同的函数,所以,之所以有Django、Flask这样的技术,主要是为了实现WSGI App逻辑,因此WSGI App就会变成巨大无比,由此形成一个Web框架;
    一般情况下,是对不同的URL请求去调用不同的执行逻辑,不管是函数还是类,即使找不到也可能会有一个缺省调用逻辑,那么这些调用逻辑里面,也需要在返回正文之前先调用start_response函数,这就叫做URL映射,也称为路由,所以,不管Django多么重量级,它实际上只是一个WSGI App;

总结

    综上所述,WSGI就是一套遵循HTTP协议标准的外部程序的规范,WSGI Server本质就是一个监听在特定端口之上的TCP Server,主要就是解决两个问题,第一个是作为一个HTTP Server允许在一个TCP协议之上,然后负责将动态请求转发给WSGI App进行处理,所以它解决的就是WSGI Server与WSGI App之间调用的问题;
    它不负责管理HTTP原文是什么,它只需要按照协议将这个请求交给WSGI Server后段的WSGI App即可,所以这就是WSGI,所以Django或Flask这种Web框架不能独立运行,因为他们不是WSGI Server,它们不监听端口,它们是WSGI App,它们只负责处理请求,生产结果即可;
    所以WSGI App会写的越来越负责,慢慢形成一种框架,这就是Djnago、Flask,所以还必须有个WSGI Server,没有WSGI Server的话,Web框架是无法启动作为一个Server的,这就是WSGI App的本质;

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注