Привети, Хабр!
Сегодня рассмотрим, как написать кастомный модуль для 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 <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> 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-подключения на вашем сервере. Записаться
ссылка на оригинал статьи https://habr.com/ru/articles/890026/
Добавить комментарий