/**
* Response compression (lib/web/compress.js, wired into sendHtml + serveStatic).
*
* The daemon shipped everything uncompressed. Apache's mod_deflate on 7rnx
* hid that from the browser but not from the wire that matters: the VPS had
* to pull the full uncompressed response across the ~96ms VPN hop first.
*
* Pinned here: Accept-Encoding is parsed strictly, small bodies are left
* alone, Vary is always sent, and an uncompressed path always remains.
*/
import test from 'brittle'
import zlib from '#node:zlib'
import { acceptsGzip, gzipIfWorthIt, __test } from '../lib/web/compress.js'
import { sendHtml } from '../lib/web/helpers.js'
import { serveStatic } from '../lib/web/static.js'
const { MIN_COMPRESS_BYTES } = __test
function mockRes (opts = {}) {
return {
acceptsGzip: opts.acceptsGzip,
status: null,
headers: null,
body: null,
writeHead (status, headers) {
this.status = status
this.headers = headers
},
end (body) { this.body = body },
header (name) {
const key = Object.keys(this.headers || {})
.find((k) => k.toLowerCase() === name.toLowerCase())
return key ? this.headers[key] : undefined
}
}
}
const big = '<div class="repo-card">a repo row</div>'.repeat(200)
test('acceptsGzip parses the header strictly', (t) => {
t.ok(acceptsGzip({ headers: { 'accept-encoding': 'gzip' } }))
t.ok(acceptsGzip({ headers: { 'accept-encoding': 'gzip, deflate, br' } }))
t.ok(acceptsGzip({ headers: { 'accept-encoding': 'deflate, gzip' } }))
t.ok(acceptsGzip({ headers: { 'accept-encoding': 'gzip;q=1.0, *;q=0.5' } }))
t.ok(acceptsGzip({ headers: { 'accept-encoding': '*' } }), 'wildcard counts')
t.absent(acceptsGzip({ headers: {} }), 'absent header')
t.absent(acceptsGzip({ headers: { 'accept-encoding': '' } }), 'empty header')
t.absent(acceptsGzip({ headers: { 'accept-encoding': 'deflate, br' } }))
t.absent(acceptsGzip(null), 'no request at all')
// Substring false-positives the old naive `.includes('gzip')` would hit.
t.absent(acceptsGzip({ headers: { 'accept-encoding': 'notgzip' } }),
'must not match gzip inside another token')
})
test('small bodies are left uncompressed', (t) => {
t.is(gzipIfWorthIt('tiny'), null)
t.is(gzipIfWorthIt('x'.repeat(MIN_COMPRESS_BYTES - 1)), null,
'just under the threshold')
t.ok(gzipIfWorthIt('x'.repeat(MIN_COMPRESS_BYTES)),
'at the threshold it compresses')
t.is(gzipIfWorthIt(null), null, 'null body')
})
test('compressed output round-trips to the original bytes', (t) => {
const gz = gzipIfWorthIt(big)
t.ok(gz, 'compressed')
t.ok(gz.byteLength < Buffer.byteLength(big) / 2, 'meaningfully smaller')
t.is(zlib.gunzipSync(Buffer.from(gz)).toString(), big,
'gunzip returns exactly the input')
})
test('incompressible input is sent as-is rather than inflated', (t) => {
// Already-gzipped bytes: re-compressing makes them bigger.
const already = zlib.gzipSync(Buffer.from(big))
t.is(gzipIfWorthIt(already), null,
'must not ship a body larger than the original')
})
test('sendHtml compresses when the client accepts it', (t) => {
const res = mockRes({ acceptsGzip: true })
sendHtml(res, big)
t.is(res.status, 200)
t.is(res.header('content-encoding'), 'gzip')
t.is(res.header('vary'), 'Accept-Encoding')
t.ok(res.body.byteLength < Buffer.byteLength(big), 'body is smaller')
t.is(zlib.gunzipSync(Buffer.from(res.body)).toString(), big, 'round-trips')
})
test('sendHtml sends plain HTML when the client does not', (t) => {
const res = mockRes({ acceptsGzip: false })
sendHtml(res, big)
t.absent(res.header('content-encoding'), 'no encoding header')
t.is(res.header('vary'), 'Accept-Encoding', 'Vary is sent regardless')
t.is(res.body, big, 'untouched string body')
})
test('an unstamped res (no router) still renders', (t) => {
// sendHtml is exported; anything calling it outside the router listener
// has no res.acceptsGzip and must degrade to uncompressed, not crash.
const res = mockRes()
sendHtml(res, big)
t.is(res.body, big)
t.absent(res.header('content-encoding'))
})
test('sendHtml preserves a non-200 status', (t) => {
const res = mockRes({ acceptsGzip: true })
sendHtml(res, big, 404)
t.is(res.status, 404)
})
test('static assets compress too', async (t) => {
const res = mockRes()
const handled = await serveStatic('vendor/marked.min.js', res, {
url: '/static/vendor/marked.min.js',
headers: { 'accept-encoding': 'gzip' }
})
t.ok(handled, 'served')
t.is(res.header('content-encoding'), 'gzip')
t.is(res.header('vary'), 'Accept-Encoding')
t.ok(res.header('etag'), 'still validated')
const plain = mockRes()
await serveStatic('vendor/marked.min.js', plain, {
url: '/static/vendor/marked.min.js',
headers: {}
})
t.absent(plain.header('content-encoding'), 'no gzip when not accepted')
t.ok(res.body.byteLength < plain.body.byteLength, 'gzipped is smaller')
})
test('a 304 carries no compressed body', async (t) => {
const first = mockRes()
await serveStatic('pgv3-flash.js', first, {
url: '/static/pgv3-flash.js',
headers: {}
})
const etag = first.header('etag')
const res = mockRes()
await serveStatic('pgv3-flash.js', res, {
url: '/static/pgv3-flash.js',
headers: { 'accept-encoding': 'gzip', 'if-none-match': etag }
})
t.is(res.status, 304)
t.absent(res.body, 'no body on a 304')
t.absent(res.header('content-encoding'), 'and no encoding claim')
})