Ориентация главы
Поверх фундамента главы 89 эта глава собирает полный слой сообщений HTTP/1: xrtHttp1RequestParse/xrtHttp1ResponseParse разбирают заголовки запроса/ответа из сети в структуру «стартовая строка + массив полей» (трёхзначный возврат, заимствованные представления), xrtHttp1ResponseWrite упаковывает строку состояния и поля за один раз, а поиск по полям напрямую использует фундаментные xrtHttpFieldFind/FieldNext (поиск без учёта регистра по разобранному массиву). Ключевое слово дизайна — привязанный массив: xhttp1head привязывает предоставленный вызывающим массив полей — один Head на поток, используемый вновь и вновь, ноль распределений на запрос; это фундаментный путь рантайма xhttp и самодельных шлюзов. Тело (chunked и прочее) — тема кадрирования главы 91; эта глава останавливается на «пустой строке, завершающей блок заголовков».
90.1引入
Сервер HTTP/1 обрабатывает десятки тысяч запросов в секунду, каждый заголовок около 1 КБ с дюжиной полей. Если каждый разбор распределяет массив полей и освобождает его после — блокировки и фрагментация аллокатора становятся узким местом раньше всего, а путь восстановления после OOM добавляет удар. Ответ XRT виден в самой форме API: xrtHttp1HeadInit(&Head, Fields, 8) привязывает стековый массив вызывающего к Head — разбор лишь заполняет массив представлениями; следующий запрос переиспользует тот же Head и массив, распределений ноль.
Второе проектное решение — трёхзначный возврат: XHTTP1_READY (полный заголовок прибыл), XHTTP1_MORE (входа мало, ждать данных), XHTTP1_ERROR (структура нелегальна). С соглашением «отказ не продвигает» потоковый сервер может бездумно «принял отрезок — попробовал разобрать — MORE: принял ещё» — HTTP-редакция цикла кадрирования главы 72.
90.2概念
Разбор: от байтов к структуре
xhttp1head после разбора: Method/Target (запрос) или Status/Reason (ответ), Version, массив полей (Fields/FieldCount — зона заполнения привязанного массива), Bytes (общее число байтов блока заголовков с завершающей пустой строкой — тело начинается отсюда). Модель трёхзначного возврата:
Запрос и ответ делят структуру Head (роль определяет вход вызова: RequestParse/ResponseParse). ResponseParse принимает параметр метода — семантика ответа зависит от метода запроса (ответ HEAD без тела; этот контекст в Plan главы 91 станет жёстким правилом).
Транспортные привязки парсера (http1_net / http1_tls)
Вход RequestParse/ResponseParse — непрерывная память — форма тестовых векторов и целых буферов. Настоящий приёмный путь выдаёт цепочку буферов движка (заголовок может быть разрезан на два блока, глава 65), а TLS-соединение — поток открытого текста через записи (глава 87). Слой привязок подключает парсер к этим двум настоящим источникам: xrtHttp1RequestParseBuffer/xrtHttp1ResponseParseBuffer потребляют xnetbuf (представления Head заимствуют буфер; только при заголовке на стыке выделяется непрерывный префикс по надобности — «функция не потребляет вход; вызывающий потребляет Head.Bytes после обработки»); xrtHttp1RequestParseTls/xrtHttp1ResponseParseTls едят xtlsstream напрямую (открытый текст не потребляется; на стыке записей объединяется только Header — без буфера фиксированного размера на соединение). В каждой привязке по две функции, но сигнатуры и трёхзначная семантика полностью совпадают с версиями в памяти: привязка тонка, потому что парсер с самого дизайна оставил «откуда приходят байты» параметру — защита лимитов, трёхзначные возвраты и дисциплина нуля выделений переносятся целиком. Сервер xhttp главы 104 и потоковый сервис главы 108 в своей основе идут именно по этим двум привязкам.
Поиск и обход
Типовые операции после разбора: по массиву Head.Fields напрямую применяются xrtHttpFieldFind/FieldNext главы 89 (без учёта регистра — имена HTTP-полей семантически регистронезависимы; одноимённые многозначные поля обходятся по порядку, стиль Set-Cookie), xrtHttpFieldGet/FieldGetUnique (взятие значения и семантика уникальности). В комбинации с токен-итератором главы 89 они покрывают составные суждения вида «содержит ли Connection upgrade» — HTTP-обновление главы 94 (рукопожатие WebSocket) использует именно эту комбинацию.
Упаковка: строка состояния + поля одной записью
xrtHttp1ResponseWrite(版本, 状态码, 短语, 字段数组, 数量, 输出, 容量, &长度) (версия, код состояния, фраза, массив полей, количество, вывод, ёмкость, &длина) — строка состояния и блок полей пишутся за один раз: "HTTP/1.1 200 OK\r\nContent-Type: ...\r\n\r\n". Атомарность ёмкости как в главе 88: нехватка — ноль записей. Запросная сторона зеркально — xrtHttp1RequestWrite.Почему не собирать printf-ом: каждый шаг пути сборки может отказать на середине (полсообщения), а детали формата (пробелы строки состояния, терминаторы полей) расползаются по коду — однократная упаковка собирает их в один вход, прогнанный через мутационное тестирование.
Потолки и оборона
xhttp1limits (опционально передаётся в Parse) управляет: потолком числа полей (ёмкость привязанного массива — естественный потолок), потолком общей длины блока заголовков, потолками длины метода/цели.Почему обязательно: злой клиент может прислать блок заголовков на 2 ГБ — парсер без потолков это усилитель памяти. Умолчания консервативны, прокси-сценарии расширяют по надобности — потолки это решение развёртывания, а не глобальная константа.
Предвестие слоёв Message и Body
API этой главы — до «конца блока заголовков». MessageParse главы 91 комбинирует «заголовок + тело» за один раз (для малых сообщений), семейство Body читает тело потоково (для прокси «принял-переслал») — оба слоя делят разбор заголовков этой главы.
90.3示例
Первая полная программа: разбор запроса и упаковка ответа
Программа ниже из examples/http/http1/main.c — кратчайший путь «принял запрос — отправил ответ»:
#include <stdio.h> #include <xrt.h> */ /* 展示零拷贝解析与直接响应封包的基础路径。 */ int main(void) { static const char Request[] = "GET /health HTTP/1.1\r\nHost: localhost\r\n\r\n"; static const xhttpfield ResponseFields[] = { { XRT_STR_INIT("Content-Type"), XRT_STR_INIT("application/json") }, { XRT_STR_INIT("Content-Length"), XRT_STR_INIT("11") } }; xhttpfield RequestFields[8]; xhttp1head Head; xbytesview Input; char Response[256]; size_t iSize; Input.Data = (cbytes)Request; Input.Size = sizeof(Request) - 1u; xrtHttp1HeadInit(&Head, RequestFields, 8); if ( xrtHttp1RequestParse( Input, &Head, NULL, NULL ) != XHTTP1_READY ) { return 1; } printf("%.*s %.*s\n", (int)Head.Method.Size, Head.Method.Data, (int)Head.Target.Size, Head.Target.Data); if ( !xrtHttp1ResponseWrite( XHTTP_VERSION_1_1, 200, XRT_STR_LITERAL("OK"), ResponseFields, 2, Response, sizeof(Response), &iSize ) ) { return 2; } fwrite(Response, 1, iSize, stdout); fwrite("{\"ok\":true}", 1, 11, stdout); return 0; }
Что только что произошло. (1) Вход — полные байты запроса (статический массив имитирует однократное прибытие); HeadInit привязывает 8-элементный стековый массив — разбор без распределений. (2) RequestParse возвращает READY: Head.Method/Target — заимствованные представления (указывают внутрь входа), печатается GET /health. (3) ResponseWrite упаковывает одной записью: строка состояния (версия/код/фраза) + два поля + пустая строка; iSize — полное число байтов, после которого тело {"ok":true} пишется сразу.Это весь ответ сервера: никакого построчного printf, никакой ручной сборки CRLF. В реальной службе вход приходит из приёмного буфера главы 67 (MORE → принимать дальше), вывод уходит в путь отправки (бюджет записи главы 68).
Вторая полная программа: полный обход возможностей Head
Вторая программа из examples/http1/head_tour/main.c — шесть строк покрывают потолки, поиск, итерацию, строку chunk и Message:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> int main(void) { static const char sFixed[] = "POST /a HTTP/1.1\r\n" "Host: a.test\r\n" "Content-Length: 2\r\n" "\r\n" "ok"; static const char sChunked[] = "POST /b HTTP/1.1\r\n" "Host: a.test\r\n" "Transfer-Encoding: gzip, chunked\r\n" "\r\n"; xhttp1limits Limits; xhttp1bodylimits BodyLimits; xhttp1bodyplan Plan; xhttp1body Body; xhttp1message Message; xhttp1errorinfo Error; xhttp1head Head; xhttpfield Fields[8]; xhttpfield Trailers[4]; xhttp1transfercoding Coding; const xhttpfield* pField; xbytesview View; xhttpnext Next; size_t iOffset = 0; size_t iSize = 0; size_t iBytes = 0; size_t iCount = 0; uint8 arrLine[16]; int iResult = 1; /* ---- 限额默认值 + request-target 合法性正反。 ---- */ xrtHttp1LimitsInit(&Limits); if ( (Limits.MaxStartLine != 8192u) || (Limits.MaxHead != 65536u) || (Limits.MaxFields != 100u) || xrtHttp1TargetValid(XRT_STR_LITERAL("/a b")) || xrtHttp1TargetValid(XRT_STR_LITERAL("/a#f")) || xrtHttp1TargetValid(XRT_STR_LITERAL("")) || !xrtHttp1TargetValid(XRT_STR_LITERAL("/api?v=1")) ) { goto Cleanup; } printf("http1: limits + target valid +/- ok\n"); /* ---- 字段查找 + Transfer-Encoding 迭代。 ---- */ xrtHttp1HeadInit(&Head, Fields, 8); if ( (xrtHttp1RequestParse( (xbytesview) { (const uint8*)sChunked, sizeof(sChunked) - 1u }, &Head, &Limits, &Error) != XHTTP1_READY) || ((pField = xrtHttp1Field(&Head, XRT_STR_LITERAL("host"))) == NULL) || (pField->Value.Size != 6u) || (memcmp(pField->Value.Data, "a.test", 6u) != 0) || (xrtHttp1Field(&Head, XRT_STR_LITERAL("x-nope")) != NULL) ) { goto Cleanup; } /* TE 迭代须在 Transfer-Encoding 字段值上进行。 */ if ( (pField = xrtHttp1Field(&Head, XRT_STR_LITERAL("transfer-encoding"))) == NULL ) { goto Cleanup; } iOffset = 0; if ( ((Next = xrtHttp1TransferCodingNext( pField->Value, &iOffset, &Coding)) != XHTTP_NEXT_ITEM) || (Coding.Name.Size != 4u) || (memcmp(Coding.Name.Data, "gzip", 4u) != 0) || (xrtHttp1TransferCodingNext(pField->Value, &iOffset, &Coding) != XHTTP_NEXT_ITEM) || (Coding.Name.Size != 7u) || (memcmp(Coding.Name.Data, "chunked", 7u) != 0) || (xrtHttp1TransferCodingNext(pField->Value, &iOffset, &Coding) != XHTTP_NEXT_END) ) { goto Cleanup; } printf("http1: field lookup + transfer-coding iteration ok\n"); /* ---- 分帧计划:chunked 头优先于 Content-Length。 ---- */ if ( !xrtHttp1RequestBodyPlan(&Head, &Plan) || (Plan.Mode != XHTTP1_BODY_CHUNKED) ) { goto Cleanup; } xrtHttp1HeadInit(&Head, Fields, 8); if ( (xrtHttp1RequestParse( (xbytesview) { (const uint8*)sFixed, sizeof(sFixed) - 1u }, &Head, &Limits, &Error) != XHTTP1_READY) || !xrtHttp1RequestBodyPlan(&Head, &Plan) || (Plan.Mode != XHTTP1_BODY_FIXED) || (Plan.Length != 2u) ) { goto Cleanup; } printf("http1: request body plan fixed/chunked ok\n"); /* ---- chunk-size 行:裸 size 与带扩展两种形态。 ---- */ if ( !xrtHttp1ChunkLineWrite(5u, XRT_STR_LITERAL(""), arrLine, sizeof(arrLine), &iSize) || (iSize != 3u) || (memcmp(arrLine, "5\r\n", 3u) != 0) || !xrtHttp1ChunkLineWrite(5u, XRT_STR_LITERAL(";x"), arrLine, sizeof(arrLine), &iSize) || (iSize != 5u) || (memcmp(arrLine, "5;x\r\n", 5u) != 0) ) { goto Cleanup; } printf("http1: chunk line 5 -> \"5\\r\\n\" (+ext) ok\n"); * BodyLimits 必须先初始化——消息解析会按限额约束正文。 ---- */ xrtHttp1BodyLimitsInit(&BodyLimits); * Head 容量不足时解析器返回 FIELDS。 */ xrtHttp1MessageInit(&Message, Fields, 8, NULL, 0); if ( (xrtHttp1RequestMessageParse( (xbytesview) { (const uint8*)sFixed, sizeof(sFixed) - 1u }, true, &Message, &Limits, &BodyLimits, &Error) != XHTTP1_READY) || ((View = xrtHttp1MessageBodyView(&Message)).Size != 2u) || (memcmp(View.Data, "ok", 2u) != 0) ) { goto Cleanup; } printf("http1: message parse -> borrowed body \"ok\" ok\n"); * 描述符 → 读入 trailer 区;另演示独立 TrailersParse。 ---- */ { static const char sChunkWire[] = "5\r\nhello\r\n0\r\n"; static const char sTrailer[] = "X-Sum: 3\r\n\r\n"; xhttp1bodystatus BodyStatus; xbytesview Data; /* 换回 chunked 请求头重建 Plan。 */ xrtHttp1HeadInit(&Head, Fields, 8); if ( (xrtHttp1RequestParse( (xbytesview) { (const uint8*)sChunked, sizeof(sChunked) - 1u }, &Head, &Limits, &Error) != XHTTP1_READY) || !xrtHttp1RequestBodyPlan(&Head, &Plan) || (Plan.Mode != XHTTP1_BODY_CHUNKED) || !xrtHttp1BodyInit(&Body, &Plan, NULL, 0u, &BodyLimits) ) { goto Cleanup; } /* 数据块与终止块分两步喂入。 */ BodyStatus = xrtHttp1BodyRead(&Body, (xbytesview) { (const uint8*)sChunkWire, sizeof(sChunkWire) - 1u }, false, &iBytes, &Data, &Error); if ( (BodyStatus != XHTTP1_BODY_DATA) || (Data.Size != 5u) || (memcmp(Data.Data, "hello", 5u) != 0) ) { goto Cleanup; } BodyStatus = xrtHttp1BodyRead(&Body, (xbytesview) { (const uint8*)sChunkWire + 8u, 5u }, false, &iBytes, &Data, &Error); if ( (BodyStatus != XHTTP1_BODY_MORE) || (iBytes != 5u) || /* 终止块后处于 trailer 态——此刻才能重绑。 */ !xrtHttp1BodyTrailers(&Body, Trailers, 4u) ) { goto Cleanup; } BodyStatus = xrtHttp1BodyRead(&Body, (xbytesview) { (const uint8*)sTrailer, sizeof(sTrailer) - 1u }, true, &iBytes, &Data, &Error); if ( (BodyStatus != XHTTP1_BODY_DONE) || (Body.TrailerCount != 1u) || !xrtHttp1BodyDone(&Body) ) { goto Cleanup; } /* 独立 trailer 区解析(不经 Body Reader)。 */ iCount = 0; if ( (xrtHttp1TrailersParse( (xbytesview) { (const uint8*)sTrailer, sizeof(sTrailer) - 1u }, Trailers, 4u, &BodyLimits, &iBytes, &iCount, &Error) != XHTTP1_READY) || (iCount != 1u) || (Trailers[0].Value.Size != 1u) ) { goto Cleanup; } } printf("http1: body trailers rebind + trailer parse ok\n"); iResult = 0; Cleanup: return iResult; }
Что только что произошло. (1) Строка 1 проверяет модель потолков: длина target в пределе проходит, сверх — отказ; обе стороны параметра обороны. (2) Строка 2 — комбинация поиска: поле по имени + токен-итерация transfer-coding (TokenNext главы 89 на Head). (3) Строка 3 задаёт тон главе 90: Plan тела запроса (вердикт фиксированная длина/chunked) доступен сразу после разбора заголовка. (4) Строка 4 проверяет генерацию строки размера chunk (5\r\n — допустимы расширения). (5) Строка 5 — слой Message: однократный разбор «заголовок+тело», тело — заимствованное представление ok — самый экономный путь малых сообщений. (6) Строка 6 проверяет перепривязку массива trailer и разбор блока trailer — предвестие главы 91. Шесть строк вместе — API-поверхность этой главы и следующей.
Полная программа: ParseBuffer — разбор запроса/ответа на цепочке буферов
Из репозиторного образца examples/http1/parse_buffer/main.c:
*/ #include <stdio.h> #include <xrt.h> int main(void) { static const char sRequest[] = "GET /x HTTP/1.1\r\nHost: a.test\r\n\r\n"; static const char sResponse[] = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok"; xnetbufpool* pPool = xrtNetBufPoolCreate(NULL); xnetbuf ReqBuf; xnetbuf RspBuf; xhttpfield Fields[8]; xhttp1head Head; bool bOk = false; if ( pPool == NULL ) { return 1; } /* 请求链:分两块追加(头跨块场景的最小化模拟)。 */ if ( xrtNetBufInit(&ReqBuf, pPool) && xrtNetBufAppend(&ReqBuf, sRequest, 12u) && xrtNetBufAppend(&ReqBuf, sRequest + 12u, sizeof(sRequest) - 13u) ) { xrtHttp1HeadInit(&Head, Fields, 8); if ( xrtHttp1RequestParseBuffer(&ReqBuf, &Head, NULL, NULL) == XHTTP1_READY ) { printf("request: %.*s %.*s\n", (int)Head.Method.Size, Head.Method.Data, (int)Head.Target.Size, Head.Target.Data); bOk = true; } xrtNetBufClear(&ReqBuf); } /* 响应链:单块。 */ if ( bOk && xrtNetBufInit(&RspBuf, pPool) && xrtNetBufAppend(&RspBuf, sResponse, sizeof(sResponse) - 1u) ) { xrtHttp1HeadInit(&Head, Fields, 8); if ( xrtHttp1ResponseParseBuffer(&RspBuf, &Head, NULL, NULL) == XHTTP1_READY ) { printf("response: %u\n", (unsigned)Head.Status); bOk = Head.Status == 200u; } xrtNetBufClear(&RspBuf); } xrtNetBufPoolDestroy(pPool); return bOk ? 0 : 2; }
Что только что произошло. (1) Байты запроса нарочно дописываются в два блока — минимальная имитация заголовка на стыке; ParseBuffer доказывает: разбор по цепочке не требует сшивки на стороне вызывающего. (2) Результат изоморфен версии в памяти: Head.Method/Target заимствуют представления и печатаются напрямую. (3) Сторона ответа идёт через ResponseParseBuffer — входы запрос/ответ, один Head. (4) У варианта ParseTls нет отдельного образца: он отличается от версии Buffer только «откуда поток» (xtlsstream вместо xnetbuf); строка контракта это говорит, и образец не выдумывается.
90.4契约
- Привязанный массив: Head привязывает массив полей вызывающего; число полей естественно ограничено ёмкостью массива; перед переиспользованием Head чистить не нужно (каждый Parse строит заново).
- Трёхзначный возврат:
READY/NEXT/ERROR;NEXTничего не потребляет и не сообщает об ошибке; отказ не продвигает (семантика повтора как в кадрировании главы 72). - Заимствование представлений: Method/Target/Status/Reason/поля заимствуют вход;
Bytes— длина блока заголовков с завершающей пустой строкой (старт тела). - Семантика поиска: имена полей без учёта регистра; одноимённые многозначные поля обходятся по порядку; промах — явный отказ, не значение NULL.
- Атомарность упаковки: строка состояния + поля одной записью; нехватка ёмкости — ноль записей; успех публикует общую длину.
- Потолочная оборона: limits управляют общей длиной блока/метода/цели; разбор без потолков — усилитель памяти; умолчания консервативны.
- Семантика метода: разбор ответа несёт контекст метода запроса (правило «HEAD/204 без тела» исполняет Plan главы 91).
- Ноль распределений на запрос: пути разбора и упаковки без кучи (Head/массив/буферы предоставляет вызывающий).
- Обрезка:
HTTP1_HEAD/HTTP1_BODY/HTTP1_MESSAGE— независимые макросы возможностей, зависящие от фундамента HTTP.
Типичные ошибки
Симптом: «принял отрезок — попробовал» потокового сервера превращается в мёртвый цикл или повторный разбор тех же байтов — холостое вращение CPU или задвоение полей.
Причина: семантика NEXT — «этого входа мало»: правильное действие — дописать новые данные в тот же буфер и Parse снова (вход повторяется с начала; парсер гарантирует отсутствие побочных эффектов на пути NEXT); либо продвинуться по возвращённому потреблённому смещению. Обращаться с ним как с «ошибкой», выбрасывая буфер, — возврат в ад сборки.
while ( recv(buf) > 0 ) { if ( Parse(buf) == XHTTP1_MORE ) { continue; /* пробуются только новые байты: никогда не соберётся */ } break; }
/* буфер с дописыванием (глава 65): всё принятое копится в одном отрезке */ while ( recv_append(&In) > 0 ) { if ( xrtHttp1RequestParse(view(&In), &Head, NULL, NULL) == XHTTP1_READY ) { break; /* байты после Bytes - тело/следующий запрос */ } }
Симптом: чуть более длинный запрос (легален, но полей на два больше) не разбирается — сервер «случайно» отвергает часть клиентов.
Причина: ёмкость привязанного массива — жёсткий потолок. У здоровых браузеров 15-25 полей в заголовке — обычное дело; прокси-цепочки ещё дописывают семейство X-Forwarded-*.
xhttpfield Fields[4]; /* отвергает большинство реальных запросов */ xrtHttp1HeadInit(&Head, Fields, 4);
xhttpfield Fields[32]; /* практика: реальные запросы 25 + запас на форвардинг */ xrtHttp1HeadInit(&Head, Fields, 32); прокси расширяет или явно отвергает (само по себе решение против злоупотреблений) */
Симптом: самописный ответ без Content-Length и без объявления chunked — клиент не знает, когда кончается тело, висит до тайм-аута или закрытия соединения.
Причина: тело HTTP/1 обязано нести объявление ограничения (Content-Length, chunked или «конец по закрытию соединения» — надёжно только для семантики HTTP/1.0). API упаковки пишет поля за вас, но поле ограничения — семантика, которую обязаны предоставить вы.
xrtHttp1ResponseWrite(XHTTP_VERSION_1_1, 200, OK, NoFields, 0, Out, sizeof(Out), &iSize); send(Out, iSize); send(Body, BodySize); /* без ограничения: клиент не знает, когда конец */
static const xhttpfield F[] = { { XRT_STR_INIT("Content-Length"), XRT_STR_INIT("11") } }; xrtHttp1ResponseWrite(XHTTP_VERSION_1_1, 200, XRT_STR_LITERAL("OK"), F, 1, Out, sizeof(Out), &iSize); send(Out, iSize); /* заголовок объявляет 11 байтов */ send(Body, 11); /* тело ровно 11: ограничение полное */
Практические задания
Замените запрос образца http1 на POST (с Content-Length: 5 и телом в 5 байтов), после разбора напечатайте Head.Bytes и проверьте «тело начинается с Bytes». Критерии приёмки: Bytes сходится с ручным подсчётом; представление тела соответствует исходным байтам.
Подавайте 41-байтовый запрос случайными кусками по 1..N байтов (имитация TCP-сегментации), после каждого дописанного куска Parse — посчитайте, с какого куска наступает READY. Затем нарочно вставьте уродство (имя поля с двумя двоеточиями подряд) и проверьте ERROR с неповреждённым предыдущим состоянием. Критерии приёмки: любой порядок кусков в итоге READY; ошибочный вход сообщает ERROR при прибытии куска с уродством.
В одном буфере склеены два полных запроса (HTTP/1.1 pipelining): разобрать первый (Bytes находит границу) → потребить → разобрать второй на остатке. Реализуйте parse_pipelined(输入, 回调) (вход, обратный вызов) циклом до NEXT. Критерии приёмки: оба запроса разобраны верно; неполный второй останавливается на NEXT с продолжаемым буфером; весь путь — один Head/один массив, ноль распределений.
Итоги
| Тема | Кратко |
|---|---|
| Основные входы | RequestParse / ResponseParse (три состояния); ResponseWrite / RequestWrite (упаковка одной записью) |
| Привязанный массив | HeadInit привязывает массив полей вызывающего; ёмкость — потолок числа полей; переиспользование без распределений |
| Три состояния | READY (Bytes = старт тела) / MORE (ждать данных, без побочных эффектов) / ERROR (не продвигает) |
| Поиск | HeadFind без учёта регистра; многозначные поля по порядку; в паре с TokenNext для суждений о списках |
| Упаковка | строка состояния + поля одной записью; нехватка ёмкости — ноль записей; поля ограничения (CL/chunked) - семантическая ответственность вызывающего |
| Потолки | limits управляют блоком/методом/целью; умолчания консервативны; против усиления памяти |
| Контекст метода | разбор ответа несёт метод запроса; правило «HEAD/204 без тела» исполняет Body Plan |
| Слой Message | заголовок + тело за один раз (малые сообщения); семейство Body потоковое (глава 91) |
| Ноль распределений | пути разбора/упаковки без кучи; Head/массив/буферы — от вызывающего |
| Транспортные привязки | ParseBuffer ест цепочку буферов / ParseTls ест TLS-поток — сигнатуры и три состояния как в версии в памяти; объединение только через блоки/записи |
📖 Справочники API в этой главе:http1 (39 записей)http (72 записей)http1_net (2 записей)http1_tls (2 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки