Paste模块的世界


 1. 缘起

当看到OpenStack的api-paste.ini中大量出现的composite,pipline,filter,app之后顿时觉得不淡定了,这些都是啥,是啥!在详细研究一番之后,却发现了意外的惊喜,好似发现了另一个世界,也明白了OpenStack使用这套东西却有他的高明之处。

本文不会阐述任何与OpenStack相关的paste内容,就是很纯粹地来讲paste,讲paste这个美妙的世界。所以本篇被归类为python分类。


2. paste的世界观

paste单词本意为:糊料,糨糊,是不是想到这是一种粘合剂。你猜对了,就是粘合剂。

继续,那么我们在看看下面这些关键词:

filter:过滤器,滤网。

pipline:管道

app:application 应用,在这个语境下我举个例子吧,lavabo(洗手盆),fish tank(鱼缸)...... 这些都是应用

composite:意为合成,或合成结构。

那么这些到底都是什么,有没有想到管道工程,估计你也猜到了,估计paste作者的灵感就是从这地方来的吧。

为了搞明白这个,好不容易找了一张图来说明这个。

Paste模块的世界_第1张图片

我也不是专业管道工程的,所以按我的理解给关键字归归类吧。

paste是粘合剂,这里暂且看做是结合处的钉子吧。

filter:过滤器,这里可以就以图中的7假设里面有过滤网,8 (假设里面有过滤网),12(压力表),17(流量表指代吧。

app:这里就用13来指代吧,就是水龙头了吧。

pipeline:管线,1, 2, 3,5,15都算吧。

composite:这个用6来指代就很贴切了。

再回过头来看看,paste的整个体系就浮现在眼前了吧。


3. paste的目的与用心

有了上面一整套的东西(积木),或者说管道工程的世界观,paste的目的是什么,它要做什么?用过django,flask或者tornado的人一定知道,一个url一般与一个类或方法对应,以典型的flask为例:  

@app.route('/')
def index():
    return 'Index Page'
@app.route('/hello')
def hello():
    return 'Hello World'

这里可以看到“/”对应了index() ,“/hello”对应了hello()。这里的装饰器被称为route。

其实单从URL上来说,是存在某种逻辑关系的,这种逻辑关系就像管道工程!

这种URL有可能必须经过某个分发(多通管道),又经过某个验证过程(过滤)之后再最终执行某个过程(应用)。

估计发明paste的作者在思考怎么把这种虚拟的逻辑关系用现实中的物体表示的时候,看到身边的管道之后就开始浮现连篇了吧。取名也被叫做了paste,确实是煞费苦心。


4. 实例

管道中的液体,暂且假设为水,那么我们假设有如下URL,假设这是一家酒店:

/master_valve/hydrant                                 #/总阀/消防栓

/master_valve/tap                                        #/总阀/水龙头

/master_valve/boiler/shower                       #/总阀/锅炉/花洒(淋浴房)

/master_valve/purifier/drinking_fountain    #/总阀/净化器/直饮水机

根据上面的URL我们用paste来描述出它们的逻辑关系

首先应该确定关键词:

composite:master_valve

filter:purifier,boiler

app:hydrant,tap,shower,drinking_fountain

按照这一逻辑,水从总阀中引出后经过管线进入一个多通管道,被分别引入消防栓,水龙头,净水器和锅炉。从净水器引出后进入直饮水机,而从锅炉引出后进入淋浴房的花洒。

Paste模块的世界_第2张图片


5. 动手描述逻辑关系

根据上面的假设,我们开始编写一个ini用来描述这种逻辑关系

下面我们来写配置文件configured.ini,逻辑关系是很清晰的,一目了然。

[app:hydrant]
paste.app_factory = hydrant:app_factory
in_arg = water

[app:tap]
paste.app_factory = tap:app_factory
in_arg = water

[app:shower]
paste.app_factory = shower:app_factory
in_arg = hot_water

[app:drinking_fountain]
paste.app_factory = drinking_fountain:app_factory
in_arg = pure_water

[filter:purifier]
paste.filter_app_factory = purifier:filter_app_factory
in_arg = water

[filter:boiler]
paste.filter_app_factory = boiler:filter_app_factory
in_arg = water

[pipeline:pip_to_shower]
pipeline = boiler shower

[pipeline:pip_to_drinking_fountain]
pipeline = purifier drinking_fountain

[composite:valve]
use = egg:Paste#urlmap
/hydrant = hydrant
/tap = tap
/boiler/shower = pip_to_shower
/purifier/drinking_fountain = pip_to_drinking_fountain

[composite:main]
use = egg:Paste#urlmap
/master_valve: valve

这里需要注意的是里面的命名规则,还是来解释一下这个配置文件吧。

里面有app,filter,pipeline,composite,与之对应的是里面要确定对应的factory,以

[app:tap]
paste.app_factory = tap:app_factory
in_arg = water

为例,表示tap的paste.app_factory存在于tap(tap.py)的app_factory中,这是一个方法,在后面我们会提到。

同样的,对于filter也是类似的,只不过换成了paste.filter_app_factory,在真正实现的时候,要注意与app的factory的区别。

接下来就是pipeline,它主要起到组合的作用,将filter(过滤器)和app(应用)组合起来,形成一条管线。

注意:本来,按逻辑上来讲,水从master_valve(总阀)出来也应该经过pipeline(管线)再到tap(水龙头)或hydrant(消防栓),这里只是形式上省略了。

[composite:valve]
use = egg:Paste#urlmap
/hydrant = hydrant
/tap = tap
/boiler/shower = pip_to_shower
/purifier/drinking_fountain = pip_to_drinking_fountain

这一部分,Paste#urlmap表示默认使用了Paste.urlmap 

在http://pythonpaste.org/deploy/ 上有一段关于urlmap的解释:

use = egg:Paste#urlmap means to use the composite application named urlmap from the Paste package. urlmap is a particularly common composite application – it uses a path prefix to map your request to another application. 

翻译过来大意为:

use = egg:Paste#urlmap意味着直接使用来自于Paste包的urlmap的composite应用。 urlmap是特别常见的composite应用——它使用路径前缀来映射将你的请求与其他应用对应起来。

基本含义就是说,这是Paste已经提供好的一个composite,如果你想自定义就需要另外写一个composite_factory了。

paste其实还提供很多已经写好的factory,可以分别用于app,filter,composite,pipeline,例如:egg:Paste#static。

/hydrant = hydrant

这些就是路径对应,不做过多解释。


6. 实现里面的类

建立一个工程,里面文件结构如下:

Paste模块的世界_第3张图片

配置文件configured.ini在第5节中已经展示过,这里就不再提及了。

从总阀开始说起,

#master_valve.py
#pip install paste
#pip install PasteDeploy
#import os
#os.chdir('D:/py_test/paste/hotel')
if __name__ == '__main__':
    from paste import httpserver
    from paste.deploy import loadapp
    httpserver.serve(loadapp('config:configured.ini', relative_to='.'),
                     host='0.0.0.0', port='8080')

这里使用了httpserver,在loadapp中将配置文件载入,并绑定在8080端口。

这里要注意的是路径问题,如果你是在windows下运行,可能需要os.chdir('/xxx')定位到你所在的目录中去。

还有就是需要安装paste和PasteDeploy,使用pip安装即可。


下面介绍的是四个应用(app)

水龙头

#tap.py
class Tap(object):
    def __init__(self, in_arg):
        self.in_arg = in_arg
    
    def __call__(self, environ, start_response):
        print 'Tap'
        status = '200 OK'
        response_headers = [('Content-Type', 'text/plain')]
        start_response(status, response_headers)
        return ['%s, %s!\n' % (self.in_arg, 'Tap')]
def app_factory(global_config, in_arg):
    return Tap(in_arg)

花洒(淋浴房)

#shower.py
class Shower(object):
    def __init__(self, in_arg):
        self.in_arg = in_arg
    
    def __call__(self, environ, start_response):
        print 'Shower'
        status = '200 OK'
        response_headers = [('Content-Type', 'text/plain')]
        start_response(status, response_headers)
        return ['%s, %s!\n' % (self.in_arg, 'Shower')]
def app_factory(global_config, in_arg):
    return Shower(in_arg)

消防栓

#hydrant.py
class Hydrant(object):
    def __init__(self, in_arg):
        self.in_arg = in_arg
    
    def __call__(self, environ, start_response):
        print 'Hydrant'
        status = '200 OK'
        response_headers = [('Content-Type', 'text/plain')]
        start_response(status, response_headers)
        return ['%s, %s!\n' % (self.in_arg, 'Hydrant')]
def app_factory(global_config, in_arg):
    return Hydrant(in_arg)

直饮水机

#drinking_fountain.py
class DrinkingFountain(object):
    def __init__(self, in_arg):
        self.in_arg = in_arg
    
    def __call__(self, environ, start_response):
        print 'DrinkingFountain'
        status = '200 OK'
        response_headers = [('Content-Type', 'text/plain')]
        start_response(status, response_headers)
        return ['%s, %s!\n' % (self.in_arg, 'DrinkingFountain')]
def app_factory(global_config, in_arg):
    return DrinkingFountain(in_arg)

这四个应用的结构基本是类似的,需要注意的是factory方法的参数,这会与后面介绍的filter的factory有所不同。

每一个应用类内部的__init__和__call__方法是需要自己实现的,__call__方法返回的字符串将会在web页面上显示,start_response(status, response_headers)语句及相关参数涉及到wsgi,因为涉及的知识更多,这里就不再展开了。

本博可能会另外写一篇文章专门阐述wsgi,敬请关注......

应用部分介绍结束。

下面就是过滤器了,这里一共有两个过滤器,分别是锅炉和净水器。

锅炉

#boiler.py
class Boiler(object):
    def __init__(self, app, in_arg):
        self.app = app
        self.in_arg = in_arg
        
    def __call__(self, environ, start_response):
        print 'Boiler'
        return self.app(environ, start_response)
def filter_app_factory(app, global_config, in_arg):
    return Boiler(app, in_arg)

净水器

#purifier.py
class purifier(object):
    def __init__(self, app, in_arg):
        self.app = app
        self.in_arg = in_arg
        
    def __call__(self, environ, start_response):
        print 'purifier'
        return self.app(environ, start_response)
def filter_app_factory(app, global_config, in_arg):
    return purifier(app, in_arg)

注意到过滤器和应用的不同了吧,在实现过滤器factory的时候,必须要将app参数带入,且必须是第一个位置

这里过滤器中并没有真正过滤什么,实际上应该在__call__中进行一定的处理,设定一定的条件,将一定的条件拦截。

由于这里只是粗略的实现,只做逻辑上的说明。


7. 执行

python master_valve.py

在浏览器中输入

http://127.0.0.1:8080/master_valve/tap 

Paste模块的世界_第4张图片

此时,程序只是运行了tap这个app。


再次在浏览器中输入

http://127.0.0.1:8080/master_valve/boiler/shower 

Paste模块的世界_第5张图片

此时,水经过锅炉,再进入花洒,执行了两个app。


特别声明:本文原创,欢迎大家转载,但还请注明出处,谢谢!http://my.oschina.net/crooner/blog/606895 







你可能感兴趣的:(deploy,paste,paste,PasteDeploy)