blob: f655599b28a2a178a4720face5b83adc79e6f684 [file] [log] [blame]
Brad Bishopaa65f6e2015-10-27 16:28:51 -04001#!/usr/bin/env python
2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05003import os
4import sys
Brad Bishopaa65f6e2015-10-27 16:28:51 -04005import dbus
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05006import dbus.exceptions
7import json
8import logging
9from xml.etree import ElementTree
10from rocket import Rocket
11from bottle import Bottle, abort, request, response, JSONPlugin, HTTPError
Brad Bishopaa65f6e2015-10-27 16:28:51 -040012import OpenBMCMapper
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050013from OpenBMCMapper import Mapper, PathTree, IntrospectionNodeParser, ListMatch
Brad Bishopaa65f6e2015-10-27 16:28:51 -040014
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050015DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.UnknownInterface'
16DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
17DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs'
18DELETE_IFACE = 'org.openbmc.object.Delete'
Brad Bishop9ee57c42015-11-03 14:59:29 -050019
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050020_4034_msg = "The specified %s cannot be %s: '%s'"
Brad Bishopaa65f6e2015-10-27 16:28:51 -040021
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050022def find_case_insensitive(value, lst):
23 return next((x for x in lst if x.lower() == value.lower()), None)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040024
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050025def makelist(data):
26 if isinstance(data, list):
27 return data
28 elif data:
29 return [data]
30 else:
31 return []
Brad Bishopaa65f6e2015-10-27 16:28:51 -040032
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050033class RouteHandler(object):
34 def __init__(self, app, bus, verbs, rules):
35 self.app = app
36 self.bus = bus
37 self.mapper = Mapper(bus)
38 self._verbs = makelist(verbs)
39 self._rules = rules
Brad Bishopaa65f6e2015-10-27 16:28:51 -040040
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050041 def _setup(self, **kw):
42 request.route_data = {}
43 if request.method in self._verbs:
44 return self.setup(**kw)
45 else:
46 self.find(**kw)
47 raise HTTPError(405, "Method not allowed.",
48 Allow=','.join(self._verbs))
Brad Bishopaa65f6e2015-10-27 16:28:51 -040049
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050050 def __call__(self, **kw):
51 return getattr(self, 'do_' + request.method.lower())(**kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040052
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050053 def install(self):
54 self.app.route(self._rules, callback = self,
55 method = ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
Brad Bishopaa65f6e2015-10-27 16:28:51 -040056
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050057 @staticmethod
58 def try_mapper_call(f, callback = None, **kw):
Brad Bishopaa65f6e2015-10-27 16:28:51 -040059 try:
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050060 return f(**kw)
61 except dbus.exceptions.DBusException, e:
62 if e.get_dbus_name() != OpenBMCMapper.MAPPER_NOT_FOUND:
63 raise
64 if callback is None:
65 def callback(e, **kw):
66 abort(404, str(e))
Brad Bishopaa65f6e2015-10-27 16:28:51 -040067
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050068 callback(e, **kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040069
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050070 @staticmethod
71 def try_properties_interface(f, *a):
Brad Bishopaa65f6e2015-10-27 16:28:51 -040072 try:
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050073 return f(*a)
74 except dbus.exceptions.DBusException, e:
75 if DBUS_UNKNOWN_INTERFACE in e.get_dbus_message():
76 # interface doesn't have any properties
77 return None
78 if DBUS_UNKNOWN_METHOD == e.get_dbus_name():
79 # properties interface not implemented at all
80 return None
81 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -040082
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050083class DirectoryHandler(RouteHandler):
84 verbs = 'GET'
85 rules = '<path:path>/'
Brad Bishopaa65f6e2015-10-27 16:28:51 -040086
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050087 def __init__(self, app, bus):
88 super(DirectoryHandler, self).__init__(
89 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040090
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050091 def find(self, path = '/'):
92 return self.try_mapper_call(
93 self.mapper.get_subtree_paths,
94 path = path, depth = 1)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040095
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050096 def setup(self, path = '/'):
97 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040098
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050099 def do_get(self, path = '/'):
100 return request.route_data['map']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400101
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500102class ListNamesHandler(RouteHandler):
103 verbs = 'GET'
104 rules = ['/list', '<path:path>/list']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400105
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500106 def __init__(self, app, bus):
107 super(ListNamesHandler, self).__init__(
108 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400109
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500110 def find(self, path = '/'):
111 return self.try_mapper_call(
112 self.mapper.get_subtree, path = path).keys()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400113
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500114 def setup(self, path = '/'):
115 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400116
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500117 def do_get(self, path = '/'):
118 return request.route_data['map']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400119
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500120class ListHandler(RouteHandler):
121 verbs = 'GET'
122 rules = ['/enumerate', '<path:path>/enumerate']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400123
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500124 def __init__(self, app, bus):
125 super(ListHandler, self).__init__(
126 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400127
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500128 def find(self, path = '/'):
129 return self.try_mapper_call(
130 self.mapper.get_subtree, path = path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400131
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500132 def setup(self, path = '/'):
133 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400134
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500135 def do_get(self, path = '/'):
Brad Bishop936f5fe2015-11-03 15:10:11 -0500136 objs = {}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500137 mapper_data = request.route_data['map']
Brad Bishop936f5fe2015-11-03 15:10:11 -0500138 tree = PathTree()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400139 for x,y in mapper_data.iteritems():
Brad Bishop936f5fe2015-11-03 15:10:11 -0500140 tree[x] = y
141
142 try:
143 # Check to see if the root path implements
144 # enumerate in addition to any sub tree
145 # objects.
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500146 root = self.try_mapper_call(self.mapper.get_object,
147 path = path)
148 mapper_data[path] = root
Brad Bishop936f5fe2015-11-03 15:10:11 -0500149 except:
150 pass
151
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500152 have_enumerate = [ (x[0], self.enumerate_capable(*x)) \
153 for x in mapper_data.iteritems() \
154 if self.enumerate_capable(*x) ]
Brad Bishop936f5fe2015-11-03 15:10:11 -0500155
156 for x,y in have_enumerate:
157 objs.update(self.call_enumerate(x, y))
158 tmp = tree[x]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500159 # remove the subtree
Brad Bishop936f5fe2015-11-03 15:10:11 -0500160 del tree[x]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500161 # add the new leaf back since enumerate results don't
162 # include the object enumerate is being invoked on
Brad Bishop936f5fe2015-11-03 15:10:11 -0500163 tree[x] = tmp
164
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500165 # make dbus calls for any remaining objects
Brad Bishop936f5fe2015-11-03 15:10:11 -0500166 for x,y in tree.dataitems():
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500167 objs[x] = self.app.instance_handler.do_get(x)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400168
169 return objs
170
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500171 @staticmethod
172 def enumerate_capable(path, bus_data):
173 busses = []
174 for name, ifaces in bus_data.iteritems():
175 if OpenBMCMapper.ENUMERATE_IFACE in ifaces:
176 busses.append(name)
177 return busses
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400178
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500179 def call_enumerate(self, path, busses):
180 objs = {}
181 for b in busses:
182 obj = self.bus.get_object(b, path, introspect = False)
183 iface = dbus.Interface(obj, OpenBMCMapper.ENUMERATE_IFACE)
184 objs.update(iface.enumerate())
185 return objs
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400186
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500187class MethodHandler(RouteHandler):
188 verbs = 'POST'
189 rules = '<path:path>/action/<method>'
190 request_type = list
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400191
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500192 def __init__(self, app, bus):
193 super(MethodHandler, self).__init__(
194 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400195
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500196 def find(self, path, method):
197 busses = self.try_mapper_call(self.mapper.get_object,
198 path = path)
199 for items in busses.iteritems():
200 m = self.find_method_on_bus(path, method, *items)
201 if m:
202 return m
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400203
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500204 abort(404, _4034_msg %('method', 'found', method))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400205
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500206 def setup(self, path, method):
207 request.route_data['method'] = self.find(path, method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400208
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500209 def do_post(self, path, method):
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400210 try:
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500211 if request.parameter_list:
212 return request.route_data['method'](*request.parameter_list)
213 else:
214 return request.route_data['method']()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400215
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500216 except dbus.exceptions.DBusException, e:
217 if e.get_dbus_name() == DBUS_INVALID_ARGS:
218 abort(400, str(e))
219 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400220
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500221 @staticmethod
222 def find_method_in_interface(method, obj, interface, methods):
223 if methods is None:
224 return None
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400225
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500226 method = find_case_insensitive(method, methods.keys())
227 if method is not None:
228 iface = dbus.Interface(obj, interface)
229 return iface.get_dbus_method(method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400230
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500231 def find_method_on_bus(self, path, method, bus, interfaces):
232 obj = self.bus.get_object(bus, path, introspect = False)
233 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
234 data = iface.Introspect()
235 parser = IntrospectionNodeParser(
236 ElementTree.fromstring(data),
237 intf_match = ListMatch(interfaces))
238 for x,y in parser.get_interfaces().iteritems():
239 m = self.find_method_in_interface(method, obj, x,
240 y.get('method'))
241 if m:
242 return m
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400243
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500244class PropertyHandler(RouteHandler):
245 verbs = ['PUT', 'GET']
246 rules = '<path:path>/attr/<prop>'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400247
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500248 def __init__(self, app, bus):
249 super(PropertyHandler, self).__init__(
250 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400251
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500252 def find(self, path, prop):
253 self.app.instance_handler.setup(path)
254 obj = self.app.instance_handler.do_get(path)
255 try:
256 obj[prop]
257 except KeyError, e:
258 if request.method == 'PUT':
259 abort(403, _4034_msg %('property', 'created', str(e)))
260 else:
261 abort(404, _4034_msg %('property', 'found', str(e)))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400262
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500263 return { path: obj }
264
265 def setup(self, path, prop):
266 request.route_data['obj'] = self.find(path, prop)
267
268 def do_get(self, path, prop):
269 return request.route_data['obj'][path][prop]
270
271 def do_put(self, path, prop, value = None):
272 if value is None:
273 value = request.parameter_list
274
275 prop, iface, properties_iface = self.get_host_interface(
276 path, prop, request.route_data['map'][path])
277 try:
278 properties_iface.Set(iface, prop, value)
279 except ValueError, e:
280 abort(400, str(e))
281 except dbus.exceptions.DBusException, e:
282 if e.get_dbus_name() == DBUS_INVALID_ARGS:
283 abort(403, str(e))
284 raise
285
286 def get_host_interface(self, path, prop, bus_info):
287 for bus, interfaces in bus_info.iteritems():
288 obj = self.bus.get_object(bus, path, introspect = True)
289 properties_iface = dbus.Interface(
290 obj, dbus_interface=dbus.PROPERTIES_IFACE)
291
292 info = self.get_host_interface_on_bus(
293 path, prop, properties_iface,
294 bus, interfaces)
295 if info is not None:
296 prop, iface = info
297 return prop, iface, properties_iface
298
299 def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces):
300 for i in interfaces:
301 properties = self.try_properties_interface(iface.GetAll, i)
302 if properties is None:
303 continue
304 prop = find_case_insensitive(prop, properties.keys())
305 if prop is None:
306 continue
307 return prop, i
308
309class InstanceHandler(RouteHandler):
310 verbs = ['GET', 'PUT', 'DELETE']
311 rules = '<path:path>'
312 request_type = dict
313
314 def __init__(self, app, bus):
315 super(InstanceHandler, self).__init__(
316 app, bus, self.verbs, self.rules)
317
318 def find(self, path, callback = None):
319 return { path: self.try_mapper_call(
320 self.mapper.get_object,
321 callback,
322 path = path) }
323
324 def setup(self, path):
325 callback = None
326 if request.method == 'PUT':
327 def callback(e, **kw):
328 abort(403, _4034_msg %('resource',
329 'created', path))
330
331 if request.route_data.get('map') is None:
332 request.route_data['map'] = self.find(path, callback)
333
334 def do_get(self, path):
335 properties = {}
336 for item in request.route_data['map'][path].iteritems():
337 properties.update(self.get_properties_on_bus(
338 path, *item))
339
340 return properties
341
342 @staticmethod
343 def get_properties_on_iface(properties_iface, iface):
344 properties = InstanceHandler.try_properties_interface(
345 properties_iface.GetAll, iface)
346 if properties is None:
347 return {}
348 return properties
349
350 def get_properties_on_bus(self, path, bus, interfaces):
351 properties = {}
352 obj = self.bus.get_object(bus, path, introspect = False)
353 properties_iface = dbus.Interface(
354 obj, dbus_interface=dbus.PROPERTIES_IFACE)
355 for i in interfaces:
356 properties.update(self.get_properties_on_iface(
357 properties_iface, i))
358
359 return properties
360
361 def do_put(self, path):
362 # make sure all properties exist in the request
363 obj = set(self.do_get(path).keys())
364 req = set(request.parameter_list.keys())
365
366 diff = list(obj.difference(req))
367 if diff:
368 abort(403, _4034_msg %('resource', 'removed',
369 '%s/attr/%s' %(path, diff[0])))
370
371 diff = list(req.difference(obj))
372 if diff:
373 abort(403, _4034_msg %('resource', 'created',
374 '%s/attr/%s' %(path, diff[0])))
375
376 for p,v in request.parameter_list.iteritems():
377 self.app.property_handler.do_put(
378 path, p, v)
379
380 def do_delete(self, path):
381 for bus_info in request.route_data['map'][path].iteritems():
382 if self.bus_missing_delete(path, *bus_info):
383 abort(403, _4034_msg %('resource', 'removed',
384 path))
385
386 for bus in request.route_data['map'][path].iterkeys():
387 self.delete_on_bus(path, bus)
388
389 def bus_missing_delete(self, path, bus, interfaces):
390 return DELETE_IFACE not in interfaces
391
392 def delete_on_bus(self, path, bus):
393 obj = self.bus.get_object(bus, path, introspect = False)
394 delete_iface = dbus.Interface(
395 obj, dbus_interface = DELETE_IFACE)
396 delete_iface.Delete()
397
398class JsonApiRequestPlugin(object):
399 ''' Ensures request content satisfies the OpenBMC json api format. '''
400 name = 'json_api_request'
401 api = 2
402
403 error_str = "Expecting request format { 'data': <value> }, got '%s'"
404 type_error_str = "Unsupported Content-Type: '%s'"
405 json_type = "application/json"
406 request_methods = ['PUT', 'POST', 'PATCH']
407
408 @staticmethod
409 def content_expected():
410 return request.method in JsonApiRequestPlugin.request_methods
411
412 def validate_request(self):
413 if request.content_length > 0 and \
414 request.content_type != self.json_type:
415 abort(415, self.type_error_str %(request.content_type))
416
417 try:
418 request.parameter_list = request.json.get('data')
419 except ValueError, e:
420 abort(400, str(e))
421 except (AttributeError, KeyError, TypeError):
422 abort(400, self.error_str %(request.json))
423
424 def apply(self, callback, route):
425 verbs = getattr(route.get_undecorated_callback(),
426 '_verbs', None)
427 if verbs is None:
428 return callback
429
430 if not set(self.request_methods).intersection(verbs):
431 return callback
432
433 def wrap(*a, **kw):
434 if self.content_expected():
435 self.validate_request()
436 return callback(*a, **kw)
437
438 return wrap
439
440class JsonApiRequestTypePlugin(object):
441 ''' Ensures request content type satisfies the OpenBMC json api format. '''
442 name = 'json_api_method_request'
443 api = 2
444
445 error_str = "Expecting request format { 'data': %s }, got '%s'"
446
447 def apply(self, callback, route):
448 request_type = getattr(route.get_undecorated_callback(),
449 'request_type', None)
450 if request_type is None:
451 return callback
452
453 def validate_request():
454 if not isinstance(request.parameter_list, request_type):
455 abort(400, self.error_str %(str(request_type), request.json))
456
457 def wrap(*a, **kw):
458 if JsonApiRequestPlugin.content_expected():
459 validate_request()
460 return callback(*a, **kw)
461
462 return wrap
463
464class JsonApiResponsePlugin(object):
465 ''' Emits normal responses in the OpenBMC json api format. '''
466 name = 'json_api_response'
467 api = 2
468
469 def apply(self, callback, route):
470 def wrap(*a, **kw):
471 resp = { 'data': callback(*a, **kw) }
472 resp['status'] = 'ok'
473 resp['message'] = response.status_line
474 return resp
475 return wrap
476
477class JsonApiErrorsPlugin(object):
478 ''' Emits error responses in the OpenBMC json api format. '''
479 name = 'json_api_errors'
480 api = 2
481
482 def __init__(self, **kw):
483 self.app = None
484 self.function_type = None
485 self.original = None
486 self.json_opts = { x:y for x,y in kw.iteritems() \
487 if x in ['indent','sort_keys'] }
488
489 def setup(self, app):
490 self.app = app
491 self.function_type = type(app.default_error_handler)
492 self.original = app.default_error_handler
493 self.app.default_error_handler = self.function_type(
494 self.json_errors, app, Bottle)
495
496 def apply(self, callback, route):
497 return callback
498
499 def close(self):
500 self.app.default_error_handler = self.function_type(
501 self.original, self.app, Bottle)
502
503 def json_errors(self, res, error):
504 response_object = {'status': 'error', 'data': {} }
505 response_object['message'] = error.status_line
506 response_object['data']['description'] = str(error.body)
507 if error.status_code == 500:
508 response_object['data']['exception'] = repr(error.exception)
509 response_object['data']['traceback'] = error.traceback.splitlines()
510
511 json_response = json.dumps(response_object, **self.json_opts)
512 res.content_type = 'application/json'
513 return json_response
514
515class RestApp(Bottle):
516 def __init__(self, bus):
517 super(RestApp, self).__init__(autojson = False)
Brad Bishop53fd4932015-10-30 09:22:30 -0400518 self.bus = bus
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500519 self.mapper = Mapper(bus)
520
521 self.install_hooks()
522 self.install_plugins()
523 self.create_handlers()
524 self.install_handlers()
525
526 def install_plugins(self):
527 # install json api plugins
528 json_kw = {'indent': 2, 'sort_keys': True}
529 self.install(JSONPlugin(**json_kw))
530 self.install(JsonApiErrorsPlugin(**json_kw))
531 self.install(JsonApiResponsePlugin())
532 self.install(JsonApiRequestPlugin())
533 self.install(JsonApiRequestTypePlugin())
534
535 def install_hooks(self):
536 self.real_router_match = self.router.match
537 self.router.match = self.custom_router_match
538 self.add_hook('before_request', self.strip_extra_slashes)
539
540 def create_handlers(self):
541 # create route handlers
542 self.directory_handler = DirectoryHandler(self, self.bus)
543 self.list_names_handler = ListNamesHandler(self, self.bus)
544 self.list_handler = ListHandler(self, self.bus)
545 self.method_handler = MethodHandler(self, self.bus)
546 self.property_handler = PropertyHandler(self, self.bus)
547 self.instance_handler = InstanceHandler(self, self.bus)
548
549 def install_handlers(self):
550 self.directory_handler.install()
551 self.list_names_handler.install()
552 self.list_handler.install()
553 self.method_handler.install()
554 self.property_handler.install()
555 # this has to come last, since it matches everything
556 self.instance_handler.install()
557
558 def custom_router_match(self, environ):
559 ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is
560 needed doesn't work for us since the instance rules match everything.
561 This monkey-patch lets the route handler figure out which response is
562 needed. This could be accomplished with a hook but that would require
563 calling the router match function twice.
564 '''
565 route, args = self.real_router_match(environ)
566 if isinstance(route.callback, RouteHandler):
567 route.callback._setup(**args)
568
569 return route, args
570
571 @staticmethod
572 def strip_extra_slashes():
573 path = request.environ['PATH_INFO']
574 trailing = ("","/")[path[-1] == '/']
575 parts = filter(bool, path.split('/'))
576 request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400577
578if __name__ == '__main__':
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500579 log = logging.getLogger('Rocket.Errors')
580 log.setLevel(logging.INFO)
581 log.addHandler(logging.StreamHandler(sys.stdout))
582
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400583 bus = dbus.SystemBus()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500584 app = RestApp(bus)
585 default_cert = os.path.join(sys.prefix, 'share',
586 os.path.basename(__file__), 'cert.pem')
587
588 server = Rocket(('0.0.0.0',
589 443,
590 default_cert,
591 default_cert),
592 'wsgi', {'wsgi_app': app})
593 server.start()