@nifrajs/middleware 2.8.1 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/_utils.d.ts +6 -1
- package/dist/_utils.d.ts.map +1 -1
- package/dist/_utils.js +39 -28
- package/dist/_utils.js.map +1 -1
- package/dist/cache-control.d.ts.map +1 -1
- package/dist/cache-control.js +31 -15
- package/dist/cache-control.js.map +1 -1
- package/dist/cache.d.ts +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +2 -3
- package/dist/cache.js.map +1 -1
- package/dist/compression.d.ts +10 -10
- package/dist/compression.d.ts.map +1 -1
- package/dist/compression.js +191 -70
- package/dist/compression.js.map +1 -1
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js +27 -16
- package/dist/cors.js.map +1 -1
- package/dist/etag.d.ts +4 -2
- package/dist/etag.d.ts.map +1 -1
- package/dist/etag.js +135 -63
- package/dist/etag.js.map +1 -1
- package/dist/idempotency.d.ts.map +1 -1
- package/dist/idempotency.js +10 -1
- package/dist/idempotency.js.map +1 -1
- package/dist/jwt.js +1 -1
- package/dist/jwt.js.map +1 -1
- package/dist/language.d.ts.map +1 -1
- package/dist/language.js +14 -15
- package/dist/language.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +34 -16
- package/dist/logger.js.map +1 -1
- package/dist/powered-by.d.ts.map +1 -1
- package/dist/powered-by.js +8 -5
- package/dist/powered-by.js.map +1 -1
- package/dist/pretty-json.d.ts +5 -2
- package/dist/pretty-json.d.ts.map +1 -1
- package/dist/pretty-json.js +85 -64
- package/dist/pretty-json.js.map +1 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +76 -19
- package/dist/rate-limit.js.map +1 -1
- package/dist/request-id.js +1 -1
- package/dist/request-id.js.map +1 -1
- package/dist/security-headers.d.ts +8 -4
- package/dist/security-headers.d.ts.map +1 -1
- package/dist/security-headers.js +18 -16
- package/dist/security-headers.js.map +1 -1
- package/package.json +3 -3
- package/src/_utils.ts +39 -24
- package/src/cache-control.ts +27 -14
- package/src/cache.ts +2 -4
- package/src/compression.ts +202 -62
- package/src/cors.ts +23 -14
- package/src/etag.ts +141 -59
- package/src/idempotency.ts +11 -1
- package/src/jwt.ts +1 -1
- package/src/language.ts +12 -13
- package/src/logger.ts +24 -6
- package/src/powered-by.ts +8 -4
- package/src/pretty-json.ts +95 -70
- package/src/rate-limit.ts +86 -20
- package/src/request-id.ts +1 -1
- package/src/security-headers.ts +17 -15
package/dist/compression.js
CHANGED
|
@@ -2,6 +2,33 @@ import { definePlugin } from "@nifrajs/core/server";
|
|
|
2
2
|
// Text-like payloads benefit from gzip; binary media (images/video/archives) is already compressed.
|
|
3
3
|
const COMPRESSIBLE = /^(?:text\/|application\/(?:json|[\w.-]+\+json|javascript|xml|[\w.-]+\+xml|wasm|x-ndjson)|image\/svg\+xml)/i;
|
|
4
4
|
const defaultCompressible = (contentType) => COMPRESSIBLE.test(contentType);
|
|
5
|
+
/** Honor explicit q=0 exclusions; a client that says `gzip;q=0` must not receive gzip. */
|
|
6
|
+
function acceptsGzip(value) {
|
|
7
|
+
if (value === null)
|
|
8
|
+
return false;
|
|
9
|
+
let wildcard;
|
|
10
|
+
let gzip;
|
|
11
|
+
for (const item of value.split(",")) {
|
|
12
|
+
const parts = item.trim().split(";");
|
|
13
|
+
const coding = parts[0]?.trim().toLowerCase();
|
|
14
|
+
if (coding !== "gzip" && coding !== "*")
|
|
15
|
+
continue;
|
|
16
|
+
let quality = 1;
|
|
17
|
+
for (let i = 1; i < parts.length; i++) {
|
|
18
|
+
const [name, raw] = parts[i].trim().split("=", 2);
|
|
19
|
+
if (name?.toLowerCase() === "q") {
|
|
20
|
+
const parsed = Number(raw);
|
|
21
|
+
quality = Number.isFinite(parsed) && parsed >= 0 && parsed <= 1 ? parsed : 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (coding === "gzip")
|
|
25
|
+
gzip = quality;
|
|
26
|
+
else
|
|
27
|
+
wildcard = quality;
|
|
28
|
+
}
|
|
29
|
+
return (gzip ?? wildcard ?? 0) > 0;
|
|
30
|
+
}
|
|
31
|
+
const GZIP_ENCODER = new TextEncoder();
|
|
5
32
|
const concat = (chunks, total) => {
|
|
6
33
|
const out = new Uint8Array(total);
|
|
7
34
|
let offset = 0;
|
|
@@ -14,92 +41,186 @@ const concat = (chunks, total) => {
|
|
|
14
41
|
const gzipHeaders = (source) => {
|
|
15
42
|
const headers = new Headers(source);
|
|
16
43
|
headers.set("content-encoding", "gzip");
|
|
17
|
-
headers.delete("content-length");
|
|
44
|
+
headers.delete("content-length");
|
|
45
|
+
addGzipVary(headers);
|
|
46
|
+
return headers;
|
|
47
|
+
};
|
|
48
|
+
function addGzipVary(headers) {
|
|
18
49
|
const vary = headers.get("vary");
|
|
19
|
-
if (vary === null)
|
|
50
|
+
if (vary === null) {
|
|
20
51
|
headers.set("vary", "Accept-Encoding");
|
|
21
|
-
|
|
52
|
+
}
|
|
53
|
+
else if (!vary
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
.split(",")
|
|
56
|
+
.some((part) => part.trim() === "accept-encoding")) {
|
|
22
57
|
headers.set("vary", `${vary}, Accept-Encoding`);
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function hasNoTransform(value) {
|
|
61
|
+
return (value
|
|
62
|
+
?.split(",")
|
|
63
|
+
.some((directive) => directive.trim().split(";", 1)[0]?.trim().toLowerCase() === "no-transform") ?? false);
|
|
64
|
+
}
|
|
65
|
+
/** Gzip a resident buffer through the Web-standard `CompressionStream` (available everywhere). */
|
|
66
|
+
async function gzipBytes(bytes) {
|
|
67
|
+
// No explicit generic: the chunk type infers from `enqueue`, which keeps the stream assignable
|
|
68
|
+
// to `CompressionStream`'s typing across the Bun/DOM lib variants.
|
|
69
|
+
const source = new ReadableStream({
|
|
70
|
+
start(controller) {
|
|
71
|
+
controller.enqueue(bytes);
|
|
72
|
+
controller.close();
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const compressed = await new Response(source.pipeThrough(new CompressionStream("gzip"))).arrayBuffer();
|
|
76
|
+
return new Uint8Array(compressed);
|
|
77
|
+
}
|
|
78
|
+
/** Stream-safe fallback for handler-returned/proxied Responses. Peek only enough to enforce the
|
|
79
|
+
* threshold, then replay the prefix through CompressionStream without buffering the remainder. */
|
|
80
|
+
function compressRawResponse(response, req, threshold, isCompressible) {
|
|
81
|
+
const body = response.body;
|
|
82
|
+
if (body === null)
|
|
83
|
+
return response;
|
|
84
|
+
if (response.status === 204 || response.status === 205 || response.status === 304) {
|
|
85
|
+
return response;
|
|
86
|
+
}
|
|
87
|
+
if (!acceptsGzip(req.headers.get("accept-encoding")))
|
|
88
|
+
return response;
|
|
89
|
+
if (response.headers.has("content-encoding"))
|
|
90
|
+
return response;
|
|
91
|
+
if (response.status === 206 || response.headers.has("content-range"))
|
|
92
|
+
return response;
|
|
93
|
+
if (hasNoTransform(response.headers.get("cache-control")))
|
|
94
|
+
return response;
|
|
95
|
+
if (!isCompressible(response.headers.get("content-type") ?? ""))
|
|
96
|
+
return response;
|
|
97
|
+
const declared = response.headers.get("content-length");
|
|
98
|
+
if (declared !== null && Number(declared) < threshold)
|
|
99
|
+
return response;
|
|
100
|
+
return compressRawBody(response, body, threshold);
|
|
101
|
+
}
|
|
102
|
+
async function compressRawBody(response, body, threshold) {
|
|
103
|
+
const reader = body.getReader();
|
|
104
|
+
const buffered = [];
|
|
105
|
+
let total = 0;
|
|
106
|
+
try {
|
|
68
107
|
while (total < threshold) {
|
|
69
108
|
const { done, value } = await reader.read();
|
|
70
109
|
if (done) {
|
|
71
|
-
// Body ended below the threshold - too small to be worth gzipping. Emit as-is.
|
|
72
110
|
return new Response(concat(buffered, total), {
|
|
73
|
-
status:
|
|
74
|
-
statusText:
|
|
75
|
-
headers:
|
|
111
|
+
status: response.status,
|
|
112
|
+
statusText: response.statusText,
|
|
113
|
+
headers: response.headers,
|
|
76
114
|
});
|
|
77
115
|
}
|
|
116
|
+
if (value === undefined)
|
|
117
|
+
return replayRawResponse(response, buffered, reader);
|
|
78
118
|
buffered.push(value);
|
|
79
119
|
total += value.byteLength;
|
|
80
120
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return replayRawResponse(response, buffered, reader);
|
|
124
|
+
}
|
|
125
|
+
const source = new ReadableStream({
|
|
126
|
+
start(controller) {
|
|
127
|
+
for (const chunk of buffered)
|
|
128
|
+
controller.enqueue(chunk);
|
|
129
|
+
},
|
|
130
|
+
async pull(controller) {
|
|
131
|
+
try {
|
|
90
132
|
const { done, value } = await reader.read();
|
|
91
|
-
if (done)
|
|
133
|
+
if (done || value === undefined)
|
|
92
134
|
controller.close();
|
|
93
135
|
else
|
|
94
136
|
controller.enqueue(value);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
controller.error(error);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
cancel: (reason) => reader.cancel(reason),
|
|
143
|
+
});
|
|
144
|
+
return new Response(source.pipeThrough(new CompressionStream("gzip")), {
|
|
145
|
+
status: response.status,
|
|
146
|
+
statusText: response.statusText,
|
|
147
|
+
headers: gzipHeaders(response.headers),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function replayRawResponse(response, chunks, reader) {
|
|
151
|
+
const stream = new ReadableStream({
|
|
152
|
+
start(controller) {
|
|
153
|
+
for (const chunk of chunks)
|
|
154
|
+
controller.enqueue(chunk);
|
|
155
|
+
},
|
|
156
|
+
async pull(controller) {
|
|
157
|
+
try {
|
|
158
|
+
const { done, value } = await reader.read();
|
|
159
|
+
if (done || value === undefined)
|
|
160
|
+
controller.close();
|
|
161
|
+
else
|
|
162
|
+
controller.enqueue(value);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
controller.error(error);
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
cancel: (reason) => reader.cancel(reason),
|
|
169
|
+
});
|
|
170
|
+
return new Response(stream, {
|
|
171
|
+
status: response.status,
|
|
172
|
+
statusText: response.statusText,
|
|
173
|
+
headers: response.headers,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Transparently **gzip** responses when the client sends `Accept-Encoding: gzip` and the body is a
|
|
178
|
+
* compressible type larger than `threshold`. Uses the Web-standard `CompressionStream`, so it runs
|
|
179
|
+
* on every nifra runtime including the edge; gzip is the one encoding `CompressionStream`
|
|
180
|
+
* guarantees everywhere, so brotli isn't offered.
|
|
181
|
+
*
|
|
182
|
+
* Built on the portable `onResponseBody` tier for framework-serialized bytes, with a stream-safe raw
|
|
183
|
+
* fallback for handler-returned/proxied Responses. The common JSON path stays on Node's direct
|
|
184
|
+
* writer; raw streams are compressed incrementally without being fully buffered.
|
|
185
|
+
*
|
|
186
|
+
* Skips: clients that don't accept gzip, already-encoded responses, bodyless responses,
|
|
187
|
+
* `Range` responses (206 / `Content-Range`), `Cache-Control: no-transform`, non-compressible
|
|
188
|
+
* types, and bodies below `threshold` (gzip's ~20-byte overhead would enlarge them). Adds
|
|
189
|
+
* `Vary: Accept-Encoding`.
|
|
190
|
+
*
|
|
191
|
+
* ```ts
|
|
192
|
+
* app.use(compression())
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
export function compression(options = {}) {
|
|
196
|
+
const threshold = options.threshold ?? 1024;
|
|
197
|
+
if (!Number.isSafeInteger(threshold) || threshold < 0) {
|
|
198
|
+
throw new Error("compression: threshold must be a non-negative safe integer");
|
|
199
|
+
}
|
|
200
|
+
const isCompressible = options.compressible ?? defaultCompressible;
|
|
201
|
+
return definePlugin("compression", (app) => app.use({
|
|
202
|
+
onResponseBody(body, headers, req, status) {
|
|
203
|
+
if (!acceptsGzip(req.header("accept-encoding")))
|
|
204
|
+
return undefined;
|
|
205
|
+
if (headers.has("content-encoding"))
|
|
206
|
+
return undefined;
|
|
207
|
+
if (status === 206 || headers.has("content-range"))
|
|
208
|
+
return undefined;
|
|
209
|
+
if (hasNoTransform(headers.get("cache-control")))
|
|
210
|
+
return undefined;
|
|
211
|
+
if (!isCompressible(headers.get("content-type") ?? ""))
|
|
212
|
+
return undefined;
|
|
213
|
+
const bytes = typeof body === "string" ? GZIP_ENCODER.encode(body) : body;
|
|
214
|
+
if (bytes.byteLength < threshold)
|
|
215
|
+
return undefined;
|
|
216
|
+
return gzipBytes(bytes).then((compressed) => {
|
|
217
|
+
headers.set("content-encoding", "gzip");
|
|
218
|
+
headers.delete("content-length");
|
|
219
|
+
addGzipVary(headers);
|
|
220
|
+
return compressed;
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
onResponseRaw: (response, req) => compressRawResponse(response, req, threshold, isCompressible),
|
|
103
224
|
}));
|
|
104
225
|
}
|
|
105
226
|
//# sourceMappingURL=compression.js.map
|
package/dist/compression.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compression.js","sourceRoot":"","sources":["../src/compression.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"compression.js","sourceRoot":"","sources":["../src/compression.ts"],"names":[],"mappings":"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"}
|
package/dist/cors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../src/cors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../src/cors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD,MAAM,WAAW,WAAW;IAC1B,wFAAwF;IACxF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAA;IAChF,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACxC,iGAAiG;IACjG,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC/C,+CAA+C;IAC/C,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC/C,2GAA2G;IAC3G,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAwBD;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,UAAU,CAmD1D"}
|
package/dist/cors.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withHeaders } from "./_utils.js";
|
|
2
1
|
const DEFAULT_METHODS = [
|
|
3
2
|
"GET",
|
|
4
3
|
"HEAD",
|
|
@@ -51,24 +50,36 @@ export function cors(options = {}) {
|
|
|
51
50
|
headers.set("Access-Control-Allow-Headers", requested);
|
|
52
51
|
if (maxAge !== undefined)
|
|
53
52
|
headers.set("Access-Control-Max-Age", String(maxAge));
|
|
54
|
-
// Allow-Origin / Allow-Credentials are added by
|
|
53
|
+
// Allow-Origin / Allow-Credentials are added by onResponseHeaders (runs on every response).
|
|
55
54
|
return new Response(null, { status: 204, headers });
|
|
56
55
|
},
|
|
57
|
-
|
|
58
|
-
const
|
|
56
|
+
onNodeRequest(req) {
|
|
57
|
+
const isPreflight = req.method === "OPTIONS" && req.header("access-control-request-method") !== null;
|
|
58
|
+
if (!isPreflight)
|
|
59
|
+
return undefined;
|
|
60
|
+
const headers = {
|
|
61
|
+
"Access-Control-Allow-Methods": methods,
|
|
62
|
+
};
|
|
63
|
+
const requested = allowedHeaders ?? req.header("access-control-request-headers");
|
|
64
|
+
if (requested)
|
|
65
|
+
headers["Access-Control-Allow-Headers"] = requested;
|
|
66
|
+
if (maxAge !== undefined)
|
|
67
|
+
headers["Access-Control-Max-Age"] = String(maxAge);
|
|
68
|
+
return new Response(null, { status: 204, headers });
|
|
69
|
+
},
|
|
70
|
+
onResponseHeaders(headers, req) {
|
|
71
|
+
const allowOrigin = resolveAllowOrigin(origin, req.header("origin"));
|
|
59
72
|
if (allowOrigin === null)
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
headers.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
});
|
|
73
|
+
return;
|
|
74
|
+
headers.set("access-control-allow-origin", allowOrigin);
|
|
75
|
+
if (allowOrigin !== "*") {
|
|
76
|
+
const vary = headers.get("vary");
|
|
77
|
+
headers.set("vary", vary === null || vary === "" ? "Origin" : `${vary}, Origin`);
|
|
78
|
+
}
|
|
79
|
+
if (credentials)
|
|
80
|
+
headers.set("access-control-allow-credentials", "true");
|
|
81
|
+
if (exposedHeaders !== undefined)
|
|
82
|
+
headers.set("access-control-expose-headers", exposedHeaders);
|
|
72
83
|
},
|
|
73
84
|
};
|
|
74
85
|
}
|
package/dist/cors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.js","sourceRoot":"","sources":["../src/cors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cors.js","sourceRoot":"","sources":["../src/cors.ts"],"names":[],"mappings":"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"}
|
package/dist/etag.d.ts
CHANGED
|
@@ -7,8 +7,10 @@ export interface ETagOptions {
|
|
|
7
7
|
/**
|
|
8
8
|
* A {@link definePlugin} plugin that adds a content-hash `ETag` to `GET` `200` responses and returns
|
|
9
9
|
* **`304 Not Modified`** when the client's `If-None-Match` matches - saving bandwidth on unchanged
|
|
10
|
-
* responses.
|
|
11
|
-
*
|
|
10
|
+
* responses. Built on the portable `onResponseBody` tier: the hook receives the final
|
|
11
|
+
* framework-serialized bytes on every runtime with nothing drained, so the middleware stays on
|
|
12
|
+
* Node's direct writer. Handler-returned raw `Response`s (streams, proxied fetches) use a capped,
|
|
13
|
+
* replaying fallback so oversized bodies remain intact. Idempotent.
|
|
12
14
|
*/
|
|
13
15
|
export declare function etag(options?: ETagOptions): import("@nifrajs/core/server").NifraPlugin<import("@nifrajs/core/server").AnyServer, import("@nifrajs/core/server").AnyServer>;
|
|
14
16
|
//# sourceMappingURL=etag.d.ts.map
|
package/dist/etag.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"etag.d.ts","sourceRoot":"","sources":["../src/etag.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,WAAW;IAC1B,0GAA0G;IAC1G,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,oGAAoG;IACpG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;
|
|
1
|
+
{"version":3,"file":"etag.d.ts","sourceRoot":"","sources":["../src/etag.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,WAAW;IAC1B,0GAA0G;IAC1G,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,oGAAoG;IACpG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AA4ED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,OAAO,GAAE,WAAgB,kIAwC7C"}
|
package/dist/etag.js
CHANGED
|
@@ -8,40 +8,150 @@ function fnv1a(bytes) {
|
|
|
8
8
|
}
|
|
9
9
|
return (h >>> 0).toString(16);
|
|
10
10
|
}
|
|
11
|
+
const ETAG_ENCODER = new TextEncoder();
|
|
12
|
+
async function sha256Tag(bytes) {
|
|
13
|
+
const input = new Uint8Array(bytes.byteLength);
|
|
14
|
+
input.set(bytes);
|
|
15
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", input));
|
|
16
|
+
let hex = "";
|
|
17
|
+
for (const byte of digest)
|
|
18
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
19
|
+
return hex;
|
|
20
|
+
}
|
|
21
|
+
const concat = (chunks, total) => {
|
|
22
|
+
const out = new Uint8Array(total);
|
|
23
|
+
let offset = 0;
|
|
24
|
+
for (const chunk of chunks) {
|
|
25
|
+
out.set(chunk, offset);
|
|
26
|
+
offset += chunk.byteLength;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
};
|
|
30
|
+
/** Read a raw response without losing a streamed body when the cap is exceeded or the upstream
|
|
31
|
+
* fails. The already-read prefix is replayed through the same reader instead of cancelling a clone,
|
|
32
|
+
* which can stall the original body on some runtimes. */
|
|
33
|
+
async function readBytesCapped(res, maxBytes) {
|
|
34
|
+
const body = res.body;
|
|
35
|
+
if (body === null)
|
|
36
|
+
return { bytes: new Uint8Array() };
|
|
37
|
+
const reader = body.getReader();
|
|
38
|
+
const chunks = [];
|
|
39
|
+
let total = 0;
|
|
40
|
+
try {
|
|
41
|
+
for (;;) {
|
|
42
|
+
const { done, value } = await reader.read();
|
|
43
|
+
if (done)
|
|
44
|
+
return { bytes: concat(chunks, total) };
|
|
45
|
+
if (value === undefined)
|
|
46
|
+
return { response: replayFrom(res, chunks, reader) };
|
|
47
|
+
chunks.push(value);
|
|
48
|
+
total += value.byteLength;
|
|
49
|
+
if (total > maxBytes)
|
|
50
|
+
return { response: replayFrom(res, chunks, reader) };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { response: replayFrom(res, chunks, reader) };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function replayFrom(res, chunks, reader) {
|
|
58
|
+
const stream = new ReadableStream({
|
|
59
|
+
start(controller) {
|
|
60
|
+
for (const chunk of chunks)
|
|
61
|
+
controller.enqueue(chunk);
|
|
62
|
+
},
|
|
63
|
+
async pull(controller) {
|
|
64
|
+
try {
|
|
65
|
+
const { done, value } = await reader.read();
|
|
66
|
+
if (done || value === undefined)
|
|
67
|
+
controller.close();
|
|
68
|
+
else
|
|
69
|
+
controller.enqueue(value);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
controller.error(error);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
cancel: (reason) => reader.cancel(reason),
|
|
76
|
+
});
|
|
77
|
+
return new Response(stream, {
|
|
78
|
+
status: res.status,
|
|
79
|
+
statusText: res.statusText,
|
|
80
|
+
headers: res.headers,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
11
83
|
/**
|
|
12
84
|
* A {@link definePlugin} plugin that adds a content-hash `ETag` to `GET` `200` responses and returns
|
|
13
85
|
* **`304 Not Modified`** when the client's `If-None-Match` matches - saving bandwidth on unchanged
|
|
14
|
-
* responses.
|
|
15
|
-
*
|
|
86
|
+
* responses. Built on the portable `onResponseBody` tier: the hook receives the final
|
|
87
|
+
* framework-serialized bytes on every runtime with nothing drained, so the middleware stays on
|
|
88
|
+
* Node's direct writer. Handler-returned raw `Response`s (streams, proxied fetches) use a capped,
|
|
89
|
+
* replaying fallback so oversized bodies remain intact. Idempotent.
|
|
16
90
|
*/
|
|
17
91
|
export function etag(options = {}) {
|
|
18
|
-
const
|
|
92
|
+
const weak = options.weak ?? true;
|
|
93
|
+
const prefix = weak ? "W/" : "";
|
|
19
94
|
const maxBytes = options.maxBytes ?? 1_000_000;
|
|
20
|
-
if (!Number.
|
|
21
|
-
throw new Error("etag: maxBytes must be a non-negative integer");
|
|
95
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
96
|
+
throw new Error("etag: maxBytes must be a non-negative safe integer");
|
|
22
97
|
}
|
|
23
|
-
return definePlugin("etag", (app) => app.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
98
|
+
return definePlugin("etag", (app) => app.use({
|
|
99
|
+
onResponseBody(body, headers, req, status) {
|
|
100
|
+
if (req.method !== "GET" || status !== 200)
|
|
101
|
+
return undefined;
|
|
102
|
+
const bytes = typeof body === "string" ? ETAG_ENCODER.encode(body) : body;
|
|
103
|
+
if (bytes.byteLength > maxBytes)
|
|
104
|
+
return undefined;
|
|
105
|
+
const finish = (fingerprint) => {
|
|
106
|
+
const tag = prefix + String.fromCharCode(34) + fingerprint + String.fromCharCode(34);
|
|
107
|
+
headers.set("etag", tag);
|
|
108
|
+
if (matchesIfNoneMatch(req.header("if-none-match"), tag)) {
|
|
109
|
+
// A 304 carries no body - drop the body-describing headers so strict intermediaries
|
|
110
|
+
// do not see a null body with a non-zero Content-Length.
|
|
111
|
+
headers.delete("content-length");
|
|
112
|
+
headers.delete("content-type");
|
|
113
|
+
return { body: null, status: 304 };
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
};
|
|
117
|
+
if (weak)
|
|
118
|
+
return finish(fnv1a(bytes));
|
|
119
|
+
return sha256Tag(bytes).then(finish);
|
|
120
|
+
},
|
|
121
|
+
onResponseRaw(response, req) {
|
|
122
|
+
if (req.method !== "GET" || response.status !== 200 || response.body === null) {
|
|
123
|
+
return response;
|
|
124
|
+
}
|
|
125
|
+
const declared = response.headers.get("content-length");
|
|
126
|
+
if (declared !== null && /^(?:0|[1-9]\d*)$/.test(declared) && Number(declared) > maxBytes) {
|
|
127
|
+
return response;
|
|
128
|
+
}
|
|
129
|
+
return etagRawResponse(response, req, weak, prefix, maxBytes);
|
|
130
|
+
},
|
|
43
131
|
}));
|
|
44
132
|
}
|
|
133
|
+
async function etagRawResponse(response, req, weak, prefix, maxBytes) {
|
|
134
|
+
const read = await readBytesCapped(response, maxBytes);
|
|
135
|
+
if ("response" in read)
|
|
136
|
+
return read.response;
|
|
137
|
+
const fingerprint = weak ? fnv1a(read.bytes) : await sha256Tag(read.bytes);
|
|
138
|
+
const tag = prefix + String.fromCharCode(34) + fingerprint + String.fromCharCode(34);
|
|
139
|
+
const headers = new Headers(response.headers);
|
|
140
|
+
headers.set("etag", tag);
|
|
141
|
+
if (matchesIfNoneMatch(req.headers.get("if-none-match"), tag)) {
|
|
142
|
+
headers.delete("content-length");
|
|
143
|
+
headers.delete("content-type");
|
|
144
|
+
return new Response(null, { status: 304, headers });
|
|
145
|
+
}
|
|
146
|
+
// The body was consumed and rebuilt from bytes. Re-derive framing at the serving adapter
|
|
147
|
+
// instead of trusting a stale upstream declaration.
|
|
148
|
+
headers.delete("content-length");
|
|
149
|
+
return new Response(read.bytes, {
|
|
150
|
+
status: response.status,
|
|
151
|
+
statusText: response.statusText,
|
|
152
|
+
headers,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
45
155
|
function matchesIfNoneMatch(value, tag) {
|
|
46
156
|
if (value === null)
|
|
47
157
|
return false;
|
|
@@ -58,42 +168,4 @@ function matchesIfNoneMatch(value, tag) {
|
|
|
58
168
|
function weakComparableTag(tag) {
|
|
59
169
|
return tag.startsWith("W/") ? tag.slice(2) : tag;
|
|
60
170
|
}
|
|
61
|
-
function parseLength(value) {
|
|
62
|
-
if (value === null)
|
|
63
|
-
return undefined;
|
|
64
|
-
if (!/^(?:0|[1-9]\d*)$/.test(value))
|
|
65
|
-
return undefined;
|
|
66
|
-
return Number(value);
|
|
67
|
-
}
|
|
68
|
-
async function readBytesCapped(res, maxBytes) {
|
|
69
|
-
const body = res.clone().body;
|
|
70
|
-
if (body === null)
|
|
71
|
-
return new Uint8Array();
|
|
72
|
-
const reader = body.getReader();
|
|
73
|
-
const chunks = [];
|
|
74
|
-
let total = 0;
|
|
75
|
-
try {
|
|
76
|
-
for (;;) {
|
|
77
|
-
const { done, value } = await reader.read();
|
|
78
|
-
if (done) {
|
|
79
|
-
const out = new Uint8Array(total);
|
|
80
|
-
let offset = 0;
|
|
81
|
-
for (const chunk of chunks) {
|
|
82
|
-
out.set(chunk, offset);
|
|
83
|
-
offset += chunk.byteLength;
|
|
84
|
-
}
|
|
85
|
-
return out;
|
|
86
|
-
}
|
|
87
|
-
total += value.byteLength;
|
|
88
|
-
if (total > maxBytes) {
|
|
89
|
-
await reader.cancel();
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
chunks.push(value);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
171
|
//# sourceMappingURL=etag.js.map
|