@hono/node-server 2.0.6 → 2.0.9
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 +2 -7
- package/dist/index.cjs +126 -15
- package/dist/index.mjs +126 -15
- package/dist/serve-static.cjs +54 -13
- package/dist/serve-static.mjs +54 -13
- package/package.json +17 -17
package/README.md
CHANGED
|
@@ -41,16 +41,11 @@ It works on Node.js versions greater than 20.x.
|
|
|
41
41
|
|
|
42
42
|
## Installation
|
|
43
43
|
|
|
44
|
-
You can install it from the npm registry
|
|
44
|
+
You can install it from the npm registry:
|
|
45
45
|
|
|
46
46
|
```sh
|
|
47
47
|
npm install @hono/node-server
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Or use `yarn`:
|
|
51
|
-
|
|
52
|
-
```sh
|
|
53
|
-
yarn add @hono/node-server
|
|
48
|
+
pnpm add @hono/node-server
|
|
54
49
|
```
|
|
55
50
|
|
|
56
51
|
## Usage
|
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,54 @@ const newHeadersFromIncoming = (incoming) => {
|
|
|
60
60
|
return new Headers(headerRecord);
|
|
61
61
|
};
|
|
62
62
|
const wrapBodyStream = Symbol("wrapBodyStream");
|
|
63
|
+
const byteExactEncodings = new Set([
|
|
64
|
+
"latin1",
|
|
65
|
+
"binary",
|
|
66
|
+
"hex",
|
|
67
|
+
"base64",
|
|
68
|
+
"base64url"
|
|
69
|
+
]);
|
|
70
|
+
const isByteExactEncoding = (encoding) => encoding === null || byteExactEncodings.has(encoding);
|
|
71
|
+
const bodyBufferedBeforeDisconnectKey = Symbol("bodyBufferedBeforeDisconnect");
|
|
72
|
+
const bodyBufferedLengthBeforeDisconnectKey = Symbol("bodyBufferedLengthBeforeDisconnect");
|
|
73
|
+
const toBufferChunk = (chunk, encoding) => Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding ?? "utf8");
|
|
74
|
+
const isRecoverableDisconnectedIncoming = (incoming) => !(incoming instanceof node_http2.Http2ServerRequest) && !!incoming.complete && !!incoming.readableAborted && typeof incoming.read === "function" && isByteExactEncoding(incoming.readableEncoding);
|
|
75
|
+
const recordBodyBufferedBeforeDisconnect = (incoming) => {
|
|
76
|
+
if (incoming.readableDidRead || !isRecoverableDisconnectedIncoming(incoming)) return;
|
|
77
|
+
const incomingWithRecovery = incoming;
|
|
78
|
+
incomingWithRecovery[bodyBufferedLengthBeforeDisconnectKey] ??= incoming.readableLength;
|
|
79
|
+
};
|
|
80
|
+
const readBodyBufferedBeforeDisconnect = (incoming, chunks) => {
|
|
81
|
+
if (incoming.readableDidRead && !chunks || !isRecoverableDisconnectedIncoming(incoming)) return;
|
|
82
|
+
const incomingWithRecovery = incoming;
|
|
83
|
+
if (incomingWithRecovery[bodyBufferedBeforeDisconnectKey] !== void 0) return incomingWithRecovery[bodyBufferedBeforeDisconnectKey];
|
|
84
|
+
let result;
|
|
85
|
+
const errored = incoming.errored;
|
|
86
|
+
if (errored && errored.code !== "ECONNRESET") result = errored;
|
|
87
|
+
else if (incomingWithRecovery[bodyBufferedLengthBeforeDisconnectKey] !== void 0 && incoming.readableLength !== incomingWithRecovery[bodyBufferedLengthBeforeDisconnectKey]) result = newBodyUnusableError();
|
|
88
|
+
else {
|
|
89
|
+
const bodyChunks = chunks ?? [];
|
|
90
|
+
const chunk = incoming.read();
|
|
91
|
+
if (chunk !== null) bodyChunks.push(toBufferChunk(chunk, incoming.readableEncoding));
|
|
92
|
+
const buffer = bodyChunks.length === 1 ? bodyChunks[0] : Buffer.concat(bodyChunks);
|
|
93
|
+
result = buffer;
|
|
94
|
+
const contentLength = incoming.headers["content-length"];
|
|
95
|
+
if (typeof contentLength === "string" && /^\d+$/.test(contentLength)) {
|
|
96
|
+
const expectedLength = Number(contentLength);
|
|
97
|
+
if (Number.isSafeInteger(expectedLength) && buffer.length !== expectedLength) result = newBodyUnusableError();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
incomingWithRecovery[bodyBufferedBeforeDisconnectKey] = result;
|
|
101
|
+
return result;
|
|
102
|
+
};
|
|
103
|
+
const enqueueBufferedBody = (controller, buffered) => {
|
|
104
|
+
if (buffered instanceof Error) {
|
|
105
|
+
controller.error(buffered);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (buffered.length > 0) controller.enqueue(buffered);
|
|
109
|
+
controller.close();
|
|
110
|
+
};
|
|
63
111
|
const newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
|
|
64
112
|
const init = {
|
|
65
113
|
method,
|
|
@@ -82,6 +130,13 @@ const newRequestFromIncoming = (method, url, headers, incoming, abortController)
|
|
|
82
130
|
let reader;
|
|
83
131
|
init.body = new ReadableStream({ async pull(controller) {
|
|
84
132
|
try {
|
|
133
|
+
if (!reader) {
|
|
134
|
+
const buffered = readBodyBufferedBeforeDisconnect(incoming);
|
|
135
|
+
if (buffered !== void 0) {
|
|
136
|
+
enqueueBufferedBody(controller, buffered);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
85
140
|
reader ||= node_stream.Readable.toWeb(incoming).getReader();
|
|
86
141
|
const { done, value } = await reader.read();
|
|
87
142
|
if (done) controller.close();
|
|
@@ -90,7 +145,13 @@ const newRequestFromIncoming = (method, url, headers, incoming, abortController)
|
|
|
90
145
|
controller.error(error);
|
|
91
146
|
}
|
|
92
147
|
} });
|
|
93
|
-
} else
|
|
148
|
+
} else {
|
|
149
|
+
const buffered = readBodyBufferedBeforeDisconnect(incoming);
|
|
150
|
+
if (buffered !== void 0) init.body = new ReadableStream({ start(controller) {
|
|
151
|
+
enqueueBufferedBody(controller, buffered);
|
|
152
|
+
} });
|
|
153
|
+
else init.body = node_stream.Readable.toWeb(incoming);
|
|
154
|
+
}
|
|
94
155
|
return new Request$1(url, init);
|
|
95
156
|
};
|
|
96
157
|
const getRequestCache = Symbol("getRequestCache");
|
|
@@ -178,11 +239,23 @@ const readRawBodyIfAvailable = (request) => {
|
|
|
178
239
|
const incoming = request[incomingKey];
|
|
179
240
|
if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) return incoming.rawBody;
|
|
180
241
|
};
|
|
242
|
+
const normalizeAbortError = (request, incoming) => {
|
|
243
|
+
if (incoming.errored) return incoming.errored;
|
|
244
|
+
const reason = request[abortReasonKey];
|
|
245
|
+
if (reason !== void 0) return reason instanceof Error ? reason : new Error(String(reason));
|
|
246
|
+
return /* @__PURE__ */ new Error("Client connection prematurely closed.");
|
|
247
|
+
};
|
|
181
248
|
const readBodyDirect = (request) => {
|
|
182
249
|
if (request[bodyBufferKey]) return Promise.resolve(request[bodyBufferKey]);
|
|
183
250
|
if (request[bodyReadPromiseKey]) return request[bodyReadPromiseKey];
|
|
184
251
|
const incoming = request[incomingKey];
|
|
185
|
-
if (
|
|
252
|
+
if (incoming.readableDidRead) return rejectBodyUnusable();
|
|
253
|
+
const buffered = readBodyBufferedBeforeDisconnect(incoming);
|
|
254
|
+
if (buffered !== void 0) {
|
|
255
|
+
if (buffered instanceof Error) return Promise.reject(buffered);
|
|
256
|
+
request[bodyBufferKey] = buffered;
|
|
257
|
+
return Promise.resolve(buffered);
|
|
258
|
+
}
|
|
186
259
|
const promise = new Promise((resolve, reject) => {
|
|
187
260
|
const chunks = [];
|
|
188
261
|
let settled = false;
|
|
@@ -192,8 +265,22 @@ const readBodyDirect = (request) => {
|
|
|
192
265
|
cleanup();
|
|
193
266
|
callback();
|
|
194
267
|
};
|
|
268
|
+
const recoverCompleteBodyAfterDisconnect = (error) => {
|
|
269
|
+
const streamError = incoming.errored ?? error;
|
|
270
|
+
if (!isRecoverableDisconnectedIncoming(incoming) || streamError && streamError.code !== "ECONNRESET") return false;
|
|
271
|
+
finish(() => {
|
|
272
|
+
const recovered = readBodyBufferedBeforeDisconnect(incoming, chunks);
|
|
273
|
+
if (recovered instanceof Error) reject(recovered);
|
|
274
|
+
else if (recovered === void 0) reject(error ?? normalizeAbortError(request, incoming));
|
|
275
|
+
else {
|
|
276
|
+
request[bodyBufferKey] = recovered;
|
|
277
|
+
resolve(recovered);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
return true;
|
|
281
|
+
};
|
|
195
282
|
const onData = (chunk) => {
|
|
196
|
-
chunks.push(
|
|
283
|
+
chunks.push(toBufferChunk(chunk, incoming.readableEncoding));
|
|
197
284
|
};
|
|
198
285
|
const onEnd = () => {
|
|
199
286
|
finish(() => {
|
|
@@ -203,6 +290,7 @@ const readBodyDirect = (request) => {
|
|
|
203
290
|
});
|
|
204
291
|
};
|
|
205
292
|
const onError = (error) => {
|
|
293
|
+
if (recoverCompleteBodyAfterDisconnect(error)) return;
|
|
206
294
|
finish(() => {
|
|
207
295
|
reject(error);
|
|
208
296
|
});
|
|
@@ -212,17 +300,9 @@ const readBodyDirect = (request) => {
|
|
|
212
300
|
onEnd();
|
|
213
301
|
return;
|
|
214
302
|
}
|
|
303
|
+
if (recoverCompleteBodyAfterDisconnect()) return;
|
|
215
304
|
finish(() => {
|
|
216
|
-
|
|
217
|
-
reject(incoming.errored);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const reason = request[abortReasonKey];
|
|
221
|
-
if (reason !== void 0) {
|
|
222
|
-
reject(reason instanceof Error ? reason : new Error(String(reason)));
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
reject(/* @__PURE__ */ new Error("Client connection prematurely closed."));
|
|
305
|
+
reject(normalizeAbortError(request, incoming));
|
|
226
306
|
});
|
|
227
307
|
};
|
|
228
308
|
const cleanup = () => {
|
|
@@ -617,8 +697,13 @@ const drainIncoming = (incoming) => {
|
|
|
617
697
|
incoming.resume();
|
|
618
698
|
};
|
|
619
699
|
const makeCloseHandler = (req, incoming, outgoing, needsBodyCleanup) => () => {
|
|
620
|
-
if (incoming.errored)
|
|
621
|
-
|
|
700
|
+
if (incoming.errored) {
|
|
701
|
+
recordBodyBufferedBeforeDisconnect(incoming);
|
|
702
|
+
req[abortRequest](incoming.errored.toString());
|
|
703
|
+
} else if (!outgoing.writableFinished) {
|
|
704
|
+
recordBodyBufferedBeforeDisconnect(incoming);
|
|
705
|
+
req[abortRequest]("Client connection prematurely closed.");
|
|
706
|
+
}
|
|
622
707
|
if (needsBodyCleanup && !incoming.readableEnded) setTimeout(() => {
|
|
623
708
|
if (!incoming.readableEnded) setTimeout(() => {
|
|
624
709
|
drainIncoming(incoming);
|
|
@@ -839,6 +924,32 @@ const CloseEvent = globalThis.CloseEvent ?? class extends Event {
|
|
|
839
924
|
return this.#eventInitDict.reason ?? "";
|
|
840
925
|
}
|
|
841
926
|
};
|
|
927
|
+
/**
|
|
928
|
+
* Node.js has no global `ErrorEvent`, unlike `Event`/`MessageEvent`/`CloseEvent`.
|
|
929
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
|
|
930
|
+
*/
|
|
931
|
+
const ErrorEvent = globalThis.ErrorEvent ?? class extends Event {
|
|
932
|
+
#eventInitDict;
|
|
933
|
+
constructor(type, eventInitDict = {}) {
|
|
934
|
+
super(type, eventInitDict);
|
|
935
|
+
this.#eventInitDict = eventInitDict;
|
|
936
|
+
}
|
|
937
|
+
get message() {
|
|
938
|
+
return this.#eventInitDict.message ?? "";
|
|
939
|
+
}
|
|
940
|
+
get filename() {
|
|
941
|
+
return this.#eventInitDict.filename ?? "";
|
|
942
|
+
}
|
|
943
|
+
get lineno() {
|
|
944
|
+
return this.#eventInitDict.lineno ?? 0;
|
|
945
|
+
}
|
|
946
|
+
get colno() {
|
|
947
|
+
return this.#eventInitDict.colno ?? 0;
|
|
948
|
+
}
|
|
949
|
+
get error() {
|
|
950
|
+
return this.#eventInitDict.error ?? null;
|
|
951
|
+
}
|
|
952
|
+
};
|
|
842
953
|
const generateConnectionSymbol = () => Symbol("connection");
|
|
843
954
|
const CONNECTION_SYMBOL_KEY = Symbol("CONNECTION_SYMBOL_KEY");
|
|
844
955
|
const WAIT_FOR_WEBSOCKET_SYMBOL = Symbol("WAIT_FOR_WEBSOCKET_SYMBOL");
|
package/dist/index.mjs
CHANGED
|
@@ -59,6 +59,54 @@ const newHeadersFromIncoming = (incoming) => {
|
|
|
59
59
|
return new Headers(headerRecord);
|
|
60
60
|
};
|
|
61
61
|
const wrapBodyStream = Symbol("wrapBodyStream");
|
|
62
|
+
const byteExactEncodings = new Set([
|
|
63
|
+
"latin1",
|
|
64
|
+
"binary",
|
|
65
|
+
"hex",
|
|
66
|
+
"base64",
|
|
67
|
+
"base64url"
|
|
68
|
+
]);
|
|
69
|
+
const isByteExactEncoding = (encoding) => encoding === null || byteExactEncodings.has(encoding);
|
|
70
|
+
const bodyBufferedBeforeDisconnectKey = Symbol("bodyBufferedBeforeDisconnect");
|
|
71
|
+
const bodyBufferedLengthBeforeDisconnectKey = Symbol("bodyBufferedLengthBeforeDisconnect");
|
|
72
|
+
const toBufferChunk = (chunk, encoding) => Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding ?? "utf8");
|
|
73
|
+
const isRecoverableDisconnectedIncoming = (incoming) => !(incoming instanceof Http2ServerRequest) && !!incoming.complete && !!incoming.readableAborted && typeof incoming.read === "function" && isByteExactEncoding(incoming.readableEncoding);
|
|
74
|
+
const recordBodyBufferedBeforeDisconnect = (incoming) => {
|
|
75
|
+
if (incoming.readableDidRead || !isRecoverableDisconnectedIncoming(incoming)) return;
|
|
76
|
+
const incomingWithRecovery = incoming;
|
|
77
|
+
incomingWithRecovery[bodyBufferedLengthBeforeDisconnectKey] ??= incoming.readableLength;
|
|
78
|
+
};
|
|
79
|
+
const readBodyBufferedBeforeDisconnect = (incoming, chunks) => {
|
|
80
|
+
if (incoming.readableDidRead && !chunks || !isRecoverableDisconnectedIncoming(incoming)) return;
|
|
81
|
+
const incomingWithRecovery = incoming;
|
|
82
|
+
if (incomingWithRecovery[bodyBufferedBeforeDisconnectKey] !== void 0) return incomingWithRecovery[bodyBufferedBeforeDisconnectKey];
|
|
83
|
+
let result;
|
|
84
|
+
const errored = incoming.errored;
|
|
85
|
+
if (errored && errored.code !== "ECONNRESET") result = errored;
|
|
86
|
+
else if (incomingWithRecovery[bodyBufferedLengthBeforeDisconnectKey] !== void 0 && incoming.readableLength !== incomingWithRecovery[bodyBufferedLengthBeforeDisconnectKey]) result = newBodyUnusableError();
|
|
87
|
+
else {
|
|
88
|
+
const bodyChunks = chunks ?? [];
|
|
89
|
+
const chunk = incoming.read();
|
|
90
|
+
if (chunk !== null) bodyChunks.push(toBufferChunk(chunk, incoming.readableEncoding));
|
|
91
|
+
const buffer = bodyChunks.length === 1 ? bodyChunks[0] : Buffer.concat(bodyChunks);
|
|
92
|
+
result = buffer;
|
|
93
|
+
const contentLength = incoming.headers["content-length"];
|
|
94
|
+
if (typeof contentLength === "string" && /^\d+$/.test(contentLength)) {
|
|
95
|
+
const expectedLength = Number(contentLength);
|
|
96
|
+
if (Number.isSafeInteger(expectedLength) && buffer.length !== expectedLength) result = newBodyUnusableError();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
incomingWithRecovery[bodyBufferedBeforeDisconnectKey] = result;
|
|
100
|
+
return result;
|
|
101
|
+
};
|
|
102
|
+
const enqueueBufferedBody = (controller, buffered) => {
|
|
103
|
+
if (buffered instanceof Error) {
|
|
104
|
+
controller.error(buffered);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (buffered.length > 0) controller.enqueue(buffered);
|
|
108
|
+
controller.close();
|
|
109
|
+
};
|
|
62
110
|
const newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
|
|
63
111
|
const init = {
|
|
64
112
|
method,
|
|
@@ -81,6 +129,13 @@ const newRequestFromIncoming = (method, url, headers, incoming, abortController)
|
|
|
81
129
|
let reader;
|
|
82
130
|
init.body = new ReadableStream({ async pull(controller) {
|
|
83
131
|
try {
|
|
132
|
+
if (!reader) {
|
|
133
|
+
const buffered = readBodyBufferedBeforeDisconnect(incoming);
|
|
134
|
+
if (buffered !== void 0) {
|
|
135
|
+
enqueueBufferedBody(controller, buffered);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
84
139
|
reader ||= Readable.toWeb(incoming).getReader();
|
|
85
140
|
const { done, value } = await reader.read();
|
|
86
141
|
if (done) controller.close();
|
|
@@ -89,7 +144,13 @@ const newRequestFromIncoming = (method, url, headers, incoming, abortController)
|
|
|
89
144
|
controller.error(error);
|
|
90
145
|
}
|
|
91
146
|
} });
|
|
92
|
-
} else
|
|
147
|
+
} else {
|
|
148
|
+
const buffered = readBodyBufferedBeforeDisconnect(incoming);
|
|
149
|
+
if (buffered !== void 0) init.body = new ReadableStream({ start(controller) {
|
|
150
|
+
enqueueBufferedBody(controller, buffered);
|
|
151
|
+
} });
|
|
152
|
+
else init.body = Readable.toWeb(incoming);
|
|
153
|
+
}
|
|
93
154
|
return new Request$1(url, init);
|
|
94
155
|
};
|
|
95
156
|
const getRequestCache = Symbol("getRequestCache");
|
|
@@ -177,11 +238,23 @@ const readRawBodyIfAvailable = (request) => {
|
|
|
177
238
|
const incoming = request[incomingKey];
|
|
178
239
|
if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) return incoming.rawBody;
|
|
179
240
|
};
|
|
241
|
+
const normalizeAbortError = (request, incoming) => {
|
|
242
|
+
if (incoming.errored) return incoming.errored;
|
|
243
|
+
const reason = request[abortReasonKey];
|
|
244
|
+
if (reason !== void 0) return reason instanceof Error ? reason : new Error(String(reason));
|
|
245
|
+
return /* @__PURE__ */ new Error("Client connection prematurely closed.");
|
|
246
|
+
};
|
|
180
247
|
const readBodyDirect = (request) => {
|
|
181
248
|
if (request[bodyBufferKey]) return Promise.resolve(request[bodyBufferKey]);
|
|
182
249
|
if (request[bodyReadPromiseKey]) return request[bodyReadPromiseKey];
|
|
183
250
|
const incoming = request[incomingKey];
|
|
184
|
-
if (
|
|
251
|
+
if (incoming.readableDidRead) return rejectBodyUnusable();
|
|
252
|
+
const buffered = readBodyBufferedBeforeDisconnect(incoming);
|
|
253
|
+
if (buffered !== void 0) {
|
|
254
|
+
if (buffered instanceof Error) return Promise.reject(buffered);
|
|
255
|
+
request[bodyBufferKey] = buffered;
|
|
256
|
+
return Promise.resolve(buffered);
|
|
257
|
+
}
|
|
185
258
|
const promise = new Promise((resolve, reject) => {
|
|
186
259
|
const chunks = [];
|
|
187
260
|
let settled = false;
|
|
@@ -191,8 +264,22 @@ const readBodyDirect = (request) => {
|
|
|
191
264
|
cleanup();
|
|
192
265
|
callback();
|
|
193
266
|
};
|
|
267
|
+
const recoverCompleteBodyAfterDisconnect = (error) => {
|
|
268
|
+
const streamError = incoming.errored ?? error;
|
|
269
|
+
if (!isRecoverableDisconnectedIncoming(incoming) || streamError && streamError.code !== "ECONNRESET") return false;
|
|
270
|
+
finish(() => {
|
|
271
|
+
const recovered = readBodyBufferedBeforeDisconnect(incoming, chunks);
|
|
272
|
+
if (recovered instanceof Error) reject(recovered);
|
|
273
|
+
else if (recovered === void 0) reject(error ?? normalizeAbortError(request, incoming));
|
|
274
|
+
else {
|
|
275
|
+
request[bodyBufferKey] = recovered;
|
|
276
|
+
resolve(recovered);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
return true;
|
|
280
|
+
};
|
|
194
281
|
const onData = (chunk) => {
|
|
195
|
-
chunks.push(
|
|
282
|
+
chunks.push(toBufferChunk(chunk, incoming.readableEncoding));
|
|
196
283
|
};
|
|
197
284
|
const onEnd = () => {
|
|
198
285
|
finish(() => {
|
|
@@ -202,6 +289,7 @@ const readBodyDirect = (request) => {
|
|
|
202
289
|
});
|
|
203
290
|
};
|
|
204
291
|
const onError = (error) => {
|
|
292
|
+
if (recoverCompleteBodyAfterDisconnect(error)) return;
|
|
205
293
|
finish(() => {
|
|
206
294
|
reject(error);
|
|
207
295
|
});
|
|
@@ -211,17 +299,9 @@ const readBodyDirect = (request) => {
|
|
|
211
299
|
onEnd();
|
|
212
300
|
return;
|
|
213
301
|
}
|
|
302
|
+
if (recoverCompleteBodyAfterDisconnect()) return;
|
|
214
303
|
finish(() => {
|
|
215
|
-
|
|
216
|
-
reject(incoming.errored);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
const reason = request[abortReasonKey];
|
|
220
|
-
if (reason !== void 0) {
|
|
221
|
-
reject(reason instanceof Error ? reason : new Error(String(reason)));
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
reject(/* @__PURE__ */ new Error("Client connection prematurely closed."));
|
|
304
|
+
reject(normalizeAbortError(request, incoming));
|
|
225
305
|
});
|
|
226
306
|
};
|
|
227
307
|
const cleanup = () => {
|
|
@@ -616,8 +696,13 @@ const drainIncoming = (incoming) => {
|
|
|
616
696
|
incoming.resume();
|
|
617
697
|
};
|
|
618
698
|
const makeCloseHandler = (req, incoming, outgoing, needsBodyCleanup) => () => {
|
|
619
|
-
if (incoming.errored)
|
|
620
|
-
|
|
699
|
+
if (incoming.errored) {
|
|
700
|
+
recordBodyBufferedBeforeDisconnect(incoming);
|
|
701
|
+
req[abortRequest](incoming.errored.toString());
|
|
702
|
+
} else if (!outgoing.writableFinished) {
|
|
703
|
+
recordBodyBufferedBeforeDisconnect(incoming);
|
|
704
|
+
req[abortRequest]("Client connection prematurely closed.");
|
|
705
|
+
}
|
|
621
706
|
if (needsBodyCleanup && !incoming.readableEnded) setTimeout(() => {
|
|
622
707
|
if (!incoming.readableEnded) setTimeout(() => {
|
|
623
708
|
drainIncoming(incoming);
|
|
@@ -838,6 +923,32 @@ const CloseEvent = globalThis.CloseEvent ?? class extends Event {
|
|
|
838
923
|
return this.#eventInitDict.reason ?? "";
|
|
839
924
|
}
|
|
840
925
|
};
|
|
926
|
+
/**
|
|
927
|
+
* Node.js has no global `ErrorEvent`, unlike `Event`/`MessageEvent`/`CloseEvent`.
|
|
928
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
|
|
929
|
+
*/
|
|
930
|
+
const ErrorEvent = globalThis.ErrorEvent ?? class extends Event {
|
|
931
|
+
#eventInitDict;
|
|
932
|
+
constructor(type, eventInitDict = {}) {
|
|
933
|
+
super(type, eventInitDict);
|
|
934
|
+
this.#eventInitDict = eventInitDict;
|
|
935
|
+
}
|
|
936
|
+
get message() {
|
|
937
|
+
return this.#eventInitDict.message ?? "";
|
|
938
|
+
}
|
|
939
|
+
get filename() {
|
|
940
|
+
return this.#eventInitDict.filename ?? "";
|
|
941
|
+
}
|
|
942
|
+
get lineno() {
|
|
943
|
+
return this.#eventInitDict.lineno ?? 0;
|
|
944
|
+
}
|
|
945
|
+
get colno() {
|
|
946
|
+
return this.#eventInitDict.colno ?? 0;
|
|
947
|
+
}
|
|
948
|
+
get error() {
|
|
949
|
+
return this.#eventInitDict.error ?? null;
|
|
950
|
+
}
|
|
951
|
+
};
|
|
841
952
|
const generateConnectionSymbol = () => Symbol("connection");
|
|
842
953
|
const CONNECTION_SYMBOL_KEY = Symbol("CONNECTION_SYMBOL_KEY");
|
|
843
954
|
const WAIT_FOR_WEBSOCKET_SYMBOL = Symbol("WAIT_FOR_WEBSOCKET_SYMBOL");
|
package/dist/serve-static.cjs
CHANGED
|
@@ -19,6 +19,41 @@ const getStats = (path) => {
|
|
|
19
19
|
} catch {}
|
|
20
20
|
return stats;
|
|
21
21
|
};
|
|
22
|
+
const BYTE_RANGE_PATTERN = /^(?:bytes=)?(?!-$)(\d*)-(\d*)$/;
|
|
23
|
+
const parseByteRange = (range) => {
|
|
24
|
+
const match = range.match(BYTE_RANGE_PATTERN);
|
|
25
|
+
if (!match) return;
|
|
26
|
+
const [, start, end] = match;
|
|
27
|
+
if (start === "") return {
|
|
28
|
+
type: "suffix",
|
|
29
|
+
length: Number(end)
|
|
30
|
+
};
|
|
31
|
+
if (end === "") return {
|
|
32
|
+
type: "open-ended",
|
|
33
|
+
start: Number(start)
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
type: "bounded",
|
|
37
|
+
start: Number(start),
|
|
38
|
+
end: Number(end)
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const resolveByteRange = (spec, size) => {
|
|
42
|
+
if (size === 0) return;
|
|
43
|
+
if (spec.type === "suffix") {
|
|
44
|
+
if (spec.length === 0) return;
|
|
45
|
+
return {
|
|
46
|
+
start: Math.max(size - spec.length, 0),
|
|
47
|
+
end: size - 1
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const end = spec.type === "bounded" ? Math.min(spec.end, size - 1) : size - 1;
|
|
51
|
+
if (spec.start >= size || spec.start > end) return;
|
|
52
|
+
return {
|
|
53
|
+
start: spec.start,
|
|
54
|
+
end
|
|
55
|
+
};
|
|
56
|
+
};
|
|
22
57
|
const tryDecode = (str, decoder) => {
|
|
23
58
|
try {
|
|
24
59
|
return decoder(str);
|
|
@@ -61,7 +96,7 @@ const serveStatic = (options = { root: "" }) => {
|
|
|
61
96
|
}
|
|
62
97
|
const mimeType = (0, hono_utils_mime.getMimeType)(path);
|
|
63
98
|
c.header("Content-Type", mimeType || "application/octet-stream");
|
|
64
|
-
if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
|
99
|
+
if (options.precompressed && (!mimeType || mimeType === "application/octet-stream" || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
|
65
100
|
const acceptEncodingSet = new Set(c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim()));
|
|
66
101
|
for (const encoding of ENCODINGS_ORDERED_KEYS) {
|
|
67
102
|
if (!acceptEncodingSet.has(encoding)) continue;
|
|
@@ -88,18 +123,24 @@ const serveStatic = (options = { root: "" }) => {
|
|
|
88
123
|
result = c.body(require_utils_stream.createStreamBody((0, node_fs.createReadStream)(path)), 200);
|
|
89
124
|
} else {
|
|
90
125
|
c.header("Accept-Ranges", "bytes");
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
126
|
+
const resolvedRange = resolveByteRange(parseByteRange(range) ?? {
|
|
127
|
+
type: "open-ended",
|
|
128
|
+
start: 0
|
|
129
|
+
}, size);
|
|
130
|
+
if (!resolvedRange) {
|
|
131
|
+
c.header("Content-Range", `bytes */${size}`);
|
|
132
|
+
result = c.body(null, 416);
|
|
133
|
+
} else {
|
|
134
|
+
const { start, end } = resolvedRange;
|
|
135
|
+
const chunkSize = end - start + 1;
|
|
136
|
+
const stream = (0, node_fs.createReadStream)(path, {
|
|
137
|
+
start,
|
|
138
|
+
end
|
|
139
|
+
});
|
|
140
|
+
c.header("Content-Length", chunkSize.toString());
|
|
141
|
+
c.header("Content-Range", `bytes ${start}-${end}/${size}`);
|
|
142
|
+
result = c.body(require_utils_stream.createStreamBody(stream), 206);
|
|
143
|
+
}
|
|
103
144
|
}
|
|
104
145
|
await options.onFound?.(path, c);
|
|
105
146
|
return result;
|
package/dist/serve-static.mjs
CHANGED
|
@@ -18,6 +18,41 @@ const getStats = (path) => {
|
|
|
18
18
|
} catch {}
|
|
19
19
|
return stats;
|
|
20
20
|
};
|
|
21
|
+
const BYTE_RANGE_PATTERN = /^(?:bytes=)?(?!-$)(\d*)-(\d*)$/;
|
|
22
|
+
const parseByteRange = (range) => {
|
|
23
|
+
const match = range.match(BYTE_RANGE_PATTERN);
|
|
24
|
+
if (!match) return;
|
|
25
|
+
const [, start, end] = match;
|
|
26
|
+
if (start === "") return {
|
|
27
|
+
type: "suffix",
|
|
28
|
+
length: Number(end)
|
|
29
|
+
};
|
|
30
|
+
if (end === "") return {
|
|
31
|
+
type: "open-ended",
|
|
32
|
+
start: Number(start)
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
type: "bounded",
|
|
36
|
+
start: Number(start),
|
|
37
|
+
end: Number(end)
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const resolveByteRange = (spec, size) => {
|
|
41
|
+
if (size === 0) return;
|
|
42
|
+
if (spec.type === "suffix") {
|
|
43
|
+
if (spec.length === 0) return;
|
|
44
|
+
return {
|
|
45
|
+
start: Math.max(size - spec.length, 0),
|
|
46
|
+
end: size - 1
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const end = spec.type === "bounded" ? Math.min(spec.end, size - 1) : size - 1;
|
|
50
|
+
if (spec.start >= size || spec.start > end) return;
|
|
51
|
+
return {
|
|
52
|
+
start: spec.start,
|
|
53
|
+
end
|
|
54
|
+
};
|
|
55
|
+
};
|
|
21
56
|
const tryDecode = (str, decoder) => {
|
|
22
57
|
try {
|
|
23
58
|
return decoder(str);
|
|
@@ -60,7 +95,7 @@ const serveStatic = (options = { root: "" }) => {
|
|
|
60
95
|
}
|
|
61
96
|
const mimeType = getMimeType(path);
|
|
62
97
|
c.header("Content-Type", mimeType || "application/octet-stream");
|
|
63
|
-
if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
|
98
|
+
if (options.precompressed && (!mimeType || mimeType === "application/octet-stream" || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
|
64
99
|
const acceptEncodingSet = new Set(c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim()));
|
|
65
100
|
for (const encoding of ENCODINGS_ORDERED_KEYS) {
|
|
66
101
|
if (!acceptEncodingSet.has(encoding)) continue;
|
|
@@ -87,18 +122,24 @@ const serveStatic = (options = { root: "" }) => {
|
|
|
87
122
|
result = c.body(createStreamBody(createReadStream(path)), 200);
|
|
88
123
|
} else {
|
|
89
124
|
c.header("Accept-Ranges", "bytes");
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
125
|
+
const resolvedRange = resolveByteRange(parseByteRange(range) ?? {
|
|
126
|
+
type: "open-ended",
|
|
127
|
+
start: 0
|
|
128
|
+
}, size);
|
|
129
|
+
if (!resolvedRange) {
|
|
130
|
+
c.header("Content-Range", `bytes */${size}`);
|
|
131
|
+
result = c.body(null, 416);
|
|
132
|
+
} else {
|
|
133
|
+
const { start, end } = resolvedRange;
|
|
134
|
+
const chunkSize = end - start + 1;
|
|
135
|
+
const stream = createReadStream(path, {
|
|
136
|
+
start,
|
|
137
|
+
end
|
|
138
|
+
});
|
|
139
|
+
c.header("Content-Length", chunkSize.toString());
|
|
140
|
+
c.header("Content-Range", `bytes ${start}-${end}/${size}`);
|
|
141
|
+
result = c.body(createStreamBody(stream), 206);
|
|
142
|
+
}
|
|
102
143
|
}
|
|
103
144
|
await options.onFound?.(path, c);
|
|
104
145
|
return result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hono/node-server",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
4
4
|
"description": "Node.js Adapter for Hono",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -66,18 +66,6 @@
|
|
|
66
66
|
]
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
|
-
"scripts": {
|
|
70
|
-
"test": "vitest",
|
|
71
|
-
"build": "tsdown --external hono",
|
|
72
|
-
"watch": "tsdown --watch",
|
|
73
|
-
"postbuild": "publint",
|
|
74
|
-
"prerelease": "bun run build && bun run test --run",
|
|
75
|
-
"release": "np --no-publish",
|
|
76
|
-
"lint": "eslint src test",
|
|
77
|
-
"lint:fix": "eslint src test --fix",
|
|
78
|
-
"format": "prettier --check \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\"",
|
|
79
|
-
"format:fix": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\""
|
|
80
|
-
},
|
|
81
69
|
"license": "MIT",
|
|
82
70
|
"repository": {
|
|
83
71
|
"type": "git",
|
|
@@ -87,7 +75,8 @@
|
|
|
87
75
|
"author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
|
|
88
76
|
"publishConfig": {
|
|
89
77
|
"registry": "https://registry.npmjs.org",
|
|
90
|
-
"access": "public"
|
|
78
|
+
"access": "public",
|
|
79
|
+
"provenance": true
|
|
91
80
|
},
|
|
92
81
|
"engines": {
|
|
93
82
|
"node": ">=20"
|
|
@@ -103,7 +92,7 @@
|
|
|
103
92
|
"np": "^11.2.1",
|
|
104
93
|
"prettier": "^3.2.4",
|
|
105
94
|
"publint": "^0.3.18",
|
|
106
|
-
"supertest": "^
|
|
95
|
+
"supertest": "^7.2.2",
|
|
107
96
|
"tsdown": "^0.20.3",
|
|
108
97
|
"typescript": "^5.3.2",
|
|
109
98
|
"vitest": "^4.0.18",
|
|
@@ -112,5 +101,16 @@
|
|
|
112
101
|
"peerDependencies": {
|
|
113
102
|
"hono": "^4"
|
|
114
103
|
},
|
|
115
|
-
"
|
|
116
|
-
|
|
104
|
+
"scripts": {
|
|
105
|
+
"test": "vitest",
|
|
106
|
+
"build": "tsdown --external hono",
|
|
107
|
+
"watch": "tsdown --watch",
|
|
108
|
+
"postbuild": "publint",
|
|
109
|
+
"prerelease": "pnpm run build && pnpm run test --run",
|
|
110
|
+
"release": "np --no-publish",
|
|
111
|
+
"lint": "eslint src test",
|
|
112
|
+
"lint:fix": "eslint src test --fix",
|
|
113
|
+
"format": "prettier --check \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\"",
|
|
114
|
+
"format:fix": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\""
|
|
115
|
+
}
|
|
116
|
+
}
|