From d3a1720619c32a1a1fe4a998bffbf7b92eb698ee Mon Sep 17 00:00:00 2001 From: TekMonksGitHub Date: Thu, 20 May 2021 15:12:58 +0900 Subject: [PATCH] Merge httpclient and rest.js from Monkshu. --- flows/pipeline_RESTtoSOAPtoRESTUsingURL.json | 42 +++ flows/pipeline_emailFileNotification.json | 2 +- install.sh.bat | 7 + lib/httpclient.js | 319 +++++++++---------- lib/rest.js | 226 +++++-------- routes/httpclient.js | 26 +- routes/rest.js | 27 +- 7 files changed, 322 insertions(+), 327 deletions(-) create mode 100644 flows/pipeline_RESTtoSOAPtoRESTUsingURL.json create mode 100644 install.sh.bat diff --git a/flows/pipeline_RESTtoSOAPtoRESTUsingURL.json b/flows/pipeline_RESTtoSOAPtoRESTUsingURL.json new file mode 100644 index 0000000..b85e603 --- /dev/null +++ b/flows/pipeline_RESTtoSOAPtoRESTUsingURL.json @@ -0,0 +1,42 @@ +{ + "flow": { + "name":"Expose SOAP API as REST", + "description": "Exposes NOAA Weather SOAP Web Service as a REST API. Uses URLs. Input LAT and LON with west as negative and south as negative, and using only two decimal places.", + "disabled": false, + "autoGC": false + }, + "listener": { + "type":"rest_listener", + "isMessageGenerator": true, + "host": "127.0.0.1", + "port":9091, + "url":"/weather", + "allow_origin": "*", + "timeout": 120000 + }, + "route0":{ + "type": "httpclient", + "dependencies":["listener"], + "url":"https://graphical.weather.gov/xml/SOAP_server/ndfdXMLclient.php?whichClient=NDFDgen&lat={{message.content.lat}}&lon={{message.content.lon}}&listLatLon=&lat1=&lon1=&lat2=&lon2=&resolutionSub=&listLat1=&listLon1=&listLat2=&listLon2=&resolutionList=&endPoint1Lat=&endPoint1Lon=&endPoint2Lat=&endPoint2Lon=&listEndPoint1Lat=&listEndPoint1Lon=&listEndPoint2Lat=&listEndPoint2Lon=&zipCodeList=&listZipCodeList=¢erPointLat=¢erPointLon=&distanceLat=&distanceLon=&resolutionSquare=&listCenterPointLat=&listCenterPointLon=&listDistanceLat=&listDistanceLon=&listResolutionSquare=&citiesLevel=&listCitiesLevel=§or=&gmlListLatLon=&featureType=&requestedTime=&startTime=&endTime=&compType=&propertyName=&product=glance&begin=2004-01-01T00%3A00%3A00&end=2022-09-16T00%3A00%3A00&Unit=e&temp=temp&Submit=Submit", + "timeout": 180000, + "headers":["USER-AGENT: JSON_ESB", "ACCEPT: text/html,application/xhtml+xml,application/xml"] + }, + "route1": { + "type":"xmlparser", + "dependencies":["route0"] + }, + "route.error": { + "type":"js", + "dependencies":[["listener.error"],["route0.error"],["route1.error"]], + "js":"message.content={}; message.content.result=false;" + }, + "output": { + "type":"rest_responder", + "dependencies":[["route1"],["route.error"]] + }, + "garbagecollector": { + "type": "simple", + "dependencies":[["output"],["route.error"],["output.error"]], + "isMessageConsumer": true + } +} \ No newline at end of file diff --git a/flows/pipeline_emailFileNotification.json b/flows/pipeline_emailFileNotification.json index 76e4e17..d375348 100644 --- a/flows/pipeline_emailFileNotification.json +++ b/flows/pipeline_emailFileNotification.json @@ -1,7 +1,7 @@ { "flow":{ "name":"Email file notification", - "disabled":false + "disabled":true }, "listener": { "type":"file", diff --git a/install.sh.bat b/install.sh.bat new file mode 100644 index 0000000..937e3e6 --- /dev/null +++ b/install.sh.bat @@ -0,0 +1,7 @@ +echo "Use sh ./install.sh.bat to run on Linux" + +npm install line-by-line +npm install papaparse +npm install fast-xml-parser +npm install cron +npm install nodemailer \ No newline at end of file diff --git a/lib/httpclient.js b/lib/httpclient.js index 3c23c2c..3c3f989 100644 --- a/lib/httpclient.js +++ b/lib/httpclient.js @@ -1,170 +1,155 @@ -/* - * (C) 2018 TekMonks. All rights reserved. +/** + * (C) 2020 TekMonks. All rights reserved. + * + * httpClient.js - perform HTTP client calls. Accepts callback. + * Returns promise if callback is not passed. * * callback format -> callback(error, data) + * promise resolves to -> { data, status, resHeaders, error } + * promise rejects -> error + * + * Returned data is a Buffer object. It should be converted based on + * returned MIME headers. */ - -const http = require("http"); -const https = require("https"); - -const querystring = require("querystring"); - -function post(host, port, path, headers, req, timeout = 120000, callback) { - if (req) req = (typeof (req) == "object" ? querystring.stringify(req):req); - headers["Content-Length"] = Buffer.byteLength(req, "utf8"); - - let optionspost = { - host : host, - port : port, - path : path, - method : "POST", - headers : headers, - timeout: timeout - }; - - doCall(req, optionspost, false, callback); -} - -function postHttps(host, port, path, headers, req, timeout = 120000, callback) { - if (req) req = (typeof (req) == "object" ? querystring.stringify(req):req); - headers["Content-Length"] = Buffer.byteLength(req, "utf8"); - - let optionspost = { - host : host, - port : port, - path : path, - method : 'POST', - headers : headers, - timeout: timeout - }; - - doCall(req, optionspost, true, callback); -} - -function put(host, port, path, headers, req, timeout = 120000, callback) { - if (req) req = (typeof (req) == "object" ? querystring.stringify(req):req); - headers["Content-Length"] = Buffer.byteLength(req, "utf8"); - - let optionsput = { - host : host, - port : port, - path : path, - method : 'PUT', - headers : headers, - timeout: timeout - }; - - doCall(req, optionsput, false, callback); -} - -function putHttps(host, port, path, headers, req, timeout = 120000, callback) { - if (req) req = (typeof (req) == "object" ? querystring.stringify(req):req); - headers["Content-Length"] = Buffer.byteLength(req, "utf8"); - - let optionsput = { - host : host, - port : port, - path : path, - method : 'PUT', - headers : headers, - timeout: timeout - }; - - doCall(req, optionsput, true, callback); -} - -function get(host, port, path, headers, req, timeout = 120000, callback) { - if (req) path += "?" + (typeof (req) == "object" ? querystring.stringify(req):req); - - let optionsget = { - host : host, - port : port, - path : path, - method : 'GET', - headers : headers, - timeout: timeout - }; - - doCall(null, optionsget, false, callback); -} - -function getHttps(host, port, path, headers, req, timeout = 120000, callback) { - if (req) path += "?" + (typeof (req) == "object" ? querystring.stringify(req):req); - - let optionsget = { - host : host, - port : port, - path : path, - method : 'GET', - headers : headers, - timeout: timeout - }; - - doCall(null, optionsget, true, callback); -} - -function deleteHttp(host, port, path, headers, req, timeout = 120000, callback) { - if (req) path += "?" + (typeof (req) == "object" ? querystring.stringify(req):req); - - let optionsdelete = { - host : host, - port : port, - path : path, - method : 'DELETE', - headers : headers, - timeout: timeout - }; - - doCall(null, optionsdelete, false, callback); -} - -function deleteHttps(host, port, path, headers, req, timeout = 120000, callback) { - if (req) path += "?" + (typeof (req) == "object" ? querystring.stringify(req):req); - - let optionsdelete = { - host : host, - port : port, - path : path, - method : 'GET', - headers : headers, - timeout: timeout - }; - - doCall(null, optionsdelete, true, callback); -} - -function doCall(reqStr, options, secure, callback) { - let caller = secure ? https : http; - let response = ""; - let req = caller.request(options, res => { - res.on("data", d => response += d); - - res.on("end", function() { - let status = this.statusCode; - callback(null, {response, status}); - }); - }); - - if (reqStr) req.write(reqStr); - req.end(); - req.on("error", (e) => {callback(e, null)}) -} - -if (require.main === module) { - let args = process.argv.slice(2); - - if (args.length == 0) console.log("Usage: httpclient "); - else post(args[0], args[1], args[2], args[3], (e, data) => { - if (!e) console.log(JSON.stringify(data)); else console.log(e); - }); -} - -exports.get = get; -exports.post = post; -exports.put = put; -exports.delete = deleteHttp; - -exports.getHttps = getHttps; -exports.postHttps = postHttps; -exports.putHttps = putHttps; -exports.deleteHttps = deleteHttps; \ No newline at end of file + if (!global.CONSTANTS) global.CONSTANTS = require(__dirname + "/constants.js"); // to support direct execution + + const PROC_MEMORY = {}; + const http = require("http"); + const zlib = require("zlib"); + const https = require("https"); + const fspromises = require("fs").promises; + const querystring = require("querystring"); + const utils = require(CONSTANTS.LIBDIR + "/utils.js"); + const crypt = require(CONSTANTS.LIBDIR + "/crypt.js"); + + const DEFAULT_HTTP_TIMEOUT = 120000; + + function post(host, port, path, headers, req, timeout, callback) { + headers = headers||{}; const body = req; _addHeaders(headers, body); + const optionspost = { host, port, path, method: 'POST', headers, timeout: timeout||DEFAULT_HTTP_TIMEOUT }; + const result = _doCall(body, optionspost, false); if (!callback) return result; + result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); + } + + function postHttps(host, port, path, headers, req, timeout, sslObj, callback) { + headers = headers||{}; const body = req; _addHeaders(headers, body); + const optionspost = { host, port, path, method: 'POST', headers, timeout: timeout||DEFAULT_HTTP_TIMEOUT }; + const result = _doCall(body, optionspost, true, sslObj); if (!callback) return result; + result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); + } + + function put(host, port, path, headers, req, timeout, callback) { + headers = headers||{}; const body = req; _addHeaders(headers, body); + const optionsput = { host, port, path, method: 'PUT', headers, timeout: timeout||DEFAULT_HTTP_TIMEOUT }; + const result = _doCall(body, optionsput, false); if (!callback) return result; + result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); + } + + function putHttps(host, port, path, headers, req, timeout, sslObj, callback) { + headers = headers||{}; const body = req; _addHeaders(headers, body); + const optionsput = { host, port, path, method: 'PUT', headers, timeout: timeout||DEFAULT_HTTP_TIMEOUT }; + const result = _doCall(body, optionsput, true, sslObj); if (!callback) return result; + result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); + } + + function get(host, port, path, headers, req, timeout, callback) { + headers = headers||{}; _addHeaders(headers, null); + if (req && typeof req == "object") req = querystring.stringify(req); if (req && req.trim() !== "") path += `?${req}`; + const optionsget = { host, port, path, method: 'GET', headers, timeout: timeout||DEFAULT_HTTP_TIMEOUT }; + const result = _doCall(null, optionsget, false); if (!callback) return result; + result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); + } + + function getHttps(host, port, path, headers = {}, req, timeout, sslObj, callback) { + headers = headers||{}; _addHeaders(headers, null); + if (req && typeof req == "object") req = querystring.stringify(req); if (req && req.trim() !== "") path += `?${req}`; + const optionsget = { host, port, path, method: 'GET', headers, timeout: timeout||DEFAULT_HTTP_TIMEOUT }; + const result = _doCall(null, optionsget, true, sslObj); if (!callback) return result; + result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); + } + + function deleteHttp(host, port, path, headers, req, timeout, callback) { + headers = headers||{}; _addHeaders(headers, null); + if (req && typeof req == "object") req = querystring.stringify(req); if (req && req.trim() !== "") path += `?${req}`; + const optionsdelete = { host, port, path, method: 'DELETE', headers, timeout: timeout||DEFAULT_HTTP_TIMEOUT }; + const result = _doCall(null, optionsdelete, false); if (!callback) return result; + result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); + } + + function deleteHttps(host, port, path, headers = {}, req, timeout, sslObj, callback) { + headers = headers||{}; _addHeaders(headers, null); + if (req && typeof req == "object") req = querystring.stringify(req); if (req && req.trim() !== "") path += `?${req}`; + const optionsdelete = { host, port, path, method: 'DELETE', headers, timeout: timeout||DEFAULT_HTTP_TIMEOUT }; + const result = _doCall(null, optionsdelete, true, sslObj); if (!callback) return result; + result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); + } + + function _addHeaders(headers, body) { + if (body) headers["Content-Type"] = headers["Content-Type"] || "application/x-www-form-urlencoded"; + if (body) headers["Content-Length"] = Buffer.byteLength(body, "utf8"); + headers["Accept"] = headers["Accept"] || "*/*"; + } + + function _doCall(reqStr, options, secure, sslObj) { + return new Promise(async (resolve, reject) => { + const caller = secure ? https : http; + let resp, ignoreEvents = false, resPiped; + if (sslObj & typeof sslObj == "object") try{await _addSecureOptions(options, sslObj)} catch (err) {reject(err); return;}; + const req = caller.request(options, res => { + const encoding = utils.getObjectKeyValueCaseInsensitive(res.headers, "Content-Encoding") || "identity"; + if (encoding.toLowerCase() == "gzip") { resPiped = zlib.createGunzip(); res.pipe(resPiped); } else resPiped = res; + + resPiped.on("data", chunk => { if (!ignoreEvents) resp = resp ? Buffer.concat([resp,chunk]) : chunk }); + + const sendError = error => { reject(error); ignoreEvents = true; }; + res.on("error", error => sendError(error)); resPiped.on("error", error => sendError(error)); + + resPiped.on("end", () => { + if (ignoreEvents) return; + const status = res.statusCode, resHeaders = { ...res.headers }; + const statusOK = Math.trunc(status / 200) == 1 && status % 200 < 100; + + if (!statusOK) resolve({ error: `Bad status: ${status}`, data: resp, status, resHeaders }); + else resolve({ error: null, data: resp, status, resHeaders }); + }); + }); + + if (reqStr) req.write(reqStr); + req.end(); + req.on("error", error => reject(error)); + }); + } + + async function _addSecureOptions(options, sslObj) { + const _cacheReadFile = async filepath => { + if (!PROC_MEMORY[filepath]) PROC_MEMORY[filepath] = await fspromises.readFile(filepath); + return PROC_MEMORY[filepath]; + } + + if (sslObj.pfxPath && sslObj.encryptedPassphrase) { + options.pfx = await _cacheReadFile(sslObj.pfxPath); + options.passphrase = crypt.decrypt(sslObj.encryptedPassphrase, sslObj.encryptionKey); + } else if (sslObj.certPath && sslObj.encryptedKeyPath) { + options.cert = await _cacheReadFile(ssl.certPath); + options.key = crypt.decrypt(await _cacheReadFile(ssl.encryptedKeyPath), sslObj.encryptionKey); + } + } + + if (require.main === module) main(); + + function main() { + const args = process.argv.slice(2); if (args[0]) args[0] = args[0].toLowerCase(); + if (args.length == 0) console.error("Usage: httpClient.js [ssl-options]"); + else { + const url = new URL(args[1]); if (url.protocol == "https:") args[0] = args[0]+"Https"; if (args[0]=="delete") args[0] = "deleteHttp"; + const port = url.port && url.port != "" ? url.port : (url.protocol=="https:"?443:80), out = process.stdout.write.bind(process.stdout), + headers = args[3] && args[3] != "" ? JSON.parse(args[3]):{}, sslOptions = args[4] ? JSON.parse(args[4]):null, + totalPath = url.pathname + (url.search?url.search:""); + eval(args[0]).call( null, url.hostname, port, totalPath, headers, args[2], sslOptions, (err, data) => { + const funcToWriteTo = err?console.error:out, dataToWrite = err ? err : (process.stdout.isTTY?data.toString("utf8"):data); + funcToWriteTo(dataToWrite); + }); + } + } + + module.exports = { get, post, put, delete: deleteHttp, getHttps, postHttps, putHttps, deleteHttps, main }; \ No newline at end of file diff --git a/lib/rest.js b/lib/rest.js index eff907a..787ae9b 100644 --- a/lib/rest.js +++ b/lib/rest.js @@ -2,150 +2,92 @@ * (C) 2020 TekMonks. All rights reserved. * * rest.js - perform REST API calls. Accepts callback. - * Returns promise if callback is not passed. + * Returns promise if callback is not passed. + * REST/JSON wrapper around the http client library. * * callback format -> callback(error, data) * promise resolves to -> { data, status, resHeaders, error } * promise rejects -> error - * */ -if (!global.CONSTANTS) global.CONSTANTS = require(__dirname + "/constants.js"); // to support direct execution -if (!CONSTANTS.SHARED_PROC_MEMORY["__com_tekmonks_monkshu_rest_ssh_file"]) CONSTANTS.SHARED_PROC_MEMORY["__com_tekmonks_monkshu_rest_ssh_file"] = {}; - -const fs = require("fs"); -const http = require("http"); -const zlib = require("zlib"); -const https = require("https"); -const path = require("path"); -const util = require("util"); -const querystring = require("querystring"); -const readFileAsync = util.promisify(fs.readFile); -const utils = require(CONSTANTS.LIBDIR + "/utils.js"); -const crypt = require(CONSTANTS.LIBDIR + "/crypt.js"); - - -function post(host, port, path, headers = {}, req, sslObj, callback) { - const jsonStr = typeof (req) == "object" ? JSON.stringify(req) : req; addHeaders(headers, jsonStr); - const optionspost = { host, port, path, method: 'POST', headers }; - const result = doCall(jsonStr, optionspost, false, sslObj); if (!callback) return result; - result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); -} - -function postHttps(host, port, path, headers = {}, req, sslObj, callback) { - const jsonStr = typeof (req) == "object" ? JSON.stringify(req) : req; addHeaders(headers, jsonStr); - const optionspost = { host, port, path, method: 'POST', headers }; - const result = doCall(jsonStr, optionspost, true, sslObj); if (!callback) return result; - result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); -} - -function put(host, port, path, headers = {}, req, sslObj, callback) { - const jsonStr = typeof (req) == "object" ? JSON.stringify(req) : req; addHeaders(headers, jsonStr); - const optionsput = { host, port, path, method: 'PUT', headers }; - const result = doCall(jsonStr, optionsput, false, sslObj); if (!callback) return result; - result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); -} - -function putHttps(host, port, path, headers = {}, req, sslObj, callback) { - const jsonStr = typeof (req) == "object" ? JSON.stringify(req) : req; addHeaders(headers, jsonStr); - const optionsput = { host, port, path, method: 'PUT', headers }; - const result = doCall(jsonStr, optionsput, true, sslObj); if (!callback) return result; - result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); -} - -function get(host, port, path, headers = {}, req, sslObj, callback) { - if (req && typeof req == "object") req = querystring.stringify(req); - if (req && req.trim() !== "") path += `?${req}`; headers["Accept"] = "application/json"; - const optionsget = { host, port, path, method: 'GET', headers }; - const result = doCall(null, optionsget, false, sslObj); if (!callback) return result; - result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); -} - -function getHttps(host, port, path, headers = {}, req, sslObj, callback) { - if (req && typeof req == "object") req = querystring.stringify(req); - if (req && req.trim() !== "") path += `?${req}`; headers["Accept"] = "application/json"; - const optionsget = { host, port, path, method: 'GET', headers }; - const result = doCall(null, optionsget, true, sslObj); if (!callback) return result; - result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); -} - -function deleteHttp(host, port, path, headers = {}, _req, sslObj, callback) { - headers["Accept"] = "application/json"; - const optionsdelete = { host, port, path, method: 'DELETE', headers }; - const result = doCall(null, optionsdelete, false, sslObj); if (!callback) return result; - result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); -} - -function deleteHttps(host, port, path, headers = {}, _req, sslObj, callback) { - headers["Accept"] = "application/json"; - const optionsdelete = { host, port, path, method: 'DELETE', headers }; - const result = doCall(null, optionsdelete, true, sslObj); if (!callback) return result; - result.then(({ data, status, resHeaders, error }) => callback(error, data, status, resHeaders)).catch((error) => callback(error, null)); -} - -function addHeaders(headers, jsonStr) { - headers["Content-Type"] = "application/json"; - headers["Content-Length"] = Buffer.byteLength(jsonStr, "utf8"); - headers["Accept"] = "application/json"; -} - -function doCall(reqStr, options, secure, sslObj) { - return new Promise(async (resolve, reject) => { - const caller = secure ? https : http; - let resp, ignoreEvents = false, resPiped; - if (sslObj & typeof sslObj == "object") await addSecureOptions(options, sslObj); - const req = caller.request(options, res => { - const encoding = utils.getObjectKeyValueCaseInsensitive(res.headers, "Content-Encoding") || "identity"; - if (encoding.toLowerCase() == "gzip") { resPiped = zlib.createGunzip(); res.pipe(resPiped); } else resPiped = res; - - resPiped.on("data", chunk => { if (!ignoreEvents) resp = resp ? resp + chunk : chunk }); - - const sendError = error => { reject(error); ignoreEvents = true; }; - res.on("error", error => sendError(error)); resPiped.on("error", error => sendError(error)); - - resPiped.on("end", () => { - if (ignoreEvents) return; - const status = res.statusCode, resHeaders = { ...res.headers }; - const statusOK = Math.trunc(status / 200) == 1 && status % 200 < 100; - - if (!statusOK) resolve({ error: `Bad status: ${status}`, data: null, status, resHeaders }); - else try { resolve({ error: null, data: resp ? JSON.parse(resp) : resp, status, resHeaders }) } - catch (e) { resolve({ error: `Bad JSON Response: ${resp}, error: ${e}`, data: null, status, resHeaders }) } - }); - }); - - if (reqStr) req.write(reqStr); - req.end(); - req.on("error", error => reject(error)); - }); -} - -async function addSecureOptions(options, sslObj) { - try { - if (sslObj.pfxPath && sslObj.encryptedPassphrase) { - options.pfx = await _getFileContents(sslObj.pfxPath); - options.passphrase = crypt.decrypt(sslObj.encryptedPassphrase, sslObj.encryptionKey); - } else if (sslObj.certPath && sslObj.encryptedKeyPath) { - options.cert = await _getFileContents(ssl.certPath); - options.key = crypt.decrypt(await _getFileContents(ssl.encryptedKeyPath), sslObj.encryptionKey); - } - } catch (error) { console.error(error); return; } -} - -async function _getFileContents(filepath) { - try { - filepath = path.resolve(filepath); - if (!CONSTANTS.SHARED_PROC_MEMORY["__com_tekmonks_monkshu_rest_ssh_file"][filepath]) - CONSTANTS.SHARED_PROC_MEMORY["__com_tekmonks_monkshu_rest_ssh_file"][filepath] = await readFileAsync(filepath); - return CONSTANTS.SHARED_PROC_MEMORY["__com_tekmonks_monkshu_rest_ssh_file"][filepath]; - } catch (error) { throw error; } -} - -if (require.main === module) { - const args = process.argv.slice(2); - if (args.length == 0) console.log("Usage: rest [ssl-options]"); - else post(args[0], args[1], args[2], JSON.parse(args[3]), JSON.parse(args[4]), args[5] ? JSON.parse(args[5]) : null, (err, data) => { - (err) ? console.log(err): console.log(JSON.stringify(data)) - }); -} - -module.exports = { get, post, put, delete: deleteHttp, getHttps, postHttps, putHttps, deleteHttps }; + if (!global.CONSTANTS) global.CONSTANTS = require(__dirname + "/constants.js"); // to support direct execution + + const httpClient = require(CONSTANTS.LIBDIR + "/httpClient.js"); + + async function post(host, port, path, headers, req, timeout, callback) { + const jsonStr = typeof (req) == "object" ? JSON.stringify(req) : req; headers = _getRESTHeaders(headers); + try { + const result = await httpClient.post(host, port, path, headers, jsonStr, timeout); + if (result.data) result.data = JSON.parse(result.data); if (callback) callback(null, result); else return result; + } catch (err) { if (callback) callback(err); else throw err; } + } + + async function postHttps(host, port, path, headers, req, timeout, sslObj, callback) { + const jsonStr = typeof (req) == "object" ? JSON.stringify(req) : req; headers = _getRESTHeaders(headers); + try { + const result = await httpClient.postHttps(host, port, path, headers, jsonStr, timeout, sslObj); + if (result.data) result.data = JSON.parse(result.data); if (callback) callback(null, result); else return result; + } catch (err) { if (callback) callback(err); else throw err; } + } + + async function put(host, port, path, headers, req, timeout, callback) { + const jsonStr = typeof (req) == "object" ? JSON.stringify(req) : req; headers = _getRESTHeaders(headers); + try { + const result = await httpClient.put(host, port, path, headers, jsonStr, timeout); + if (result.data) result.data = JSON.parse(result.data); if (callback) callback(null, result); else return result; + } catch (err) { if (callback) callback(err); else throw err; } + } + + async function putHttps(host, port, path, headers, req, timeout, sslObj, callback) { + const jsonStr = typeof (req) == "object" ? JSON.stringify(req) : req; headers = _getRESTHeaders(headers); + try { + const result = await httpClient.putHttps(host, port, path, headers, jsonStr, timeout, sslObj); + if (result.data) result.data = JSON.parse(result.data); if (callback) callback(null, result); else return result; + } catch (err) { if (callback) callback(err); else throw err; } + } + + async function get(host, port, path, headers, req, timeout, callback) { + try { + const result = await httpClient.get(host, port, path, headers, req, timeout); + if (result.data) result.data = JSON.parse(result.data); if (callback) callback(null, result); else return result; + } catch (err) { if (callback) callback(err); else throw err; } + } + + async function getHttps(host, port, path, headers, req, timeout, sslObj, callback) { + try { + const result = await httpClient.getHttps(host, port, path, headers, req, timeout, sslObj); + if (result.data) result.data = JSON.parse(result.data); if (callback) callback(null, result); else return result; + } catch (err) { if (callback) callback(err); else throw err; } + } + + async function deleteHttp(host, port, path, headers, _req, timeout, callback) { + try { + const result = await httpClient.deleteHttp(host, port, path, headers, _req, timeout); + if (result.data) result.data = JSON.parse(result.data); if (callback) callback(null, result); else return result; + } catch (err) { if (callback) callback(err); else throw err; } + } + + async function deleteHttps(host, port, path, headers, _req, timeout, sslObj, callback) { + try { + const result = await httpClient.deleteHttps(host, port, path, headers, _req, timeout, sslObj); + if (result.data) result.data = JSON.parse(result.data); if (callback) callback(null, result); else return result; + } catch (err) { if (callback) callback(err); else throw err; } + } + + const _getRESTHeaders = headers => headers?{...headers, "Content-Type": "application/json", "Accept": "application/json"}:{"Content-Type": "application/json", "Accept": "application/json"}; + + if (require.main === module) { + const args = process.argv.slice(2); if (args[0]) args[0] = args[0].toLowerCase(); + if (args.length == 0) console.error("Usage: rest.js [ssl-options]"); + else { + const url = new URL(args[1]); if (url.protocol == "https:") args[0] = args[0]+"Https"; if (args[0]=="delete") args[0] = "deleteHttp"; + const port = url.port && url.port != "" ? url.port : (url.protocol=="https:"?443:80), jsonReq = args[2] && args[2] != "" ? JSON.parse(args[2]):null, + headers = args[3] && args[3] != "" ? JSON.parse(args[3]):{}, sslOptions = args[4] ? JSON.parse(args[4]):null, + totalPath = url.pathname + (url.search?url.search:""); + eval(args[0]).call( null, url.hostname, port, totalPath, headers, jsonReq, sslOptions, (err, result) => { + const funcToWriteTo = err?console.error:console.log, dataToWrite = err ? err : (result.data?JSON.stringify(result.data, null, 2):""); + funcToWriteTo.call(console, dataToWrite); + }); + } + } + + module.exports = { get, post, put, delete: deleteHttp, getHttps, postHttps, putHttps, deleteHttps }; \ No newline at end of file diff --git a/routes/httpclient.js b/routes/httpclient.js index e488695..e859504 100644 --- a/routes/httpclient.js +++ b/routes/httpclient.js @@ -4,6 +4,7 @@ * (C) 2018 TekMonks. All rights reserved. */ +const utils = require(`${CONSTANTS.LIBDIR}/utils.js`); const http = require(`${CONSTANTS.LIBDIR}/httpclient.js`); exports.start = (routeName, httpclient, _messageContainer, message) => { @@ -11,22 +12,31 @@ exports.start = (routeName, httpclient, _messageContainer, message) => { if (!message.env[routeName]) message.env[routeName] = {}; message.env[routeName].isProcessing = true; message.setGCEligible(false); + if (httpclient.url) { // parse URLs here and prioritize them first, support parsed properties for URLs + const urlToCall = new URL(utils.expandProperty(httpclient.url, httpclient.flow, message)); + httpclient.port = urlToCall.port; httpclient.isSecure = urlToCall.protocol == "https:"; + httpclient.host = urlToCall.hostname; httpclient.path = urlToCall.pathname + urlToCall.search; + if (!httpclient.method) httpclient.method = "get"; + } + LOG.info(`[HTTP] HTTP call to ${httpclient.host}:${httpclient.port} with incoming message with timestamp: ${message.timestamp}`); - if (!httpclient.port) httpclient.port = (httpClient.isSecure?443:80); // handle ports + if (!httpclient.port) httpclient.port = (httpclient.isSecure?443:80); // handle ports if (httpclient.isSecure && !httpclient.method.endsWith("Https")) httpclient.method += "Https"; if (httpclient.method == "delete") httpclient.method = "deleteHttp"; // delete is a reserved word in JS let headers = {}; // handle headers - if (httpclient.headers) httpclient.headers.forEach(v => { - let pair = v.split(":"); pair.forEach((v, i) => pair[i] = v.trim()); - const key = pair[0]; pair.splice(0,1); const value = pair.join(""); + if (httpclient.headers) for (v of httpclient.headers) { + const pair = v.split(":"); for (const [i,v] of pair.entries()) pair[i] = v.trim(); + const key = pair[0]; pair.splice(0,1); const value = pair.join(":"); headers[key] = value; - }); + } + + httpclient.path = httpclient.path.trim(); if (!httpclient.path.startsWith("/")) httpclient.path = `/${httpclient.path}`; http[httpclient.method](httpclient.host, httpclient.port, httpclient.path, headers, message.content, - httpclient.timeout, (error, result) => { + httpclient.timeout, httpclient.sslObj, (error, data) => { if (error) { LOG.error(`[HTTP] Call failed with error: ${error}, for message with timestamp: ${message.timestamp}`); @@ -37,9 +47,9 @@ exports.start = (routeName, httpclient, _messageContainer, message) => { message.addRouteDone(routeName); delete message.env[routeName]; // clean up our mess message.setGCEligible(true); - message.content = result.response; + message.content = httpclient.isBinary?data:data.toString("utf8"); LOG.info(`[HTTP] Response received for message with timestamp: ${message.timestamp}`); - LOG.debug(`[HTTP] Response data is: ${result.response}`); + LOG.debug(`[HTTP] Response data is: ${message.content}`); } }); } \ No newline at end of file diff --git a/routes/rest.js b/routes/rest.js index 3c3854b..ab86dd1 100644 --- a/routes/rest.js +++ b/routes/rest.js @@ -11,21 +11,30 @@ exports.start = (routeName, rest, _messageContainer, message) => { if (!message.env[routeName]) message.env[routeName] = {}; message.env[routeName].isProcessing = true; message.setGCEligible(false); + if (rest.url) { // parse URLs here and prioritize them first, support parsed properties for URLs + const urlToCall = new URL(utils.expandProperty(rest.url)); + rest.port = urlToCall.port; rest.isSecure = urlToCall.protocol == "https:"; + rest.host = urlToCall.hostname; rest.path = urlToCall.pathname + urlToCall.search; + if (!rest.method) rest.method = "get"; + } + LOG.info(`[REST] REST call to ${rest.host}:${rest.port} with incoming message with timestamp: ${message.timestamp}`); - if (rest.isSecure && !rest.method.endsWith("Https")) rest.method += "Https"; - if (rest.method == "delete") rest.method = "deleteHttp"; // delete is a reserved word in JS + if (!rest.port) rest.port = (rest.isSecure?443:80); // handle ports + + if (rest.isSecure && !rest.method.endsWith("Https")) rest.method += "Https"; + if (rest.method == "delete") rest.method = "deleteHttp"; // delete is a reserved word in JS - let headers = {}; // handle headers - if (rest.headers) rest.headers.forEach(v => { - let pair = v.split(":"); pair.forEach((v, i) => pair[i] = v.trim()); - const key = pair[0]; pair.splice(0,1); const value = pair.join(""); + let headers = {}; // handle headers + if (rest.headers) for (v of rest.headers) { + const pair = v.split(":"); for (const [i,v] of pair.entries()) pair[i] = v.trim(); + const key = pair[0]; pair.splice(0,1); const value = pair.join(":"); headers[key] = value; - }); + } - if (!rest.path.startsWith("/")) rest.path = `/${rest.path.trim()}`; + rest.path = rest.path.trim(); if (!rest.path.startsWith("/")) rest.path = `/${rest.path}`; - restClient[rest.method](rest.host, rest.port, rest.path, headers, message.content, rest.sslObj, (error, data) =>{ + restClient[rest.method](rest.host, rest.port, rest.path, headers, message.content, rest.timeout, rest.sslObj, (error, data) =>{ if (error) { LOG.error(`[REST] Call failed with error: ${error}`); message.addRouteError(routeName);