Sunday, 10 May 2009

Clojure on the Google App Engine

The Google App Engine offers a complete stack for deploying applications in the cloud. Initially, support only existed for Python, but recently support was announced for Java.

Although, the main page announces this as support for the Java Language, it's much more than that, it's support for the Java Virtual Machine. The list of languages on the JVM is huge. This means that in theory, any of these languages can now be hosted in the cloud.

So how easy is it to get Clojure going in the cloud?

Firstly, register at http://appengine.google.com and get yourself an account. Download the Java AppEngine SDK too and unpack that and get the development server up and running.

GAE is based on the Servlet 2.5 specification, so the typical directory structure looks very similar to any JSP/Servlet type application:


  • Root - Top level directory for the project
  • Root/src - Source code
  • Root/war - JSP files and HTML artefacts
  • Root/war/WEB-INF -Application configuration files
  • Root/war/WEB-INF/classes - Compiled source files
  • Root/war/WEB-INF/lib - Deployment time dependencies


As GAE is based on servlets, we need to define a simple servlet for the mandatory hello world demo! This code goes in the src directory:


(ns helloclojure.servlet
(:gen-class :extends javax.servlet.http.HttpServlet))

(defn -doGet
[_ request response]
(let [w (.getWriter response)]
(.println w "Hello world!")))


:gen-class causes Clojure to emit a class file representing this name space. The - before doGet indicates that this is a member function with three arguments (the first representing "this", unused and therefore a _ symbol). So all we do for this servlet is write "hello world" whenever any request is made.

Next we need to make a standard web.xml descriptor and put that in META-INF. This registers the servlet and specifies the mapping between a URL format and the servlet that deals with the request.



<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
<servlet>
<servlet-name>helloclojure</servlet-name>
<servlet-class>helloclojure.servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>helloclojure</servlet-name>
<url-pattern>/helloclojure</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>


Also in the META-INF directory we need a descriptor for the GAE.



<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application></application>
<version>1</version>
</appengine-web-app>


That's all the scaffolding you need. Finally, we need some way to build and deploy this. Thankfully, someone has already looked at this, so I took their build script and made a few modifications (remove the test target for example) in the name of simplification.

The net result is the following Ant script.


<project name="helloclojure" basedir="." default="compile">

<property environment="env" />
<property name="sdk.dir" location="/home/jfoster/appengine-java-sdk-1.2.0" />
<property name="classes.dir" value="war/WEB-INF/classes" />
<property name="lib.dir" value="war/WEB-INF/lib" />
<property name="src.dir" value="src/" />

<import file="${sdk.dir}/config/user/ant-macros.xml"/>

<path id="project.classpath">
<pathelement path="${classes.dir}" />
<pathelement path="${src.dir}/helloworld" />
<fileset dir="${lib.dir}">
<include name="**/*.jar" />
</fileset>
<fileset dir="${sdk.dir}/lib">
<include name="**/*.jar" />
</fileset>
</path>

<target name="clean">
<delete dir="${classes.dir}" />
</target>

<target name="init">
<mkdir dir="${classes.dir}" />
</target>

<target name="compile" depends="clean,init">
<java classname="clojure.lang.Compile" classpathref="project.classpath" failonerror="true">
<classpath path="${src.dir}" />
<sysproperty key="clojure.compile.path" value="${classes.dir}" />
<arg value="helloclojure.servlet" />
</java>
</target>

<target name="devserver" description="run local dev appserver" depends="compile">
<dev_appserver war="war" />
</target>

<target name="deploy" description="deploy to appspot" depends="compile">
<appcfg action="update" war="war" />
</target>

</project>


All code is available on my Git Repo