WSGI必知必会

1. 简介 
html

WSGI是Web Server Gateway Interface的简称。它不是服务器,python模块,框架,API和任何种类的软件。它仅仅是一个服务器和应用间的接口规范。python

这个规范的具体内容能够参考(https://www.python.org/dev/peps/pep-0333/  注:新规范为 https://www.python.org/dev/peps/pep-3333  )。规范中提到,尽管在Python中存在着各类各样的Web框架,可是,你选择框架的同时,也限制了你的选择。规范里提到了Java的Servlet,尽管Java也有那么多的Web框架,可是你却可使用基础的Servlet构建任何复杂的Web应用。这就是通用性,你不会受到任何框架的限制。web

WSGI就是这样一个规范,它的目的就是规范Web服务器与Web应用(框架)之间的交互。shell


2. 概念设计模式

WSGI提出了三个概念,应用,服务器,中间件。缓存

    * 应用:指能够被调用的一个对象,通常指的是包含一个__call__方法的对象。服务器

    * 服务器:指的是实现了调用应用的部分。
app

    * 中间件:处于服务器和应用两侧,起粘合做用,具体包括:请求处理,environ处理等。框架

这里仍是举个例子吧,要不都是概念了。工具

from wsgiref.simple_server import make_server
from webob import Request, Response
#application
class AppTestByManual(object):        
    def __call__(self, environ, start_response):  
        req = Request(environ)
        return self.test(environ, start_response)
        
    def test(self, environ, start_response):
        res = Response()
        res.status = 200     #middleware
        res.body   = "spch"  #middleware
        return res(environ, start_response)
        
application = AppTestByManual()

#server
httpd = make_server('0.0.0.0', 8000, application)  
httpd.serve_forever()

在这个例子中,httpd服务器调用AppTestByManual应用,中间件部分对Response作了处理。

在这里须要注意的是应用部分实现了一个__call__方法,这是WSGI规范中规定的。

其实,对于中间件的定义是比较宽泛的,中间件主要是处理通过的Request和Response。


3. environ 变量

须要注意的是environ 变量,environ 字典中包含了在 CGI 规范中定义了的 CGI 环境变量。

REQUEST_METHODHTTP 请求的类型,好比「GET」或者「POST」。这个不多是空字符串,因此是必须给出的。

SCRIPT_NAMEURL 请求中路径的开始部分,对应应用程序对象(?),这样应用程序就知道它的虚拟位置。若是该应用程序对应服务器的根目录的话,它多是空字符串。

PATH_INFOURL 请求中路径的剩余部分,指定请求的目标在应用程序内部的虚拟位置(?)。若是请求的目标是应用程序根目录而且没有末尾的斜杠的话,可能为空字符串。

QUERY_STRINGURL 请求中跟在「?」后面的那部分,可能为空或不存在。

CONTENT_TYPEHTTP 请求中任何 Content-Type 域的内容,可能为空或不存在。

CONTENT_LENGTHHTTP 请求中任何 Content-Length 域的内容,可能为空或不存在。

SERVER_NAME,SERVER_PORT这些变量能够和 SCRIPT_NAME、PATH_INFO 一块儿组成完整的URL。然而要注意的是,重建请求 URL 的时候应该优先使用 HTTP_HOST 而非 SERVER_NAME 。。SERVER_NAME 和 SERVER_PORT 永远不能为空字符串,也老是必须存在的。

SERVER_PROTOCOL客户端发送请求所使用协议的版本。一般是相似「HTTP/1.0」或「HTTP/1.1」的东西,能够被用来判断如何处理请求包头。(既然这个变量表示的是请求中使用的协议,并且和服务器响应时使用的协议无关,也许它应该被叫作REQUEST_PROTOCOL。不过为了保持和 CGI 的兼容性,咱们仍是使用这个名字。)

wsgi.version:(1, 0) 元组,表明 WSGI 1.0 版

wsgi.url_scheme:字符串,表示应用请求的 URL 所属的协议,一般为「http」或「https」。

wsgi.input: 类文件对象的输入流,用于读取 HTTP 请求包体的内容。(服务端在应用端请求时开始读取,或者预读客户端请求包体内容缓存在内存或磁盘中,或者视状况而定采用任何其余技术提供此输入流。)

wsgi.errors: 类文件对象的输出流,用于写入错误信息,以集中规范地记录程序产生的或其余相关错误信息。这是一个文本流,即应用应该使用「n」来表示行尾,并假定其会被服务端正确地转换。(在 str 类型是 Unicode 编码的平台上,错误流应该正常接收并记录任意 Unicode 编码而不报错,而且容许自行替代在该平台编码中没法渲染的字符。)不少 Web 服务器中 wsgi.errors 是主要的错误日志,也有一些使用 sys.stderr 或其余形式的文件来记录。Web 服务器的自述文档中应该包含如何配置错误日志以及如何找到记录的位置。服务端能够在被要求的状况下,向不一样的应用提供不一样的错误日志。

wsgi.multithread:若是应用对象可能会被同一进程的另外一个线程同步调用,此变量值为真,不然为假。

wsgi.multiprocess:若是同一个应用对象可能会被另外一个进程同步调用,此变量值为真,不然为假。

wsgi.run_once:若是服务端指望(可是不保证能获得知足)应用对象在生命周期中之辈调用一次,此变量值为真,不然为假。通常只有在基于相似 CGI 的网关服务器中此变量才会为真。

from webob import Request
from pprint import pprint
req = Request.blank('/article?id=1')
pprint(req.environ)

输出结果为:

{'HTTP_HOST': 'localhost:80',
 'PATH_INFO': '/article',
 'QUERY_STRING': 'id=1',
 'REQUEST_METHOD': 'GET',
 'SCRIPT_NAME': '',
 'SERVER_NAME': 'localhost',
 'SERVER_PORT': '80',
 'SERVER_PROTOCOL': 'HTTP/1.0',
 'wsgi.errors': <open file '<stderr>', mode 'w' at 0x0157D0D0>,
 'wsgi.input': <_io.BytesIO object at 0x01EE8360>,
 'wsgi.multiprocess': False,
 'wsgi.multithread': False,
 'wsgi.run_once': False,
 'wsgi.url_scheme': 'http',
 'wsgi.version': (1, 0)}


4. 相关模块

wsgirefwsgiref是一个实现wsgi规范的模块,它提供了操纵WSGI环境变量和response头的工具,而且还实现了一个WSGI服务器。具体能够参考(https://docs.python.org/2/library/wsgiref.html )。

webob(WebOb):webob提供了包装后的WSGI请求(Request)环境,并辅助建立WSGI响应(Response)。具体能够参考(http://webob.org/ )。

安装:

pip install webob

routes:是一个管理URL(路由)的模块。具体参考(https://routes.readthedocs.org/en/latest/ )。

安装:

pip install routes

在本文中将主要使用这三个模块来讲明相关的例子。


5. Request和Response

在“3. environ 变量”中Request,它是对environ的包装,固然,咱们也能够打印出Response。

from webob import Response
res = Response(content_type='text/plain', charset='utf8')
f = res.body_file
f.write('hey')
print res.status, res.headerlist, res.body

输出结果:

200 OK [('Content-Type', 'text/plain; charset=utf8'), ('Content-Length', '3')] hey


通常状况下,Request和Response二者是相关的,咱们会根据请求来肯定响应。

def MyApp(environ, start_response):
    req = Request(environ)
    res = Response()
    if req.path_info == '/':
        res.body_file.write('url true!')
    else:
        res.body_file.write('url error!')
    return res(environ, start_response)
    
req = Request.blank('/')
res = req.get_response(MyApp)
print res

这里假设了一个请求(请求URL为"/"),当请求为"/"时,响应的body设置为“url true!”,不然设置为“url error!”。

固然这里仅仅是假设了这样一个过程,当请求进入MyApp,在MyApp中作对应处理再生成相应的响应。


6. 加上服务器,简化App的书写

在“2. 概念”中,已经有一个例子,如今再让咱们看看这个例子:

from wsgiref.simple_server import make_server
from webob import Request, Response

#application
class AppTestByManual(object):        
    def __call__(self, environ, start_response):  
        req = Request(environ)
        return self.test(environ, start_response)
        
    def test(self, environ, start_response):
        res = Response()
        res.status = 200     #middleware
        res.body   = "spch"  #middleware
        return res(environ, start_response)
        
application = AppTestByManual()

#server
httpd = make_server('0.0.0.0', 8000, application)  
httpd.serve_forever()

例子中AppTestByManual这个App实现了一个__call__方法供服务器调用,在App中将请求部分放入__call__处理,处理完成后调用test生成相应的响应。固然,这种写法是能够改进的。

from wsgiref.simple_server import make_server
from webob import Request, Response
from webob.dec import *

class AppTestByAuto(object):
    @wsgify
    def __call__(self, req):
        return self.test(req) 
        
    @wsgify
    def test(self, req):
        res = Response()
        res.status = 200
        res.body   = "spch"
        return res 
        
application = AppTestByAuto()

httpd = make_server('0.0.0.0', 8000, application)  
httpd.serve_forever()

这个例子是对上一个例子的改进,这里使用了@wsgify装饰器,这样咱们就不须要写那么多参数了,这个语法糖为咱们作了封装。


7. 路由处理

本节将会在上节例子的基础上,加入路由(URL)处理。这个例子里面,使用routes模块来管理URL。

from wsgiref.simple_server import make_server
from webob import Request, Response
from webob.dec import *
from routes import Mapper, middleware

class Test(object):
    def index(self):
        return "do index()"
    def add(self):
        return "do show()"
        
class MyApp(object):
    def __init__(self):
        print '__init__'
        self.controller = Test()
        m = Mapper()
        m.connect('blog', '/blog/{action}/{id}', controller=Test,
                  conditions={'method': ['GET']})
        m.redirect('/', '/blog/index/1')
        self.router = middleware.RoutesMiddleware(self.dispatch, m)
        
    @wsgify
    def dispatch(self, req):
        match = req.environ['wsgiorg.routing_args'][1]
        print req.environ['wsgiorg.routing_args']
        if not match:
            return 'error url: %s' % req.environ['PATH_INFO']
        action = match['action']
        if hasattr(self.controller, action):
            func = getattr(self.controller, action)
            ret = func()
            return ret
        else:
            return "has no action:%s" % action
            
    @wsgify
    def __call__(self, req):
        print '__call__'
        return self.router
        
if __name__ == '__main__':
    httpd = make_server('0.0.0.0', 8000, MyApp())
    print "Listening on port 8000...."
    httpd.serve_forever()

在这个例子中,routes模块是WSGI中典型的中间件模块。在MyApp的构造方法中,将路由和dispatch方法放入中间件self.router,在实现__call__方法时返回中间件self.router。

“dispatch”中的“getattr(self.controller, action)”使用了python的内省机制(相似设计模式中的反射),经过匹配map中的url规则自动匹配对应的类和方法。


按照这个例子,咱们还能够修改为这样:

from wsgiref.simple_server import make_server
from webob import Request, Response
from webob.dec import *
from routes import Mapper, middleware

class images(object):
    def index(self):
        return "images do index()"
    def create(self):
        return "images do create()"
    def detail(self):
        return "images do detail()"
        
class servers(object):
    def index(self):
        return "servers do index()"
    def create(self):
        return "servers do create()"
    def detail(self):
        return "servers do detail()"
        
class APIRouter(object):
    def __init__(self):
        print '__init__'
        self.controller = None
        m = Mapper()
        m.connect('blog', '/{class_name}/{action}/{id}',
                  conditions={'method': ['GET']})
        m.redirect('/', '/servers/index/1')
        self.router = middleware.RoutesMiddleware(self.dispatch, m)
        print self.router
        
    @wsgify
    def dispatch(self, req):
        match = req.environ['wsgiorg.routing_args'][1]
        print req.environ['wsgiorg.routing_args']
        if not match:
            return 'error url: %s' % req.environ['PATH_INFO']
        
        class_name = match['class_name']
        self.controller = globals()[class_name]()
            
        action = match['action']
        if hasattr(self.controller, action):
            func = getattr(self.controller, action)
            ret = func()
            return ret
        else:
            return "has no action:%s" % action
    @wsgify
    def __call__(self, req):
        print '__call__'
        return self.router
        
if __name__ == '__main__':
    httpd = make_server('0.0.0.0', 8000, APIRouter())
    print "Listening on port 8000...."
    httpd.serve_forever()

是否是像OpenStack里面的某一部分,^_^

相关文章
相关标签/搜索