一、概述
- RESTful API: 表征状态迁移,也就是说client端使用http的基本操作(主要四种:get, post, put, delete 对应增删改查)使服务端的资源状态转化;
- WSGI: web server gateway interface web服务网关接口,可以看作一个桥梁,一端连这服务端(wsgi server), 一端连接应用程序(wsgi app),桥体(wsgi middleware),也就是说wsgi server直接处理client端的http请求,将请求内容转译为应用app能够处理的对象;
api服务是入口,主要把client端发送http请求映射到具体处理函数上,主要涉及三个模块:
- paste.deploy: 构建openstack的wsgi服务, /etc下都有对应项目的paste的文件,nova为例: /etc/nova/api-paste.ini, paste.deploy构建wsgi服务就是基于该配置文件
- webob: 对wsgi的请求和响应进行封装(将wsgi与应用app的信息进行处理,使用端更方便进行处理,可以放入wsgi模块内理解)
- routes: 定义url到内部函数的映射;
二、python route 深入
route 可以从url提取相应的参数,如controller,action或者其它用户自己定义的变量
1 #routes中的Mapper 2 #Mapper().connect Mapper().match 3 4 from routes import Mapper 5 map = Mapper() 6 map.connect(None,"error/{action}/{id}",controller="controller_obj") #定义匹配规则 7 result = map.match('error/myapp/4') #匹配url='error/myapp/4' 8 #result 匹配结果 9 {'action': u'myapp', 'controller': u'controller_obj', 'id': u'4'} 10 map.connect(None,"/message/:name",controller='my_contro') #除 {} 外,:也可以作为匹配符号 11 result = map.match('/message/12') 12 #result 匹配结果 13 {'controller': u'my_contro', 'name': u'12'}
openstack中的nova-api分析:
1 #!/usr/bin/env/python 2 #coding=utf-8 3 from routes import Mapper 4 from routes import middleware 5 import webob.dec 6 from wsgiref.simple_server import make_server 7 8 class controller(object): 9 def __init__(self): 10 self.i = 1 11 def __call__(self): 12 print self.i 13 def search(self): 14 return "do search()" 15 def show(self): 16 return "do show()" 17 def index(self): 18 return "do index()" 19 def update(self): 20 return "do update()" 21 def delete(self): 22 return "do delete()" 23 def create(self): 24 return "do create()" 25 def create_many(self): 26 return "do create_many()" 27 def update_many(self): 28 return "do update_many()" 29 def list_many(self): 30 return "do list_many()" 31 def delete_many(self): 32 return "do delete_many()" 33 34 class appclass(object): 35 def __init__(self): 36 a = controller() 37 map = Mapper() 38 """路由匹配条件1""" 39 #curl -X GET http://localhost:8088/images 40 #匹配条件指定了curl的动作为GET ,访问路径为images 对应的action 为search 41 42 #map.connect('/images',controller=a, 43 # action='search', 44 # conditions={'method':['GET']}) 45 46 """路由匹配条件2""" 47 #curl -X GET http://localhost:8088/show/hihi 48 #curl -X POST http://lcoalhost:8088/failfunc/test 49 #匹配条件没有指定curl的动作,即action参数, 50 #因此所有的动作(PUT, POST, GET, ...)都匹配 51 #第二个curl请求,匹配的action为failfunc, pid为test;但是 52 #没有定义failfunc函数,报错 53 #map.connect('name',"/{action}/{pid}",controller=a) 54 55 """路由匹配条件3""" 56 #map.resource内部定义了默认的匹配条件 57 #第一个参数message为 member_name(资源名), 58 #第二个参数messages为collection_name(资源集合名),一般定义资源集合名为资源名的复数,我这里随便取名 59 #collection_name作为访问的路径名,且当没有传入参数controller时,controller=collection_name 60 #该匹配相当于: 61 # map.connect('/messages',controller=a,action='index',conditions={'method':['GET']}) 62 # map.connect('/messages',controller=a,action='create',conditions={'method':['POST']}) 63 # map.connect('/messages/{id}',controller=a,action='show',conditions={'method':['GET']}) 64 # map.connect('/messages/{id}',controller=a,action='update',conditions={'method':['PUT']}) 65 # map.connect('/messages/{id}',controller=a,action='delete',conditions={'method':['DELETE']}) 66 #前两条是针对整个资源集合的操作,后三条是针对资源集合中某个固定资源的操作 67 68 #map.resource("message","messages",controller=a,collection={'search':'GET'}) 69 70 """路由匹配条件4""" 71 """ 72 map.resource除了默认的路由条件外,还可以额外的 '定义资源集合的方法',以及'单个资源的方法' 73 collection={'search':'GET','create_many':'POST'} 定义了资源集合方法 search, 74 其curl动作为GET,create_many,其curl动作为POST 75 member={'update_many':'POST','delete_many':'POST'} 定义了单个资源方法update_many, 76 其curl动作为POST, 其curl动作为POST 77 """ 78 #map.resource('message', 'messages',controller=a, 79 #collection={'list_many':'GET','create_many':'POST'}, 80 #member={'update_many':'POST','delete_many':'POST'}) 81 82 """路由匹配条件5""" 83 """ 84 map.resource初始化时还可以指定curl访问路径的前缀路径,如匹配条件3及4没有指定时,默认为 85 collection_name(资源集合名) 86 """ 87 map.resource('message', 'messages',controller=a,path_prefix='/{projectid}', 88 collection={'list_many':'GET','create_many':'POST'}, 89 member={'update_many':'POST','delete_many':'POST'}) 90 self.route = middleware.RoutesMiddleware(self.dispatch,map) 91 @webob.dec.wsgify 92 def __call__(self,req): 93 return self.route 94 95 @staticmethod 96 @webob.dec.wsgify 97 def dispatch(req): 98 match = req.environ['wsgiorg.routing_args'][1] 99 print "route match result is:",match 100 if not match: 101 return "fake url" 102 103 controller = match['controller'] 104 action = match['action'] 105 if hasattr(controller,action): 106 func = getattr(controller,action) 107 ret = func() 108 return ret 109 else: 110 return "has no action:%s" %action 111 112 113 if __name__=="__main__": 114 app = appclass() 115 server = make_server('',8088,app) 116 server.serve_forever()
分析:
1)webob.dec.wsgify是webob为WSGI应用程序提供的一个装饰器,作用是将一个函数转换成一个WSGI应用。
参考资料 http://tumblr.wachang.NET/post/38149417931/Python-paste-webob-3
2)routes.middleware.RoutesMiddleware,将接受到的url,自动调用map.match()方法,将url进行路由匹配并将结果存入request请求的环境变量['wsgiorg.routing_args'],最后会调用其第一个参数给出的函数接口,即self.dispatch。
参考资料 http://blog.csdn.Net/networm3/article/details/8666150
3)map.connect 及map.resource均用来建立路由匹配条件
针对程序中的匹配条件1
map.connect('/images',controller=a,action='search',conditions={'method':['GET']})
curl | 路由匹配结果 (程序中的route match result is) | curl请求得到的结果 |
curl -X GET http://localhost:8088/images | {'action': u'search', 'controller': <__main__.controller object at 0x10c2b10>} | "do search()" |
匹配条件指定了curl的动作为GET ,访问路径为images 对应的action 为search
匹配条件2
map.connect('name',"/{action}/{pid}",controller=a)
curl | 路由匹配结果 (程序中的route match result is) | curl请求得到的结果 |
curl -X GET http://localhost:8088/show/hihi | {'action': u'show', 'controller': <__main__.controller object at 0x2203b10>, 'pid': u'hihi'} | "do show()" |
curl -X POST http://localhost:8088/failfunc/test | {'action': u'failfunc', 'controller': <__main__.controller object at 0x2203b10>, 'pid': u'test'} | "has no action:failfunc" |
匹配条件没有指定curl的动作,因此所有的动作(PUT,POST,GET,。。)都匹配,第二个curl请求,匹配的action 为failfunc,pid为test,但是程序没有定义failfunc函数,报错
匹配条件3
map.resource("message","messages",controller=a) ,map.resource内部定义了默认的匹配条件
第一个参数message为 member_name(资源名),第二个参数messages为collection_name(资源集合名),一般定义资源集合名为资源名的复数,我这里随便取名
collection_name作为访问的路径名,且当没有传入参数controller时,controller=collection_name
map.resource("message","messages",controller=a) 等同于以下匹配条件:
map.connect('/messages',controller=a,action='index',conditions={'method':['GET']})
map.connect('/messages',controller=a,action='create',conditions={'method':['POST']})
map.connect('/messages/{id}',controller=a,action='show',conditions={'method':['GET']})
map.connect('/messages/{id}',controller=a,action='update',conditions={'method':['PUT']})
map.connect('/messages/{id}',controller=a,action='delete',conditions={'method':['DELETE']})
前两条是针对整个资源集合的操作,后三条是针对资源集合中某个固定资源的操作
这里匹配结果中的id为某个具体资源id,这里乱取,后三条curl针对具体资源(id为12)的操作,前两条是针对整个资源集合的操作
当url传入的id包含'.',会将'.'后的字符窜匹配为format,如输入的id 为 '12.hihi' ,匹配id='12', format='hihi'
匹配条件4
map.resource('message', 'messages',controller=a,
collection={'search':'GET','create_many':'POST'},
member={'update_many':'POST','delete_many':'POST'})
map.resource除了默认的路由条件外,还可以额外的定义‘资源集合的方法’以及‘单个资源的方法’
collection={'search':'GET','create_many':'POST'} 定义了资源集合方法 search,其curl动作为GET,create_many,其curl动作为POST
member={'update_many':'POST','delete_many':'POST'} 定义了单个资源方法 update_many,其curl动作为POST,delete_many,其curl动作为POST
匹配条件5
map.resource('message', 'messages',controller=a,path_prefix='/{projectid}',
collection={'list_many':'GET','create_many':'POST'},
member={'update_many':'POST','delete_many':'POST'})
map.resource初始化时还可以指定curl访问路径的前缀路径,如匹配条件3及4没有指定时,默认为collection_name(资源集合名)
指定path_prefix后,路径为path_prefix/collection_name
在路由5的条件下,添加一条
map.resource('type', 'types',controller=other_controller,
parent_resource=dict(member_name='message',
collection_name='messages'),
path_prefix = '{projectid}/%s/:%s_id' %('nex','nexs'))
curl -X POST http://localhost:8088/proj1/nex/17/types
匹配nexs_id 为17,controller 为other_controller, parent_resource的作用为形成name_prefix = 'message_',具体作用不详,有待研究
参考资料:http://routes.readthedocs.org/en/latest/restful.html
疑问:
resource中的controller对象的类定义必须要有__call__,要不然,匹配后变为type(controller)为unicode,原因不明,有待研究