Модель выполнения и точки интеграции
| Модель выполнения Точки интеграции HTTP Точки интеграции stream Выбор точки интеграции Состояние и время жизни Использование njs вне nginx |
Код JavaScript в njs никогда не выполняется самостоятельно. nginx вызывает его в фиксированном наборе точек интеграции, каждая из которых связывается с обработчиком директивой конфигурации. Стартовый сценарий, фоновые потоки и собственный цикл событий отсутствуют: nginx вызывает обработчик, и по его завершении управление возвращается nginx.
В статье приводятся сведения о вызове обработчиков и рекомендации по выбору подходящего. Подробное описание поведения директив см. в документации модулей ngx_http_js_module и ngx_stream_js_module.
Модель выполнения
- При загрузке конфигурации директива js_import компилирует модули JavaScript. Синтаксические ошибки обнаруживаются на этом этапе, до начала обработки трафика nginx.
- Когда запрос или stream-сессия достигает одной из точек интеграции, создаётся контекст JavaScript. В нём выполняются импортированные модули, после чего вызываются настроенные обработчики.
- По завершении запроса или сессии контекст уничтожается либо, при использовании движка QuickJS, возвращается в пул повторно используемых контекстов. Остаются только данные, явно сохранённые в разделяемый словарь или отправленные наружу.
Единственная точка интеграции, не связанная с клиентским запросом, — это
js_periodic, вызываемая по таймеру nginx.
Код JavaScript не может создать собственную точку интеграции:
он не может открыть слушающий сокет, породить поток
или инициировать свой вызов вне обработчиков,
заданных в конфигурации.
Точки интеграции HTTP
Обработчики получают первым аргументом объект
HTTP Request,
за исключением js_periodic,
который получает объект
Periodic Session.
| Директива | Когда вызывается | Асинхронные операции |
js_access |
на фазе доступа, до дальнейшей обработки запроса | поддерживаются |
js_content |
на фазе содержимого, вместо проксирования или отдачи статического файла | поддерживаются |
js_header_filter |
один раз, когда заголовок ответа готов к отправке | не поддерживаются |
js_body_filter |
для каждого блока данных тела ответа | не поддерживаются |
js_set |
при первом обращении к переменной,
на той фазе, где это произошло,
либо при каждом обращении с параметром nocache |
не поддерживаются |
js_periodic |
через заданные промежутки времени, без клиентского запроса | поддерживаются |
Обработчики, для которых асинхронные операции не поддерживаются,
должны получить результат до возврата управления:
они не могут дождаться подзапроса, запроса наружу или таймера.
Такой обработчик тем не менее может быть объявлен как
async, если всё, что он ожидает,
завершается немедленно.
Обработчиков для фаз перезаписи и логирования нет.
Переменная js_set, на которую ссылается директива
соответствующей фазы, вычисляется на этой фазе,
что и является обычным способом выполнить код
в самом конце обработки запроса.
Точки интеграции stream
Обработчики получают первым аргументом объект
Stream Session,
за исключением js_periodic,
который получает объект
Periodic Session.
| Директива | Когда вызывается | Асинхронные операции |
js_access |
на фазе доступа | в колбэках
s.on() |
js_preread |
на фазе предварительного чтения, до подключения к проксируемому серверу | в колбэках
s.on() |
js_filter |
на фазе содержимого, для установки фильтров данных в обоих направлениях | не поддерживаются |
js_set |
при первом обращении к переменной,
на той фазе, где это произошло,
либо при каждом обращении с параметром nocache |
не поддерживаются |
js_periodic |
через заданные промежутки времени, без клиентской сессии | поддерживаются |
Обработчики js_access, js_preread
и js_filter вызываются один раз
и предназначены для установки колбэков
s.on()
для данных, которые придут позже.
Сам обработчик должен вернуть управление сразу.
Выбор точки интеграции
- сформировать ответ, а не проксировать его
-
js_content - разрешить или запретить запрос или соединение, возможно, обратившись к внешнему сервису
-
js_accessдля запросов,js_accessдля соединений илиjs_preread, если решение зависит от данных, присланных клиентом - вычислить значение для другой директивы nginx
-
js_setсвязывает обработчик с переменной, и именно так вычисленное значение попадает вproxy_pass,log_formatи другие директивы, принимающие переменные. Вызванный ранее обработчик может также присвоить значение переменной, объявленной директивойjs_var, черезr.variables - изменить ответ проксируемого сервера
-
js_header_filterдля заголовка,js_body_filterдля тела - изменить поток байт проксируемого соединения
-
js_filter - обновить кэш, опросить API или отправить метрики без клиентского запроса
-
js_periodic
Для одного запроса можно использовать несколько обработчиков, например проверку доступа и вслед за ней фильтр тела ответа. Каждый из них является отдельным вызовом со своими ограничениями.
Состояние и время жизни
Код верхнего уровня модуля выполняется для каждого нового контекста JavaScript, то есть для каждого запроса или stream-сессии, в которых вызывается JavaScript. Для движка QuickJS контекст вместо этого может быть взят из пула повторно используемых контекстов, см. js_context_reuse; в этом случае модули заново не выполняются, а изменения, внесённые в область видимости модуля ранее, наследуются.
Поэтому переменные уровня модуля нельзя использовать для передачи данных между запросами или для кэширования результатов: в зависимости от движка и от повторного использования контекста такие данные либо теряются, либо наследуются от постороннего запроса. Дорогая инициализация в области видимости модуля также оплачивается для каждого нового контекста.
В nginx рабочие процессы по умолчанию не используют совместно память. njs предоставляет механизм для соместного использования состояния между рабочими процессами — разделяемый словарь, объявляемый директивой js_shared_dict_zone.
Асинхронные операции, такие как
ngx.fetch(),
r.subrequest()
и
setTimeout(),
выполняются циклом событий nginx.
Потоки не используются: обработчик, выполняющий длительные вычисления,
блокирует весь рабочий процесс, а вместе с ним и любое другое соединение,
обслуживаемое этим процессом.
Использование njs вне nginx
- Утилита командной строки запускает njs без nginx, что удобно для разработки и отладки частей сценария, не зависящих от запроса.
- njs не является Node.js. В нём реализованы язык ECMAScript, набор встроенных модулей и объекты nginx, но не API Node.js и не разрешение имён пакетов. Тем не менее, библиотеки можно использовать, если предварительно собрать их в один файл, см. Использование модулей Node.js в njs.
- Нативные модули расширяют njs кодом на C. Разделяемая библиотека загружается при разборе конфигурации, а зарегистрированный ею модуль затем импортируется, как любой другой.