Flask 301 Response

Nalum picture Nalum · Jan 10, 2014 · Viewed 14.6k times · Source

My flask app is doing a 301 redirect for one of the urls.

The traceback in New Relic is:

Traceback (most recent call last):
  File "/var/www/app/env/local/lib/python2.7/site-packages/flask/app.py", line 1358, in full_dispatch_request
    rv = self.dispatch_request()
  File "/var/www/app/env/local/lib/python2.7/site-packages/flask/app.py", line 1336, in dispatch_request
    self.raise_routing_exception(req)
  File "/var/www/app/env/local/lib/python2.7/site-packages/flask/app.py", line 1319, in raise_routing_exception
    raise request.routing_exception
RequestRedirect: 301: Moved Permanently

It doesn't look like it is even hitting my code or rather the traceback isn't showing any of my files in it. At one point I did have Nginx redirect all non SSL request to HTTPS but had to disable that as Varnish was not able to make the request to port 443 with out an error... probably some configuration that I did or didn't make.

It doesn't always return a 301 though, I can request the URL and get it without any trouble. But someone out in the world requesting the URL is getting a 301 response.

It is a GET request with some custom headers to link it to the account.

At no point in my code is there a 301 redirect.

Answer

Martijn Pieters picture Martijn Pieters · Jan 10, 2014

The traceback shows that it was the route matching that raised a redirect; usually (e.g. unless you added explicit redirect routes), that means the client tried to access a branch URL (one that ends with a trailing slash), but the requested URL did not include the last slash. The client is simply being redirected to the canonical branch URL with the slash.

From the Werkzeug Rule documentation:

URL rules that end with a slash are branch URLs, others are leaves. If you have strict_slashes enabled (which is the default), all branch URLs that are matched without a trailing slash will trigger a redirect to the same URL with the missing slash appended.

From the routing documentation:

Flask’s URL rules are based on Werkzeug’s routing module. The idea behind that module is to ensure beautiful and unique URLs based on precedents laid down by Apache and earlier HTTP servers.

Take these two rules:

@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/about')
def about():
    return 'The about page'

Though they look rather similar, they differ in their use of the trailing slash in the URL definition. In the first case, the canonical URL for the projects endpoint has a trailing slash. In that sense, it is similar to a folder on a file system. Accessing it without a trailing slash will cause Flask to redirect to the canonical URL with the trailing slash.

In the second case, however, the URL is defined without a trailing slash, rather like the pathname of a file on UNIX-like systems. Accessing the URL with a trailing slash will produce a 404 “Not Found” error.

This behavior allows relative URLs to continue working even if the trailing slash is ommited, consistent with how Apache and other servers work. Also, the URLs will stay unique, which helps search engines avoid indexing the same page twice.

As documented, if you do not want this behaviour (and have the url without the trailing slash be a 404 Not Found instead), you must set the strict_slashes=False option on your route.