Ключевые слова:web, nginx, (найти похожие документы)
From: Валерий Холодков
Date: Mon, 25 Sep 2010 17:02:14 +0000 (UTC)
Group: Конференция HighLoad++ 2008
Subject: Разработка модулей для nginx
Оригинал: http://www.grid.net.ru/nginx/nginx-modules.html
Введение
Материал, который Вы читаете в данный момент, основан на моих
собственных исследованиях исходного кода и материалах списка рассылки
nginx. Поскольку я не являюсь автором nginx и участвовал в обсуждении
далеко не всех деталей реализации, приведенная информация может быть не
верна на 100%.
Вы предупреждены!
Материал организован следующим образом: в первой главе описываются
общие принципы работы асинхронных серверов и объясняется почему
разработка модулей для nginx требует особого подхода, во второй главе
описывается API, который предоставляет nginx модулям, в третьей главе
описываются особенности взаимодействия nginx и модулей, в четвертой и
последней главе описываются подходы к реализации модулей.
1. Асинхронные серверы
Главной особенностью реализации nginx, является то, что все сетевые
операции ввода-вывода выполняются асинхронно относительно выполнения
рабочих процессов. Это дает следующие преимущества:
* Минимизирует количество рабочих процессов, и таким образом:
* Минимизирует использование памяти, а так же:
* Минимизирует синхронизацию между рабочими процессами, и кроме того:
* Минимизирует количество системных вызовов
Однако обладает следующими недостатками:
* Требует специального подхода к программированию, поэтому:
* Невозможно использовать блокирующие операции ввода-вывода, и таким
образом большинство библиотек и сред
Рабочие процессы nginx выполняют цикл в начале которого с помощью
специального системного вызова все сокеты опрашиваются на предмет
появления событий. Событием может быть:
* Запрос на установку нового соединения (для слушающих сокетов);
* Факт появления данных во входном буфере сокета;
* Факт появления свободного пространства в выходном буфере сокета.
В завершении цикла выполняется обработка событий. Для всех запросов на
установку соединения выполняется прием нового соединения. Для всех
сокетов, имеющих данные во входном буфере выполняется прием и обработка
этих данных. Для всех сокетов, имеющих свободное пространство в
выходном буфере выполняется отправка новых данных. В каждом из этих
случаев используются неблокирующие операции.
Когда обрабатывается один сокет одним из рабочих процессов, все
остальные сокеты этого рабочего процесса продолжают ожидать обработки.
Если обработка одного сокета затягивается, то остальные сокеты начинают
испытывать "голод": приходящие данные скапливаются во входных буферах
сокетов, а готовые к записи сокеты не получают новых данных. На
клиентской стороне подобная ситуация выглядит как "зависание". Для
предотвращения голодания сокетов сервер и компоненты сервера должны
быть реализованы с использованием следующих принципов:
* Избегать длительных вычислительных процессов;
* Минимизировать число синхронизаций с другими процессами;
* Избегать блокирующих системных вызовов.
В UNIX дисковые операции ввода-вывода всегда блокирующие (за
исключением POSIX realtime API). Казалось бы, они должны противоречить
последнему принципу. Однако, за счет кэширования и упреждающего чтения
блокирование удается свести к минимуму.
Из-за описанных выше ограничений полномасштабные веб-приложения сложно
реализовать исключительно в модулях nginx.
2. API nginx
2.1. Управление памятью
Управление памятью в nginx осуществляется с помощью пулов. Пул -- это
последовательность предварительно выделенных блоков динамической
памяти. Пул привязан к объекту, который определяет время жизни всех
объектов, выделенных в пуле. Таким объектом может быть, например,
запрос или цикл обработки событий. Пулы используются исходя из
следующих соображений:
* для минимизации накладных расходов на выделение мелких объектов;
* для минимизации накладных расходов на удаление объектов;
* чтобы исключить утечки памяти;
* для уменьшения числа системных вызовов sbrk.
Для выделения памяти используются следующие функции:
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 -- пул, в который будет возвращена память;
size -- размер выделяемой структуры;
результат -- NGX_OK, если удалось освободить, NGX_DECLINED, если
невозможно освободить.
Для регистрации деструктора (например для закрытия файловых
дескрипторов или удаления файлов) используется следующие структура и
функции:
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 ...]
}
2.2. Векторы
Векторы в 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 -- вектор, который будет удален;
2.3. Управление буферами и очередями
2.3.1 Управление буферами
Буферы используются для отслеживания прогресса приема, отправления и
обработки данных. Данные могут располагаться в изменяемой или
неизменяемой памяти или в файле. Для изменяемой памяти актуальны
следующие поля:
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, указывающих на идентичные
данные, располагающиеся, например, в сегменте константных данных. Окно
определяет прогресс отправления этих данных.
Для выделения памяти под структуру 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.
2.3.2 Управление очередями
Очереди (или цепи) связывают несколько буферов в последовательность.
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, если не удалось выделить память;
функция ngx_chain_update_chains добавляет все обработанные или
отправленные буферы с тегом tag из цепи out и busy к цепи free,
оставшиеся добавляет к цепи busy.
pool -- пул, из которого будут выделены звенья при необходимости,
2.4. Строки
В nginx строки хранятся в Pascal-like форме с целью избежать накладных
расходов при вычислении длины, а так же копирования в некоторых
ситуациях.
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
len -- длина строки в байтах, data -- указатель на память, содержащую
строку.
2.5. Переменные
Переменные -- это именованные контейнеры данных, которые можно
преобразовывать в строки или из строк. Значения переменных могут
хранится в любой форме. Для преобразования из строк и в строки
используются эксессоры -- функции установки и получения значения
переменной:
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 -- тип функций,
вызываемых для получения значения переменной.
Аргументы функций:
r -- запрос, в контексте которого устанавливает или запрашивается
значение переменной,
v -- устанавливаемое или получаемое значение переменной,
data -- контекст переменной.
Сама переменная описывается следующей структурой:
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 -- имя переменной,
set_handler -- функция установки значения,
get_handler -- функция получения значения,
data -- контекст переменной,
flags -- флаги:
* NGX_HTTP_VAR_CHANGEABLE -- переменная изменяема (например командой
set модуля rewrite);
* NGX_HTTP_VAR_NOCACHEABLE -- значение не кэшируемо;
* [...]
Добавление переменной
Для добавления новой переменной используется следующая функция:
ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags);
cf -- конфигурация, в которой создается переменная,
name -- имя переменной,
flags -- флаги,
результат -- указатель на структуру ngx_http_variable_t.
Пример вызова:
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 -- конфигурация, в которой определена переменная,
name -- имя переменной,
результат -- индекс переменной.
Получения строкового значения переменной
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 -- запрос, в контексте которого запрашивается переменная,
index -- индекс переменной,
результат -- значение переменной в виде указателя на структуру
ngx_http_variable_value_t (синоним ngx_variable_value_t).
Поля в структуре ngx_variable_value_t означают следующее:
* len -- длина строкового значения перменной;
* data -- указатель на память, содержащую строковое значение
перменной;
* valid -- 1, если строковое значение переменной в кэше актуально;
* no_cacheable -- 1, если значение переменной не кэшируемо;
* not_found -- 1, если значение переменной не найдено;
2.6. Скрипты
Скрипты в 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. Пример:
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;
}
Для выполнения скрипта используется функция ngx_http_script_run:
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_http_script_variables_count для их подсчета:
ngx_uint_t ngx_http_script_variables_count(ngx_str_t *value);
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_http_script_run, проверив, что любой из векторов, сожержищих
байт-код, не инициализирован:
[...]
{
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.7. Регулярные выражения
Регулярные выражения в nginx реализованы на основе библиотеки PCRE и
доступны только при её наличии в системе. В исходном коде доступность
регулярных выражений индицируется макросом NGX_PCRE. Регулярные
выражения используюутся по принципу аналогичному скриптам:
компилируются один раз, затем многократно выполняются. Для компиляции
регулярного выражения используется функция ngx_regex_compile:
#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
pattern -- указатель на строку, содержащую регулярное выражение;
options -- флаги, задающие некоторые параметры;
pool -- пул, в котором будет выделена память под регулярное выражение;
err -- строка, содержащая текстовое описание ошибки, которая произошла
при компиляции регулярного выражения;
результат -- указатель на структуру, содержащую скомпилированное
регулярное выражение.
Параметр options может содержать флаг NGX_REGEX_CASELESS, означающий,
что регулярное выражение не чувствительно к регистру символов.
Пример компиляции регулярного выражения:
Для подсчета числа ссылок в регулярном выражении используется функция
ngx_regex_capture_count
#if (NGX_PCRE)
ngx_int_t ngx_regex_capture_count(ngx_regex_t *re);
#endif
re -- указатель на скомпилированное регулярное выражение;
результат -- число ссылок.
Для выполнения регулярного выражения используется функция
ngx_regex_exec
#if (NGX_PCRE)
ngx_int_t ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, ngx_int_t size);
#endif
re -- указатель на скомпилированное регулярное выражение;
s -- указатель строку, для которой будет выполнено регулярное выражение;
captures -- указатель вектор, в который будут помещены позиции
подстрок, соответствующие ссылкам;
size -- число элементов вектора captures;
результат -- 0, если регулярное выражение совпало,
NGX_REGEX_NO_MATCHED, если регулярное выражение не совпало, значение
меньше NGX_REGEX_NO_MATCHED если произошла ошибка.
Число элементов вектора captures должно быть ровно в три раза больше
числа ссылок в регулярном выражении. В первые две трети вектора
помещаются позиции подстрок, соответствующие ссылкам в регулярном
выражении, оставшаяся часть используется библиотекой PCRE для
внутренних нужд. Каждый первый элемент первых двух третей вектора
содержит позицию первого символа подстроки, каждый второй -- позицию,
слудющую за позицией последнего символа подстроки.
2.8. Конфигурация модуля
Конфигурация модуля во время выполнения храниться в бинарном виде в
структурах определяемых разработчиком. 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 -- структура [1]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 -- структура [2]ngx_module_t (описание модуля),
rvalue -- указатель на конфигурацию модуля соответствующего уровня.
2.9. Контекст модуля
Контекст модуля во время обработки запроса храниться в бинарном виде в
структурах определяемых разработчиком. Для установки контекста модуля
используется следующий макрос:
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 -- указатель на
контекст модуля.
3. Nginx и модули
3.1. Фазы обработки запросов в nginx
Nginx обрабатывает запросы с использованием нескольких фаз. На каждой
фазе вызываются 0 или более хэндлеров.
1. NGX_HTTP_SERVER_REWRITE_PHASE -- фаза преобразования URI запроса на
уровне виртуального сервера;
2. NGX_HTTP_FIND_CONFIG_PHASE -- фаза поиска контекста запроса
(location'a);
3. NGX_HTTP_REWRITE_PHASE -- фаза преобразования URI запроса на уровне
location'a;
4. NGX_HTTP_POST_REWRITE_PHASE -- фаза обработки результатов
преобразования URI запроса;
5. NGX_HTTP_PREACCESS_PHASE -- фаза подготовки данных для проверки
ограничений доступа к ресурсу;
6. NGX_HTTP_ACCESS_PHASE -- фаза проверки ограничений доступа к
ресурсу;
7. NGX_HTTP_POST_ACCESS_PHASE -- фаза обработки результатов проверки
ограничений доступа к ресурсу;
8. NGX_HTTP_CONTENT_PHASE -- фаза генерации ответа;
9. NGX_HTTP_LOG_PHASE -- фаза записи в лог.
На всех фазах могут быть зарегистрированы пользовательские хэндлеры, за
исключением следующих фаз:
* NGX_HTTP_FIND_CONFIG_PHASE. На этой фазе вызов хэндлеров не
происходит, вместо этого выполняется поиск контекста запроса
(location'a) и заполнение заголовка ответа "Location".
* NGX_HTTP_POST_ACCESS_PHASE. На этой фазе вызов хэндлеров не
происходит, а лишь применяется результат проверки доступа к
ресурсу. Фаза необходима для реализации директивы satisfy all/any.
* NGX_HTTP_POST_REWRITE_PHASE. На этой фазе вызов хэндлеров не
происходит, вместо этого выполняется
Хэндлером могут быть возвращены следующие константы:
* NGX_OK -- выполнение хэндлера завершено успешно, необходимо перейти
к выполнению следующей фазы запроса;
* NGX_DECLINED -- запрос не предназначен данному хэндлеру, необходимо
перейти к следующему хэндлеру;
* NGX_AGAIN, NGX_DONE -- выполнение хэндлера завершено успешно,
необходимо подождать появления некоторого события (например,
возможности записи в сокет) и повторить вызов хэндлера;
* NGX_ERROR, NGX_HTTP_... -- при выполнении хэндлера произошла
ошибка.
Для регистрации хэндлера необходимо обратиться к основной конфигурации
модуля 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_OK -- хэндлер позволяет обратиться к ресурсу;
* NGX_HTTP_FORBIDDEN, NGX_HTTP_UNAUTHORIZED -- хэндлер не позволяет
обратиться к ресурсу.
В случае satisfy all для перехода к следующей фазе необходимо, чтобы
все хэндлеры вернули NGX_OK. В случае satisfy any нужно чтобы хотя бы
один хэндлер вернул NGX_OK.
Фаза NGX_HTTP_CONTENT_PHASE используется для генерации ответа. Если в
конфигурации уровня location'а модуля ngx_http_core_module
переопределен параметр handler, то все запросы направляются этому
хэндлеру, в противном случае используются хэндлеры фазы
NGX_HTTP_CONTENT_PHASE из основной конфигурации. Пример переопределния
хэндлера 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_smaple_handler;
return NGX_CONF_OK;
}
3.2. Встраивание модуля в nginx
Для встраивания в 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-модуля
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 */
};
3.2.1. Описание и обработка директив конфигурации модуля
Директивы конфигурации описываются структурой 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() на уровне локейшена
set -- хэндлер, вызываемый в момент обнаружения директивы. Для удобства
реализовано множество стандартных хэндлеров:
Название хэндлера Тип данных Тип поля в конфигурации модуля, на который
указывает offset
ngx_conf_set_flag_slot Флаг ngx_flag_t
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_set_bitmask_slot Битовая карта ngx_uint_t
ngx_conf_set_path_slot Путь в файловой системе и число символов в хэшированных каталогах ngx_path_t
ngx_conf_set_access_slot Права доступа ngx_uint_t
conf -- уровень конфигурации модуля, на которую ссылается директива,
либо 0, если обработка директивы
offset -- смещение поля в конфигурации модуля, которое задается этой
директивой,
post -- указатель на дескриптор постпроцессинга,
Список директив модуля описываться вектором, который заканчивается
значением 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. В процессе слияния все неустановленные параметры
конфигурации заполняются значениями из соответствующих параметров
шаблонной конфигурации, либо значением по-умолчанию. Для упрощения
слияния используются следующие макросы:
Название Тип данных Тип поля в конфигурации модуля
ngx_conf_merge_ptr_value Указатель pointer
ngx_conf_merge_uint_value Целое число ngx_uint_t
ngx_conf_merge_msec_value Время в миллисекундах ngx_msec_t
ngx_conf_merge_sec_value Время в секундах time_t
ngx_conf_merge_size_value Длина size_t
ngx_conf_merge_bufs_value Число и размер буферов ngx_bufs_t
ngx_conf_merge_bitmask_value Битовая карта ngx_uint_t
ngx_conf_merge_path_value Путь в файловой системе и число символов в хэшированных каталогах ngx_path_t
Для слияния конфигурации виртуального сервера используется хэндлер
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 */
};
3.2.2. Описание и вычисление переменных модуля
Поддерживаемые модулем переменные должны быть созданы перед разбором
блоков файла конфигурации, в которых эти переменные могут встретиться.
Чтобы создать переменные до разбора файлов конфигурации нужно
использовать хэндлер preconfiguration в структуре [6]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;
}
Для генерации значения переменной необходимо реализовать функцию,
которая заполняет структуру [7]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;
}
3.3. Компиляция модуля и сборка с nginx
Для сборки модуля с 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 станут доступны директивы и
переменные подключенного модуля.
4. Модули
Это -- недописанная глава. Она должна содержать важный и интересный
материал, но у автора пока нет хорошей идеи, относительно того, как его
преподнести.
Связаться с автором
Valery Kholodkov <[email protected]>
Пожалуйста, используйте расширение адреса при составлении письма мне.
Ссылки
Nginx: http://www.sysoev.ru/nginx/ -- это веб-сервер разработанный Игорем Сысоевым.