14

How can I prevent a user from accessing my app at example.appspot.com and force them to access it at example.com? I already have example.com working, but I don't want users to be able to access the appspot domain. I'm using python.

Joshua Patton
  • 462
  • 4
  • 19

5 Answers5

17

You can check if os.environ['HTTP_HOST'].endswith('.appspot.com') -- if so, then you're serving from something.appspot.com and can send a redirect, or otherwise alter your behavior as desired.

You could deploy this check-and-redirect-if-needed (or other behavior alteration of your choice) in any of various ways (decorators, WSGI middleware, inheritance from an intermediate base class of yours that subclasses webapp.RequestHandler [[or whatever other base handler class you're currently using]] and method names different than get and post in your application-level handler classes, and others yet) but I think that the key idea here is that os.environ is set by the app engine framework according to CGI standards and so you can rely on those standards (similarly WSGI builds its own environment based on the values it picks up from os.environ).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
7

The code posted above has two problems - it tries to redirect secure traffic (which isn't supported on custom domains), and also your cron jobs will fail when Google call them on your appspot domain and you serve up a 301.

I posted a slightly modified version to my blog: http://blog.dantup.com/2009/12/redirecting-requests-from-appid-appspot-com-to-a-custom-domain

I've included the code below for convenience.

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

def run_app(url_mapping):
    application = webapp.WSGIApplication(url_mapping, debug=True)
    application = redirect_from_appspot(application)
    run_wsgi_app(application)

def redirect_from_appspot(wsgi_app):
    """Handle redirect to my domain if called from appspot (and not SSL)"""
    from_server = "dantup-blog.appspot.com"
    to_server = "blog.dantup.com"

    def redirect_if_needed(env, start_response):

        # If we're calling on the appspot address, and we're not SSL (SSL only works on appspot)
        if env["HTTP_HOST"].endswith(from_server) and env["HTTPS"] == "off":

            # Parse the URL
            import webob, urlparse
            request = webob.Request(env)
            scheme, netloc, path, query, fragment = urlparse.urlsplit(request.url)
            url = urlparse.urlunsplit([scheme, to_server, path, query, fragment])

            # Exclude /admin calls, since they're used by Cron, TaskQueues and will fail if they return a redirect
            if not path.startswith('/admin'):
                # Send redirect
                start_response("301 Moved Permanently", [("Location", url)])
                return ["301 Moved Peramanently", "Click Here %s" % url]

        # Else, we return normally
        return wsgi_app(env, start_response)

    return redirect_if_needed
Danny Tuppeny
  • 40,147
  • 24
  • 151
  • 275
  • 2
    Thanks, deployed your code (+1). I wonder why Google doesn't offer built-in solution for this, since they usually encourages good practices such as url canonicalization. – jholster Apr 25 '10 at 08:21
  • I wondered the same thing. It's actually quite annoying having to put this code into all my apps (and change the domains) :-( – Danny Tuppeny May 02 '10 at 16:57
3
def redirect_from_appspot(wsgi_app):
def redirect_if_needed(env, start_response):
    if env["HTTP_HOST"].startswith('my_app_name.appspot.com'):
        import webob, urlparse
        request = webob.Request(env)
        scheme, netloc, path, query, fragment = urlparse.urlsplit(request.url)
        url = urlparse.urlunsplit([scheme, 'www.my_domain.com', path, query, fragment])
        start_response('301 Moved Permanently', [('Location', url)])
        return ["301 Moved Peramanently",
              "Click Here" % url]
    else:
        return wsgi_app(env, start_response)
return redirect_if_needed  
Niklas Rosencrantz
  • 25,640
  • 75
  • 229
  • 424
1

You can redirect it to your custom domain (here example.com) by overriding the initialize method in your webapp2.RequestHandler base class the following way :

def initialize(self, *a, **kw):
    webapp2.RequestHandler.initialize(self, *a, **kw)

    if self.request.host.endswith('appspot.com'):
        self.request.host = 'example.com'
        self.redirect(self.request.url, permanent=True)
lendenmc
  • 350
  • 3
  • 11
0

In case you are using webapp2, a slightly easier (depending on your use case) solution is possible using its DomainRoute.

If this is your URL map:

url_map = [
    ('/', HomeHandler),
    ('/about', AboutHandler),
    ('/admin/(.+)', AdminHandler),
    MailForwardHandler.mapping(),
]
application = webapp2.WSGIApplication(url_map, debug=True)

and the admin and mail should not redirect, then add the redirect routes as follows:

from webapp2_extras import routes

url_map = [
    routes.DomainRoute('appid.appspot.com', [
        routes.RedirectRoute('/', redirect_to='http://app.com', schemes=['http']),
        routes.RedirectRoute('/about', redirect_to='http://app.com/about', schemes=['http']),
    ],
    ('/', HomeHandler),
    ('/about', AboutHandler),
    ('/admin/(.+)', AdminHandler),
    MailForwardHandler.mapping(),
]
application = webapp2.WSGIApplication(url_map, debug=True)

This will, as specified, only redirect http, not https. It uses a permanent redirect (301), as desired.

You can also pass a function to redirect_to, as described in the documentation, which should make it easier when you have lots of rules.

JW.
  • 2,081
  • 1
  • 20
  • 24