mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
fix(HTTP Request Tool Node): Respond with an error when receive binary response (#11219)
This commit is contained in:
@@ -281,6 +281,7 @@ export class ToolHttpRequest implements INodeType {
|
||||
'User-Agent': undefined,
|
||||
},
|
||||
body: {},
|
||||
returnFullResponse: true,
|
||||
};
|
||||
|
||||
const authentication = this.getNodeParameter('authentication', itemIndex, 'none') as
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
import get from 'lodash/get';
|
||||
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
|
||||
import type { N8nTool } from '../../../../utils/N8nTool';
|
||||
import { ToolHttpRequest } from '../ToolHttpRequest.node';
|
||||
|
||||
const createExecuteFunctionsMock = (parameters: IDataObject, requestMock: any) => {
|
||||
const nodeParameters = parameters;
|
||||
|
||||
return {
|
||||
getNodeParameter(parameter: string) {
|
||||
return get(nodeParameters, parameter);
|
||||
},
|
||||
getNode() {
|
||||
return {
|
||||
name: 'HTTP Request',
|
||||
};
|
||||
},
|
||||
getInputData() {
|
||||
return [{ json: {} }];
|
||||
},
|
||||
getWorkflow() {
|
||||
return {
|
||||
name: 'Test Workflow',
|
||||
};
|
||||
},
|
||||
continueOnFail() {
|
||||
return false;
|
||||
},
|
||||
addInputData() {
|
||||
return { index: 0 };
|
||||
},
|
||||
addOutputData() {
|
||||
return;
|
||||
},
|
||||
helpers: {
|
||||
httpRequest: requestMock,
|
||||
},
|
||||
} as unknown as IExecuteFunctions;
|
||||
};
|
||||
|
||||
describe('ToolHttpRequest', () => {
|
||||
let httpTool: ToolHttpRequest;
|
||||
let mockRequest: jest.Mock;
|
||||
|
||||
describe('Binary response', () => {
|
||||
beforeEach(() => {
|
||||
httpTool = new ToolHttpRequest();
|
||||
mockRequest = jest.fn();
|
||||
});
|
||||
|
||||
it('should return the error when receiving a binary response', async () => {
|
||||
mockRequest.mockResolvedValue({
|
||||
body: Buffer.from(''),
|
||||
headers: {
|
||||
'content-type': 'image/jpeg',
|
||||
},
|
||||
});
|
||||
|
||||
const { response } = await httpTool.supplyData.call(
|
||||
createExecuteFunctionsMock(
|
||||
{
|
||||
method: 'GET',
|
||||
url: 'https://httpbin.org/image/jpeg',
|
||||
options: {},
|
||||
placeholderDefinitions: {
|
||||
values: [],
|
||||
},
|
||||
},
|
||||
mockRequest,
|
||||
),
|
||||
0,
|
||||
);
|
||||
|
||||
const res = await (response as N8nTool).invoke('');
|
||||
|
||||
expect(res).toContain('error');
|
||||
expect(res).toContain('Binary data is not supported');
|
||||
});
|
||||
|
||||
it('should return the response text when receiving a text response', async () => {
|
||||
mockRequest.mockResolvedValue({
|
||||
body: 'Hello World',
|
||||
headers: {
|
||||
'content-type': 'text/plain',
|
||||
},
|
||||
});
|
||||
|
||||
const { response } = await httpTool.supplyData.call(
|
||||
createExecuteFunctionsMock(
|
||||
{
|
||||
method: 'GET',
|
||||
url: 'https://httpbin.org/text/plain',
|
||||
options: {},
|
||||
placeholderDefinitions: {
|
||||
values: [],
|
||||
},
|
||||
},
|
||||
mockRequest,
|
||||
),
|
||||
0,
|
||||
);
|
||||
|
||||
const res = await (response as N8nTool).invoke('');
|
||||
expect(res).toEqual('Hello World');
|
||||
});
|
||||
|
||||
it('should return the response text when receiving a text response with a charset', async () => {
|
||||
mockRequest.mockResolvedValue({
|
||||
body: 'こんにちは世界',
|
||||
headers: {
|
||||
'content-type': 'text/plain; charset=iso-2022-jp',
|
||||
},
|
||||
});
|
||||
|
||||
const { response } = await httpTool.supplyData.call(
|
||||
createExecuteFunctionsMock(
|
||||
{
|
||||
method: 'GET',
|
||||
url: 'https://httpbin.org/text/plain',
|
||||
options: {},
|
||||
placeholderDefinitions: {
|
||||
values: [],
|
||||
},
|
||||
},
|
||||
mockRequest,
|
||||
),
|
||||
0,
|
||||
);
|
||||
|
||||
const res = await (response as N8nTool).invoke('');
|
||||
expect(res).toEqual('こんにちは世界');
|
||||
});
|
||||
|
||||
it('should return the response object when receiving a JSON response', async () => {
|
||||
const mockJson = { hello: 'world' };
|
||||
|
||||
mockRequest.mockResolvedValue({
|
||||
body: mockJson,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const { response } = await httpTool.supplyData.call(
|
||||
createExecuteFunctionsMock(
|
||||
{
|
||||
method: 'GET',
|
||||
url: 'https://httpbin.org/json',
|
||||
options: {},
|
||||
placeholderDefinitions: {
|
||||
values: [],
|
||||
},
|
||||
},
|
||||
mockRequest,
|
||||
),
|
||||
0,
|
||||
);
|
||||
|
||||
const res = await (response as N8nTool).invoke('');
|
||||
expect(jsonParse(res)).toEqual(mockJson);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,12 @@
|
||||
import { Readability } from '@mozilla/readability';
|
||||
import cheerio from 'cheerio';
|
||||
import { convert } from 'html-to-text';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import get from 'lodash/get';
|
||||
import set from 'lodash/set';
|
||||
import unset from 'lodash/unset';
|
||||
import * as mime from 'mime-types';
|
||||
import { getOAuth2AdditionalParameters } from 'n8n-nodes-base/dist/nodes/HttpRequest/GenericFunctions';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
IDataObject,
|
||||
@@ -7,20 +16,8 @@ import type {
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionType, NodeOperationError, jsonParse } from 'n8n-workflow';
|
||||
|
||||
import { getOAuth2AdditionalParameters } from 'n8n-nodes-base/dist/nodes/HttpRequest/GenericFunctions';
|
||||
|
||||
import set from 'lodash/set';
|
||||
import get from 'lodash/get';
|
||||
import unset from 'lodash/unset';
|
||||
|
||||
import cheerio from 'cheerio';
|
||||
import { convert } from 'html-to-text';
|
||||
|
||||
import { Readability } from '@mozilla/readability';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { z } from 'zod';
|
||||
import type { DynamicZodObject } from '../../../types/zod.types';
|
||||
|
||||
import type {
|
||||
ParameterInputType,
|
||||
ParametersValues,
|
||||
@@ -29,6 +26,7 @@ import type {
|
||||
SendIn,
|
||||
ToolParameter,
|
||||
} from './interfaces';
|
||||
import type { DynamicZodObject } from '../../../types/zod.types';
|
||||
|
||||
const genericCredentialRequest = async (ctx: IExecuteFunctions, itemIndex: number) => {
|
||||
const genericType = ctx.getNodeParameter('genericAuthType', itemIndex) as string;
|
||||
@@ -176,6 +174,7 @@ const htmlOptimizer = (ctx: IExecuteFunctions, itemIndex: number, maxLength: num
|
||||
);
|
||||
}
|
||||
const returnData: string[] = [];
|
||||
|
||||
const html = cheerio.load(response);
|
||||
const htmlElements = html(cssSelector);
|
||||
|
||||
@@ -574,6 +573,7 @@ export const configureToolFunction = (
|
||||
// Clone options and rawRequestOptions to avoid mutating the original objects
|
||||
const options: IHttpRequestOptions | null = structuredClone(requestOptions);
|
||||
const clonedRawRequestOptions: { [key: string]: string } = structuredClone(rawRequestOptions);
|
||||
let fullResponse: any;
|
||||
let response: string = '';
|
||||
let executionError: Error | undefined = undefined;
|
||||
|
||||
@@ -732,8 +732,6 @@ export const configureToolFunction = (
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
const errorMessage = 'Input provided by model is not valid';
|
||||
|
||||
if (error instanceof NodeOperationError) {
|
||||
@@ -749,11 +747,29 @@ export const configureToolFunction = (
|
||||
|
||||
if (options) {
|
||||
try {
|
||||
response = optimizeResponse(await httpRequest(options));
|
||||
fullResponse = await httpRequest(options);
|
||||
} catch (error) {
|
||||
const httpCode = (error as NodeApiError).httpCode;
|
||||
response = `${httpCode ? `HTTP ${httpCode} ` : ''}There was an error: "${error.message}"`;
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
try {
|
||||
// Check if the response is binary data
|
||||
if (fullResponse?.headers?.['content-type']) {
|
||||
const contentType = fullResponse.headers['content-type'] as string;
|
||||
const mimeType = contentType.split(';')[0].trim();
|
||||
|
||||
if (mime.charset(mimeType) !== 'UTF-8') {
|
||||
throw new NodeOperationError(ctx.getNode(), 'Binary data is not supported');
|
||||
}
|
||||
}
|
||||
|
||||
response = optimizeResponse(fullResponse.body);
|
||||
} catch (error) {
|
||||
response = `There was an error: "${error.message}"`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof response !== 'string') {
|
||||
|
||||
Reference in New Issue
Block a user