-
-
Save Sepero/2204099 to your computer and use it in GitHub Desktop.
""" Author contact: sepero 111 @ gmail . com """ | |
from django import http | |
from django.utils.http import urlquote | |
from django.core import urlresolvers | |
class RemoveSlashMiddleware(object): | |
""" | |
This middleware works like django's built in APPEND_SLASH, but in reverse. Eg | |
It removes all ending slashes from a URL, and if that doesn't resolve, it will add one slash and try again. | |
Set APPEND_SLASH to False when using this middleware. | |
Forked from the original code at http://gregbrown.co.nz/code/append-or-remove-slash/ | |
""" | |
def process_request(self, request): | |
# check if the url is valid | |
path = new_path = request.path_info | |
# Remove all trailing slashes from new_path. | |
while new_path.endswith('/'): | |
new_path = new_path[:-1] | |
urlconf = getattr(request, 'urlconf', None) | |
if not _is_valid_path(new_path, urlconf): | |
# If removing slashes made new_path invalid, add one slash and try again. | |
new_path = new_path + '/' | |
if path != new_path and _is_valid_path(new_path, urlconf): | |
return self.adjust_path(request, new_path) | |
elif path != new_path: | |
# If new_path is valid and not eq to path, send a permanent redirect. | |
return self.adjust_path(request, new_path) | |
def adjust_path(self, request, new_path): | |
""" | |
Redirect the clients browser to new_path, and tell it that all future requests to the desired URL should be sent to new_path. (This method looks like it may be able to be made more efficient, but I'm not familiar enough with request.path_info and other django variables to know how.) | |
""" | |
if request.get_host(): | |
new_url = "%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), urlquote(new_path)) | |
else: | |
new_url = urlquote(new_path) | |
if request.GET: | |
new_url += '?' + request.META['QUERY_STRING'] | |
return http.HttpResponseRedirect(new_url) | |
def _is_valid_path(path, urlconf=None): | |
""" | |
Returns True if the given path resolves against the default URL resolver, | |
False otherwise. | |
""" | |
try: | |
urlresolvers.resolve(path, urlconf) | |
return True | |
except urlresolvers.Resolver404: | |
return False | |
""" Author contact: sepero 111 @ gmail . com """ |
I had to change line 18 to: while len(new_path) > 0 and new_path[-1] == '/':
since it was broken for /
Um, I don't think that works? If new_path == '/', it wouldn't strip off the / like it's supposed to.
Yes, exactly :) Actually, I bet you could change it to while new_path.endswith('/'):
Also, maybe it could be more efficient if you return right away if the path doesn't end with a /
?
Good call with the while new_path.endswith('/'):
Edited that.
As for returning right away when there is no slash at the end- the middleware still needs to verify the url resolves and if it doesn't, then add a slash.
Your input has been excellent on this. Very much appreciated.
Oh, I see. The original was called "AppendOrRemoveSlashMiddleware", but you changed it to just RemoveSlashMiddleware but it still has the functionality of the original
Yeah, "AppendOrRemoveSlashMiddleware" tests if a url will resolve, and if the url doesn't resolve, then adds (or removes) the ending slash and tries again.
This "RemoveSlashMiddleware" is different, as it removes the ending slash(es) immediately, then if the url doesn't resolve, it adds a slash and tries again. So basically it works like the opposite of Django's APPEND_SLASH setting. (I prefer this way of handling url's because it's more easily readable by users, as well as being the method most commonly by other websites.)
Ah, I see. So it still can add a slash though, it doesn't always only remove slashes. If URL http://example.com/foo
came in and didn't resolve, it would try http://example.com/foo/
and if that resolved, it would redirect to it. I changed mine to just return right away if the URL doesn't end in a slash. I have no cases where I actually want to add a slash.
That is correct. I edited the explanatory comment block to try to clarify the how the middleware works. Any ideas on how can I make it more clear to say what this middleware does?
Maybe call it RemoveOrAppendMiddleware
instead of AppendOrRemoveMiddleware
? :)
That's definitely a thought, but the predecessor might be better off named "ToggleSlash", as it just toggles slash on/off if the requested url doesn't work.
This middleware doesn't operate as the reverse of it's predecessor, since it doesn't toggle the slash. The predecessor was just a good code base to build from. This middleware is meant to operate as the reverse/opposite of APPEND_SLASH in django settings (like a REMOVE_SLASH setting). Hopefully that explains things. lol
Yes, that makes sense. It's not exactly the opposite of APPEND_SLASH though, because APPEND_SLASH will never return a redirect to a URL that removes a slash.
It's easier to:
rewrite ^(.+)/+$ $1 permanent;
In nginx.
Writing this for those that are building an API with Django and ended up here because of trailing slash issues.
When you write an API, the APPEND_SLASH
behavior is problematic because you don't always control what the client will request, and you don't want redirects (where you loose POST body) nor do you want 404s.
The solution is pretty simple in this case actually. You don't care about your URL being unique, you don't care about SEO: you're building an API. So you're fine with your URLs being accessible with both a slash and without it. Which would be an SEO issue (duplicate content) and the reason why APPEND_SLASH
was introduced. But you don't care, so no need to use the gist here and try to do the opposite (redirect slash urls to urls without a slash) either.
All you need to do is:
- have
APPEND_SLASH
set to False - Define a middleware that will add a slash internally to any request that don't end with one
- Define all your URLs with a trailing slash
def add_slash(get_response):
def middleware(request):
if not request.path.endswith('/'):
request.path_info = request.path = f"{request.path}/"
return get_response(request)
return middleware
MIDDLEWARE = [
# ...
'yourapp.middleware.add_slash',
# ...
]
Minor efficiency increase