12

I've got a Python Flask app running on Heroku (Cedar stack) with two custom domains (one with and one without the www subdomain). I'd like to redirect all incoming requests to the www. version of the resource requested (the inverse of this question). I think I need some WSGI middleware for this but I can't find a good example.

How do I do this?

Community
  • 1
  • 1
John Sheehan
  • 77,456
  • 30
  • 160
  • 194
  • To clarify, you have `example1.com`, `example2.com` and `www.example2.com`. You want all requests to `example1.com` and `example2.com` to be redirected to `www.example2.com`. Is that correct? – Burhan Khalid Mar 19 '12 at 06:58
  • just example.com and www.example.com with example.com/ redirecting to www.example.com/ – John Sheehan Mar 19 '12 at 06:59
  • Couldn't you just do this with DNS? Why do you want to do that at the application layer? – Troy Howard Mar 19 '12 at 17:36

5 Answers5

18

An easier solution than to create a separate Heroku app would be a before_request function.

from urllib.parse import urlparse, urlunparse

@app.before_request
def redirect_nonwww():
    """Redirect non-www requests to www."""
    urlparts = urlparse(request.url)
    if urlparts.netloc == 'example.com':
        urlparts_list = list(urlparts)
        urlparts_list[1] = 'www.example.com'
        return redirect(urlunparse(urlparts_list), code=301)

This will redirect all non-www requests to www using a "HTTP 301 Moved Permanently" response.

Stephen Fuhry
  • 12,624
  • 6
  • 56
  • 55
Danilo Bargen
  • 18,626
  • 15
  • 91
  • 127
  • 1
    I did some something similar, but instead of replacing an index, you can use `urlparts = urlparts._replace(netloc='www.example.com')` and `urlunparse(urlparts)` – Allan Lei Jul 07 '15 at 09:29
  • 2
    for python3: replace `from urlparse import urlparse, urlunparse` with `from urllib.parse import urlparse, urlunparse` – aflaisler Jul 18 '18 at 00:30
  • The best would be to organise the redirect natively with your dns provider with an A ALIAS for example – aflaisler Jul 18 '18 at 00:51
  • @aflaisler DNS doesn't change the hostname sent by the client in the HTTP request. That's what this is for. – ryan Aug 02 '21 at 00:31
3

According to the Heroku Docs, you've got the right idea about using the www subdomain (eg www.foo.com) vs apex domain (eg foo.com). Their suggestion for dealing with this is to use a DNS layer redirect:

To quote:

Subdomain redirection

Subdomain redirection results in a 301 permanent redirect to the specified subdomain for all requests to the apex domain so all current and future requests are properly routed and the full www hostname is displayed in the user’s location field.

Almost all DNS providers offer domain redirection services - sometimes also called domain forwarding. DNSimple provides a convenient URL redirect seen here redirecting from the heroku-sslendpoint.com apex domain to the www.heroku-sslendpoint.com subdomain.

Source: http://devcenter.heroku.com/articles/avoiding-apex-domains-dns-arecords#subdomain_redirection

Hope that helps!

Troy Howard
  • 2,612
  • 21
  • 25
1

One possible approach would be to add a function to listen on request_started, and do the appropriate redirection.

This signal is sent before any request processing started but when the request context was set up. Because the request context is already bound, the subscriber can access the request with the standard global proxies such as request.

Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284
  • looks promising. not sure how i would issue a response from there yet, but looking into it. – John Sheehan Mar 19 '12 at 08:10
  • Initially I thought you'd need to mess with werkzeug but its not as straight forward as the flask hooks into it. If the signals don't work, you can always try your hand at some [werkzeug middleware](http://werkzeug.pocoo.org/docs/middlewares/). – Burhan Khalid Mar 19 '12 at 08:14
0

What I ended up doing was creating a second Heroku app, assigning the non-www hostname to that one and using a catch all Flask route to redirect to the www version keeping the path intact.

John Sheehan
  • 77,456
  • 30
  • 160
  • 194
0

Try this:

@app.route('/', methods=['GET', 'POST'], subdomain="www")
@app.route('/<string:path>', methods=['GET', 'POST'], subdomain="www")
def www(path=''):
    return redirect(url_for('app.index')+path)
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129