Все статьи >> Разработка модулей для nginx Конференция HighLoad++ 2008 (просмотров: 2020)

Разработка модулей для nginx Конференция HighLoad++ 2008

Введение

Материал, который Вы читаете в данный момент, основан на моих собственных исследованиях исходного кода и материалах списка рассылки nginx. Поскольку я не являюсь автором nginx и участвовал в обсуждении далеко не всех деталей реализации, приведенная информация может быть не верна на 100%.

Вы предупреждены!

Материал организован следующим образом: в первой главе описываются общие принципы работы асинхронных серверов и объясняется почему разработка модулей для nginx требует особого подхода, во второй главе описывается API, который предоставляет nginx модулям, в третьей главе описываются особенности взаимодействия nginx и модулей, в четвертой и последней главе описываются подходы к реализации модулей.

1. Асинхронные серверы

Главной особенностью реализации nginx, является то, что все сетевые операции ввода-вывода выполняются асинхронно относительно выполнения рабочих процессов. Это дает следующие преимущества:

  • Минимизирует количество рабочих процессов, и таким образом:
  • Минимизирует использование памяти, а так же:
  • Минимизирует синхронизацию между рабочими процессами, и кроме того:
  • Минимизирует количество переключений контекста

Однако обладает следующими недостатками:

  • Требует специального подхода к программированию, поэтому:
  • Невозможно использовать блокирующие операции ввода-вывода, и таким образом большинство библиотек и сред

Рабочие процессы nginx выполняют цикл в начале которого с помощью специального системного вызова все сокеты опрашиваются на предмет появления событий. Событием может быть:

  • Запрос на установку нового соединения (для слушающих сокетов);
  • Факт появления данных во входном буфере сокета;
  • Факт появления свободного пространства в выходном буфере сокета.

В завершении цикла выполняется обработка событий. Для всех запросов на установку соединения выполняется прием нового соединения. Для всех сокетов, имеющих данные во входном буфере выполняется прием и обработка этих данных. Для всех сокетов, имеющих свободное пространство в выходном буфере выполняется отправка новых данных. В каждом из этих случаев используются неблокирующие операции.

 

Когда обрабатывается один сокет одним из рабочих процессов, все остальные сокеты этого рабочего процесса продолжают ожидать обработки. Если обработка одного сокета затягивается, то остальные сокеты начинают испытывать "голод": приходящие данные скапливаются во входных буферах сокетов, а готовые к записи сокеты не получают новых данных. На клиентской стороне подобная ситуация выглядит как "зависание". Для предотвращения голодания сокетов сервер и компоненты сервера должны быть реализованы с использованием следующих принципов:

  • Избегать длительных вычислительных процессов;
  • Минимизировать число синхронизаций с другими процессами;
  • Избегать блокирующих системных вызовов.

В UNIX дисковые операции ввода-вывода всегда блокирующие (за исключением POSIX realtime API). Казалось бы, они должны противоречить последнему принципу. Однако, за счет кэширования и упреждающего чтения блокирование удается свести к минимуму.

 

Из-за описанных выше ограничений полномасштабные веб-приложения сложно реализовать исключительно в модулях nginx.

2. API nginx

2.1. Управление памятью

Управление памятью в nginx осуществляется с помощью пулов. Пул -- это последовательность предварительно выделенных блоков динамической памяти. Пул привязан к объекту, который определяет время жизни всех объектов, выделенных в пуле. Таким объектом может быть, например, запрос или цикл обработки событий. Пулы используются исходя из следующих соображений:

  • для минимизации накладных расходов на выделение мелких объектов;
  • для минимизации накладных расходов на удаление объектов;
  • минимизаровать вероятность утечек памяти;
  • для уменьшения числа системных вызовов sbrk.

Для выделения памяти используются следующие функции:

void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);

pool -- пул, из которого будет выделена память;
size -- размер выделяемой памяти в байтах;
результат -- указатель на выделенную память, либо NULL, если не удалось выделить.

ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);

pool -- пул, в который будет возвращена память;
size -- размер выделяемой структуры;
результат -- NGX_OK, если удалось освободить, NGX_DECLINED, если невозможно освободить.

 

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;
    void                 *data;
    ngx_pool_cleanup_t   *next;
};

ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);

p -- пул, в котором будет зарегистрирован деструктор;
size -- размер выделяемой структуры-контекста, которая будет передана деструктору;
результат -- указатель на деструктор, если удалось выделить, NULL, если не удалось выделить.

ngx_pool_cleanup_t  имеют следующее значение:
handler -- хэндлер, который будет вызван при удалении пула;
data -- указатель на структуру-контекст, которая будет передана деструктору;
next -- указатель на следующий деструктор в пуле.

Пример использования:

static void ngx_sample_cleanup_handler(void *data);

static ngx_int_t ngx_http_sample_module_handler(ngx_http_request_t *r)
{
    ngx_pool_cleanup_t    *cln;
    ngx_sample_cleanup_t  *scln;

    cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_sample_cleanup_t));

    if(cln == NULL)
        return NGX_ERROR;

    cln->handler = ngx_sample_cleanup_handler;

    scln = cln->data;

    [... инициализация scln ...]
}

static void ngx_sample_cleanup_handler(void *data)
{
    ngx_sample_cleanup_t        *scln = data;

    [... использование scln ...]
}

2.2. Векторы

Векторы в nginx описываются следующей структурой:

struct ngx_array_s {
    void        *elts;
    ngx_uint_t   nelts;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *pool;
};

typedef struct ngx_array_s ngx_array_t;

pool -- пул, в котором будет распределена память под элементы;
elts -- указатель на элементы;
nelts -- число элементов в векторе в данный момент;
size -- размер элемента вектора в байтах;
nalloc -- число элементов, для которых распределена память в данный момент.

Для создания вектора используется функция ngx_array_create:

ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);

p -- пул, в котором будет распределена память под вектор и его элементы;
n -- число элементов, под которые будет зарезервирована память;
size -- размер элемента вектора в байтах;
результат -- указатель на вектор, если удалось выделить, NULL, если не удалось выделить.

typedef struct {
    [...]
} ngx_sample_struct_t;

{
    ngx_array_t *v;

    v = ngx_array_create(pool, 10, sizeof(ngx_sample_struct_t));

    if (v == NULL) {
        return NGX_ERROR;
    }
}

ngx_int_t ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size);

array -- указатель на структуру ngx_array_t;
p -- пул, в котором будет распределена память под элементы вектора;
n -- число элементов, под которые будет зарезервирована память;
size -- размер элемента вектора в байтах;
результат -- NGX_OK, если удалось выделить, NGX_ERROR, если не удалось выделить.

typedef struct {
    ngx_array_t v;
    [...]
} ngx_sample_struct_t;

{
    ngx_sample_struct_t t;

    if(ngx_array_init(&t.v, pool, 10, sizeof(ngx_sample_struct_t)) != NGX_OK) {
        return NGX_ERROR;
    }
}

void *ngx_array_push(ngx_array_t *a);
void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);

a -- вектор, к которому добавляется элемент(ы);
n -- число элементов, которые будут выделены;
результат -- указатель на элемент, если удалось выделить, NULL, если не удалось выделить.

typedef struct {
    [...]
} ngx_sample_struct_t;

{
    ngx_array_t *v;
    
    [...]

    h = ngx_array_push(v);
    if (h == NULL) {
        return NGX_ERROR;
    }
    
    [... использование h...]
}

void ngx_array_destroy(ngx_array_t *a);

Общие сведения

В nginx (для 0.6.29 вполне актуально) есть связанные списки. Каждый список представляет собой структуру:

typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;

Здесь:

  • last — указатель на предыдущий элемент списка
  • part — указатель на текущий элемент списка
  • size — размер элементов списка. Т.е. если надо хранить строки, то размер будет равен sizeof(ngx_str_t)
  • nalloc — количество выделенных элементов памяти для одной части.
  • pool — указатель на пул памяти

Список состоит из частей (часть — выделенный блок памяти):

typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};

Здесь:

  • elts — указатель на память, где располагаются элементы списка
  • nelts — количество использованных элементов в части
  • next — указатель на следующую часть

Т.е. список — это несколько частей (минимум 1), в которых располагаются элементы. Каждая часть имеет свою длину и указатель на следующую часть или NULL, если в ней еще есть место. Размер всех частей равен.

Работа со списком

Создание

Для создания списка используется функция:

ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);

size — размер одного элемента, n — количество элементов в части, pool — пул памяти, в котором выделяется память.

Функция создает новый список и инициализирует его (создает первую часть и определяет размер части). Возвращаемое значение — указатель на список или NULL в случае ошибки.

Инициализация

Список можно не создавать, а просто инициализировать (если у нас он определен на стеке), используя функцию:

static ngx_inline ngx_int_t ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)

Параметры ее аналогичны ngx_list_create, за исключением первого — это указатель на список, который будет инициализироваться (или переинициализироваться). Функция возвращает либо NGX_OK в случае успеха, либо NGX_ERROR в случае проблем. Функция небезопасна, т.е. можно переинициализировать любой существующий список и все старые данные потеряются ☺

Добавление элемента

Для добавления элемента в список используется функция:

void *ngx_list_push(ngx_list_t *list);

Она принимает указатель на список и возвращает указатель на место в части, в которое можно записывать значения. Если часть закончилась, создается новая и возвращается указатель на ее первый элемент.

Навигация по элементам списка

Для навигации по списку можно использовать такой код:

part = &list.part;
data = part->elts;

for (i = 0 ;;i++) {
  if (i >= part->nelts) {
    if (part->next == NULL) {
      break;
    }

    part = part->next;
    data = part->elts;
    i = 0;
  }

  /* use element of list as data[i] */

}

Вместо комментария вставляется ваш код, в котором будет происходить обращение к элементам списка.

Пример использования списка

Пример функции, в которой создается список из двух частей и осуществляется навигация по нему:

void ngx_list_sample_using(ngx_http_request_t *r)
{
  ngx_list_t       list;
  ngx_uint_t       i;
  ngx_list_part_t *part;
  ngx_uint_t      *list_element;
  ngx_uint_t      *sum = 0;

  if (ngx_list_init(&list, r->pool, 5, sizeof(ngx_uint_t)) == NGX_ERROR) {
    return;
  }

  for (i = 0; i < 10; i++) {
    list_element = ngx_list_push(&list);
    if (list_element == NULL) {
      return;
    }
    *list_element = i; 
  }

  part = &list.part;
  data = part->elts;

  for (i = 0 ;;i++) {
    if (i >= part->nelts) {
      if (part->next == NULL) {
        break;
      }

      part = part->next;
      data = part->elts;
      i = 0;
    }

  sum += data[i];

  }
/* here sum is 45 */
}

2.4. Управление буферами и очередями

2.4.1 Управление буферами

Буферы используются для отслеживания прогресса приема, отправления и обработки данных. Заголовок буфера описывается структурой ngx_buf_t. Данные буфера могут располагаться в изменяемой или неизменяемой памяти или в файле. Для изменяемой памяти актуальны следующие поля:

typedef struct ngx_buf_s ngx_buf_t;

struct ngx_buf_s {
    [...]

    u_char          *pos; /* начало окна */
    u_char          *last; /* конец окна */
    u_char          *start; /* начало буфера */
    u_char          *end; /* конец буфера */

    unsigned         temporary:1; /* = 1 */

    [...]
};

struct ngx_buf_s {
    [...]

    u_char          *pos; /* начало окна */
    u_char          *last; /* конец окна */
    u_char          *start; /* начало буфера */
    u_char          *end; /* конец буфера */

    unsigned         memory:1; /* = 1 */

    [...]
};

struct ngx_buf_s {
    [...]

    off_t            file_pos; /* начало окна */
    off_t            file_last /* конец окна */
    ngx_file_t      *file; /* указатель на файл */

    unsigned         in_file:1; /* = 1 */

    [...]
};

 

struct ngx_buf_s {
    [...]

    unsigned         recycled:1; /* буфер повторно использован после освобождения */
    unsigned         flush:1; /* все буферизированные данные должны быть обработаны и переданы на следующий уровень после обработки этого буфера */
    unsigned         last_buf:1; /* указывает на то, что буфер является последним в потоке данных */
    unsigned         last_in_chain:1; /* указывает на то, что буфер является последним в данной цепи (очереди) */
    unsigned         temp_file:1; /* указывает на то, что буфер располагается во временном файле */

    [...]
};

ngx_buf_t, указывающих на идентичные данные, располагающиеся, например, в сегменте константных данных или в конфигурации модуля . Окно определяет прогресс отправления этих данных.

Для выделения памяти под структуру ngx_buf_t используются макросы:

ngx_alloc_buf(pool);
ngx_calloc_buf(pool);

pool -- пул, из которого будет выделен буфер;
rvalue -- указатель на структуру 
ngx_buf_t, если удалось выделить, NULL, если не удалось выделить. После выделения все поля структуры необходимо инициализировать. Макрос ngx_calloc_bufпреобразуется в функцию, которая в дополнение зполняет выделенную память нулями.

Для выделения памяти под временный буфер используется следующая функция:

ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size);

pool -- пул, из которого будет выделен буфер;
size -- размер буфера (расстояние между start и end);
результат -- указатель на структуру 
ngx_buf_t, если удалось выделить, NULL, если не удалось выделить. После выделения pos и last будут равны start и флаг temporary будет установлен в 1.

2.4.2 Управление очередями

Очереди (или цепи) связывают несколько буферов в последовательность, которая определяет порядок приема, отправления или обработки данных.

struct ngx_chain_s {
    ngx_buf_t    *buf; /* буфер, связанный с текущим звеном */
    ngx_chain_t  *next; /* следующее звено */
};

typedef struct ngx_chain_s ngx_chain_t;

Для выделения памяти под цепи используются следующие функции:

typedef struct {
    ngx_int_t    num;
    size_t       size;
} ngx_bufs_t;

ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool);
ngx_chain_t *ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs);
ngx_chain_t *ngx_chain_get_free_buf(ngx_pool_t *p, ngx_chain_t **free);

функция ngx_alloc_chain_link выделяет память под одно звено из пула;

функция ngx_create_chain_of_bufs выделяет память под цепь звеньев и буферы;

функция ngx_chain_get_free_buf выделяет звено из цепи cвободных буферов или из пула, если цепь пуста.

pool -- пул, из которого будет(будут) выделен(ы) звенья/буферы при необходимости;
bufs -- структура, описывающая размер и число буферов;
free -- указатель на цепочку свободных буферов;
результат -- указатель на структуру ngx_chain_t, если удалось выделить, NULL, если не удалось выделить.

ngx_free_chain(pool, cl)

ngx_int_t ngx_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain,
    ngx_chain_t *in);
void ngx_chain_update_chains(ngx_chain_t **free, ngx_chain_t **busy,
    ngx_chain_t **out, ngx_buf_tag_t tag);

функция ngx_chain_add_copy добавляет буферы из цепи in к цепи chain, выделяя новые звенья. Возвращает NGX_OK, если удалось успешно добавить, NGX_ERROR, если не удалось выделить память;

функция ngx_chain_update_chains добавляет все обработанные или отправленные буферы с тегом tag из цепи out и busy к цепи free, оставшиеся добавляет к цепи busy.

pool -- пул, из которого будут выделены звенья при необходимости,

 

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;


 

 

typedef void (*ngx_http_set_variable_pt) (ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data);
typedef ngx_int_t (*ngx_http_get_variable_pt) (ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data);

ngx_http_set_variable_pt -- тип функций, вызываемых для установки значения переменной; ngx_http_get_variable_pt -- тип функций, вызываемых для получения значения переменной.

Аргументы функций:
r -- запрос, в контексте которого устанавливает или запрашивается значение переменной,
v -- устанавливаемое или получаемое значение переменной,
data -- контекст переменной.

Сама переменная описывается следующей структурой:

struct ngx_http_variable_s {
    ngx_str_t                     name;
    ngx_http_set_variable_pt      set_handler;
    ngx_http_get_variable_pt      get_handler;
    uintptr_t                     data;
    ngx_uint_t                    flags;

    [...]
};

name -- имя переменной,
set_handler -- функция установки значения,
get_handler -- функция получения значения,
data -- контекст переменной,
flags -- флаги:

ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name,
    ngx_uint_t flags);

cf -- конфигурация, в которой создается переменная,
name -- имя переменной,
flags -- флаги,
результат -- указатель на структуру ngx_http_variable_t.

Пример вызова:

static ngx_str_t  ngx_http_var_name = ngx_string("var");

[...]

{
    ngx_http_variable_t  *var;

    var = ngx_http_add_variable(cf, &ngx_http_var_name, NGX_HTTP_VAR_NOCACHEABLE);
}

ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);

cf -- конфигурация, в которой определена переменная,
name -- имя переменной,
результат -- индекс переменной.

 

typedef struct {
    unsigned    len:28;

    unsigned    valid:1;
    unsigned    no_cacheable:1;
    unsigned    not_found:1;

    [...]

    u_char     *data;
} ngx_variable_value_t;

typedef ngx_variable_value_t  ngx_http_variable_value_t;

ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r,
    ngx_uint_t index);

r -- запрос, в контексте которого запрашивается переменная,
index -- индекс переменной,
результат -- значение переменной в виде указателя на структуру ngx_http_variable_value_t (синоним ngx_variable_value_t).
Поля в структуре ngx_variable_value_t означают следующее:

 

typedef struct {
    ngx_conf_t                 *cf; /* указатель на конфигурацию ngx_conf_t */
    ngx_str_t                  *source /* компилируемый шаблон */;

    ngx_array_t               **lengths; /* код для определения длины результата */
    ngx_array_t               **values; /* код для генерации результата */
    
    ngx_uint_t                  variables; /* предполагаемое число переменных */

    unsigned                    complete_lengths:1; /* генерировать код для определения длины */
    unsigned                    complete_values:1; /* генерировать код для генерации значения */
} ngx_http_script_compile_t;

static ngx_str_t ngx_http_script_source = ngx_string("Your IP-address is $remote_addr");

{
    ngx_http_script_compile_t   sc;
    ngx_array_t                 *lengths = NULL;
    ngx_array_t                 *values = NULL;

    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

    sc.cf = cf;
    sc.source = &ngx_http_script_source;
    sc.lengths = &lengths;
    sc.values = &values;
    sc.variables = 1;
    sc.complete_lengths = 1;
    sc.complete_values = 1;

    if (ngx_http_script_compile(&sc) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}

u_char *ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
    void *code_lengths, size_t reserved, void *code_values);

r -- запрос, в контексте которого выполняется скрипт,
value -- указатель на строку, в которую будет помещен результат,
code_lengths -- указатель на код для получения длины результата,
reserved -- зарезервированный аргумент,
code_values -- указатель на код для получения результата,
результат -- указатель на байт памяти, следующий за последним байтом результата, либо NULL, если при выполнении скрипта произошла ошибка.

Пример:

[...]
{
    ngx_str_t value;

    if (ngx_http_script_run(r, &value, lengths->elts, 0,
        values->elts) == NULL)
    {
        return NGX_ERROR;
    }

    [...]
}

ngx_uint_t ngx_http_script_variables_count(ngx_str_t *value);

value -- указатель на строку,
результат -- число переменных в строке.

Пример:

static ngx_str_t ngx_http_script_source = ngx_string("Your IP-address is $remote_addr");

{
    ngx_int_t                   n;
    ngx_http_script_compile_t   sc;
    ngx_array_t                 *lengths = NULL;
    ngx_array_t                 *values = NULL;

    n = ngx_http_script_variables_count(&ngx_http_script_source);

    if(n > 0) {
        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

        sc.cf = cf;
        sc.source = &ngx_http_script_source;
        sc.lengths = &lengths;
        sc.values = &values;
        sc.variables = n;
        sc.complete_lengths = 1;
        sc.complete_values = 1;

        if (ngx_http_script_compile(&sc) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }

    return NGX_CONF_OK;
}

[...]
{
    ngx_str_t value;

    if (lengths == NULL) {
        value.data = ngx_http_script_source.data;
        value.len = ngx_http_script_source.len;
    }else{
        if (ngx_http_script_run(r, &value, lengths->elts, 0,
            values->elts) == NULL)
        {
            return NGX_ERROR;
        }
    }

    [...]
}

#if (NGX_PCRE)
typedef pcre  ngx_regex_t;

ngx_regex_t *ngx_regex_compile(ngx_str_t *pattern, ngx_int_t options,
    ngx_pool_t *pool, ngx_str_t *err);
#endif

pattern -- указатель на строку, содержащую регулярное выражение;
options -- флаги, задающие некоторые параметры;
pool -- пул, в котором будет выделена память под регулярное выражение;
err -- строка, содержащая текстовое описание ошибки, которая произошла при компиляции регулярного выражения;
результат -- указатель на структуру, содержащую скомпилированное регулярное выражение.

Параметр options может содержать флаг NGX_REGEX_CASELESS, означающий, что регулярное выражение не чувствительно к регистру символов.


 

#if (NGX_PCRE)
ngx_int_t ngx_regex_capture_count(ngx_regex_t *re);
#endif

re -- указатель на скомпилированное регулярное выражение;
результат -- число ссылок.

#if (NGX_PCRE)
ngx_int_t ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures,
    ngx_int_t size);
#endif

re -- указатель на скомпилированное регулярное выражение;
s -- указатель строку, для которой будет выполнено регулярное выражение;
captures -- указатель вектор, в который будут помещены позиции подстрок, соответствующие ссылкам;
size -- число элементов вектора captures;
результат -- 0, если регулярное выражение совпало, NGX_REGEX_NO_MATCHED, если регулярное выражение не совпало, значение меньше NGX_REGEX_NO_MATCHED если произошла ошибка.


ngx_http_conf_get_module_main_conf(cf, module)
ngx_http_conf_get_module_srv_conf(cf, module)
ngx_http_conf_get_module_loc_conf(cf, module)

cf -- указатель на структуру ngx_conf_t (конфигурация),
module -- структура 
ngx_module_t (описание модуля),
rvalue -- указатель на конфигурацию модуля соответствующего уровня.

 

Для доступа к конфигурациям всех уровней во время обработки запроса используются следующие макросы:

ngx_http_get_module_main_conf(r, module)
ngx_http_get_module_srv_conf(r, module)
ngx_http_get_module_loc_conf(r, module)

r -- указатель на структуру ngx_http_request_t (запрос),
module -- структура 
ngx_module_t (описание модуля),
rvalue -- указатель на конфигурацию модуля соответствующего уровня.

2.10. Контекст модуля

Контекст модуля во время обработки запроса храниться в бинарном виде в структурах определяемых разработчиком. Для установки контекста м


Автор: Валерий Холодков
 
 
Email:
не зарегистрированы?
Пароль:
забыли?
 
Астраханские магазины