mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
✨ Add DELETE, PATCH and PUT request support to Webhooks
This commit is contained in:
parent
ec5bfaf895
commit
9a06d0fffc
|
@ -72,6 +72,7 @@ import {
|
|||
IWorkflowBase,
|
||||
LoggerProxy,
|
||||
NodeHelpers,
|
||||
WebhookHttpMethod,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -202,6 +203,8 @@ class App {
|
|||
|
||||
presetCredentialsLoaded: boolean;
|
||||
|
||||
webhookMethods: WebhookHttpMethod[];
|
||||
|
||||
constructor() {
|
||||
this.app = express();
|
||||
|
||||
|
@ -237,6 +240,8 @@ class App {
|
|||
this.presetCredentialsLoaded = false;
|
||||
this.endpointPresetCredentials = config.get('credentials.overwrite.endpoint') as string;
|
||||
|
||||
this.webhookMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'];
|
||||
|
||||
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
||||
|
||||
const telemetrySettings: ITelemetrySettings = {
|
||||
|
@ -2704,8 +2709,8 @@ class App {
|
|||
WebhookServer.registerProductionWebhooks.apply(this);
|
||||
}
|
||||
|
||||
// HEAD webhook requests (test for UI)
|
||||
this.app.head(
|
||||
// Register all webhook requests (test for UI)
|
||||
this.app.all(
|
||||
`/${this.endpointWebhookTest}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-test/" to get the registred part of the url
|
||||
|
@ -2713,98 +2718,36 @@ class App {
|
|||
this.endpointWebhookTest.length + 2,
|
||||
);
|
||||
|
||||
const method = req.method.toUpperCase() as WebhookHttpMethod;
|
||||
|
||||
if (method === 'OPTIONS') {
|
||||
let allowedMethods: string[];
|
||||
try {
|
||||
allowedMethods = await this.testWebhooks.getWebhookMethods(requestUrl);
|
||||
allowedMethods.push('OPTIONS');
|
||||
|
||||
// Add custom "Allow" header to satisfy OPTIONS response.
|
||||
res.append('Allow', allowedMethods);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, {}, true, 204);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.webhookMethods.includes(method)) {
|
||||
ResponseHelper.sendErrorResponse(
|
||||
res,
|
||||
new Error(`The method ${method} is not supported.`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await this.testWebhooks.callTestWebhook('HEAD', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(
|
||||
res,
|
||||
response.data,
|
||||
true,
|
||||
response.responseCode,
|
||||
response.headers,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// HEAD webhook requests (test for UI)
|
||||
this.app.options(
|
||||
`/${this.endpointWebhookTest}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-test/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhookTest.length + 2,
|
||||
);
|
||||
|
||||
let allowedMethods: string[];
|
||||
try {
|
||||
allowedMethods = await this.testWebhooks.getWebhookMethods(requestUrl);
|
||||
allowedMethods.push('OPTIONS');
|
||||
|
||||
// Add custom "Allow" header to satisfy OPTIONS response.
|
||||
res.append('Allow', allowedMethods);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, {}, true, 204);
|
||||
},
|
||||
);
|
||||
|
||||
// GET webhook requests (test for UI)
|
||||
this.app.get(
|
||||
`/${this.endpointWebhookTest}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-test/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhookTest.length + 2,
|
||||
);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await this.testWebhooks.callTestWebhook('GET', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(
|
||||
res,
|
||||
response.data,
|
||||
true,
|
||||
response.responseCode,
|
||||
response.headers,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// POST webhook requests (test for UI)
|
||||
this.app.post(
|
||||
`/${this.endpointWebhookTest}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-test/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhookTest.length + 2,
|
||||
);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await this.testWebhooks.callTestWebhook('POST', requestUrl, req, res);
|
||||
response = await this.testWebhooks.callTestWebhook(method, requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
|
|
|
@ -16,6 +16,7 @@ import * as _ from 'lodash';
|
|||
import * as compression from 'compression';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as parseUrl from 'parseurl';
|
||||
import { WebhookHttpMethod } from 'n8n-workflow';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
ActiveExecutions,
|
||||
|
@ -41,8 +42,8 @@ export function registerProductionWebhooks() {
|
|||
// Regular Webhooks
|
||||
// ----------------------------------------
|
||||
|
||||
// HEAD webhook requests
|
||||
this.app.head(
|
||||
// Register all webhook requests
|
||||
this.app.all(
|
||||
`/${this.endpointWebhook}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
|
@ -50,99 +51,34 @@ export function registerProductionWebhooks() {
|
|||
this.endpointWebhook.length + 2,
|
||||
);
|
||||
|
||||
const method = req.method.toUpperCase() as WebhookHttpMethod;
|
||||
|
||||
if (method === 'OPTIONS') {
|
||||
let allowedMethods: string[];
|
||||
try {
|
||||
allowedMethods = await this.activeWorkflowRunner.getWebhookMethods(requestUrl);
|
||||
allowedMethods.push('OPTIONS');
|
||||
|
||||
// Add custom "Allow" header to satisfy OPTIONS response.
|
||||
res.append('Allow', allowedMethods);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, {}, true, 204);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.webhookMethods.includes(method)) {
|
||||
ResponseHelper.sendErrorResponse(res, new Error(`The method ${method} is not supported.`));
|
||||
return;
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
response = await this.activeWorkflowRunner.executeWebhook('HEAD', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(
|
||||
res,
|
||||
response.data,
|
||||
true,
|
||||
response.responseCode,
|
||||
response.headers,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// OPTIONS webhook requests
|
||||
this.app.options(
|
||||
`/${this.endpointWebhook}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhook.length + 2,
|
||||
);
|
||||
|
||||
let allowedMethods: string[];
|
||||
try {
|
||||
allowedMethods = await this.activeWorkflowRunner.getWebhookMethods(requestUrl);
|
||||
allowedMethods.push('OPTIONS');
|
||||
|
||||
// Add custom "Allow" header to satisfy OPTIONS response.
|
||||
res.append('Allow', allowedMethods);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, {}, true, 204);
|
||||
},
|
||||
);
|
||||
|
||||
// GET webhook requests
|
||||
this.app.get(
|
||||
`/${this.endpointWebhook}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhook.length + 2,
|
||||
);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await this.activeWorkflowRunner.executeWebhook('GET', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(
|
||||
res,
|
||||
response.data,
|
||||
true,
|
||||
response.responseCode,
|
||||
response.headers,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// POST webhook requests
|
||||
this.app.post(
|
||||
`/${this.endpointWebhook}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhook.length + 2,
|
||||
);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await this.activeWorkflowRunner.executeWebhook('POST', requestUrl, req, res);
|
||||
response = await this.activeWorkflowRunner.executeWebhook(method, requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
|
@ -169,8 +105,8 @@ export function registerProductionWebhooks() {
|
|||
|
||||
const waitingWebhooks = new WaitingWebhooks();
|
||||
|
||||
// HEAD webhook-waiting requests
|
||||
this.app.head(
|
||||
// Register all webhook-waiting requests
|
||||
this.app.all(
|
||||
`/${this.endpointWebhookWaiting}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-waiting/" to get the registred part of the url
|
||||
|
@ -178,73 +114,20 @@ export function registerProductionWebhooks() {
|
|||
this.endpointWebhookWaiting.length + 2,
|
||||
);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await waitingWebhooks.executeWebhook('HEAD', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
const method = req.method.toUpperCase() as WebhookHttpMethod;
|
||||
|
||||
// TOOD: Add support for OPTIONS in the future
|
||||
// if (method === 'OPTIONS') {
|
||||
// }
|
||||
|
||||
if (!this.webhookMethods.includes(method)) {
|
||||
ResponseHelper.sendErrorResponse(res, new Error(`The method ${method} is not supported.`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(
|
||||
res,
|
||||
response.data,
|
||||
true,
|
||||
response.responseCode,
|
||||
response.headers,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// GET webhook-waiting requests
|
||||
this.app.get(
|
||||
`/${this.endpointWebhookWaiting}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-waiting/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhookWaiting.length + 2,
|
||||
);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await waitingWebhooks.executeWebhook('GET', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(
|
||||
res,
|
||||
response.data,
|
||||
true,
|
||||
response.responseCode,
|
||||
response.headers,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// POST webhook-waiting requests
|
||||
this.app.post(
|
||||
`/${this.endpointWebhookWaiting}/*`,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-waiting/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(
|
||||
this.endpointWebhookWaiting.length + 2,
|
||||
);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await waitingWebhooks.executeWebhook('POST', requestUrl, req, res);
|
||||
response = await waitingWebhooks.executeWebhook(method, requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
|
|
|
@ -250,6 +250,10 @@ export class Wait implements INodeType {
|
|||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'DELETE',
|
||||
value: 'DELETE',
|
||||
},
|
||||
{
|
||||
name: 'GET',
|
||||
value: 'GET',
|
||||
|
@ -258,10 +262,18 @@ export class Wait implements INodeType {
|
|||
name: 'HEAD',
|
||||
value: 'HEAD',
|
||||
},
|
||||
{
|
||||
name: 'PATCH',
|
||||
value: 'PATCH',
|
||||
},
|
||||
{
|
||||
name: 'POST',
|
||||
value: 'POST',
|
||||
},
|
||||
{
|
||||
name: 'PUT',
|
||||
value: 'PUT',
|
||||
},
|
||||
],
|
||||
default: 'GET',
|
||||
description: 'The HTTP method of the Webhook call',
|
||||
|
@ -514,6 +526,8 @@ export class Wait implements INodeType {
|
|||
displayOptions: {
|
||||
show: {
|
||||
'/httpMethod': [
|
||||
'PATCH',
|
||||
'PUT',
|
||||
'POST',
|
||||
],
|
||||
},
|
||||
|
|
|
@ -120,6 +120,10 @@ export class Webhook implements INodeType {
|
|||
name: 'httpMethod',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'DELETE',
|
||||
value: 'DELETE',
|
||||
},
|
||||
{
|
||||
name: 'GET',
|
||||
value: 'GET',
|
||||
|
@ -128,10 +132,18 @@ export class Webhook implements INodeType {
|
|||
name: 'HEAD',
|
||||
value: 'HEAD',
|
||||
},
|
||||
{
|
||||
name: 'PATCH',
|
||||
value: 'PATCH',
|
||||
},
|
||||
{
|
||||
name: 'POST',
|
||||
value: 'POST',
|
||||
},
|
||||
{
|
||||
name: 'PUT',
|
||||
value: 'PUT',
|
||||
},
|
||||
],
|
||||
default: 'GET',
|
||||
description: 'The HTTP method to listen to.',
|
||||
|
@ -265,6 +277,8 @@ export class Webhook implements INodeType {
|
|||
displayOptions: {
|
||||
show: {
|
||||
'/httpMethod': [
|
||||
'PATCH',
|
||||
'PUT',
|
||||
'POST',
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1178,7 +1178,7 @@ export interface IWorkflowMetadata {
|
|||
active: boolean;
|
||||
}
|
||||
|
||||
export type WebhookHttpMethod = 'GET' | 'POST' | 'HEAD' | 'OPTIONS';
|
||||
export type WebhookHttpMethod = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS';
|
||||
|
||||
export interface IWebhookResponseData {
|
||||
workflowData?: INodeExecutionData[][];
|
||||
|
|
Loading…
Reference in a new issue