通过PasteDeploy+Webob来配置WSGI服务器接口
Webob是一种封装了HTTP协议的模块,具体课参考官方文档,不过这两天不知为什么不能访问,我是直接下载的源代码,源代码下docs自带本地文档,可以通过sphnix-builder的命令来生成本地文档
测试了两种方案
一种是不使用Webob装饰器的方式
一种是使用Webob装饰器的方式
配置文件如下test-deploy.ini
[DEFAULT]
key1=value1
key2=value2
key3=values
[composite:main]
use=egg:Paste#urlmap
/=show
/auther=auther
/version=version
[pipeline:show]
pipeline = auth root
[pipeline:version]
pipeline = logrequest showversion
[pipeline:auther]
pipeline = logrequest showauther
[filter:logrequest]
username = root
password = 123
paste.filter_factory = test.testdeploy:log_factory
[app:showversion]
version = 1.0.0
paste.app_factory = test.testdeploy:version_factory
[app:showauther]
auther = bluefire1991
paste.app_factory = test.testdeploy:showauther_factory
[app:root]
paste.app_factory = test.testdeploy:show_factory
[filter:auth]
paste.filter_factory = test.testdeploy:filter_factory
代码文件testdeploy.py
'''
Created on 2013-10-28
@author: root
'''
import logging
import os
import sys
import webob
from webob import Request
from webob import Response
from webob.dec import *
from webob.exc import *
from paste.deploy import loadapp
from wsgiref.simple_server import make_server
import signal
def sigint_handler(signal, frame):
"""Exits at SIGINT signal."""
logging.debug('SIGINT received, stopping servers.')
sys.exit(0)
#用于封装app
@wsgify
def test(request):
return Response('Here!')
#用于封装app
@wsgify
def application(request):
return Response('Hello and Welcome!')
#wsgify.middleware用于wsgi的对外filter层,必须要两个参数,request对象和app对象,app用于向下一个filter或者app传递参数
@wsgify.middleware
def auth_filter(request, app):
if request.headers.get('X-Auth-Token') != 'bluefire1991':
#类似于在这里写了start_response和return函数,只不过这里由webob的Request对象封装好了,本来test是一个函数对象,调用需要test(req),
#通过装饰器@wsgi直接变成一个对象,参数在装饰器内部实现wsgify(test)
return test
return app(request)
#app_factory
def show_factory(global_conf,**local_conf):
return application
#app_factory
def version_factory(global_conf,**local_conf):
return ShowVersion(global_conf,local_conf)
#app_fatory
def showauther_factory(global_conf,**local_conf):
return ShowAuther(global_conf,local_conf)
#filter_factory
def filter_factory(global_conf, **local_conf):
return auth_filter
#filter_factory
def log_factory(global_conf,**local_conf):
def filter(app):
return LogFilter(app,global_conf,local_conf)
return filter
class LogFilter():
def __init__(self,app,global_conf,local_conf):
self.app = app
self.global_conf=global_conf
self.local_conf=local_conf
def __call__(self,environ,start_response):
print "filter:LogFilter is called."
req = Request(environ)
if req.GET.get("username", "")==self.local_conf['username'] and req.GET.get("password", "")==self.local_conf['password']:
return self.app(environ,start_response)
start_response("200 OK",[("Content-type", "text/plain")])
return ["You are not authorized"]
class ShowVersion():
def __init__(self,global_conf,local_conf):
self.global_conf=global_conf
self.local_conf=local_conf
def __call__(self,environ,start_response):
start_response("200 OK",[("Content-type", "text/plain")])
return ['Version',self.local_conf['version']]
class ShowAuther():
def __init__(self,global_conf,local_conf):
self.global_conf=global_conf
self.local_conf=local_conf
def __call__(self,environ,start_response):
res = Response()
res.status = "200 OK"
res.content_type = "text/plain"
# get operands
res.body = ["auther",self.local_conf['auther']]
return res
if __name__ == '__main__':
signal.signal(signal.SIGINT, sigint_handler)
configfile="test-deploy.ini"
appname="main"
wsgi_app = loadapp("config:%s" % os.path.abspath(configfile), appname)
server = make_server('localhost',8088,wsgi_app)
server.serve_forever()
pass
但是到openstack这里有一个问题,在keystone处理时,有多个路径,不可能都放在ini里面配置(而且里面也没有),那这么多路径他是怎么实现的呢?
源代码说明,他用了Routes,来实现路径映射功能,官方文档地址是Routes官方文档
先放几段Openstack的关键源代码,他在xxx_factory(都很类似)实现的代码如下
@fail_gracefully
def public_app_factory(global_conf, **local_conf):
controllers.register_version('v2.0')
conf = global_conf.copy()
conf.update(local_conf)
return wsgi.ComposingRouter(routes.Mapper(),
[identity.routers.Public(),
token.routers.Router(),
routers.VersionV2('public'),
routers.Extension(False)])
class ComposingRouter(Router):
def __init__(self, mapper=None, routers=None):
if mapper is None:
mapper = routes.Mapper()
if routers is None:
routers = []
for router in routers:
router.add_routes(mapper)
super(ComposingRouter, self).__init__(mapper)
可见创建了mapper对象,调用[]里面所有对象(identity.routers.Public())下的add_routers方法,再跟进add_routers方法看看,以identity.routers.Public()为例
class Public(wsgi.ComposableRouter):
def add_routes(self, mapper):
tenant_controller = controllers.Tenant()
mapper.connect('/tenants',
controller=tenant_controller,
action='get_projects_for_token',
conditions=dict(method=['GET']))
可见,他用mapper.connect生成了一个路径/tenants,访问方式必须为GET,调用函数为tenant_controller(controller.Tenant()对象)下get_projects_for_token函数。如下
class Tenant(controller.V2Controller):
def get_all_projects(self, context, **kw):
"""Gets a list of all tenants for an admin user."""
if 'name' in context['query_string']:
return self.get_project_by_name(
context, context['query_string'].get('name'))
self.assert_admin(context)
tenant_refs = self.identity_api.list_projects()
for tenant_ref in tenant_refs:
tenant_ref = self.filter_domain_id(tenant_ref)
params = {
'limit': context['query_string'].get('limit'),
'marker': context['query_string'].get('marker'),
}
return self._format_project_list(tenant_refs, **params)
def get_projects_for_token(self, context, **kw):
"""Get valid tenants for token based on token used to authenticate.
Pulls the token from the context, validates it and gets the valid
tenants for the user in the token.
Doesn't care about token scopedness.
"""
try:
token_ref = self.token_api.get_token(context['token_id'])
except exception.NotFound as e:
LOG.warning('Authentication failed: %s' % e)
raise exception.Unauthorized(e)
user_ref = token_ref['user']
tenant_refs = (
self.assignment_api.list_projects_for_user(user_ref['id']))
tenant_refs = [self.filter_domain_id(ref) for ref in tenant_refs
if ref['domain_id'] == DEFAULT_DOMAIN_ID]
params = {
'limit': context['query_string'].get('limit'),
'marker': context['query_string'].get('marker'),
}
return self._format_project_list(tenant_refs, **params)
1.paste_factory的对象不是用webob来封装的HTTP服务吗,我在get_projects_for_token函数下没看见啊?
2.mapper.connect()配置完成后为什么就能实现映射了呢?
问题1.我的理解:
Tenant的基类是V2Controller,V2Controller的基类是class V2Controller(wsgi.Application),而在wsgi.Application类中实现如下
class Application(BaseApplication):
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
arg_dict = req.environ['wsgiorg.routing_args'][1]
action = arg_dict.pop('action')
del arg_dict['controller']
LOG.debug(_('arg_dict: %s'), arg_dict)
# allow middleware up the stack to provide context, params and headers.
context = req.environ.get(CONTEXT_ENV, {})
context['query_string'] = dict(req.params.iteritems())
context['headers'] = dict(req.headers.iteritems())
context['path'] = req.environ['PATH_INFO']
params = req.environ.get(PARAMS_ENV, {})
for name in ['REMOTE_USER', 'AUTH_TYPE']:
try:
context[name] = req.environ[name]
except KeyError:
try:
del context[name]
except KeyError:
pass
params.update(arg_dict)
context.setdefault('is_admin', False)
# TODO(termie): do some basic normalization on methods
method = getattr(self, action)
# NOTE(vish): make sure we have no unicode keys for py2.6.
params = self._normalize_dict(params)
try:
result = method(context, **params)
except exception.Unauthorized as e:
LOG.warning(
_('Authorization failed. %(exception)s from %(remote_addr)s') %
{'exception': e, 'remote_addr': req.environ['REMOTE_ADDR']})
return render_exception(e, user_locale=req.best_match_language())
except exception.Error as e:
LOG.warning(e)
return render_exception(e, user_locale=req.best_match_language())
except TypeError as e:
LOG.exception(e)
return render_exception(exception.ValidationError(e),
user_locale=req.best_match_language())
except Exception as e:
LOG.exception(e)
return render_exception(exception.UnexpectedError(exception=e),
user_locale=req.best_match_language())
if result is None:
return render_response(status=(204, 'No Content'))
elif isinstance(result, basestring):
return result
elif isinstance(result, webob.Response):
return result
elif isinstance(result, webob.exc.WSGIHTTPException):
return result
response_code = self._get_response_code(req)
return render_response(body=result, status=response_code)
终于看到了久违的wsgi了,那我怎么在调用对象时候,调用我需要的函数呢,代码很清楚
method = getattr(self, action)
# NOTE(vish): make sure we have no unicode keys for py2.6.
params = self._normalize_dict(params)
try:
result = method(context, **params)
getattr里action就是我们上面的需要的函数,在这里调用,再通过render_response(body=result, status=response_code),render_response是自己的http返回函数。问题到这里应该能得到解答(?)
问题2,我的理解:
跟到class ComposingRouter(Router)的基类Router
class Router(object):
"""WSGI middleware that maps incoming requests to WSGI apps."""
def __init__(self, mapper):
"""Create a router for the given routes.Mapper.
Each route in `mapper` must specify a 'controller', which is a
WSGI app to call. You'll probably want to specify an 'action' as
well and have your controller be an object that can route
the request to the action-specific method.
Examples:
mapper = routes.Mapper()
sc = ServerController()
# Explicit mapping of one route to a controller+action
mapper.connect(None, '/svrlist', controller=sc, action='list')
# Actions are all implicitly defined
mapper.resource('server', 'servers', controller=sc)
# Pointing to an arbitrary WSGI app. You can specify the
# {path_info:.*} parameter so the target app can be handed just that
# section of the URL.
mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
"""
# if we're only running in debug, bump routes' internal logging up a
# notch, as it's very spammy
if CONF.debug:
logging.getLogger('routes.middleware')
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
self.map)
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
"""Route the incoming request to a controller based on self.map.
If no match, return a 404.
"""
return self._router
@staticmethod
@webob.dec.wsgify(RequestClass=Request)
def _dispatch(req):
"""Dispatch the request to the appropriate controller.
Called by self._router after matching the incoming request to a route
and putting the information into req.environ. Either returns 404
or the routed WSGI app's response.
"""
match = req.environ['wsgiorg.routing_args'][1]
if not match:
return render_exception(
exception.NotFound(_('The resource could not be found.')),
user_locale=req.best_match_language())
app = match['controller']
return app
根据Router基类下routes对象自己的实现机制__dispatch和map实现对请求路径匹配,在找到路径后根据routes机制即可映射wsgi服务(怎么实现的?routes官方文档没有搜索到)。问题2应该能得到解答(?)
疑惑的部分:
1.routes的机制到底怎么实现routes到webob的映射,我是看到一篇别人的博客才找到思路,一会转载到我的收藏夹
地址:http://blog.csdn.net/spch2008/article/details/9005885
bluefire1991
2013/10/29
23:15