Tuesday, May 10, 2011

Groovy on Grails: Serving static files from the root context

Grails is a great platform for web development, but there are certain things that are fairly trivial to do in a production environment that are rather difficult in a development environment. One of those is serving up static files from the root of your host name. These could be anything, like robots.txt or crossdomain.xml. It's simple enough to do this with Tomcat. Simply configure your ROOT context, drop in the files and you're done.

But how do you do this in a development environment? Generally, a developer will use the grails run-app command. This invokes a built-in instance of Jetty, which makes your Grails application available at the URL http://localhost:8080/yourApp/. It's easy enough to serve static files from below the yourApp directory. Simply drop them in the web-app folder in your Grails project, but there isn't any way to serve static files from above this.

The problem is that Grails is not designed to work this way. Many of the solutions I found online try to work against Grails' design, such as hacking at the internals directly, or forcing the configuration to serve everything from the root context. These have potential problems when it comes to things like upgrading Grails, or deploying to production.

Instead, I opted to find an external mechanism to serve these static files. This way the Grails application can remain untethered from the rest of its environment. It doesn't need to be aware of what's going on outside its context, its realm of control. This can be accomplished with Apache and mod_proxy. We can use Apache to serve documents from the root, then we can use mod_proxy to delegate any requests from a subdirectory to the Jetty instance used by Grails.

1. Install Apache httpd 2.2

The specifics of installing httpd are beyond the scope of this blog post. If your operating system has a packaging system (apt-get, yum, etc.), use that. If not, refer to the available Apache documentation.


For the rest of this tutorial, I will be assuming use of a Linux system.

Start httpd.
sudo /sbin/service httpd start
Test that it is installed correctly using a web browser.

2. Create a root directory

Pick a location on your disk where you will keep your static files. This will be the document root for httpd. I will be using /var/grails_root.
mkdir /var/grails_root
touch /var/grails_root/crossdomain.xml
3. Create a VirtualHost in httpd.conf

Open httpd.conf in your favorite text editor.
vim /etc/httpd/conf/httpd.conf
Pick your favorite port, and create a virtual host on that port. I will be using 9090, but any port will do.

Add these lines to httpd.conf
Listen 9090
<VirtualHost *:9090>
    DocumentRoot "/var/grails_root"
    <Directory "/var/grails_root">
        Allow from all
    </Directory>
</VirtualHost>
Restart httpd
sudo /sbin/service httpd restart
Test that you are now able to access the static files in your document root directory. If not, you will need to fix this before moving on to the next step.

4. Enable mod_proxy and mod_proxy_http

You need to load both of these modules. mod_proxy has the base functionality for proxying, and the mod_proxy_xxx modules have information specific to a protocol. They ship standard with httpd 2.2, so you shouldn't need to install anything extra.

Add these lines to httpd.conf
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
Now modify the virtual host you set up in the previous step. (You can omit the comments)
<VirtualHost *:9090>
    DocumentRoot "/var/grails_root"
    <Directory "/var/grails_root">
        Allow from all
    </Directory>
# New lines start here
    ProxyRequests Off
    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>
    ProxyPass /grailsApp http://your.grails.server:8080/grailsApp
# New lines end here
</VirtualHost>
Restart httpd
sudo /sbin/service httpd restart
Now, you should be able to access both your static files and your Grails app via port 9090.

More information on this can be found here.