Redirect Domain On Google App Engine

August 1, 2017
Redirect appspot.com to custom domain

Common use cases

  • Redirect projectid.appspot.com to www.mydomain.com.
  • Redirect (e.g. v2.mydomain.com) to www.mydomain.com
  • Redirect naked domain mydomain.com to www.mydomain.com

Redirect Naked Domain to www

This setup is the easiest without any coding involved.

At Google Cloud Console, goto App Engine > Settings > Custom Domains (or search Custom Domains at the top search bar).

Assuming you already setup custom domain for app engine and done your subdomain mapping correctly, you should be able to see this.

Custom Domain Mapping

If the above is configured correctly, it should redirect http://mydomain.com -> http://www.mydomain.com. Else, refer to add custom domain to app engine to reconfigure again.

Sadly this method wouldn’t work in redirecting from a subdomain to another subdomain. Both subdomains will show the same website without redirection taking place.

Redirect appspot.com to custom domain

There are a few methods to achieve this, depending on your needs. These methods can also be used to redirect subdomains.

You might be wondering would it be possible to conigure domain redirection with app.yaml? The answer is no, as app.yaml only handles path redirection.

Redirect Request Handler

Create redirect.py Python script to handle http requests. The script will listen to all requests to detect if the host is appspot.com. If appspot.com if detected, it will redirect the request to the correct domain.

import webapp2


class MainPage(webapp2.RequestHandler):
    def get(self):
        if self.request.host.startswith('projectid.appspot.com'):
            url = self.request.url.replace(self.request.host, 'www.mydomain.com')
            # assume no SSL support yet
            if url.startswith("https://"):
                url = url.replace("https://", "http://")
            return self.redirect(url, True)
        else:
            self.response.headers['Content-Type'] = 'text/plain'
            self.response.write("Hello, World at {0}".format(self.request.path))


app = webapp2.WSGIApplication([
    ('/.*', MainPage),
], debug=True)

Edit app.yaml to enable redirect.py

runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /.*
  script: redirect.app

This solution is suitable for most use cases, unless you don’t plan to run server code to handle every requests (e.g. using App Engine to host static website). In that case, you can explore the following solutions.

Request routing with dispatch.yaml

Create an App Engine Service. Assuming you created a new project in hello-service directory and the service is named micro.

Go to the original application directory (not the newly created service), assuming created in the hello-app directory.
Create a dispatch file (dispatch.yaml). This service will handle all requests using the appspot domain.

dispatch:
  - url: "projectid.appspot.com/*"
    service: micro

Deploy the dispatch file.

gcloud app deploy dispatch.yaml
# output
Configurations to update:

descriptor:      [/projects/hello-app/dispatch.yaml]
type:            [routing rules]
target project:  [projectid]

Now test the following:

  • Visit https://micro-dot-projectid.appspot.com/ and you should see I am a micro service (The new service is working!)
  • Visit https://projectid.appspot.com/ and you should see I am a micro service (The routing is working!)
  • Visit http://code.luasoftware.com/ and you should see your original website (The routing is working perfectly!)

Server code 301 redirect

We need to configure the new micro service to redirect all requests to www.mydomain.com. Goto hello-service directory and edit hello-service/main.py

import webapp2


class MainPage(webapp2.RequestHandler):
    def get(self):
        # do this to redirect all version and services: e.g. https://micro-dot-projectid.appspot.com/
        # if self.request.host.endswith('.projectid.appspot.com'):

        # do this to redirect the default service and version only
        if self.request.host == 'projectid.appspot.com':
            url = self.request.url.replace(self.request.host, 'www.mydomain.com')
            is_ssl = url.startswith("https://")
            # assume no SSL support yet
            if is_ssl:
                url = url.replace("https://", "http://")
            return self.redirect(url, True)
        else:
            self.response.headers['Content-Type'] = 'text/plain'
            self.response.write("I am a micro service at {0}".format(self.request.path))


app = webapp2.WSGIApplication([
    ('/.*', MainPage),
], debug=True)

Deploy the micro service.

# at hello-service
gcloud app deploy -v 1

Visit https://projectid.appspot.com/ and it shall be redirected to http://www.mydomain.com.

This solution of micro service is independent of the default application, but the drawbacks is higher instance cost. Whenever someone try to access https://projectid.appspot.com/, the App Engine will startup micro instance to handle the request (separate from the default instance). If more requests trigger the needs for redirection, the higher the instance cost shall be; if the request is low, the cost is negligible.

By default, the service will run Automatic Scaling (free quota of 28h per day). Instances are created on demand and automatically shutdown when idle. I would recommend the following app.yaml scaling settings to incur minimum cost.

# settings assuming extremely low usage
automatic_scaling:
  min_idle_instances: 0
  max_idle_instances: 1
  min_pending_latency: 500ms  # default 30ms
  max_pending_latency: 1000ms
  max_concurrent_requests: 80 # default 8

HTML Redirect

Instead of redirecting using server code, you can perform HTML redirect using static HTML files instead using the above method.

Create index.html with the following content.

<!DOCTYPE HTML>
<html lang="en-US">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="refresh" content="1; url=http://www.mydomain.com">
    <script type="text/javascript">
        window.location.href = "http://www.mydomain.com"
    </script>
    <title>Redirect: MyDomain</title>
  </head>
  <body>
      If you are not redirected automatically, follow this <a href='http://www.mydomain.com'>link</a>.
  </body>
</html>

Edit app.yaml with the following changes.

#handlers:
#- url: /.*
#  script: main.app

handlers:
- url: /(.*)
  static_files: index.html
  upload: index.html

The disadvantage of this method is that it doesn’t redirect with full original path. For example, https://projectid.appspot.com/test will be redirected to http://www.mydomain.com (missing /test path).

Javascript redirect with full path

Edit index.html with the following content.

<!DOCTYPE HTML>
<html lang="en-US">
  <head>
    <meta charset="UTF-8">
    <script type="text/javascript">
        window.location.href = "http://www.mydomain.com" + (location.pathname+location.search).substr(1)
    </script>
    <title>Redirect: MyDomain</title>
  </head>
  <body>
    Redirecting ... 
    <noscript>
      You browser doesn't support JavaScript, please follow this <a href='http://www.mydomain.com'>link</a>
    </noscript>
  </body>
</html>

Javascript allow redirecting with full original path and query string, though the browser needs to support and enable JavaScript.

Does serving static files save instance cost?

Why go through all the trouble to switch working Server Code to JavaScript and HTML for redirection purpose? I am guessing static contents like HTML on App Engine might not spin up an instance to handle these requests (as these contents are hosted on external more CDN-friendly servers). I tested multiple redirection using index.html on micro service and observed no instances being startup (Google Clound Engine > App Engine > Instances).

Conclusion: using HTML + Javascript for redirection would save instance cost.

Is JavaScript redirect SEO-friendly?

Basically Google suggest 301 redirects is the recommended ways to perform redirect. I assume JavaScript redirect stands pretty neutral in terms of SEO as long as it’s not abused.

If this is a new project and projectid.appspot.com has not been exposed to any search engine yet, then it’s not necessary to perform any redirection. We just have to make sure no contents are served using projectid.appspot.com, thus no pages shall be leaked to any search engine. We can use request routing with dispatch.yaml method to serve empty HTML for projectid.appspot.com, and put it robots.txt to disable crawling.

If the pages of projectid.appspot.com have been exposed to search engine for a long time or we are migrating to a different domain or subdomain, then using request routing with dispatch.yaml + server code 301 redirect is probably a good choice. If the cost of using App Engine redirect is too high, we can consider hosting a cheap server to perform the redirect (I wonder is there a service or product which handle redirection of domain pages).

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.