| … | … | @@ -166,3 +166,55 @@ function setCorsHeaders (res, origin) { |
| 166 | 166 | res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization') |
| 167 | 167 | res.setHeader('Access-Control-Max-Age', '86400') |
| 168 | 168 | } |
| 169 | |
| 170 | // ── HTTP Caching Helpers ───────────────────────────────────── |
| 171 | |
| 172 | /** |
| 173 | * Set Cache-Control header |
| 174 | * @param {Object} res |
| 175 | * @param {{ maxAge?: number, public?: boolean, immutable?: boolean, noCache?: boolean }} opts |
| 176 | */ |
| 177 | export function cacheControl (res, opts = {}) { |
| 178 | const parts = [] |
| 179 | if (opts.noCache) { parts.push('no-cache', 'no-store'); res.setHeader('Cache-Control', parts.join(', ')); return } |
| 180 | if (opts.public !== false) parts.push('public') |
| 181 | if (opts.maxAge != null) parts.push(`max-age=${opts.maxAge}`) |
| 182 | if (opts.immutable) parts.push('immutable') |
| 183 | res.setHeader('Cache-Control', parts.join(', ')) |
| 184 | } |
| 185 | |
| 186 | /** |
| 187 | * Send response with ETag. Returns true if 304 Not Modified was sent. |
| 188 | * @param {Object} req |
| 189 | * @param {Object} res |
| 190 | * @param {string} etag — ETag value (without quotes) |
| 191 | * @param {Function} sendFn — called with (res) if content needs to be sent |
| 192 | * @returns {boolean} true if 304 was sent |
| 193 | */ |
| 194 | export function withEtag (req, res, etag, sendFn) { |
| 195 | const quoted = `"${etag}"` |
| 196 | res.setHeader('ETag', quoted) |
| 197 | |
| 198 | const ifNoneMatch = req.headers['if-none-match'] |
| 199 | if (ifNoneMatch === quoted) { |
| 200 | res.writeHead(304) |
| 201 | res.end() |
| 202 | return true |
| 203 | } |
| 204 | |
| 205 | sendFn(res) |
| 206 | return false |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * Send JSON response with short-lived cache (for dynamic data that changes on push) |
| 211 | */ |
| 212 | export function jsonCached (res, data, maxAge = 10) { |
| 213 | const body = JSON.stringify(data) |
| 214 | res.writeHead(200, { |
| 215 | 'Content-Type': 'application/json', |
| 216 | 'Content-Length': Buffer.byteLength(body), |
| 217 | 'Cache-Control': `public, max-age=${maxAge}` |
| 218 | }) |
| 219 | res.end(body) |
| 220 | } |