How to throw exceptions from AWS lambdas but not trigger AWS cloudwatch? - amazon-web-services

So I have the following scenario.
We have a specific set of custom exceptions that we need to throw from the backend (written with AWS Lambdas) so that the frontend can receive a specific response that he knows how to treat.
Our responses for the client (frontend) look like this in case of an error.
We have code, statusCode, message
{
"error": {
"code": 4004,
"statusCode": 404,
"message": "Not found"
}
}
Now, the issue is that the way we do it is by extending the Exception class in Python, so when something happens we can do raise CustomException.
It's all working fine, the only issue that we have is that AWS Cloudwatch treats this as ERRORS and so we are in a case where every single BadRequest is costing us an error in cloudwatch, which is something we do not want.
I would like to build it in such a way that I manage to throw this response to the client but the status of the HTTP request to be the actual statusCode that I put. So far, I have only managed to do something like this:
As you can see, I have the response I need, however, the statusCode for the HTTP request is 200, when in fact it should be 404.
This is how we create the endpoint in the cdk:
props.apiStack.createEndpoint("GET", "/clinics/{clinicID}/credits/stats", getCreditsStats, {
authorizationType: AuthorizationType.COGNITO,
requestParameters: {
"method.request.header.x-user-auth": true,
"method.request.path.clinicID": true
},
requestTemplateBody: `{
"httpMethod": "$context.httpMethod",
"token": "$input.params('x-user-auth')",
"clinicID": "$input.params('clinicID')"
}`,
});
And this is how the createEndpoint method looks like:
public createEndpoint(method: string, path: string, lambda: Function, props: EndpointProps): Method {
const { authorizationType, requestParameters, requestTemplateBody } = props;
const integration = new LambdaIntegration(lambda, {
proxy: false,
requestParameters: Object.keys(requestParameters || {}).reduce(
(o, key) => ({ ...o, [`${key.replace("method.", "integration.")}`]: key }),
{}
),
requestTemplates: {
"application/json": requestTemplateBody,
},
// TODO Check this param
passthroughBehavior: PassthroughBehavior.NEVER,
integrationResponses,
});
return this.api.root.resourceForPath(path).addMethod(method, integration, {
authorizationType,
authorizer: { authorizerId: this.cognitoAuthorizer.ref },
requestParameters,
methodResponses: this.methodResponses,
});
}
Also, integrationResponse looks like this:
{
selectionPattern: '.*"statusCode": 404.*',
statusCode: "404",
responseTemplates: {
"application/json": `#set ($obj = $util.parseJson($input.path('$.errorMessage')))
{"error": {
"code":$obj.error.code,
"statusCode":$obj.error.statusCode,
"message":"$obj.error.msg"}
}`,
},
responseParameters: {
"method.response.header.Content-Type": "'application/json'",
"method.response.header.Access-Control-Allow-Origin": "'*'",
"method.response.header.Access-Control-Allow-Credentials": "'true'",
"method.response.header.Access-Control-Expose-Headers": "'x-amzn-requestid'",
"method.response.header.Strict-Transport-Security": "'max-age=63072000'",
},
}
I was presuming that if I define it the way I did, it would work, and it does to some extent, we don't receive an error in Cloudwatch anymore, but the frontend cannot pickup the error because for the frontend, it's a 200 OK response.
Any help would be highly appreciated!

Related

Adding allow all to 'Access-Control-Allow-Origin' header in AWS Gateway response using CDK

I am using CDK to create API endpoints. I would like to set 'Access-Control-Allow-Origin' to allow all in the header responses. Here is what I have tried
api.addGatewayResponse('4xx-error-response', {
type: ResponseType.DEFAULT_4XX ,
statusCode: '400',
responseHeaders: {
'Access-Control-Allow-Origin': `*`
},
templates: {
'application/json': '{ "message": "Access denied", "statusCode": "403", "type": "$context.error.responseType" }'
}
});
When I try to deploy this, I get the following error
Resource handler returned message: "Invalid mapping expression specified: Validation Result: warnings : [], errors : [Invalid mapping expression specified: *]
Question: How do I add a gateway response like in the below screenshot using CDK
try this way:
const resource = api.addGatewayResource('MyResource', {params...});
resource.addCorsPreflight({
allowOrigins: api.Cors.ALL_ORIGINS // eqivalent to ['*']
allowCredentials: true, // optional for credentials
});
According to the AWS CORS docs You need to set more than one header, so I would just use the method that is doing that for me.
The CDK test directories are a good source of information, next to the official docs. You may find there some useful examples. Here the ApiGateway insides.
in response header instead of * for Access-Control-Allow-Origin you'll have to use '*'.
Like this:
api.addGatewayResponse('invalid-endpoint-error-response', {
type: ResponseType.MISSING_AUTHENTICATION_TOKEN,
statusCode: '500',
responseHeaders: {
'Access-Control-Allow-Origin': "'*'",
},
templates: {
'application/json': '{ "message": $context.error.messageString, "statusCode": "488", "type": "$context.error.responseType" }'
}
});

API Gateway -> SQS HTTP POST MessageAttributes

I have an API gateway setup which sends to SQS which fires a Lambda, I am trying to pass message attributes to the SQS but when I hit the endpoint in postman I keep getting a 400 Bad Request.. what is the right way to send the attributes over a JSON POST body
here is body from postman (have tried a few options based on this link)
"message": "Message",
"MessageAttributes": {
"Name": "Name",
"Type": "String",
"Value": "my value"
}
}
Here is how API Gateway is configured
Incase someone stumbles on this later here is worked from the CDK side
let intergation = new apiGateway.CfnIntegration(this, 'Integration', {
apiId: props.httpApi.httpApiId,
payloadFormatVersion: '1.0',
integrationType: 'AWS_PROXY',
credentialsArn: apigwRole.roleArn,
integrationSubtype: 'SQS-SendMessage',
requestParameters: {
QueueUrl: sqsqueue.queueUrl,
MessageBody: '$request.body',
MessageAttributes: '$request.body.MessageAttributes'
}
})
new apiGateway.CfnRoute(this, 'Route', {
apiId: props.httpApi.httpApiId,
routeKey: apiGateway.HttpRouteKey.with('/url/foo', apiGateway.HttpMethod.POST).key,
target: `integrations/${intergation .ref}`
}).addDependsOn(intergation);
and the cloudformation
MessageBody: $request.body
MessageAttributes: $request.body.MessageAttribute
then in post man the POST body content type as application/json
{
"message": "Message",
"MessageAttributes": {
"Attributes": {
"DataType": "String",
"StringValue": "my value"
}
}
}
the lamba would log out both separate for each Record from the event body
Records: [
{
....
body: 'Message',
attributes: [Object],
messageAttributes: [Object]
}
]
}
the messageAttributes object from above:
{
Attributes: {
stringValue: 'my value',
stringListValues: [],
binaryListValues: [],
dataType: 'String'
}
}
This is using AWS API Gateway v2 HTTP API also

AWS CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. The headers are present

I have been battling with these dreaded CORS issues with AWS for a while now. I thought I had it sorted out and then it turned up again... I have done exactly want I have in the other Lambda functions that work fine.
Why won't it work now?
I have added in the headers in the response to all of the Lambda functions in my handler.js file (I am using serverless to deploy to AWS)
docClient.get(params, function (err, data) {
if (err) {
const response = {
statusCode: 500,
headers: {
"Access-Control-Allow-Origin": "*", // Required for CORS support to work
"Access-Control-Allow-Credentials": true
},
body: JSON.stringify({
message: 'Failed to fetch service request from the database.',
error: err
}),
};
callback(null, response);
}
else {
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*", // Required for CORS support to work
"Access-Control-Allow-Credentials": true
}
};
callback(null, response);
}
});
And in the .yml file:
myLambdaFunc:
handler: handler.myLambdaFunc
events:
- http:
path: myLambdaFunc
method: POST
cors: true
I figured out that the problem lies with the docClient.get. I was testing with data where the primary key item being searched for was not in the table.
I wish it didn't tell me that it was a CORS issue because it really wasn't..

Amazon Lambda function return wrong response

I'm using AWS S3, API Gateway and Lambda function to resize my images on the fly. I keep having this error when the image doesn't exist:
Failed to load resource: the server responded with a status of 502 ()
It should return a 404 instead. Here the code in the lambda function:
S3.headObject({Bucket: BUCKET, Key: parameters.orignalImagePath}, function(err,data) {
if(err) {
console.log("[404] Image Not Found: " + parameters.orignalImagePath);
return callback(null, {
statusCode: '404',
body: '{ "message":"Image not found." }',
})
}
});
Here the logs from CloudWatch:
START
{"errorMessage":"The specified key does not exist.","errorType":"NoSuchKey"....}
[404] Image Not Found: Folder/image.png
END
Why i'm getting a 502 when my code return a 404. I checked the ApiGateway settings but couldn't find anything.
I found this: https://aws.amazon.com/premiumsupport/knowledge-center/malformed-502-api-gateway/
It might be because of the malformed response. I'm going to try with the updated response and the four fields.
{
"isBase64Encoded": true|false,
"statusCode": httpStatusCode,
"headers": { "headerName": "headerValue", ... },
"body": "..."
}
UPDATE: It definitely helps to change the response with those 4 fields. However, the same request gets sometimes a 404 and sometimes a 502.

Error Handling in AWS API Gateway and Lambda always return 502

I use serverless to implement Lambda and Api gateway.
When I implement Error Handling, below code always get 502 bad gateway.
handler.js
module.exports.hello = (event, context, callback) => {
const response = {
statusCode: 400,
headers: {
"Content-Type" : "application/json"
},
body: JSON.stringify({
"status": "error",
"message": "Missing Params"
})
};
callback(response);
};
CloudWatch do log error.
{
"errorMessage": "[object Object]"
}
I code this way by following the method "Custom error object serialization" in below AWS blog.
Ref
I change callback first parms to null and work fine. Ref
module.exports.hello = (event, context, callback) => {
const response = {
statusCode: 400,
headers: {
"Content-Type" : "application/json"
},
body: JSON.stringify({
"status": "error",
"message": "Missing Params"
})
};
callback(null, response);
};
This is a common pattern in Node.js and its called Error-First Callbacks.
Basically, if you pass a first argument into your callback, it will be considered and handled as an Error.
As you mentioned, once you put a callback(null, response);, it all worked as expected, since the first argument is null.