diff --git a/reference/configuration/options.md b/reference/configuration/options.md index 61adfbf68..8b597c827 100644 --- a/reference/configuration/options.md +++ b/reference/configuration/options.md @@ -34,7 +34,7 @@ http: ``` - `sessionAffinity` — Route requests from same client to same worker thread (`ip` or header name) -- `compressionThreshold` — Response size threshold for Brotli compression; _Default_: `1200` (bytes) +- `compressionThreshold` — Response size threshold (bytes) for Brotli compression; `0` disables compression; _Default_: `0` - `cors` — Enable CORS; _Default_: `true` - `corsAccessList` — Allowed domains for CORS requests - `corsAccessControlAllowHeaders` — `Access-Control-Allow-Headers` value for OPTIONS preflight diff --git a/reference/http/api.md b/reference/http/api.md index 885717333..4d0646210 100644 --- a/reference/http/api.md +++ b/reference/http/api.md @@ -449,9 +449,7 @@ A `Readable` is the usual return, but the non-stream forms are not theoretical. Harper's own `ContentTypeHandler` TypeScript interface is currently narrower than this runtime contract, so a typed handler returning a `Readable` or an iterable may need a cast. -:::caution -Prefer returning a stream when your handler may run with response compression enabled — `Readable.from([value])` wraps an already-serialized value. Harper pipes the `serializeStream` result into the Brotli compressor, so a `Buffer` or string return throws `TypeError: stream.pipe is not a function` for a client that sends `Accept-Encoding: br`. Compression applies only when `http.compressionThreshold` is set to a non-zero value, and normalizing the non-stream forms belongs in Harper's compression path rather than in every handler — treat this as a current limitation, not as part of the return contract. -::: + Every return form above is also compressed when response compression is enabled (`http.compressionThreshold` set to a non-zero value) and the client sends `Accept-Encoding: br`: a returned `Buffer` or string is compressed as a whole body when its `length` exceeds the threshold (a string's `length` counts UTF-16 code units, not UTF-8 bytes, so a non-ASCII string can be over the threshold in bytes and still be sent uncompressed), and a returned `Readable` or iterable is compressed as a stream. Before v5.3.0, a `Buffer`, string, or generator return failed with `TypeError: stream.pipe is not a function` under compression, so a handler targeting older releases should return a `Readable` — `Readable.from([value])` wraps an already-serialized value. --- diff --git a/reference/http/configuration.md b/reference/http/configuration.md index ab9d85a59..1f8cab5b8 100644 --- a/reference/http/configuration.md +++ b/reference/http/configuration.md @@ -114,9 +114,9 @@ The maximum estimated request queue time in milliseconds. When the queue exceeds Type: `number` -Default: `1200` (bytes) +Default: `0` (compression disabled) -For clients that support Brotli encoding (`Accept-Encoding: br`), responses larger than this threshold are compressed. Streaming query responses are always compressed for supporting clients, regardless of this setting (since their size is unknown upfront). +The threshold is also the on/off switch: `0` disables compression rather than compressing every response, and any non-zero byte count enables it. For clients that send `Accept-Encoding: br`, responses larger than this threshold are Brotli-compressed; Brotli is the only encoding Harper applies, so a client that accepts only `gzip` or `deflate` receives an uncompressed body. A string body, such as serialized JSON, is measured by its JavaScript `length` (UTF-16 code units) rather than its UTF-8 byte size, so text with non-ASCII characters can be over the threshold in bytes and still be sent uncompressed. A response the content type serializes as a stream (query results, for example) is compressed for those clients whenever the threshold is non-zero, regardless of size, since its size is unknown upfront; an iterable body that the content type serializes to a single buffer, such as a plain array sent as MessagePack, is compressed only above the threshold. Server-Sent Events are never compressed. `1200` bytes, about one TCP packet, is a reasonable starting value. ```yaml http: diff --git a/reference/http/overview.md b/reference/http/overview.md index 16f81c2d1..0fa911e64 100644 --- a/reference/http/overview.md +++ b/reference/http/overview.md @@ -111,7 +111,7 @@ HTTP/2 can be enabled with the `http2: true` option in `harper-config.yaml`. Whe ## Compression -Harper automatically compresses HTTP responses using Brotli for clients that advertise `Accept-Encoding: br`. Compression applies when the response body exceeds the configured `compressionThreshold` (default 1200 bytes). Streaming query responses are always compressed for clients that support it (since their size is not known upfront). +Response compression is off by default. Setting [`http.compressionThreshold`](./configuration#httpcompressionthreshold) to a non-zero byte count enables it: Harper then Brotli-compresses responses larger than the threshold for clients that advertise `Accept-Encoding: br`, and compresses streamed responses regardless of size (since their size is not known upfront). Server-Sent Events are never compressed. ## Logging diff --git a/reference/rest/headers.md b/reference/rest/headers.md index f0a98c215..71603c802 100644 --- a/reference/rest/headers.md +++ b/reference/rest/headers.md @@ -94,7 +94,7 @@ If the record has not changed, Harper returns `304 Not Modified` with no body. T ### Accept-Encoding -Harper supports standard HTTP compression. Including this header enables compressed responses: +When response compression is enabled with [`http.compressionThreshold`](../http/configuration.md#httpcompressionthreshold) (it is off by default), Harper Brotli-compresses responses for clients that include `br` in this header. Brotli is the only encoding Harper applies, so a client that accepts only `gzip` receives an uncompressed body: ```http Accept-Encoding: gzip, br