Модель выполнения и точки интеграции

Модель выполнения
Точки интеграции HTTP
Точки интеграции stream
Выбор точки интеграции
Состояние и время жизни
Использование njs вне nginx

Код JavaScript в njs никогда не выполняется самостоятельно. nginx вызывает его в фиксированном наборе точек интеграции, каждая из которых связывается с обработчиком директивой конфигурации. Стартовый сценарий, фоновые потоки и собственный цикл событий отсутствуют: nginx вызывает обработчик, и по его завершении управление возвращается nginx.

В статье приводятся сведения о вызове обработчиков и рекомендации по выбору подходящего. Подробное описание поведения директив см. в документации модулей ngx_http_js_module и ngx_stream_js_module.

Модель выполнения

  1. При загрузке конфигурации директива js_import компилирует модули JavaScript. Синтаксические ошибки обнаруживаются на этом этапе, до начала обработки трафика nginx.
  2. Когда запрос или stream-сессия достигает одной из точек интеграции, создаётся контекст JavaScript. В нём выполняются импортированные модули, после чего вызываются настроенные обработчики.
  3. По завершении запроса или сессии контекст уничтожается либо, при использовании движка 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