Skip to content

Instantly share code, notes, and snippets.

@tippexs
Last active January 19, 2022 08:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tippexs/2ffcd872ba8046cce96091746340fa12 to your computer and use it in GitHub Desktop.
Save tippexs/2ffcd872ba8046cce96091746340fa12 to your computer and use it in GitHub Desktop.
njs 0.7.0

NGINX JavaScript Module (njs) 0.7.0 just arrived!

I am super excited about this new release! On October 19th 2021 NGINX launched the latest version of NGINX njs 0.7.0.

This represents a significant step forward for njs and introduces highly anticipated features and functionality including support for the ECMAScript6 (ES6) feature async/await and the implementation of the webcrypto API. In this post, we’ll explore these aspects in more detail, starting with async/await and Promises. Once these concepts are understood, we’ll then dig into the webcrypto functionality.

Let’s get started!

JavaScript 101 - Promises

According to ECMA262—the language specification standard for ECMAScript 2021, which is the standard scripting language for JavaScript—Promises are defined as follows:

A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.

Any Promise is in one of three mutually exclusive states: fulfilled, rejected, and pending:

  • A promise p is fulfilled if p.then(f, r) will immediately enqueue a Job to call the function f.
  • A promise p is rejected if p.then(f, r) will immediately enqueue a Job to call the function r.
  • A promise is pending if it is neither fulfilled nor rejected.

Another way to think of a JavaScript Promise is like a “promise” from a good friend, that can be subject to change. Essentially, it says "something" that eventually results in a state that you may have been unaware of at the moment.

For example, let's say your friend says "Even if it looks like it will rain - It will stay dry! Promise you!" This “promise” is pending until it is either fulfilled (it stayed dry) or rejected (it started to rain). That’s the same way Promises works in JavaScript. But back to the code!

In JavaScript (and therefore in njs, as well) it is important to handle such Promises correctly. To do so, we have to declare the wrapping function and wait for a Promise to fulfill or reject with the async keyword.

In the following async function it is then possible to use the await keyword right before the Promise object.

async function AwaitTest(r) {
  let promise = Promise.resolve(1);
  let result = await promise;#
  return result;
}

To wrap things up: async functions returning or awaiting Promises. To resolve a Promise, you can use the keyword await.

Wondering why that’s important? Stay tuned!

Welcome to the NJS Party, webcrypto

We are now able to make use of cryptographical operations in njs. Some examples of cryptographical operations include:

  • Generating secure random numbers for session IDs
  • Encrypting or decrypting messages, data, and cookies
  • Creating or validating digital signatures using symmetric as well as asymmetric crypto material

Let’s look at generating secure random numbers. The below function and variable uses a cryptographic operation to generate random numbers.

var buffer = crypto.getRandomValues(new Uint32Array(8));
console.log(buffer);

The output of the above function results in these outputs:

$ njs demo.js
Uint32Array [124101979,847021928,263767684,1662679730,2945141756,3420152236,3579302021,1257759542]

The getRandomValues function is a great entry point to get started with secure random numbers and webcrypto in general. Its implementation is quite simple and as we can see in the documentation, it’s not returning a Promise Object. So, nothing async here.

But let's have a look at some heavier cryptographical functions. For example, let’s look at сrypto.subtle.digest

From the documentation, this function:

Generates a digest of the given data. Takes as its arguments an identifier for the digest algorithm to use and the data to digest. Returns a Promise which will be fulfilled with the digest.

The function returns a Promise and, as we now know, must handle this Promise correctly to resolve. Let's have a look at the njs function wrapper to learn more.

export default { host_hash };

async function host_hash(r) {
     let hash = await crypto.subtle.digest('SHA-512', r.headersIn.host);
     r.setReturnValue(Buffer.from(hash).toString('hex'));
}

As you can see, the function is wrapped by the async keyword, and will await the Promise to be resolved or rejected. The setReturnValue comand was also introduced in 0.7.0 to set the value, based on an async function for a js_set command. Let's look at these concepts when configuring NGINX.

js_import host from conf.d/host.js;
js_set $hosthash host.host_hash;

server {
  listen 80;

  location / {
    return 200 $hosthash;
  }
}

This above configuration will return the hashed hostname example.com.

$ curl -H "Host: example.com" 127.1
#
e8e624a82179b53b78364ae14d14d63dfeccd843b026bc8d959ffe0c39fc4ded1f4dcf4c8ebe871e657a12db6f11c3af87c9a1d4f2b096ba3deb56596f06b6f4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment