Привет, Хабр!
Nginx — это не просто веб‑сервер, а платформа, возможности которой можно расширять своими модулями. Если вам не хватает стандартного функционала и хочется больше контроля над обработкой запросов, кастомные модули могут позволить внедрять свою логику, оптимизировать маршрутизацию, фильтровать контент и реализовывать нестандартные механизмы аутентификации.
Начнем с базового модуля.
Простейший модуль «Hello World»
Когда я только начинал, всегда первым делом создавал модуль, который отвечал простым текстом. Минимальный пример модуля, который отвечает строкой «Hello World from custom Nginx module!».
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
// Обработчик запроса: возвращает приветственное сообщение
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t r) {
ngx_int_t rc;
ngx_buf_t b;
ngx_chain_t out;
// Устанавливаем тип контента
r->headers_out.content_type.len = sizeof("text/plain") - 1;
r->headers_out.content_type.data = (u_char )"text/plain";
// Выделяем буфер ответа из пула памяти
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// Текст ответа, который мы собираемся отправить
u_char response = (u_char )"Hello World from custom Nginx module!";
size_t response_len = ngx_strlen(response);
// Заполняем буфер
b->pos = response;
b->last = response + response_len;
b->memory = 1; // Данные находятся в памяти (не на диске)
b->last_buf = 1; // Это последний буфер в цепочке
out.buf = b;
out.next = NULL;
// Настраиваем заголовки ответа
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = response_len;
rc = ngx_http_send_header(r);
if (rc != NGX_OK) {
return rc;
}
return ngx_http_output_filter(r, &out);
}
// Функция для установки обработчика через директиву "hello"
static char ngx_http_hello(ngx_conf_t cf, ngx_command_t cmd, void conf) {
// Получаем конфигурацию текущей локации и назначаем наш обработчик
ngx_http_core_loc_conf_t clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_hello_handler;
return NGX_CONF_OK;
}
static ngx_command_t ngx_http_hello_commands[] = {
{ ngx_string("hello"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
ngx_http_hello,
0,
0,
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_hello_module_ctx = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
ngx_module_t ngx_http_hello_module = {
NGX_MODULE_V1,
&ngx_http_hello_module_ctx,
ngx_http_hello_commands,
NGX_HTTP_MODULE,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NGX_MODULE_V1_PADDING
};
В функции‑обработчике выделяем буфер из пула памяти (Nginx работает с памятью весьма бережно, так что никаких malloc‑ов — только пул), устанавливаем тип контента и формируем ответ. Этот модуль можно собрать как статически, так и динамически, а дальше можно расширять его по своему усмотрению.
Модуль с кастомной конфигурацией
Когда первый модуль уже прошел проверку временем, хочется добавить гибкости. Допустим, стоит задача задавать приветственное сообщение через конфигурацию Nginx — ну, чтобы не перекомпилировать сервер при каждом изменении идеи.
Рассмотрим модуль, который читает директиву hello_msg и использует её для формирования ответа. В этой версии создаем структуру конфигурации, регистрируем её и затем используем в обработчике.
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct {
ngx_str_t greeting;
} ngx_http_custom_conf_t;
// Создание конфигурации для локации
static void ngx_http_custom_create_loc_conf(ngx_conf_t cf) {
ngx_http_custom_conf_t conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_custom_conf_t));
if (conf == NULL) {
return NGX_CONF_ERROR;
}
// По умолчанию приветствие не задано
conf->greeting.len = 0;
conf->greeting.data = NULL;
return conf;
}
// Обработчик запроса, который использует кастомное сообщение
static ngx_int_t ngx_http_custom_handler(ngx_http_request_t r) {
ngx_int_t rc;
ngx_buf_t b;
ngx_chain_t out;
ngx_http_custom_conf_t conf;
conf = ngx_http_get_module_loc_conf(r, ngx_http_custom_module);
if (conf->greeting.len == 0) {
// Если сообщение не задано, используем стандартное
conf->greeting.data = (u_char )"Default custom greeting!";
conf->greeting.len = ngx_strlen(conf->greeting.data);
}
r->headers_out.content_type.len = sizeof("text/plain") - 1;
r->headers_out.content_type.data = (u_char )"text/plain";
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b->pos = conf->greeting.data;
b->last = conf->greeting.data + conf->greeting.len;
b->memory = 1;
b->last_buf = 1;
out.buf = b;
out.next = NULL;
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = conf->greeting.len;
rc = ngx_http_send_header(r);
if (rc != NGX_OK) {
return rc;
}
return ngx_http_output_filter(r, &out);
}
// Функция установки директивы "hello_msg"
static char ngx_http_custom_set_greeting(ngx_conf_t cf, ngx_command_t cmd, void conf) {
ngx_http_custom_conf_t custom_conf = conf;
ngx_str_t value = cf->args->elts;
custom_conf->greeting = value[1];
return NGX_CONF_OK;
}
static ngx_command_t ngx_http_custom_commands[] = {
{ ngx_string("hello"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
ngx_http_custom_handler,
0,
0,
NULL },
{ ngx_string("hello_msg"),
NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
ngx_http_custom_set_greeting,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_custom_conf_t, greeting),
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_custom_module_ctx = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
ngx_http_custom_create_loc_conf,
NULL
};
ngx_module_t ngx_http_custom_module = {
NGX_MODULE_V1,
&ngx_http_custom_module_ctx,
ngx_http_custom_commands,
NGX_HTTP_MODULE,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NGX_MODULE_V1_PADDING
};
Определили структуру ngx_http_custom_conf_t
для хранения строки приветствия. Функция ngx_http_custom_create_loc_conf
выделяет память под эту структуру, а функция установки директивы hello_msg
позволяет пользователю задавать своё сообщение через конфигурационный файл. Таким образом, в конфигурации Nginx можно написать:
location /custom {
hello;
hello_msg "Привет, мир! Это мое кастомное приветствие.";
}
Так можно менять поведение модуля можно на ходу, без перекомпиляции.
Фильтрующий модуль
Бывает, хочется не просто отдавать ответ, а немного пофантазировать с ним — добавить подпись, изменить формат, обогащать контент. Фильтрующие модули Nginx позволяют вмешиваться в цепочку формирования ответа. Я, например, люблю добавлять в конец HTML‑страницы комментарий с подписью «»
:
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static ngx_int_t ngx_http_filter_header_filter(ngx_http_request_t r);
static ngx_int_t ngx_http_filter_body_filter(ngx_http_request_t r, ngx_chain_t in);
// Указатели на следующие фильтры в цепочке
static ngx_http_output_header_filter_pt next_header_filter;
static ngx_http_output_body_filter_pt next_body_filter;
// Строка, которую мы добавляем в конец ответа
static ngx_str_t custom_footer = ngx_string("<!-- Powered by custom filter -->");
// Фильтр заголовков: пропускаем дальше без изменений
static ngx_int_t ngx_http_filter_header_filter(ngx_http_request_t r) {
return next_header_filter(r);
}
// Фильтр тела: добавляем наш footer к последнему буферу
static ngx_int_t ngx_http_filter_body_filter(ngx_http_request_t r, ngx_chain_t in) {
ngx_chain_t cl;
if (in == NULL) {
return next_body_filter(r, in);
}
// Ищем последний буфер в цепочке
for (cl = in; cl; cl = cl->next) {
if (cl->buf->last_buf) {
size_t footer_size = custom_footer.len;
// Создаем новый временный буфер для footer
ngx_buf_t b = ngx_create_temp_buf(r->pool, footer_size);
if (b == NULL) {
return NGX_ERROR;
}
ngx_memcpy(b->pos, custom_footer.data, footer_size);
b->last = b->pos + footer_size;
b->last_buf = 1;
// Снимаем флаг последнего буфера у старого буфера
cl->buf->last_buf = 0;
// Создаем новый элемент цепочки для footer
ngx_chain_t new_cl = ngx_alloc_chain_link(r->pool);
if (new_cl == NULL) {
return NGX_ERROR;
}
new_cl->buf = b;
new_cl->next = NULL;
cl->next = new_cl;
break;
}
}
return next_body_filter(r, in);
}
static ngx_int_t ngx_http_filter_init(ngx_conf_t cf) {
// Сохраняем указатели на существующие фильтры и заменяем их нашими функциями
next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_filter_header_filter;
next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_filter_body_filter;
return NGX_OK;
}
static ngx_http_module_t ngx_http_filter_module_ctx = {
NULL,
ngx_http_filter_init,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
ngx_module_t ngx_http_filter_module = {
NGX_MODULE_V1,
&ngx_http_filter_module_ctx,
NULL,
NGX_HTTP_MODULE,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NGX_MODULE_V1_PADDING
};
Когда приходит последний буфер, создаём дополнительный буфер с нашим комментарием и подставляем его вместо исходного последнего. Таким образом, к каждому ответу добавляется наш footer.
Модуль аутентификации
А теперь перейдем к более серьёзной задаче — базовой аутентификации. Допустим, хочется ограничить доступ к определённому ресурсу, проверяя наличие специального HTTP‑заголовка (например, X‑Auth‑Token). Если его нет — сервер сразу возвращает 403 Forbidden. Вот как это можно реализовать:
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static ngx_int_t ngx_http_auth_handler(ngx_http_request_t r) {
ngx_table_elt_t *h;
ngx_list_part_t part;
ngx_uint_t i;
ngx_str_t header_name = ngx_string(«X‑Auth‑Token»);
part = &r→headers_in.headers.part;
h = part→elts;
// Перебираем все заголовки, пока не найдем нужный
for (i = 0;; i++) {
if (i >= part→nelts) {
if (part→next == NULL) {
break;
}
part = part→next;
h = part→elts;
i = 0;
}
if (ngx_strcasecmp(h[i]→key.data, header_name.data) == 0) {
// Наш заголовок найден — пропускаем дальше
return NGX_DECLINED;
}
}
// Заголовок не найден — отдаем 403
return NGX_HTTP_FORBIDDEN;
}
static ngx_int_t ngx_http_auth_init(ngx_conf_t cf) {
ngx_http_handler_pt h;
ngx_http_core_main_conf_t cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf→phases[NGX_HTTP_PREACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_auth_handler;
return NGX_OK;
}
static ngx_http_module_t ngx_http_auth_module_ctx = {
NULL,
ngx_http_auth_init,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
ngx_module_t ngx_http_auth_module = {
NGX_MODULE_V1,
&ngx_http_auth_module_ctx,
NULL,
NGX_HTTP_MODULE,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NGX_MODULE_V1_PADDING
};
Обработчик перебирает все входящие заголовки, и если нужный заголовок не найден — сервер мгновенно отказывает в доступе.
Сборка и интеграция модулей
Какой бы модуль вы ни написали, он должен быть интегрирован в Nginx. Есть два варианта: статическая или динамическая сборка.
Статическая сборка
Скачиваем исходники Nginx, добавляем ваш модуль (например, в каталог /path/to/your/module), и конфигурируем сборку:
/configure ‑add‑module=/path/to/your/module
make
sudo make install
Динамическая сборка
Если вы хотите иметь возможность менять модули без перекомпиляции всего сервера (начиная с версии 1.9.11):
/configure ‑add‑dynamic‑module=/path/to/your/module
make modules
После сборки добавляем в конфигурационный файл nginx.conf строку:
load_module modules/ngx_http_your_module.so;
Это позволит серверу подгружать модуль при запуске.
А какие модули вы уже писали или планируете написать? Делитесь своим опытом в комментариях.
Больше актуальных навыков по IT-инфраструктуре вы можете получить в рамках практических онлайн-курсов от экспертов отрасли. В календаре мероприятий можно ознакомиться со списком открытых уроков.