Skip to content

Commit 379978a

Browse files
committed
fix(core): Webhooks responding with binary data should not prematurely end the response stream
1 parent bfb0eb7 commit 379978a

File tree

2 files changed

+15
-18
lines changed

2 files changed

+15
-18
lines changed

packages/cli/src/WebhookHelpers.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import type express from 'express';
1010
import { Container } from 'typedi';
1111
import get from 'lodash/get';
12-
import { pipeline } from 'stream/promises';
12+
import { finished } from 'stream/promises';
1313
import formidable from 'formidable';
1414

1515
import { BinaryDataService, NodeExecuteFunctions } from 'n8n-core';
@@ -30,6 +30,7 @@ import type {
3030
IWebhookResponseData,
3131
IWorkflowDataProxyAdditionalKeys,
3232
IWorkflowExecuteAdditionalData,
33+
WebhookResponseMode,
3334
Workflow,
3435
WorkflowExecuteMode,
3536
} from 'n8n-workflow';
@@ -272,7 +273,7 @@ export async function executeWebhook(
272273
additionalKeys,
273274
undefined,
274275
'onReceived',
275-
);
276+
) as WebhookResponseMode;
276277
const responseCode = workflow.expression.getSimpleParameterValue(
277278
workflowStartNode,
278279
webhookData.webhookDescription.responseCode as string,
@@ -291,7 +292,7 @@ export async function executeWebhook(
291292
'firstEntryJson',
292293
);
293294

294-
if (!['onReceived', 'lastNode', 'responseNode'].includes(responseMode as string)) {
295+
if (!['onReceived', 'lastNode', 'responseNode'].includes(responseMode)) {
295296
// If the mode is not known we error. Is probably best like that instead of using
296297
// the default that people know as early as possible (probably already testing phase)
297298
// that something does not resolve properly.
@@ -562,7 +563,8 @@ export async function executeWebhook(
562563
if (binaryData?.id) {
563564
res.header(response.headers);
564565
const stream = await Container.get(BinaryDataService).getAsStream(binaryData.id);
565-
await pipeline(stream, res);
566+
stream.pipe(res, { end: false });
567+
await finished(stream);
566568
responseCallback(null, { noWebhookResponse: true });
567569
} else if (Buffer.isBuffer(response.body)) {
568570
res.header(response.headers);
@@ -595,6 +597,7 @@ export async function executeWebhook(
595597
});
596598
}
597599

600+
process.nextTick(() => res.end());
598601
didSendResponse = true;
599602
})
600603
.catch(async (error) => {
@@ -659,17 +662,9 @@ export async function executeWebhook(
659662
return data;
660663
}
661664

662-
if (responseMode === 'responseNode') {
663-
if (!didSendResponse) {
664-
// Return an error if no Webhook-Response node did send any data
665-
responseCallback(null, {
666-
data: {
667-
message: 'Workflow executed successfully',
668-
},
669-
responseCode,
670-
});
671-
didSendResponse = true;
672-
}
665+
// in `responseNode` mode `responseCallback` is called by `responsePromise`
666+
if (responseMode === 'responseNode' && responsePromise) {
667+
await Promise.allSettled([responsePromise.promise()]);
673668
return undefined;
674669
}
675670

@@ -795,14 +790,16 @@ export async function executeWebhook(
795790
res.setHeader('Content-Type', binaryData.mimeType);
796791
if (binaryData.id) {
797792
const stream = await Container.get(BinaryDataService).getAsStream(binaryData.id);
798-
await pipeline(stream, res);
793+
stream.pipe(res, { end: false });
794+
await finished(stream);
799795
} else {
800-
res.end(Buffer.from(binaryData.data, BINARY_ENCODING));
796+
res.write(Buffer.from(binaryData.data, BINARY_ENCODING));
801797
}
802798

803799
responseCallback(null, {
804800
noWebhookResponse: true,
805801
});
802+
process.nextTick(() => res.end());
806803
}
807804
} else if (responseData === 'noData') {
808805
// Return without data

packages/workflow/src/Interfaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1834,7 +1834,7 @@ export interface IWebhookResponseData {
18341834
}
18351835

18361836
export type WebhookResponseData = 'allEntries' | 'firstEntryJson' | 'firstEntryBinary' | 'noData';
1837-
export type WebhookResponseMode = 'onReceived' | 'lastNode';
1837+
export type WebhookResponseMode = 'onReceived' | 'lastNode' | 'responseNode';
18381838

18391839
export interface INodeTypes {
18401840
getByName(nodeType: string): INodeType | IVersionedNodeType;

0 commit comments

Comments
 (0)