AWS Lambda finish before sending message to SQS - node.js

I'm running a "Node.JS" lambda on AWS that sends a message to SQS.
For some reason the SQS callback function get execute only once every couple of calls. It's looks like that the thread that running the lambda finish the run (because it's not a synchronous call to SQS and also can't return a Future) and therefore the lambda doesn't "stay alive" for the callback to get executed.
How can I solve this issue and have the lambda wait for the SQS callback to get execute?
Here is my lambda code:
exports.handler = async (event, context) => {
// Set the region
AWS.config.update({region: 'us-east-1'});
// Create an SQS service object
var sqs = new AWS.SQS({apiVersion: '2012-11-05'});
const SQS_QUEUE_URL = process.env.SQS_QUEUE_URL;
var params = {
MessageGroupId: "cv",
MessageDeduplicationId: key,
MessageBody: "My Message",
QueueUrl: SQS_QUEUE_URL
};
console.log(`Sending notification via SQS: ${SQS_QUEUE_URL}.`);
sqs.sendMessage(params, function(err, data) { //<-- This function get called about one time every 4 lambda calls
if (err) {
console.log("Error", err);
context.done('error', "ERROR Put SQS");
} else {
console.log("Success", data.MessageId);
context.done(null,'');
}
});
};

You should either stick to callback based approach, or to promise based one. I recommend you to use the latter:
exports.handler = async (event, context) => {
// Set the region
AWS.config.update({region: 'us-east-1'});
// Create an SQS service object
var sqs = new AWS.SQS({apiVersion: '2012-11-05'});
const SQS_QUEUE_URL = process.env.SQS_QUEUE_URL;
var params = {
MessageGroupId: "cv",
MessageDeduplicationId: key,
MessageBody: "My Message",
QueueUrl: SQS_QUEUE_URL
};
console.log(`Sending notification via SQS: ${SQS_QUEUE_URL}.`);
try {
await sqs.sendMessage(params).promise(); // since your handler returns a promise, lambda will only resolve after sqs responded with either failure or success
} catch (err) {
// do something here
}
};
P.S. Instantiating aws classes in the handler is not a good idea in lambda environment, since it increases the cold start time. It's better to move new AWS.SQS(...) action out of handler and AWS.config.update() too, since these actions will be executed on each call of the handler, but you really need them to be executed only once.

Related

How to get AWS SQS queue ARN in nodeJS?

I'm trying to build an application with a basic client-server infrastructure. The server infrastructure is hosted on AWS, and when a client logs on, it sends a message to the server to set up various infrastructure considerations. One of the pieces of infrastructure is an SQS Queue that the client can poll from to get updates from the server (eventually I'd like to build a push service but I don't know how for right now).
I'm building this application in NodeJS using the Node AWS SDK. The problem I'm having is I need the queue ARN to do various things like subscribe the SQS queue to an SNS topic that the application uses, but the create queue API returns the queue URL, not ARN. So I can get the ARN from the URL using the getQueueAttributes API, but it doesn't seem to be working. Whenever I call it, I get undefined as the response. Here's my code, please tell me what I'm doing wrong:
exports.handler = (event, context, callback) => {
new aws.SQS({apiVersion: '2012-11-05'}).createQueue({
QueueName: event.userId
}).promise()
)
.then(data => { /* This has the Queue URL */
new aws.SQS({apiVersion: '2012-11-05'}).getQueueAttributes({
QueueUrl: data.QueueUrl,
AttributeNames: ['QueueArn']
}).promise()
})
.then(data => {
console.log(JSON.stringify(data)); /* prints "undefined" */
})
/* Some more code down here that's irrelevant */
}
Thanks!
const AWS = require('aws-sdk');
const sqs = new AWS.SQS();
exports.handler = async(event, context, callback) => {
var params = {
QueueUrl: 'my-queue-url',
AttributeNames: ['QueueArn']
};
let fo = await sqs.getQueueAttributes(params).promise();
console.log(fo);
};
and it printed
{
ResponseMetadata: { RequestId: '123456-1234-1234-1234-12345' },
Attributes: {
QueueArn: 'arn:aws:sqs:eu-west-1:12345:my-queue-name'
}
}
With the help of Ersoy, I realized that I was using block-formatting (with {}) to write my Promises, but I was never returning anything from those blocks. I had thought that the last value in the Promise block was the return value by default, but it seems that was not the case. When I added return before the SQS API command, then it worked (without using async/await).

AWS Lambda node-soap function (serverless framework)

I am testing a call to a SOAP service using node-soap library.
This works fine as a standalone node.js app and the SOAP service responds, however when I package the same code up as a serverless AWS lambda function (using serverless framework, but also executed directly in AWS lambda), it doesn’t appear to create the soap client.
Any thoughts on why this might be happening?
export async function main(event, context) {
var soap = require('soap');
var url = 'https://service.blah.co.uk/Service/Function.asmx?wsdl';
var soapOptions = {
forceSoap12Headers: true
};
var soapHeader = {
'SOAPAction': 'http://www.blah.co.uk/Services/GetToken'
};
var params = {
xmlMessage: message
};
console.log(JSON.stringify(params));
soap.createClient(url, soapOptions, function (err, client) {
//the serverless AWS lambda function never reaches this point (deployed and invoked locally)
console.log("In create client")
if (err) console.log(err);
client.addSoapHeader(soapHeader);
client.GetToken(params, function (err, data) {
console.log(data);
});
});
}
I have created a very minimal working example using async/await. Rather using the old style callback approach, just invoke soap.createClientAsync(url).
Here's the code:
'use strict';
const soap = require('soap');
module.exports.hello = async (event, context) => {
const url = 'http://www.thomas-bayer.com/axis2/services/BLZService?wsdl';
const client = await soap.createClientAsync(url)
console.log(client)
return {
statusCode: 200,
message: 'It works!'
}
}
And here are the logs (partially):
EDIT: Function's output (via AWS's Lambda Console)
EDIT 2 - The REAL problem
Check the link above around async/await. The async keyword will execute asynchronously and will return a Promise. Since it's an asynchronous function, your Lambda is being terminated before it could actually execute your callback.
Using async/await will make your code simpler and will have you stop beating your brains out for such a small thing like this.

AWS SES create template with lambda function always return null

so on my first time learning AWS stuff (it is a beast), I'm trying to create e-mail templates, I have this lambda function:
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region
AWS.config.update({ region: "us-east-1" });
exports.handler = async (event, context, callback) => {
// Create createTemplate params
var params = {
Template: {
TemplateName: "notification" /* required */,
HtmlPart: "HTML_CONTENT",
SubjectPart: "SUBJECT_LINE",
TextPart: "sending emails with aws lambda"
}
};
// Create the promise and SES service object
const templatePromise = new AWS.SES({ apiVersion: "2010-12-01" })
.createTemplate(params)
.promise();
// Handle promise's fulfilled/rejected states
templatePromise
.then((data) => {
console.log(data);
callback(null, JSON.stringify(data) );
// also tried callback(null, data);
}, (err) => {
console.error(err, err.stack);
callback(JSON.stringify(err) );
});
as far as I am understanding, this function should return me a template? an object, anything? when I use the lambda test functionality I always got null in the request response
does anyone know what I am doing wrong here?
edit: and It is not creating the e-mail template, I check the SES Panel - email templates and it is empty
edit2: if I try to return a string eg: callback(null, "some success message"); it does return the string, so my guess is something wrong with the SES, but this function is exactly what we have in the AWS docs, so I assume it should just work..
Try not to resolve the Promise and change your code to just returning it as-is:
return await templatePromise;
which should present you some more detail of what is really going wrong in your code - it might be some hidden access issue - so you might need to adjust the role your lambda function is using. createTemplate on the other side should not return much in case of successful execution but just create the template.
Also try to follow the following try/catch pattern when using async (as described here in more detail: https://aws.amazon.com/de/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/)
exports.handler = async (event) => {
try {
data = await lambda.getAccountSettings().promise();
}
catch (err) {
console.log(err);
return err;
}
return data;
};

Return data from async AWS Lambda

How do I get the data back from a lambda invoked with as an event to the calling function?
Essentially the lambda function I have is:
exports.handler = function(event, context, callback) {
var data = {};
data.foo ='hello';
callback(null, data)
}
and the invoking function looks like this:
var AWS = require('aws-sdk');
var lambda = new AWS.Lambda();
var params = {
FunctionName: 'SomeFunction',
InvocationType: 'Event'
};
lambda.invoke(params, function (err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
} else {
console.log(JSON.stringify(data, null, 2));
}
});
However the only thing I get back from the function is
{
"StatusCode": 202,
"Payload": ""
}
I thought the point of the callback parameter was to allow the invoking function to get the data when the function has finished. Am I using it wrong or is what I am asking not possible with Lambdas?
When you invoke the Lambda function you need to set the InvocationType to 'RequestResponse' instead of 'Event'.
When using the Event type your callback is invoked when the payload has been received by AWS's servers. When using the RequestResponse type your callback is invoked only after the Lambda function has completed and you will receive the data it provided to its callback. It is not possible to do what you want with the Event type.
As #MatthewPope commented in this answer, if you do need the results from an asynchronous Lambda execution, you should consider having the Lambda function write its results to S3 (or something to that effect) at a known location where clients of the Lambda function can periodically check for the existence of the result.

AWS Lambda get context message

I am using the test function from AWS console:
console.log('Loading event');
exports.handler = function(event, context) {
console.log('value1 = ' + event.key1);
console.log('value2 = ' + event.key2);
console.log('value3 = ' + event.key3);
context.done(null, 'Hello World'); // SUCCESS with message
};
And calling it in nodejs as follows:
var params = {
FunctionName: 'MY_FUNCTION_NAME', /* required */
InvokeArgs: JSON.stringify({
"key1": "value1",
"key2": "value2",
"key3": "value3"
})
};
lambda.invokeAsync(params, function(err, data) {
if (err) {
// an error occurred
console.log(err, err.stack);
return cb(err);
}
// successful response
console.log(data);
});
and everything works fine:
//Console Output
{ Status: 202 }
But I was expecting to receive the message from context.done(null, 'Message') as well...
Any idea how to get the message?
As Eric mentioned, currently Lambda doesn't offer a REST endpoint to run the function and return its result, but may in the future.
Right now, your best bet would be to use a library like lambdaws, which wraps the function deployment and execution for you and handles returning results via an SQS queue. If you'd like more control by rolling your own solution, the process is straightforward:
Create an SQS queue
Have your Lambda function write its result to this queue
In your client, poll the queue for a result
You are calling invokeAsync, so your Lambda function is run asynchronously. This mean you get the success means back at the point your function is successfully started, and not after it completes.
As of this writing, AWS Lambda does not yet offer a way to invoke a function synchronously, returning information from the function directly back to the caller. However, this appears to be a common request and Amazon has publicly stated they are considering the feature.

Resources