Разработка#

Angie — проект с открытым исходным кодом, поучаствовать в котором могут все.

Исходный код#

Клонировать исходный код Angie можно из наших открытых репозиториев: Mercurial, Git.

Стиль кода#

Вносимые изменения должны сочетаться с остальным кодом Angie; хорошей отправной точкой будут правила оформления кода.

Совет

При сомнениях посмотрите на окружающий код и следуйте его примеру либо запустите grep по базе кода в поисках вдохновения.

Коммит-сообщения#

Исторически коммит-лог ведется на английском языке.

Сообщение начинается с однострочной сводки о проделанной работе. Она может иметь префикс, уже использованный в коммит-логе для этой части кода. Сводка имеет длину до 67 символов включительно; за ней может следовать пустая строка, после которой идут подробности.

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

API: bad things removed, good things added.

As explained elsewhere[1], the original API was bad because stuff;
this change was introduced to improve that aspect locally.

Levels of goodness have been implemented to mitigate the badness;
this is now the preferred way to work.  Also, the badness is gone.

[1] https://example.com

Детали, которые могли остаться незамеченными:

  • Резюме кончается точкой и начинается с прописной (большой) буквы.

  • Если использован префикс, то за ним следует строчная (маленькая) буква.

  • Предложения в одной строке разделяются двойным пробелом.

Финальные проверки#

  • Проверьте, что ваши изменения работают на всех целевых платформах.

  • Запустите тесты на всех платформах, чтобы устранить возможность регрессии:

    $ cd tests
    $ prove .
    

    Подробности см. в файле tests/README.

  • Убедитесь, что вас устраивают юридические условия.

Куда отправлять патчи#

Чтобы отправить патч, создайте PR-запрос в нашем зеркале на GitHub.

С вопросами и предложениями обращайтесь к разработчикам через GitHub Issues.

Правила оформления кода#

Исходный код следует следующей структуре и соглашениям.

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

  • auto — Скрипты сборки

  • src

    • core — Базовые типы и функции — строки, массивы, журнал, пул и т.д.

    • event — Ядро событий

      • modules — Модули уведомления о событиях: epoll, kqueue, select и т.д.

    • http — Основной HTTP модуль и общий код

      • modules — Другие HTTP модули

      • v2 — HTTP/2

    • mail — Почтовые модули

    • os — Платформо-зависимый код

      • unix

      • win32

    • stream — Потоковые модули

Включаемые файлы#

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

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

В дополнение к этому, HTTP код должен включать

#include <ngx_http.h>

Почтовый код должен включать

#include <ngx_mail.h>

Потоковый код должен включать

#include <ngx_stream.h>

Целые числа#

Для общих целей код Angie использует два целочисленных типа, ngx_int_t и ngx_uint_t, которые являются typedef'ами для intptr_t и uintptr_t соответственно.

Общие коды возврата#

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

  • NGX_OK — Операция выполнена успешно.

  • NGX_ERROR — Операция завершилась неудачей.

  • NGX_AGAIN — Операция не завершена; вызовите функцию снова.

  • NGX_DECLINED — Операция отклонена, например, потому что она отключена в конфигурации. Это никогда не является ошибкой.

  • NGX_BUSY — Ресурс недоступен.

  • NGX_DONE — Операция завершена или продолжена в другом месте. Также используется как альтернативный код успеха.

  • NGX_ABORT — Функция была прервана. Также используется как альтернативный код ошибки.

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

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

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

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

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

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

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

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

        return 1;
    }

    return 0;
}

Строки#

Обзор#

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

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

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

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

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

  • ngx_strcmp()

  • ngx_strncmp()

  • ngx_strstr()

  • ngx_strlen()

  • ngx_strchr()

  • ngx_memcmp()

  • ngx_memset()

  • ngx_memcpy()

  • ngx_memmove()

Другие строковые функции специфичны для Angie:

  • ngx_memzero() — Заполняет память нулями.

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

  • ngx_cpymem() — Делает то же самое, что и ngx_memcpy(), но возвращает конечный адрес назначения. Это удобно для последовательного добавления нескольких строк.

  • ngx_movemem() — Делает то же самое, что и ngx_memmove(), но возвращает конечный адрес назначения.

  • ngx_strlchr() — Ищет символ в строке, ограниченной двумя указателями.

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

  • ngx_tolower()

  • ngx_toupper()

  • ngx_strlow()

  • ngx_strcasecmp()

  • ngx_strncasecmp()

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

  • ngx_string(text) — статический инициализатор для типа ngx_str_t из строкового литерала C text

  • ngx_null_string — статический инициализатор пустой строки для типа ngx_str_t

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

  • ngx_str_null(str) — инициализирует строку str типа ngx_str_t * пустой строкой

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

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

  • 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. Некоторые из них:

  • %Ooff_t

  • %Ttime_t

  • %zssize_t

  • %ingx_int_t

  • %pvoid *

  • %Vngx_str_t *

  • %su_char * (завершенная нулевым символом)

  • %*ssize_t + u_char *

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

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

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

  • ngx_atoi(line, n)ngx_int_t

  • ngx_atosz(line, n)ssize_t

  • ngx_atoof(line, n)off_t

  • ngx_atotm(line, n)time_t

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

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

  • ngx_hextoi(line, n) — Преобразует шестнадцатеричное представление положительного целого числа в ngx_int_t.

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

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

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

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

u_char                errstr[NGX_MAX_CONF_ERRSTR];

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

ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

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

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

re = rc.regex;
#endif

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

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

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

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

n = ngx_regex_exec(re, &input, captures, (1 + rc.captures) * 3);
if (n >= 0) {
    /* строка соответствует выражению */

} 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() — время, выраженное как UTC

  • ngx_localtime(), ngx_libc_localtime() — время, выраженное относительно местного часового пояса

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

Контейнеры#

Массив#

Тип массива 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_shared_memory_add(cf, name, size, tag) добавляет новую запись разделяемой памяти ngx_shm_zone_t в цикл. Функция получает name и size зоны. Каждая разделяемая зона должна иметь уникальное имя. Если запись разделяемой зоны с предоставленными name и tag уже существует, существующая запись зоны используется повторно. Функция завершается с ошибкой, если существующая запись с тем же именем имеет другой тег. Обычно адрес структуры модуля передается как tag, что позволяет повторно использовать разделяемые зоны по имени в рамках одного модуля Angie.

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

  • init — обратный вызов инициализации, вызываемый после того, как разделяемая зона отображается на фактическую память

  • data — контекст данных, используемый для передачи произвольных данных в обратный вызов init

  • noreuse — флаг, который отключает повторное использование разделяемой зоны из старого цикла

  • tag — тег разделяемой зоны

  • shm — платформо-специфичный объект типа ngx_shm_t, имеющий как минимум следующие поля:

    • addr — адрес отображенной разделяемой памяти, изначально NULL

    • size — размер разделяемой памяти

    • name — имя разделяемой памяти

    • log — журнал разделяемой памяти

    • exists — флаг, указывающий, что разделяемая память была унаследована от главного процесса (специфично для Windows)

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

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

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

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

ngx_str_t        name;
ngx_foo_ctx_t   *ctx;
ngx_shm_zone_t  *shm_zone;

ngx_str_set(&name, "foo");

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

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

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


...


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

    size_t            len;
    ngx_foo_ctx_t    *ctx;
    ngx_slab_pool_t  *shpool;

    value = shm_zone->data;

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

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

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

    /* initialize shared zone */

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

    shpool->data = ctx->value;

    return NGX_OK;
}

Логирование#

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

  • stderr — логирование в стандартный поток ошибок (stderr)

  • file — логирование в файл

  • syslog — логирование в syslog

  • memory — логирование во внутреннее хранилище памяти для целей разработки; к памяти можно получить доступ позже с помощью отладчика

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

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

  • NGX_LOG_EMERG

  • NGX_LOG_ALERT

  • NGX_LOG_CRIT

  • NGX_LOG_ERR

  • NGX_LOG_WARN

  • NGX_LOG_NOTICE

  • NGX_LOG_INFO

  • NGX_LOG_DEBUG

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

  • NGX_LOG_DEBUG_CORE

  • NGX_LOG_DEBUG_ALLOC

  • NGX_LOG_DEBUG_MUTEX

  • NGX_LOG_DEBUG_EVENT

  • NGX_LOG_DEBUG_HTTP

  • NGX_LOG_DEBUG_MAIL

  • NGX_LOG_DEBUG_STREAM

Обычно логгеры создаются существующим кодом 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 отказывается принимать новых клиентов или подключаться к upstream-серверам.

  • 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 ожидается на сокете, даже если могут быть доступны некоторые данные перед ним. Флаг доставляется через событие EPOLLRDHUP epoll или флаг EV_EOF kqueue.

  • 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_MODULE

  • NGX_EVENT_MODULE

  • NGX_HTTP_MODULE

  • NGX_MAIL_MODULE

  • NGX_STREAM_MODULE

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

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

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

где name — это строка с именем модуля, create_conf и init_conf — указатели на функции, которые создают и инициализируют конфигурацию модуля соответственно. Для основных модулей 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..:samp: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) — Возвращает контекст module

    • ngx_http_set_ctx(r, c, module) — Устанавливает c как контекст module

  • main_conf, srv_conf, loc_conf — Массивы конфигураций текущего запроса. Конфигурации сохраняются в позициях ctx_index модуля.

  • read_event_handler, write_event_handler - Обработчики событий чтения и записи для запроса. Обычно как обработчики событий чтения, так и записи для HTTP-соединения устанавливаются в ngx_http_request_handler(). Эта функция вызывает обработчики read_event_handler и write_event_handler для текущего активного запроса.

  • cache — Объект кеша запроса для кеширования ответа upstream.

  • upstream — Объект upstream запроса для проксирования.

  • pool — Пул запроса. Сам объект запроса выделяется в этом пуле, который уничтожается при удалении запроса. Для выделений, которые должны быть доступны в течение всего времени жизни клиентского соединения, используйте вместо этого пул ngx_connection_t.

  • header_in — Буфер, в который читается заголовок HTTP-запроса клиента.

  • headers_in, headers_out — Объекты входящих и исходящих HTTP-заголовков. Оба объекта содержат поле headers типа ngx_list_t для хранения сырого списка заголовков. В дополнение к этому, специфические заголовки доступны для получения и установки как отдельные поля, например content_length_n, status и т.д.

  • request_body — Объект тела запроса клиента.

  • start_sec, start_msec — Момент времени, когда запрос был создан, используется для отслеживания продолжительности запроса.

  • method, method_name — Числовое и текстовое представление метода HTTP-запроса клиента. Числовые значения для методов определены в src/http/ngx_http_request.h макросами NGX_HTTP_GET, NGX_HTTP_HEAD, NGX_HTTP_POST и т.д.

  • http_protocol — Версия HTTP-протокола клиента в оригинальной текстовой форме ("HTTP/1.0", "HTTP/1.1" и т.д.).

  • http_version — Версия HTTP-протокола клиента в числовой форме (NGX_HTTP_VERSION_10, NGX_HTTP_VERSION_11 и т.д.).

  • http_major, http_minor — Версия HTTP-протокола клиента в числовой форме, разделенная на основную и младшую части.

  • request_line, unparsed_uri — Строка запроса и URI в оригинальном запросе клиента.

  • uri, args, exten — URI, аргументы и расширение файла для текущего запроса. Значение URI здесь может отличаться от оригинального URI, отправленного клиентом, из-за нормализации. В процессе обработки запроса эти значения могут изменяться при выполнении внутренних перенаправлений.

  • main — Указатель на объект основного запроса. Этот объект создается для обработки HTTP-запроса клиента, в отличие от подзапросов, которые создаются для выполнения конкретной подзадачи в рамках основного запроса.

  • parent — Указатель на родительский запрос подзапроса.

  • postponed — Список выходных буферов и подзапросов в том порядке, в котором они отправляются и создаются. Список используется фильтром 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 отдельно. Если в конфигурации location HTTP-модуль установлен 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_ */

Комментарии#

  • комментарии // не используются

  • текст написан на английском языке, предпочтительна американская орфография

  • многострочные комментарии форматируются следующим образом:

    /*
     * The red-black tree code is based on the algorithm described in
     * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
     */
    
    /* find the server configuration for the address:port */
    

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

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

#define NGX_CONF_BUFFER  4096

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

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

#define ngx_null_string  { 0, NULL }

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

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

Типы#

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

typedef ngx_uint_t  ngx_rbtree_key_t;

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

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

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

typedef struct ngx_list_part_s  ngx_list_part_t;

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


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

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

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

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

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

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

Перечисления имеют типы, заканчивающиеся на _e:

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

Переменные#

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

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

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

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

Существует множество часто используемых комбинаций тип/имя:

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

Функции#

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

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

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

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

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


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

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

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

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

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

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

Выражения#

Бинарные операторы, кроме . и ->, должны быть отделены от своих операндов одним пробелом. Унарные операторы и индексы не отделяются от своих операндов пробелами:

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

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

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

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

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

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

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

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

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

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

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

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

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

Условные операторы и циклы#

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

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

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

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

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

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

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

while (p < last && *p == ' ') {
    p++;
}
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 или Сторонние модули?

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, реализованная в модуле angie__auth_request.

Для второго случая можно использовать базовую функциональность HTTP-клиента, доступную в Angie. Например, модуль OCSP реализует простой HTTP-клиент.