| #!/usr/bin/env python | 
 |  | 
 | import os | 
 | import sys | 
 | import dbus | 
 | import dbus.exceptions | 
 | import json | 
 | import logging | 
 | from xml.etree import ElementTree | 
 | from rocket import Rocket | 
 | from bottle import Bottle, abort, request, response, JSONPlugin, HTTPError | 
 | import OpenBMCMapper | 
 | from OpenBMCMapper import Mapper, PathTree, IntrospectionNodeParser, ListMatch | 
 |  | 
 | DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.UnknownInterface' | 
 | DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' | 
 | DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs' | 
 | DELETE_IFACE = 'org.openbmc.object.Delete' | 
 |  | 
 | _4034_msg = "The specified %s cannot be %s: '%s'" | 
 |  | 
 | def find_case_insensitive(value, lst): | 
 | 	return next((x for x in lst if x.lower() == value.lower()), None) | 
 |  | 
 | def makelist(data): | 
 | 	if isinstance(data, list): | 
 | 		return data | 
 | 	elif data: | 
 | 		return [data] | 
 | 	else: | 
 | 		return [] | 
 |  | 
 | class RouteHandler(object): | 
 | 	def __init__(self, app, bus, verbs, rules): | 
 | 		self.app = app | 
 | 		self.bus = bus | 
 | 		self.mapper = Mapper(bus) | 
 | 		self._verbs = makelist(verbs) | 
 | 		self._rules = rules | 
 |  | 
 | 	def _setup(self, **kw): | 
 | 		request.route_data = {} | 
 | 		if request.method in self._verbs: | 
 | 			return self.setup(**kw) | 
 | 		else: | 
 | 			self.find(**kw) | 
 | 			raise HTTPError(405, "Method not allowed.", | 
 | 					Allow=','.join(self._verbs)) | 
 |  | 
 | 	def __call__(self, **kw): | 
 | 		return getattr(self, 'do_' + request.method.lower())(**kw) | 
 |  | 
 | 	def install(self): | 
 | 		self.app.route(self._rules, callback = self, | 
 | 				method = ['GET', 'PUT', 'PATCH', 'POST', 'DELETE']) | 
 |  | 
 | 	@staticmethod | 
 | 	def try_mapper_call(f, callback = None, **kw): | 
 | 		try: | 
 | 			return f(**kw) | 
 | 		except dbus.exceptions.DBusException, e: | 
 | 			if e.get_dbus_name() != OpenBMCMapper.MAPPER_NOT_FOUND: | 
 | 				raise | 
 | 			if callback is None: | 
 | 				def callback(e, **kw): | 
 | 					abort(404, str(e)) | 
 |  | 
 | 			callback(e, **kw) | 
 |  | 
 | 	@staticmethod | 
 | 	def try_properties_interface(f, *a): | 
 | 		try: | 
 | 			return f(*a) | 
 | 		except dbus.exceptions.DBusException, e: | 
 | 			if DBUS_UNKNOWN_INTERFACE in e.get_dbus_message(): | 
 | 				# interface doesn't have any properties | 
 | 				return None | 
 | 			if DBUS_UNKNOWN_METHOD == e.get_dbus_name(): | 
 | 				# properties interface not implemented at all | 
 | 				return None | 
 | 			raise | 
 |  | 
 | class DirectoryHandler(RouteHandler): | 
 | 	verbs = 'GET' | 
 | 	rules = '<path:path>/' | 
 |  | 
 | 	def __init__(self, app, bus): | 
 | 		super(DirectoryHandler, self).__init__( | 
 | 				app, bus, self.verbs, self.rules) | 
 |  | 
 | 	def find(self, path = '/'): | 
 | 		return self.try_mapper_call( | 
 | 				self.mapper.get_subtree_paths, | 
 | 				path = path, depth = 1) | 
 |  | 
 | 	def setup(self, path = '/'): | 
 | 		request.route_data['map'] = self.find(path) | 
 |  | 
 | 	def do_get(self, path = '/'): | 
 | 		return request.route_data['map'] | 
 |  | 
 | class ListNamesHandler(RouteHandler): | 
 | 	verbs = 'GET' | 
 | 	rules = ['/list', '<path:path>/list'] | 
 |  | 
 | 	def __init__(self, app, bus): | 
 | 		super(ListNamesHandler, self).__init__( | 
 | 				app, bus, self.verbs, self.rules) | 
 |  | 
 | 	def find(self, path = '/'): | 
 | 		return self.try_mapper_call( | 
 | 				self.mapper.get_subtree, path = path).keys() | 
 |  | 
 | 	def setup(self, path = '/'): | 
 | 		request.route_data['map'] = self.find(path) | 
 |  | 
 | 	def do_get(self, path = '/'): | 
 | 		return request.route_data['map'] | 
 |  | 
 | class ListHandler(RouteHandler): | 
 | 	verbs = 'GET' | 
 | 	rules = ['/enumerate', '<path:path>/enumerate'] | 
 |  | 
 | 	def __init__(self, app, bus): | 
 | 		super(ListHandler, self).__init__( | 
 | 				app, bus, self.verbs, self.rules) | 
 |  | 
 | 	def find(self, path = '/'): | 
 | 		return self.try_mapper_call( | 
 | 				self.mapper.get_subtree, path = path) | 
 |  | 
 | 	def setup(self, path = '/'): | 
 | 		request.route_data['map'] = self.find(path) | 
 |  | 
 | 	def do_get(self, path = '/'): | 
 | 		objs = {} | 
 | 		mapper_data = request.route_data['map'] | 
 | 		tree = PathTree() | 
 | 		for x,y in mapper_data.iteritems(): | 
 | 			tree[x] = y | 
 |  | 
 | 		try: | 
 | 			# Check to see if the root path implements | 
 | 			# enumerate in addition to any sub tree | 
 | 			# objects. | 
 | 			root = self.try_mapper_call(self.mapper.get_object, | 
 | 					path = path) | 
 | 			mapper_data[path] = root | 
 | 		except: | 
 | 			pass | 
 |  | 
 | 		have_enumerate = [ (x[0], self.enumerate_capable(*x)) \ | 
 | 				for x in mapper_data.iteritems() \ | 
 | 					if self.enumerate_capable(*x) ] | 
 |  | 
 | 		for x,y in have_enumerate: | 
 | 			objs.update(self.call_enumerate(x, y)) | 
 | 			tmp = tree[x] | 
 | 			# remove the subtree | 
 | 			del tree[x] | 
 | 			# add the new leaf back since enumerate results don't | 
 | 			# include the object enumerate is being invoked on | 
 | 			tree[x] = tmp | 
 |  | 
 | 		# make dbus calls for any remaining objects | 
 | 		for x,y in tree.dataitems(): | 
 | 			objs[x] = self.app.instance_handler.do_get(x) | 
 |  | 
 | 		return objs | 
 |  | 
 | 	@staticmethod | 
 | 	def enumerate_capable(path, bus_data): | 
 | 		busses = [] | 
 | 		for name, ifaces in bus_data.iteritems(): | 
 | 			if OpenBMCMapper.ENUMERATE_IFACE in ifaces: | 
 | 				busses.append(name) | 
 | 		return busses | 
 |  | 
 | 	def call_enumerate(self, path, busses): | 
 | 		objs = {} | 
 | 		for b in busses: | 
 | 			obj = self.bus.get_object(b, path, introspect = False) | 
 | 			iface = dbus.Interface(obj, OpenBMCMapper.ENUMERATE_IFACE) | 
 | 			objs.update(iface.enumerate()) | 
 | 		return objs | 
 |  | 
 | class MethodHandler(RouteHandler): | 
 | 	verbs = 'POST' | 
 | 	rules = '<path:path>/action/<method>' | 
 | 	request_type = list | 
 |  | 
 | 	def __init__(self, app, bus): | 
 | 		super(MethodHandler, self).__init__( | 
 | 				app, bus, self.verbs, self.rules) | 
 |  | 
 | 	def find(self, path, method): | 
 | 		busses = self.try_mapper_call(self.mapper.get_object, | 
 | 				path = path) | 
 | 		for items in busses.iteritems(): | 
 | 			m = self.find_method_on_bus(path, method, *items) | 
 | 			if m: | 
 | 				return m | 
 |  | 
 | 		abort(404, _4034_msg %('method', 'found', method)) | 
 |  | 
 | 	def setup(self, path, method): | 
 | 		request.route_data['method'] = self.find(path, method) | 
 |  | 
 | 	def do_post(self, path, method): | 
 | 		try: | 
 | 			if request.parameter_list: | 
 | 				return request.route_data['method'](*request.parameter_list) | 
 | 			else: | 
 | 				return request.route_data['method']() | 
 |  | 
 | 		except dbus.exceptions.DBusException, e: | 
 | 			if e.get_dbus_name() == DBUS_INVALID_ARGS: | 
 | 				abort(400, str(e)) | 
 | 			raise | 
 |  | 
 | 	@staticmethod | 
 | 	def find_method_in_interface(method, obj, interface, methods): | 
 | 		if methods is None: | 
 | 			return None | 
 |  | 
 | 		method = find_case_insensitive(method, methods.keys()) | 
 | 		if method is not None: | 
 | 			iface = dbus.Interface(obj, interface) | 
 | 			return iface.get_dbus_method(method) | 
 |  | 
 | 	def find_method_on_bus(self, path, method, bus, interfaces): | 
 | 		obj = self.bus.get_object(bus, path, introspect = False) | 
 | 		iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE) | 
 | 		data = iface.Introspect() | 
 | 		parser = IntrospectionNodeParser( | 
 | 				ElementTree.fromstring(data), | 
 | 				intf_match = ListMatch(interfaces)) | 
 | 		for x,y in parser.get_interfaces().iteritems(): | 
 | 			m = self.find_method_in_interface(method, obj, x, | 
 | 					y.get('method')) | 
 | 			if m: | 
 | 				return m | 
 |  | 
 | class PropertyHandler(RouteHandler): | 
 | 	verbs = ['PUT', 'GET'] | 
 | 	rules = '<path:path>/attr/<prop>' | 
 |  | 
 | 	def __init__(self, app, bus): | 
 | 		super(PropertyHandler, self).__init__( | 
 | 				app, bus, self.verbs, self.rules) | 
 |  | 
 | 	def find(self, path, prop): | 
 | 		self.app.instance_handler.setup(path) | 
 | 		obj = self.app.instance_handler.do_get(path) | 
 | 		try: | 
 | 			obj[prop] | 
 | 		except KeyError, e: | 
 | 			if request.method == 'PUT': | 
 | 				abort(403, _4034_msg %('property', 'created', str(e))) | 
 | 			else: | 
 | 				abort(404, _4034_msg %('property', 'found', str(e))) | 
 |  | 
 | 		return { path: obj } | 
 |  | 
 | 	def setup(self, path, prop): | 
 | 		request.route_data['obj'] = self.find(path, prop) | 
 |  | 
 | 	def do_get(self, path, prop): | 
 | 		return request.route_data['obj'][path][prop] | 
 |  | 
 | 	def do_put(self, path, prop, value = None): | 
 | 		if value is None: | 
 | 			value = request.parameter_list | 
 |  | 
 | 		prop, iface, properties_iface = self.get_host_interface( | 
 | 				path, prop, request.route_data['map'][path]) | 
 | 		try: | 
 | 			properties_iface.Set(iface, prop, value) | 
 | 		except ValueError, e: | 
 | 			abort(400, str(e)) | 
 | 		except dbus.exceptions.DBusException, e: | 
 | 			if e.get_dbus_name() == DBUS_INVALID_ARGS: | 
 | 				abort(403, str(e)) | 
 | 			raise | 
 |  | 
 | 	def get_host_interface(self, path, prop, bus_info): | 
 | 		for bus, interfaces in bus_info.iteritems(): | 
 | 			obj = self.bus.get_object(bus, path, introspect = True) | 
 | 			properties_iface = dbus.Interface( | 
 | 				obj, dbus_interface=dbus.PROPERTIES_IFACE) | 
 |  | 
 | 			info = self.get_host_interface_on_bus( | 
 | 					path, prop, properties_iface, | 
 | 					bus, interfaces) | 
 | 			if info is not None: | 
 | 				prop, iface = info | 
 | 				return prop, iface, properties_iface | 
 |  | 
 | 	def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces): | 
 | 		for i in interfaces: | 
 | 			properties = self.try_properties_interface(iface.GetAll, i) | 
 | 			if properties is None: | 
 | 				continue | 
 | 			prop = find_case_insensitive(prop, properties.keys()) | 
 | 			if prop is None: | 
 | 				continue | 
 | 			return prop, i | 
 |  | 
 | class InstanceHandler(RouteHandler): | 
 | 	verbs = ['GET', 'PUT', 'DELETE'] | 
 | 	rules = '<path:path>' | 
 | 	request_type = dict | 
 |  | 
 | 	def __init__(self, app, bus): | 
 | 		super(InstanceHandler, self).__init__( | 
 | 				app, bus, self.verbs, self.rules) | 
 |  | 
 | 	def find(self, path, callback = None): | 
 | 		return { path: self.try_mapper_call( | 
 | 			self.mapper.get_object, | 
 | 			callback, | 
 | 			path = path) } | 
 |  | 
 | 	def setup(self, path): | 
 | 		callback = None | 
 | 		if request.method == 'PUT': | 
 | 			def callback(e, **kw): | 
 | 				abort(403, _4034_msg %('resource', | 
 | 					'created', path)) | 
 |  | 
 | 		if request.route_data.get('map') is None: | 
 | 			request.route_data['map'] = self.find(path, callback) | 
 |  | 
 | 	def do_get(self, path): | 
 | 		properties = {} | 
 | 		for item in request.route_data['map'][path].iteritems(): | 
 | 			properties.update(self.get_properties_on_bus( | 
 | 				path, *item)) | 
 |  | 
 | 		return properties | 
 |  | 
 | 	@staticmethod | 
 | 	def get_properties_on_iface(properties_iface, iface): | 
 | 		properties = InstanceHandler.try_properties_interface( | 
 | 				properties_iface.GetAll, iface) | 
 | 		if properties is None: | 
 | 			return {} | 
 | 		return properties | 
 |  | 
 | 	def get_properties_on_bus(self, path, bus, interfaces): | 
 | 		properties = {} | 
 | 		obj = self.bus.get_object(bus, path, introspect = False) | 
 | 		properties_iface = dbus.Interface( | 
 | 				obj, dbus_interface=dbus.PROPERTIES_IFACE) | 
 | 		for i in interfaces: | 
 | 			properties.update(self.get_properties_on_iface( | 
 | 				properties_iface, i)) | 
 |  | 
 | 		return properties | 
 |  | 
 | 	def do_put(self, path): | 
 | 		# make sure all properties exist in the request | 
 | 		obj = set(self.do_get(path).keys()) | 
 | 		req = set(request.parameter_list.keys()) | 
 |  | 
 | 		diff = list(obj.difference(req)) | 
 | 		if diff: | 
 | 			abort(403, _4034_msg %('resource', 'removed', | 
 | 				'%s/attr/%s' %(path, diff[0]))) | 
 |  | 
 | 		diff = list(req.difference(obj)) | 
 | 		if diff: | 
 | 			abort(403, _4034_msg %('resource', 'created', | 
 | 				'%s/attr/%s' %(path, diff[0]))) | 
 |  | 
 | 		for p,v in request.parameter_list.iteritems(): | 
 | 			self.app.property_handler.do_put( | 
 | 					path, p, v) | 
 |  | 
 | 	def do_delete(self, path): | 
 | 		for bus_info in request.route_data['map'][path].iteritems(): | 
 | 			if self.bus_missing_delete(path, *bus_info): | 
 | 				abort(403, _4034_msg %('resource', 'removed', | 
 | 					path)) | 
 |  | 
 | 		for bus in request.route_data['map'][path].iterkeys(): | 
 | 			self.delete_on_bus(path, bus) | 
 |  | 
 | 	def bus_missing_delete(self, path, bus, interfaces): | 
 | 		return DELETE_IFACE not in interfaces | 
 |  | 
 | 	def delete_on_bus(self, path, bus): | 
 | 		obj = self.bus.get_object(bus, path, introspect = False) | 
 | 		delete_iface = dbus.Interface( | 
 | 				obj, dbus_interface = DELETE_IFACE) | 
 | 		delete_iface.Delete() | 
 |  | 
 | class JsonApiRequestPlugin(object): | 
 | 	''' Ensures request content satisfies the OpenBMC json api format. ''' | 
 | 	name = 'json_api_request' | 
 | 	api = 2 | 
 |  | 
 | 	error_str = "Expecting request format { 'data': <value> }, got '%s'" | 
 | 	type_error_str = "Unsupported Content-Type: '%s'" | 
 | 	json_type = "application/json" | 
 | 	request_methods = ['PUT', 'POST', 'PATCH'] | 
 |  | 
 | 	@staticmethod | 
 | 	def content_expected(): | 
 | 		return request.method in JsonApiRequestPlugin.request_methods | 
 |  | 
 | 	def validate_request(self): | 
 | 		if request.content_length > 0 and \ | 
 | 				request.content_type != self.json_type: | 
 | 			abort(415, self.type_error_str %(request.content_type)) | 
 |  | 
 | 		try: | 
 | 			request.parameter_list = request.json.get('data') | 
 | 		except ValueError, e: | 
 | 			abort(400, str(e)) | 
 | 		except (AttributeError, KeyError, TypeError): | 
 | 			abort(400, self.error_str %(request.json)) | 
 |  | 
 | 	def apply(self, callback, route): | 
 | 		verbs = getattr(route.get_undecorated_callback(), | 
 | 				'_verbs', None) | 
 | 		if verbs is None: | 
 | 			return callback | 
 |  | 
 | 		if not set(self.request_methods).intersection(verbs): | 
 | 			return callback | 
 |  | 
 | 		def wrap(*a, **kw): | 
 | 			if self.content_expected(): | 
 | 				self.validate_request() | 
 | 			return callback(*a, **kw) | 
 |  | 
 | 		return wrap | 
 |  | 
 | class JsonApiRequestTypePlugin(object): | 
 | 	''' Ensures request content type satisfies the OpenBMC json api format. ''' | 
 | 	name = 'json_api_method_request' | 
 | 	api = 2 | 
 |  | 
 | 	error_str = "Expecting request format { 'data': %s }, got '%s'" | 
 |  | 
 | 	def apply(self, callback, route): | 
 | 		request_type = getattr(route.get_undecorated_callback(), | 
 | 				'request_type', None) | 
 | 		if request_type is None: | 
 | 			return callback | 
 |  | 
 | 		def validate_request(): | 
 | 			if not isinstance(request.parameter_list, request_type): | 
 | 				abort(400, self.error_str %(str(request_type), request.json)) | 
 |  | 
 | 		def wrap(*a, **kw): | 
 | 			if JsonApiRequestPlugin.content_expected(): | 
 | 				validate_request() | 
 | 			return callback(*a, **kw) | 
 |  | 
 | 		return wrap | 
 |  | 
 | class JsonApiResponsePlugin(object): | 
 | 	''' Emits normal responses in the OpenBMC json api format. ''' | 
 | 	name = 'json_api_response' | 
 | 	api = 2 | 
 |  | 
 | 	def apply(self, callback, route): | 
 | 		def wrap(*a, **kw): | 
 | 			resp = { 'data': callback(*a, **kw) } | 
 | 			resp['status'] = 'ok' | 
 | 			resp['message'] = response.status_line | 
 | 			return resp | 
 | 		return wrap | 
 |  | 
 | class JsonApiErrorsPlugin(object): | 
 | 	''' Emits error responses in the OpenBMC json api format. ''' | 
 | 	name = 'json_api_errors' | 
 | 	api = 2 | 
 |  | 
 | 	def __init__(self, **kw): | 
 | 		self.app = None | 
 | 		self.function_type = None | 
 | 		self.original = None | 
 | 		self.json_opts = { x:y for x,y in kw.iteritems() \ | 
 | 			if x in ['indent','sort_keys'] } | 
 |  | 
 | 	def setup(self, app): | 
 | 		self.app = app | 
 | 		self.function_type = type(app.default_error_handler) | 
 | 		self.original = app.default_error_handler | 
 | 		self.app.default_error_handler = self.function_type( | 
 | 			self.json_errors, app, Bottle) | 
 |  | 
 | 	def apply(self, callback, route): | 
 | 		return callback | 
 |  | 
 | 	def close(self): | 
 | 		self.app.default_error_handler = self.function_type( | 
 | 				self.original, self.app, Bottle) | 
 |  | 
 | 	def json_errors(self, res, error): | 
 | 		response_object = {'status': 'error', 'data': {} } | 
 | 		response_object['message'] = error.status_line | 
 | 		response_object['data']['description'] = str(error.body) | 
 | 		if error.status_code == 500: | 
 | 			response_object['data']['exception'] = repr(error.exception) | 
 | 			response_object['data']['traceback'] = error.traceback.splitlines() | 
 |  | 
 | 		json_response = json.dumps(response_object, **self.json_opts) | 
 | 		response.content_type = 'application/json' | 
 | 		return json_response | 
 |  | 
 | class RestApp(Bottle): | 
 | 	def __init__(self, bus): | 
 | 		super(RestApp, self).__init__(autojson = False) | 
 | 		self.bus = bus | 
 | 		self.mapper = Mapper(bus) | 
 |  | 
 | 		self.install_hooks() | 
 | 		self.install_plugins() | 
 | 		self.create_handlers() | 
 | 		self.install_handlers() | 
 |  | 
 | 	def install_plugins(self): | 
 | 		# install json api plugins | 
 | 		json_kw = {'indent': 2, 'sort_keys': True} | 
 | 		self.install(JSONPlugin(**json_kw)) | 
 | 		self.install(JsonApiErrorsPlugin(**json_kw)) | 
 | 		self.install(JsonApiResponsePlugin()) | 
 | 		self.install(JsonApiRequestPlugin()) | 
 | 		self.install(JsonApiRequestTypePlugin()) | 
 |  | 
 | 	def install_hooks(self): | 
 | 		self.real_router_match = self.router.match | 
 | 		self.router.match = self.custom_router_match | 
 | 		self.add_hook('before_request', self.strip_extra_slashes) | 
 |  | 
 | 	def create_handlers(self): | 
 | 		# create route handlers | 
 | 		self.directory_handler = DirectoryHandler(self, self.bus) | 
 | 		self.list_names_handler = ListNamesHandler(self, self.bus) | 
 | 		self.list_handler = ListHandler(self, self.bus) | 
 | 		self.method_handler = MethodHandler(self, self.bus) | 
 | 		self.property_handler = PropertyHandler(self, self.bus) | 
 | 		self.instance_handler = InstanceHandler(self, self.bus) | 
 |  | 
 | 	def install_handlers(self): | 
 | 		self.directory_handler.install() | 
 | 		self.list_names_handler.install() | 
 | 		self.list_handler.install() | 
 | 		self.method_handler.install() | 
 | 		self.property_handler.install() | 
 | 		# this has to come last, since it matches everything | 
 | 		self.instance_handler.install() | 
 |  | 
 | 	def custom_router_match(self, environ): | 
 | 		''' The built-in Bottle algorithm for figuring out if a 404 or 405 is | 
 |                     needed doesn't work for us since the instance rules match everything. | 
 |                     This monkey-patch lets the route handler figure out which response is | 
 |                     needed.  This could be accomplished with a hook but that would require | 
 | 		    calling the router match function twice. | 
 | 		''' | 
 | 		route, args = self.real_router_match(environ) | 
 | 		if isinstance(route.callback, RouteHandler): | 
 | 			route.callback._setup(**args) | 
 |  | 
 | 		return route, args | 
 |  | 
 | 	@staticmethod | 
 | 	def strip_extra_slashes(): | 
 | 		path = request.environ['PATH_INFO'] | 
 | 		trailing = ("","/")[path[-1] == '/'] | 
 | 		parts = filter(bool, path.split('/')) | 
 | 		request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing | 
 |  | 
 | if __name__ == '__main__': | 
 | 	log = logging.getLogger('Rocket.Errors') | 
 | 	log.setLevel(logging.INFO) | 
 | 	log.addHandler(logging.StreamHandler(sys.stdout)) | 
 |  | 
 | 	bus = dbus.SystemBus() | 
 | 	app = RestApp(bus) | 
 | 	default_cert = os.path.join(sys.prefix, 'share', | 
 | 			os.path.basename(__file__), 'cert.pem') | 
 |  | 
 | 	server = Rocket(('0.0.0.0', | 
 | 			443, | 
 | 			default_cert, | 
 | 			default_cert), | 
 | 		'wsgi', {'wsgi_app': app}) | 
 | 	server.start() |