I would like to retrieve data from the Kraken.com API. I am trying to call the "Private" methods. (Those one need to be authenticated)
As precised here: https://www.kraken.com/help/api
The expected signature is:
API-Sign = Message signature using HMAC-SHA512 of (URI path + SHA256(nonce + POST data)) and base64 decoded secret API key
I've found here a function from their node.js library that should do the job, but I can't seem to get it.
/**
* This method returns a signature for a request as a Base64-encoded string
* #param {String} path The relative URL path for the request
* #param {Object} request The POST body
* #param {Integer} nonce A unique, incrementing integer
* #return {String} The request signature
*/
function getMessageSignature(path, request, nonce) {
var message = querystring.stringify(request);
var secret = new Buffer(config.secret, 'base64');
var hash = new crypto.createHash('sha256');
var hmac = new crypto.createHmac('sha512', secret);
var hash_digest = hash.update(nonce + message).digest('binary');
var hmac_digest = hmac.update(path + hash_digest, 'binary').digest('base64');
return hmac_digest;
}
Here is my full code
function main() {
var apiKey = "API-KEY";
var apiSecret = "API-SECRET";
var url = "https://api.kraken.com/0/private/Balance";
var path = "/0/private/Balance";
const nonce = new Date() * 1000;
const payload = {
'nonce': nonce
};
const postData = 'nonce=' + nonce;
const signature = getMessageSignature(path, apiSecret, postData, nonce);
var httpOptions = {
'method': 'post',
'headers': {
"API-Key": apiKey,
"API-Sign": signature
},
'payload': postData
};
var response = UrlFetchApp.fetch(url, httpOptions);
Logger.log(response.getContentText());
}
function getMessageSignature(url, secret, data, nonce) {
const hash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, nonce + data);
const hmac_digest = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_512, url + hash, Utilities.base64Decode(secret));
return Utilities.base64Encode(hmac_digest);
}
But i end up getting error
{"error":["EAPI:Invalid key"]}
Thanks in anticipation.
I finally got the code to work using jsSHA
function getKrakenSignature (path, postdata, nonce) {
var sha256obj = new jsSHA ("SHA-256", "BYTES");
sha256obj.update (nonce + postdata);
var hash_digest = sha256obj.getHash ("BYTES");
var sha512obj = new jsSHA ("SHA-512", "BYTES");
sha512obj.setHMACKey ("HMACKEY", "B64");
sha512obj.update (path);
sha512obj.update (hash_digest);
return sha512obj.getHMAC ("B64");
}
function getKrakenBalance () {
var path = "/0/private/Balance";
var nonce = new Date () * 1000;
var postdata = "nonce=" + nonce;
var signature = getKrakenSignature (path, postdata, nonce);
var url = "https://api.kraken.com" + path;
var options = {
method: 'post',
headers: {
'API-Key': "<API-KEY>",
'API-Sign': signature
},
payload: postdata
};
var response = UrlFetchApp.fetch (url, options);
return response.getContentText ();
}
function main() {
Logger.log(getKrakenBalance());
}
Related
In Google Apps Script, I'm using some code I adapted from a project I found. It calls an API endpoint and lays out the data in a spreadsheet. I was able to get it to loop through multiple API calls in order to pull data from multiple documents. However, the code breaks if it finds a document with no data. In this case, I want it to just skip that iteration and start again at the next cardIds.forEach iteration.
Here's a link to a sample sheet.
I tried:
if (response == "") {
return;
}
But no dice. Here's the full code (also it's very inefficient. I have params on their twice because I'm not sure how to consolidate them with all the functions inside other functions..)
const DATA_SHEET = "Data";
const USERNAME_CELL = "B1";
const API_TOKEN_CELL = "B2";
const CARD_ID_COL = "Cards!B:B"
const DATA_RANGE = "A4:C"
var getNextPage = function(response) {
var url = response.getAllHeaders().Link;
if (!url) {
return "";
}
return /<([^>]+)/.exec(url)[1];
};
var getIt = function(path, username, apiToken) {
var params = {
"method": "GET",
"muteHttpExceptions": true,
"headers": {
"Content-Type": "application/json",
"Authorization": "Basic " + Utilities.base64Encode(username + ":" + apiToken),
"x-guru-application": "spreadsheet",
"X-Amzn-Trace-Id": "GApp=spreadsheet"
}
};
var response = UrlFetchApp.fetch(path, params);
var data = JSON.parse(response.getContentText());
// check if there's another page of results.
var nextPage = getNextPage(response);
if (nextPage) {
data.nextPage = nextPage;
};
return data;
};
var getItAll = function(url, username, apiToken, callback) {
var data = [];
while (url) {
var page = getIt(url, username, apiToken);
var startIndex = data.length;
page.forEach(function(a) {
data.push(a);
});
// get the url of the next page of results.
url = page.nextPage;
if (callback) {
// the second parameter is whether we're done or not.
// if there's a url for the next page that means we're not done yet.
callback(data, startIndex, page.length, url ? false : true);
}
}
return data;
};
function eachCard() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(DATA_SHEET);
sheet.getRange(DATA_RANGE).clearContent();
var username = sheet.getRange(USERNAME_CELL).getValue();
var apiToken = sheet.getRange(API_TOKEN_CELL).getValue();
var cardIds = sheet.getRange(CARD_ID_COL).getValues().flat().filter(r=>r!="");
var params = {
"method": "GET",
"muteHttpExceptions": true,
"headers": {
"Content-Type": "application/json",
"Authorization": "Basic " + Utilities.base64Encode(username + ":" + apiToken),
"x-guru-application": "spreadsheet",
"X-Amzn-Trace-Id": "GApp=spreadsheet"
}
};
cardIds.forEach(function (cardId) {
var fullUrl = "https://api.getguru.com/api/v1/cards/"+cardId+"/comments"
var cardComments = getItAll(fullUrl, username, apiToken);
var fullUrl = "https://api.getguru.com/api/v1/cards/"+cardId+"/extended"
var response = UrlFetchApp.fetch(fullUrl, params);
var cardData = JSON.parse(response.getContentText());
var sheetLastRow = sheet.getLastRow();
var range = sheet.getRange("A" + sheetLastRow);
if (range.getValue() !== "") {
var lastRow = sheetLastRow+1;
} else {
var lastRow = range.getNextDataCell(SpreadsheetApp.Direction.UP).getRow()+1;
}
cardComments.forEach(function(comment, commentIndex) {
sheet.getRange(lastRow + commentIndex, 1).setValue(comment.dateCreated);
sheet.getRange(lastRow + commentIndex, 1 + 1).setValue(comment.content);
sheet.getRange(lastRow + commentIndex, 1 + 2).setValue(cardData.preferredPhrase);
});
});
}
Currently, I'm using the code (written in Apps Script) given below to sync data from multiple google sheets to the database with the help of URLs. When the "sheetLinks" is given URL of sheets within a single google sheet file, it doesn't read the gid of the URL which is the only thing that differs between multiple sheets within a google sheet file thus making the for loop to run twice(as it is considering only the base URL which is same in both).
const sheetLinks = [
"https://docs.google.com/spreadsheets/d/same/edit#gid=0",
"https://docs.google.com/spreadsheets/d/same/edit#gid=654836738"
];
let baseUrl = "https://something/api";
function syncData() {
for (filenum = 0; filenum < sheetLinks.length; filenum++) {
let spreadsheet = SpreadsheetApp.openByUrl(sheetLinks[filenum]);
let values = spreadsheet.getDataRange().getValues();
for (let y = 1; y < values.length; y++)
{
let data = {};
data.name = values[y][11].toString();
data.platform = values[y][5].toString();
data.platform_link = values[y][6].toString();
let dbId = values[y][0];
let responseData = {};
if (dbId) {
responseData = updateSession(dbId, data);
console.log("Session updated! session ID: " + responseData.id);
} else {
responseData = createSession(data);
// write the id to the dbId cell
let actualRowNumber = y + 1; // y + 1 because we start the loop from y 1
spreadsheet.getRange("A" + actualRowNumber).setValue(responseData.id);
console.log("Session created! New session ID: " + responseData.id);
}
y++;
}
}
function apiRequest(url, method, data) {
let options = {
method,
contentType: "application/json",
payload: JSON.stringify(data),
muteHttpExceptions: true,
};
return UrlFetchApp.fetch(url, options);
}
function createSession(sessionData) {
let url = baseUrl + "/session";
let response = apiRequest(url, "post", sessionData);
console.log(response.getContentText());
let responseData = JSON.parse(response.getContentText());
return responseData;
}
function updateSession(sessionId, sessionData) {
let url = baseUrl + "/session/" + sessionId;
let response = apiRequest(url, "patch", sessionData);
let responseData = JSON.parse(response.getContentText());
return responseData;
}
}
I am trying to obtain Container details in Azure Storage Blob. But it throws Auth Failed, I think there might be problems with my resource string formulation.
Here's the code:
const CryptoJS = require("crypto-js");
const request = require("request");
const parser = require("xml2json");
require("dotenv").config();
const account = process.env.ACCOUNT_NAME || "";
const key = process.env.ACCOUNT_KEY || "";
var strTime = new Date().toUTCString();
var strToSign =
"GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:" +
strTime +
`\nx-ms-version:2018-03-28\n/${account}/demo?restype:container`;
var secret = CryptoJS.enc.Base64.parse(key);
var hash = CryptoJS.HmacSHA256(strToSign, secret);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
var auth = `SharedKey ${account}:${hashInBase64}`;
const options = {
url: `https://${account}.blob.core.windows.net/demo?restype=container`,
headers: {
Authorization: auth,
"x-ms-date": strTime,
"x-ms-version": "2018-03-28",
},
};
function callback(error, response, body) {
console.log(error);
console.log(response.headers["Last-Modified"]);
console.log(response);
}
request(options, callback);
In the above example demo is a private container in my account.
Please try by changing the following line of code:
var strToSign =
"GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:" +
strTime +
`\nx-ms-version:2018-03-28\n/${account}/demo?restype:container`;
to
var strToSign =
"GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:" +
strTime +
`\nx-ms-version:2018-03-28\n/${account}/demo\nrestype:container`;
const https = require('https');
const crypto = require('crypto');
const utf8 = require('utf8');
const awsIoT = require('aws-iot-device-sdk');
const {AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY} = require('../../config');
const endpointFile = require('../../endpoint.json');
function sign(key, msg){
// since we are deriving key, refer getSignatureKey function
// we use binary format hash => digest() and not digest('hex')
return crypto.createHmac("sha256", key).update(utf8.encode(msg)).digest();
}
function getSignatureKey(key, date, region, service){
// deriving the key as follows:
// HMAC(HMAC(HMAC(HMAC("AWS4" + kSecret,"20150830"),"us-east-1"),"iam"),"aws4_request")
let kDate = sign(utf8.encode(`AWS4${key}`), date);
let kRegion = sign(kDate, region);
let kService = sign(kRegion, service);
let kSigning = sign(kService, 'aws4_request');
return kSigning;
}
function getTime(){
let timeObj = new Date().toISOString();
let arr = timeObj.split(':');
let len = arr[0].length;
let hh = arr[0].slice(len-2,len);
let mm = arr[1];
let ss = arr[2].slice(0,2);
return `${hh}${mm}${ss}`;
}
function getDate(){
let timeObj = new Date().toISOString();
let arr = timeObj.split('-');
let year = arr[0];
let month = arr[1];
let day = arr[2].slice(0,2);
return `${year}${month}${day}`;
}
const ENDPOINT = endpointFile.endpointAddress;
const THING_NAME = __dirname.split('/').pop();
const URI = `https://${ENDPOINT}/things/${THING_NAME}/shadow`;
console.log(URI);
// access key = access key ID + secret access key
// SigV4
// Signing Key derived from credential scope
// Step1
// Create a canonical request (CR)
// CanonicalRequest =
// HTTPRequestMethod + '\n' +
// CanonicalURI + '\n' +
// CanonicalQueryString + '\n' +
// CanonicalHeaders + '\n' +
// SignedHeaders + '\n' +
// HexEncode(Hash(RequestPayload))
const CONTENT_TYPE = "application/json; charset=utf-8";
const HTTP_REQUEST_METHOD = "GET";
// remove the protocol part from URI and query parameters (none in this case)
const CANONICAL_URI = URI.slice(URI.indexOf('/')+2, URI.length);
// console.log(`CANONICAL_URI: ${CANONICAL_URI}`);
const CANONICAL_QUERY_STRING = "";
const HOST = `${ENDPOINT}`;
const DATE = getDate();
const TIME = getTime();
const X_AMZ_DATE = `${DATE}T${TIME}Z`;
console.log(`X_AMZ_DATE: ${X_AMZ_DATE}`);
// note the trailing \n is present
const CANONICAL_HEADER = `content-type:${CONTENT_TYPE}\n`+
`host:${HOST}\n`+
`x-amz-date:${X_AMZ_DATE}\n`;
const SIGNED_HEADER = "content-type;host;x-amz-date";
// payload is the contents of request body
const PAYLOAD = "";
const PAYLOAD_HEX_HASH_ENCODED = crypto.createHash("sha256").update(utf8.encode(PAYLOAD)).digest("hex");
// string for signing CR_STRING = canonical request + metadata
const CANONICAL_REQUEST = `${HTTP_REQUEST_METHOD}\n`+
`${CANONICAL_URI}\n`+
`${CANONICAL_QUERY_STRING}\n`+
`${CANONICAL_HEADER}\n`+
`${SIGNED_HEADER}\n`+
`${PAYLOAD_HEX_HASH_ENCODED}`;
// Step2
// signing key STR_TO_SIGN
const HASH_ALGO = "AWS4-HMAC-SHA256";
const REGION = "us-east-2";
const SERVICE = "iot";
const CREDENTIAL_SCOPE = `${DATE}/`+
`${REGION}/`+
`${SERVICE}/`+
`aws4_request`;
const STRING_TO_SIGN = `${HASH_ALGO}\n`+
`${X_AMZ_DATE}\n`+
`${CREDENTIAL_SCOPE}\n`+
crypto.createHash("sha256")
.update(CANONICAL_REQUEST)
.digest("hex");
// Step3
const SECRET_KEY = AWS_SECRET_ACCESS_KEY;
const SIGNING_KEY = getSignatureKey(SECRET_KEY, DATE, REGION, SERVICE);
const SIGNATURE = crypto.createHmac("sha256", SIGNING_KEY).update(utf8.encode(STRING_TO_SIGN)).digest("hex");
// Step4
// Add SIGNATURE to HTTP request in a header or as a query string parameter
const ACCESS_KEY_ID = AWS_ACCESS_KEY_ID;
const AUTHORIZATION_HEADER = `${HASH_ALGO}`+
` Credential=`+
`${ACCESS_KEY_ID}`+
`/`+
`${CREDENTIAL_SCOPE}`+
`, SignedHeaders=`+
`${SIGNED_HEADER}`+
`, Signature=`+
`${SIGNATURE}`;
const HEADERS = {
'host':HOST,
'content-type':CONTENT_TYPE,
'Authorization':AUTHORIZATION_HEADER,
'x-amz-date':X_AMZ_DATE
};
const OPTIONS = {
hostname: HOST,
path: `/things/${THING_NAME}/shadow`,
headers: HEADERS
};
// send request
https.get(OPTIONS, res=>{
res.setEncoding("utf-8");
let body = "";
res.on("data", data=>{
body += data;
});
res.on("end", ()=>{
body = JSON.parse(body);
console.log(body);
});
});
On running this code the typical response I'm getting is
{ message: 'Signature expired: 20201017T000000Z is now earlier than 20201017T073249Z (20201017T073749Z - 5 min.)', traceId: 'b8f04573-2afd-d26a-5f2a-a13dd2dade3' }
I don't know what is going wrong or what to do to remove this error.
The ISO format is used here with this structure YYYYMMDDTHHMMSSZ
Signature expired: YYYYMMDDT000000Z is now earlier than YYYYMMDDT073249Z (YYYYMMDDT073749Z - 5 min.)
Why is HHMMSS always zero in the reported message?
What I'm trying to do is get "thing" shadow document by sending a request to the API referring to this (AWS_IOT_GetThingShadow API)
However, for authenticating my request I have to do a lot of other stuff which is stated here Signing AWS requests. I have simply performed the 4 tasks / steps mentioned in order to sign the request.
They have provided an example script (sigv4-signed-request-examples) in python which I followed to write my code.
I have been stuck on this for quite a while now. If anyone has any idea about this please help.
EDIT: The above problem was solved by using X_AMZ_DATE in STRING_TO_SIGN and HEADERS. I was wrongly using DATE. I have updated the above code accordingly.
New error I am gettiing is
{ message: 'Credential should be scoped to correct service. ', traceId: 'e711927a-11f4-ae75-c4fe-8cdc5a120c0d' }
I am not sure what is wrong with the credentials. I have set the REGION correctly. I am using SERVICE as iot which should be correct as well for requesting shadow API.
EDIT: It turns out iot is wrong. Changed SERVICE = "iotdata" and now I can successfully request shadow data. Solution found here. It is strange that I couldn't find it anywhere in the AWS docs. Another thing wrong was CANONICAL_URI = path in URI after domain and before query strings
So in my case it will be CANONICAL_URI = /things/${THING_NAME}/shadow
I am posting the correct final version of my code in case anyone is facing similar issue.
Three things were wrong in my original code.
X_AMZ_DATE (YYYYMMDDTHHMMSSZ) didn't use it in HEADERS and STRING_TO_SIGN. Hence, was getting Signature expired error.
SERVICE I thought would be iot but it is iotdata. Credential should be scoped to correct service error was resolved.
CANONICAL_URI should only contain part after the domain and before query parameters. Eg. If request URI is https://foo.bar.baz.com/foo1/foo2/foo3?bar1=baz1&bar2=baz2 then CANONICAL_URI = "/foo1/foo2/foo3"
const https = require('https');
const crypto = require('crypto');
const utf8 = require('utf8');
const {AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY} = require('../../config');
const endpointFile = require('../../endpoint.json');
function sign(key, msg){
// since we are deriving key, refer getSignatureKey function
// we use binary format hash => digest() and not digest('hex')
return crypto.createHmac("sha256", key).update(utf8.encode(msg)).digest();
}
function getSignatureKey(key, date, region, service){
// deriving the key as follows:
// HMAC(HMAC(HMAC(HMAC("AWS4" + kSecret,"20150830"),"us-east-1"),"iam"),"aws4_request")
let kDate = sign(utf8.encode(`AWS4${key}`), date);
let kRegion = sign(kDate, region);
let kService = sign(kRegion, service);
let kSigning = sign(kService, 'aws4_request');
return kSigning;
}
function getTime(){
let timeObj = new Date().toISOString();
let arr = timeObj.split(':');
let len = arr[0].length;
let hh = arr[0].slice(len-2,len);
let mm = arr[1];
let ss = arr[2].slice(0,2);
return `${hh}${mm}${ss}`;
}
function getDate(){
let timeObj = new Date().toISOString();
let arr = timeObj.split('-');
let year = arr[0];
let month = arr[1];
let day = arr[2].slice(0,2);
return `${year}${month}${day}`;
}
const ENDPOINT = endpointFile.endpointAddress;
const THING_NAME = __dirname.split('/').pop();
const URI = `https://${ENDPOINT}/things/${THING_NAME}/shadow`;
console.log(URI);
// access key = access key ID + secret access key
// SigV4
// Signing Key derived from credential scope
// Step1
// Create a canonical request (CR)
// CanonicalRequest =
// HTTPRequestMethod + '\n' +
// CanonicalURI + '\n' +
// CanonicalQueryString + '\n' +
// CanonicalHeaders + '\n' +
// SignedHeaders + '\n' +
// HexEncode(Hash(RequestPayload))
const CONTENT_TYPE = "application/json; charset=utf-8";
const HTTP_REQUEST_METHOD = "GET";
const CANONICAL_URI = `/things/${THING_NAME}/shadow`;
const CANONICAL_QUERY_STRING = "";
const HOST = `${ENDPOINT}`;
const DATE = getDate();
const TIME = getTime();
const X_AMZ_DATE = `${DATE}T${TIME}Z`;
// note the trailing \n is present
const CANONICAL_HEADER = `content-type:${CONTENT_TYPE}\n`+
`host:${HOST}\n`+
`x-amz-date:${X_AMZ_DATE}\n`;
const SIGNED_HEADER = "content-type;host;x-amz-date";
// payload is the contents of request body
const PAYLOAD = "";
const PAYLOAD_HEX_HASH_ENCODED = crypto.createHash("sha256").update(utf8.encode(PAYLOAD)).digest("hex");
// console.log(`Payload: ${PAYLOAD_HEX_HASH_ENCODED}`);
// string for signing CR_STRING = canonical request + metadata
const CANONICAL_REQUEST = `${HTTP_REQUEST_METHOD}\n`+
`${CANONICAL_URI}\n`+
`${CANONICAL_QUERY_STRING}\n`+
`${CANONICAL_HEADER}\n`+
`${SIGNED_HEADER}\n`+
`${PAYLOAD_HEX_HASH_ENCODED}`;
// Step2
// signing key STR_TO_SIGN
const HASH_ALGO = "AWS4-HMAC-SHA256";
const REGION = "us-east-2";
const SERVICE = "iotdata";
const CREDENTIAL_SCOPE = `${DATE}/`+
`${REGION}/`+
`${SERVICE}/`+
`aws4_request`;
const STRING_TO_SIGN = `${HASH_ALGO}\n`+
`${X_AMZ_DATE}\n`+
`${CREDENTIAL_SCOPE}\n`+
crypto.createHash("sha256")
.update(CANONICAL_REQUEST)
.digest("hex");
// Step3
const SECRET_KEY = AWS_SECRET_ACCESS_KEY;
const SIGNING_KEY = getSignatureKey(SECRET_KEY, DATE, REGION, SERVICE);
const SIGNATURE = crypto.createHmac("sha256", SIGNING_KEY).update(utf8.encode(STRING_TO_SIGN)).digest("hex");
// Step4
// Add SIGNATURE to HTTP request in a header or as a query string parameter
const ACCESS_KEY_ID = AWS_ACCESS_KEY_ID;
const AUTHORIZATION_HEADER = `${HASH_ALGO}`+
` Credential=`+
`${ACCESS_KEY_ID}`+
`/`+
`${CREDENTIAL_SCOPE}`+
`, SignedHeaders=`+
`${SIGNED_HEADER}`+
`, Signature=`+
`${SIGNATURE}`;
const HEADERS = {
'host':HOST,
'content-type':CONTENT_TYPE,
'Authorization':AUTHORIZATION_HEADER,
'x-amz-date':X_AMZ_DATE
};
const OPTIONS = {
hostname: HOST,
path: `/things/${THING_NAME}/shadow`,
headers: HEADERS
};
https.get(OPTIONS, res=>{
res.setEncoding("utf-8");
let body = "";
res.on("data", data=>{
body += data;
});
res.on("end", ()=>{
body = JSON.parse(body);
console.log(body);
});
});
I have successfully figured out node.js/Express code for making a single http.request to my server. However, the next step is to make multiple requests which use the same res.render statement at the end.
Here is my successful working code:
module.exports = function (app) {
// MODULES - INCLUDES
var xml2js = require('xml2js');
var parser = new xml2js.Parser();
// FORM - SUBMIT - CUCMMAPPER
app.post('/cucmmapper/submit', function (req, res) {
// FORM - DATA COLLECTION
var cucmpub = req.body.cucmpub;
var cucmversion = req.body.cucmversion;
var username = req.body.username;
var password = req.body.password;
// JS - VARIABLE DEFINITION
var authentication = username + ":" + password;
var soapreplyx = '';
var cssx = '';
var spacer = '-----';
var rmline1 = '';
var rmline2 = '';
var rmline3 = '';
var rmline4 = '';
var rmbottomup1 = '';
var rmbottomup2 = '';
var rmbottomup3 = '';
// HTTP.REQUEST - BUILD CALL
var https = require("https");
var headers = {
'SoapAction': 'CUCM:DB ver=' + cucmversion + ' listCss',
'Authorization': 'Basic ' + new Buffer(authentication).toString('base64'),
'Content-Type': 'text/xml; charset=utf-8'
};
// SOAP - AXL CALL
var soapBody = new Buffer('<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.5">' +
'<soapenv:Header/>' +
'<soapenv:Body>' +
'<ns:listCss sequence="?">' +
'<searchCriteria>' +
'<name>%</name>' +
'</searchCriteria>' +
'<returnedTags uuid="?">' +
'<name>?</name>' +
'<description>?</description>' +
'<clause>?</clause>' +
'</returnedTags>' +
'</ns:listCss>' +
'</soapenv:Body>' +
'</soapenv:Envelope>');
// HTTP.REQUEST - OPTIONS
var options = {
host: cucmpub, // IP ADDRESS OF CUCM PUBLISHER
port: 8443, // DEFAULT CISCO SSL PORT
path: '/axl/', // AXL URL
method: 'POST', // AXL REQUIREMENT OF POST
headers: headers, // HEADER VAR
rejectUnauthorized: false // REQUIRED TO ACCEPT SELF-SIGNED CERTS
};
// HTTP.REQUEST - Doesn't seem to need this line, but it might be useful anyway for pooling?
options.agent = new https.Agent(options);
// HTTP.REQUEST - OPEN SESSION
let soapRequest = https.request(options, soapResponse => {
soapResponse.setEncoding('utf8');
soapResponse.on('data', chunk => {
soapreplyx += chunk
});
// HTTP.REQUEST - RESULTS + RENDER
soapResponse.on('end', () => {
// EDIT - SCRUB XML OUTPUT
var rmline1 = soapreplyx.replace(/<\?xml\sversion='1\.0'\sencoding='utf-8'\?>/g, '');
var rmline2 = rmline1.replace(/<soapenv:Envelope\sxmlns:soapenv="http:\/\/schemas.xmlsoap.org\/soap\/envelope\/">/g, '');
var rmline3 = rmline2.replace(/<soapenv:Body>/g, '');
var rmline4 = rmline3.replace(/<ns:listCssResponse\sxmlns:ns="http:\/\/www\.cisco\.com\/AXL\/API\/[0-9]*\.[0-9]">/g, '');
var rmbottomup1 = rmline4.replace(/<\/soapenv:Envelope>/g, '');
var rmbottomup2 = rmbottomup1.replace(/<\/soapenv:Body>/g, '');
var xmlscrubbed = rmbottomup2.replace(/<\/ns:listCssResponse>/g, '');
// console.log(xmlscrubbed);
// console.log(spacer);
// XML2JS - TESTING
parser.parseString(xmlscrubbed, function (err, result) {
var cssx = result['return']['css'];
// console.log(cssx);
// console.log(spacer);
res.render('cucmmapper-results.html', {
title: 'CUCM Toolbox',
cucmpub: cucmpub,
cssx: cssx,
soapreply: soapreplyx,
xmlscrubbed: xmlscrubbed
});
});
});
});
// SOAP - SEND AXL CALL
soapRequest.write(soapBody);
soapRequest.end();
});
}
My guess is that I have to setup several things to make this work:
Another "var soapBody" with my new request (I can do this).
Another "let soapRequest" (I'm good with this too).
Another "soapRequest.write" statement (Again, easy enough).
Split the "res.render" statement out of the specific "let soapRequest" statement and gather all the variable (this is where I'm stuck).
My guess is that I need to use async. However, I can't for the life of me wrap my head around how to get that "res.render" to work with async.
Here is the closest I can come to an answer. However, the "cssx" and "partitionsx" variable are not translated over to the "function complete". They both still show up as null.
module.exports = function (app) {
// MODULES - INCLUDES
var xml2js = require('xml2js');
var parser = new xml2js.Parser();
// FORM - SUBMIT - CUCMMAPPER
app.post('/cucmmapper/submit', function (req, res) {
// FORM - DATA COLLECTION
var cucmpub = req.body.cucmpub;
var cucmversion = req.body.cucmversion;
var username = req.body.username;
var password = req.body.password;
// JS - VARIABLE DEFINITION - GLOBAL
var authentication = username + ":" + password;
var soapreplyx = '';
var cssx = null;
var spacer = '-----';
var rmline1 = '';
var rmline2 = '';
var rmline3 = '';
var rmline4 = '';
var rmbottomup1 = '';
var rmbottomup2 = '';
var rmbottomup3 = '';
var soapreplyp = '';
var partitionsx = null;
var rmline1p = '';
var rmline2p = '';
var rmline3p = '';
var rmline4p = '';
var rmbottomup1p = '';
var rmbottomup2p = '';
var rmbottomup3p = '';
// HTTP.REQUEST - BUILD CALL - GLOBAL
var https = require("https");
var headers = {
'SoapAction': 'CUCM:DB ver=' + cucmversion + ' listCss',
'Authorization': 'Basic ' + new Buffer(authentication).toString('base64'),
'Content-Type': 'text/xml; charset=utf-8'
};
// SOAP - AXL CALL - CSS
var soapBody = new Buffer('<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.5">' +
'<soapenv:Header/>' +
'<soapenv:Body>' +
'<ns:listCss sequence="?">' +
'<searchCriteria>' +
'<name>%</name>' +
'</searchCriteria>' +
'<returnedTags uuid="?">' +
'<name>?</name>' +
'<description>?</description>' +
'<clause>?</clause>' +
'</returnedTags>' +
'</ns:listCss>' +
'</soapenv:Body>' +
'</soapenv:Envelope>');
// SOAP - AXL CALL - PARTITIONS
var soapBody2 = new Buffer('<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.5">' +
'<soapenv:Header/>' +
'<soapenv:Body>' +
'<ns:listRpite{artotopm} sequence="?">' +
'<searchCriteria>' +
'<name>%</name>' +
'</searchCriteria>' +
'<returnedTags uuid="?">' +
'<name>?</name>' +
'</returnedTags>' +
'</ns:listRoutePartition>' +
'</soapenv:Body>' +
'</soapenv:Envelope>');
// HTTP.REQUEST - OPTIONS - GLOBAL
var options = {
host: cucmpub, // IP ADDRESS OF CUCM PUBLISHER
port: 8443, // DEFAULT CISCO SSL PORT
path: '/axl/', // AXL URL
method: 'POST', // AXL REQUIREMENT OF POST
headers: headers, // HEADER VAR
rejectUnauthorized: false // REQUIRED TO ACCEPT SELF-SIGNED CERTS
};
// HTTP.REQUEST - GLOBAL (Doesn't seem to need this line, but it might be useful anyway for pooling?)
options.agent = new https.Agent(options);
// HTTP.REQUEST - OPEN SESSION - CSS
var soapRequest = https.request(options, soapResponse => {
soapResponse.setEncoding('utf8');
soapResponse.on('data', chunk => {
soapreplyx += chunk
});
// HTTP.REQUEST - RESULTS + RENDER
soapResponse.on('end', () => {
// EDIT - SCRUB XML OUTPUT
var rmline1 = soapreplyx.replace(/<\?xml\sversion='1\.0'\sencoding='utf-8'\?>/g, '');
var rmline2 = rmline1.replace(/<soapenv:Envelope\sxmlns:soapenv="http:\/\/schemas.xmlsoap.org\/soap\/envelope\/">/g, '');
var rmline3 = rmline2.replace(/<soapenv:Body>/g, '');
var rmline4 = rmline3.replace(/<ns:listCssResponse\sxmlns:ns="http:\/\/www\.cisco\.com\/AXL\/API\/[0-9]*\.[0-9]">/g, '');
var rmbottomup1 = rmline4.replace(/<\/soapenv:Envelope>/g, '');
var rmbottomup2 = rmbottomup1.replace(/<\/soapenv:Body>/g, '');
var xmlscrubbed = rmbottomup2.replace(/<\/ns:listCssResponse>/g, '');
// console.log(xmlscrubbed);
// console.log(spacer);
// XML2JS - TESTING
parser.parseString(xmlscrubbed, function (err, result) {
var cssx = result['return']['css'];
// console.log(cssx);
// console.log(spacer);
complete();
});
});
});
// SOAP - SEND AXL CALL - CSS
soapRequest.write(soapBody);
soapRequest.end();
// SOAP - SEND AXL CALL - PARTITIONS
var soapRequest2 = https.request(options, soapResponse2 => {
soapResponse2.setEncoding('utf8');
soapResponse2.on('data', chunk => {
soapreplyp += chunk
});
// HTTP.REQUEST - RESULTS + RENDER
soapResponse2.on('end', () => {
// EDIT - SCRUB XML OUTPUT
var rmline1p = soapreplyy.replace(/<\?xml\sversion='1\.0'\sencoding='utf-8'\?>/g, '');
var rmline2p = rmline1.replace(/<soapenv:Envelope\sxmlns:soapenv="http:\/\/schemas.xmlsoap.org\/soap\/envelope\/">/g, '');
var rmline3p = rmline2.replace(/<soapenv:Body>/g, '');
var rmline4p = rmline3.replace(/<ns:listCssResponse\sxmlns:ns="http:\/\/www\.cisco\.com\/AXL\/API\/[0-9]*\.[0-9]">/g, '');
var rmbottomup1p = rmline4.replace(/<\/soapenv:Envelope>/g, '');
var rmbottomup2p = rmbottomup1.replace(/<\/soapenv:Body>/g, '');
var xmlscrubbedp = rmbottomup2.replace(/<\/ns:listCssResponse>/g, '');
console.log(xmlscrubbedp);
console.log(spacer);
// XML2JS - TESTING
parser.parseString(xmlscrubbedp, function (err, result) {
var partitionsx = result['return']['css'];
// console.log(partitionsx);
// console.log(spacer);
complete();
});
});
});
// SOAP - SEND AXL CALL - PARTITIONS
soapRequest2.write(soapBody2);
soapRequest2.end();
// PAGE - RENDER
function complete() {
if (cssx !== null && partitionsx !== null) {
res.render('cucmmapper-results.html', {
title: 'CUCM Toolbox',
cucmpub: cucmpub,
cssx: cssx,
partitionsx: partitionsx,
})
} else {
res.render('cucmerror.html', {
title: 'CUCM Toolbox',
})
}
};
});
}
Any help or suggestions would be greatly appreciated.
OK, so the thing to remember is that there is always one request mapped to one response in HTTP. So you can't send multiple requests and expect to get just one response from that.
Instead, you need to have the server keep track of what's been posted (perhaps in a database on a production app), and respond to each request in turn. One way might be to respond with partial documents, or respond with other codes that indicate the submission was accepted but that you need to send another request to push more info, that sort of thing.
But again, you can't strictly accept multiple requests without responding and then respond only after all requests are given.