Skip to content

huntc/trireme

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Trireme

This is a set of libraries for running node.js scripts inside Java.

What is Trireme?

Trireme runs Node.js scripts inside the JVM. This is important because there is a lot of software out there (including our own) that is built in Java and isn't going to get rewritten in JavaScript now or in the future.

Trireme is specifically designed to be embeddable within any Java program. There is a lot of support inside Trireme for this specific case:

  • Many Node.js scripts can run inside a single JVM, subject to memory constraints.
  • Each script is totally isolated from the others -- there is no way for one script to affect the heap of the others.
  • A sandbox is provided that lets the container control how, or if, the script gains access to the filesystem and to the network.
  • The HTTP server implementation is pluggable. An "HTTP Adapter" is supported that allows a container to embed a Node.js script inside an existing HTTP container like a web server or other product. (A sample adapter, built using Netty 4.0, is included.)
  • The sandbox supports a Rhino feature that makes it possible to limit the execution time for a script. With this feature enabled, a script that runs an infinite loop can be terminated after some time.

How Complete is Trireme?

Trireme supports most of the Node.js APIs and passes much of the Node.js test suite.

The table below shows each module and its status. "Complete" means that a module is functionally complete, although it may not necessarily pass all the node.js tests.

ModuleStatusSource
assertCompletenode.js
child_processPartialTrireme
clusterNot Implemented Yetnode.js
consoleCompletenode.js
cryptoPartialnode.js + Trireme
debuggerNot Implemented
dgramPartialnode.js + Trireme
dnsPartialTrireme
domainCompletenode.js + Trireme
eventsCompletenode.js
fsCompletenode.js + Trireme
globalsCompletenode.js + Trireme
httpCompletenode.js + Trireme
httpsMostly Complete - See NotesTrireme
moduleCompletenode.js
netCompletenode.js + Trireme
osPartialTrireme
pathCompletenode.js
processCompleteTrireme
punycodeCompletenode.js
querystringCompletenode.js
readlinePartialnode.js + Trireme
replNot Implementednode.js + Trireme
streamCompletenode.js
string_decoderCompletenode.js
timersCompletenode.js + Trireme
tlsMostly Complete - See NotesTrireme
ttyNot ImplementedNA
urlCompletenode.js
utilCompletenode.js
vmCompleteTrireme
zlibCompleteTrireme

What are the Major Differences with "real" node.js?

A few of the modules are different, some in major ways:

JavaScript Language

Trireme runs in the JVM on Rhino, which is the most complete JavaScript implementation for the JVM. Rhino currently implements version 1.8 of JavaScript, which means that a few things supported in later versions of JavaScript, such as the "trimLeft" method of the "String" object, are not supported.

Also, newer features of V8, particularly the primitive array types, are not supported in Rhino.

Most of the time the differences between V8 and Rhino do not affect Node.js code, but occassionally there is a problem. We would love some help from the Rhino community to start to address these differences.

TLS/SSL and HTTPS

Trireme uses Java's standard "SSLEngine" for TLS/SSL and HTTPS support, whereas standard Node.js uses OpenSSL. The TLS implementation in Node.js is a fairly thin layer on top of OpenSSL and we chose not to try and replicate this in Java.

SSLEngine and OpenSSl are not exactly the same. There are a few differences:

  1. Most notably, especially with Java 7, SSLEngine supports a different set of cipher suites, particularly the various elliptical curve ciphers. There are ciphers in common (otherwise almost everything will break) but there are many that are not. Many Node.js tests that rely on older cipher suites using DES or RC4 will not run on Trireme because many of these older and weaker cipher suites are disabled by default in Java. However, "OpenSSL style" names work in Trireme just as they do in regular Node and if the JVM supports a particular cipher suite from OpenSSL, you will get the same one in Trireme.

  2. Java handles SSL sessions differently, and gives the user less control about it. Right now, Trireme is unable to support the ability of a TLS or HTTPS client to retrieve the session from an existing connection and re-use it for another TCP connection.

  3. Java also will produce different certificate validation errors than OpenSSL does. The errors will still come in the same places and for the same reasons, but if your code depends on a specific error message, it will likely get a different one.

  4. Java's SSLEngine relies on its own "keystore" files, whereas OpenSSL can operate on a variety of files but typically processes PEM files. Trireme handles this disparity by using the "Bouncy Castle" crypto framework to translate PEM files into keys and certificates that SSLEngine can understand. In addition, you can also use regular Java keystore files, as described below.

  5. "securepair" isn't implemented yet -- the vast majority of TLS code we have seen just uses the regular "cleartext stream." If you really want to see "securepair," it's probably not too difficult.

In order to support TLS and HTTPS using PEM files, the "trireme-crypto" module and its depdendencies (Bouncy Castle) must be in the class path. If they are not present, then TLS is still available, but it will only work with Java keystore files (see below) or without using any keys at all. Trireme checks for this dependency at runtime, so it is simply a matter of including it on the class path, since it will fail at runtime if the dependency is neded, and work otherwise.

(For instance, Trireme can still execute a Node program that acts as an HTTPS client using only default certificates without requiring trireme-crypto. But if it needs to validate a particular CA certificate or if it needs to use a client-side certificate then trireme-crypto is also necessary.)

In addition, the TLS and HTTPS-related methods in Trireme can use a Java keystore instead of PEM files. There are three parameters that are relevant here:

  • keystore: The file name of a Java ".jks" keystore file containing a key and certificate
  • truststore: The file name of a Java ".jks" keystore file containing trusted CA certificates
  • passphrase: The passphrase for the keystore and truststore

But the corresponding Trireme script must be written like this, as it would be in any Node.js program. Howewver, if the "trireme-crypto" module is not present in the classpath, then this will raise an exception:

var options = {
  key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'),
  cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem')
};

var server = https.createServer(options, function(req, res) {
  console.log('got request');
});

In addition, the following is also valid, and "trireme-crypto" will not be needed:

var options = {
  keystore: common.fixturesDir + '/keys/agent1.jks',
  passphrase: 'secure'
};

var server = https.createServer(options, function(req, res) {
  console.log('got request');
});

Crypto

With the combination of the built-in crypto support in Java, plus Bouncy Castle, crypto support can be fairly complete.

Like TLS, certain features (Sign/Verify in particular) only work if the "trireme-crypto" module and its dependencies are in the class path. If they are not present then these methods will throw an exception.

The following crypto features work the same way in Trireme as they do in standard Node.js:

  • Random bytes
  • Hash
  • Hmac
  • Sign / Verify (requires the "cryto" module because it handles PEM files)
  • PBKDF2 (however Java and OpenSSL appear to implement different algorithms, so some more work is required)

The following features have not yet been implemented (although the underlying platform has all the support required to make it happen):

  • Cipher / Decipher
  • Diffie-Hellman

In the particular case of "Cipher," Node.js uses a particular algorithm for "createCipher" based on a password with no salt that follows no known standard, and without the use of salt it is not terribly secure. Should we even implement this in Trireme, or strongly discourage its use? (There is also a variant that can take a key generated by PBKDF2 which would be a lot more secure.)

Finally, the "Context" feature of the Crypto module is not implemented. This module is really used inside Node.js to implement TLS, and Trireme does TLS a different way, as explained above.

Child Process

Child processes are supported. Arbitary commands may be executed, just like in standard Node.js.

When a Trireme script uses "fork" to spawn a new instance of itself, the script runs as a separate thread inside the same JVM, rather than as a separate OS process as it works in regular Node.js.

Some Node.js scripts rely on the ability to spawn a process called "./node" in order to fork itself. Trireme looks for this and tries to use it to spawn a new thread but it does not work in all cases. It does seem to be mostly the Node.js test suite itself that does this.

Cluster

The "cluster" module is not yet supported. When it is, it will support running multiple scripts within a single JVM, like the child process module works as described above.

Filesystem

The filesystem is fairly complete, but remember that Java is an abstraction on top of the OS so it may not behave exactly the same as it does on Linux.

On Java 6, the filesystem implementation falls back to using only the APIs supported in this version of Java, which means that many things like symbolic links are not supported, and support for "chmod" and the like is not exactly the same as in standard Node.js. On Java 7, Trireme is able to use additional features and the filesystem support is much more complete.

OS

Again, Trireme runs on top of the JVM, which presents an operating-system abstraction. Things that Node.js programs can do like set up signal handlers and the like are not supported.

How Fast is It?

Rhino on the JVM is much slower than V8. (In some benchmarks it is 50 times slower.) However, Node.js programs take advantage of a lot of native code, especially when HTTP and TLS are used, so Trireme generally fares much better.

In general, we have seen simple HTTP benchmarks run at about one-half the speed of the same programs on standard Node.js. Some things are slower than that, and others are faster -- it all depends, as it does with all benchmarks.

We would love to be able to use a faster JavaScript implementation, which would speed up all of Trireme. However, for many programs, Trireme on Rhino will be just fine, and the ability to embed Trireme inside another container is especially helpful.

What Are the Dependencies?

Since Trireme is supposed to be highly embeddable, we try to minimize the dependencies.

Rhino.

This is the most mature framework for running JavaScript under Java. When some of the other efforts are closer to working, we may look at replacing it if, as anticipated, they are much faster.

Slf4j

This is the de facto standard logging API for Java.

Java SE 6

Java is a great platform with a lot of support for a lot of nice things. We're going to try and do everything we can without any additional stuff.

Design

Node.js Implementation

Trireme has a similar architecture to Node.js itself. Many of the core modules in standard Node.js rely on a JavaScript shell, with native modules underneath that are written in C++.

Trireme is similar, and in many cases it exposes Java modules that mimic the interfaces of the C++ native modules in Node.js. So for instance, Trireme implements a native "tcp_wrap" module in Java that uses NIO to emulate the same API as the "tcp_wrap" module in Node.js. The same goes for udp, HTTP parsing, and many other things.

Threading Model

Each Trireme script runs in a single thread. In other words, when the script is executed, it spawns a new thread and occupies it until the script exits. Ticks and timers are implemented within that single thread. If the script exits (has no ticks or timers, is not "pinned" by a library like http, and falls off the bottom of the code) then the thread exits.

This way, there is no need for synchronization for most of the things that the scripts do, just like in regular Node.js.

However, some modules, such as the filesystem, may block, so those modules dispatch to a thread pool, just like in many other Java programs.

Similarly, the "HTTP adapter" allows Trireme to be embedded inside an existing server container, and in that case HTTP requests may come from many different threads. For that reason, the main event loop for each Trireme script depends on underlying collections that are thread-safe, so that different threads may place events on the event loop.

In the future, we may choose to support multi-tenant script threads, so that many isolated scripts may run in the same thread. That would decrease memory usage and context switching for servers that run many scripts.

HTTP Adapter

The HTTP adapter is an interface that a server may implement and plug in to Trireme. When it is plugged in, the adapter is responsible for calling Trireme when new HTTP requests arrive, and for presenting the actual HTTP requests and responses.

When this is used, Trireme scripts work just as they do in standard Node.js, but the "server" part of http is delegated to the adapter. (The client side of http continues to work the same way, however.)

The Sandbox

The sandbox is an interface that a server may implement that allows control over what scripts are allowed to do. It allows a script to accept or reject requests to access the filesystem, access the network, and execute programs. Using the sandbox, it is possible to run Node.js scripts in a totally isolated environment in a muti-tenant server.

Running Trireme

The "jar" module builds a self-contained JAR that may be used to launch Trireme on the command line just like regular Node.js:

mvn install
java -jar jar/target/trireme.X.Y.Z.jar <script name>

(and with no arguments it will launch the "repl" but that implementation is not complete)

Embedding Trireme

There is JavaDoc for the "NodeEnvironment" and "NodeScript" classes, and many other features. Here are the basics:

import org.apigee.trireme.core.NodeEnvironment;
import org.apigee.trireme.core.NodeScript;

// The NodeEnvironment controls the environment for many scripts
NodeEnvironment env = new NodeEnvironment();

// Pass in the script file name, a File pointing to the actual script, and an Object[] containg "argv"
NodeScript script = env.createScript("my-test-script.js",
                                     new File("my-test-script.js"), null);

// Wait for the script to complete
ScriptStatus status = script.execute().get();

// Check the exit code
System.exit(status.getExitCode());

About

Embed Node.js inside a Java Virtual Machine

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 74.6%
  • Java 23.2%
  • Python 1.6%
  • Shell 0.2%
  • C++ 0.1%
  • Ruby 0.1%
  • Other 0.2%