Материал, который Вы читаете в данный момент, основан на моих собственных исследованиях исходного кода и материалах списка рассылки 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/ -- это веб-сервер разработанный Игорем Сысоевым.