njs ‒ родной JavaSсript-скриптинг в nginx

(модуль для создания переменных и обработчиков стадий запроса на JavaScript)

Скриптинг в nginx

Современный proxy-server уже умеет:

Сервисы делятся на микросервисы. Теперь мы движемся от прокси к API-gateway. Теперь nginx умеет ещё и в авторизацию.

njs-nginx 1

Авторизация средствами прокси: nginx проверяет специальный токен.

Выбор: либо реализовать логику авторизации самостоятельно на низкоуровневом языке, либо…?

Что не так с openresty?

Вот что:

Цели проекта

Хочется написать своё решение, в котором не будет недостатков openresty. Вот, что нам важно:

Выбрали JavaScript:

Интерпретатор njs

Так, а зачем делать собственный интерпретатор?

Чем njs не является

Почему njs работает быстро?

Бенчмарк: создаём пустые контексты запроса на основе каждого из интерпретаторов. График логарифмический!

njs-nginx-2

njs в nginx

Начало работы

apt-get install nginx nginx-module-njs

Пример в докере: https://github.com/xeioex/njs-examples

Напишем hello world

nginx.conf:

#Сначала загрузим с помощью директивы `load_module`

load_module
modules/ngx_http_js_module.so;
#...

http {
    # Директива `js_include` добавляет код из `example.njs`.
    js_include example.njs;
    
    server {
        listen 8000;
        
        location /hello {
            # Директива `js_content` указывает на имя функции, которая должна вернуть ответ.
            js_content hello;
        }
#...

Код обработчика в example.njs:

function  hello(r) {
    r.return(200, "Hello world!");
}

Проксирование запросов с заголовком авторизации

Давайте сделаем что-нибудь поинтереснее. Будем проксировать запросы в S3 bucket на амазоне.

aws-s3-njs.conf:

#...
# Вот это стандартные вещи, которые уже есть в nginx:
location ~* ^/s3/(.*) {
    set $bucket     'test-bucket';
    set $aws_access '...';
    set $aws_secret '...';
    
    proxy_set_header    Host $bucket.s3.amazonaws.com;
    proxy_pass          http://s3.amazonaws.com;
    
    # Но нам ещё нужно вычислить два заголовка:
    # тут будет дата в специальном формате
    proxy_set_header    x-amz-date $now;
    # А тут подписать своим ключом путь, на который мы хотим пойти
    proxy_set_header    Authorization "GET $aws_access:$aws_sign";
}   

И теперь в начало aws-s3-njs.conf мы добавляем такое:

js_set $now now;
js_wet $aws_sign aws_sign;
#...

Эти директивы связывают переменные в конфиге nginx с кодом на JS:

aws-s3-njs.njs:

function now(r) {
    return new Date().toISOString().replace(/[:\-]|\.\d{3}/g, '');
}

function aws_sign(r) {
    var v = r.variables;
    var to_sign = `GET\n\n\n\nx-amz-date:${v.now}\n/${v.bucket}/${v.path}`;
    
    return require('crypto').createHmac('sha1', v.aws.secret)
                            .update(to_sign).digest('base64');
}

Ура, мы сделали подписанный заголовок авторизации.

Сложные редиректы

nginx.conf:

location / {
    auth_request /resolv;
    auth_request_set $route $sent_http_route;
    proxy_pass http://backend$route$is_args$args;
}

location = /resolv {
    internal;
    js_content resolv;
}

location = /_add {
    allow 127.0.0.1;
    deny all;
    js_content add;
}

И такой complex_redirects.js:

function resolv(r) {
    var map = open_db();
    var mapped_uri = map[r.uri];
    // ...
    r.headersOut['Route'] = mapped_uri ? mapped_uri : r.uri
    r.return(200);  
}
// пополняем map с парами редиректов
function add(r) {
    var body = r.requestBody;
    var pair = JSON.parse(body);
    if (!pair.from || pair.to) {
        r.return(400, "invalid request: ...");
        return;
    }
    
    var map = open_db();
    // ...
    map[pair.from] = pair.to;
    
    r.return(commit_db(map));
}

Отладка

Для отладки используем докер:

docker run -i -t nginx:mainline /usr/bin/njs

Что уже есть в интерпретаторе

Ближайшие планы

Ссылки

Репозиторий: github.com/nginx/njs

Написать автору вопрос или устроиться на работу в команду njs:

njs-nginx-03