Коротко про то, как написать кастомный модуль для Angie

Привети, Хабр!
Сегодня рассмотрим, как написать кастомный модуль для Angie — форка Nginx, который уже давно перерос в самостоятельного монстра с кучей фич.
Архитектуа Angie
Разберёмся, что такое модуль в контексте Angie (и Nginx, потому что архитектура похожа).
Важные моменты:
Модуль — это C‑библиотека, которая загружается динамически (если настроена поддержка DSO) или встраивается на этапе компиляции.
Он может добавлять директивы в конфиг, перехватывать события, менять обработку запросов.
Основные хуки:
preconfiguration
,postconfiguration
,init_module
,init_process
,handler
и другие.Можно писать фильтры, хендлеры, логеры и вообще менять что угодно.
Готовим окружение
Прежде чем написать модуль, установим всё необходимое:
# Ставим зависимости
sudo apt update && sudo apt install build-essential libpcre3-dev libssl-dev zlib1g-dev
# Клоним Angie (если у тебя его нет)
git clone https://github.com/angie-web/angie.git && cd angie
# Собираем минималку
./configure --with-debug --add-dynamic-module=../my_module
make && sudo make install
В --add-dynamic-module
указываем путь к нашему будущему модулю.
Пишем минимальный модуль
Модуль состоит из двух частей:
Кода самого модуля
Конфигурационного файла
Создаём структуру проекта:
mkdir -p ~/my_module/src
cd ~/my_module
Создаём src/ngx_http_my_module.c
и запихиваем туда минимальный рабочий код:
#include
#include
#include
static ngx_int_t ngx_http_my_handler(ngx_http_request_t *r) {
ngx_str_t response = ngx_string("Hello from my module!");
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = response.len;
ngx_http_send_header(r);
ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len);
ngx_memcpy(b->pos, response.data, response.len);
b->last = b->pos + response.len;
b->last_buf = 1;
ngx_chain_t out = { .buf = b, .next = NULL };
return ngx_http_output_filter(r, &out);
}
static ngx_command_t ngx_http_my_commands[] = {
{ ngx_string("my_directive"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_conf_t, my_enabled),
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_my_module_ctx = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
ngx_module_t ngx_http_my_module = {
NGX_MODULE_V1,
&ngx_http_my_module_ctx,
ngx_http_my_commands,
NGX_HTTP_MODULE,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NGX_MODULE_V1_PADDING
};
Этот код добавляет новую директиву my_directive, которая, когда включена, будет отвечать «Hello from my module!» на HTTP‑запрос.
Теперь создадим config файл:
echo "ngx_addon_name=\"ngx_http_my_module\"
HTTP_MODULES="\$HTTP_MODULES ngx_http_my_module"
NGX_ADDON_SRCS="\$NGX_ADDON_SRCS \$(ngx_feature_path ngx_http_my_module.c)"" > config
Собираем и тестируем
Компилируем модуль:
cd ~/my_module
make -f ../angie/objs/Makefile modules
После сборки появится .so
файл в objs/ngx_http_my_module.so
. Теперь его можно подключить в angie.conf
:
load_module modules/ngx_http_my_module.so;
server {
listen 8080;
location /test {
my_directive;
}
}
Рестартуем Angie и проверяем:
curl -i http://localhost:8080/test
Должно вернуться:
HTTP/1.1 200 OK
...
Hello from my module!
Теперь модуль готов.
Добавляем фичи
Пока наш модуль тупо шлёт текст, но сделаем что‑то полезное, например:
Авторизацию по токену
Логирование всех запросов в отдельный файл
Пример с авторизацией:
static ngx_int_t ngx_http_my_auth_handler(ngx_http_request_t *r) {
ngx_str_t token = ngx_string("supersecuretoken");
if (r->headers_in.authorization == NULL ||
ngx_strncmp(r->headers_in.authorization->value.data, token.data, token.len) != 0) {
return NGX_HTTP_FORBIDDEN;
}
return NGX_DECLINED;
}
Такой обработчик можно вставить перед отдачей контента, проверяя заголовок Authorization
.
Такой обработчик можно вставить перед отдачей контента, проверяя заголовок Authorization.
Логирование всех запросов в отдельный файл
static ngx_int_t ngx_http_my_logger_handler(ngx_http_request_t *r) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "Incoming request: %V", &r->uri);
return NGX_DECLINED;
}
Ограничение количества запросов от одного IP
static ngx_int_t ngx_http_rate_limit_handler(ngx_http_request_t *r) {
static ngx_rbtree_t *request_tracker;
static ngx_rbtree_node_t sentinel;
if (request_tracker == NULL) {
request_tracker = ngx_pcalloc(r->pool, sizeof(ngx_rbtree_t));
ngx_rbtree_init(request_tracker, &sentinel, ngx_str_rbtree_insert_value);
}
ngx_rbtree_node_t *node = ngx_rbtree_lookup(request_tracker, &r->connection->addr_text);
if (node == NULL) {
node = ngx_pcalloc(r->pool, sizeof(ngx_rbtree_node_t));
node->key = ngx_crc32_short(r->connection->addr_text.data, r->connection->addr_text.len);
ngx_rbtree_insert(request_tracker, node);
}
if (node->data >= 10) {
return NGX_HTTP_TOO_MANY_REQUESTS;
}
node->data++;
return NGX_DECLINED;
}
Код отслеживает количество запросов от одного IP и ограничивает их.
Динамическое изменение заголовков ответа
static ngx_int_t ngx_http_add_dynamic_header_handler(ngx_http_request_t *r) {
ngx_table_elt_t *h = ngx_list_push(&r->headers_out.headers);
h->hash = 1;
ngx_str_set(&h->key, "X-Server-Time");
h->value.data = ngx_pnalloc(r->pool, NGX_TIME_T_LEN);
h->value.len = ngx_sprintf(h->value.data, "%T", ngx_time()) - h->value.data;
return NGX_DECLINED;
}
Этот обработчик добавляет заголовок X‑Server‑Time с текущим временем.
Заключение
Дальше можно улучшать: кешировать ответы, проксировать запросы, подключать Redis. Напиши в комментах, какие ещё модули написать!
В заключение напомню про открытые уроки по Angie:
17 марта. Балансировка HTTP и L4 сервисов в Angie.
Поймёте основные типы балансировки в Angie, научитесь применять различные варианты решений для повышения отказоустойчивости веб-приложений. Записаться24 марта. Автоматические TLS-сертификаты: модуль ACME.
Научитесь настраивать модуль ACME в Angie, а также оптимально настраивать HTTPS-подключения на вашем сервере. Записаться