dsqzhaoyue 发表于 2015-4-26 10:55:52

Python paste模块和routes模块

  在阅读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)
4server = make_server('localhost',80,wsgi_app)
5 server.serve_forever()
  python_paste.ini




use = egg:Paste#urlmap
/:showversion
/log:showversion_log
/v1:apiv1app

pipeline = filter_log showversion

#filter2 deal with time,read args belowmanage
paste.filter_factory = manage:LogFilter.factory

paste.app_factory = v1.router:MyRouterApp.factory

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, result
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']
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'].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也是大同小异。
  
页: [1]
查看完整版本: Python paste模块和routes模块