Руководство по разработке
- Введение
- Структура кода
- Файлы включения
- Целые числа
- Распространённые коды возврата
- Обработка ошибок
- Строки
- Общие сведения
- Форматирование
- Преобразование чисел
- Регулярные выражения
- Время
- Контейнеры
- Массив
- Список
- Очередь
- Красно-чёрное дерево
- Хеш-таблица
- Управление памятью
- Куча
- Пул
- Разделяемая память
- Ведение журнала
- Цикл
- Буфер
- Сеть
- Соединение
- События
- Событие
- События ввода-вывода
- События таймера
- Отложенные события
- Цикл обработки событий
- Процессы
- Потоки
- Модули
- Добавление новых модулей
- Основные модули
- Директивы конфигурации
- HTTP
- Соединение
- Запрос
- Конфигурация
- Фазы
- Переменные
- Составные значения
- Перенаправление запросов
- Подзапросы
- Завершение запроса
- Тело запроса
- Фильтры тела запроса
- Ответ
- Тело ответа
- Фильтры тела ответа
- Создание модулей фильтров
- Повторное использование буферов
- Балансировка нагрузки
- Примеры
- Стиль кода
- Общие правила
- Файлы
- Комментарии
- Препроцессор
- Типы
- Переменные
- Функции
- Выражения
- Условия и циклы
- Метки
- Отладка проблем с памятью
- Распространённые ошибки
- Написание модуля на C
- Строки C
- Глобальные переменные
- Ручное управление памятью
- Потоки
- Блокирующие библиотеки
- HTTP-запросы к внешним службам
Введение
Структура кода
-
auto— сценарии сборки -
src-
core— базовые типы и функции — строки, массивы, журнал, пул и т. д. -
event— ядро событий-
modules— модули уведомления о событиях:epoll,kqueue,selectи т. д.
-
-
http— основной модуль HTTP и общий код-
modules— прочие модули HTTP -
v2— HTTP/2
-
-
mail— почтовые модули -
os— платформозависимый код-
unix -
win32
-
-
stream— модули потоковой передачи
-
Файлы включения
Следующие два оператора #include должны находиться в начале каждого файла nginx:
#include <ngx_config.h> #include <ngx_core.h>
Кроме того, код HTTP должен включать
#include <ngx_http.h>
Код почтовых модулей должен включать
#include <ngx_mail.h>
Код потоковой передачи должен включать
#include <ngx_stream.h>
Целые числа
В коде nginx общего назначения используются два целочисленных типа: ngx_int_t и ngx_uint_t, которые являются псевдонимами типов intptr_t и uintptr_t соответственно.
Распространённые коды возврата
Большинство функций nginx возвращают следующие коды:
-
NGX_OK— операция выполнена успешно. -
NGX_ERROR— операция завершилась ошибкой. -
NGX_AGAIN— операция не завершена; вызовите функцию ещё раз. -
NGX_DECLINED— операция отклонена, например, потому что она отключена в конфигурации. Это не является ошибкой. -
NGX_BUSY— ресурс недоступен. -
NGX_DONE— операция завершена или продолжена в другом месте. Также используется как альтернативный код успешного выполнения. -
NGX_ABORT— функция прервана. Также используется как альтернативный код ошибки.
Обработка ошибок
Макрос ngx_errno возвращает код последней системной ошибки. В POSIX-системах он соответствует errno, а в Windows — вызову GetLastError(). Макрос ngx_socket_errno возвращает номер последней ошибки сокета. Как и макрос ngx_errno, в POSIX-системах он соответствует errno. В Windows он соответствует вызову WSAGetLastError(). Многократное последовательное обращение к значениям ngx_errno или ngx_socket_errno может привести к снижению производительности. Если значение ошибки может понадобиться несколько раз, сохраните его в локальной переменной типа ngx_err_t. Для установки ошибок используйте макросы ngx_set_errno(errno) и ngx_set_socket_errno(errno).
Значения ngx_errno и ngx_socket_errno можно передавать функциям ведения журнала ngx_log_error() и ngx_log_debugX(); в этом случае в сообщение журнала добавляется текст системной ошибки.
Пример использования ngx_errno:
ngx_int_t
ngx_my_kill(ngx_pid_t pid, ngx_log_t *log, int signo)
{
ngx_err_t err;
if (kill(pid, signo) == -1) {
err = ngx_errno;
ngx_log_error(NGX_LOG_ALERT, log, err, "kill(%P, %d) failed", pid, signo);
if (err == NGX_ESRCH) {
return 2;
}
return 1;
}
return 0;
}
Строки
Общие сведения
Для строк C nginx использует указатель на символы без знака u_char *.
Строковый тип nginx ngx_str_t определён следующим образом:
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
Поле len содержит длину строки, а data — данные строки. Строка, хранящаяся в ngx_str_t, может быть завершена или не завершена нулевым символом после len байтов. В большинстве случаев она не завершается нулевым символом. Однако в некоторых частях кода (например, при разборе конфигурации) известно, что объекты ngx_str_t завершаются нулевым символом. Это упрощает сравнение строк и позволяет легче передавать их системным вызовам.
Операции со строками в nginx объявлены в src/core/ngx_string.h. Некоторые из них являются обёртками над стандартными функциями C:
-
ngx_strcmp() -
ngx_strncmp() -
ngx_strstr() -
ngx_strlen() -
ngx_strchr() -
ngx_memcmp() -
ngx_memset() -
ngx_memcpy() -
ngx_memmove()
Другие функции для работы со строками специфичны для nginx
-
ngx_memzero()— заполняет память нулями. -
ngx_explicit_memzero()— выполняет то же, что иngx_memzero(), но этот вызов никогда не удаляется компилятором при оптимизации удаления мёртвых записей. Эту функцию можно использовать для очистки конфиденциальных данных, например паролей и ключей. -
ngx_cpymem()— выполняет то же, что иngx_memcpy(), но возвращает конечный адрес назначения. Эта функция удобна для последовательного добавления нескольких строк. -
ngx_movemem()— выполняет то же, что иngx_memmove(), но возвращает конечный адрес назначения. -
ngx_strlchr()— ищет символ в строке, ограниченной двумя указателями.
Следующие функции выполняют преобразование регистра и сравнение:
-
ngx_tolower() -
ngx_toupper() -
ngx_strlow() -
ngx_strcasecmp() -
ngx_strncasecmp()
Следующие макросы упрощают инициализацию строк:
-
ngx_string(text)— статический инициализатор типаngx_str_tна основе строкового литерала 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;
Преобразование чисел
В nginx реализовано несколько функций для преобразования чисел. Первые четыре преобразуют строку заданной длины в положительное целое число указанного типа. В случае ошибки они возвращают NGX_ERROR.
-
ngx_atoi(line, n)—ngx_int_t -
ngx_atosz(line, n)—ssize_t -
ngx_atoof(line, n)—off_t -
ngx_atotm(line, n)—time_t
Существуют ещё две функции преобразования чисел. Как и первые четыре, в случае ошибки они возвращают NGX_ERROR.
-
ngx_atofp(line, n, point)— преобразует число с фиксированной точкой заданной длины в положительное целое число типаngx_int_t. Результат сдвигается влево наpointдесятичных разрядов. Строковое представление числа должно содержать не болееpointsдробных знаков. Например,ngx_atofp("10.5", 4, 2)возвращает1050. -
ngx_hextoi(line, n)— преобразует шестнадцатеричное представление положительного целого числа вngx_int_t.
Регулярные выражения
Интерфейс регулярных выражений в nginx является обёрткой над библиотекой PCRE. Соответствующий заголовочный файл — src/core/ngx_regex.h.
Чтобы использовать регулярное выражение для сопоставления строк, его сначала нужно скомпилировать; обычно это выполняется на этапе конфигурации. Поскольку поддержка PCRE является необязательной, весь код, использующий этот интерфейс, должен быть защищён окружающим макросом NGX_PCRE:
#if (NGX_PCRE)
ngx_regex_t *re;
ngx_regex_compile_t rc;
u_char errstr[NGX_MAX_CONF_ERRSTR];
ngx_str_t value = ngx_string("message (\\d\\d\\d).*Codeword is '(?<cw>\\w+)'");
ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
rc.pattern = value;
rc.pool = cf->pool;
rc.err.len = NGX_MAX_CONF_ERRSTR;
rc.err.data = errstr;
/* rc.options can be set to NGX_REGEX_CASELESS */
if (ngx_regex_compile(&rc) != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
return NGX_CONF_ERROR;
}
re = rc.regex;
#endif
После успешной компиляции поля captures и named_captures структуры ngx_regex_compile_t содержат количество всех захватов и именованных захватов, найденных в регулярном выражении, соответственно.
Затем скомпилированное регулярное выражение можно использовать для поиска совпадений в строках:
ngx_int_t n;
int captures[(1 + rc.captures) * 3];
ngx_str_t input = ngx_string("This is message 123. Codeword is 'foobar'.");
n = ngx_regex_exec(re, &input, captures, (1 + rc.captures) * 3);
if (n >= 0) {
/* string matches expression */
} else if (n == NGX_REGEX_NO_MATCHED) {
/* no match was found */
} else {
/* some error */
ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_regex_exec_n " failed: %i", n);
}
Аргументы ngx_regex_exec() — это скомпилированное регулярное выражение re, строка для сопоставления input, необязательный массив целых чисел для хранения найденных captures и размер этого массива size. Размер массива captures должен быть кратен трём, как того требует API PCRE. В примере размер рассчитывается исходя из общего количества захватов плюс один для самой совпавшей строки.
Если совпадения найдены, получить доступ к захватам можно следующим образом:
u_char *p;
size_t size;
ngx_str_t name, value;
/* all captures */
for (i = 0; i < n * 2; i += 2) {
value.data = input.data + captures[i];
value.len = captures[i + 1] — captures[i];
}
/* accessing named captures */
size = rc.name_size;
p = rc.names;
for (i = 0; i < rc.named_captures; i++, p += size) {
/* capture name */
name.data = &p[2];
name.len = ngx_strlen(name.data);
n = 2 * ((p[0] << 8) + p[1]);
/* captured value */
value.data = &input.data[captures[n]];
value.len = captures[n + 1] — captures[n];
}
Функция ngx_regex_exec_array() принимает массив элементов ngx_regex_elt_t (которые представляют собой скомпилированные регулярные выражения с соответствующими именами), строку для сопоставления и журнал. Функция применяет выражения из массива к строке, пока не найдёт совпадение или не закончатся выражения. При наличии совпадения возвращается NGX_OK, в противном случае — NGX_DECLINED, а в случае ошибки — NGX_ERROR.
Время
Структура ngx_time_t представляет время с помощью трёх отдельных типов для секунд, миллисекунд и смещения относительно GMT:
typedef struct {
time_t sec;
ngx_uint_t msec;
ngx_int_t gmtoff;
} ngx_time_t;
Структура ngx_tm_t является псевдонимом struct tm на платформах UNIX и SYSTEMTIME в Windows.
Чтобы получить текущее время, обычно достаточно обратиться к одной из доступных глобальных переменных, содержащих кэшированное значение времени в нужном формате.
Доступны следующие строковые представления:
-
ngx_cached_err_log_time— используется в записях журнала ошибок:"1970/09/28 12:00:00" -
ngx_cached_http_log_time— используется в записях журнала доступа HTTP:"28/Sep/1970:12:00:00 +0600" -
ngx_cached_syslog_time— используется в записях системного журнала:"Sep 28 12:00:00" -
ngx_cached_http_time— используется в заголовках HTTP:"Mon, 28 Sep 1970 06:00:00 GMT" -
ngx_cached_http_log_iso8601— формат стандарта ISO 8601:"1970-09-28T12:00:00+06:00"
Макросы ngx_time() и ngx_timeofday() возвращают текущее время в секундах и являются предпочтительным способом доступа к кэшированному значению времени.
Чтобы получить время явно, используйте ngx_gettimeofday(), обновляющую свой аргумент (указатель на struct timeval). Время обновляется каждый раз, когда nginx возвращается в цикл обработки событий после системных вызовов. Для немедленного обновления времени вызовите ngx_time_update() или ngx_time_sigsafe_update(), если время обновляется в контексте обработчика сигнала.
Следующие функции преобразуют time_t в указанное разложенное представление времени. Первая функция в каждой паре преобразует time_t в ngx_tm_t, а вторая (с вставкой _libc_) — в struct tm:
-
ngx_gmtime(), ngx_libc_gmtime()— время в формате UTC -
ngx_localtime(), ngx_libc_localtime()— время относительно местного часового пояса
Функция ngx_http_time(buf, time) возвращает строковое представление, подходящее для использования в заголовках HTTP (например, "Mon, 28 Sep 1970 06:00:00 GMT"). Функция ngx_http_cookie_time(buf, time) возвращает строковое представление, подходящее для файлов cookie HTTP ("Thu, 31-Dec-37 23:55:55 GMT").
Контейнеры
Массив
Тип массива nginx ngx_array_t определён следующим образом
typedef struct {
void *elts;
ngx_uint_t nelts;
size_t size;
ngx_uint_t nalloc;
ngx_pool_t *pool;
} ngx_array_t;
Элементы массива доступны в поле elts. Поле nelts содержит количество элементов. Поле size содержит размер одного элемента и задаётся при инициализации массива.
Используйте вызов ngx_array_create(pool, n, size) для создания массива в пуле, а вызов ngx_array_init(array, pool, n, size) — для инициализации уже выделенного объекта массива.
ngx_array_t *a, b; /* create an array of strings with preallocated memory for 10 elements */ a = ngx_array_create(pool, 10, sizeof(ngx_str_t)); /* initialize string array for 10 elements */ ngx_array_init(&b, pool, 10, sizeof(ngx_str_t));
Для добавления элементов в массив используйте следующие функции:
-
ngx_array_push(a)добавляет один элемент в конец и возвращает указатель на него -
ngx_array_push_n(a, n)добавляетnэлементов в конец и возвращает указатель на первый из них
Если объёма уже выделенной памяти недостаточно для новых элементов, выделяется новый блок памяти, в который копируются существующие элементы. Новый блок памяти обычно вдвое больше существующего.
s = ngx_array_push(a); ss = ngx_array_push_n(&b, 3);
Список
В nginx список представляет собой последовательность массивов, оптимизированную для добавления потенциально большого количества элементов. Тип списка ngx_list_t определён следующим образом:
typedef struct {
ngx_list_part_t *last;
ngx_list_part_t part;
size_t size;
ngx_uint_t nalloc;
ngx_pool_t *pool;
} ngx_list_t;
Фактические элементы хранятся в частях списка, определённых следующим образом:
typedef struct ngx_list_part_s ngx_list_part_t;
struct ngx_list_part_s {
void *elts;
ngx_uint_t nelts;
ngx_list_part_t *next;
};
Перед использованием список необходимо инициализировать вызовом ngx_list_init(list, pool, n, size) или создать вызовом ngx_list_create(pool, n, size). Обе функции принимают размер одного элемента и количество элементов в каждой части списка. Для добавления элемента в список используйте функцию ngx_list_push(list). Для перебора элементов обращайтесь непосредственно к полям списка, как показано в примере:
ngx_str_t *v;
ngx_uint_t i;
ngx_list_t *list;
ngx_list_part_t *part;
list = ngx_list_create(pool, 100, sizeof(ngx_str_t));
if (list == NULL) { /* error */ }
/* add items to the list */
v = ngx_list_push(list);
if (v == NULL) { /* error */ }
ngx_str_set(v, "foo");
v = ngx_list_push(list);
if (v == NULL) { /* error */ }
ngx_str_set(v, "bar");
/* iterate over the list */
part = &list->part;
v = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
v = part->elts;
i = 0;
}
ngx_do_smth(&v[i]);
}
Списки используются преимущественно для заголовков HTTP-запросов и ответов.
Списки не поддерживают удаление элементов. Однако при необходимости элементы можно внутренне пометить как отсутствующие, не удаляя их из списка. Например, чтобы пометить выходные заголовки HTTP (которые хранятся как объекты ngx_table_elt_t) как отсутствующие, установите поле hash в ngx_table_elt_t равным нулю. Такие элементы явно пропускаются при переборе заголовков.
Очередь
В nginx очередь представляет собой двусвязный список с встраиваемыми узлами; каждый узел определяется следующим образом:
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t *prev;
ngx_queue_t *next;
};
Головной узел очереди не связан ни с какими данными. Перед использованием вызовите ngx_queue_init(q) для инициализации головного узла списка. Очереди поддерживают следующие операции:
-
ngx_queue_insert_head(h, x),ngx_queue_insert_tail(h, x)— вставить новый узел -
ngx_queue_remove(x)— удалить узел очереди -
ngx_queue_split(h, q, n)— разделить очередь в указанном узле, поместив хвост очереди в отдельную очередь -
ngx_queue_add(h, n)— добавить вторую очередь в первую -
ngx_queue_head(h),ngx_queue_last(h)— получить первый или последний узел очереди -
ngx_queue_sentinel(h)— получить объект-сторож очереди, на котором следует завершить перебор -
ngx_queue_data(q, type, link)— получить ссылку на начало структуры данных узла очереди с учётом смещения поля очереди в ней
Пример:
typedef struct {
ngx_str_t value;
ngx_queue_t queue;
} ngx_foo_t;
ngx_foo_t *f;
ngx_queue_t values, *q;
ngx_queue_init(&values);
f = ngx_palloc(pool, sizeof(ngx_foo_t));
if (f == NULL) { /* error */ }
ngx_str_set(&f->value, "foo");
ngx_queue_insert_tail(&values, &f->queue);
/* insert more nodes here */
for (q = ngx_queue_head(&values);
q != ngx_queue_sentinel(&values);
q = ngx_queue_next(q))
{
f = ngx_queue_data(q, ngx_foo_t, queue);
ngx_do_smth(&f->value);
}
Красно-чёрное дерево
Заголовочный файл src/core/ngx_rbtree.h предоставляет доступ к эффективной реализации красно-чёрных деревьев.
typedef struct {
ngx_rbtree_t rbtree;
ngx_rbtree_node_t sentinel;
/* custom per-tree data here */
} my_tree_t;
typedef struct {
ngx_rbtree_node_t rbnode;
/* custom per-node data */
foo_t val;
} my_node_t;
Для работы с деревом в целом нужны два узла: корень и сторожевой узел. Обычно их добавляют в пользовательскую структуру, что позволяет организовать данные в дерево, листья которого содержат ссылку на ваши данные или сами включают их.
Для инициализации дерева:
my_tree_t root; ngx_rbtree_init(&root.rbtree, &root.sentinel, insert_value_function);
Для обхода дерева и вставки новых значений используйте функции «insert_value». Например, функция ngx_str_rbtree_insert_value работает с типом ngx_str_t. Её аргументы — указатели на корневой узел дерева, новый узел для добавления и сторожевой узел дерева.
void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node,
ngx_rbtree_node_t *sentinel)
Обход дерева достаточно прост; его можно представить следующим шаблоном функции поиска:
my_node_t *
my_rbtree_lookup(ngx_rbtree_t *rbtree, foo_t *val, uint32_t hash)
{
ngx_int_t rc;
my_node_t *n;
ngx_rbtree_node_t *node, *sentinel;
node = rbtree->root;
sentinel = rbtree->sentinel;
while (node != sentinel) {
n = (my_node_t *) node;
if (hash != node->key) {
node = (hash < node->key) ? node->left : node->right;
continue;
}
rc = compare(val, node->val);
if (rc < 0) {
node = node->left;
continue;
}
if (rc > 0) {
node = node->right;
continue;
}
return n;
}
return NULL;
}
Функция compare() — это классическая функция сравнения, возвращающая значение меньше нуля, равное нулю или больше нуля. Для ускорения поиска и во избежание сравнения больших пользовательских объектов используется целочисленное поле хеша.
Чтобы добавить узел в дерево, выделите новый узел, инициализируйте его и вызовите ngx_rbtree_insert():
my_node_t *my_node;
ngx_rbtree_node_t *node;
my_node = ngx_palloc(...);
init_custom_data(&my_node->val);
node = &my_node->rbnode;
node->key = create_key(my_node->val);
ngx_rbtree_insert(&root->rbtree, node);
Для удаления узла вызовите функцию ngx_rbtree_delete():
ngx_rbtree_delete(&root->rbtree, node);
Хеш-таблица
Функции для работы с хеш-таблицами объявлены в src/core/ngx_hash.h. Поддерживается точное сопоставление и сопоставление с подстановочными знаками. Последнее требует дополнительной настройки и описано в отдельном разделе ниже.
Перед инициализацией хеш-таблицы необходимо знать количество элементов, которые она будет содержать, чтобы nginx мог оптимально её построить. Нужно настроить два параметра: max_size и bucket_size; подробнее они описаны в отдельном документе. Обычно их значения задаёт пользователь. Параметры инициализации хеш-таблицы хранятся в типе ngx_hash_init_t, а сама хеш-таблица имеет тип ngx_hash_t:
ngx_hash_t foo_hash; ngx_hash_init_t hash; hash.hash = &foo_hash; hash.key = ngx_hash_key; hash.max_size = 512; hash.bucket_size = ngx_align(64, ngx_cacheline_size); hash.name = "foo_hash"; hash.pool = cf->pool; hash.temp_pool = cf->temp_pool;
key — это указатель на функцию, создающую целочисленный ключ хеша из строки. Существуют две универсальные функции создания ключей: ngx_hash_key(data, len) и ngx_hash_key_lc(data, len). Последняя преобразует строку в нижний регистр, поэтому передаваемая строка должна быть доступна для записи. Если это не так, передайте функции флаг NGX_HASH_READONLY_KEY при инициализации массива ключей (см. ниже).
Ключи хеш-таблицы хранятся в ngx_hash_keys_arrays_t и инициализируются с помощью ngx_hash_keys_array_init(arr, type). Второй параметр (type) определяет объём ресурсов, предварительно выделяемых для хеш-таблицы; он может принимать значение NGX_HASH_SMALL или NGX_HASH_LARGE. Последний вариант подходит, если ожидается, что хеш-таблица будет содержать тысячи элементов.
ngx_hash_keys_arrays_t foo_keys; foo_keys.pool = cf->pool; foo_keys.temp_pool = cf->temp_pool; ngx_hash_keys_array_init(&foo_keys, NGX_HASH_SMALL);
Для добавления ключей в массив ключей хеш-таблицы используйте функцию ngx_hash_add_key(keys_array, key, value, flags):
ngx_str_t k1 = ngx_string("key1");
ngx_str_t k2 = ngx_string("key2");
ngx_hash_add_key(&foo_keys, &k1, &my_data_ptr_1, NGX_HASH_READONLY_KEY);
ngx_hash_add_key(&foo_keys, &k2, &my_data_ptr_2, NGX_HASH_READONLY_KEY);
Для построения хеш-таблицы вызовите функцию ngx_hash_init(hinit, key_names, nelts):
ngx_hash_init(&hash, foo_keys.keys.elts, foo_keys.keys.nelts);
Функция завершается ошибкой, если значения параметров max_size или bucket_size слишком малы.
После построения хеш-таблицы используйте функцию ngx_hash_find(hash, key, name, len) для поиска элементов:
my_data_t *data;
ngx_uint_t key;
key = ngx_hash_key(k1.data, k1.len);
data = ngx_hash_find(&foo_hash, key, k1.data, k1.len);
if (data == NULL) {
/* key not found */
}
Сопоставление с подстановочными знаками
Для создания хеш-таблицы с поддержкой подстановочных знаков используйте тип ngx_hash_combined_t. Он включает описанный выше тип хеш-таблицы и два дополнительных массива ключей: dns_wc_head и dns_wc_tail. Инициализация основных свойств аналогична инициализации обычной хеш-таблицы:
ngx_hash_init_t hash ngx_hash_combined_t foo_hash; hash.hash = &foo_hash.hash; hash.key = ...;
Добавлять ключи с подстановочными знаками можно с помощью флага NGX_HASH_WILDCARD_KEY:
/* k1 = ".example.org"; */ /* k2 = "foo.*"; */ ngx_hash_add_key(&foo_keys, &k1, &data1, NGX_HASH_WILDCARD_KEY); ngx_hash_add_key(&foo_keys, &k2, &data2, NGX_HASH_WILDCARD_KEY);
Функция распознаёт подстановочные знаки и добавляет ключи в соответствующие массивы. Описание синтаксиса подстановочных знаков и алгоритма сопоставления см. в документации модуля map.
В зависимости от содержимого добавленных ключей может потребоваться инициализировать до трёх массивов ключей: один для точного сопоставления (описан выше) и ещё два для поиска от начала или конца строки:
if (foo_keys.dns_wc_head.nelts) {
ngx_qsort(foo_keys.dns_wc_head.elts,
(size_t) foo_keys.dns_wc_head.nelts,
sizeof(ngx_hash_key_t),
cmp_dns_wildcards);
hash.hash = NULL;
hash.temp_pool = pool;
if (ngx_hash_wildcard_init(&hash, foo_keys.dns_wc_head.elts,
foo_keys.dns_wc_head.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}
foo_hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
}
Массив ключей необходимо отсортировать, а результаты инициализации добавить в объединённую хеш-таблицу. Массив dns_wc_tail инициализируется аналогичным образом.
Поиск в объединённой хеш-таблице выполняет функция ngx_hash_find_combined(chash, key, name, len):
/* key = "bar.example.org"; — will match ".example.org" */ /* key = "foo.example.com"; — will match "foo.*" */ hkey = ngx_hash_key(key.data, key.len); res = ngx_hash_find_combined(&foo_hash, hkey, key.data, key.len);
Управление памятью
Куча
Для выделения памяти из системной кучи используйте следующие функции:
-
ngx_alloc(size, log)— выделяет память из системной кучи. Это обёртка надmalloc()с поддержкой ведения журнала. Ошибки выделения и отладочная информация записываются вlog. -
ngx_calloc(size, log)— выделяет память из системной кучи, какngx_alloc(), но после выделения заполняет её нулями. -
ngx_memalign(alignment, size, log)— выделяет выровненную память из системной кучи. На платформах, предоставляющих эту функцию, является обёрткой надposix_memalign(). В противном случае реализация используетngx_alloc(), обеспечивающую максимальное выравнивание. -
ngx_free(p)— освобождает выделенную память. Это обёртка надfree()
Пул
Большинство выделений памяти в nginx выполняется в пулах. Память, выделенная в пуле nginx, автоматически освобождается при уничтожении пула. Это обеспечивает высокую производительность выделения памяти и упрощает управление памятью.
Внутри пул выделяет объекты в непрерывных блоках памяти. Когда блок заполняется, выделяется новый блок и добавляется в список блоков памяти пула. Если запрошенный объём памяти слишком велик и не помещается в блок, запрос передаётся системному аллокатору, а возвращённый указатель сохраняется в пуле для последующего освобождения.
Для пулов nginx используется тип ngx_pool_t. Поддерживаются следующие операции:
-
ngx_create_pool(size, log)— создаёт пул с указанным размером блока. Возвращаемый объект пула также выделяется в пуле. Значениеsizeдолжно быть не меньшеNGX_MIN_POOL_SIZEи кратноNGX_POOL_ALIGNMENT. -
ngx_destroy_pool(pool)— освобождает всю память пула, включая сам объект пула. -
ngx_palloc(pool, size)— выделяет выровненную память из указанного пула. -
ngx_pcalloc(pool, size)— выделяет выровненную память из указанного пула и заполняет её нулями. -
ngx_pnalloc(pool, size)— выделяет невыровненную память из указанного пула. В основном используется для выделения строк. -
ngx_pfree(pool, p)— освобождает память, ранее выделенную в указанном пуле. Освобождать можно только память, выделенную запросами, переданными системному аллокатору.
u_char *p;
ngx_str_t *s;
ngx_pool_t *pool;
pool = ngx_create_pool(1024, log);
if (pool == NULL) { /* error */ }
s = ngx_palloc(pool, sizeof(ngx_str_t));
if (s == NULL) { /* error */ }
ngx_str_set(s, "foo");
p = ngx_pnalloc(pool, 3);
if (p == NULL) { /* error */ }
ngx_memcpy(p, "foo", 3);
Связанные списки (ngx_chain_t) активно используются в nginx, поэтому реализация пула nginx позволяет повторно использовать их элементы. Поле chain структуры ngx_pool_t содержит список ранее выделенных элементов, готовых к повторному использованию. Для эффективного выделения элемента связанного списка в пуле используйте функцию ngx_alloc_chain_link(pool). Она ищет свободный элемент в списке пула и выделяет новый, если список пула пуст. Для освобождения элемента вызовите функцию ngx_free_chain(pool, cl).
В пуле можно зарегистрировать обработчики очистки. Обработчик очистки — это функция обратного вызова с аргументом, которая вызывается при уничтожении пула. Обычно пул привязан к определённому объекту nginx (например, HTTP-запросу) и уничтожается по окончании срока жизни этого объекта. Регистрация обработчика очистки пула — удобный способ освобождения ресурсов, закрытия файловых дескрипторов или выполнения завершающих действий с общими данными, связанными с основным объектом.
Для регистрации обработчика очистки пула вызовите ngx_pool_cleanup_add(pool, size); функция возвращает указатель ngx_pool_cleanup_t, который должен заполнить вызывающий код. Используйте аргумент size для выделения контекста обработчика очистки.
ngx_pool_cleanup_t *cln;
cln = ngx_pool_cleanup_add(pool, 0);
if (cln == NULL) { /* error */ }
cln->handler = ngx_my_cleanup;
cln->data = "foo";
...
static void
ngx_my_cleanup(void *data)
{
u_char *msg = data;
ngx_do_smth(msg);
}
Разделяемая память
nginx использует разделяемую память для совместного доступа процессов к общим данным. Функция ngx_shared_memory_add(cf, name, size, tag) добавляет в цикл новую запись разделяемой памяти ngx_shm_zone_t. Функция получает name и size зоны. Имя каждой разделяемой зоны должно быть уникальным. Если запись разделяемой зоны с указанными name и tag уже существует, используется существующая запись. Функция завершается ошибкой, если существующая запись с тем же именем имеет другую метку. Обычно в качестве tag передаётся адрес структуры модуля, что позволяет повторно использовать разделяемые зоны по имени в пределах одного модуля nginx.
Структура записи разделяемой памяти ngx_shm_zone_t содержит следующие поля:
-
init— функция обратного вызова инициализации, вызываемая после отображения разделяемой зоны в реальную память -
data— контекст данных, используемый для передачи произвольных данных функции обратного вызоваinit -
noreuse— флаг, запрещающий повторное использование разделяемой зоны из предыдущего цикла -
tag— метка разделяемой зоны -
shm— платформозависимый объект типаngx_shm_t, содержащий как минимум следующие поля:-
addr— адрес отображённой разделяемой памяти, изначально равен NULL -
size— размер разделяемой памяти -
name— имя разделяемой памяти -
log— журнал разделяемой памяти -
exists— флаг, указывающий, что разделяемая память унаследована от главного процесса (только для Windows)
-
Записи разделяемых зон отображаются в реальную память в ngx_init_cycle() после разбора конфигурации. В POSIX-системах для создания анонимного отображения разделяемой памяти используется системный вызов mmap(). В Windows используется пара вызовов CreateFileMapping()/ MapViewOfFileEx().
Для выделения памяти в общей памяти nginx предоставляет тип пула slab ngx_slab_pool_t. Пул slab для выделения памяти автоматически создаётся в каждой общей зоне nginx. Пул расположен в начале общей зоны, и к нему можно обратиться с помощью выражения (ngx_slab_pool_t *) shm_zone->shm.addr. Чтобы выделить память в общей зоне, вызовите ngx_slab_alloc(pool, size) или ngx_slab_calloc(pool, size). Чтобы освободить память, вызовите ngx_slab_free(pool, p).
Пул slab делит всю общую зону на страницы. Каждая страница используется для выделения объектов одного и того же размера. Указанный размер должен быть степенью двойки и превышать минимальный размер в 8 байт. Значения, не соответствующие этому требованию, округляются вверх. Битовая маска для каждой страницы отслеживает, какие блоки используются, а какие свободны для выделения. Для размеров, превышающих половину страницы (обычно это 2048 байт), выделяется целая страница
Чтобы защитить данные в общей памяти от одновременного доступа, используйте мьютекс, доступный в поле mutex структуры ngx_slab_pool_t. Чаще всего пул slab использует мьютекс при выделении и освобождении памяти, но его также можно использовать для защиты любых других пользовательских структур данных, выделенных в общей зоне. Чтобы заблокировать или разблокировать мьютекс, вызовите соответственно ngx_shmtx_lock(&shpool->mutex) или ngx_shmtx_unlock(&shpool->mutex).
ngx_str_t name;
ngx_foo_ctx_t *ctx;
ngx_shm_zone_t *shm_zone;
ngx_str_set(&name, "foo");
/* allocate shared zone context */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_foo_ctx_t));
if (ctx == NULL) {
/* error */
}
/* add an entry for 64k shared zone */
shm_zone = ngx_shared_memory_add(cf, &name, 65536, &ngx_foo_module);
if (shm_zone == NULL) {
/* error */
}
/* register init callback and context */
shm_zone->init = ngx_foo_init_zone;
shm_zone->data = ctx;
...
static ngx_int_t
ngx_foo_init_zone(ngx_shm_zone_t *shm_zone, void *data)
{
ngx_foo_ctx_t *octx = data;
size_t len;
ngx_foo_ctx_t *ctx;
ngx_slab_pool_t *shpool;
value = shm_zone->data;
if (octx) {
/* reusing a shared zone from old cycle */
ctx->value = octx->value;
return NGX_OK;
}
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
if (shm_zone->shm.exists) {
/* initialize shared zone context in Windows nginx worker */
ctx->value = shpool->data;
return NGX_OK;
}
/* initialize shared zone */
ctx->value = ngx_slab_alloc(shpool, sizeof(ngx_uint_t));
if (ctx->value == NULL) {
return NGX_ERROR;
}
shpool->data = ctx->value;
return NGX_OK;
}
Ведение журнала
Для ведения журнала nginx использует объекты ngx_log_t. Журналирование в nginx поддерживает несколько типов вывода:
- stderr — запись в стандартный поток ошибок (stderr)
- file — запись в файл
- syslog — запись в syslog
- memory — запись во внутреннее хранилище в памяти для целей разработки; позднее к памяти можно обратиться с помощью отладчика
Экземпляр журнала может представлять собой цепочку журналов, связанных друг с другом полем next. В этом случае каждое сообщение записывается во все журналы цепочки.
Для каждого журнала уровень важности определяет, какие сообщения записываются (регистрируются только события, которым присвоен этот уровень или более высокий). Поддерживаются следующие уровни важности:
-
NGX_LOG_EMERG -
NGX_LOG_ALERT -
NGX_LOG_CRIT -
NGX_LOG_ERR -
NGX_LOG_WARN -
NGX_LOG_NOTICE -
NGX_LOG_INFO -
NGX_LOG_DEBUG
Для отладочного журналирования также проверяется маска отладки. Маски отладки:
-
NGX_LOG_DEBUG_CORE -
NGX_LOG_DEBUG_ALLOC -
NGX_LOG_DEBUG_MUTEX -
NGX_LOG_DEBUG_EVENT -
NGX_LOG_DEBUG_HTTP -
NGX_LOG_DEBUG_MAIL -
NGX_LOG_DEBUG_STREAM
Обычно журналы создаются существующим кодом nginx на основе директив error_log и доступны почти на каждом этапе обработки в объектах цикла, конфигурации, клиентского соединения и других.
В nginx предусмотрены следующие макросы журналирования:
-
ngx_log_error(level, log, err, fmt, ...)— запись ошибок -
ngx_log_debug0(level, log, err, fmt),ngx_log_debug1(level, log, err, fmt, arg1)и т. д. — отладочное журналирование с поддержкой до восьми аргументов форматирования
Сообщение журнала форматируется в буфере размером NGX_MAX_ERROR_STR (в настоящее время 2048 байт) на стеке. Перед сообщением добавляются уровень важности, идентификатор процесса (PID), идентификатор соединения (хранится в log->connection) и текст системной ошибки. Для сообщений, не относящихся к отладке, также вызывается log->handler, чтобы добавить к сообщению журнала более подробную информацию. HTTP-модуль задаёт функцию ngx_http_log_error() в качестве обработчика журнала, чтобы регистрировать адреса клиента и сервера, текущее действие (хранится в log->action), строку запроса клиента, имя сервера и т. д.
/* specify what is currently done */
log->action = "sending mp4 to client";
/* error and debug log */
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely
closed connection");
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
"mp4 start:%ui, length:%ui", mp4->start, mp4->length);
Приведённый выше пример приводит к таким записям в журнале:
2016/09/16 22:08:52 [info] 17445#0: *1 client prematurely closed connection while sending mp4 to client, client: 127.0.0.1, server: , request: "GET /file.mp4 HTTP/1.1" 2016/09/16 23:28:33 [debug] 22140#0: *1 mp4 start:0, length:10000
Цикл
Объект цикла хранит контекст времени выполнения nginx, созданный на основе определённой конфигурации. Его тип — ngx_cycle_t. На текущий цикл ссылается глобальная переменная ngx_cycle, которая наследуется рабочими процессами nginx при их запуске. При каждой перезагрузке конфигурации nginx на её основе создаётся новый цикл; старый цикл обычно удаляется после успешного создания нового.
Цикл создаётся функцией ngx_init_cycle(), которой в качестве аргумента передаётся предыдущий цикл. Функция находит файл конфигурации предыдущего цикла и наследует от него как можно больше ресурсов. При запуске nginx создаётся временный цикл с именем «init cycle», который затем заменяется фактическим циклом, построенным на основе конфигурации.
К членам цикла относятся:
-
pool— Пул цикла. Создаётся для каждого нового цикла. -
log— Журнал цикла. Изначально наследуется от старого цикла, а после чтения конфигурации указывает наnew_log. -
new_log— Журнал цикла, созданный конфигурацией. На него влияет директиваerror_logв корневом контексте. -
connections,connection_n— Массив соединений типаngx_connection_t, создаваемых модулем событий при инициализации каждого рабочего процесса nginx. Директиваworker_connectionsв конфигурации nginx задаёт количество соединенийconnection_n. -
free_connections,free_connection_n— Список и количество доступных в данный момент соединений. Если доступных соединений нет, рабочий процесс nginx отказывается принимать новых клиентов или подключаться к вышестоящим серверам. -
files,files_n— Массив для сопоставления файловых дескрипторов с соединениями nginx. Это сопоставление используют модули событий с флагомNGX_USE_FD_EVENT(в настоящее время этоpollиdevpoll). -
conf_ctx— Массив конфигураций основных модулей. Конфигурации создаются и заполняются при чтении файлов конфигурации nginx. -
modules,modules_n— Массив модулей типаngx_module_t, как статических, так и динамических, загруженных текущей конфигурацией. -
listening— Массив объектов прослушивания типаngx_listening_t. Объекты прослушивания обычно добавляются директивойlistenразличных модулей, вызывающих функциюngx_create_listening(). На основе объектов прослушивания создаются сокеты прослушивания. -
paths— Массив путей типаngx_path_t. Пути добавляются вызовом функцииngx_add_path()из модулей, которым предстоит работать с определёнными каталогами. Если этих каталогов нет, nginx создаёт их после чтения конфигурации. Кроме того, для каждого пути можно добавить два обработчика:- загрузчик пути — Выполняется только один раз в течение 60 секунд после запуска или перезагрузки nginx. Обычно загрузчик читает каталог и сохраняет данные в общей памяти nginx. Обработчик вызывается выделенным процессом nginx «nginx cache loader».
- менеджер пути — Выполняется периодически. Обычно менеджер удаляет старые файлы из каталога и обновляет память nginx с учётом изменений. Обработчик вызывается выделенным процессом «nginx cache manager».
-
open_files— Список открытых файловых объектов типаngx_open_file_t, создаваемых вызовом функцииngx_conf_open_file(). В настоящее время nginx использует такие открытые файлы для ведения журнала. После чтения конфигурации nginx открывает все файлы из спискаopen_filesи сохраняет дескриптор каждого файла в полеfdобъекта. Файлы открываются в режиме добавления данных и создаются, если отсутствуют. Файлы из списка повторно открываются рабочими процессами nginx после получения сигнала повторного открытия (чаще всегоUSR1). В этом случае дескриптор в полеfdзаменяется новым значением. -
shared_memory— Список зон общей памяти, каждая из которых добавляется вызовом функцииngx_shared_memory_add(). Общие зоны отображаются в один и тот же диапазон адресов во всех процессах nginx и используются для совместного доступа к общим данным, например к дереву кэша HTTP в памяти.
Буфер
Для операций ввода-вывода nginx предоставляет тип буфера ngx_buf_t. Обычно он используется для хранения данных, которые нужно записать в место назначения или прочитать из источника. Буфер может ссылаться на данные в памяти или в файле; технически возможно, чтобы буфер одновременно ссылался и на то и на другое. Память для буфера выделяется отдельно и не связана со структурой буфера ngx_buf_t.
Структура ngx_buf_t содержит следующие поля:
-
start,end— Границы блока памяти, выделенного для буфера. -
pos,last— Границы буфера памяти; обычно это поддиапазонstart..end. -
file_pos,file_last— Границы файлового буфера, выраженные как смещения от начала файла. -
tag— Уникальное значение, используемое для различения буферов; создаётся различными модулями nginx, обычно для повторного использования буферов. -
file— Файловый объект. -
temporary— Флаг, указывающий, что буфер ссылается на доступную для записи память. -
memory— Флаг, указывающий, что буфер ссылается на память только для чтения. -
in_file— Флаг, указывающий, что буфер ссылается на данные в файле. -
flush— Флаг, указывающий, что все данные перед буфером необходимо сбросить. -
recycled— Флаг, указывающий, что буфер можно повторно использовать и его необходимо обработать как можно скорее. -
sync— Флаг, указывающий, что буфер не содержит данных или специального сигнала, напримерflushилиlast_buf. По умолчанию nginx считает такие буферы ошибочным состоянием, но этот флаг указывает nginx пропустить проверку на ошибку. -
last_buf— Флаг, указывающий, что буфер является последним в выходных данных. -
last_in_chain— Флаг, указывающий, что в запросе или подзапросе больше нет буферов с данными. -
shadow— Ссылка на другой («теневой») буфер, связанный с текущим буфером; обычно это означает, что буфер использует данные теневого буфера. После обработки буфера теневой буфер обычно также помечается как обработанный. -
last_shadow— Флаг, указывающий, что буфер является последним, ссылающимся на определённый теневой буфер. -
temp_file— Флаг, указывающий, что буфер находится во временном файле.
Для операций ввода и вывода буферы связываются в цепочки. Цепочка представляет собой последовательность звеньев типа ngx_chain_t, определённого следующим образом:
typedef struct ngx_chain_s ngx_chain_t;
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};
Каждое звено цепочки содержит ссылку на свой буфер и ссылку на следующее звено.
Пример использования буферов и цепочек:
ngx_chain_t *
ngx_get_my_chain(ngx_pool_t *pool)
{
ngx_buf_t *b;
ngx_chain_t *out, *cl, **ll;
/* first buf */
cl = ngx_alloc_chain_link(pool);
if (cl == NULL) { /* error */ }
b = ngx_calloc_buf(pool);
if (b == NULL) { /* error */ }
b->start = (u_char *) "foo";
b->pos = b->start;
b->end = b->start + 3;
b->last = b->end;
b->memory = 1; /* read-only memory */
cl->buf = b;
out = cl;
ll = &cl->next;
/* second buf */
cl = ngx_alloc_chain_link(pool);
if (cl == NULL) { /* error */ }
b = ngx_create_temp_buf(pool, 3);
if (b == NULL) { /* error */ }
b->last = ngx_cpymem(b->last, "foo", 3);
cl->buf = b;
cl->next = NULL;
*ll = cl;
return out;
}
Сетевое взаимодействие
Соединение
Тип соединения ngx_connection_t представляет собой оболочку для дескриптора сокета. Он включает следующие поля:
-
fd— Дескриптор сокета -
data— Произвольный контекст соединения. Обычно это указатель на объект более высокого уровня, построенный поверх соединения, например запрос HTTP или сеанс Stream. -
read,write— События чтения и записи для соединения. -
recv,send,recv_chain,send_chain— Операции ввода-вывода для соединения. -
pool— Пул соединений. -
log— Журнал соединения. -
sockaddr,socklen,addr_text— Адрес удалённого сокета в двоичном и текстовом форматах. -
local_sockaddr,local_socklen— Локальный адрес сокета в двоичном формате. Изначально эти поля пусты. Для получения локального адреса сокета используйте функциюngx_connection_local_sockaddr(). -
proxy_protocol_addr,proxy_protocol_port— Адрес и порт клиента по протоколу PROXY, если для соединения включён протокол PROXY. -
ssl— Контекст SSL для соединения. -
reusable— Флаг, указывающий, что соединение находится в состоянии, допускающем повторное использование. -
close— Флаг, указывающий, что соединение повторно используется и его необходимо закрыть.
Соединение nginx может прозрачно инкапсулировать уровень SSL. В этом случае поле соединения ssl содержит указатель на структуру ngx_ssl_connection_t, в которой хранятся все связанные с SSL данные соединения, включая SSL_CTX и SSL. Обработчики recv, send, recv_chain и send_chain также заменяются функциями с поддержкой SSL.
Директива worker_connections в конфигурации nginx ограничивает количество соединений на один рабочий процесс nginx. Все структуры соединений предварительно создаются при запуске рабочего процесса и сохраняются в поле connections объекта цикла. Чтобы получить структуру соединения, используйте функцию ngx_get_connection(s, log). В качестве аргумента s ей передаётся дескриптор сокета, который необходимо обернуть в структуру соединения.
Поскольку количество соединений на один рабочий процесс ограничено, nginx предоставляет возможность получать соединения, которые уже используются. Чтобы включить или отключить возможность повторного использования соединения, вызовите функцию ngx_reusable_connection(c, reusable). Вызов ngx_reusable_connection(c, 1) устанавливает флаг reuse в структуре соединения и добавляет соединение в reusable_connections_queue цикла. Каждый раз, когда ngx_get_connection() обнаруживает, что в списке free_connections цикла нет доступных соединений, она вызывает ngx_drain_connections() для освобождения определённого количества повторно используемых соединений. Для каждого такого соединения устанавливается флаг close и вызывается его обработчик чтения, который должен освободить соединение вызовом ngx_close_connection(c) и сделать его доступным для повторного использования. Чтобы выйти из состояния, в котором соединение может быть повторно использовано, вызывается ngx_reusable_connection(c, 0). Пример повторно используемых соединений в nginx — клиентские HTTP-соединения; они помечаются как доступные для повторного использования до получения первого байта запроса от клиента.
События
Событие
Объект события ngx_event_t в nginx предоставляет механизм уведомления о возникновении определённого события.
Поля ngx_event_t включают:
-
data— Произвольный контекст события, используемый в обработчиках событий; обычно это указатель на связанное с событием соединение. -
handler— Функция обратного вызова, вызываемая при возникновении события. -
write— Флаг события записи. Отсутствие этого флага означает, что это событие чтения. -
active— Флаг, указывающий, что событие зарегистрировано для получения уведомлений о вводе-выводе, обычно от механизмов уведомлений, таких какepoll,kqueue,poll. -
ready— Флаг, указывающий, что событие получило уведомление о вводе-выводе. -
delayed— Флаг, указывающий, что ввод-вывод задерживается из-за ограничения скорости. -
timer— Узел красно-чёрного дерева для добавления события в дерево таймеров. -
timer_set— Флаг, указывающий, что таймер события установлен и ещё не истёк. -
timedout— Флаг, указывающий, что время таймера события истекло. -
eof— Флаг, указывающий, что при чтении данных достигнут конец файла (EOF). -
pending_eof— Флаг, указывающий, что для сокета ожидается EOF, хотя перед ним могут быть доступны данные. Флаг передаётся через событиеEPOLLRDHUPepollили флагEV_EOFkqueue. -
error— Флаг, указывающий на возникновение ошибки при чтении (для события чтения) или записи (для события записи). -
cancelable— Флаг события таймера, указывающий, что событие следует игнорировать при завершении работы рабочего процесса. Корректное завершение работы рабочего процесса откладывается, пока остаются запланированные события таймера, которые нельзя отменить. -
posted— Флаг, указывающий, что событие помещено в очередь. -
queue— Узел очереди для помещения события в очередь.
События ввода-вывода
К каждому соединению, полученному вызовом функции ngx_get_connection(), прикреплены два события — c->read и c->write. Они используются для получения уведомлений о готовности сокета к чтению или записи. Все такие события работают в режиме срабатывания по фронту, то есть уведомления генерируются только при изменении состояния сокета. Например, частичное чтение сокета не приводит к повторному уведомлению nginx о чтении, пока в сокет не поступят дополнительные данные. Даже если базовый механизм уведомлений о вводе-выводе использует срабатывание по уровню (poll, select и т. д.), nginx преобразует уведомления в срабатывающие по фронту. Чтобы уведомления о событиях nginx были согласованными во всех системах уведомлений на разных платформах, после обработки уведомления о вводе-выводе сокета или вызова любой функции ввода-вывода для этого сокета необходимо вызывать функции ngx_handle_read_event(rev, flags) и ngx_handle_write_event(wev, lowat). Обычно эти функции вызываются один раз в конце каждого обработчика события чтения или записи.
События таймера
Для события можно настроить отправку уведомления по истечении тайм-аута. Используемый событиями таймер отсчитывает миллисекунды с некоторого неопределённого момента в прошлом, преобразованные к типу ngx_msec_t. Его текущее значение можно получить из переменной ngx_current_msec.
Функция ngx_add_timer(ev, timer) задаёт тайм-аут для события, а ngx_del_timer(ev) удаляет ранее заданный тайм-аут. В глобальном красно-чёрном дереве тайм-аутов ngx_event_timer_rbtree хранятся все установленные в данный момент тайм-ауты. Ключ в дереве имеет тип ngx_msec_t и содержит время наступления события. Структура дерева обеспечивает быстрое добавление и удаление элементов, а также доступ к ближайшим тайм-аутам, что позволяет nginx определять, как долго ожидать события ввода-вывода и истечения тайм-аутов.
Помещённые в очередь события
Событие можно поместить в очередь: это означает, что его обработчик будет вызван позднее в рамках текущей итерации цикла событий. Помещение событий в очередь упрощает код и помогает избежать переполнения стека. Помещённые в очередь события хранятся в очереди. Макрос ngx_post_event(ev, q) помещает событие ev в очередь q. Макрос ngx_delete_posted_event(ev) удаляет событие ev из очереди, в которую оно в данный момент помещено. Обычно события помещаются в очередь ngx_posted_events, обрабатываемую в конце цикла событий — после обработки всех событий ввода-вывода и таймера. Для обработки очереди событий вызывается функция ngx_event_process_posted(). Она вызывает обработчики событий, пока очередь не опустеет. Это означает, что обработчик помещённого в очередь события может поместить дополнительные события, которые будут обработаны в рамках текущей итерации цикла событий.
Пример:
void
ngx_my_connection_read(ngx_connection_t *c)
{
ngx_event_t *rev;
rev = c->read;
ngx_add_timer(rev, 1000);
rev->handler = ngx_my_read_handler;
ngx_my_read(rev);
}
void
ngx_my_read_handler(ngx_event_t *rev)
{
ssize_t n;
ngx_connection_t *c;
u_char buf[256];
if (rev->timedout) { /* timeout expired */ }
c = rev->data;
while (rev->ready) {
n = c->recv(c, buf, sizeof(buf));
if (n == NGX_AGAIN) {
break;
}
if (n == NGX_ERROR) { /* error */ }
/* process buf */
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) { /* error */ }
}
Цикл событий
За исключением главного процесса nginx, все процессы nginx выполняют операции ввода-вывода и поэтому имеют цикл событий. (Главный процесс nginx вместо этого большую часть времени проводит в вызове sigsuspend(), ожидая поступления сигналов.) Цикл событий nginx реализован в функции ngx_process_events_and_timers(), которая вызывается повторно, пока процесс не завершится.
Цикл событий включает следующие этапы:
- Найти тайм-аут, до истечения которого осталось меньше всего времени, вызвав
ngx_event_find_timer(). Эта функция находит самый левый узел в дереве таймеров и возвращает количество миллисекунд до истечения его тайм-аута. - Обработать события ввода-вывода, вызвав обработчик, соответствующий механизму уведомлений о событиях, выбранному конфигурацией nginx. Этот обработчик ожидает наступления хотя бы одного события ввода-вывода, но не дольше, чем до истечения следующего тайм-аута. При возникновении события чтения или записи устанавливается флаг
readyи вызывается обработчик события. В Linux обычно используется обработчикngx_epoll_process_events(), вызывающийepoll_wait()для ожидания событий ввода-вывода. - Обработать истёкшие таймеры, вызвав
ngx_event_expire_timers(). Дерево таймеров обходится от самого левого элемента вправо, пока не будет найден тайм-аут, который ещё не истёк. Для каждого истёкшего узла устанавливается флаг событияtimedout, сбрасывается флагtimer_setи вызывается обработчик события - Обработать помещённые в очередь события, вызвав
ngx_event_process_posted(). Функция последовательно удаляет первый элемент из очереди событий и вызывает его обработчик, пока очередь не опустеет.
Все процессы nginx также обрабатывают сигналы. Обработчики сигналов только устанавливают глобальные переменные, которые проверяются после вызова ngx_process_events_and_timers().
Процессы
В nginx есть несколько типов процессов. Тип процесса хранится в глобальной переменной ngx_process и может принимать одно из следующих значений:
-
NGX_PROCESS_MASTER— Главный процесс, который читает конфигурацию NGINX, создаёт циклы, запускает дочерние процессы и управляет ими. Он не выполняет операции ввода-вывода и реагирует только на сигналы. Его функция цикла —ngx_master_process_cycle(). -
NGX_PROCESS_WORKER— Рабочий процесс, обрабатывающий клиентские соединения. Он запускается главным процессом и также реагирует на его сигналы и команды через канал. Его функция цикла —ngx_worker_process_cycle(). Количество рабочих процессов может быть больше одного; оно задаётся директивойworker_processes. -
NGX_PROCESS_SINGLE— Единственный процесс, существующий только в режимеmaster_process offи являющийся единственным запущенным процессом в этом режиме. Он создаёт циклы (как главный процесс) и обрабатывает клиентские соединения (как рабочий процесс). Его функция цикла —ngx_single_process_cycle(). -
NGX_PROCESS_HELPER— Вспомогательный процесс; в настоящее время существует два его типа: менеджер кэша и загрузчик кэша. Функция цикла для обоих типов —ngx_cache_manager_process_cycle().
Процессы nginx обрабатывают следующие сигналы:
-
NGX_SHUTDOWN_SIGNAL(SIGQUITв большинстве систем) — Корректное завершение работы. Получив этот сигнал, главный процесс отправляет сигнал завершения всем дочерним процессам. Когда дочерних процессов не остаётся, главный процесс уничтожает пул цикла и завершает работу. Получив этот сигнал, рабочий процесс закрывает все сокеты прослушивания и ожидает, пока не останется запланированных событий, которые нельзя отменить; затем он уничтожает пул цикла и завершает работу. Процесс менеджера кэша или загрузчика кэша при получении этого сигнала немедленно завершает работу. Когда процесс получает этот сигнал, переменнаяngx_quitустанавливается в1и сразу же сбрасывается после обработки. На время завершения работы рабочего процесса переменнаяngx_exitingустанавливается в1. -
NGX_TERMINATE_SIGNAL(SIGTERMв большинстве систем) — Завершение работы. Получив этот сигнал, главный процесс отправляет сигнал завершения всем дочерним процессам. Если дочерний процесс не завершится в течение 1 секунды, главный процесс отправляет сигналSIGKILL, чтобы принудительно завершить его. Когда дочерних процессов не остаётся, главный процесс уничтожает пул цикла и завершает работу. Получив этот сигнал, рабочий процесс, процесс менеджера кэша или процесс загрузчика кэша уничтожает пул цикла и завершает работу. При получении этого сигнала переменнаяngx_terminateустанавливается в1. -
NGX_NOACCEPT_SIGNAL(SIGWINCHв большинстве систем) — Остановить все рабочие и вспомогательные процессы. Получив этот сигнал, главный процесс останавливает свои дочерние процессы. Если ранее запущенный новый бинарный файл nginx завершается, дочерние процессы старого главного процесса запускаются снова. Получив этот сигнал, рабочий процесс завершает работу в режиме отладки, заданном директивойdebug_points. -
NGX_RECONFIGURE_SIGNAL(SIGHUPв большинстве систем) — Перечитать конфигурацию. Получив этот сигнал, главный процесс повторно читает конфигурацию и создаёт на её основе новый цикл. Если новый цикл создан успешно, старый цикл удаляется и запускаются новые дочерние процессы. Тем временем старые дочерние процессы получают сигналNGX_SHUTDOWN_SIGNAL. В однопроцессном режиме nginx создаёт новый цикл, но сохраняет старый, пока не останется клиентов с активными соединениями, связанными с ним. Рабочие и вспомогательные процессы игнорируют этот сигнал. -
NGX_REOPEN_SIGNAL(SIGUSR1в большинстве систем) — Повторно открыть файлы. Главный процесс отправляет этот сигнал рабочим процессам, которые повторно открывают все связанные с циклом файлыopen_files. -
NGX_CHANGEBIN_SIGNAL(SIGUSR2в большинстве систем) — Заменить бинарный файл nginx. Главный процесс запускает новый бинарный файл nginx и передаёт ему список всех сокетов прослушивания. Список в текстовом формате передаётся в переменной окружения“NGINX”и состоит из номеров дескрипторов, разделённых точками с запятой. Новый бинарный файл nginx считывает переменную“NGINX”и добавляет сокеты в свой цикл инициализации. Другие процессы игнорируют этот сигнал.
Хотя все рабочие процессы nginx могут получать сигналы POSIX и корректно их обрабатывать, главный процесс не использует стандартный системный вызов kill() для отправки сигналов рабочим и вспомогательным процессам. Вместо этого nginx использует пары сокетов для межпроцессного взаимодействия, позволяющие отправлять сообщения между всеми процессами nginx. Однако в настоящее время сообщения отправляются только от главного процесса его дочерним процессам. В сообщениях передаются стандартные сигналы.
Потоки
Задачи, которые иначе блокировали бы рабочий процесс nginx, можно передавать для выполнения в отдельный поток. Например, nginx можно настроить на использование потоков для выполнения ввода-вывода файлов. Ещё один вариант использования — библиотека без асинхронного интерфейса, которую поэтому нельзя использовать с nginx обычным способом. Следует учитывать, что интерфейс потоков — это вспомогательное средство для существующего асинхронного подхода к обработке клиентских соединений, а не его замена.
Для синхронизации доступны следующие обёртки над примитивами pthreads:
-
typedef pthread_mutex_t ngx_thread_mutex_t;-
ngx_int_t ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log); -
ngx_int_t ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log); -
ngx_int_t ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log); -
ngx_int_t ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
-
typedef pthread_cond_t ngx_thread_cond_t;-
ngx_int_t ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log); -
ngx_int_t ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log); -
ngx_int_t ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log); -
ngx_int_t ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
Вместо создания нового потока для каждой задачи nginx использует стратегию пула потоков. Для разных целей можно настроить несколько пулов потоков (например, для выполнения операций ввода-вывода на разных наборах дисков). Каждый пул потоков создаётся при запуске и содержит ограниченное количество потоков, обрабатывающих очередь задач. После выполнения задачи вызывается заранее определённый обработчик завершения.
Файл заголовка src/core/ngx_thread_pool.h содержит соответствующие определения:
struct ngx_thread_task_s {
ngx_thread_task_t *next;
ngx_uint_t id;
void *ctx;
void (*handler)(void *data, ngx_log_t *log);
ngx_event_t event;
};
typedef struct ngx_thread_pool_s ngx_thread_pool_t;
ngx_thread_pool_t *ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name);
ngx_thread_pool_t *ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name);
ngx_thread_task_t *ngx_thread_task_alloc(ngx_pool_t *pool, size_t size);
ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task);
При настройке модуль, которому требуется использовать потоки, должен получить ссылку на пул потоков вызовом ngx_thread_pool_add(cf, name): эта функция либо создаёт новый пул потоков с заданным name, либо возвращает ссылку на уже существующий пул с таким именем.
Чтобы во время выполнения добавить task в очередь указанного пула потоков tp, используйте функцию ngx_thread_task_post(tp, task). Для выполнения функции в потоке передайте параметры и задайте обработчик завершения с помощью структуры ngx_thread_task_t:
typedef struct {
int foo;
} my_thread_ctx_t;
static void
my_thread_func(void *data, ngx_log_t *log)
{
my_thread_ctx_t *ctx = data;
/* this function is executed in a separate thread */
}
static void
my_thread_completion(ngx_event_t *ev)
{
my_thread_ctx_t *ctx = ev->data;
/* executed in nginx event loop */
}
ngx_int_t
my_task_offload(my_conf_t *conf)
{
my_thread_ctx_t *ctx;
ngx_thread_task_t *task;
task = ngx_thread_task_alloc(conf->pool, sizeof(my_thread_ctx_t));
if (task == NULL) {
return NGX_ERROR;
}
ctx = task->ctx;
ctx->foo = 42;
task->handler = my_thread_func;
task->event.handler = my_thread_completion;
task->event.data = ctx;
if (ngx_thread_task_post(conf->thread_pool, task) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
Модули
Добавление новых модулей
Каждый автономный модуль nginx находится в отдельном каталоге, содержащем как минимум два файла: config и файл с исходным кодом модуля. Файл config содержит всю информацию, необходимую nginx для интеграции модуля, например:
ngx_module_type=CORE ngx_module_name=ngx_foo_module ngx_module_srcs="$ngx_addon_dir/ngx_foo_module.c" . auto/module ngx_addon_name=$ngx_module_name
Файл config — это скрипт оболочки POSIX, который может задавать и использовать следующие переменные:
-
ngx_module_type— Тип собираемого модуля. Возможные значения:CORE,HTTP,HTTP_FILTER,HTTP_INIT_FILTER,HTTP_AUX_FILTER,MAIL,STREAMилиMISC. -
ngx_module_name— Имена модулей. Чтобы собрать несколько модулей из набора исходных файлов, укажите разделённый пробелами список имён. Первое имя указывает имя выходного двоичного файла динамического модуля. Имена в списке должны совпадать с именами, используемыми в исходном коде. -
ngx_addon_name— Имя модуля, отображаемое в выводе сценария configure в консоли. -
ngx_module_srcs— Разделённый пробелами список исходных файлов, используемых для компиляции модуля. Переменная$ngx_addon_dirможет использоваться для обозначения пути к каталогу модуля. -
ngx_module_incs— Пути включения, необходимые для сборки модуля -
ngx_module_deps— Разделённый пробелами список зависимостей модуля. Обычно это список заголовочных файлов. -
ngx_module_libs— Разделённый пробелами список библиотек, с которыми нужно связать модуль. Например, используйтеngx_module_libs=-lpthreadдля связывания с библиотекойlibpthread. Для связывания с теми же библиотеками, что и nginx, можно использовать следующие макросы:LIBXSLT,LIBGD,GEOIP,PCRE,OPENSSL,MD5,SHA1,ZLIBиPERL. -
ngx_module_link— Переменная, устанавливаемая системой сборки в значениеDYNAMICдля динамического модуля илиADDONдля статического модуля и используемая для определения различных действий в зависимости от типа связывания. -
ngx_module_order— Порядок загрузки модуля; полезен для типов модулейHTTP_FILTERиHTTP_AUX_FILTER. Формат этого параметра — разделённый пробелами список модулей. Все модули в списке, указанные после имени текущего модуля, оказываются после него в общем списке модулей, задавая порядок инициализации модулей. Для модулей-фильтров более поздняя инициализация означает более раннее выполнение.Обычно в качестве ориентиров используются следующие модули.
ngx_http_copy_filter_moduleсчитывает данные для других модулей-фильтров и помещается ближе к концу списка, чтобы выполняться одним из первых.ngx_http_write_filter_moduleзаписывает данные в клиентский сокет и помещается ближе к началу списка, чтобы выполняться последним.По умолчанию модули-фильтры помещаются в списке модулей перед
ngx_http_copy_filter, чтобы обработчик фильтра выполнялся после обработчика фильтра копирования. Для модулей других типов по умолчанию используется пустая строка.
Чтобы статически скомпилировать модуль в nginx, используйте аргумент --add-module=/path/to/module сценария configure. Чтобы скомпилировать модуль для последующей динамической загрузки в nginx, используйте аргумент --add-dynamic-module=/path/to/module.
Основные модули
Модули — это строительные блоки nginx, и большая часть его функциональности реализована в виде модулей. Исходный файл модуля должен содержать глобальную переменную типа ngx_module_t, определённого следующим образом:
struct ngx_module_s {
/* private part is omitted */
void *ctx;
ngx_command_t *commands;
ngx_uint_t type;
ngx_int_t (*init_master)(ngx_log_t *log);
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t *cycle);
void (*exit_process)(ngx_cycle_t *cycle);
void (*exit_master)(ngx_cycle_t *cycle);
/* stubs for future extensions are omitted */
};
Пропущенная закрытая часть включает версию модуля и сигнатуру и заполняется с помощью предопределённого макроса NGX_MODULE_V1.
Каждый модуль хранит свои закрытые данные в поле ctx, распознаёт директивы конфигурации, указанные в массиве commands, и может вызываться на определённых этапах жизненного цикла nginx. Жизненный цикл модуля состоит из следующих событий:
- Обработчики директив конфигурации вызываются в том порядке, в котором директивы встречаются в файлах конфигурации, в контексте главного процесса.
- После успешного разбора конфигурации в контексте главного процесса вызывается обработчик
init_module. Обработчикinit_moduleвызывается в главном процессе при каждой загрузке конфигурации. - Главный процесс создаёт один или несколько рабочих процессов, и в каждом из них вызывается обработчик
init_process. - Когда рабочий процесс получает от главного процесса команду завершения работы или остановки, он вызывает обработчик
exit_process. - Перед завершением главный процесс вызывает обработчик
exit_master.
Поскольку в nginx потоки используются только как вспомогательное средство ввода-вывода с собственным API, обработчики init_thread и exit_thread в настоящее время не вызываются. Также отсутствует обработчик init_master, поскольку он создавал бы ненужные накладные расходы.
Модуль type точно определяет, что хранится в поле ctx. Его значение относится к одному из следующих типов:
NGX_CORE_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, модуль stream, модуль mail и модули событий также являются основными. Контекст основного модуля определяется следующим образом:
typedef struct {
ngx_str_t name;
void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;
где name — строка с именем модуля, а create_conf и init_conf — указатели на функции, которые соответственно создают и инициализируют конфигурацию модуля. Для основных модулей nginx вызывает create_conf перед разбором новой конфигурации и init_conf после успешного разбора всей конфигурации. Типичная функция create_conf выделяет память для конфигурации и задаёт значения по умолчанию.
Например, упрощённый модуль под названием ngx_foo_module может выглядеть так:
/*
* Copyright (C) Author.
*/
#include <ngx_config.h>
#include <ngx_core.h>
typedef struct {
ngx_flag_t enable;
} ngx_foo_conf_t;
static void *ngx_foo_create_conf(ngx_cycle_t *cycle);
static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf);
static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t ngx_foo_enable_post = { ngx_foo_enable };
static ngx_command_t ngx_foo_commands[] = {
{ ngx_string("foo_enabled"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_foo_conf_t, enable),
&ngx_foo_enable_post },
ngx_null_command
};
static ngx_core_module_t ngx_foo_module_ctx = {
ngx_string("foo"),
ngx_foo_create_conf,
ngx_foo_init_conf
};
ngx_module_t ngx_foo_module = {
NGX_MODULE_V1,
&ngx_foo_module_ctx, /* module context */
ngx_foo_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static void *
ngx_foo_create_conf(ngx_cycle_t *cycle)
{
ngx_foo_conf_t *fcf;
fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t));
if (fcf == NULL) {
return NULL;
}
fcf->enable = NGX_CONF_UNSET;
return fcf;
}
static char *
ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf)
{
ngx_foo_conf_t *fcf = conf;
ngx_conf_init_value(fcf->enable, 0);
return NGX_CONF_OK;
}
static char *
ngx_foo_enable(ngx_conf_t *cf, void *post, void *data)
{
ngx_flag_t *fp = data;
if (*fp == 0) {
return NGX_CONF_OK;
}
ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled");
return NGX_CONF_OK;
}
Директивы конфигурации
Тип ngx_command_t определяет одну директиву конфигурации. Каждый модуль, поддерживающий конфигурацию, предоставляет массив таких структур, описывающих обработку аргументов и вызываемые обработчики:
typedef struct ngx_command_s ngx_command_t;
struct ngx_command_s {
ngx_str_t name;
ngx_uint_t type;
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_uint_t conf;
ngx_uint_t offset;
void *post;
};
Завершите массив специальным значением ngx_null_command. name — имя директивы в том виде, в каком она записана в файле конфигурации, например «worker_processes» или «listen». type — битовое поле флагов, задающих количество аргументов директивы, её тип и контекст, в котором она встречается. Флаги:
-
NGX_CONF_NOARGS— директива не принимает аргументов. -
NGX_CONF_1MORE— директива принимает один или несколько аргументов. -
NGX_CONF_2MORE— директива принимает два или более аргумента. -
NGX_CONF_TAKE1..NGX_CONF_TAKE7— директива принимает ровно указанное количество аргументов. -
NGX_CONF_TAKE12,NGX_CONF_TAKE13,NGX_CONF_TAKE23,NGX_CONF_TAKE123,NGX_CONF_TAKE1234— директива может принимать разное количество аргументов. Допустимы только указанные значения. Например,NGX_CONF_TAKE12означает, что директива принимает один или два аргумента.
Флаги типов директив:
-
NGX_CONF_BLOCK— директива является блоком, то есть может содержать другие директивы между открывающей и закрывающей фигурными скобками или даже реализовывать собственный анализатор для обработки содержимого блока. -
NGX_CONF_FLAG— директива принимает логическое значение:onилиoff.
Контекст директивы определяет, где она может встречаться в конфигурации:
-
NGX_MAIN_CONF— в контексте верхнего уровня. -
NGX_HTTP_MAIN_CONF— в блокеhttp. -
NGX_HTTP_SRV_CONF— в блокеserverвнутри блокаhttp. -
NGX_HTTP_LOC_CONF— в блокеlocationвнутри блокаhttp. -
NGX_HTTP_UPS_CONF— в блокеupstreamвнутри блокаhttp. -
NGX_HTTP_SIF_CONF— в блокеifвнутри блокаserverв блокеhttp. -
NGX_HTTP_LIF_CONF— в блокеifвнутри блокаlocationв блокеhttp. -
NGX_HTTP_LMT_CONF— в блокеlimit_exceptвнутри блокаhttp. -
NGX_STREAM_MAIN_CONF— в блокеstream. -
NGX_STREAM_SRV_CONF— в блокеserverвнутри блокаstream. -
NGX_STREAM_UPS_CONF— в блокеupstreamвнутри блокаstream. -
NGX_MAIL_MAIN_CONF— в блокеmail. -
NGX_MAIL_SRV_CONF— в блокеserverвнутри блокаmail. -
NGX_EVENT_CONF— в блокеevent. -
NGX_DIRECT_CONF— используется модулями, которые не создают иерархию контекстов и имеют только одну глобальную конфигурацию. Эта конфигурация передаётся обработчику в качестве аргументаconf.
Анализатор конфигурации использует эти флаги, чтобы выдавать ошибку, если директива размещена в недопустимом месте, и вызывает обработчики директив с соответствующим указателем на конфигурацию. Благодаря этому одинаковые директивы в разных местах могут хранить значения в разных местах.
Поле set задаёт обработчик, который обрабатывает директиву и сохраняет разобранные значения в соответствующей конфигурации. Для выполнения распространённых преобразований предусмотрен ряд функций:
-
ngx_conf_set_flag_slot— преобразует строковые значенияonиoffв значение типаngx_flag_tсо значениями 1 и 0 соответственно. -
ngx_conf_set_str_slot— сохраняет строку как значение типаngx_str_t. -
ngx_conf_set_str_array_slot— добавляет значение в массивngx_array_tстрокngx_str_t. Если массива ещё нет, он создаётся. -
ngx_conf_set_keyval_slot— добавляет пару «ключ-значение» в массивngx_array_tпар «ключ-значение»ngx_keyval_t. Первая строка становится ключом, а вторая — значением. Если массива ещё нет, он создаётся. -
ngx_conf_set_num_slot— преобразует аргумент директивы в значение типаngx_int_t. -
ngx_conf_set_size_slot— преобразует размер в значениеsize_t, выраженное в байтах. -
ngx_conf_set_off_slot— преобразует смещение в значениеoff_t, выраженное в байтах. -
ngx_conf_set_msec_slot— преобразует время в значениеngx_msec_t, выраженное в миллисекундах. -
ngx_conf_set_sec_slot— преобразует время в значениеtime_t, выраженное в секундах. -
ngx_conf_set_bufs_slot— преобразует два переданных аргумента в объектngx_bufs_t, содержащий количество и размер буферов. -
ngx_conf_set_enum_slot— преобразует переданный аргумент в значениеngx_uint_t. Переданный в полеpostмассивngx_conf_enum_tс завершающим нулём задаёт допустимые строки и соответствующие целочисленные значения. -
ngx_conf_set_bitmask_slot— преобразует переданные аргументы в значениеngx_uint_t. Значения масок для каждого аргумента объединяются операцией ИЛИ, формируя результат. Переданный в полеpostмассивngx_conf_bitmask_tс завершающим нулём задаёт допустимые строки и соответствующие значения масок. -
set_path_slot— преобразует переданные аргументы в значениеngx_path_tи выполняет все необходимые инициализации. Подробности см. в документации к директиве proxy_temp_path. -
set_access_slot— преобразует переданные аргументы в маску прав доступа к файлу. Подробности см. в документации к директиве proxy_store_access.
Поле conf определяет, какая структура конфигурации передаётся обработчику директивы. У основных модулей есть только глобальная конфигурация, доступ к которой обеспечивается установкой флага NGX_DIRECT_CONF. Модули, такие как HTTP, Stream или Mail, создают иерархии конфигураций. Например, конфигурация модуля создаётся для областей server, location и if.
-
NGX_HTTP_MAIN_CONF_OFFSET— конфигурация блокаhttp. -
NGX_HTTP_SRV_CONF_OFFSET— конфигурация блокаserverвнутри блокаhttp. -
NGX_HTTP_LOC_CONF_OFFSET— конфигурация блокаlocationвнутриhttp. -
NGX_STREAM_MAIN_CONF_OFFSET— конфигурация блокаstream. -
NGX_STREAM_SRV_CONF_OFFSET— конфигурация блокаserverвнутри блокаstream. -
NGX_MAIL_MAIN_CONF_OFFSET— конфигурация блокаmail. -
NGX_MAIL_SRV_CONF_OFFSET— конфигурация блокаserverвнутри блокаmail.
offset задаёт смещение поля в структуре конфигурации модуля, в котором хранятся значения конкретной директивы. Обычно для этого используется макрос offsetof().
Поле post предназначено для двух целей: оно может задавать обработчик, вызываемый после завершения основного обработчика, или передавать дополнительные данные основному обработчику. В первом случае структуру ngx_conf_post_t необходимо инициализировать указателем на обработчик, например:
static char *ngx_do_foo(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t ngx_foo_post = { ngx_do_foo };
Аргумент post — это сам объект ngx_conf_post_t, а data — указатель на значение соответствующего типа, преобразованное из аргументов основным обработчиком.
HTTP
Соединение
Каждое HTTP-соединение с клиентом проходит следующие этапы:
-
ngx_event_accept()принимает TCP-соединение от клиента. Этот обработчик вызывается в ответ на уведомление о чтении сокета, ожидающего соединения. На этом этапе создаётся новый объектngx_connection_t, представляющий только что принятый клиентский сокет. Каждый слушающий сокет nginx предоставляет обработчик для передачи ему нового объекта соединения. Для HTTP-соединений этоngx_http_init_connection(c). -
ngx_http_init_connection()выполняет начальную инициализацию HTTP-соединения. На этом этапе для соединения создаётся объектngx_http_connection_t, ссылка на который сохраняется в полеdataсоединения. Позже он будет заменён объектом HTTP-запроса. На этом же этапе запускаются анализатор протокола PROXY и SSL-рукопожатие. - Обработчик события чтения
ngx_http_wait_request_handler()вызывается, когда на клиентском сокете доступны данные. На этом этапе создаётся объект HTTP-запросаngx_http_request_tи устанавливается в полеdataсоединения. - Обработчик события чтения
ngx_http_process_request_line()считывает строку запроса клиента. Обработчик задаётся функциейngx_http_wait_request_handler(). Данные считываются вbufferсоединения. Начальный размер буфера задаётся директивой client_header_buffer_size. Предполагается, что весь заголовок клиента помещается в буфер. Если начального размера недостаточно, выделяется буфер большего размера, ёмкость которого задаётся директивойlarge_client_header_buffers. - Обработчик события чтения
ngx_http_process_request_headers()устанавливается послеngx_http_process_request_line()для чтения заголовка запроса клиента. -
ngx_http_core_run_phases()вызывается после полного чтения и разбора заголовка запроса. Эта функция выполняет фазы обработки запроса отNGX_HTTP_POST_READ_PHASEдоNGX_HTTP_CONTENT_PHASE. Последняя фаза предназначена для формирования ответа и передачи его по цепочке фильтров. На этом этапе ответ не обязательно отправляется клиенту. Он может оставаться в буфере и отправляться на этапе завершения. -
ngx_http_finalize_request()обычно вызывается, когда запрос сформировал весь вывод или произошла ошибка. Во втором случае находится соответствующая страница ошибки и используется в качестве ответа. Если к этому моменту ответ отправлен клиенту не полностью, активируется HTTP-обработчик записиngx_http_writer()для завершения отправки оставшихся данных. -
ngx_http_finalize_connection()вызывается после отправки клиенту полного ответа, когда запрос можно уничтожить. Если включена функция постоянного соединения с клиентом, вызываетсяngx_http_set_keepalive(), которая уничтожает текущий запрос и ожидает следующий запрос в соединении. В противном случаеngx_http_close_request()уничтожает и запрос, и соединение.
Запрос
Для каждого HTTP-запроса клиента создаётся объект ngx_http_request_t. Некоторые поля этого объекта:
-
connection— указатель на объект клиентского соединенияngx_connection_t. На один объект соединения могут одновременно ссылаться несколько запросов — один основной запрос и его подзапросы. После удаления запроса в том же соединении может быть создан новый запрос.Обратите внимание, что для HTTP-соединений поле
dataобъектаngx_connection_tуказывает на запрос. Такие запросы называются активными, в отличие от остальных запросов, связанных с соединением. Активный запрос используется для обработки событий клиентского соединения и может отправлять клиенту свой ответ. Обычно каждый запрос в какой-то момент становится активным, чтобы отправить свой вывод. -
ctx— массив контекстов HTTP-модулей. Каждый модуль типаNGX_HTTP_MODULEможет хранить в запросе любое значение (обычно указатель на структуру). Значение хранится в массивеctxв позицииctx_indexмодуля. Для удобного получения и установки контекстов запросов предусмотрены следующие макросы:-
ngx_http_get_module_ctx(r, module)— возвращает контекстmodule -
ngx_http_set_ctx(r, c, module)— устанавливаетcв качестве контекстаmodule
-
-
main_conf,srv_conf,loc_conf— массивы текущих конфигураций запроса. Конфигурации хранятся в позицияхctx_indexсоответствующего модуля. -
read_event_handler,write_event_handler— обработчики событий чтения и записи запроса. Обычно оба обработчика событий чтения и записи для HTTP-соединения установлены вngx_http_request_handler(). Эта функция вызывает обработчикиread_event_handlerиwrite_event_handlerдля текущего активного запроса. -
cache— объект кэша запроса для кэширования ответа upstream-сервера. -
upstream— объект upstream-запроса для проксирования. -
pool— пул запроса. Сам объект запроса выделяется в этом пуле, который уничтожается при удалении запроса. Для выделения памяти, которая должна быть доступна в течение всего времени существования клиентского соединения, используйте пул объектаngx_connection_t. -
header_in— буфер, в который считывается заголовок HTTP-запроса клиента. -
headers_in,headers_out— объекты входящих и исходящих HTTP-заголовков. Оба объекта содержат полеheadersтипаngx_list_tдля хранения исходного списка заголовков. Кроме того, отдельные заголовки доступны для получения и установки в виде отдельных полей, напримерcontent_length_n,statusи т. д. -
request_body— объект тела запроса клиента. -
start_sec,start_msec— момент создания запроса, используемый для отслеживания его длительности. -
method,method_name— числовое и текстовое представления метода HTTP-запроса клиента. Числовые значения методов определены вsrc/http/ngx_http_request.hс помощью макросовNGX_HTTP_GET,NGX_HTTP_HEAD,NGX_HTTP_POSTи т. д. -
http_protocol— версия HTTP-протокола клиента в исходном текстовом виде («HTTP/1.0», «HTTP/1.1» и т. д.). -
http_version— версия HTTP-протокола клиента в числовом виде (NGX_HTTP_VERSION_10,NGX_HTTP_VERSION_11и т. д.). -
http_major,http_minor— версия HTTP-протокола клиента в числовом виде, разделённая на основную и дополнительную части. -
request_line,unparsed_uri— строка запроса и URI из исходного запроса клиента. -
uri,args,exten— URI, аргументы и расширение файла текущего запроса. Значение URI здесь может отличаться от исходного URI, отправленного клиентом, из-за нормализации. В ходе обработки запроса эти значения могут меняться при выполнении внутренних перенаправлений. -
main— указатель на объект основного запроса. Этот объект создаётся для обработки HTTP-запроса клиента, в отличие от подзапросов, которые создаются для выполнения конкретной подзадачи в рамках основного запроса. -
parent— указатель на родительский запрос подзапроса. -
postponed— список выходных буферов и подзапросов в порядке их отправки и создания. Список используется фильтром отложенной обработки, чтобы обеспечить согласованный вывод запроса, части которого создаются подзапросами. -
post_subrequest— указатель на обработчик с контекстом, вызываемый при завершении подзапроса. Для основных запросов не используется. -
posted_requests— список запросов, которые нужно запустить или возобновить вызовомwrite_event_handlerзапроса. Обычно этот обработчик содержит главную функцию запроса, которая сначала выполняет фазы обработки запроса, а затем формирует вывод.Запрос обычно помещается в очередь вызовом
ngx_http_post_request(r, NULL). Он всегда добавляется в списокposted_requestsосновного запроса. Функцияngx_http_run_posted_requests(c)запускает все запросы, поставленные в очередь основного запроса активного запроса переданного соединения. Все обработчики событий вызываютngx_http_run_posted_requests, что может привести к появлению новых запросов в очереди. Обычно эта функция вызывается после выполнения обработчика чтения или записи запроса. -
phase_handler— индекс текущей фазы запроса. -
ncaptures,captures,captures_data— захваченные группы регулярного выражения, полученные при последнем сопоставлении регулярного выражения в запросе. Сопоставление с регулярным выражением может происходить в нескольких местах при обработке запроса: при поиске в map, поиске сервера по SNI или HTTP Host, переписывании, proxy_redirect и т. д. Захваченные при поиске группы сохраняются в указанных выше полях. Полеncapturesсодержит количество захваченных групп,captures— границы захваченных групп, аcaptures_data— строку, с которой сопоставлялось регулярное выражение и которая используется для извлечения захваченных групп. После каждого нового сопоставления значения захваченных групп запроса сбрасываются и заменяются новыми. -
count— счётчик ссылок на запрос. Поле имеет смысл только для основного запроса. Для увеличения счётчика достаточно выполнитьr->main->count++. Чтобы уменьшить счётчик, вызовитеngx_http_finalize_request(r, rc). Создание подзапроса и выполнение процесса чтения тела запроса увеличивают счётчик. -
subrequests— текущий уровень вложенности подзапросов. Каждый подзапрос наследует уровень вложенности родительского запроса, уменьшенный на единицу. Если значение достигает нуля, возникает ошибка. Значение для основного запроса определяется константойNGX_HTTP_MAX_SUBREQUESTS. -
uri_changes— количество оставшихся изменений URI для запроса. Общее число раз, которое запрос может изменить URI, ограничено константойNGX_HTTP_MAX_URI_CHANGES. При каждом изменении значение уменьшается, пока не достигнет нуля; в этот момент возникает ошибка. Переписывания и внутренние перенаправления в обычные или именованные местоположения считаются изменениями URI. -
blocked— счётчик блокировок, удерживаемых запросом. Пока это значение не равно нулю, запрос нельзя завершить. В настоящее время значение увеличивают ожидающие операции AIO (POSIX AIO и операции с потоками) и активная блокировка кэша. -
buffered— битовая маска, показывающая, какие модули буферизовали вывод запроса. Вывод могут буферизовать различные фильтры; например, sub_filter может буферизовать данные из-за частичного совпадения строки, а фильтр копирования — из-за отсутствия свободных выходных буферов и т. д. Пока это значение не равно нулю, завершение запроса откладывается до сброса буферов. -
header_only— флаг, указывающий, что для вывода не требуется тело. Например, этот флаг используется для HTTP-запросов HEAD. -
keepalive— флаг, указывающий, поддерживается ли постоянное соединение с клиентом. Значение определяется версией HTTP и значением заголовка «Connection». -
header_sent— флаг, указывающий, что заголовок вывода уже отправлен запросом. -
internal— флаг, указывающий, что текущий запрос является внутренним. Чтобы перейти во внутреннее состояние, запрос должен пройти внутреннее перенаправление или быть подзапросом. Внутренним запросам разрешено переходить во внутренние местоположения. -
allow_ranges— флаг, указывающий, что клиенту можно отправить частичный ответ, запрошенный заголовком HTTP Range. -
subrequest_ranges— флаг, указывающий, что во время обработки подзапроса можно отправлять частичный ответ. -
single_range— флаг, указывающий, что клиенту можно отправить только один непрерывный диапазон выходных данных. Обычно этот флаг устанавливается при отправке потока данных, например с проксируемого сервера, когда весь ответ недоступен в одном буфере. -
main_filter_need_in_memory,filter_need_in_memory— флаги, требующие формирования вывода в буферах памяти, а не в файлах. Это сигнал фильтру копирования считывать данные из файловых буферов, даже если включён sendfile. Разница между двумя флагами заключается в расположении модулей-фильтров, которые их устанавливают. Фильтры, вызываемые до фильтра отложенной обработки в цепочке фильтров, устанавливаютfilter_need_in_memory, требуя помещать в буферы памяти только вывод текущего запроса. Фильтры, вызываемые позже в цепочке фильтров, устанавливаютmain_filter_need_in_memory, требуя считывать файлы в память как для основного запроса, так и для всех подзапросов при отправке вывода. -
filter_need_temporary— флаг, требующий формировать вывод запроса во временных буферах, но не в буферах только для чтения или файловых буферах. Используется фильтрами, которые могут изменять вывод непосредственно в буферах, из которых он отправляется.
Конфигурация
Каждый HTTP-модуль может иметь три типа конфигурации:
- Основная конфигурация — применяется ко всему блоку
http. Содержит глобальные настройки модуля. - Конфигурация сервера — применяется к одному блоку
server. Содержит настройки модуля для конкретного сервера. - Конфигурация местоположения — применяется к одному блоку
location,ifилиlimit_except. Содержит настройки модуля для конкретного местоположения.
Структуры конфигурации создаются на этапе конфигурирования nginx вызовом функций, которые выделяют память для структур, инициализируют и объединяют их. В следующем примере показано, как создать простую конфигурацию местоположения для модуля. В конфигурации есть один параметр foo типа беззнакового целого числа.
typedef struct {
ngx_uint_t foo;
} ngx_http_foo_loc_conf_t;
static ngx_http_module_t ngx_http_foo_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_foo_create_loc_conf, /* create location configuration */
ngx_http_foo_merge_loc_conf /* merge location configuration */
};
static void *
ngx_http_foo_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_foo_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_foo_loc_conf_t));
if (conf == NULL) {
return NULL;
}
conf->foo = NGX_CONF_UNSET_UINT;
return conf;
}
static char *
ngx_http_foo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_foo_loc_conf_t *prev = parent;
ngx_http_foo_loc_conf_t *conf = child;
ngx_conf_merge_uint_value(conf->foo, prev->foo, 1);
}
Как видно из примера, функция ngx_http_foo_create_loc_conf() создаёт новую структуру конфигурации, а ngx_http_foo_merge_loc_conf() объединяет конфигурацию с конфигурацией более высокого уровня. На самом деле конфигурации сервера и местоположения существуют не только на уровнях сервера и местоположения, но и создаются для всех вышестоящих уровней. В частности, конфигурация сервера также создаётся на основном уровне, а конфигурации местоположения создаются на основном уровне, уровне сервера и уровне местоположения. Эти конфигурации позволяют задавать настройки для конкретного сервера и местоположения на любом уровне файла конфигурации nginx. В итоге конфигурации объединяются сверху вниз. Для обозначения отсутствующего параметра и его игнорирования при объединении предусмотрен ряд макросов, например NGX_CONF_UNSET и NGX_CONF_UNSET_UINT. Стандартные макросы nginx для объединения, например ngx_conf_merge_value() и ngx_conf_merge_uint_value(), позволяют удобно объединять параметр и задавать значение по умолчанию, если ни в одной из конфигураций не было указано явное значение. Полный список макросов для разных типов см. в src/core/ngx_conf_file.h.
Для доступа к конфигурации HTTP-модулей на этапе конфигурирования доступны следующие макросы. В качестве первого аргумента каждый из них принимает ссылку ngx_conf_t.
-
ngx_http_conf_get_module_main_conf(cf, module) -
ngx_http_conf_get_module_srv_conf(cf, module) -
ngx_http_conf_get_module_loc_conf(cf, module)
В следующем примере получается указатель на конфигурацию расположения стандартного основного модуля nginx ngx_http_core_module и заменяется обработчик содержимого расположения, хранящийся в поле handler структуры.
static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r);
static ngx_command_t ngx_http_foo_commands[] = {
{ ngx_string("foo"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_foo,
0,
0,
NULL },
ngx_null_command
};
static char *
ngx_http_foo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_bar_handler;
return NGX_CONF_OK;
}
Для доступа к конфигурации HTTP-модулей во время выполнения доступны следующие макросы.
-
ngx_http_get_module_main_conf(r, module) -
ngx_http_get_module_srv_conf(r, module) -
ngx_http_get_module_loc_conf(r, module)
Эти макросы принимают ссылку на HTTP-запрос ngx_http_request_t. Основная конфигурация запроса никогда не меняется. Конфигурация сервера может измениться относительно конфигурации по умолчанию после выбора виртуального сервера для запроса. Конфигурация расположения, выбранного для обработки запроса, может меняться несколько раз в результате операции переписывания или внутреннего перенаправления. В следующем примере показано, как получить доступ к конфигурации HTTP-модуля во время выполнения.
static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
ngx_http_foo_loc_conf_t *flcf;
flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module);
...
}
Фазы
Каждый HTTP-запрос проходит последовательность фаз. На каждой фазе выполняется отдельный тип обработки запроса. В большинстве фаз можно зарегистрировать обработчики, специфичные для модулей, и многие стандартные модули nginx регистрируют обработчики фаз, чтобы вызываться на определённом этапе обработки запроса. Фазы обрабатываются последовательно, а обработчики фаз вызываются, когда запрос достигает соответствующей фазы. Ниже приведён список фаз HTTP в nginx.
-
NGX_HTTP_POST_READ_PHASE— Первая фаза. На этой фазе ngx_http_realip_module регистрирует обработчик, чтобы разрешить подстановку адресов клиентов до вызова любых других модулей. -
NGX_HTTP_SERVER_REWRITE_PHASE— Фаза, на которой обрабатываются директивы переписывания, заданные в блокеserver(но вне блокаlocation). На этой фазе ngx_http_rewrite_module устанавливает свой обработчик. -
NGX_HTTP_FIND_CONFIG_PHASE— Специальная фаза, на которой расположение выбирается на основе URI запроса. До этой фазы запросу назначается расположение по умолчанию соответствующего виртуального сервера, и любой модуль, запрашивающий конфигурацию расположения, получает конфигурацию расположения сервера по умолчанию. На этой фазе запросу назначается новое расположение. Зарегистрировать дополнительные обработчики на этой фазе нельзя. -
NGX_HTTP_REWRITE_PHASE— АналогичнаNGX_HTTP_SERVER_REWRITE_PHASE, но применяется к правилам переписывания, заданным в расположении, выбранном на предыдущей фазе. -
NGX_HTTP_POST_REWRITE_PHASE— Специальная фаза, на которой запрос перенаправляется в новое расположение, если его URI изменился в ходе переписывания. Это реализовано путём повторного прохождения запросом фазыNGX_HTTP_FIND_CONFIG_PHASE. Зарегистрировать дополнительные обработчики на этой фазе нельзя. -
NGX_HTTP_PREACCESS_PHASE— Общая фаза для различных типов обработчиков, не связанных с контролем доступа. На этой фазе свои обработчики регистрируют стандартные модули nginx ngx_http_limit_conn_module и ngx_http_limit_req_module. -
NGX_HTTP_ACCESS_PHASE— Фаза, на которой проверяется, имеет ли клиент право выполнить запрос. На этой фазе свои обработчики регистрируют стандартные модули nginx, например ngx_http_access_module и ngx_http_auth_basic_module . По умолчанию клиент должен пройти проверку авторизации во всех обработчиках, зарегистрированных на этой фазе, чтобы запрос перешёл к следующей фазе. Директиву satisfy можно использовать, чтобы разрешить продолжение обработки, если клиент авторизован любым из обработчиков фазы. -
NGX_HTTP_POST_ACCESS_PHASE— Специальная фаза, на которой обрабатывается директива satisfy any. Если некоторые обработчики фазы доступа запретили доступ, а ни один из них явно его не разрешил, обработка запроса завершается. Зарегистрировать дополнительные обработчики на этой фазе нельзя. -
NGX_HTTP_PRECONTENT_PHASE— Фаза для обработчиков, вызываемых перед генерацией содержимого. На этой фазе свои обработчики регистрируют стандартные модули, например ngx_http_try_files_module и ngx_http_mirror_module. -
NGX_HTTP_CONTENT_PHASE— Фаза, на которой обычно формируется ответ. На этой фазе свои обработчики регистрируют несколько стандартных модулей nginx, в том числе ngx_http_index_module илиngx_http_static_module. Они вызываются последовательно, пока один из них не сформирует вывод. Также можно задавать обработчики содержимого отдельно для каждого расположения. Если в конфигурации расположения ngx_http_core_module заданоhandler, оно вызывается как обработчик содержимого, а обработчики, установленные на этой фазе, игнорируются. -
NGX_HTTP_LOG_PHASE— Фаза, на которой выполняется журналирование запросов. В настоящее время только ngx_http_log_module регистрирует на этом этапе обработчик для ведения журнала доступа. Обработчики фазы журналирования вызываются в самом конце обработки запроса, непосредственно перед освобождением запроса.
Ниже приведён пример обработчика фазы предварительного доступа.
static ngx_http_module_t ngx_http_foo_module_ctx = {
NULL, /* preconfiguration */
ngx_http_foo_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
ngx_table_elt_t *ua;
ua = r->headers_in.user_agent;
if (ua == NULL) {
return NGX_DECLINED;
}
/* reject requests with "User-Agent: foo" */
if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) {
return NGX_HTTP_FORBIDDEN;
}
return NGX_DECLINED;
}
static ngx_int_t
ngx_http_foo_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_foo_handler;
return NGX_OK;
}
От обработчиков фаз ожидается возврат определённых кодов:
-
NGX_OK— Перейти к следующей фазе. -
NGX_DECLINED— Перейти к следующему обработчику текущей фазы. Если текущий обработчик — последний в текущей фазе, перейти к следующей фазе. -
NGX_AGAIN,NGX_DONE— Приостановить обработку фазы до будущего события, например асинхронной операции ввода-вывода или просто задержки. Предполагается, что обработка фазы будет возобновлена позднее вызовомngx_http_core_run_phases(). - Любое другое значение, возвращённое обработчиком фазы, рассматривается как код завершения запроса, в частности как код HTTP-ответа. Запрос завершается с указанным кодом.
Для некоторых фаз возвращаемые коды обрабатываются несколько иначе. На фазе содержимого любой код возврата, кроме NGX_DECLINED, считается кодом завершения. Любой код возврата обработчиков содержимого расположения считается кодом завершения. На фазе доступа в режиме satisfy any любой код возврата, кроме NGX_OK, NGX_DECLINED, NGX_AGAIN, NGX_DONE, считается отказом. Если последующие обработчики доступа не разрешают и не запрещают доступ с другим кодом, код отказа становится кодом завершения.
Переменные
Доступ к существующим переменным
На переменные можно ссылаться по индексу (это наиболее распространённый способ) или по имени (см. ниже). Индекс создаётся на этапе конфигурирования, когда переменная добавляется в конфигурацию. Чтобы получить индекс переменной, используйте ngx_http_get_variable_index():
ngx_str_t name; /* ngx_string("foo") */
ngx_int_t index;
index = ngx_http_get_variable_index(cf, &name);
Здесь cf — указатель на конфигурацию nginx, а name указывает на строку, содержащую имя переменной. В случае ошибки функция возвращает NGX_ERROR, в противном случае — допустимый индекс, который обычно сохраняется в конфигурации модуля для дальнейшего использования.
Все переменные HTTP вычисляются в контексте заданного HTTP-запроса, а результаты относятся к этому запросу и кэшируются в нём. Все функции, вычисляющие переменные, возвращают тип ngx_http_variable_value_t, представляющий значение переменной:
typedef ngx_variable_value_t ngx_http_variable_value_t;
typedef struct {
unsigned len:28;
unsigned valid:1;
unsigned no_cacheable:1;
unsigned not_found:1;
unsigned escape:1;
u_char *data;
} ngx_variable_value_t;
где:
-
len— Длина значения -
data— Само значение -
valid— Значение действительно -
not_found— Переменная не найдена, поэтому поляdataиlenне имеют значения; например, это может произойти с переменными типа$arg_foo, если соответствующий аргумент не был передан в запросе -
no_cacheable— Не кэшировать результат -
escape— Используется модулем журналирования внутри nginx для пометки значений, требующих экранирования при выводе.
Для получения значения переменной используются функции ngx_http_get_flushed_variable() и ngx_http_get_indexed_variable(). У них одинаковый интерфейс: они принимают HTTP-запрос r в качестве контекста для вычисления переменной и index, идентифицирующий её. Пример типичного использования:
ngx_http_variable_value_t *v;
v = ngx_http_get_flushed_variable(r, index);
if (v == NULL || v->not_found) {
/* we failed to get value or there is no such variable, handle it */
return NGX_ERROR;
}
/* some meaningful value is found */
Разница между функциями заключается в том, что ngx_http_get_indexed_variable() возвращает кэшированное значение, а ngx_http_get_flushed_variable() сбрасывает кэш для некэшируемых переменных.
Некоторым модулям, например SSI и Perl, необходимо работать с переменными, имя которых неизвестно на этапе конфигурирования. Поэтому для доступа к ним нельзя использовать индекс, однако доступна функция ngx_http_get_variable(r, name, key). Она ищет переменную с заданным name и его хешем key, вычисленным на основе имени.
Создание переменных
Чтобы создать переменную, используйте функцию ngx_http_add_variable(). Ей передаются конфигурация (в которой регистрируется переменная), имя переменной и флаги, управляющие поведением функции:
-
NGX_HTTP_VAR_CHANGEABLE— Разрешает переопределение переменной: конфликт не возникает, если другой модуль определяет переменную с тем же именем. Это позволяет директиве set переопределять переменные. -
NGX_HTTP_VAR_NOCACHEABLE— Отключает кэширование, что полезно для таких переменных, как$time_local. -
NGX_HTTP_VAR_NOHASH— Указывает, что доступ к этой переменной возможен только по индексу, но не по имени. Это небольшая оптимизация, полезная в случаях, когда известно, что переменная не понадобится таким модулям, как SSI или Perl. -
NGX_HTTP_VAR_PREFIX— Имя переменной является префиксом. В этом случае обработчик должен реализовать дополнительную логику для получения значения конкретной переменной. Например, все переменные «arg_» обрабатываются одним обработчиком, который выполняет поиск в аргументах запроса и возвращает значение конкретного аргумента.
В случае ошибки функция возвращает NULL, в противном случае — указатель на ngx_http_variable_t:
struct ngx_http_variable_s {
ngx_str_t name;
ngx_http_set_variable_pt set_handler;
ngx_http_get_variable_pt get_handler;
uintptr_t data;
ngx_uint_t flags;
ngx_uint_t index;
};
Обработчики get и set вызываются для получения или установки значения переменной, обработчикам переменных передаётся data, а в index хранится присвоенный переменной индекс, используемый для обращения к ней.
Обычно модуль создаёт статический массив структур ngx_http_variable_t с нулевым завершающим элементом и обрабатывает его на этапе предварительной конфигурации, чтобы добавить переменные в конфигурацию. Например:
static ngx_http_variable_t ngx_http_foo_vars[] = {
{ ngx_string("foo_v1"), NULL, ngx_http_foo_v1_variable, 0, 0, 0 },
ngx_http_null_variable
};
static ngx_int_t
ngx_http_foo_add_variables(ngx_conf_t *cf)
{
ngx_http_variable_t *var, *v;
for (v = ngx_http_foo_vars; v->name.len; v++) {
var = ngx_http_add_variable(cf, &v->name, v->flags);
if (var == NULL) {
return NGX_ERROR;
}
var->get_handler = v->get_handler;
var->data = v->data;
}
return NGX_OK;
}
В этом примере эта функция используется для инициализации поля preconfiguration контекста HTTP-модуля и вызывается до разбора конфигурации HTTP, чтобы анализатор мог обращаться к этим переменным.
Обработчик get отвечает за вычисление переменной в контексте конкретного запроса. Например:
static ngx_int_t
ngx_http_variable_connection(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
u_char *p;
p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN);
if (p == NULL) {
return NGX_ERROR;
}
v->len = ngx_sprintf(p, "%uA", r->connection->number) - p;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = p;
return NGX_OK;
}
В случае внутренней ошибки (например, при неудачном выделении памяти) он возвращает NGX_ERROR, в противном случае — NGX_OK. Чтобы узнать результат вычисления переменной, проверьте флаги в ngx_http_variable_value_t (см. описание выше).
Обработчик set позволяет задавать свойство, на которое ссылается переменная. Например, обработчик установки переменной $limit_rate изменяет поле limit_rate запроса:
...
{ ngx_string("limit_rate"), ngx_http_variable_request_set_size,
ngx_http_variable_request_get_size,
offsetof(ngx_http_request_t, limit_rate),
NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
...
static void
ngx_http_variable_request_set_size(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
ssize_t s, *sp;
ngx_str_t val;
val.len = v->len;
val.data = v->data;
s = ngx_parse_size(&val);
if (s == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"invalid size \"%V\"", &val);
return;
}
sp = (ssize_t *) ((char *) r + data);
*sp = s;
return;
}
Составные значения
Составное значение, несмотря на своё название, позволяет легко вычислять выражения, которые могут содержать текст, переменные и их комбинации.
Описание составного значения в ngx_http_compile_complex_value на этапе конфигурирования компилируется в ngx_http_complex_value_t, которое используется во время выполнения для получения результатов вычисления выражения.
ngx_str_t *value;
ngx_http_complex_value_t cv;
ngx_http_compile_complex_value_t ccv;
value = cf->args->elts; /* directive arguments */
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &cv;
ccv.zero = 1;
ccv.conf_prefix = 1;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
Здесь ccv содержит все параметры, необходимые для инициализации составного значения cv:
-
cf— Указатель на конфигурацию -
value— Разбираемая строка (входные данные) -
complex_value— Скомпилированное значение (выходные данные) -
zero— Флаг, включающий завершение значения нулём -
conf_prefix— Добавляет к результату префикс конфигурации (каталог, в котором nginx в данный момент ищет конфигурацию) -
root_prefix— Добавляет к результату корневой префикс (обычный префикс установки nginx)
Флаг zero полезен, когда результаты передаются библиотекам, требующим строк с завершающим нулём, а префиксы удобны при работе с именами файлов.
После успешной компиляции cv.lengths содержит сведения о наличии переменных в выражении. Значение NULL означает, что выражение содержало только статический текст, поэтому его можно хранить в обычной строке, а не в виде составного значения.
ngx_http_set_complex_value_slot() — это удобная функция, используемая для полной инициализации составного значения непосредственно в объявлении директивы.
Во время выполнения составное значение можно вычислить с помощью функции ngx_http_complex_value():
ngx_str_t res;
if (ngx_http_complex_value(r, &cv, &res) != NGX_OK) {
return NGX_ERROR;
}
Получив запрос r и ранее скомпилированное значение cv, функция вычисляет выражение и записывает результат в res.
Перенаправление запросов
HTTP-запрос всегда связан с расположением через поле loc_conf структуры ngx_http_request_t. Это означает, что в любой момент конфигурацию расположения любого модуля можно получить из запроса, вызвав ngx_http_get_module_loc_conf(r, module). Расположение запроса может меняться несколько раз за время его существования. Изначально запросу назначается расположение по умолчанию сервера по умолчанию. Если запрос переключается на другой сервер (выбранный по заголовку HTTP «Host» или расширению SSL SNI), запрос также переключается на расположение по умолчанию этого сервера. Следующее изменение расположения происходит на фазе запроса NGX_HTTP_FIND_CONFIG_PHASE. На этой фазе расположение выбирается по URI запроса среди всех неназванных расположений, настроенных для сервера. Модуль ngx_http_rewrite_module может изменить URI запроса на фазе запроса NGX_HTTP_REWRITE_PHASE в результате применения директивы rewrite и вернуть запрос на фазу NGX_HTTP_FIND_CONFIG_PHASE, чтобы выбрать новое расположение на основе нового URI.
Также запрос можно перенаправить в новое расположение в любой момент, вызвав одну из функций ngx_http_internal_redirect(r, uri, args) или ngx_http_named_location(r, name).
Функция ngx_http_internal_redirect(r, uri, args) изменяет URI запроса и возвращает запрос на фазу NGX_HTTP_SERVER_REWRITE_PHASE. Обработка запроса продолжается в расположении сервера по умолчанию. Позднее, на фазе NGX_HTTP_FIND_CONFIG_PHASE, на основе нового URI запроса выбирается новое расположение.
В следующем примере выполняется внутреннее перенаправление с новыми аргументами запроса.
ngx_int_t
ngx_http_foo_redirect(ngx_http_request_t *r)
{
ngx_str_t uri, args;
ngx_str_set(&uri, "/foo");
ngx_str_set(&args, "bar=1");
return ngx_http_internal_redirect(r, &uri, &args);
}
Функция ngx_http_named_location(r, name) перенаправляет запрос в именованное расположение. Имя расположения передаётся в качестве аргумента. Выполняется поиск расположения среди всех именованных расположений текущего сервера, после чего запрос переключается на фазу NGX_HTTP_REWRITE_PHASE.
В следующем примере выполняется перенаправление в именованное расположение @foo.
ngx_int_t
ngx_http_foo_named_redirect(ngx_http_request_t *r)
{
ngx_str_t name;
ngx_str_set(&name, "foo");
return ngx_http_named_location(r, &name);
}
Обе функции — ngx_http_internal_redirect(r, uri, args) и ngx_http_named_location(r, name) — можно вызывать, когда модули nginx уже сохранили некоторые контексты в поле ctx запроса. Эти контексты могут стать несовместимыми с новой конфигурацией расположения. Чтобы избежать несогласованности, обе функции перенаправления удаляют все контексты запроса.
Вызов ngx_http_internal_redirect(r, uri, args) или ngx_http_named_location(r, name) увеличивает count запроса. Для согласованного подсчёта ссылок на запрос после его перенаправления вызовите ngx_http_finalize_request(r, NGX_DONE). Это завершит текущий путь обработки запроса и уменьшит счётчик.
Перенаправленные и переписанные запросы становятся внутренними и могут обращаться к расположениям internal. Для внутренних запросов установлен флаг internal.
Подзапросы
Подзапросы в первую очередь используются для вставки вывода одного запроса в другой, возможно, вместе с другими данными. Подзапрос похож на обычный запрос, но использует с родительским запросом некоторые общие данные. В частности, общими являются все поля, связанные с вводом от клиента, поскольку подзапрос не получает от клиента никаких других данных. Поле запроса parent для подзапроса содержит ссылку на родительский запрос, а для основного запроса имеет значение NULL. Поле main содержит ссылку на основной запрос в группе запросов.
Подзапрос начинается на фазе NGX_HTTP_SERVER_REWRITE_PHASE. Он проходит те же последующие фазы, что и обычный запрос, и ему назначается расположение на основе его собственного URI.
Заголовок ответа подзапроса всегда игнорируется. ngx_http_postpone_filter размещает тело вывода подзапроса в нужной позиции относительно других данных, сформированных родительским запросом.
Подзапросы связаны с понятием активных запросов. Запрос r считается активным, если c->data == r, где c — объект клиентского соединения. В любой момент времени только активный запрос в группе запросов может передавать свои буферы клиенту. Неактивный запрос всё ещё может передавать свой вывод в цепочку фильтров, но этот вывод не проходит дальше ngx_http_postpone_filter и остаётся в буфере этого фильтра, пока запрос не станет активным. Ниже приведены правила активации запросов:
- Изначально активен основной запрос.
- Первый подзапрос активного запроса становится активным сразу после создания.
-
ngx_http_postpone_filterактивирует следующий запрос в списке подзапросов активного запроса, когда будут отправлены все данные, предшествующие этому запросу. - При завершении запроса активируется его родительский запрос.
Создайте подзапрос вызовом функции ngx_http_subrequest(r, uri, args, psr, ps, flags), где r — родительский запрос, uri и args — URI и аргументы подзапроса, psr — выходной параметр, в который записывается ссылка на созданный подзапрос, ps — объект обратного вызова, уведомляющий родительский запрос о завершении подзапроса, а flags — битовая маска флагов. Доступны следующие флаги:
-
NGX_HTTP_SUBREQUEST_IN_MEMORY— Вывод не отправляется клиенту, а сохраняется в памяти. Этот флаг влияет только на подзапросы, обрабатываемые одним из прокси-модулей. После завершения подзапроса его вывод доступен вr->outтипаngx_buf_t. -
NGX_HTTP_SUBREQUEST_WAITED— Флагdoneподзапроса устанавливается, даже если при завершении подзапрос не является активным. Этот флаг подзапроса используется фильтром SSI. -
NGX_HTTP_SUBREQUEST_CLONE— Подзапрос создаётся как клон родительского запроса. Он начинается в том же расположении и продолжает обработку с той же фазы, что и родительский запрос.
В следующем примере создаётся подзапрос с URI /foo.
ngx_int_t rc;
ngx_str_t uri;
ngx_http_request_t *sr;
...
ngx_str_set(&uri, "/foo");
rc = ngx_http_subrequest(r, &uri, NULL, &sr, NULL, 0);
if (rc == NGX_ERROR) {
/* error */
}
В этом примере текущий запрос клонируется и для подзапроса задаётся обратный вызов завершения.
ngx_int_t
ngx_http_foo_clone(ngx_http_request_t *r)
{
ngx_http_request_t *sr;
ngx_http_post_subrequest_t *ps;
ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
if (ps == NULL) {
return NGX_ERROR;
}
ps->handler = ngx_http_foo_subrequest_done;
ps->data = "foo";
return ngx_http_subrequest(r, &r->uri, &r->args, &sr, ps,
NGX_HTTP_SUBREQUEST_CLONE);
}
ngx_int_t
ngx_http_foo_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
char *msg = (char *) data;
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"done subrequest r:%p msg:%s rc:%i", r, msg, rc);
return rc;
}
Обычно подзапросы создаются в фильтре тела, в таком случае их вывод можно обрабатывать как вывод любого явного запроса. Это означает, что в конечном счёте вывод подзапроса отправляется клиенту после всех явных буферов, переданных до создания подзапроса, и перед любыми буферами, переданными после его создания. Такой порядок сохраняется даже для больших иерархий подзапросов. В следующем примере вывод подзапроса вставляется после всех буферов данных запроса, но перед последним буфером с флагом last_buf.
ngx_int_t
ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_uint_t last;
ngx_chain_t *cl, out;
ngx_http_request_t *sr;
ngx_http_foo_filter_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module);
if (ctx == NULL) {
return ngx_http_next_body_filter(r, in);
}
last = 0;
for (cl = in; cl; cl = cl->next) {
if (cl->buf->last_buf) {
cl->buf->last_buf = 0;
cl->buf->last_in_chain = 1;
cl->buf->sync = 1;
last = 1;
}
}
/* Output explicit output buffers */
rc = ngx_http_next_body_filter(r, in);
if (rc == NGX_ERROR || !last) {
return rc;
}
/*
* Create the subrequest. The output of the subrequest
* will automatically be sent after all preceding buffers,
* but before the last_buf buffer passed later in this function.
*/
if (ngx_http_subrequest(r, ctx->uri, NULL, &sr, NULL, 0) != NGX_OK) {
return NGX_ERROR;
}
ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module);
/* Output the final buffer with the last_buf flag */
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
return NGX_ERROR;
}
b->last_buf = 1;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
Подзапрос можно создавать и для целей, не связанных с выводом данных. Например, модуль ngx_http_auth_request_module создаёт подзапрос на фазе NGX_HTTP_ACCESS_PHASE. Чтобы отключить вывод на этом этапе, для подзапроса устанавливается флаг header_only. Это не позволяет отправить тело подзапроса клиенту. Обратите внимание, что заголовок подзапроса никогда не отправляется клиенту. Результат подзапроса можно проанализировать в обработчике обратного вызова.
Завершение запроса
HTTP-запрос завершается вызовом функции ngx_http_finalize_request(r, rc). Обычно его завершает обработчик содержимого после передачи всех выходных буферов в цепочку фильтров. На этом этапе весь вывод может быть ещё не отправлен клиенту: его часть может оставаться в буфере где-либо в цепочке фильтров. В таком случае функция ngx_http_finalize_request(r, rc) автоматически устанавливает специальный обработчик ngx_http_writer(r) для завершения отправки вывода. Запрос также завершается в случае ошибки или если клиенту необходимо вернуть стандартный код ответа HTTP.
Функция ngx_http_finalize_request(r, rc) принимает следующие значения rc:
-
NGX_DONE— Быстрое завершение. Уменьшитьcountзапроса и уничтожить запрос, если счётчик достиг нуля. После уничтожения текущего запроса клиентское соединение можно использовать для других запросов. -
NGX_ERROR,NGX_HTTP_REQUEST_TIME_OUT(408),NGX_HTTP_CLIENT_CLOSED_REQUEST(499) — Завершение из-за ошибки. Как можно скорее прервать обработку запроса и закрыть клиентское соединение. -
NGX_HTTP_CREATED(201),NGX_HTTP_NO_CONTENT(204), коды, большие или равныеNGX_HTTP_SPECIAL_RESPONSE(300) — Завершение со специальным ответом. Для этих значений nginx либо отправляет клиенту страницу ответа по умолчанию для данного кода, либо выполняет внутреннее перенаправление в расположение error_page, если оно настроено для этого кода. - Другие коды считаются кодами успешного завершения и могут активировать механизм отправки ответа, чтобы закончить передачу тела ответа. После полной отправки тела значение
countзапроса уменьшается. Если оно достигает нуля, запрос уничтожается, но клиентское соединение всё ещё можно использовать для других запросов. Еслиcountбольше нуля, в запросе остаются незавершённые действия, которые будут завершены позднее.
Тело запроса
Для работы с телом клиентского запроса nginx предоставляет функции ngx_http_read_client_request_body(r, post_handler) и ngx_http_discard_request_body(r). Первая функция считывает тело запроса и делает его доступным через поле запроса request_body. Вторая функция указывает nginx отбросить (считать и игнорировать) тело запроса. Для каждого запроса необходимо вызвать одну из этих функций. Обычно это делает обработчик содержимого.
Считывать или отбрасывать тело клиентского запроса из подзапроса нельзя. Это всегда необходимо делать в основном запросе. При создании подзапрос наследует объект request_body родительского запроса, который подзапрос может использовать, если основной запрос ранее считал тело запроса.
Функция ngx_http_read_client_request_body(r, post_handler) запускает процесс чтения тела запроса. Когда тело будет считано полностью, для продолжения обработки запроса вызывается обратный вызов post_handler. Если тело запроса отсутствует или уже было считано, обратный вызов вызывается немедленно. В небуферизованном режиме обратный вызов может быть вызван до того, как будет считано всё тело. Функция ngx_http_read_client_request_body(r, post_handler) выделяет для запроса поле request_body типа ngx_http_request_body_t. В поле bufs этого объекта результат хранится в виде цепочки буферов. Тело может сохраняться в буферах памяти или файловых буферах, если объёма, заданного директивой client_body_buffer_size, недостаточно для хранения всего тела в памяти.
В следующем примере считывается тело клиентского запроса и возвращается его размер.
ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
rc = ngx_http_read_client_request_body(r, ngx_http_foo_init);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
/* error */
return rc;
}
return NGX_DONE;
}
void
ngx_http_foo_init(ngx_http_request_t *r)
{
off_t len;
ngx_buf_t *b;
ngx_int_t rc;
ngx_chain_t *in, out;
if (r->request_body == NULL) {
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
len = 0;
for (in = r->request_body->bufs; in; in = in->next) {
len += ngx_buf_size(in->buf);
}
b = ngx_create_temp_buf(r->pool, NGX_OFF_T_LEN);
if (b == NULL) {
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
b->last = ngx_sprintf(b->pos, "%O", len);
b->last_buf = (r == r->main) ? 1 : 0;
b->last_in_chain = 1;
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = b->last - b->pos;
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
ngx_http_finalize_request(r, rc);
return;
}
out.buf = b;
out.next = NULL;
rc = ngx_http_output_filter(r, &out);
ngx_http_finalize_request(r, rc);
}
Следующие поля запроса определяют способ чтения тела запроса:
-
request_body_in_single_buf— Считать тело в один буфер памяти. -
request_body_in_file_only— Всегда считывать тело в файл, даже если оно помещается в буфер памяти. -
request_body_in_persistent_file— Не удалять файл сразу после создания. Файл с этим флагом можно переместить в другой каталог. -
request_body_in_clean_file— Удалить файл при завершении запроса. Это может быть полезно, если файл предполагалось переместить в другой каталог, но по какой-либо причине он не был перемещён. -
request_body_file_group_access— Разрешить групповой доступ к файлу, заменив маску доступа по умолчанию 0600 на 0660. -
request_body_file_log_level— Уровень важности для журналирования ошибок файла. -
request_body_no_buffering— Считывать тело запроса без буферизации.
Флаг request_body_no_buffering включает небуферизованный режим чтения тела запроса. В этом режиме после вызова ngx_http_read_client_request_body() цепочка bufs может содержать только часть тела. Флаг запроса reading_body указывает, что чтение тела запроса ещё продолжается. Чтобы считать следующую часть, вызовите функцию ngx_http_read_unbuffered_request_body(r). Возвращаемое значение NGX_AGAIN означает, что требуются дополнительные данные тела или место в буфере. Если после вызова этой функции bufs имеет значение NULL, данные тела в данный момент недоступны. Обратный вызов запроса read_event_handler будет вызван, когда появятся новые данные тела от клиента.
Фильтры тела запроса
После считывания части тела запроса она передаётся в цепочку фильтров тела запроса вызовом первого обработчика фильтра тела, хранящегося в переменной ngx_http_top_request_body_filter. Предполагается, что каждый обработчик тела вызывает следующий обработчик в цепочке, пока не будет вызван последний обработчик ngx_http_request_body_save_filter(r, cl). Этот обработчик собирает буферы в r->request_body->bufs и при необходимости записывает их в файл. Для последнего буфера тела запроса флаг last_buf ненулевой.
Если фильтр планирует задерживать буферы данных, при первом вызове ему следует установить флаг r->request_body->filter_need_buffering в значение 1.
Ниже приведён пример простого фильтра тела запроса, который задерживает обработку тела запроса на одну секунду.
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#define NGX_HTTP_DELAY_BODY 1000
typedef struct {
ngx_event_t event;
ngx_chain_t *out;
} ngx_http_delay_body_ctx_t;
static ngx_int_t ngx_http_delay_body_filter(ngx_http_request_t *r,
ngx_chain_t *in);
static void ngx_http_delay_body_cleanup(void *data);
static void ngx_http_delay_body_event_handler(ngx_event_t *ev);
static ngx_int_t ngx_http_delay_body_init(ngx_conf_t *cf);
static ngx_http_module_t ngx_http_delay_body_module_ctx = {
NULL, /* preconfiguration */
ngx_http_delay_body_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_http_delay_body_filter_module = {
NGX_MODULE_V1,
&ngx_http_delay_body_module_ctx, /* module context */
NULL, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_http_request_body_filter_pt ngx_http_next_request_body_filter;
static ngx_int_t
ngx_http_delay_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_int_t rc;
ngx_chain_t *cl, *ln;
ngx_http_cleanup_t *cln;
ngx_http_delay_body_ctx_t *ctx;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"delay request body filter");
ctx = ngx_http_get_module_ctx(r, ngx_http_delay_body_filter_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_delay_body_ctx_t));
if (ctx == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_http_delay_body_filter_module);
r->request_body->filter_need_buffering = 1;
}
if (ngx_chain_add_copy(r->pool, &ctx->out, in) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (!ctx->event.timedout) {
if (!ctx->event.timer_set) {
/* cleanup to remove the timer in case of abnormal termination */
cln = ngx_http_cleanup_add(r, 0);
if (cln == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
cln->handler = ngx_http_delay_body_cleanup;
cln->data = ctx;
/* add timer */
ctx->event.handler = ngx_http_delay_body_event_handler;
ctx->event.data = r;
ctx->event.log = r->connection->log;
ngx_add_timer(&ctx->event, NGX_HTTP_DELAY_BODY);
}
return ngx_http_next_request_body_filter(r, NULL);
}
rc = ngx_http_next_request_body_filter(r, ctx->out);
for (cl = ctx->out; cl; /* void */) {
ln = cl;
cl = cl->next;
ngx_free_chain(r->pool, ln);
}
ctx->out = NULL;
return rc;
}
static void
ngx_http_delay_body_cleanup(void *data)
{
ngx_http_delay_body_ctx_t *ctx = data;
if (ctx->event.timer_set) {
ngx_del_timer(&ctx->event);
}
}
static void
ngx_http_delay_body_event_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
ngx_http_request_t *r;
r = ev->data;
c = r->connection;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"delay request body event");
ngx_post_event(c->read, &ngx_posted_events);
}
static ngx_int_t
ngx_http_delay_body_init(ngx_conf_t *cf)
{
ngx_http_next_request_body_filter = ngx_http_top_request_body_filter;
ngx_http_top_request_body_filter = ngx_http_delay_body_filter;
return NGX_OK;
}
Ответ
В nginx HTTP-ответ формируется отправкой заголовка ответа, за которым следует необязательное тело ответа. Заголовок и тело проходят через цепочку фильтров и в конечном счёте записываются в сокет клиента. Модуль nginx может установить свой обработчик в цепочку фильтров заголовков или тела и обрабатывать вывод, поступающий от предыдущего обработчика.
Заголовок ответа
Функция ngx_http_send_header(r) отправляет заголовок ответа. Не вызывайте эту функцию, пока r->headers_out не содержит все данные, необходимые для формирования заголовка HTTP-ответа. Поле status в r->headers_out должно быть задано всегда. Если код состояния ответа указывает, что за заголовком следует тело ответа, можно также задать content_length_n. Значение этого поля по умолчанию — -1, что означает, что размер тела неизвестен. В этом случае используется блочное кодирование передачи. Чтобы вывести произвольный заголовок, добавьте его в список headers.
static ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_table_elt_t *h;
/* send header */
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = 3;
/* X-Foo: foo */
h = ngx_list_push(&r->headers_out.headers);
if (h == NULL) {
return NGX_ERROR;
}
h->hash = 1;
ngx_str_set(&h->key, "X-Foo");
ngx_str_set(&h->value, "foo");
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
/* send body */
...
}
Фильтры заголовков
Функция ngx_http_send_header(r) вызывает цепочку фильтров заголовков, вызывая первый обработчик фильтра заголовков, хранящийся в переменной ngx_http_top_header_filter. Предполагается, что каждый обработчик заголовков вызывает следующий обработчик в цепочке, пока не будет вызван последний обработчик ngx_http_header_filter(r). Последний обработчик заголовков формирует HTTP-ответ на основе r->headers_out и передаёт его в ngx_http_writer_filter для вывода.
Чтобы добавить обработчик в цепочку фильтров заголовков, во время конфигурирования сохраните его адрес в глобальной переменной ngx_http_top_header_filter. Адрес предыдущего обработчика обычно хранится в статической переменной модуля и вызывается новым обработчиком перед завершением работы.
Следующий пример модуля фильтра заголовков добавляет заголовок HTTP «X-Foo: foo» ко всем ответам со статусом 200.
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf);
static ngx_http_module_t ngx_http_foo_header_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_foo_header_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_http_foo_header_filter_module = {
NGX_MODULE_V1,
&ngx_http_foo_header_filter_module_ctx, /* module context */
NULL, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_int_t
ngx_http_foo_header_filter(ngx_http_request_t *r)
{
ngx_table_elt_t *h;
/*
* The filter handler adds "X-Foo: foo" header
* to every HTTP 200 response
*/
if (r->headers_out.status != NGX_HTTP_OK) {
return ngx_http_next_header_filter(r);
}
h = ngx_list_push(&r->headers_out.headers);
if (h == NULL) {
return NGX_ERROR;
}
h->hash = 1;
ngx_str_set(&h->key, "X-Foo");
ngx_str_set(&h->value, "foo");
return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_foo_header_filter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_foo_header_filter;
return NGX_OK;
}
Тело ответа
Чтобы отправить тело ответа, вызовите функцию ngx_http_output_filter(r, cl). Её можно вызывать несколько раз. При каждом вызове отправляется часть тела ответа в виде цепочки буферов. Установите флаг last_buf в последнем буфере тела.
В следующем примере формируется полный HTTP-ответ с телом "foo". Чтобы пример работал как для подзапроса, так и для основного запроса, для последнего буфера вывода устанавливается флаг last_in_chain. Флаг last_buf устанавливается только для основного запроса, поскольку последний буфер подзапроса не завершает весь вывод.
static ngx_int_t
ngx_http_bar_content_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
/* send header */
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = 3;
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
/* send body */
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
return NGX_ERROR;
}
b->last_buf = (r == r->main) ? 1 : 0;
b->last_in_chain = 1;
b->memory = 1;
b->pos = (u_char *) "foo";
b->last = b->pos + 3;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
Фильтры тела ответа
Функция ngx_http_output_filter(r, cl) запускает цепочку фильтров тела, вызывая первый обработчик фильтра тела, сохраненный в переменной ngx_http_top_body_filter. Предполагается, что каждый обработчик тела вызывает следующий обработчик в цепочке, пока не будет вызван последний обработчик ngx_http_write_filter(r, cl).
Обработчик фильтра тела получает цепочку буферов. Предполагается, что обработчик обрабатывает буферы и передает следующему обработчику, возможно, новую цепочку. Стоит отметить, что звенья цепочки ngx_chain_t входящей цепочки принадлежат вызывающему коду, и их нельзя повторно использовать или изменять. Сразу после завершения работы обработчика вызывающий код может использовать звенья выходной цепочки для отслеживания переданных им буферов. Чтобы сохранить цепочку буферов или заменить некоторые буферы перед передачей следующему фильтру, обработчику необходимо выделить собственные звенья цепочки.
Ниже приведен пример простого фильтра тела, который подсчитывает количество байтов в теле. Результат доступен в переменной $counter, которую можно использовать в журнале доступа.
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
off_t count;
} ngx_http_counter_filter_ctx_t;
static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r,
ngx_chain_t *in);
static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf);
static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf);
static ngx_http_module_t ngx_http_counter_filter_module_ctx = {
ngx_http_counter_add_variables, /* preconfiguration */
ngx_http_counter_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
ngx_module_t ngx_http_counter_filter_module = {
NGX_MODULE_V1,
&ngx_http_counter_filter_module_ctx, /* module context */
NULL, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
static ngx_str_t ngx_http_counter_name = ngx_string("counter");
static ngx_int_t
ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_chain_t *cl;
ngx_http_counter_filter_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_counter_filter_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module);
}
for (cl = in; cl; cl = cl->next) {
ctx->count += ngx_buf_size(cl->buf);
}
return ngx_http_next_body_filter(r, in);
}
static ngx_int_t
ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
uintptr_t data)
{
u_char *p;
ngx_http_counter_filter_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module);
if (ctx == NULL) {
v->not_found = 1;
return NGX_OK;
}
p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
if (p == NULL) {
return NGX_ERROR;
}
v->data = p;
v->len = ngx_sprintf(p, "%O", ctx->count) - p;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
return NGX_OK;
}
static ngx_int_t
ngx_http_counter_add_variables(ngx_conf_t *cf)
{
ngx_http_variable_t *var;
var = ngx_http_add_variable(cf, &ngx_http_counter_name, 0);
if (var == NULL) {
return NGX_ERROR;
}
var->get_handler = ngx_http_counter_variable;
return NGX_OK;
}
static ngx_int_t
ngx_http_counter_filter_init(ngx_conf_t *cf)
{
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_counter_body_filter;
return NGX_OK;
}
Сборка модулей фильтров
При написании фильтра тела или заголовков уделяйте особое внимание его позиции в порядке фильтров. Стандартные модули nginx регистрируют ряд фильтров заголовков и тела. Важно зарегистрировать новый модуль фильтра в правильном месте относительно них. Обычно модули регистрируют фильтры в обработчиках постконфигурации. Порядок вызова фильтров во время обработки, очевидно, обратен порядку их регистрации.
Для сторонних модулей фильтров nginx предоставляет специальный слот HTTP_AUX_FILTER_MODULES. Чтобы зарегистрировать модуль фильтра в этом слоте, установите переменную ngx_module_type в значение HTTP_AUX_FILTER в конфигурации модуля.
В следующем примере показан файл конфигурации модуля, предполагающий, что у модуля есть только один исходный файл — ngx_http_foo_filter_module.c.
ngx_module_type=HTTP_AUX_FILTER ngx_module_name=ngx_http_foo_filter_module ngx_module_srcs="$ngx_addon_dir/ngx_http_foo_filter_module.c" . auto/module
Повторное использование буферов
При выдаче или изменении потока буферов часто желательно повторно использовать выделенные буферы. Стандартный и широко применяемый в коде nginx подход заключается в том, чтобы хранить для этой цели две цепочки буферов: free и busy. В цепочке free хранятся все свободные буферы, которые можно использовать повторно. В цепочке busy хранятся все буферы, отправленные текущим модулем, которые еще используются каким-либо другим обработчиком фильтра. Буфер считается используемым, если его размер больше нуля. Обычно, когда фильтр потребляет буфер, его pos (или file_pos для файлового буфера) перемещается к last (file_last для файлового буфера). После полного потребления буфер готов к повторному использованию. Чтобы добавить недавно освобожденные буферы в цепочку free, достаточно пройти по цепочке busy и переместить буферы нулевого размера в ее начале в free. Эта операция настолько распространена, что для нее существует специальная функция — ngx_chain_update_chains(free, busy, out, tag). Функция добавляет выходную цепочку out в busy и перемещает свободные буферы из начала busy в free. Повторно используются только буферы с указанным tag. Это позволяет модулю повторно использовать только буферы, выделенные им самим.
В следующем примере показан фильтр тела, который вставляет строку «foo» перед каждым входящим буфером. Новые буферы, выделенные модулем, по возможности используются повторно. Обратите внимание, что для правильной работы этого примера также необходимо настроить фильтр заголовков и сбросить content_length_n в значение -1, но соответствующий код здесь не приведен.
typedef struct {
ngx_chain_t *free;
ngx_chain_t *busy;
} ngx_http_foo_filter_ctx_t;
ngx_int_t
ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t *cl, *tl, *out, **ll;
ngx_http_foo_filter_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_foo_filter_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module);
}
/* create a new chain "out" from "in" with all the changes */
ll = &out;
for (cl = in; cl; cl = cl->next) {
/* append "foo" in a reused buffer if possible */
tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
if (tl == NULL) {
return NGX_ERROR;
}
b = tl->buf;
b->tag = (ngx_buf_tag_t) &ngx_http_foo_filter_module;
b->memory = 1;
b->pos = (u_char *) "foo";
b->last = b->pos + 3;
*ll = tl;
ll = &tl->next;
/* append the next incoming buffer */
tl = ngx_alloc_chain_link(r->pool);
if (tl == NULL) {
return NGX_ERROR;
}
tl->buf = cl->buf;
*ll = tl;
ll = &tl->next;
}
*ll = NULL;
/* send the new chain */
rc = ngx_http_next_body_filter(r, out);
/* update "busy" and "free" chains for reuse */
ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
(ngx_buf_tag_t) &ngx_http_foo_filter_module);
return rc;
}
Балансировка нагрузки
Модуль ngx_http_upstream_module предоставляет основные функции, необходимые для передачи запросов на удаленные серверы. Этими функциями пользуются модули, реализующие конкретные протоколы, такие как HTTP или FastCGI. Модуль также предоставляет интерфейс для создания пользовательских модулей балансировки нагрузки и реализует метод балансировки по умолчанию round-robin.
Модули least_conn и hash реализуют альтернативные методы балансировки нагрузки, но фактически являются расширениями модуля балансировки upstream round-robin и используют с ним много общего кода, например представление группы серверов. Модуль keepalive — независимый модуль, расширяющий функциональность upstream.
Модуль ngx_http_upstream_module можно настроить явно, поместив в файл конфигурации соответствующий блок upstream, либо неявно, используя такие директивы, как proxy_pass, принимающие URL, который в некоторый момент преобразуется в список серверов. Альтернативные методы балансировки нагрузки доступны только при явной конфигурации upstream. У конфигурации модуля upstream есть собственный контекст директив NGX_HTTP_UPS_CONF. Структура определена следующим образом:
struct ngx_http_upstream_srv_conf_s {
ngx_http_upstream_peer_t peer;
void **srv_conf;
ngx_array_t *servers; /* ngx_http_upstream_server_t */
ngx_uint_t flags;
ngx_str_t host;
u_char *file_name;
ngx_uint_t line;
in_port_t port;
ngx_uint_t no_port; /* unsigned no_port:1 */
#if (NGX_HTTP_UPSTREAM_ZONE)
ngx_shm_zone_t *shm_zone;
#endif
};
-
srv_conf— контекст конфигурации модулей upstream. -
servers— массивngx_http_upstream_server_t, результат разбора набора директив server в блокеupstream. -
flags— флаги, в основном указывающие, какие функции поддерживаются методом балансировки нагрузки. Функции настраиваются с помощью параметров директивы server:-
NGX_HTTP_UPSTREAM_CREATE— отличает явно заданные upstream от автоматически созданных директивой proxy_pass и «родственными» директивами (FastCGI, SCGI и т. д.). -
NGX_HTTP_UPSTREAM_WEIGHT— поддерживается параметр «weight» -
NGX_HTTP_UPSTREAM_MAX_FAILS— поддерживается параметр «max_fails» -
NGX_HTTP_UPSTREAM_FAIL_TIMEOUT— поддерживается параметр «fail_timeout» -
NGX_HTTP_UPSTREAM_DOWN— поддерживается параметр «down» -
NGX_HTTP_UPSTREAM_BACKUP— поддерживается параметр «backup» -
NGX_HTTP_UPSTREAM_MAX_CONNS— поддерживается параметр «max_conns»
-
-
host— имя upstream. -
file_name, line— имя файла конфигурации и строка, в которой находится блокupstream. -
portиno_port— не используются для явно заданных групп upstream. -
shm_zone— зона разделяемой памяти, используемая этой группой upstream, если она есть. -
peer— объект, содержащий общие методы инициализации конфигурации upstream:typedef struct { ngx_http_upstream_init_pt init_upstream; ngx_http_upstream_init_peer_pt init; void *data; } ngx_http_upstream_peer_t;. Модуль, реализующий алгоритм балансировки нагрузки, должен задать эти методы и инициализировать закрытую структуруdata. Еслиinit_upstreamне была инициализирована при разборе конфигурации,ngx_http_upstream_moduleзадает для нее алгоритмngx_http_upstream_init_round_robinпо умолчанию.-
init_upstream(cf, us)— метод, отвечающий во время конфигурации за инициализацию группы серверов и инициализацию методаinit()в случае успеха. Типичный модуль балансировки нагрузки использует список серверов в блокеupstreamдля создания эффективной структуры данных, использует ее и сохраняет собственную конфигурацию в полеdata. -
init(r, us)— инициализирует структуруngx_http_upstream_peer_t.peerдля отдельного запроса, используемую для балансировки нагрузки (не путать с описанной вышеngx_http_upstream_srv_conf_t.peer, которая относится к отдельному upstream). Она передается как аргументdataвсем обратным вызовам, связанным с выбором сервера.
-
Когда nginx должен передать запрос другому узлу для обработки, он использует настроенный метод балансировки нагрузки, чтобы получить адрес для подключения. Метод берется из объекта ngx_http_upstream_t.peer типа ngx_peer_connection_t:
struct ngx_peer_connection_s {
...
struct sockaddr *sockaddr;
socklen_t socklen;
ngx_str_t *name;
ngx_uint_t tries;
ngx_event_get_peer_pt get;
ngx_event_free_peer_pt free;
ngx_event_notify_peer_pt notify;
void *data;
#if (NGX_SSL || NGX_COMPAT)
ngx_event_set_peer_session_pt set_session;
ngx_event_save_peer_session_pt save_session;
#endif
...
};
Структура содержит следующие поля:
-
sockaddr,socklen,name— адрес upstream-сервера, к которому нужно подключиться; это выходные параметры метода балансировки нагрузки. -
data— данные метода балансировки нагрузки для отдельного запроса; содержат состояние алгоритма выбора и обычно включают ссылку на конфигурацию upstream. Передаются как аргумент всем методам, связанным с выбором сервера (см. ниже). -
tries— допустимое число попыток подключения к upstream-серверу. -
get,free,notify,set_sessionиsave_session— методы модуля балансировки нагрузки, описанные ниже.
Все методы принимают как минимум два аргумента: объект соединения с узлом pc и структуру data, созданную методом ngx_http_upstream_srv_conf_t.peer.init(). Обратите внимание, что она может отличаться от pc.data из-за «цепочки» модулей балансировки нагрузки.
-
get(pc, data)— метод, вызываемый, когда модуль upstream готов передать запрос upstream-серверу и ему нужно узнать его адрес. Метод должен заполнить поляsockaddr,socklenиnameструктурыngx_peer_connection_t. Возможны следующие возвращаемые значения:-
NGX_OK— сервер выбран. -
NGX_ERROR— произошла внутренняя ошибка. -
NGX_BUSY— в данный момент нет доступных серверов. Это может произойти по разным причинам: динамическая группа серверов пуста, все серверы в группе находятся в состоянии сбоя или все серверы в группе уже обрабатывают максимально допустимое число соединений. -
NGX_DONE— нижележащее соединение было повторно использовано, поэтому создавать новое соединение с upstream-сервером не нужно. Это значение задается модулемkeepalive.
-
-
free(pc, data, state)— метод, вызываемый, когда модуль upstream завершил работу с определенным сервером. Аргументstate— статус завершения соединения upstream, битовая маска со следующими возможными значениями:-
NGX_PEER_FAILED— попытка была неудачной -
NGX_PEER_NEXT— особый случай, когда upstream-сервер возвращает коды403или404, которые не считаются сбоем. -
NGX_PEER_KEEPALIVE— в настоящее время не используется
tries. -
-
notify(pc, data, type)— в настоящее время не используется в версии OSS. -
set_session(pc, data)иsave_session(pc, data)— методы, относящиеся к SSL и позволяющие кэшировать сеансы с upstream-серверами. Их реализация предоставляется методом балансировки round-robin.
Примеры
В репозитории nginx-dev-examples приведены примеры модулей nginx.
Стиль кода
Общие правила
- максимальная длина строки — 80 символов
- отступ — 4 пробела
- табуляция и пробелы в конце строки не допускаются
- элементы списка в одной строке разделяются пробелами
- шестнадцатеричные литералы записываются строчными буквами
- имена файлов, функций и типов, а также глобальные переменные имеют префикс
ngx_или более конкретный префикс, напримерngx_http_иngx_mail_
size_t
ngx_utf8_length(u_char *p, size_t n)
{
u_char c, *last;
size_t len;
last = p + n;
for (len = 0; p < last; len++) {
c = *p;
if (c < 0x80) {
p++;
continue;
}
if (ngx_utf8_decode(&p, last - p) > 0x10ffff) {
/* invalid UTF-8 */
return n;
}
}
return len;
}
Файлы
Типичный исходный файл может содержать следующие разделы, разделенные двумя пустыми строками:
- сведения об авторских правах
- директивы включения
- определения препроцессора
- определения типов
- прототипы функций
- определения переменных
- определения функций
Сведения об авторских правах выглядят так:
/* * Copyright (C) Author Name * Copyright (C) Organization, Inc. */
Если файл значительно изменен, список авторов следует обновить, добавив нового автора в начало.
Файлы ngx_config.h и ngx_core.h всегда включаются первыми, за ними следует один из файлов ngx_http.h, ngx_stream.h или ngx_mail.h. Затем идут необязательные внешние заголовочные файлы:
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> #include <libxml/parser.h> #include <libxml/tree.h> #include <libxslt/xslt.h> #if (NGX_HAVE_EXSLT) #include <libexslt/exslt.h> #endif
В заголовочных файлах должна использоваться так называемая «защита заголовка»:
#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_ #define _NGX_PROCESS_CYCLE_H_INCLUDED_ ... #endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */
Комментарии
- комментарии «
//» не используются - текст пишется на английском языке, предпочтительно использовать американский вариант написания
- многострочные комментарии оформляются следующим образом:
/* * The red-black tree code is based on the algorithm described in * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest. */
/* find the server configuration for the address:port */
Препроцессор
Имена макросов начинаются с префикса ngx_ или NGX_ (либо более конкретного префикса). Имена макросов-констант пишутся заглавными буквами. Параметризованные макросы и макросы для инициализаторов пишутся строчными буквами. Имя макроса и его значение разделяются как минимум двумя пробелами:
#define NGX_CONF_BUFFER 4096
#define ngx_buf_in_memory(b) (b->temporary || b->memory || b->mmap)
#define ngx_buf_size(b) \
(ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos): \
(b->file_last - b->file_pos))
#define ngx_null_string { 0, NULL }
Условия заключаются в скобки, отрицание указывается снаружи:
#if (NGX_HAVE_KQUEUE)
...
#elif ((NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \
|| (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT)))
...
#elif (NGX_HAVE_EPOLL && !(NGX_TEST_BUILD_EPOLL))
...
#elif (NGX_HAVE_POLL)
...
#else /* select */
...
#endif /* NGX_HAVE_KQUEUE */
Типы
Имена типов заканчиваются суффиксом «_t». Имя определяемого типа отделяется как минимум двумя пробелами:
typedef ngx_uint_t ngx_rbtree_key_t;
Структурные типы определяются с помощью typedef. Внутри структур типы и имена членов выравниваются:
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
Сохраняйте одинаковое выравнивание во всех структурах файла. Структура, содержащая указатель на саму себя, имеет имя, оканчивающееся на «_s». Соседние определения структур разделяются двумя пустыми строками:
typedef struct ngx_list_part_s ngx_list_part_t;
struct ngx_list_part_s {
void *elts;
ngx_uint_t nelts;
ngx_list_part_t *next;
};
typedef struct {
ngx_list_part_t *last;
ngx_list_part_t part;
size_t size;
ngx_uint_t nalloc;
ngx_pool_t *pool;
} ngx_list_t;
Каждый член структуры объявляется в отдельной строке:
typedef struct {
ngx_uint_t hash;
ngx_str_t key;
ngx_str_t value;
u_char *lowcase_key;
} ngx_table_elt_t;
Указатели на функции внутри структур имеют определенные типы, оканчивающиеся на «_pt»:
typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
off_t limit);
typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
off_t limit);
typedef struct {
ngx_recv_pt recv;
ngx_recv_chain_pt recv_chain;
ngx_recv_pt udp_recv;
ngx_send_pt send;
ngx_send_pt udp_send;
ngx_send_chain_pt udp_send_chain;
ngx_send_chain_pt send_chain;
ngx_uint_t flags;
} ngx_os_io_t;
Типы перечислений оканчиваются на «_e»:
typedef enum {
ngx_http_fastcgi_st_version = 0,
ngx_http_fastcgi_st_type,
...
ngx_http_fastcgi_st_padding
} ngx_http_fastcgi_state_e;
Переменные
Переменные объявляются в порядке возрастания длины базового типа, а затем по алфавиту. Имена типов и переменных выравниваются. «Столбцы» типа и имени разделяются двумя пробелами. Большие массивы размещаются в конце блока объявлений:
u_char | | *rv, *p; ngx_conf_t | | *cf; ngx_uint_t | | i, j, k; unsigned int | | len; struct sockaddr | | *sa; const unsigned char | | *data; ngx_peer_connection_t | | *pc; ngx_http_core_srv_conf_t | |**cscfp; ngx_http_upstream_srv_conf_t| | *us, *uscf; u_char | | text[NGX_SOCKADDR_STRLEN];
Статические и глобальные переменные можно инициализировать при объявлении:
static ngx_str_t ngx_http_memcached_key = ngx_string("memcached_key");
static ngx_uint_t mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static uint32_t ngx_crc32_table16[] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
...
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};
Существует ряд часто используемых сочетаний типов и имен:
u_char *rv; ngx_int_t rc; ngx_conf_t *cf; ngx_connection_t *c; ngx_http_request_t *r; ngx_peer_connection_t *pc; ngx_http_upstream_srv_conf_t *us, *uscf;
Функции
Для всех функций (даже статических) должны быть объявлены прототипы. Прототипы включают имена аргументов. Длинные прототипы переносятся с одинарным отступом в строках продолжения:
static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf,
ngx_http_core_main_conf_t *cmcf);
static char *ngx_http_merge_servers(ngx_conf_t *cf,
ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module,
ngx_uint_t ctx_index);
Имя функции в определении начинается с новой строки. Открывающая и закрывающая фигурные скобки тела функции размещаются на отдельных строках. Тело функции имеет отступ. Между функциями оставляются две пустые строки:
static ngx_int_t
ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len)
{
...
}
static ngx_int_t
ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
...
}
После имени функции и открывающей скобки пробел не ставится. Длинные вызовы функций переносятся так, чтобы строки продолжения начинались на позиции первого аргумента функции. Если это невозможно, первую строку продолжения следует отформатировать так, чтобы она заканчивалась на позиции 79:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http header: \"%V: %V\"",
&h->key, &h->value);
hc->busy = ngx_palloc(r->connection->pool,
cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
Вместо inline следует использовать макрос ngx_inline:
static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);
Выражения
Бинарные операторы, кроме «.» и «−>», отделяются от операндов одним пробелом. Унарные операторы и индексы не отделяются от операндов пробелами:
width = width * 10 + (*fmt++ - '0');
ch = (u_char) ((decoded << 4) + (ch - '0'));
r->exten.data = &r->uri.data[i + 1];
Приведения типов отделяются одним пробелом от выражений, к которым они применяются. Звездочка внутри приведения типа отделяется пробелом от имени типа:
len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 1);
Если выражение не помещается в одну строку, его переносят. Предпочтительное место переноса — бинарный оператор. Строка продолжения выравнивается по началу выражения:
if (status == NGX_HTTP_MOVED_PERMANENTLY
|| status == NGX_HTTP_MOVED_TEMPORARILY
|| status == NGX_HTTP_SEE_OTHER
|| status == NGX_HTTP_TEMPORARY_REDIRECT
|| status == NGX_HTTP_PERMANENT_REDIRECT)
{
...
}
p->temp_file->warn = "an upstream response is buffered "
"to a temporary file";
В крайнем случае выражение можно перенести так, чтобы строка продолжения заканчивалась на позиции 79:
hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
+ size * sizeof(ngx_hash_elt_t *));
Приведенные выше правила также применяются к подвыражениям, каждое из которых имеет собственный уровень отступа:
if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING)
|| c->stale_updating) && !r->background
&& u->conf->cache_background_update)
{
...
}
Иногда удобно переносить выражение после приведения типа. В этом случае строка продолжения имеет отступ:
node = (ngx_rbtree_node_t *)
((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
Указатели явно сравниваются с NULL (а не с 0):
if (ptr != NULL) {
...
}
Условные конструкции и циклы
Ключевое слово «if» отделяется от условия одним пробелом. Открывающая фигурная скобка располагается в той же строке или, если условие занимает несколько строк, в отдельной строке. Закрывающая скобка располагается в отдельной строке, за которой при необходимости следует «else if / else». Обычно перед частью «else if / else» оставляется пустая строка:
if (node->left == sentinel) {
temp = node->right;
subst = node;
} else if (node->right == sentinel) {
temp = node->left;
subst = node;
} else {
subst = ngx_rbtree_min(node->right, sentinel);
if (subst->left != sentinel) {
temp = subst->left;
} else {
temp = subst->right;
}
}
Аналогичные правила форматирования применяются к циклам «do» и «while»:
while (p < last && *p == ' ') {
p++;
}
do {
ctx->node = rn;
ctx = ctx->next;
} while (ctx);
Ключевое слово «switch» отделяется от условия одним пробелом. Открывающая фигурная скобка располагается в той же строке. Закрывающая скобка располагается в отдельной строке. Ключевые слова «case» выравниваются по «switch»:
switch (ch) {
case '!':
looked = 2;
state = ssi_comment0_state;
break;
case '<':
copy_end = p;
break;
default:
copy_end = p;
looked = 0;
state = ssi_start_state;
break;
}
Большинство циклов «for» оформляются следующим образом:
for (i = 0; i < ccf->env.nelts; i++) {
...
}
for (q = ngx_queue_head(locations);
q != ngx_queue_sentinel(locations);
q = ngx_queue_next(q))
{
...
}
Если какая-либо часть оператора «for» опущена, это обозначается комментарием «/* void */»:
for (i = 0; /* void */ ; i++) {
...
}
Цикл с пустым телом также обозначается комментарием «/* void */», который можно поместить в ту же строку:
for (cl = *busy; cl->next; cl = cl->next) { /* void */ }
Бесконечный цикл выглядит так:
for ( ;; ) {
...
}
Метки
Метки отделяются пустыми строками и имеют отступ предыдущего уровня:
if (i == 0) {
u->err = "host not found";
goto failed;
}
u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t));
if (u->addrs == NULL) {
goto failed;
}
u->naddrs = i;
...
return NGX_OK;
failed:
freeaddrinfo(res);
return NGX_ERROR;
Отладка проблем с памятью
Для отладки проблем с памятью, таких как переполнение буфера или ошибки использования освобожденной памяти, можно использовать AddressSanitizer (ASan), поддерживаемый некоторыми современными компиляторами. Чтобы включить ASan с gcc и clang, используйте параметр компилятора и компоновщика -fsanitize=address. При сборке nginx этот параметр можно добавить в параметры --with-cc-opt и --with-ld-opt сценария configure.
Поскольку в nginx большинство выделений памяти выполняется из внутреннего пула nginx, включения ASan может быть недостаточно для отладки проблем с памятью. Внутренний пул выделяет большой участок памяти у системы и нарезает из него более мелкие блоки. Однако этот механизм можно отключить, установив макрос NGX_DEBUG_PALLOC в значение 1. В этом случае выделение памяти выполняется непосредственно системным распределителем, который полностью контролирует границы буферов.
Следующая строка конфигурации обобщает приведенные выше сведения. Ее рекомендуется использовать при разработке сторонних модулей и тестировании nginx на разных платформах.
auto/configure --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1'
--with-ld-opt=-fsanitize=address
Распространенные ошибки
Написание модуля на C
Самая распространенная ошибка — попытка написать полноценный модуль на C, хотя этого можно избежать. В большинстве случаев задачу можно решить, создав подходящую конфигурацию. Если без написания модуля не обойтись, постарайтесь сделать его как можно меньше и проще. Например, модуль может экспортировать только некоторые переменные.
Перед началом работы над модулем рассмотрите следующие вопросы:
- Можно ли реализовать нужную функциональность с помощью уже доступных модулей?
- Можно ли решить задачу с помощью встроенных языков сценариев, таких как Perl или njs?
Строки C
Самый распространенный в nginx тип строк, ngx_str_t, не является строкой в стиле C с нулевым завершающим символом. Нельзя передавать данные стандартным функциям библиотеки C, таким как strlen() или strstr(). Вместо этого следует использовать аналоги nginx, принимающие ngx_str_t, либо указатель на данные и их длину. Однако бывают случаи, когда ngx_str_t содержит указатель на строку с нулевым завершающим символом: строки, полученные в результате разбора файла конфигурации, завершаются нулевым символом.
Глобальные переменные
Избегайте использования глобальных переменных в своих модулях. Скорее всего, наличие глобальной переменной — ошибка. Все глобальные данные должны быть привязаны к циклу конфигурации и выделяться из соответствующего пула памяти. Это позволяет nginx выполнять плавную перезагрузку конфигурации. Попытка использовать глобальные переменные, скорее всего, нарушит эту возможность, поскольку станет невозможно одновременно иметь две конфигурации и освобождать их. Иногда глобальные переменные необходимы. В таком случае нужно уделить особое внимание корректной обработке переконфигурации. Также проверьте, нет ли у используемых вашим кодом библиотек неявного глобального состояния, которое может нарушиться при перезагрузке.
Ручное управление памятью
Вместо того чтобы использовать подверженный ошибкам подход malloc/free, научитесь работать с пулами nginx. Пул создается и привязывается к объекту — конфигурации, циклу, соединению или HTTP-запросу. При уничтожении объекта уничтожается и связанный с ним пул. Поэтому при работе с объектом можно выделить нужный объем памяти из соответствующего пула и не беспокоиться об освобождении памяти даже в случае ошибок.
Потоки
В nginx рекомендуется избегать использования потоков, поскольку это неизбежно нарушит работу: большинство функций nginx не являются потокобезопасными. Предполагается, что поток будет выполнять только системные вызовы и потокобезопасные библиотечные функции. Если нужно запускать код, не связанный с обработкой запросов клиентов, правильный способ — запланировать таймер в обработчике модуля init_process и выполнить необходимые действия в обработчике таймера. Внутри nginx использует потоки для ускорения операций ввода-вывода, но это особый случай со множеством ограничений.
Блокирующие библиотеки
Распространенная ошибка — использование библиотек, блокирующих выполнение внутри себя. Большинство существующих библиотек по своей природе синхронны и блокирующие. Другими словами, они выполняют по одной операции за раз и тратят время на ожидание ответа от другого узла. В результате при обработке запроса с помощью такой библиотеки блокируется весь рабочий процесс nginx, что приводит к снижению производительности. Используйте только библиотеки с асинхронным интерфейсом, которые не блокируют весь процесс.
HTTP-запросы к внешним сервисам
Часто модулям необходимо выполнять HTTP-вызовы к внешним сервисам. Распространенная ошибка — использовать для этого внешнюю библиотеку, например libcurl. Совершенно не нужно подключать большой объем внешнего (возможно, блокирующего!) кода для задачи, которую nginx может выполнить самостоятельно.
Есть два основных сценария, в которых требуется внешний запрос:
- в контексте обработки клиентского запроса (например, в обработчике содержимого)
- в контексте рабочего процесса (например, в обработчике таймера)
В первом случае лучше всего использовать API подзапросов. Вместо прямого обращения к внешнему сервису объявите расположение в конфигурации nginx и направьте подзапрос на него. Это расположение не ограничивается проксированием запросов, в нем можно использовать и другие директивы nginx. Пример такого подхода — директива auth_request, реализованная в модуле ngx_http_auth_request.
Во втором случае можно использовать базовые функции HTTP-клиента, доступные в nginx. Например, в модуле OCSP реализован простой HTTP-клиент.
© 2002-2021 Igor Sysoev
© 2011-2026 Nginx, Inc.
Licensed under the BSD License.
https://nginx.org/en/docs/dev/development_guide.html