在阅读openstack源码时,发现其各个组件基本都是使用paste模块和routes模块来构建server以及处理路由信息的,为了熟悉这些模块决定使用这些模块写一个简单的server,此server的构架和glance组件的设计保持一致。
首先使用paste创建一个app,然后wsgiref.simple_server启动他,至于app的功能通过配置文件来决定
1 config = "python_paste.ini" 2 appname = "common" 3 wsgi_app = loadapp("config:%s" % os.path.abspath(config), appname) 4 server = make_server(‘localhost‘,80,wsgi_app) 5 server.serve_forever()
python_paste.ini
[composite:common] use = egg:Paste#urlmap /:showversion /log:showversion_log /v1:apiv1app [pipeline:showversion_log] pipeline = filter_log showversion [filter:filter_log ] #filter2 deal with time,read args belowmanage paste.filter_factory = manage:LogFilter.factory [app:apiv1app] paste.app_factory = v1.router:MyRouterApp.factory [app:showversion] version = 1.0.0 paste.app_factory = manage:ShowVersion.factory
/:showversion:打印版本号 /log:showversion_log:打印版本号的同时做相应的日志记录 /v1:apiv1app:模仿glance中的多版本api,将所有v1的请求转发apiv1app处理。
1 class ShowVersion(object): 2 ‘‘‘ 3 app 4 ‘‘‘ 5 def __init__(self,version): 6 self.version = version 7 def __call__(self,environ,start_response): 8 res = Response() 9 res.status = ‘200 OK‘ 10 res.content_type = "text/plain" 11 content = [] 12 content.append("%s\n" % self.version) 13 res.body = ‘\n‘.join(content) 14 return res(environ,start_response) 15 @classmethod 16 def factory(cls,global_conf,**kwargs): 17 print ‘factory‘ 18 print "kwargs:",kwargs 19 return ShowVersion(kwargs[‘version‘])
1 class LogFilter(object): 2 ‘‘‘ 3 Log 4 ‘‘‘ 5 def __init__(self,app): 6 self.app = app 7 def __call__(self,environ,start_response): 8 print ‘you can write log.‘ 9 return self.app(environ,start_response) 10 @classmethod 11 def factory(cls,global_conf,**kwargs): 12 return LogFilter
到此uri为/和/log时,均可得到服务器的正常响应。但是/v1还没有实现。不得不佩服python的强大,这么几行就实现了一个web服务器的基本功能,下面在介绍routes,配合routes就能够实现更多的更优雅的uri路由机制。
注:此处对paste的配置文件介绍不多,大家去paste官网查看即可,写的很详细了,这个也不是很难,没必要去一一介绍。
新建一个python模块v1,在v1里面新建两个文件router.py和wsgi.py,wsgi.py是一个通用文件,和业务无关,我们只需要关注router.py的实现即可,使用起来非常简单方便。
router.py:
1 import wsgi 2 3 class ControllerTest(object): 4 def __init__(self): 5 print "ControllerTest!!!!" 6 def test(self,req): 7 print "req",req 8 return { 9 ‘name‘: "test", 10 ‘properties‘: "test" 11 } 12 13 class MyRouterApp(wsgi.Router): 14 ‘‘‘ 15 app 16 ‘‘‘ 17 def __init__(self,mapper): 18 controller = ControllerTest() 19 mapper.connect(‘/test‘, 20 controller=wsgi.Resource(controller), 21 action=‘test‘, 22 conditions={‘method‘: [‘GET‘]}) 23 super(MyRouterApp, self).__init__(mapper)
wsgi.py:
1 import datetime 2 import json 3 import routes 4 import routes.middleware 5 import webob 6 import webob.dec 7 import webob.exc 8 9 class APIMapper(routes.Mapper): 10 """ 11 Handle route matching when url is ‘‘ because routes.Mapper returns 12 an error in this case. 13 """ 14 15 def routematch(self, url=None, environ=None): 16 if url is "": 17 result = self._match("", environ) 18 return result[0], result[1] 19 return routes.Mapper.routematch(self, url, environ) 20 21 class Router(object): 22 def __init__(self, mapper): 23 mapper.redirect("", "/") 24 self.map = mapper 25 self._router = routes.middleware.RoutesMiddleware(self._dispatch, 26 self.map) 27 28 @classmethod 29 def factory(cls, global_conf, **local_conf): 30 return cls(APIMapper()) 31 32 @webob.dec.wsgify 33 def __call__(self, req): 34 """ 35 Route the incoming request to a controller based on self.map. 36 If no match, return a 404. 37 """ 38 return self._router 39 40 @staticmethod 41 @webob.dec.wsgify 42 def _dispatch(req): 43 """ 44 Called by self._router after matching the incoming request to a route 45 and putting the information into req.environ. Either returns 404 46 or the routed WSGI app‘s response. 47 """ 48 match = req.environ[‘wsgiorg.routing_args‘][1] 49 if not match: 50 return webob.exc.HTTPNotFound() 51 app = match[‘controller‘] 52 return app 53 54 class Request(webob.Request): 55 """Add some Openstack API-specific logic to the base webob.Request.""" 56 57 def best_match_content_type(self): 58 """Determine the requested response content-type.""" 59 supported = (‘application/json‘,) 60 bm = self.accept.best_match(supported) 61 return bm or ‘application/json‘ 62 63 def get_content_type(self, allowed_content_types): 64 """Determine content type of the request body.""" 65 if "Content-Type" not in self.headers: 66 return 67 68 content_type = self.content_type 69 70 if content_type not in allowed_content_types: 71 return 72 else: 73 return content_type 74 75 class JSONRequestDeserializer(object): 76 def has_body(self, request): 77 """ 78 Returns whether a Webob.Request object will possess an entity body. 79 80 :param request: Webob.Request object 81 """ 82 if ‘transfer-encoding‘ in request.headers: 83 return True 84 elif request.content_length > 0: 85 return True 86 87 return False 88 89 def _sanitizer(self, obj): 90 """Sanitizer method that will be passed to json.loads.""" 91 return obj 92 93 def from_json(self, datastring): 94 try: 95 return json.loads(datastring, object_hook=self._sanitizer) 96 except ValueError: 97 msg = _(‘Malformed JSON in request body.‘) 98 raise webob.exc.HTTPBadRequest(explanation=msg) 99 100 def default(self, request): 101 if self.has_body(request): 102 return {‘body‘: self.from_json(request.body)} 103 else: 104 return {} 105 106 class JSONResponseSerializer(object): 107 108 def _sanitizer(self, obj): 109 """Sanitizer method that will be passed to json.dumps.""" 110 if isinstance(obj, datetime.datetime): 111 return obj.isoformat() 112 if hasattr(obj, "to_dict"): 113 return obj.to_dict() 114 return obj 115 116 def to_json(self, data): 117 return json.dumps(data, default=self._sanitizer) 118 119 def default(self, response, result): 120 response.content_type = ‘application/json‘ 121 response.body = self.to_json(result) 122 123 class Resource(object): 124 def __init__(self, controller, deserializer=None, serializer=None): 125 self.controller = controller 126 self.serializer = serializer or JSONResponseSerializer() 127 self.deserializer = deserializer or JSONRequestDeserializer() 128 129 @webob.dec.wsgify(RequestClass=Request) 130 def __call__(self, request): 131 """WSGI method that controls (de)serialization and method dispatch.""" 132 action_args = self.get_action_args(request.environ) 133 action = action_args.pop(‘action‘, None) 134 135 deserialized_request = self.dispatch(self.deserializer, 136 action, request) 137 action_args.update(deserialized_request) 138 139 action_result = self.dispatch(self.controller, action, 140 request, **action_args) 141 try: 142 response = webob.Response(request=request) 143 self.dispatch(self.serializer, action, response, action_result) 144 return response 145 146 except webob.exc.HTTPException as e: 147 return e 148 # return unserializable result (typically a webob exc) 149 except Exception: 150 return action_result 151 152 def dispatch(self, obj, action, *args, **kwargs): 153 """Find action-specific method on self and call it.""" 154 try: 155 method = getattr(obj, action) 156 except AttributeError: 157 method = getattr(obj, ‘default‘) 158 159 return method(*args, **kwargs) 160 161 def get_action_args(self, request_environment): 162 """Parse dictionary created by routes library.""" 163 try: 164 args = request_environment[‘wsgiorg.routing_args‘][1].copy() 165 except Exception: 166 return {} 167 168 try: 169 del args[‘controller‘] 170 except KeyError: 171 pass 172 173 try: 174 del args[‘format‘] 175 except KeyError: 176 pass 177 178 return args
我们使用 mapper.connect 接口创建路由信息:
/test :uri
controller:控制器对象
action:控制器对象中的方法,即在请求uri时最终执行的方法
contitions:请求类型
1 mapper.connect(‘/test‘, 2 controller=wsgi.Resource(controller), 3 action=‘test‘, 4 conditions={‘method‘: [‘GET‘]})
在这个例子中,当我们执行/v1/test 即可得到如下回复:
{
‘name‘: "test",
‘properties‘:
"test"
}
本文介绍的server框架和glance一模一样,和keystone也是大同小异。