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

Том 10 закрывается. После серверных глав (103–107) оставшиеся продвинутые темы сходятся здесь: сжатие ответов (reply_compress/server_compress — кодирование выбирается согласованием Accept-Encoding клиента, тела ответов сжимаются прозрачно — зеркальная сторона декодирования главы 93); Range и составные ответы (range_multipart — ответы multipart/byteranges на многоинтервальные запросы, дуал клиентской сборки Range главы 101); семейство заголовков прокси (Via/нормализованная замена X-Forwarded-* — Forwarded, статус прокси proxy_status — словарь написания шлюзов). Напоследок том закрывает панорамой «один запрос сквозь весь стек тома 10»: каждый слой от вызова easy до сжатого Range-ответа. С главы 110 начинается том 11 (семейство библиотек-расширений xws/xruntime/xmail/xssh).

109.1引入

Сжатие — обмен полосы на CPU: ответы JSON API сжимаются обычно на 70–90%, но «всегда gzip» натыкается на два рода клиентов — не поддерживающие не распаковывают (обязателен откат identity), а уже сжатое содержимое сжимать снова не нужно (повторное сжатие картинок лишь увеличивает и замедляет). Поэтому сжатие обязано быть управляемым согласованием: читать q-значения Accept-Encoding (глава 92), решать по типу содержимого и размеру, выбирать лучшее кодирование, доступное обеим сторонам. Сборка Range — другой обмен: клиенту нужны байты трёх интервалов — один поход к источнику за отрезками или сервер/прокси сразу собирает multipart-ответ? Второе экономит круги, но сборка и границы (глава 91, smuggling-защита в multipart-издании) обязаны быть верными. Общая черта этих двух дел: правильной реализации надо читать много контекста (заголовки согласования, метаданные содержимого, состояние кэша); xhttp делает их слоями «конфигурация как политика», бизнес-маршрутизация не замечает.

109.2概念

Сжатие ответов: управляется согласованием

1Согласованиечитать Accept-Encoding запроса (q в тысячных - модель главы 92)
2Решениетип содержимого (сжимаемо? JSON/текст да, картинки нет), размер (малый ответ не стоит), уже имеющееся кодирование (не сжимать снова)
3Кодированиевыбрать лучшее по q и возможностям сервера (gzip/deflate/identity)
4Применениесжатие как трансформированное тело (трансформированный источник главы 108) + заголовок Content-Encoding + обновление длины

xhttpreplycompressconfig несёт политику (минимальная сжимаемая длина, белый список типов содержимого, предпочтение кодировок); слой reply_compress применяет сжатие после построения Reply — бизнес-код пишет несжатое тело, сжатие — это «проверка на выезде».Откат identity: клиент не поддерживает или политика признала несжимаемым — identity как есть, никогда принудительно. server_compress серверной среды выполнения — та же способность в форме сервиса (включается на уровне middleware или среды выполнения).

Range и multipart/byteranges

Один интервал (Range: bytes=0-99) → 206 + Content-Range; много интервалов (bytes=0-99,200-299) → 206 + multipart/byteranges (у каждой части свой Content-Range). range_multipart ведает сборкой границ — записывающая сторона-композиция, дуальная «читающей стороне многоинтервальности» главы 91. Локальная сборка Range клиентского кэша главы 101 (фрагменты + дозапрос) в конце концов тоже пересобирает ответы этим словарём записывающей стороны.

Семейство заголовков прокси: синтаксис шлюза

Каждый прыжок цепи прокси обязан говорить: Via (пройденные прокси и протоколы), Forwarded (нормализованная по RFC 7239 замена X-Forwarded-For/Proto/Host — Forwarded: for=192.0.2.60;proto=https), proxy_status (стандартизованный отчёт об ошибках прокси и статусах восходящего потока). Proxy-семейство модулей xhttp (forward/forwarded/via/proxy_alias/proxy_status и их _write) даёт оба конца — чтение и запись — HTTP-заголовковое сопровождение прямого прокси главы 70.

Один запрос сквозь весь стек тома 10

1ВызовxrtHttpClientGetSync (easy главы 98) -> заморозка среды выполнения (98)
2Соединениефрагмент пула берёт/переиспользует соединение (98) -> TLS (том 8) -> выписывание запроса (том 9, 89)
3Автоматикаперенаправления/Cookie/повторы (99) -> поиск/валидация кэша (100)
4URL/queryразбор и построение (101/102) -> внедрение аутентификации (105)
5Серверцепочка событий (103) -> луковица middleware (104) -> статика/маршрутизация
6Ответисточник тела (107) -> согласование сжатия (эта глава) -> кадрирование (том 9, 90) -> транспорт
7Клиент принимаетдекодирование (том 9, 92) -> запись в кэш (100) -> снимок результата (97)

Каждая станция — одна глава тома; это и есть приёмочная карта блочной структуры xhttp.

109.3示例

Первая полная программа: решение и продукт согласованного сжатия

Программа ниже из examples/http/reply_compress — сжатие ответов по возможностям клиента:

extlibs/xhttp/examples/http/reply_compress/main.c源码 ↗
#include <stdio.h>
#include <string.h>

#include <xrt/http_compress.h>



/* 构建常见 JSON Reply,并按请求能力选择 gzip 或 identity。 */
int main(void)
{
	static const char Json[] =
		"{\"code\":200,\"message\":\"OK\","
		"\"data\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}";
	xhttpacceptencoding Accept;
	xhttpreplycompressconfig Config;
	xhttpreply* pReply;
	xhttpreply* pOutput = NULL;
	xhttpreplycompressstatus Status;

	xrtHttpAcceptEncodingInit(&Accept);
	if ( !xrtHttpAcceptEncodingAdd(
		&Accept,
		XRT_STR_LITERAL("gzip, identity;q=0.5")
	) ) {
		return 1;
	}
	pReply = xrtHttpReplyCreate(XHTTP_STATUS_OK);
	if ( (pReply == NULL) ||
		!xrtHttpReplySetBytes(
			pReply,
			(xbytesview){
				(cbytes)Json,
				sizeof(Json) - 1u
			},
			XRT_STR_LITERAL(
				"application/json; charset=utf-8"
			)
		) ) {
		xrtHttpReplyDestroy(pReply);
		return 1;
	}
	xrtHttpReplyCompressConfigInit(&Config);
	Config.MinimumSize = 0;
	Status = xrtHttpReplyCompress(
		&Accept,
		XRT_STR_LITERAL("GET"),
		pReply,
		&Config,
		&pOutput
	);
	if ( Status == XHTTP_REPLY_COMPRESS_ERROR ) {
		xrtHttpReplyDestroy(pReply);
		return 1;
	}
	if ( Status == XHTTP_REPLY_COMPRESS_NOT_ACCEPTABLE ) {
		printf("406 Not Acceptable\n");
	} else if ( Status == XHTTP_REPLY_COMPRESS_SKIP ) {
		printf("use original Reply\n");
	} else {
		const xhttpfield* pEncoding = xrtHttpReplyHeader(
			pOutput,
			XRT_STR_LITERAL("Content-Encoding")
		);

		printf("encoding=%.*s\n",
			pEncoding != NULL ?
				(int)pEncoding->Value.Size : 8,
			pEncoding != NULL ?
				pEncoding->Value.Data : "identity");
	}
	xrtHttpReplyDestroy(pOutput);
	xrtHttpReplyDestroy(pReply);
	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/reply_compress/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(输出压缩协商与产出响应的自检结果)

Что только что произошло. (1) xrtHttpAcceptEncodingInit/Add("gzip, identity;q=0.5") строит декларацию возможностей клиента — q в тысячных (gzip полный балл, identity половина) — прямое использование модели согласования главы 92. (2) Обычный ReplyCreate/SetBytes строит несжатый JSON-ответ — бизнес-код сжатия не ощущает. (3) Слой сжатия читает возможности + политику, решает и выдаёт сжатую версию Reply — побеждает gzip с высоким q, трансформированное тело (источник главы 108) заменяет исходное, поля заголовков обновляются. (4) Путь контрастного эксперимента: перезапуск с возможностью identity — откат identity как есть; вместе с клиентским client_decompress (декодирование главы 93) получается полный замкнутый цикл сжатия.

Вторая полная программа: многоинтервальный составной ответ

Вторая программа из examples/http/range_multipart — записывающая сторона multipart/byteranges:

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

#include <xhttp.h>



/* 测量并写出一个可与文件区间零复制组合的 Part 头。 */
int main(void)
{
	xhttpbyterange Ranges[2] = {
		{ 0, 99 },
		{ 900, 999 }
	};
	char Head[160];
	uint64 iLength;
	size_t iHeadSize;

	if ( !xrtHttpRangeMultipartLength(
			Ranges,
			2,
			1000,
			XRT_STR_LITERAL("application/pdf"),
			XRT_STR_LITERAL("xrt-4f9a"),
			&iLength
		) || !xrtHttpRangeMultipartHeadWrite(
			&Ranges[0],
			1000,
			XRT_STR_LITERAL("application/pdf"),
			XRT_STR_LITERAL("xrt-4f9a"),
			Head,
			sizeof(Head),
			&iHeadSize
		) ) {
		return 1;
	}
	printf("content-length: %llu\n", (unsigned long long)iLength);
	(void)fwrite(Head, 1, iHeadSize, stdout);
	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/range_multipart/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(输出多区间响应组合的自检结果)

Что только что произошло. (1) Разбор множества интервалов многоинтервального запроса (словарь Range главы 91) → каждый интервал вырезается из тела → сборка multipart/byteranges по границам: строки границы, Content-Range каждой части, тело части, завершающая граница — дисциплина границ записывающей стороны симметрична smuggling-защите читающей стороны главы 91 (граница, смещённая на одну позицию, — окно smuggling). (2) Перекличка с главой 100: локальная сборка клиентского кэша (свои фрагменты + дозапрос недостающего) производит именно такие ответы — одна глава о записывающей стороне, другая о читающей, вместе полная система Range. (3) Сопроводительная инспекция: vary (разбивка ключей кэша по Vary — протокольная грань поля Vary главы 101), proxy_status/proxy_status_write (отчёт о статусе прокси), via/forwarded (семейство заголовков цепи прокси) — полный словарь написания шлюзов.

Три маршрута финала для читателя

Разработчик бэкенд-сервисов: 103→104→105→107 — ствол (цепочка событий, луковица, аутентификация, тела); SSE (106) по надобности бизнеса — сжатие и Range этой главы суть главы оптимизации «вернуться перед запуском».Разработчик клиентов/инструментов: 97→98→99→101→102 уже готовы на P18; здесь нужна только секция согласования сжатия (в паре с декодированием главы 93).Автор шлюза: семейство заголовков прокси этой главы + сервер главы 104 + клиентская среда выполнения главы 99 — всё ваше сырьё; образцы exchange/forward демонстрируют обе формы «принял запрос и переслал», собранные в одном процессе. Каким бы ни был маршрут, критерий самопроверки финала один: на каждую станцию панорамы тома 10 вы можете указать главу — можете указать — блоки на месте; не можете — вернитесь в ту главу; это приёмка честнее, чем «прочитано».

109.4契约

  • Согласование сжатия: читать q из Accept-Encoding (тысячные); решать по типу содержимого/размеру/уже имеющемуся кодированию; откат identity никогда принудительно; продукт — трансформированное тело + обновление заголовков.
  • Политика сжатия: минимальная длина/белый список типов/предпочтение кодировок — в конфигурации; бизнес-маршрутизация не замечает (форма «проверки на выезде»).
  • Записывающая сторона Range: один интервал 206+Content-Range; много интервалов 206+multipart/byteranges; атомарность и корректность сборки границ — симметричная грань smuggling-защиты.
  • Семейство заголовков прокси: Via/Forwarded (RFC 7239)/proxy_status на обоих концах чтение и запись; нормализованная замена X-Forwarded-*.
  • Vary: объявляемое ответом поле разбивки ключей — компонент главного ключа кэша (глава 101); Vary: * не кэшируется.
  • Дисциплина композиции: слой сжатия не меняет семантики (обновление Content-Length/адаптация кадрирования автоматически); Range и сжатие складываются (сперва Range потом сжатие, или наоборот — политика явная).
  • Обрезка: HTTP_COMPRESS/Range/proxy-семейство независимые макросы — развёртывания без сжатия не платят ничего.

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

Ловушка 1: повторное сжатие уже сжатого содержимого:Ловушка 1: повторное сжатие уже сжатого содержимого

Симптом: ответы JPEG/MP4 после сжатия вырастают, CPU горит впустую — степень сжатия 0 плюс накладные расходы.

Причина: белый список типов содержимого — ключевой ингредиент политики — картинки/видео/содержимое уже с Content-Encoding пропускаются. Белый список слоя сжатия по умолчанию их не содержит, но самодеятельное «жать всё» натыкается ровно сюда.

错误写法
Config.MinLength = 0;   /* жать всё: JPEG тоже - больше и медленнее */
正确写法
/* белый список типов по умолчанию содержит только сжимаемые текстовые семейства; порог длины отсекает малые ответы */
/* при настройке вручную явно исключите image/* video/* и уже кодированное содержимое */
Ловушка 2: строка границы Range написана с ошибкой (окно smuggling):Ловушка 2: строка границы Range написана с ошибкой (окно smuggling)

Симптом: самосплетённая multipart-граница сталкивается с телом — противоположная сторона разбирает байты тела как границу, интервалы смещаются.

Причина: уникальность границы — предпосылка безопасности multipart (требование к boundary из упражнения главы 103 действует и здесь) — генератор границ range_multipart имеет встроенную защиту от столкновений; у самосплетённой строки её нет.

错误写法
snprintf(Out, "----boundary\r\nContent-Range: ...");  /* фиксированная граница: столкновение, если тело содержит ту же строку */
正确写法
/* брать слой сборки Range: генерация границ/экранирование/завершение встроены и корректны */
/* при ручной сплетке: случайная граница + скан тела на столкновение + корректные префиксы/суффиксы "--" */
Ловушка 3: в цепи прокси потеряна семантика Forwarded:Ловушка 3: в цепи прокси потеряна семантика Forwarded

Симптом: после многих слоёв прокси сервер видит IP клиента как адрес предыдущего прыжка — аудит и rate limiting все искажены.

Причина: прокси каждого прыжка обязан дописывать (не перезаписывать) Forwarded/X-Forwarded-For; перезапись выбрасывает историю. Семантика дописывания модуля forwarded встроена — ручная сплетка легко выходит перезаписью.

错误写法
set_header("X-Forwarded-For", current_peer_ip);   /* перезапись: прежняя история испарилась */
正确写法
/* дописать текущий прыжок, сохранив существующую цепь */
forwarded_append(reply, current_peer_ip, proto);

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

Базовое: матрица согласования сжатия

Постройте четыре возможности клиента (gzip полный балл/identity/заголовка нет вовсе/*;q=0), прогоните один и тот же JSON-ответ через слой сжатия, зафиксируйте четыре продукта. Критерии приёмки: результат согласования сходится с выводом q; при отсутствии поддержки — откат identity.

Продвинутое: многоинтервальный загрузчик

Клиент запрашивает три интервала → сервер отвечает через range_multipart → клиент разбирает multipart и пересобирает файл (Body главы 91 + параметрический слой главы 92). Критерии приёмки: хэш пересобранного файла совпадает с оригиналом; обработка границ без перекрытий и пропусков.

Вызов: двухпрыжковый прокси-шлюз

Промежуточный прокси (комбинация форм сервера и клиента: принять запрос → дописать Forwarded → переслать вверх по течению → принять ответ → дописать Via → вернуть клиенту). Критерии приёмки: после двух прыжков сервер видит полную цепь Forwarded; Via накапливается по порядку; при 5xx верхнего потока proxy_status отчитывается по стандарту — проверьте по заголовкам curl-ом.

Итоги

ТемаКратко
Согласование сжатияq из Accept-Encoding (тысячных); откат identity никогда принудительно; форма «проверки на выезде»
Политика сжатияминимальная длина/белый список типов/предпочтение кодировок в конфигурации; бизнес не замечает; уже сжатое пропускается
Записывающая сторона Rangeодин интервал 206+Content-Range; много интервалов multipart/byteranges; защита границ от столкновений
Семейство заголовков проксиVia дописывает/Forwarded (RFC 7239)/proxy_status — каждый прыжок дописывает, не перезаписывает
Varyобъявляемая ответом разбивка ключей кэша; Vary: * не кэшируется (глава 101)
Дисциплина композициисжатие автообновляет длину и кадрирование; Range×сжатие складываются, политика явная
Панорама тома 10easy→среда выполнения→пул→TLS→автоматика→кэш→URL→цепочка сервера→тела→сжатие — глава за главой
ОбрезкаCOMPRESS/Range/proxy-семейство независимые макросы

📖 Справочники API в этой главе:xhttp-http_server (75 записей)xhttp-http_compress (3 записей)xhttp-http_cache_range (9 записей)

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