人生在世,有得就有失,有付出就有回报,鱼和熊掌不能兼得。有时你的付出不一定能得到回报,但自己要想明白一些,不要太苛求自己,生命总有它的轮回,上帝是公平的,它对每个人都是一样的垂青。人生苦短,就好好的潇洒走一回。
简介
官方文档把PasteDeploy介绍为一个发现并配置WSGI应用和服务的一个系统,通过loadapp(一个简单的函数)就可以部署WSGI,而且不需要知道WSGI应用的细节。
我们先来看下,在WSGI简单的模型中,如果有多个URL,一般是怎么做的呢?直接在代码判断请求方和请求路径的然后分别处理不同的URL。
处理函数 app.py
1 | def application(environ, start_response): |
服务器函数 server.py
1 | # 从wsgiref模块导入: |
这种方法是简单,直接的,但也是可重复性差,不可维护的。一个restful中有可能50个以上的URL路径,如果这么写下去,那维护的人要抓狂。
所以要以一种简单清晰,可维护性好的方式去解析URL,这种方式便是使用PasteDeploy模型。
PasteDeploy的工作模式是使用一个配置文件configure.ini去解析URL。
paste 配置分析
PasteDeploy配置文件由若干section组成,section的声明格式如下:
1 | [type:name] |
其中,方括号括起的section声明一个新section的开始,section的声明由两部分组成,section的类型(type)和section的名称(name),如:[app:main]等。section的type可以有:app、composite、filter、pipeline、filter-app等。
每个section中具体配置项的格式就是基本的ini格式:key = value,此外,PasteDeploy的配置文件中使用“#”标注注释。
在基本了解PasteDeploy配置文件的书写格式后,我们不妨看一个实例,来具体了解不同type的section。
先从一个PasteDeploy的配置文件(config.ini)入手分析
1 | [composite:main] |
上面的示例文件列出了若干不同type的section示意,下面就一一探讨PasteDeploy可定义的section type。
Type = composite(组合应用)
顾名思义,组合应用由若干WSGI应用组成,composite为这些应用提供更高一层的分配工作。
我们具体分析示例1中的如下部分:
1 | [composite:main] |
该段配置文件定义了一个名为main、类型为composite的section,方括号的声明以下是该section的具体配置,遵循 key = value 的统一格式。
Composite类型的section将URL请求分配给其他的WSGI应用。
use = egg:Paste#urlmap 意味着使用 Paste 包中的 urlmap 应用。urlmap是Paste提供的一套通用的composite应用,作用就是根据用户请求的URL前缀,将用户请求映射到对应的WSGI应用上去。这里的WSGI应用有:“home”, “blog”, “wiki” 和 “config:cms.ini”。
最后一项仅仅是参考了同一个目录中的另一个文件”cms.ini”。
Type = app(WSGI应用)
回到示例1中的下一部分:
1 | [app:home] |
app类型的section声明一个具体的WSGI应用。调用哪个python module中的app代码则由的use后的值指定。
这里的 egg:Paste#static 是另一个简单应用,作用仅仅是呈现静态页面。它接收了一个配置项: document_root ,后面的值可以从全局配置DEFAULT中提取,提取方法s是使用变量替换:比如 %(var_name)s 的形式。
这里 %(here)s 的意思是这个示例配置文件所在的目录,因为相对路径在不同服务器中的解释方法不同,出于移植性的考虑,官方文档上推荐当前这种写法。
示例中定义了多个app类型的section,因为PasteDeploy的配置文件中允许定义多个app类型的section,同时要求每个WSGI应用也都应该拥有自己的section。这样,每一个WSGI应用在配置文件中都有一个app类型的section与之对应,默认地,”main”应用对应于 app:main 或 app 。
应用的具体实现要在section中配置,有两种方法专门用于指出应用对应的代码:使用URI(用use标识)或 直接指向实现代码(用protocol标识)。
使用另一个URI
采用该方法的特点是指出应用的实现代码的那一条 key = value 配置项采用”use”作为键,该方法也有许多变种,官方的示例中给出了一些介绍:
1 | [app:myapp] |
最后指向其他的section的那个例子,看起来似乎没有什么意义,似乎只是两个相同的WSGI应用。但是这样的定义允许我们在 [app:mylastapp] 这个应用中定义一些局部的配置项,从而在重用代码的同时覆写它引用的应用配置。
直接指向应用的实现代码
采用该方法的特点是指出实现代码的那一条 key = value 配置项采用“协议”作为键,所谓“协议”即protocol,告诉PasteDeploy即将加载的对象类型,如:
1 | [app:myapp] |
该例的protocol paste.app_factory 是一个应用的工厂函数,指明import对象的类型;值 myapp.modulename:app_factory 指明具体加载的模块和方法。
除了app_factory外还有composite_facory,fliter_factory, fliter_app_factory,server_factory,server_runner等,后面跟上myapp.modulename模块下的app_factory类,不同协议其app_factory也不一样。
那么这几种工厂模式,配置了之后在代码中怎么使用呢?
实现factory
app_factory
1 | def app_factory(global_config, **local_conf): |
global_config传入的是字典参数,local_conf传入的是key-value参数,返回一个wsgi application
composite_factory
1 | def composite_factory(loader, global_config, **local_conf): |
loader调用一些特殊函数, get_app(name_or_uri, global_conf=None) return a WSGI application with the given name. get_filter and get_server work the same way。如下使用方案:
1 | def pipeline_factory(loader, global_config, pipeline): |
1 | [composite:main] |
将数据读取传入,将filter printdebug,session读入并付给filter,读取app为pipeline参数的最后一个myapp,翻转filter先运行session(myapp),再运行egg:Paste#printdebug(myapp)。
fliter_factory
类似app_factroy但返回的是filter对象,而且仅仅接受WSGI application为唯一的参数,如下所示
1 | def auth_filter_factory(global_conf, req_usernames): |
定义了AuthFilter对象实现请求变量’REMOTE_USER’下的变量和req_usernames是否一致,filter(app)调用唯一对象app,再里面再做处理并返回信息,通过call把类像函数一样调用。
fliter_app_factory
1 | class AuthFilter(object): |
除了参数包括app,返回对象为WSGI application外,其他不变。
server_factory
一个参数 wsgi_app,返回serve
1 | def server_factory(global_conf, host, port): |
server_runner
官方文档说不传wsgi_app,其他和server_factory一样,没给例子
Type = filter*(过滤器)
filter是作用于WSGI应用上的函数或方法,以app为唯一的参数,并返回一个“过滤”后的app。归功于WSGI接口的规范,不同的filter可以依次“过滤”某一app,事实上多个filter处理一个app也就是下文中提到的管道(pipeline)。
主要有三种方法进行filter,第一种是fliter-with,第二种是filter-app,第三种是pipeline,多说一句,openstack用的最多的是pipeline。
Type = filter(过滤器)
第一种,通过fliter-with指向下一个fliter应用
在PasteDeploy的配置文件中有多种方法来“过滤”应用,比如示例1中:
1 | [app:main] |
在 [app:main] 的 filter-with 字段指明用来处理该应用的filter,就指定了名为”printdebug”的filter来处理应用”main”。在 [filter:printdebug] 中还可以定义新的 filter-with 字段,从而将处理关系延续下去。
Type = filter-app
第二种,通过next配置指向下一个filter应用
同样是处理应用,在PasteDeploy配置文件中可以有着不同的写法,比如示例1中的下面部分,就是使用filter-app类型的section来声明一个filter:
1 | [filter-app:blog] |
该部分采用了[filter-app:NAME]类型的section声明了一个filter,指定使用的代码,以及要处理的应用: next 字段的值。从而PasteDeploy会自动地将过滤器”blog”作用在应用”blogapp”上。
Type = pipeline
第三种,pipeline依次列出filter即可
pipeline便于对一个应用添加多个过滤器,比如示例1中:
1 | [pipeline:main] |
就指定了在app上施加三个filter进行处理。
总结起来,想要在某个应用前添加多个filter,共有 [filter-app:…] [pipeline:…] 和 [app:…] filter-with = … 等方法。
egg模式,通过setuptools打包python源文件,类似java jar的格式;
1
2
3
4
5
6
7
8
9
10 > setup(
> name='MyApp',
> ...
> entry_points={
> 'paste.app_factory': [
> 'main=myapp.mymodule:app_factory',
> 'ob2=myapp.mymodule:ob_factory'],
> },
> )
>
>
通过上面配置安装egg,use=egg:MyApp#main或者use=egg:MyApp#ob2,可以分别找到myapp.mymodule下的app_factory对象和ob_factory对象
局部配置与全局配置
局部配置与全局配置的格式
PasteDeploy配置文件的所有配置项均使用 key = value 格式,但是局部配置项和全局配置项定义的位置不同。如:
1 | [app:blog] |
每一个section内设置具体的键值关系,构成这些section自己的局部配置。
为了便于不同的应用读取某些固定的系统信息,PasteDeploy允许设置全局配置变量,所有的全局配置必须放在[DEFAULT]字段下设置,如:
1 | [DEFAULT] |
注意[DEFAULT]段名是大小写敏感的,因此必须严格大写。
局部配置和全局配置的覆写
上面的两个例子,实际上展示了局部配置和全局配置的覆写,这里详细介绍,首先看局部配置的覆写:
1 | [app:blog] |
已经知道,一些section可以直接复用其他section的代码,并定制配置信息,这里 [app:otherblog] 就采用了 [app:blog] 的代码,同时将配置项 blogname 改为自己特定的。
另一方面,应用在本地可以修改全局配置项的值:
1 | [DEFAULT] |
只需要在要覆写的键前加 set 即可。
至于为什么要探讨局部配置与全局配置,是因为二者在传递给不同类型的factory function时对应的参数不同,这些将在下文详细探讨。
paste 使用
使用示例 1
借鉴一个例子来讲解,原文 http://blog.csdn.net/li_101357/article/details/52755367
在家里水系统的模型大概如下图,以及对应的模拟路径:
1 | 总开关 /main |
PasteDeploy模型中大致可分部件,分别是:
对应到上例:
- app 水龙头 淋浴器
- filter 热水器
- pipeline 热水器 + 淋浴器
- composite 总开关
确定了对应的URL之后,使用PasteDeploy组件来构造解析文件configure.ini:
1 | [composite:main] |
我们再一一分析下,顺便复习一下:
1 | [composite:main] |
Paste#urlmap 表示,默认使用Paste.urlmap。
use = egg:Paste#urlmap 意味着直接使用来自于Paste包的urlmap的composite应用。 urlmap是特别常见的composite应用——它使用路径前缀来映射将你的请求与其他应用对应起来。
基本含义就是说,这是Paste已经提供好的一个composite,如果你想自定义就需要另外写一个composite_factory了。
1 | [app:tap] |
表示路径”/tap”的处理方法paste.app_factory存在于tap.py文件的的app_factory中,这是一个方法。
1 | [pipeline:pip_to_shower] |
pipeline 主要起到组合的作用,将filter(过滤器)和app(应用)组合起来,形成一条管道。
1 | [filter:boiler] |
filter类似app,只不过换成了paste.filter_app_factory,filter首先执行过滤功能,然后执行app。
配置文件中将路径的处理都配置好:
/main/tap
路径对应的处理函数 tap ,tap是文件tap.py的app_factory方法。
/main/boil/shower
路径对应的处理函数是管道 pip_to_shower 。管道由过滤器 boiler和应用shower组成。首次经过boiler的过滤,然后调用shower函数处理。
下面完成tap、shower和filter文件:
tap.py
1 | class Tap(object): |
app_factory是tap对应的处理方法,返回时调用了Tap方法,Tap对应的是类Tap的call方法,在该方法中打印一个”Tap”,然后发送报文头,最后返回一个字符串。
shower.py
1 | class Shower(object): |
shower的分析同上
boiler.py
1 | class Boiler(object): |
filter_app_factory是boiler对应的处理方法,其中传入的参数中有一个app,返回时调用了Boiler,并传入参数app。
Boiler是类Boiler的call方法,首先打印了字符串”Boiler”,然后返回时调用了函数app。这个app具体到本例就是调用了shower
所有的文件都准备齐全了,接下来开启WSGI服务,让程序跑起来。
server.py
1 | from wsgiref.simple_server import make_server |
运行server程序。
在浏览器中请求URL http://[ip ]:8080/main/tap
在浏览器中请求URL http://[ip ]:8080/main/boiler/shower
小结 1
使用PasteDeploy模块将URL解析从判断的方式转变到文件配置的方式。
使用configuer.ini文件配置了URL /main/tap 对应处理函数 app_factory和/main/boiler/shower 对应处理函数 shower.py中的app_factory。
这个案例讲的就是使用PasteDeploy模块去配置WSGI解析URL。在openstack源码中就是基于这样的模型去完成restful 的解析,处理等。当一条restful的请求如:http://192.168.252.177:5000/v2.0/token
到达服务器时,服务器处理的流程就是如上,通过配置文件查找URL处理函数,然后调用处理函数返回处理结果。
使用示例 2
paste.ini
1 | [composite:main] |
wsgi_server.py
1 | import os |
一切都准备好后,在终端执行 python wsgi_server.py
来启动 web server
如果像上图一样一切正常,那么打开浏览器
- 访问http://127.0.0.1:8000/blog,应该显示:This is Blog’s response body.
- 访问http://127.0.0.1:8000/wiki,应该显示:This is Wiki’s response body.。
注意:urlmap对url的大小写是敏感的,例如如果访问http://127.0.0.1:8000/BLOG,在url映射中未能找到大写的BLOG。
小结 2
掌握 PasteDeploy ,你只要按照以下三个步骤逐个完成即可。
1、配置 PasteDeploy使用的ini文件;
2、定义WSGI应用;
3、通过loadapp函数加载WSGI应用;
参考: