Ориентация главы
Над средой выполнения главы 99 лежат три слоя автоматического поведения — они превращают «клей, который пишет каждый клиент» в переключатели конфигурации.Перенаправления (http_client_redirect): по умолчанию максимум десять прыжков; после 301/302/303 POST меняется на GET, 307/308 сохраняют метод, но требуют переигрываемого тела; на чужом origin учётные данные срезаются, деградация HTTPS→HTTP по умолчанию отвергается.Автоматические повторы (http_client_retry): по умолчанию выключены — повторяется только идемпотентный метод с переигрываемым телом, выдержка уважает Retry-After; решение «повторять ли побочный эффект» остаётся за вами.Автоматические Cookie (http_client_cookies): Jar подключается в конфигурацию — Set-Cookie автоматически попадает в банку, последующие запросы автоматически несут. Три слоя независимо обрезаются, семантика явная — «автоматически» не значит «магически»: поведение каждого прыжка предсказуемо и диагностируемо (поля Wire Info главы 99, накапливающие всю цепочку перенаправлений, тому свидетельство).
100.1引入
Перенаправление кажется простым («иди за Location»), но плотность ловушек здесь высочайшая среди HTTP-клиентов. POST получил 302 — отправить GET или POST? (Исторический раскол: браузеры меняют на GET, спецификация говорит сохранять — современная семантика: 303 ясно меняет на GET, 307 ясно сохраняет.) Перенаправление на чужой домен — нести ли Authorization? (Нет — следование учётных данных есть новая поверхность атаки.) HTTPS перенаправляют на HTTP — идти ли? (По умолчанию нет — деградация срезает шифрование.) Телу нужна повторная передача — а если тело потоковое? (Не переигрывается — отказ, а не тихое обрезание.) Каждый пункт — источник реальных CVE. xhttp делает эти решения безопасной по умолчанию реализацией протокола: семантика мейнстрим-клиентов, опасные пути — за явными переключателями.
Значения по умолчанию для повторов — намеренно консервативные: по умолчанию выключено. Ибо «безопасен ли повтор» — бизнес-суждение: повтор GET безвреден, повтор POST может задвоить списание. Библиотека умеетопределить «технически повторяемо» (идемпотентный метод + переигрываемое тело); «бизнес разрешает» обязаны заявить вы.
100.2概念
Перенаправления: семантика методов и граница безопасности
Точная граница переписывания метода: соответствие только стандартный метод POST — самодельный метод post не считается (методы HTTP чувствительны к регистру, то же правило источника, что в главе 90); путь сохранения 307/308 требует объявленного переигрываемого тела (бит способности xhttpbody главы 99) — потоковому телу производителя не переиграться, ясный отказ лучше тихой потери. Всей цепочкой разделяется один общий дедлайн (глава 99) — десять прыжков не раздувает бюджет. Каждый прыжок выбирает независимо: наследовать/следовать/вернуть исходный ответ/считать ошибкой (четыре режима поля перенаправления в xhttpcalloptions).
Автоматические повторы: идемпотентность и тройное условие
Один повтор обязан одновременно удовлетворять: лимит не исчерпан (MaxRetries), метод идемпотентен (проверяет xrtHttpMethodIdempotent() — GET/HEAD/PUT/DELETE и другие безопасно повторяемы, POST не в списке), тело переигрываемо (при наличии тела — объявлено xhttpbody). Три условия неразделимы — это техническое ограждение; бизнес-ограждение — сам ваш акт установки лимита. Расчёт выдержки переиспользует чистый протокольный модуль http_retry: экспоненциальная выдержка (BaseDelay/MaxDelay) + уважение серверному Retry-After. Четыре режима на уровне вызова: DEFAULT наследует Client / DISABLED выключает на этот раз / ENABLED (при нулевом лимите Client берёт потолок по умолчанию); ошибка конфигурации (неизвестный режим, нулевой MaxDelay, Base>Max) падает до сетевой операции — перехват при старте.
Автоматические Cookie: подключение Jar
Когда конфигурация Cookie в xhttpclientconfig принимает xcookiejar: Set-Cookie ответа автоматически идёт в банку (домен/путь/истечение/безопасность обрабатываются по RFC 6265 — семантика, разобранная в главе 32, здесь автоматизирована), последующие запросы автоматически подбирают и несут. Jar — независимый объект (персистентность, разделение между Client, изоляция по разделам — всё ваше; та же форма, что и Store кэша главы 101). Нет Jar — нет автоматического поведения — явная сборка.
Общая форма автоматического поведения: явно, диагностируемо, независимо обрезается
Три слоя разделяют три дисциплины дизайна: безопасно по умолчанию (перенаправления: десять прыжков + срезание учётных данных + отказ понижения; повторы выключены; Cookie без Jar не включаются); явный отказ (не переигрывается, конфигурация нелегальна, понижение — всё это определённые классы ошибок, не молчаливый пропуск); видимая диагностика (ResponseWireBytes Info накапливает всю цепочку, Phase сохраняет фактическую стадию завершения — «сколько было прыжков» читается из объекта результата: итоговый действующий URL отличается от URL запроса, значит следовали).
100.3示例
Первая полная программа: явная сборка конфигурации повторов
Программа ниже из examples/http/client_retry — контроль «выключено по умолчанию» против «явно включено»:
#include <xrt/http_client_runtime.h> #include <stdio.h> /* 展示默认关闭和显式启用的客户端重试配置。 */ int main(void) { xhttpclientconfig Config; xrtHttpClientConfigInit(&Config); Config.Retry.MaxRetries = 3; Config.Retry.BaseDelay = UINT64_C(250000); Config.Retry.MaxDelay = UINT64_C(5000000); printf( "retries=%u base=%llu max=%llu\n", (unsigned int)Config.Retry.MaxRetries, (unsigned long long)Config.Retry.BaseDelay, (unsigned long long)Config.Retry.MaxDelay ); return 0; }
Что только что произошло. (1) ConfigInit по умолчанию MaxRetries=0 — автоматических повторов по умолчанию не существует; после установки 3 раза/250 мс база/5 с потолок слой повторов оживает. (2) Вывод повторяет конфигурацию — сам пример и есть документирование конфигурационной грани: три параметра (лимит/базовая выдержка/максимальная выдержка) — все ручки. (3) На уровне вызова можно временно перекрыть: Options.Retry.Mode четырьмя режимами выключает или принуждает для одного вызова — «эту одну не повторять» в пакетной задаче без нового Client. (4) Сравните с ограждениями допуска повторов: эта конфигурация выражает лишь «технический потолок»; повторяем ли каждый запрос фактически — всё равно идемпотентность + переигрываемость; конфигурация и допуск живут по слоям, никто не лезет на чужое.
Вторая полная программа: подключение Cookie Jar
Вторая программа из examples/http/client_cookies — сборочная грань автоматических Cookie:
#include <stdio.h> #include <xhttp.h> /* 配置一个由高层 HTTP Client 持有并自动使用的共享 CookieJar。 */ int main(void) { xnetengineconfig EngineConfig; xhttpclientconfig ClientConfig; xhttpcalloptions CallOptions; xnetengine* pEngine; xcookiejar* pJar; xhttpclient* pClient; xhttprequest* pRequest; xrtNetEngineConfigInit(&EngineConfig); pEngine = xrtNetEngineCreate(&EngineConfig); if ( (pEngine == NULL) || !xrtNetEngineStart(pEngine) ) { xrtNetEngineDestroy(pEngine); return 1; } pJar = xrtCookieJarCreate(NULL); if ( (pJar == NULL) || (xrtCookieJarStoreUrl( pJar, XRT_STR_LITERAL("https://api.example.test/"), XRT_STR_LITERAL( "session=ready; Path=/; Secure; HttpOnly" ), NULL ) != XCOOKIE_STORE_STORED) ) { xrtCookieJarRelease(pJar); xrtNetEngineDestroy(pEngine); return 1; } xrtHttpClientConfigInit(&ClientConfig); ClientConfig.Cookies = pJar; pClient = xrtHttpClientCreate(pEngine, &ClientConfig); xrtCookieJarRelease(pJar); if ( pClient == NULL ) { xrtNetEngineDestroy(pEngine); return 1; } pRequest = xrtHttpRequestCreate( XRT_STR_LITERAL("GET"), XRT_STR_LITERAL("https://api.example.test/items") ); xrtHttpCallOptionsInit(&CallOptions); CallOptions.Cookies.Flags |= XHTTP_COOKIE_TOP_LEVEL; if ( pRequest == NULL ) { xrtHttpClientDestroy(pClient); xrtNetEngineDestroy(pEngine); return 1; } printf( "cookies=%zu same_site=%u top_level=%u\n", xrtCookieJarCount(xrtHttpClientCookieJar(pClient)), (unsigned int)( (CallOptions.Cookies.Flags & XHTTP_COOKIE_SAME_SITE) != 0 ), (unsigned int)( (CallOptions.Cookies.Flags & XHTTP_COOKIE_TOP_LEVEL) != 0 ) ); xrtHttpRequestDestroy(pRequest); xrtHttpClientDestroy(pClient); if ( !xrtNetEngineDestroy(pEngine) ) { return 1; } return 0; }
Что только что произошло. (1) Созданный Jar кладётся в конфигурацию Cookie Client — Client держит ссылку, свою можно отпустить. (2) Запрос строится и отправляется как обычно — приём Set-Cookie и возврат Cookie полностью автоматны: ответ прибыл — в банку (семантику домен/путь/истечение реализует Jar), следующий запрос к тому же сайту автоматически несёт подходящие. (3) Смысл явной сборки: без Jar поведение совершенно отсутствует (никакой скрытой глобальной банки); с Jar персистентность (запись на диск), разделение (много Client одна банка), изоляция (ключ раздела) — обычные операции с объектом в ваших руках; семантика Cookie главы 32 и Store кэша главы 101 — тот же сорт «библиотека даёт объект, вы даёте политику».
100.4契约
- Перенаправления по умолчанию: максимум десять прыжков; 301/302/303+стандартный POST меняется на GET; 307/308 сохраняют метод (тело обязано переигрываться, иначе отказ REPLAY).
- Граница учётных данных: на чужом origin по умолчанию срезаются Authorization/Proxy-Authorization/Cookie;
FORWARD_CREDENTIALSявно сохраняет; понижение по умолчанию отвергается,ALLOW_DOWNGRADEявно открывает. - Семантика fragment: без # наследует, явный пустой # блокирует; fragment остаётся только в итоговом URL и не входит в target.
- Повторы выключены по умолчанию: включение задаёт лимит; один повтор = лимит + идемпотентный метод + переигрываемое тело, три условия одновременно.
- Валидация конфигурации повторов: неизвестный режим/Flags, нулевой MaxDelay, Base>Max падают до сетевой операции (перехват при старте).
- Перекрытие на уровне вызова: четыре режима повторов, четыре режима перенаправлений (наследовать/следовать/вернуть исходный/считать ошибкой) — независимо на каждый вызов.
- Подключение Cookie: Jar в конфигурации — автоматный приём и возврат; без Jar поведения нет; Jar — открытый объект (персистентность/разделение/изоляция у вызывающего).
- Бюджет цепочки: вся цепочка перенаправлений разделяет общий дедлайн; байты Wire накапливают всю цепочку.
- Обрезка:
HTTP_CLIENT_REDIRECT/_RETRY/_COOKIESнезависимые макросы.
Типичные ошибки
Симптом: у POST включены повторы, сетевой сбой даёт повторный заказ — инвентарь списан дважды, пришла жалоба.
Причина: из тройного условия повторов «идемпотентный метод» — техническое суждение, бизнес-семантику не удержать: ваш POST с уникальным ключом безопасно переигрывается, но переигрываемое тело не объявлено; переименование в «дедуплицирующий GET-запрос» меняет семантику. Правильный путь: по умолчанию выключено; неидемпотентному запросу, действительно нуждающемуся в повторах, после подтверждения бизнес-защиты включайте явно для этого вызова (контракт позволяет вызывающему подтверждать и устанавливать).
Config.Retry.MaxRetries = 3; /* глобально - POST тоже попадает под проверку (с переигрываемым телом реально повторится) */
Config.Retry.MaxRetries = 3; /* лимит на уровне Client */ /* неидемпотентные вызовы выключаем поштучно: */ Options.Retry.Mode = XHTTP_RETRY_DISABLED; pCall = xrtHttpClientDo(pClient, pOrderReq, &Options, ...); /* явно включать только для подтверждённо переигрываемых вызовов */
Симптом: запрос api.example.com (с Authorization) получил 302 на attacker.net — в конфигурации по умолчанию заголовок срезан (безопасно); но «ради совместимости со старым шлюзом» вы включили FORWARD_CREDENTIALS — токен ушёл стороннему домену.
Причина: следование междоменных учётных данных — расширение доверия на цель перенаправления. Срезание по умолчанию — защита; при явном открытии вы обязаны доверять и своему источнику перенаправлений (он управляет, куда указывает Location).
/* глобально: каждый прыжок несёт токен */ Options.Redirect.Flags |= XHTTP_REDIRECT_FORWARD_CREDENTIALS; /* наследуется глобально */
и только для односайтовых контролируемых источников перенаправлений */ if ( is_trusted_redirect_source(Req) ) { Opts.Redirect.Flags |= XHTTP_REDIRECT_FORWARD_CREDENTIALS; }
Симптом: PUT с телом-потоком производителя получил 307 — запрос падает с REDIRECT_REPLAY; кто-то «чинит», обрезая уже прочитанное и переотправляя, — сервер получает испорченное тело.
Причина: семантика 307/308 требует переигрывать тело в исходном виде; потоковое тело (обратный вызов производителя) не перематывается — правильное поведение библиотеки именно отказ. Обрезанная переотправка превращает протокольную ошибку в порчу данных.
/* самоделка: принять уже прочитанную часть потока за полное тело */ redirect_with_partial_body(...); /* порча данных на сервере */
2) потоковое тело при отказе REPLAY: дочитать тело и переотправить новым запросом */ if ( error_is(REDIRECT_REPLAY) ) { buffer_body_then_retry_as_new_call(); }
Практические задания
Локальный сервис отвечает 301/302/303/307/308 на GET и POST; клиент следует и печатает итоговый метод, итоговый URL, наличие тела каждого прыжка. Критерии приёмки: поведение десяти комбинаций сходится с контрактной матрицей; Info.ResponseWireBytes совпадает с числом прыжков, посчитанным вручную.
Настройте 3 повтора; отправьте GET (идемпотентный), POST с байтовым телом (переигрываемым), POST с потоковым телом (непереигрываемым) — против сервиса с перемежающимся 503 измерьте число повторов каждого. Критерии приёмки: GET и переигрываемый POST повторяются до успеха; потоковый POST — финал с первого отказа; при действии Retry-After выдержка уважает серверное значение.
Cookie Jar реализует три шага: точка входа (принять Set-Cookie сессии) → запрос защищённого ресурса с сессией → выход. Посередине вставьте одно междоменное перенаправление для проверки срезания учётных данных (по умолчанию стороннему без Cookie). Критерии приёмки: три шага автоматически продолжают сессию; в запросе междоменного прыжка нет сессионного Cookie; жизненный цикл Jar корректно отделён от Client (порядок Destroy).
Итоги
| Тема | Кратко |
|---|---|
| Перенаправления по умолчанию | десять прыжков; 301/302/303+POST→GET; 307/308 сохраняют метод |
| Переигрывание тела | 307/308 требуют переигрываемого тела; непереигрываемое = отказ REPLAY, конечное состояние без обрезания |
| Граница учётных данных | на чужом origin срезаются три рода; FORWARD_CREDENTIALS явно; понижение по умолчанию отвергается |
| fragment | без # наследует / пустой # блокирует; не входит в request-target |
| Повторы по умолчанию | выключены; включение задаёт лимит; один повтор = лимит + идемпотентность + переигрываемость |
| Выдержка повторов | экспонента Base/MaxDelay + уважение Retry-After; ошибка конфигурации падает до сети |
| Перекрытие вызова | четыре режима повторов / четыре режима перенаправлений — поштучное управление в пакете |
| Cookie | Jar в конфигурации — автоматный приём и возврат; без Jar поведения нет; персистентность/разделение ваши |
| Бюджет цепочки | вся цепочка разделяет общий дедлайн; Wire накапливает; итоговый URL показывает результат прыжков |
| Обрезка | REDIRECT/RETRY/COOKIES независимые макросы |
📖 Справочники API в этой главе:xhttp-http_client (101 записей)xhttp-http_retry (7 записей)xhttp-cookie_jar (20 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки