2 * Copyright 2013,2014 IBM Corp.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 module.exports = function(RED) {
19 var http = require("follow-redirects").http;
20 var https = require("follow-redirects").https;
21 var urllib = require("url");
22 var express = require("express");
23 var getBody = require('raw-body');
24 var mustache = require("mustache");
25 var querystring = require("querystring");
27 var cors = require('cors');
28 var jsonParser = express.json();
29 var urlencParser = express.urlencoded();
31 function rawBodyParser(req, res, next) {
32 if (req._body) { return next(); }
37 length: req.headers['content-length'],
39 }, function (err, buf) {
40 if (err) { return next(err); }
48 RED.nodes.createNode(this,n);
49 if (RED.settings.httpNodeRoot !== false) {
52 this.method = n.method;
56 this.errorHandler = function(err,req,res,next) {
61 this.callback = function(req,res) {
62 if (node.method == "post") {
63 node.send({req:req,res:res,payload:req.body});
64 } else if (node.method == "get") {
65 node.send({req:req,res:res,payload:req.query});
67 node.send({req:req,res:res});
71 var corsHandler = function(req,res,next) { next(); }
73 if (RED.settings.httpNodeCors) {
74 corsHandler = cors(RED.settings.httpNodeCors);
75 RED.httpNode.options(this.url,corsHandler);
78 if (this.method == "get") {
79 RED.httpNode.get(this.url,corsHandler,this.callback,this.errorHandler);
80 } else if (this.method == "post") {
81 RED.httpNode.post(this.url,corsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
82 } else if (this.method == "put") {
83 RED.httpNode.put(this.url,corsHandler,jsonParser,urlencParser,rawBodyParser,this.callback,this.errorHandler);
84 } else if (this.method == "delete") {
85 RED.httpNode.delete(this.url,corsHandler,this.callback,this.errorHandler);
88 this.on("close",function() {
89 var routes = RED.httpNode.routes[this.method];
90 for (var i = 0; i<routes.length; i++) {
91 if (routes[i].path == this.url) {
96 if (RED.settings.httpNodeCors) {
97 var route = RED.httpNode.route['options'];
98 for (var j = 0; j<route.length; j++) {
99 if (route[j].path == this.url) {
107 this.warn("Cannot create http-in node when httpNodeRoot set to false");
110 RED.nodes.registerType("http in",HTTPIn);
113 function HTTPOut(n) {
114 RED.nodes.createNode(this,n);
116 this.on("input",function(msg) {
119 msg.res.set(msg.headers);
121 var statusCode = msg.statusCode || 200;
122 if (typeof msg.payload == "object" && !Buffer.isBuffer(msg.payload)) {
123 msg.res.jsonp(statusCode,msg.payload);
125 if (msg.res.get('content-length') == null) {
127 if (msg.payload == null) {
129 } else if (typeof msg.payload == "number") {
130 len = Buffer.byteLength(""+msg.payload);
132 len = Buffer.byteLength(msg.payload);
134 msg.res.set('content-length', len);
136 msg.res.send(statusCode,msg.payload);
139 node.warn("No response object");
143 RED.nodes.registerType("http response",HTTPOut);
145 function HTTPRequest(n) {
146 RED.nodes.createNode(this,n);
148 var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
149 var nodeMethod = n.method || "GET";
151 this.on("input",function(msg) {
152 node.status({fill:"blue",shape:"dot",text:"requesting"});
156 } else if (isTemplatedUrl) {
157 url = mustache.render(nodeUrl,msg);
161 // url must start http:// or https:// so assume http:// if not set
162 if (!((url.indexOf("http://")===0) || (url.indexOf("https://")===0))) {
166 var method = (msg.method||nodeMethod).toUpperCase();
167 //node.log(method+" : "+url);
168 var opts = urllib.parse(url);
169 opts.method = method;
172 for (var v in msg.headers) {
173 if (msg.headers.hasOwnProperty(v)) {
174 var name = v.toLowerCase();
175 if (name !== "content-type" && name !== "content-length") {
176 // only normalise the known headers used later in this
177 // function. Otherwise leave them alone.
180 opts.headers[name] = msg.headers[v];
184 if (this.credentials && this.credentials.user) {
185 opts.auth = this.credentials.user+":"+(this.credentials.password||"");
189 if (msg.payload && (method == "POST" || method == "PUT") ) {
190 if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
191 payload = msg.payload;
192 } else if (typeof msg.payload == "number") {
193 payload = msg.payload+"";
195 if (opts.headers['content-type'] == 'application/x-www-form-urlencoded') {
196 payload = querystring.stringify(msg.payload);
198 payload = JSON.stringify(msg.payload);
199 if (opts.headers['content-type'] == null) {
200 opts.headers['content-type'] = "application/json";
204 if (opts.headers['content-length'] == null) {
205 opts.headers['content-length'] = Buffer.byteLength(payload);
209 var req = ((/^https/.test(url))?https:http).request(opts,function(res) {
210 res.setEncoding('utf8');
211 msg.statusCode = res.statusCode;
212 msg.headers = res.headers;
214 res.on('data',function(chunk) {
215 msg.payload += chunk;
217 res.on('end',function() {
222 req.on('error',function(err) {
223 msg.payload = err.toString() + " : " + url;
224 msg.statusCode = err.code;
226 node.status({fill:"red",shape:"ring",text:err.code});
235 RED.nodes.registerType("http request",HTTPRequest,{
238 password: {type: "password"}