Нативные модули

Обзор
Когда использовать нативные модули
Загрузка нативных модулей в NGINX
Сборка нативных модулей
Дополнительные ресурсы

Обзор

Нативные модули позволяют загружать разделяемые библиотеки на основе C (файлы .so) в NGINX JavaScript для выполнения критичных по производительности операций или системных интеграций. Данная возможность доступна только с движком QuickJS и не поддерживается встроенным движком njs. Поддержка нативных модулей доступна начиная с версии 0.9.5.

Когда использовать нативные модули

Нативные модули могут использоваться в следующих случаях:

Нативные модули следует использовать для низкоуровневых примитивов, а не для сложной бизнес-логики, например для криптографических операций (хеширование, шифрование), сжатия/распаковки данных, разбора бинарных протоколов, высокопроизводительных строковых операций или математических вычислений. Сложная логика приложения должна оставаться в JavaScript, где её легче сопровождать, отлаживать и изменять.

Ограничения:

Загрузка нативных модулей в NGINX

Нативные модули загружаются с помощью следующих директив, указанных в контексте main:

Пример конфигурации:

js_load_http_native_module /path/to/mylib.so;
js_load_http_native_module /path/to/other.so as myalias;

http {
    js_import main.js;

    server {
        listen 8000;
        location / {
            js_content main.handler;
        }
    }
}

После загрузки модуль можно импортировать в JavaScript-коде:

// Импорт по имени файла
import * as mylib from 'mylib.so';

// Импорт по псевдониму
import * as myalias from 'myalias';

function handler(r) {
    let result = mylib.add(5, 10);
    r.return(200, `Result: ${result}\n`);
}

export default { handler };

Сборка нативных модулей

Нативные модули должны реализовывать функцию js_init_module в качестве точки входа. Эта функция вызывается QuickJS при загрузке модуля.

Пример простого нативного модуля, экспортирующего две функции:

#include <quickjs.h>

#define countof(x) (sizeof(x) / sizeof((x)[0]))

/* Сложение двух чисел */
static JSValue
js_add(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
    int a, b;

    if (argc < 2) {
        return JS_ThrowTypeError(ctx, "expected 2 arguments");
    }

    if (JS_ToInt32(ctx, &a, argv[0]) < 0) {
        return JS_EXCEPTION;
    }

    if (JS_ToInt32(ctx, &b, argv[1]) < 0) {
        return JS_EXCEPTION;
    }

    return JS_NewInt32(ctx, a + b);
}

/* Переворот строки */
static JSValue
js_reverse_string(JSContext *ctx, JSValueConst this_val, int argc,
    JSValueConst *argv)
{
    char        *reversed;
    size_t       i, len;
    JSValue      result;
    const char  *str;

    if (argc < 1) {
        return JS_ThrowTypeError(ctx, "expected 1 argument");
    }

    str = JS_ToCStringLen(ctx, &len, argv[0]);
    if (!str) {
        return JS_EXCEPTION;
    }

    reversed = js_malloc(ctx, len + 1);
    if (!reversed) {
        JS_FreeCString(ctx, str);
        return JS_EXCEPTION;
    }

    for (i = 0; i < len; i++) {
        reversed[i] = str[len - 1 - i];
    }
    reversed[len] = '\0';

    result = JS_NewString(ctx, reversed);

    js_free(ctx, reversed);
    JS_FreeCString(ctx, str);

    return result;
}

/* Список функций модуля */
static const JSCFunctionListEntry js_module_funcs[] = {
    JS_CFUNC_DEF("add", 2, js_add),
    JS_CFUNC_DEF("reverseString", 1, js_reverse_string),
};

/* Инициализация модуля */
static int
js_module_init(JSContext *ctx, JSModuleDef *m)
{
    return JS_SetModuleExportList(ctx, m, js_module_funcs,
                                  countof(js_module_funcs));
}

/* Обязательная точка входа */
JSModuleDef *
js_init_module(JSContext *ctx, const char *module_name)
{
    JSModuleDef  *m;

    m = JS_NewCModule(ctx, module_name, js_module_init);
    if (!m) {
        return NULL;
    }

    JS_AddModuleExportList(ctx, m, js_module_funcs,
                           countof(js_module_funcs));

    return m;
}

Для компиляции нативного модуля:

gcc -fPIC -shared -I/path/to/quickjs -o mymodule.so mymodule.c

где /path/to/quickjs — каталог, содержащий заголовочные файлы QuickJS.

Для корректного учёта памяти всегда используйте функции выделения памяти QuickJS (js_malloc, js_free) вместо функций стандартной библиотеки (malloc, free).

Дополнительные ресурсы

Для получения дополнительной информации о C API QuickJS: