google-app-enginehttp-redirectgoogle-cloud-platformweb2pycanonicalization

Canonicalize URLs for static site with custom domain in GAE


I'm running a static site on GAE and using a custom domain (let's call it example.com) with SSL certificates enabled. I'd like to canonicalize URLs to https://www.example.com/. That means catching any requests to myproject.appspot.com, plain HTTP, and/or the naked domain, and redirecting to www over HTTPS.

I understand that it's not possible to put redirect logic in app.yaml, but ideally I'd like to keep the static file serving logic there, and only have app code for the redirect. (As opposed to doing the static serving in app code as well.)

Here's what I have so far:

Contents of the file app.yaml:

runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /
  static_files: www/index.html
  upload: www/index.html

- url: /(.*)
  static_files: www/\1
  upload: www/(.*)

Contents of the file dispatch.yaml:

dispatch:
- url: "myproject.appspot.com/*"
  module: canonicalizer

Contents of the file canonicalizer.yaml:

module: canonicalizer
runtime: python27
api_version: 1
threadsafe: true

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

Contents of the file canonicalizer.py:

import webapp2

def get_redirect_uri(handler, *args, **kwargs):
    return 'https://www.example.com/' + kwargs.get('path')

app = webapp2.WSGIApplication([
    webapp2.Route('/<path:.*>',
    webapp2.RedirectHandler,
    defaults={'_uri': get_redirect_uri, '_code': 302}),
], debug=True)

As you can see, I've only attempted to implement redirecting myproject.appspot.com so far. I haven't been able to get it working; myproject.appspot.com still serves content rather than redirecting to the custom domain.

I saw a similar SO question and used it as a basis for my code above. I followed it fairly closely, so I'm not sure if it's outdated or missing details.

I'm not very familiar with webapp2. Also open to solutions in a different framework or even different programming language.


Solution

  • As sllopis said in their answer, an HTTP to HTTPS redirect can be implemented via a secure: always element.

    The rest of what I wanted to do needed to be done in app code. The code in my answer was on the right track, but I had some confusion about how services work in GAE and about dispatch.yaml. Here's my final code:

    <application root>/app.yaml

    runtime: python27
    api_version: 1
    threadsafe: true
    
    handlers:
    - url: /
      static_files: www/index.html
      upload: www/index.html
      secure: always
      redirect_http_response_code: 301
    
    - url: /(.*)
      static_files: www/\1
      upload: www/(.*)
      secure: always
      redirect_http_response_code: 301
    

    <application root>/dispatch.yaml

    dispatch:
    - url: "*.appspot.com/*"
      service: canonicalizer
    
    - url: "example.com/*"
      service: canonicalizer
    

    <application root>/canonicalizer/app.yaml

    service: canonicalizer
    runtime: python27
    api_version: 1
    threadsafe: true
    
    handlers:
    - url: /.*
      script: canonicalizer.app
    

    <application root>/canonicalizer/canonicalizer.py

    import webapp2
    
    def get_redirect_uri(handler, *args, **kwargs):
        return 'https://www.justinforcentral.com/' + kwargs.get('path')
    
    app = webapp2.WSGIApplication([
        webapp2.Route('/<path:.*>',
        webapp2.RedirectHandler,
        defaults={'_uri': get_redirect_uri, '_code': 301}),
    ], debug=False)
    

    This allows all the redirects to be done while still maintaining the ability to route the static site via static_files handlers.

    As an aside, I also didn't realize that simply doing gcloud app deploy . from the application root only deploys the default service. To deploy this whole thing I had to run gcloud app deploy . dispatch.yaml canonicalizer.