Unable to run AWS StepFunctions inside an AWS Lambda - node.js

I'm trying to start the execution of an AWS StepFunction from inside an AWS Lambda, but I receive null as result, with no error message. The StepFunctions here is an Express State Machine, so I use the method startSyncExecution(params = {}, callback), as pointed in the docs.
Here is the code of the Lambda:
const AWS = require('aws-sdk');
exports.handler = async(event, context, callback) => {
var params = {
stateMachineArn: "arn:aws:states:us-east-1:[AccountID]:stateMachine:BookLectureStateMachine",
input: JSON.stringify(event),
name: "test-from-lambda"
}
var stepfunctions = new AWS.StepFunctions();
console.log("Everything okay") //This one is logged
stepfunctions.startSyncExecution(params, function(err, data) {
console.log("This log isn't shown"); //This one isn't logged
if (err) {
callback(null, {
statusCode: 400,
body: err,
headers: {
'Access-Control-Allow-Origin': '*'
}
})
} else {
callback(null, {
statusCode: 200,
body: 'Lecture booked',
headers: {
'Access-Control-Allow-Origin': '*'
}
})
}
});
};
The response is null, nothing else.
I've checked the permissions, and the Lambda has Full Access to the Step Functions.
Any idea on how to solve it?
UPDATE
I think the StepFunction is not executed, since the logs are empty.
I increased the Lambda timeout to 1min in order to avoid timeout scenarios. The billed duration is about half a second

I believe it may have something to do with the mix of callbacks and async. Since you aren't using await in your handler anywhere, I would try removing async from the handler.
Either that or you could try changing the code to:
var data = await stepfunctions.startSyncExecution(params).promise()

Related

LambdaInvalidResponse nodejs

I have the following code running as an AWS Lambda function:
const getDb = require('./db');
const Service = require('./service');
exports.main = async function (event, context) {
context.callbackWaitsForEmptyEventLoop = false;
const endpoint = event.path.split('/')[1].toLowerCase();
const service = new Service(
await getDb()
);
const result = await service[endpoint](); // result might be number or string, i.e. 123 or '123'
return {
isBase64Encoded: false,
statusCode: 200,
statusDescription: '200 OK',
headers: {
'Set-cookie': 'cookies',
'Content-Type': 'application/json',
},
body: result,
};
};
When I invoke it with aws-cli or the testing function in AWS console it works, but when I put it behind an ALB (application load balancer) it returns 502 Bad Gateway.
I have viewed the Lambda output CloudWatch logs. The endpoint is correctly invoked by the load balancer, but the load balancer access logs have the following entry:
2021-11-08T16:28:24.033000Z "forward" "-" "LambdaInvalidResponse" "-" "-" "-" "-"
Any ideas why I'm experiencing this issue?
Not sure which of these things fixed it, but I did fix it:
Wrap all properties in response object in quotes, i.e. { "body": result } instead of { body: result }
serialized result, i.e. { "body": JSON.stringify({ result }) } instead of { "body": result }
removed property statusDescription from response object

"Error invoking Lambda: Unable to execute HTTP request: Read timed out"... for a simple POST request?

I'm struggling to do a simple POST request without it failing with this message :
Error invoking Lambda: Unable to execute HTTP request: Read timed out
My Lambda code consists of this :
const axios = require("axios");
const api = require('lambda-api')();
let PROJECT_ID = process.env.PROJECT_ID_CREATE
let TOKEN = process.env.TOKEN
api.post("/api-gitlab-launcher/create", async (lambdaRequest, res) => {
return {
"ami_id": lambdaRequest.body["ami_id"],
"ip_address":lambdaRequest.ip
}
}
)
exports.handler = async (event, context) => {
let config = {
method: 'post',
url: `https://gitlab.com/api/v4/projects/${PROJECT_ID}/trigger/pipeline?token=${TOKEN}&ref=terraform-v1-mirror`,
headers: {}
};
await axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
return await api.run(event, context);
}
This problem has been driving me mad for days... And I can't pinpoint where the bug is! If someone has an idea where it could from, I'd be glad.
UPDATE
It does work locally. Just tested it and the POST request is sent when run locally. I checked with the VPCs and it's allowing all outbound traffic.
UPDATE 2

Do I need to import AWS SDK into lambda

Might be a dumb question, but couldn't find a clear answer on stack/aws docs. My assumption is that it should be built in to lambda.
I am running Node10.x, with Axios module, in AWS Lambda. I have a successful function which checks a DNS/EC2/Endpoint pathway and returns the proper response. I want to automate it so it checks, lets say...every 10 minutes. It will notify me in SES if there is an error, and do nothing if its a good response.
All the functionality works, except I am having trouble getting SES integrated. inside the if statement below, i have added this code, the console.log works, so its just the SES part im having issues with.
exports.handler = async (event, context) => {
let aws = require('aws-sdk');
let ses = new aws.SES({
region: 'us-east-1'
});
let data = "document_contents=<?xml version=\"1.0\" encoding=\"UTF-8\"?><auth><user>fake</user><pass>info</pass></auth>";
var axios = require("axios");
var config = {
headers: { 'Content-Type': 'text/xml' },
};
let res = await axios.post('https://awebsiteidontwanttogiveout.com', data, config);
let eParams;
if (res.status === 200) {
console.log("Success");
eParams = {
Destination: {
ToAddresses: ["banana#apples.com"]
},
Message: {
Body: {
Text: {
Data: "Test SUCCESSFUL"
}
},
Subject: {Test SUCCESSFUL"
}
},
Source: "banana#apples.com"
};
ses.sendEmail(eParams);
}
if (res.status != 200) {
console.log("Failure");
eParams = {
Destination: {
ToAddresses: ["banana#apples.com"]
},
Message: {
Body: {
Text: {
Data: "Test FAIL"
}
},
Subject: {
Data: "Test FAIL"
}
},
Source: "banana#apples.com"
};
ses.sendEmail(eParams);
}
};
I'm getting a time out after 3 seconds. I uploaded a zip file to node, with dependencies. do I need to install AWS SDK and upload that with the file? shouldn't it be built in somehow? am I missing something in my SES call?
thanks
There are two issues to address:
sendEmail is an async function from AWS SDK, you have to use:
await ses.sendEmail(eParams).promise()
or else, Lambda would end execution before sendEmail method completes.
Lambda's default timeout is 3 seconds. This can be increased to a max of 15 minutes in the Lambda configuration.

Awaiting http request in AWS Lambda / Pulumi

I have an AWS Lambda function which triggers https request to Google API. I want the function to be awaitable, so that it does not end immediately, but only after getting response from Google API.
Yes, I know I pay for the execution, but this will not be called often, so it is fine.
The problem is that the http request does not seem to fire correctly. The callback is never executed.
I have made sure that the async/await works as expected by using setTimeout in a Promise. So the issue is somewhere in the https.request.
Also note that I am using Pulumi to deploy to AWS, so there might be some hidden problem in there. I just can't figure out where.
The relevant code:
AWS Lambda which calls the Google API
import config from '../../config';
import { IUserInfo } from '../../interfaces';
const https = require('https');
function sendHttpsRequest(options: any): Promise<any> {
console.log(`sending request to ${options.host}`);
console.log(`Options are ${JSON.stringify(options)}`);
return new Promise(function (resolve, reject) {
console.log(` request to ${options.host} has been sent A`);
let body = new Array<Buffer>();
const request = https.request(options, function (res: any) {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
if (res.statusCode != 200) {
reject(res.statusCode);
}
res.on('data', (data: any) => {
console.log(`body length is ${body.length}`);
console.log('data arrived', data);
body.push(data);
console.log('pushed to array');
console.log(data.toString());
});
});
request.on('end', () => {
console.error('Request ended');
// at this point, `body` has the entire request body stored in it as a string
let result = Buffer.concat(body).toString();
resolve(result);
});
request.on('error', async (err: Error) => {
console.error('Errooooorrrr', err.stack);
console.error('Errooooorrrr request failed');
reject(err);
});
request.end();
console.log(` request to ${options.host} has been sent B`);
});
}
/**
* AWS Lambda to create new Google account in TopMonks domain
*/
export default async function googleLambdaImplementation(userInfo: IUserInfo) {
const payload = JSON.stringify({
"primaryEmail": userInfo.topmonksEmail,
"name": {
"givenName": userInfo.firstName,
"familyName": userInfo.lastName
},
"password": config.defaultPassword,
"changePasswordAtNextLogin": true
});
const resultResponse: Response = {
statusCode: 200,
body: 'Default response. This should not come back to users'
}
console.log('Calling google api via post request');
try {
const options = {
host: 'www.googleapis.com',
path: '/admin/directory/v1/users',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': payload.length.toString()
},
form: payload
}
const responseFromGoogle = await sendHttpsRequest(options);
console.log('responseFromGoogle', JSON.stringify(responseFromGoogle));
}
catch (err) {
console.log('Calling google api failed with error', err);
resultResponse.statusCode = 503;
resultResponse.body = `Error creating new Google Account for ${userInfo.topmonksEmail}.`;
return resultResponse;
}
console.log('request to google sent');
return resultResponse;
}
The problem is that the http request does not seem to fire correctly. The callback is never executed.
I believe this part of the issue is related to some combination of (a) potentially not actually sending the https request and (b) not using the correct callback signature for https.request. See the documentation at https://nodejs.org/api/https.html#https_https_request_options_callback for details on both of these.
Use node-fetch package
The following example works for me using node-fetch:
import * as aws from "#pulumi/aws";
import fetch from "node-fetch";
const api = new aws.apigateway.x.API("api", {
routes: [{
method: "GET", path: "/", eventHandler: async (ev) => {
const resp = await fetch("https://www.google.com");
const body = await resp.text();
return {
statusCode: resp.status,
body: body,
}
},
}],
})
export const url = api.url;
Pulumi complains, it something like "Can not serialize native function" or something like that. The problematic part is that node-fetch relies on Symbol.iterator
As noted in the comments, some of the conditions that can lead to this are documented at https://pulumi.io/reference/serializing-functions.html. However, I don't see any clear reason why this code would hit any of those limitations. There may be details of how this is used outside the context of the snippet shared above which lead to this.

How to handle responseText for Node.js?

I have a javascript function as below which I hosted it at S3
function myFunction() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function(){
if(this.readyState == 4 && this.status == 200){
document.getElementById("testing").innerHTML = this.responseText;
}
};
xhttp.open("GET","https://id.execute-api.ap-southeast-1.amazonaws.com/prod/lambdafunction", true);
xhttp.send();
}
And this lambdafunction is written in Node.jsas below
'use strict';
console.log('Loading function');
exports.handler = (event, context, callback) => {
let response = {
statusCode: '200',
body: JSON.stringify({ error: 'you messed up!' }),
headers: {
'Content-Type': 'application/json',
}
};
context.succeed(response);
//callback(null, context); // Echo back the first key value
//callback('Something went wrong');
};
What I expect was that div with id testing will be replaced by error: 'you messed up! but nothing happened? May I know which part may have gone wrong?
It looks like you are using the api for the (very) old node v.0.10.42.
It seems more likely that you would be using the newer version so you should have:
callback(null, JSON.stringify({ error: 'you messed up!' }));
// if you are not using proxy integration
or
callback(null, response)
// if you set up the function with proxy integration
If this doesn't help, it would be useful to know what you get when you access the url directly and if you are seeing anything in the AWS logs. You should also be able to directly invoke the lambda function from the AWS console, which makes testing easier.

Resources