Ориентация главы
Том 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概念
Сжатие ответов: управляется согласованием
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
Каждая станция — одна глава тома; это и есть приёмочная карта блочной структуры xhttp.
109.3示例
Первая полная программа: решение и продукт согласованного сжатия
Программа ниже из examples/http/reply_compress — сжатие ответов по возможностям клиента:
#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; }
Что только что произошло. (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:
#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; }
Что только что произошло. (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-семейство независимые макросы — развёртывания без сжатия не платят ничего.
Типичные ошибки
Симптом: ответы JPEG/MP4 после сжатия вырастают, CPU горит впустую — степень сжатия 0 плюс накладные расходы.
Причина: белый список типов содержимого — ключевой ингредиент политики — картинки/видео/содержимое уже с Content-Encoding пропускаются. Белый список слоя сжатия по умолчанию их не содержит, но самодеятельное «жать всё» натыкается ровно сюда.
Config.MinLength = 0; /* жать всё: JPEG тоже - больше и медленнее */
/* белый список типов по умолчанию содержит только сжимаемые текстовые семейства; порог длины отсекает малые ответы */ /* при настройке вручную явно исключите image/* video/* и уже кодированное содержимое */
Симптом: самосплетённая multipart-граница сталкивается с телом — противоположная сторона разбирает байты тела как границу, интервалы смещаются.
Причина: уникальность границы — предпосылка безопасности multipart (требование к boundary из упражнения главы 103 действует и здесь) — генератор границ range_multipart имеет встроенную защиту от столкновений; у самосплетённой строки её нет.
snprintf(Out, "----boundary\r\nContent-Range: ..."); /* фиксированная граница: столкновение, если тело содержит ту же строку */
/* брать слой сборки Range: генерация границ/экранирование/завершение встроены и корректны */ /* при ручной сплетке: случайная граница + скан тела на столкновение + корректные префиксы/суффиксы "--" */
Симптом: после многих слоёв прокси сервер видит 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×сжатие складываются, политика явная |
| Панорама тома 10 | easy→среда выполнения→пул→TLS→автоматика→кэш→URL→цепочка сервера→тела→сжатие — глава за главой |
| Обрезка | COMPRESS/Range/proxy-семейство независимые макросы |
📖 Справочники API в этой главе:xhttp-http_server (75 записей)xhttp-http_compress (3 записей)xhttp-http_cache_range (9 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки