Server-side JavaScript with Rhino

I've only made limited use of the Java runtime for App Engine so far: The two runtimes are largely equivalent in terms of features, and my personal preference is for Python. There are, however, a few really cool things that you can only do on the Java runtime. One of them is embedding an interpreter for a scripting language.

Rhino is a JavaScript interpreter implemented in Java. It supports sandboxing, meaning you can create an environment in which it's safe to execute user-provided code, and it allows you to expose arbitrary Java objects to the JavaScript runtime, meaning you can easily provide interfaces with which the JavaScript code can carry out whatever actions it's designed to carry out.

Rhino also supports several rather cool additional features. You can set callback functions that count the number of operations executed by the JavaScript code, and terminate it if it takes too long, you can serialize a context or individual objects and deserialize them later, and you can even use continuations to pause execution when waiting for data, and pick it up again later - possibly even in another request! Hopefully we'll show off some of these features in future posts.

You may still be wondering how useful server-side JavaScript can be in an App Engine app. Here's a few example use-cases:

  • Allow users to provide custom code to be executed when conditions are met in your app.
  • A tool for writing straightforward RESTful APIs for text mainpulation etc.
  • A programming game, allowing people to submit programs that compete against each other for rankings.
  • A CodeWiki, where users can enter code and demonstrate the result of executing it.
  • A Google Wave bot that executes code entered in a Wave and adds a response with the result.
  • A tool for performing operations over the entire datastore without having to explicitly cater for breaking tasks up into smaller chunks.

That, of course, is just a few ideas - I'm sure you can think of others yourself. Let us know in the comments if you think of anything interesting!

Implementation

Using Rhino is surprisingly simple. Create a new Web Application Project, select App Engine, and unselect GWT. I've called mine 'rhinopark'. Open index.html in the war directory, and replace it with the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script> <script type="text/javascript"> function sendCode() { var code = $("#code").val(); $("#output").load("/rhinopark", {"code": code}); } </script> <title>Server-side JavaScript demo</title> </head> <body> <h1>Server-side JavaScript demo</h1> <pre style="border: 1px solid black; width: 590px; padding: 5px; margin-bottom: 16px;" id="output"></pre> <textarea id="code" style="display: block; width: 600px; height: 300px;"></textarea> <input type="button" value="Submit" onclick="sendCode();" /> </body> </html>

This is all very straightforward. We provide a textarea for users to enter code in, and a submit button. Instead of a regular form submission, however, we use JQuery to send the code to the /rhinopark URL on the server, and insert the response in the 'output' element.

Next, you need to download Rhino. Extract the file 'js.jar' from the downloaded zip, and copy it to the war/WEB-INF/lib subdirectory of your project. Right-click on your project's entry, and select "Build Path -> Configure Build Path". Then, click on Add JARs..., and select js.jar from the directory you just placed it in.

Then, open the RhinoParkServlet that was created automatically, and replace the body of the class with the following:

public class RhinoparkServlet extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/plain"); String code = req.getParameter("code"); Context ctx = Context.enter(); try { Scriptable scope = ctx.initStandardObjects(); Object result = ctx.evaluateString(scope, code, "<code>", 1, null); resp.getWriter().print(Context.toString(result)); } catch(RhinoException ex) { resp.getWriter().println(ex.getMessage()); } finally { Context.exit(); } } }

This snippet illustrates the basic steps of using Rhino. First, we call Context.enter(). This returns a Context object, which we will need in order to use Rhino, as well as setting the thread's context to the returned object. The rest of the code is wrapped in a try/finally block, to ensure that Context.exit() gets called once we're done.

Next, we call initStandardObjects() on our Context. This creates a Scriptable we can use as a global scope, containing the basic objects that JavaScript scripts expect to find in its environment.

Then, we call evaluateString on the context, passing in the scope we created, the code to execute, a name and line number for the module, and an optional security manager we don't need one, so we leave it as null. This actually executes the code, returning the result. We stringify the result, and write it to the response. If the Javascript has an error, Rhino will throw a RhinoException when we call evaluateString. In that case, we catch the exception and return it in the response instead.

Try it out - start the development server, and go to http://localhost:8080/. Enter a snippet of JavaScript and click 'Submit' to see it evaluated and returned.

Sandboxing

All isn't quite well yet, though. Try entering the following snippet in the console:

new java.lang.String("foo")

Rhino supports a JavaScript feature called LiveConnect, which assists interoperation between Java and JavaScript by making it possible for JavaScript to seamlessly instantiate and use Java classes. Unfortunately, on its own, this constitutes a huge security hole. Rhino makes it easy to patch, however. Create a new class in the project called DenyAllClassShutter, and enter the following:

public class DenyAllClassShutter implements ClassShutter { public boolean visibleToScripts(String arg0) { return false; } }

This class implements the ClassShutter interface from Rhino, which has a single method, visibleToScripts. This method is passed the name of a class or package, and is expected to return a boolean indicating if the class or package in question should be accessible from JavaScript. Since we don't want to permit any LiveConnect functionality in our case, we simply return false always.

To make use of this, we need to make a single modification to RhinoparkServlet:

Context ctx = Context.enter(); try { ctx.setClassShutter(new DenyAllClassShutter()); Scriptable scope = ctx.initStandardObjects();

Try the snippet from above again - it will now return an error.

In a future post, I'll pick up the embedded interpreter theme again, and we'll see what other neat tricks we can play with it on App Engine.

Comments

blog comments powered by Disqus