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
Related
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!
I'm trying to get my "queryStringParameters" in the event into the "body" key of the event so I can parse them in the same way that I parse post requests. Is this possible with a HTTP api? I tried using parameter mappings in the integration settings but this only allows me to append the body to the queryString.
The way that you get the values from a POST request using body and with query parameters are different. So both ca not be in the body property as you want.
The queryStringParameters is passed when you add a URL Query String Parameter in your Api gateway resource.
When you send a request to the event in your API the following event will be send to the Lamba function
{
"resource": "/{proxy+}",
"path": "/path/to/resource",
"httpMethod": "POST",
"isBase64Encoded": true,
"queryStringParameters": {
"foo": "bar"
},
"multiValueQueryStringParameters": {
"foo": [
"bar"
]
},
"pathParameters": {
"proxy": "/path/to/resource"
},
"stageVariables": {
"baz": "qux"
},
"body":"{ \"time\": \"evening\" }",
"headers": {
...
},
...
}
You be able to get the query parameters of event in your Lambda using the properties:
event.queryStringParameters && event.queryStringParameters.foo
And the body we need to get with the body property:
if (event.body) {
let body = JSON.parse(event.body)
if (body.time)
time = body.time;
}
In this way in your Lambda you need to parse them in different ways.
For more information of how to work with API Gateway and Lambda take a look here: Tutorial: Build a Hello World REST API with Lambda proxy integration
How could I retrieve the output of custom authorizer in lambda integration?
for example, lets assume below is my swagger file with aws api gateway integration, lambda authorizer output and AwsProxyHttpServletRequest;
A question in SO here AWS API Gateway with Lambda Authorizer says it works, but not for me.
REST API
openapi: 3.0.0
info:
title: Sample Event
version: 1.0.0
# Enable request validator. See doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-validation-sample-api-swagger.html
x-amazon-apigateway-request-validators:
all:
validateRequestBody: true
validateRequestParameters: true
x-amazon-apigateway-request-validator: all
x-amazon-apigateway-gateway-responses:
# Provide more detailed error message for bad request body errors. See doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-gateway-responses.html
BAD_REQUEST_BODY:
responseTemplates:
application/json: '{"errorCode": "BadRequestBody", "message": "$context.error.validationErrorString"}'
responseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
DEFAULT_4XX:
responseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
DEFAULT_5XX:
responseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
paths:
/events:
post:
operationId: CreateEvent
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/CreateEventInput"
required: true
responses:
"201":
description: "Successfully Created an event."
content:
application/json:
schema:
$ref: "#/components/schemas/Event"
"400":
description: "Bad Request Exception"
content:
application/json:
schema:
$ref: "#/components/schemas/BadRequestException"
"401":
description: "Unauthorized Exception"
content:
application/json:
schema:
$ref: "#/components/schemas/UnauthorizedException"
"409":
description: "Conflict Exception"
content:
application/json:
schema:
$ref: "#/components/schemas/ConflictException"
"429":
description: "Too Many Requests Exception"
content:
application/json:
schema:
$ref: "#/components/schemas/TooManyRequestsException"
"500":
description: "Internal Server Error"
content:
application/json:
schema:
$ref: "#/components/schemas/InternalServerErrorException"
x-amazon-apigateway-integration:
uri:
Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EventsApiLambda.Arn}:live/invocations
httpMethod: POST
type: aws_proxy
requestParameters:
integration.request.header.x-api-auth-user: "context.authorizer.x-api-auth-user"
integration.request.header.x-api-auth-resource-uri: "context.authorizer.x-api-auth-resource-uri"
integration.request.header.x-api-auth-type: "context.authorizer.x-api-auth-type"
integration.request.header.x-api-auth-resource-id: "context.authorizer.x-api-auth-resource-id"
integration.request.header.x-api-auth-resource-type: "context.authorizer.x-api-auth-resource-type"
integration.request.header.x-api-auth-resource-permissions: "context.authorizer.x-api-auth-resource-permissions"
passthroughBehavior: never
security:
- tokenAuthorizer: []
Lambda Authorizer Output (from API-Gateway-Execution-Logs-xxxx)
(023bd04b-e1c9-4980-ae14-xxxxx) Authorizer result body before parsing:
{
"principalId": "act-xxxxxxxxx",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": "arn:aws:execute-api:*:*:*"
}
]
},
"context": {
"x-api-auth-user": "act-xxxxxxx",
"x-api-auth-type": "email",
"x-api-auth-resource-id": "11eb2825-18cc-fb80-9d6c-xxxxx",
"x-api-auth-resource-type": "cb:event",
"x-api-auth-resource-permissions": "read,write"
}
}
I can see the output from authorizer reaching the api execution stage. But making it into neither the requestContext nor the multiValueHeaders.
API-Gateway-Execution-Logs_xxxx
(023bd04b-e1c9-4980-ae14-xxxx) Endpoint request headers: {X-Amz-Date=20210112T170314Z, x-amzn-apigateway-api-id=xxx, Accept=application/json, User-Agent=AmazonAPIGateway_xxxx, x-api-auth-type=email, Host=lambda.us-east-1.amazonaws.com, x-api-auth-resource-id=11eb2825-18cc-fb80-9d6c-xxxx, X-Amz-Content-Sha256=xxxxxx, X-Amzn-Trace-Id=Root=1-5ffdd64b-xxxxx;Parent=xxxx;Sampled=1, x-amzn-lambda-integration-tag=023bd04b-e1c9-4980-ae14-xxxxxx, Authorization=**********************282c30, X-Amz-Source-Arn=arn:aws:execute-api:us-east-1:38067 [TRUNCATED]
Lambda input
{
"path": "/events",
"isBase64Encoded": false,
"requestContext": {
"resourceId": "xxxxx",
"apiId": "xxxxx",
"resourcePath": "/events",
"httpMethod": "POST",
"requestId": "xxxxxx-15f9-4ca2-9a71-xxxxx",
"extendedRequestId": "xxxxxx=",
"accountId": "xxxx",
"identity": {
"userAgent": "PostmanRuntime/7.26.8",
"sourceIp": "xxxxx"
},
"authorizer": {
"principalId": "act-VNJQUexxxxx"
},
"stage": "v1",
"path": "/event/events",
"protocol": "HTTP/1.1",
"requestTime": "12/Jan/2021:17:53:06 +0000",
"requestTimeEpoch": xxx
},
....[TRUNCATED]....
}
Do I need to explicitly specify the authorizer result to be in each path's header/body?
Any idea?
I try to define my AWS Api Gateway infrastructure using Swagger/OpenAPI. Everything is working so far, however I have problems enabling the need for an API-Key for my endpoints.
My Swagger file looks like this (shortened):
---
swagger: 2.0
basePath: /dev
info:
title: My API
description: Proof of concept
schemes:
- https
securityDefinitions:
api_key:
type: apiKey
name: X-Api-Key
in: header
paths:
/example-path:
options:
consumes:
- application/json
produces:
- application/json
x-amazon-apigateway-integration:
type: mock
requestTemplates:
application/json: |
{
"statusCode" : 200
}
responses:
"default":
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'GET,HEAD,OPTIONS'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Origin: "'*'"
responseTemplates:
application/json: |
{}
responses:
200:
description: Default response for CORS method
headers:
Access-Control-Allow-Headers:
type: "string"
Access-Control-Allow-Methods:
type: "string"
Access-Control-Allow-Origin:
type: "string"
get:
security:
- api_key: []
x-amazon-apigateway-integration:
# Further definition of the endpoint, calling Lambda etc...
Linked inside a CloudFormation template the Swagger file is processed successfully. But when I open the endpoint in the AWS Web Console, the flag for API Key Required is still false.
Any suggestions? Thanks.
Found the solution: the API key has to be named x-api-key (all lowercase).
It seems like only this way the setting is recognized during import.
To enable required API Key you need to add this "x-amazon-apigateway-api-key-source" : "HEADER" in security scheme block.
See an example:
"components" : {
"securitySchemes" : {
"api-key" : {
"type" : "apiKey",
"name" : "x-api-key",
"in" : "header",
"x-amazon-apigateway-api-key-source" : "HEADER"
}
}
}
It's an example using proxy requests.
Your JSON should be like this:
openapi3
{
"openapi": "3.0.3",
"info": {
"title": "User Portal",
"description": "API focused in User Portal.",
"version": "v1"
},
"paths": {
"users/{proxy+}": {
"options": {
"x-amazon-apigateway-integration": {
"httpMethod": "OPTIONS",
"payloadFormatVersion": "1.0",
"type": "MOCK"
}
},
"x-amazon-apigateway-any-method": {
"produces":[ "application/json"],
"parameters": [
{
"name": "proxy",
"in": "path",
"required": "true",
"type": "string"
}
],
"responses": {},
"security": [
{
"api-key": []
}
],
"x-amazon-apigateway-integration": {
"uri":"https://test.com.br/users/{proxy}",
"httpMethod":"ANY",
"type": "HTTP_PROXY"
}
}
}
},
"components" : {
"securitySchemes" : {
"api-key" : {
"type" : "apiKey",
"name" : "x-api-key",
"in" : "header",
"x-amazon-apigateway-api-key-source" : "HEADER"
}
}
}
}
In openapi2 you can add this in your yml.
swagger: 2.0
basePath: /dev
info:
title: My API
description: Proof of concept
schemes:
- https
securityDefinitions:
api_key:
type: apiKey
name: X-Api-Key
in: header
x-amazon-apigateway-api-key-source: HEADER
If you have troubles using api integration with openapi you can see this article: Working with API Gateway extensions to OpenAPI
I have the following Lambda function configured in AWS Lambda :
var AWS = require('aws-sdk');
var DOC = require('dynamodb-doc');
var dynamo = new DOC.DynamoDB();
exports.handler = function(event, context) {
var item = { id: 123,
foo: "bar"};
var cb = function(err, data) {
if(err) {
console.log(err);
context.fail('unable to update hit at this time' + err);
} else {
console.log(data);
context.done(null, data);
}
};
// This doesn't work. How do I get current stage ?
tableName = 'my_dynamo_table_' + stage;
dynamo.putItem({TableName:tableName, Item:item}, cb);
};
Everything works as expected (I insert an item in DynamoDB every time I call it).
I would like the dynamo table name to depend on the stage in which the lambda is deployed.
My table would be:
my_dynamo_table_staging for stage staging
my_dynamo_table_prod for stage prod
However, how do I get the name of the current stage inside the lambda ?
Edit: My Lambda is invoked by HTTP via an endpoint defined with API Gateway
If you have checked "Lambda Proxy Integration" in your Method Integration Request on API Gateway, you should receive the stage from API Gateway, as well as any stageVariable you have configured.
Here's an example of an event object from a Lambda function invoked by API Gateway configured with "Lambda Proxy Integration":
{
"resource": "/resourceName",
"path": "/resourceName",
"httpMethod": "POST",
"headers": {
"header1": "value1",
"header2": "value2"
},
"queryStringParameters": null,
"pathParameters": null,
"stageVariables": null,
"requestContext": {
"accountId": "123",
"resourceId": "abc",
"stage": "dev",
"requestId": "456",
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"apiKey": null,
"sourceIp": "1.1.1.1",
"accessKey": null,
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "agent",
"user": null
},
"resourcePath": "/resourceName",
"httpMethod": "POST",
"apiId": "abc123"
},
"body": "body here",
"isBase64Encoded": false
}
I managed it after much fiddling. Here is a walkthrough:
I assume that you have API Gateway and Lambda configured. If not, here's a good guide. You need part-1 and part-2. You can skip the end of part-2 by clicking the newly introduced button "Enable CORS" in API Gateway
Go to API Gateway.
Click here:
Click here:
Then expand Body Mapping Templates, enter application/json as content type, click the add button, then select mapping template, click edit
And paste the following content in "Mapping Template":
{
"body" : $input.json('$'),
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end
#end
},
"stage" : "$context.stage"
}
Then click the button "Deploy API" (this is important for changes in API Gateway to take effect)
You can test by changing the Lambda function to this:
var AWS = require('aws-sdk');
var DOC = require('dynamodb-doc');
var dynamo = new DOC.DynamoDB();
exports.handler = function(event, context) {
var currentStage = event['stage'];
if (true || !currentStage) { // Used for debugging
context.fail('Cannot find currentStage.' + ' stage is:'+currentStage);
return;
}
// ...
}
Then call your endpoint. You should have a HTTP 200 response, with the following response body:
{"errorMessage":"Cannot find currentStage. stage is:development"}
Important note:
If you have a Body Mapping Template that is too simple, like this: {"stage" : "$context.stage"}, this will override the params in the request. That's why body and headers keys are present in the Body Mapping Template. If they are not, your Lambda has not access to it.
For those who use the serverless framework it's already implemented and they can access to event.stage without any additional configurations.
See this issue for more information.
You can get it from event variable. I logged my event object and got this.
{ ...
"resource": "/test"
"stageVariables": {
"Alias": "beta"
}
}