Материал, который Вы читаете в данный момент, основан на моих собственных исследованиях исходного кода и материалах списка рассылки nginx. Поскольку я не являюсь автором nginx и участвовал в обсуждении далеко не всех деталей реализации, приведенная информация может быть не верна на 100%.
Вы предупреждены!
Материал организован следующим образом: в первой главе описываются общие принципы работы асинхронных серверов и объясняется почему разработка модулей для nginx требует особого подхода, во второй главе описывается API, который предоставляет nginx модулям, в третьей главе описываются особенности взаимодействия nginx и модулей, в четвертой и последней главе описываются подходы к реализации модулей.
Главной особенностью реализации nginx, является то, что все сетевые операции ввода-вывода выполняются асинхронно относительно выполнения рабочих процессов. Это дает следующие преимущества:
Когда обрабатывается один сокет одним из рабочих процессов, все остальные сокеты этого рабочего процесса продолжают ожидать обработки. Если обработка одного сокета затягивается, то остальные сокеты начинают испытывать "голод": приходящие данные скапливаются во входных буферах сокетов, а готовые к записи сокеты не получают новых данных. На клиентской стороне подобная ситуация выглядит как "зависание". Для предотвращения голодания сокетов сервер и компоненты сервера должны быть реализованы с использованием следующих принципов:
Из-за описанных выше ограничений полномасштабные веб-приложения сложно реализовать исключительно в модулях nginx.
Управление памятью в nginx осуществляется с помощью пулов. Пул -- это последовательность предварительно выделенных блоков динамической памяти. Пул привязан к объекту, который определяет время жизни всех объектов, выделенных в пуле. Таким объектом может быть, например, запрос или цикл обработки событий. Пулы используются исходя из следующих соображений:
Для выделения памяти используются следующие функции:
void *ngx_palloc(ngx_pool_t *pool, size_t size); void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
pool -- пул, из которого будет выделена память;
size -- размер выделяемой памяти в байтах;
результат -- указатель на выделенную память, либо NULL, если не
удалось выделить.
Функция ngx_pcalloc в дополнение заполняет выделенную память нулями.
Для маловероятного случая освобождения памяти используется функция ngx_pfree:
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);pool -- пул, в который будет возвращена память;
Для регистрации деструктора (например для закрытия файловых дескрипторов или удаления файлов) используется следующие структура и функции:
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t; struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void *data; ngx_pool_cleanup_t *next; }; ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
p -- пул, в котором будет зарегистрирован деструктор;
size -- размер выделяемой структуры-контекста, которая будет передана деструктору;
результат -- указатель на деструктор, если удалось выделить, NULL,
если не удалось выделить.
После выполнения ngx_pool_cleanup_add поле data указывает на контекст длиной size, который переходит в распоряжение пользователя.
Поля в структуре ngx_pool_cleanup_t имеют следующее значение:
handler -- хэндлер, который будет вызван при удалении пула;
data -- указатель на структуру-контекст, которая будет передана деструктору;
next -- указатель на следующий деструктор в пуле.
Пример использования:
static void ngx_sample_cleanup_handler(void *data); static ngx_int_t ngx_http_sample_module_handler(ngx_http_request_t *r) { ngx_pool_cleanup_t *cln; ngx_sample_cleanup_t *scln; cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_sample_cleanup_t)); if(cln == NULL) return NGX_ERROR; cln->handler = ngx_sample_cleanup_handler; scln = cln->data; [... инициализация scln ...] } static void ngx_sample_cleanup_handler(void *data) { ngx_sample_cleanup_t *scln = data; [... использование scln ...] }
Векторы в nginx описываются следующей структурой:
struct ngx_array_s { void *elts; ngx_uint_t nelts; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; }; typedef struct ngx_array_s ngx_array_t;
pool -- пул, в котором будет распределена память под элементы;
elts -- указатель на элементы;
nelts -- число элементов в векторе в данный момент;
size -- размер элемента вектора в байтах;
nalloc -- число элементов, для которых распределена память в данный момент.
Для создания вектора используется функция ngx_array_create:
ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
p -- пул, в котором будет распределена память под вектор и его элементы;
n -- число элементов, под которые будет зарезервирована память;
size -- размер элемента вектора в байтах;
результат -- указатель на вектор, если удалось выделить, NULL,
если не удалось выделить.
Пример создания вектора:
typedef struct { [...] } ngx_sample_struct_t; { ngx_array_t *v; v = ngx_array_create(pool, 10, sizeof(ngx_sample_struct_t)); if (v == NULL) { return NGX_ERROR; } }
Если память под структуру ngx_array_t предварительно выделена, то для её инициализации используется функция ngx_array_init:
ngx_int_t ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size);
array -- указатель на структуру ngx_array_t;
p -- пул, в котором будет распределена память под элементы вектора;
n -- число элементов, под которые будет зарезервирована память;
size -- размер элемента вектора в байтах;
результат -- NGX_OK, если удалось выделить, NGX_ERROR, если не удалось выделить.
Пример инициализации вектора:
typedef struct { ngx_array_t v; [...] } ngx_sample_struct_t; { ngx_sample_struct_t t; if(ngx_array_init(&t.v, pool, 10, sizeof(ngx_sample_struct_t)) != NGX_OK) { return NGX_ERROR; } }
Для добавления элементов в конец вектора используются функции ngx_array_push и ngx_array_push_n:
void *ngx_array_push(ngx_array_t *a); void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);
a -- вектор, к которому добавляется элемент(ы);
n -- число элементов, которые будут выделены;
результат -- указатель на элемент, если удалось выделить, NULL,
если не удалось выделить.
Функция ngx_array_push_n добавляет n элементов к вектору.
При добавлении новых элементов к вектору необходимо учитывать, что отрезок памяти, выделенный под элементы, расширяется за счет свободной памяти пула, расположенной непосредственно после него. Если свободной памяти не обнаруживается, то выделяется новый непрерывный отрезок памяти, и его длина каждый раз растет по двоично-экспонециальному принципу.
Пример:
typedef struct { [...] } ngx_sample_struct_t; { ngx_array_t *v; [...] h = ngx_array_push(v); if (h == NULL) { return NGX_ERROR; } [... использование h...] }
Для удаления вектора используется функция ngx_array_destroy. Удаление вектора выполняется только в том случае, если память, распределенная под его элементы, располагалась в конце пула.
void ngx_array_destroy(ngx_array_t *a);
a -- вектор, который будет удален;
В nginx (для 0.6.29 вполне актуально) есть связанные списки. Каждый список представляет собой структуру:
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;
};
Здесь:
Т.е. список — это несколько частей (минимум 1), в которых располагаются элементы. Каждая часть имеет свою длину и указатель на следующую часть или NULL, если в ней еще есть место. Размер всех частей равен.
Для создания списка используется функция:
ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);
size — размер одного элемента, n — количество элементов в части, pool — пул памяти, в котором выделяется память.
Функция создает новый список и инициализирует его (создает первую часть и определяет размер части). Возвращаемое значение — указатель на список или NULL в случае ошибки.
Список можно не создавать, а просто инициализировать (если у нас он определен на стеке), используя функцию:
static ngx_inline ngx_int_t ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)
Параметры ее аналогичны ngx_list_create, за исключением первого — это указатель на список, который будет инициализироваться (или переинициализироваться). Функция возвращает либо NGX_OK в случае успеха, либо NGX_ERROR в случае проблем. Функция небезопасна, т.е. можно переинициализировать любой существующий список и все старые данные потеряются ☺
Для добавления элемента в список используется функция:
void *ngx_list_push(ngx_list_t *list);
Она принимает указатель на список и возвращает указатель на место в части, в которое можно записывать значения. Если часть закончилась, создается новая и возвращается указатель на ее первый элемент.
Для навигации по списку можно использовать такой код:
part = &list.part;
data = part->elts;
for (i = 0 ;;i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
data = part->elts;
i = 0;
}
/* use element of list as data[i] */
}
Вместо комментария вставляется ваш код, в котором будет происходить обращение к элементам списка.
Пример функции, в которой создается список из двух частей и осуществляется навигация по нему:
void ngx_list_sample_using(ngx_http_request_t *r)
{
ngx_list_t list;
ngx_uint_t i;
ngx_list_part_t *part;
ngx_uint_t *list_element;
ngx_uint_t *sum = 0;
if (ngx_list_init(&list, r->pool, 5, sizeof(ngx_uint_t)) == NGX_ERROR) {
return;
}
for (i = 0; i < 10; i++) {
list_element = ngx_list_push(&list);
if (list_element == NULL) {
return;
}
*list_element = i;
}
part = &list.part;
data = part->elts;
for (i = 0 ;;i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
data = part->elts;
i = 0;
}
sum += data[i];
}
/* here sum is 45 */
}
Буферы используются для отслеживания прогресса приема, отправления и обработки данных. Заголовок буфера описывается структурой ngx_buf_t. Данные буфера могут располагаться в изменяемой или неизменяемой памяти или в файле. Для изменяемой памяти актуальны следующие поля:
typedef struct ngx_buf_s ngx_buf_t; struct ngx_buf_s { [...] u_char *pos; /* начало окна */ u_char *last; /* конец окна */ u_char *start; /* начало буфера */ u_char *end; /* конец буфера */ unsigned temporary:1; /* = 1 */ [...] };
Для неизменяемой памяти:
struct ngx_buf_s { [...] u_char *pos; /* начало окна */ u_char *last; /* конец окна */ u_char *start; /* начало буфера */ u_char *end; /* конец буфера */ unsigned memory:1; /* = 1 */ [...] };
Для файла:
struct ngx_buf_s { [...] off_t file_pos; /* начало окна */ off_t file_last /* конец окна */ ngx_file_t *file; /* указатель на файл */ unsigned in_file:1; /* = 1 */ [...] };
Окно определяет часть буфера, которую осталось отправить, осталось обработать, либо заполнена полученными данными. В процессе заполнения указатель last перемещается в направлении end, в процессе обработки или отправления указатель pos перемещается в направлении last (или file_pos в направлении file_last). В случае, если все данные буфера отправлены или обработаны, то pos == last или file_pos == file_last. В случае, если буфер не заполнен, то pos == last == start.
Кроме того, буфер содержит флаги, описывающие как необходимо обрабатывать данные, содержащиеся в буфере:
struct ngx_buf_s { [...] unsigned recycled:1; /* буфер повторно использован после освобождения */ unsigned flush:1; /* все буферизированные данные должны быть обработаны и переданы на следующий уровень после обработки этого буфера */ unsigned last_buf:1; /* указывает на то, что буфер является последним в потоке данных */ unsigned last_in_chain:1; /* указывает на то, что буфер является последним в данной цепи (очереди) */ unsigned temp_file:1; /* указывает на то, что буфер располагается во временном файле */ [...] };
В случае, если буфер константный, в оперативной памяти может находиться произвольное число структур ngx_buf_t, указывающих на идентичные данные, располагающиеся, например, в сегменте константных данных или в конфигурации модуля (см. 2.9). Окно определяет прогресс отправления этих данных.
Для выделения памяти под структуру ngx_buf_t используются макросы:
ngx_alloc_buf(pool); ngx_calloc_buf(pool);
pool -- пул, из которого будет выделен буфер;
rvalue -- указатель на структуру ngx_buf_t, если удалось выделить,
NULL, если не удалось выделить. После выделения все поля структуры необходимо
инициализировать. Макрос ngx_calloc_buf преобразуется в функцию, которая в дополнение
зполняет выделенную память нулями.
Для выделения памяти под временный буфер используется следующая функция:
ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size);
pool -- пул, из которого будет выделен буфер;
size -- размер буфера (расстояние между start и end);
результат -- указатель на структуру ngx_buf_t, если удалось выделить,
NULL, если не удалось выделить. После выделения pos и last будут равны
start и флаг temporary будет установлен в 1.
Очереди (или цепи) связывают несколько буферов в последовательность, которая определяет порядок приема, отправления или обработки данных.
struct ngx_chain_s { ngx_buf_t *buf; /* буфер, связанный с текущим звеном */ ngx_chain_t *next; /* следующее звено */ }; typedef struct ngx_chain_s ngx_chain_t;
Для выделения памяти под цепи используются следующие функции:
typedef struct { ngx_int_t num; size_t size; } ngx_bufs_t; ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool); ngx_chain_t *ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs); ngx_chain_t *ngx_chain_get_free_buf(ngx_pool_t *p, ngx_chain_t **free);
функция ngx_alloc_chain_link выделяет память под одно звено из пула;
функция ngx_create_chain_of_bufs выделяет память под цепь звеньев и буферы;
функция ngx_chain_get_free_buf выделяет звено из цепи cвободных
буферов или из пула, если цепь пуста.
pool -- пул, из которого будет(будут) выделен(ы) звенья/буферы при необходимости;
bufs -- структура, описывающая размер и число буферов;
free -- указатель на цепочку свободных буферов;
результат -- указатель на структуру ngx_chain_t, если удалось выделить,
NULL, если не удалось выделить.
Для освобождения звеньев используется следующий макрос:
ngx_free_chain(pool, cl)
pool -- пул, в который возвращается звено,
cl -- возвращаемое звено.
Управление очередями:
ngx_int_t ngx_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain, ngx_chain_t *in); void ngx_chain_update_chains(ngx_chain_t **free, ngx_chain_t **busy, ngx_chain_t **out, ngx_buf_tag_t tag);функция ngx_chain_add_copy добавляет буферы из цепи in к цепи chain, выделяя новые звенья. Возвращает NGX_OK, если удалось успешно добавить, NGX_ERROR, если не удалось выделить память;
В nginx строки хранятся в Pascal-like форме с целью избежать накладных расходов при вычислении длины, а так же копирования в некоторых ситуациях.
typedef struct { size_t len; u_char *data; } ngx_str_t;len -- длина строки в байтах, data -- указатель на память, содержащую строку.
Переменные -- это именованные контейнеры данных, которые можно преобразовывать в строки или из строк. Значения переменных могут хранится в любой форме. Для преобразования из строк и в строки используются эксессоры -- функции установки и получения значения переменной:
typedef void (*ngx_http_set_variable_pt) (ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); typedef ngx_int_t (*ngx_http_get_variable_pt) (ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data);ngx_http_set_variable_pt -- тип функций, вызываемых для установки значения переменной; ngx_http_get_variable_pt -- тип функций, вызываемых для получения значения переменной.
struct ngx_http_variable_s { ngx_str_t name; ngx_http_set_variable_pt set_handler; ngx_http_get_variable_pt get_handler; uintptr_t data; ngx_uint_t flags; [...] };name -- имя переменной,
Для добавления новой переменной используется следующая функция:
ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags);cf -- конфигурация, в которой создается переменная,
static ngx_str_t ngx_http_var_name = ngx_string("var"); [...] { ngx_http_variable_t *var; var = ngx_http_add_variable(cf, &ngx_http_var_name, NGX_HTTP_VAR_NOCACHEABLE); }
ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);cf -- конфигурация, в которой определена переменная,
typedef struct { unsigned len:28; unsigned valid:1; unsigned no_cacheable:1; unsigned not_found:1; [...] u_char *data; } ngx_variable_value_t; typedef ngx_variable_value_t ngx_http_variable_value_t; ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index);r -- запрос, в контексте которого запрашивается переменная,
Скрипты в nginx -- байт-код для генерации строк. Скрипт можно создать или скомпилировать из шаблона, затем выполнить произвольное число раз. Шаблон -- это строка со ссылками на переменные, которые имеют вид $varible_name или ${variable_name}. Переменные могут быть символьными, либо позиционными, которые имеют индексы от 0 до 9: $0, ... $9. Позиционные переменные заполняются модулем rewrite. При выполнении скрипта используются значения переменных на этот момент выполнения, либо на момент попадания значения переменной в кэш, если значение кэшируемо. Для компиляции шаблона необходимо заполнить следующую структуру:
typedef struct { ngx_conf_t *cf; /* указатель на конфигурацию ngx_conf_t */ ngx_str_t *source /* компилируемый шаблон */; ngx_array_t **lengths; /* код для определения длины результата */ ngx_array_t **values; /* код для генерации результата */ ngx_uint_t variables; /* предполагаемое число переменных */ unsigned complete_lengths:1; /* генерировать код для определения длины */ unsigned complete_values:1; /* генерировать код для генерации значения */ } ngx_http_script_compile_t;
Заполненную структуру необходимо передать в функцию ngx_http_script_compile. Пример:
Для выполнения скрипта используется функция ngx_http_script_run: Если число переменных в шаблоне неизвестно, то можно использовать функцию
ngx_http_script_variables_count для их подсчета:
value -- указатель на строку, Если шаблон не содержит переменных, то можно соптимизировать вызов ngx_http_script_run,
проверив, что любой из векторов, сожержищих байт-код, не инициализирован: Регулярные выражения в nginx реализованы на основе библиотеки PCRE и доступны
только при её наличии в системе. В исходном коде доступность регулярных выражений
индицируется макросом NGX_PCRE. Регулярные выражения используюутся по принципу
аналогичному скриптам: компилируются один раз, затем многократно выполняются. Для компиляции
регулярного выражения используется функция ngx_regex_compile:
pattern -- указатель на строку, содержащую регулярное выражение; Пример компиляции регулярного выражения: Для подсчета числа ссылок в регулярном выражении используется
функция ngx_regex_capture_count
re -- указатель на скомпилированное регулярное выражение; Для выполнения регулярного выражения используется
функция ngx_regex_exec
re -- указатель на скомпилированное регулярное выражение; Число элементов вектора captures должно быть ровно в три раза больше
числа ссылок в регулярном выражении. В первые две трети вектора помещаются
позиции подстрок, соответствующие ссылкам в регулярном выражении, оставшаяся
часть используется библиотекой PCRE для внутренних нужд. Каждый первый
элемент первых двух третей вектора содержит позицию первого символа подстроки,
каждый второй -- позицию, следющую за позицией последнего символа подстроки.
static ngx_str_t ngx_http_script_source = ngx_string("Your IP-address is $remote_addr");
{
ngx_http_script_compile_t sc;
ngx_array_t *lengths = NULL;
ngx_array_t *values = NULL;
ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
sc.cf = cf;
sc.source = &ngx_http_script_source;
sc.lengths = &lengths;
sc.values = &values;
sc.variables = 1;
sc.complete_lengths = 1;
sc.complete_values = 1;
if (ngx_http_script_compile(&sc) != NGX_OK) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
u_char *ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
void *code_lengths, size_t reserved, void *code_values);
r -- запрос, в контексте которого выполняется скрипт,
value -- указатель на строку, в которую будет помещен результат,
code_lengths -- указатель на код для получения длины результата,
reserved -- зарезервированный аргумент,
code_values -- указатель на код для получения результата,
результат -- указатель на байт памяти, следующий за последним
байтом результата, либо NULL, если при выполнении скрипта произошла ошибка.
Пример:
[...]
{
ngx_str_t value;
if (ngx_http_script_run(r, &value, lengths->elts, 0,
values->elts) == NULL)
{
return NGX_ERROR;
}
[...]
}
ngx_uint_t ngx_http_script_variables_count(ngx_str_t *value);
результат -- число переменных в строке.
Пример:
static ngx_str_t ngx_http_script_source = ngx_string("Your IP-address is $remote_addr");
{
ngx_int_t n;
ngx_http_script_compile_t sc;
ngx_array_t *lengths = NULL;
ngx_array_t *values = NULL;
n = ngx_http_script_variables_count(&ngx_http_script_source);
if(n > 0) {
ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
sc.cf = cf;
sc.source = &ngx_http_script_source;
sc.lengths = &lengths;
sc.values = &values;
sc.variables = n;
sc.complete_lengths = 1;
sc.complete_values = 1;
if (ngx_http_script_compile(&sc) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
return NGX_CONF_OK;
}
[...]
{
ngx_str_t value;
if (lengths == NULL) {
value.data = ngx_http_script_source.data;
value.len = ngx_http_script_source.len;
}else{
if (ngx_http_script_run(r, &value, lengths->elts, 0,
values->elts) == NULL)
{
return NGX_ERROR;
}
}
[...]
}
2.8. Регулярные выражения
#if (NGX_PCRE)
typedef pcre ngx_regex_t;
ngx_regex_t *ngx_regex_compile(ngx_str_t *pattern, ngx_int_t options,
ngx_pool_t *pool, ngx_str_t *err);
#endif
options -- флаги, задающие некоторые параметры;
pool -- пул, в котором будет выделена память под регулярное выражение;
err -- строка, содержащая текстовое описание ошибки, которая произошла при компиляции регулярного выражения;
результат -- указатель на структуру, содержащую скомпилированное регулярное выражение.
Параметр options может содержать флаг NGX_REGEX_CASELESS, означающий, что
регулярное выражение не чувствительно к регистру символов.
#if (NGX_PCRE)
ngx_int_t ngx_regex_capture_count(ngx_regex_t *re);
#endif
результат -- число ссылок.
#if (NGX_PCRE)
ngx_int_t ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures,
ngx_int_t size);
#endif
s -- указатель строку, для которой будет выполнено регулярное выражение;
captures -- указатель вектор, в который будут помещены позиции подстрок,
соответствующие ссылкам;
size -- число элементов вектора captures;
результат -- 0, если регулярное выражение совпало, NGX_REGEX_NO_MATCHED,
если регулярное выражение не совпало, значение меньше NGX_REGEX_NO_MATCHED если
произошла ошибка.2.9. Конфигурация модуля
Конфигурация модуля во время выполнения храниться в бинарном виде
в структурах определяемых разработчиком. HTTP-запрос связывается с
конфигурациями трех уровней: основной, виртуального сервера
и location'а. На каждом из уровней имеет смысл хранить только те
параметры конфигурации, которые нельзя разделить между
экземплярами конфигураций последующих уровней. Например,
имена виртуального сервера и адрес слушающего сокета
нельзя разделить между location'ами, поэтому имеет смысл
хранить эти параметры в конфигурации виртуального сервера.
Для доступа к конфигурациям всех уровней
во время разбора файла конфигурации используются следующие макросы:
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)
cf -- указатель на структуру ngx_conf_t (конфигурация),
module -- структура ngx_module_t (описание модуля),
rvalue -- указатель на конфигурацию модуля соответствующего
уровня.
Для доступа к конфигурациям всех уровней
во время обработки запроса используются следующие макросы:
ngx_http_get_module_main_conf(r, module)
ngx_http_get_module_srv_conf(r, module)
ngx_http_get_module_loc_conf(r, module)
r -- указатель на структуру ngx_http_request_t (запрос),
module -- структура ngx_module_t (описание модуля),
rvalue -- указатель на конфигурацию модуля соответствующего
уровня.
Контекст модуля во время обработки запроса храниться в бинарном виде в структурах определяемых разработчиком. Для установки контекста модуля используется следующий макрос:
ngx_http_set_ctx(r, c, module)
r -- указатель на структуру ngx_http_request_t (запрос), c -- указатель на контекст модуля (структура определяемая разработчиком), module -- структура ngx_module_t (описание модуля).
Для доступа к контексту модуля используется следующий макрос:
ngx_http_get_module_ctx(r, module)
r -- указатель на структуру ngx_http_request_t (запрос),
module -- структура ngx_module_t (описание модуля).
rvalue -- указатель на контекст модуля.
Nginx обрабатывает запросы с использованием нескольких фаз. На каждой фазе вызываются 0 или более хэндлеров.
Для регистрации хэндлера необходимо обратиться к основной конфигурации модуля ngx_http_core_module и добавить хэндлер к одному из элементов вектора phases. Пример регистрации хэндлера на фазе NGX_HTTP_CONTENT_PHASE:
static ngx_int_t ngx_http_sample_module_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_CONTENT_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_sample_module_handler; return NGX_OK; }
Хэндлеры фаз вызываются вне зависимости от конфигурации. В связи с этим, хэндлер должен уметь определять когда он не применим и возвращать NGX_DECLINED, и делать это как можно быстрее, чтобы избежать потери производительности.
Фаза NGX_HTTP_ACCESS_PHASE используется для вызова хэндлеров, ограничивающих доступ к ресурсам. На этой фазе порядок перехода к следующим хэндлерам или фазам определяеться директивой satisfy. Значения, возвращаемые хэндлером, преобретают дополнительный смысл:
Фаза NGX_HTTP_CONTENT_PHASE используется для генерации ответа. Если в конфигурации уровня location'а модуля ngx_http_core_module переопределен параметр handler, то все запросы направляются этому хэндлеру, в противном случае используются хэндлеры фазы NGX_HTTP_CONTENT_PHASE из основной конфигурации. Хэндлер location'а не может быть возобновлен: возврат NGX_DONE не приводит к повторному вызову хэндлера. Пример переопределния хэндлера location'а:
static char * ngx_http_sample_module_command(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_sample_handler; return NGX_CONF_OK; }
Для встраивания в nginx модуль должен содержать метаинформацию, которая
описывает как инициализировать и конфигурировать модуль. Метаинформация
представляется структурой ngx_module_t:
struct ngx_module_s {
[...]
ngx_uint_t version;
void *ctx;
ngx_command_t *commands;
ngx_uint_t type;
[...]
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
[...]
void (*exit_process)(ngx_cycle_t *cycle);
void (*exit_master)(ngx_cycle_t *cycle);
[...]
};
typedef struct ngx_module_s ngx_module_t;
Назначение полей:
version -- содержит версию модуля (на данный момент 1),
ctx -- указатель на глобальный контекст модуля,
commands -- указатель на вектор описателей директив модуля,
type -- тип модуля: NGX_HTTP_MODULE, NGX_EVENT_MODULE, NGX_MAIL_MODULE и другие,
init_module -- хэндлер, вызываемый при инициализации модуля в основном процессе,
init_process -- хэндлер, вызываемый при инициализации рабочего процесса,
exit_process -- хэндлер, вызываемый при завершении рабочего процесса,
exit_master -- хэндлер, вызываемый при завершении основного процесса.
Пример определения:
#include <ngx_config.h> #include <ngx_core.h> [...] ngx_module_t ngx_http_some_module = { NGX_MODULE_V1, &ngx_http_some_module_ctx, /* module context */ ngx_http_some_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING };
Примечание: экземпляр типа ngx_module_t должен быть объявлен с квалификатором extern. Поскольку все определения имеют квалификатор extern, то в примере он опущен.
HTTP-модули в поле ctx структуры ngx_module_t содержат указатель на структуру ngx_http_module_t.
typedef struct { ngx_int_t (*preconfiguration)(ngx_conf_t *cf); ngx_int_t (*postconfiguration)(ngx_conf_t *cf); void *(*create_main_conf)(ngx_conf_t *cf); char *(*init_main_conf)(ngx_conf_t *cf, void *conf); void *(*create_srv_conf)(ngx_conf_t *cf); char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); void *(*create_loc_conf)(ngx_conf_t *cf); char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); } ngx_http_module_t;
Назначение полей структуры:
preconfiguration -- хэндлер, вызываемый перед обработкой файла конфигурации,
postconfiguration -- хэндлер, вызываемый после обработки файла конфигурации,
create_main_conf -- хэндлер, вызываемый для создания основной конфигурации,
init_main_conf -- хэндлер, вызываемый для инициализации основной конфигурации,
create_srv_conf -- хэндлер, вызываемый для создания конфигурации виртуального сервера,
merge_srv_conf -- хэндлер, вызываемый для слияния конфигураций виртуального сервера,
create_loc_conf -- хэндлер, вызываемый создания конфигурации location'а,
merge_loc_conf -- хэндлер, вызываемый для слияния конфигураций location'а.
Любое из полей может содержать значение NULL, означающее, что вызывать хэндлер не нужно.
Пример определения:
ngx_http_module_t ngx_http_some_module_ctx = { ngx_http_some_module_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_some_module_create_loc_conf, /* create location configuration */ NULL /* merge location configuration */ };
Директивы конфигурации описываются структурой 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; }; #define ngx_null_command { ngx_null_string, 0, NULL, 0, 0, NULL } typedef struct ngx_command_s ngx_command_t;
Назначение полей структуры:
name -- название директивы,
type -- тип директивы, число аргументов и блоки, в которых может присутствовать директива:
Флаг | Значение |
NGX_CONF_NOARGS | Директива не принимает аргументов |
NGX_CONF_TAKE1 ... NGX_CONF_TAKE7 | Директива принимает указанное количество аргументов |
NGX_CONF_TAKE12 | Директива принимает 1 или 2 аргумента |
NGX_CONF_TAKE13 | Директива принимает 1 или 3 аргумента |
NGX_CONF_TAKE123 | Директива принимает 1, 2 или 3 аргумента |
NGX_CONF_TAKE1234 | Директива принимает 1, 2, 3 или 4 аргумента |
NGX_CONF_BLOCK | Дополнительный аргумент директивы является блоком |
NGX_CONF_FLAG | Директива является флагом |
NGX_CONF_ANY | Директива принимает 0 или более аргументов |
NGX_CONF_1MORE | Директива принимает 1 или более аргументов |
NGX_CONF_2MORE | Директива принимает 2 или более аргументов |
NGX_DIRECT_CONF | Директива может присутствовать в основном файле конфигурации |
NGX_MAIN_CONF | Директива может присутствовать на корневом уровне конфигурации |
NGX_ANY_CONF | Директива может присутствовать на любом уровне конфигурации |
NGX_HTTP_MAIN_CONF | Директива может присутствовать на уровне физического HTTP-сервера |
NGX_HTTP_SRV_CONF | Директива может присутствовать на уровне виртуального HTTP-сервера |
NGX_HTTP_LOC_CONF | Директива может присутствовать на уровне location'а |
NGX_HTTP_LMT_CONF | Директива может присутствовать в блоке limit_except |
NGX_HTTP_LIF_CONF | Директива может присутствовать в блоке if() на уровне локейшена |
Список директив модуля описываться вектором, который заканчивается значением ngx_null_command. Пример:
typedef struct { ngx_str_t foobar; } ngx_http_some_module_loc_conf_t; static ngx_command_t ngx_http_some_module_commands[] = { { ngx_string("foobar"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_some_module_loc_conf_t, foobar), NULL }, ngx_null_command };
Описана директива foobar, принимающая 1 аргумент. Директива может присутствовать на основном уровне конфигурации, конфигурации виртуального HTTP-сервера и локейшена. Аргумент директивы конвертируется в строку и записывается в поле foobar конфигурации модуля .
Перед обработкой файла конфигурации структуры, содержащие конфигурацию, должны быть выделены и инициализированы. Для этого используются хэндлеры create_main_conf, create_srv_conf и create_loc_conf.
Для упрощения конфигурирования, на каждом из уровней конфигурации создается шаблон конфигурации каждого из последующих уровней. Рассмотрим пример:
http { server { zip_buffers 10 4k; location /foobar { # gzip_buffers 10 4k наследована с предыдущего уровня gzip on; } location /foobaz { # gzip_buffers 10 4k наследована с предыдущего уровня } } }
При обработке блока server будет создана шаблонная конфигурация для модуля ngx_http_gzip_filter_module и к ней будет применена директива gzip_buffers. При переходе к обработке блока location /foobar {} будет создана ещё одна конфигурация для модуля ngx_http_gzip_filter_module и к ней будет применена директива gzip. После обработки блока http {} шаблонную конфигурацию необходимо слить с конфигурацией блоков /foobar и /foobaz. В процессе слияния все неустановленные параметры конфигурации заполняются значениями из соответствующих параметров шаблонной конфигурации, либо значением по-умолчанию. Для упрощения слияния используются следующие макросы:
Для слияния конфигурации виртуального сервера используется хэндлер merge_srv_conf, для конфигурации location'a используется хэндлер merge_loc_conf. Пример:
typedef struct { ngx_str_t str_param; ngx_uint_t int_param; } ngx_http_sample_module_loc_conf_t; static char * ngx_http_sample_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_sample_module_loc_conf_t *prev = parent; ngx_http_sample_module_loc_conf_t *conf = child; ngx_conf_merge_str_value(conf->str_param, prev->str_param, "default value"); ngx_conf_merge_uint_value(conf->int_param, prev->int_param, 1); return NGX_CONF_OK; } ngx_http_module_t ngx_http_some_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_some_module_create_loc_conf, /* create location configuration */ ngx_http_some_module_merge_loc_conf /* merge location configuration */ };
Поддерживаемые модулем переменные должны быть созданы перед разбором блоков файла конфигурации, в которых эти переменные могут встретиться. Чтобы создать переменные до разбора файлов конфигурации нужно использовать хэндлер preconfiguration в структуре ngx_http_module_t. Пример:
static ngx_int_t ngx_http_some_module_add_variables(ngx_conf_t *cf); ngx_http_module_t ngx_http_some_module_ctx = { ngx_http_some_module_add_variables, /* preconfiguration */ [...] }; static ngx_http_variable_t ngx_http_some_module_variables[] = { { ngx_string("var"), NULL, ngx_http_some_module_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_null_string, NULL, NULL, 0, 0, 0 } }; static ngx_int_t ngx_http_some_module_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_some_module_variables; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; }Для генерации значения переменной необходимо реализовать функцию, которая заполняет структуру ngx_http_variable_value_t, используя данные из запроса, из контекстов, конфигураций модулей или из других источников. Пример:
static ngx_int_t ngx_http_some_module_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { v->valid = 1; v->no_cacheable = 1; v->not_found = 0; v->data = (u_char*)"42"; v->len = 2; return NGX_OK; }
Для сборки модуля с nginx необходимо указать путь к каталогу с исходным кодом модуля скрипту ./configure в параметре --add-module= командной строки. В указанном каталоге должен находиться файл config. Файл config -- это скрипт, который система сборки nginx включает и выполняет на стадии конфигурации. Задача скрипта -- установить набор переменных, управляющих сборкой. Список наиболее важных переменных:
Имя перменной | Назначение |
ngx_addon_name | Имя текущего дополнительного модуля |
NGX_ADDON_SRCS | Список всех исходных файлов всех дополнительных модулей, которые нужно скомпилировать |
NGX_ADDON_DEPS | Список всех зависимых файлов всех дополнительных модулей (как правило заголовочные файлы). |
HTTP_MODULES | Список всех HTTP модулей |
HTTP_AUX_FILTER_MODULES | Список всех вспомогательных фильтров |
USE_MD5 | Использовать ли поддержку MD5 (YES/NO) |
USE_SHA1 | Использовать ли поддержку SHA-1 (YES/NO) |
USE_ZLIB | Использовать ли библиотеку zlib (YES/NO) |
Для ссылок на каталог, в котором расположены файлы модуля, используется переменная ngx_addon_dir. Пример файла config:
ngx_addon_name=ngx_http_sample_module HTTP_MODULES="$HTTP_MODULES ngx_http_sample_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_sample_module.c"
Предположим, файлы модуля расположены в каталоге /home/valery/work/sample_module. Для включения модуля nginx нужно конфигурировать следующим образом:
path/to/nginx$ ./configure --add-module=/home/valery/work/sample_module
Далее:
path/to/nginx$ make path/to/nginx$ make install
Инсталлированному экземпляру nginx станут доступны директивы и переменные подключенного модуля.
Это -- недописанная глава. Она должна содержать важный и интересный материал, но у автора пока нет хорошей идеи, относительно того, как его преподнести.
Valery Kholodkov valery+nginx@grid.net.ru
Пожалуйста, используйте расширение адреса при составлении письма мне.
Nginx: www.sysoev.ru/nginx/ -- это веб-сервер разработанный Игорем Сысоевым.