Lambda return only 200 respose code - amazon-web-services

I have created a sample lambda function for producing success & error responses. function is like below
exports.handler = (event, context, callback) => {
if(event.val1=="1")
{
callback(null, 'success');
}else
{
callback(true, 'fail');
}
};
When i have tested this function using API Gateway , I got different response body, But the response code is Same (always return 200 ok response code).
Is it possible to customize status code from lambda function(eg: need 500 for error responses & 200 for success responses)?

You may want to look at API Gateway's new simplified Lambda proxy feature.
Using this you can define your status codes, return headers and body content directly from your Lambda.
Example from docs:
'use strict';
console.log('Loading hello world function');
exports.handler = function(event, context) {
var name = "World";
var responseCode = 200;
console.log("request: " + JSON.stringify(event));
if (event.queryStringParameters !== null && event.queryStringParameters !== undefined) {
if (event.queryStringParameters.name !== undefined && event.queryStringParameters.name !== null && event.queryStringParameters.name !== "") {
console.log("Received name: " + event.queryStringParameters.name);
name = event.queryStringParameters.name;
}
if (event.queryStringParameters.httpStatus !== undefined && event.queryStringParameters.httpStatus !== null && event.queryStringParameters.httpStatus !== "") {
console.log("Received http status: " + event.queryStringParameters.httpStatus);
responseCode = event.queryStringParameters.httpStatus;
}
}
var responseBody = {
message: "Hello " + name + "!",
input: event
};
var response = {
statusCode: responseCode,
headers: {
"x-custom-header" : "my custom header value"
},
body: JSON.stringify(responseBody)
};
console.log("response: " + JSON.stringify(response))
context.succeed(response);
};

In order to send a custom error code from AWS API GW you should use response mapping template in integration response.
You basically define a regular expression for each status code you'd like to return from API GW.
Steps:
Define Method Response for each status code AWS Documentation
Define Integration Response RegEx for each status mapping to the Correct Method Response AWS Documentation
Using this configuration the HTTP return code returned by API GW to the client is the one matching the regular expression in "selectionPattern".
Finally I strongly suggest to use an API GW framework to handle these configuration, Serverless is a very good framework.
Using Servereless you can define a template as follows (serverless 0.5 snippet):
myResponseTemplate:
application/json;charset=UTF-8: |
#set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage'))) {
"status" : $errorMessageObj.status,
"error":{
"error_message":"$errorMessageObj.error.message",
"details":"$errorMessageObj.error.custom_message"
}
}
responsesValues:
'202':
selectionPattern: '.*"status": 202.*'
statusCode: '202'
responseParameters: {}
responseModels: {}
responseTemplates: '$${myResponseTemplate}'
'400':
selectionPattern: '.*"status": 400.*'
statusCode: '400'
responseParameters: {}
responseModels: {}
responseTemplates: '$${myResponseTemplate}'
Then simply return a json object from your lambda, as in the following python snippet (you can use similar approach in nodejs):
def handler(event, context):
# Your function code ...
response = {
'status':400,
'error':{
'error_message' : 'your message',
'details' : 'your details'
}
}
return response
I hope this helps.
G.

Related

When I use a x-amz-tagging in header it gives 403 forbidden error

Hi I have a working serverless function that uses s3 signedurl to put a file in an s3 bucket using on the Serverless framework that I am trying to migrate to a vercel serverless function using Next.
The function works via the serverless function and Postman, but when I try on Vercel although it generates the signedurl ok but when I try to use it with a "x-amz-tagging"="test" header I get a 403 error. Here is the relevant bit of my code:
//serverless function
const allowCors = fn => async (req, res) => {
res.setHeader('Access-Control-Allow-Credentials', true)
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT')
res.setHeader(
'Access-Control-Allow-Headers',
'X-CSRF-Token, X-Requested-With, x-amz-tagging, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
)
return await fn(req, res)
}
function requestUploadURL(req, res) {
...
}
module.exports = allowCors(requestUploadURL)
//code in app
try {
const config = {
onUploadProgress(progressEvent) {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total)
adduploadprogress({
file: fileSelectedArray[i].file,
fileType: fileSelectedArray[i].fileType,
myFsize: fileSelectedArray[i].myFsize,
percent,
})
},
headers: {
'Content-Type': fileSelectedArray[i].fileType,
'x-amz-tagging': 'test', // THIS CAUSES 403 ERROR
},
}
const resp1 = await axios.put(
uploadURL,
fileSelectedArray[i].file,
config
)
Any advice gratefully received
For some reason I also need to use Tagging:'' as part of the s3 params in the function being wrapped by the allowCors function. I didn't need to do this before
const { body } = req
const s3Params = {
Bucket: UNIQUEBUCKET,
Key: body.name,
ContentType: body.type,
ACL: 'public-read',
Tagging: '',
}
const uploadURL = s3.getSignedUrl('putObject', s3Params)

How to perform post request using AWS's HTTP API?

I have a simple Nodejs Lambda function which is:
exports.handler = async (event) => {
// TODO implement
let key = event.key;
const response = {
statusCode: 200,
body: 'Hello from Lambda! ' + key,
};
return response;
};
When I send the key using POSTMAN Via a POST Request, I get the following response :
Hello from Lambda! undefined
The event object contains a body parameter which is the JSON string of the request payload.
Assuming your payload is:
{"key": "world"}
Then the following code should return Hello from Lambda! world:
let body = JSON.parse(event.body);
let key = body.key;
The event sent to lambda is of type object if sent from Postman. It is suggested to parse this object using JSON.parse(). Since this is coming from Postman as POST call, Postman wraps the request in body parameter. You need to extract the request from body in your lambda by event.body and parse it. If you test directly through Lambda console, you don't need to extract body as there is not wrapping. So make sure you do a check from where the request is coming from and type of request.
exports.handler = async (event) => {
// TODO implement
var request;
if(event.body){
console.log("Incoming request from postman");
request = JSON.parse(event.body)
}
else{
console.log("incoming request from lambda test event");
request = event;
}
key = request.key;
const response = {
statusCode: 200,
body: 'Hello from Lambda! ' + key,
};
return response;
};

API Gateway -> Lambda -> DynamoDB using Cognito. HTTP POST-> Unable to read response but returns a code 200

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 .

Connect AWS mobile backend to DynamoDB

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

aws api gateway and lambda return new error status

So when you create a new lambda from scracth you get the following default index.js inline code:
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!')
};
return response;
};
This lambda is used by an api gateway endpoint. What is the proper way to return an error code if something isn't quite right? Would the gateway handle it or should I just change the status above to a new error code?
1xx (Informational): The request was received, continuing process
2xx (Successful): The request was successfully received, understood, and accepted
3xx (Redirection): Further action needs to be taken in order to complete the request
4xx (Client Error): The request contains bad syntax or cannot be fulfilled
5xx (Server Error): The server failed to fulfill an apparently valid request
This is just returning a 200 response which is a successful response. You simply just need to return the correct code depending on the implementation you have. I think That is what you are asking for.
For more information on status code you can read this Wiki
https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
But since you're asking about implementation I would do the following
exports.handler = async (event) => {
// TODO implement
let statusCode = 200;
let message = 'success';
... // do stuff
statusCode = 400 // something went wrong
const response = {
statusCode: statusCode,
body: JSON.stringify(message)
};
return response;
};
It's really all up to you based on how you want your application to scale. Just remember, never duplicate code. Keep it modular.
Additionally you can also declare this in a function
// Somewhere else
response = function (statusCode, message) {
return {
statusCode: statusCode,
body: JSON.stringify(message)
};
};
exports.handler = async (event) => {
// TODO implement
let statusCode = 200;
let message = 'success';
... // do stuff
statusCode = 400 // something went wrong
return response(statusCode, message);
};
Check out Error Handling Patterns from AWS.
It would be a good start unless you already read it.
https://aws.amazon.com/blogs/compute/error-handling-patterns-in-amazon-api-gateway-and-aws-lambda/
Essentially you need something of the following
exports.handler = (event, context, callback) => {
var myErrorObj = {
errorType : "InternalServerError",
httpStatus : 500,
requestId : context.awsRequestId,
message : "An unknown error has occurred. Please try again."
}
callback(JSON.stringify(myErrorObj));
};