Руководство по разработке
- Введение
- Структура кода
- Файлы include
- Целые числа
- Общие коды возврата
- Обработка ошибок
- Строки
- Обзор
- Форматирование
- Преобразование чисел
- Регулярные выражения
- Время
- Контейнеры
- Массив
- Список
- Очередь
- Дерево красно-черного цвета
- Хэш-таблица
- Управление памятью
- Куча
- Пул
- Обменная память
- Ведение журнала
- Цикл
- Буфер
- Сеть
- Подключение
- События
- Событие
- События ввода-вывода
- События таймера
- Размещённые события
- Цикл обработки событий
- Процессы
- Потоки
- Модули
- Добавление новых модулей
- Основные модули
- Директивы конфигурации
- 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
Следующие две #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 возвращает последний код ошибки системы. Он сопоставлен с errno на платформах POSIX и с вызовом GetLastError() в Windows. Макрос ngx_socket_errno возвращает последний номер ошибки сокета. Подобно макросу ngx_errno, он сопоставлен с errno на платформах POSIX. Он сопоставлен с вызовом WSAGetLastError() в Windows. Многократное обращение к значениям 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из строковой литерала Ctext -
ngx_null_string— статическая инициализация пустой строки для типаngx_str_t -
ngx_str_set(str, text)— инициализирует строкуstrтипаngx_str_t *со строковой литерала Ctext -
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;END_OF_DOCUMENT_MARKER
В 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 представляет время с тремя отдельными типами для секунд, миллисекунд и смещения по Гринвичу:
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— Используется в записях syslog:"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) возвращает строковое представление, подходящее для 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 предоставляет тип пула фрагментов ngx_slab_pool_t. Пул фрагментов для выделения памяти автоматически создаётся в каждой зоне совместного доступа nginx. Пул расположен в начале зоны совместного доступа и может быть доступен по выражению (ngx_slab_pool_t *) shm_zone->shm.addr. Для выделения памяти в зоне совместного доступа вызовите либо ngx_slab_alloc(pool, size), либо ngx_slab_calloc(pool, size). Для освобождения памяти вызовите ngx_slab_free(pool, p).
Пул фрагментов делит всю зону совместного доступа на страницы. Каждая страница используется для выделения объектов одного размера. Указанный размер должен быть степенью двойки и больше минимального размера 8 байт. Несоответствующие значения округляются вверх. Битовая маска для каждой страницы отслеживает, какие блоки используются, а какие свободны для выделения. Для размеров, превышающих половину страницы (обычно 2048 байт), выделение выполняется сразу на всю страницу.
Для защиты данных в общей памяти от одновременного доступа используйте мьютекс, доступный в поле mutex объекта ngx_slab_pool_t. Мьютекс чаще всего используется пулом фрагментов при выделении и освобождении памяти, но его можно использовать для защиты любых других пользовательских структур данных, выделенных в зоне совместного доступа. Для блокировки или разблокировки мьютекса вызовите 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 создается новый цикл на основе новой конфигурации nginx; старый цикл обычно удаляется после успешного создания нового.
Цикл создается функцией ngx_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-запрос или сеанс потока. -
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). Подключения HTTP-клиентов являются примером повторно используемых подключений в nginx; они помечаются как повторно используемые до получения первого байта запроса от клиента.
События
Событие
Объект события 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) ожидается в сокете, даже если некоторые данные могут быть доступны перед ним. Флаг передаётся через событиеEPOLLRDHUPepollили флагEV_EOFkqueue. -
error— флаг, указывающий на ошибку при чтении (для события чтения) или записи (для события записи). -
cancelable— флаг события таймера, указывающий, что событие должно быть проигнорировано во время завершения работы процесса. Плавное завершение работы процесса откладывается до тех пор, пока не будет запланировано никаких неотменяемых событий таймера. -
posted— флаг, указывающий, что событие помещено в очередь. -
queue— узел очереди для помещения события в очередь.
События ввода/вывода
Каждое подключение, полученное с помощью функции ngx_get_connection(), имеет два присоединённых события, c->read и c->write, которые используются для получения уведомления о том, что сокет готов к чтению или записи. Все такие события работают в режиме Edge-Triggered, что означает, что они генерируют уведомления только при изменении состояния сокета. Например, частичное чтение из сокета не заставляет nginx повторно отправлять уведомление о чтении, пока не поступят дополнительные данные в сокет. Даже когда лежащий в основе механизм уведомлений об операциях ввода/вывода по сути является Level-Triggered (poll, select и т. д.), nginx преобразует уведомления в Edge-Triggered. Для обеспечения согласованности уведомлений 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на большинстве систем) — Переоткрытие файлов. Мастер-процесс отправляет этот сигнал рабочим процессам, которые переоткрывают все файлы, связанные с циклом. -
NGX_CHANGEBIN_SIGNAL(SIGUSR2на большинстве систем) — Изменение двоичного файла nginx. Мастер-процесс запускает новый двоичный файл nginx и передаёт список всех сокетов прослушивания. Список в текстовом формате, переданный в переменной окружения“NGINX”, состоит из номеров дескрипторов, разделённых точкой с запятой. Новый двоичный файл nginx считывает переменную“NGINX”и добавляет сокеты в свой цикл инициализации. Другие процессы игнорируют этот сигнал.
Хотя все рабочие процессы nginx могут принимать и правильно обрабатывать POSIX-сигналы, мастер-процесс не использует стандартную системную вызов kill() для передачи сигналов рабочим и вспомогательным процессам. Вместо этого nginx использует пары межпроцессных сокетов, которые позволяют отправлять сообщения между всеми процессами nginx. Однако в настоящее время сообщения отправляются только от мастера к его дочерним процессам. Сообщения содержат стандартные сигналы.
Потоки
Возможна передача задач, которые в противном случае блокировали бы рабочий процесс nginx, в отдельный поток. Например, nginx можно настроить на использование потоков для выполнения операций ввода-вывода файлов file I/O. Ещё один случай использования — библиотека, не имеющая асинхронного интерфейса и поэтому не может быть нормально использована с 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 реализует стратегию thread_pool. Можно настроить несколько пулов потоков для разных целей (например, выполнения операций ввода-вывода на разных наборах дисков). Каждый пул потоков создаётся при запуске и содержит ограниченное количество потоков, которые обрабатывают очередь задач. Когда задача завершена, вызывается предопределённый обработчик завершения.
Заголовок файла 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_MODULENGX_EVENT_MODULENGX_HTTP_MODULENGX_MAIL_MODULENGX_STREAM_MODULE
Модуль NGX_CORE_MODULE является самым базовым, а значит, самым общим и низкоуровневым типом модуля. Другие типы модулей реализованы поверх него и предоставляют более удобный способ работы с соответствующими областями, например, обработкой событий или HTTP-запросов.
Набор основных модулей включает модули ngx_core_module, ngx_errlog_module, ngx_regex_module, ngx_thread_pool_module и ngx_openssl_module. Также к основным модулям относятся модули HTTP, потоков, почты и событий. Контекст основного модуля определяется как:
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. Нуль-терминированный массивngx_conf_enum_t, переданный в полеpost, определяет допустимые строки и соответствующие целочисленные значения. -
ngx_conf_set_bitmask_slot— Преобразует предоставленные аргументы в значениеngx_uint_t. Значения масок для каждого аргумента складываются по битовому ИЛИ, давая результат. Нуль-терминированный массивngx_conf_bitmask_t, переданный в полеpost, определяет допустимые строки и соответствующие значения масок. -
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()вызывается, когда полный ответ отправлен клиенту и запрос может быть уничтожен. Если включена функция keepalive клиентского подключения, вызываетсяngx_http_set_keepalive(), которое уничтожает текущий запрос и ожидает следующего запроса на подключении. В противном случаеngx_http_close_request()уничтожает как запрос, так и подключение.
Запрос
Для каждого клиентского HTTP-запроса создается объект ngx_http_request_t. Некоторые поля этого объекта:
-
connection— Указатель на объект соединения с клиентомngx_connection_t. Несколько запросов могут ссылаться на один и тот же объект соединения одновременно — основной запрос и его подзапросы. После удаления запроса новый запрос может быть создан на том же соединении.Обратите внимание, что для HTTP-соединений поле
ngx_connection_tобъектаdataуказывает на запрос. Такие запросы называются активными, в отличие от других запросов, связанных с соединением. Активный запрос используется для обработки событий соединения с клиентом и имеет право выводить свой ответ клиенту. Обычно каждый запрос в какой-то момент становится активным, чтобы он мог отправить свой вывод. -
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— Результаты захвата, полученные последним совпадением по регулярному выражению запроса. Совпадение по регулярному выражению может произойти в ряде мест во время обработки запроса: поиск по карте, поиск сервера по 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— Битовая маска, показывающая, какие модули буферизировали вывод, произведенный запросом. Ряд фильтров может буферизировать вывод; например, подфильтр может буферизировать данные из-за частичного совпадения строк, копирующий фильтр может буферизировать данные из-за отсутствия свободных буферов вывода и т. д. Пока это значение не равно нулю, запрос не завершен до ожидания сброса. -
header_only— Флаг, указывающий, что вывод не требует тела. Например, этот флаг используется HTTP HEAD-запросами. -
keepalive— Флаг, указывающий, поддерживается ли keep-alive соединения с клиентом. Значение определяется по версии 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 регистрирует свой обработчик на этом этапе для ведения журнала доступа. Обработчики фазы журнала вызываются в самом конце обработки запроса, непосредственно перед освобождением запроса.
Ниже приведен пример обработчика фазы preaccess.
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-запроса, и результаты специфичны для этого 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— Используется внутри модулем ведения журнала для маркировки значений, которые требуют экранирования при выводе.
Функции 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 может содержать только часть тела. Чтобы прочитать следующую часть, вызовите функцию ngx_http_read_unbuffered_request_body(r). Возвращаемое значение NGX_AGAIN и флаг запроса reading_body указывают, что доступно больше данных. Если 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, что означает, что размер тела неизвестен. В этом случае используется кодировка chunked. Для вывода произвольного заголовка добавьте список 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 во время конфигурации. Адрес предыдущего обработчика обычно хранится в статической переменной в модуле и вызывается новым обработчиком перед выходом.
В следующем примере модуля фильтра заголовка к каждому ответу со статусом 200 добавляется заголовок HTTP "X-Foo: foo".
#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 регистрируют ряд фильтров заголовков и тел, и важно зарегистрировать новый модуль фильтра в нужном месте относительно их. Обычно модули регистрируют фильтры в своих обработчиках постконфигурации. Порядок вызова фильтров во время обработки, очевидно, обратный порядку их регистрации.
Для модулей фильтров третьих сторон 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" перед каждым входящим буфером. Новые буферы, выделенные модулем, переиспользуются, если это возможно. Обратите внимание, что для правильной работы этого примера также необходимо настроить фильтр заголовков header filter и сбросить 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 реализуют альтернативные методы балансировки нагрузки, но фактически реализованы как расширения модуля 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— Методы модуля балансировки нагрузки, описанные ниже.
Все методы принимают по крайней мере два аргумента: объект подключения к peer 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 пробела
- нет табуляции, нет trailing пробелов
- элементы списка на одной строке разделены пробелами
- шестнадцатеричные литералы — строчные
- имена файлов, функций и типов, глобальные переменные имеют префикс
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 *));
Макрос ngx_inline должен использоваться вместо 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, для выполнения HTTP-запроса. Абсолютно нет необходимости подключать большое количество внешнего (вероятно, блокирующего!) кода для задачи, которую может выполнить сам nginx.
Есть два основных сценария использования, когда требуется внешний запрос:
- в контексте обработки запроса клиента (например, в обработчике содержимого)
- в контексте процесса-работника (например, обработчик таймера)
В первом случае лучше использовать API субзапросов. Вместо прямого доступа к внешнему сервису, вы объявляете расположение в конфигурации nginx и направляете свой субзапрос на это расположение. Это расположение не ограничивается проксированием запросов, но может содержать и другие директивы nginx. Примером такого подхода является директива auth_request, реализованная в модуле ngx_http_auth_request.
Во втором случае можно использовать базовые возможности HTTP-клиента, доступные в nginx. Например, модуль OCSP реализует простой HTTP-клиент.
© 2002-2021 Igor Sysoev
© 2011-2024 Nginx, Inc.
Licensed under the BSD License.
https://nginx.org/en/docs/dev/development_guide.html