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

    View full-size slide

  2. AGENDA
    Interop 101
    bcrypt
    rabbitmq
    hacks

    View full-size slide

  3. Interop 101
    01

    View full-size slide

  4. The docs
    ▪ https://github.com/graalvm/graaljs/blob/master/docs/user/JavaInterop.md
    ▪ https://www.graalvm.org/docs/reference-manual/languages/js/
    INTEROP 101

    View full-size slide

  5. 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

    View full-size slide

  6. hello-world.js
    node --jvm hello-world.js
    > Hello world!
    const System = Java.type("java.lang.System");
    System.out.println("Hello world!");

    View full-size slide

  7. 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

    View full-size slide

  8. 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

    View full-size slide

  9. The goal
    ▪ Use the JVM jbcrypt library in node.js
    ▪ Integrate it into our server to handle user logins
    BCRYPT

    View full-size slide

  10. 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

    View full-size slide

  11. 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

    View full-size slide

  12. 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

    View full-size slide

  13. 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

    View full-size slide

  14. Cool, ship it!

    View full-size slide

  15. Go home early
    BCRYPT

    View full-size slide

  16. 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

    View full-size slide

  17. The goal
    ▪ Consume messages from rabbitmq
    ▪ We want to use the rabbitmq “push API”
    RABBITMQ

    View full-size slide

  18. 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

    View full-size slide

  19. 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

    View full-size slide

  20. 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

    View full-size slide

  21. 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

    View full-size slide

  22. 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);
    })();

    View full-size slide

  23. We can’t call JS from another thread

    View full-size slide

  24. So...
    ▪ No event handlers written in node.js?
    ▪ No using async Java IO libraries in node.js?
    ▪ No reactive streams?
    RABBITMQ

    View full-size slide

  25. 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.

    View full-size slide

  26. 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

    View full-size slide

  27. GraalJS does not provide that out of
    the box

    View full-size slide

  28. Let’s see what we can do about that

    View full-size slide

  29. 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’)

    View full-size slide

  30. Back to rabbitmq

    View full-size slide

  31. public class JSDeliverCallback implements DeliverCallback {
    private final BoundJSCallback jsCallback;
    public JSDeliverCallback(BoundJSCallback jsCallback) {
    this.jsCallback = jsCallback;
    }
    @Override
    public void handle(String consumerTag, Delivery message) {
    jsCallback.apply(message, null);
    }
    }
    JSDeliverCallback.java

    View full-size slide

  32. +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

    View full-size slide

  33. 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)

    View full-size slide

  34. Thank You
    [email protected] linkedin.com/in/laurynaslubys github.com/laurynasl-wix
    github.com/laurynasl-wix/graal-jvm-interop-talk

    View full-size slide