From 3d70bc722ae270a85cc74d8b14955beeea203a76 Mon Sep 17 00:00:00 2001 From: Osamu Katada Date: Wed, 3 Oct 2018 09:58:25 +0900 Subject: [PATCH] Add http-proxy for http-request node. --- .../@node-red/nodes/core/io/06-httpproxy.html | 136 ++++++++++++++++++ .../@node-red/nodes/core/io/06-httpproxy.js | 33 +++++ .../nodes/core/io/21-httprequest.html | 33 ++++- .../@node-red/nodes/core/io/21-httprequest.js | 17 +++ .../nodes/locales/en-US/messages.json | 4 + .../nodes/locales/ja/io/06-httpproxy.html | 22 +++ .../nodes/locales/ja/io/21-httprequest.html | 2 +- .../@node-red/nodes/locales/ja/messages.json | 4 + test/nodes/core/io/21-httprequest_spec.js | 132 +++++++++++++++++ 9 files changed, 380 insertions(+), 3 deletions(-) create mode 100644 packages/node_modules/@node-red/nodes/core/io/06-httpproxy.html create mode 100644 packages/node_modules/@node-red/nodes/core/io/06-httpproxy.js create mode 100644 packages/node_modules/@node-red/nodes/locales/ja/io/06-httpproxy.html diff --git a/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.html b/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.html new file mode 100644 index 000000000..e4d03d1db --- /dev/null +++ b/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.html @@ -0,0 +1,136 @@ + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.js b/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.js new file mode 100644 index 000000000..abcee66f6 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/core/io/06-httpproxy.js @@ -0,0 +1,33 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +module.exports = function(RED) { + 'use strict'; + + function HTTPProxyConfig(n) { + RED.nodes.createNode(this, n); + this.name = n.name; + this.url = n.url; + this.noproxy = n.noproxy; + }; + + RED.nodes.registerType('http proxy', HTTPProxyConfig, { + credentials: { + username: {type:'text'}, + password: {type:'password'} + } + }); +}; diff --git a/packages/node_modules/@node-red/nodes/core/io/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/io/21-httprequest.html index f3c4c0228..e0d1f5ba2 100644 --- a/packages/node_modules/@node-red/nodes/core/io/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/io/21-httprequest.html @@ -53,6 +53,13 @@ +
+ + +
+ +
+
@@ -112,7 +119,7 @@ url to be constructed using values of the incoming message. For example, if the url is set to 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.

+

Note: If running behind a proxy, the standard http_proxy=... environment variable should be set and Node-RED restarted, or use Proxy Configuration. If Proxy Configuration was set, the configuration take precedence over environment variable.

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 @@ -141,7 +148,8 @@ method:{value:"GET"}, ret: {value:"txt"}, url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} }, - tls: {type:"tls-config",required: false} + tls: {type:"tls-config",required: false}, + proxy: {type:"http proxy",required: false} }, credentials: { user: {type:"text"}, @@ -192,6 +200,24 @@ $("#node-input-usetls").on("click",function() { updateTLSOptions(); }); + + function updateProxyOptions() { + if ($("#node-input-useProxy").is(":checked")) { + $("#node-input-useProxy-row").show(); + } else { + $("#node-input-useProxy-row").hide(); + } + } + if (this.proxy) { + $("#node-input-useProxy").prop("checked", true); + } else { + $("#node-input-useProxy").prop("checked", false); + } + updateProxyOptions(); + $("#node-input-useProxy").on("click", function() { + updateProxyOptions(); + }); + $("#node-input-ret").change(function() { if ($("#node-input-ret").val() === "obj") { $("#tip-json").show(); @@ -204,6 +230,9 @@ if (!$("#node-input-usetls").is(':checked')) { $("#node-input-tls").val("_ADD_"); } + if (!$("#node-input-useProxy").is(":checked")) { + $("#node-input-proxy").val("_ADD_"); + } } }); diff --git a/packages/node_modules/@node-red/nodes/core/io/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/io/21-httprequest.js index 28d67d077..59f4f8fdc 100644 --- a/packages/node_modules/@node-red/nodes/core/io/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/io/21-httprequest.js @@ -41,6 +41,13 @@ module.exports = function(RED) { if (process.env.no_proxy != null) { noprox = process.env.no_proxy.split(","); } if (process.env.NO_PROXY != null) { noprox = process.env.NO_PROXY.split(","); } + var proxyConfig = null; + if (n.proxy) { + proxyConfig = RED.nodes.getNode(n.proxy); + prox = proxyConfig.url; + noprox = proxyConfig.noproxy; + } + this.on("input",function(msg) { var preRequestTimestamp = process.hrtime(); node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"}); @@ -84,6 +91,7 @@ module.exports = function(RED) { opts.encoding = null; // Force NodeJs to return a Buffer (instead of a string) opts.maxRedirects = 21; opts.jar = request.jar(); + opts.proxy = null; var ctSet = "Content-Type"; // set default camel case var clSet = "Content-Length"; if (msg.headers) { @@ -206,6 +214,15 @@ module.exports = function(RED) { opts.proxy = null; } } + if (proxyConfig && proxyConfig.credentials && opts.proxy == proxyConfig.url) { + var proxyUsername = proxyConfig.credentials.username || ''; + var proxyPassword = proxyConfig.credentials.password || ''; + if (proxyUsername || proxyPassword) { + opts.headers['proxy-authorization'] = + 'Basic ' + + Buffer.from(proxyUsername + ':' + proxyPassword).toString('base64'); + } + } if (tlsNode) { tlsNode.addTLSOptions(opts); } else { 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 a8f9490f0..65894d80c 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 @@ -383,6 +383,10 @@ "basicauth": "Use basic authentication", "use-tls": "Enable secure (SSL/TLS) connection", "tls-config":"TLS Configuration", + "use-proxy": "Use proxy", + "proxy-config": "Proxy Configuration", + "use-proxyauth": "Use proxy authentication", + "noproxy-hosts": "Ignore hosts", "utf8": "a UTF-8 string", "binary": "a binary buffer", "json": "a parsed JSON object", diff --git a/packages/node_modules/@node-red/nodes/locales/ja/io/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/ja/io/06-httpproxy.html new file mode 100644 index 000000000..03beddb8d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/ja/io/06-httpproxy.html @@ -0,0 +1,22 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/ja/io/21-httprequest.html b/packages/node_modules/@node-red/nodes/locales/ja/io/21-httprequest.html index 662367343..a22be8f3d 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/io/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/io/21-httprequest.html @@ -49,7 +49,7 @@

詳細

ノードの設定でurlプロパティを指定する場合、mustache形式のタグを含めることができます。これにより、URLを入力メッセージの値から構成することができます。例えば、urlがexample.com/{{{topic}}}の場合、msg.topicの値による置き換えを自動的に行います。{{{...}}}表記を使うと、/、&といった文字をmustacheがエスケープするのを抑止できます。

-

: proxyサーバを利用している場合、環境変数http_proxy=...を設定して、Node-REDを再起動してください。

+

: proxyサーバを利用している場合、環境変数http_proxy=...を設定して、Node-REDを再起動するか、あるいはプロキシ設定をしてください。

複数のHTTPリクエストノードの利用

同一フローで本ノードを複数利用するためには、msg.headersプロパティの扱いに注意しなくてはなりません。例えば、最初のノードがレスポンスヘッダにこのプロパティを設定し、次のノードがこのプロパティをリクエストヘッダに利用するというのは一般的には期待する動作ではありません。msg.headersプロパティをノード間で変更しないままとすると、2つ目のノードで無視されることになります。カスタムヘッダを設定するためには、msg.headersをまず削除もしくは空のオブジェクト{}にリセットします。

クッキーの扱い

diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index f160164d3..9f5102eb3 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -383,6 +383,10 @@ "basicauth": "ベーシック認証を使用", "use-tls": "SSL/TLS接続を有効化", "tls-config": "TLS設定", + "use-proxy": "プロキシを使用", + "proxy-config": "プロキシ設定", + "use-proxyauth": "プロキシ認証を使用", + "noproxy-hosts": "例外ホスト", "utf8": "文字列", "binary": "バイナリバッファ", "json": "JSON", diff --git a/test/nodes/core/io/21-httprequest_spec.js b/test/nodes/core/io/21-httprequest_spec.js index e7e8625b3..1633d6c38 100644 --- a/test/nodes/core/io/21-httprequest_spec.js +++ b/test/nodes/core/io/21-httprequest_spec.js @@ -24,6 +24,7 @@ var stoppable = require('stoppable'); var helper = require("node-red-node-test-helper"); var httpRequestNode = require("nr-test-utils").require("@node-red/nodes/core/io/21-httprequest.js"); var tlsNode = require("nr-test-utils").require("@node-red/nodes/core/io/05-tls.js"); +var httpProxyNode = require("nr-test-utils").require("@node-red/nodes/core/io/06-httpproxy.js"); var hashSum = require("hash-sum"); var httpProxy = require('http-proxy'); var cookieParser = require('cookie-parser'); @@ -1194,6 +1195,80 @@ describe('HTTP Request Node', function() { n1.receive({payload:"foo"}); }); }); + + it('should use http-proxy-config', function(done) { + var flow = [ + {id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect'),proxy:"n3"}, + {id:"n2",type:"helper"}, + {id:"n3",type:"http proxy",url:"http://localhost:" + testProxyPort} + ]; + var testNode = [ httpRequestNode, httpProxyNode ]; + deleteProxySetting(); + helper.load(testNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should not use http-proxy-config when invalid url is specified', function(done) { + var flow = [ + {id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect'),proxy:"n3"}, + {id:"n2", type:"helper"}, + {id:"n3",type:"http proxy",url:"invalidvalue"} + ]; + var testNode = [ httpRequestNode, httpProxyNode ]; + deleteProxySetting(); + helper.load(testNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.not.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should use http-proxy-config when valid noproxy is specified', function(done) { + var flow = [ + {id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect'),proxy:"n3"}, + {id:"n2", type:"helper"}, + {id:"n3",type:"http proxy",url:"http://localhost:" + testProxyPort,noproxy:["foo","localhost"]} + ]; + var testNode = [ httpRequestNode, httpProxyNode ]; + deleteProxySetting(); + helper.load(testNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.headers.should.not.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); }); describe('authentication', function() { @@ -1264,6 +1339,63 @@ describe('HTTP Request Node', function() { n1.receive({payload:"foo"}); }); }); + + it('should authenticate on proxy server(http-proxy-config)', function(done) { + var flow = [ + {id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate'),proxy:"n3"}, + {id:"n2", type:"helper"}, + {id:"n3",type:"http proxy",url:"http://localhost:" + testProxyPort} + ]; + var testNode = [ httpRequestNode, httpProxyNode ]; + deleteProxySetting(); + helper.load(testNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + n3.credentials = {username:'foouser', password:'barpassword'}; + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('user', 'foouser'); + msg.payload.should.have.property('pass', 'barpassword'); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should output an error when proxy authentication was failed(http-proxy-config)', function(done) { + var flow = [ + {id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate'),proxy:"n3"}, + {id:"n2", type:"helper"}, + {id:"n3",type:"http proxy",url:"http://@localhost:" + testProxyPort} + ]; + var testNode = [ httpRequestNode, httpProxyNode ]; + deleteProxySetting(); + helper.load(testNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + n3.credentials = {username:'xxxuser', password:'barpassword'}; + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',407); + msg.headers.should.have.property('proxy-authenticate', 'BASIC realm="test"'); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); }); describe('redirect-cookie', function() {