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

Query-строка URL и HTTP-форма — две близкие, но не одинаковые синтаксические системы ключ-значение; xhttp даёт два слоя инструментов.Слой без выделения (модуль query): xrtQueryNext обходит прямо по исходной строке — XQUERY_HAS_VALUE из xquerypair точно различает a (нет значения), a= (пустое значение), =v (пустой ключ); xrtQueryFind/Count/Validate того же семейства; после включения query_codec добавляется percent-кодирование/декодирование.Владеющий слой (модуль query_params): контейнер xqueryparams разбирает, переписывает (Set — сохранить позицию и дедуплицировать/Append — добавить в хвост/Sort — стабильная сортировка/Compact — уплотнение) и пересобирает — стандартный путь «изменил один параметр и склеил обратно». Сторона форм — две формы: application/x-www-form-urlencoded (тот же синтаксис + семантика + как пробела) и multipart/form-data (поле боя quoted-string главы 92). Два слоя raw и владеющий полностью изоморфны делению глав 89/92 — сперва синтаксический слой, потом контейнер.

103.1引入

Высоко частотные операции API-клиента: пагинация (page=2 сменить на page=3), добавить фильтр (дописать tag=network), скопировать ссылку, поменяв параметры. Проблема ручной склейки строк — дублирующиеся ключи: в ?tag=c&tag=xlang&debug у tag два значения — дописать третий? заменить все? чью позицию сохранить? Семантика xqueryparams однозначна: Set сохраняет первую одноимённую позицию, удаляя остальные, Append добавляет в хвост, Find итерирует все одноимённые по порядку — три намерения, три глагола. А ценность слоя без выделения — горячий путь чтения: прокси ветвится по флагу debug, каждый запрос заглядывает в query — обход представления с нулевой стоимостью.

Различие a и a= пронизывает оба слоя: XQUERY_HAS_VALUE не установлен = нет значения (в URL не написано =), установлен и пуст = явное пустое значение — «дописывать ли =» при обратном выписывании решается этим. Та же философия бита присутствия, что в главе 101: синтаксическую структуру не угадывают по длине представления.

103.2概念

Слой без выделения: итерация, поиск и валидация

xrtQueryNext(串, &偏移, &对) (строка, &смещение, &пара) — итератор трёх состояний (XQUERY_NEXT_ITEM/END/ERROR); дублирующиеся ключи сохраняют исходный порядок; ведущий ? можно опустить; пустые сегменты припоследовательныхных/ведущих/хвостовых & пропускаются. xrtQueryFind от смещения итерирует одноимённые по имени (стандартное чтение дублирующихся ключей); xrtQueryCount считает; xrtQueryValidate судит легальность. Слой не декодирует — %2F это три символа без интерпретации; когда значение нужно декодировать — идите в query_codec (там же мягкие переключатели вроде XQUERY_PARAMS_LENIENT_PERCENT).

Владеющий слой: четыре глагола контейнера

1РазборParamsParse (правила форм: + -> пробел, percent-декодирование, ErrorOffset сообщает позицию процента)
2ПерезаписьSet (сохранить первую одноимённую позицию, удалить прочие) / Append (в хвост) / AppendPair-SetPair (сохраняют форму без знака равенства)
3Наведение порядкаSort (стабильная сортировка по байтам имени - одноимённые сохраняют порядок) / Compact (выбрасывает мёртвые байты от удалений)
4ПересборкаBuild -> строка с нулевым завершителем под xrtFree (выписывание сразу в канонической форме)
  • Семантика разбора: правила application/x-www-form-urlencoded — по умолчанию строго отвергать нелегальный percent (ErrorOffset даёт позицию процента); мягкий режим (XQUERY_PARAMS_LENIENT_PERCENT) сохраняет недействительный процент как обычный байт — сценарий совместимости с браузерным URLSearchParams.
  • Семантика изменения: добавление/установка копируют имя и значение (ввод можно заимствовать из самого контейнера — безопасно менять на месте); Get читает первое одноимённое, Find итерирует все; изменение контейнера инвалидирует существующие представления (общее правило главы 18).
  • Потолки: xqueryparamsconfig ограничивает пары/элементы списка/одно значение/суммарные декодированные байты — физическая строковая область уплотняется, прежде чем упереться в MaxBytes (логика потолков не включает запас на рост).
  • Атомарность: ParseAppend разбирает в рабочей копии — провал по потолку/синтаксису/памяти не раскрывает частичного добавления.

Слой кодирования и выписывание

query_codec зависит от query и percent-кодека: строит RFC 3986 query прямо из «незакодированных полей» — семейство xrtQueryBuild/QueryAppendWrite встраивает правила «какие символы кодировать» (структурные &/= сохраняются, выбор формы пробела). Слой raw (разбор и пересылка без перезаписи) и слой codec (построение новой строки) обрезаются независимо — чистому прокси кодировщик не нужен.

Две формы форм

  • urlencoded: тело и есть query-синтаксис (a=1&b=2) — весь словарь главы 103 применим напрямую; Content-Type: application/x-www-form-urlencoded.
  • multipart/form-data: структура частей, разделённых границей (у каждой части свои заголовки и содержимое) — носитель загрузки файлов; boundary приходит из параметра Content-Type главы 92 (та самая заклинённая quoted-string); семейство xrtFormDataConfigInit/FormDataAppendBody даёт построение и разбор. examples/http/form_data/multipart/multipart_stream/multipart_write покрывают построение, потоковость и выписывание.

Разделение труда с параметрическим слоем главы 92

Глава 92 ведает параметрами с точкой с запятой внутри значения поля (Content-Type; key=value — синтаксис HTTP-заголовков); эта глава — URL query и формы (разделитель & — синтаксис URI/форм). Итераторы одной формы (смещение + три состояния), но синтаксические области различны — перепутали область, получили ошибку разбора. Семантика + действует тоже только в области форм (+ у urlencoded — пробел; в чистом URI query + — обычный символ; границу разбирали в главе 88).

103.3示例

Первая полная программа: обход без выделения и различение семантики

Программа ниже из examples/url/query/main.c — четыре формы ключ-значение одним обходом:

extlibs/xhttp/examples/url/query/main.c源码 ↗
#include <stdio.h>

#include <xhttp.h>



/* 演示无分配遍历重复查询键,并区分缺失值和空值。 */
int main(void)
{
	xstrview Query = XRT_STR_LITERAL("?tag=c&tag=xlang&debug&empty=");
	xquerypair Pair;
	size_t iOffset = 0;

	for ( ;; ) {
		xquerynext Next = xrtQueryNext(Query, &iOffset, &Pair);

		if ( Next == XQUERY_NEXT_END ) {
			break;
		}
		if ( Next == XQUERY_NEXT_ERROR ) {
			return 1;
		}
		printf("%.*s", (int)Pair.Key.Size, Pair.Key.Data);
		if ( (Pair.Flags & XQUERY_HAS_VALUE) != 0 ) {
			printf(" = %.*s", (int)Pair.Value.Size, Pair.Value.Data);
		} else {
			printf(" = <missing>");
		}
		printf("\n");
	}
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXHTTP_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xhttp.h impl.c extlibs/xhttp/examples/url/query/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapitag = ctag = xlangdebug = <missing>empty =

Что только что произошло. (1) Ввод ?tag=c&tag=xlang&debug&empty= — четыре позиции: дублирующийся ключ tag с двумя значениями в исходном порядке, debug без значения (бит не установлен — печатается <missing>), empty с явным пустым значением (бит установлен, длина 0 — печатается пустая строка). (2) Четыре формы одним циклом: один бит XQUERY_HAS_VALUE разделяет две пары семантики (нет значения против пустого). (3) Весь путь без выделений — Key/Value заимствуют исходную строку; такова форма стоимости чтения query прокси/шлюзом. (4) Этот вывод одновременно контрольный список: поведение любого кода обработки query на этих четырёх входах должно явно расходиться по веткам, вот так.

Вторая полная программа: перезапись и пересборка контейнера

Вторая программа из examples/url/query_params/main.c — замкнутый цикл разбор → Set/Append → Build:

extlibs/xhttp/examples/url/query_params/main.c源码 ↗
#include <xhttp.h>

#include <stdio.h>



/* 解析、修改并重新构建一组拥有型 URL 查询参数。 */
int main(void)
{
	xqueryparams* pParams;
	str sQuery;
	size_t iError;
	size_t iSize;

	pParams = xrtQueryParamsParse(
		XRT_STR_LITERAL("page=1&tag=c&tag=network"),
		NULL,
		&iError
	);
	if ( pParams == NULL ) {
		return 1;
	}
	if ( !xrtQueryParamsSet(
		pParams, XRT_STR_LITERAL("page"), XRT_STR_LITERAL("2")
	) || !xrtQueryParamsAppend(
		pParams, XRT_STR_LITERAL("tag"), XRT_STR_LITERAL("xlang")
	) ) {
		xrtQueryParamsDestroy(pParams);
		return 1;
	}
	sQuery = xrtQueryParamsBuild(pParams, &iSize);
	if ( sQuery == NULL ) {
		xrtQueryParamsDestroy(pParams);
		return 1;
	}
	printf("%.*s\n", (int)iSize, sQuery);
	xrtFree(sQuery);
	xrtQueryParamsDestroy(pParams);
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXHTTP_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xhttp.h impl.c extlibs/xhttp/examples/url/query_params/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapipage=2&tag=c&tag=network&tag=xlang

Что только что произошло. (1) ParamsParse("page=1&tag=c&tag=network") разбирает три позиции по правилам форм (декодирование + и percent происходит здесь; ErrorOffset при провале сообщает позицию процента). (2) Set("page","2") — page меняет значение на месте; Append("tag","xlang") — третий tag в хвост: разница намерений Set и Append для дублирующихся ключей проявляется наглядно (Set вычистил бы остальные одноимённые; здесь у tag Append, и три значения сосуществуют). (3) Build пересобирает page=2&tag=c&tag=network&tag=xlang — каноническая строка с нулевым завершителем под xrtFree. Три строки шаблона «пагинация, фильтр, копия с правкой» — вот эти два Set/Append и один Build.

103.4契约

  • Слой без выделения: итерация/поиск/подсчёт/валидация — всё на представлениях; три состояния возврата; дубликаты сохраняют порядок; пустые сегменты пропускаются; ведущий ? можно опустить; декодирования нет.
  • Различение семантики: XQUERY_HAS_VALUE разделяет четыре формы нет-значения/пусто/пустой ключ/норма; верность цикла выписывания держится на бите.
  • Разбор контейнера: правила форм (+→пробел, percent-декодирование); по умолчанию строго (ErrorOffset сообщает позицию); мягкий режим сохраняет недействительные проценты байтами.
  • Четыре глагола перезаписи: Set (сохранить позицию, дедуплицировать)/Append (добавить)/Pair-формы (сохраняют элементы без равенства)/Sort (стабильный порядок по байтам имени)/Compact (вычистить мёртвое хранилище).
  • Владение: контейнер копирует вводы добавления/установки (можно заимствовать себя); изменение инвалидирует представления; Build возвращает строку под xrtFree.
  • Потолки: четыре линии пары/список/одно значение/суммарные декодированные байты; перед упором — сперва уплотнение; ParseAppend атомарен (провал не раскрывает частичного результата).
  • Слой кодирования: query_codec независим (raw и построение обрезаются раздельно); правила сохранения структурных символов встроены.
  • Формы: urlencoded = query-синтаксис напрямую; multipart = структура частей (семейство FormData); boundary из параметра Content-Type.
  • Граница областей: эта глава — область &; глава 92 — область ;; итераторы одной формы, синтаксис разный.

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

Ловушка 1: переписать отсутствующее значение как пустое (или наоборот):Ловушка 1: переписать отсутствующее значение как пустое (или наоборот)

Симптом: ?debug переписан в ?debug= — семантика сменилась с «флаг существует» на «значение пусто»; или в обратную сторону потерян =.

Причина: отсутствие значения и пустое значение — две синтаксические структуры; суждение по Pair.Value.Size == 0 валит их в одну кучу — судить надо по XQUERY_HAS_VALUE, выписывать формосохраняющими AppendPair/SetPair.

错误写法
if ( Pair.Value.Size == 0 ) {
	treat_as_flag(Pair.Key);   /* debug и empty= обращены одинаково */
}
正确写法
if ( (Pair.Flags & XQUERY_HAS_VALUE) == 0 ) {
	treat_as_flag(Pair.Key);         /* по-настоящему без значения */
} else {
	treat_as_value(Pair.Key, Pair.Value);  /* значение есть (возможно, пустая строка) */
}
Ловушка 2: контейнерный разбор как URL-разбор (двусмысленность плюса):Ловушка 2: контейнерный разбор как URL-разбор (двусмысленность плюса)

Симптом: ParamsParse пущен на неформальный URI query — a+b декодирован в a b, хотя в чистой URI-семантике + — просто знак сложения.

Причина: +→пробел — правило форм urlencoded; в query RFC 3986 + — обычный символ. Правила слоёв различны: читайте URI query слоем без выделения (без декодирования) + percent-декодирование по надобности; ParamsParse — только для тела форм.

错误写法
/* обработка query поискового URL: + ошибочно принят за пробел */
pParams = xrtQueryParamsParse(Url.Query, NULL, &iErr);
正确写法
/* URI query: обход без выделения + явное percent-декодирование */
while ( xrtQueryNext(Url.Query, &iOff, &Pair) == XQUERY_NEXT_ITEM ) {
	percent_decode_value(Pair.Value, Buf, sizeof(Buf));   /* декодировать только %xx */
}
Ловушка 3: генерация ссылки пагинации обходом с немедленным Build теряет остальные параметры:Ловушка 3: генерация ссылки пагинации обходом с немедленным Build теряет остальные параметры

Симптом: в ссылке пагинации остался один page — tag, сортировка и прочее исчезли.

Причина: новая query склеена с нуля (записан только page), а не изменена на существующем контейнере. Правильный путь: Parse существующей → Set("page", N) → Build — изменить один параметр, не трогая остальные.

错误写法
snprintf(Next, "%s?page=%u", Path, Page + 1);  /* остальные параметры испарились */
正确写法
pParams = xrtQueryParamsParse(Url.Query, NULL, &iErr);
xrtQueryParamsSet(pParams, XRT_STR_LITERAL("page"), PageText);
sNext = xrtQueryParamsBuild(pParams, &iSize);
/* page изменён на месте, tag/sort и прочее сохранены целиком */

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

Базовое: цикл четырёх форм

Постройте query с четырьмя формами (без значения/пустое значение/пустой ключ/норма), обойдите слоем без выделения с печатью; затем ParamsParse → Build — сравните различия цикла (какие формы ParamsParse нормализует). Критерии приёмки: вы можете указать границу между сохранением семантики и нормализацией в цикле.

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

Реализуйте next_page(当前URL文本, &下一页文本) (текущий текст URL, &текст следующей страницы): UrlParse → ParamsParse → Set("page") → Build → склейка обратно в URL. Проверьте на URL с дублирующимся ключом tag и флагом debug без значения. Критерии приёмки: пагинация трогает только page; дубликаты и флаг сохраняются как есть; итоговая строка с нулевым завершителем корректно освобождается.

Вызов: конвертер двух форм форм

Реализуйте urlencoded_to_multipart(字段容器, 输出, 容量) (контейнер полей, вывод, ёмкость): отрендерить поля контейнера Params как multipart-части (boundary сгенерировать, каждой части дать Content-Disposition: form-data; name="..." — имя по правилам quoted-string). Обратно — разбор multipart в контейнер. Сверьте с образцом form_data. Критерии приёмки: двустороннее преобразование сохраняет поля (включая значения с кавычками/точками с запятой); boundary удовлетворяет требованию уникальности разделителя (в теле не встречается).

Итоги

ТемаКратко
Двухслойная структурабез выделения (Next/Find/Count/Validate, без декодирования) + владеющий (контейнер Params)
Четыре формыa=нет значения / a==пустое значение / =v=пустой ключ / норма — один бит XQUERY_HAS_VALUE разделяет всё
Разбор контейнераправила форм (+→пробел/percent-декодирование); строгий дефолт + ErrorOffset; мягкий режим включаем
Четыре глаголаSet сохраняет позицию и дедуплицирует / Append добавляет / Sort стабилен / Compact вычищает мёртвое
АтомарностьParseAppend в рабочей копии — провал не раскрывает частичного добавления
Потолкичетыре линии пары/список/значение/суммарные байты; перед упором — уплотнение
Слой кодированияquery_codec независим (строит новую строку); raw и построение обрезаются раздельно
Область плюсаформы = пробел; URI query = обычный символ — слоёв не смешивать
Две формы формurlencoded = query-синтаксис; multipart = части + boundary (семейство FormData)
Граница с главой 91эта глава — область & (URI/формы); глава 92 — область ; (значения полей заголовков)

📖 Справочники API в этой главе:xhttp-query (8 записей)xhttp-query_params (24 записей)xhttp-form (8 записей)xhttp-form_data (24 записей)

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