Spec-Zone.ru › nginx

Руководство по разработке

  • Введение
  • Структура кода
  • Файлы включения
  • Целые числа
  • Распространённые коды возврата
  • Обработка ошибок
  • Строки
  • Общие сведения
  • Форматирование
  • Преобразование чисел
  • Регулярные выражения
  • Время
  • Контейнеры
  • Массив
  • Список
  • Очередь
  • Красно-чёрное дерево
  • Хеш-таблица
  • Управление памятью
  • Куча
  • Пул
  • Разделяемая память
  • Ведение журнала
  • Цикл
  • Буфер
  • Сеть
  • Соединение
  • События
  • Событие
  • События ввода-вывода
  • События таймера
  • Отложенные события
  • Цикл обработки событий
  • Процессы
  • Потоки
  • Модули
  • Добавление новых модулей
  • Основные модули
  • Директивы конфигурации
  • HTTP
  • Соединение
  • Запрос
  • Конфигурация
  • Фазы
  • Переменные
  • Составные значения
  • Перенаправление запросов
  • Подзапросы
  • Завершение запроса
  • Тело запроса
  • Фильтры тела запроса
  • Ответ
  • Тело ответа
  • Фильтры тела ответа
  • Создание модулей фильтров
  • Повторное использование буферов
  • Балансировка нагрузки
  • Примеры
  • Стиль кода
  • Общие правила
  • Файлы
  • Комментарии
  • Препроцессор
  • Типы
  • Переменные
  • Функции
  • Выражения
  • Условия и циклы
  • Метки
  • Отладка проблем с памятью
  • Распространённые ошибки
  • Написание модуля на C
  • Строки C
  • Глобальные переменные
  • Ручное управление памятью
  • Потоки
  • Блокирующие библиотеки
  • HTTP-запросы к внешним службам

Введение

Структура кода

  • auto — сценарии сборки
  • src
    • core — базовые типы и функции — строки, массивы, журнал, пул и т. д.
    • event — ядро событий
      • modules — модули уведомления о событиях: epoll, kqueue, select и т. д.
    • http — основной модуль HTTP и общий код
      • modules — прочие модули HTTP
      • v2 — HTTP/2
    • mail — почтовые модули
    • os — платформозависимый код
      • unix
      • win32
    • stream — модули потоковой передачи

Файлы включения

Следующие два оператора #include должны находиться в начале каждого файла nginx:

#include <ngx_config.h>
#include <ngx_core.h>

Кроме того, код HTTP должен включать

#include <ngx_http.h>

Код почтовых модулей должен включать

#include <ngx_mail.h>

Код потоковой передачи должен включать

#include <ngx_stream.h>

Целые числа

В коде nginx общего назначения используются два целочисленных типа: ngx_int_t и ngx_uint_t, которые являются псевдонимами типов intptr_t и uintptr_t соответственно.

Распространённые коды возврата

Большинство функций nginx возвращают следующие коды:

  • NGX_OK — операция выполнена успешно.
  • NGX_ERROR — операция завершилась ошибкой.
  • NGX_AGAIN — операция не завершена; вызовите функцию ещё раз.
  • NGX_DECLINED — операция отклонена, например, потому что она отключена в конфигурации. Это не является ошибкой.
  • NGX_BUSY — ресурс недоступен.
  • NGX_DONE — операция завершена или продолжена в другом месте. Также используется как альтернативный код успешного выполнения.
  • NGX_ABORT — функция прервана. Также используется как альтернативный код ошибки.

Обработка ошибок

Макрос ngx_errno возвращает код последней системной ошибки. В POSIX-системах он соответствует errno, а в Windows — вызову GetLastError(). Макрос ngx_socket_errno возвращает номер последней ошибки сокета. Как и макрос ngx_errno, в POSIX-системах он соответствует errno. В Windows он соответствует вызову WSAGetLastError(). Многократное последовательное обращение к значениям ngx_errno или ngx_socket_errno может привести к снижению производительности. Если значение ошибки может понадобиться несколько раз, сохраните его в локальной переменной типа ngx_err_t. Для установки ошибок используйте макросы ngx_set_errno(errno) и ngx_set_socket_errno(errno).

Значения ngx_errno и ngx_socket_errno можно передавать функциям ведения журнала ngx_log_error() и ngx_log_debugX(); в этом случае в сообщение журнала добавляется текст системной ошибки.

Пример использования ngx_errno:

ngx_int_t
ngx_my_kill(ngx_pid_t pid, ngx_log_t *log, int signo)
{
    ngx_err_t  err;

    if (kill(pid, signo) == -1) {
        err = ngx_errno;

        ngx_log_error(NGX_LOG_ALERT, log, err, "kill(%P, %d) failed", pid, signo);

        if (err == NGX_ESRCH) {
            return 2;
        }

        return 1;
    }

    return 0;
}

Строки

Общие сведения

Для строк C nginx использует указатель на символы без знака u_char *.

Строковый тип nginx ngx_str_t определён следующим образом:

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

Поле len содержит длину строки, а data — данные строки. Строка, хранящаяся в ngx_str_t, может быть завершена или не завершена нулевым символом после len байтов. В большинстве случаев она не завершается нулевым символом. Однако в некоторых частях кода (например, при разборе конфигурации) известно, что объекты ngx_str_t завершаются нулевым символом. Это упрощает сравнение строк и позволяет легче передавать их системным вызовам.

Операции со строками в nginx объявлены в src/core/ngx_string.h. Некоторые из них являются обёртками над стандартными функциями C:

  • ngx_strcmp()
  • ngx_strncmp()
  • ngx_strstr()
  • ngx_strlen()
  • ngx_strchr()
  • ngx_memcmp()
  • ngx_memset()
  • ngx_memcpy()
  • ngx_memmove()

Другие функции для работы со строками специфичны для nginx

  • ngx_memzero() — заполняет память нулями.
  • ngx_explicit_memzero() — выполняет то же, что и ngx_memzero(), но этот вызов никогда не удаляется компилятором при оптимизации удаления мёртвых записей. Эту функцию можно использовать для очистки конфиденциальных данных, например паролей и ключей.
  • ngx_cpymem() — выполняет то же, что и ngx_memcpy(), но возвращает конечный адрес назначения. Эта функция удобна для последовательного добавления нескольких строк.
  • ngx_movemem() — выполняет то же, что и ngx_memmove(), но возвращает конечный адрес назначения.
  • ngx_strlchr() — ищет символ в строке, ограниченной двумя указателями.

Следующие функции выполняют преобразование регистра и сравнение:

  • ngx_tolower()
  • ngx_toupper()
  • ngx_strlow()
  • ngx_strcasecmp()
  • ngx_strncasecmp()

Следующие макросы упрощают инициализацию строк:

  • ngx_string(text) — статический инициализатор типа ngx_str_t на основе строкового литерала C text
  • ngx_null_string — статический инициализатор пустой строки типа ngx_str_t
  • ngx_str_set(str, text) — инициализирует строку str типа ngx_str_t * строковым литералом C text
  • ngx_str_null(str) — инициализирует строку str типа ngx_str_t * пустой строкой

Форматирование

Следующие функции форматирования поддерживают типы, специфичные для nginx:

  • ngx_sprintf(buf, fmt, ...)
  • ngx_snprintf(buf, max, fmt, ...)
  • ngx_slprintf(buf, last, fmt, ...)
  • ngx_vslprintf(buf, last, fmt, args)
  • ngx_vsnprintf(buf, max, fmt, args)

Полный список параметров форматирования, поддерживаемых этими функциями, приведён в src/core/ngx_string.c. Вот некоторые из них:

  • %O — off_t
  • %T — time_t
  • %z — ssize_t
  • %i — ngx_int_t
  • %p — void *
  • %V — ngx_str_t *
  • %s — u_char * (с завершающим нулевым символом)
  • %*s — size_t + u_char *

Перед большинством типов можно добавить u, чтобы сделать их беззнаковыми. Для преобразования вывода в шестнадцатеричный формат используйте X или x.

Например:

u_char      buf[NGX_INT_T_LEN];
size_t      len;
ngx_uint_t  n;

/* set n here */

len = ngx_sprintf(buf, "%ui", n) — buf;

Преобразование чисел

В nginx реализовано несколько функций для преобразования чисел. Первые четыре преобразуют строку заданной длины в положительное целое число указанного типа. В случае ошибки они возвращают NGX_ERROR.

  • ngx_atoi(line, n) — ngx_int_t
  • ngx_atosz(line, n) — ssize_t
  • ngx_atoof(line, n) — off_t
  • ngx_atotm(line, n) — time_t

Существуют ещё две функции преобразования чисел. Как и первые четыре, в случае ошибки они возвращают NGX_ERROR.

  • ngx_atofp(line, n, point) — преобразует число с фиксированной точкой заданной длины в положительное целое число типа ngx_int_t. Результат сдвигается влево на point десятичных разрядов. Строковое представление числа должно содержать не более points дробных знаков. Например, ngx_atofp("10.5", 4, 2) возвращает 1050.
  • ngx_hextoi(line, n) — преобразует шестнадцатеричное представление положительного целого числа в ngx_int_t.

Регулярные выражения

Интерфейс регулярных выражений в nginx является обёрткой над библиотекой PCRE. Соответствующий заголовочный файл — src/core/ngx_regex.h.

Чтобы использовать регулярное выражение для сопоставления строк, его сначала нужно скомпилировать; обычно это выполняется на этапе конфигурации. Поскольку поддержка PCRE является необязательной, весь код, использующий этот интерфейс, должен быть защищён окружающим макросом NGX_PCRE:

#if (NGX_PCRE)
ngx_regex_t          *re;
ngx_regex_compile_t   rc;

u_char                errstr[NGX_MAX_CONF_ERRSTR];

ngx_str_t  value = ngx_string("message (\\d\\d\\d).*Codeword is '(?<cw>\\w+)'");

ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

rc.pattern = value;
rc.pool = cf->pool;
rc.err.len = NGX_MAX_CONF_ERRSTR;
rc.err.data = errstr;
/* rc.options can be set to NGX_REGEX_CASELESS */

if (ngx_regex_compile(&rc) != NGX_OK) {
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
    return NGX_CONF_ERROR;
}

re = rc.regex;
#endif

После успешной компиляции поля captures и named_captures структуры ngx_regex_compile_t содержат количество всех захватов и именованных захватов, найденных в регулярном выражении, соответственно.

Затем скомпилированное регулярное выражение можно использовать для поиска совпадений в строках:

ngx_int_t  n;
int        captures[(1 + rc.captures) * 3];

ngx_str_t input = ngx_string("This is message 123. Codeword is 'foobar'.");

n = ngx_regex_exec(re, &input, captures, (1 + rc.captures) * 3);
if (n >= 0) {
    /* string matches expression */

} else if (n == NGX_REGEX_NO_MATCHED) {
    /* no match was found */

} else {
    /* some error */
    ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_regex_exec_n " failed: %i", n);
}

Аргументы ngx_regex_exec() — это скомпилированное регулярное выражение re, строка для сопоставления input, необязательный массив целых чисел для хранения найденных captures и размер этого массива size. Размер массива captures должен быть кратен трём, как того требует API PCRE. В примере размер рассчитывается исходя из общего количества захватов плюс один для самой совпавшей строки.

Если совпадения найдены, получить доступ к захватам можно следующим образом:

u_char     *p;
size_t      size;
ngx_str_t   name, value;

/* all captures */
for (i = 0; i < n * 2; i += 2) {
    value.data = input.data + captures[i];
    value.len = captures[i + 1] — captures[i];
}

/* accessing named captures */

size = rc.name_size;
p = rc.names;

for (i = 0; i < rc.named_captures; i++, p += size) {

    /* capture name */
    name.data = &p[2];
    name.len = ngx_strlen(name.data);

    n = 2 * ((p[0] << 8) + p[1]);

    /* captured value */
    value.data = &input.data[captures[n]];
    value.len = captures[n + 1] — captures[n];
}

Функция ngx_regex_exec_array() принимает массив элементов ngx_regex_elt_t (которые представляют собой скомпилированные регулярные выражения с соответствующими именами), строку для сопоставления и журнал. Функция применяет выражения из массива к строке, пока не найдёт совпадение или не закончатся выражения. При наличии совпадения возвращается NGX_OK, в противном случае — NGX_DECLINED, а в случае ошибки — NGX_ERROR.

Время

Структура ngx_time_t представляет время с помощью трёх отдельных типов для секунд, миллисекунд и смещения относительно GMT:

typedef struct {
    time_t      sec;
    ngx_uint_t  msec;
    ngx_int_t   gmtoff;
} ngx_time_t;

Структура ngx_tm_t является псевдонимом struct tm на платформах UNIX и SYSTEMTIME в Windows.

Чтобы получить текущее время, обычно достаточно обратиться к одной из доступных глобальных переменных, содержащих кэшированное значение времени в нужном формате.

Доступны следующие строковые представления:

  • ngx_cached_err_log_time — используется в записях журнала ошибок: "1970/09/28 12:00:00"
  • ngx_cached_http_log_time — используется в записях журнала доступа HTTP: "28/Sep/1970:12:00:00 +0600"
  • ngx_cached_syslog_time — используется в записях системного журнала: "Sep 28 12:00:00"
  • ngx_cached_http_time — используется в заголовках HTTP: "Mon, 28 Sep 1970 06:00:00 GMT"
  • ngx_cached_http_log_iso8601 — формат стандарта ISO 8601: "1970-09-28T12:00:00+06:00"

Макросы ngx_time() и ngx_timeofday() возвращают текущее время в секундах и являются предпочтительным способом доступа к кэшированному значению времени.

Чтобы получить время явно, используйте ngx_gettimeofday(), обновляющую свой аргумент (указатель на struct timeval). Время обновляется каждый раз, когда nginx возвращается в цикл обработки событий после системных вызовов. Для немедленного обновления времени вызовите ngx_time_update() или ngx_time_sigsafe_update(), если время обновляется в контексте обработчика сигнала.

Следующие функции преобразуют time_t в указанное разложенное представление времени. Первая функция в каждой паре преобразует time_t в ngx_tm_t, а вторая (с вставкой _libc_) — в struct tm:

  • ngx_gmtime(), ngx_libc_gmtime() — время в формате UTC
  • ngx_localtime(), ngx_libc_localtime() — время относительно местного часового пояса

Функция ngx_http_time(buf, time) возвращает строковое представление, подходящее для использования в заголовках HTTP (например, "Mon, 28 Sep 1970 06:00:00 GMT"). Функция ngx_http_cookie_time(buf, time) возвращает строковое представление, подходящее для файлов cookie HTTP ("Thu, 31-Dec-37 23:55:55 GMT").

Контейнеры

Массив

Тип массива nginx ngx_array_t определён следующим образом

typedef struct {
    void        *elts;
    ngx_uint_t   nelts;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *pool;
} ngx_array_t;

Элементы массива доступны в поле elts. Поле nelts содержит количество элементов. Поле size содержит размер одного элемента и задаётся при инициализации массива.

Используйте вызов ngx_array_create(pool, n, size) для создания массива в пуле, а вызов ngx_array_init(array, pool, n, size) — для инициализации уже выделенного объекта массива.

ngx_array_t  *a, b;

/* create an array of strings with preallocated memory for 10 elements */
a = ngx_array_create(pool, 10, sizeof(ngx_str_t));

/* initialize string array for 10 elements */
ngx_array_init(&b, pool, 10, sizeof(ngx_str_t));

Для добавления элементов в массив используйте следующие функции:

  • ngx_array_push(a) добавляет один элемент в конец и возвращает указатель на него
  • ngx_array_push_n(a, n) добавляет n элементов в конец и возвращает указатель на первый из них

Если объёма уже выделенной памяти недостаточно для новых элементов, выделяется новый блок памяти, в который копируются существующие элементы. Новый блок памяти обычно вдвое больше существующего.

s = ngx_array_push(a);
ss = ngx_array_push_n(&b, 3);

Список

В nginx список представляет собой последовательность массивов, оптимизированную для добавления потенциально большого количества элементов. Тип списка ngx_list_t определён следующим образом:

typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;

Фактические элементы хранятся в частях списка, определённых следующим образом:

typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};

Перед использованием список необходимо инициализировать вызовом ngx_list_init(list, pool, n, size) или создать вызовом ngx_list_create(pool, n, size). Обе функции принимают размер одного элемента и количество элементов в каждой части списка. Для добавления элемента в список используйте функцию ngx_list_push(list). Для перебора элементов обращайтесь непосредственно к полям списка, как показано в примере:

ngx_str_t        *v;
ngx_uint_t        i;
ngx_list_t       *list;
ngx_list_part_t  *part;

list = ngx_list_create(pool, 100, sizeof(ngx_str_t));
if (list == NULL) { /* error */ }

/* add items to the list */

v = ngx_list_push(list);
if (v == NULL) { /* error */ }
ngx_str_set(v, "foo");

v = ngx_list_push(list);
if (v == NULL) { /* error */ }
ngx_str_set(v, "bar");

/* iterate over the list */

part = &list->part;
v = part->elts;

for (i = 0; /* void */; i++) {

    if (i >= part->nelts) {
        if (part->next == NULL) {
            break;
        }

        part = part->next;
        v = part->elts;
        i = 0;
    }

    ngx_do_smth(&v[i]);
}

Списки используются преимущественно для заголовков HTTP-запросов и ответов.

Списки не поддерживают удаление элементов. Однако при необходимости элементы можно внутренне пометить как отсутствующие, не удаляя их из списка. Например, чтобы пометить выходные заголовки HTTP (которые хранятся как объекты ngx_table_elt_t) как отсутствующие, установите поле hash в ngx_table_elt_t равным нулю. Такие элементы явно пропускаются при переборе заголовков.

Очередь

В nginx очередь представляет собой двусвязный список с встраиваемыми узлами; каждый узел определяется следующим образом:

typedef struct ngx_queue_s  ngx_queue_t;

struct ngx_queue_s {
    ngx_queue_t  *prev;
    ngx_queue_t  *next;
};

Головной узел очереди не связан ни с какими данными. Перед использованием вызовите ngx_queue_init(q) для инициализации головного узла списка. Очереди поддерживают следующие операции:

  • ngx_queue_insert_head(h, x), ngx_queue_insert_tail(h, x) — вставить новый узел
  • ngx_queue_remove(x) — удалить узел очереди
  • ngx_queue_split(h, q, n) — разделить очередь в указанном узле, поместив хвост очереди в отдельную очередь
  • ngx_queue_add(h, n) — добавить вторую очередь в первую
  • ngx_queue_head(h), ngx_queue_last(h) — получить первый или последний узел очереди
  • ngx_queue_sentinel(h) — получить объект-сторож очереди, на котором следует завершить перебор
  • ngx_queue_data(q, type, link) — получить ссылку на начало структуры данных узла очереди с учётом смещения поля очереди в ней

Пример:

typedef struct {
    ngx_str_t    value;
    ngx_queue_t  queue;
} ngx_foo_t;

ngx_foo_t    *f;
ngx_queue_t   values, *q;

ngx_queue_init(&values);

f = ngx_palloc(pool, sizeof(ngx_foo_t));
if (f == NULL) { /* error */ }
ngx_str_set(&f->value, "foo");

ngx_queue_insert_tail(&values, &f->queue);

/* insert more nodes here */

for (q = ngx_queue_head(&values);
     q != ngx_queue_sentinel(&values);
     q = ngx_queue_next(q))
{
    f = ngx_queue_data(q, ngx_foo_t, queue);

    ngx_do_smth(&f->value);
}

Красно-чёрное дерево

Заголовочный файл src/core/ngx_rbtree.h предоставляет доступ к эффективной реализации красно-чёрных деревьев.

typedef struct {
    ngx_rbtree_t       rbtree;
    ngx_rbtree_node_t  sentinel;

    /* custom per-tree data here */
} my_tree_t;

typedef struct {
    ngx_rbtree_node_t  rbnode;

    /* custom per-node data */
    foo_t              val;
} my_node_t;

Для работы с деревом в целом нужны два узла: корень и сторожевой узел. Обычно их добавляют в пользовательскую структуру, что позволяет организовать данные в дерево, листья которого содержат ссылку на ваши данные или сами включают их.

Для инициализации дерева:

my_tree_t  root;

ngx_rbtree_init(&root.rbtree, &root.sentinel, insert_value_function);

Для обхода дерева и вставки новых значений используйте функции «insert_value». Например, функция ngx_str_rbtree_insert_value работает с типом ngx_str_t. Её аргументы — указатели на корневой узел дерева, новый узел для добавления и сторожевой узел дерева.

void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
                                 ngx_rbtree_node_t *node,
                                 ngx_rbtree_node_t *sentinel)

Обход дерева достаточно прост; его можно представить следующим шаблоном функции поиска:

my_node_t *
my_rbtree_lookup(ngx_rbtree_t *rbtree, foo_t *val, uint32_t hash)
{
    ngx_int_t           rc;
    my_node_t          *n;
    ngx_rbtree_node_t  *node, *sentinel;

    node = rbtree->root;
    sentinel = rbtree->sentinel;

    while (node != sentinel) {

        n = (my_node_t *) node;

        if (hash != node->key) {
            node = (hash < node->key) ? node->left : node->right;
            continue;
        }

        rc = compare(val, node->val);

        if (rc < 0) {
            node = node->left;
            continue;
        }

        if (rc > 0) {
            node = node->right;
            continue;
        }

        return n;
    }

    return NULL;
}

Функция compare() — это классическая функция сравнения, возвращающая значение меньше нуля, равное нулю или больше нуля. Для ускорения поиска и во избежание сравнения больших пользовательских объектов используется целочисленное поле хеша.

Чтобы добавить узел в дерево, выделите новый узел, инициализируйте его и вызовите ngx_rbtree_insert():

    my_node_t          *my_node;
    ngx_rbtree_node_t  *node;

    my_node = ngx_palloc(...);
    init_custom_data(&my_node->val);

    node = &my_node->rbnode;
    node->key = create_key(my_node->val);

    ngx_rbtree_insert(&root->rbtree, node);

Для удаления узла вызовите функцию ngx_rbtree_delete():

ngx_rbtree_delete(&root->rbtree, node);

Хеш-таблица

Функции для работы с хеш-таблицами объявлены в src/core/ngx_hash.h. Поддерживается точное сопоставление и сопоставление с подстановочными знаками. Последнее требует дополнительной настройки и описано в отдельном разделе ниже.

Перед инициализацией хеш-таблицы необходимо знать количество элементов, которые она будет содержать, чтобы nginx мог оптимально её построить. Нужно настроить два параметра: max_size и bucket_size; подробнее они описаны в отдельном документе. Обычно их значения задаёт пользователь. Параметры инициализации хеш-таблицы хранятся в типе ngx_hash_init_t, а сама хеш-таблица имеет тип ngx_hash_t:

ngx_hash_t       foo_hash;
ngx_hash_init_t  hash;

hash.hash = &foo_hash;
hash.key = ngx_hash_key;
hash.max_size = 512;
hash.bucket_size = ngx_align(64, ngx_cacheline_size);
hash.name = "foo_hash";
hash.pool = cf->pool;
hash.temp_pool = cf->temp_pool;

key — это указатель на функцию, создающую целочисленный ключ хеша из строки. Существуют две универсальные функции создания ключей: ngx_hash_key(data, len) и ngx_hash_key_lc(data, len). Последняя преобразует строку в нижний регистр, поэтому передаваемая строка должна быть доступна для записи. Если это не так, передайте функции флаг NGX_HASH_READONLY_KEY при инициализации массива ключей (см. ниже).

Ключи хеш-таблицы хранятся в ngx_hash_keys_arrays_t и инициализируются с помощью ngx_hash_keys_array_init(arr, type). Второй параметр (type) определяет объём ресурсов, предварительно выделяемых для хеш-таблицы; он может принимать значение NGX_HASH_SMALL или NGX_HASH_LARGE. Последний вариант подходит, если ожидается, что хеш-таблица будет содержать тысячи элементов.

ngx_hash_keys_arrays_t  foo_keys;

foo_keys.pool = cf->pool;
foo_keys.temp_pool = cf->temp_pool;

ngx_hash_keys_array_init(&foo_keys, NGX_HASH_SMALL);

Для добавления ключей в массив ключей хеш-таблицы используйте функцию ngx_hash_add_key(keys_array, key, value, flags):

ngx_str_t k1 = ngx_string("key1");
ngx_str_t k2 = ngx_string("key2");

ngx_hash_add_key(&foo_keys, &k1, &my_data_ptr_1, NGX_HASH_READONLY_KEY);
ngx_hash_add_key(&foo_keys, &k2, &my_data_ptr_2, NGX_HASH_READONLY_KEY);

Для построения хеш-таблицы вызовите функцию ngx_hash_init(hinit, key_names, nelts):

ngx_hash_init(&hash, foo_keys.keys.elts, foo_keys.keys.nelts);

Функция завершается ошибкой, если значения параметров max_size или bucket_size слишком малы.

После построения хеш-таблицы используйте функцию ngx_hash_find(hash, key, name, len) для поиска элементов:

my_data_t   *data;
ngx_uint_t   key;

key = ngx_hash_key(k1.data, k1.len);

data = ngx_hash_find(&foo_hash, key, k1.data, k1.len);
if (data == NULL) {
    /* key not found */
}

Сопоставление с подстановочными знаками

Для создания хеш-таблицы с поддержкой подстановочных знаков используйте тип ngx_hash_combined_t. Он включает описанный выше тип хеш-таблицы и два дополнительных массива ключей: dns_wc_head и dns_wc_tail. Инициализация основных свойств аналогична инициализации обычной хеш-таблицы:

ngx_hash_init_t      hash
ngx_hash_combined_t  foo_hash;

hash.hash = &foo_hash.hash;
hash.key = ...;

Добавлять ключи с подстановочными знаками можно с помощью флага NGX_HASH_WILDCARD_KEY:

/* k1 = ".example.org"; */
/* k2 = "foo.*";        */
ngx_hash_add_key(&foo_keys, &k1, &data1, NGX_HASH_WILDCARD_KEY);
ngx_hash_add_key(&foo_keys, &k2, &data2, NGX_HASH_WILDCARD_KEY);

Функция распознаёт подстановочные знаки и добавляет ключи в соответствующие массивы. Описание синтаксиса подстановочных знаков и алгоритма сопоставления см. в документации модуля map.

В зависимости от содержимого добавленных ключей может потребоваться инициализировать до трёх массивов ключей: один для точного сопоставления (описан выше) и ещё два для поиска от начала или конца строки:

if (foo_keys.dns_wc_head.nelts) {

    ngx_qsort(foo_keys.dns_wc_head.elts,
              (size_t) foo_keys.dns_wc_head.nelts,
              sizeof(ngx_hash_key_t),
              cmp_dns_wildcards);

    hash.hash = NULL;
    hash.temp_pool = pool;

    if (ngx_hash_wildcard_init(&hash, foo_keys.dns_wc_head.elts,
                               foo_keys.dns_wc_head.nelts)
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    foo_hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
}

Массив ключей необходимо отсортировать, а результаты инициализации добавить в объединённую хеш-таблицу. Массив dns_wc_tail инициализируется аналогичным образом.

Поиск в объединённой хеш-таблице выполняет функция ngx_hash_find_combined(chash, key, name, len):

/* key = "bar.example.org"; — will match ".example.org" */
/* key = "foo.example.com"; — will match "foo.*"        */

hkey = ngx_hash_key(key.data, key.len);
res = ngx_hash_find_combined(&foo_hash, hkey, key.data, key.len);

Управление памятью

Куча

Для выделения памяти из системной кучи используйте следующие функции:

  • ngx_alloc(size, log) — выделяет память из системной кучи. Это обёртка над malloc() с поддержкой ведения журнала. Ошибки выделения и отладочная информация записываются в log.
  • ngx_calloc(size, log) — выделяет память из системной кучи, как ngx_alloc(), но после выделения заполняет её нулями.
  • ngx_memalign(alignment, size, log) — выделяет выровненную память из системной кучи. На платформах, предоставляющих эту функцию, является обёрткой над posix_memalign(). В противном случае реализация использует ngx_alloc(), обеспечивающую максимальное выравнивание.
  • ngx_free(p) — освобождает выделенную память. Это обёртка над free()

Пул

Большинство выделений памяти в nginx выполняется в пулах. Память, выделенная в пуле nginx, автоматически освобождается при уничтожении пула. Это обеспечивает высокую производительность выделения памяти и упрощает управление памятью.

Внутри пул выделяет объекты в непрерывных блоках памяти. Когда блок заполняется, выделяется новый блок и добавляется в список блоков памяти пула. Если запрошенный объём памяти слишком велик и не помещается в блок, запрос передаётся системному аллокатору, а возвращённый указатель сохраняется в пуле для последующего освобождения.

Для пулов nginx используется тип ngx_pool_t. Поддерживаются следующие операции:

  • ngx_create_pool(size, log) — создаёт пул с указанным размером блока. Возвращаемый объект пула также выделяется в пуле. Значение size должно быть не меньше NGX_MIN_POOL_SIZE и кратно NGX_POOL_ALIGNMENT.
  • ngx_destroy_pool(pool) — освобождает всю память пула, включая сам объект пула.
  • ngx_palloc(pool, size) — выделяет выровненную память из указанного пула.
  • ngx_pcalloc(pool, size) — выделяет выровненную память из указанного пула и заполняет её нулями.
  • ngx_pnalloc(pool, size) — выделяет невыровненную память из указанного пула. В основном используется для выделения строк.
  • ngx_pfree(pool, p) — освобождает память, ранее выделенную в указанном пуле. Освобождать можно только память, выделенную запросами, переданными системному аллокатору.
u_char      *p;
ngx_str_t   *s;
ngx_pool_t  *pool;

pool = ngx_create_pool(1024, log);
if (pool == NULL) { /* error */ }

s = ngx_palloc(pool, sizeof(ngx_str_t));
if (s == NULL) { /* error */ }
ngx_str_set(s, "foo");

p = ngx_pnalloc(pool, 3);
if (p == NULL) { /* error */ }
ngx_memcpy(p, "foo", 3);

Связанные списки (ngx_chain_t) активно используются в nginx, поэтому реализация пула nginx позволяет повторно использовать их элементы. Поле chain структуры ngx_pool_t содержит список ранее выделенных элементов, готовых к повторному использованию. Для эффективного выделения элемента связанного списка в пуле используйте функцию ngx_alloc_chain_link(pool). Она ищет свободный элемент в списке пула и выделяет новый, если список пула пуст. Для освобождения элемента вызовите функцию ngx_free_chain(pool, cl).

В пуле можно зарегистрировать обработчики очистки. Обработчик очистки — это функция обратного вызова с аргументом, которая вызывается при уничтожении пула. Обычно пул привязан к определённому объекту nginx (например, HTTP-запросу) и уничтожается по окончании срока жизни этого объекта. Регистрация обработчика очистки пула — удобный способ освобождения ресурсов, закрытия файловых дескрипторов или выполнения завершающих действий с общими данными, связанными с основным объектом.

Для регистрации обработчика очистки пула вызовите ngx_pool_cleanup_add(pool, size); функция возвращает указатель ngx_pool_cleanup_t, который должен заполнить вызывающий код. Используйте аргумент size для выделения контекста обработчика очистки.

ngx_pool_cleanup_t  *cln;

cln = ngx_pool_cleanup_add(pool, 0);
if (cln == NULL) { /* error */ }

cln->handler = ngx_my_cleanup;
cln->data = "foo";

...

static void
ngx_my_cleanup(void *data)
{
    u_char  *msg = data;

    ngx_do_smth(msg);
}

Разделяемая память

nginx использует разделяемую память для совместного доступа процессов к общим данным. Функция ngx_shared_memory_add(cf, name, size, tag) добавляет в цикл новую запись разделяемой памяти ngx_shm_zone_t. Функция получает name и size зоны. Имя каждой разделяемой зоны должно быть уникальным. Если запись разделяемой зоны с указанными name и tag уже существует, используется существующая запись. Функция завершается ошибкой, если существующая запись с тем же именем имеет другую метку. Обычно в качестве tag передаётся адрес структуры модуля, что позволяет повторно использовать разделяемые зоны по имени в пределах одного модуля nginx.

Структура записи разделяемой памяти ngx_shm_zone_t содержит следующие поля:

  • init — функция обратного вызова инициализации, вызываемая после отображения разделяемой зоны в реальную память
  • data — контекст данных, используемый для передачи произвольных данных функции обратного вызова init
  • noreuse — флаг, запрещающий повторное использование разделяемой зоны из предыдущего цикла
  • tag — метка разделяемой зоны
  • shm — платформозависимый объект типа ngx_shm_t, содержащий как минимум следующие поля:
    • addr — адрес отображённой разделяемой памяти, изначально равен NULL
    • size — размер разделяемой памяти
    • name — имя разделяемой памяти
    • log — журнал разделяемой памяти
    • exists — флаг, указывающий, что разделяемая память унаследована от главного процесса (только для Windows)

Записи разделяемых зон отображаются в реальную память в ngx_init_cycle() после разбора конфигурации. В POSIX-системах для создания анонимного отображения разделяемой памяти используется системный вызов mmap(). В Windows используется пара вызовов CreateFileMapping()/ MapViewOfFileEx().

Для выделения памяти в общей памяти nginx предоставляет тип пула slab ngx_slab_pool_t. Пул slab для выделения памяти автоматически создаётся в каждой общей зоне nginx. Пул расположен в начале общей зоны, и к нему можно обратиться с помощью выражения (ngx_slab_pool_t *) shm_zone->shm.addr. Чтобы выделить память в общей зоне, вызовите ngx_slab_alloc(pool, size) или ngx_slab_calloc(pool, size). Чтобы освободить память, вызовите ngx_slab_free(pool, p).

Пул slab делит всю общую зону на страницы. Каждая страница используется для выделения объектов одного и того же размера. Указанный размер должен быть степенью двойки и превышать минимальный размер в 8 байт. Значения, не соответствующие этому требованию, округляются вверх. Битовая маска для каждой страницы отслеживает, какие блоки используются, а какие свободны для выделения. Для размеров, превышающих половину страницы (обычно это 2048 байт), выделяется целая страница

Чтобы защитить данные в общей памяти от одновременного доступа, используйте мьютекс, доступный в поле mutex структуры ngx_slab_pool_t. Чаще всего пул slab использует мьютекс при выделении и освобождении памяти, но его также можно использовать для защиты любых других пользовательских структур данных, выделенных в общей зоне. Чтобы заблокировать или разблокировать мьютекс, вызовите соответственно ngx_shmtx_lock(&shpool->mutex) или ngx_shmtx_unlock(&shpool->mutex).

ngx_str_t        name;
ngx_foo_ctx_t   *ctx;
ngx_shm_zone_t  *shm_zone;

ngx_str_set(&name, "foo");

/* allocate shared zone context */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_foo_ctx_t));
if (ctx == NULL) {
    /* error */
}

/* add an entry for 64k shared zone */
shm_zone = ngx_shared_memory_add(cf, &name, 65536, &ngx_foo_module);
if (shm_zone == NULL) {
    /* error */
}

/* register init callback and context */
shm_zone->init = ngx_foo_init_zone;
shm_zone->data = ctx;


...


static ngx_int_t
ngx_foo_init_zone(ngx_shm_zone_t *shm_zone, void *data)
{
    ngx_foo_ctx_t  *octx = data;

    size_t            len;
    ngx_foo_ctx_t    *ctx;
    ngx_slab_pool_t  *shpool;

    value = shm_zone->data;

    if (octx) {
        /* reusing a shared zone from old cycle */
        ctx->value = octx->value;
        return NGX_OK;
    }

    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;

    if (shm_zone->shm.exists) {
        /* initialize shared zone context in Windows nginx worker */
        ctx->value = shpool->data;
        return NGX_OK;
    }

    /* initialize shared zone */

    ctx->value = ngx_slab_alloc(shpool, sizeof(ngx_uint_t));
    if (ctx->value == NULL) {
        return NGX_ERROR;
    }

    shpool->data = ctx->value;

    return NGX_OK;
}

Ведение журнала

Для ведения журнала nginx использует объекты ngx_log_t. Журналирование в nginx поддерживает несколько типов вывода:

  • stderr — запись в стандартный поток ошибок (stderr)
  • file — запись в файл
  • syslog — запись в syslog
  • memory — запись во внутреннее хранилище в памяти для целей разработки; позднее к памяти можно обратиться с помощью отладчика

Экземпляр журнала может представлять собой цепочку журналов, связанных друг с другом полем next. В этом случае каждое сообщение записывается во все журналы цепочки.

Для каждого журнала уровень важности определяет, какие сообщения записываются (регистрируются только события, которым присвоен этот уровень или более высокий). Поддерживаются следующие уровни важности:

  • NGX_LOG_EMERG
  • NGX_LOG_ALERT
  • NGX_LOG_CRIT
  • NGX_LOG_ERR
  • NGX_LOG_WARN
  • NGX_LOG_NOTICE
  • NGX_LOG_INFO
  • NGX_LOG_DEBUG

Для отладочного журналирования также проверяется маска отладки. Маски отладки:

  • NGX_LOG_DEBUG_CORE
  • NGX_LOG_DEBUG_ALLOC
  • NGX_LOG_DEBUG_MUTEX
  • NGX_LOG_DEBUG_EVENT
  • NGX_LOG_DEBUG_HTTP
  • NGX_LOG_DEBUG_MAIL
  • NGX_LOG_DEBUG_STREAM

Обычно журналы создаются существующим кодом nginx на основе директив error_log и доступны почти на каждом этапе обработки в объектах цикла, конфигурации, клиентского соединения и других.

В nginx предусмотрены следующие макросы журналирования:

  • ngx_log_error(level, log, err, fmt, ...) — запись ошибок
  • ngx_log_debug0(level, log, err, fmt), ngx_log_debug1(level, log, err, fmt, arg1) и т. д. — отладочное журналирование с поддержкой до восьми аргументов форматирования

Сообщение журнала форматируется в буфере размером NGX_MAX_ERROR_STR (в настоящее время 2048 байт) на стеке. Перед сообщением добавляются уровень важности, идентификатор процесса (PID), идентификатор соединения (хранится в log->connection) и текст системной ошибки. Для сообщений, не относящихся к отладке, также вызывается log->handler, чтобы добавить к сообщению журнала более подробную информацию. HTTP-модуль задаёт функцию ngx_http_log_error() в качестве обработчика журнала, чтобы регистрировать адреса клиента и сервера, текущее действие (хранится в log->action), строку запроса клиента, имя сервера и т. д.

/* specify what is currently done */
log->action = "sending mp4 to client";

/* error and debug log */
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely
              closed connection");

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
               "mp4 start:%ui, length:%ui", mp4->start, mp4->length);

Приведённый выше пример приводит к таким записям в журнале:

2016/09/16 22:08:52 [info] 17445#0: *1 client prematurely closed connection while
sending mp4 to client, client: 127.0.0.1, server: , request: "GET /file.mp4 HTTP/1.1"
2016/09/16 23:28:33 [debug] 22140#0: *1 mp4 start:0, length:10000

Цикл

Объект цикла хранит контекст времени выполнения nginx, созданный на основе определённой конфигурации. Его тип — ngx_cycle_t. На текущий цикл ссылается глобальная переменная ngx_cycle, которая наследуется рабочими процессами nginx при их запуске. При каждой перезагрузке конфигурации nginx на её основе создаётся новый цикл; старый цикл обычно удаляется после успешного создания нового.

Цикл создаётся функцией ngx_init_cycle(), которой в качестве аргумента передаётся предыдущий цикл. Функция находит файл конфигурации предыдущего цикла и наследует от него как можно больше ресурсов. При запуске nginx создаётся временный цикл с именем «init cycle», который затем заменяется фактическим циклом, построенным на основе конфигурации.

К членам цикла относятся:

  • pool — Пул цикла. Создаётся для каждого нового цикла.
  • log — Журнал цикла. Изначально наследуется от старого цикла, а после чтения конфигурации указывает на new_log.
  • new_log — Журнал цикла, созданный конфигурацией. На него влияет директива error_log в корневом контексте.
  • connections, connection_n — Массив соединений типа ngx_connection_t, создаваемых модулем событий при инициализации каждого рабочего процесса nginx. Директива worker_connections в конфигурации nginx задаёт количество соединений connection_n.
  • free_connections, free_connection_n — Список и количество доступных в данный момент соединений. Если доступных соединений нет, рабочий процесс nginx отказывается принимать новых клиентов или подключаться к вышестоящим серверам.
  • files, files_n — Массив для сопоставления файловых дескрипторов с соединениями nginx. Это сопоставление используют модули событий с флагом NGX_USE_FD_EVENT (в настоящее время это poll и devpoll).
  • conf_ctx — Массив конфигураций основных модулей. Конфигурации создаются и заполняются при чтении файлов конфигурации nginx.
  • modules, modules_n — Массив модулей типа ngx_module_t, как статических, так и динамических, загруженных текущей конфигурацией.
  • listening — Массив объектов прослушивания типа ngx_listening_t. Объекты прослушивания обычно добавляются директивой listen различных модулей, вызывающих функцию ngx_create_listening(). На основе объектов прослушивания создаются сокеты прослушивания.
  • paths — Массив путей типа ngx_path_t. Пути добавляются вызовом функции ngx_add_path() из модулей, которым предстоит работать с определёнными каталогами. Если этих каталогов нет, nginx создаёт их после чтения конфигурации. Кроме того, для каждого пути можно добавить два обработчика:
    • загрузчик пути — Выполняется только один раз в течение 60 секунд после запуска или перезагрузки nginx. Обычно загрузчик читает каталог и сохраняет данные в общей памяти nginx. Обработчик вызывается выделенным процессом nginx «nginx cache loader».
    • менеджер пути — Выполняется периодически. Обычно менеджер удаляет старые файлы из каталога и обновляет память nginx с учётом изменений. Обработчик вызывается выделенным процессом «nginx cache manager».
  • open_files — Список открытых файловых объектов типа ngx_open_file_t, создаваемых вызовом функции ngx_conf_open_file(). В настоящее время nginx использует такие открытые файлы для ведения журнала. После чтения конфигурации nginx открывает все файлы из списка open_files и сохраняет дескриптор каждого файла в поле fd объекта. Файлы открываются в режиме добавления данных и создаются, если отсутствуют. Файлы из списка повторно открываются рабочими процессами nginx после получения сигнала повторного открытия (чаще всего USR1). В этом случае дескриптор в поле fd заменяется новым значением.
  • shared_memory — Список зон общей памяти, каждая из которых добавляется вызовом функции ngx_shared_memory_add(). Общие зоны отображаются в один и тот же диапазон адресов во всех процессах nginx и используются для совместного доступа к общим данным, например к дереву кэша HTTP в памяти.

Буфер

Для операций ввода-вывода nginx предоставляет тип буфера ngx_buf_t. Обычно он используется для хранения данных, которые нужно записать в место назначения или прочитать из источника. Буфер может ссылаться на данные в памяти или в файле; технически возможно, чтобы буфер одновременно ссылался и на то и на другое. Память для буфера выделяется отдельно и не связана со структурой буфера ngx_buf_t.

Структура ngx_buf_t содержит следующие поля:

  • start, end — Границы блока памяти, выделенного для буфера.
  • pos, last — Границы буфера памяти; обычно это поддиапазон start .. end.
  • file_pos, file_last — Границы файлового буфера, выраженные как смещения от начала файла.
  • tag — Уникальное значение, используемое для различения буферов; создаётся различными модулями nginx, обычно для повторного использования буферов.
  • file — Файловый объект.
  • temporary — Флаг, указывающий, что буфер ссылается на доступную для записи память.
  • memory — Флаг, указывающий, что буфер ссылается на память только для чтения.
  • in_file — Флаг, указывающий, что буфер ссылается на данные в файле.
  • flush — Флаг, указывающий, что все данные перед буфером необходимо сбросить.
  • recycled — Флаг, указывающий, что буфер можно повторно использовать и его необходимо обработать как можно скорее.
  • sync — Флаг, указывающий, что буфер не содержит данных или специального сигнала, например flush или last_buf. По умолчанию nginx считает такие буферы ошибочным состоянием, но этот флаг указывает nginx пропустить проверку на ошибку.
  • last_buf — Флаг, указывающий, что буфер является последним в выходных данных.
  • last_in_chain — Флаг, указывающий, что в запросе или подзапросе больше нет буферов с данными.
  • shadow — Ссылка на другой («теневой») буфер, связанный с текущим буфером; обычно это означает, что буфер использует данные теневого буфера. После обработки буфера теневой буфер обычно также помечается как обработанный.
  • last_shadow — Флаг, указывающий, что буфер является последним, ссылающимся на определённый теневой буфер.
  • temp_file — Флаг, указывающий, что буфер находится во временном файле.

Для операций ввода и вывода буферы связываются в цепочки. Цепочка представляет собой последовательность звеньев типа ngx_chain_t, определённого следующим образом:

typedef struct ngx_chain_s  ngx_chain_t;

struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};

Каждое звено цепочки содержит ссылку на свой буфер и ссылку на следующее звено.

Пример использования буферов и цепочек:

ngx_chain_t *
ngx_get_my_chain(ngx_pool_t *pool)
{
    ngx_buf_t    *b;
    ngx_chain_t  *out, *cl, **ll;

    /* first buf */
    cl = ngx_alloc_chain_link(pool);
    if (cl == NULL) { /* error */ }

    b = ngx_calloc_buf(pool);
    if (b == NULL) { /* error */ }

    b->start = (u_char *) "foo";
    b->pos = b->start;
    b->end = b->start + 3;
    b->last = b->end;
    b->memory = 1; /* read-only memory */

    cl->buf = b;
    out = cl;
    ll = &cl->next;

    /* second buf */
    cl = ngx_alloc_chain_link(pool);
    if (cl == NULL) { /* error */ }

    b = ngx_create_temp_buf(pool, 3);
    if (b == NULL) { /* error */ }

    b->last = ngx_cpymem(b->last, "foo", 3);

    cl->buf = b;
    cl->next = NULL;
    *ll = cl;

    return out;
}

Сетевое взаимодействие

Соединение

Тип соединения ngx_connection_t представляет собой оболочку для дескриптора сокета. Он включает следующие поля:

  • fd — Дескриптор сокета
  • data — Произвольный контекст соединения. Обычно это указатель на объект более высокого уровня, построенный поверх соединения, например запрос HTTP или сеанс Stream.
  • read, write — События чтения и записи для соединения.
  • recv, send, recv_chain, send_chain — Операции ввода-вывода для соединения.
  • pool — Пул соединений.
  • log — Журнал соединения.
  • sockaddr, socklen, addr_text — Адрес удалённого сокета в двоичном и текстовом форматах.
  • local_sockaddr, local_socklen — Локальный адрес сокета в двоичном формате. Изначально эти поля пусты. Для получения локального адреса сокета используйте функцию ngx_connection_local_sockaddr().
  • proxy_protocol_addr, proxy_protocol_port — Адрес и порт клиента по протоколу PROXY, если для соединения включён протокол PROXY.
  • ssl — Контекст SSL для соединения.
  • reusable — Флаг, указывающий, что соединение находится в состоянии, допускающем повторное использование.
  • close — Флаг, указывающий, что соединение повторно используется и его необходимо закрыть.

Соединение nginx может прозрачно инкапсулировать уровень SSL. В этом случае поле соединения ssl содержит указатель на структуру ngx_ssl_connection_t, в которой хранятся все связанные с SSL данные соединения, включая SSL_CTX и SSL. Обработчики recv, send, recv_chain и send_chain также заменяются функциями с поддержкой SSL.

Директива worker_connections в конфигурации nginx ограничивает количество соединений на один рабочий процесс nginx. Все структуры соединений предварительно создаются при запуске рабочего процесса и сохраняются в поле connections объекта цикла. Чтобы получить структуру соединения, используйте функцию ngx_get_connection(s, log). В качестве аргумента s ей передаётся дескриптор сокета, который необходимо обернуть в структуру соединения.

Поскольку количество соединений на один рабочий процесс ограничено, nginx предоставляет возможность получать соединения, которые уже используются. Чтобы включить или отключить возможность повторного использования соединения, вызовите функцию ngx_reusable_connection(c, reusable). Вызов ngx_reusable_connection(c, 1) устанавливает флаг reuse в структуре соединения и добавляет соединение в reusable_connections_queue цикла. Каждый раз, когда ngx_get_connection() обнаруживает, что в списке free_connections цикла нет доступных соединений, она вызывает ngx_drain_connections() для освобождения определённого количества повторно используемых соединений. Для каждого такого соединения устанавливается флаг close и вызывается его обработчик чтения, который должен освободить соединение вызовом ngx_close_connection(c) и сделать его доступным для повторного использования. Чтобы выйти из состояния, в котором соединение может быть повторно использовано, вызывается ngx_reusable_connection(c, 0). Пример повторно используемых соединений в nginx — клиентские HTTP-соединения; они помечаются как доступные для повторного использования до получения первого байта запроса от клиента.

События

Событие

Объект события ngx_event_t в nginx предоставляет механизм уведомления о возникновении определённого события.

Поля ngx_event_t включают:

  • data — Произвольный контекст события, используемый в обработчиках событий; обычно это указатель на связанное с событием соединение.
  • handler — Функция обратного вызова, вызываемая при возникновении события.
  • write — Флаг события записи. Отсутствие этого флага означает, что это событие чтения.
  • active — Флаг, указывающий, что событие зарегистрировано для получения уведомлений о вводе-выводе, обычно от механизмов уведомлений, таких как epoll, kqueue, poll.
  • ready — Флаг, указывающий, что событие получило уведомление о вводе-выводе.
  • delayed — Флаг, указывающий, что ввод-вывод задерживается из-за ограничения скорости.
  • timer — Узел красно-чёрного дерева для добавления события в дерево таймеров.
  • timer_set — Флаг, указывающий, что таймер события установлен и ещё не истёк.
  • timedout — Флаг, указывающий, что время таймера события истекло.
  • eof — Флаг, указывающий, что при чтении данных достигнут конец файла (EOF).
  • pending_eof — Флаг, указывающий, что для сокета ожидается EOF, хотя перед ним могут быть доступны данные. Флаг передаётся через событие EPOLLRDHUP epoll или флаг EV_EOF kqueue.
  • error — Флаг, указывающий на возникновение ошибки при чтении (для события чтения) или записи (для события записи).
  • cancelable — Флаг события таймера, указывающий, что событие следует игнорировать при завершении работы рабочего процесса. Корректное завершение работы рабочего процесса откладывается, пока остаются запланированные события таймера, которые нельзя отменить.
  • posted — Флаг, указывающий, что событие помещено в очередь.
  • queue — Узел очереди для помещения события в очередь.

События ввода-вывода

К каждому соединению, полученному вызовом функции ngx_get_connection(), прикреплены два события — c->read и c->write. Они используются для получения уведомлений о готовности сокета к чтению или записи. Все такие события работают в режиме срабатывания по фронту, то есть уведомления генерируются только при изменении состояния сокета. Например, частичное чтение сокета не приводит к повторному уведомлению nginx о чтении, пока в сокет не поступят дополнительные данные. Даже если базовый механизм уведомлений о вводе-выводе использует срабатывание по уровню (poll, select и т. д.), nginx преобразует уведомления в срабатывающие по фронту. Чтобы уведомления о событиях nginx были согласованными во всех системах уведомлений на разных платформах, после обработки уведомления о вводе-выводе сокета или вызова любой функции ввода-вывода для этого сокета необходимо вызывать функции ngx_handle_read_event(rev, flags) и ngx_handle_write_event(wev, lowat). Обычно эти функции вызываются один раз в конце каждого обработчика события чтения или записи.

События таймера

Для события можно настроить отправку уведомления по истечении тайм-аута. Используемый событиями таймер отсчитывает миллисекунды с некоторого неопределённого момента в прошлом, преобразованные к типу ngx_msec_t. Его текущее значение можно получить из переменной ngx_current_msec.

Функция ngx_add_timer(ev, timer) задаёт тайм-аут для события, а ngx_del_timer(ev) удаляет ранее заданный тайм-аут. В глобальном красно-чёрном дереве тайм-аутов ngx_event_timer_rbtree хранятся все установленные в данный момент тайм-ауты. Ключ в дереве имеет тип ngx_msec_t и содержит время наступления события. Структура дерева обеспечивает быстрое добавление и удаление элементов, а также доступ к ближайшим тайм-аутам, что позволяет nginx определять, как долго ожидать события ввода-вывода и истечения тайм-аутов.

Помещённые в очередь события

Событие можно поместить в очередь: это означает, что его обработчик будет вызван позднее в рамках текущей итерации цикла событий. Помещение событий в очередь упрощает код и помогает избежать переполнения стека. Помещённые в очередь события хранятся в очереди. Макрос ngx_post_event(ev, q) помещает событие ev в очередь q. Макрос ngx_delete_posted_event(ev) удаляет событие ev из очереди, в которую оно в данный момент помещено. Обычно события помещаются в очередь ngx_posted_events, обрабатываемую в конце цикла событий — после обработки всех событий ввода-вывода и таймера. Для обработки очереди событий вызывается функция ngx_event_process_posted(). Она вызывает обработчики событий, пока очередь не опустеет. Это означает, что обработчик помещённого в очередь события может поместить дополнительные события, которые будут обработаны в рамках текущей итерации цикла событий.

Пример:

void
ngx_my_connection_read(ngx_connection_t *c)
{
    ngx_event_t  *rev;

    rev = c->read;

    ngx_add_timer(rev, 1000);

    rev->handler = ngx_my_read_handler;

    ngx_my_read(rev);
}


void
ngx_my_read_handler(ngx_event_t *rev)
{
    ssize_t            n;
    ngx_connection_t  *c;
    u_char             buf[256];

    if (rev->timedout) { /* timeout expired */ }

    c = rev->data;

    while (rev->ready) {
        n = c->recv(c, buf, sizeof(buf));

        if (n == NGX_AGAIN) {
            break;
        }

        if (n == NGX_ERROR) { /* error */ }

        /* process buf */
    }

    if (ngx_handle_read_event(rev, 0) != NGX_OK) { /* error */ }
}

Цикл событий

За исключением главного процесса nginx, все процессы nginx выполняют операции ввода-вывода и поэтому имеют цикл событий. (Главный процесс nginx вместо этого большую часть времени проводит в вызове sigsuspend(), ожидая поступления сигналов.) Цикл событий nginx реализован в функции ngx_process_events_and_timers(), которая вызывается повторно, пока процесс не завершится.

Цикл событий включает следующие этапы:

  • Найти тайм-аут, до истечения которого осталось меньше всего времени, вызвав ngx_event_find_timer(). Эта функция находит самый левый узел в дереве таймеров и возвращает количество миллисекунд до истечения его тайм-аута.
  • Обработать события ввода-вывода, вызвав обработчик, соответствующий механизму уведомлений о событиях, выбранному конфигурацией nginx. Этот обработчик ожидает наступления хотя бы одного события ввода-вывода, но не дольше, чем до истечения следующего тайм-аута. При возникновении события чтения или записи устанавливается флаг ready и вызывается обработчик события. В Linux обычно используется обработчик ngx_epoll_process_events(), вызывающий epoll_wait() для ожидания событий ввода-вывода.
  • Обработать истёкшие таймеры, вызвав ngx_event_expire_timers(). Дерево таймеров обходится от самого левого элемента вправо, пока не будет найден тайм-аут, который ещё не истёк. Для каждого истёкшего узла устанавливается флаг события timedout, сбрасывается флаг timer_set и вызывается обработчик события
  • Обработать помещённые в очередь события, вызвав ngx_event_process_posted(). Функция последовательно удаляет первый элемент из очереди событий и вызывает его обработчик, пока очередь не опустеет.

Все процессы nginx также обрабатывают сигналы. Обработчики сигналов только устанавливают глобальные переменные, которые проверяются после вызова ngx_process_events_and_timers().

Процессы

В nginx есть несколько типов процессов. Тип процесса хранится в глобальной переменной ngx_process и может принимать одно из следующих значений:

  • NGX_PROCESS_MASTER — Главный процесс, который читает конфигурацию NGINX, создаёт циклы, запускает дочерние процессы и управляет ими. Он не выполняет операции ввода-вывода и реагирует только на сигналы. Его функция цикла — ngx_master_process_cycle().

  • NGX_PROCESS_WORKER — Рабочий процесс, обрабатывающий клиентские соединения. Он запускается главным процессом и также реагирует на его сигналы и команды через канал. Его функция цикла — ngx_worker_process_cycle(). Количество рабочих процессов может быть больше одного; оно задаётся директивой worker_processes.

  • NGX_PROCESS_SINGLE — Единственный процесс, существующий только в режиме master_process off и являющийся единственным запущенным процессом в этом режиме. Он создаёт циклы (как главный процесс) и обрабатывает клиентские соединения (как рабочий процесс). Его функция цикла — ngx_single_process_cycle().

  • NGX_PROCESS_HELPER — Вспомогательный процесс; в настоящее время существует два его типа: менеджер кэша и загрузчик кэша. Функция цикла для обоих типов — ngx_cache_manager_process_cycle().

Процессы nginx обрабатывают следующие сигналы:

  • NGX_SHUTDOWN_SIGNAL (SIGQUIT в большинстве систем) — Корректное завершение работы. Получив этот сигнал, главный процесс отправляет сигнал завершения всем дочерним процессам. Когда дочерних процессов не остаётся, главный процесс уничтожает пул цикла и завершает работу. Получив этот сигнал, рабочий процесс закрывает все сокеты прослушивания и ожидает, пока не останется запланированных событий, которые нельзя отменить; затем он уничтожает пул цикла и завершает работу. Процесс менеджера кэша или загрузчика кэша при получении этого сигнала немедленно завершает работу. Когда процесс получает этот сигнал, переменная ngx_quit устанавливается в 1 и сразу же сбрасывается после обработки. На время завершения работы рабочего процесса переменная ngx_exiting устанавливается в 1.

  • NGX_TERMINATE_SIGNAL (SIGTERM в большинстве систем) — Завершение работы. Получив этот сигнал, главный процесс отправляет сигнал завершения всем дочерним процессам. Если дочерний процесс не завершится в течение 1 секунды, главный процесс отправляет сигнал SIGKILL, чтобы принудительно завершить его. Когда дочерних процессов не остаётся, главный процесс уничтожает пул цикла и завершает работу. Получив этот сигнал, рабочий процесс, процесс менеджера кэша или процесс загрузчика кэша уничтожает пул цикла и завершает работу. При получении этого сигнала переменная ngx_terminate устанавливается в 1.

  • NGX_NOACCEPT_SIGNAL (SIGWINCH в большинстве систем) — Остановить все рабочие и вспомогательные процессы. Получив этот сигнал, главный процесс останавливает свои дочерние процессы. Если ранее запущенный новый бинарный файл nginx завершается, дочерние процессы старого главного процесса запускаются снова. Получив этот сигнал, рабочий процесс завершает работу в режиме отладки, заданном директивой debug_points.

  • NGX_RECONFIGURE_SIGNAL (SIGHUP в большинстве систем) — Перечитать конфигурацию. Получив этот сигнал, главный процесс повторно читает конфигурацию и создаёт на её основе новый цикл. Если новый цикл создан успешно, старый цикл удаляется и запускаются новые дочерние процессы. Тем временем старые дочерние процессы получают сигнал NGX_SHUTDOWN_SIGNAL. В однопроцессном режиме nginx создаёт новый цикл, но сохраняет старый, пока не останется клиентов с активными соединениями, связанными с ним. Рабочие и вспомогательные процессы игнорируют этот сигнал.

  • NGX_REOPEN_SIGNAL (SIGUSR1 в большинстве систем) — Повторно открыть файлы. Главный процесс отправляет этот сигнал рабочим процессам, которые повторно открывают все связанные с циклом файлы open_files.

  • NGX_CHANGEBIN_SIGNAL (SIGUSR2 в большинстве систем) — Заменить бинарный файл nginx. Главный процесс запускает новый бинарный файл nginx и передаёт ему список всех сокетов прослушивания. Список в текстовом формате передаётся в переменной окружения “NGINX” и состоит из номеров дескрипторов, разделённых точками с запятой. Новый бинарный файл nginx считывает переменную “NGINX” и добавляет сокеты в свой цикл инициализации. Другие процессы игнорируют этот сигнал.

Хотя все рабочие процессы nginx могут получать сигналы POSIX и корректно их обрабатывать, главный процесс не использует стандартный системный вызов kill() для отправки сигналов рабочим и вспомогательным процессам. Вместо этого nginx использует пары сокетов для межпроцессного взаимодействия, позволяющие отправлять сообщения между всеми процессами nginx. Однако в настоящее время сообщения отправляются только от главного процесса его дочерним процессам. В сообщениях передаются стандартные сигналы.

Потоки

Задачи, которые иначе блокировали бы рабочий процесс nginx, можно передавать для выполнения в отдельный поток. Например, nginx можно настроить на использование потоков для выполнения ввода-вывода файлов. Ещё один вариант использования — библиотека без асинхронного интерфейса, которую поэтому нельзя использовать с nginx обычным способом. Следует учитывать, что интерфейс потоков — это вспомогательное средство для существующего асинхронного подхода к обработке клиентских соединений, а не его замена.

Для синхронизации доступны следующие обёртки над примитивами pthreads:

  • typedef pthread_mutex_t ngx_thread_mutex_t;
    • ngx_int_t ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log);
    • ngx_int_t ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log);
    • ngx_int_t ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
    • ngx_int_t ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
  • typedef pthread_cond_t ngx_thread_cond_t;
    • ngx_int_t ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log);
    • ngx_int_t ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log);
    • ngx_int_t ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log);
    • ngx_int_t ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx, ngx_log_t *log);

Вместо создания нового потока для каждой задачи nginx использует стратегию пула потоков. Для разных целей можно настроить несколько пулов потоков (например, для выполнения операций ввода-вывода на разных наборах дисков). Каждый пул потоков создаётся при запуске и содержит ограниченное количество потоков, обрабатывающих очередь задач. После выполнения задачи вызывается заранее определённый обработчик завершения.

Файл заголовка src/core/ngx_thread_pool.h содержит соответствующие определения:

struct ngx_thread_task_s {
    ngx_thread_task_t   *next;
    ngx_uint_t           id;
    void                *ctx;
    void               (*handler)(void *data, ngx_log_t *log);
    ngx_event_t          event;
};

typedef struct ngx_thread_pool_s  ngx_thread_pool_t;

ngx_thread_pool_t *ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name);
ngx_thread_pool_t *ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name);

ngx_thread_task_t *ngx_thread_task_alloc(ngx_pool_t *pool, size_t size);
ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task);

При настройке модуль, которому требуется использовать потоки, должен получить ссылку на пул потоков вызовом ngx_thread_pool_add(cf, name): эта функция либо создаёт новый пул потоков с заданным name, либо возвращает ссылку на уже существующий пул с таким именем.

Чтобы во время выполнения добавить task в очередь указанного пула потоков tp, используйте функцию ngx_thread_task_post(tp, task). Для выполнения функции в потоке передайте параметры и задайте обработчик завершения с помощью структуры ngx_thread_task_t:

typedef struct {
    int    foo;
} my_thread_ctx_t;


static void
my_thread_func(void *data, ngx_log_t *log)
{
    my_thread_ctx_t *ctx = data;

    /* this function is executed in a separate thread */
}


static void
my_thread_completion(ngx_event_t *ev)
{
    my_thread_ctx_t *ctx = ev->data;

    /* executed in nginx event loop */
}


ngx_int_t
my_task_offload(my_conf_t *conf)
{
    my_thread_ctx_t    *ctx;
    ngx_thread_task_t  *task;

    task = ngx_thread_task_alloc(conf->pool, sizeof(my_thread_ctx_t));
    if (task == NULL) {
        return NGX_ERROR;
    }

    ctx = task->ctx;

    ctx->foo = 42;

    task->handler = my_thread_func;
    task->event.handler = my_thread_completion;
    task->event.data = ctx;

    if (ngx_thread_task_post(conf->thread_pool, task) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}

Модули

Добавление новых модулей

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

ngx_module_type=CORE
ngx_module_name=ngx_foo_module
ngx_module_srcs="$ngx_addon_dir/ngx_foo_module.c"

. auto/module

ngx_addon_name=$ngx_module_name

Файл config — это скрипт оболочки POSIX, который может задавать и использовать следующие переменные:

  • ngx_module_type — Тип собираемого модуля. Возможные значения: CORE, HTTP, HTTP_FILTER, HTTP_INIT_FILTER, HTTP_AUX_FILTER, MAIL, STREAM или MISC.
  • ngx_module_name — Имена модулей. Чтобы собрать несколько модулей из набора исходных файлов, укажите разделённый пробелами список имён. Первое имя указывает имя выходного двоичного файла динамического модуля. Имена в списке должны совпадать с именами, используемыми в исходном коде.
  • ngx_addon_name — Имя модуля, отображаемое в выводе сценария configure в консоли.
  • ngx_module_srcs — Разделённый пробелами список исходных файлов, используемых для компиляции модуля. Переменная $ngx_addon_dir может использоваться для обозначения пути к каталогу модуля.
  • ngx_module_incs — Пути включения, необходимые для сборки модуля
  • ngx_module_deps — Разделённый пробелами список зависимостей модуля. Обычно это список заголовочных файлов.
  • ngx_module_libs — Разделённый пробелами список библиотек, с которыми нужно связать модуль. Например, используйте ngx_module_libs=-lpthread для связывания с библиотекой libpthread. Для связывания с теми же библиотеками, что и nginx, можно использовать следующие макросы: LIBXSLT, LIBGD, GEOIP, PCRE, OPENSSL, MD5, SHA1, ZLIB и PERL.
  • ngx_module_link — Переменная, устанавливаемая системой сборки в значение DYNAMIC для динамического модуля или ADDON для статического модуля и используемая для определения различных действий в зависимости от типа связывания.
  • ngx_module_order — Порядок загрузки модуля; полезен для типов модулей HTTP_FILTER и HTTP_AUX_FILTER. Формат этого параметра — разделённый пробелами список модулей. Все модули в списке, указанные после имени текущего модуля, оказываются после него в общем списке модулей, задавая порядок инициализации модулей. Для модулей-фильтров более поздняя инициализация означает более раннее выполнение.

    Обычно в качестве ориентиров используются следующие модули. ngx_http_copy_filter_module считывает данные для других модулей-фильтров и помещается ближе к концу списка, чтобы выполняться одним из первых. ngx_http_write_filter_module записывает данные в клиентский сокет и помещается ближе к началу списка, чтобы выполняться последним.

    По умолчанию модули-фильтры помещаются в списке модулей перед ngx_http_copy_filter, чтобы обработчик фильтра выполнялся после обработчика фильтра копирования. Для модулей других типов по умолчанию используется пустая строка.

Чтобы статически скомпилировать модуль в nginx, используйте аргумент --add-module=/path/to/module сценария configure. Чтобы скомпилировать модуль для последующей динамической загрузки в nginx, используйте аргумент --add-dynamic-module=/path/to/module.

Основные модули

Модули — это строительные блоки nginx, и большая часть его функциональности реализована в виде модулей. Исходный файл модуля должен содержать глобальную переменную типа ngx_module_t, определённого следующим образом:

struct ngx_module_s {

    /* private part is omitted */

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);

    void                (*exit_master)(ngx_cycle_t *cycle);

    /* stubs for future extensions are omitted */
};

Пропущенная закрытая часть включает версию модуля и сигнатуру и заполняется с помощью предопределённого макроса NGX_MODULE_V1.

Каждый модуль хранит свои закрытые данные в поле ctx, распознаёт директивы конфигурации, указанные в массиве commands, и может вызываться на определённых этапах жизненного цикла nginx. Жизненный цикл модуля состоит из следующих событий:

  • Обработчики директив конфигурации вызываются в том порядке, в котором директивы встречаются в файлах конфигурации, в контексте главного процесса.
  • После успешного разбора конфигурации в контексте главного процесса вызывается обработчик init_module. Обработчик init_module вызывается в главном процессе при каждой загрузке конфигурации.
  • Главный процесс создаёт один или несколько рабочих процессов, и в каждом из них вызывается обработчик init_process.
  • Когда рабочий процесс получает от главного процесса команду завершения работы или остановки, он вызывает обработчик exit_process.
  • Перед завершением главный процесс вызывает обработчик exit_master.

Поскольку в nginx потоки используются только как вспомогательное средство ввода-вывода с собственным API, обработчики init_thread и exit_thread в настоящее время не вызываются. Также отсутствует обработчик init_master, поскольку он создавал бы ненужные накладные расходы.

Модуль type точно определяет, что хранится в поле ctx. Его значение относится к одному из следующих типов:

  • NGX_CORE_MODULE
  • NGX_EVENT_MODULE
  • NGX_HTTP_MODULE
  • NGX_MAIL_MODULE
  • NGX_STREAM_MODULE

NGX_CORE_MODULE — самый базовый, а потому наиболее универсальный и низкоуровневый тип модуля. Другие типы модулей реализованы на его основе и обеспечивают более удобный способ работы с соответствующими областями, например обработки событий или HTTP-запросов.

К набору основных модулей относятся модули ngx_core_module, ngx_errlog_module, ngx_regex_module, ngx_thread_pool_module и ngx_openssl_module. Модуль HTTP, модуль stream, модуль mail и модули событий также являются основными. Контекст основного модуля определяется следующим образом:

typedef struct {
    ngx_str_t             name;
    void               *(*create_conf)(ngx_cycle_t *cycle);
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;

где name — строка с именем модуля, а create_conf и init_conf — указатели на функции, которые соответственно создают и инициализируют конфигурацию модуля. Для основных модулей nginx вызывает create_conf перед разбором новой конфигурации и init_conf после успешного разбора всей конфигурации. Типичная функция create_conf выделяет память для конфигурации и задаёт значения по умолчанию.

Например, упрощённый модуль под названием ngx_foo_module может выглядеть так:

/*
 * Copyright (C) Author.
 */


#include <ngx_config.h>
#include <ngx_core.h>


typedef struct {
    ngx_flag_t  enable;
} ngx_foo_conf_t;


static void *ngx_foo_create_conf(ngx_cycle_t *cycle);
static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf);

static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t  ngx_foo_enable_post = { ngx_foo_enable };


static ngx_command_t  ngx_foo_commands[] = {

    { ngx_string("foo_enabled"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      0,
      offsetof(ngx_foo_conf_t, enable),
      &ngx_foo_enable_post },

      ngx_null_command
};


static ngx_core_module_t  ngx_foo_module_ctx = {
    ngx_string("foo"),
    ngx_foo_create_conf,
    ngx_foo_init_conf
};


ngx_module_t  ngx_foo_module = {
    NGX_MODULE_V1,
    &ngx_foo_module_ctx,                   /* module context */
    ngx_foo_commands,                      /* module directives */
    NGX_CORE_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};


static void *
ngx_foo_create_conf(ngx_cycle_t *cycle)
{
    ngx_foo_conf_t  *fcf;

    fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t));
    if (fcf == NULL) {
        return NULL;
    }

    fcf->enable = NGX_CONF_UNSET;

    return fcf;
}


static char *
ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf)
{
    ngx_foo_conf_t *fcf = conf;

    ngx_conf_init_value(fcf->enable, 0);

    return NGX_CONF_OK;
}


static char *
ngx_foo_enable(ngx_conf_t *cf, void *post, void *data)
{
    ngx_flag_t  *fp = data;

    if (*fp == 0) {
        return NGX_CONF_OK;
    }

    ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled");

    return NGX_CONF_OK;
}

Директивы конфигурации

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

typedef struct ngx_command_s  ngx_command_t;

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

Завершите массив специальным значением ngx_null_command. name — имя директивы в том виде, в каком она записана в файле конфигурации, например «worker_processes» или «listen». type — битовое поле флагов, задающих количество аргументов директивы, её тип и контекст, в котором она встречается. Флаги:

  • NGX_CONF_NOARGS — директива не принимает аргументов.
  • NGX_CONF_1MORE — директива принимает один или несколько аргументов.
  • NGX_CONF_2MORE — директива принимает два или более аргумента.
  • NGX_CONF_TAKE1..NGX_CONF_TAKE7 — директива принимает ровно указанное количество аргументов.
  • NGX_CONF_TAKE12, NGX_CONF_TAKE13, NGX_CONF_TAKE23, NGX_CONF_TAKE123, NGX_CONF_TAKE1234 — директива может принимать разное количество аргументов. Допустимы только указанные значения. Например, NGX_CONF_TAKE12 означает, что директива принимает один или два аргумента.

Флаги типов директив:

  • NGX_CONF_BLOCK — директива является блоком, то есть может содержать другие директивы между открывающей и закрывающей фигурными скобками или даже реализовывать собственный анализатор для обработки содержимого блока.
  • NGX_CONF_FLAG — директива принимает логическое значение: on или off.

Контекст директивы определяет, где она может встречаться в конфигурации:

  • NGX_MAIN_CONF — в контексте верхнего уровня.
  • NGX_HTTP_MAIN_CONF — в блоке http.
  • NGX_HTTP_SRV_CONF — в блоке server внутри блока http.
  • NGX_HTTP_LOC_CONF — в блоке location внутри блока http.
  • NGX_HTTP_UPS_CONF — в блоке upstream внутри блока http.
  • NGX_HTTP_SIF_CONF — в блоке if внутри блока server в блоке http.
  • NGX_HTTP_LIF_CONF — в блоке if внутри блока location в блоке http.
  • NGX_HTTP_LMT_CONF — в блоке limit_except внутри блока http.
  • NGX_STREAM_MAIN_CONF — в блоке stream.
  • NGX_STREAM_SRV_CONF — в блоке server внутри блока stream.
  • NGX_STREAM_UPS_CONF — в блоке upstream внутри блока stream.
  • NGX_MAIL_MAIN_CONF — в блоке mail.
  • NGX_MAIL_SRV_CONF — в блоке server внутри блока mail.
  • NGX_EVENT_CONF — в блоке event.
  • NGX_DIRECT_CONF — используется модулями, которые не создают иерархию контекстов и имеют только одну глобальную конфигурацию. Эта конфигурация передаётся обработчику в качестве аргумента conf.

Анализатор конфигурации использует эти флаги, чтобы выдавать ошибку, если директива размещена в недопустимом месте, и вызывает обработчики директив с соответствующим указателем на конфигурацию. Благодаря этому одинаковые директивы в разных местах могут хранить значения в разных местах.

Поле set задаёт обработчик, который обрабатывает директиву и сохраняет разобранные значения в соответствующей конфигурации. Для выполнения распространённых преобразований предусмотрен ряд функций:

  • ngx_conf_set_flag_slot — преобразует строковые значения on и off в значение типа ngx_flag_t со значениями 1 и 0 соответственно.
  • ngx_conf_set_str_slot — сохраняет строку как значение типа ngx_str_t.
  • ngx_conf_set_str_array_slot — добавляет значение в массив ngx_array_t строк ngx_str_t. Если массива ещё нет, он создаётся.
  • ngx_conf_set_keyval_slot — добавляет пару «ключ-значение» в массив ngx_array_t пар «ключ-значение» ngx_keyval_t. Первая строка становится ключом, а вторая — значением. Если массива ещё нет, он создаётся.
  • ngx_conf_set_num_slot — преобразует аргумент директивы в значение типа ngx_int_t.
  • ngx_conf_set_size_slot — преобразует размер в значение size_t, выраженное в байтах.
  • ngx_conf_set_off_slot — преобразует смещение в значение off_t, выраженное в байтах.
  • ngx_conf_set_msec_slot — преобразует время в значение ngx_msec_t, выраженное в миллисекундах.
  • ngx_conf_set_sec_slot — преобразует время в значение time_t, выраженное в секундах.
  • ngx_conf_set_bufs_slot — преобразует два переданных аргумента в объект ngx_bufs_t, содержащий количество и размер буферов.
  • ngx_conf_set_enum_slot — преобразует переданный аргумент в значение ngx_uint_t. Переданный в поле post массив ngx_conf_enum_t с завершающим нулём задаёт допустимые строки и соответствующие целочисленные значения.
  • ngx_conf_set_bitmask_slot — преобразует переданные аргументы в значение ngx_uint_t. Значения масок для каждого аргумента объединяются операцией ИЛИ, формируя результат. Переданный в поле post массив ngx_conf_bitmask_t с завершающим нулём задаёт допустимые строки и соответствующие значения масок.
  • set_path_slot — преобразует переданные аргументы в значение ngx_path_t и выполняет все необходимые инициализации. Подробности см. в документации к директиве proxy_temp_path.
  • set_access_slot — преобразует переданные аргументы в маску прав доступа к файлу. Подробности см. в документации к директиве proxy_store_access.

Поле conf определяет, какая структура конфигурации передаётся обработчику директивы. У основных модулей есть только глобальная конфигурация, доступ к которой обеспечивается установкой флага NGX_DIRECT_CONF. Модули, такие как HTTP, Stream или Mail, создают иерархии конфигураций. Например, конфигурация модуля создаётся для областей server, location и if.

  • NGX_HTTP_MAIN_CONF_OFFSET — конфигурация блока http.
  • NGX_HTTP_SRV_CONF_OFFSET — конфигурация блока server внутри блока http.
  • NGX_HTTP_LOC_CONF_OFFSET — конфигурация блока location внутри http.
  • NGX_STREAM_MAIN_CONF_OFFSET — конфигурация блока stream.
  • NGX_STREAM_SRV_CONF_OFFSET — конфигурация блока server внутри блока stream.
  • NGX_MAIL_MAIN_CONF_OFFSET — конфигурация блока mail.
  • NGX_MAIL_SRV_CONF_OFFSET — конфигурация блока server внутри блока mail.

offset задаёт смещение поля в структуре конфигурации модуля, в котором хранятся значения конкретной директивы. Обычно для этого используется макрос offsetof().

Поле post предназначено для двух целей: оно может задавать обработчик, вызываемый после завершения основного обработчика, или передавать дополнительные данные основному обработчику. В первом случае структуру ngx_conf_post_t необходимо инициализировать указателем на обработчик, например:

static char *ngx_do_foo(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t  ngx_foo_post = { ngx_do_foo };

Аргумент post — это сам объект ngx_conf_post_t, а data — указатель на значение соответствующего типа, преобразованное из аргументов основным обработчиком.

HTTP

Соединение

Каждое HTTP-соединение с клиентом проходит следующие этапы:

  • ngx_event_accept() принимает TCP-соединение от клиента. Этот обработчик вызывается в ответ на уведомление о чтении сокета, ожидающего соединения. На этом этапе создаётся новый объект ngx_connection_t, представляющий только что принятый клиентский сокет. Каждый слушающий сокет nginx предоставляет обработчик для передачи ему нового объекта соединения. Для HTTP-соединений это ngx_http_init_connection(c).
  • ngx_http_init_connection() выполняет начальную инициализацию HTTP-соединения. На этом этапе для соединения создаётся объект ngx_http_connection_t, ссылка на который сохраняется в поле data соединения. Позже он будет заменён объектом HTTP-запроса. На этом же этапе запускаются анализатор протокола PROXY и SSL-рукопожатие.
  • Обработчик события чтения ngx_http_wait_request_handler() вызывается, когда на клиентском сокете доступны данные. На этом этапе создаётся объект HTTP-запроса ngx_http_request_t и устанавливается в поле data соединения.
  • Обработчик события чтения ngx_http_process_request_line() считывает строку запроса клиента. Обработчик задаётся функцией ngx_http_wait_request_handler(). Данные считываются в buffer соединения. Начальный размер буфера задаётся директивой client_header_buffer_size. Предполагается, что весь заголовок клиента помещается в буфер. Если начального размера недостаточно, выделяется буфер большего размера, ёмкость которого задаётся директивой large_client_header_buffers.
  • Обработчик события чтения ngx_http_process_request_headers() устанавливается после ngx_http_process_request_line() для чтения заголовка запроса клиента.
  • ngx_http_core_run_phases() вызывается после полного чтения и разбора заголовка запроса. Эта функция выполняет фазы обработки запроса от NGX_HTTP_POST_READ_PHASE до NGX_HTTP_CONTENT_PHASE. Последняя фаза предназначена для формирования ответа и передачи его по цепочке фильтров. На этом этапе ответ не обязательно отправляется клиенту. Он может оставаться в буфере и отправляться на этапе завершения.
  • ngx_http_finalize_request() обычно вызывается, когда запрос сформировал весь вывод или произошла ошибка. Во втором случае находится соответствующая страница ошибки и используется в качестве ответа. Если к этому моменту ответ отправлен клиенту не полностью, активируется HTTP-обработчик записи ngx_http_writer() для завершения отправки оставшихся данных.
  • ngx_http_finalize_connection() вызывается после отправки клиенту полного ответа, когда запрос можно уничтожить. Если включена функция постоянного соединения с клиентом, вызывается ngx_http_set_keepalive(), которая уничтожает текущий запрос и ожидает следующий запрос в соединении. В противном случае ngx_http_close_request() уничтожает и запрос, и соединение.

Запрос

Для каждого HTTP-запроса клиента создаётся объект ngx_http_request_t. Некоторые поля этого объекта:

  • connection — указатель на объект клиентского соединения ngx_connection_t. На один объект соединения могут одновременно ссылаться несколько запросов — один основной запрос и его подзапросы. После удаления запроса в том же соединении может быть создан новый запрос.

    Обратите внимание, что для HTTP-соединений поле data объекта ngx_connection_t указывает на запрос. Такие запросы называются активными, в отличие от остальных запросов, связанных с соединением. Активный запрос используется для обработки событий клиентского соединения и может отправлять клиенту свой ответ. Обычно каждый запрос в какой-то момент становится активным, чтобы отправить свой вывод.

  • ctx — массив контекстов HTTP-модулей. Каждый модуль типа NGX_HTTP_MODULE может хранить в запросе любое значение (обычно указатель на структуру). Значение хранится в массиве ctx в позиции ctx_index модуля. Для удобного получения и установки контекстов запросов предусмотрены следующие макросы:

    • ngx_http_get_module_ctx(r, module) — возвращает контекст module
    • ngx_http_set_ctx(r, c, module) — устанавливает c в качестве контекста module
  • main_conf, srv_conf, loc_conf — массивы текущих конфигураций запроса. Конфигурации хранятся в позициях ctx_index соответствующего модуля.
  • read_event_handler, write_event_handler — обработчики событий чтения и записи запроса. Обычно оба обработчика событий чтения и записи для HTTP-соединения установлены в ngx_http_request_handler(). Эта функция вызывает обработчики read_event_handler и write_event_handler для текущего активного запроса.
  • cache — объект кэша запроса для кэширования ответа upstream-сервера.
  • upstream — объект upstream-запроса для проксирования.
  • pool — пул запроса. Сам объект запроса выделяется в этом пуле, который уничтожается при удалении запроса. Для выделения памяти, которая должна быть доступна в течение всего времени существования клиентского соединения, используйте пул объекта ngx_connection_t.
  • header_in — буфер, в который считывается заголовок HTTP-запроса клиента.
  • headers_in, headers_out — объекты входящих и исходящих HTTP-заголовков. Оба объекта содержат поле headers типа ngx_list_t для хранения исходного списка заголовков. Кроме того, отдельные заголовки доступны для получения и установки в виде отдельных полей, например content_length_n, status и т. д.
  • request_body — объект тела запроса клиента.
  • start_sec, start_msec — момент создания запроса, используемый для отслеживания его длительности.
  • method, method_name — числовое и текстовое представления метода HTTP-запроса клиента. Числовые значения методов определены в src/http/ngx_http_request.h с помощью макросов NGX_HTTP_GET, NGX_HTTP_HEAD, NGX_HTTP_POST и т. д.
  • http_protocol — версия HTTP-протокола клиента в исходном текстовом виде («HTTP/1.0», «HTTP/1.1» и т. д.).
  • http_version — версия HTTP-протокола клиента в числовом виде (NGX_HTTP_VERSION_10, NGX_HTTP_VERSION_11 и т. д.).
  • http_major, http_minor — версия HTTP-протокола клиента в числовом виде, разделённая на основную и дополнительную части.
  • request_line, unparsed_uri — строка запроса и URI из исходного запроса клиента.
  • uri, args, exten — URI, аргументы и расширение файла текущего запроса. Значение URI здесь может отличаться от исходного URI, отправленного клиентом, из-за нормализации. В ходе обработки запроса эти значения могут меняться при выполнении внутренних перенаправлений.
  • main — указатель на объект основного запроса. Этот объект создаётся для обработки HTTP-запроса клиента, в отличие от подзапросов, которые создаются для выполнения конкретной подзадачи в рамках основного запроса.
  • parent — указатель на родительский запрос подзапроса.
  • postponed — список выходных буферов и подзапросов в порядке их отправки и создания. Список используется фильтром отложенной обработки, чтобы обеспечить согласованный вывод запроса, части которого создаются подзапросами.
  • post_subrequest — указатель на обработчик с контекстом, вызываемый при завершении подзапроса. Для основных запросов не используется.
  • posted_requests — список запросов, которые нужно запустить или возобновить вызовом write_event_handler запроса. Обычно этот обработчик содержит главную функцию запроса, которая сначала выполняет фазы обработки запроса, а затем формирует вывод.

    Запрос обычно помещается в очередь вызовом ngx_http_post_request(r, NULL). Он всегда добавляется в список posted_requests основного запроса. Функция ngx_http_run_posted_requests(c) запускает все запросы, поставленные в очередь основного запроса активного запроса переданного соединения. Все обработчики событий вызывают ngx_http_run_posted_requests, что может привести к появлению новых запросов в очереди. Обычно эта функция вызывается после выполнения обработчика чтения или записи запроса.

  • phase_handler — индекс текущей фазы запроса.
  • ncaptures, captures, captures_data — захваченные группы регулярного выражения, полученные при последнем сопоставлении регулярного выражения в запросе. Сопоставление с регулярным выражением может происходить в нескольких местах при обработке запроса: при поиске в map, поиске сервера по SNI или HTTP Host, переписывании, proxy_redirect и т. д. Захваченные при поиске группы сохраняются в указанных выше полях. Поле ncaptures содержит количество захваченных групп, captures — границы захваченных групп, а captures_data — строку, с которой сопоставлялось регулярное выражение и которая используется для извлечения захваченных групп. После каждого нового сопоставления значения захваченных групп запроса сбрасываются и заменяются новыми.
  • count — счётчик ссылок на запрос. Поле имеет смысл только для основного запроса. Для увеличения счётчика достаточно выполнить r->main->count++. Чтобы уменьшить счётчик, вызовите ngx_http_finalize_request(r, rc). Создание подзапроса и выполнение процесса чтения тела запроса увеличивают счётчик.
  • subrequests — текущий уровень вложенности подзапросов. Каждый подзапрос наследует уровень вложенности родительского запроса, уменьшенный на единицу. Если значение достигает нуля, возникает ошибка. Значение для основного запроса определяется константой NGX_HTTP_MAX_SUBREQUESTS.
  • uri_changes — количество оставшихся изменений URI для запроса. Общее число раз, которое запрос может изменить URI, ограничено константой NGX_HTTP_MAX_URI_CHANGES. При каждом изменении значение уменьшается, пока не достигнет нуля; в этот момент возникает ошибка. Переписывания и внутренние перенаправления в обычные или именованные местоположения считаются изменениями URI.
  • blocked — счётчик блокировок, удерживаемых запросом. Пока это значение не равно нулю, запрос нельзя завершить. В настоящее время значение увеличивают ожидающие операции AIO (POSIX AIO и операции с потоками) и активная блокировка кэша.
  • buffered — битовая маска, показывающая, какие модули буферизовали вывод запроса. Вывод могут буферизовать различные фильтры; например, sub_filter может буферизовать данные из-за частичного совпадения строки, а фильтр копирования — из-за отсутствия свободных выходных буферов и т. д. Пока это значение не равно нулю, завершение запроса откладывается до сброса буферов.
  • header_only — флаг, указывающий, что для вывода не требуется тело. Например, этот флаг используется для HTTP-запросов HEAD.
  • keepalive — флаг, указывающий, поддерживается ли постоянное соединение с клиентом. Значение определяется версией HTTP и значением заголовка «Connection».

  • header_sent — флаг, указывающий, что заголовок вывода уже отправлен запросом.
  • internal — флаг, указывающий, что текущий запрос является внутренним. Чтобы перейти во внутреннее состояние, запрос должен пройти внутреннее перенаправление или быть подзапросом. Внутренним запросам разрешено переходить во внутренние местоположения.
  • allow_ranges — флаг, указывающий, что клиенту можно отправить частичный ответ, запрошенный заголовком HTTP Range.
  • subrequest_ranges — флаг, указывающий, что во время обработки подзапроса можно отправлять частичный ответ.
  • single_range — флаг, указывающий, что клиенту можно отправить только один непрерывный диапазон выходных данных. Обычно этот флаг устанавливается при отправке потока данных, например с проксируемого сервера, когда весь ответ недоступен в одном буфере.
  • main_filter_need_in_memory, filter_need_in_memory — флаги, требующие формирования вывода в буферах памяти, а не в файлах. Это сигнал фильтру копирования считывать данные из файловых буферов, даже если включён sendfile. Разница между двумя флагами заключается в расположении модулей-фильтров, которые их устанавливают. Фильтры, вызываемые до фильтра отложенной обработки в цепочке фильтров, устанавливают filter_need_in_memory, требуя помещать в буферы памяти только вывод текущего запроса. Фильтры, вызываемые позже в цепочке фильтров, устанавливают main_filter_need_in_memory, требуя считывать файлы в память как для основного запроса, так и для всех подзапросов при отправке вывода.
  • filter_need_temporary — флаг, требующий формировать вывод запроса во временных буферах, но не в буферах только для чтения или файловых буферах. Используется фильтрами, которые могут изменять вывод непосредственно в буферах, из которых он отправляется.

Конфигурация

Каждый HTTP-модуль может иметь три типа конфигурации:

  • Основная конфигурация — применяется ко всему блоку http. Содержит глобальные настройки модуля.
  • Конфигурация сервера — применяется к одному блоку server. Содержит настройки модуля для конкретного сервера.
  • Конфигурация местоположения — применяется к одному блоку location, if или limit_except. Содержит настройки модуля для конкретного местоположения.

Структуры конфигурации создаются на этапе конфигурирования nginx вызовом функций, которые выделяют память для структур, инициализируют и объединяют их. В следующем примере показано, как создать простую конфигурацию местоположения для модуля. В конфигурации есть один параметр foo типа беззнакового целого числа.

typedef struct {
    ngx_uint_t  foo;
} ngx_http_foo_loc_conf_t;


static ngx_http_module_t  ngx_http_foo_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_foo_create_loc_conf,          /* create location configuration */
    ngx_http_foo_merge_loc_conf            /* merge location configuration */
};


static void *
ngx_http_foo_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_foo_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_foo_loc_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->foo = NGX_CONF_UNSET_UINT;

    return conf;
}


static char *
ngx_http_foo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_foo_loc_conf_t *prev = parent;
    ngx_http_foo_loc_conf_t *conf = child;

    ngx_conf_merge_uint_value(conf->foo, prev->foo, 1);
}

Как видно из примера, функция ngx_http_foo_create_loc_conf() создаёт новую структуру конфигурации, а ngx_http_foo_merge_loc_conf() объединяет конфигурацию с конфигурацией более высокого уровня. На самом деле конфигурации сервера и местоположения существуют не только на уровнях сервера и местоположения, но и создаются для всех вышестоящих уровней. В частности, конфигурация сервера также создаётся на основном уровне, а конфигурации местоположения создаются на основном уровне, уровне сервера и уровне местоположения. Эти конфигурации позволяют задавать настройки для конкретного сервера и местоположения на любом уровне файла конфигурации nginx. В итоге конфигурации объединяются сверху вниз. Для обозначения отсутствующего параметра и его игнорирования при объединении предусмотрен ряд макросов, например NGX_CONF_UNSET и NGX_CONF_UNSET_UINT. Стандартные макросы nginx для объединения, например ngx_conf_merge_value() и ngx_conf_merge_uint_value(), позволяют удобно объединять параметр и задавать значение по умолчанию, если ни в одной из конфигураций не было указано явное значение. Полный список макросов для разных типов см. в src/core/ngx_conf_file.h.

Для доступа к конфигурации HTTP-модулей на этапе конфигурирования доступны следующие макросы. В качестве первого аргумента каждый из них принимает ссылку ngx_conf_t.

  • ngx_http_conf_get_module_main_conf(cf, module)
  • ngx_http_conf_get_module_srv_conf(cf, module)
  • ngx_http_conf_get_module_loc_conf(cf, module)

В следующем примере получается указатель на конфигурацию расположения стандартного основного модуля nginx ngx_http_core_module и заменяется обработчик содержимого расположения, хранящийся в поле handler структуры.

static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r);


static ngx_command_t  ngx_http_foo_commands[] = {

    { ngx_string("foo"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_foo,
      0,
      0,
      NULL },

      ngx_null_command
};


static char *
ngx_http_foo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_bar_handler;

    return NGX_CONF_OK;
}

Для доступа к конфигурации HTTP-модулей во время выполнения доступны следующие макросы.

  • ngx_http_get_module_main_conf(r, module)
  • ngx_http_get_module_srv_conf(r, module)
  • ngx_http_get_module_loc_conf(r, module)

Эти макросы принимают ссылку на HTTP-запрос ngx_http_request_t. Основная конфигурация запроса никогда не меняется. Конфигурация сервера может измениться относительно конфигурации по умолчанию после выбора виртуального сервера для запроса. Конфигурация расположения, выбранного для обработки запроса, может меняться несколько раз в результате операции переписывания или внутреннего перенаправления. В следующем примере показано, как получить доступ к конфигурации HTTP-модуля во время выполнения.

static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
    ngx_http_foo_loc_conf_t  *flcf;

    flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module);

    ...
}

Фазы

Каждый HTTP-запрос проходит последовательность фаз. На каждой фазе выполняется отдельный тип обработки запроса. В большинстве фаз можно зарегистрировать обработчики, специфичные для модулей, и многие стандартные модули nginx регистрируют обработчики фаз, чтобы вызываться на определённом этапе обработки запроса. Фазы обрабатываются последовательно, а обработчики фаз вызываются, когда запрос достигает соответствующей фазы. Ниже приведён список фаз HTTP в nginx.

  • NGX_HTTP_POST_READ_PHASE — Первая фаза. На этой фазе ngx_http_realip_module регистрирует обработчик, чтобы разрешить подстановку адресов клиентов до вызова любых других модулей.
  • NGX_HTTP_SERVER_REWRITE_PHASE — Фаза, на которой обрабатываются директивы переписывания, заданные в блоке server (но вне блока location). На этой фазе ngx_http_rewrite_module устанавливает свой обработчик.
  • NGX_HTTP_FIND_CONFIG_PHASE — Специальная фаза, на которой расположение выбирается на основе URI запроса. До этой фазы запросу назначается расположение по умолчанию соответствующего виртуального сервера, и любой модуль, запрашивающий конфигурацию расположения, получает конфигурацию расположения сервера по умолчанию. На этой фазе запросу назначается новое расположение. Зарегистрировать дополнительные обработчики на этой фазе нельзя.
  • NGX_HTTP_REWRITE_PHASE — Аналогична NGX_HTTP_SERVER_REWRITE_PHASE, но применяется к правилам переписывания, заданным в расположении, выбранном на предыдущей фазе.
  • NGX_HTTP_POST_REWRITE_PHASE — Специальная фаза, на которой запрос перенаправляется в новое расположение, если его URI изменился в ходе переписывания. Это реализовано путём повторного прохождения запросом фазы NGX_HTTP_FIND_CONFIG_PHASE. Зарегистрировать дополнительные обработчики на этой фазе нельзя.
  • NGX_HTTP_PREACCESS_PHASE — Общая фаза для различных типов обработчиков, не связанных с контролем доступа. На этой фазе свои обработчики регистрируют стандартные модули nginx ngx_http_limit_conn_module и ngx_http_limit_req_module.
  • NGX_HTTP_ACCESS_PHASE — Фаза, на которой проверяется, имеет ли клиент право выполнить запрос. На этой фазе свои обработчики регистрируют стандартные модули nginx, например ngx_http_access_module и ngx_http_auth_basic_module . По умолчанию клиент должен пройти проверку авторизации во всех обработчиках, зарегистрированных на этой фазе, чтобы запрос перешёл к следующей фазе. Директиву satisfy можно использовать, чтобы разрешить продолжение обработки, если клиент авторизован любым из обработчиков фазы.
  • NGX_HTTP_POST_ACCESS_PHASE — Специальная фаза, на которой обрабатывается директива satisfy any. Если некоторые обработчики фазы доступа запретили доступ, а ни один из них явно его не разрешил, обработка запроса завершается. Зарегистрировать дополнительные обработчики на этой фазе нельзя.
  • NGX_HTTP_PRECONTENT_PHASE — Фаза для обработчиков, вызываемых перед генерацией содержимого. На этой фазе свои обработчики регистрируют стандартные модули, например ngx_http_try_files_module и ngx_http_mirror_module.
  • NGX_HTTP_CONTENT_PHASE — Фаза, на которой обычно формируется ответ. На этой фазе свои обработчики регистрируют несколько стандартных модулей nginx, в том числе ngx_http_index_module или ngx_http_static_module. Они вызываются последовательно, пока один из них не сформирует вывод. Также можно задавать обработчики содержимого отдельно для каждого расположения. Если в конфигурации расположения ngx_http_core_module задано handler, оно вызывается как обработчик содержимого, а обработчики, установленные на этой фазе, игнорируются.
  • NGX_HTTP_LOG_PHASE — Фаза, на которой выполняется журналирование запросов. В настоящее время только ngx_http_log_module регистрирует на этом этапе обработчик для ведения журнала доступа. Обработчики фазы журналирования вызываются в самом конце обработки запроса, непосредственно перед освобождением запроса.

Ниже приведён пример обработчика фазы предварительного доступа.

static ngx_http_module_t  ngx_http_foo_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_foo_init,                     /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    NULL,                                  /* create location configuration */
    NULL                                   /* merge location configuration */
};


static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
    ngx_table_elt_t  *ua;

    ua = r->headers_in.user_agent;

    if (ua == NULL) {
        return NGX_DECLINED;
    }

    /* reject requests with "User-Agent: foo" */
    if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) {
        return NGX_HTTP_FORBIDDEN;
    }

    return NGX_DECLINED;
}


static ngx_int_t
ngx_http_foo_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_foo_handler;

    return NGX_OK;
}

От обработчиков фаз ожидается возврат определённых кодов:

  • NGX_OK — Перейти к следующей фазе.
  • NGX_DECLINED — Перейти к следующему обработчику текущей фазы. Если текущий обработчик — последний в текущей фазе, перейти к следующей фазе.
  • NGX_AGAIN, NGX_DONE — Приостановить обработку фазы до будущего события, например асинхронной операции ввода-вывода или просто задержки. Предполагается, что обработка фазы будет возобновлена позднее вызовом ngx_http_core_run_phases().
  • Любое другое значение, возвращённое обработчиком фазы, рассматривается как код завершения запроса, в частности как код HTTP-ответа. Запрос завершается с указанным кодом.

Для некоторых фаз возвращаемые коды обрабатываются несколько иначе. На фазе содержимого любой код возврата, кроме NGX_DECLINED, считается кодом завершения. Любой код возврата обработчиков содержимого расположения считается кодом завершения. На фазе доступа в режиме satisfy any любой код возврата, кроме NGX_OK, NGX_DECLINED, NGX_AGAIN, NGX_DONE, считается отказом. Если последующие обработчики доступа не разрешают и не запрещают доступ с другим кодом, код отказа становится кодом завершения.

Переменные

Доступ к существующим переменным

На переменные можно ссылаться по индексу (это наиболее распространённый способ) или по имени (см. ниже). Индекс создаётся на этапе конфигурирования, когда переменная добавляется в конфигурацию. Чтобы получить индекс переменной, используйте ngx_http_get_variable_index():

ngx_str_t  name;  /* ngx_string("foo") */
ngx_int_t  index;

index = ngx_http_get_variable_index(cf, &name);

Здесь cf — указатель на конфигурацию nginx, а name указывает на строку, содержащую имя переменной. В случае ошибки функция возвращает NGX_ERROR, в противном случае — допустимый индекс, который обычно сохраняется в конфигурации модуля для дальнейшего использования.

Все переменные HTTP вычисляются в контексте заданного HTTP-запроса, а результаты относятся к этому запросу и кэшируются в нём. Все функции, вычисляющие переменные, возвращают тип ngx_http_variable_value_t, представляющий значение переменной:

typedef ngx_variable_value_t  ngx_http_variable_value_t;

typedef struct {
    unsigned    len:28;

    unsigned    valid:1;
    unsigned    no_cacheable:1;
    unsigned    not_found:1;
    unsigned    escape:1;

    u_char     *data;
} ngx_variable_value_t;

где:

  • len — Длина значения
  • data — Само значение
  • valid — Значение действительно
  • not_found — Переменная не найдена, поэтому поля data и len не имеют значения; например, это может произойти с переменными типа $arg_foo, если соответствующий аргумент не был передан в запросе
  • no_cacheable — Не кэшировать результат
  • escape — Используется модулем журналирования внутри nginx для пометки значений, требующих экранирования при выводе.

Для получения значения переменной используются функции ngx_http_get_flushed_variable() и ngx_http_get_indexed_variable(). У них одинаковый интерфейс: они принимают HTTP-запрос r в качестве контекста для вычисления переменной и index, идентифицирующий её. Пример типичного использования:

ngx_http_variable_value_t  *v;

v = ngx_http_get_flushed_variable(r, index);

if (v == NULL || v->not_found) {
    /* we failed to get value or there is no such variable, handle it */
    return NGX_ERROR;
}

/* some meaningful value is found */

Разница между функциями заключается в том, что ngx_http_get_indexed_variable() возвращает кэшированное значение, а ngx_http_get_flushed_variable() сбрасывает кэш для некэшируемых переменных.

Некоторым модулям, например SSI и Perl, необходимо работать с переменными, имя которых неизвестно на этапе конфигурирования. Поэтому для доступа к ним нельзя использовать индекс, однако доступна функция ngx_http_get_variable(r, name, key). Она ищет переменную с заданным name и его хешем key, вычисленным на основе имени.

Создание переменных

Чтобы создать переменную, используйте функцию ngx_http_add_variable(). Ей передаются конфигурация (в которой регистрируется переменная), имя переменной и флаги, управляющие поведением функции:

  • NGX_HTTP_VAR_CHANGEABLE — Разрешает переопределение переменной: конфликт не возникает, если другой модуль определяет переменную с тем же именем. Это позволяет директиве set переопределять переменные.
  • NGX_HTTP_VAR_NOCACHEABLE — Отключает кэширование, что полезно для таких переменных, как $time_local.
  • NGX_HTTP_VAR_NOHASH — Указывает, что доступ к этой переменной возможен только по индексу, но не по имени. Это небольшая оптимизация, полезная в случаях, когда известно, что переменная не понадобится таким модулям, как SSI или Perl.
  • NGX_HTTP_VAR_PREFIX — Имя переменной является префиксом. В этом случае обработчик должен реализовать дополнительную логику для получения значения конкретной переменной. Например, все переменные «arg_» обрабатываются одним обработчиком, который выполняет поиск в аргументах запроса и возвращает значение конкретного аргумента.

В случае ошибки функция возвращает NULL, в противном случае — указатель на ngx_http_variable_t:

struct ngx_http_variable_s {
    ngx_str_t                     name;
    ngx_http_set_variable_pt      set_handler;
    ngx_http_get_variable_pt      get_handler;
    uintptr_t                     data;
    ngx_uint_t                    flags;
    ngx_uint_t                    index;
};

Обработчики get и set вызываются для получения или установки значения переменной, обработчикам переменных передаётся data, а в index хранится присвоенный переменной индекс, используемый для обращения к ней.

Обычно модуль создаёт статический массив структур ngx_http_variable_t с нулевым завершающим элементом и обрабатывает его на этапе предварительной конфигурации, чтобы добавить переменные в конфигурацию. Например:

static ngx_http_variable_t  ngx_http_foo_vars[] = {

    { ngx_string("foo_v1"), NULL, ngx_http_foo_v1_variable, 0, 0, 0 },

      ngx_http_null_variable
};

static ngx_int_t
ngx_http_foo_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var, *v;

    for (v = ngx_http_foo_vars; v->name.len; v++) {
        var = ngx_http_add_variable(cf, &v->name, v->flags);
        if (var == NULL) {
            return NGX_ERROR;
        }

        var->get_handler = v->get_handler;
        var->data = v->data;
    }

    return NGX_OK;
}

В этом примере эта функция используется для инициализации поля preconfiguration контекста HTTP-модуля и вызывается до разбора конфигурации HTTP, чтобы анализатор мог обращаться к этим переменным.

Обработчик get отвечает за вычисление переменной в контексте конкретного запроса. Например:

static ngx_int_t
ngx_http_variable_connection(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    u_char  *p;

    p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN);
    if (p == NULL) {
        return NGX_ERROR;
    }

    v->len = ngx_sprintf(p, "%uA", r->connection->number) - p;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;
    v->data = p;

    return NGX_OK;
}

В случае внутренней ошибки (например, при неудачном выделении памяти) он возвращает NGX_ERROR, в противном случае — NGX_OK. Чтобы узнать результат вычисления переменной, проверьте флаги в ngx_http_variable_value_t (см. описание выше).

Обработчик set позволяет задавать свойство, на которое ссылается переменная. Например, обработчик установки переменной $limit_rate изменяет поле limit_rate запроса:

...
{ ngx_string("limit_rate"), ngx_http_variable_request_set_size,
  ngx_http_variable_request_get_size,
  offsetof(ngx_http_request_t, limit_rate),
  NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
...

static void
ngx_http_variable_request_set_size(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    ssize_t    s, *sp;
    ngx_str_t  val;

    val.len = v->len;
    val.data = v->data;

    s = ngx_parse_size(&val);

    if (s == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "invalid size \"%V\"", &val);
        return;
    }

    sp = (ssize_t *) ((char *) r + data);

    *sp = s;

    return;
}

Составные значения

Составное значение, несмотря на своё название, позволяет легко вычислять выражения, которые могут содержать текст, переменные и их комбинации.

Описание составного значения в ngx_http_compile_complex_value на этапе конфигурирования компилируется в ngx_http_complex_value_t, которое используется во время выполнения для получения результатов вычисления выражения.

ngx_str_t                         *value;
ngx_http_complex_value_t           cv;
ngx_http_compile_complex_value_t   ccv;

value = cf->args->elts; /* directive arguments */

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &cv;
ccv.zero = 1;
ccv.conf_prefix = 1;

if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
    return NGX_CONF_ERROR;
}

Здесь ccv содержит все параметры, необходимые для инициализации составного значения cv:

  • cf — Указатель на конфигурацию
  • value — Разбираемая строка (входные данные)
  • complex_value — Скомпилированное значение (выходные данные)
  • zero — Флаг, включающий завершение значения нулём
  • conf_prefix — Добавляет к результату префикс конфигурации (каталог, в котором nginx в данный момент ищет конфигурацию)
  • root_prefix — Добавляет к результату корневой префикс (обычный префикс установки nginx)

Флаг zero полезен, когда результаты передаются библиотекам, требующим строк с завершающим нулём, а префиксы удобны при работе с именами файлов.

После успешной компиляции cv.lengths содержит сведения о наличии переменных в выражении. Значение NULL означает, что выражение содержало только статический текст, поэтому его можно хранить в обычной строке, а не в виде составного значения.

ngx_http_set_complex_value_slot() — это удобная функция, используемая для полной инициализации составного значения непосредственно в объявлении директивы.

Во время выполнения составное значение можно вычислить с помощью функции ngx_http_complex_value():

ngx_str_t  res;

if (ngx_http_complex_value(r, &cv, &res) != NGX_OK) {
    return NGX_ERROR;
}

Получив запрос r и ранее скомпилированное значение cv, функция вычисляет выражение и записывает результат в res.

Перенаправление запросов

HTTP-запрос всегда связан с расположением через поле loc_conf структуры ngx_http_request_t. Это означает, что в любой момент конфигурацию расположения любого модуля можно получить из запроса, вызвав ngx_http_get_module_loc_conf(r, module). Расположение запроса может меняться несколько раз за время его существования. Изначально запросу назначается расположение по умолчанию сервера по умолчанию. Если запрос переключается на другой сервер (выбранный по заголовку HTTP «Host» или расширению SSL SNI), запрос также переключается на расположение по умолчанию этого сервера. Следующее изменение расположения происходит на фазе запроса NGX_HTTP_FIND_CONFIG_PHASE. На этой фазе расположение выбирается по URI запроса среди всех неназванных расположений, настроенных для сервера. Модуль ngx_http_rewrite_module может изменить URI запроса на фазе запроса NGX_HTTP_REWRITE_PHASE в результате применения директивы rewrite и вернуть запрос на фазу NGX_HTTP_FIND_CONFIG_PHASE, чтобы выбрать новое расположение на основе нового URI.

Также запрос можно перенаправить в новое расположение в любой момент, вызвав одну из функций ngx_http_internal_redirect(r, uri, args) или ngx_http_named_location(r, name).

Функция ngx_http_internal_redirect(r, uri, args) изменяет URI запроса и возвращает запрос на фазу NGX_HTTP_SERVER_REWRITE_PHASE. Обработка запроса продолжается в расположении сервера по умолчанию. Позднее, на фазе NGX_HTTP_FIND_CONFIG_PHASE, на основе нового URI запроса выбирается новое расположение.

В следующем примере выполняется внутреннее перенаправление с новыми аргументами запроса.

ngx_int_t
ngx_http_foo_redirect(ngx_http_request_t *r)
{
    ngx_str_t  uri, args;

    ngx_str_set(&uri, "/foo");
    ngx_str_set(&args, "bar=1");

    return ngx_http_internal_redirect(r, &uri, &args);
}

Функция ngx_http_named_location(r, name) перенаправляет запрос в именованное расположение. Имя расположения передаётся в качестве аргумента. Выполняется поиск расположения среди всех именованных расположений текущего сервера, после чего запрос переключается на фазу NGX_HTTP_REWRITE_PHASE.

В следующем примере выполняется перенаправление в именованное расположение @foo.

ngx_int_t
ngx_http_foo_named_redirect(ngx_http_request_t *r)
{
    ngx_str_t  name;

    ngx_str_set(&name, "foo");

    return ngx_http_named_location(r, &name);
}

Обе функции — ngx_http_internal_redirect(r, uri, args) и ngx_http_named_location(r, name) — можно вызывать, когда модули nginx уже сохранили некоторые контексты в поле ctx запроса. Эти контексты могут стать несовместимыми с новой конфигурацией расположения. Чтобы избежать несогласованности, обе функции перенаправления удаляют все контексты запроса.

Вызов ngx_http_internal_redirect(r, uri, args) или ngx_http_named_location(r, name) увеличивает count запроса. Для согласованного подсчёта ссылок на запрос после его перенаправления вызовите ngx_http_finalize_request(r, NGX_DONE). Это завершит текущий путь обработки запроса и уменьшит счётчик.

Перенаправленные и переписанные запросы становятся внутренними и могут обращаться к расположениям internal. Для внутренних запросов установлен флаг internal.

Подзапросы

Подзапросы в первую очередь используются для вставки вывода одного запроса в другой, возможно, вместе с другими данными. Подзапрос похож на обычный запрос, но использует с родительским запросом некоторые общие данные. В частности, общими являются все поля, связанные с вводом от клиента, поскольку подзапрос не получает от клиента никаких других данных. Поле запроса parent для подзапроса содержит ссылку на родительский запрос, а для основного запроса имеет значение NULL. Поле main содержит ссылку на основной запрос в группе запросов.

Подзапрос начинается на фазе NGX_HTTP_SERVER_REWRITE_PHASE. Он проходит те же последующие фазы, что и обычный запрос, и ему назначается расположение на основе его собственного URI.

Заголовок ответа подзапроса всегда игнорируется. ngx_http_postpone_filter размещает тело вывода подзапроса в нужной позиции относительно других данных, сформированных родительским запросом.

Подзапросы связаны с понятием активных запросов. Запрос r считается активным, если c->data == r, где c — объект клиентского соединения. В любой момент времени только активный запрос в группе запросов может передавать свои буферы клиенту. Неактивный запрос всё ещё может передавать свой вывод в цепочку фильтров, но этот вывод не проходит дальше ngx_http_postpone_filter и остаётся в буфере этого фильтра, пока запрос не станет активным. Ниже приведены правила активации запросов:

  • Изначально активен основной запрос.
  • Первый подзапрос активного запроса становится активным сразу после создания.
  • ngx_http_postpone_filter активирует следующий запрос в списке подзапросов активного запроса, когда будут отправлены все данные, предшествующие этому запросу.
  • При завершении запроса активируется его родительский запрос.

Создайте подзапрос вызовом функции ngx_http_subrequest(r, uri, args, psr, ps, flags), где r — родительский запрос, uri и args — URI и аргументы подзапроса, psr — выходной параметр, в который записывается ссылка на созданный подзапрос, ps — объект обратного вызова, уведомляющий родительский запрос о завершении подзапроса, а flags — битовая маска флагов. Доступны следующие флаги:

  • NGX_HTTP_SUBREQUEST_IN_MEMORY — Вывод не отправляется клиенту, а сохраняется в памяти. Этот флаг влияет только на подзапросы, обрабатываемые одним из прокси-модулей. После завершения подзапроса его вывод доступен в r->out типа ngx_buf_t.
  • NGX_HTTP_SUBREQUEST_WAITED — Флаг done подзапроса устанавливается, даже если при завершении подзапрос не является активным. Этот флаг подзапроса используется фильтром SSI.
  • NGX_HTTP_SUBREQUEST_CLONE — Подзапрос создаётся как клон родительского запроса. Он начинается в том же расположении и продолжает обработку с той же фазы, что и родительский запрос.

В следующем примере создаётся подзапрос с URI /foo.

ngx_int_t            rc;
ngx_str_t            uri;
ngx_http_request_t  *sr;

...

ngx_str_set(&uri, "/foo");

rc = ngx_http_subrequest(r, &uri, NULL, &sr, NULL, 0);
if (rc == NGX_ERROR) {
    /* error */
}

В этом примере текущий запрос клонируется и для подзапроса задаётся обратный вызов завершения.

ngx_int_t
ngx_http_foo_clone(ngx_http_request_t *r)
{
    ngx_http_request_t          *sr;
    ngx_http_post_subrequest_t  *ps;

    ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
    if (ps == NULL) {
        return NGX_ERROR;
    }

    ps->handler = ngx_http_foo_subrequest_done;
    ps->data = "foo";

    return ngx_http_subrequest(r, &r->uri, &r->args, &sr, ps,
                               NGX_HTTP_SUBREQUEST_CLONE);
}


ngx_int_t
ngx_http_foo_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
    char  *msg = (char *) data;

    ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                  "done subrequest r:%p msg:%s rc:%i", r, msg, rc);

    return rc;
}

Обычно подзапросы создаются в фильтре тела, в таком случае их вывод можно обрабатывать как вывод любого явного запроса. Это означает, что в конечном счёте вывод подзапроса отправляется клиенту после всех явных буферов, переданных до создания подзапроса, и перед любыми буферами, переданными после его создания. Такой порядок сохраняется даже для больших иерархий подзапросов. В следующем примере вывод подзапроса вставляется после всех буферов данных запроса, но перед последним буфером с флагом last_buf.

ngx_int_t
ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_buf_t                  *b;
    ngx_uint_t                  last;
    ngx_chain_t                *cl, out;
    ngx_http_request_t         *sr;
    ngx_http_foo_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module);
    if (ctx == NULL) {
        return ngx_http_next_body_filter(r, in);
    }

    last = 0;

    for (cl = in; cl; cl = cl->next) {
        if (cl->buf->last_buf) {
            cl->buf->last_buf = 0;
            cl->buf->last_in_chain = 1;
            cl->buf->sync = 1;
            last = 1;
        }
    }

    /* Output explicit output buffers */

    rc = ngx_http_next_body_filter(r, in);

    if (rc == NGX_ERROR || !last) {
        return rc;
    }

    /*
     * Create the subrequest.  The output of the subrequest
     * will automatically be sent after all preceding buffers,
     * but before the last_buf buffer passed later in this function.
     */

    if (ngx_http_subrequest(r, ctx->uri, NULL, &sr, NULL, 0) != NGX_OK) {
        return NGX_ERROR;
    }

    ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module);

    /* Output the final buffer with the last_buf flag */

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    b->last_buf = 1;

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}

Подзапрос можно создавать и для целей, не связанных с выводом данных. Например, модуль ngx_http_auth_request_module создаёт подзапрос на фазе NGX_HTTP_ACCESS_PHASE. Чтобы отключить вывод на этом этапе, для подзапроса устанавливается флаг header_only. Это не позволяет отправить тело подзапроса клиенту. Обратите внимание, что заголовок подзапроса никогда не отправляется клиенту. Результат подзапроса можно проанализировать в обработчике обратного вызова.

Завершение запроса

HTTP-запрос завершается вызовом функции ngx_http_finalize_request(r, rc). Обычно его завершает обработчик содержимого после передачи всех выходных буферов в цепочку фильтров. На этом этапе весь вывод может быть ещё не отправлен клиенту: его часть может оставаться в буфере где-либо в цепочке фильтров. В таком случае функция ngx_http_finalize_request(r, rc) автоматически устанавливает специальный обработчик ngx_http_writer(r) для завершения отправки вывода. Запрос также завершается в случае ошибки или если клиенту необходимо вернуть стандартный код ответа HTTP.

Функция ngx_http_finalize_request(r, rc) принимает следующие значения rc:

  • NGX_DONE — Быстрое завершение. Уменьшить count запроса и уничтожить запрос, если счётчик достиг нуля. После уничтожения текущего запроса клиентское соединение можно использовать для других запросов.
  • NGX_ERROR, NGX_HTTP_REQUEST_TIME_OUT (408), NGX_HTTP_CLIENT_CLOSED_REQUEST (499) — Завершение из-за ошибки. Как можно скорее прервать обработку запроса и закрыть клиентское соединение.
  • NGX_HTTP_CREATED (201), NGX_HTTP_NO_CONTENT (204), коды, большие или равные NGX_HTTP_SPECIAL_RESPONSE (300) — Завершение со специальным ответом. Для этих значений nginx либо отправляет клиенту страницу ответа по умолчанию для данного кода, либо выполняет внутреннее перенаправление в расположение error_page, если оно настроено для этого кода.
  • Другие коды считаются кодами успешного завершения и могут активировать механизм отправки ответа, чтобы закончить передачу тела ответа. После полной отправки тела значение count запроса уменьшается. Если оно достигает нуля, запрос уничтожается, но клиентское соединение всё ещё можно использовать для других запросов. Если count больше нуля, в запросе остаются незавершённые действия, которые будут завершены позднее.

Тело запроса

Для работы с телом клиентского запроса nginx предоставляет функции ngx_http_read_client_request_body(r, post_handler) и ngx_http_discard_request_body(r). Первая функция считывает тело запроса и делает его доступным через поле запроса request_body. Вторая функция указывает nginx отбросить (считать и игнорировать) тело запроса. Для каждого запроса необходимо вызвать одну из этих функций. Обычно это делает обработчик содержимого.

Считывать или отбрасывать тело клиентского запроса из подзапроса нельзя. Это всегда необходимо делать в основном запросе. При создании подзапрос наследует объект request_body родительского запроса, который подзапрос может использовать, если основной запрос ранее считал тело запроса.

Функция ngx_http_read_client_request_body(r, post_handler) запускает процесс чтения тела запроса. Когда тело будет считано полностью, для продолжения обработки запроса вызывается обратный вызов post_handler. Если тело запроса отсутствует или уже было считано, обратный вызов вызывается немедленно. В небуферизованном режиме обратный вызов может быть вызван до того, как будет считано всё тело. Функция ngx_http_read_client_request_body(r, post_handler) выделяет для запроса поле request_body типа ngx_http_request_body_t. В поле bufs этого объекта результат хранится в виде цепочки буферов. Тело может сохраняться в буферах памяти или файловых буферах, если объёма, заданного директивой client_body_buffer_size, недостаточно для хранения всего тела в памяти.

В следующем примере считывается тело клиентского запроса и возвращается его размер.

ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t  rc;

    rc = ngx_http_read_client_request_body(r, ngx_http_foo_init);

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        /* error */
        return rc;
    }

    return NGX_DONE;
}


void
ngx_http_foo_init(ngx_http_request_t *r)
{
    off_t         len;
    ngx_buf_t    *b;
    ngx_int_t     rc;
    ngx_chain_t  *in, out;

    if (r->request_body == NULL) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }

    len = 0;

    for (in = r->request_body->bufs; in; in = in->next) {
        len += ngx_buf_size(in->buf);
    }

    b = ngx_create_temp_buf(r->pool, NGX_OFF_T_LEN);
    if (b == NULL) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }

    b->last = ngx_sprintf(b->pos, "%O", len);
    b->last_buf = (r == r->main) ? 1 : 0;
    b->last_in_chain = 1;

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = b->last - b->pos;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        ngx_http_finalize_request(r, rc);
        return;
    }

    out.buf = b;
    out.next = NULL;

    rc = ngx_http_output_filter(r, &out);

    ngx_http_finalize_request(r, rc);
}

Следующие поля запроса определяют способ чтения тела запроса:

  • request_body_in_single_buf — Считать тело в один буфер памяти.
  • request_body_in_file_only — Всегда считывать тело в файл, даже если оно помещается в буфер памяти.
  • request_body_in_persistent_file — Не удалять файл сразу после создания. Файл с этим флагом можно переместить в другой каталог.
  • request_body_in_clean_file — Удалить файл при завершении запроса. Это может быть полезно, если файл предполагалось переместить в другой каталог, но по какой-либо причине он не был перемещён.
  • request_body_file_group_access — Разрешить групповой доступ к файлу, заменив маску доступа по умолчанию 0600 на 0660.
  • request_body_file_log_level — Уровень важности для журналирования ошибок файла.
  • request_body_no_buffering — Считывать тело запроса без буферизации.

Флаг request_body_no_buffering включает небуферизованный режим чтения тела запроса. В этом режиме после вызова ngx_http_read_client_request_body() цепочка bufs может содержать только часть тела. Флаг запроса reading_body указывает, что чтение тела запроса ещё продолжается. Чтобы считать следующую часть, вызовите функцию ngx_http_read_unbuffered_request_body(r). Возвращаемое значение NGX_AGAIN означает, что требуются дополнительные данные тела или место в буфере. Если после вызова этой функции bufs имеет значение NULL, данные тела в данный момент недоступны. Обратный вызов запроса read_event_handler будет вызван, когда появятся новые данные тела от клиента.

Фильтры тела запроса

После считывания части тела запроса она передаётся в цепочку фильтров тела запроса вызовом первого обработчика фильтра тела, хранящегося в переменной ngx_http_top_request_body_filter. Предполагается, что каждый обработчик тела вызывает следующий обработчик в цепочке, пока не будет вызван последний обработчик ngx_http_request_body_save_filter(r, cl). Этот обработчик собирает буферы в r->request_body->bufs и при необходимости записывает их в файл. Для последнего буфера тела запроса флаг last_buf ненулевой.

Если фильтр планирует задерживать буферы данных, при первом вызове ему следует установить флаг r->request_body->filter_need_buffering в значение 1.

Ниже приведён пример простого фильтра тела запроса, который задерживает обработку тела запроса на одну секунду.

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


#define NGX_HTTP_DELAY_BODY  1000


typedef struct {
    ngx_event_t   event;
    ngx_chain_t  *out;
} ngx_http_delay_body_ctx_t;


static ngx_int_t ngx_http_delay_body_filter(ngx_http_request_t *r,
    ngx_chain_t *in);
static void ngx_http_delay_body_cleanup(void *data);
static void ngx_http_delay_body_event_handler(ngx_event_t *ev);
static ngx_int_t ngx_http_delay_body_init(ngx_conf_t *cf);


static ngx_http_module_t  ngx_http_delay_body_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_delay_body_init,      /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */

    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

    NULL,                          /* create location configuration */
    NULL                           /* merge location configuration */
};


ngx_module_t  ngx_http_delay_body_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_delay_body_module_ctx, /* module context */
    NULL,                          /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};


static ngx_http_request_body_filter_pt   ngx_http_next_request_body_filter;


static ngx_int_t
ngx_http_delay_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_chain_t                *cl, *ln;
    ngx_http_cleanup_t         *cln;
    ngx_http_delay_body_ctx_t  *ctx;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "delay request body filter");

    ctx = ngx_http_get_module_ctx(r, ngx_http_delay_body_filter_module);

    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_delay_body_ctx_t));
        if (ctx == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_delay_body_filter_module);

        r->request_body->filter_need_buffering = 1;
    }

    if (ngx_chain_add_copy(r->pool, &ctx->out, in) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (!ctx->event.timedout) {
        if (!ctx->event.timer_set) {

            /* cleanup to remove the timer in case of abnormal termination */

            cln = ngx_http_cleanup_add(r, 0);
            if (cln == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            cln->handler = ngx_http_delay_body_cleanup;
            cln->data = ctx;

            /* add timer */

            ctx->event.handler = ngx_http_delay_body_event_handler;
            ctx->event.data = r;
            ctx->event.log = r->connection->log;

            ngx_add_timer(&ctx->event, NGX_HTTP_DELAY_BODY);
        }

        return ngx_http_next_request_body_filter(r, NULL);
    }

    rc = ngx_http_next_request_body_filter(r, ctx->out);

    for (cl = ctx->out; cl; /* void */) {
        ln = cl;
        cl = cl->next;
        ngx_free_chain(r->pool, ln);
    }

    ctx->out = NULL;

    return rc;
}


static void
ngx_http_delay_body_cleanup(void *data)
{
    ngx_http_delay_body_ctx_t *ctx = data;

    if (ctx->event.timer_set) {
        ngx_del_timer(&ctx->event);
    }
}


static void
ngx_http_delay_body_event_handler(ngx_event_t *ev)
{
    ngx_connection_t    *c;
    ngx_http_request_t  *r;

    r = ev->data;
    c = r->connection;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "delay request body event");

    ngx_post_event(c->read, &ngx_posted_events);
}


static ngx_int_t
ngx_http_delay_body_init(ngx_conf_t *cf)
{
    ngx_http_next_request_body_filter = ngx_http_top_request_body_filter;
    ngx_http_top_request_body_filter = ngx_http_delay_body_filter;

    return NGX_OK;
}

Ответ

В nginx HTTP-ответ формируется отправкой заголовка ответа, за которым следует необязательное тело ответа. Заголовок и тело проходят через цепочку фильтров и в конечном счёте записываются в сокет клиента. Модуль nginx может установить свой обработчик в цепочку фильтров заголовков или тела и обрабатывать вывод, поступающий от предыдущего обработчика.

Заголовок ответа

Функция ngx_http_send_header(r) отправляет заголовок ответа. Не вызывайте эту функцию, пока r->headers_out не содержит все данные, необходимые для формирования заголовка HTTP-ответа. Поле status в r->headers_out должно быть задано всегда. Если код состояния ответа указывает, что за заголовком следует тело ответа, можно также задать content_length_n. Значение этого поля по умолчанию — -1, что означает, что размер тела неизвестен. В этом случае используется блочное кодирование передачи. Чтобы вывести произвольный заголовок, добавьте его в список headers.

static ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t         rc;
    ngx_table_elt_t  *h;

    /* send header */

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = 3;

    /* X-Foo: foo */

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "X-Foo");
    ngx_str_set(&h->value, "foo");

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* send body */

    ...
}

Фильтры заголовков

Функция ngx_http_send_header(r) вызывает цепочку фильтров заголовков, вызывая первый обработчик фильтра заголовков, хранящийся в переменной ngx_http_top_header_filter. Предполагается, что каждый обработчик заголовков вызывает следующий обработчик в цепочке, пока не будет вызван последний обработчик ngx_http_header_filter(r). Последний обработчик заголовков формирует HTTP-ответ на основе r->headers_out и передаёт его в ngx_http_writer_filter для вывода.

Чтобы добавить обработчик в цепочку фильтров заголовков, во время конфигурирования сохраните его адрес в глобальной переменной ngx_http_top_header_filter. Адрес предыдущего обработчика обычно хранится в статической переменной модуля и вызывается новым обработчиком перед завершением работы.

Следующий пример модуля фильтра заголовков добавляет заголовок HTTP «X-Foo: foo» ко всем ответам со статусом 200.

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf);


static ngx_http_module_t  ngx_http_foo_header_filter_module_ctx = {
    NULL,                                   /* preconfiguration */
    ngx_http_foo_header_filter_init,        /* postconfiguration */

    NULL,                                   /* create main configuration */
    NULL,                                   /* init main configuration */

    NULL,                                   /* create server configuration */
    NULL,                                   /* merge server configuration */

    NULL,                                   /* create location configuration */
    NULL                                    /* merge location configuration */
};


ngx_module_t  ngx_http_foo_header_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_foo_header_filter_module_ctx, /* module context */
    NULL,                                   /* module directives */
    NGX_HTTP_MODULE,                        /* module type */
    NULL,                                   /* init master */
    NULL,                                   /* init module */
    NULL,                                   /* init process */
    NULL,                                   /* init thread */
    NULL,                                   /* exit thread */
    NULL,                                   /* exit process */
    NULL,                                   /* exit master */
    NGX_MODULE_V1_PADDING
};


static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;


static ngx_int_t
ngx_http_foo_header_filter(ngx_http_request_t *r)
{
    ngx_table_elt_t  *h;

    /*
     * The filter handler adds "X-Foo: foo" header
     * to every HTTP 200 response
     */

    if (r->headers_out.status != NGX_HTTP_OK) {
        return ngx_http_next_header_filter(r);
    }

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "X-Foo");
    ngx_str_set(&h->value, "foo");

    return ngx_http_next_header_filter(r);
}


static ngx_int_t
ngx_http_foo_header_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_foo_header_filter;

    return NGX_OK;
}

Тело ответа

Чтобы отправить тело ответа, вызовите функцию ngx_http_output_filter(r, cl). Её можно вызывать несколько раз. При каждом вызове отправляется часть тела ответа в виде цепочки буферов. Установите флаг last_buf в последнем буфере тела.

В следующем примере формируется полный HTTP-ответ с телом "foo". Чтобы пример работал как для подзапроса, так и для основного запроса, для последнего буфера вывода устанавливается флаг last_in_chain. Флаг last_buf устанавливается только для основного запроса, поскольку последний буфер подзапроса не завершает весь вывод.

static ngx_int_t
ngx_http_bar_content_handler(ngx_http_request_t *r)
{
    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_chain_t   out;

    /* send header */

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = 3;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* send body */

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    b->last_buf = (r == r->main) ? 1 : 0;
    b->last_in_chain = 1;

    b->memory = 1;

    b->pos = (u_char *) "foo";
    b->last = b->pos + 3;

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}

Фильтры тела ответа

Функция ngx_http_output_filter(r, cl) запускает цепочку фильтров тела, вызывая первый обработчик фильтра тела, сохраненный в переменной ngx_http_top_body_filter. Предполагается, что каждый обработчик тела вызывает следующий обработчик в цепочке, пока не будет вызван последний обработчик ngx_http_write_filter(r, cl).

Обработчик фильтра тела получает цепочку буферов. Предполагается, что обработчик обрабатывает буферы и передает следующему обработчику, возможно, новую цепочку. Стоит отметить, что звенья цепочки ngx_chain_t входящей цепочки принадлежат вызывающему коду, и их нельзя повторно использовать или изменять. Сразу после завершения работы обработчика вызывающий код может использовать звенья выходной цепочки для отслеживания переданных им буферов. Чтобы сохранить цепочку буферов или заменить некоторые буферы перед передачей следующему фильтру, обработчику необходимо выделить собственные звенья цепочки.

Ниже приведен пример простого фильтра тела, который подсчитывает количество байтов в теле. Результат доступен в переменной $counter, которую можно использовать в журнале доступа.

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


typedef struct {
    off_t  count;
} ngx_http_counter_filter_ctx_t;


static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r,
    ngx_chain_t *in);
static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf);
static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf);


static ngx_http_module_t  ngx_http_counter_filter_module_ctx = {
    ngx_http_counter_add_variables,        /* preconfiguration */
    ngx_http_counter_filter_init,          /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    NULL,                                  /* create location configuration */
    NULL                                   /* merge location configuration */
};


ngx_module_t  ngx_http_counter_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_counter_filter_module_ctx,   /* module context */
    NULL,                                  /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};


static ngx_http_output_body_filter_pt  ngx_http_next_body_filter;

static ngx_str_t  ngx_http_counter_name = ngx_string("counter");


static ngx_int_t
ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_chain_t                    *cl;
    ngx_http_counter_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_counter_filter_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module);
    }

    for (cl = in; cl; cl = cl->next) {
        ctx->count += ngx_buf_size(cl->buf);
    }

    return ngx_http_next_body_filter(r, in);
}


static ngx_int_t
ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
    uintptr_t data)
{
    u_char                         *p;
    ngx_http_counter_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module);
    if (ctx == NULL) {
        v->not_found = 1;
        return NGX_OK;
    }

    p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
    if (p == NULL) {
        return NGX_ERROR;
    }

    v->data = p;
    v->len = ngx_sprintf(p, "%O", ctx->count) - p;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;

    return NGX_OK;
}


static ngx_int_t
ngx_http_counter_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var;

    var = ngx_http_add_variable(cf, &ngx_http_counter_name, 0);
    if (var == NULL) {
        return NGX_ERROR;
    }

    var->get_handler = ngx_http_counter_variable;

    return NGX_OK;
}


static ngx_int_t
ngx_http_counter_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_counter_body_filter;

    return NGX_OK;
}

Сборка модулей фильтров

При написании фильтра тела или заголовков уделяйте особое внимание его позиции в порядке фильтров. Стандартные модули nginx регистрируют ряд фильтров заголовков и тела. Важно зарегистрировать новый модуль фильтра в правильном месте относительно них. Обычно модули регистрируют фильтры в обработчиках постконфигурации. Порядок вызова фильтров во время обработки, очевидно, обратен порядку их регистрации.

Для сторонних модулей фильтров nginx предоставляет специальный слот HTTP_AUX_FILTER_MODULES. Чтобы зарегистрировать модуль фильтра в этом слоте, установите переменную ngx_module_type в значение HTTP_AUX_FILTER в конфигурации модуля.

В следующем примере показан файл конфигурации модуля, предполагающий, что у модуля есть только один исходный файл — ngx_http_foo_filter_module.c.

ngx_module_type=HTTP_AUX_FILTER
ngx_module_name=ngx_http_foo_filter_module
ngx_module_srcs="$ngx_addon_dir/ngx_http_foo_filter_module.c"

. auto/module

Повторное использование буферов

При выдаче или изменении потока буферов часто желательно повторно использовать выделенные буферы. Стандартный и широко применяемый в коде nginx подход заключается в том, чтобы хранить для этой цели две цепочки буферов: free и busy. В цепочке free хранятся все свободные буферы, которые можно использовать повторно. В цепочке busy хранятся все буферы, отправленные текущим модулем, которые еще используются каким-либо другим обработчиком фильтра. Буфер считается используемым, если его размер больше нуля. Обычно, когда фильтр потребляет буфер, его pos (или file_pos для файлового буфера) перемещается к last (file_last для файлового буфера). После полного потребления буфер готов к повторному использованию. Чтобы добавить недавно освобожденные буферы в цепочку free, достаточно пройти по цепочке busy и переместить буферы нулевого размера в ее начале в free. Эта операция настолько распространена, что для нее существует специальная функция — ngx_chain_update_chains(free, busy, out, tag). Функция добавляет выходную цепочку out в busy и перемещает свободные буферы из начала busy в free. Повторно используются только буферы с указанным tag. Это позволяет модулю повторно использовать только буферы, выделенные им самим.

В следующем примере показан фильтр тела, который вставляет строку «foo» перед каждым входящим буфером. Новые буферы, выделенные модулем, по возможности используются повторно. Обратите внимание, что для правильной работы этого примера также необходимо настроить фильтр заголовков и сбросить content_length_n в значение -1, но соответствующий код здесь не приведен.

typedef struct {
    ngx_chain_t  *free;
    ngx_chain_t  *busy;
}  ngx_http_foo_filter_ctx_t;


ngx_int_t
ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_buf_t                  *b;
    ngx_chain_t                *cl, *tl, *out, **ll;
    ngx_http_foo_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_foo_filter_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module);
    }

    /* create a new chain "out" from "in" with all the changes */

    ll = &out;

    for (cl = in; cl; cl = cl->next) {

        /* append "foo" in a reused buffer if possible */

        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
        if (tl == NULL) {
            return NGX_ERROR;
        }

        b = tl->buf;
        b->tag = (ngx_buf_tag_t) &ngx_http_foo_filter_module;
        b->memory = 1;
        b->pos = (u_char *) "foo";
        b->last = b->pos + 3;

        *ll = tl;
        ll = &tl->next;

        /* append the next incoming buffer */

        tl = ngx_alloc_chain_link(r->pool);
        if (tl == NULL) {
            return NGX_ERROR;
        }

        tl->buf = cl->buf;
        *ll = tl;
        ll = &tl->next;
    }

    *ll = NULL;

    /* send the new chain */

    rc = ngx_http_next_body_filter(r, out);

    /* update "busy" and "free" chains for reuse */

    ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
                            (ngx_buf_tag_t) &ngx_http_foo_filter_module);

    return rc;
}

Балансировка нагрузки

Модуль ngx_http_upstream_module предоставляет основные функции, необходимые для передачи запросов на удаленные серверы. Этими функциями пользуются модули, реализующие конкретные протоколы, такие как HTTP или FastCGI. Модуль также предоставляет интерфейс для создания пользовательских модулей балансировки нагрузки и реализует метод балансировки по умолчанию round-robin.

Модули least_conn и hash реализуют альтернативные методы балансировки нагрузки, но фактически являются расширениями модуля балансировки upstream round-robin и используют с ним много общего кода, например представление группы серверов. Модуль keepalive — независимый модуль, расширяющий функциональность upstream.

Модуль ngx_http_upstream_module можно настроить явно, поместив в файл конфигурации соответствующий блок upstream, либо неявно, используя такие директивы, как proxy_pass, принимающие URL, который в некоторый момент преобразуется в список серверов. Альтернативные методы балансировки нагрузки доступны только при явной конфигурации upstream. У конфигурации модуля upstream есть собственный контекст директив NGX_HTTP_UPS_CONF. Структура определена следующим образом:

struct ngx_http_upstream_srv_conf_s {
    ngx_http_upstream_peer_t         peer;
    void                           **srv_conf;

    ngx_array_t                     *servers;  /* ngx_http_upstream_server_t */

    ngx_uint_t                       flags;
    ngx_str_t                        host;
    u_char                          *file_name;
    ngx_uint_t                       line;
    in_port_t                        port;
    ngx_uint_t                       no_port;  /* unsigned no_port:1 */

#if (NGX_HTTP_UPSTREAM_ZONE)
    ngx_shm_zone_t                  *shm_zone;
#endif
};
  • srv_conf — контекст конфигурации модулей upstream.
  • servers — массив ngx_http_upstream_server_t, результат разбора набора директив server в блоке upstream.
  • flags — флаги, в основном указывающие, какие функции поддерживаются методом балансировки нагрузки. Функции настраиваются с помощью параметров директивы server:
    • NGX_HTTP_UPSTREAM_CREATE — отличает явно заданные upstream от автоматически созданных директивой proxy_pass и «родственными» директивами (FastCGI, SCGI и т. д.).
    • NGX_HTTP_UPSTREAM_WEIGHT — поддерживается параметр «weight»
    • NGX_HTTP_UPSTREAM_MAX_FAILS — поддерживается параметр «max_fails»
    • NGX_HTTP_UPSTREAM_FAIL_TIMEOUT — поддерживается параметр «fail_timeout»
    • NGX_HTTP_UPSTREAM_DOWN — поддерживается параметр «down»
    • NGX_HTTP_UPSTREAM_BACKUP — поддерживается параметр «backup»
    • NGX_HTTP_UPSTREAM_MAX_CONNS — поддерживается параметр «max_conns»
  • host — имя upstream.
  • file_name, line — имя файла конфигурации и строка, в которой находится блок upstream.
  • port и no_port — не используются для явно заданных групп upstream.
  • shm_zone — зона разделяемой памяти, используемая этой группой upstream, если она есть.
  • peer — объект, содержащий общие методы инициализации конфигурации upstream:
    typedef struct {
        ngx_http_upstream_init_pt        init_upstream;
        ngx_http_upstream_init_peer_pt   init;
        void                            *data;
    } ngx_http_upstream_peer_t;
    
    . Модуль, реализующий алгоритм балансировки нагрузки, должен задать эти методы и инициализировать закрытую структуру data. Если init_upstream не была инициализирована при разборе конфигурации, ngx_http_upstream_module задает для нее алгоритм ngx_http_upstream_init_round_robin по умолчанию.
    • init_upstream(cf, us) — метод, отвечающий во время конфигурации за инициализацию группы серверов и инициализацию метода init() в случае успеха. Типичный модуль балансировки нагрузки использует список серверов в блоке upstream для создания эффективной структуры данных, использует ее и сохраняет собственную конфигурацию в поле data.
    • init(r, us) — инициализирует структуру ngx_http_upstream_peer_t.peer для отдельного запроса, используемую для балансировки нагрузки (не путать с описанной выше ngx_http_upstream_srv_conf_t.peer, которая относится к отдельному upstream). Она передается как аргумент data всем обратным вызовам, связанным с выбором сервера.

Когда nginx должен передать запрос другому узлу для обработки, он использует настроенный метод балансировки нагрузки, чтобы получить адрес для подключения. Метод берется из объекта ngx_http_upstream_t.peer типа ngx_peer_connection_t:

struct ngx_peer_connection_s {
    ...

    struct sockaddr                 *sockaddr;
    socklen_t                        socklen;
    ngx_str_t                       *name;

    ngx_uint_t                       tries;

    ngx_event_get_peer_pt            get;
    ngx_event_free_peer_pt           free;
    ngx_event_notify_peer_pt         notify;
    void                            *data;

#if (NGX_SSL || NGX_COMPAT)
    ngx_event_set_peer_session_pt    set_session;
    ngx_event_save_peer_session_pt   save_session;
#endif

    ...
};

Структура содержит следующие поля:

  • sockaddr, socklen, name — адрес upstream-сервера, к которому нужно подключиться; это выходные параметры метода балансировки нагрузки.
  • data — данные метода балансировки нагрузки для отдельного запроса; содержат состояние алгоритма выбора и обычно включают ссылку на конфигурацию upstream. Передаются как аргумент всем методам, связанным с выбором сервера (см. ниже).
  • tries — допустимое число попыток подключения к upstream-серверу.
  • get, free, notify, set_session и save_session — методы модуля балансировки нагрузки, описанные ниже.

Все методы принимают как минимум два аргумента: объект соединения с узлом pc и структуру data, созданную методом ngx_http_upstream_srv_conf_t.peer.init(). Обратите внимание, что она может отличаться от pc.data из-за «цепочки» модулей балансировки нагрузки.

  • get(pc, data) — метод, вызываемый, когда модуль upstream готов передать запрос upstream-серверу и ему нужно узнать его адрес. Метод должен заполнить поля sockaddr, socklen и name структуры ngx_peer_connection_t. Возможны следующие возвращаемые значения:
    • NGX_OK — сервер выбран.
    • NGX_ERROR — произошла внутренняя ошибка.
    • NGX_BUSY — в данный момент нет доступных серверов. Это может произойти по разным причинам: динамическая группа серверов пуста, все серверы в группе находятся в состоянии сбоя или все серверы в группе уже обрабатывают максимально допустимое число соединений.
    • NGX_DONE — нижележащее соединение было повторно использовано, поэтому создавать новое соединение с upstream-сервером не нужно. Это значение задается модулем keepalive.
  • free(pc, data, state) — метод, вызываемый, когда модуль upstream завершил работу с определенным сервером. Аргумент state — статус завершения соединения upstream, битовая маска со следующими возможными значениями:
    • NGX_PEER_FAILED — попытка была неудачной
    • NGX_PEER_NEXT — особый случай, когда upstream-сервер возвращает коды 403 или 404, которые не считаются сбоем.
    • NGX_PEER_KEEPALIVE — в настоящее время не используется
    Этот метод также уменьшает счетчик tries.
  • notify(pc, data, type) — в настоящее время не используется в версии OSS.
  • set_session(pc, data) и save_session(pc, data) — методы, относящиеся к SSL и позволяющие кэшировать сеансы с upstream-серверами. Их реализация предоставляется методом балансировки round-robin.

Примеры

В репозитории nginx-dev-examples приведены примеры модулей nginx.

Стиль кода

Общие правила

  • максимальная длина строки — 80 символов
  • отступ — 4 пробела
  • табуляция и пробелы в конце строки не допускаются
  • элементы списка в одной строке разделяются пробелами
  • шестнадцатеричные литералы записываются строчными буквами
  • имена файлов, функций и типов, а также глобальные переменные имеют префикс ngx_ или более конкретный префикс, например ngx_http_ и ngx_mail_
size_t
ngx_utf8_length(u_char *p, size_t n)
{
    u_char  c, *last;
    size_t  len;

    last = p + n;

    for (len = 0; p < last; len++) {

        c = *p;

        if (c < 0x80) {
            p++;
            continue;
        }

        if (ngx_utf8_decode(&p, last - p) > 0x10ffff) {
            /* invalid UTF-8 */
            return n;
        }
    }

    return len;
}

Файлы

Типичный исходный файл может содержать следующие разделы, разделенные двумя пустыми строками:

  • сведения об авторских правах
  • директивы включения
  • определения препроцессора
  • определения типов
  • прототипы функций
  • определения переменных
  • определения функций

Сведения об авторских правах выглядят так:

/*
 * Copyright (C) Author Name
 * Copyright (C) Organization, Inc.
 */

Если файл значительно изменен, список авторов следует обновить, добавив нового автора в начало.

Файлы ngx_config.h и ngx_core.h всегда включаются первыми, за ними следует один из файлов ngx_http.h, ngx_stream.h или ngx_mail.h. Затем идут необязательные внешние заголовочные файлы:

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxslt/xslt.h>

#if (NGX_HAVE_EXSLT)
#include <libexslt/exslt.h>
#endif

В заголовочных файлах должна использоваться так называемая «защита заголовка»:

#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_
#define _NGX_PROCESS_CYCLE_H_INCLUDED_
...
#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */

Комментарии

  • комментарии «//» не используются
  • текст пишется на английском языке, предпочтительно использовать американский вариант написания
  • многострочные комментарии оформляются следующим образом:
    /*
     * The red-black tree code is based on the algorithm described in
     * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
     */
    
    /* find the server configuration for the address:port */
    

Препроцессор

Имена макросов начинаются с префикса ngx_ или NGX_ (либо более конкретного префикса). Имена макросов-констант пишутся заглавными буквами. Параметризованные макросы и макросы для инициализаторов пишутся строчными буквами. Имя макроса и его значение разделяются как минимум двумя пробелами:

#define NGX_CONF_BUFFER  4096

#define ngx_buf_in_memory(b)  (b->temporary || b->memory || b->mmap)

#define ngx_buf_size(b)                                                      \
    (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos):                      \
                            (b->file_last - b->file_pos))

#define ngx_null_string  { 0, NULL }

Условия заключаются в скобки, отрицание указывается снаружи:

#if (NGX_HAVE_KQUEUE)
...
#elif ((NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \
       || (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT)))
...
#elif (NGX_HAVE_EPOLL && !(NGX_TEST_BUILD_EPOLL))
...
#elif (NGX_HAVE_POLL)
...
#else /* select */
...
#endif /* NGX_HAVE_KQUEUE */

Типы

Имена типов заканчиваются суффиксом «_t». Имя определяемого типа отделяется как минимум двумя пробелами:

typedef ngx_uint_t  ngx_rbtree_key_t;

Структурные типы определяются с помощью typedef. Внутри структур типы и имена членов выравниваются:

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

Сохраняйте одинаковое выравнивание во всех структурах файла. Структура, содержащая указатель на саму себя, имеет имя, оканчивающееся на «_s». Соседние определения структур разделяются двумя пустыми строками:

typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};


typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;

Каждый член структуры объявляется в отдельной строке:

typedef struct {
    ngx_uint_t        hash;
    ngx_str_t         key;
    ngx_str_t         value;
    u_char           *lowcase_key;
} ngx_table_elt_t;

Указатели на функции внутри структур имеют определенные типы, оканчивающиеся на «_pt»:

typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
    off_t limit);
typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
    off_t limit);

typedef struct {
    ngx_recv_pt        recv;
    ngx_recv_chain_pt  recv_chain;
    ngx_recv_pt        udp_recv;
    ngx_send_pt        send;
    ngx_send_pt        udp_send;
    ngx_send_chain_pt  udp_send_chain;
    ngx_send_chain_pt  send_chain;
    ngx_uint_t         flags;
} ngx_os_io_t;

Типы перечислений оканчиваются на «_e»:

typedef enum {
    ngx_http_fastcgi_st_version = 0,
    ngx_http_fastcgi_st_type,
    ...
    ngx_http_fastcgi_st_padding
} ngx_http_fastcgi_state_e;

Переменные

Переменные объявляются в порядке возрастания длины базового типа, а затем по алфавиту. Имена типов и переменных выравниваются. «Столбцы» типа и имени разделяются двумя пробелами. Большие массивы размещаются в конце блока объявлений:

u_char                      |  | *rv, *p;
ngx_conf_t                  |  | *cf;
ngx_uint_t                  |  |  i, j, k;
unsigned int                |  |  len;
struct sockaddr             |  | *sa;
const unsigned char         |  | *data;
ngx_peer_connection_t       |  | *pc;
ngx_http_core_srv_conf_t    |  |**cscfp;
ngx_http_upstream_srv_conf_t|  | *us, *uscf;
u_char                      |  |  text[NGX_SOCKADDR_STRLEN];

Статические и глобальные переменные можно инициализировать при объявлении:

static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");
static ngx_uint_t  mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static uint32_t  ngx_crc32_table16[] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    ...
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

Существует ряд часто используемых сочетаний типов и имен:

u_char                        *rv;
ngx_int_t                      rc;
ngx_conf_t                    *cf;
ngx_connection_t              *c;
ngx_http_request_t            *r;
ngx_peer_connection_t         *pc;
ngx_http_upstream_srv_conf_t  *us, *uscf;

Функции

Для всех функций (даже статических) должны быть объявлены прототипы. Прототипы включают имена аргументов. Длинные прототипы переносятся с одинарным отступом в строках продолжения:

static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf,
    ngx_http_core_main_conf_t *cmcf);

static char *ngx_http_merge_servers(ngx_conf_t *cf,
    ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module,
    ngx_uint_t ctx_index);

Имя функции в определении начинается с новой строки. Открывающая и закрывающая фигурные скобки тела функции размещаются на отдельных строках. Тело функции имеет отступ. Между функциями оставляются две пустые строки:

static ngx_int_t
ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len)
{
    ...
}


static ngx_int_t
ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
    ...
}

После имени функции и открывающей скобки пробел не ставится. Длинные вызовы функций переносятся так, чтобы строки продолжения начинались на позиции первого аргумента функции. Если это невозможно, первую строку продолжения следует отформатировать так, чтобы она заканчивалась на позиции 79:

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
               "http header: \"%V: %V\"",
               &h->key, &h->value);

hc->busy = ngx_palloc(r->connection->pool,
                  cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));

Вместо inline следует использовать макрос ngx_inline:

static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);

Выражения

Бинарные операторы, кроме «.» и «−>», отделяются от операндов одним пробелом. Унарные операторы и индексы не отделяются от операндов пробелами:

width = width * 10 + (*fmt++ - '0');
ch = (u_char) ((decoded << 4) + (ch - '0'));
r->exten.data = &r->uri.data[i + 1];

Приведения типов отделяются одним пробелом от выражений, к которым они применяются. Звездочка внутри приведения типа отделяется пробелом от имени типа:

len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 1);

Если выражение не помещается в одну строку, его переносят. Предпочтительное место переноса — бинарный оператор. Строка продолжения выравнивается по началу выражения:

if (status == NGX_HTTP_MOVED_PERMANENTLY
    || status == NGX_HTTP_MOVED_TEMPORARILY
    || status == NGX_HTTP_SEE_OTHER
    || status == NGX_HTTP_TEMPORARY_REDIRECT
    || status == NGX_HTTP_PERMANENT_REDIRECT)
{
    ...
}
p->temp_file->warn = "an upstream response is buffered "
                     "to a temporary file";

В крайнем случае выражение можно перенести так, чтобы строка продолжения заканчивалась на позиции 79:

hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
                                     + size * sizeof(ngx_hash_elt_t *));

Приведенные выше правила также применяются к подвыражениям, каждое из которых имеет собственный уровень отступа:

if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING)
     || c->stale_updating) && !r->background
    && u->conf->cache_background_update)
{
    ...
}

Иногда удобно переносить выражение после приведения типа. В этом случае строка продолжения имеет отступ:

node = (ngx_rbtree_node_t *)
           ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));

Указатели явно сравниваются с NULL (а не с 0):

if (ptr != NULL) {
    ...
}

Условные конструкции и циклы

Ключевое слово «if» отделяется от условия одним пробелом. Открывающая фигурная скобка располагается в той же строке или, если условие занимает несколько строк, в отдельной строке. Закрывающая скобка располагается в отдельной строке, за которой при необходимости следует «else if / else». Обычно перед частью «else if / else» оставляется пустая строка:

if (node->left == sentinel) {
    temp = node->right;
    subst = node;

} else if (node->right == sentinel) {
    temp = node->left;
    subst = node;

} else {
    subst = ngx_rbtree_min(node->right, sentinel);

    if (subst->left != sentinel) {
        temp = subst->left;

    } else {
        temp = subst->right;
    }
}

Аналогичные правила форматирования применяются к циклам «do» и «while»:

while (p < last && *p == ' ') {
    p++;
}
do {
    ctx->node = rn;
    ctx = ctx->next;
} while (ctx);

Ключевое слово «switch» отделяется от условия одним пробелом. Открывающая фигурная скобка располагается в той же строке. Закрывающая скобка располагается в отдельной строке. Ключевые слова «case» выравниваются по «switch»:

switch (ch) {
case '!':
    looked = 2;
    state = ssi_comment0_state;
    break;

case '<':
    copy_end = p;
    break;

default:
    copy_end = p;
    looked = 0;
    state = ssi_start_state;
    break;
}

Большинство циклов «for» оформляются следующим образом:

for (i = 0; i < ccf->env.nelts; i++) {
    ...
}
for (q = ngx_queue_head(locations);
     q != ngx_queue_sentinel(locations);
     q = ngx_queue_next(q))
{
    ...
}

Если какая-либо часть оператора «for» опущена, это обозначается комментарием «/* void */»:

for (i = 0; /* void */ ; i++) {
    ...
}

Цикл с пустым телом также обозначается комментарием «/* void */», который можно поместить в ту же строку:

for (cl = *busy; cl->next; cl = cl->next) { /* void */ }

Бесконечный цикл выглядит так:

for ( ;; ) {
    ...
}

Метки

Метки отделяются пустыми строками и имеют отступ предыдущего уровня:

    if (i == 0) {
        u->err = "host not found";
        goto failed;
    }

    u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t));
    if (u->addrs == NULL) {
        goto failed;
    }

    u->naddrs = i;

    ...

    return NGX_OK;

failed:

    freeaddrinfo(res);
    return NGX_ERROR;

Отладка проблем с памятью

Для отладки проблем с памятью, таких как переполнение буфера или ошибки использования освобожденной памяти, можно использовать AddressSanitizer (ASan), поддерживаемый некоторыми современными компиляторами. Чтобы включить ASan с gcc и clang, используйте параметр компилятора и компоновщика -fsanitize=address. При сборке nginx этот параметр можно добавить в параметры --with-cc-opt и --with-ld-opt сценария configure.

Поскольку в nginx большинство выделений памяти выполняется из внутреннего пула nginx, включения ASan может быть недостаточно для отладки проблем с памятью. Внутренний пул выделяет большой участок памяти у системы и нарезает из него более мелкие блоки. Однако этот механизм можно отключить, установив макрос NGX_DEBUG_PALLOC в значение 1. В этом случае выделение памяти выполняется непосредственно системным распределителем, который полностью контролирует границы буферов.

Следующая строка конфигурации обобщает приведенные выше сведения. Ее рекомендуется использовать при разработке сторонних модулей и тестировании nginx на разных платформах.

auto/configure --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1'
               --with-ld-opt=-fsanitize=address

Распространенные ошибки

Написание модуля на C

Самая распространенная ошибка — попытка написать полноценный модуль на C, хотя этого можно избежать. В большинстве случаев задачу можно решить, создав подходящую конфигурацию. Если без написания модуля не обойтись, постарайтесь сделать его как можно меньше и проще. Например, модуль может экспортировать только некоторые переменные.

Перед началом работы над модулем рассмотрите следующие вопросы:

  • Можно ли реализовать нужную функциональность с помощью уже доступных модулей?
  • Можно ли решить задачу с помощью встроенных языков сценариев, таких как Perl или njs?

Строки C

Самый распространенный в nginx тип строк, ngx_str_t, не является строкой в стиле C с нулевым завершающим символом. Нельзя передавать данные стандартным функциям библиотеки C, таким как strlen() или strstr(). Вместо этого следует использовать аналоги nginx, принимающие ngx_str_t, либо указатель на данные и их длину. Однако бывают случаи, когда ngx_str_t содержит указатель на строку с нулевым завершающим символом: строки, полученные в результате разбора файла конфигурации, завершаются нулевым символом.

Глобальные переменные

Избегайте использования глобальных переменных в своих модулях. Скорее всего, наличие глобальной переменной — ошибка. Все глобальные данные должны быть привязаны к циклу конфигурации и выделяться из соответствующего пула памяти. Это позволяет nginx выполнять плавную перезагрузку конфигурации. Попытка использовать глобальные переменные, скорее всего, нарушит эту возможность, поскольку станет невозможно одновременно иметь две конфигурации и освобождать их. Иногда глобальные переменные необходимы. В таком случае нужно уделить особое внимание корректной обработке переконфигурации. Также проверьте, нет ли у используемых вашим кодом библиотек неявного глобального состояния, которое может нарушиться при перезагрузке.

Ручное управление памятью

Вместо того чтобы использовать подверженный ошибкам подход malloc/free, научитесь работать с пулами nginx. Пул создается и привязывается к объекту — конфигурации, циклу, соединению или HTTP-запросу. При уничтожении объекта уничтожается и связанный с ним пул. Поэтому при работе с объектом можно выделить нужный объем памяти из соответствующего пула и не беспокоиться об освобождении памяти даже в случае ошибок.

Потоки

В nginx рекомендуется избегать использования потоков, поскольку это неизбежно нарушит работу: большинство функций nginx не являются потокобезопасными. Предполагается, что поток будет выполнять только системные вызовы и потокобезопасные библиотечные функции. Если нужно запускать код, не связанный с обработкой запросов клиентов, правильный способ — запланировать таймер в обработчике модуля init_process и выполнить необходимые действия в обработчике таймера. Внутри nginx использует потоки для ускорения операций ввода-вывода, но это особый случай со множеством ограничений.

Блокирующие библиотеки

Распространенная ошибка — использование библиотек, блокирующих выполнение внутри себя. Большинство существующих библиотек по своей природе синхронны и блокирующие. Другими словами, они выполняют по одной операции за раз и тратят время на ожидание ответа от другого узла. В результате при обработке запроса с помощью такой библиотеки блокируется весь рабочий процесс nginx, что приводит к снижению производительности. Используйте только библиотеки с асинхронным интерфейсом, которые не блокируют весь процесс.

HTTP-запросы к внешним сервисам

Часто модулям необходимо выполнять HTTP-вызовы к внешним сервисам. Распространенная ошибка — использовать для этого внешнюю библиотеку, например libcurl. Совершенно не нужно подключать большой объем внешнего (возможно, блокирующего!) кода для задачи, которую nginx может выполнить самостоятельно.

Есть два основных сценария, в которых требуется внешний запрос:

  • в контексте обработки клиентского запроса (например, в обработчике содержимого)
  • в контексте рабочего процесса (например, в обработчике таймера)

В первом случае лучше всего использовать API подзапросов. Вместо прямого обращения к внешнему сервису объявите расположение в конфигурации nginx и направьте подзапрос на него. Это расположение не ограничивается проксированием запросов, в нем можно использовать и другие директивы nginx. Пример такого подхода — директива auth_request, реализованная в модуле ngx_http_auth_request.

Во втором случае можно использовать базовые функции HTTP-клиента, доступные в nginx. Например, в модуле OCSP реализован простой HTTP-клиент.

© 2002-2021 Igor Sysoev
© 2011-2026 Nginx, Inc.
Licensed under the BSD License.
https://nginx.org/en/docs/dev/development_guide.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API