Refactor raw body capture middleware to use a Set for route management and improve code consistency

This commit is contained in:
Debadutta Panda 2025-02-09 21:09:30 +05:30
parent 9a26ee3052
commit caeb5d3538

View File

@ -26,11 +26,9 @@ module.exports = function(RED) {
var mediaTyper = require('media-typer'); var mediaTyper = require('media-typer');
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) var rootApp = getRootApp(RED.httpNode);
var rawDataRoutes = new Set();
// 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. * This middleware parses the raw body if the user enables it.
@ -69,7 +67,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()
@ -77,7 +75,7 @@ module.exports = function(RED) {
Object.defineProperty(req, "rawRequestBody", { Object.defineProperty(req, "rawRequestBody", {
value: buf, value: buf,
enumerable: true enumerable: true
}) });
return next(); return next();
}); });
} }
@ -91,7 +89,7 @@ module.exports = function(RED) {
if (typeof app.parent === 'undefined') { if (typeof app.parent === 'undefined') {
return app; return app;
} }
return getRootApp(app.parent) return getRootApp(app.parent);
} }
/** /**
@ -100,9 +98,9 @@ module.exports = function(RED) {
* @returns * @returns
*/ */
function getRouteKey(obj) { function getRouteKey(obj) {
var method = obj.method.toUpperCase() var method = obj.method.toUpperCase();
// Normalize the URL by replacing double slashes with a single slash and removing the trailing slash // Normalize the URL by replacing double slashes with a single slash and removing the trailing slash
var url = obj.url.replace(/\/{2,}/g, '/').replace(/\/$/, '') var url = obj.url.replace(/\/{2,}/g, '/').replace(/\/$/, '');
return `${method}:${url}`; return `${method}:${url}`;
} }
@ -114,17 +112,9 @@ module.exports = function(RED) {
* @returns * @returns
*/ */
function setupRawBodyCapture(req, _res, next) { function setupRawBodyCapture(req, _res, next) {
var routeKey = getRouteKey({method: req.method, url: req._parsedUrl.pathname}) 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(routeKey)) { if (rawDataRoutes.has(routeKey)) {
// Get the httpInNode by routeId
var httpInNode = httpInNodes.get(routeKey);
// If raw body inclusion is disabled, skip the processing
if (!httpInNode.includeRawBody) {
return next();
}
// Create a PassThrough stream to capture the request body // Create a PassThrough stream to capture the request body
var cloneStream = new PassThrough(); var cloneStream = new PassThrough();
@ -159,7 +149,7 @@ module.exports = function(RED) {
// Remove listeners once the request is closed // Remove listeners once the request is closed
.once('close', () => { .once('close', () => {
req.removeListener('data', onData); req.removeListener('data', onData);
}) });
// Attach the clone stream to the request // Attach the clone stream to the request
Object.defineProperty(req, "_nodeRedReqStream", { Object.defineProperty(req, "_nodeRedReqStream", {
@ -172,14 +162,10 @@ module.exports = function(RED) {
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
rootApp._router.stack.unshift(rootApp._router.stack.pop()) rootApp._router.stack.unshift(rootApp._router.stack.pop());
}
function createRequestWrapper(node,req) { function createRequestWrapper(node,req) {
// This misses a bunch of properties (eg headers). Before we use this function // This misses a bunch of properties (eg headers). Before we use this function
@ -301,6 +287,12 @@ module.exports = function(RED) {
this.swaggerDoc = n.swaggerDoc; this.swaggerDoc = n.swaggerDoc;
var node = this; var node = this;
var routeKey = getRouteKey({method: this.method, url: RED.httpNode.path() + this.url});
// If the user enables raw body, add it to the raw data routes.
if(this.includeRawBody) {
rawDataRoutes.add(routeKey);
}
this.errorHandler = function(err,req,res,next) { this.errorHandler = function(err,req,res,next) {
node.warn(err); node.warn(err);
@ -383,14 +375,10 @@ 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);
} }
var routeKey = getRouteKey({method: this.method, url: RED.httpNode.path() + this.url})
var httpInNodes = rootApp.get('httpInNodes')
httpInNodes.set(routeKey, this);
this.on("close",function() { this.on("close",function() {
httpInNodes.delete(routeKey) // Remove it from the raw data routes if the node is closed
rawDataRoutes.delete(routeKey);
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]) {