@aetherframework/middleware 1.0.2 → 1.0.4
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 +0 -3
- package/docs/readme/README.md +14 -3
- package/docs/readme/README_zh.md +0 -3
- package/examples/advanced-server.js +122 -112
- package/examples/basic-server.js +322 -64
- package/index.js +9 -11
- package/package.json +1 -1
- package/src/core/AetherCompiler.js +117 -63
- package/src/core/AetherContext.js +221 -93
- package/src/core/AetherPipeline.js +261 -285
- package/src/core/AetherRouter.js +358 -256
- package/src/core/AetherStore.js +114 -67
- package/src/middleware/compression.js +165 -91
- package/src/middleware/json.js +180 -169
- package/src/middleware/rate-limit.js +76 -146
- package/src/middleware/security.js +33 -54
- package/src/middleware/session.js +89 -86
|
@@ -1,374 +1,350 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
2
|
* @license MIT
|
|
3
3
|
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
* @module @aetherframework/middleware/core/AetherPipeline
|
|
6
6
|
*/
|
|
7
|
+
|
|
7
8
|
import { EventEmitter } from "events";
|
|
8
9
|
import AetherContext from "./AetherContext.js";
|
|
9
10
|
|
|
11
|
+
// ==========================================
|
|
12
|
+
// [V8-OPT] PRE-ALLOCATED STATIC BUFFERS
|
|
13
|
+
// ==========================================
|
|
10
14
|
const STATIC_RESPONSES = new Map([
|
|
11
|
-
[200, Buffer.from(
|
|
12
|
-
[404, Buffer.from(
|
|
13
|
-
[500, Buffer.from(
|
|
15
|
+
[200, Buffer.from('{"status":"ok"}')],
|
|
16
|
+
[404, Buffer.from('{"error":"Not Found"}')],
|
|
17
|
+
[500, Buffer.from('{"error":"Internal Server Error"}')],
|
|
14
18
|
]);
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
// ==========================================
|
|
21
|
+
// [V8-OPT] O(1) HIGH-PERFORMANCE CACHE
|
|
22
|
+
// ==========================================
|
|
23
|
+
class FastCache {
|
|
24
|
+
constructor(maxSize = 2000) {
|
|
25
|
+
this.maxSize = maxSize;
|
|
26
|
+
this.cache = new Map();
|
|
27
|
+
}
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
totalRequests: 0,
|
|
32
|
-
averageLatency: 0,
|
|
33
|
-
errorCount: 0,
|
|
34
|
-
cacheHits: 0,
|
|
35
|
-
cacheMisses: 0,
|
|
36
|
-
poolHits: 0,
|
|
37
|
-
poolMisses: 0,
|
|
38
|
-
};
|
|
29
|
+
get(key) {
|
|
30
|
+
return this.cache.get(key);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set(key, value) {
|
|
34
|
+
if (this.cache.size >= this.maxSize) {
|
|
35
|
+
const firstKey = this.cache.keys().next().value;
|
|
36
|
+
if (firstKey) this.cache.delete(firstKey);
|
|
37
|
+
}
|
|
38
|
+
this.cache.set(key, value);
|
|
39
|
+
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
clear() {
|
|
42
|
+
this.cache.clear();
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
get size() { return this.cache.size; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ==========================================
|
|
49
|
+
// [V8-OPT] LOCK-FREE CONTEXT POOL
|
|
50
|
+
// ==========================================
|
|
51
|
+
class ContextPool {
|
|
52
|
+
constructor(size = 8192) {
|
|
53
|
+
this.pool = new Array(size);
|
|
54
|
+
this.index = size;
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < size; i++) {
|
|
45
57
|
const ctx = new AetherContext(null, null);
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
ctx._inPool = true;
|
|
59
|
+
this.pool[i] = ctx;
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
get(req, res) {
|
|
64
|
+
let ctx;
|
|
65
|
+
if (this.index > 0) {
|
|
66
|
+
ctx = this.pool[--this.index];
|
|
67
|
+
} else {
|
|
68
|
+
ctx = new AetherContext(null, null);
|
|
69
|
+
ctx._inPool = false;
|
|
58
70
|
}
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
|
|
72
|
+
// [PERF] Call _reset WITH arguments when ACQUIRING
|
|
73
|
+
if (typeof ctx._reset === 'function') {
|
|
74
|
+
ctx._reset(req, res);
|
|
75
|
+
} else {
|
|
76
|
+
ctx.req = req;
|
|
77
|
+
ctx.res = res;
|
|
78
|
+
ctx.statusCode = 200;
|
|
79
|
+
ctx._terminated = false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
ctx._inPool = false;
|
|
83
|
+
return ctx;
|
|
61
84
|
}
|
|
62
85
|
|
|
63
|
-
|
|
64
|
-
if (!
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
release(ctx) {
|
|
87
|
+
if (!ctx || ctx._inPool) return;
|
|
88
|
+
|
|
89
|
+
if (this.index < this.pool.length) {
|
|
90
|
+
ctx.req = null;
|
|
91
|
+
ctx.res = null;
|
|
92
|
+
ctx._body = null;
|
|
93
|
+
ctx._queryCache = null;
|
|
94
|
+
ctx._ipCache = null;
|
|
95
|
+
ctx.statusCode = 200;
|
|
96
|
+
ctx._terminated = false;
|
|
97
|
+
ctx.params = null;
|
|
98
|
+
ctx.route = null;
|
|
99
|
+
|
|
100
|
+
// [V8-OPT] Clear headers without breaking Hidden Class
|
|
101
|
+
if (ctx._headersCount > 0) {
|
|
102
|
+
for (let i = 0; i < ctx._headersCount; i++) {
|
|
103
|
+
ctx._headersObj[ctx._headersKeys[i]] = undefined;
|
|
104
|
+
}
|
|
105
|
+
ctx._headersCount = 0;
|
|
106
|
+
} else if (ctx._headers) {
|
|
107
|
+
for (const key in ctx._headers) {
|
|
108
|
+
ctx._headers[key] = undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (ctx._stateObj) {
|
|
113
|
+
for (const key in ctx._stateObj) {
|
|
114
|
+
ctx._stateObj[key] = undefined;
|
|
115
|
+
}
|
|
76
116
|
}
|
|
77
117
|
|
|
78
|
-
|
|
79
|
-
|
|
118
|
+
ctx._inPool = true;
|
|
119
|
+
this.pool[this.index++] = ctx;
|
|
80
120
|
}
|
|
81
121
|
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const contextPool = new ContextPool(8192);
|
|
125
|
+
|
|
126
|
+
// ==========================================
|
|
127
|
+
// AETHER PIPELINE CORE - STATISTICS REMOVED
|
|
128
|
+
// ==========================================
|
|
129
|
+
class AetherPipeline extends EventEmitter {
|
|
130
|
+
constructor() {
|
|
131
|
+
super();
|
|
132
|
+
this.middlewares = [];
|
|
133
|
+
this.cache = new FastCache(2000);
|
|
134
|
+
this._compiledChain = null;
|
|
135
|
+
}
|
|
82
136
|
|
|
83
137
|
use(middleware) {
|
|
84
138
|
if (typeof middleware !== "function") {
|
|
85
139
|
throw new TypeError("Middleware must be a function");
|
|
86
140
|
}
|
|
87
|
-
this.
|
|
88
|
-
this.
|
|
89
|
-
this._compiledSync = null;
|
|
141
|
+
this.middlewares.push(middleware);
|
|
142
|
+
this._compiledChain = null;
|
|
90
143
|
return this;
|
|
91
144
|
}
|
|
92
145
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
compile() {
|
|
98
|
-
if (this._compiled) return this._compiled;
|
|
99
|
-
const middlewares = this._middlewares;
|
|
146
|
+
_compileChain() {
|
|
147
|
+
if (this._compiledChain) return this._compiledChain;
|
|
148
|
+
|
|
149
|
+
const middlewares = this.middlewares;
|
|
100
150
|
const len = middlewares.length;
|
|
101
|
-
|
|
102
|
-
|
|
151
|
+
|
|
152
|
+
if (len === 0) {
|
|
153
|
+
this._compiledChain = async (ctx) => {};
|
|
154
|
+
return this._compiledChain;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this._compiledChain = async function execute(ctx) {
|
|
103
158
|
async function dispatch(i) {
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
context.isTerminated() ||
|
|
107
|
-
(context._response && context._response.writableEnded)
|
|
108
|
-
) {
|
|
109
|
-
if (context._finalize) context._finalize();
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 2. Pipeline end: Safely trigger final _finalize
|
|
159
|
+
if (ctx._terminated || (ctx.res && ctx.res.writableEnded)) return;
|
|
114
160
|
if (i >= len) {
|
|
115
|
-
if (
|
|
161
|
+
if (typeof ctx._finalize === 'function') ctx._finalize();
|
|
116
162
|
return;
|
|
117
163
|
}
|
|
118
|
-
|
|
164
|
+
|
|
119
165
|
const mw = middlewares[i];
|
|
120
|
-
|
|
121
|
-
// 3. Strictly bind current middleware with next subsequent chain's asynchronous timing
|
|
122
|
-
await mw(context, function next() {
|
|
166
|
+
await mw(ctx, function next() {
|
|
123
167
|
return dispatch(i + 1);
|
|
124
168
|
});
|
|
125
169
|
}
|
|
126
|
-
|
|
127
|
-
// 🚀 Start the first middleware and strictly wait for the entire chain lifecycle to end
|
|
170
|
+
|
|
128
171
|
await dispatch(0);
|
|
129
172
|
};
|
|
130
|
-
|
|
131
|
-
return this.
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
_compileSync() {
|
|
135
|
-
if (this._compiledSync) return this._compiledSync;
|
|
136
|
-
const middlewares = this._middlewares;
|
|
137
|
-
const len = middlewares.length;
|
|
138
|
-
|
|
139
|
-
const allSync = middlewares.every((mw) => {
|
|
140
|
-
const funcStr = mw.toString();
|
|
141
|
-
return (
|
|
142
|
-
!funcStr.includes("async ") &&
|
|
143
|
-
!funcStr.includes(".then") &&
|
|
144
|
-
!funcStr.includes("await ")
|
|
145
|
-
);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
if (!allSync) return null;
|
|
149
|
-
|
|
150
|
-
this._compiledSync = function executePipelineSync(context) {
|
|
151
|
-
for (let i = 0; i < len; i++) {
|
|
152
|
-
middlewares[i](context, () => {});
|
|
153
|
-
if (context.isTerminated() || context.res?.writableEnded) return;
|
|
154
|
-
}
|
|
155
|
-
if (context._finalize) context._finalize();
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
return this._compiledSync;
|
|
173
|
+
|
|
174
|
+
return this._compiledChain;
|
|
159
175
|
}
|
|
160
176
|
|
|
161
177
|
async handle(request, response) {
|
|
162
|
-
this._stats.totalRequests++;
|
|
163
178
|
const url = request.url;
|
|
164
179
|
const method = request.method;
|
|
165
180
|
|
|
166
|
-
// 1.
|
|
167
|
-
if (method ===
|
|
168
|
-
const
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
]);
|
|
180
|
-
response.end(STATIC_RESPONSES.get(200));
|
|
181
|
-
if (socket) socket.uncork();
|
|
182
|
-
return { cacheHit: true, static: true };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// 2. Cache route hit
|
|
186
|
-
let methodCache = this._cache.get(method);
|
|
187
|
-
if (!methodCache) {
|
|
188
|
-
methodCache = new Map();
|
|
189
|
-
this._cache.set(method, methodCache);
|
|
190
|
-
}
|
|
191
|
-
const cached = methodCache.get(url);
|
|
192
|
-
|
|
193
|
-
if (cached) {
|
|
194
|
-
if (this.enableMetrics) this._stats.cacheHits++;
|
|
195
|
-
const socket = response.socket;
|
|
196
|
-
if (socket) socket.cork();
|
|
197
|
-
response.writeHead(cached.status, cached.headers);
|
|
198
|
-
response.end(cached.buffer);
|
|
199
|
-
if (socket) socket.uncork();
|
|
200
|
-
return { cacheHit: true };
|
|
181
|
+
// 1. [V8-OPT] Ultra-fast cache check without statistics
|
|
182
|
+
if (method === 'GET') {
|
|
183
|
+
const cached = this.cache.get(url);
|
|
184
|
+
if (cached) {
|
|
185
|
+
const socket = response.socket;
|
|
186
|
+
if (socket && !socket.destroyed) {
|
|
187
|
+
socket.cork();
|
|
188
|
+
response.writeHead(cached.status, cached.headers);
|
|
189
|
+
response.end(cached.buffer);
|
|
190
|
+
socket.uncork();
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
201
194
|
}
|
|
195
|
+
|
|
202
196
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// 3. Context construction and core scheduling
|
|
206
|
-
const context = this._getContext(request, response);
|
|
197
|
+
// 2. Get context from pool
|
|
198
|
+
const ctx = contextPool.get(request, response);
|
|
207
199
|
|
|
208
200
|
try {
|
|
209
|
-
//
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
await this.compile()(context);
|
|
201
|
+
// 3. Execute middleware chain
|
|
202
|
+
const chain = this._compileChain();
|
|
203
|
+
await chain(ctx);
|
|
204
|
+
|
|
205
|
+
// 4. [CRITICAL] Cache GET responses BEFORE checking writableEnded
|
|
206
|
+
if (method === 'GET' && ctx.statusCode === 200 && ctx._body) {
|
|
207
|
+
this._cacheResponse(url, ctx);
|
|
217
208
|
}
|
|
218
209
|
|
|
219
|
-
//
|
|
220
|
-
if (
|
|
221
|
-
this.
|
|
210
|
+
// 5. Send response if not already sent by the middleware chain
|
|
211
|
+
if (!ctx._terminated && !response.headersSent) {
|
|
212
|
+
this._sendResponse(ctx);
|
|
222
213
|
}
|
|
223
214
|
|
|
224
|
-
this._returnContext(context);
|
|
225
|
-
return { cacheHit: false };
|
|
226
215
|
} catch (error) {
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
216
|
+
if (!response.headersSent) {
|
|
217
|
+
try {
|
|
218
|
+
const socket = response.socket;
|
|
219
|
+
if (socket && !socket.destroyed) {
|
|
220
|
+
socket.cork();
|
|
221
|
+
response.writeHead(500, [
|
|
222
|
+
"Content-Type", "application/json; charset=utf-8",
|
|
223
|
+
"Content-Length", String(STATIC_RESPONSES.get(500).length),
|
|
224
|
+
"Connection", "keep-alive"
|
|
225
|
+
]);
|
|
226
|
+
response.end(STATIC_RESPONSES.get(500));
|
|
227
|
+
socket.uncork();
|
|
228
|
+
}
|
|
229
|
+
} catch (e) {}
|
|
230
|
+
}
|
|
231
|
+
} finally {
|
|
232
|
+
contextPool.release(ctx);
|
|
241
233
|
}
|
|
242
234
|
}
|
|
243
235
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
236
|
+
_sendResponse(ctx) {
|
|
237
|
+
const response = ctx.res;
|
|
238
|
+
if (!response || response.headersSent) return;
|
|
239
|
+
|
|
240
|
+
const statusCode = ctx.statusCode || 200;
|
|
241
|
+
let body = ctx._body;
|
|
242
|
+
|
|
249
243
|
const rawHeaders = ["Connection", "keep-alive"];
|
|
250
244
|
const lowerKeys = new Set(["connection"]);
|
|
251
245
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (val !== undefined && key) {
|
|
260
|
-
const kLower = key.toLowerCase();
|
|
261
|
-
if (!lowerKeys.has(kLower)) {
|
|
262
|
-
rawHeaders.push(key, String(val));
|
|
263
|
-
lowerKeys.add(kLower);
|
|
264
|
-
}
|
|
246
|
+
if (ctx._headersCount > 0) {
|
|
247
|
+
for (let i = 0; i < ctx._headersCount; i++) {
|
|
248
|
+
const key = ctx._headersKeys[i];
|
|
249
|
+
const val = ctx._headersObj[key];
|
|
250
|
+
if (val !== undefined) {
|
|
251
|
+
rawHeaders.push(key, String(val));
|
|
252
|
+
lowerKeys.add(key.toLowerCase());
|
|
265
253
|
}
|
|
266
254
|
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
context?._headers,
|
|
274
|
-
context?.headers,
|
|
275
|
-
context?.res?.headers,
|
|
276
|
-
];
|
|
277
|
-
for (const dict of potentialDicts) {
|
|
278
|
-
if (dict && typeof dict === "object" && !(dict instanceof Set)) {
|
|
279
|
-
for (const key in dict) {
|
|
280
|
-
if (Object.prototype.hasOwnProperty.call(dict, key)) {
|
|
281
|
-
const kLower = key.toLowerCase();
|
|
282
|
-
if (!lowerKeys.has(kLower) && dict[key] !== undefined) {
|
|
283
|
-
rawHeaders.push(key, String(dict[key]));
|
|
284
|
-
lowerKeys.add(kLower);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
255
|
+
} else if (ctx._headers) {
|
|
256
|
+
for (const key in ctx._headers) {
|
|
257
|
+
const val = ctx._headers[key];
|
|
258
|
+
if (val !== undefined) {
|
|
259
|
+
rawHeaders.push(key, String(val));
|
|
260
|
+
lowerKeys.add(key.toLowerCase());
|
|
287
261
|
}
|
|
288
262
|
}
|
|
289
263
|
}
|
|
290
264
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
265
|
+
if (!lowerKeys.has("content-type")) {
|
|
266
|
+
rawHeaders.push("Content-Type", "application/json; charset=utf-8");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (body !== undefined && body !== null) {
|
|
270
|
+
const bodyStr = typeof body === 'string' ? body :
|
|
271
|
+
Buffer.isBuffer(body) ? body :
|
|
272
|
+
JSON.stringify(body);
|
|
273
|
+
|
|
274
|
+
const bodyBuffer = Buffer.isBuffer(bodyStr) ? bodyStr : Buffer.from(bodyStr);
|
|
275
|
+
rawHeaders.push("Content-Length", String(bodyBuffer.length));
|
|
276
|
+
|
|
277
|
+
const socket = response.socket;
|
|
278
|
+
if (socket) socket.cork();
|
|
279
|
+
response.writeHead(statusCode, rawHeaders);
|
|
280
|
+
response.end(bodyBuffer);
|
|
281
|
+
if (socket) socket.uncork();
|
|
282
|
+
} else {
|
|
283
|
+
rawHeaders.push("Content-Length", "0");
|
|
284
|
+
const socket = response.socket;
|
|
285
|
+
if (socket) socket.cork();
|
|
286
|
+
response.writeHead(statusCode, rawHeaders);
|
|
287
|
+
response.end();
|
|
288
|
+
if (socket) socket.uncork();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
ctx._terminated = true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
_cacheResponse(url, ctx) {
|
|
295
|
+
let bodyBuffer;
|
|
296
|
+
const body = ctx._body;
|
|
297
|
+
|
|
298
|
+
if (Buffer.isBuffer(body)) {
|
|
299
|
+
bodyBuffer = body;
|
|
300
|
+
} else if (typeof body === 'string') {
|
|
301
|
+
bodyBuffer = Buffer.from(body);
|
|
302
|
+
} else {
|
|
303
|
+
bodyBuffer = Buffer.from(JSON.stringify(body));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const rawHeaders = ["Connection", "keep-alive"];
|
|
307
|
+
const lowerKeys = new Set(["connection"]);
|
|
308
|
+
|
|
309
|
+
if (ctx._headersCount > 0) {
|
|
310
|
+
for (let i = 0; i < ctx._headersCount; i++) {
|
|
311
|
+
const key = ctx._headersKeys[i];
|
|
312
|
+
const val = ctx._headersObj[key];
|
|
313
|
+
if (val !== undefined) {
|
|
314
|
+
rawHeaders.push(key, String(val));
|
|
315
|
+
lowerKeys.add(key.toLowerCase());
|
|
310
316
|
}
|
|
311
317
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const kLower = key.toLowerCase();
|
|
319
|
-
if (!lowerKeys.has(kLower)) {
|
|
320
|
-
const val = response.getHeader(key);
|
|
321
|
-
if (val !== undefined && val !== null) {
|
|
322
|
-
rawHeaders.push(
|
|
323
|
-
key,
|
|
324
|
-
Array.isArray(val) ? val.join(", ") : String(val),
|
|
325
|
-
);
|
|
326
|
-
lowerKeys.add(kLower);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
318
|
+
} else if (ctx._headers) {
|
|
319
|
+
for (const key in ctx._headers) {
|
|
320
|
+
const val = ctx._headers[key];
|
|
321
|
+
if (val !== undefined) {
|
|
322
|
+
rawHeaders.push(key, String(val));
|
|
323
|
+
lowerKeys.add(key.toLowerCase());
|
|
330
324
|
}
|
|
331
325
|
}
|
|
332
|
-
|
|
333
|
-
// 🟢 Removed response._headers detection code that would cause high-version Node.js crashes and deprecation warnings
|
|
334
326
|
}
|
|
335
327
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
// ==========================================
|
|
339
|
-
let body = context._body || "";
|
|
340
|
-
const buffer = Buffer.isBuffer(body)
|
|
341
|
-
? body
|
|
342
|
-
: Buffer.from(typeof body === "string" ? body : JSON.stringify(body));
|
|
343
|
-
|
|
344
|
-
// Complete length header
|
|
345
|
-
if (!lowerKeys.has("content-length")) {
|
|
346
|
-
rawHeaders.push("Content-Length", String(buffer.length));
|
|
328
|
+
if (!lowerKeys.has("content-type")) {
|
|
329
|
+
rawHeaders.push("Content-Type", "application/json; charset=utf-8");
|
|
347
330
|
}
|
|
331
|
+
rawHeaders.push("Content-Length", String(bodyBuffer.length));
|
|
348
332
|
|
|
349
|
-
|
|
333
|
+
this.cache.set(url, {
|
|
350
334
|
headers: rawHeaders,
|
|
351
|
-
status:
|
|
352
|
-
buffer
|
|
353
|
-
timestamp: Date.now(),
|
|
335
|
+
status: ctx.statusCode || 200,
|
|
336
|
+
buffer: bodyBuffer
|
|
354
337
|
});
|
|
355
|
-
|
|
356
|
-
if (methodCache.size > this._cacheMaxSize) {
|
|
357
|
-
const firstKey = methodCache.keys().next().value;
|
|
358
|
-
methodCache.delete(firstKey);
|
|
359
|
-
}
|
|
360
338
|
}
|
|
361
339
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
340
|
+
|
|
341
|
+
|
|
365
342
|
clearCache() {
|
|
366
|
-
this.
|
|
343
|
+
this.cache.clear();
|
|
367
344
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
this.
|
|
371
|
-
return this;
|
|
345
|
+
|
|
346
|
+
useRouter(router) {
|
|
347
|
+
return this.use(router.middleware());
|
|
372
348
|
}
|
|
373
349
|
}
|
|
374
350
|
|