Last updated at Fri, 03 Nov 2017 19:41:13 GMT

It’s easy to see how JavaScript is everywhere these days. The barrier to entry is extremely low; anybody with a browser can write and evaluate it, and with advancements in runtimes like Google’s V8, writing server-side JS is now a viable proposition. It’s easy to forget, then, that Rhino- one of original JavaScript interpreters was written in Java. Not only that, but Mozilla is still looking after the venerable codebase and a variant is still bundled with every Java runtime. While Rhino has been given several new leases of life, the latest & greatest version doesn’t ship with modern JVMs.

In addition, Sun Oracle has decided to jump on the server-side JS bandwagon with their own internally developed implementation, Nashorn, a brand new interpreter built into Java 8 which was designed from the outset to take advantage of the bytecode-level improvements in recent JREs. It also comes out of the box with ECMAScript 5 support (6 is in the pipeline), CommonJS module support and other features that node.js developers take for granted. A node compatibility layer is also under heavy development.

Inside a Java process, you can get hold of a nashorn instance like this:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// and the requisite hello world:
engine.eval("print('Hello World!');");

It’s also possible to require external JS modules which selectively expose parts of their APIs:

// greeter.js
var sayHi = function(name) {
  return "Hello, " + name;
}
var somePrivateFunction = function() {
  return 'not gonna happen';
}
module.exports.sayHi = sayHi;

And from another JS file in the same directory:

var greeter = require('./greeter.js');
greeter.sayHi('Chris');
// => 'Hello, Chris';
greeter.somePrivateFunction();
// not accessible- throws an exception

Modularity is handy, but not wildly exciting. What’s more interesting is the language interoperability- i.e. your JS code can extend and call Java objects and vice-versa, like this:

var Callable = Java.type('java.util.concurrent.Callable');
var Work = Java.extend(Callable, {
  call: function() {
    return "Hello, Callable!";
  }
};
var myWork = new Work()
myWork.call(); // 'Hello, Callable!'

To illustrate how this can be used to good effect, let’s show a real use-case: writing routing logic for Zuul in JavaScript. Zuul is an open-source load balancer written by Netflix and heavily used at the edge of their services. Its main attraction over components like ELB, HAProxy, e.t.c is the ability to decorate, filter and reroute traffic in arbitrarily complex ways without incurring any down-time. Routing logic can also theoretically be written in any language that runs on the JVM (code that Netflix has made public largely uses Groovy), so it should be easy enough for us to create a JavaScript implementation

First thing we need to do is implement the DynamicCodeCompiler interface, taking Netflix’s Groovy implementation as a reference. A DynamicCodeCompiler is the piece that gives Zuul much of its value- by taking source files it discovers on a predefined path and interpreting them on the fly:

package com.logentries.api;

import com.netflix.zuul.DynamicCodeCompiler;
import jdk.internal.dynalink.beans.StaticClass;
import jdk.nashorn.api.scripting.NashornScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.File;
import java.nio.file.Files;

public class JSCompiler implements DynamicCodeCompiler {

    private static final NashornScriptEngine ENGINE = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn");

    @Override
    public Class compile(String sCode, String sName) throws Exception {
        Object obj = ENGINE.eval(sCode);
        // slightly naughty- StaticClass lives in an internal package
        StaticClass klass = (StaticClass) obj;
        return klass.getRepresentedClass();
    }

    @Override
    public Class compile(File file) throws Exception {
        String sourceCode = new String(Files.readAllBytes(file.toPath()));
        return compile(sourceCode, null);
    }
}

Next, let’s add a filter- we’ll pull the Netflix sample project which just redirects requests to the Apache organization home page and take some inspiration from the Groovy filters that come out of the box. Let’s reimplement a simple pre-routing filter that adds information about the remote host to the debug log. First, we need to import a few classes that come with the Zuul core library:

// import a single type
var ZuulFilter = Java.type('com.netflix.zuul.ZuulFilter');
// we can also import a whole package and use it in a restricted scope
var contextImports = new JavaImporter(com.netflix.zuul.context);

Next, we need to extend ZuulFilter with our implementation:

var MyJSDebugFilter = Java.extend(ZuulFilter, {
  filterType: function() {
    return "pre";
  },
  filterOrder: function() {
    return 10000;
  },
  shouldFilter: function() {
    true;
  },
  run: function() {
    // classes and interfaces inside contextImports only
    // live inside the scope of the block below
    with (contextImports) {
      // get the current request
      var req = RequestContext.currentContext().request();
      // add the remote address to the debug log
      Debug.addRequestDebug("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort());
      Debug.addRequestDebug("REQUEST:: > " + req.getMethod() + " " + req.getRequestURI() + " " + req.getProtocol());
    }
    return null;
  }
});

For reference, the original Groovy implementation can be found here. Notice that the Debug and RequestContext classes are only visible inside the with() {} block.

Conclusion

Technical innovations like Nashorn show that you’re not tied to writing Java when you have a big investment in the JVM; if a more expressive language or framework is more appropriate for the job at hand, there’s nothing stopping you using it while still leveraging your existing code, something we take advantage of  here at Logentries.