Keystone服用中用到了PasteDeploy来部署WSGI应用,所有来分析一下。
一.paste deploy简介
官方文档把PasteDeploy介绍为一个发现并配置WSGI应用和服务的一个系统,通过loadapp(一个简单的函数)就可以部署WSGI,而且不许要知道WSGI应用的细节。
从一个PasteDeploy的配置文件(config.ini)入手分析
[composite:main]
use = egg:Paste#urlmap
/ = home
/blog = blog
/wiki = wiki
/cms = config:cms.ini
[app:home]
use = egg:Paste#static
document_root = %(here)s/htdocs
[filter-app:blog]
use = egg:Authentication#auth
next = blogapp
roles = admin
htpasswd = /home/me/users.htpasswd
[app:blogapp]
use = egg:BlogApp
database = sqlite:/home/me/blog.db
[app:wiki]
use = call:mywiki.main:application
database = sqlite:/home/me/wiki.db
一个一个分析,首先是composite,compsite部分意味着把请求分发到其他应用上,main应该是这个部分的标识(官方文档无解释,自己猜测)。
use = egg:Paste#urlmap意味着一种简单的映射方式,利用Paste包下的urlmap应用,把路径不同的请求进行映射。如后面紧跟的/,/blog,/wiki等路径。
特别要注意的是最后一个,/cms=config:cms.ini,说明如果路径是这样写的话,指向相同目录下的cms.ini,再根据里面的配置进行映射。
[composite:main]
use = egg:Paste#urlmap
/ = home
/blog = blog
/wiki = wiki
/cms = config:cms.ini
然后是app,标识为home,连用到了一种新的映射方式static,进行静态映射,需要配置document_root的参数,指向静态文件目录,这里要注意的一点书%(here)s,官方文档的说明是:You can use variable substitution, which will pul lvariables from the section
[DEFAULT] (case sensitive!) with markers like
%(var_name)s。大概意思是用[DEFAULT]下的配置变量var_name=xxxx代替了[app:home]下的%(var_name)s的值。像这个配置文件就需要在[DEFAULT]下配置here=xxx为静态。
[app:home]
use = egg:Paste#static
document_root = %(here)s/htdocs
然后,filter-app代表了对blog应用进行过滤,这里没有具体说明,但是use表明使用了Authenication包下的auth应用,next说明指向下一个应用,roles和htpasswd都是配置选项,一个key-value键值对。下面的blogapp指向了egg封装的BlogApp应用,并配置了数据库目录。
[filter-app:blog]
use = egg:Authentication#auth
next = blogapp
roles = admin
htpasswd = /home/me/users.htpasswd
[app:blogapp]
use = egg:BlogApp
database = sqlite:/home/me/blog.db
最后,和上面配置的唯一区别就是use选项配置不再是egg封装的应用,直接通过call命令调用mywiki,main模块下application应用。
[app:wiki]
use = call:mywiki.main:application
database = sqlite:/home/me/wiki.db
至此,第一个配置文件分析完毕。
二.Paste Deploy基本用法
如下所示通过loadapp调用config.ini文件,进行应用WSGI接口的配置。需要注意的是config:xxx后面跟的路径如果是绝对路径,只有在config.ini里面再配置路径,如上面的/cms,才是相对路径。
from paste.deploy import loadapp
wsgi_app = loadapp('config:/path/to/config.ini')
config文件格式如下
[section_name]
key = value
another key = a long value
that extends over multiple lines
注释行,开头为#或者;
app应用部署方式有一下四种:
[app:myapp]
use = config:another_config_file.ini#app_name
# or any URI:
[app:myotherapp]
use = egg:MyApp
# or a callable from a module:
[app:mythirdapp]
use = call:my.project:myapplication
# or even another section:
[app:mylastapp]
use = myotherapp
第一种指向同目录下另一个config,ini中的app应用
第二种指向egg分装的应用
第三种指向一个模块下的应用
第四中指向本config.ini下的另一个app
还有一种特别的app的应用部署方式
[app:myapp]
paste.app_factory = myapp.modulename:app_factory
paste.app_factory是一种协议格式,除了app_factory外还有composite_facory,fliter_factory,
fliter_app_factory,server_factory,server_runner等。后面跟上myapp.modulename模块下的app_factroy类,不同协议其app_factory也不一样,后面会详细介绍如何定义每一个factories.
全局变量定义在[DEFAULT]下,但是可以修改全局变量,以如下方式
[DEFAULT]
admin_email = webmaster@example.com
[app:main]
use = ...
set admin_email = bob@example.com
这样就修修改了 admin_email
Composite Application,姑且成为复合应用吧,他本身不提供应用,而是通过类似上文url的方式提供映射,到不同的应用。
Filter Composition
有三种方法进行filter,第一种是fliter-with,第二种是filter-app,第三种是pipeline,多说一句,openstack用的最多的是pipeline。
第一种,通过fliter-with指向下一个fliter应用
[app:main]
use = egg:MyEgg
filter-with = printdebug
[filter:printdebug]
use = egg:Paste#printdebug
# and you could have another filter-with here, and so on...
第二种,通过next配置指向下一个filter应用
[filter-app:blog]
use = egg:Authentication#auth
next = blogapp
roles = admin
htpasswd = /home/me/users.htpasswd
[app:blogapp]
use = egg:BlogApp
database = sqlite:/home/me/blog.db
第三种,pipeline依次列出filter即可
[pipeline:main]
pipeline = filter1 egg:FilterEgg#filter2 filter3 app
[filter:filter1]
...
egg模式,通过setuptools打包python源文件,类似java jar的格式
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对象
三.Define Factories
app_factory
def app_factory(global_config, **local_conf):
return wsgi_app
global_config传入的是字典参数,local_conf传入的是key-value参数
返回一个wsgi application
composite_factory
def composite_factory(loader, global_config, **local_conf): return wsgi_apploader调用一些特殊函数, 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。如下使用方案:
def pipeline_factory(loader, global_config, pipeline):
# space-separated list of filter and app names:
pipeline = pipeline.split()
filters = [loader.get_filter(n) for n in pipeline[:-1]]
app = loader.get_app(pipeline[-1])
filters.reverse() # apply in reverse order!
for filter in filters:
app = filter(app)
return app
[composite:main]
use = <pipeline_factory_uri>
pipeline = egg:Paste#printdebug session myapp
[filter:session]
use = egg:Paste#session
store = memory
[app:myapp]
use = egg:MyApp
将数据读取传入,将filter printdebug,session读入并付给filter,读取app为pipeline参数的最后一个myapp,翻转filter先运行session(myapp),再运行egg:Paste#printdebug(myapp)。
fliter_factory
类似app_factroy但返回的是filter对象,而且仅仅接受WSGI application为唯一的参数,如下所示
def auth_filter_factory(global_conf, req_usernames):
# space-separated list of usernames:
req_usernames = req_usernames.split()
def filter(app):
return AuthFilter(app, req_usernames)
return filter
class AuthFilter(object):
def __init__(self, app, req_usernames):
self.app = app
self.req_usernames = req_usernames
def __call__(self, environ, start_response):
if environ.get('REMOTE_USER') in self.req_usernames:
return self.app(environ, start_response)
start_response(
'403 Forbidden', [('Content-type', 'text/html')])
return ['You are forbidden to view this resource']
定义了AuthFilter对象实现请求变量'REMOTE_USER'下的变量和req_usernames是否一致,filter(app)调用唯一对象app,再里面再做处理并返回信息,通过__call__把类像函数一样调用。
fliter_app_factory
class AuthFilter(object):
def __init__(self, app, global_conf, req_usernames):
....
除了参数包括app,返回对象为WSGI application外,其他不变。
server_factory
一个参数 wsgi_app,返回serve
def server_factory(global_conf, host, port):
port = int(port)
def serve(app):
s = Server(app, host=host, port=port)
s.serve_forever()
return serve
server_runner
官方文档说不传wsgi_app,其他和server_factory一样,没给例子
bluefire
2013/10/28
13:27