Pull to refresh

Обработка POST тела в разрабатываемых модулях

Reading time 5 min
Views 3.2K
На сегодняшний день достаточно руководств и описаний, как разработать собственный модуль под nginx. Для тех, кто на танке, ссылки можно найти на сайте nginx
Но, как использовать данные POST, к сожалению информации кот наплакал.


Начну с того, что Игорь Сысоев (автор nginx ) очень осторожно смотрит на использование POST данных в модулях. И это в принципе, обоснованно: как правило, в POST данные занимают большой объём по сравнению с объемом заголовком, по этому и механизм обработки у них иной. А обработка лишних данных всегда может косвенно повлиять на производительность. Процессы в nginx должны быть как можно легче, чтоб вокеры как можно быстрее обработали соединение и приступили к обработке следующего.

Рассмотрим цикл обработки:
  • фаза чтения запроса;
  • фаза преобразование URI на уровне сервера;
  • поиск конфигурации в которой будет обрабатываться запрос;
  • фаза преобразование URI на уровне location;
  • фаза обработки результатов преобразование URI запроса;
  • подготовительная фаза для проверки доступа;
  • фаза проверки доступа;
  • фаза обработки результатов проверки доступа;
  • фаза обработки try_files;
  • фаза генерации ответа;
  • фаза записи логов.


Как правило, все http модули вешаются на фазу генерации ответа. Исключение, конечно, upstream модули и фильтры. Более подробно у Emiller

В случае с POST все иначе, он может вообще не обрабатываться. Если мы посмотрим структуру запроса ngx_http_request_t * r, которая красной нитью проходит по всем http модулям, то в контент хандлере, значение полей:
r->request_body->buf — текущий буфер и
r->request_body->bufs — цепочка буферов данных POST запроса будут пустыми (NULL). Это потому, что он и не начинался обрабатываться.

Обработка POST запроса осуществляется установкой кэлбека «хандлер тела» в ngx_http_read_client_request_body (функция определена в http_request_body.c).
Должен быть приблизительно такой код:
 rc = ngx_http_read_client_request_body(r,    ngx_http_mymodule_body_handler   ); // устанавливаем обработчик "хандлер тела"<br/>
    // проверяем результат обработки, если не отработало, то возвращает код NGX_AGAIN,<br/>
 <br/>
    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {<br/>
        return rc;<br/>
    }<br/>
 <br/>
    return NGX_DONE;


ngx_http_mymodule_body_handler — это хандлер обработки тела POST запроса. В Хандлере тела, вызывается Хандлер фазы. Код Хандлера тела должен иметь приблизительно такой вид:
static void ngx_http_mymodule_body_handler ( request_body *) {<br/>
        ngx_int_t rc = NGX_OK;<br/>
 <br/>
        rc = ngx_http_mymodule_phase_handler ( r ); //  вызываем явно обработчик хандлера фазы <br/>
        if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {<br/>
            ngx_http_finalize_request(f,0); // если все удачно, то финализируем запрос, иначе зависним.<br/>
        }<br/>
        return;<br/>
    }<br/>
 



В Хандлере фазы осуществляется обработка чанков, содержащих POST данные. Хандлер фазы обрабатывает данные и вновь устанавливает кэллбэк на обработку тела. Код Хандлера фазы должен быть приблизительно такой:
 <br/>
ngx_int_t  ngx_http_mymodule_phase_handler (request_body *) {<br/>
 <br/>
        ngx_int_t rc = NGX_OK;<br/>
        if(r->request_body == NULL) { <br/>
         // в этом месте POST запрос еще не принимался, необходимо установить хандлер <br/>
            rc = ngx_http_read_client_request_body(r,  ngx_http_mymodule_body_handler   );<br/>
 <br/>
        if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {<br/>
            return rc;<br/>
        }<br/>
 <br/>
        return NGX_DONE;<br/>
    }<br/>
 <br/>
    // Всё ещё принимаем тело?<br/>
     if(r->request_body->rest) {<br/>
        return NGX_DONE;<br/>
    }<br/>
     return rc;<br/>
} <br/>
 




Данные POSt запроса находятся в структуре ngx_http_request_body_t определенной в ngx_http_request.h
typedef struct {<br/>
    ngx_temp_file_t               *temp_file; // имя временного файла (если необходимо)<br/>
    ngx_chain_t                     *bufs; // цепочка вх. буферов<br/>
    ngx_buf_t                        *buf;  // текущий буфер<br/>
    off_t                                 rest;  <br/>
    ngx_chain_t                     *to_write; <br/>
    ngx_http_client_body_handler_pt   post_handler;<br/>
} ngx_http_request_body_t;<br/>
 


указатель на эту структуру определен в структуре запроса ngx_http_request_s:
r->request_body;

Данные текущего окна определены фреймом буфера:
r->request_body->buf->start
r->request_body->buf->end
или при больших размерах POST запроса данные извлекаются из цепочки буферов r->request_body->bufs->buf->start...end и далее извлекается следующий буфер, адрес которого в поле next. см структуру ngx_chain_s:
// core/ngx_buf.h
struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};


size -- размер буфера (расстояние между start и end) должен соответствовать значению Content-Length заголовка определенного в r->headers_in->off_t ;
Для цепочки буферов - это сумма расстояний каждого из буферов.

Отдельное спасибо Валерию Холодкову за консультации.
Tags:
Hubs:
+4
Comments 7
Comments Comments 7

Articles