本章导读
卷十收官。三章服务端(104–108)之后,剩余的高级主题在此合拢:响应压缩(reply_compress/server_compress——按客户端 Accept-Encoding 协商选编码、透明压缩响应正文——第 93 章解码的镜像侧);Range 与组合响应(range_multipart——多区间请求的 multipart/byteranges 响应,与第 101 章客户端 Range 组合对偶);代理头族(Via/X-Forwarded-* 的规范化替代 Forwarded、proxy_status 代理状态——网关写作的词汇)。最后以"一次请求穿越卷十全栈"的全景图收卷:从 easy 调用到压缩 Range 响应的每一层。第 110 章起进入卷十一(xws/xruntime/xmail/xssh 扩展库家族)。
109.1引入
压缩是带宽与 CPU 的交易:JSON API 响应压缩率常达 70-90%,但"永远 gzip"会撞上两类客户端——不支持的不解(必须 identity 回退)、已预压缩的内容不该再压(图片再压只会更大更慢)。所以压缩必须是协商驱动:读 Accept-Encoding 的 q 值(第 92 章)、按内容类型与大小决策、选双方最优编码。Range 组合是另一笔交易:客户端要三个区间的字节——一次回源逐段拿,还是服务端/代理直接拼一个 multipart 响应?后者省往返,但拼装与边界(第 91 章走私防御的 multipart 版)要正确。这两件事共同的特点是:正确的实现要读的上下文多(协商头、内容元数据、缓存状态),xhttp 把它们做成"配置即策略"的层,业务路由无感。
109.2概念
响应压缩:协商驱动
xhttpreplycompressconfig 承载策略(最小压缩长度、内容类型白名单、编码偏好);reply_compress 层在 Reply 构建后应用压缩——业务代码写的是未压缩正文,压缩是"出关检查"。恒等回退:客户端不支持或策略判不可压——identity 原样,绝不强制。服务端运行时的 server_compress 是同一能力的服务形态(中间件或运行时层启用)。
Range 与 multipart/byteranges
单区间(Range: bytes=0-99)→ 206 + Content-Range;多区间(bytes=0-99,200-299)→ 206 + multipart/byteranges(每部件自带 Content-Range)。range_multipart 处理边界拼装——与第 91 章"读侧多区间"对偶的写侧组合。第 101 章客户端缓存的本地 Range 组合(片段+回源)最终也走这套写侧词汇重建响应。
代理头族:网关的语法
代理链的每一跳都要说话:Via(经过的代理与协议)、Forwarded(X-Forwarded-For/Proto/Host 的 RFC 7239 规范化替代——Forwarded: for=192.0.2.60;proto=https)、proxy_status(代理错误与上游状态的标准化报告)。xhttp 的 proxy 族模块(forward/forwarded/via/proxy_alias/proxy_status 及各自 _write)提供读写两端——第 70 章正向代理的 HTTP 头部配套。
一次请求穿越卷十全栈
每一站都是本卷一章——这就是 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; }
刚才发生了什么。 ① xrtHttpAcceptEncodingInit/Add("gzip, identity;q=0.5") 构造客户端能力声明——q 千分位(gzip 全分、identity 半分)——第 92 章协商模型的直接使用。② 普通 ReplyCreate/SetBytes 构建未压缩 JSON 响应——业务代码不感知压缩。③ 压缩层读能力+策略做决策并产出压缩版 Reply——高 q 的 gzip 胜出、变换正文(第 108 章来源)替换原正文、头字段更新。④ 对照实验路径:把能力换成 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; }
刚才发生了什么。 ① 多区间请求的区间集合解析(第 91 章 Range 词汇)→ 每区间从正文切出 → 按边界拼 multipart/byteranges:边界行、每部件的 Content-Range、部件正文、终止边界——写侧的边界纪律与第 91 章读侧走私防御对称(边界错一位就是走私窗口)。② 与第 101 章呼应:客户端缓存的本地组合(已有片段+回源补齐)产出的正是这种响应——一章写侧、一章读侧,合起来是完整的 Range 体系。③ 配套巡检:vary(Vary 驱动的缓存分键——第 101 章 Vary 字段的协议面)、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 示例演示同一进程内"收请求再转发"的双形态组装。无论哪条走法,收官的自检标准只有一个:卷十全景图的每一站你都能指到章——能指到,说明积木在位;指不到,回到那章补——这是比"读完"更诚实的验收。
109.4契约
- 压缩协商:读 Accept-Encoding 的 q(千分位);按内容类型/大小/已有编码决策;恒等回退绝不强制;产出变换正文+头更新。
- 压缩策略:最小长度/类型白名单/编码偏好归配置;业务路由无感(出关检查形态)。
- Range 写侧:单区间 206+Content-Range;多区间 206+multipart/byteranges;边界拼装的原子性与正确性是走私防御的对称面。
- 代理头族:Via/Forwarded(RFC 7239)/proxy_status 读写两端;X-Forwarded-* 的规范化替代。
- Vary:响应声明的分键字段——缓存主键成分(第 101 章);
Vary: *不可缓存。 - 组合纪律:压缩层不改语义(Content-Length 更新/分帧适配自动);Range 与压缩可叠加(先 Range 后压或反之——策略显式)。
- 裁剪:
HTTP_COMPRESS/Range/代理族独立宏——不压缩的部署零成本。
常见错误
症状:JPEG/MP4 响应压完变大、CPU 白烧——压缩率 0 还倒贴开销。
原因:内容类型白名单是策略的核心成分——图片/视频/已带 Content-Encoding 的内容跳过。压缩层默认白名单不含它们,但自作聪明"全压"就撞上。
Config.MinLength = 0; /* 全压:JPEG 也压——变大变慢 */
/* 类型白名单默认只含可压文本族;长度阈值挡小响应 */ /* 自定义时显式排除 image/* video/* 与已有编码 */
症状:自拼 multipart 边界与正文撞车——对端把正文当边界解析,区间错位。
原因:边界唯一性是 multipart 的安全前提(第 103 章练习的 boundary 要求同样适用)——range_multipart 的边界生成内建防撞;手拼字符串没有。
snprintf(Out, "----boundary\r\nContent-Range: ..."); /* 固定边界:正文含同串即撞 */
/* 用 Range 组合层:边界生成/转义/终止内建正确 */ /* 自拼时:随机边界 + 正文扫描防撞 + 正确的 "--" 前后缀 */
症状:多层代理后服务端看到的客户端 IP 是上一跳代理的——审计与限流全部失真。
原因:每跳代理应追加(不是覆盖)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 推导一致;不支持时恒等回退。
客户端请求三区间 → 服务端 range_multipart 响应 → 客户端解析 multipart 拼回文件(第 91 章 Body + 第 92 章参数层)。验收标准:拼回文件与原文件哈希一致;边界处理无重叠无缺失。
中间代理(服务端+客户端形态组合:收请求→Forwarded 追加→转发上游→收响应→Via 追加→回客户端)。验收标准:两跳后服务端可见完整 Forwarded 链;Via 按序累积;上游 5xx 时 proxy_status 规范报告——用 curl 逐头验证。
小结
| 知识点 | 速查 |
|---|---|
| 压缩协商 | Accept-Encoding q(千分位)驱动;恒等回退绝不强制;出关检查形态 |
| 压缩策略 | 最小长/类型白名单/编码偏好归配置;业务无感;已压内容跳过 |
| Range 写侧 | 单区间 206+Content-Range;多区间 multipart/byteranges;边界防撞 |
| 代理头族 | Via 追加/Forwarded(RFC 7239)/proxy_status——每跳追加不覆盖 |
| Vary | 响应声明的缓存分键;Vary: * 不可缓存(第 101 章) |
| 组合纪律 | 压缩自动更新长度与分帧;Range×压缩可叠且策略显式 |
| 卷十全景 | easy→运行时→池→TLS→自动行为→缓存→URL→服务端链→正文→压缩——章章对应 |
| 裁剪 | COMPRESS/Range/代理族独立宏 |
📖 本章涉及的 API 参考手册:xhttp-http_server(75 个条目)xhttp-http_compress(3 个条目)xhttp-http_cache_range(9 个条目)
点击查看完整签名、参数约束、返回值与错误说明