diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.html b/packages/node_modules/@node-red/nodes/core/network/21-httpin.html index f828077a1..59c2bfb6f 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.html @@ -25,11 +25,22 @@ -
+ +
- - +
+
+ + +
+
+ + +
+
+ +
@@ -74,6 +85,7 @@ label:RED._("node-red:httpin.label.url")}, method: {value:"get",required:true}, upload: {value:false}, + skipBodyParsing: {value:false}, swaggerDoc: {type:"swagger-doc", required:false} }, inputs:0, @@ -115,16 +127,22 @@ $('.row-swagger-doc').hide(); } $("#node-input-method").on("change", function() { - if ($(this).val() === "post") { - $(".form-row-http-in-upload").show(); + var method = $(this).val(); + if(["post", "put", "patch","delete"].includes(method)){ + $("#form-reqBody-http-in-controller").show(); + $("#form-row-http-in-parsing").show(); + if (method === "post") { + $("#form-row-http-in-upload").show(); + } else { + $("#form-row-http-in-upload").hide(); + } } else { - $(".form-row-http-in-upload").hide(); + $("#form-row-http-in-parsing").hide(); + $("#form-row-http-in-upload").hide(); + $("#form-reqBody-http-in-controller").hide(); } }).change(); - - } - }); var headerTypes = [ {value:"content-type",label:"Content-Type",hasValue: false}, diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index 22e83b411..060d479b6 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -16,6 +16,7 @@ module.exports = function(RED) { "use strict"; + var rootApp; var bodyParser = require("body-parser"); var multer = require("multer"); var cookieParser = require("cookie-parser"); @@ -26,6 +27,7 @@ module.exports = function(RED) { var mediaTyper = require('media-typer'); var isUtf8 = require('is-utf8'); var hashSum = require("hash-sum"); + var rawDataRoutes = new Set(); function rawBodyParser(req, res, next) { if (req.skipRawBodyParser) { next(); } // don't parse this if told to skip @@ -71,7 +73,65 @@ module.exports = function(RED) { }); } - var corsSetup = false; + /** + * This method retrieves the root app by traversing the parent hierarchy. + * @param {import('express').Application} app + * @returns {import('express').Application} + */ + function getRootApp(app) { + if (typeof app.parent === 'undefined') { + return app; + } + return getRootApp(app.parent); + } + + /** + * 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}`; + } + + /** + * This middleware is for capture raw body + * @param {import('express').Request} req + * @param {import('express').Response} _res + * @param {import('express').NextFunction} next + * @returns + */ + function rawBodyCapture(req, _res, next) { + var routeKey = getRouteKey({ method: req.method, url: req._parsedUrl.pathname }); + // Check if routeKey exist in rawDataRoutes + if (rawDataRoutes.has(routeKey)) { + // Convert the request stream to buffer + getBody(req, { + length: req.headers['content-length'], + }, function (err, buf) { + if (err) { + return next(err); + } + req.body = buf; + // Skip the body parsing + req.skipRawBodyParser = true; + req._body = true; + next(); + }) + } else { + next(); + } + } + + if(typeof RED.httpNode === 'function' && (rootApp = getRootApp(RED.httpNode))) { + // Add middleware to the stack + rootApp.use(rawBodyCapture); + // Move the middleware to top of the stack + rootApp._router.stack.unshift(rootApp._router.stack.pop()); + } function createRequestWrapper(node,req) { // This misses a bunch of properties (eg headers). Before we use this function @@ -125,6 +185,7 @@ module.exports = function(RED) { return wrapper; } + function createResponseWrapper(node,res) { var wrapper = { _res: res @@ -188,9 +249,16 @@ module.exports = function(RED) { } this.method = n.method; this.upload = n.upload; + this.skipBodyParsing = n.skipBodyParsing; this.swaggerDoc = n.swaggerDoc; 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.skipBodyParsing) { + rawDataRoutes.add(routeKey); + } this.errorHandler = function(err,req,res,next) { node.warn(err); @@ -227,7 +295,9 @@ module.exports = function(RED) { } var maxApiRequestSize = RED.settings.apiMaxLength || '5mb'; + var jsonParser = bodyParser.json({limit:maxApiRequestSize}); + var urlencParser = bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}); var metricsHandler = function(req,res,next) { next(); } @@ -254,25 +324,27 @@ module.exports = function(RED) { var mp = multer({ storage: multer.memoryStorage() }).any(); multipartParser = function(req,res,next) { mp(req,res,function(err) { - req._body = true; next(err); }) }; } if (this.method == "get") { - RED.httpNode.get(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,this.callback,this.errorHandler); + RED.httpNode.get(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, this.callback, this.errorHandler); } else if (this.method == "post") { - RED.httpNode.post(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,multipartParser,rawBodyParser,this.callback,this.errorHandler); + RED.httpNode.post(this.url,cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, multipartParser, rawBodyParser, this.callback, this.errorHandler); } else if (this.method == "put") { - RED.httpNode.put(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler); + RED.httpNode.put(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler); } else if (this.method == "patch") { - RED.httpNode.patch(this.url,cookieParser(),httpMiddleware,corsHandler,metricsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler); + RED.httpNode.patch(this.url, cookieParser(), httpMiddleware, corsHandler, metricsHandler, jsonParser, urlencParser, rawBodyParser, this.callback, this.errorHandler); } else if (this.method == "delete") { - 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); } - + + this.on("close",function() { + // Remove it from the raw data routes if the node is closed + rawDataRoutes.delete(routeKey); var node = this; RED.httpNode._router.stack.forEach(function(route,i,routes) { if (route.route && route.route.path === node.url && route.route.methods[node.method]) { @@ -284,9 +356,9 @@ module.exports = function(RED) { this.warn(RED._("httpin.errors.not-created")); } } + RED.nodes.registerType("http in",HTTPIn); - function HTTPOut(n) { RED.nodes.createNode(this,n); var node = this; @@ -361,5 +433,6 @@ module.exports = function(RED) { done(); }); } + RED.nodes.registerType("http response",HTTPOut); } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 6d33e78aa..97fdf5571 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -516,6 +516,7 @@ "doc": "Docs", "return": "Return", "upload": "Accept file uploads?", + "parsing": "Skip body parsing?", "status": "Status code", "headers": "Headers", "other": "other", diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index d98b69f8f..af0c34fa7 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -66,6 +66,7 @@ var knownOpts = { "define": [String, Array], "no-telemetry": Boolean }; + var shortHands = { "?":["--help"], "p":["--port"], @@ -292,7 +293,6 @@ httpsPromise.then(function(startupHttps) { server = http.createServer(function(req,res) {app(req,res);}); } server.setMaxListeners(0); - function formatRoot(root) { if (root[0] != "/") { root = "/" + root;