Как стать автором
Обновить
532.57
OTUS
Цифровые навыки от ведущих экспертов

Пишем модули для Nginx

Время на прочтение10 мин
Количество просмотров4.7K

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

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-инфраструктуре вы можете получить в рамках практических онлайн-курсов от экспертов отрасли. В календаре мероприятий можно ознакомиться со списком открытых уроков.

Теги:
Хабы:
Всего голосов 11: ↑9 и ↓2+8
Комментарии0

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS