Ориентация главы
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).
Владеющий слой: четыре глагола контейнера
- Семантика разбора: правила
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 — четыре формы ключ-значение одним обходом:
#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; }
Что только что произошло. (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:
#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; }
Что только что произошло. (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 — область;; итераторы одной формы, синтаксис разный.
Типичные ошибки
Симптом: ?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); /* значение есть (возможно, пустая строка) */ }
Симптом: 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 */ }
Симптом: в ссылке пагинации остался один 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 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки