Тела запросов: инкрементальное чтение фиксированных и chunked
Тело запроса — не кусок памяти, ожидающий вас, а поток. Глава разбирает автомат состояний xrtHttp1BodyRead — и выполняет обязательство /echo из главы 4.
Обзор
16.1 Введение
Тела приходят тремя способами: фиксированная длина (Content-Length), chunked и «конец по закрытию соединения» (без длины; редко и опасно). Все делят один интерфейс чтения — автомат pReq->body плюс xrtHttp1BodyRead. Кодировка вас не волнует: автомат сводит три режима к вопросу «сколько прочитано и есть ли ещё».
16.2 Автомат кадрирования
Ключевое понимание: разборщик HTTP/1.1 отвергает конфликтные объявления длины (Content-Length вместе с Transfer-Encoding — ошибка протокола) — одно из семейств среди 35 мелкогранулярных кодов ошибок; битый ввод останавливается до вашего бизнеса.
16.3 Паттерн цикла чтения
/* Полностью вычитать тело в динамический буфер (маленькие тела).
* Большие тела обрабатываются по мере поступления — см. вызов. */
static bool ReadBodyAll(XS_HttpReq* pReq, xbuffer** ppOut)
{
xbuffer* pBuf = xrtBufferCreate(...);
char arrChunk[8192];
for ( ;; ) {
xhttp1bodystatus st = xrtHttp1BodyRead(
pReq->body, arrChunk, sizeof(arrChunk), &iGot);
if ( st == XHTTP1_BODY_ERROR ) {
xrtBufferDestroy(pBuf);
return false; /* битый ввод */
}
if ( iGot > 0 ) xrtBufferAppend(pBuf, arrChunk, iGot);
if ( st == XHTTP1_BODY_DONE ) break;
if ( iGot == 0 ) {
/* в этом раунде ничего нового: реальный код входит в колбэк по событиям */
}
}
*ppOut = pBuf;
return true;
}
Что только что произошло: чтение инкрементальное — каждый вызов даёт «что есть сейчас»; если данные ещё не дошли, ждём событий (колбэк повторно входит в последующих циклах драйвера того же соединения). Именно поэтому большая загрузка не тащит всё тело в память: пришёл кусок — обработали кусок.
Теперь можно выполнить /echo из главы 4: POST → полностью вычитать тело → вернуть как есть через ReplyRaw главы 15 (с эхом Content-Type запроса).
16.4 Зонды целостности и 413
- body_limit (ручка Custom из главы 9): превышение — 413; линия обороны в драйвере, ваш колбэк вообще не вызывается
- Зонд целостности: драйвер даёт проверку, не потребляющую транспортный буфер, — пришло ли тело целиком, чтобы не отвечать на половине
- Битый chunked: плохие строки chunk, нелегальные трейлеры, сверхдлинные строки chunk — коды ошибок разборщика; соединение закрывается с 400
- Медленная атака: idle_timeout — временная линия; соединение, кормящее тело по байту, закрывается по истечении
16.5 Контракт
- Чтение: тело потребляется только через BodyRead; драйвер сверяет ваш расход с сетью — расхождение рвёт соединение.
- Завершение: перед XS_OK тело либо дочитано, либо явно возвращено драйверу на вычитку — XS_OK при получтении ломает keep-alive.
- Память: большие тела обрабатывайте по мере чтения, не берите ReadBodyAll по умолчанию.
Частые ошибки
- Симптом
- На keep-alive следующий запрос смещается / драйвер рвёт соединение.
- Причина
- У тела остался непотреблённый остаток — драйвер вычитает его за вас, но семантика ответа уже неполна (в части сценариев сразу провал). Дочитайте или явно откажитесь (с записью в лог) до возврата.
- Симптом
- Большие тела иногда обрабатываются наполовину.
- Причина
- 0 значит лишь «сейчас новых данных нет»; сигнал завершения — DONE автомата (или совпадение накопленной длины). Условие цикла обязано их различать.
Упражнения
Реализуйте /echo из главы 4: тело POST возвращается как есть.
Отправьте chunked-тело через curl -H "Transfer-Encoding: chunked" --data-binary и убедитесь, что цикл чтения не требует правок.
Реализуйте /hash: считать SHA-256 по мере чтения (семейство xrtSha), по завершении вернуть hex-дайджест — не кэшируя тело целиком.
Итоги
| Тезис | Вывод |
|---|---|
| Единый автомат | фиксированные / chunked / close — один интерфейс Read |
| Инкрементальность | кусок за куском; 0 = сейчас нет данных ≠ готово |
| Оборона | body_limit 413 · битое 400 · idle_timeout против медленных |
| Сверка потребления | перед XS_OK тело дочитано или явно возвращено |
- справочник xrt · http_te — детали протокола Transfer-Encoding
- справочник xrt · hash — потоковое семейство SHA (для вызова)