参考:http://www.cnblogs.com/sammyliu/p/4272611.html
《OpenStack开源云王者归来》 戢友
WSGI就是web server和应用程序之间的接口定义
1. webob库
webob.dec中的wsgify修饰符将函数(参数request,返回response)封装为wsgi应用,该函数也可以普通调用
@wsgify
def myfunc(req): #也可以修饰类的方法def myfunc(self, req)
if True:
return webob.Response('xxx') #返回webob.Response对象
else:
raise webob.exc.HTTPForbidden #将异常转换为wsgi的response
wsgify的参数:参数RequestClass表示request的类型,它可以是webob.Request的自定义子类;参数args和kwargs不太常用
函数返回值:webob.Response对象;任何wsgi应用;None被转换为req.response;string被转换为带有该string的req.response;raise webob.exc中的异常
2. paste.deploy
只关心配置文件中带有[type:name]类型的section:[app:{name}]定义wsgi应用,[server:{name}]定义wsgi的server,[composite:{name}]将request分配到多个app上,[filter:{name}]定义wsgi过滤器,[DEFAULT]定义默认变量;应用入口是name为main的应用
单个配置文件可以定义多个应用,定义的格式:使用另外一个配置文件use=config:{config_file}#{app_name};使用egg包中的use=egg:{func_name};使用callable对象use=call:{module_name}:{func_name};使用工厂方法paste.app_factory={module_name}:{func_name}。在use之后的键值会作为func的参数
2.1 factory函数
工厂函数的协议:paste.app_factory,paste.composite_factory,paste.filter_factory,paste.server_factory,所有工厂函数返回含有__call__可执行的方法(函数、方法、类)
1)paste.app_factory参数是字典global_config,关键字参数local_config
app_factory(global_config, **local_config),返回函数对象
app(request),返回response
2)paste.composite_factory额外包含参数loader,可以根据name返回一个wsgi应用,例如get_app、get_filter和get_server
def composite_factory(loader, global_config, **local_config): #将filter和app组合起来
pipeline=pipeline.split()
filters=[loader.get_filter(p) for p in pipeline[:-1]] #返回filter应用
app=loader.get_app(pipeline[-1]) #返回app应用
for filter in filters.reverse():
app=filter(app)
return app
3)paste.filter_factor返回的方法带有参数app
filter_factory(global_config, **local_config),返回函数对象
filter(app),返回函数对象
filter_func(request),最后调用request.get_response(app)或直接返回response
def filter_factory(global_config, **local_config):
def filter(app): #返回带有唯一参数app的callable对象
return AuthFilter(app, username) #返回一个被filter的app
return filter
class AuthFilter(object):
def __init__(self, app, username):
self.app=app
self.username=username
def __call__(self, environ, start_response):
if environ.get('REMOTE_USER') == self.username:
return self.app(environ, start_response)
start_response('403 Forbidden', [('Content-type', 'text/html')])
return ['You are forbidden to view this resource']
4)paste.server_factory返回的函数没有返回值
def server_factory(global_conf, host, port):
port=int(port)
def serve(app):
Server(app, host, port).serve_forever()
return serve
2.2 使用方法
from paste import deploy
app = deploy.loadapp('config:{config_name}')
底层使用ConfigParser来解析ini文件,deploy有loadapp、loadfilter和loadserver
loadapp会在ini文件中查找main section,它由name_prefix和name组成,name_prefix由object_type.config_prefixes(list)指定,name默认为main,将里面的options组成local_conf字典,根据name_prefix的不同(pipeline、app或filter)产生不同的LoaderContext类型(PIPELINE,APP或FILTER),其成员loader包含了ini文件的信息
pipeline的LoadContext由filter_contexts和app_context组成;app和filter会从local_conf中找object_type.egg_protocols指定的option
遍历生成的context树,通过LoaderContext的create方法依次初始化app、filter函数对象,其中app函数对象在初始化阶段会对URL做映射,并将映射关系放入routes.Mapper中,filter函数对象会在初始化阶段输入app对象,最后返回filter函数对象
3. routes库
http://www.cnblogs.com/sammyliu/p/4272611.html
routes.Mapper对象用于URL解析,调用connect添加URL到方法实例controller的映射;routes.Mapper有个成员maxkeys字典,将参数类型相同(除action,controller以外,URL中包含的参数)的映射关系放在一起,每个映射关系由一个route.Route表示
routes.middleware.RoutesMiddleware是工厂函数app_factory返回的app,它通过wsgi_app函数获取URL的解析结果,如果结果为空,说明相应的URL没有在mapper对象中注册,如果不为空,则返回URL相应的controller对象,由于controller对象也是可调用的,最终会调用controller对象中的__call__方法处理HTTP请求
request.environ['wsgiorg.routing_args'][1]是个dict,包含URL的参数(action,conditions和用户填入的{instance_id})和URL映射的controller对象
request.params是个dict,包含客户端提交的数据
4. 管理Resource的类
4.1 APIRouter
负责分发HTTP Request到其管理的Resource
* 属性resources是个数组,用来保存所有的Resource的Controller类的instance,Resource是Controller的封装,具体实现在Controller类中
* 属性mapper用来保存所有resource,extension resource,resource extension的routes供RoutesMiddleware使用,它其实是一张路由表,每个表项是URL和controller以及action的映射关系
* 属性routes是routes.middleware.RoutesMiddleware的实例,它是WSGI app,它使用mapper和_dispatch_进行初始化,功能是根据URL得到controller和它的action
4.2 Controller
Controller代表对某个资源的操作集合,每个Resource都有一个相应的Controller类,里面定义了CRUD方法,例如create, show, index, update, delete
4.3 ExtensionManager
这么多资源需要使用一个集中的方式对它们进行管理,统一管理用的就是ExtensionManager,配置文件里xxx_extension配置项告诉ExtensionManager去哪找扩展的类文件
4.4 ProjectMapper
APIRouters使用一个mapper属性来保存为所有resource建立的mapping,继承关系是ProjectMapper->APIMapper->routes.Mapper
service.py
import wsgi
class WSGIService(object):
def __init__(self):
self.loader = wsgi.Loader()
self.app = self.loader.load_app()
self.server = wsgi.Server(self.app,
'0.0.0.0',
8080)
def start(self):
self.server.start()
def wait(self):
self.server.wait()
def stop(self):
self.server.stop()
if __name__ == "__main__":
server = WSGIService()
server.start()
server.wait()
wsgi.py
import eventlet
import eventlet.wsgi
import greenlet
import sys
import os
from paste import deploy
class Loader(object):
def load_app(self):
ini_path = os.path.normpath(
os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
'api-paste.ini')
)
if not os.path.isfile(ini_path):
print("Cannot find api-paste.ini.\n")
exit(1)
return deploy.loadapp('config:' + ini_path)
class Server(object):
def __init__(self, app, host='0.0.0.0', port=0):
self._pool = eventlet.GreenPool(10)
self.app = app
self._socket = eventlet.listen((host, port), backlog=10)
(self.host, self.port) = self._socket.getsockname()
print("Listening on %(host)s:%(port)s" % self.__dict__)
def start(self):
self._server = eventlet.spawn(eventlet.wsgi.server,
self._socket,
self.app,
protocol=eventlet.wsgi.HttpProtocol,
custom_pool=self._pool)
def stop(self):
if self._server is not None:
self._pool.resize(0)
self._server.kill()
def wait(self):
try:
self._server.wait()
except greenlet.GreenletExit:
print("WSGI server has stopped.")
api-paste.ini
[pipeline:main]
pipeline = auth instance
[app:instance]
paste.app_factory = routers:app_factory
[filter:auth]
paste.filter_factory = middleware:Auth.factory
middleware.py
from webob import Response
from webob.dec import wsgify
from webob import exc
import webob
class Auth(object):
def __init__(self, app):
self.app = app
@classmethod
def factory(cls, global_config, **local_config):
def _factory(app):
return cls(app)
return _factory
@wsgify(RequestClass=webob.Request)
def __call__(self, req):
resp = self.process_request(req)
if resp:
return resp
return req.get_response(self.app)
def process_request(self, req):
if req.headers.get('X-Auth-Token') != 'open-sesame':
return exc.HTTPForbidden()
routers.py
import routes
import routes.middleware
import webob
import webob.dec
from webob.dec import wsgify
import controllers
class Router(object):
def __init__(self):
self.mapper = routes.Mapper()
self.add_routes()
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
self.mapper)
def add_routes(self):
controller = controllers.Controller()
self.mapper.connect("/instances",
controller=controller, action="create",
conditions=dict(method=["POST"]))
self.mapper.connect("/instances",
controller=controller, action="index",
conditions=dict(method=["GET"]))
self.mapper.connect("/instances/{instance_id}",
controller=controller, action="show",
conditions=dict(method=["GET"]))
self.mapper.connect("/instances/{instance_id}",
controller=controller, action="update",
conditions=dict(method=["PUT"]))
self.mapper.connect("/instances/{instance_id}",
controller=controller, action="delete",
conditions=dict(method=["DELETE"]))
@wsgify(RequestClass=webob.Request)
def __call__(self, request):
return self._router
@staticmethod
@wsgify(RequestClass=webob.Request)
def _dispatch(request):
match = request.environ['wsgiorg.routing_args'][1]
if not match:
return _err()
app = match['controller']
return app
def _err():
return 'The Resource is Not Found.'
def app_factory(global_config, **local_config):
return Router()
controllers.py
import uuid
import webob
import simplejson
from webob.dec import wsgify
class Controller(object):
def __init__(self):
self.instances = {}
for i in xrange(3):
inst_id = str(uuid.uuid4())
self.instances[inst_id] = {'id': inst_id,
'name': 'inst-' + str(i)}
def create(self, req):
print(req.params)
name = req.params['name']
if name:
inst_id = str(uuid.uuid4())
inst = {'id': inst_id,
'name': name}
self.instances[inst_id] = inst
return {'instance': inst}
def show(self, req, instance_id):
inst = self.instances.get(instance_id)
return {'instance': inst}
def index(self, req):
return {'instances': self.instances.values()}
def delete(self, req, instance_id):
if self.instances.get(instance_id):
self.instances.pop(instance_id)
def update(self, req, instance_id):
inst = self.instances.get(instance_id)
name = req.params['name']
if inst and name:
inst['name'] = name
return {'instance': inst}
@wsgify(RequestClass=webob.Request)
def __call__(self, req):
arg_dict = req.environ['wsgiorg.routing_args'][1]
action = arg_dict.pop('action')
del arg_dict['controller']
method = getattr(self, action)
result = method(req, **arg_dict)
if result is None:
return webob.Response(body='',
status='204 Not Found',
headerlist=[('Content-Type',
'application/json')])
else:
if not isinstance(result, basestring):
result = simplejson.dumps(result)
return result