Chapter Orientation
Volume ten closes. After the server chapters (103–107), the remaining advanced topics converge here: response compression (reply_compress/server_compress — the encoding chosen by negotiating the client's Accept-Encoding, response bodies compressed transparently — the mirror side of Chapter 93's decoding); Range and composed responses (range_multipart — multipart/byteranges responses for multi-range requests, the dual of Chapter 101's client-side Range composition); the proxy header family (Via/X-Forwarded-*'s normalized replacement Forwarded, proxy_status proxy status — the vocabulary of gateway writing). Finally the volume closes with the panorama of "one request crossing volume ten's full stack": every layer from the easy call to the compressed Range response. From Chapter 110 we enter volume eleven (the xws/xruntime/xmail/xssh extension library family).
109.1引入
Compression is a trade between bandwidth and CPU: JSON API responses often compress 70–90%, but "always gzip" runs into two client kinds — those that don't support it don't decompress (an identity fallback is mandatory), and already-compressed content should not be recompressed (compressing images again only makes them bigger and slower). So compression must be negotiation-driven: read Accept-Encoding's q values (Chapter 92), decide by content type and size, choose the best encoding both sides support. Range composition is another trade: the client wants bytes from three ranges — one origin trip fetching segment by segment, or the server/proxy directly assembling a multipart response? The latter saves round trips, but the assembly and the boundaries (Chapter 91's smuggling defense, the multipart edition) must be correct. The shared trait of these two: a correct implementation must read a lot of context (negotiation headers, content metadata, cache state); xhttp makes them "configuration as policy" layers, invisible to business routing.
109.2概念
Response compression: negotiation-driven
xhttpreplycompressconfig carries the policy (minimum compressible length, content-type whitelist, encoding preference); the reply_compress layer applies compression after the Reply is built — business code writes the uncompressed body, and compression is the "exit inspection". Identity fallback: the client doesn't support it, or the policy judges it incompressible — identity as-is, never forced. The server runtime's server_compress is the same capability in service form (enabled at the middleware or runtime layer).
Range and multipart/byteranges
A single range (Range: bytes=0-99) → 206 + Content-Range; multiple ranges (bytes=0-99,200-299) → 206 + multipart/byteranges (each part carries its own Content-Range). range_multipart handles the boundary assembly — the write-side composition dual to Chapter 91's "read-side multi-range". Chapter 101's client-side local Range composition (fragments + origin refill) ultimately rebuilds responses with this same write-side vocabulary.
The proxy header family: gateway syntax
Every hop of a proxy chain must speak: Via (proxies and protocols passed through), Forwarded (the RFC 7239 normalized replacement for X-Forwarded-For/Proto/Host — Forwarded: for=192.0.2.60;proto=https), proxy_status (standardized reporting of proxy errors and upstream status). xhttp's proxy-family modules (forward/forwarded/via/proxy_alias/proxy_status and each one's _write) provide both the read and write ends — the HTTP-header companion of Chapter 70's forward proxy.
One request through volume ten's full stack
Every station is one chapter of this volume — this is xhttp's building-block acceptance map.
109.3示例
First complete program: negotiation-driven compression decisions and output
The program below is from examples/http/reply_compress — compressing responses by client capability:
#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; }
What just happened. (1) xrtHttpAcceptEncodingInit/Add("gzip, identity;q=0.5") builds the client capability declaration — q in thousandths (gzip full score, identity half) — a direct use of Chapter 92's negotiation model. (2) The ordinary ReplyCreate/SetBytes builds the uncompressed JSON response — business code is unaware of compression. (3) The compression layer reads capability + policy, decides, and produces the compressed Reply — the higher-q gzip wins, a transformed body (Chapter 108's source) replaces the original, and the headers update. (4) The contrast-experiment path: rerun with the capability switched to identity — identity fallback, as-is; paired with the client's client_decompress (Chapter 93's decoding) it forms the complete compression loop.
Second complete program: the multi-range composed response
The second program is from examples/http/range_multipart — the write side of 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; }
What just happened. (1) Parse the range set of the multi-range request (Chapter 91's Range vocabulary) → slice each range out of the body → assemble multipart/byteranges by boundary: boundary lines, each part's Content-Range, part bodies, the closing boundary — the write-side boundary discipline symmetric with Chapter 91's read-side smuggling defense (a boundary off by one position is a smuggling window). (2) Echoing Chapter 101: what the client cache's local composition (owned fragments + origin refill) produces is exactly this kind of response — one chapter on the write side, one on the read side, together a complete Range system. (3) Companion inspections: vary (Vary-driven cache key partitioning — Chapter 101's Vary field on the protocol side), proxy_status/proxy_status_write (proxy status reporting), via/forwarded (the proxy-chain header family) — the complete vocabulary of gateway writing.
Three closing routes for readers
Backend service developers: 103→104→105→107 is the trunk (event chain, onion, auth, bodies); SSE (106) as business demands — this chapter's compression and Range are "come back before launch" optimization chapters. Client/tool developers: 97→98→99→101→102 are ready at P18; here you need only the compression-negotiation section (paired with Chapter 93's decoding). Gateway authors: this chapter's proxy header family + Chapter 104's server + Chapter 99's client runtime are all your raw materials — the exchange/forward examples demonstrate both forms of "receive a request and forward it" assembled in one process. Whichever route, the closing self-check has a single standard: for every station of volume ten's panorama you can point to the chapter — if you can point, the building blocks are in place; if not, go back to that chapter — an acceptance more honest than "finished reading".
109.4契约
- Compression negotiation: read Accept-Encoding's q (thousandths); decide by content type/size/existing encoding; identity fallback never forced; output is a transformed body + header updates.
- Compression policy: minimum length/type whitelist/encoding preference belong to configuration; business routing unaware (the exit-inspection shape).
- Range write side: single range 206+Content-Range; multi-range 206+multipart/byteranges; atomicity and correctness of boundary assembly are the symmetric face of smuggling defense.
- Proxy header family: Via/Forwarded (RFC 7239)/proxy_status on both read and write ends; the normalized replacement for X-Forwarded-*.
- Vary: the response-declared key-partitioning field — a cache-primary-key component (Chapter 101);
Vary: *is not cacheable. - Composition discipline: the compression layer changes no semantics (Content-Length updates/framing adaptation automatic); Range and compression stack (Range-then-compress or the reverse — explicit policy).
- Trimming:
HTTP_COMPRESS/Range/proxy-family independent macros — zero cost for deployments that don't compress.
Common Mistakes
Symptom: JPEG/MP4 responses grow after compression and the CPU burns for nothing — a 0% compression ratio plus overhead.
Cause: the content-type whitelist is the policy's core ingredient — images/videos/content already carrying Content-Encoding are skipped. The compression layer's default whitelist excludes them, but playing clever with "compress everything" walks right into it.
Config.MinLength = 0; /* compress everything: JPEG too - bigger and slower */
/* the type whitelist by default holds only compressible text families; the length threshold blocks small responses */ /* when customizing, explicitly exclude image/* video/* and already-encoded content */
Symptom: a hand-spliced multipart boundary collides with the body — the peer parses body bytes as the boundary, ranges misaligned.
Cause: boundary uniqueness is multipart's security premise (Chapter 103's exercise boundary requirement applies equally) — range_multipart's boundary generation has anti-collision built in; hand-spliced strings do not.
snprintf(Out, "----boundary\r\nContent-Range: ..."); /* fixed boundary: collides if the body contains the same string */
/* use the Range composition layer: boundary generation/escaping/termination built in correct */ /* when hand-splicing: random boundary + body scan for collision + correct "--" prefixes and suffixes */
Symptom: after multiple proxy layers the client IP the server sees is the previous hop's — auditing and rate limiting all distorted.
Cause: each hop's proxy must append (not overwrite) Forwarded/X-Forwarded-For; overwriting discards history. The forwarded module's append semantics are built in — hand-splicing easily writes an overwrite.
set_header("X-Forwarded-For", current_peer_ip); /* overwrite: the prior history evaporates */
/* append the current hop, preserving the existing chain */ forwarded_append(reply, current_peer_ip, proto);
Programming Exercises
Construct four client capabilities (gzip full score/identity/no header at all/*;q=0), run the same JSON response through the compression layer, and record the four outputs. Acceptance criteria: negotiation results match the q derivation; identity fallback when unsupported.
The client requests three ranges → the server responds via range_multipart → the client parses the multipart and reassembles the file (Chapter 91's Body + Chapter 92's parameter layer). Acceptance criteria: the reassembled file's hash matches the original; boundary handling with no overlap and no gap.
An intermediate proxy (server + client forms combined: receive request → append Forwarded → forward upstream → receive response → append Via → return to client). Acceptance criteria: after two hops the server sees the complete Forwarded chain; Via accumulates in order; on upstream 5xx, proxy_status reports per the standard — verify header by header with curl.
Summary
| Topic | Quick reference |
|---|---|
| Compression negotiation | Accept-Encoding q (thousandths) driven; identity fallback never forced; the exit-inspection shape |
| Compression policy | minimum length/type whitelist/encoding preference in configuration; business unaware; already-compressed skipped |
| Range write side | single range 206+Content-Range; multi-range multipart/byteranges; boundary anti-collision |
| Proxy header family | Via appends/Forwarded (RFC 7239)/proxy_status — every hop appends, never overwrites |
| Vary | the response-declared cache key partitioning; Vary: * not cacheable (Chapter 101) |
| Composition discipline | compression auto-updates length and framing; Range×compression stackable with explicit policy |
| Volume ten panorama | easy→runtime→pool→TLS→automatic behavior→cache→URL→server chain→bodies→compression — chapter for chapter |
| Trimming | COMPRESS/Range/proxy-family independent macros |
📖 API references in this chapter:xhttp-http_server (75 entries)xhttp-http_compress (3 entries)xhttp-http_cache_range (9 entries)
Click for full signatures, parameter constraints, return values and error notes