@hono/node-server 2.0.0 → 2.0.2

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/dist/index.cjs CHANGED
@@ -836,10 +836,31 @@ const CloseEvent = globalThis.CloseEvent ?? class extends Event {
836
836
  const generateConnectionSymbol = () => Symbol("connection");
837
837
  const CONNECTION_SYMBOL_KEY = Symbol("CONNECTION_SYMBOL_KEY");
838
838
  const WAIT_FOR_WEBSOCKET_SYMBOL = Symbol("WAIT_FOR_WEBSOCKET_SYMBOL");
839
- const rejectUpgradeRequest = (socket, status) => {
840
- socket.end(`HTTP/1.1 ${status.toString()} ${node_http.STATUS_CODES[status] ?? ""}\r\nConnection: close\r
841
- Content-Length: 0\r
842
- \r
839
+ const responseHeadersToSkip = new Set([
840
+ "connection",
841
+ "content-length",
842
+ "keep-alive",
843
+ "proxy-authenticate",
844
+ "proxy-authorization",
845
+ "te",
846
+ "trailer",
847
+ "transfer-encoding",
848
+ "upgrade",
849
+ "sec-websocket-accept",
850
+ "sec-websocket-extensions",
851
+ "sec-websocket-protocol"
852
+ ]);
853
+ const appendResponseHeaders = (headers, responseHeaders) => {
854
+ if (!responseHeaders) return;
855
+ responseHeaders.forEach((value, key) => {
856
+ if (responseHeadersToSkip.has(key.toLowerCase())) return;
857
+ headers.push(`${key}: ${value}`);
858
+ });
859
+ };
860
+ const rejectUpgradeRequest = (socket, status, responseHeaders) => {
861
+ const responseLines = ["Connection: close", "Content-Length: 0"];
862
+ appendResponseHeaders(responseLines, responseHeaders);
863
+ socket.end(`HTTP/1.1 ${status.toString()} ${node_http.STATUS_CODES[status] ?? ""}\r\n${responseLines.join("\r\n")}\r\n\r
843
864
  `);
844
865
  };
845
866
  const createUpgradeRequest = (request) => {
@@ -880,9 +901,13 @@ const setupWebSocket = (options) => {
880
901
  [WAIT_FOR_WEBSOCKET_SYMBOL]: waitForWebSocket
881
902
  };
882
903
  let status = 400;
904
+ let responseHeaders;
883
905
  try {
884
906
  const response = await fetchCallback(createUpgradeRequest(request), env);
885
- if (response instanceof Response) status = response.status;
907
+ if (response instanceof Response) {
908
+ status = response.status;
909
+ responseHeaders = response.headers;
910
+ }
886
911
  } catch {
887
912
  if (server.listenerCount("upgrade") === 1) rejectUpgradeRequest(socket, 500);
888
913
  return;
@@ -890,12 +915,20 @@ const setupWebSocket = (options) => {
890
915
  const waiter = waiterMap.get(request);
891
916
  if (!waiter || waiter.connectionSymbol !== env[CONNECTION_SYMBOL_KEY]) {
892
917
  waiterMap.delete(request);
893
- if (server.listenerCount("upgrade") === 1) rejectUpgradeRequest(socket, status);
918
+ if (server.listenerCount("upgrade") === 1) rejectUpgradeRequest(socket, status, responseHeaders);
894
919
  return;
895
920
  }
896
- wss.handleUpgrade(request, socket, head, (ws) => {
897
- wss.emit("connection", ws, request);
898
- });
921
+ const addResponseHeaders = (headers) => {
922
+ appendResponseHeaders(headers, responseHeaders);
923
+ };
924
+ wss.on("headers", addResponseHeaders);
925
+ try {
926
+ wss.handleUpgrade(request, socket, head, (ws) => {
927
+ wss.emit("connection", ws, request);
928
+ });
929
+ } finally {
930
+ wss.off("headers", addResponseHeaders);
931
+ }
899
932
  });
900
933
  server.on("close", () => {
901
934
  wss.close();
package/dist/index.mjs CHANGED
@@ -835,10 +835,31 @@ const CloseEvent = globalThis.CloseEvent ?? class extends Event {
835
835
  const generateConnectionSymbol = () => Symbol("connection");
836
836
  const CONNECTION_SYMBOL_KEY = Symbol("CONNECTION_SYMBOL_KEY");
837
837
  const WAIT_FOR_WEBSOCKET_SYMBOL = Symbol("WAIT_FOR_WEBSOCKET_SYMBOL");
838
- const rejectUpgradeRequest = (socket, status) => {
839
- socket.end(`HTTP/1.1 ${status.toString()} ${STATUS_CODES[status] ?? ""}\r\nConnection: close\r
840
- Content-Length: 0\r
841
- \r
838
+ const responseHeadersToSkip = new Set([
839
+ "connection",
840
+ "content-length",
841
+ "keep-alive",
842
+ "proxy-authenticate",
843
+ "proxy-authorization",
844
+ "te",
845
+ "trailer",
846
+ "transfer-encoding",
847
+ "upgrade",
848
+ "sec-websocket-accept",
849
+ "sec-websocket-extensions",
850
+ "sec-websocket-protocol"
851
+ ]);
852
+ const appendResponseHeaders = (headers, responseHeaders) => {
853
+ if (!responseHeaders) return;
854
+ responseHeaders.forEach((value, key) => {
855
+ if (responseHeadersToSkip.has(key.toLowerCase())) return;
856
+ headers.push(`${key}: ${value}`);
857
+ });
858
+ };
859
+ const rejectUpgradeRequest = (socket, status, responseHeaders) => {
860
+ const responseLines = ["Connection: close", "Content-Length: 0"];
861
+ appendResponseHeaders(responseLines, responseHeaders);
862
+ socket.end(`HTTP/1.1 ${status.toString()} ${STATUS_CODES[status] ?? ""}\r\n${responseLines.join("\r\n")}\r\n\r
842
863
  `);
843
864
  };
844
865
  const createUpgradeRequest = (request) => {
@@ -879,9 +900,13 @@ const setupWebSocket = (options) => {
879
900
  [WAIT_FOR_WEBSOCKET_SYMBOL]: waitForWebSocket
880
901
  };
881
902
  let status = 400;
903
+ let responseHeaders;
882
904
  try {
883
905
  const response = await fetchCallback(createUpgradeRequest(request), env);
884
- if (response instanceof Response) status = response.status;
906
+ if (response instanceof Response) {
907
+ status = response.status;
908
+ responseHeaders = response.headers;
909
+ }
885
910
  } catch {
886
911
  if (server.listenerCount("upgrade") === 1) rejectUpgradeRequest(socket, 500);
887
912
  return;
@@ -889,12 +914,20 @@ const setupWebSocket = (options) => {
889
914
  const waiter = waiterMap.get(request);
890
915
  if (!waiter || waiter.connectionSymbol !== env[CONNECTION_SYMBOL_KEY]) {
891
916
  waiterMap.delete(request);
892
- if (server.listenerCount("upgrade") === 1) rejectUpgradeRequest(socket, status);
917
+ if (server.listenerCount("upgrade") === 1) rejectUpgradeRequest(socket, status, responseHeaders);
893
918
  return;
894
919
  }
895
- wss.handleUpgrade(request, socket, head, (ws) => {
896
- wss.emit("connection", ws, request);
897
- });
920
+ const addResponseHeaders = (headers) => {
921
+ appendResponseHeaders(headers, responseHeaders);
922
+ };
923
+ wss.on("headers", addResponseHeaders);
924
+ try {
925
+ wss.handleUpgrade(request, socket, head, (ws) => {
926
+ wss.emit("connection", ws, request);
927
+ });
928
+ } finally {
929
+ wss.off("headers", addResponseHeaders);
930
+ }
898
931
  });
899
932
  server.on("close", () => {
900
933
  wss.close();
@@ -1,9 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- let node_stream = require("node:stream");
2
+ const require_utils_stream = require('./utils/stream.cjs');
3
3
  let hono_utils_mime = require("hono/utils/mime");
4
4
  let node_fs = require("node:fs");
5
5
  let node_path = require("node:path");
6
- let node_process = require("node:process");
7
6
 
8
7
  //#region src/serve-static.ts
9
8
  const COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
@@ -13,30 +12,6 @@ const ENCODINGS = {
13
12
  gzip: ".gz"
14
13
  };
15
14
  const ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
16
- const pr54206Applied = () => {
17
- const [major, minor] = node_process.versions.node.split(".").map((component) => parseInt(component));
18
- return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
19
- };
20
- const useReadableToWeb = pr54206Applied();
21
- const createStreamBody = (stream) => {
22
- if (useReadableToWeb) return node_stream.Readable.toWeb(stream);
23
- return new ReadableStream({
24
- start(controller) {
25
- stream.on("data", (chunk) => {
26
- controller.enqueue(chunk);
27
- });
28
- stream.on("error", (err) => {
29
- controller.error(err);
30
- });
31
- stream.on("end", () => {
32
- controller.close();
33
- });
34
- },
35
- cancel() {
36
- stream.destroy();
37
- }
38
- });
39
- };
40
15
  const getStats = (path) => {
41
16
  let stats;
42
17
  try {
@@ -103,16 +78,16 @@ const serveStatic = (options = { root: "" }) => {
103
78
  let result;
104
79
  const size = stats.size;
105
80
  const range = c.req.header("range") || "";
81
+ c.header("Last-Modified", stats.mtime.toUTCString());
106
82
  if (c.req.method == "HEAD" || c.req.method == "OPTIONS") {
107
83
  c.header("Content-Length", size.toString());
108
84
  c.status(200);
109
85
  result = c.body(null);
110
86
  } else if (!range) {
111
87
  c.header("Content-Length", size.toString());
112
- result = c.body(createStreamBody((0, node_fs.createReadStream)(path)), 200);
88
+ result = c.body(require_utils_stream.createStreamBody((0, node_fs.createReadStream)(path)), 200);
113
89
  } else {
114
90
  c.header("Accept-Ranges", "bytes");
115
- c.header("Date", stats.birthtime.toUTCString());
116
91
  const parts = range.replace(/bytes=/, "").split("-", 2);
117
92
  const start = parseInt(parts[0], 10) || 0;
118
93
  let end = parseInt(parts[1], 10) || size - 1;
@@ -124,7 +99,7 @@ const serveStatic = (options = { root: "" }) => {
124
99
  });
125
100
  c.header("Content-Length", chunksize.toString());
126
101
  c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
127
- result = c.body(createStreamBody(stream), 206);
102
+ result = c.body(require_utils_stream.createStreamBody(stream), 206);
128
103
  }
129
104
  await options.onFound?.(path, c);
130
105
  return result;
@@ -1,8 +1,7 @@
1
- import { Readable } from "node:stream";
1
+ import { createStreamBody } from "./utils/stream.mjs";
2
2
  import { getMimeType } from "hono/utils/mime";
3
3
  import { createReadStream, existsSync, statSync } from "node:fs";
4
4
  import { join } from "node:path";
5
- import { versions } from "node:process";
6
5
 
7
6
  //#region src/serve-static.ts
8
7
  const COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
@@ -12,30 +11,6 @@ const ENCODINGS = {
12
11
  gzip: ".gz"
13
12
  };
14
13
  const ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
15
- const pr54206Applied = () => {
16
- const [major, minor] = versions.node.split(".").map((component) => parseInt(component));
17
- return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
18
- };
19
- const useReadableToWeb = pr54206Applied();
20
- const createStreamBody = (stream) => {
21
- if (useReadableToWeb) return Readable.toWeb(stream);
22
- return new ReadableStream({
23
- start(controller) {
24
- stream.on("data", (chunk) => {
25
- controller.enqueue(chunk);
26
- });
27
- stream.on("error", (err) => {
28
- controller.error(err);
29
- });
30
- stream.on("end", () => {
31
- controller.close();
32
- });
33
- },
34
- cancel() {
35
- stream.destroy();
36
- }
37
- });
38
- };
39
14
  const getStats = (path) => {
40
15
  let stats;
41
16
  try {
@@ -102,6 +77,7 @@ const serveStatic = (options = { root: "" }) => {
102
77
  let result;
103
78
  const size = stats.size;
104
79
  const range = c.req.header("range") || "";
80
+ c.header("Last-Modified", stats.mtime.toUTCString());
105
81
  if (c.req.method == "HEAD" || c.req.method == "OPTIONS") {
106
82
  c.header("Content-Length", size.toString());
107
83
  c.status(200);
@@ -111,7 +87,6 @@ const serveStatic = (options = { root: "" }) => {
111
87
  result = c.body(createStreamBody(createReadStream(path)), 200);
112
88
  } else {
113
89
  c.header("Accept-Ranges", "bytes");
114
- c.header("Date", stats.birthtime.toUTCString());
115
90
  const parts = range.replace(/bytes=/, "").split("-", 2);
116
91
  const start = parseInt(parts[0], 10) || 0;
117
92
  let end = parseInt(parts[1], 10) || size - 1;
@@ -0,0 +1,65 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let node_stream = require("node:stream");
3
+ let node_process = require("node:process");
4
+
5
+ //#region src/utils/stream.ts
6
+ const pr54206Applied = () => {
7
+ const [major, minor] = node_process.versions.node.split(".").map((component) => parseInt(component));
8
+ return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
9
+ };
10
+ const useReadableToWeb = pr54206Applied();
11
+ const createStreamBody = (stream, useNativeReadableToWeb = useReadableToWeb) => {
12
+ if (useNativeReadableToWeb) return node_stream.Readable.toWeb(stream);
13
+ let controller;
14
+ let settled = false;
15
+ const cleanup = () => {
16
+ stream.off("data", onData);
17
+ stream.off("error", onError);
18
+ stream.off("end", onTerminate);
19
+ stream.off("close", onTerminate);
20
+ };
21
+ const settle = (callback) => {
22
+ if (settled) return;
23
+ settled = true;
24
+ cleanup();
25
+ callback?.();
26
+ };
27
+ const onData = (chunk) => {
28
+ if (settled || !controller) return;
29
+ controller.enqueue(chunk);
30
+ if ((controller.desiredSize ?? 0) <= 0) stream.pause();
31
+ };
32
+ const onError = (error) => {
33
+ settle(() => {
34
+ controller?.error(error);
35
+ });
36
+ };
37
+ const onTerminate = () => {
38
+ settle(() => {
39
+ controller?.close();
40
+ });
41
+ };
42
+ return new ReadableStream({
43
+ start(streamController) {
44
+ controller = streamController;
45
+ stream.on("data", onData);
46
+ stream.on("error", onError);
47
+ stream.on("end", onTerminate);
48
+ stream.on("close", onTerminate);
49
+ stream.pause();
50
+ },
51
+ pull() {
52
+ if (!settled) stream.resume();
53
+ },
54
+ cancel() {
55
+ settle();
56
+ const ignoreError = () => {};
57
+ stream.on("error", ignoreError);
58
+ stream.once("close", () => stream.off("error", ignoreError));
59
+ stream.destroy();
60
+ }
61
+ });
62
+ };
63
+
64
+ //#endregion
65
+ exports.createStreamBody = createStreamBody;
@@ -0,0 +1,6 @@
1
+ import { ReadStream } from "node:fs";
2
+
3
+ //#region src/utils/stream.d.ts
4
+ declare const createStreamBody: (stream: ReadStream, useNativeReadableToWeb?: boolean) => ReadableStream<Uint8Array>;
5
+ //#endregion
6
+ export { createStreamBody };
@@ -0,0 +1,6 @@
1
+ import { ReadStream } from "node:fs";
2
+
3
+ //#region src/utils/stream.d.ts
4
+ declare const createStreamBody: (stream: ReadStream, useNativeReadableToWeb?: boolean) => ReadableStream<Uint8Array>;
5
+ //#endregion
6
+ export { createStreamBody };
@@ -0,0 +1,64 @@
1
+ import { Readable } from "node:stream";
2
+ import { versions } from "node:process";
3
+
4
+ //#region src/utils/stream.ts
5
+ const pr54206Applied = () => {
6
+ const [major, minor] = versions.node.split(".").map((component) => parseInt(component));
7
+ return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
8
+ };
9
+ const useReadableToWeb = pr54206Applied();
10
+ const createStreamBody = (stream, useNativeReadableToWeb = useReadableToWeb) => {
11
+ if (useNativeReadableToWeb) return Readable.toWeb(stream);
12
+ let controller;
13
+ let settled = false;
14
+ const cleanup = () => {
15
+ stream.off("data", onData);
16
+ stream.off("error", onError);
17
+ stream.off("end", onTerminate);
18
+ stream.off("close", onTerminate);
19
+ };
20
+ const settle = (callback) => {
21
+ if (settled) return;
22
+ settled = true;
23
+ cleanup();
24
+ callback?.();
25
+ };
26
+ const onData = (chunk) => {
27
+ if (settled || !controller) return;
28
+ controller.enqueue(chunk);
29
+ if ((controller.desiredSize ?? 0) <= 0) stream.pause();
30
+ };
31
+ const onError = (error) => {
32
+ settle(() => {
33
+ controller?.error(error);
34
+ });
35
+ };
36
+ const onTerminate = () => {
37
+ settle(() => {
38
+ controller?.close();
39
+ });
40
+ };
41
+ return new ReadableStream({
42
+ start(streamController) {
43
+ controller = streamController;
44
+ stream.on("data", onData);
45
+ stream.on("error", onError);
46
+ stream.on("end", onTerminate);
47
+ stream.on("close", onTerminate);
48
+ stream.pause();
49
+ },
50
+ pull() {
51
+ if (!settled) stream.resume();
52
+ },
53
+ cancel() {
54
+ settle();
55
+ const ignoreError = () => {};
56
+ stream.on("error", ignoreError);
57
+ stream.once("close", () => stream.off("error", ignoreError));
58
+ stream.destroy();
59
+ }
60
+ });
61
+ };
62
+
63
+ //#endregion
64
+ export { createStreamBody };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hono/node-server",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Node.js Adapter for Hono",
5
5
  "main": "dist/index.mjs",
6
6
  "type": "module",