# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # 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. """ A filter middleware that inspects the requested URI for a version string and/or Accept headers and attempts to negotiate an API controller to return """ import logging import re from heat.api import versions from heat.common import wsgi logger = logging.getLogger('heat.api.middleware.version_negotiation') class VersionNegotiationFilter(wsgi.Middleware): def __init__(self, app, conf, **local_conf): self.versions_app = versions.Controller(conf) self.version_uri_regex = re.compile(r"^v(\d+)\.?(\d+)?") self.conf = conf super(VersionNegotiationFilter, self).__init__(app) def process_request(self, req): """ If there is a version identifier in the URI, simply return the correct API controller, otherwise, if we find an Accept: header, process it """ # See if a version identifier is in the URI passed to # us already. If so, simply return the right version # API controller msg = _("Processing request: %(method)s %(path)s Accept: " "%(accept)s") % ({'method': req.method, 'path': req.path, 'accept': req.accept}) logger.debug(msg) # If the request is for /versions, just return the versions container if req.path_info_peek() == "versions": return self.versions_app match = self._match_version_string(req.path_info_peek(), req) if match: if (req.environ['api.major_version'] == 1 and req.environ['api.minor_version'] == 0): logger.debug(_("Matched versioned URI. Version: %d.%d"), req.environ['api.major_version'], req.environ['api.minor_version']) # Strip the version from the path req.path_info_pop() return None else: logger.debug(_("Unknown version in versioned URI: %d.%d. " "Returning version choices."), req.environ['api.major_version'], req.environ['api.minor_version']) return self.versions_app accept = str(req.accept) if accept.startswith('application/vnd.openstack.images-'): token_loc = len('application/vnd.openstack.images-') accept_version = accept[token_loc:] match = self._match_version_string(accept_version, req) if match: if (req.environ['api.major_version'] == 1 and req.environ['api.minor_version'] == 0): logger.debug(_("Matched versioned media type. " "Version: %d.%d"), req.environ['api.major_version'], req.environ['api.minor_version']) return None else: logger.debug(_("Unknown version in accept header: %d.%d..." "returning version choices."), req.environ['api.major_version'], req.environ['api.minor_version']) return self.versions_app else: if req.accept not in ('*/*', ''): logger.debug(_("Unknown accept header: %s..." "returning version choices."), req.accept) return self.versions_app return None def _match_version_string(self, subject, req): """ Given a subject string, tries to match a major and/or minor version number. If found, sets the api.major_version and api.minor_version environ variables. Returns True if there was a match, false otherwise. :param subject: The string to check :param req: Webob.Request object """ match = self.version_uri_regex.match(subject) if match: major_version, minor_version = match.groups(0) major_version = int(major_version) minor_version = int(minor_version) req.environ['api.major_version'] = major_version req.environ['api.minor_version'] = minor_version return match is not None