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