diff --git a/nodes/core/io/21-httpin.js b/nodes/core/io/21-httpin.js index 76c10a7bf..d71a7fa29 100644 --- a/nodes/core/io/21-httpin.js +++ b/nodes/core/io/21-httpin.js @@ -26,6 +26,7 @@ module.exports = function(RED) { var onHeaders = require('on-headers'); var typer = require('media-typer'); var isUtf8 = require('is-utf8'); + var hashSum = require("hash-sum"); function rawBodyParser(req, res, next) { if (req.skipRawBodyParser) { next(); } // don't parse this if told to skip @@ -277,9 +278,19 @@ module.exports = function(RED) { if (msg.res) { var headers = RED.util.cloneMessage(node.headers); if (msg.headers) { - for (var h in msg.headers) { - if (msg.headers.hasOwnProperty(h) && !headers.hasOwnProperty(h)) { - headers[h] = msg.headers[h]; + if (msg.headers.hasOwnProperty('x-node-red-request-node')) { + var headerHash = msg.headers['x-node-red-request-node']; + delete msg.headers['x-node-red-request-node']; + var hash = hashSum(msg.headers); + if (hash === headerHash) { + delete msg.headers; + } + } + if (msg.headers) { + for (var h in msg.headers) { + if (msg.headers.hasOwnProperty(h) && !headers.hasOwnProperty(h)) { + headers[h] = msg.headers[h]; + } } } } diff --git a/nodes/core/io/21-httprequest.html b/nodes/core/io/21-httprequest.html index 7beffe261..b910b6bd6 100644 --- a/nodes/core/io/21-httprequest.html +++ b/nodes/core/io/21-httprequest.html @@ -108,6 +108,12 @@ example.com/{{{topic}}}, it will have the value of msg.topic automatically inserted. Using {{{...}}} prevents mustache from escaping characters like / & etc.

Note: If running behind a proxy, the standard http_proxy=... environment variable should be set and Node-RED restarted.

+

Using multiple HTTP Request nodes

+

In order to use more than one of these nodes in the same flow, care must be taken with + the msg.headers property. The first node will set this property with + the response headers. The next node will then use those headers for its request - this + is not usually the right thing to do. The msg.headers property must + be deleted with a change node in order for the requests to work as expected.

Cookie handling

The cookies property passed to the node must be an object of name/value pairs. The value can be either a string to set the value of the cookie or it can be an diff --git a/nodes/core/io/21-httprequest.js b/nodes/core/io/21-httprequest.js index f38b8eecc..bc8c16a06 100644 --- a/nodes/core/io/21-httprequest.js +++ b/nodes/core/io/21-httprequest.js @@ -22,6 +22,7 @@ module.exports = function(RED) { var mustache = require("mustache"); var querystring = require("querystring"); var cookie = require("cookie"); + var hashSum = require("hash-sum"); function HTTPRequest(n) { RED.nodes.createNode(this,n); @@ -83,17 +84,27 @@ module.exports = function(RED) { var ctSet = "Content-Type"; // set default camel case var clSet = "Content-Length"; if (msg.headers) { - for (var v in msg.headers) { - if (msg.headers.hasOwnProperty(v)) { - var name = v.toLowerCase(); - if (name !== "content-type" && name !== "content-length") { - // only normalise the known headers used later in this - // function. Otherwise leave them alone. - name = v; + if (msg.headers.hasOwnProperty('x-node-red-request-node')) { + var headerHash = msg.headers['x-node-red-request-node']; + delete msg.headers['x-node-red-request-node']; + var hash = hashSum(msg.headers); + if (hash === headerHash) { + delete msg.headers; + } + } + if (msg.headers) { + for (var v in msg.headers) { + if (msg.headers.hasOwnProperty(v)) { + var name = v.toLowerCase(); + if (name !== "content-type" && name !== "content-length") { + // only normalise the known headers used later in this + // function. Otherwise leave them alone. + name = v; + } + else if (name === 'content-type') { ctSet = v; } + else { clSet = v; } + opts.headers[name] = msg.headers[v]; } - else if (name === 'content-type') { ctSet = v; } - else { clSet = v; } - opts.headers[name] = msg.headers[v]; } } } @@ -207,7 +218,7 @@ module.exports = function(RED) { }) } - + msg.headers['x-node-red-request-node'] = hashSum(msg.headers); // msg.url = url; // revert when warning above finally removed res.on('data',function(chunk) { if (!Buffer.isBuffer(chunk)) { diff --git a/package.json b/package.json index 19277f5c8..f5d21cdc1 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "follow-redirects":"1.2.4", "fs-extra": "1.0.0", "fs.notify":"0.0.4", + "hash-sum":"1.0.2", "i18next":"1.10.6", "is-utf8":"0.2.1", "js-yaml": "3.8.4", @@ -48,7 +49,7 @@ "mqtt": "2.9.0", "multer": "1.3.0", "mustache": "2.3.0", - "nopt": "4.0.1", + "nopt": "3.0.6", "oauth2orize":"1.8.0", "on-headers":"1.0.1", "passport":"0.3.2", diff --git a/test/nodes/core/io/21-httprequest_spec.js b/test/nodes/core/io/21-httprequest_spec.js index 942ff1890..1d2dc5d71 100644 --- a/test/nodes/core/io/21-httprequest_spec.js +++ b/test/nodes/core/io/21-httprequest_spec.js @@ -21,6 +21,7 @@ var express = require("express"); var bodyParser = require('body-parser'); var helper = require("../../helper.js"); var httpRequestNode = require("../../../../nodes/core/io/21-httprequest.js"); +var hashSum = require("hash-sum"); describe('HTTP Request Node', function() { var testApp; @@ -399,4 +400,46 @@ describe('HTTP Request Node', function() { }); }) + it('ignores unmodified msg.headers property', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.payload.headers.should.have.property('content-type').which.startWith('application/json'); + msg.payload.headers.should.not.have.property('x-node-red-request-node'); + done(); + } catch(err) { + done(err); + } + }); + // Pass in a headers property with an unmodified x-node-red-request-node hash + // This should cause the node to ignore the headers + n1.receive({payload:{foo:"bar"}, headers: { 'content-type': 'text/plain', "x-node-red-request-node":"67690139"}}); + }); + }) + + it('uses modified msg.headers property', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); + msg.payload.headers.should.not.have.property('x-node-red-request-node'); + done(); + } catch(err) { + done(err); + } + }); + // Pass in a headers property with a x-node-red-request-node hash that doesn't match the contents + // This should cause the node to use the headers + n1.receive({payload:{foo:"bar"}, headers: { 'content-type': 'text/plain', "x-node-red-request-node":"INVALID_SUM"}}); + }); + }) + });