WSGI是什么?

来源:互联网 发布:php代码去除图片水印 编辑:程序博客网 时间:2024/06/02 00:58
WSGI是什么?
        摸索了一两个月还没怎么弄清楚。之所以没弄清楚,不在于WSGI有多复杂,而是目前基于WSGI的框架(如Django)和应用服务(如openstack)过于复杂。再加上它们使用一些辅助的Lib库(如Route,webob,eventlet等)使得学习起来有点过于繁杂。本能的好奇,使得在看代码时,碰到一个不懂的类库,不懂的函数都无法继续,想一探究竟。然而这种好奇却给了懒惰一个的很好的接口,每当碰到不懂的函数时,会去找相关的文档,一般是英文的,看一会不懂,看中文的,有点懂,然后发呆,不知道干嘛。。。再然后就可以回去吃饭睡觉了。。。好了,还是回到主题吧!
        WSGI(Web Server Gateway Interface)是一种规范,它定义了使用python编写的web app与web server之间接口格式,实现web app与web server间的解耦。WSGI在2003年被提出,那时用python编写一个web应用,不仅仅要考虑web应用本身的结构和功能,还要考虑web server的选择,因为不同的web server对web app提供的接口是不一样的。一旦针对某个web server写好了特定的web app,那么它将很难再通过别的web server发布,否则需要大量修改和调试。WSGI的出现就是为了解决这种困境,只要web server和web app都遵循WSGI规范,那么web app的编写无需考虑web server的影响,都可通过任一web server发布,实现解耦。
        上面讲到web server和web app都要遵循WSGI规范,那么WSGI肯定包含web server端的接口规范和web app端的接口规范。除了这两个接口规范,还包含一个Middleware规范,这里可以忽略它。web server端的接口规范,是web server实现时需要考虑的。我们实现web app时需要遵循app端接口规范,不过考虑到url的分发、大型web app的复杂和小型web app可重用性,现在很少有人直接编写直接发布的web app,而是使用Flask、pecan这样的框架来实现web app,所以基本也不用怎么考虑WSGI。还好WSGI比较简单,满足好奇心的代价不大。

web app端接口规范
        web app实际上是一个python 可调用对象(可以是函数、可调用实例、class等),当有HTTP请求时,该对象会被调用。web app端接口规范规定这个对象的参数长啥样的,它的返回结果是长啥样的?至于该对象函数的名子和功能是啥样的,那无所谓。简单而言,WSGI就是规定了处理函数的输入和输出格式。下面来看看一个简单的web app接口涨啥样!
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!\n']
       web app接收两个参数environ和start_response,当发生特定HTTP请求时,由web server调用。environ是一个dict对象,它包含了当前HTTP请求的变量,如:REQUEST_METHOD(HTTP请求方式,如GET、POST),SCRIPT_NAME(已经处理过的url的path路径),PATH_INFO(未处理的url的path路径),QUERY_STRING(url中?字符后面的字符串),CONTENT_TYPE(请求内容类型,如application/json,application/xml),CONTENT_LENGTH(请求的内容长度),SERVER_NAME(请求的服务器名称,如www.jd.com),SERVER_PORT(请求的端口号)SERVER_PROTOCOL(HTTP请求版本, "HTTP/1.0" or "HTTP/1.1"),HTTP_XXXXX(http请求header信息),关于具体包含哪些参数及其定义,具体可参考CGI协议(参考文献【2】)。
       environ还包含一些WSGI定义的一些变量:wsgi.version(该请求遵守的WSGI版本信息,tuple类型),wsgi.url_scheme(HTTP请求的协议:https or http),wsgi.input(一个输入流,类似于file对象,用于读取用户传输的body),wsgi.errors(出入流,用于输出错误处理信息),wsgi.multithread(bool类型,为True时,表明该web app可能同时被多个线程调用),wsgi.multiprocess(bool类型,为True时,表明该web app可能同时被多个进程调用),wsgi.run_once(bool类型,为True时,表明该web app在web server服务的时间中,只会被调用一次)。
       start_response是一个可调用对象,由web server以参数的形式传给web app。web app调用start_response,用以返回HTTP响应的status和headers。是不是很好奇start_response怎么实现的?下面在web server端接口规范中,有一个start_response的简单实现,可以看看端倪!
       web app根据相应的environ,完成相应功能。最后通过start_response返回相应的status和headers,最后在退出时,以一个iterable对象返回body。

web server端接口规范
       我们一般人是不会自己去实现web server,但考虑到编写web app的时候,总会想我返回的status和headers是怎样被它处理的,返回的body又是怎样被它处理的呢?看看下面一段代码,实现了一个最简单的,遵循CGI的server。理解下web server大概的原理:
import os, sysdef 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()

        根据上面的实现的web app和web server(上面实现的不算是web server,因为它不能处理HTTP请求,它只是自己构造模仿一个HTTP请求而已),就可以看看一个最简单的app发布流程:
run_with_cgi(simple_app)
        就这么简单,对,就这么简单。不过它还不能真正接收HTTP请求。在python自带库wsgiref中提供了一个简单的WSGI server,可以用来发布wsgi app:
#!/usr/bin/env pythonfrom wsgiref.simple_server importmake_server#规定参数第一个为environ,此为server传过来的,包含所有request相关的信息。start_response#是server传的函数,用于返回status和headers。def simple_app(environ, start_response):    status = '200OK'    headers = [('Content-type','text/plain')]    print environ    start_response(status,headers) #调用以返回status和headers    #规定返回结果为list类型    return ["helloworld"]  httpd = make_server('',                  #listen ip                    8000,                #listen port                    simple_app)      #appilication name print"Serving on port 8000..."httpd.serve_forever()

       运行该脚本,用浏览器访问localhost:8000就可以得到hello world了。关于WSGI规范更详细的介绍可参考文献【1】。


参考文献
【1】 http://legacy.python.org/dev/peps/pep-0333/
【2】 http://ken.coar.org/cgi/draft-coar-cgi-v11-03.txt