Friday, November 6, 2009

Leveraging the Java Servlet API with Rhino

As I've previously state, I'm rather enamored of the JavaScript language, and I enjoy exploring its use in various contexts outside of the web browser. There's currently a large contingent of developers bent on exactly the same thing, particularly with respect to server-side web development. There are many projects, both old and new which attempt to use JavaScript productively on the server.

I've lately had the opportunity to explore this a bit myself. For my course in compilers, we're writing a compiler for a Domain Specific Language called WIG. I won't go into the specifics of what WIG is or what it does, but suffice it to say that my group has chosen to target Rhino, Mozilla's implementation of JavaScript on the Java platform. In this post, I'll attempt to sketch out how you might get started using Rhino to develop server-side web applications. I won't talk about WHY you might want to do this, as opposed to using, for example, pure Java. I think it's enough to say that JavaScript may be a very productive language, and the JVM may be a very productive environment, and so the union of the two is very intriguing.

To begin, it's important to note that there are roughly two ways to leverage Rhino on the server: via CGI, and via the Java Servlet API.

CGI



I don't have too much to say about this. The main thing you need to know in order to run Rhino as CGI is how to set it up to run with a shebang.

Here's an example of a minimal Rhino CGI script:


#!/usr/bin/env rhino

print("Hello world!");


After that, it's mostly a matter of setting up your web server to run .js files as CGI.

Servlets



Using Rhino to leverage the Java Servlet API was much more interesting to me. When I initially looked into this, I found an article that talked about using Rhino with servlets, but it worked only by using the context of a host Java application. I wanted to use pure JavaScript and stay completely away from Java, and I wasn't able to find too much information on how this might be done.

First, here's a tarball of the project in case you're interested in exploring my implementation: RhinoServlet.tar.gz

It's an Eclipse project, but it's driven by an ant build.xml script. Creating the build.xml script was a nontrivial part of the project, and so it's worth briefly examining. The build.xml script is responsible for setting up the classpath, compiling any Java code (there is none), compiling any JavaScript code (more on what this means in a moment), creating a WAR archive, and potentially deploying the WAR to a Tomcat server.

There are two JavaScript files in the project, TinyServlet.js, and TestServlet.js. TestServlet is very minimal, and TinyServlet aims to be a bit more complex. Both implement the Java Servlet API, and in fact, extend javax.servlet.http.HttpServlet. This is possible, thanks to the jsc tool bundled with Rhino, which compiles JavaScript to Java .class files. Each .js file will map to one top-level .class file, and potentially several other auxiliary classes or subclasses. jsc may be told that the generated top-level class should inherit from some other Java class, via the "-extends" argument. Likewise, the class generated from the .js file may implement one or more interfaces through jsc's "-implements" command-line argument. The best resource I found on extending JavaScript objects from existing Java classes in general may be found here. The best resource I found on using jsc to extend the top-level Java classes generated from the JavaScript may be found here.

For a .js file to inherit the servlet API by extending the javax.servlet.http.HttpServlet class, then, it must be compiled with a "-extends javax.servlet.http.HttpServlet" command-line argument, and javax.servlet.http.HttpServlet must be on the classpath. Ant does all of the heavy lifting, then, both setting up the classpath, and using jsc to compile with all appropriate command-line arguments.

Here's the relevant ant task that does this work:


<target name="compile-js" >
                <mkdir dir="${js-classdir}"/>
                <echo>Compiling ${targetjs}</echo>
                <java classname="org.mozilla.javascript.tools.jsc.Main" classpathref="project.class.path" >
                        <arg value="-extends"/>
                        <arg value="javax.servlet.http.HttpServlet"/>
                        <arg value="-g"/>
                        <arg value="-opt"/>
                        <arg value="-1"/>
                        <arg value="${targetjs}"/>
                </java>
                <move todir="${js-classdir}">
                        <fileset dir=".">
                                <include name="*.class"/>
                        </fileset>
                </move>
        </target>


TinyServlet.js, then is able to implement the servlet API functions in the global namespace. In this way, doPost, doGet, and the other familiar servlet API methods, will actually override those of the HttpServlet class. TinyServlet.js, then, becomes, in effect, a real subclass of HttpServlet, with no Java-language host context required.

TinyServlet.js then compiles to two class files, one of which is called TinyServlet.class and may be imported and used by other JVM classes. These classes may be put into a WAR using the standard ant war task, and then deployed to a server. There is nothing to indicate to the servlet container that the original language used was JavaScript and not Java.

All-in-all, I think this is pretty slick. There is one caveat which must be taken into account, however, which is that jsc will not compile JavaScript code that uses continuations. This limitation is not very well-documented, and certainly confused me when I first encountered it. This isn't a huge limitation, however, as closures still work very nicely.

Anyhow, I found this to be a very interesting exercise. At this moment, the project is ongoing. Now that the technical part is out of the way, we'll actually be able to focus on generating JavaScript code from a high-level DSL - I feel like the most exciting part is yet to come.

3 comments:

  1. This is really interesting stuff. I'm looking at plain old js in a new light now

    ReplyDelete
  2. Just wait, there's more to come :)

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete