feat(API): Routes now treat paths with a trailing slash as identical.
Implements #48
This commit is contained in:
3
NOTES.md
3
NOTES.md
@@ -22,7 +22,8 @@ This requires some discipline on the part of the developer.
|
|||||||
* URI template and query string field names must include only ASCII a-z, A-Z, and the underscore '_' character. Try it; you'll like it. This simplifies parsing and helps speed things up a bit.
|
* URI template and query string field names must include only ASCII a-z, A-Z, and the underscore '_' character. Try it; you'll like it. This simplifies parsing and helps speed things up a bit.
|
||||||
* Query params must have a value. In other words, 'foo' or 'foo=' will result in the parameter being ignored.
|
* Query params must have a value. In other words, 'foo' or 'foo=' will result in the parameter being ignored.
|
||||||
* If the WSGI server passes an empty path, Falcon will force it to '/', so you don't have to test for the empty string in your app.
|
* If the WSGI server passes an empty path, Falcon will force it to '/', so you don't have to test for the empty string in your app.
|
||||||
* If you are hosting multiple apps with a single WSGI server, the SCRIPT_NAME variable can read from req.app
|
* The routes '/foo/bar' and '/foo/bar/' are identical as far as Falcon is concerned. Requests coming in for either will be sent to the same resource.
|
||||||
|
* If you are hosting multiple apps with a single WSGI server, the SCRIPT_NAME variable can be read from req.app
|
||||||
* If you have several headers to set, consider using set_headers to avoid function call overhead
|
* If you have several headers to set, consider using set_headers to avoid function call overhead
|
||||||
* Don't set content-length. It will only be overridden.
|
* Don't set content-length. It will only be overridden.
|
||||||
* The order in which header fields are sent in the response is undefined. Headers are not grouped according to the recommendation in [RFC 2616](http://tools.ietf.org/html/rfc2616#section-4.2) in order to generate responses as quickly as possible.
|
* The order in which header fields are sent in the response is undefined. Headers are not grouped according to the recommendation in [RFC 2616](http://tools.ietf.org/html/rfc2616#section-4.2) in order to generate responses as quickly as possible.
|
||||||
|
|||||||
@@ -143,11 +143,13 @@ def compile_uri_template(template=None):
|
|||||||
|
|
||||||
if not template:
|
if not template:
|
||||||
template = '/'
|
template = '/'
|
||||||
|
elif template != '/' and template.endswith('/'):
|
||||||
|
template = template[:-1]
|
||||||
|
|
||||||
# Convert Level 1 var patterns to equivalent named regex groups
|
# Convert Level 1 var patterns to equivalent named regex groups
|
||||||
escaped = re.sub(r'([\.\(\)\[\]\?\*\+\^\|])', r'\.', template)
|
escaped = re.sub(r'([\.\(\)\[\]\?\*\+\^\|])', r'\.', template)
|
||||||
pattern = re.sub(r'{([a-zA-Z][a-zA-Z_]*)}', r'(?P<\1>[^/]+)', escaped)
|
pattern = re.sub(r'{([a-zA-Z][a-zA-Z_]*)}', r'(?P<\1>[^/]+)', escaped)
|
||||||
pattern = r'\A' + pattern + r'\Z'
|
pattern = r'\A' + pattern + r'/?\Z'
|
||||||
|
|
||||||
return re.compile(pattern, re.IGNORECASE)
|
return re.compile(pattern, re.IGNORECASE)
|
||||||
|
|
||||||
|
|||||||
@@ -68,17 +68,29 @@ class TestUriTemplates(testing.TestSuite):
|
|||||||
self.assertEquals(req.get_param('id'), None)
|
self.assertEquals(req.get_param('id'), None)
|
||||||
|
|
||||||
def test_single_trailing_slash(self):
|
def test_single_trailing_slash(self):
|
||||||
resource = IDResource()
|
resource1 = IDResource()
|
||||||
self.api.add_route('/widgets/{id}/', resource)
|
self.api.add_route('/1/{id}/', resource1)
|
||||||
|
|
||||||
self.simulate_request('/widgets/123')
|
self.simulate_request('/1/123')
|
||||||
self.assertFalse(resource.called)
|
self.assertTrue(resource1.called)
|
||||||
|
self.assertEquals(resource1.id, '123')
|
||||||
|
self.assertEquals(resource1.name, None)
|
||||||
|
|
||||||
self.simulate_request('/widgets/123/')
|
resource2 = IDResource()
|
||||||
self.assertTrue(resource.called)
|
self.api.add_route('/2/{id}/', resource2)
|
||||||
|
|
||||||
self.assertEquals(resource.id, '123')
|
self.simulate_request('/2/123/')
|
||||||
self.assertEquals(resource.name, None)
|
self.assertTrue(resource2.called)
|
||||||
|
self.assertEquals(resource2.id, '123')
|
||||||
|
self.assertEquals(resource2.name, None)
|
||||||
|
|
||||||
|
resource3 = IDResource()
|
||||||
|
self.api.add_route('/3/{id}', resource3)
|
||||||
|
|
||||||
|
self.simulate_request('/3/123/')
|
||||||
|
self.assertTrue(resource3.called)
|
||||||
|
self.assertEquals(resource3.id, '123')
|
||||||
|
self.assertEquals(resource3.name, None)
|
||||||
|
|
||||||
def test_multiple(self):
|
def test_multiple(self):
|
||||||
resource = IDResource()
|
resource = IDResource()
|
||||||
|
|||||||
Reference in New Issue
Block a user