Let’s build your own server-side Javascript run-time with Rhino

Rhino is a JavaScript interpreter implemented in Java that supports sandboxing, allowing you to expose arbitrary Java objects to the JavaScript runtime.

Earlier, Javascript was used in web browsers as a simple scripting language. It was used in web sites to make some animations or to handle simple dynamic behaviours such as real-time input validations and DOM property changes with the browser events. Whereas, nowadays Javascript has jumped out from the web browser; it can be found everywhere. Node took out Javascript engine from the web browser's sandbox into the machine. Therefore, Javascript was popular in the server-side afterwards.


Javascript Engines

Javascript engine is a computer program which executes the Javascript code. It usually does interpretation but in order to reach high efficiency, they may use an intermediate byte-code version using JIT(Just-In-Time) technique. Usually, Javascript engines are embedded into web browsers by enabling them to execute client-side Javascript. Most of Javascript engines are written in C and C++. Thus, ECMAScript introduces a standard or a specification for Javascript language and Javascript engines/interpreters are known as implementations of ECMAScript.


Rhino

Mozilla’s Rhino is a Javascript engine written completely in Java. Moreover, it is a jar-type library which can be embedded into your projects. Importantly, We can take advantage of Java API using Javascript as the working language with the help of Rhino. It means that a custom Javascript run-time can be developed.

What we are going to do is that we will introduce a new Javascript class ProgramRunner with a method exec which will execute a given system command through the Java API. For an instance following Javascript snippet will print the version of your machine’s python environment.

var pr = new ConsoleRunner(); 
pr.exec("python --version");

Getting Started

First of all, we need to start with a fresh Java project. I use a simple maven project for this demo.

Add a dependency for Rhino into pom.xml

<dependencies>
    <dependency>
        <groupId>cat.inspiracio</groupId>
        <artifactId>rhino-js-engine</artifactId>
        <version>1.7.10</version>
    </dependency>
</dependencies>

Play with several simple Javascript statements!

Let’s try to evaluate this simple snippet

var a = [2,8,5]; 
a[1] + a[0];

First line simply assigns[2, 8, 5] array to the identifier “a”. Eventually we do a simple expression a[1] + a[0]. So the evaluated end-result should be 10

Create RhinoExample.java as per below.

package com.mycompany.rhinojsexample;
 
import com.mycompany.rhinojsexample.hostobjects.ConsoleRunner;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
 
public class RhinoExample {
    public static void main(String[] args) {
        String code =  "var a=[2,8,5]; a[1] + a[0];";
        try {
            Context cx = Context.enter();
            Scriptable scope = cx.initStandardObjects();
            Object ob = cx.evaluateString(scope, code, "rhinodemojs", 1, null);
            System.out.println(Context.toObject(ob, scope));
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }
}

When you execute this it will give10as the output as we expected before.

A context is like a session environment which holds the execution information per each script. The classes from standard Java API can be used directly through the Javascript sources. For an instance java.lang.System.out.println(“some text”) will display characters in the console with the use of Java API.


Creating a host object

Thereafter, We need to create ConsoleRunner java class because we are going to use ConsoleRunner and its exec method to achieve our goal within our custom Javascript engine. These mapped classes are known as host objects because we are able to directly use them inside our custom environment.

Create ConsoleRunner.java by extending with the ScriptableObject class

package com.mycompany.rhinojsexample.hostobjects;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.mozilla.javascript.ScriptableObject;
 
public class ConsoleRunner extends ScriptableObject {
   
    public ConsoleRunner() {}
   
    public void jsConstructor() {}
   
    public String jsFunction_exec(String command) throws IOException {
        StringBuilder out = new StringBuilder();
        Runtime runtime = Runtime.getRuntime();
        Process pr = runtime.exec(command);
        BufferedReader br = new BufferedReader(new InputStreamReader(pr.getInputStream()));
        String outputLine = null;
        while((outputLine = br.readLine()) != null) {
            out.append(outputLine + '\n');
        }
        br.close();
        return out.toString();
    }
 
    @Override
    public String getClassName() {
        return "ConsoleRunner";
    }

Here are some important facts about the above source,

jsConstructor is triggered with new ConsoleRunner constructor call from Javascript

jsFunction_exec is triggered by the exec method from Javascript.jsFunction_prefix should be used in each mapped methods.

– Overridden getClassName method should always return the current class name.


Let put all the stuff together

We need to modify RhinoExample.java by adding our sample code snippet and also by adding a reference to our host object class.

package com.mycompany.rhinojsexample;
 
import com.mycompany.rhinojsexample.hostobjects.ConsoleRunner;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
 
public class RhinoExample {
    public static void main(String[] args) {
        String code =  "var cr = new ConsoleRunner();"
                + " cr.exec('python --version');";
        try {
            Context cx = Context.enter();
            Scriptable scope = cx.initStandardObjects();
            ScriptableObject.defineClass(scope, ConsoleRunner.class);
            Object ob = cx.evaluateString(scope, code, "rhinodemojs", 1, null);
            System.out.println(Context.toObject(ob, scope));
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }
}

When you execute above launcher program you will see the version of your Python environment as we expected at the beginning. According to the example, it is very easy to handle things like database connections, threads etc via Javascript using the Rhino engine. Thus. Node-like our own Javascript run-time can be developed. Source code for this simple demo can be found here.


Find Shalitha Suranga on Medium for more tutorials and articles

Happy Coding 👨‍💻


References

1.https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Documentation

2.http://blog.notdot.net/2009/10/Server-side-JavaScript-with-Rhino