Not going into AWS HttpClient.handleRequest to elasticsearch in lambda, Nodejs - node.js

I know this same question was basically asked and answered, however, trying to implement the answer did not get it to work. Here is the original question: AWS.HttpClient handleRequest is not working in AWS lambda
I tried putting async/await on multiple different portions of the request, but none of them worked as mentioned in one of the comments in the referred to link.
The situation is that I have a lambda function that listens for events in the S3 buckets, when an event happens it is supposed to index the documents in elasticsearch service. The issue happens when a the PUT request is sent to es.
I have done the test event with an S3 bucket and it WORKS, but for some reason it will hang/not go into the handleRequest function when I run an actual event to my S3 bucket.
Here is my code:
Index.js
const AWS = require('aws-sdk');
const s3 = new AWS.S3()
const elastic_client = require('elastic.js');
exports.handler = async (event, context) => {
const Bucket = event.Records[0].s3.bucket.name;
const Key = event.Records[0].s3.object.key;
const data = await s3.getObject({ Bucket, Key }).promise();
for (const quote_doc of data.Body) {
elastic_client.indexQuote(quote_doc);
}
}
elastic.js
var AWS = require('aws-sdk');
require('dotenv').config();
var region = process.env.AWS_REGION;
var domain = process.env.AWS_ELASTIC_DOMAIN;
function indexQuote(quote) {
var endpoint = new AWS.Endpoint(domain);
var request = new AWS.HttpRequest(endpoint, region);
var index = 'quotes';
var type = '_doc';
var id = quote.QuoteId;
request.method = 'PUT';
request.path += index + '/' + type + '/' + id;
request.body = JSON.stringify(quote);
request.headers['host'] = domain;
request.headers['Content-Type'] = 'application/json';
request.headers['Content-Length'] = Buffer.byteLength(request.body);
var credentials = new AWS.EnvironmentCredentials('AWS');
credentials.accessKeyId = process.env.AWS_ACCESS_KEY_ID;
credentials.secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
var signer = new AWS.Signers.V4(request, 'es');
signer.addAuthorization(credentials, new Date());
var client = new AWS.HttpClient();
client.handleRequest(request, null, function(response) { // Here is where it gets hung up
console.log(response.statusCode + ' ' + response.statusMessage); // Never outputs this
var responseBody = '';
response.on('data', function (chunk) {
responseBody += chunk;
});
response.on('end', function (chunk) {
console.log('Response body: ' + responseBody);
});
}, function(error) {
console.log('Error: ' + error);
});
}
The confusing part for me is that it works fine when i do a test event, and it works fine when I index it locally on my own computer, but then just doesn't go into the handleRequest. Any help/direction is appreciated, thank you.
Edit:
package.json
{
"dependencies": {
"aws-sdk": "*",
"aws-xray-sdk": "^3.2.0",
"dotenv": "^8.2.0"
}
}

Try wrapping the handleRequest function inside a Promise. Your function indexQuote() would look almost the same, but at the end it would return a Promise
function indexQuote(quote) {
...
return new Promise((resolve, reject) => {
client.handleRequest(request, null,
response => {
const { statusCode, statusMessage, headers } = response;
let body = '';
response.on('data', chunk => {
body += chunk;
});
response.on('end', () => {
const data = {
statusCode,
statusMessage,
headers
};
if (body) {
data.body = body;
}
resolve(data);
});
},
err => {
reject(err);
});
});
And then you can await and inspect the result:
const result = await indexQuote(quote);
console.log("Index result: " + result);

Related

Lambda NodeJS works intermittent with async method

I have a lambda function written in node.js. The lambda logic is to extract secrets from aws secret manager and pass the params to another module with a token to make a soap call.This lambda was working as expected without async method to get secreats when secreats hardcoded.
But after adding an async getsecrets method the lambda works intermittently while testing in aws console. The getsecreats returns params every time. But the lambda terminates without the intended output. It doesn't make any soap call.
The logic of the lambda code is
Get the secret
Pass the secret to the CallServicewithToken()
get XML data from soap and populate it into the database.
Why it works intermittently when introducing async calls? I have tried introducing async to all methods. still the same issue. Appreciate any input/help.
The code is as below
'use strict';
var soap = require('strong-soap').soap;
const aws = require("aws-sdk");
const request = require('request');
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;
var rfcUrl = process.env.rfcser_url;
var testUrl = process.env.faccser_url;
var region = process.env.region;
var secretName = process.env.secretName;
var oauthurl = process.env.oauthurl;
var rfcRequestArgs;
var parsedResult;
var tokenrequest;
exports.handler = async function(event, context,callback) {
const secret = await getSecrets();
CallServicewithToken();
};
async function getSecrets() {
const config = { region : region, apiVersion: 'latest' };
let secretManager = new aws.SecretsManager(config);
const Result = await secretManager.getSecretValue({ SecretId: secretName }).promise();
parsedResult = JSON.parse(Result.SecretString);
tokenrequest = {
url: oauthurl,
form: {
client_id: parsedResult.client_id,
client_secret: parsedResult.client_secret,
grant_type:'client_credentials',
scope:parsedResult.scope
}
};
console.log('client_id: ' + parsedResult.client_id);
console.log('client_secret: ' + parsedResult.client_secret);
console.log('testservice_userid: ' + parsedResult.testservice_userid);
}
function CallServicewithToken() {
console.log('Calling CallServicewithToken ');
request.post(tokenrequest, (err, res, body) => {
if (err) {
console.log(' error2: ' + err);
return;
}
rfcRequestArgs = {
UserName: parsedResult.service_username
};
var tokenobj = JSON.parse(body);
var token = 'Bearer '+ tokenobj.access_token;
var credentials = {
Authorization:{
AuthToken: token
}
}
var options = {};
console.log('Calling Service.');
soap.createClient(rfcUrl, options, function(err, client) {
client.addSoapHeader(
`<aut:Authorization xmlns:aut="http://soap.xyznet.net">
<aut:AuthToken>${token}</aut:AuthToken>
</aut:Authorization>`
);
var method = client['GetSourceLocationData'];
method(RequestArgs, function(err, result, envelope, soapHeader) {
if (err) {
console.log('error3: ' + err);
return;
}
else
{
console.log('Received response from GetSourceLocationData().');
CallTESTService(JSON.stringify(result));
}
});
function CallTESTService(LocData)
{
var testRequestArgs = {
UserID: parsedResult.testservice_userid,
AuthorizationKey: parsedResult.testservice_authorizationkey,
LocationData: LocData
};
console.log('Calling test Service.');
options = {};
soap.createClient(testUrl, options, function(err, client) {
client.addSoapHeader(
`<Authorization xmlns="testWebService">
<AuthToken>${token}</AuthToken>
</Authorization>`
);
var test_method = client['UpdateLocationData'];
console.log('Called UpdateLocationData service method.');
test_method(testRequestArgs, function(err, result, envelope, soapHeader) {
if(err) {
console.log('test error: ' + err);
return;
}
else
{
console.log('Response: \n' + JSON.stringify(result));
console.log('Data updated through test service method.');
}
});
});
}
});
});
}

An issue with reading a gzipped file (.gz) with IBM Cloud Function (Action: Node.js 12)

I can read the data.json.gz file on my local machine with the code mentioned below (node --version: v14.15.0). But when I try to use the same in IBM Cloud with an Action (Node.js 12) to read the same file from an Object Store Bucket, I get the below error
["stderr: ERROR: undefined - input_buf.on is not a function"].
I am very new to NodeJS; Can someone help to identify the issue here?
I do appreciate your support.
Code that works on Local machine (Windows 10):
function decompressFile(filename) {
var fs = require("fs"),
zlib = require("zlib"),
var input = fs.createReadStream(filename);
var data = [];
input.on('data', function(chunk){
data.push(chunk);
}).on('end', function(){
var buf = Buffer.concat(data);
zlib.gunzip(buf, function(err, buffer) {
if (!err) {
var dataString = buffer.toString()
console.log(dataString, dataString+'\n');
var dataJSON = JSON.parse(dataString.toString('utf8'));
}else{
console.log(err);
}
});
});
}
decompressFile("data.json.gz");
Code that does not work on IBM Cloud Function and Object Store Bucket:
// Get file contents of gzipped item
async function getGzippedItem(cosClient, bucketName, itemName) { // <<< async keyword added
const fs = require('fs');
const zlib = require('zlib');
return await cosClient.getObject({ // <<< turned into assignment with await
Bucket: bucketName,
Key: itemName
}).promise()
.then((instream=fs.createReadStream(itemName)) => {
if (instream != null) {
var data = [];
var input_buf = instream.Body
input_buf.on('data', function(chunk){
data.push(chunk);
}).on('end', function() {
var buf = Buffer.concat(data);
zlib.gunzip(buf, function (err, buffer) {
if (!err) {
var dataString = buffer.toString()
var dataJSON = JSON.parse(dataString.toString('utf8'));
} else {
console.log(err);
}
});
});
return buf
}
})
.catch((e) => {
console.error(`ERROR: ${e.code} - ${e.message}\n`);
});
};
async function main(params) {
bucketName = 'bucket'
itemName = 'data.json.gz'
var ibm = require('ibm-cos-sdk');
var util = require('util');
var fs = require('fs');
// Initializing configuration
const myCOS = require('ibm-cos-sdk');
var config = {
endpoint: 'endpoint',
apiKeyId: 'apiKeyId',
ibmAuthEndpoint: 'ibmAuthEndpoint',
serviceInstanceId: 'serviceInstanceId',
};
var cosClient = new myCOS.S3(config);
gzippedItemContent = await getGzippedItem(cosClient, bucketName, itemName) // <<< await keyword added
console.log(">>>>>>>>>>>>>>>: ", typeof gzippedItemContent, gzippedItemContent )
}
The message is telling you, that your input_buf object is not of the type you expect it to be. The result of your createReadStream() call is just a stream:
[Stream] the readable stream object that can be piped or read from (by registering 'data' event listeners).
So you should be able to access the value directly
(not declaring var input_buf = instream.Body):
var getObjectStream = cosClient.getObject({
Bucket: 'BUCKET',
Key: 'KEY'
}).createReadStream();
getObjectStream.on('data', function(c) {
data += c.toString();
});
Have a look at the test section of the ibm-cos-sdk-js project, it is describing how to use the API.

How to chain writeFile() and OCR with NodeJS in Google Cloud Functions?

The scenario is as follows:
From an Amazon S3 bucket a file is fetched, then it is stored in a temporary folder and then Object Character Recognition is to be performed using the API.
Unfortunately, this doesn't work, I think it's due to the asynchronous/synchronous execution, but I've already tried several variants with callbacks/promises and didn't get any further.
If someone can give me a hint on how to construct this scenario I would be grateful!
The current error is:
TypeError: Cannot read property 'writeFile' of undefined at Response.<anonymous> (/srv/index.js:38:32) (it's the 'await fs.writeFile(dir,data);' line)
/**
* Responds to any HTTP request.
*
* #param {!express:Request} req HTTP request context.
* #param {!express:Response} res HTTP response context.
*/
const AWS = require('aws-sdk');
const fs = require('fs').promises;
const Vision = require('#google-cloud/vision');
var os = require('os');
exports.helloWorld = async (req,res) => {
var bucket, fileName, fileUrl;
req.on('data', chunk => {
body += chunk.toString();
data.push(chunk);
});
req.on('end', () => {
bucket = JSON.parse(data).value1;
fileName = JSON.parse(data).value2;
fileUrl = JSON.parse(data).value3;
var s3 = new AWS.S3();
s3.getObject({
Bucket: bucket,
Key: fileName
},
async function(error, data) {
if (error != null) {
console.log("Failed to retrieve an object: " + error);
} else {
console.log("Loaded " + data.ContentType + " bytes");
var tmpdir = os.tmpdir();
var dir = tmpdir+'/'+fileName;
try{
await fs.writeFile(dir,data);
const vision = new Vision.ImageAnnotatorClient();
let text;
await vision
.textDetection('/tmp/' + fileName)
.then(([detections]) => {
const annotation = detections.textAnnotations[0];
console.log(1);
text = annotation ? annotation.description : '';
console.log(`Extracted text from image (${text.length} chars)`);
console.log(1);
console.log(text);
resolve("Finished ocr successfully");
})
.catch(error =>{
console.log(error);
reject("Error with OCR");
})
}catch(error){
console.log(error);
}
}
},
);
let message = bucket + fileName + fileUrl;
res.status(200).send(message);
});
};
You're getting that error, because you're running on an older version of Node (< 10.0.0), where fs.promises is not available. That's why fs is undefined, and you're getting:
TypeError: Cannot read property 'writeFile' of undefined at Response.<anonymous> (/srv/index.js:38:32) (it's the 'await fs.writeFile(dir,data);' line)
Either use a newer version, or just promisify the code.
const { promisify } = require('util');
const fs = require('fs');
// const fs = require('fs').promises
const writeFile = promisify(fs.writeFile);
And now use writeFile instead of fs.writeFile in your code.
Aside from that, there are a few issues with your code.
req.on('data', chunk => {
body += chunk.toString();
data.push(chunk);
});
data is not defined anywhere, and it doesn't make sense to push data into an array and then running JSON.parse on that array, given the next few lines.
bucket = JSON.parse(data).value1;
fileName = JSON.parse(data).value2;
fileUrl = JSON.parse(data).value3;
Furthermore, JSON.parse should be called only once, instead of parsing the same string (which is an array in your code, and will yield an error) 3 times.
const values = JSON.parse(body); // should be body instead of data with the posted code
bucket = values.value1;
fileName = values.value2;
fileUrl = values.value3;
This can be improved greatly by just posting bucket, fileName & fileUrl in the JSON instead of valueN.
const { bucket, fileName, fileUrl } = JSON.parse(body);
The whole code can be rewritten into:
const AWS = require('aws-sdk');
const { promisify } = require('util');
const fs = require('fs');
const Vision = require('#google-cloud/vision');
const os = require('os');
const path = require('path');
const writeFile = promisify(fs.writeFile);
exports.helloWorld = async (req,res) => {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', async() => {
// post { "bucket": "x", "fileName": "x", "fileUrl": "x" }
const { bucket, fileName, fileUrl } = JSON.parse(body);
var s3 = new AWS.S3();
try {
const data = await s3.getObject({
Bucket: bucket,
Key: fileName
}).promise();
const tmpdir = os.tmpdir();
const filePath = path.join(tmpdir, fileName)
await writeFile(filePath, data);
const vision = new Vision.ImageAnnotatorClient();
const [detections] = await vision.textDetection(filePath)
const annotation = detections.textAnnotations[0];
const text = annotation ? annotation.description : '';
console.log(`Extracted text from image (${text.length} chars)`);
let message = bucket + fileName + fileUrl;
res.status(200).send(message);
} catch(e) {
console.error(e);
res.status(500).send(e.message);
}
});
};
NOTE: I don't know if Vision API works like this, but I used the same logic and parameters that you're using.

How to return prematurely from lambda

I'm trying to trigger a lambda when I drop a new file in a bucket.
This code is working as in it's detecting the file and send the info to my API.
I'm also trying to ignore every file not name "text.txt" but I can't figure out how to return from the lambda inside that includes block
const http = require('http');
exports.handler = async (event, context) => {
return new Promise((resolve, reject) => {
const srcRegion = event.Records[0].awsRegion;
const srcEventTime = event.Records[0].eventTime;
const srcEventName = event.Records[0].eventName;
const srcIP = event.Records[0].requestParameters.sourceIPAddress;
const srcBucket = event.Records[0].s3.bucket.name;
const srcKey = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " "));
const srcETag = event.Records[0].s3.object.eTag;
if (!srcKey.includes('text.txt')) {
resolve('Not original file');
}
const data = JSON.stringify({
lambda: {
src_bucket: srcBucket,
src_key: srcKey,
src_region: srcRegion,
src_event_time: srcEventTime,
src_event_name: srcEventName,
src_ip: srcIP,
src_etag: srcETag
}
});
const options = {
host: '*****************'
path: '*****************'
port: '*****************'
method: '*****************'
headers: '*****************'
};
const req = http.request(options, (res) => {
res.on('data', function(d) {
console.log(d);
});
});
req.on('error', (e) => {
// reject(e.message);
resolve('Error');
});
// send the request
req.write(data);
req.end();
resolve('Success');
});
};
Try this inside the includes block: context.done(undefined, 'Done.')

Dialogflow Node.js is not getting the value of the https.get inside app.inent

I am having trouble getting the value returned by my https.get function inside the my Dialogflow intent to close the conversation. Regardless whether I execute this call in the app.intent or pass it one to an external function, it fails. I new to node.js but have used angular.js and javascript before but not having success in being able to close the conversation with a response. Google Actions emulator gives me the error
MalformedResponse
expected_inputs[0].input_prompt.rich_initial_prompt: 'rich_response'
must contain at least one item.
Below is my code:
app.intent('mywebhook', (conv, params) => {
const stateName = params['geo-state-us'];
console.log("My State is " + stateName);
var pathString = 'api path' + encodeURIComponent(stateName);
var request = https.get({
host: 'www.mydomainame.com',
path: pathString
}, function (response){
var json = "";
response.on('data', function(chunk) {
json += chunk;
});
response.on('end', function(){
var jsonData = JSON.parse(json);
var myfirstvar = jsonData[0].firstvar;
var chat = "the value of first var is " + chat;
console.log(chat); // this works fine
conv.close(chat);
});
}).on('error', (e) => {
console.error(e);
});
}
I even tried doing conv.close(chat) outside and JSON.stringify(request) to get the value of myfirstvar but nothing worked. Spent a whole day trying different things but no avail.
Try the refactored code below ( and comment if that works or not ):
app.intent('mywebhook', myWebHookFunction);
function myWebHookFunction(conv, params) {
return new Promise(function (resolve, reject) {
const stateName = params['geo-state-us'];
console.log("My State is " + stateName);
var pathString = 'api path' + encodeURIComponent(stateName);
var request = https.get({
host: 'www.mydomainame.com',
path: pathString
}, function (response) {
var json = "";
response.on('data', function (chunk) {
json += chunk;
});
response.on('end', function () {
var jsonData = JSON.parse(json);
var myfirstvar = jsonData[0].firstvar;
var chat = "the value of first var is " + chat; // chat or myfirstvar?
console.log(chat); // this works fine
conv.close(chat);
resolve();
});
}).on('error', (e) => {
console.error(e);
reject();
});
});
}

Resources