diff --git a/nova/api/auth.py b/nova/api/auth.py index c59b2d502d88..4663d6444a99 100644 --- a/nova/api/auth.py +++ b/nova/api/auth.py @@ -22,10 +22,10 @@ from oslo_serialization import jsonutils import webob.dec import webob.exc +from nova.api import wsgi import nova.conf from nova import context from nova.i18n import _ -from nova import wsgi CONF = nova.conf.CONF diff --git a/nova/api/metadata/handler.py b/nova/api/metadata/handler.py index 64053218724a..eb3e84e2e29a 100644 --- a/nova/api/metadata/handler.py +++ b/nova/api/metadata/handler.py @@ -27,13 +27,13 @@ import webob.dec import webob.exc from nova.api.metadata import base +from nova.api import wsgi from nova import cache_utils import nova.conf from nova import context as nova_context from nova import exception from nova.i18n import _ from nova.network.neutronv2 import api as neutronapi -from nova import wsgi CONF = nova.conf.CONF LOG = logging.getLogger(__name__) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 3443554015ff..13467c180236 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -24,16 +24,28 @@ import webob.dec import webob.exc from nova.api.openstack import wsgi +from nova.api import wsgi as base_wsgi import nova.conf from nova.i18n import translate -from nova import utils -from nova import wsgi as base_wsgi LOG = logging.getLogger(__name__) CONF = nova.conf.CONF +def walk_class_hierarchy(clazz, encountered=None): + """Walk class hierarchy, yielding most derived classes first.""" + if not encountered: + encountered = [] + for subclass in clazz.__subclasses__(): + if subclass not in encountered: + encountered.append(subclass) + # drill down to leaves first + for subsubclass in walk_class_hierarchy(subclass, encountered): + yield subsubclass + yield subclass + + class FaultWrapper(base_wsgi.Middleware): """Calls down the middleware stack, making exceptions into faults.""" @@ -42,7 +54,7 @@ class FaultWrapper(base_wsgi.Middleware): @staticmethod def status_to_type(status): if not FaultWrapper._status_to_type: - for clazz in utils.walk_class_hierarchy(webob.exc.HTTPError): + for clazz in walk_class_hierarchy(webob.exc.HTTPError): FaultWrapper._status_to_type[clazz.code] = clazz return FaultWrapper._status_to_type.get( status, webob.exc.HTTPInternalServerError)() diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index a3bcd0cad156..d5966b8ce6a4 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -18,9 +18,9 @@ import webob.dec import webob.exc from nova.api.openstack import wsgi +from nova.api import wsgi as base_wsgi import nova.conf from nova import context -from nova import wsgi as base_wsgi CONF = nova.conf.CONF diff --git a/nova/api/openstack/compute/routes.py b/nova/api/openstack/compute/routes.py index a9f209c5be7f..e6d55c27347c 100644 --- a/nova/api/openstack/compute/routes.py +++ b/nova/api/openstack/compute/routes.py @@ -93,8 +93,8 @@ from nova.api.openstack.compute import versionsV21 from nova.api.openstack.compute import virtual_interfaces from nova.api.openstack.compute import volumes from nova.api.openstack import wsgi +from nova.api import wsgi as base_wsgi import nova.conf -from nova import wsgi as base_wsgi CONF = nova.conf.CONF diff --git a/nova/api/openstack/requestlog.py b/nova/api/openstack/requestlog.py index 85c0e8ed5725..3c41f114f0a5 100644 --- a/nova/api/openstack/requestlog.py +++ b/nova/api/openstack/requestlog.py @@ -20,7 +20,7 @@ import webob.dec import webob.exc from nova.api.openstack import wsgi -from nova import wsgi as base_wsgi +from nova.api import wsgi as base_wsgi # TODO(sdague) maybe we can use a better name here for the logger LOG = logging.getLogger(__name__) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 3f7eebc2372a..2eef33c01265 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -26,11 +26,10 @@ import webob from nova.api.openstack import api_version_request as api_version from nova.api.openstack import versioned_method +from nova.api import wsgi from nova import exception from nova import i18n from nova.i18n import _ -from nova import utils -from nova import wsgi LOG = logging.getLogger(__name__) @@ -334,16 +333,28 @@ class ResponseObject(object): if self.obj is not None: body = serializer.serialize(self.obj) response = webob.Response(body=body) - if response.headers.get('Content-Length'): - # NOTE(andreykurilin): we need to encode 'Content-Length' header, - # since webob.Response auto sets it if "body" attr is presented. - # https://github.com/Pylons/webob/blob/1.5.0b0/webob/response.py#L147 - response.headers['Content-Length'] = utils.utf8( - response.headers['Content-Length']) response.status_int = self.code - for hdr, value in self._headers.items(): - response.headers[hdr] = utils.utf8(value) - response.headers['Content-Type'] = utils.utf8(content_type) + for hdr, val in self._headers.items(): + if not isinstance(val, six.text_type): + val = six.text_type(val) + if six.PY2: + # In Py2.X Headers must be byte strings + response.headers[hdr] = encodeutils.safe_encode(val) + else: + # In Py3.X Headers must be utf-8 strings + response.headers[hdr] = encodeutils.safe_decode( + encodeutils.safe_encode(val)) + # Deal with content_type + if not isinstance(content_type, six.text_type): + content_type = six.text_type(content_type) + if six.PY2: + # In Py2.X Headers must be byte strings + response.headers['Content-Type'] = encodeutils.safe_encode( + content_type) + else: + # In Py3.X Headers must be utf-8 strings + response.headers['Content-Type'] = encodeutils.safe_decode( + encodeutils.safe_encode(content_type)) return response @property @@ -658,13 +669,15 @@ class Resource(wsgi.Application): if hasattr(response, 'headers'): for hdr, val in list(response.headers.items()): + if not isinstance(val, six.text_type): + val = six.text_type(val) if six.PY2: # In Py2.X Headers must be byte strings - response.headers[hdr] = utils.utf8(val) + response.headers[hdr] = encodeutils.safe_encode(val) else: # In Py3.X Headers must be utf-8 strings response.headers[hdr] = encodeutils.safe_decode( - utils.utf8(val)) + encodeutils.safe_encode(val)) if not request.api_version_request.is_null(): response.headers[API_VERSION_REQUEST_HEADER] = \ diff --git a/nova/api/wsgi.py b/nova/api/wsgi.py new file mode 100644 index 000000000000..a365d1320d65 --- /dev/null +++ b/nova/api/wsgi.py @@ -0,0 +1,298 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""WSGI primitives used throughout the nova WSGI apps.""" + +import os +import sys + +from oslo_log import log as logging +from paste import deploy +import routes.middleware +import six +import webob + +import nova.conf +from nova import exception +from nova.i18n import _, _LE + + +CONF = nova.conf.CONF + +LOG = logging.getLogger(__name__) + + +class Request(webob.Request): + def __init__(self, environ, *args, **kwargs): + if CONF.wsgi.secure_proxy_ssl_header: + scheme = environ.get(CONF.wsgi.secure_proxy_ssl_header) + if scheme: + environ['wsgi.url_scheme'] = scheme + super(Request, self).__init__(environ, *args, **kwargs) + + +class Application(object): + """Base WSGI application wrapper. Subclasses need to implement __call__.""" + + @classmethod + def factory(cls, global_config, **local_config): + """Used for paste app factories in paste.deploy config files. + + Any local configuration (that is, values under the [app:APPNAME] + section of the paste config) will be passed into the `__init__` method + as kwargs. + + A hypothetical configuration would look like: + + [app:wadl] + latest_version = 1.3 + paste.app_factory = nova.api.fancy_api:Wadl.factory + + which would result in a call to the `Wadl` class as + + import nova.api.fancy_api + fancy_api.Wadl(latest_version='1.3') + + You could of course re-implement the `factory` method in subclasses, + but using the kwarg passing it shouldn't be necessary. + + """ + return cls(**local_config) + + def __call__(self, environ, start_response): + r"""Subclasses will probably want to implement __call__ like this: + + @webob.dec.wsgify(RequestClass=Request) + def __call__(self, req): + # Any of the following objects work as responses: + + # Option 1: simple string + res = 'message\n' + + # Option 2: a nicely formatted HTTP exception page + res = exc.HTTPForbidden(explanation='Nice try') + + # Option 3: a webob Response object (in case you need to play with + # headers, or you want to be treated like an iterable, or ...) + res = Response() + res.app_iter = open('somefile') + + # Option 4: any wsgi app to be run next + res = self.application + + # Option 5: you can get a Response object for a wsgi app, too, to + # play with headers etc + res = req.get_response(self.application) + + # You can then just return your response... + return res + # ... or set req.response and return None. + req.response = res + + See the end of http://pythonpaste.org/webob/modules/dec.html + for more info. + + """ + raise NotImplementedError(_('You must implement __call__')) + + +class Middleware(Application): + """Base WSGI middleware. + + These classes require an application to be + initialized that will be called next. By default the middleware will + simply call its wrapped app, or you can override __call__ to customize its + behavior. + + """ + + @classmethod + def factory(cls, global_config, **local_config): + """Used for paste app factories in paste.deploy config files. + + Any local configuration (that is, values under the [filter:APPNAME] + section of the paste config) will be passed into the `__init__` method + as kwargs. + + A hypothetical configuration would look like: + + [filter:analytics] + redis_host = 127.0.0.1 + paste.filter_factory = nova.api.analytics:Analytics.factory + + which would result in a call to the `Analytics` class as + + import nova.api.analytics + analytics.Analytics(app_from_paste, redis_host='127.0.0.1') + + You could of course re-implement the `factory` method in subclasses, + but using the kwarg passing it shouldn't be necessary. + + """ + def _factory(app): + return cls(app, **local_config) + return _factory + + def __init__(self, application): + self.application = application + + def process_request(self, req): + """Called on each request. + + If this returns None, the next application down the stack will be + executed. If it returns a response then that response will be returned + and execution will stop here. + + """ + return None + + def process_response(self, response): + """Do whatever you'd like to the response.""" + return response + + @webob.dec.wsgify(RequestClass=Request) + def __call__(self, req): + response = self.process_request(req) + if response: + return response + response = req.get_response(self.application) + return self.process_response(response) + + +class Debug(Middleware): + """Helper class for debugging a WSGI application. + + Can be inserted into any WSGI application chain to get information + about the request and response. + + """ + + @webob.dec.wsgify(RequestClass=Request) + def __call__(self, req): + print(('*' * 40) + ' REQUEST ENVIRON') + for key, value in req.environ.items(): + print(key, '=', value) + print() + resp = req.get_response(self.application) + + print(('*' * 40) + ' RESPONSE HEADERS') + for (key, value) in resp.headers.items(): + print(key, '=', value) + print() + + resp.app_iter = self.print_generator(resp.app_iter) + + return resp + + @staticmethod + def print_generator(app_iter): + """Iterator that prints the contents of a wrapper string.""" + print(('*' * 40) + ' BODY') + for part in app_iter: + sys.stdout.write(six.text_type(part)) + sys.stdout.flush() + yield part + print() + + +class Router(object): + """WSGI middleware that maps incoming requests to WSGI apps.""" + + def __init__(self, mapper): + """Create a router for the given routes.Mapper. + + Each route in `mapper` must specify a 'controller', which is a + WSGI app to call. You'll probably want to specify an 'action' as + well and have your controller be an object that can route + the request to the action-specific method. + + Examples: + mapper = routes.Mapper() + sc = ServerController() + + # Explicit mapping of one route to a controller+action + mapper.connect(None, '/svrlist', controller=sc, action='list') + + # Actions are all implicitly defined + mapper.resource('server', 'servers', controller=sc) + + # Pointing to an arbitrary WSGI app. You can specify the + # {path_info:.*} parameter so the target app can be handed just that + # section of the URL. + mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp()) + + """ + self.map = mapper + self._router = routes.middleware.RoutesMiddleware(self._dispatch, + self.map) + + @webob.dec.wsgify(RequestClass=Request) + def __call__(self, req): + """Route the incoming request to a controller based on self.map. + + If no match, return a 404. + + """ + return self._router + + @staticmethod + @webob.dec.wsgify(RequestClass=Request) + def _dispatch(req): + """Dispatch the request to the appropriate controller. + + Called by self._router after matching the incoming request to a route + and putting the information into req.environ. Either returns 404 + or the routed WSGI app's response. + + """ + match = req.environ['wsgiorg.routing_args'][1] + if not match: + return webob.exc.HTTPNotFound() + app = match['controller'] + return app + + +class Loader(object): + """Used to load WSGI applications from paste configurations.""" + + def __init__(self, config_path=None): + """Initialize the loader, and attempt to find the config. + + :param config_path: Full or relative path to the paste config. + :returns: None + + """ + self.config_path = None + + config_path = config_path or CONF.wsgi.api_paste_config + if not os.path.isabs(config_path): + self.config_path = CONF.find_file(config_path) + elif os.path.exists(config_path): + self.config_path = config_path + + if not self.config_path: + raise exception.ConfigNotFound(path=config_path) + + def load_app(self, name): + """Return the paste URLMap wrapped WSGI application. + + :param name: Name of the application to load. + :returns: Paste URLMap object wrapping the requested application. + :raises: `nova.exception.PasteAppNotFound` + + """ + try: + LOG.debug("Loading app %(name)s from %(path)s", + {'name': name, 'path': self.config_path}) + return deploy.loadapp("config:%s" % self.config_path, name=name) + except LookupError: + LOG.exception(_LE("Couldn't lookup app: %s"), name) + raise exception.PasteAppNotFound(name=name, path=self.config_path) diff --git a/nova/service.py b/nova/service.py index a04a2e96d559..5f43d8557a65 100644 --- a/nova/service.py +++ b/nova/service.py @@ -27,6 +27,7 @@ import oslo_messaging as messaging from oslo_service import service from oslo_utils import importutils +from nova.api import wsgi as api_wsgi from nova import baserpc from nova import conductor import nova.conf @@ -326,7 +327,7 @@ class WSGIService(service.Service): self.binary = 'nova-%s' % name self.topic = None self.manager = self._get_manager() - self.loader = loader or wsgi.Loader() + self.loader = loader or api_wsgi.Loader() self.app = self.loader.load_app(name) # inherit all compute_api worker counts from osapi_compute if name.startswith('openstack_compute_api'): diff --git a/nova/tests/fixtures.py b/nova/tests/fixtures.py index 0871e077e482..fbc9be16f9f4 100644 --- a/nova/tests/fixtures.py +++ b/nova/tests/fixtures.py @@ -39,6 +39,7 @@ from wsgi_intercept import interceptor from nova.api.openstack.compute import tenant_networks from nova.api.openstack.placement import deploy as placement_deploy from nova.api.openstack import wsgi_app +from nova.api import wsgi from nova.compute import rpcapi as compute_rpcapi from nova import context from nova.db import migration @@ -53,7 +54,6 @@ from nova import rpc from nova import service from nova.tests.functional.api import client from nova.tests import uuidsentinel -from nova import wsgi _TRUE_VALUES = ('True', 'true', '1', 'yes') diff --git a/nova/tests/unit/api/openstack/compute/test_versions.py b/nova/tests/unit/api/openstack/compute/test_versions.py index 98e5568b5826..8603f7e0eccd 100644 --- a/nova/tests/unit/api/openstack/compute/test_versions.py +++ b/nova/tests/unit/api/openstack/compute/test_versions.py @@ -19,11 +19,11 @@ from oslo_serialization import jsonutils from nova.api.openstack import api_version_request as avr from nova.api.openstack.compute import views +from nova.api import wsgi from nova import test from nova.tests.unit.api.openstack import fakes from nova.tests.unit import matchers from nova.tests import uuidsentinel as uuids -from nova import wsgi NS = { diff --git a/nova/tests/unit/api/openstack/fakes.py b/nova/tests/unit/api/openstack/fakes.py index 554c40b54dc5..0d58501d301f 100644 --- a/nova/tests/unit/api/openstack/fakes.py +++ b/nova/tests/unit/api/openstack/fakes.py @@ -30,6 +30,7 @@ from nova.api.openstack import compute from nova.api.openstack.compute import versions from nova.api.openstack import urlmap from nova.api.openstack import wsgi as os_wsgi +from nova.api import wsgi from nova.compute import flavors from nova.compute import vm_states import nova.conf @@ -44,7 +45,6 @@ from nova.tests.unit import fake_block_device from nova.tests.unit import fake_network from nova.tests.unit.objects import test_keypair from nova import utils -from nova import wsgi CONF = nova.conf.CONF diff --git a/nova/tests/unit/api/test_wsgi.py b/nova/tests/unit/api/test_wsgi.py index d83cc67fbd40..4ad4055752b3 100644 --- a/nova/tests/unit/api/test_wsgi.py +++ b/nova/tests/unit/api/test_wsgi.py @@ -24,8 +24,8 @@ import routes from six.moves import StringIO import webob +from nova.api import wsgi from nova import test -from nova import wsgi class Test(test.NoDBTestCase): diff --git a/nova/tests/unit/test_service.py b/nova/tests/unit/test_service.py index 5b83d5496aad..bbba577f2472 100644 --- a/nova/tests/unit/test_service.py +++ b/nova/tests/unit/test_service.py @@ -263,7 +263,7 @@ class TestWSGIService(test.NoDBTestCase): def setUp(self): super(TestWSGIService, self).setUp() - self.stub_out('nova.wsgi.Loader.load_app', + self.stub_out('nova.api.wsgi.Loader.load_app', lambda *a, **kw: mock.MagicMock()) @mock.patch('nova.objects.Service.get_by_host_and_binary') diff --git a/nova/tests/unit/test_wsgi.py b/nova/tests/unit/test_wsgi.py index c5ecbf7cb751..4b863d015e5c 100644 --- a/nova/tests/unit/test_wsgi.py +++ b/nova/tests/unit/test_wsgi.py @@ -29,6 +29,7 @@ import six import testtools import webob +import nova.api.wsgi import nova.exception from nova import test from nova.tests.unit import utils @@ -51,14 +52,14 @@ class TestLoaderNothingExists(test.NoDBTestCase): self.flags(api_paste_config='api-paste.ini', group='wsgi') self.assertRaises( nova.exception.ConfigNotFound, - nova.wsgi.Loader, + nova.api.wsgi.Loader, ) def test_asbpath_config_not_found(self): self.flags(api_paste_config='/etc/nova/api-paste.ini', group='wsgi') self.assertRaises( nova.exception.ConfigNotFound, - nova.wsgi.Loader, + nova.api.wsgi.Loader, ) @@ -77,7 +78,7 @@ document_root = /tmp self.config.write(self._paste_config.lstrip()) self.config.seek(0) self.config.flush() - self.loader = nova.wsgi.Loader(self.config.name) + self.loader = nova.api.wsgi.Loader(self.config.name) def test_config_found(self): self.assertEqual(self.config.name, self.loader.config_path) diff --git a/nova/utils.py b/nova/utils.py index 9d835fcd7222..8abe8db07121 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -683,19 +683,6 @@ def tempdir(**kwargs): LOG.error(_LE('Could not remove tmpdir: %s'), e) -def walk_class_hierarchy(clazz, encountered=None): - """Walk class hierarchy, yielding most derived classes first.""" - if not encountered: - encountered = [] - for subclass in clazz.__subclasses__(): - if subclass not in encountered: - encountered.append(subclass) - # drill down to leaves first - for subsubclass in walk_class_hierarchy(subclass, encountered): - yield subsubclass - yield subclass - - class UndoManager(object): """Provides a mechanism to facilitate rolling back a series of actions when an exception is raised. diff --git a/nova/wsgi.py b/nova/wsgi.py index c841b2060b56..07021f16c3fd 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -22,7 +22,6 @@ from __future__ import print_function import os.path import socket import ssl -import sys import eventlet import eventlet.wsgi @@ -30,11 +29,6 @@ import greenlet from oslo_log import log as logging from oslo_service import service from oslo_utils import excutils -from paste import deploy -import routes.middleware -import six -import webob.dec -import webob.exc import nova.conf from nova import exception @@ -230,271 +224,3 @@ class Server(service.ServiceBase): self._server.wait() except greenlet.GreenletExit: LOG.info(_LI("WSGI server has stopped.")) - - -class Request(webob.Request): - def __init__(self, environ, *args, **kwargs): - if CONF.wsgi.secure_proxy_ssl_header: - scheme = environ.get(CONF.wsgi.secure_proxy_ssl_header) - if scheme: - environ['wsgi.url_scheme'] = scheme - super(Request, self).__init__(environ, *args, **kwargs) - - -class Application(object): - """Base WSGI application wrapper. Subclasses need to implement __call__.""" - - @classmethod - def factory(cls, global_config, **local_config): - """Used for paste app factories in paste.deploy config files. - - Any local configuration (that is, values under the [app:APPNAME] - section of the paste config) will be passed into the `__init__` method - as kwargs. - - A hypothetical configuration would look like: - - [app:wadl] - latest_version = 1.3 - paste.app_factory = nova.api.fancy_api:Wadl.factory - - which would result in a call to the `Wadl` class as - - import nova.api.fancy_api - fancy_api.Wadl(latest_version='1.3') - - You could of course re-implement the `factory` method in subclasses, - but using the kwarg passing it shouldn't be necessary. - - """ - return cls(**local_config) - - def __call__(self, environ, start_response): - r"""Subclasses will probably want to implement __call__ like this: - - @webob.dec.wsgify(RequestClass=Request) - def __call__(self, req): - # Any of the following objects work as responses: - - # Option 1: simple string - res = 'message\n' - - # Option 2: a nicely formatted HTTP exception page - res = exc.HTTPForbidden(explanation='Nice try') - - # Option 3: a webob Response object (in case you need to play with - # headers, or you want to be treated like an iterable, or ...) - res = Response() - res.app_iter = open('somefile') - - # Option 4: any wsgi app to be run next - res = self.application - - # Option 5: you can get a Response object for a wsgi app, too, to - # play with headers etc - res = req.get_response(self.application) - - # You can then just return your response... - return res - # ... or set req.response and return None. - req.response = res - - See the end of http://pythonpaste.org/webob/modules/dec.html - for more info. - - """ - raise NotImplementedError(_('You must implement __call__')) - - -class Middleware(Application): - """Base WSGI middleware. - - These classes require an application to be - initialized that will be called next. By default the middleware will - simply call its wrapped app, or you can override __call__ to customize its - behavior. - - """ - - @classmethod - def factory(cls, global_config, **local_config): - """Used for paste app factories in paste.deploy config files. - - Any local configuration (that is, values under the [filter:APPNAME] - section of the paste config) will be passed into the `__init__` method - as kwargs. - - A hypothetical configuration would look like: - - [filter:analytics] - redis_host = 127.0.0.1 - paste.filter_factory = nova.api.analytics:Analytics.factory - - which would result in a call to the `Analytics` class as - - import nova.api.analytics - analytics.Analytics(app_from_paste, redis_host='127.0.0.1') - - You could of course re-implement the `factory` method in subclasses, - but using the kwarg passing it shouldn't be necessary. - - """ - def _factory(app): - return cls(app, **local_config) - return _factory - - def __init__(self, application): - self.application = application - - def process_request(self, req): - """Called on each request. - - If this returns None, the next application down the stack will be - executed. If it returns a response then that response will be returned - and execution will stop here. - - """ - return None - - def process_response(self, response): - """Do whatever you'd like to the response.""" - return response - - @webob.dec.wsgify(RequestClass=Request) - def __call__(self, req): - response = self.process_request(req) - if response: - return response - response = req.get_response(self.application) - return self.process_response(response) - - -class Debug(Middleware): - """Helper class for debugging a WSGI application. - - Can be inserted into any WSGI application chain to get information - about the request and response. - - """ - - @webob.dec.wsgify(RequestClass=Request) - def __call__(self, req): - print(('*' * 40) + ' REQUEST ENVIRON') - for key, value in req.environ.items(): - print(key, '=', value) - print() - resp = req.get_response(self.application) - - print(('*' * 40) + ' RESPONSE HEADERS') - for (key, value) in resp.headers.items(): - print(key, '=', value) - print() - - resp.app_iter = self.print_generator(resp.app_iter) - - return resp - - @staticmethod - def print_generator(app_iter): - """Iterator that prints the contents of a wrapper string.""" - print(('*' * 40) + ' BODY') - for part in app_iter: - sys.stdout.write(six.text_type(part)) - sys.stdout.flush() - yield part - print() - - -class Router(object): - """WSGI middleware that maps incoming requests to WSGI apps.""" - - def __init__(self, mapper): - """Create a router for the given routes.Mapper. - - Each route in `mapper` must specify a 'controller', which is a - WSGI app to call. You'll probably want to specify an 'action' as - well and have your controller be an object that can route - the request to the action-specific method. - - Examples: - mapper = routes.Mapper() - sc = ServerController() - - # Explicit mapping of one route to a controller+action - mapper.connect(None, '/svrlist', controller=sc, action='list') - - # Actions are all implicitly defined - mapper.resource('server', 'servers', controller=sc) - - # Pointing to an arbitrary WSGI app. You can specify the - # {path_info:.*} parameter so the target app can be handed just that - # section of the URL. - mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp()) - - """ - self.map = mapper - self._router = routes.middleware.RoutesMiddleware(self._dispatch, - self.map) - - @webob.dec.wsgify(RequestClass=Request) - def __call__(self, req): - """Route the incoming request to a controller based on self.map. - - If no match, return a 404. - - """ - return self._router - - @staticmethod - @webob.dec.wsgify(RequestClass=Request) - def _dispatch(req): - """Dispatch the request to the appropriate controller. - - Called by self._router after matching the incoming request to a route - and putting the information into req.environ. Either returns 404 - or the routed WSGI app's response. - - """ - match = req.environ['wsgiorg.routing_args'][1] - if not match: - return webob.exc.HTTPNotFound() - app = match['controller'] - return app - - -class Loader(object): - """Used to load WSGI applications from paste configurations.""" - - def __init__(self, config_path=None): - """Initialize the loader, and attempt to find the config. - - :param config_path: Full or relative path to the paste config. - :returns: None - - """ - self.config_path = None - - config_path = config_path or CONF.wsgi.api_paste_config - if not os.path.isabs(config_path): - self.config_path = CONF.find_file(config_path) - elif os.path.exists(config_path): - self.config_path = config_path - - if not self.config_path: - raise exception.ConfigNotFound(path=config_path) - - def load_app(self, name): - """Return the paste URLMap wrapped WSGI application. - - :param name: Name of the application to load. - :returns: Paste URLMap object wrapping the requested application. - :raises: `nova.exception.PasteAppNotFound` - - """ - try: - LOG.debug("Loading app %(name)s from %(path)s", - {'name': name, 'path': self.config_path}) - return deploy.loadapp("config:%s" % self.config_path, name=name) - except LookupError: - LOG.exception(_LE("Couldn't lookup app: %s"), name) - raise exception.PasteAppNotFound(name=name, path=self.config_path)