Treat endpoints with trailing slashes the same way as without them
We've been historically using endpoints without trailing slashes in our API. Apparently, some libraries (like gophercloud) are quite opinionated about it (see the story), so let's handle both. The implementation could be simpler if we just added trailing slash to all routes, but it would cause redirects for current users. Change-Id: Icbd971a8e792f93f9c3fa66ba29bec055dcdee32 Story: #2007660 Task: #39749
This commit is contained in:
parent
8d4613c906
commit
963e9cf75a
@ -172,8 +172,9 @@ def add_version_headers(res):
|
||||
def create_link_object(urls):
|
||||
links = []
|
||||
for url in urls:
|
||||
links.append({"rel": "self",
|
||||
"href": os.path.join(flask.request.url_root, url)})
|
||||
links.append({
|
||||
"rel": "self",
|
||||
"href": os.path.join(flask.request.url_root, url).rstrip('/')})
|
||||
return links
|
||||
|
||||
|
||||
@ -181,7 +182,7 @@ def generate_resource_data(resources):
|
||||
data = []
|
||||
for resource in resources:
|
||||
item = {}
|
||||
item['name'] = str(resource).split('/')[-1]
|
||||
item['name'] = str(resource).rstrip('/').split('/')[-1]
|
||||
item['links'] = create_link_object([str(resource)[1:]])
|
||||
data.append(item)
|
||||
return data
|
||||
@ -226,6 +227,11 @@ def api(path, is_public_api=False, rule=None, verb_to_rule_map=None,
|
||||
and strings to format the 'rule' string with
|
||||
:param kwargs: all the rest kwargs are passed to flask app.route
|
||||
"""
|
||||
# Force uniform behavior with regards to trailing slashes
|
||||
if not path.endswith('/'):
|
||||
path = path + '/'
|
||||
flask_kwargs['strict_slashes'] = False
|
||||
|
||||
def outer(func):
|
||||
@_app.route(path, **flask_kwargs)
|
||||
@convert_exceptions
|
||||
@ -264,7 +270,7 @@ def api_root():
|
||||
@api('/<version>', rule='introspection:version', is_public_api=True,
|
||||
methods=['GET'])
|
||||
def version_root(version):
|
||||
pat = re.compile(r'^\/%s\/[^\/]*?$' % version)
|
||||
pat = re.compile(r'^\/%s\/[^\/]*?/?$' % version)
|
||||
|
||||
resources = []
|
||||
for url in _app.url_map.iter_rules():
|
||||
@ -272,7 +278,7 @@ def version_root(version):
|
||||
resources.append(url)
|
||||
|
||||
if not resources:
|
||||
raise utils.Error(_('Version not found.'), code=404)
|
||||
raise utils.Error(_('Version %s not found.') % version, code=404)
|
||||
|
||||
return flask.jsonify(resources=generate_resource_data(resources))
|
||||
|
||||
@ -392,7 +398,7 @@ def api_introspection_reapply(node_id):
|
||||
def rule_repr(rule, short):
|
||||
result = rule.as_dict(short=short)
|
||||
result['links'] = [{
|
||||
'href': flask.url_for('api_rule', uuid=result['uuid']),
|
||||
'href': flask.url_for('api_rule', uuid=result['uuid']).rstrip('/'),
|
||||
'rel': 'self'
|
||||
}]
|
||||
return result
|
||||
|
@ -582,8 +582,8 @@ class TestApiVersions(BaseAPITest):
|
||||
@mock.patch.object(main._app.url_map, "iter_rules", autospec=True)
|
||||
def test_version_endpoint(self, mock_rules):
|
||||
mock_rules.return_value = ["/v1/endpoint1", "/v1/endpoint2/<uuid>",
|
||||
"/v1/endpoint1/<name>",
|
||||
"/v2/endpoint1", "/v1/endpoint3",
|
||||
"/v1/endpoint1/<name>/",
|
||||
"/v2/endpoint1", "/v1/endpoint3/",
|
||||
"/v1/endpoint2/<uuid>/subpoint"]
|
||||
endpoint = "/v1"
|
||||
res = self.app.get(endpoint)
|
||||
@ -606,6 +606,12 @@ class TestApiVersions(BaseAPITest):
|
||||
]}
|
||||
self.assertEqual(expected, json_data)
|
||||
|
||||
def test_version_endpoint_with_slash(self):
|
||||
endpoint = "/v1/"
|
||||
res = self.app.get(endpoint)
|
||||
self.assertEqual(200, res.status_code)
|
||||
self._check_version_present(res)
|
||||
|
||||
def test_version_endpoint_invalid(self):
|
||||
endpoint = "/v-1"
|
||||
res = self.app.get(endpoint)
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Fixes accessing API endpoints with trailing slashes. Now they're treated
|
||||
the same way as without slashes, although the latter remain canonical URLs.
|
Loading…
Reference in New Issue
Block a user