博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Python paste模块和routes模块

Posted on 2014-03-19 17:43  Beat-Cat  阅读(10328)  评论(0编辑  收藏  举报

  在阅读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也是大同小异。