Refactor raw body capture middleware and improve route key generation for HTTP nodes

This commit is contained in:
Debadutta Panda 2025-02-09 13:26:05 +05:30
parent 9d811c7bce
commit 9a26ee3052

View File

@ -27,7 +27,18 @@ module.exports = function(RED) {
var isUtf8 = require('is-utf8'); var isUtf8 = require('is-utf8');
var hashSum = require("hash-sum"); var hashSum = require("hash-sum");
var PassThrough = require('stream').PassThrough var PassThrough = require('stream').PassThrough
var rootApp = getRootApp(RED.httpNode)
// Check if middleware already exists
var isMiddlewareExists = rootApp._router.stack.some(layer => layer.name === 'setupRawBodyCapture')
/**
* This middleware parses the raw body if the user enables it.
* @param {import('express').Request} req
* @param {import('express').Response} _res
* @param {import('express').NextFunction} next
* @returns
*/
function rawBodyParser(req, _res, next) { function rawBodyParser(req, _res, next) {
if (!req._nodeRedReqStream) { if (!req._nodeRedReqStream) {
return next(); return next();
@ -58,7 +69,7 @@ module.exports = function(RED) {
encoding: isText ? "utf8" : null encoding: isText ? "utf8" : null
}, function (err, buf) { }, function (err, buf) {
if (err) { if (err) {
return next(err); return next(err)
} }
if (!isText && checkUTF && isUtf8(buf)) { if (!isText && checkUTF && isUtf8(buf)) {
buf = buf.toString() buf = buf.toString()
@ -71,6 +82,11 @@ module.exports = function(RED) {
}); });
} }
/**
* This method retrieves the root app by traversing the parent hierarchy.
* @param {import('express').Application} app
* @returns {import('express').Application}
*/
function getRootApp(app) { function getRootApp(app) {
if (typeof app.parent === 'undefined') { if (typeof app.parent === 'undefined') {
return app; return app;
@ -78,36 +94,32 @@ module.exports = function(RED) {
return getRootApp(app.parent) return getRootApp(app.parent)
} }
function getRouteId(req) { /**
var method = req.method.toLowerCase(), url = req.url; * It provide the unique route key
* @param {{method: string, url: string}} obj
* @returns
*/
function getRouteKey(obj) {
var method = obj.method.toUpperCase()
// Normalize the URL by replacing double slashes with a single slash and removing the trailing slash
var url = obj.url.replace(/\/{2,}/g, '/').replace(/\/$/, '')
return `${method}:${url}`; return `${method}:${url}`;
} }
var rootApp = getRootApp(RED.httpNode)
// Check if middleware already exists
var isMiddlewarePresent = rootApp._router.stack.some(layer => layer.name === 'setupRawBodyCapture')
if (!isMiddlewarePresent) {
// Initialize HTTP node settings storage
var httpInNodes = new Map()
rootApp.set('httpInNodes', httpInNodes);
/** /**
* This middleware must always be at the root to handle the raw request body correctly * This middleware is for clone the request stream
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} _res * @param {import('express').Response} _res
* @param {import('express').NextFunction} next * @param {import('express').NextFunction} next
* @returns * @returns
*/ */
function setupRawBodyCapture(req, _res, next) { function setupRawBodyCapture(req, _res, next) {
var httpInNodeId = getRouteId(req) var routeKey = getRouteKey({method: req.method, url: req._parsedUrl.pathname})
var httpInNodes = rootApp.get('httpInNodes')
// Check if settings for this ID exist // Check if settings for this ID exist
if (httpInNodes.has(httpInNodeId)) { if (httpInNodes.has(routeKey)) {
// Get the httpInNode by routeId // Get the httpInNode by routeId
var httpInNode = httpInNodes.get(httpInNodeId); var httpInNode = httpInNodes.get(routeKey);
// If raw body inclusion is disabled, skip the processing // If raw body inclusion is disabled, skip the processing
if (!httpInNode.includeRawBody) { if (!httpInNode.includeRawBody) {
@ -159,6 +171,10 @@ module.exports = function(RED) {
// Proceed to the next middleware if no settings found // Proceed to the next middleware if no settings found
return next(); return next();
} }
if (!isMiddlewareExists) {
// Initialize HTTP node storage
rootApp.set('httpInNodes', new Map());
// Add middleware to the stack // Add middleware to the stack
rootApp.use(setupRawBodyCapture) rootApp.use(setupRawBodyCapture)
// Move the router to top of the stack // Move the router to top of the stack
@ -367,18 +383,14 @@ module.exports = function(RED) {
RED.httpNode.delete(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler); RED.httpNode.delete(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler);
} }
// unique id for httpInNode based on method name and url path var routeKey = getRouteKey({method: this.method, url: RED.httpNode.path() + this.url})
var httpInNodeId = getRouteId({url: this.url, method: this.method})
// get httpInNode from RED.httpNode
var httpInNodes = rootApp.get('httpInNodes') var httpInNodes = rootApp.get('httpInNodes')
// Add httpInNode to RED.httpNode httpInNodes.set(routeKey, this);
httpInNodes.set(httpInNodeId, this);
this.on("close",function() { this.on("close",function() {
// remove httpInNodeSettings from RED.httpNode httpInNodes.delete(routeKey)
httpInNodes.delete(httpInNodeId)
var node = this; var node = this;
RED.httpNode._router.stack.forEach(function(route,i,routes) { RED.httpNode._router.stack.forEach(function(route,i,routes) {
if (route.route && route.route.path === node.url && route.route.methods[node.method]) { if (route.route && route.route.path === node.url && route.route.methods[node.method]) {