Руководство разработчика#
Правила оформления кода#
Исходный код следует следующей структуре и соглашениям. Следующие два оператора В дополнение к этому, HTTP код должен включать Почтовый код должен включать Потоковый код должен включать Для общих целей код Angie использует два целочисленных типа,
Большинство функций в Angie возвращают следующие коды: Макрос Значения Пример использования Для C строк Angie использует указатель на беззнаковый символьный тип
Строковый тип Angie Поле Операции со строками в Angie объявлены в
Другие строковые функции специфичны для Angie: Следующие функции выполняют преобразование регистра и сравнение: Следующие макросы упрощают инициализацию строк: Следующие функции форматирования поддерживают специфичные для Angie типы: Полный список опций форматирования, поддерживаемых этими функциями, находится
в Вы можете добавить префикс В Angie реализовано несколько функций для преобразования чисел.
Первые четыре преобразуют строку заданной длины в положительное целое число
указанного типа.
Они возвращают Есть две дополнительные функции преобразования чисел.
Как и первые четыре, они возвращают Интерфейс регулярных выражений в Angie является оберткой вокруг
библиотеки PCRE.
Соответствующий заголовочный файл — Чтобы использовать регулярное выражение для сопоставления строк, его сначала нужно
скомпилировать, что обычно делается на этапе конфигурации.
Обратите внимание, что поскольку поддержка PCRE опциональна, весь код, использующий интерфейс, должен
быть защищен окружающим макросом После успешной компиляции поля Скомпилированное регулярное выражение затем может использоваться для сопоставления со строками: Аргументы Если есть совпадения, к захватам можно получить доступ следующим образом: Функция Структура Структура Для получения текущего времени обычно достаточно обратиться к одной из
доступных глобальных переменных, представляющих кэшированное значение времени в желаемом
формате. Доступные строковые представления: Макросы Для явного получения времени используйте Следующие функции преобразуют Функция Структура кода#
auto — Скрипты сборкиsrccore — Базовые типы и функции — строки, массивы, журнал,
пул и т.д.event — Ядро событийmodules — Модули уведомления о событиях:
epoll, kqueue, select
и т.д.http — Основной HTTP модуль и общий кодmodules — Другие HTTP модулиv2 — HTTP/2mail — Почтовые модулиos — Платформо-зависимый кодunixwin32stream — Потоковые модулиВключаемые файлы#
#include должны присутствовать в
начале каждого файла Angie:#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_mail.h>
#include <ngx_stream.h>
Целые числа#
ngx_int_t и ngx_uint_t, которые являются
typedef'ами для intptr_t и uintptr_t
соответственно.Общие коды возврата#
NGX_OK — Операция выполнена успешно.NGX_ERROR — Операция завершилась неудачей.NGX_AGAIN — Операция не завершена; вызовите функцию снова.NGX_DECLINED — Операция отклонена, например, потому что она
отключена в конфигурации. Это никогда не является ошибкой.NGX_BUSY — Ресурс недоступен.NGX_DONE — Операция завершена или продолжена в другом месте.
Также используется как альтернативный код успеха.NGX_ABORT — Функция была прервана.
Также используется как альтернативный код ошибки.Обработка ошибок#
ngx_errno возвращает последний код системной ошибки.
Он сопоставлен с errno на POSIX платформах и с
вызовом GetLastError() в Windows.
Макрос ngx_socket_errno возвращает последний номер ошибки
сокета.
Как и макрос ngx_errno, он сопоставлен с
errno на POSIX платформах.
Он сопоставлен с вызовом WSAGetLastError() в Windows.
Обращение к значениям ngx_errno или
ngx_socket_errno более одного раза подряд может вызвать
проблемы с производительностью.
Если значение ошибки может использоваться несколько раз, сохраните его в локальной переменной
типа ngx_err_t.
Для установки ошибок используйте макросы ngx_set_errno(errno) и
ngx_set_socket_errno(errno).ngx_errno и
ngx_socket_errno могут быть переданы функциям журналирования
ngx_log_error() и ngx_log_debugX(), в
этом случае текст системной ошибки добавляется к сообщению журнала.ngx_errno:ngx_int_t
ngx_my_kill(ngx_pid_t pid, ngx_log_t *log, int signo)
{
ngx_err_t err;
if (kill(pid, signo) == -1) {
err = ngx_errno;
ngx_log_error(NGX_LOG_ALERT, log, err, "kill(%P, %d) failed", pid, signo);
if (err == NGX_ESRCH) {
return 2;
}
return 1;
}
return 0;
}
Строки#
Обзор#
u_char *.ngx_str_t определен следующим образом:typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
len содержит длину строки, а
data содержит данные строки.
Строка, содержащаяся в ngx_str_t, может быть завершена
нулевым символом после len байт, а может и не быть.
В большинстве случаев она не завершена нулевым символом.
Однако в некоторых частях кода (например, при разборе конфигурации)
объекты ngx_str_t заведомо завершены нулевым символом, что
упрощает сравнение строк и облегчает передачу строк в
системные вызовы.src/core/ngx_string.h.
Некоторые из них являются обертками вокруг стандартных функций C:ngx_strcmp()ngx_strncmp()ngx_strstr()ngx_strlen()ngx_strchr()ngx_memcmp()ngx_memset()ngx_memcpy()ngx_memmove()ngx_memzero() — Заполняет память нулями.ngx_explicit_memzero() — Делает то же самое, что и
ngx_memzero(), но этот вызов никогда не удаляется
оптимизацией компилятора по устранению мертвых записей.
Эта функция может использоваться для очистки чувствительных данных, таких как пароли и ключи.ngx_cpymem() — Делает то же самое, что и
ngx_memcpy(), но возвращает конечный адрес назначения.
Это удобно для последовательного добавления нескольких строк.ngx_movemem() — Делает то же самое, что и
ngx_memmove(), но возвращает конечный адрес назначения.ngx_strlchr() — Ищет символ в строке,
ограниченной двумя указателями.ngx_tolower()ngx_toupper()ngx_strlow()ngx_strcasecmp()ngx_strncasecmp()ngx_string(text) — статический инициализатор для типа
ngx_str_t из строкового литерала C
textngx_null_string — статический инициализатор пустой строки для типа
ngx_str_tngx_str_set(str, text) — инициализирует строку
str типа ngx_str_t * строковым литералом C
textngx_str_null(str) — инициализирует строку str
типа ngx_str_t * пустой строкойФорматирование#
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.Преобразование чисел#
NGX_ERROR при ошибке.ngx_atoi(line, n) — ngx_int_tngx_atosz(line, n) — ssize_tngx_atoof(line, n) — off_tngx_atotm(line, n) — time_tNGX_ERROR при ошибке.ngx_atofp(line, n, point) — Преобразует число с фиксированной точкой
заданной длины в положительное целое число типа
ngx_int_t.
Результат сдвигается влево на point десятичных
позиций.
Ожидается, что строковое представление числа имеет не более
point дробных цифр.
Например, ngx_atofp("10.5", 4, 2) возвращает
1050.ngx_hextoi(line, n) — Преобразует шестнадцатеричное представление
положительного целого числа в ngx_int_t.Регулярные выражения#
src/core/ngx_regex.h.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) {
/* строка соответствует выражению */
} else if (n == NGX_REGEX_NO_MATCHED) {
/* совпадение не найдено */
} else {
/* какая-то ошибка */
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;
/* все захваты */
for (i = 0; i < n * 2; i += 2) {
value.data = input.data + captures[i];
value.len = captures[i + 1] - captures[i];
}
/* доступ к именованным захватам */
size = rc.name_size;
p = rc.names;
for (i = 0; i < rc.named_captures; i++, p += size) {
/* имя захвата */
name.data = &p[2];
name.len = ngx_strlen(name.data);
n = 2 * ((p[0] << 8) + p[1]);
/* захваченное значение */
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 — используется в записях syslog:
"Sep 28 12:00:00"ngx_cached_http_time — используется в HTTP-заголовках:
"Mon, 28 Sep 1970 06:00:00 GMT"ngx_cached_http_log_iso8601 — стандартный формат ISO 8601:
"1970-09-28T12:00:00+06:00"ngx_time() и ngx_timeofday()
возвращают текущее значение времени в секундах и являются предпочтительным способом доступа
к кэшированному значению времени.ngx_gettimeofday(),
которая обновляет свой аргумент (указатель на
struct timeval).
Время всегда обновляется, когда Angie возвращается в цикл событий из системных
вызовов.
Для немедленного обновления времени вызовите ngx_time_update(),
или ngx_time_sigsafe_update(), если обновляете время в
контексте обработчика сигналов.time_t в указанное
разложенное представление времени.
Первая функция в каждой паре преобразует time_t в
ngx_tm_t, а вторая (с инфиксом _libc_)
в struct tm:ngx_gmtime(), ngx_libc_gmtime() — время, выраженное как UTCngx_localtime(), ngx_libc_localtime() — время, выраженное
относительно местного часового поясаngx_http_time(buf, time) возвращает строковое
представление, подходящее для использования в HTTP-заголовках (например,
"Mon, 28 Sep 1970 06:00:00 GMT").
Функция ngx_http_cookie_time(buf, time) возвращает строковое
представление, подходящее для HTTP-куки ("Thu, 31-Dec-37 23:55:55 GMT").
Контейнеры#
Массив#
Тип массива Angie 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;
/* создать массив строк с предварительно выделенной памятью для 10 элементов */
a = ngx_array_create(pool, 10, sizeof(ngx_str_t));
/* инициализировать массив строк для 10 элементов */
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);
Список#
В Angie список представляет собой последовательность массивов, оптимизированную для вставки потенциально
большого количества элементов.
Тип списка 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) { /* ошибка */ }
/* добавить элементы в список */
v = ngx_list_push(list);
if (v == NULL) { /* ошибка */ }
ngx_str_set(v, "foo");
v = ngx_list_push(list);
if (v == NULL) { /* ошибка */ }
ngx_str_set(v, "bar");
/* итерация по списку */
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 в
ноль.
Элементы, помеченные таким образом, явно пропускаются при итерации
по заголовкам.
Очередь#
В Angie очередь представляет собой интрузивный двусвязный список, где каждый узел определяется как следует:
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) { /* ошибка */ }
ngx_str_set(&f->value, "foo");
ngx_queue_insert_tail(&values, &f->queue);
/* вставить больше узлов здесь */
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;
/* пользовательские данные для дерева */
} my_tree_t;
typedef struct {
ngx_rbtree_node_t rbnode;
/* пользовательские данные для узла */
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.
Поддерживается как точное, так и шаблонное сопоставление.
Последнее требует дополнительной настройки и описано в отдельном разделе ниже.
Перед инициализацией хеша необходимо знать количество элементов, которые он будет
содержать, чтобы Angie мог построить его оптимально.
Два параметра, которые необходимо настроить: 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) {
/* ключ не найден */
}
Шаблонное сопоставление#
Для создания хеша, работающего с шаблонами, используйте тип
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"; - будет соответствовать ".example.org" */
/* key = "foo.example.com"; - будет соответствовать "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().
Пул#
Большинство выделений памяти в Angie выполняется в пулах. Память, выделенная в пуле Angie, автоматически освобождается при уничтожении пула. Это обеспечивает хорошую производительность выделения памяти и упрощает контроль памяти.
Пул внутренне выделяет объекты в непрерывных блоках памяти. Когда блок заполняется, выделяется новый блок и добавляется в список блоков памяти пула. Когда запрашиваемое выделение слишком велико для размещения в блоке, запрос перенаправляется системному аллокатору, а возвращенный указатель сохраняется в пуле для последующего освобождения.
Тип для пулов Angie — 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) активно используются в Angie,
поэтому реализация пула Angie предоставляет способ их повторного использования.
Поле chain структуры ngx_pool_t хранит
список ранее выделенных звеньев, готовых к повторному использованию.
Для эффективного выделения звена цепочки в пуле используйте функцию
ngx_alloc_chain_link(pool).
Эта функция ищет свободное звено цепочки в списке пула и выделяет новое
звено цепочки, если список пула пуст.
Для освобождения звена вызовите функцию ngx_free_chain(pool, cl).
В пуле могут быть зарегистрированы обработчики очистки. Обработчик очистки — это обратный вызов с аргументом, который вызывается при уничтожении пула. Пул обычно связан с конкретным объектом Angie (например, 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);
}
Логирование#
Для логирования Angie использует объекты ngx_log_t.
Логгер Angie поддерживает несколько типов вывода:
stderr — логирование в стандартный поток ошибок (stderr)
file — логирование в файл
syslog — логирование в syslog
memory — логирование во внутреннее хранилище памяти для целей разработки; к памяти можно получить доступ позже с помощью отладчика
Экземпляр логгера может быть цепочкой логгеров, связанных друг с другом через
поле next.
В этом случае каждое сообщение записывается во все логгеры в цепочке.
Для каждого логгера уровень серьезности контролирует, какие сообщения записываются в лог (логируются только события, назначенные этому уровню или выше). Поддерживаются следующие уровни серьезности:
NGX_LOG_EMERGNGX_LOG_ALERTNGX_LOG_CRITNGX_LOG_ERRNGX_LOG_WARNNGX_LOG_NOTICENGX_LOG_INFONGX_LOG_DEBUG
Для отладочного логирования также проверяется маска отладки. Маски отладки:
NGX_LOG_DEBUG_CORENGX_LOG_DEBUG_ALLOCNGX_LOG_DEBUG_MUTEXNGX_LOG_DEBUG_EVENTNGX_LOG_DEBUG_HTTPNGX_LOG_DEBUG_MAILNGX_LOG_DEBUG_STREAM
Обычно логгеры создаются существующим кодом Angie из
директив 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
Цикл#
Объект цикла хранит контекст выполнения Angie, созданный из конкретной
конфигурации.
Его тип — ngx_cycle_t.
Текущий цикл ссылается на глобальную переменную ngx_cycle и
наследуется рабочими процессами Angie при их запуске.
Каждый раз при перезагрузке конфигурации Angie создается новый цикл из
новой конфигурации Angie; старый цикл обычно удаляется после успешного
создания нового.
Цикл создается функцией ngx_init_cycle(), которая
принимает предыдущий цикл в качестве аргумента.
Функция находит файл конфигурации предыдущего цикла и наследует как
можно больше ресурсов от предыдущего цикла.
Цикл-заполнитель, называемый "init cycle", создается при запуске Angie, затем
заменяется фактическим циклом, построенным из конфигурации.
Члены цикла включают:
pool— пул цикла. Создается для каждого нового цикла.log— журнал цикла. Первоначально наследуется от старого цикла, устанавливается для указания наnew_logпосле чтения конфигурации.new_log— журнал цикла, созданный конфигурацией. На него влияет директиваerror_logкорневой области видимости.connections,connection_n— массив соединений типаngx_connection_t, созданный модулем событий при инициализации каждого рабочего процесса Angie. Директиваworker_connectionsв конфигурации Angie устанавливает количество соединенийconnection_n.free_connections,free_connection_n— список и количество доступных в данный момент соединений. Если соединения недоступны, рабочий процесс Angie отказывается принимать новых клиентов или подключаться к проксируемым серверам.files,files_n— массив для сопоставления файловых дескрипторов с соединениями Angie. Это сопоставление используется модулями событий, имеющими флагNGX_USE_FD_EVENT(в настоящее время этоpollиdevpoll).conf_ctx— массив конфигураций основных модулей. Конфигурации создаются и заполняются во время чтения файлов конфигурации Angie.modules,modules_n— массив модулей типаngx_module_t, как статических, так и динамических, загруженных текущей конфигурацией.listening— массив объектов прослушивания типаngx_listening_t. Объекты прослушивания обычно добавляются директивойlistenразличных модулей, которые вызывают функциюngx_create_listening(). Сокеты прослушивания создаются на основе объектов прослушивания.paths— массив путей типаngx_path_t. Пути добавляются вызовом функцииngx_add_path()из модулей, которые собираются работать с определенными каталогами. Эти каталоги создаются Angie после чтения конфигурации, если отсутствуют. Более того, для каждого пути можно добавить два обработчика:загрузчик пути — выполняется только один раз в 60 секунд после запуска или перезагрузки Angie. Обычно загрузчик читает каталог и сохраняет данные в разделяемой памяти Angie. Обработчик вызывается из выделенного процесса Angie "cache loader".
менеджер пути — выполняется периодически. Обычно менеджер удаляет старые файлы из каталога и обновляет память Angie для отражения изменений. Обработчик вызывается из выделенного процесса "cache manager".
open_files— список объектов открытых файлов типаngx_open_file_t, которые создаются вызовом функцииngx_conf_open_file(). В настоящее время Angie использует такие открытые файлы для журналирования. После чтения конфигурации Angie открывает все файлы в спискеopen_filesи сохраняет каждый файловый дескриптор в полеfdобъекта. Файлы открываются в режиме добавления и создаются, если отсутствуют. Файлы в списке переоткрываются рабочими процессами Angie при получении сигнала переоткрытия (чаще всегоUSR1). В этом случае дескриптор в полеfdизменяется на новое значение.shared_memory— список зон разделяемой памяти, каждая добавляется вызовом функцииngx_shared_memory_add(). Разделяемые зоны отображаются на один и тот же диапазон адресов во всех процессах Angie и используются для совместного использования общих данных, например, дерева HTTP-кеша в памяти.
Буфер#
Для операций ввода/вывода Angie предоставляет тип буфера
ngx_buf_t.
Обычно он используется для хранения данных, которые должны быть записаны в место назначения или прочитаны из
источника.
Буфер может ссылаться на данные в памяти или в файле, и технически
возможно, чтобы буфер ссылался на оба одновременно.
Память для буфера выделяется отдельно и не связана со структурой
буфера ngx_buf_t.
Структура ngx_buf_t имеет следующие поля:
start,end— границы блока памяти, выделенного для буфера.pos,last— границы буфера памяти; обычно подмножествоstart..end.file_pos,file_last— границы файлового буфера, выраженные как смещения от начала файла.tag— уникальное значение, используемое для различения буферов; создается различными модулями Angie, обычно с целью повторного использования буфера.file— объект файла.temporary— флаг, указывающий, что буфер ссылается на записываемую память.memory— флаг, указывающий, что буфер ссылается на память только для чтения.in_file— флаг, указывающий, что буфер ссылается на данные в файле.flush— флаг, указывающий, что все данные перед буфером должны быть сброшены.recycled— флаг, указывающий, что буфер может быть повторно использован и должен быть обработан как можно скорее.sync— флаг, указывающий, что буфер не несет данных или специальный сигнал, такой какflushилиlast_buf. По умолчанию Angie считает такие буферы условием ошибки, но этот флаг говорит Angie пропустить проверку ошибки.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— флаг, указывающий, что соединение повторно используется и должно быть закрыто.
Соединение Angie может прозрачно инкапсулировать SSL-слой.
В этом случае поле ssl соединения содержит указатель на
структуру ngx_ssl_connection_t, хранящую все SSL-связанные данные
для соединения, включая SSL_CTX и
SSL.
Обработчики recv, send,
recv_chain и send_chain также устанавливаются
в SSL-совместимые функции.
Директива worker_connections в конфигурации Angie
ограничивает количество соединений на один рабочий процесс Angie.
Все структуры соединений предварительно создаются при запуске рабочего процесса и сохраняются в
поле connections объекта цикла.
Для получения структуры соединения используйте функцию
ngx_get_connection(s, log).
Она принимает в качестве аргумента s дескриптор сокета, который нужно
обернуть в структуру соединения.
Поскольку количество соединений на рабочий процесс ограничено, Angie предоставляет
способ захватывать соединения, которые в данный момент используются.
Для включения или отключения повторного использования соединения вызовите функцию
ngx_reusable_connection(c, reusable).
Вызов ngx_reusable_connection(c, 1) устанавливает
флаг reuse в структуре соединения и вставляет
соединение в reusable_connections_queue цикла.
Когда ngx_get_connection() обнаруживает, что нет
доступных соединений в списке free_connections цикла,
она вызывает ngx_drain_connections() для освобождения
определенного количества повторно используемых соединений.
Для каждого такого соединения устанавливается флаг close и вызывается его обработчик чтения,
который должен освободить соединение, вызвав
ngx_close_connection(c) и сделать его доступным для повторного использования.
Для выхода из состояния, когда соединение может быть повторно использовано,
вызывается ngx_reusable_connection(c, 0).
HTTP-соединения клиентов являются примером повторно используемых соединений в Angie; они
помечаются как повторно используемые до тех пор, пока первый байт запроса не будет получен от клиента.
События#
Событие#
Объект события ngx_event_t в Angie предоставляет механизм
для уведомления о том, что произошло определенное событие.
Поля в ngx_event_t включают:
data— произвольный контекст события, используемый в обработчиках событий, обычно как указатель на соединение, связанное с событием.handler— функция обратного вызова, которая будет вызвана при наступлении события.write— флаг, указывающий на событие записи. Отсутствие флага указывает на событие чтения.active— флаг, указывающий, что событие зарегистрировано для получения уведомлений ввода-вывода, обычно от механизмов уведомления, таких какepoll,kqueue,poll.ready— флаг, указывающий, что событие получило уведомление ввода-вывода.delayed— флаг, указывающий, что ввод-вывод задержан из-за ограничения скорости.timer— узел красно-черного дерева для вставки события в дерево таймеров.timer_set— флаг, указывающий, что таймер события установлен и еще не истек.timedout— флаг, указывающий, что таймер события истек.eof— флаг, указывающий, что произошел EOF при чтении данных.pending_eof— флаг, указывающий, что EOF ожидается на сокете, даже если могут быть доступны некоторые данные перед ним. Флаг доставляется через событиеEPOLLRDHUPepollили флагEV_EOFkqueue.error— флаг, указывающий, что произошла ошибка во время чтения (для события чтения) или записи (для события записи).cancelable— флаг события таймера, указывающий, что событие должно игнорироваться при завершении работы рабочего процесса. Корректное завершение работы рабочего процесса задерживается до тех пор, пока не останется запланированных неотменяемых событий таймера.posted— флаг, указывающий, что событие помещено в очередь.queue— узел очереди для помещения события в очередь.
События ввода-вывода#
Каждое соединение, полученное путем вызова функции ngx_get_connection(),
имеет два прикрепленных события, c->read и
c->write, которые используются для получения уведомления о том, что
сокет готов для чтения или записи.
Все такие события работают в режиме Edge-Triggered, что означает, что они
запускают уведомления только при изменении состояния сокета.
Например, выполнение частичного чтения из сокета не заставляет Angie доставить
повторное уведомление о чтении до тех пор, пока в сокет не поступят новые данные.
Даже когда базовый механизм уведомлений ввода-вывода по существу является
Level-Triggered (poll, select и т.д.), Angie
преобразует уведомления в Edge-Triggered.
Чтобы сделать уведомления о событиях Angie согласованными во всех системах
уведомлений на разных платформах, функции
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 и представляет время,
когда происходит событие.
Структура дерева обеспечивает быстрые операции вставки и удаления, а также
доступ к ближайшим тайм-аутам, что Angie использует для определения того,
как долго ждать событий ввода-вывода и для истечения событий тайм-аута.
Отложенные события#
Событие может быть отложено, что означает, что его обработчик будет вызван
в какой-то момент позже в рамках текущей итерации цикла событий.
Откладывание событий является хорошей практикой для упрощения кода и избежания
переполнения стека.
Отложенные события хранятся в очереди отложенных событий.
Макрос 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 */ }
}
Цикл событий#
За исключением главного процесса Angie, все процессы Angie выполняют операции ввода-вывода и поэтому имеют цикл событий.
(Главный процесс Angie вместо этого проводит большую часть времени в вызове
sigsuspend(), ожидая поступления сигналов.)
Цикл событий Angie реализован в функции
ngx_process_events_and_timers(), которая вызывается
многократно до завершения процесса.
Цикл событий имеет следующие этапы:
Найти таймаут, который ближе всего к истечению, вызвав
ngx_event_find_timer(). Эта функция находит самый левый узел в дереве таймеров и возвращает количество миллисекунд до истечения узла.Обработать события ввода-вывода, вызвав обработчик, специфичный для механизма уведомления о событиях, выбранного конфигурацией Angie. Этот обработчик ожидает возникновения по крайней мере одного события ввода-вывода, но только до истечения следующего таймаута. Когда происходит событие чтения или записи, устанавливается флаг
readyи вызывается обработчик события. Для Linux обычно используется обработчикngx_epoll_process_events(), который вызываетepoll_wait()для ожидания событий ввода-вывода.Истечь таймеры, вызвав
ngx_event_expire_timers(). Дерево таймеров обходится от самого левого элемента вправо до тех пор, пока не будет найден неистекший таймаут. Для каждого истекшего узла устанавливается флаг событияtimedout, сбрасывается флагtimer_setи вызывается обработчик события.Обработать отложенные события, вызвав
ngx_event_process_posted(). Функция многократно удаляет первый элемент из очереди отложенных событий и вызывает обработчик элемента, пока очередь не станет пустой.
Все процессы Angie также обрабатывают сигналы.
Обработчики сигналов только устанавливают глобальные переменные, которые проверяются после
вызова ngx_process_events_and_timers().
Процессы#
В Angie существует несколько типов процессов.
Тип процесса хранится в глобальной переменной 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().
Процессы Angie обрабатывают следующие сигналы:
NGX_SHUTDOWN_SIGNAL(SIGQUITв большинстве систем) — корректное завершение работы. При получении этого сигнала главный процесс отправляет сигнал завершения всем дочерним процессам. Когда дочерних процессов не остается, главный процесс уничтожает пул циклов и завершается. Когда рабочий процесс получает этот сигнал, он закрывает все слушающие сокеты и ожидает, пока не останется запланированных неотменяемых событий, затем уничтожает пул циклов и завершается. Когда менеджер кеша или процесс загрузчика кеша получает этот сигнал, он завершается немедленно. Переменнаяngx_quitустанавливается в1, когда процесс получает этот сигнал, и немедленно сбрасывается после обработки. Переменнаяngx_exitingустанавливается в1, пока рабочий процесс находится в состоянии завершения.NGX_TERMINATE_SIGNAL(SIGTERMв большинстве систем) — завершить. При получении этого сигнала главный процесс отправляет сигнал завершения всем дочерним процессам. Если дочерний процесс не завершается в течение 1 секунды, главный процесс отправляет сигналSIGKILLдля его принудительного завершения. Когда дочерних процессов не остается, главный процесс уничтожает пул циклов и завершается. Когда рабочий процесс, процесс менеджера кеша или процесс загрузчика кеша получает этот сигнал, он уничтожает пул циклов и завершается. Переменнаяngx_terminateустанавливается в1при получении этого сигнала.NGX_NOACCEPT_SIGNAL(SIGWINCHв большинстве систем) — завершить все рабочие и вспомогательные процессы. При получении этого сигнала главный процесс завершает свои дочерние процессы. Если ранее запущенный новый двоичный файл Angie завершается, дочерние процессы старого главного процесса запускаются снова. Когда рабочий процесс получает этот сигнал, он завершается в режиме отладки, установленном директивойdebug_points.NGX_RECONFIGURE_SIGNAL(SIGHUPв большинстве систем) — перенастроить. При получении этого сигнала главный процесс перечитывает конфигурацию и создает новый цикл на ее основе. Если новый цикл создан успешно, старый цикл удаляется и запускаются новые дочерние процессы. Тем временем старые дочерние процессы получают сигналNGX_SHUTDOWN_SIGNAL. В однопроцессном режиме Angie создает новый цикл, но сохраняет старый до тех пор, пока больше нет клиентов с активными соединениями, привязанными к нему. Рабочие и вспомогательные процессы игнорируют этот сигнал.NGX_REOPEN_SIGNAL(SIGUSR1в большинстве систем) — переоткрыть файлы. Главный процесс отправляет этот сигнал рабочим процессам, которые переоткрывают всеopen_files, связанные с циклом.NGX_CHANGEBIN_SIGNAL(SIGUSR2в большинстве систем) — изменить исполняемый файл Angie. Главный процесс запускает новый исполняемый файл Angie и передает ему список всех прослушиваемых сокетов. Список в текстовом формате, передаваемый в переменной окружения"NGINX", состоит из номеров дескрипторов, разделенных точками с запятой. Новый исполняемый файл Angie читает переменную"NGINX"и добавляет сокеты в свой цикл инициализации. Другие процессы игнорируют этот сигнал.
Хотя все рабочие процессы Angie способны получать и правильно обрабатывать POSIX
сигналы, главный процесс не использует стандартный системный вызов kill()
для передачи сигналов рабочим процессам и вспомогательным процессам.
Вместо этого Angie использует межпроцессные пары сокетов, которые позволяют отправлять сообщения
между всеми процессами Angie.
В настоящее время, однако, сообщения отправляются только от главного процесса к его дочерним процессам.
Сообщения несут стандартные сигналы.
Потоки#
Возможно выгрузить в отдельный поток задачи, которые иначе заблокировали бы рабочий процесс Angie. Например, Angie можно настроить на использование потоков для выполнения файлового ввода-вывода. Другой случай использования — библиотека, которая не имеет асинхронного интерфейса и поэтому не может быть нормально использована с Angie. Имейте в виду, что интерфейс потоков является помощником для существующего асинхронного подхода к обработке клиентских соединений, и ни в коем случае не предназначен в качестве замены.
Для работы с синхронизацией доступны следующие обертки над
примитивами 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);
Вместо создания нового потока для каждой задачи Angie реализует стратегию пула потоков. Несколько пулов потоков могут быть настроены для разных целей (например, для выполнения ввода-вывода на разных наборах дисков). Каждый пул потоков создается при запуске и содержит ограниченное количество потоков, которые обрабатывают очередь задач. Когда задача завершена, вызывается предопределенный обработчик завершения.
Заголовочный файл 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 Angie 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;
}
Модули#
Добавление новых модулей#
Каждый отдельный модуль Angie находится в отдельном каталоге, который содержит
как минимум два файла:
config и файл с исходным кодом модуля.
Файл config содержит всю информацию, необходимую Angie для
интеграции модуля, например:
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 shell-скриптом, который может устанавливать
и обращаться к следующим переменным:
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. Следующие макросы могут использоваться для связывания с теми же библиотеками, что и Angie: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в списке модулей, чтобы обработчик фильтра выполнялся после обработчика фильтра копирования. Для других типов модулей по умолчанию используется пустая строка.
Для компиляции модуля в Angie статически используйте аргумент
--add-module=/path/to/module для скрипта configure.
Для компиляции модуля для последующей динамической загрузки в Angie используйте аргумент
--add-dynamic-module=/path/to/module.
Основные модули#
Модули являются строительными блоками Angie, и большая часть его функциональности
реализована в виде модулей.
Исходный файл модуля должен содержать глобальную переменную типа
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, и может быть вызван на определенных стадиях
жизненного цикла Angie.
Жизненный цикл модуля состоит из следующих событий:
Обработчики директив конфигурации вызываются по мере их появления в файлах конфигурации в контексте главного процесса.
После успешного разбора конфигурации вызывается обработчик
init_moduleв контексте главного процесса. Обработчикinit_moduleвызывается в главном процессе каждый раз при загрузке конфигурации.Главный процесс создает один или несколько рабочих процессов и обработчик
init_processвызывается в каждом из них.Когда рабочий процесс получает команду завершения или остановки от главного процесса, он вызывает обработчик
exit_process.Главный процесс вызывает обработчик
exit_masterперед выходом.
Поскольку потоки используются в Angie только как вспомогательное средство ввода-вывода со своим
собственным 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
— указатели на функции, которые создают и инициализируют конфигурацию модуля
соответственно.
Для основных модулей Angie вызывает 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— В блокеevents.NGX_DIRECT_CONF— Используется модулями, которые не создают иерархию контекстов и имеют только одну глобальную конфигурацию. Эта конфигурация передается обработчику как аргументconf.
Парсер конфигурации использует эти флаги для выдачи ошибки в случае неправильно размещенной директивы и вызывает обработчики директив, снабженные соответствующим указателем конфигурации, так что одни и те же директивы в разных местах могут сохранять свои значения в отдельных местах.
Поле set определяет обработчик, который обрабатывает директиву
и сохраняет разобранные значения в соответствующую конфигурацию.
Существует ряд функций, которые выполняют общие преобразования:
ngx_conf_set_flag_slot— Преобразует литеральные строкиonиoffв значениеngx_flag_tсо значениями 1 или 0, соответственно.ngx_conf_set_str_slot— Сохраняет строку как значение типаngx_str_t.ngx_conf_set_str_array_slot— Добавляет значение в массивngx_array_tстрокngx_str_t. Массив создается, если еще не существует.ngx_conf_set_keyval_slot— Добавляет пару ключ-значение в массивngx_array_tпар ключ-значениеngx_keyval_t. Первая строка становится ключом, а вторая — значением. Массив создается, если еще не существует.ngx_conf_set_num_slot— Преобразует аргумент директивы в значениеngx_int_t.ngx_conf_set_size_slot— Преобразует размер в значениеsize_t, выраженное в байтах.ngx_conf_set_off_slot— Преобразует смещение в значениеoff_t, выраженное в байтах.ngx_conf_set_msec_slot— Преобразует время в значениеngx_msec_t, выраженное в миллисекундах.ngx_conf_set_sec_slot— Преобразует время в значениеtime_t, выраженное в секундах.ngx_conf_set_bufs_slot— Преобразует два предоставленных аргумента в объектngx_bufs_t, который содержит количество и размер буферов.ngx_conf_set_enum_slot— Преобразует предоставленный аргумент в значениеngx_uint_t. Завершающийся нулем массивngx_conf_enum_t, переданный в полеpost, определяет допустимые строки и соответствующие целочисленные значения.ngx_conf_set_bitmask_slot— Преобразует предоставленные аргументы в значениеngx_uint_t. Значения масок для каждого аргумента объединяются операцией ИЛИ, создавая результат. Завершающийся нулем массивngx_conf_bitmask_t, переданный в полеpost, определяет допустимые строки и соответствующие значения масок.ngx_conf_set_path_slot— Преобразует предоставленные аргументы в значениеngx_path_tи выполняет все необходимые инициализации. Подробности см. в документации для директивы proxy_temp_path.ngx_conf_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для обертывания вновь принятого клиентского сокета. Каждый слушатель Angie предоставляет обработчик для передачи нового объекта соединения. Для HTTP-соединений этоngx_http_init_connection(c).ngx_http_init_connection()выполняет раннюю инициализацию HTTP-соединения. На этом этапе создается объектngx_http_connection_tдля соединения, и его ссылка сохраняется в полеdataсоединения. Позже он будет заменен объектом HTTP-запроса. На этом этапе также запускается парсер протокола PROXY и SSL-рукопожатие.ngx_http_wait_request_handler()обработчик события чтения вызывается, когда данные доступны на клиентском сокете. На этом этапе создается объект HTTP-запросаngx_http_request_tи устанавливается в полеdataсоединения.ngx_http_process_request_line()обработчик события чтения читает строку запроса клиента. Обработчик устанавливаетсяngx_http_wait_request_handler(). Данные читаются вbufferсоединения. Размер буфера изначально устанавливается директивой client_header_buffer_size. Предполагается, что весь заголовок клиента поместится в буфер. Если начального размера недостаточно, выделяется больший буфер с емкостью, установленной директивой large_client_header_buffers.ngx_http_process_request_headers()обработчик события чтения устанавливается послеngx_http_process_request_line()для чтения заголовка запроса клиента.ngx_http_core_run_phases()вызывается, когда заголовок запроса полностью прочитан и разобран. Эта функция запускает фазы запроса отNGX_HTTP_POST_READ_PHASEдоNGX_HTTP_CONTENT_PHASE. Последняя фаза предназначена для генерации ответа и передачи его по цепочке фильтров. Ответ не обязательно отправляется клиенту на этой фазе. Он может остаться в буфере и быть отправленным на этапе финализации.ngx_http_finalize_request()обычно вызывается, когда запрос сгенерировал весь вывод или произвел ошибку. В последнем случае ищется соответствующая страница ошибки и используется как ответ. Если ответ не полностью отправлен клиенту к этому моменту, активируется HTTP-писательngx_http_writer()для завершения отправки оставшихся данных.ngx_http_finalize_connection()вызывается, когда полный ответ отправлен клиенту и запрос может быть уничтожен. Если включена функция keepalive клиентского соединения, вызываетсяngx_http_set_keepalive(), которая уничтожает текущий запрос и ожидает следующий запрос на соединении. В противном случаеngx_http_close_request()уничтожает как запрос, так и соединение.
Запрос#
Для каждого HTTP-запроса клиента создается объект ngx_http_request_t.
Некоторые поля этого объекта:
connection— Указатель на объект клиентского соединенияngx_connection_t. Несколько запросов могут ссылаться на один и тот же объект соединения одновременно - один основной запрос и его подзапросы. После удаления запроса на том же соединении может быть создан новый запрос.Обратите внимание, что для HTTP-соединений поле
dataобъектаngx_connection_tуказывает обратно на запрос. Такие запросы называются активными, в отличие от других запросов, привязанных к соединению. Активный запрос используется для обработки событий клиентского соединения и может выводить свой ответ клиенту. Обычно каждый запрос становится активным в какой-то момент, чтобы отправить свой вывод.ctx— Массив контекстов HTTP-модулей. Каждый модуль типаNGX_HTTP_MODULEможет хранить любое значение (обычно указатель на структуру) в запросе. Значение сохраняется в массивеctxв позицииctx_indexмодуля. Следующие макросы предоставляют удобный способ получения и установки контекстов запроса:ngx_http_get_module_ctx(r, module)— Возвращает контекстmodulengx_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— Список выходных буферов и подзапросов в том порядке, в котором они отправляются и создаются. Список используется фильтром postpone для обеспечения согласованного вывода запроса, когда части его создаются подзапросами.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— Индекс текущей фазы запроса.phase_handler— Индекс текущей фазы запроса.ncaptures,captures,captures_data— Захваты регулярных выражений, полученные при последнем совпадении регулярного выражения запроса. Совпадение регулярного выражения может произойти в нескольких местах во время обработки запроса: поиск в карте, поиск сервера по SNI или HTTP Host, перезапись, proxy_redirect и т.д. Захваты, полученные при поиске, сохраняются в вышеупомянутых полях. Полеncapturesсодержит количество захватов,capturesсодержит границы захватов, аcaptures_dataсодержит строку, с которой сравнивалось регулярное выражение и которая используется для извлечения захватов. После каждого нового совпадения регулярного выражения захваты запроса сбрасываются для хранения новых значений.count— Счетчик ссылок запроса. Поле имеет смысл только для основного запроса. Увеличение счетчика выполняется простымr->main->count++. Для уменьшения счетчика вызовитеngx_http_finalize_request(r, rc). Создание подзапроса и запуск процесса чтения тела запроса увеличивают счетчик.subrequests— Текущий уровень вложенности подзапросов. Каждый подзапрос наследует уровень вложенности своего родителя, уменьшенный на единицу. Если значение достигает нуля, генерируется ошибка. Значение для основного запроса определяется константойNGX_HTTP_MAX_SUBREQUESTS.uri_changes— Количество оставшихся изменений URI для запроса. Общее количество раз, когда запрос может изменить свой URI, ограничено константойNGX_HTTP_MAX_URI_CHANGES. С каждым изменением значение уменьшается до тех пор, пока не достигнет нуля, после чего генерируется ошибка. Перезаписи и внутренние перенаправления к обычным или именованным местоположениям считаются изменениями URI.blocked— Счетчик блокировок, удерживаемых на запросе. Пока это значение не равно нулю, запрос не может быть завершен. В настоящее время это значение увеличивается ожидающими операциями AIO (POSIX AIO и операции потоков) и активной блокировкой кэша.buffered— Битовая маска, показывающая, какие модули буферизовали вывод, произведенный запросом. Ряд фильтров может буферизовать вывод; например, sub_filter может буферизовать данные из-за частичного совпадения строки, copy filter может буферизовать данные из-за отсутствия свободных буферов вывода и т.д. Пока это значение не равно нулю, запрос не завершается в ожидании сброса.header_only— Флаг, указывающий, что вывод не требует тела. Например, этот флаг используется HTTP-запросами HEAD.keepalive— Флаг, указывающий, поддерживается ли keepalive клиентского соединения. Значение выводится из версии HTTP и значения заголовка "Connection".header_sent— Флаг, указывающий, что заголовок вывода уже был отправлен запросом.internal— Флаг, указывающий, что текущий запрос является внутренним. Чтобы войти во внутреннее состояние, запрос должен пройти через внутреннее перенаправление или быть подзапросом. Внутренним запросам разрешено входить во внутренние местоположения.allow_ranges— Флаг, указывающий, что частичный ответ может быть отправлен клиенту, как запрошено заголовком HTTP Range.subrequest_ranges— Флаг, указывающий, что частичный ответ может быть отправлен во время обработки подзапроса.single_range— Флаг, указывающий, что только один непрерывный диапазон выходных данных может быть отправлен клиенту. Этот флаг обычно устанавливается при отправке потока данных, например, от прокси-сервера, и весь ответ недоступен в одном буфере.main_filter_need_in_memory,filter_need_in_memory— Флаги, запрашивающие, чтобы вывод производился в буферах памяти, а не в файлах. Это сигнал для copy filter читать данные из файловых буферов, даже если sendfile включен. Разница между двумя флагами заключается в расположении модулей фильтров, которые их устанавливают. Фильтры, вызываемые до фильтра postpone в цепочке фильтров, устанавливаютfilter_need_in_memory, запрашивая, чтобы только вывод текущего запроса поступал в буферы памяти. Фильтры, вызываемые позже в цепочке фильтров, устанавливаютmain_filter_need_in_memory, запрашивая, чтобы как основной запрос, так и все подзапросы читали файлы в памяти при отправке вывода.filter_need_temporary— Флаг, запрашивающий, чтобы вывод запроса производился во временных буферах, но не в буферах памяти только для чтения или файловых буферах. Это используется фильтрами, которые могут изменять вывод непосредственно в буферах, где он отправляется.
Конфигурация#
Каждый HTTP-модуль может иметь три типа конфигурации:
Основная конфигурация — Применяется ко всему блоку
http. Функционирует как глобальные настройки для модуля.Конфигурация сервера — Применяется к одному блоку
server. Функционирует как специфичные для сервера настройки модуля.Конфигурация местоположения — Применяется к одному блоку
location,ifилиlimit_except. Функционирует как специфичные для местоположения настройки модуля.
Структуры конфигурации создаются на этапе конфигурации Angie путем
вызова функций, которые выделяют структуры, инициализируют их
и объединяют их.
Следующий пример показывает, как создать простую конфигурацию местоположения
для модуля.
Конфигурация имеет одну настройку, foo, типа
unsigned integer.
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() объединяет конфигурацию с
конфигурацией более высокого уровня.
На самом деле, конфигурации сервера и location существуют не только на уровнях
сервера и location, но также создаются для всех уровней выше них.
В частности, конфигурация сервера также создается на главном уровне, а
конфигурации location создаются на главном уровне, уровне сервера и уровне location.
Эти конфигурации позволяют указывать настройки, специфичные для сервера и location,
на любом уровне конфигурационного файла Angie.
В конечном итоге конфигурации объединяются вниз по иерархии.
Предоставляется ряд макросов, таких как NGX_CONF_UNSET и
NGX_CONF_UNSET_UINT, для обозначения отсутствующей настройки
и игнорирования ее при объединении.
Стандартные макросы объединения Angie, такие как 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)
Следующий пример получает указатель на конфигурацию location
стандартного основного модуля
и заменяет обработчик содержимого location, хранящийся
в поле 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.
Главная конфигурация запроса никогда не изменяется.
Конфигурация сервера может измениться с настроек по умолчанию после
выбора виртуального сервера для запроса.
Конфигурация location, выбранная для обработки запроса, может изменяться
несколько раз в результате операции перезаписи или внутреннего перенаправления.
Следующий пример показывает, как получить доступ к 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-запрос проходит через последовательность фаз. В каждой фазе выполняется определенный тип обработки запроса. Специфичные для модуля обработчики могут быть зарегистрированы в большинстве фаз, и многие стандартные модули Angie регистрируют свои обработчики фаз как способ быть вызванными на определенной стадии обработки запроса. Фазы обрабатываются последовательно, и обработчики фаз вызываются, как только запрос достигает фазы. Ниже приведен список фаз Angie HTTP.
NGX_HTTP_POST_READ_PHASE— Первая фаза. RealIP регистрирует свой обработчик на этой фазе, чтобы включить замену адресов клиентов до вызова любого другого модуля.NGX_HTTP_SERVER_REWRITE_PHASE— Фаза, где обрабатываются директивы rewrite, определенные в блокеserver(но вне блокаlocation). Rewrite устанавливает свой обработчик на этой фазе.NGX_HTTP_FIND_CONFIG_PHASE— Специальная фаза, где выбираетсяlocationна основе URI запроса. До этой фазы запросу назначаетсяlocationпо умолчанию для соответствующего виртуального сервера, и любой модуль, запрашивающий конфигурациюlocation, получает конфигурацию дляlocationсервера по умолчанию. Эта фаза назначает новыйlocationзапросу. На этой фазе нельзя зарегистрировать дополнительные обработчики.NGX_HTTP_REWRITE_PHASE— То же самое, что иNGX_HTTP_SERVER_REWRITE_PHASE, но для правил rewrite, определенных вlocation, выбранном на предыдущей фазе.NGX_HTTP_POST_REWRITE_PHASE— Специальная фаза, где запрос перенаправляется в новыйlocation, если его URI изменился во время rewrite. Это реализуется путем прохождения запроса черезNGX_HTTP_FIND_CONFIG_PHASEснова. На этой фазе нельзя зарегистрировать дополнительные обработчики.NGX_HTTP_PREACCESS_PHASE— Общая фаза для различных типов обработчиков, не связанных с контролем доступа. Стандартные модули Angie Limit Conn и Limit Req регистрируют свои обработчики на этой фазе.NGX_HTTP_ACCESS_PHASE— Фаза, где проверяется, что клиент авторизован для выполнения запроса. Стандартные модули Angie, такие как Access и Auth Basic регистрируют свои обработчики на этой фазе. По умолчанию клиент должен пройти проверку авторизации всех обработчиков, зарегистрированных на этой фазе, чтобы запрос продолжился к следующей фазе. Директива satisfy может использоваться для разрешения продолжения обработки, если любой из обработчиков фазы авторизует клиента.NGX_HTTP_POST_ACCESS_PHASE— Специальная фаза, где обрабатывается директива satisfy. Если некоторые обработчики фазы доступа запретили доступ и ни один явно не разрешил его, запрос завершается. На этой фазе нельзя зарегистрировать дополнительные обработчики.NGX_HTTP_PRECONTENT_PHASE— Фаза для обработчиков, которые должны быть вызваны до генерации контента. Стандартные модули, такие как try_files и Mirror, регистрируют свои обработчики на этой фазе.NGX_HTTP_CONTENT_PHASE— Фаза, где обычно генерируется ответ. Множество стандартных модулей Angie регистрируют свои обработчики на этой фазе, включая Index. Они вызываются последовательно, пока один из них не произведет вывод. Также возможно установить обработчики контента для каждогоlocationотдельно. Если в конфигурацииlocationHTTP-модуль установленhandler, он установленhandler, он вызывается как обработчик контента, и обработчики, установленные на этой фазе, игнорируются.NGX_HTTP_LOG_PHASE— Фаза, где выполняется логирование запроса. В настоящее время только Log регистрирует свой обработчик на этой стадии для логирования доступа. Обработчики фазы логирования вызываются в самом конце обработки запроса, прямо перед освобождением запроса.
Ниже приведен пример обработчика фазы preaccess.
static ngx_http_module_t ngx_http_foo_module_ctx = {
NULL, /* preconfiguration */
ngx_http_foo_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
ngx_table_elt_t *ua;
ua = r->headers_in.user_agent;
if (ua == NULL) {
return NGX_DECLINED;
}
/* reject requests with "User-Agent: foo" */
if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) {
return NGX_HTTP_FORBIDDEN;
}
return NGX_DECLINED;
}
static ngx_int_t
ngx_http_foo_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_foo_handler;
return NGX_OK;
}
Ожидается, что обработчики фаз возвращают определенные коды:
NGX_OK— перейти к следующей фазе.NGX_DECLINED— перейти к следующему обработчику текущей фазы. Если текущий обработчик является последним в текущей фазе, перейти к следующей фазе.NGX_AGAIN,NGX_DONE— приостановить обработку фазы до некоторого будущего события, которым может быть асинхронная операция ввода-вывода или просто задержка, например. Предполагается, что обработка фазы будет возобновлена позже путем вызоваngx_http_core_run_phases().Любое другое значение, возвращаемое обработчиком фазы, рассматривается как код завершения запроса, в частности, как код HTTP-ответа. Запрос завершается с предоставленным кодом.
Для некоторых фаз коды возврата обрабатываются несколько по-другому.
На фазе содержимого любой код возврата, отличный от
NGX_DECLINED, считается кодом завершения.
Любой код возврата от обработчиков содержимого местоположения считается
кодом завершения.
На фазе доступа в режиме
satisfy any
любой код возврата, отличный от NGX_OK,
NGX_DECLINED, NGX_AGAIN,
NGX_DONE, считается отказом.
Если никакие последующие обработчики доступа не разрешают или не запрещают доступ с другим
кодом, код отказа станет кодом завершения.
Примеры#
Репозиторий nginx-dev-examples предоставляет примеры модулей nginx, подходящие и для Angie.
Стиль кода#
Общие правила#
максимальная ширина текста составляет 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_ */
Препроцессор#
Имена макросов начинаются с префикса ngx_ или NGX_
(или более специфичного).
Имена макросов для констант записываются прописными буквами.
Параметризованные макросы и макросы для инициализаторов записываются строчными буквами.
Имя макроса и значение разделяются как минимум двумя пробелами:
#define NGX_CONF_BUFFER 4096
#define ngx_buf_in_memory(b) (b->temporary || b->memory || b->mmap)
#define ngx_buf_size(b) \
(ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos): \
(b->file_last - b->file_pos))
#define ngx_null_string { 0, NULL }
Условия заключаются в скобки, отрицание находится снаружи:
#if (NGX_HAVE_KQUEUE)
...
#elif ((NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \
|| (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT)))
...
#elif (NGX_HAVE_EPOLL && !(NGX_TEST_BUILD_EPOLL))
...
#elif (NGX_HAVE_POLL)
...
#else /* select */
...
#endif /* NGX_HAVE_KQUEUE */
Типы#
Имена типов заканчиваются суффиксом _t.
Определенное имя типа отделяется как минимум двумя пробелами:
typedef ngx_uint_t ngx_rbtree_key_t;
Типы структур определяются с использованием typedef.
Внутри структур типы и имена членов выравниваются:
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
Сохраняйте одинаковое выравнивание среди различных структур в файле.
Структура, которая указывает на себя, имеет имя, заканчивающееся на
_s.
Соседние определения структур разделяются двумя пустыми строками:
typedef struct ngx_list_part_s ngx_list_part_t;
struct ngx_list_part_s {
void *elts;
ngx_uint_t nelts;
ngx_list_part_t *next;
};
typedef struct {
ngx_list_part_t *last;
ngx_list_part_t part;
size_t size;
ngx_uint_t nalloc;
ngx_pool_t *pool;
} ngx_list_t;
Каждый член структуры объявляется на отдельной строке:
typedef struct {
ngx_uint_t hash;
ngx_str_t key;
ngx_str_t value;
u_char *lowcase_key;
} ngx_table_elt_t;
Указатели на функции внутри структур имеют определенные типы, заканчивающиеся
на _pt:
typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
off_t limit);
typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
off_t limit);
typedef struct {
ngx_recv_pt recv;
ngx_recv_chain_pt recv_chain;
ngx_recv_pt udp_recv;
ngx_send_pt send;
ngx_send_pt udp_send;
ngx_send_chain_pt udp_send_chain;
ngx_send_chain_pt send_chain;
ngx_uint_t flags;
} ngx_os_io_t;
Перечисления имеют типы, заканчивающиеся на _e:
typedef enum {
ngx_http_fastcgi_st_version = 0,
ngx_http_fastcgi_st_type,
...
ngx_http_fastcgi_st_padding
} ngx_http_fastcgi_state_e;
Переменные#
Переменные объявляются отсортированными по длине базового типа, затем в алфавитном порядке. Имена типов и имена переменных выравниваются. "Столбцы" типа и имени разделяются двумя пробелами. Большие массивы помещаются в конец блока объявлений:
u_char *rv, *p;
ngx_conf_t *cf;
ngx_uint_t i, j, k;
unsigned int len;
struct sockaddr *sa;
const unsigned char *data;
ngx_peer_connection_t *pc;
ngx_http_core_srv_conf_t **cscfp;
ngx_http_upstream_srv_conf_t *us, *uscf;
u_char text[NGX_SOCKADDR_STRLEN];
Статические и глобальные переменные могут быть инициализированы при объявлении:
static ngx_str_t ngx_http_memcached_key = ngx_string("memcached_key");
static ngx_uint_t mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static uint32_t ngx_crc32_table16[] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
...
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};
Существует множество часто используемых комбинаций тип/имя:
u_char *rv;
ngx_int_t rc;
ngx_conf_t *cf;
ngx_connection_t *c;
ngx_http_request_t *r;
ngx_peer_connection_t *pc;
ngx_http_upstream_srv_conf_t *us, *uscf;
Функции#
Все функции (даже статические) должны иметь прототипы. Прототипы включают имена аргументов. Длинные прототипы переносятся с одним отступом на строках продолжения:
static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf,
ngx_http_core_main_conf_t *cmcf);
static char *ngx_http_merge_servers(ngx_conf_t *cf,
ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module,
ngx_uint_t ctx_index);
Имя функции в определении начинается с новой строки. Открывающая и закрывающая скобки тела функции находятся на отдельных строках. Тело функции имеет отступ. Между функциями есть две пустые строки:
static ngx_int_t
ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len)
{
...
}
static ngx_int_t
ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
...
}
Нет пробела после имени функции и открывающей скобки. Длинные вызовы функций переносятся таким образом, что строки продолжения начинаются с позиции первого аргумента функции. Если это невозможно, отформатируйте первую строку продолжения так, чтобы она заканчивалась на позиции 79:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http header: \"%V: %V\"",
&h->key, &h->value);
hc->busy = ngx_palloc(r->connection->pool,
cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
Макрос ngx_inline следует использовать вместо
inline:
static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);
Выражения#
Бинарные операторы, кроме . и ->,
должны быть отделены от своих операндов одним пробелом.
Унарные операторы и индексы не отделяются от своих операндов пробелами:
width = width * 10 + (*fmt++ - '0');
ch = (u_char) ((decoded << 4) + (ch - '0'));
r->exten.data = &r->uri.data[i + 1];
Приведения типов отделяются одним пробелом от приводимых выражений. Звездочка внутри приведения типа отделяется пробелом от имени типа:
len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 1);
Если выражение не помещается в одну строку, оно переносится. Предпочтительное место для разрыва строки — бинарный оператор. Строка продолжения выравнивается с началом выражения:
if (status == NGX_HTTP_MOVED_PERMANENTLY
|| status == NGX_HTTP_MOVED_TEMPORARILY
|| status == NGX_HTTP_SEE_OTHER
|| status == NGX_HTTP_TEMPORARY_REDIRECT
|| status == NGX_HTTP_PERMANENT_REDIRECT)
{
...
}
p->temp_file->warn = "an upstream response is buffered "
"to a temporary file";
В крайнем случае можно перенести выражение так, чтобы строка продолжения заканчивалась на позиции 79:
hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
+ size * sizeof(ngx_hash_elt_t *));
Вышеуказанные правила также применяются к подвыражениям, где каждое подвыражение имеет свой собственный уровень отступа:
if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING)
|| c->stale_updating) && !r->background
&& u->conf->cache_background_update)
{
...
}
Иногда удобно перенести выражение после приведения типа. В этом случае строка продолжения имеет отступ:
node = (ngx_rbtree_node_t *)
((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
Указатели явно сравниваются с
NULL (не с 0):
if (ptr != NULL) {
...
}
Условные операторы и циклы#
Ключевое слово if отделяется от условия
одним пробелом.
Открывающая скобка располагается на той же строке или на
отдельной строке, если условие занимает несколько строк.
Закрывающая скобка располагается на отдельной строке, опционально за которой следует
else if / else.
Обычно перед частью
else if / else есть пустая строка:
if (node->left == sentinel) {
temp = node->right;
subst = node;
} else if (node->right == sentinel) {
temp = node->left;
subst = node;
} else {
subst = ngx_rbtree_min(node->right, sentinel);
if (subst->left != sentinel) {
temp = subst->left;
} else {
temp = subst->right;
}
}
Аналогичные правила форматирования применяются к циклам do
и while:
while (p < last && *p == ' ') {
p++;
}
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.
При сборке Angie это можно сделать, добавив опцию к
параметрам --with-cc-opt и --with-ld-opt
скрипта configure.
Поскольку большинство выделений памяти в Angie производится из внутреннего
пула Angie, включение ASan не всегда может быть достаточным для отладки
проблем с памятью.
Внутренний пул выделяет большой блок памяти из системы и вырезает
из него меньшие выделения.
Однако этот механизм можно отключить, установив
макрос NGX_DEBUG_PALLOC в 1.
В этом случае выделения передаются напрямую системному аллокатору, предоставляя ему
полный контроль над границами буферов.
Следующая строка конфигурации обобщает информацию, представленную выше. Она рекомендуется при разработке сторонних модулей и тестировании Angie на различных платформах.
auto/configure --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1'
--with-ld-opt=-fsanitize=address
Распространенные ошибки#
Написание C-модуля#
Наиболее распространенная ошибка — это попытка написать полноценный C-модуль,
когда этого можно избежать.
В большинстве случаев ваша задача может быть решена созданием правильной конфигурации.
Если написание модуля неизбежно, постарайтесь сделать его
максимально маленьким и простым.
Например, модуль может только экспортировать некоторые
переменные <#http_variables>.
Перед началом создания модуля рассмотрите следующие вопросы:
Возможно ли реализовать желаемую функциональность, используя уже Собственные модули?
Возможно ли решить проблему, используя встроенные языки сценариев, такие как perl или NJS?
C-строки#
Наиболее используемый тип строк в Angie,
ngx_str_t <#string_overview>, не является C-строкой,
завершающейся нулем.
Вы не можете передавать данные в стандартные функции библиотеки C,
такие как strlen() или strstr().
Вместо этого следует использовать Angie аналоги <#string_overview>,
которые принимают либо ngx_str_t, либо указатель на данные и длину.
Однако есть случай, когда ngx_str_t содержит
указатель на строку, завершающуюся нулем: строки, которые получаются в результате
разбора файла конфигурации, завершаются нулем.
Глобальные переменные#
Избегайте использования глобальных переменных в ваших модулях.
Скорее всего, наличие глобальной переменной является ошибкой.
Любые глобальные данные должны быть привязаны к циклу конфигурации <#cycle>
и выделяться из соответствующего пула памяти <#pool>.
Это позволяет Angie выполнять плавную перезагрузку конфигурации.
Попытка использовать глобальные переменные, вероятно, нарушит эту функциональность,
поскольку будет невозможно иметь две конфигурации одновременно
и избавляться от них.
Иногда глобальные переменные необходимы.
В этом случае требуется особое внимание для правильного управления
реконфигурацией.
Также проверьте, имеют ли библиотеки, используемые вашим кодом, неявное
глобальное состояние, которое может быть нарушено при перезагрузке.
Ручное управление памятью#
Вместо работы с подходом malloc/free, который подвержен ошибкам,
изучите, как использовать Angie пулы <#pool>.
Пул создается и привязывается к объекту —
конфигурации <#http_conf>,
циклу <#cycle>,
соединению <#connection>
или HTTP-запросу <#http_request>.
Когда объект уничтожается, связанный пул также уничтожается.
Поэтому при работе с объектом можно выделить необходимое количество памяти
из соответствующего пула и не заботиться об освобождении памяти
даже в случае ошибок.
Потоки#
Рекомендуется избегать использования потоков в Angie, поскольку это
определенно нарушит работу: большинство функций Angie не являются потокобезопасными.
Ожидается, что поток будет выполнять только системные вызовы и
потокобезопасные функции библиотек.
Если вам нужно выполнить код, не связанный с обработкой клиентских запросов,
правильный способ — запланировать таймер в обработчике модуля init_process
и выполнить необходимые действия в обработчике таймера.
Внутренне Angie использует потоки <#threads> для
ускорения операций, связанных с вводом-выводом, но это особый случай с множеством
ограничений.
Блокирующие библиотеки#
Распространенная ошибка — использование библиотек, которые внутренне блокируют выполнение. Большинство существующих библиотек являются синхронными и блокирующими по своей природе. Другими словами, они выполняют одну операцию за раз и тратят время на ожидание ответа от другой стороны. В результате, когда запрос обрабатывается с помощью такой библиотеки, весь рабочий процесс Angie блокируется, что разрушает производительность. Используйте только библиотеки, которые предоставляют асинхронный интерфейс и не блокируют весь процесс.
HTTP-запросы к внешним сервисам#
Часто модулям необходимо выполнить HTTP-вызов к какому-либо внешнему сервису.
Распространенная ошибка — использование какой-либо внешней библиотеки, такой как libcurl,
для выполнения HTTP-запроса.
Совершенно не нужно привносить огромное количество внешнего
(вероятно, блокирующего <#using_libraries>!) кода
для задачи, которая может быть выполнена самим Angie.
Существует два основных сценария использования, когда необходим внешний запрос:
в контексте обработки клиентского запроса (например, в обработчике содержимого)
в контексте рабочего процесса (например, обработчик таймера)
В первом случае лучше всего использовать
API подзапросов <#http_subrequests>.
Вместо прямого обращения к внешнему сервису вы объявляете location
в конфигурации Angie и направляете свой подзапрос к этому location.
Этот location не ограничивается
проксированием
запросов, но может содержать другие директивы Angie.
Пример такого подхода — директива
auth_request,
реализованная в модуле
auth_request.
Для второго случая можно использовать базовую функциональность HTTP-клиента, доступную в Angie. Например, модуль OCSP реализует простой HTTP-клиент.
Комментарии#
комментарии
//не используютсятекст написан на английском языке, предпочтительна американская орфография
многострочные комментарии форматируются следующим образом:
/* find the server configuration for the address:port */