Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Using JVM libraries in node.js

Using JVM libraries in node.js

GraalVM promises seamless interoperability between multiple languages, all running on the JVM. It makes it trivial to call code implemented in JVM languages from node.js. However, given Node’s event loop model, blocking it is not a viable option. In this talk we’ll look into how we can use blocking JVM libraries in node.js as well as call back from JVM libraries into node.js

Laurynas Lubys

December 12, 2018
Tweet

More Decks by Laurynas Lubys

Other Decks in Technology

Transcript

  1. Laurynas Lubys, backend developer Using JVM libraries in node.js [email protected]

    linkedin.com/in/laurynaslubys github.com/laurynasl-wix
  2. TLDR; with regards to node.js ▪ Drop in replacement for

    node.js ▪ Mostly compatible with existing libs ▪ JVM interop enabled with --jvm ▪ Primitives are automatically converted ▪ JS objects can be converted to Map<> INTEROP 101
  3. hello-world.js node --jvm hello-world.js > Hello world! const System =

    Java.type("java.lang.System"); System.out.println("Hello world!");
  4. bcrypt? bcrypt is a password hashing function designed by Niels

    Provos and David Mazières, based on the Blowfish cipher, and presented at USENIX in 1999.[1] Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power. Blowfish is notable among block ciphers for its expensive key setup phase. It starts off with subkeys in a standard state, then uses this state to perform a block encryption using part of the key, and uses the result of that encryption (which is more accurately a hashing) to replace some of the subkeys. Then it uses this modified state to encrypt another part of the key, and uses the result to replace more of the subkeys. It proceeds in this fashion, using a progressively modified state to hash the key and replace bits of state, until all subkeys have been set. Provos and Mazières took advantage of this, and took it further. They developed a new key setup algorithm for Blowfish, dubbing the resulting cipher "Eksblowfish" ("expensive key schedule Blowfish"). The key setup begins with a modified form of the standard Blowfish key setup, in which both the salt and password are used to set all subkeys. There are then a number of rounds in which the standard Blowfish keying algorithm is applied, using alternatively the salt and the password as the key, each round starting with the subkey state from the previous round. In theory, this is no stronger than the standard Blowfish key schedule, but the number of rekeying rounds is configurable; this process can therefore be made arbitrarily slow, which helps deter brute-force attacks upon the hash or salt. - https://en.wikipedia.org/wiki/Bcrypt BCRYPT
  5. bcrypt. It’s a hashing function with a knob you can

    turn to change the computational resources needed to calculate the hash, so that it’s harder to brute-force. - Me BCRYPT
  6. The goal ▪ Use the JVM jbcrypt library in node.js

    ▪ Integrate it into our server to handle user logins BCRYPT
  7. The Plan 1. Tell the JVM where to find the

    jar: --jvm.cp=... 2. Call it from node 3. Use it in our application 4. Go home early BCRYPT
  8. Tell the JVM where to find the jar ▪ Many

    options ▪ None of them great ▪ Packaging a jar with an npm module is an issue ▪ We’ll dump the classpath from maven and feed it to --jvm.cp BCRYPT
  9. FLAGS="\ --jvm \ --jvm.cp=./jbcrypt-0.3m.jar" node $FLAGS bcrypt.js > $2a$10$sfMWTWRO... const

    BCrypt = Java.type("org.mindrot.jbcrypt.BCrypt"); const salt = "$2a$10$sfMWTWROyUu5JxTEwm413u"; const hash = BCrypt.hashpw("Hello", salt); console.log(hash) bcrypt.js
  10. const express = require('express'); const BCrypt = Java.type("org.mindrot.jbcrypt.BCrypt"); const salt

    = "$2a$10$sfMWTWROyUu5JxTEwm413u"; const app = express(); app.post('/login', (req, res) => { const pass = req.query.password; res.send(BCrypt.hashpw(pass, salt)); }); app.get('/', (req, res) => { res.send("Welcome!") }); module.exports = { app }; app.js
  11. Go home early ▪ We blocked the event loop ▪

    The node community knows how to deal with blocking workloads ▪ We can create a pool of workers and do the work there BCRYPT
  12. The goal ▪ Consume messages from rabbitmq ▪ We want

    to use the rabbitmq “push API” RABBITMQ
  13. The Plan 1. Figure out how to do callbacks 2.

    Figure out the Java API 3. Implement a callback in node 4. Handle a rabbitmq message in node.js RABBITMQ
  14. node --jvm callback.js > In node: 1 > In node:

    2 const List = Java.type("java.util.ArrayList"); const aList = new List(); aList.add(1); aList.add(2); aList.forEach(v => console.log(`In node: ${v}`)); callback.js
  15. package examples; import com.rabbitmq.client.*; public class RabbitMQJava { public static

    void main(String[] args) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setUsername("rabbitmq"); factory.setPassword("rabbitmq"); factory.setHost("localhost"); factory.setPort(5672); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); String queueName = "hello-rabbit"; channel.queueDeclare(queueName, false, false, false, null); DeliverCallback deliverCallback = (consumerTag, message) -> System.out.println("Received message: " + new String(message.getBody())); CancelCallback cancelCallback = consumerTag -> { } channel.basicConsume(queueName, true, deliverCallback, cancelCallback); System.out.println("Listening for messages on " + queueName); } } Figure out the API
  16. const ConnectionFactory = Java.type("com.rabbitmq.client.ConnectionFactory"); const JString = Java.type("java.lang.String"); const ExtendDeliverCallback

    = Java.extend(Java.type("com.rabbitmq.client.DeliverCallback")); const ExtendCancelCallback = Java.extend(Java.type("com.rabbitmq.client.CancelCallback")); const factory = new ConnectionFactory(); factory.setUsername("rabbitmq"); factory.setPassword("rabbitmq"); factory.setHost("localhost"); factory.setPort(5672); const connection = factory.newConnection(); const channel = connection.createChannel(); const queueName = "hello-rabbit"; channel.queueDeclare(queueName, false, false, false, null); const deliverCallback = new ExtendDeliverCallback((consumerTag, message) => console.log("Received message: " + new JString(message.getBody()))); const cancelCallback = new ExtendCancelCallback(consumerTag => { }); channel.basicConsume(queueName, true, deliverCallback, cancelCallback); console.log("Listening for messages on " + queueName); (function wait() { setTimeout(wait, 1000); })(); Implement a callback
  17. package examples; import com.rabbitmq.client.*; public class RabbitMQJava { public static

    void main(String[] args) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setUsername("rabbitmq"); factory.setPassword("rabbitmq"); factory.setHost("localhost"); factory.setPort(5672); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); String queueName = "hello-rabbit"; channel.queueDeclare(queueName, false, false, false, null); DeliverCallback deliverCallback = (consumerTag, message) -> System.out.println("Received message: " + new String(message.getBody())); CancelCallback cancelCallback = consumerTag -> { } channel.basicConsume(queueName, true, deliverCallback, cancelCallback); System.out.println("Listening for messages on " + queueName); } } const ConnectionFactory = Java.type("com.rabbitmq.client.ConnectionFactory"); const JString = Java.type("java.lang.String"); const ExtendDeliverCallback = Java.extend(Java.type("com.rabbitmq.client.DeliverCallback")); const ExtendCancelCallback = Java.extend(Java.type("com.rabbitmq.client.CancelCallback")); const factory = new ConnectionFactory(); factory.setUsername("rabbitmq"); factory.setPassword("rabbitmq"); factory.setHost("localhost"); factory.setPort(5672); const connection = factory.newConnection(); const channel = connection.createChannel(); const queueName = "hello-rabbit"; channel.queueDeclare(queueName, false, false, false, null); const deliverCallback = new ExtendDeliverCallback((consumerTag, message) => console.log("Received message: " + new JString(message.getBody()))); const cancelCallback = new ExtendCancelCallback(consumerTag => { }); channel.basicConsume(queueName, true, deliverCallback, cancelCallback); console.log("Listening for messages on " + queueName); (function wait() { setTimeout(wait, 1000); })();
  18. So... ▪ No event handlers written in node.js? ▪ No

    using async Java IO libraries in node.js? ▪ No reactive streams? RABBITMQ
  19. Take away points ▪ Dependency management is not solved (but

    manageable) ▪ Calling synchronous code from node is simple (but blocks the event loop) ▪ Calling async Java code from node is not solved.
  20. node ${FLAGS} async.js > Let's wait now > node.js: Hello

    from JVM! > JVM: Hey! const {bindCallback} = require('???'); const SomethingAsync = Java.type("examples.Async"); const bound = bindCallback((error, result) => { console.log(`node.js: ${result}`); return new Promise((resolve) => { setTimeout(() => resolve("Hey!"), 100); }) }); SomethingAsync.callMeBack(bound); console.log("Let's wait now"); async.js
  21. InteropBus Async Java code pending ready complete( ) ( ,

    ) We want to call with async results from java. node.js worker node.js Java land bindCallback on(‘message’)
  22. public class JSDeliverCallback implements DeliverCallback { private final BoundJSCallback<Delivery, Void>

    jsCallback; public JSDeliverCallback(BoundJSCallback<Delivery, Void> jsCallback) { this.jsCallback = jsCallback; } @Override public void handle(String consumerTag, Delivery message) { jsCallback.apply(message, null); } } JSDeliverCallback.java
  23. +const {bindCallback} = require("../../lib/interop/simplified"); -const ExtendDeliverCallback = Java.extend(Java.type("com.rabbitmq.client.DeliverCallback")); +const JSDeliverCallback

    = Java.type("examples.JSDeliverCallback"); -const deliverCallback = new ExtendDeliverCallback((consumerTag, message) => - console.log("Received message: " + new JString(message.getBody()))); +const deliverCallback = new JSDeliverCallback(bindCallback((error, message) => + console.log("Received message: " + new JString(message.getBody())))); patch
  24. Take away points ▪ Dependency management is not solved (but

    manageable) ▪ Calling synchronous code from node is simple (but blocks the event loop) ▪ Calling async Java code from node is not solved (but hackable)