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:
parent
9d811c7bce
commit
9a26ee3052
@ -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,87 +94,87 @@ 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)
|
/**
|
||||||
|
* This middleware is for clone the request stream
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} _res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function setupRawBodyCapture(req, _res, next) {
|
||||||
|
var routeKey = getRouteKey({method: req.method, url: req._parsedUrl.pathname})
|
||||||
|
var httpInNodes = rootApp.get('httpInNodes')
|
||||||
|
// Check if settings for this ID exist
|
||||||
|
if (httpInNodes.has(routeKey)) {
|
||||||
|
// Get the httpInNode by routeId
|
||||||
|
var httpInNode = httpInNodes.get(routeKey);
|
||||||
|
|
||||||
// Check if middleware already exists
|
// If raw body inclusion is disabled, skip the processing
|
||||||
var isMiddlewarePresent = rootApp._router.stack.some(layer => layer.name === 'setupRawBodyCapture')
|
if (!httpInNode.includeRawBody) {
|
||||||
|
|
||||||
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
|
|
||||||
* @param {import('express').Request} req
|
|
||||||
* @param {import('express').Response} _res
|
|
||||||
* @param {import('express').NextFunction} next
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function setupRawBodyCapture(req, _res, next) {
|
|
||||||
var httpInNodeId = getRouteId(req)
|
|
||||||
|
|
||||||
// Check if settings for this ID exist
|
|
||||||
if (httpInNodes.has(httpInNodeId)) {
|
|
||||||
// Get the httpInNode by routeId
|
|
||||||
var httpInNode = httpInNodes.get(httpInNodeId);
|
|
||||||
|
|
||||||
// If raw body inclusion is disabled, skip the processing
|
|
||||||
if (!httpInNode.includeRawBody) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a PassThrough stream to capture the request body
|
|
||||||
var cloneStream = new PassThrough();
|
|
||||||
|
|
||||||
// Function to handle 'data' event
|
|
||||||
function onData(chunk) {
|
|
||||||
// Safely call clone stream write
|
|
||||||
if (cloneStream.writable) {
|
|
||||||
cloneStream.write(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to handle 'end' or 'error' events
|
|
||||||
function onEnd(err) {
|
|
||||||
if (err) {
|
|
||||||
// Safely call clone stream destroy method
|
|
||||||
if (!cloneStream.destroyed) {
|
|
||||||
cloneStream.destroy(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Safely call clone stream end method
|
|
||||||
if(cloneStream.writable) {
|
|
||||||
cloneStream.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach event listeners to the request stream
|
|
||||||
req.on('data', onData)
|
|
||||||
.once('end', onEnd)
|
|
||||||
.once('error', onEnd)
|
|
||||||
// Remove listeners once the request is closed
|
|
||||||
.once('close', () => {
|
|
||||||
req.removeListener('data', onData);
|
|
||||||
})
|
|
||||||
|
|
||||||
// Attach the clone stream to the request
|
|
||||||
Object.defineProperty(req, "_nodeRedReqStream", {
|
|
||||||
value: cloneStream
|
|
||||||
});
|
|
||||||
// Proceed to the next middleware if no settings found
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a PassThrough stream to capture the request body
|
||||||
|
var cloneStream = new PassThrough();
|
||||||
|
|
||||||
|
// Function to handle 'data' event
|
||||||
|
function onData(chunk) {
|
||||||
|
// Safely call clone stream write
|
||||||
|
if (cloneStream.writable) {
|
||||||
|
cloneStream.write(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to handle 'end' or 'error' events
|
||||||
|
function onEnd(err) {
|
||||||
|
if (err) {
|
||||||
|
// Safely call clone stream destroy method
|
||||||
|
if (!cloneStream.destroyed) {
|
||||||
|
cloneStream.destroy(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Safely call clone stream end method
|
||||||
|
if (cloneStream.writable) {
|
||||||
|
cloneStream.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach event listeners to the request stream
|
||||||
|
req.on('data', onData)
|
||||||
|
.once('end', onEnd)
|
||||||
|
.once('error', onEnd)
|
||||||
|
// Remove listeners once the request is closed
|
||||||
|
.once('close', () => {
|
||||||
|
req.removeListener('data', onData);
|
||||||
|
})
|
||||||
|
|
||||||
|
// Attach the clone stream to the request
|
||||||
|
Object.defineProperty(req, "_nodeRedReqStream", {
|
||||||
|
value: cloneStream
|
||||||
|
});
|
||||||
// Proceed to the next middleware if no settings found
|
// Proceed to the next middleware if no settings found
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
// Proceed to the next middleware if no settings found
|
||||||
|
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]) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user