mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Add middleware to capture raw request body for HTTP nodes
This commit is contained in:
parent
57ccd1e15f
commit
aaa0467ba0
@ -26,6 +26,7 @@ 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
|
||||||
|
|
||||||
function rawBodyParser(req, _res, next) {
|
function rawBodyParser(req, _res, next) {
|
||||||
if(!req._nodeRedReqStream) {
|
if(!req._nodeRedReqStream) {
|
||||||
@ -70,6 +71,85 @@ module.exports = function(RED) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRootRouter(app) {
|
||||||
|
if (typeof app.parent === 'undefined') {
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
return getRootRouter(app.parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRouteId(req) {
|
||||||
|
var method = req.method.toLowerCase(), url = req.url;
|
||||||
|
return `${method}:${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootApp = getRootRouter(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
|
||||||
|
function setupRawBodyCapture(req, _res, next) {
|
||||||
|
var httpInNodeId = createRouteId(req)
|
||||||
|
|
||||||
|
// Check if settings for this ID exist
|
||||||
|
if (httpInNodes.has(httpInNodeId)) {
|
||||||
|
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 passThrough = new PassThrough();
|
||||||
|
|
||||||
|
// Function to handle 'data' event
|
||||||
|
function onData(chunk) {
|
||||||
|
passThrough.write(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to handle 'end' or 'error' events
|
||||||
|
function onEnd(err) {
|
||||||
|
if (err) {
|
||||||
|
passThrough.destroy(err);
|
||||||
|
} else {
|
||||||
|
passThrough.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach event listeners to the request stream
|
||||||
|
req.on('data', onData);
|
||||||
|
req.on('end', onEnd);
|
||||||
|
req.on('error', onEnd);
|
||||||
|
|
||||||
|
// Remove listeners once the request is closed
|
||||||
|
req.once('close', function () {
|
||||||
|
req.removeListener('data', onData);
|
||||||
|
req.removeListener('end', onEnd);
|
||||||
|
req.removeListener('error', onEnd);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attach the passThrough stream to the request
|
||||||
|
Object.defineProperty(req, "_nodeRedReqStream", {
|
||||||
|
value: passThrough
|
||||||
|
});
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed to the next middleware if no settings found
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
// Add middleware to the stack
|
||||||
|
rootApp.use(setupRawBodyCapture)
|
||||||
|
// Move the router to top of the stack
|
||||||
|
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
|
||||||
// need to ensure it captures everything documented by Express and HTTP modules.
|
// need to ensure it captures everything documented by Express and HTTP modules.
|
||||||
@ -122,6 +202,7 @@ module.exports = function(RED) {
|
|||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createResponseWrapper(node,res) {
|
function createResponseWrapper(node,res) {
|
||||||
var wrapper = {
|
var wrapper = {
|
||||||
_res: res
|
_res: res
|
||||||
@ -272,10 +353,10 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unique id for httpInNodeSettings based on method name and url path
|
// unique id for httpInNodeSettings based on method name and url path
|
||||||
var httpInNodeId = `${this.method.toLowerCase()}:${this.url}`
|
var httpInNodeId = createRouteId({url: this.url, method: this.method})
|
||||||
|
|
||||||
// get httpInNodeSettings from RED.httpNode
|
// get httpInNodeSettings from RED.httpNode
|
||||||
var httpInNodes = RED.httpNode.get('httpInNodes')
|
var httpInNodes = rootApp.get('httpInNodes')
|
||||||
|
|
||||||
// Add httpInNode to RED.httpNode
|
// Add httpInNode to RED.httpNode
|
||||||
httpInNodes.set(httpInNodeId, this);
|
httpInNodes.set(httpInNodeId, this);
|
||||||
|
58
packages/node_modules/node-red/red.js
vendored
58
packages/node_modules/node-red/red.js
vendored
@ -287,65 +287,9 @@ httpsPromise.then(function(startupHttps) {
|
|||||||
} else {
|
} else {
|
||||||
server = http.createServer(function(req,res) {app(req,res);});
|
server = http.createServer(function(req,res) {app(req,res);});
|
||||||
}
|
}
|
||||||
|
|
||||||
server.setMaxListeners(0);
|
server.setMaxListeners(0);
|
||||||
|
|
||||||
var httpInNodes = new Map()
|
|
||||||
|
|
||||||
app.set('httpInNodes', httpInNodes);
|
|
||||||
|
|
||||||
// This middleware must always be at the root to handle the raw request body correctly.
|
|
||||||
app.use(function(req, _res, next) {
|
|
||||||
var method = req.method.toLowerCase(), url = req.url;
|
|
||||||
var httpInNodeId = `${method}:${url}`
|
|
||||||
|
|
||||||
// Check if settings for this ID exist
|
|
||||||
if (httpInNodes.has(httpInNodeId)) {
|
|
||||||
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
|
|
||||||
const passThrough = new PassThrough();
|
|
||||||
|
|
||||||
// Function to handle 'data' event
|
|
||||||
function onData(chunk) {
|
|
||||||
passThrough.write(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to handle 'end' or 'error' events
|
|
||||||
function onEnd(err) {
|
|
||||||
if (err) {
|
|
||||||
passThrough.destroy(err);
|
|
||||||
} else {
|
|
||||||
passThrough.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach event listeners to the request stream
|
|
||||||
req.on('data', onData);
|
|
||||||
req.on('end', onEnd);
|
|
||||||
req.on('error', onEnd);
|
|
||||||
|
|
||||||
// Remove listeners once the request is closed
|
|
||||||
req.once('close', function() {
|
|
||||||
req.removeListener('data', onData);
|
|
||||||
req.removeListener('end', onEnd);
|
|
||||||
req.removeListener('error', onEnd);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Attach the passThrough stream to the request
|
|
||||||
Object.defineProperty(req, "_nodeRedReqStream", {
|
|
||||||
value: passThrough
|
|
||||||
});
|
|
||||||
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proceed to the next middleware if no settings found
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
|
|
||||||
function formatRoot(root) {
|
function formatRoot(root) {
|
||||||
if (root[0] != "/") {
|
if (root[0] != "/") {
|
||||||
root = "/" + root;
|
root = "/" + root;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user