- Брюс Уэйн: Что это?
- Люциус Фокс: Бэтмобиль? Ох… лучше вам не знать.
Чтобы лучше разобраться в Nginx'е, web-сервере, надо сначала понять Бэтмена, персонажа комиксов.
Бэтмен быстр. Nginx быстр. Batman борется с преступностью. Nginx борется с лишней нагрузкой на процессор и утечками памяти. Бэтмен хорошо держится под натиском врага. Nginx, в свою очередь, держится превосходно при очень большой нагузке на сервер.
Но кем бы был Бэтмен без своего Пояса.
Рисунок 1: Пояс Бэтмена, в обнимку с пузом Кристиана Бэйла.
В любой момент в Поясе Бэтмена может найтись набор отмычек, пара бумерангов, бэт-наручники, бэт-маячок, бэт-дротик, прибор ночного видения, термитные гранаты, дымовая завеса, фонарик, обруч из криптонита, паяльная лампа или iPhone. Если Бэтмену надо кого-то усыпить, ослепить, оглушить, выследить, пристукнуть, притормозить, довести до слез или заэсэмэсить насмерть, то он тянется к своему Поясу. Для Бэтмена он так много значит, что он скорей забыл бы одеть штаны, чем пояс. А у Бэтмена штанов и нет, вместо них ему приходится носить резиновые бронеритузы (рис. 1).
Вместо Пояса Бэтмена у Nginx имеется свой набор модулей. Когда нужно сжать запрос или передать его по частям, Nginx запускает соответсвующий модуль. Когда Nginx блокирует доступ с какого-либо IP адреса или проверяет данные HTTP-авторизации, на самом деле это делает один из модулей. Если Nginx подсоединяется к Memcache или FastCGI-серверу, то именно модуль связывает их.
Пояс Бэтмена напичкан всякими полезностями, но иногда Бэтмену нужно что-то новенькое. Например, обнаружился новый противник, которого не удержать бэт-наручниками и не сломить бумерангом. Или Бэтмену скоро пригодится новая способность, такая как способность дышать под водой. Вот тогда-то Бэтмен и зовет Люциуса Фокса, чтобы тот придумал новую бэт-штуковину.
Рисунок 2: Брюс Уэйн (он же Бэтмен) со своим инженером Люциусом Фоксом.
Целью этого руководства является подробно рассказать вам о модулях Nginx, чтобы вы смогли стать как Люциус Фокс. После прочтения руководства вы сможете проектировать и реализовывать отличные модули, которые помогут Nginx делать то, чего он раньше не умел. Система модулей Nginx содержит много ньюансов и особенностей, так что вы, наверно, будете часто возвращаться к этому руководству. Я постарался изложить концепции настолько доступно, насколько это возможно. но, без сомнений, создание модулей для Nginx все равно остается трудной задачей.
Но кто говорил, что создавать бэт-штуковины будет легко?
- Для начала
- Устройство модулей в первом приближении
- Компоненты модуля Nginx
- Обработчики, фильтры и балансировщики нагрузки
Вы должны неплохо знать Си. Не просто его синтаксис, а то, как работать со структурами и не бояться указателей и ссылок на функции. А также иметь представление о препроцессоре и макросах. Если вам надо немного освежить знания, то ничто не сможет сравниться с K&R(англ.).
Полезно понимать основы HTTP. Мы же, вообще-то, собираемся работать с web-сервером.
Пригодятся знания структуры конфигурационного файла Nginx'а. Вот основные моменты: существуют четыре контекста (называеются они main — главный, server — сервер, upstream — апстрим, и location — локейшн) в которых могут быть директивы с одним и более параметрами. Директивы в главном контексте применяются ко всему-всему; директивы из котекста сервера применяются к конкретному хосту/порту; директивы в апстриме описывают набор бэкендов; а директивы в контексте локешна применяются к разным путям запроса (например, "/", "/images" и т.д.) Локешн наследует конфигурацию содержащему его серверному контексту, а сервер наследует главному контексту. Контекст апстрима не наследует никому, у него собственные директивы, которых больше нигде не используются. Я буду иногда упоминать эти четыре контекста, так что… не забывайте про них.
Ну что же, начнем!
- обработчики обрабатывают запрос и генерируют данные ответа
- фильтры обрабатывают данные, полученные от обработчика
- балансировщики выбирают бэкенд, которому передать запрос, если определено несколько бэкендов
Модули делают реальную работу, которую обычно делают web-серверы: когда Nginx отправляет файл или проксирует запрос к другому серверу, то это делает модуль-обработчик. Когда Nginx гзипит данные или обрабатывает SSI-директивы, он делает это с помощью модуля-фильтра. Ядро Nginx'а берет на себя работу с сетью и реализацию протоколов, а также запускает модули, которые необходимы для обработки запроса. Децентрализованная архитектура позволяет нам создавать отдельные компоненты, которые делают что-то, что нам нужно.
Замечание: в отличие от модулей Apache, модули Nginx'а не подгружаются динамически (другими словами, модули вкомпилированы прямо в бинарник Nginx'а).
Как же тогда модули задействуются? Обычно, на стадии загрузки сервера каждый обработчик получает шанс прикрепиться к каким-либо локейшнам из конфигурационного файла. Если несколько обработчиков попробуют занять один локейшн, то победит только один (в хорошем конфиге такого не произойдет). Обработчик может завершиться с тремя результатами: все хорошо, произошла ошибка, или он может отказаться от обработки локешна в пользу обработка по умолчанию (обычно, это выдача статических файлов).
Если обработчик является реверс-прокси, то ему понадобится помощь балансировщика нагрузки. Балансировщик получает запрос вместе с набором бэкендов и принимает решение, какому серверу передать запрос. Nginx поставлется с двумя модулями балансировки: round-robin, который выбирает серверы по очереди, и модуль с методом хеширования IP адреса, который гарантирует, что запрос конкретного клиента каждый раз будет передаваться одному и тому же бэкенду.
Если обработчик не вернул ошибку, управление перейдет к фильтрам. Один локешн могут фильтровать несколько модулей, так, например, ответ может быть сжат, а потом выдаваться chunk'ами. Порядок запуска фильтров определяется на этапе компиляции. Фильры используют классический паттерн «цепочка обязанностей»: запускается один фильтр, делает свою работу, потом запускается второй, и так далее, пока не выполнится последний фильтр, и Nginx завершит обработку запроса.
Самая вкусная особенность цепочки фильтров заключается в том, что один фильтр не должен ждать, пока другой завершит свою работу целиком. Можно начать обрабатывать результат работы предыдущего фильтра по мере поступления, почти как птоки (пайпы) в юниксе. Фильры оперируют буферами, размер которых, обычно, равен размеру страницы (4 Кб), но размер всегда можно задать в nginx.conf. Это означает, например, то, что что модуль может начать сжимать ответ и отправлять его клиенту еще до того, как бэкенд полностью передат данные ответа. Чудесно!
Чтобы увидеть картину в целом, рассмотрим типичный цикл обработки запроса:- клиент посылает HTTP-запрос;
- Nginx выбирает подходящий обработчик на основе конфига;
- балансировщик (если необходимо) выбирет бэкенд;
- обработчик делает свое дело и передает каждый буфер с данными результата первому фильтру;
- фильтр передает результаты второму фильтру;
- второй — третьему, третий — четвертому, и так далее;
- получившийся ответ отправляется клиенту.
- Прямо перед чтением конфигурационного файла
- Для каждой директивы конфигурации локешна или сервера по мере их поступления
- Когда Nginx инициализирует главную конфигурацию
- Когда Nginx инициализирует конфигурацию сервера (хост/порт)
- Когда Nginx мерджит конфигурацию сервера с главной конфигурацией
- Когда Nginx инициализирует конфигурацию локешна
- Когда Nginx мерджит конфигурацией сервера с вложенной конфигурацией локешна
- Когда запускается главный процесс Nginx'а
- Когда запускается новый рабочий процесс
- Когда рабочий процесс завершается
- Когда главный процесс завершается
- Для обработки запроса
- Для фильтрации заголовка ответа
- Для фильтрации тела ответа
- Для выбора бэкенда
- В момент инициализации запроса к бэкенда
- В момент переинициализации запроса к бэкенду
- Для обработки ответа от бэкенда
- В момент завершения работы с бэкендом
Боже мой! Это может смутить. В вашем распоряжении большая мощь, но можно начать делать что-то полезное, используя всего несколько хуков и соответсвующих функций. Время погрузиться в модули Nginx'а.
Как я уже сказал, в вашем распоряжении огромный запас гибкости для разработки модуля для Nginx'а. В этом разделе те части, которые есть практически в любом модуле. Это моможет нам лучше понять устройство модуля. А так же вы сможете оценить, когда можно будет перейти к написанию собственного модуля.
ngx_http_<название_модуля>_(main|srv|loc)_conf_t. Вот пример, взятый из модуля dav:
typedef struct {
ngx_uint_t methods;
ngx_flag_t create_full_put_path;
ngx_uint_t access;
} ngx_http_dav_loc_conf_t;
Заметьте, что в Nginx'е используются специальные типы данных (ngx_uint_t и ngx_flag_t). Это просто алиасы для простых типов данных, которые мы все знаем и любим (см. core/ngx_config.h, если есть сомнения).
2.2. Директивы модуля
Директивы описыватся в статическом массиве элементов типаngx_command_t. Вот пример того, как их определять (взят из маленького модуля, который я написал):
static ngx_command_t ngx_http_circle_gif_commands[] = {
{ ngx_string("circle_gif"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_circle_gif,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
{ ngx_string("circle_gif_min_radius"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_circle_gif_loc_conf_t, min_radius),
NULL },
...
ngx_null_command
};
А вот определение структуры ngx_command_t (той, что мы сейчас заполняем), оно взято из файла core/ngx_conf_file.h:
struct ngx_command_t {
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;
};
На первый взгляд слишком много, но у каждого поля свое назначение.
В name хранится имя директивы, обязательно без пробелов. Используется тип ngx_str_t, значение которого чаще всего создаются с помощью макроса ngx_str("proxy_pass"). Замечание: структура ngx_str_t состоит из поля data, которое содержит саму строку, и поля len, в котором хранится длина строки. В большинстве случаев Nginx использует эту структуру взамен обычных строк.
Значение type задается с помощью набора флагов, которые определяют, где можно использовать эту директиву, и сколько она принимает параметров. Значение получается с помощью бинарного или:
NGX_HTTP_MAIN_CONF: разрешает использовать директиву в главном контекстеNGX_HTTP_SRV_CONF: в контексте сервера (хоста)NGX_HTTP_LOC_CONF: в контексте локешнаNGX_HTTP_UPS_CONF: в контексте апстрима
NGX_CONF_NOARGS: сообщает, что директива не принимает аргументыNGX_CONF_TAKE1: принимает ровно 1 аргументыNGX_CONF_TAKE2: принимает ровно 2 аргумента- …
NGX_CONF_TAKE7: принимает ровно 7 аргументов
NGX_CONF_FLAG: тип аргумента должен быть булев??? ("on" или "off")NGX_CONF_1MORE: директиве принимает 1 или более аргументовNGX_CONF_2MORE: директиве принимает 2 или более аргументов
set хранится указатель на функцию, вызываемую для настройки какой-то части модуля; обычно, эта функция преводит данные из аргументов в удобный формат и сохраняет их в соответстующей структуре конфигурации модуля. Функция принимает три аргумента:
- указатель на структуру
ngx_conf_t, которая содержит переданные директиве аргументы - указатель на текущую структуру
ngx_command_t - указатель на собственную структуру конфигурации модуля
ngx_conf_set_flag_slot: переводит "on" или "off" в 1 или 0ngx_conf_set_str_slot: певодит строку параметра вngx_str_tngx_conf_set_num_slot: парсит число и возвращает его какintngx_conf_set_size_slot: парсит размер ("8k", "1m" и т.д.) и возвращает его какsize_t
ngx_command_t: conf и offset. conf указывае Nginx'у куда сохранить данные: в главную, серверную или конфигурационную структуру локешна (задается с помощью NGX_HTTP_MAIN_CONF_OFFSET, NGX_HTTP_SRV_CONF_OFFSET, or NGX_HTTP_LOC_CONF_OFFSET). offset указывает в какую часть структуры записать значение.
И,наконец, post это еще одна штукенция, которая может пригодится модулю на этапе конфигурации. Чаще всего равна NULL.
Набор директив заканчивается ngx_null_command.
2.3. Контекст модуля
Это статическая структура типаngx_http_module_t, в которой определяются несколько указателей на функции для создание трех конфигураций и сливания их вместе. Называют ее ngx_http_<имя_модуля>_module_ctx. Вот назначение этих функций по порядку:
- перед конфигурацией
- после конфигурации
- создание главной конфигурации (то есть выделение памяти и задание значений по умолчанию)
- инициализация главной конфигурации (переопределение данных на взятые из nginx.conf)
- создание конфигурации сервера
- сливание ее с главной конфигурацие
- создание конфигурации локешна
- сливание ее с конфигурацие сервера
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;
Указатели на те функции, которые вам не пригодятся, можете заполнить NULL, и Nginx сделает вид, что не заметил их.
Большинство обработчиков используют только две последние: чтобы выделить память для структуры конфигурации (называется ngx_http_<имя_модуля>_create_loc_conf), и слить ее с конфигурацией выше (называется ngx_http_<имя_модуля>_merge_loc_conf). Функция, сливающая вместе конфиги, также может вернуть ошибку, что остановит загрузку сервера.
Вот пример пример структуры контекста модуля:
static ngx_http_module_t ngx_http_circle_gif_module_ctx = {
NULL, /* перед конфигурацией */
NULL, /* после конфигурации */
NULL, /* создание главной конфигурации */
NULL, /* инициализация главной конфигурации */
NULL, /* создание конфигурации сервера */
NULL, /* сливание ее с главной конфигурацие */
ngx_http_circle_gif_create_loc_conf, /* создание конфигурации локешна */
ngx_http_circle_gif_merge_loc_conf /* сливание ее с конфигурацие сервера */
};
Пришло время разобраться со всем этим подробнее. Эти конфигурационные колбеки очень похожи во всех модулях и используют одну часть Nginx API, так что их надо хорошо знать.
2.3.1. create_loc_conf
Вот так выглядит минимальная реализация функцииcreate_loc_conf, взятая из моего модуля circle_gif (за подробностями прошу в исходник). Она получает структуру (ngx_conf_t) и возвращает вновь созданную структуру конфигурации модуля (в этом примере ngx_http_circle_gif_loc_conf_t).
static void *
ngx_http_circle_gif_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_circle_gif_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_circle_gif_loc_conf_t));
if (conf == NULL) {
return NGX_CONF_ERROR;
}
conf->min_radius = NGX_CONF_UNSET_UINT;
conf->max_radius = NGX_CONF_UNSET_UINT;
return conf;
}
Прошу заметить важную особенность управления памятью в Nginx'е: он сам позаботится о вызове free только тогда, когда для выделения памяти вы используете ngx_palloc (умную обертку для malloc) или ngx_pcalloc (умную обертку для calloc).
Возможными вариантами задания UNSET являются: NGX_CONF_UNSET_UINT, NGX_CONF_UNSET_PTR, NGX_CONF_UNSET_SIZE, NGX_CONF_UNSET_MSEC, и для всех случаев NGX_CONF_UNSET. UNSET показывает функции сливания конфигов, что это значение надо переопределить.
2.3.2. merge_loc_conf
Вот так выглядит функция сливания конфигов в модуле circle_gif:static char *
ngx_http_circle_gif_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_circle_gif_loc_conf_t *prev = parent;
ngx_http_circle_gif_loc_conf_t *conf = child;
ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);
ngx_conf_merge_uint_value(conf->max_radius, prev->max_radius, 20);
if (conf->min_radius < 1) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"min_radius должен быть больше или равен 1");
return NGX_CONF_ERROR;
}
if (conf->max_radius < conf->min_radius) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"max_radius должен быть больше или равен min_radius");
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
Приятной особенностью Nginx'а является набор функций для сливания разных типов данных (ngx_conf_merge_<тип_данных>_value); их аргументами являются
- значение текущего локешна
- значение, задаваемое, если #1 не установлено
- задаваемое по умолчанию, если не установлено ни #1, ни #2
ngx_conf_merge_size_value, ngx_conf_merge_msec_value и другие. Обратитесь к core/ngx_conf_file.h за полным списком.
if).NGX_CONF_ERROR. В этом случает запуск сервера прекращается. Так как сообщение выводится с уровнем NGX_LOG_EMERG, оно будет продублировано в поток ошибок. Кстати, core/ngx_log.h содержит полный список уровнй вывода.)
2.4. Описание модуля
Теперь добавим еще один уровель абстракции, структуруngx_module_t. Переменную назовем ngx_http_<имя_модуля>_module. В ней описываются указатели на контекст и директивы модуля вместе с остальными колбеками(завершение треда, завершение процесса и т.д.). Определение модуля иногда используется, чтобы найти какие-либо данные, связанные с этим модулем. Определение модуля часто выглядит так:
ngx_module_t ngx_http_<имя_модуля>_module = {
NGX_MODULE_V1,
&ngx_http_<имя_модуля>_module_ctx, /* контекст модуля */
ngx_http_<module name>_commands, /* директивы модуля */
NGX_HTTP_MODULE, /* тип модуля */
NULL, /* инициализация мастера */
NULL, /* инициализация модуля */
NULL, /* инициализация процесса */
NULL, /* инициализация треда */
NULL, /* завершение треда */
NULL, /* завершение процесса */
NULL, /* завершение мастера */
NGX_MODULE_V1_PADDING
};
…замените <имя_модуля> на что-нибудь полезное. Модули погут определять колбеки для моментов создания и уничтожения процессов и тредов, но большинство модулей стараются не усложнять себе жизнь. Чтобы посмотреть список аргументов, обратитесь к core/ngx_conf_file.h.)
2.5. Установка модуля
Модули устанавливаются двумя способами: обработчики чаще всего устанавливаются колбеком директивы, а фильтры устанавливаются в постконфигурационном колбеке в структуре контекста модуля. Наконец, мы собираемся указать Nginx'у, где искать наш код. Балансировщики в этом вопросе особенные, их мы рассмотрим позже в разделе Устройство балансировщиков нагрузки.2.5.1. Установка обработчика
Обработчики устанавливаются с помощью кода внутри колбеков, вызываемых директивами, которые относятся к модулю. Например, моя структураngx_command_t из модуля circle_gif выглядит примерно так:
{ ngx_string("circle_gif"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_circle_gif,
0,
0,
NULL }
Третьим аргументом как раз является колбек, в примере это ngx_http_circle_gif. Вспомним, что аргументами этого колбека являются: структура директивы (ngx_conf_t, в кторой сохранены параметры из конфигурационного файла), соответствующая структура ngx_command_t и указатель на структуру конфигурации модуля. В моем модуле circle_gif эта функция выглядит так:
static char *
ngx_http_circle_gif(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_circle_gif_handler;
return NGX_CONF_OK;
}
Она выполняет работу в два этапа. Во-первых, получает внутреннюю структуру описывающую этот локешн. Во-вторых, устанавливает в ней обработчик (тоже колбек). Просто, не правда ли?
2.5.2. Установка фильтра
Фльтры устанавливаются на этапе постконфигурации. Бывает два типа фильтров: фильтры заголовков которые обрабатывают HTTP-заголовки, и фильтры тела ответа, которые обрабатывают собственно данные. Мы устанавливаем оба за один раз. В качестве простого примера посмотрим на chunked-фильтр, его контекст выглядит так:
static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_chunked_filter_init, /* postconfiguration */
...
};
Вот что происходит в ngx_http_chunked_filter_init:
static ngx_int_t
ngx_http_chunked_filter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_chunked_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_chunked_body_filter;
return NGX_OK;
}
Что это значит? Если вы помните, фильтры работают по принципу цепочки обязанностей. Когда обработчик сгенерирует ответ, он вызывает две функции: ngx_http_output_filter, которая вызывает глобальную ngx_http_top_body_filter; и ngx_http_send_header, вызывает другую глобальную ngx_top_header_filter.
Функции ngx_http_top_body_filter и ngx_http_top_header_filter являются гловными в цепочках фильтров соответственно заголовка и тела ответа. Каждое звено в цепи содержит ссылку на следующее звено (ссылки называются ngx_http_next_body_filter и ngx_http_next_header_filter). Когда фильтр закончит выполнение, он просто вызывает следующий, пока не будет вызван специальный фильтр, который уже заворачивает данные в HTTP-ответ. Все что делает функция filter_init, это добавляет свой модуль в обе эти цепи. Эта функция сохраняет ссылку на бывший первым фильтр в своей собственной переменной и определяет свои колбеки, как первые в цепочках. Цепочка действует по принципу LIFO (Last In Frist Out): последним добавлен — первым обработан.
return ngx_http_next_body_filter();
Таким обазом, если очередь в цепочке фильтров дошла до последнего («специального») фильтра, просто возвращается "OK", но если проихошла ошибка, оставшаяся цепочка пропускается и Nginx выводит соответствующее сообщение об ошибке. Это простой однонаправленный список с бастрой обработкой ошибок, выполненный в виде указателей на функции. Превосходно.3. Обработчики, фильтры и балансировщики нагрузки
Теперь рассмотрим пару простейших модулей под микроскопом и разберемся как они работает3.1. Устройство обработчиков (не проксирующих)
Обработчики, обычно, выполняют четыре шага: получают конфигурацию локешна, генерируют соответствующий ответ, отправляют заголовки и отправляют тело ответа. Хентдлер получает один аргумент: структуру запроса. В структуре запроса хранится много молезной информации о запросе клиента. Такой как, метод запроса, URI и загловки. Мы рассмотрим эти четыре шага один за другим.3.1.1. Получение конфигурации локейшна
Это простая часть. Все, что надо сделать, это вызватьngx_http_get_module_loc_conf и передать параметрами текущий запрос и описание модуля. Вот соответствующая часть хендлера из моего модуля circle_gif:
static ngx_int_t
ngx_http_circle_gif_handler(ngx_http_request_t *r)
{
ngx_http_circle_gif_loc_conf_t *circle_gif_config;
circle_gif_config = ngx_http_get_module_loc_conf(r, ngx_http_circle_gif_module);
...
Вот так я получил доступ ко всем переменным, сохраненным на этапе сливания конфигов.
3.1.2. Генерация ответа
Это та самая интересная часть, где модули делают полезную работу. Нам поможет стуктура запроса, а именно эти ее свойства:
typedef struct {
...
/* пул памяти, используемый в функциях типа ngx_palloc */
ngx_pool_t *pool;
ngx_str_t uri;
ngx_str_t args;
ngx_http_headers_in_t headers_in;
...
} ngx_http_request_t;
uri это путь запроса, например "/query.cgi".
args содержит нераспарсенные параметры запроса (идущая за знаком вопроса часть), например "name=john".
headers_in хранит много полезной информации, такой как куки и информация о браузере, но большинству модулей эти данные не пригождаюися. Посмотрите http/ngx_http_request.h, если интересно.
Этого вполне достаточно, чтобы смочь составить какой-нибудь полезный ответ. Полное описание структуры ngx_http_request_t можно найти в http/ngx_http_request.h.
3.1.3. Отправка заголовков
Заголовки ответа живут в структуре называемойheaders_out. Она в свою очередь хранится в структуре запроса. Обработчик хапроса выставляет те заголовки, которые ему надо и вызывает ngx_http_send_header(r). Вот некоторые из самых полезный элементов headers_out:
typedef stuct {
...
ngx_uint_t status;
size_t content_type_len;
ngx_str_t content_type;
ngx_table_elt_t *content_encoding;
off_t content_length_n;
time_t date_time;
time_t last_modified_time;
..
} ngx_http_headers_out_t;
Остальное можно найти в http/ngx_http_request.h.
Так, например, если модуль должен выставить Content-Type в "image/gif", Content-Length в 100 и вернуть код ответа 200 OK, то следующий код поможет ему сделать это:
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = 100;
r->headers_out.content_type.len = sizeof("image/gif") - 1;
r->headers_out.content_type.data = (u_char *) "image/gif";
ngx_http_send_header(r);
Большинство стандартных загловоков HTTP доступны (где-либо) для изменения вами. Однако, некоторые хаголовки задать немного сложнее чем те, которые вы видели выше. На пример, content_encoding имеет тип (ngx_table_elt_t*), поэтому модуль должен сам выделить память для этого заголовка. Это можно сделать с помощью функции ngx_list_push, которая принимает ngx_list_t (похож на массив) и возвращает указатель на вновь созданный элемен в этом списке (типа ngx_table_elt_t). Код ниже устанавливает заголовок Content-Encoding в значение "deflate" и отправляет заголовки:
r->headers_out.content_encoding = ngx_list_push(&r->headers_out.headers);
if (r->headers_out.content_encoding == NULL) {
return NGX_ERROR;
}
r->headers_out.content_encoding->hash = 1;
r->headers_out.content_encoding->key.len = sizeof("Content-Encoding") - 1;
r->headers_out.content_encoding->key.data = (u_char *) "Content-Encoding";
r->headers_out.content_encoding->value.len = sizeof("deflate") - 1;
r->headers_out.content_encoding->value.data = (u_char *) "deflate";
ngx_http_send_header(r);
Этот механизм, обычно, используется тогда, когда заголовок может иметь более одного значения одновременно. Этот прием (теоретически) позволяет фильтрам легче добавлять или удалять соответствующие значения, не изменяя другие, так как им не приходится заниматься работой со строками.
3.1.4. Отправка тела ответа
Теперь, когда модуль сгенерировал ответ и записал его в память, ему необходимо присвоить овет специальному буферу и затем, передать буфер в специальное звено чепочки, а потом вызвать «отправку ответа» на этом звене. Зачем нужна звенья и цепочка? Nginx позволяет обработчикам генерировать (а фильтрам обрабатывать) ответ по одному буферу за раз. Каждое звено цепи хранит ссылку на следующие звен илиNULL если оно последнее. Чтобы не усложнять пример, предстваим, что у нас есть только один буфер (и одно звено цепи).
Сначала модуль должен объявить буфер и звено цепи.
ngx_buf_t *b;
ngx_chain_t out;
Следующим шагом надо выделить память для буфера и добавить его в данные ответа:
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"Не удалось выделить буфер ответа.");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b->pos = some_bytes; /* позиция первого байта в блоке данных */
b->last = some_bytes + some_bytes_length; /* последняя позиция */
b->memory = 1; /* данные храняться в памяти только для чтения */
/* (то есть фильтры должны скопировать эти данные перед обработкой, вместо того, чтобы изменять их) */
b->last_buf = 1; /* буферов в запросе больше не будет */
А здесь модуль присваивает буфер звену цепи:
out.buf = b;
out.next = NULL;
И наконец, мы отправляем ответ и возвращаем статут вызова отправки за один раз:
return ngx_http_output_filter(r, &out);
Цепочки буферов — это критически важная часть модели ввода/вывода в Nginx'е, так что вы должны ими хорошо овладеть.
last_buf, если мы можем определить, что он последний проверив "next" на NULL?
Ответ: цепь может быть незавершенной, то есть состоять из множества буферов, не не все буферы уже подготовлены в запросе или ответе. Таким образом, некоторые буферы будут в конце цепи, но не в конце запроса. И это приводит нас к…