Wednesday, January 4, 2012

Hosting static sites using Play! and Heroku

I used to rent a virtual private server to host some blogs and static sites. Last year I hardly had any time to manage it and I also noticed a couple of times my server was not running. So, time to get rid of it and put everything in the cloud. The blogs were moved to Blogger, but what to do with my static sites?
I thought it would be fairly easy to create a Play! application for this and host it on Heroku. And ... it was!

Play! is a web framework with which you can very fast and easy build a web application, And Play! supports both Java and Scala!
Heroku is a Cloud Application Platform on which you can host your Python or Java application and they also have support for Play!.

Getting started
First, install Play! On a Mac, just install Homebrew and run:

brew install play

To create a new Play! application, run:

play new <appname>

This creates the whole appliction. Just start 'play run' and get rockin'.

Static sites location
Each static sites is put in it's own folder in the 'sites' folder in the root folder of the application. To be able to host static sites for multiple domain names, each folder has the fully qualified domain name of the site. E.g. 'www.mysite.org'

The application
The main application is in /app/controllers/Application.java. The default index method has been changed to accept both the domain name and the path. A File is constructed using these and then rendered binary so this also works for images and other binary files. If the file does not exist, a 404 - Not Found is returned.

public class Application extends Controller {

  private static final Logger LOGGER = Logger.getLogger(Application.class);

  public static final String SITES = "sites/";

  public static void index(String domainname, String path) {
    if("".equals(path)) {
      path = "/index.html";
    }
    LOGGER.debug("Request for domainname:"+domainname+" and path:"+path);

    File f = new File(SITES+domainname+"/"+path);
    if(!f.exists()) {
      if(!"favicon.ico".equals(f.getName())) {
        LOGGER.info("File not found: "+ f.getAbsolutePath());
      }
      notFound();
    }
    LOGGER.debug("Serving: "+f.getAbsolutePath());
    renderBinary(f);
  }
}

Routes configuration
In Play! a routes configuration defines how a url is mapped to a application method. For this application all requests have to route to the 'Application.index' method. It's also possible to capture values from the url and map these to method parameters. The index method takes both the domain name and path, so the route must map these. The name of the variables in the route must match the parameter names in the Java code.

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Ignore favicon requests
GET     /favicon.ico                            404

# All request
GET  {site}/{<.*>path}                          Application.index

A special regex is used to match all paths since {path} would only match '/index.html', but not '/dir/other.html' or '/images/logos/company/logo.gif'. {<.*>path} will match anything to the 'path' parameter.

{site} matches the fully qualified domain name. It would also be possible to use '{site}.mydomain.com' if you just wanted to match the subdomain name.

Note: even though this Virtual Hosts feature was introduced in Play! 1.1, it didn't work in Play! 1.2.3. So make sure you're using Play! 1.2.4 in which this feature does work. There is no need to install an additional module for this.

Deploying on Heroku
Deploying a Play! application on Heroku is extreemly easy. Details can be found on the Heroku Dev Center site, but it basically comes down to this:

# Setting up Heroku
- Create an account on Heroku
- Install Heroku app locally
- heroku login

# Create a Git repository
- git init
- git commit -am 'Init'

# Create a Heroku stack
- heroku create --stack cedar

# Deploy on Heroku
- git push heroku master

... and your done! Your application now runs in the cloud.

The next step is to add the 'Custom Domains' add-on to your application using the 'My Apps' administration of Heroku and add all the domain names of your static sites so Heroku will serve requests for these domain names.

DNS
The final step is to change your DNS settings and create a CNAME for each of your static sites and map these to the url of your application on Heroku.

The code
This code for this Play! app is available in this Git repository. If you're using this, you can skip the 'create a Git repository' step described in 'Deploying on Heroku'.