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

1Negotiateread the request's Accept-Encoding (q in thousandths - Chapter 92's model)
2Decidecontent type (compressible? JSON/text yes, images no), size (small responses not worth it), existing encoding (don't compress again)
3Encodepick the best by q and server capability (gzip/deflate/identity)
4Applycompress as a transformed body (Chapter 108's transformed source) + Content-Encoding header + length update

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

1CallxrtHttpClientGetSync (Chapter 98 easy) -> runtime freeze (98)
2Connectionpool shard takes/reuses a connection (98) -> TLS (volume eight) -> request write-out (volume nine, 89)
3Automatic behaviorredirect/cookie/retry (99) -> cache lookup/validation (100)
4URL/queryparse and build (101/102) -> auth injection (105)
5Serverevent chain (103) -> middleware onion (104) -> static/routing
6Responsebody source (107) -> compression negotiation (this chapter) -> framing (volume nine, 90) -> transport
7Client receivesdecoding (volume nine, 92) -> cache storage (100) -> result snapshot (97)

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:

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(输出压缩协商与产出响应的自检结果)

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:

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(输出多区间响应组合的自检结果)

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

Pitfall 1: recompressing already-compressed content:Pitfall 1: recompressing already-compressed content

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 */
Pitfall 2: writing the Range boundary string wrong (a smuggling window):Pitfall 2: writing the Range boundary string wrong (a smuggling window)

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 */
Pitfall 3: losing Forwarded semantics in the proxy chain:Pitfall 3: losing Forwarded semantics in the proxy chain

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

Basic: the compression negotiation matrix

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.

Advanced: a multi-range downloader

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.

Challenge: a two-hop proxy gateway

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

TopicQuick reference
Compression negotiationAccept-Encoding q (thousandths) driven; identity fallback never forced; the exit-inspection shape
Compression policyminimum length/type whitelist/encoding preference in configuration; business unaware; already-compressed skipped
Range write sidesingle range 206+Content-Range; multi-range multipart/byteranges; boundary anti-collision
Proxy header familyVia appends/Forwarded (RFC 7239)/proxy_status — every hop appends, never overwrites
Varythe response-declared cache key partitioning; Vary: * not cacheable (Chapter 101)
Composition disciplinecompression auto-updates length and framing; Range×compression stackable with explicit policy
Volume ten panoramaeasy→runtime→pool→TLS→automatic behavior→cache→URL→server chain→bodies→compression — chapter for chapter
TrimmingCOMPRESS/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