Ориентация главы

Том 9 начинается с фундаментного слоя HTTP — без сети и без версий, только определения «как выглядит поле, как распознаётся метод, как режется цель» — словарь, общий для всех верхних слоёв. xrtHttpFieldParse строго разбирает одиночное поле заголовка в заимствованные представления; xrtHttpTokenNext режет token-list по правилам HTTP; разбор Host и кодирование параметров обрабатывают два вида структурированного текста URL-слоя; FieldBlockWrite записывает представления полей обратно дословно. Парсер HTTP/1 главы 90, рантайм xhttp главы 98, ваши собственные шлюзы и посредники — всё стоит на этом наборе примитивов без распределений. Прочитав главу, вы увидите: безопасность и производительность обработки HTTP-заголовков — это, в сущности, три вещи: строгий разбор + заимствованные представления + атомарность ёмкости.

89.1引入

В чём внутренняя операция обратного прокси? «Поменять один заголовок и переслать» — разобрать заголовок запроса, найти поле по имени, изменить значение или добавить поле, затем записать весь блок заголовков обратно в поток байтов. Звучит просто, но на каждом шаге исторические ямы: как обрабатывать регистр имён полей? Как резать элементы token-list с пробелами и кавычками? Не взорвут ли парсер комментарии в значениях (устаревшие по RFC 7230, но встречающиеся в сети)? Что если буфера при перезаписи не хватает — писать половину?

Ответ фундаментного слоя — три дисциплины. Строгий разбор: нелегальный вход отвергается на месте (никаких тихих исправлений) — добрая половина HTTP-разборных CVE вырастает из «снисходительного разбора», обернувшегося двусмысленностью в руках атакующего. Заимствованные представления: результаты разбора — представления исходного входа (ноль копий, ноль распределений) — стоимость на запрос в высококонкурентном шлюзе стремится к нулю. Атомарность ёмкости: обратная запись либо полностью успешна, либо не трогает ни байта — самое коварное промежуточное состояние «полсообщения записано» исключается на уровне API.

89.2概念

Модель поля: имя, значение и заимствованные представления

xhttpfield — это два представления: Name и Value. xrtHttpFieldParse(文本, &字段) (текст, &поле) строго разбирает «имя: значение» — до двоеточия имя поля (проверка token-набора символов), после — значение, очищенное от краевого OWS (необязательных пробелов). Результат разбора заимствован: Name.Data/Value.Data указывают внутрь исходного текста — без распределений и копий. Три спутника: xrtHttpFieldCount/FieldFind/FieldNext (поиск и обход по массивам полей) и xrtHttpFieldBlockWrite(字段数组, 数量, 输出, 容量, &长度) (массив полей, количество, вывод, ёмкость, &длина) — запись массива полей обратно в полный блок «имя: значение\r\n...\r\n»; при нехватке ёмкости отказ без единого записанного байта (атомарность).

token-list: итератор культуры запятых

HTTP — мир, разделённый запятыми: Connection: keep-alive, Upgrade, Accept-Encoding: gzip, deflate, Cache-Control: no-cache, no-store. xrtHttpTokenNext(值视图, &偏移, &token) (представление значения, &смещение, &токен) — стандартный итератор: резать по , и ;, пропускать пробелы, возвращать XHTTP_NEXT_ITEM (получен элемент) / XHTTP_NEXT_END (чистое завершение).Как едут параметры: в gzip;q=0.5 часть q=0.5 — параметр после точки с запятой; итератор останавливается на ней, а разбор параметров остаётся специализированным слоям полей (например q-значениям TE из главы 91). Модель «токены впереди, параметры позади» пронизывает все списковые поля.

Методы, цели и разбор Host

  • Методы: xrtHttpMethodParse распознаёт стандартные методы (GET/POST/...) и расширенные (пользовательские токены) — строгое token-множество символов; GET; с разделителем отвергается сразу.
  • Цели: четыре формы request-target (origin-form /path?query, absolute-form полный URL, authority-form, asterisk-form) различает слой разбора;слой этой главы даёт распознавание формы xrtHttpTargetParse и разрезание путь/запрос.
  • Разбор Host: xrtHttpHostParse(值, &authority) (значение, &authority) даёт представление xhttpauthority (хост/порт) — обрабатывая домены, IPv4 и IPv6 в квадратных скобках (скобки [2001:db8::1]:8443 обязаны быть парными, литерал IPv6 проверяется группа за группой — двусмысленный вход вида домен-двоеточие-порт не распознаётся ошибочно).

Кодирование параметров: два пути строки запроса

xrtHttpParamBuild/xrtHttpParamWrite (кодирующая сторона) и xrtHttpParamCount/xrtHttpParamNext/xrtHttpParamFind (разбирающая) обрабатывают пары ключ-значение вида a=1&b=2: кодирование экранирует произвольные байты в percent-форму, декодирование восстанавливает. Две дисциплины: семантика + и %20 — декодирование следует правилам application/x-www-form-urlencoded (+ → пробел); пробелы и зарезервированные символы — кодирующая сторона экранирует только необходимое (сохраняя &/= как структурные разделители). Формы, строки запроса и значения Cookie делят этот набор.

Изоморфизм с главами 25 и 77

Вы обнаружите, что API этой главы изоморфны главе 25 (конвейер представлений Trim/Cut) и главе 77 (курсор DER): заимствованный вход → строгая проверка → представление на выходе → преобразование по надобности. Это сквозная философия обработки текста XRT во всей библиотеке — ноль копий доходит до протокольного слоя, «разобрал один раз — ссылайся везде».

Панорама конвейера разбора

1Входтекст поля заголовка из сети (заимствованный буфер, стартовая точка нуля копий)
2Строгий разборFieldParse проверяет token-набор и подрезает OWS -> представления Name/Value
3Структурные запросыTokenNext режет списки / HostParse разбирает authority / ParamCount считает параметры
4Обратная записьFieldBlockWrite выдаёт канонический блок (атомарность ёмкости) - полный контур чтения-правки-записи

89.3示例

Первая полная программа: разбор поля, итерация токенов и обратная запись

Программа ниже из examples/http/base/main.c — минимальный контур тройки фундамента:

examples/http/base/main.c源码 ↗
#include <stdio.h>

#include <xrt.h>






















 */


/* 展示传输无关的字段解析与 token-list 迭代。 */
int main(void)
{
	xhttpfield Field;
	xhttpnext Next;
	xstrview Token;
	char Output[128];
	size_t iOffset = 0;
	size_t iSize;

	if ( !xrtHttpFieldParse(
		XRT_STR_LITERAL("Connection: keep-alive, Upgrade"), &Field
	) ) {
		return 1;
	}
	while ( (Next = xrtHttpTokenNext(
		Field.Value, &iOffset, &Token
	)) == XHTTP_NEXT_ITEM ) {
		printf("%.*s\n", (int)Token.Size, Token.Data);
	}
	if ( (Next != XHTTP_NEXT_END) || !xrtHttpFieldBlockWrite(
		&Field, 1, Output, sizeof(Output), &iSize
	) ) {
		return 2;
	}
	printf("%.*s", (int)iSize, Output);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/http/base/main.c -lws2_32 -liphlpapikeep-aliveUpgradeConnection: keep-alive, Upgrade

Что только что произошло. (1) FieldParse разбирает одно поле в заимствованные представления (Name=Connection, Value=keep-alive, Upgrade — пробел после двоеточия подрезан). (2) Цикл TokenNext вырезает два токена: keep-alive и Upgrade — смещение держит вызывающий, итератор без состояния; NEXT_END — чистое завершение, а не ошибка. (3) FieldBlockWrite записывает поле дословно в полный блок — обратите внимание на канонический формат после двоеточия в выводе (один пробел): разбор нормализовал OWS, обратная запись даёт каноническую форму — после «прочитал-изменил-записал» прокси байты в канале детерминированы. Когда ёмкости мало, не пишется ни байта — атомарность позволяет спокойно «сперва попробовать малый буфер, при неудаче взять большой».

Вторая полная программа: строгость разбора Host

Вторая программа из examples/http/host/main.c — каноническое разложение IPv6 в скобках и порта:

examples/http/host/main.c源码 ↗
#include <stdio.h>
#include <xrt.h>





















 */


/* 解析 Host 字段并直接读取借用的主机与端口视图。 */
int main(void)
{
	xhttpauthority Host;
	uint16 iPort;

	if ( !xrtHttpHostParse(
		XRT_STR_LITERAL("[2001:db8::1]:8443"), &Host
	) || !xrtHttpAuthorityPort(&Host, 80u, &iPort) ) {
		return 1;
	}
	printf(
		"host=%.*s port=%u\n",
		(int)Host.Host.Size,
		Host.Host.Data,
		(unsigned)iPort
	);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/http/host/main.c -lws2_32 -liphlpapihost=2001:db8::1 port=8443

Что только что произошло. (1) Вход [2001:db8::1]:8443 раскладывается на хост 2001:db8::1 и порт 8443 — скобки проверены на парность, литерал IPv6 сверена группа за группой. (2) Границы строгости: example.com:8443 (домен с портом) легален; 2001:db8::1 (голый IPv6 без скобок с намеренным портом) — двусмысленный вход, и парсер отвергает его, а не угадывает.Зачем столько щепетильности: Host/Authority — вход решений о маршрутизации и подлинности (IP-форма SAN-сопоставления главы 80, выбор цели прокси); двусмысленный разбор означает, что решения о маршрутизации строятся на понимании, отличном от понимания противоположной стороны. Сопроводительный examples/http/field_tour (3 строки вывода) обходит счёт блока полей, поиск, итерацию токенов и обратную запись — «исполняемая шпаргалка» главы.

89.4契约

  • Заимствованные представления: результаты разбора полей/методов/целей/Host заимствуют исходный вход; действуют, пока жив вход (правила представлений главы 15).
  • Строгий разбор: нелегальные token-символы, двусмысленный Host, непарные скобки, недопустимые символы в имени поля — всё отвергается на месте; без исправлений и догадок.
  • Атомарность ёмкости: интерфейсы обратной записи вроде FieldBlockWrite при нехватке ёмкости пишут ноль байтов; длина публикуется только при успехе — полусообщений не существует.
  • Итерация токенов: резать по ,/;, пропускать пробелы; состояния NEXT_ITEM/NEXT_END; итератор без внутреннего состояния (смещение принадлежит вызывающему).
  • Нормализация OWS: разбор подрезает краевые пробелы значения; обратная запись даёт каноническую форму («имя: значение»).
  • Семантика Host: домен/IPv4/IPv6 в скобках; порт опционален; двусмысленный вход отвергается.
  • Кодирование параметров: + декодируется по семантике форм; кодирование экранирует только необходимое (&/= структурно сохранены).
  • Ноль распределений: все API главы не распределяют кучу — стоимость на запрос предсказуема.
  • Обрезка: XRT_MODULE_HTTP независимо; поддомены HTTP_HOST/HTTP_PARAM/HTTP_TARGET обрезаются независимо.

Типичные ошибки

Ловушка 1: самостоятельный поиск запятых в значениях:Ловушка 1: самостоятельный поиск запятых в значениях

Симптом: для Accept-Encoding: gzip, deflate, br пишется цикл strchr(',') — на токенах с кавычками (filename="a,b.txt") или пустых элементах (a,,b) режется неверно.

Причина: запятая HTTP-списков несёт правила экранирования кавычек и комментариев; голый strchr о них не знает. Правила встроены в TokenNext — обработка списков всегда через него.

错误写法
char* p = strtok(Value, ",");   /* запятая внутри кавычек разрезана */
while ( p ) { handle(p); p = strtok(NULL, ","); }
正确写法
size_t iOffset = 0;
xstrview Token;
while ( (Next = xrtHttpTokenNext(Value, &iOffset, &Token)) ==
		XHTTP_NEXT_ITEM ) {
	handle(Token);   /* кавычки/пробелы/граница параметров - дело итератора */
}
Ловушка 2: при перезаписи блока заголовков буфера хватило на полсообщения:Ловушка 2: при перезаписи блока заголовков буфера хватило на полсообщения

Симптом: самодельный прокси пишет перезапись «дописывающим» способом — буфер переполнился на середине: первая половина уже отправлена, вторая отброшена; противоположная сторона получает обрубленный блок, соединение гибнет.

Причина: стиль записи без сознания атомарности ёмкости. Блок HTTP-заголовков — неделимая единица; половина единицы — протокольная ошибка.

错误写法
size_t iUsed = 0;
for ( each field ) {
	iUsed += snprintf(Out + iUsed, Cap - iUsed,
		"%.*s: %.*s\r\n", ...);   /* ёмкость кончилась на середине: полблока */
}
正确写法
if ( !xrtHttpFieldBlockWrite(Fields, iCount, Out,
		sizeof(Out), &iSize) ) {
	/* взять буфер больше или стратегию chunks - Out нетронут (ноль записанных байтов) */
}
send(Out, iSize);
Ловушка 3: значение Host напрямую как цель соединения:Ловушка 3: значение Host напрямую как цель соединения

Симптом: передача исходного значения Host: [2001:db8::1]:8443 в функцию соединения проваливается или попадает не на тот порт; домены, различающиеся регистром, считаются разными хостами (расщепление ключей кеша).

Причина: значение Host — структурированный текст, а не голое имя хоста: скобки, порт и нормализацию регистра следует сперва разобрать. Лишь после того как HostParse выдал хост и порт, вход годится для маршрутизации/соединения.

错误写法
connect(g_Target /* исходное значение Host напрямую */, ...);
/* исходное значение со скобками и портом - не имя хоста */
正确写法
xhttpauthority Authority;
if ( !xrtHttpHostParse(Field.Value, &Authority) ) {
	return reject("malformed host");
}

   нормализуйте регистр (домены регистронезависимы) прежде маршрутизации/ключей кеша */

Практические задания

Базовое: прогнать обход полей

Прогоните образцы base и field_tour, построчно помечая, какой API проверяет каждая строка вывода. Затем замените вход base на Connection: keep-alive,, Upgrade (пустой элемент) и пронаблюдайте поведение итератора. Критерии приёмки: можете объяснить, пустой элемент «пропускается» или «порождает пустой токен»; три строки field_tour зелёные.

Продвинутое: перезаписчик заголовков запроса

Реализуйте rewrite_host(字段数组, 数量, 新主机, 输出, 容量, &长度) (массив полей, количество, новый хост, вывод, ёмкость, &длина): найти поле Host по имени (без учёта регистра), заменить значение, остальные поля без изменений, затем FieldBlockWrite обратно. Критерии приёмки: без Host — явный отказ; перезаписанный блок побайтово каноничен; при нехватке ёмкости ноль записей (проверить 8-байтовым буфером).

Вызов: ядро обработки заголовков мини-прокси

Прочитайте полный заголовок запроса (много строк) в массив полей (свой цикл + FieldParse), затем посчитайте: общее число полей, повторяющиеся имена, множество токенов Connection, результат разбора Host, число параметров строки запроса (ParamDecode). Для того же входа перепишите «X-Forwarded-For» и запишите блок обратно. Критерии приёмки: ноль распределений кучи (статистика главы 6); уродливый вход (пробел в имени поля, двусмысленный Host) отвергается с отчётом первой позиции ошибки.

Итоги

ТемаКратко
Три дисциплины фундаментастрогий разбор (без исправлений) + заимствованные представления (ноль копий) + атомарность ёмкости (без полусообщений)
Модель поляxhttpfield = два представления Name/Value; Parse подрезает OWS, проверяет token-набор
Обратная записьFieldBlockWrite превращает массив полей в канонический блок; нехватка ёмкости пишет ноль
Итерация токеновTokenNext режет по ,/;; NEXT_ITEM/NEXT_END; смещение принадлежит вызывающему
Параметры списковпараметры после точки с запятой (q-значения и т.п.) - дело специализированных слоёв; токен-итератор останавливается на ней
Разбор HostHostParse даёт xhttpauthority; домен/IPv4/IPv6 в скобках; двусмысленное отвергается
Метод/цельстандартные + расширенные методы с проверкой token; распознавание четырёх форм target с разрезанием путь/запрос
Кодирование параметров+→пробел (семантика форм); кодирование экранирует только необходимое; &/= структурно сохранены
Ноль распределенийво всей главе без кучи - стоимость на запрос предсказуема
ОбрезкаXRT_MODULE_HTTP независимо; поддомен fields обрезается самостоятельно

📖 Справочники API в этой главе:http (72 записей)http_connection (7 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки