If you're running your app engine project on a custom domain (like this blog), you're probably not so happy that people can still access your app at http://appid.appspot.com.

When I started working on my blog, I realised this might be an issue, and did some investigating into how I could stop it. I found solution on SackOverflow that seemed to do what I needed, so I set it up and got it working.

Not long after implementing this code, I found a few problems:

  • SSL is not supported on custom domains
  • Cron jobs fail when Google invokes them with an appspot.com address and you serve a 301

So with some tweaks, I've managed to get this working as required. The only annoyance is the hard-coded '/admin' check. This is to support cron jobs, task queues etc., which are all protected ("login: admin" in app.yaml). They must work with an appspot.com address, because Google doesn't seem to follow the redirect when invoking them. It's possible you could do an IP address check here, but I'm not sure how consistent cron/task queue IP addresses are.

The code is called like this:

def main():
	dantup.run_app([
		("/\d*", RootHandler),
		("/feed", FeedHandler),
		("/tags/.*", TagHandler),
		("/archive/.*", ArchiveHandler),
		("/.*", PostHandler)
	])

And dantup.py looks like this:

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.me.uk"

	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

Hopefully you may find this useful. If you encounter any problems with it, please let me know!