NGINX.COM
Web Server Load Balancing with NGINX Plus

Title slide for presentation by Igor Sysoev at nginx.conf 2015: 'A new and powerful way to configure nginx'

The following is adapted from a presentation given by Igor Sysoev at nginx.conf 2015, held in San Francisco in September. You can view the video on YouTube.

It describes an early version of nginScript, which is now called the NGINX JavaScript module. For a description of the current implementation of the module, which became generally available in NGINX Plus R12, see Harnessing the Power and Convenience of JavaScript for Each Request with the NGINX JavaScript Module.

Table of Contents

0:27 nginScript Beginnings
1:30 The Lua Module
2:23 Choosing JavaScript
3:33 JavaScript VMs
4:04 V8 Issues
6:15 The nginScript VM
7:51 The State of nginScript
9:10 The nginScript Interface
9:58 $r.response
11:34 Questions and Answers

0:27 nginScript Beginnings

Three alternatives were considered for running scripts inside the NGINX server: Perl, Lua, and Javascript

Since the start of NGINX’s development, I’ve wanted NGINX to have the ability to run scripts inside the server.

I first attempted to implement this capability in 2005. At that time I tried to embed Perl – which was, and still is, my favorite language for general scripting.

Perl has some drawbacks, however, that make it unsuitable as a server scripting language. For example, when it runs out of memory, Perl just exits. Now that may be acceptable for an ordinary script but it’s very bad for servers. Servers need to behave much more gracefully in those situations.

1:30 The Lua Module

Lua was rejected as the scripting language inside the NGINX server because of its syntactic oddities

Some time after that, we decided to embed Lua, another scripting language, inside NGINX. This was made available through a third‑party module.

But Lua has some oddities. If you’ve never programmed with Lua, you will find the syntax a bit strange and different from what you may be used to with C (for example). You will see strange regular expressions as well, using unusual syntax.

2:23 Choosing JavaScript

JavaScript has always been one of the two most popular languages on GitHub, holding the #1 spot since 2013

In fact, JavaScript also has a lot of its own oddities, but people are already used to them. In the case of Lua, they would have to get used to these things all over again.

Based on popularity, we decided to choose JavaScript instead of Lua. Lua is a niche language. If you look at Github’s statistics, you will see JavaScript in first place while Lua is not even in the top ten languages.

3:33 JavaScript VMs

JavaScript VMs (including Google V8, Mozilla SpiderMonkey, and Apple JavaScriptCore) were developed for browsers

There are several JavaScript implementations – by Google, Mozilla, and Apple. These engines are all very good but they were developed for the browser environment.

4:04 V8 Issues

V8 (as an example) is unsuitable for a server environment because of issues with portability, memory use, preemption, and an unstable API

If we take V8 for example, there are several issues that make it difficult to use in a server environment. The first one is portability. V8 uses just‑in‑time compilation, compiling JavaScript into machine code. This limits V8 to certain hardware platforms. For example, V8 can only run on Intel CPUs, ARM, and there are also ports for MIPS and PowerPC.

The second issue is memory. V8 runs in one browser tab, and if it runs out of memory, it will stop and maybe just close that tab. This is not acceptable for servers. Servers should behave more gracefully in these situations. It should just close that request and continue to process other requests.

The next thing is preemption. There is no easy way to use one V8 virtual machine, preempt it, and switch to running another V8 VM.

And the last thing is having to chase all the API changes made by Google. As you may know, Node.js never uses the latest version of V8. This is because Google changes the API quite often and developers simply cannot keep up.

6:15 The nginScript VM

With nginScript, there is one virtual machine per request, enabling preemption and usually eliminating garbage collection for a better NGINX config

So I decided to write my own JavaScript implementation. We run virtual machines per request. So, we do not share one JavaScript virtual machine – as Node.js does, for example. This approach allows us to preempt these machines without any issues. And also in many cases, for short‑lived requests we don’t need garbage collection at all. We can just throw these virtual machines away when the requests complete. In contrast, Node.js needs to run a garbage collector if there is a memory shortage.

All these virtual machines share the same bytecode which compiles at startup, so creating a new virtual machine is quite cheap. We just need to allocate some memory and copy some initial values to this memory. Teardown is even cheaper. We just free all allocated memory, which is a fast operation.

We use a register‑based virtual machine. This is not stack‑based, so the size of the virtual machine is quite small. For small tasks, it takes roughly several kilobytes of memory. So, it’s quite lightweight.

7:51 The State of nginScript

The current implementation of nginScript is a subset of ECMAScript 5.1

The current state of our implementation is a subset of ECMAScript 5.1. We do not support closures yet. We don’t yet support some built‑in objects, like date, month, etc., but we are considering adding these things later.

Currently, we have no garbage collector. As I’ve mentioned earlier, a garbage collector is not required for short‑lived requests due to our implementation, but we would also like to support long‑lived connections. In this case, a garbage collector may be required.

And we currently do not support just‑in‑time compilation. We use bytecode. Bytecode allows us to run on any platform where you can build JavaScript. We plan to add JIT later, probably using the Low‑Level Virtual Machines project, but it’s not something we’re looking at for the near future. [Editor – The project is now called LLVM.]

9:10 The nginScript Interface

The '$r' object represents a request in nginScript

Recently, we released nginScript, and we’re looking to get feedback on our interface, the JavaScript interface which gives access to NGINX’s internals.

We have the $r object, which represent requests. This object has several fields: method, uri, arguments{}, httpVersion, headers{}, and remoteAddress. This list will expand in the future.

9:58 $r.response

The '$r.response' object in nginScript represents a response

There is another object, inside the request object $r, which allows us to work with the response – $r.response gives us the fields status, headers{}, contentType, and contentLength.

And $r.response also has three methods that allow you to send a response to the client.

The '$r.response' object in nginScript provides three methods for sending a response

This is a short overview of our JavaScript interface. We’d like to get feedback on how JavaScript developers feel about it. We heard a lot of different opinions of how this interface should be implemented, should it be done with functions, should it be using objects, etc. So based on your feedback, we will plan our next step in developing nginScript.

11:34 Questions and Answers

Q: What phases does this execute at?

A: Currently, nginScript can be used for declaring/defining variables, and it can be run in the content‑generation phase. There are other phases in NGINX, for example the access phase, the log‑writing phase. Eventually, we will add support for these phases. But currently, nginScript is limited to the content‑generation phase.

Q: Did you do any comparisons or benchmarking against LuaJIT?

A: When I started this project, I ran some simple benchmarks. I supposed that the most common task would be working with strings, so I wrote a simple benchmark evaluating Fibonacci numbers by concatenating strings.

Instead of adding numbers, which is the slowest algorithm to calculate the Fibonacci sequence, it recursively calls two numbers. I changed the numbers to strings, and at the end of the procedure I just count the length of the string. So, this microbenchmark tests the overhead of function calls and concatenating strings which I believe will be the most common tasks while scripting inside NGINX.

I wrote this benchmark in a number of languages; and our implementation is comparable with Perl, with Ruby, with PHP, with Lua. Of course, it’s slower than LuaJIT, and it’s slower than JavaScript implementations like V8, SpiderMonkey, and JavaScriptCore which use JIT. But it’s comparable with popular scripting languages like PHP, Ruby, Python, and Perl.

Of course, it’s not suitable for some mass operations – for encryption, for compressing, etc. – but it’s good for string manipulations, and actually this is the most important task in a server environment.

To learn more about nginScript, see our blog.

Hero image
Free O'Reilly eBook: The Complete NGINX Cookbook

Updated for 2024 – Your guide to everything NGINX



About The Author

Igor Sysoev

Igor Sysoev

Senior Architect

Igor Sysoev’s passion for solving the C10K problem led him to become the author of NGINX, which was initially released in 2004. NGINX’s popularity and use became so widespread that Igor co‑founded NGINX, Inc. in 2011 and served as its Chief Technology Officer. He has 15 years of experience in deeply technical roles and is a Senior Architect at F5.

About F5 NGINX

F5, Inc. is the company behind NGINX, the popular open source project. We offer a suite of technologies for developing and delivering modern applications. Together with F5, our combined solution bridges the gap between NetOps and DevOps, with multi-cloud application services that span from code to customer.

Learn more at nginx.com or join the conversation by following @nginx on Twitter.