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