Библиотека HTTP-parser#
Описание#
HTTP-parser — это библиотека для разбора HTTP/1.x-запросов и ответов, написанная на языке C.
Библиотека ориентирована на высокопроизводительные системы. В процессе работы она не выделяет память и не буферизует данные, а поддерживает только минимальное состояние — порядка нескольких десятков байт на соединение. Разбор ведется пошагово, данные обрабатываются по мере поступления. Встроена автоматическая поддержка декодирования Transfer-Encoding: chunked и обработка механизма Upgrade, который позволяет корректно завершить HTTP-разбор и передать оставшиеся данные следующему протоколу, например WebSocket. Также HTTP-parser защищен от атак переполнения буфера.
Интеграция выполняется в три шага: подготовка структуры настроек с указанием функций-обработчиков, инициализация парсера и вызов функции разбора для каждого блока полученных данных. Callback-функции, которые задает разработчик, вызываются по мере обнаружения соответствующих элементов сообщения: начала, отдельных полей заголовков, тела, окончания. Все данные, которые требуется сохранить, необходимо копировать в собственные структуры внутри этих Callback-функций, так как парсер не хранит их после завершения обработки.
Парсер извлекает из HTTP-сообщений следующие данные:
Поля заголовков и их значения;
Длина содержимого (Content-Length);
Метод запроса;
Код статуса ответа;
Тип кодировки передачи (
Transfer-Encoding);Версия HTTP;
URL запроса;
Тело сообщения.
Установка#
Для установки библиотеки HTTP-parser выполните команду:
dnf install http-parser
Сценарий использования библиотеки#
Для каждого TCP-соединения создается объект типа http_parser. Он инициализируется функцией http_parser_init() и настраивается callback-функциями. Пример для парсера запросов:
http_parser_settings settings;
settings.on_url = <url_callback>;
settings.on_header_field = <header_field_callback>;
/* ... */
http_parser *parser = malloc(sizeof(http_parser));
http_parser_init(parser, HTTP_REQUEST);
parser->data = <socket>;
При получении данных с сокета запускается парсер через http_parser_execute() и проверяется результат на ошибки:
size_t len = 80*1024, nparsed;
char buf[len];
ssize_t recved;
recved = recv(fd, buf, len, 0);
if (recved < 0) {
}
nparsed = http_parser_execute(parser, &settings, buf, recved);
if (parser->upgrade) {
} else if (nparsed != recved) {
}
При отсутствии длины содержимого сигналом конца потока для парсера служит вызов функции http_parser_execute() с длиной данных, равной нулю. Callback-функции и ошибки могут возникать до полного завершения разбора данных.
Информация о скалярных значениях сообщения (например, код статуса ответа, метод запроса, версия HTTP) хранится временно в структуре http_parser и сбрасывается при новом сообщении. Если эти данные потребуются позже, их необходимо скопировать из структуры во время callback headers_complete.
Парсер автоматически декодирует Transfer-encoding для запросов и ответов (например, chunked) до вызова callback on_body.
Обновление протокола#
Парсер поддерживает обновление соединения до другого протокола, например, WebSocket.
Пример запроса на обновление соединения:
GET /demo HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: <domain>
Origin: <domain>
WebSocket-Protocol: sample
Запрос на обновление включает специальные HTTP-заголовки, сигнализирующие о переходе на новый протокол, после чего дальнейшая передача данных происходит вне протокола HTTP.
Парсер обрабатывает такие сообщения как обычные HTTP-запросы без тела, вызывает callback-функции on_headers_complete и on_message_complete, а затем прекращает разбор и возвращает управление. Необходимо проверить поле parser->upgrade — если оно равно 1, значит соединение обновлено, и дальнейшая передача данных происходит согласно правилам нового протокола.
Callback-функции#
Во время вызова http_parser_execute() выполняются callback-функции, заданные в http_parser_settings. Парсер сохраняет состояние и не выполняет обратный просмотр, поэтому буферизация входных данных не требуется. Для сохранения информации, необходимой для дальнейшей обработки, используется выделение и копирование данных внутри callback-функций.
Существуют два типа callback-функций:
Функции-уведомления - вызываются для информирования о событиях в ходе разбора (например, начало сообщения, окончание заголовков, конец сообщения). Имеют тип
int (*http_cb) (http_parser*)Функции для обработки данных — вызываются при получении конкретных фрагментов данных, таких как URL, поля заголовков, значения заголовков и тело сообщения. Имеют тип
int (*http_data_cb)(http_parser*, const char *at, size_t length).
Обе группы callback-функций должны возвращать 0 при успешном выполнении. Любое ненулевое значение прерывает разбор и сигнализирует об ошибке.
Для передачи локальных данных в callback и обратной связи (например, в многопоточном процессе, где один поток обрабатывает соединение, парсит запрос и формирует ответ) используется поле data структуры http_parser. В это поле помещается указатель на структуру с контекстом, доступный внутри всех callback-функций, что обеспечивает безопасный обмен данными.
Если HTTP-сообщение разбирается по частям, callback-функция для данных может вызываться несколько раз подряд. Парсер гарантирует, что переданный указатель на данные валиден только во время callback-функции.
При частичном разборе заголовков необходимо отслеживать, какой callback был вызван последним — для поля заголовка или для его значения.
Для передачи данных без создания дополнительных копий строки разбора URL предусмотрена функция http_parser_parse_url(), которую можно применять для URL, собранных из нескольких вызовов on_url.