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

Над средой выполнения главы 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概念

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

1Относительный Locationразрешается against текущего действующего URL (разрешение ссылок RFC 3986, глава 102)
2Чужой originпо умолчанию срезаются Authorization/Proxy-Authorization/Cookie; только явный FORWARD_CREDENTIALS сохраняет
3HTTPS->HTTPпо умолчанию отказ; ALLOW_DOWNGRADE явно открывает
4Семантика fragmentLocation без # наследует текущий fragment; явный пустой # блокирует наследование; fragment не входит в request-target

Точная граница переписывания метода: соответствие только стандартный метод 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 — контроль «выключено по умолчанию» против «явно включено»:

extlibs/xhttp/examples/http/client_retry/main.c源码 ↗
#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;
}


$ gcc -O1 -DXRT_MODULE_ALL -DXHTTP_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xhttp.h impl.c extlibs/xhttp/examples/http/client_retry/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapiretries=3 base=250000 max=5000000

Что только что произошло. (1) ConfigInit по умолчанию MaxRetries=0 — автоматических повторов по умолчанию не существует; после установки 3 раза/250 мс база/5 с потолок слой повторов оживает. (2) Вывод повторяет конфигурацию — сам пример и есть документирование конфигурационной грани: три параметра (лимит/базовая выдержка/максимальная выдержка) — все ручки. (3) На уровне вызова можно временно перекрыть: Options.Retry.Mode четырьмя режимами выключает или принуждает для одного вызова — «эту одну не повторять» в пакетной задаче без нового Client. (4) Сравните с ограждениями допуска повторов: эта конфигурация выражает лишь «технический потолок»; повторяем ли каждый запрос фактически — всё равно идемпотентность + переигрываемость; конфигурация и допуск живут по слоям, никто не лезет на чужое.

Вторая полная программа: подключение Cookie Jar

Вторая программа из examples/http/client_cookies — сборочная грань автоматических Cookie:

extlibs/xhttp/examples/http/client_cookies/main.c源码 ↗
#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;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXHTTP_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xhttp.h impl.c extlibs/xhttp/examples/http/client_cookies/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(示例校验 Jar 挂载后 Set-Cookie 自动进罐与后续自动携带路径)

Что только что произошло. (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 независимые макросы.

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

Ловушка 1: включить автоматические повторы для неидемпотентного запроса:Ловушка 1: включить автоматические повторы для неидемпотентного запроса

Симптом: у POST включены повторы, сетевой сбой даёт повторный заказ — инвентарь списан дважды, пришла жалоба.

Причина: из тройного условия повторов «идемпотентный метод» — техническое суждение, бизнес-семантику не удержать: ваш POST с уникальным ключом безопасно переигрывается, но переигрываемое тело не объявлено; переименование в «дедуплицирующий GET-запрос» меняет семантику. Правильный путь: по умолчанию выключено; неидемпотентному запросу, действительно нуждающемуся в повторах, после подтверждения бизнес-защиты включайте явно для этого вызова (контракт позволяет вызывающему подтверждать и устанавливать).

错误写法
Config.Retry.MaxRetries = 3;   /* глобально - POST тоже попадает под проверку (с переигрываемым телом реально повторится) */
正确写法
Config.Retry.MaxRetries = 3;   /* лимит на уровне Client */
/* неидемпотентные вызовы выключаем поштучно: */
Options.Retry.Mode = XHTTP_RETRY_DISABLED;
pCall = xrtHttpClientDo(pClient, pOrderReq, &Options, ...);
/* явно включать только для подтверждённо переигрываемых вызовов */
Ловушка 2: утечка учётных данных при следовании междоменному перенаправлению:Ловушка 2: утечка учётных данных при следовании междоменному перенаправлению

Симптом: запрос 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;
}
Ловушка 3: перенаправление 307/308 с потоковым телом:Ловушка 3: перенаправление 307/308 с потоковым телом

Симптом: 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; ошибка конфигурации падает до сети
Перекрытие вызовачетыре режима повторов / четыре режима перенаправлений — поштучное управление в пакете
CookieJar в конфигурации — автоматный приём и возврат; без Jar поведения нет; персистентность/разделение ваши
Бюджет цепочкився цепочка разделяет общий дедлайн; Wire накапливает; итоговый URL показывает результат прыжков
ОбрезкаREDIRECT/RETRY/COOKIES независимые макросы

📖 Справочники API в этой главе:xhttp-http_client (101 записей)xhttp-http_retry (7 записей)xhttp-cookie_jar (20 записей)

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