API Gateway HTTP API can't read from stream - amazon-web-services

I have lambda connected to API GW HTTP API.
If my method looks like this:
public async Task<APIGatewayHttpApiV2ProxyResponse> WebhookInternal(APIGatewayHttpApiV2ProxyRequest request) {
return new APIGatewayHttpApiV2ProxyResponse()
{
StatusCode = 200,
Body = new {
Status = 200,
Message = "OK",
}.ToJson()
};
}
everything works.
But I want to be able to keep the lambda warm by using scheduled events. So I needed to have the method more general. Therefore I changed its signature to handle Stream:
public async Task<Stream> WebhookInternal(Stream request) {
var response = new APIGatewayHttpApiV2ProxyResponse()
{
StatusCode = 200,
Body = new {
Status = 200,
Message = "OK",
}.ToJson()
};
var memoryStream = new MemoryStream();
using var streamWriter = new StreamWriter(memoryStream, leaveOpen: true);
streamWriter.Write(response.ToJson());
streamWriter.Flush();
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
Now, the response in Testing Tool is the same as before:
{"statusCode":200,"headers":null,"cookies":null,"body":"{\"Status\":200,\"Message\":\"OK\"}","isBase64Encoded":false}
But when I run test request in lambda on AWS console, the response is
""
and API GW returns 500 with reason
The response from the Lambda function doesn't match the format that API Gateway expects. Lambda body is empty
but $context.responseLength reported by API GW is correctly 35 (length of body returned by lambda property before de-escaping).

Related

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;
};

Sending CORS headers from AWS Lambda to Gateway prevents my Lambda function from executing correctly

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.

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