进一步详解WSGI

还是老样子,需要知道什么是WSGI?

一、什么是WSGI

(1)、WSGI(Web 服务器网关接口)是python中所定义的Web服务器和Web应用程序之间或框架之间的接口标准规范

(2)、WSGI接口规范的目的就是规范Web服务器与Web应用之间的交互,在协议之间进行转换

(3)、WSGI将Web组件分成三类:Web服务器(Server)、Web中间件与Web应用程序。        

   应用:指的是可以被调用的一个对象,一般指的是包含一个__call__方法(实例可以当作函数一般调用)的对象。

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

   中间件:处于服务器和应用两侧,起粘合作用,具体包括:请求处理、environ处理。

举例说明:

from wsgiref.simple_server import make_server
from webob import Request, Response    #后面介绍这个模块

class APPTest(object):
    def __call__(self, environ, start_response):
        urll =  ['%s : %s' % (key, value) for key,value in environ.items()]  #传递进来的environ环境变量

        print '\n'.join(urll)
        print '\n\n\n'

        req = Request(environ) #处理环境变量,生成Request对象,代表客户端HTTP请求传递而来的环境变量
        print req

        print '\n\n\n'
        return self.test(environ, start_response)

    def test(self, environ, start_response):

        urll =  ['%s : %s' % (key, value) for key,value in environ.items()]

        print '\n'.join(urll)
        print '\n\n\n'

        res = Response()  //Response类类型的实例对象res,实现__call__函数可以直接作为函数调用,对于HTTP响应 header和body的封装
        print res
        print '\n\n\n'
        print type(res)

        res.status = 200
        res.body = 'hello world'
        return res(environ, start_response)

application = APPTest()

httpd = make_server('127.0.0.1', 8000, application)
print 'Listen on port 8000....'
httpd.serve_forever()


这个例子中,httpd服务器调用APPTest应用,中间件部分对Response部分做了处理。中间件主要处理Request和Response(HTTP请求信息和响应信息的封装)。

print req;

进一步详解WSGI_第1张图片

print res;


print type(res);


程序执行结果:

 进一步详解WSGI_第2张图片

 

二、environ变量都有哪些

  需要注意的是environ 变量,environ 字典中包含了在 CGI 规范中定义了的 CGI 环境变量。
 
REQUEST_METHOD:HTTP 请求的类型,比如「GET」或者「POST」。这个不可能是空字符串,所以是必须给出的。

  SCRIPT_NAME:URL 请求中路径的开始部分,对应应用程序对象(?),这样应用程序就知道它的虚拟位置。如果该应用程序对应服务器的根目录的话,它可能是空字符串。
  PATH_INFO:URL 请求中路径的剩余部分,指定请求的目标在应用程序内部的虚拟位置(?)。如果请求的目标是应用程序根目录并且没有末尾的斜杠的话,可能为空字符串。


  QUERY_STRING:URL 请求中跟在「?」后面的那部分,可能为空或不存在。
  CONTENT_TYPE:HTTP 请求中任何 Content-Type 域的内容,可能为空或不存在
  CONTENT_LENGTH:HTTP 请求中任何 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 的网关服务器中此变量才会为真。

举例说明: 

#! /usr/bin/python
#coding=utf-8

from webob import Request

req = Request.blank('/article?id=1')

print req
print '\n\n'
urll = ['%s : %s' % (key,value) for key,value in sorted(req.environ.items())]
print '\n'.join(urll)

程序执行结果:

进一步详解WSGI_第3张图片


三、相关模块

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


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

   安装:apt-get install python-webob


1)、Webob通过对WSGI的请求与响应进行封装来简化WSGI应用的编写

Webob中两个最重要的对象,一是webob.Request,对WSGI请求的environ参数进行封装,一是webob.Response,包含了标准WSGI响应的所有要素  

2)、 原始的WSGI格式  
app_iter = myfunc(environ, start_response)

3)、使用webob封装之后
def myfunc(req):  
        return webob.Response('hey there')  
resp = myfunc(req)


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

   安装:apt-get install routes  (后面会详细介绍此模块)

四、Request和Response

  Request对象是对HTTP请求的environ环境变量的封装操作,那么Response是什么作用呢?

举例说明:

from webob import Response

res = Response(content_type = 'text/plain', charset = 'utf-8')
print res

print res.status     #响应状态
print res.headerlist #响应的头部信息
print res.body       #此时响应的body为空

f = res.body_file    #响应的body,文件对象
print f, type(f)   
f.write('hello world')

print res.body
运行结果:

进一步详解WSGI_第4张图片


五、加上装饰器,简化应用程序的书写

一中:将APPTest这个APP实现一个__call__方法供服务器调用,在APP中将请求部分放入__call__处理,处理完之后调用test函数生成相应的响应。通过装饰器,可以进行改进:

程序如下:

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

class App(object):

    @wsgify
    def __call__(self, req):  #通过装饰器的修饰,通过req就是Request对象,可以得到HTTP请求的变量信息
        print req
        print req.environ

        return self.test(req)

    @wsgify
    def test(self, req):
        res = Response()
        res.status = 200
        res.body_file.write('hello world!')

        return res                      #res(environ, start_response)

application = App()

httpd = make_server('localhost', 8000, application)
httpd.serve_forever()
程序运行结果:

进一步详解WSGI_第5张图片

这个例子是对最上面第一个例子的改进,使用@wsgify装饰器(可以将一个普通函数转变成WSGI应用程序),就不需要写那么多参数,这个语法糖为我们做了封装。(装饰器不熟悉的话请复习python基本的语法:python装饰器)。  用装饰器进行修饰之后,代表的req = Request(environ)实例。参数req是一个Request实例,可以通过req读取响应的环境变量。


五、路由处理

上小节基础上加入路由(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()\n"
    def add(self):
        return "do show()\n"

class Router(object):
    def __init__(self):
        print '__init__'
        self.controller = Test()
        mapper = Mapper()
        mapper.connect('blog', '/blog/{action}/{id}', controller=Test,conditions={'method': ['GET']})
        mapper.redirect('/', '/blog/index/1')
        self.router = middleware.RoutesMiddleware(self.dispatch, mapper)
        
    @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('127.0.0.1', 8000, Router())
    print "listening on 8000..."
    httpd.serve_forever()     

routes模块是WSGI中典型的中间件模块。在Router的构造方法中,将路由对象mapper和dispatch方法放入 中间件self.router,在实现__call__方法时

返回中间件self.router。


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

应的类和方法。

mapper.redirect('/', '/blog/index/1')路由重定向。。。

代码执行结果:


进一步详解WSGI_第6张图片

301,302 都是HTTP状态的编码,都代表着某个URL发生了转移,不同之处在于: 

301 redirect: 301 代表永久性转移(Permanently Moved)。

302 redirect: 302 代表暂时性转移(Temporarily Moved )。 

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()\n"
    def create(self):
        return "images do create()\n"
    def detail(self):
        return "images do detail()\n"

class servers(object):
    def index(self):
        return "servers do index()\n"
    def create(self):
        return "servers do create()\n"
    def detail(self):
        return "servers do detail()\n"

class Router(object):
    def __init__(self):
        print '__init__'
        self.controller = None
        mapper = Mapper()
        mapper.connect('blog', '/:class_name/{action}/{id}',conditions={'method': ['GET']})                              
        mapper.redirect('/', '/servers/index/1')
        self.router = middleware.RoutesMiddleware(self.dispatch, mapper)
    
    @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('127.0.0.1', 8000, Router())
    print "listening on 8000..."
    httpd.serve_forever()

程序执行结果:

进一步详解WSGI_第7张图片



自行理解程序!!!


后面会介绍@wsgify装饰器和python装饰器。  全部是我国庆期间总结看了好多博客的干货啊,基础不好伤不起啊!!!


参考博客:

https://my.oschina.net/crooner/blog/609030


你可能感兴趣的:(openstack组件研究)