mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Refactor raw body capture middleware and improve route key generation for HTTP nodes
This commit is contained in:
		| @@ -27,9 +27,20 @@ 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(); | ||||||
|         } |         } | ||||||
|         var isText = true, checkUTF = false; |         var isText = true, checkUTF = false; | ||||||
| @@ -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) { | ||||||
| @@ -134,7 +146,7 @@ module.exports = function(RED) { | |||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     // Safely call clone stream end method |                     // Safely call clone stream end method | ||||||
|                         if(cloneStream.writable) { |                     if (cloneStream.writable) { | ||||||
|                         cloneStream.end(); |                         cloneStream.end(); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| @@ -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]) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user