From 92ba7c938791f4eaafad81478a725e877c8f7b6e Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sun, 10 Apr 2011 19:19:57 -0500 Subject: [PATCH] Added special handling for mapped paths with trailing slashes --- src/milla/dispatch/routing.py | 70 ++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/src/milla/dispatch/routing.py b/src/milla/dispatch/routing.py index b6828d1..d5f7a9b 100644 --- a/src/milla/dispatch/routing.py +++ b/src/milla/dispatch/routing.py @@ -11,12 +11,6 @@ # 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. -from milla.dispatch import UnresolvedPath -import functools -import re -import sys -import urllib -import urlparse '''URL router :Created: Mar 13, 2011 @@ -25,6 +19,13 @@ import urlparse :Updater: $Author$ ''' +from milla.dispatch import UnresolvedPath +import functools +import milla +import re +import sys +import urllib +import urlparse class Router(object): '''A dispatcher that maps arbitrary paths to controller callables @@ -34,14 +35,43 @@ class Router(object): router = Router() router.add_route('/foo/{bar}/{baz:\d+}', some_func) app = milla.Application(dispatcher=router) + + In many cases, paths with trailing slashes need special handling. + The ``Router`` has two ways of dealing with requests that should + have a trailing slash but do not. The default is to send the client + an HTTP 301 Moved Permanently response, and the other is to + simply treat the request as if it had the necessary trailing slash. + A third option is to disable special handling entirely and simply + return HTTP 404 Not Found for requests with missing trailing + slashes. To change the behavior, pass a different value to the + constructor's ``trailing_slash`` keyword. + + Redirect the client to the proper path (the default):: + + router = Router(trailing_slash=Router.REDIRECT) + router.add_route('/my_collection/', some_func) + + Pretend the request had a trailing slash, even if it didn't:: + + router = Router(trailing_slash=Router.SILENT) + router.add_route('/my_collection/', some_func) + + Do nothing, let the client get a 404 error:: + + router = Router(trailing_slash=None) + router.add_route('/my_collection/', some_func) ''' + class REDIRECT(object): pass + class SILENT(object): pass + #: Compiled regular expression for variable segments template_re = re.compile(r'\{(\w+)(?::([^}]+))?\}') - def __init__(self): + def __init__(self, trailing_slash=REDIRECT): self.routes = [] self._cache = {} + self.trailing_slash=trailing_slash def resolve(self, path_info): '''Find a controller for a given path @@ -56,9 +86,8 @@ class Router(object): matches the given path. Variable segments are added as keywords to the controller function. ''' - try: - return self._cache[path_info] - except KeyError: + + def lookup(path_info): for regex, controller, vars in self.routes: match = regex.match(path_info) if match: @@ -71,6 +100,27 @@ class Router(object): func.__doc__ = controller.__doc__ self._cache[path_info] = func return func + + try: + return self._cache[path_info] + except KeyError: + func = lookup(path_info) + if func: + return func + elif self.trailing_slash and not path_info.endswith('/'): + # Try to resolve the path with a trailing slash + new_path_info = path_info + '/' + func = lookup(new_path_info) + if func and self.trailing_slash is Router.REDIRECT: + # Return a dummy function that just raises + # HTTPMovedPermanently to redirect the client to + # the canonical URL + def redir(*args, **kwargs): + raise milla.HTTPMovedPermanently(location=new_path_info) + return redir + elif func and self.trailing_slash is Router.SILENT: + # Return the function found at the alternate path + return func raise UnresolvedPath def _compile_template(self, template):