node.js and AWS Lambda Function continue function execution after callback - node.js

I am trying to use a lambda function to alter a database and then send a push notification.
I don't want to wait for the push notification server to reply. In the occasional case that the push notification is unsuccessful, that is not a concern. It is more important that the function executes in a timely manner.
Currently I'm using the following two functions. Everything works as expected except that there doesn't seem to be any time saving. ie, when there is no device token and push is not required the function is very fast. When a push is required it is very slow. That tells me what I'm doing is wrong and the function is still waiting for a callback.
I have not used node much and know there are perils with trying to use asynchronous models from other languages. Just wondering how to overcome this case.
Function for Database Insertion:
const AWS = require('aws-sdk');
var mysql = require('mysql');
var lambda = new AWS.Lambda();
exports.handler = (event, context, callback) => {
var connection = mysql.createConnection({
host: "databaseHost",
user: "databaseUser",
password: "databasePassword",
database: "databaseName",
multipleStatements: true
});
var sql = "INSERT INTO someTable SET item_id = ?, item_name = ?"
var inserts = [event.itemId, event.itemName];
connection.query(sql, inserts, function (error, results, fields) {
connection.end();
// Handle error after the release.
if (error) {
callback(error);
} else {
if (event.userToken !== null) {
callback(null, results);
var pushPayload = { "deviceToken": event.deviceToken };
var pushParams = {
FunctionName: 'sendPushNotification',
InvocationType: 'RequestResponse',
LogType: 'Tail',
Payload: JSON.stringify(pushPayload)
};
lambda.invoke(pushParams, function (err, data) {
if (err) {
context.fail(err);
} else {
context.succeed(data.Payload);
}
});
} else {
//callback(null, results);
callback(null, results);
}
}
});
};
Push notification function:
const AWS = require('aws-sdk');
var ssm = new AWS.SSM({ apiVersion: '2014-11-06' });
var apn = require("apn");
exports.handler = function (event, context) {
var options = {
token: {
key: "key",
keyId: "keyId",
teamId: "teamId"
},
production: true
};
var token = event.deviceToken;
var apnProvider = new apn.Provider(options);
var notification = new apn.Notification();
notification.alert = "message";
notification.topic = "com.example.Example";
context.callbackWaitsForEmptyEventLoop = false;
apnProvider.send(notification, [deviceToken]).then((response) => {
context.succeed(event);
});
};

In pushParams change value of InvocationType to "Event" so that calling lambda will not wait for the response. It will just invoke lambda and return you the callback.
example:
var pushParams = {
FunctionName: 'sendPushNotification',
InvocationType: 'Event',
LogType: 'Tail',
Payload: JSON.stringify(pushPayload)
};

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.');
}
});
});
}
});
});
}

DynamoDB Scan with Lambda not returning elements

I have a Lambda with the following code:
// Load the AWS SDK for Node.js.
var AWS = require("aws-sdk");
// Set the AWS Region.
AWS.config.update({ region: "us-east-2" });
exports.handler = function(event, context, callback) {
// Create DynamoDB service object.
var ddb = new AWS.DynamoDB({ apiVersion: "2012-08-10" });
var params = {
TableName: "Ranking",
ProjectionExpression: "#username, Score, Duration",
FilterExpression: "#username = :username",
ExpressionAttributeNames: {
"#username": "Username",
},
ExpressionAttributeValues: {
":username": {
S: 'Alberto'
},
}
};
let toReturn = [];
ddb.scan(params, function (err, data) {
if (err) {
toReturn = err
} else {
toReturn = data.Items;
}
});
let response = {
statusCode: 200,
body: JSON.stringify(toReturn)
};
callback(null, response)
};
However I always see [] as response...
My current DB has the following records:
So my question is... why I don't get back that item?
Since you are using callbacks, your code should be as below. Also Duration is reserved keyword, so it also needs to be modified as below:
// Load the AWS SDK for Node.js.
var AWS = require("aws-sdk");
// Set the AWS Region.
AWS.config.update({ region: "us-east-2" });
exports.handler = function(event, context, callback) {
// Create DynamoDB service object.
var ddb = new AWS.DynamoDB({ apiVersion: "2012-08-10" });
var params = {
TableName: "Ranking",
FilterExpression: "#username = :username",
ProjectionExpression: "#username, Score, #duration",
ExpressionAttributeNames: {
"#username": "Username",
"#duration": "Duration",
},
ExpressionAttributeValues: {
":username": {
S: 'Alberto'
},
}
};
ddb.scan(params, function (err, data) {
if (err) {
callback(null, err)
} else {
callback(null, data.Items)
}
});
};

Lambda Invoke not triggering second lambda

I have gone through similar threads to fix this issue but I have had no luck. Both lambdas can be trigger independently of one another, and I am able to invoke the second Lambda through the command line, but my code does not work.
'use strict'
/* eslint max-statements: ['error', 100, { 'ignoreTopLevelFunctions': true }] */
const RespHelper = require('../../lib/response')
const { uuid } = require('uuidv4')
const AWS = require('aws-sdk')
const DB = require('./dynamo')
const respHelper = new RespHelper()
const Dynamo = new DB()
const lambda = new AWS.Lambda({
region: 'us-west-2'
})
const secondLambda = async (lambdaData) => {
var params = {
LogType: 'Tail',
FunctionName: 'second_lambda_name',
InvocationType: 'RequestResponse',
Payload: JSON.stringify(lambdaData)
}
lambda.invoke(params, function (err, data) {
if (err) {
console.log(err)
} else {
console.log(`Success: ${data.Payload}`)
}
})
}
exports.handler = async event => {
const id = uuid()
let bodyData = {
uuid: id,
user: 'owner#email.com',
processingStatus: 'IN_PROGRESS'
}
let payloadData = {
uuid: id,
user: 'owner#email.com',
processingStatus: 'COMPLETE'
}
try {
await Dynamo.writeRecordToDB(bodyData)
await secondLambda(payloadData)
return respHelper.sendResponse(200, { message: bodyData })
} catch (err) {
console.log(`Failure: ${err}`)
return respHelper.sendResponse(400, { message: 'ERROR' })
}
}
I have double checked the lambda role and it has the Invoke Lambda and Invoke Asynchronous Invoke permission on all resources. Console outputs don't give me any indication of why this is not working. Any help is appreciated.
You're awaiting a callback when you need to await a promise
const secondLambda = async lambdaData =>
lambda
.invoke({
LogType: 'Tail',
FunctionName: 'second_lambda_name',
InvocationType: 'RequestResponse',
Payload: JSON.stringify(lambdaData),
})
.promise()

AWS Cloudwatch Metric and callbackWaitsForEmptyEventLoop do not work together?

below is a simplification of my code.
const AWS = require('aws-sdk');
exports.handler = async (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = true;
AWS.config.update({region: 'cn-north-1'});
// Create CloudWatch service object
var cw = new AWS.CloudWatch({apiVersion: '2010-08-01'});
var params = {
MetricData: [
{
MetricName: 'PAGES_VISITED',
Dimensions: [
{
Name: 'UNIQUE_PAGES',
Value: 'URLS'
},
],
Unit: 'None',
Value: 1.0
},
],
Namespace: 'MyNewNameSpace'
};
cw.putMetricData(params, function(err, data) {
if (err) {
console.log("Error", err);
} else {
console.log("Success", JSON.stringify(data));
}
});
callback(null, "the result");
};
It seems that once I set the callbackWaitsForEmptyEventLoop = false then the metric cannot be put up there. I donot understand this conflict.
If you set callbackWaitsForEmptyEventLoop = false then your function execution terminates before all the callbacks are done. In this case, the function terminates before the callback from cw.putMetricData is ever called, so your code is not executed. It is likely that the operation on CloudWatch actually happens, but that you just don't see the callback, as it does not happen.
Here's your function, using the async/await model, without callbacks and without callbackWaitsForEmptyEventLoop:
const AWS = require('aws-sdk');
exports.handler = async event => {
AWS.config.update({region: 'cn-north-1'});
// Create CloudWatch service object
var cw = new AWS.CloudWatch({apiVersion: '2010-08-01'});
var params = {...};
await cw.putMetricData(params)
.promise()
.then(data => {console.log("Success", JSON.stringify(data));})
.catch(err => {console.log("Error", err);})
return "the result";
};

DynamoDB queries return no items within Lambda function

Basically, I have a DynamoDB connection within a Lambda function. Will post code below. This DynamoDB connection seems to be behaving properly - it's able to call the listTable and describeTable functions successfully, which means it's got the right configuration - but querying it returns nothing, even on queries I know are correct and have tested on the Dynamo console.
UPDATE: Was able to successfully query with a string on a separate index, but still unable to query based on a binary...
Here's a part of the Lambda function:
const AWS = require('aws-sdk');
const SNS = new AWS.SNS({ apiVersion: '2010-03-31', region: 'sa-east-1' });
const DDB = new AWS.DynamoDB({ apiVersion: '2012-08-10', region: 'sa-east-1' })
const Lambda = new AWS.Lambda({ apiVersion: '2015-03-31' });
const async = require('async');
const CREATE_NOTIFICATIONS = 'create-notifications'
const QUERY_TOKENS = 'query-tokens'
function getUsers(functionName, message, callback) {
var msg = JSON.parse(message);
var users = [];
console.log(DDB);
async.forEachOf(msg.targetsb64, function(value, index, cb) {
console.log("getUsers b64: ", value)
console.log(typeof(value))
DDB.describeTable({
TableName: 'tsGroups'
}, function(err, data) {
console.log(err)
console.log(data.Table.KeySchema)
})
DDB.query({
TableName: 'tsGroups',
KeyConditionExpression: "identifier = :v_user",
ExpressionAttributeValues: {
":v_user": {"B": value}
}
}, function(err, data) {
if (err) {
cb(err)
} else {
console.log("data: ", data)
console.log("items: ", data.Items)
data.Items.forEach(function(item) {
users.push.apply(users, item.users.BS)
})
cb()
}
})
}, function(err) {
if (err) {
callback(err)
} else {
console.log("getUsers users: ", users);
const promises = users.map((user) => invokeQueryTokens(functionName, msg, user));
Promise.all(promises).then(() => {
const result = `Users messaged: ${users.length}`;
console.log(result);
callback(null, result);
});
}
})
}
I've tried using KeyConditions instead of KeyConditionExpression, to no avail. Value refers to a base64 identifier string that's passed along from an earlier Lambda function - I've tried hard-coding the correct value, doesn't help. The describeTable function is only there to detail that DynamoDB is connecting properly, and in the correct region.
TL;DR: The data.Items value in the above code snippet is always an empty array, even when doing a query I know should return something. What's my error here?
Thanks, and cheers!

Resources