Contact Us

Customizing Angie: njs, Lua and Perl

The Angie server inherited all the functionality that Nginx accumulated over decades. On top of that, its developers added a few more modules over the years and extended the capabilities of existing ones. Still, complex tasks may call for a custom solution, non-standard server behavior, and a tailored configuration. In such cases it helps to extend the server with modules built on general-purpose programming languages: njs, Lua, and Perl are available.

Video version

A video version of this article is available on YouTube.

Perl module

Start with the classic Perl module, which brings dynamism to the Angie configuration. One caveat first: the documentation warns that the module is experimental. It appeared in the early days of Nginx and is still shipped with Angie. Perl is not popular with developers these days, but it has a few advantages. First, the Perl interpreter ships with most Linux distributions, so compatibility is high. Second, the language is very stable syntactically. If you find a solution written 20 years ago, it will almost always run on a modern interpreter.

The Perl module is useful for several tasks:

  • defining Angie internal variables;
  • plugging in full request handlers;
  • running Perl scripts inside SSI.

The Perl module works only in the HTTP context. Installation and loading are standard:

apt install angie-module-perl

Loading (in the main context):

load_module modules/ngx_http_perl_module.so;

Consider a simple variable-definition example. The task: convert request URIs to lowercase. For example, /Uri -> /uri, /URI -> /uri, /TeSt -> /test.

To solve it, set up a variable holding the modified URI with a Perl subroutine:

http {
  perl_set $my_uri_to_lowercase 'sub {
    my $r = shift;
    my $uri = $r->uri;
    $uri = lc($uri);
    return $uri;
  }';
}

The perl_set directive creates the variable $my_uri_to_lowercase, which holds the request's new URI. The original request URI goes into the Perl variable $uri, and the lc function — lowercase conversion — is applied to it. The function's result is assigned to the Angie variable $my_uri_to_lowercase.

The new variable can be used like this: create a location for URIs containing uppercase characters and return a redirect to the new URI taken from the variable:

server {
  location ~ [A-Z] {
    return 301 $scheme://$host$my_uri_to_lowercase;
  }
}

That solves the task. If only some requests need case changes, the location can be nested.

More complex scenarios will likely need extra Perl modules from CPAN. The perl_require directive loads them. Here is an example that loads a module and installs a request handler. Angie configuration:

http {
    perl_modules perl/lib;
    perl_require hello.pm;

    server {
        location / {
            perl hello::handler;
        }
    }
}

This specifies the directory to search for Perl modules (perl_modules) and loads the hello.pm module. Inside the server block, the location defines the handler hello::handler — a call to the handler function from hello.pm.

Perl module code for the handler (hello.pm):

package hello;

use nginx;

sub handler {
    my $r = shift;

    $r->send_http_header("text/html");
    return OK if $r->header_only;

    $r->print("hello!\n<br/>");

    if (-f $r->filename or -d _) {
        $r->print($r->uri, " exists!\n");
    }

    return OK;
}

1;
__END__

The main part of the module is the request handler function (handler). It prints hello! and, when a file or directory matching the URI exists, appends $uri exists!.

Keep in mind that the Perl module runs synchronously: while the code executes, the worker process cannot handle other requests. For that reason, long-running operations with unpredictable response times are a poor fit for this approach.

Lua module

Third-party modules let you use the popular scripting language Lua in Angie. The basic capabilities of the lua module from the OpenResty framework are covered here.

The lua module also requires the NDK module. Installation:

apt install angie-module-lua

Loading the modules:

load_module modules/ndk_http_module.so;
load_module modules/ngx_http_lua_module.so;

For the example, write the request body to a log file. Define the matching log format and an extra variable:

http {
  log_format bodylog '$remote_addr - $remote_user [$time_local] '
      '"$request" $status $body_bytes_sent '
      '"$http_referer" "$http_user_agent" $request_time '
      '<"$request_body" >"$resp_body"';
  lua_need_request_body on;

  body_filter_by_lua '
    local resp_body = string.sub(ngx.arg[1], 1, 1000)
    ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body
    if ngx.arg[2] then
      ngx.var.resp_body = ngx.ctx.buffered
    end
  ';

  server {
    listen 80;
    set $resp_body "";
    access_log  /var/log/nginx/server.log bodylog;
  }
}

This code adds a dedicated log format and sets lua_need_request_body on so Lua can access the request body. Next comes a Lua body-filter handler whose job is to fill an Angie variable ($resp_body). In this example the size is capped at 1000 bytes. The variable itself is declared at the server block level, where the custom access_log is also used.

Unlike the Perl module, Lua works in both the HTTP and Stream contexts. Lua also lets you write non-blocking code for access to external systems. The Lua module is broad in scope and covers nearly every aspect of handling a request and response in Angie.

NJS module

Finally, the NJS module. This is the most natural way to extend Angie's functionality. NJS is a subset of JavaScript adapted specifically for use in Nginx — and therefore in Angie. NJS was created to enable flexible server configurations and non-standard request-processing scenarios.

The NJS module works in both the HTTP and Stream contexts. Installation is standard:

apt install angie-module-lua

Loading the module, as usual, happens in the main context (separate modules exist for http and stream):

load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;

For the example, take the task of lowercasing the response body. Use the code from the examples repository. Clone the repository into /etc/angie:

cd /etc/angie
git clone https://github.com/nginx/njs-examples.git

In the server configuration, load the script files and the NJS handler.

http {
  js_path "/etc/angie/njs-examples/njs/";
  js_import main from http/response/to_lower_case.js;

  server {
        listen 80;
        location / {
            js_body_filter main.to_lower_case;
            proxy_pass http://localhost:8080;
        }
  }

  server {
        listen 8080;
        location / {
            return 200 'Hello World';
        }
  }
}

The js_path directive sets the directory to search for njs script files. The script file is then imported by relative path (the code came from the repository).

The first server block (port 80) is the main entry point of the improvised application. The root location loads the js_body_filter handler. It calls the to_lower_case function from the file to_lower_case.js to process the response body. The script is as short as it gets:

function to_lower_case(r, data, flags) {
    r.sendBuffer(data.toLowerCase(), flags);
}
export default {to_lower_case};

In essence, the script lowercases any incoming data.

The second server block (8080) acts as the backend and answers any request with Hello World.

Testing this configuration shows the effect:

curl localhost
hello world

The options for working with requests, responses, and headers here are very broad. To get started quickly, look at the examples in the module's reference repository.

Summary

Angie's capabilities can be extended with standard and third-party modules built on several programming languages. Scripted processing can transform headers, modify the response or request, and build a custom authentication system. Of these, the NJS module is closest in spirit to Angie, because it was created specifically to extend the server by the Nginx developers. In a sense, NJS lets you build dynamic modules for the server without touching Angie's internals in C.