@hono/node-server 1.19.10 → 2.0.0-rc.1
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 +1 -7
- package/dist/conninfo.d.mts +4 -3
- package/dist/conninfo.d.ts +4 -3
- package/dist/conninfo.js +20 -40
- package/dist/conninfo.mjs +19 -16
- package/dist/constants-B7DBcQew.js +11 -0
- package/dist/constants-DEKKqoym.mjs +5 -0
- package/dist/index.d.mts +62 -8
- package/dist/index.d.ts +62 -8
- package/dist/index.js +28 -613
- package/dist/index.mjs +24 -573
- package/dist/listener-Brd4yZ5d.js +726 -0
- package/dist/listener-RIBxK9_x.mjs +715 -0
- package/dist/serve-static.d.mts +14 -13
- package/dist/serve-static.d.ts +14 -13
- package/dist/serve-static.js +128 -170
- package/dist/serve-static.mjs +127 -145
- package/dist/utils/response.d.mts +3 -2
- package/dist/utils/response.d.ts +3 -2
- package/dist/utils/response.js +6 -35
- package/dist/utils/response.mjs +6 -9
- package/dist/vercel.d.mts +6 -5
- package/dist/vercel.d.ts +6 -5
- package/dist/vercel.js +7 -585
- package/dist/vercel.mjs +6 -548
- package/package.json +11 -13
- package/dist/globals.d.mts +0 -2
- package/dist/globals.d.ts +0 -2
- package/dist/globals.js +0 -29
- package/dist/globals.mjs +0 -5
- package/dist/listener.d.mts +0 -13
- package/dist/listener.d.ts +0 -13
- package/dist/listener.js +0 -581
- package/dist/listener.mjs +0 -546
- package/dist/request.d.mts +0 -25
- package/dist/request.d.ts +0 -25
- package/dist/request.js +0 -227
- package/dist/request.mjs +0 -195
- package/dist/response.d.mts +0 -26
- package/dist/response.d.ts +0 -26
- package/dist/response.js +0 -99
- package/dist/response.mjs +0 -72
- package/dist/server.d.mts +0 -10
- package/dist/server.d.ts +0 -10
- package/dist/server.js +0 -607
- package/dist/server.mjs +0 -571
- package/dist/types.d.mts +0 -44
- package/dist/types.d.ts +0 -44
- package/dist/types.js +0 -18
- package/dist/types.mjs +0 -0
- package/dist/utils/response/constants.d.mts +0 -3
- package/dist/utils/response/constants.d.ts +0 -3
- package/dist/utils/response/constants.js +0 -30
- package/dist/utils/response/constants.mjs +0 -5
- package/dist/utils.d.mts +0 -9
- package/dist/utils.d.ts +0 -9
- package/dist/utils.js +0 -99
- package/dist/utils.mjs +0 -71
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
import { t as X_ALREADY_SENT } from "./constants-DEKKqoym.mjs";
|
|
2
|
+
import { Http2ServerRequest } from "node:http2";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
|
|
5
|
+
//#region src/error.ts
|
|
6
|
+
var RequestError = class extends Error {
|
|
7
|
+
constructor(message, options) {
|
|
8
|
+
super(message, options);
|
|
9
|
+
this.name = "RequestError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/url.ts
|
|
15
|
+
const isPathDelimiter = (charCode) => charCode === 47 || charCode === 63 || charCode === 35;
|
|
16
|
+
const hasDotSegment = (url, dotIndex) => {
|
|
17
|
+
if ((dotIndex === 0 ? 47 : url.charCodeAt(dotIndex - 1)) !== 47) return false;
|
|
18
|
+
const nextIndex = dotIndex + 1;
|
|
19
|
+
if (nextIndex === url.length) return true;
|
|
20
|
+
const next = url.charCodeAt(nextIndex);
|
|
21
|
+
if (isPathDelimiter(next)) return true;
|
|
22
|
+
if (next !== 46) return false;
|
|
23
|
+
const nextNextIndex = dotIndex + 2;
|
|
24
|
+
if (nextNextIndex === url.length) return true;
|
|
25
|
+
return isPathDelimiter(url.charCodeAt(nextNextIndex));
|
|
26
|
+
};
|
|
27
|
+
const allowedRequestUrlChar = new Uint8Array(128);
|
|
28
|
+
for (let c = 48; c <= 57; c++) allowedRequestUrlChar[c] = 1;
|
|
29
|
+
for (let c = 65; c <= 90; c++) allowedRequestUrlChar[c] = 1;
|
|
30
|
+
for (let c = 97; c <= 122; c++) allowedRequestUrlChar[c] = 1;
|
|
31
|
+
(() => {
|
|
32
|
+
const chars = "-./:?#[]@!$&'()*+,;=~_";
|
|
33
|
+
for (let i = 0; i < 22; i++) allowedRequestUrlChar[chars.charCodeAt(i)] = 1;
|
|
34
|
+
})();
|
|
35
|
+
const safeHostChar = new Uint8Array(128);
|
|
36
|
+
for (let c = 48; c <= 57; c++) safeHostChar[c] = 1;
|
|
37
|
+
for (let c = 97; c <= 122; c++) safeHostChar[c] = 1;
|
|
38
|
+
(() => {
|
|
39
|
+
const chars = ".-_:";
|
|
40
|
+
for (let i = 0; i < 4; i++) safeHostChar[chars.charCodeAt(i)] = 1;
|
|
41
|
+
})();
|
|
42
|
+
const buildUrl = (scheme, host, incomingUrl) => {
|
|
43
|
+
const url = `${scheme}://${host}${incomingUrl}`;
|
|
44
|
+
let needsHostValidationByURL = false;
|
|
45
|
+
for (let i = 0, len = host.length; i < len; i++) {
|
|
46
|
+
const c = host.charCodeAt(i);
|
|
47
|
+
if (c > 127 || safeHostChar[c] === 0) {
|
|
48
|
+
needsHostValidationByURL = true;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
if (c === 58) {
|
|
52
|
+
i++;
|
|
53
|
+
const firstDigit = host.charCodeAt(i);
|
|
54
|
+
if (firstDigit < 49 || firstDigit > 57 || i + 4 > len || i + (firstDigit < 54 ? 5 : 4) < len) {
|
|
55
|
+
needsHostValidationByURL = true;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
for (; i < len; i++) {
|
|
59
|
+
const c = host.charCodeAt(i);
|
|
60
|
+
if (c < 48 || c > 57) {
|
|
61
|
+
needsHostValidationByURL = true;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (needsHostValidationByURL) {
|
|
68
|
+
const urlObj = new URL(url);
|
|
69
|
+
if (urlObj.hostname.length !== host.length && urlObj.hostname !== (host.includes(":") ? host.replace(/:\d+$/, "") : host).toLowerCase()) throw new RequestError("Invalid host header");
|
|
70
|
+
return urlObj.href;
|
|
71
|
+
} else if (incomingUrl.length === 0) return url + "/";
|
|
72
|
+
else {
|
|
73
|
+
if (incomingUrl.charCodeAt(0) !== 47) throw new RequestError("Invalid URL");
|
|
74
|
+
for (let i = 1, len = incomingUrl.length; i < len; i++) {
|
|
75
|
+
const c = incomingUrl.charCodeAt(i);
|
|
76
|
+
if (c > 127 || allowedRequestUrlChar[c] === 0 || c === 46 && hasDotSegment(incomingUrl, i)) return new URL(url).href;
|
|
77
|
+
}
|
|
78
|
+
return url;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/request.ts
|
|
84
|
+
const toRequestError = (e) => {
|
|
85
|
+
if (e instanceof RequestError) return e;
|
|
86
|
+
return new RequestError(e.message, { cause: e });
|
|
87
|
+
};
|
|
88
|
+
const GlobalRequest = global.Request;
|
|
89
|
+
var Request = class extends GlobalRequest {
|
|
90
|
+
constructor(input, options) {
|
|
91
|
+
if (typeof input === "object" && getRequestCache in input) {
|
|
92
|
+
const hasReplacementBody = options !== void 0 && "body" in options && options.body != null;
|
|
93
|
+
if (input[bodyConsumedDirectlyKey] && !hasReplacementBody) throw new TypeError("Cannot construct a Request with a Request object that has already been used.");
|
|
94
|
+
input = input[getRequestCache]();
|
|
95
|
+
}
|
|
96
|
+
if (typeof (options?.body)?.getReader !== "undefined") options.duplex ??= "half";
|
|
97
|
+
super(input, options);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const newHeadersFromIncoming = (incoming) => {
|
|
101
|
+
const headerRecord = [];
|
|
102
|
+
const rawHeaders = incoming.rawHeaders;
|
|
103
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
104
|
+
const { [i]: key, [i + 1]: value } = rawHeaders;
|
|
105
|
+
if (key.charCodeAt(0) !== 58) headerRecord.push([key, value]);
|
|
106
|
+
}
|
|
107
|
+
return new Headers(headerRecord);
|
|
108
|
+
};
|
|
109
|
+
const wrapBodyStream = Symbol("wrapBodyStream");
|
|
110
|
+
const newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
|
|
111
|
+
const init = {
|
|
112
|
+
method,
|
|
113
|
+
headers,
|
|
114
|
+
signal: abortController.signal
|
|
115
|
+
};
|
|
116
|
+
if (method === "TRACE") {
|
|
117
|
+
init.method = "GET";
|
|
118
|
+
const req = new Request(url, init);
|
|
119
|
+
Object.defineProperty(req, "method", { get() {
|
|
120
|
+
return "TRACE";
|
|
121
|
+
} });
|
|
122
|
+
return req;
|
|
123
|
+
}
|
|
124
|
+
if (!(method === "GET" || method === "HEAD")) if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) init.body = new ReadableStream({ start(controller) {
|
|
125
|
+
controller.enqueue(incoming.rawBody);
|
|
126
|
+
controller.close();
|
|
127
|
+
} });
|
|
128
|
+
else if (incoming[wrapBodyStream]) {
|
|
129
|
+
let reader;
|
|
130
|
+
init.body = new ReadableStream({ async pull(controller) {
|
|
131
|
+
try {
|
|
132
|
+
reader ||= Readable.toWeb(incoming).getReader();
|
|
133
|
+
const { done, value } = await reader.read();
|
|
134
|
+
if (done) controller.close();
|
|
135
|
+
else controller.enqueue(value);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
controller.error(error);
|
|
138
|
+
}
|
|
139
|
+
} });
|
|
140
|
+
} else init.body = Readable.toWeb(incoming);
|
|
141
|
+
return new Request(url, init);
|
|
142
|
+
};
|
|
143
|
+
const getRequestCache = Symbol("getRequestCache");
|
|
144
|
+
const requestCache = Symbol("requestCache");
|
|
145
|
+
const incomingKey = Symbol("incomingKey");
|
|
146
|
+
const urlKey = Symbol("urlKey");
|
|
147
|
+
const methodKey = Symbol("methodKey");
|
|
148
|
+
const headersKey = Symbol("headersKey");
|
|
149
|
+
const abortControllerKey = Symbol("abortControllerKey");
|
|
150
|
+
const getAbortController = Symbol("getAbortController");
|
|
151
|
+
const abortRequest = Symbol("abortRequest");
|
|
152
|
+
const bodyBufferKey = Symbol("bodyBuffer");
|
|
153
|
+
const bodyReadPromiseKey = Symbol("bodyReadPromise");
|
|
154
|
+
const bodyConsumedDirectlyKey = Symbol("bodyConsumedDirectly");
|
|
155
|
+
const bodyLockReaderKey = Symbol("bodyLockReader");
|
|
156
|
+
const abortReasonKey = Symbol("abortReason");
|
|
157
|
+
const newBodyUnusableError = () => {
|
|
158
|
+
return /* @__PURE__ */ new TypeError("Body is unusable");
|
|
159
|
+
};
|
|
160
|
+
const rejectBodyUnusable = () => {
|
|
161
|
+
return Promise.reject(newBodyUnusableError());
|
|
162
|
+
};
|
|
163
|
+
const textDecoder = new TextDecoder();
|
|
164
|
+
const consumeBodyDirectOnce = (request) => {
|
|
165
|
+
if (request[bodyConsumedDirectlyKey]) return rejectBodyUnusable();
|
|
166
|
+
request[bodyConsumedDirectlyKey] = true;
|
|
167
|
+
};
|
|
168
|
+
const toArrayBuffer = (buf) => {
|
|
169
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
170
|
+
};
|
|
171
|
+
const contentType = (request) => {
|
|
172
|
+
return (request[headersKey] ||= newHeadersFromIncoming(request[incomingKey])).get("content-type") || "";
|
|
173
|
+
};
|
|
174
|
+
const methodTokenRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
175
|
+
const normalizeIncomingMethod = (method) => {
|
|
176
|
+
if (typeof method !== "string" || method.length === 0) return "GET";
|
|
177
|
+
switch (method) {
|
|
178
|
+
case "DELETE":
|
|
179
|
+
case "GET":
|
|
180
|
+
case "HEAD":
|
|
181
|
+
case "OPTIONS":
|
|
182
|
+
case "POST":
|
|
183
|
+
case "PUT": return method;
|
|
184
|
+
}
|
|
185
|
+
const upper = method.toUpperCase();
|
|
186
|
+
switch (upper) {
|
|
187
|
+
case "DELETE":
|
|
188
|
+
case "GET":
|
|
189
|
+
case "HEAD":
|
|
190
|
+
case "OPTIONS":
|
|
191
|
+
case "POST":
|
|
192
|
+
case "PUT": return upper;
|
|
193
|
+
default: return method;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const validateDirectReadMethod = (method) => {
|
|
197
|
+
if (!methodTokenRegExp.test(method)) return /* @__PURE__ */ new TypeError(`'${method}' is not a valid HTTP method.`);
|
|
198
|
+
const normalized = method.toUpperCase();
|
|
199
|
+
if (normalized === "CONNECT" || normalized === "TRACK" || normalized === "TRACE" && method !== "TRACE") return /* @__PURE__ */ new TypeError(`'${method}' HTTP method is unsupported.`);
|
|
200
|
+
};
|
|
201
|
+
const readBodyWithFastPath = (request, method, fromBuffer) => {
|
|
202
|
+
if (request[bodyConsumedDirectlyKey]) return rejectBodyUnusable();
|
|
203
|
+
const methodName = request.method;
|
|
204
|
+
if (methodName === "GET" || methodName === "HEAD") return request[getRequestCache]()[method]();
|
|
205
|
+
const methodValidationError = validateDirectReadMethod(methodName);
|
|
206
|
+
if (methodValidationError) return Promise.reject(methodValidationError);
|
|
207
|
+
if (request[requestCache]) {
|
|
208
|
+
if (methodName !== "TRACE") return request[requestCache][method]();
|
|
209
|
+
}
|
|
210
|
+
const alreadyUsedError = consumeBodyDirectOnce(request);
|
|
211
|
+
if (alreadyUsedError) return alreadyUsedError;
|
|
212
|
+
const raw = readRawBodyIfAvailable(request);
|
|
213
|
+
if (raw) {
|
|
214
|
+
const result = Promise.resolve(fromBuffer(raw, request));
|
|
215
|
+
request[bodyBufferKey] = void 0;
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
return readBodyDirect(request).then((buf) => {
|
|
219
|
+
const result = fromBuffer(buf, request);
|
|
220
|
+
request[bodyBufferKey] = void 0;
|
|
221
|
+
return result;
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
const readRawBodyIfAvailable = (request) => {
|
|
225
|
+
const incoming = request[incomingKey];
|
|
226
|
+
if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) return incoming.rawBody;
|
|
227
|
+
};
|
|
228
|
+
const readBodyDirect = (request) => {
|
|
229
|
+
if (request[bodyBufferKey]) return Promise.resolve(request[bodyBufferKey]);
|
|
230
|
+
if (request[bodyReadPromiseKey]) return request[bodyReadPromiseKey];
|
|
231
|
+
const incoming = request[incomingKey];
|
|
232
|
+
if (Readable.isDisturbed(incoming)) return rejectBodyUnusable();
|
|
233
|
+
const promise = new Promise((resolve, reject) => {
|
|
234
|
+
const chunks = [];
|
|
235
|
+
let settled = false;
|
|
236
|
+
const finish = (callback) => {
|
|
237
|
+
if (settled) return;
|
|
238
|
+
settled = true;
|
|
239
|
+
cleanup();
|
|
240
|
+
callback();
|
|
241
|
+
};
|
|
242
|
+
const onData = (chunk) => {
|
|
243
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
244
|
+
};
|
|
245
|
+
const onEnd = () => {
|
|
246
|
+
finish(() => {
|
|
247
|
+
const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks);
|
|
248
|
+
request[bodyBufferKey] = buffer;
|
|
249
|
+
resolve(buffer);
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
const onError = (error) => {
|
|
253
|
+
finish(() => {
|
|
254
|
+
reject(error);
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
const onClose = () => {
|
|
258
|
+
if (incoming.readableEnded) {
|
|
259
|
+
onEnd();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
finish(() => {
|
|
263
|
+
if (incoming.errored) {
|
|
264
|
+
reject(incoming.errored);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const reason = request[abortReasonKey];
|
|
268
|
+
if (reason !== void 0) {
|
|
269
|
+
reject(reason instanceof Error ? reason : new Error(String(reason)));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
reject(/* @__PURE__ */ new Error("Client connection prematurely closed."));
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
const cleanup = () => {
|
|
276
|
+
incoming.off("data", onData);
|
|
277
|
+
incoming.off("end", onEnd);
|
|
278
|
+
incoming.off("error", onError);
|
|
279
|
+
incoming.off("close", onClose);
|
|
280
|
+
request[bodyReadPromiseKey] = void 0;
|
|
281
|
+
};
|
|
282
|
+
incoming.on("data", onData);
|
|
283
|
+
incoming.on("end", onEnd);
|
|
284
|
+
incoming.on("error", onError);
|
|
285
|
+
incoming.on("close", onClose);
|
|
286
|
+
queueMicrotask(() => {
|
|
287
|
+
if (settled) return;
|
|
288
|
+
if (incoming.readableEnded) onEnd();
|
|
289
|
+
else if (incoming.errored) onError(incoming.errored);
|
|
290
|
+
else if (incoming.destroyed) onClose();
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
request[bodyReadPromiseKey] = promise;
|
|
294
|
+
return promise;
|
|
295
|
+
};
|
|
296
|
+
const requestPrototype = {
|
|
297
|
+
get method() {
|
|
298
|
+
return this[methodKey];
|
|
299
|
+
},
|
|
300
|
+
get url() {
|
|
301
|
+
return this[urlKey];
|
|
302
|
+
},
|
|
303
|
+
get headers() {
|
|
304
|
+
return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
|
|
305
|
+
},
|
|
306
|
+
[abortRequest](reason) {
|
|
307
|
+
if (this[abortReasonKey] === void 0) this[abortReasonKey] = reason;
|
|
308
|
+
const abortController = this[abortControllerKey];
|
|
309
|
+
if (abortController && !abortController.signal.aborted) abortController.abort(reason);
|
|
310
|
+
},
|
|
311
|
+
[getAbortController]() {
|
|
312
|
+
this[abortControllerKey] ||= new AbortController();
|
|
313
|
+
if (this[abortReasonKey] !== void 0 && !this[abortControllerKey].signal.aborted) this[abortControllerKey].abort(this[abortReasonKey]);
|
|
314
|
+
return this[abortControllerKey];
|
|
315
|
+
},
|
|
316
|
+
[getRequestCache]() {
|
|
317
|
+
const abortController = this[getAbortController]();
|
|
318
|
+
if (this[requestCache]) return this[requestCache];
|
|
319
|
+
const method = this.method;
|
|
320
|
+
if (this[bodyConsumedDirectlyKey] && !(method === "GET" || method === "HEAD")) {
|
|
321
|
+
this[bodyBufferKey] = void 0;
|
|
322
|
+
const init = {
|
|
323
|
+
method: method === "TRACE" ? "GET" : method,
|
|
324
|
+
headers: this.headers,
|
|
325
|
+
signal: abortController.signal
|
|
326
|
+
};
|
|
327
|
+
if (method !== "TRACE") {
|
|
328
|
+
init.body = new ReadableStream({ start(c) {
|
|
329
|
+
c.close();
|
|
330
|
+
} });
|
|
331
|
+
init.duplex = "half";
|
|
332
|
+
}
|
|
333
|
+
const req = new Request(this[urlKey], init);
|
|
334
|
+
if (method === "TRACE") Object.defineProperty(req, "method", { get() {
|
|
335
|
+
return "TRACE";
|
|
336
|
+
} });
|
|
337
|
+
return this[requestCache] = req;
|
|
338
|
+
}
|
|
339
|
+
return this[requestCache] = newRequestFromIncoming(this.method, this[urlKey], this.headers, this[incomingKey], abortController);
|
|
340
|
+
},
|
|
341
|
+
get body() {
|
|
342
|
+
if (!this[bodyConsumedDirectlyKey]) return this[getRequestCache]().body;
|
|
343
|
+
const request = this[getRequestCache]();
|
|
344
|
+
if (!this[bodyLockReaderKey] && request.body) this[bodyLockReaderKey] = request.body.getReader();
|
|
345
|
+
return request.body;
|
|
346
|
+
},
|
|
347
|
+
get bodyUsed() {
|
|
348
|
+
if (this[bodyConsumedDirectlyKey]) return true;
|
|
349
|
+
if (this[requestCache]) return this[requestCache].bodyUsed;
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
[
|
|
354
|
+
"cache",
|
|
355
|
+
"credentials",
|
|
356
|
+
"destination",
|
|
357
|
+
"integrity",
|
|
358
|
+
"mode",
|
|
359
|
+
"redirect",
|
|
360
|
+
"referrer",
|
|
361
|
+
"referrerPolicy",
|
|
362
|
+
"signal",
|
|
363
|
+
"keepalive"
|
|
364
|
+
].forEach((k) => {
|
|
365
|
+
Object.defineProperty(requestPrototype, k, { get() {
|
|
366
|
+
return this[getRequestCache]()[k];
|
|
367
|
+
} });
|
|
368
|
+
});
|
|
369
|
+
["clone", "formData"].forEach((k) => {
|
|
370
|
+
Object.defineProperty(requestPrototype, k, { value: function() {
|
|
371
|
+
if (this[bodyConsumedDirectlyKey]) {
|
|
372
|
+
if (k === "clone") throw newBodyUnusableError();
|
|
373
|
+
return rejectBodyUnusable();
|
|
374
|
+
}
|
|
375
|
+
return this[getRequestCache]()[k]();
|
|
376
|
+
} });
|
|
377
|
+
});
|
|
378
|
+
Object.defineProperty(requestPrototype, "text", { value: function() {
|
|
379
|
+
return readBodyWithFastPath(this, "text", (buf) => textDecoder.decode(buf));
|
|
380
|
+
} });
|
|
381
|
+
Object.defineProperty(requestPrototype, "arrayBuffer", { value: function() {
|
|
382
|
+
return readBodyWithFastPath(this, "arrayBuffer", (buf) => toArrayBuffer(buf));
|
|
383
|
+
} });
|
|
384
|
+
Object.defineProperty(requestPrototype, "blob", { value: function() {
|
|
385
|
+
return readBodyWithFastPath(this, "blob", (buf, request) => {
|
|
386
|
+
const type = contentType(request);
|
|
387
|
+
const init = type ? { headers: { "content-type": type } } : void 0;
|
|
388
|
+
return new Response(buf, init).blob();
|
|
389
|
+
});
|
|
390
|
+
} });
|
|
391
|
+
Object.defineProperty(requestPrototype, "json", { value: function() {
|
|
392
|
+
if (this[bodyConsumedDirectlyKey]) return rejectBodyUnusable();
|
|
393
|
+
return this.text().then(JSON.parse);
|
|
394
|
+
} });
|
|
395
|
+
Object.setPrototypeOf(requestPrototype, Request.prototype);
|
|
396
|
+
const newRequest = (incoming, defaultHostname) => {
|
|
397
|
+
const req = Object.create(requestPrototype);
|
|
398
|
+
req[incomingKey] = incoming;
|
|
399
|
+
req[methodKey] = normalizeIncomingMethod(incoming.method);
|
|
400
|
+
const incomingUrl = incoming.url || "";
|
|
401
|
+
if (incomingUrl[0] !== "/" && (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
|
|
402
|
+
if (incoming instanceof Http2ServerRequest) throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
|
|
403
|
+
try {
|
|
404
|
+
req[urlKey] = new URL(incomingUrl).href;
|
|
405
|
+
} catch (e) {
|
|
406
|
+
throw new RequestError("Invalid absolute URL", { cause: e });
|
|
407
|
+
}
|
|
408
|
+
return req;
|
|
409
|
+
}
|
|
410
|
+
const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
|
|
411
|
+
if (!host) throw new RequestError("Missing host header");
|
|
412
|
+
let scheme;
|
|
413
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
414
|
+
scheme = incoming.scheme;
|
|
415
|
+
if (!(scheme === "http" || scheme === "https")) throw new RequestError("Unsupported scheme");
|
|
416
|
+
} else scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
|
|
417
|
+
try {
|
|
418
|
+
req[urlKey] = buildUrl(scheme, host, incomingUrl);
|
|
419
|
+
} catch (e) {
|
|
420
|
+
if (e instanceof RequestError) throw e;
|
|
421
|
+
else throw new RequestError("Invalid URL", { cause: e });
|
|
422
|
+
}
|
|
423
|
+
return req;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region src/response.ts
|
|
428
|
+
const responseCache = Symbol("responseCache");
|
|
429
|
+
const getResponseCache = Symbol("getResponseCache");
|
|
430
|
+
const cacheKey = Symbol("cache");
|
|
431
|
+
const GlobalResponse = global.Response;
|
|
432
|
+
var Response$1 = class Response$1 {
|
|
433
|
+
#body;
|
|
434
|
+
#init;
|
|
435
|
+
[getResponseCache]() {
|
|
436
|
+
delete this[cacheKey];
|
|
437
|
+
return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
|
|
438
|
+
}
|
|
439
|
+
constructor(body, init) {
|
|
440
|
+
let headers;
|
|
441
|
+
this.#body = body;
|
|
442
|
+
if (init instanceof Response$1) {
|
|
443
|
+
const cachedGlobalResponse = init[responseCache];
|
|
444
|
+
if (cachedGlobalResponse) {
|
|
445
|
+
this.#init = cachedGlobalResponse;
|
|
446
|
+
this[getResponseCache]();
|
|
447
|
+
return;
|
|
448
|
+
} else {
|
|
449
|
+
this.#init = init.#init;
|
|
450
|
+
headers = new Headers(init.#init.headers);
|
|
451
|
+
}
|
|
452
|
+
} else this.#init = init;
|
|
453
|
+
if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) this[cacheKey] = [
|
|
454
|
+
init?.status || 200,
|
|
455
|
+
body,
|
|
456
|
+
headers || init?.headers
|
|
457
|
+
];
|
|
458
|
+
}
|
|
459
|
+
get headers() {
|
|
460
|
+
const cache = this[cacheKey];
|
|
461
|
+
if (cache) {
|
|
462
|
+
if (!(cache[2] instanceof Headers)) cache[2] = new Headers(cache[2] || { "content-type": "text/plain; charset=UTF-8" });
|
|
463
|
+
return cache[2];
|
|
464
|
+
}
|
|
465
|
+
return this[getResponseCache]().headers;
|
|
466
|
+
}
|
|
467
|
+
get status() {
|
|
468
|
+
return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
|
|
469
|
+
}
|
|
470
|
+
get ok() {
|
|
471
|
+
const status = this.status;
|
|
472
|
+
return status >= 200 && status < 300;
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
[
|
|
476
|
+
"body",
|
|
477
|
+
"bodyUsed",
|
|
478
|
+
"redirected",
|
|
479
|
+
"statusText",
|
|
480
|
+
"trailers",
|
|
481
|
+
"type",
|
|
482
|
+
"url"
|
|
483
|
+
].forEach((k) => {
|
|
484
|
+
Object.defineProperty(Response$1.prototype, k, { get() {
|
|
485
|
+
return this[getResponseCache]()[k];
|
|
486
|
+
} });
|
|
487
|
+
});
|
|
488
|
+
[
|
|
489
|
+
"arrayBuffer",
|
|
490
|
+
"blob",
|
|
491
|
+
"clone",
|
|
492
|
+
"formData",
|
|
493
|
+
"json",
|
|
494
|
+
"text"
|
|
495
|
+
].forEach((k) => {
|
|
496
|
+
Object.defineProperty(Response$1.prototype, k, { value: function() {
|
|
497
|
+
return this[getResponseCache]()[k]();
|
|
498
|
+
} });
|
|
499
|
+
});
|
|
500
|
+
Object.setPrototypeOf(Response$1, GlobalResponse);
|
|
501
|
+
Object.setPrototypeOf(Response$1.prototype, GlobalResponse.prototype);
|
|
502
|
+
|
|
503
|
+
//#endregion
|
|
504
|
+
//#region src/utils.ts
|
|
505
|
+
async function readWithoutBlocking(readPromise) {
|
|
506
|
+
return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(void 0))]);
|
|
507
|
+
}
|
|
508
|
+
function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
|
|
509
|
+
const cancel = (error) => {
|
|
510
|
+
reader.cancel(error).catch(() => {});
|
|
511
|
+
};
|
|
512
|
+
writable.on("close", cancel);
|
|
513
|
+
writable.on("error", cancel);
|
|
514
|
+
(currentReadPromise ?? reader.read()).then(flow, handleStreamError);
|
|
515
|
+
return reader.closed.finally(() => {
|
|
516
|
+
writable.off("close", cancel);
|
|
517
|
+
writable.off("error", cancel);
|
|
518
|
+
});
|
|
519
|
+
function handleStreamError(error) {
|
|
520
|
+
if (error) writable.destroy(error);
|
|
521
|
+
}
|
|
522
|
+
function onDrain() {
|
|
523
|
+
reader.read().then(flow, handleStreamError);
|
|
524
|
+
}
|
|
525
|
+
function flow({ done, value }) {
|
|
526
|
+
try {
|
|
527
|
+
if (done) writable.end();
|
|
528
|
+
else if (!writable.write(value)) writable.once("drain", onDrain);
|
|
529
|
+
else return reader.read().then(flow, handleStreamError);
|
|
530
|
+
} catch (e) {
|
|
531
|
+
handleStreamError(e);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function writeFromReadableStream(stream, writable) {
|
|
536
|
+
if (stream.locked) throw new TypeError("ReadableStream is locked.");
|
|
537
|
+
else if (writable.destroyed) return;
|
|
538
|
+
return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
|
|
539
|
+
}
|
|
540
|
+
const buildOutgoingHttpHeaders = (headers) => {
|
|
541
|
+
const res = {};
|
|
542
|
+
if (!(headers instanceof Headers)) headers = new Headers(headers ?? void 0);
|
|
543
|
+
if (headers.has("set-cookie")) {
|
|
544
|
+
const cookies = [];
|
|
545
|
+
for (const [k, v] of headers) if (k === "set-cookie") cookies.push(v);
|
|
546
|
+
else res[k] = v;
|
|
547
|
+
if (cookies.length > 0) res["set-cookie"] = cookies;
|
|
548
|
+
} else for (const [k, v] of headers) res[k] = v;
|
|
549
|
+
res["content-type"] ??= "text/plain; charset=UTF-8";
|
|
550
|
+
return res;
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region src/listener.ts
|
|
555
|
+
const outgoingEnded = Symbol("outgoingEnded");
|
|
556
|
+
const handleRequestError = () => new Response(null, { status: 400 });
|
|
557
|
+
const handleFetchError = (e) => new Response(null, { status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500 });
|
|
558
|
+
const handleResponseError = (e, outgoing) => {
|
|
559
|
+
const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
|
|
560
|
+
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") console.info("The user aborted a request.");
|
|
561
|
+
else {
|
|
562
|
+
console.error(e);
|
|
563
|
+
if (!outgoing.headersSent) outgoing.writeHead(500, { "Content-Type": "text/plain" });
|
|
564
|
+
outgoing.end(`Error: ${err.message}`);
|
|
565
|
+
outgoing.destroy(err);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
const flushHeaders = (outgoing) => {
|
|
569
|
+
if ("flushHeaders" in outgoing && outgoing.writable) outgoing.flushHeaders();
|
|
570
|
+
};
|
|
571
|
+
const responseViaCache = async (res, outgoing) => {
|
|
572
|
+
let [status, body, header] = res[cacheKey];
|
|
573
|
+
let hasContentLength = false;
|
|
574
|
+
if (!header) header = { "content-type": "text/plain; charset=UTF-8" };
|
|
575
|
+
else if (header instanceof Headers) {
|
|
576
|
+
hasContentLength = header.has("content-length");
|
|
577
|
+
header = buildOutgoingHttpHeaders(header);
|
|
578
|
+
} else if (Array.isArray(header)) {
|
|
579
|
+
const headerObj = new Headers(header);
|
|
580
|
+
hasContentLength = headerObj.has("content-length");
|
|
581
|
+
header = buildOutgoingHttpHeaders(headerObj);
|
|
582
|
+
} else for (const key in header) if (key.length === 14 && key.toLowerCase() === "content-length") {
|
|
583
|
+
hasContentLength = true;
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
if (!hasContentLength) {
|
|
587
|
+
if (typeof body === "string") header["Content-Length"] = Buffer.byteLength(body);
|
|
588
|
+
else if (body instanceof Uint8Array) header["Content-Length"] = body.byteLength;
|
|
589
|
+
else if (body instanceof Blob) header["Content-Length"] = body.size;
|
|
590
|
+
}
|
|
591
|
+
outgoing.writeHead(status, header);
|
|
592
|
+
if (typeof body === "string" || body instanceof Uint8Array) outgoing.end(body);
|
|
593
|
+
else if (body instanceof Blob) outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
594
|
+
else {
|
|
595
|
+
flushHeaders(outgoing);
|
|
596
|
+
await writeFromReadableStream(body, outgoing)?.catch((e) => handleResponseError(e, outgoing));
|
|
597
|
+
}
|
|
598
|
+
outgoing[outgoingEnded]?.();
|
|
599
|
+
};
|
|
600
|
+
const isPromise = (res) => typeof res.then === "function";
|
|
601
|
+
const responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
602
|
+
if (isPromise(res)) if (options.errorHandler) try {
|
|
603
|
+
res = await res;
|
|
604
|
+
} catch (err) {
|
|
605
|
+
const errRes = await options.errorHandler(err);
|
|
606
|
+
if (!errRes) return;
|
|
607
|
+
res = errRes;
|
|
608
|
+
}
|
|
609
|
+
else res = await res.catch(handleFetchError);
|
|
610
|
+
if (cacheKey in res) return responseViaCache(res, outgoing);
|
|
611
|
+
const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
|
|
612
|
+
if (res.body) {
|
|
613
|
+
const reader = res.body.getReader();
|
|
614
|
+
const values = [];
|
|
615
|
+
let done = false;
|
|
616
|
+
let currentReadPromise = void 0;
|
|
617
|
+
if (resHeaderRecord["transfer-encoding"] !== "chunked") {
|
|
618
|
+
let maxReadCount = 2;
|
|
619
|
+
for (let i = 0; i < maxReadCount; i++) {
|
|
620
|
+
currentReadPromise ||= reader.read();
|
|
621
|
+
const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
|
|
622
|
+
console.error(e);
|
|
623
|
+
done = true;
|
|
624
|
+
});
|
|
625
|
+
if (!chunk) {
|
|
626
|
+
if (i === 1) {
|
|
627
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
628
|
+
maxReadCount = 3;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
currentReadPromise = void 0;
|
|
634
|
+
if (chunk.value) values.push(chunk.value);
|
|
635
|
+
if (chunk.done) {
|
|
636
|
+
done = true;
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (done && !("content-length" in resHeaderRecord)) resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
|
|
641
|
+
}
|
|
642
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
643
|
+
values.forEach((value) => {
|
|
644
|
+
outgoing.write(value);
|
|
645
|
+
});
|
|
646
|
+
if (done) outgoing.end();
|
|
647
|
+
else {
|
|
648
|
+
if (values.length === 0) flushHeaders(outgoing);
|
|
649
|
+
await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
|
|
650
|
+
}
|
|
651
|
+
} else if (resHeaderRecord[X_ALREADY_SENT]) {} else {
|
|
652
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
653
|
+
outgoing.end();
|
|
654
|
+
}
|
|
655
|
+
outgoing[outgoingEnded]?.();
|
|
656
|
+
};
|
|
657
|
+
const getRequestListener = (fetchCallback, options = {}) => {
|
|
658
|
+
const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
|
|
659
|
+
if (options.overrideGlobalObjects !== false && global.Request !== Request) {
|
|
660
|
+
Object.defineProperty(global, "Request", { value: Request });
|
|
661
|
+
Object.defineProperty(global, "Response", { value: Response$1 });
|
|
662
|
+
}
|
|
663
|
+
return async (incoming, outgoing) => {
|
|
664
|
+
let res, req;
|
|
665
|
+
try {
|
|
666
|
+
req = newRequest(incoming, options.hostname);
|
|
667
|
+
let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
|
|
668
|
+
if (!incomingEnded) {
|
|
669
|
+
incoming[wrapBodyStream] = true;
|
|
670
|
+
incoming.on("end", () => {
|
|
671
|
+
incomingEnded = true;
|
|
672
|
+
});
|
|
673
|
+
if (incoming instanceof Http2ServerRequest) outgoing[outgoingEnded] = () => {
|
|
674
|
+
if (!incomingEnded) setTimeout(() => {
|
|
675
|
+
if (!incomingEnded) setTimeout(() => {
|
|
676
|
+
incoming.destroy();
|
|
677
|
+
outgoing.destroy();
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
outgoing.on("close", () => {
|
|
683
|
+
let abortReason;
|
|
684
|
+
if (incoming.errored) abortReason = incoming.errored.toString();
|
|
685
|
+
else if (!outgoing.writableFinished) abortReason = "Client connection prematurely closed.";
|
|
686
|
+
if (abortReason !== void 0) req[abortRequest](abortReason);
|
|
687
|
+
if (!incomingEnded) setTimeout(() => {
|
|
688
|
+
if (!incomingEnded) setTimeout(() => {
|
|
689
|
+
incoming.destroy();
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
res = fetchCallback(req, {
|
|
694
|
+
incoming,
|
|
695
|
+
outgoing
|
|
696
|
+
});
|
|
697
|
+
if (cacheKey in res) return responseViaCache(res, outgoing);
|
|
698
|
+
} catch (e) {
|
|
699
|
+
if (!res) if (options.errorHandler) {
|
|
700
|
+
res = await options.errorHandler(req ? e : toRequestError(e));
|
|
701
|
+
if (!res) return;
|
|
702
|
+
} else if (!req) res = handleRequestError();
|
|
703
|
+
else res = handleFetchError(e);
|
|
704
|
+
else return handleResponseError(e, outgoing);
|
|
705
|
+
}
|
|
706
|
+
try {
|
|
707
|
+
return await responseViaResponseObject(res, outgoing, options);
|
|
708
|
+
} catch (e) {
|
|
709
|
+
return handleResponseError(e, outgoing);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
//#endregion
|
|
715
|
+
export { RequestError as n, getRequestListener as t };
|