-
Notifications
You must be signed in to change notification settings - Fork 213
/
utils.ts
124 lines (114 loc) · 3.66 KB
/
utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import type { IncomingMessage, ServerResponse } from "node:http";
import type { Readable as NodeReadableStream } from "node:stream";
import { splitSetCookieString } from "cookie-es";
import type { NodeHandler, NodeMiddleware } from "../../../types/node";
import { createError } from "../../../error";
import {
sanitizeStatusCode,
sanitizeStatusMessage,
} from "../../../utils/sanitize";
export const kNodeReq: unique symbol =
/* @__PURE__ */ Symbol.for("h3.node.request");
export const kNodeRes: unique symbol =
/* @__PURE__ */ Symbol.for("h3.node.response");
export const kNodeInspect = /* @__PURE__ */ Symbol.for(
"nodejs.util.inspect.custom",
);
export function sendNodeResponse(
nodeRes: ServerResponse,
handlerRes?: BodyInit | null,
): Promise<void> {
// Web Response
if (handlerRes instanceof Response) {
for (const [key, value] of handlerRes.headers) {
if (key === "set-cookie") {
for (const setCookie of splitSetCookieString(value)) {
nodeRes.appendHeader(key, setCookie);
}
} else {
nodeRes.setHeader(key, value);
}
}
if (handlerRes.status) {
nodeRes.statusCode = sanitizeStatusCode(handlerRes.status);
}
if (handlerRes.statusText) {
nodeRes.statusMessage = sanitizeStatusMessage(handlerRes.statusText);
}
if (handlerRes.redirected) {
nodeRes.setHeader("location", handlerRes.url);
}
handlerRes = handlerRes.body; // Next step will send body as stream!
}
// Native Web Streams
// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream
if (typeof (handlerRes as ReadableStream)?.pipeTo === "function") {
return (handlerRes as ReadableStream)
.pipeTo(
new WritableStream({
write: (chunk) => {
nodeRes.write(chunk);
},
}),
)
.then(() => endNodeResponse(nodeRes));
}
// Node.js Readable Streams
// https://nodejs.org/api/stream.html#readable-streams
if (
typeof (handlerRes as unknown as NodeReadableStream)?.pipe === "function"
) {
return new Promise<void>((resolve, reject) => {
// Pipe stream to response
(handlerRes as unknown as NodeReadableStream).pipe(nodeRes);
// Handle stream events (if supported)
if ((handlerRes as unknown as NodeReadableStream).on) {
(handlerRes as unknown as NodeReadableStream).on("end", resolve);
(handlerRes as unknown as NodeReadableStream).on("error", reject);
}
// Handle request aborts
nodeRes.once("close", () => {
(handlerRes as unknown as NodeReadableStream).destroy?.();
// https://react.dev/reference/react-dom/server/renderToPipeableStream
(handlerRes as any).abort?.();
});
}).then(() => endNodeResponse(nodeRes));
}
// Send as string or buffer
return endNodeResponse(nodeRes, handlerRes);
}
export function endNodeResponse(
res: ServerResponse,
chunk?: any,
): Promise<void> {
return new Promise((resolve) => {
res.end(chunk, resolve);
});
}
export function callNodeHandler(
handler: NodeHandler | NodeMiddleware,
req: IncomingMessage,
res: ServerResponse,
) {
const isMiddleware = handler.length > 2;
return new Promise((resolve, reject) => {
const next = (err?: Error) => {
if (isMiddleware) {
res.off("close", next);
res.off("error", next);
}
return err ? reject(createError(err)) : resolve(undefined);
};
try {
const returned = handler(req, res, next);
if (isMiddleware && returned === undefined) {
res.once("close", next);
res.once("error", next);
} else {
resolve(returned);
}
} catch (error) {
next(error as Error);
}
});
}