Skip to content

Commit 8b9ca39

Browse files
authored
fix(fromNodeHandler): pipe responses once (#1273)
1 parent 2ec1eea commit 8b9ca39

2 files changed

Lines changed: 53 additions & 1 deletion

File tree

src/adapters.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,22 @@ function callNodeHandler(
8989
return new Promise((resolve, reject) => {
9090
res.once("close", () => resolve(kHandled));
9191
res.once("finish", () => resolve(kHandled));
92-
res.once("pipe", (stream) => resolve(stream));
9392
res.once("error", (error) => reject(error));
93+
res.once("pipe", (stream) => {
94+
resolve(
95+
new Promise((resolve, reject) => {
96+
stream.once("close", () => resolve(kHandled));
97+
stream.once("error", (error: any) => {
98+
console.error("[h3] Stream error in Node.js handler", {
99+
cause: error,
100+
});
101+
// We cannot alter the outgoing response at this point
102+
// TODO: We might at least call h3 error hook here by exposing app to node request
103+
reject(kHandled);
104+
});
105+
}),
106+
);
107+
});
94108
try {
95109
if (isMiddleware) {
96110
Promise.resolve(

test/app.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,44 @@ describeMatrix("app", (t, { it, expect }) => {
290290
},
291291
);
292292

293+
it.skipIf(t.target !== "node")("fromNodeHandler + piping", async () => {
294+
t.app.all(
295+
"/*",
296+
fromNodeHandler((req, res) => {
297+
const iterator = (async function* () {
298+
yield "item1,";
299+
yield "item2,";
300+
yield "item3";
301+
})();
302+
NodeStreamReadable.from(iterator).pipe(res);
303+
}),
304+
);
305+
const res = await t.fetch("/");
306+
expect(res.status).toBe(200);
307+
expect(await res.text()).toBe("item1,item2,item3");
308+
});
309+
310+
it.skipIf(t.target !== "node")(
311+
"fromNodeHandler + piping (with Error and custom status)",
312+
async () => {
313+
t.app.all(
314+
"/*",
315+
fromNodeHandler((req, res) => {
316+
res.statusCode = 201;
317+
const iterator = (async function* () {
318+
yield "item1,";
319+
yield "item2";
320+
throw new Error("Test Error");
321+
})();
322+
NodeStreamReadable.from(iterator).pipe(res);
323+
}),
324+
);
325+
const res = await t.fetch("/");
326+
expect(res.status).toBe(201);
327+
expect(await res.text()).toBe("item1,item2");
328+
},
329+
);
330+
293331
it("set headers via event.res + Response (mutable)", async () => {
294332
t.app.use((event) => {
295333
event.res.headers.set("x-from-event", "1");

0 commit comments

Comments
 (0)