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

6102cc4983a995f1e0562caa113b729b.jpg

Привети, Хабр!

Сегодня рассмотрим, как написать кастомный модуль для 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 указываем путь к нашему будущему модулю.

Пишем минимальный модуль

Модуль состоит из двух частей:

  1. Кода самого модуля

  2. Конфигурационного файла

Создаём структуру проекта:

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-подключения на вашем сервере. Записаться

© Habrahabr.ru