I am trying to use AWS mobile backend (using lambda function) to insert into dynamoDB (also configured at the mobile backend) but with no success so far.
The relevant code:
'use strict';
console.log("Loading function");
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({region:process.env.MOBILE_HUB_PROJECT_REGION});
exports.handler = function(event, context, callback) {
var responseCode = 200;
var requestBody, pathParams, queryStringParams, headerParams, stage,
stageVariables, cognitoIdentityId, httpMethod, sourceIp, userAgent,
requestId, resourcePath;
console.log("request: " + JSON.stringify(event));
// Request Body
requestBody = event.body;
if (requestBody !== undefined && requestBody !== null) {
// Set 'test-status' field in the request to test sending a specific response status code (e.g., 503)
responseCode = JSON.parse(requestBody)['test-status'];
}
// Path Parameters
pathParams = event.path;
// Query String Parameters
queryStringParams = event.queryStringParameters;
// Header Parameters
headerParams = event.headers;
if (event.requestContext !== null && event.requestContext !== undefined) {
var requestContext = event.requestContext;
// API Gateway Stage
stage = requestContext.stage;
// Unique Request ID
requestId = requestContext.requestId;
// Resource Path
resourcePath = requestContext.resourcePath;
var identity = requestContext.identity;
// Amazon Cognito User Identity
cognitoIdentityId = identity.cognitoIdentityId;
// Source IP
sourceIp = identity.sourceIp;
// User-Agent
userAgent = identity.userAgent;
}
// API Gateway Stage Variables
stageVariables = event.stageVariables;
// HTTP Method (e.g., POST, GET, HEAD)
httpMethod = event.httpMethod;
// TODO: Put your application logic here...
let params = {
Item:{
"prop1":0,
"prop2":"text"
},
TableName:"testTable"
};
docClient.put(params, function(data, err){
if(err)
responseCode = 500;
else
{
responseCode = 200;
context.succeed(data);
}
});
// For demonstration purposes, we'll just echo these values back to the client
var responseBody = {
requestBody : requestBody,
pathParams : pathParams,
queryStringParams : queryStringParams,
headerParams : headerParams,
stage : stage,
stageVariables : stageVariables,
cognitoIdentityId : cognitoIdentityId,
httpMethod : httpMethod,
sourceIp : sourceIp,
userAgent : userAgent,
requestId : requestId,
resourcePath : resourcePath
};
var response = {
statusCode: responseCode,
headers: {
"x-custom-header" : "custom header value"
},
body: JSON.stringify(responseBody)
};
console.log("response: " + JSON.stringify(response))
context.succeed(response);
};
this doesn't put the item to the table for some reason.
I gave the necessary permissions using the roles part, anything I am missing?
**responseCode is only for testing purposes.
Edit:
tried AWS node.js lambda request dynamodb but no response (no err, no return data) and doesn't work either.
Edit2:
Added the full handler code. (it the default generated code when creating first AWS lambda).
I have refactored some bits of your code to look much simpler and use async/await (make sure to select Node 8.10 as the running environment for your function) instead of callbacks. I also got rid of the context and callback parameters, as they were used for older versions of NodeJS. Once you're using Node 8+, async/await should be the default option.
Also, it is possible to chain a .promise() on docClient.putItem, so you can easily await on it, making your code way simpler. I have left only the DynamoDB part (which is what is relevant to your question)
'use strict';
console.log("Loading function");
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({region:process.env.MOBILE_HUB_PROJECT_REGION});
exports.handler = async (event) => {
let params = {
Item:{
"prop0":1,
"prop2":"text"
},
TableName:"testTable"
};
try {
await docClient.put(params).promise();
} catch (e) {
console.log(e)
return {
messsage: e.message
}
}
return { message: 'Data inserted successfully' };
};
Things to keep in mind if still it does not work:
Make sure your Lambda function has the right permissions to insert items on DynamoDB (AmazonDynamoDBFullAccess will do it)
You ALWAYS have to provide the partition key when inserting items to DynamoDB. On your example, the JSON only has two properties: prop1 and prop2. If none of them are the partition key, your code will certainly fail.
Make sure you table also exists
If you code fails, just check CloudWatch logs as any exception is now captured and printed out on the console.
The reason why no data is written in the table is because the call to DynamoDB put is asynchronous and will return by calling your callback. But during that time, the rest of the code continues to execute and your function eventually finish before the call to DynamoDB has a chance to complete.
You can use the await / async keywords to make your code sychronous :
async function writeToDynamoDB(params) {
return new Promise((resolve,reject) => {
docClient.put(params, function(data, err){
if(err)
reject(500);
else
resolve(data);
});
});
}
let params = ...
var data = await writeToDynamoDB(params)
You can find sample code I wrote (in Typescript) at https://github.com/sebsto/maxi80-alexa/blob/master/lambda/src/DDBController.ts
Related
I tried every possible solution on the internet with no hope
What I am trying to do is simply use aws lambda functions (through the aws console) to fetch user fcm token from lets say DynamoDB (not included in the question), use that token to create endpointArn, send push to that specific device
I tested to send Using SNS console and the push gets to the device successfully but I failed to get it to the device using Lambda functions although it gives success status and message ID
Here is the code I used
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'us-east-1'});
const sns = new AWS.SNS()
const sampleMessage = {
"GCM": {
"notification": {
"body": "Sample message for Android endpoints",
"title":"Title Test"
}
}
}
exports.handler = async (event) => {
const snsPayload = JSON.stringify(sampleMessage);
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
const params = {
PlatformApplicationArn: '<Platform Arn>',
Token: '<FCM Token>'
};
try {
const endpointData = await sns.createPlatformEndpoint(params).promise();
const paramsMessage = {
Message: snsPayload,
TargetArn: endpointData.EndpointArn
};
var publishTextPromise = await sns.publish(paramsMessage).promise();
response.MessageId = publishTextPromise.MessageId;
response.result = 'Success';
}
catch (e) {
console.log(e.stack)
response.result = 'Error'
}
return response;
};
After some trials and errors I figured out the solution for my own question
1- The GCM part of the payload should be a string not a json
2- The message parameter should have an attribute that explicitly sets the mime type of the payload to Json
Taking all that into consideration
const GCM_data = {
'notification': {
'body': 'Hellow from lambda function',
'title': 'Notification Title'
}
}
const data = {
"GCM": JSON.stringify(GCM_data)
}
const snsPayload = JSON.stringify(data)
and the params should look like
const paramsMessage = {
Message: snsPayload,
TargetArn: endpointData.EndpointArn,
MessageStructure: 'json'
};
and this will work :)
I'm slowly inching my way towards a rudimentary ability to use or understand AWS. I have a Gateway API set up to Post a string to a (Node 10.x)Lambda function that then gets sent to a Dynamo table. I've been having issues with CORS when trying to make API calls from webpage javascript, and found out it had something to do with the CORS in the handler in the Lambda function. Here is what I have right now:
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
exports.handler = async function(event, context) {
var responseCode = 200;
var response = {
statusCode: responseCode,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(event)
};
context.succeed(response);
console.log("Made it past context succeed");
var characterData = JSON.parse(event.body);
return await db.put(characterData).promise();
};
I run tests from the AWS API page just sending it strings to Post like
{
"TableName" : "characterTable",
"Item" : {
"userID" : "123",
"characterName" : "Alan",
"race" : "human"
}
}
When I comment out
context.succeed(response);
my function adds the data to the Dynamo table, but does not show the CORS headers in the Gateway log, meaning they weren't received, and if I try it on a webpage, the webpage will error telling me I'm missing the Access-Control-Allow-Origin header. If I leave that snippet in, the Gateway log shows the proper CORS header, and the webpage console moves past the Access-Control-Allow-Origin error onto a different error (missing token ‘content-type’ in CORS header but that's a problem for me in the future) but the data does not get passed onto the Dynamo table, even though the console.log statement right above it triggers properly.
I'm not sure how or why this is happening, so I would appreciate any insight into what might be wrong!
EDIT: Here is my webpage JS
//Default AWS sdk object
var lambda = new AWS.Lambda();
//api sdk stuff
var apigClient = apigClientFactory.newClient({
apiKey: 'iHadMyAPIKeyHere ' //placeholder for my actual API Key
});
function makeJSON(){
var userID = "";
var name = document.forms["characterForm"]["characterName"].value;
var race = document.forms["characterForm"]["race"].value;
var playerClass = document.forms["characterForm"]["class"].value;
var strength = document.forms["characterForm"]["strength"].value;
var dexterity = document.forms["characterForm"]["dexterity"].value;
var constitution = document.forms["characterForm"]["constitution"].value;
var intelligence = document.forms["characterForm"]["intelligence"].value;
var wisdom = document.forms["characterForm"]["wisdom"].value;
var charisma = document.forms["characterForm"]["charisma"].value;
characterSheetObj = {"userID": userID, "name": name, "race": race, "class": playerClass, "strength": strength, "dexterity": dexterity, "constitution": constitution, "intelligence": intelligence, "wisdom": wisdom, "charisma": charisma}
characterSheetJSON = JSON.stringify(characterSheetObj);
alert(characterSheetJSON);
var params = {
}
var body = {
"TableName" : "characterTable",
"Item" : {
"userID" : userID,
"name" : name,
"race" : race
}
}
var additionalParams = {
}
apigClient.myresourcePost(null, body);
}
Welcome to StackOverflow so you're problem is you need to use callback like so...
callback is called as the eventual result of the function, you want to return callback(....) to ensure your tests work properly later down line.
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
exports.handler = async function(event, context, callback) {
var responseCode = 200;
var response = {
statusCode: responseCode,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(event)
};
console.log("Made it past context succeed");
var characterData = JSON.parse(event.body);
await db.put(characterData).promise();
return callback(null, response)
};
NOTE Your dynamo DB call is in the wrong format and will generate an exception.
Context succeed is the old way, please see this answer for more info on returning proper http responses.
I want my function to return a list of everything in a table if there are no query parameters, and a single row if the parameter id exists
var mysql = require('mysql');
var config = require('./config.json');
var pool = mysql.createPool({
host : config.host,
user : config.user,
password : config.password,
database : config.database
});
exports.handler = (event, context, callback) => {
var whereClause
if(event.queryStringParameters.id !== null){
let id = event.queryStringParameters.id
whereClause = ' where id='+id
}
context.callbackWaitsForEmptyEventLoop = false;
pool.getConnection(function(err, connection) {
// Use the connection
connection.query('SELECT * from users'+whereClause, function (error, results, fields) {
// And done with the connection.
connection.release();
// Handle error after the release.
if (err) callback(err);
else {
var response = {
"statusCode": 200,
"headers": {
"my_header": "my_value"
},
"body": JSON.stringify(results),
"isBase64Encoded": false
};
callback(null, response);
}
});
});
};
the function fails when no query parameter is present with the error
"Cannot read property 'id' of null"
why is that?
You didn't supply any line number information or a stack trace, so I'm guessing this if statement fails because event.queryStringParameters is null:
if(event.queryStringParameters.id !== null)
let id = event.queryStringParameters.id
whereClause = ' where id='+id
}
And you should instead write:
if (event.queryStringParameters && event.queryStringParameters.id !== null) {
let id = event.queryStringParameters.id;
whereClause = ' where id=' + id;
}
Having said that, you should not inject user-supplied values (such as id) into SQL queries using string concatenation. This opens you up to a SQL Injection attack. Here are ideas for how to write this code more safely: How to prevent SQL Injection in Node.js
Do you use AWS Lambda with Amazon API Gateway?
AWS Lambda with Amazon API Gateway
In this case:
Make sure that you create a body mapping template in API gateway (Integration Request->Body Mapping Templates). As an example here's a body mapping template that would pass along the query parameter email to your lambda function: { "id": "$input.params('id')" }
AWS Developer Forum
I have Cloudfront in front of an s3 bucket that serves HLS videos. I'm trying to dynamically modify the manifest files to add an auth token to the segments inside of them.
What I would really like to do is modify the body I send back to the client in a viewer response function, but since that isn't possible, I'm attempting to use a origin request function to manually fetch the object from S3, modify it, and return a Cloudfront request with the new body. I get a 503 error of "The Lambda function result failed validation: The body is not a string, is not an object, or exceeds the maximum size"
My body is under 8kb (1MB is the limit in the docs). As far as I can tell the cloudfront request object I'm generating looks good and the base64 data decodes to what I want. I've also tried using text instead of base64. I have "include body" enabled in Cloudfront.
const fs = require('fs');
const querystring = require('querystring');
const AWS = require('aws-sdk');
const S3 = new AWS.S3();
exports.handler = async (event) => {
const cfrequest = event.Records[0].cf.request;
const queryString = querystring.parse(event.Records[0].cf.request.querystring);
const jwtToken = queryString.token;
if (cfrequest.uri.match(/\.m3u8?$/mi)) {
const s3Response = await (new Promise((resolve, reject) => {
S3.getObject({
Bucket: 'bucket',
Key: cfrequest.uri.substring(1)
}, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data);
}
});
}));
const manifestFile = s3Response.Body.toString('utf8');
const newManifest = manifestFile.replace(/^((\S+)\.(m3u8|ts|vtt))$/gmi, (_, url) => `${url}?token=${jwtToken}`);
const base64NewManifest = Buffer.from(newManifest, 'utf8').toString('base64');
const tokenizedCfRequest = {
...cfrequest,
body: {
action: 'replace',
data: base64NewManifest,
encoding: 'base64'
}
};
return tokenizedCfRequest;
}
return cfrequest;
}
If you want to generate your own response you need to use a viewer request or origin request event and return a response like this:
exports.handler = async (event) => {
const cfRequest = event.Records[0].cf.request;
const queryString = querystring.parse(event.Records[0].cf.request.querystring);
const jwtToken = queryString.token;
if (cfrequest.uri.match(/\.m3u8?$/mi)) {
// ... your code here ...
const response = {
status: 200, // only mandatory field
body: base64NewManifest,
bodyEncoding: 'base64',
};
return response;
}
// Return original request if no uri match
return cfRequest;
}
See also Generating HTTP Responses in Request Triggers.
Scenario:
I query an HTTP POST (using Authorizer as Header parameter from Cognito).
When I try to fetch/read the query response, it triggers the error event. However, in the browser, I can see how 2 HTTP POST responses with 200 code and one of them returning the valid response. For example: if I make the request via POST man I receive the data in 1 response in a good way.
Problem:
I am unable to print the result because it launches the error event with not valid response data.
Browser images:
https://i.postimg.cc/MTMsxZjw/Screenshot-1.png
https://i.postimg.cc/3RstwMgv/Screenshot-2.png
Lambda code:
'use strict';
var AWS = require('aws-sdk'),
documentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = function index(event, context, callback){
var params = {
TableName : "data-table"
};
documentClient.scan(params, function(err, data){
if(err){
callback(err, null);
}else{
console.log(JSON.stringify(data.Items));
callback(null, data.Items);
}
});
}
Client side JS code:
function requestData(pickupLocation) {
$.ajax({
type: 'POST',
url: _config.api.invokeUrl,
headers: {
Authorization: authToken,
},
data: "{}",
cache: false,
success: completeRequest,
error: errorRequest
});
}
function completeRequest(response) {
alert("hello");
alert(response.d);
}
function errorRequest(response) {
alert("hello1");
alert(response.status + ' ' + response.statusText);
}
According to further clarification based on the comments, this looks like API gateway has CORS disabled or enabled with incorrect header value returns.
The solution is to re-enable CORS through API gateway and in the advanced options add Access-Control-Allow-Origin to the header response (if not already on by default).
If you're proxying the response, you need to follow a specific format as described here
'use strict';
console.log('Loading hello world function');
exports.handler = async (event) => {
let name = "you";
let city = 'World';
let time = 'day';
let day = '';
let responseCode = 200;
console.log("request: " + JSON.stringify(event));
// This is a simple illustration of app-specific logic to return the response.
// Although only 'event.queryStringParameters' are used here, other request data,
// such as 'event.headers', 'event.pathParameters', 'event.body', 'event.stageVariables',
// and 'event.requestContext' can be used to determine what response to return.
//
if (event.queryStringParameters && event.queryStringParameters.name) {
console.log("Received name: " + event.queryStringParameters.name);
name = event.queryStringParameters.name;
}
if (event.pathParameters && event.pathParameters.proxy) {
console.log("Received proxy: " + event.pathParameters.proxy);
city = event.pathParameters.proxy;
}
if (event.headers && event.headers['day']) {
console.log("Received day: " + event.headers.day);
day = event.headers.day;
}
if (event.body) {
let body = JSON.parse(event.body)
if (body.time)
time = body.time;
}
let greeting = `Good ${time}, ${name} of ${city}. `;
if (day) greeting += `Happy ${day}!`;
let responseBody = {
message: greeting,
input: event
};
// The output from a Lambda proxy integration must be
// of the following JSON object. The 'headers' property
// is for custom response headers in addition to standard
// ones. The 'body' property must be a JSON string. For
// base64-encoded payload, you must also set the 'isBase64Encoded'
// property to 'true'.
let response = {
statusCode: responseCode,
headers: {
"x-custom-header" : "my custom header value"
},
body: JSON.stringify(responseBody)
};
console.log("response: " + JSON.stringify(response))
return response;
};
If you are using chrome you probably need the cors plugin .