API Gateway - Lambda proxy integration and asynchronous call - amazon-web-services

If I set a header X-Amz-Invocation-Type: 'Event', the call is done asynchronously but as the Amazon documentation states (https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-simple-proxy-for-lambda-output-format), when one uses Proxy Lambda Integration, the lambda function must return a well formatted response of this kind:
callback(null, {"statusCode": 200, "body": "results"})
As the lambda function is called asynchronously, the API Gateway never get an answer and then return a 502 Bad Gateway error instead of a 200 OK status.
Below an extract of the swagger configuration:
"/myFunc": {
"post": {
"parameters": [
{
"name": "myparam",
"in": "query",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "200 response"
}
},
"x-amazon-apigateway-request-validator": "Validate query string parameters and headers",
"x-amazon-apigateway-integration": {
"responses": {
"default": {
"statusCode": "200"
}
},
"uri": "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:idAccount:function:myFunc/invocations",
"passthroughBehavior": "when_no_match",
"httpMethod": "POST",
"type": "aws_proxy",
"requestParameters": {
"integration.request.header.X-Amz-Invocation-Type": "'Event'"
}
}
}
}
Is there a way to have it worked?

You can setup a custom lambda integration (without proxy flag). You will need to configure the mapping templates to transform the request/response to your desired format.
http://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-lambda-non-proxy-integration.html#getting-started-new-lambda

Related

What is the difference between path and resource (or resourcePath) in an AWS API-gateway AWS-proxy event?

I'm in the process of learning AWS Lambda. I have created a lambda that will act as a REST API (APIEvent in CloudFormation terms) and want to debug that Lambda locally using an event.
If I understand correctly, running sam local generate-event apigateway aws-proxy generates an event that is suitable for locally running/debugging my Lambda. This produces the following event (some nested values are abbreviated):
{
"body": "eyJ0ZXN0IjoiYm9keSJ9",
"resource": "/{proxy+}",
"path": "/path/to/resource",
"httpMethod": "POST",
"isBase64Encoded": true,
"queryStringParameters": {
"foo": "bar"
},
"multiValueQueryStringParameters": {
"foo": [
"bar"
]
},
"pathParameters": {
"proxy": "/path/to/resource"
},
"stageVariables": {
"baz": "qux"
},
"headers": {
...
},
"multiValueHeaders": {
...
},
"requestContext": {
"accountId": "123456789012",
"resourceId": "123456",
"stage": "prod",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"requestTime": "09/Apr/2015:12:34:56 +0000",
"requestTimeEpoch": 1428582896000,
"identity": {
...
},
"path": "/prod/path/to/resource",
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"apiId": "1234567890",
"protocol": "HTTP/1.1"
}
}
However, I do not understand the relation between resource (which is copied in requestContext.resourcePath) and path (which is copied in requestContext.path).
What am I supposed to fill in for these values?
The resource is the API resource you defined in the API gateway. For example in your case /{proxy+}.
The path is the actual path from the request.
So, when you make a request GET https://yourdomain.com/v1/pets :
the path is /v1/pets
the resource is /{proxy+}

AWS Lambda Controller route not matching, with {proxy+} - 404 Not Found

I have a .NET core 3.1 project with a GET endpoint. Locally the route works fine - "/api" GET request returns JSON string.
After publishing to my AWS Lambda function, and invoking the lambda via Postman, I get a 404 Not Found response to the "/api" GET request. Full URL: "https://(lambda domain)/default/MyLambda2/api"
In my project's Startup.cs ConfigureServices and Configure methods, I added "LambdaLogger.Log" statements. My log lines show up in CloudWatch (so I am definitely reaching the app). It's just after that, the route fails.
In ConfigureServices method, I have
services.AddControllers();
In Configure method, I have
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
And in the Controller file
namespace LambdaTest.Controllers
{
[Route("api")]
public class ValuesController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
}
I have an API Gateway trigger defined for the lambda, with
API endpoint: "https://(lambda domain)/default/MyLambda2/{proxy+}"
API type: REST
Authorization: NONE
Method: ANY
In Resources tab, I have...
/MyLambda2
ANY
/{proxy+}
ANY
OPTIONS
... and I have all Resources deployed to "default" stage (via Actions > Deploy API).
Here is the default auto-generated serverless.template file...
{
"AWSTemplateFormatVersion": "2010-09-09",
"Transform": "AWS::Serverless-2016-10-31",
"Description": "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.",
"Parameters": {},
"Conditions": {},
"Resources": {
"AspNetCoreFunction": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Handler": "LambdaTest::LambdaTest.LambdaEntryPoint::FunctionHandlerAsync",
"Runtime": "dotnetcore3.1",
"CodeUri": "",
"MemorySize": 256,
"Timeout": 30,
"Role": null,
"Policies": [
"AWSLambda_FullAccess"
],
"Events": {
"ProxyResource": {
"Type": "Api",
"Properties": {
"Path": "/{proxy+}",
"Method": "ANY"
}
},
"RootResource": {
"Type": "Api",
"Properties": {
"Path": "/",
"Method": "ANY"
}
}
}
}
}
},
"Outputs": {
"ApiURL": {
"Description": "API endpoint URL for Prod environment",
"Value": {
"Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
}
}
}
}
What could be causing the route to not get picked up in lambda?
Thanks
Found the issue. I needed my route on the controller to be
[Route("MyLambda2/api")]

Is there any official documentation of AWS Gateway events sent to Lambda?

I've been searching for some sort of official documentation for the data types sent to AWS Lambda from integrations such as, AWS API Gateway. I've been able to find some "Examples" in the API Gateway documentation like here and here. It is also relatively easy to create a Lambda that just echoes the input event as output and check the output. For example (using a REST type API with LAMBDA_PROXY integration) you get something like:
{
"resource": "/another/{parameter}",
"path": "/another/some-parameter",
"httpMethod": "GET",
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
...
},
"multiValueHeaders": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate, br"
],
...
},
"queryStringParameters": null,
"multiValueQueryStringParameters": null,
"pathParameters": {
"parameter": "some-parameter"
},
"stageVariables": null,
"requestContext": {
"resourceId": "some-id",
"resourcePath": "/another/{parameter}",
"httpMethod": "GET",
...
},
"body": null,
"isBase64Encoded": false
}
But this doesn't tell me what fields I can always, or just sometimes, expect to be in the payload or the types of the field.
The closest I have to a bit of typing of the fields is the aws-lambda TypeScript typings from the Definitely Typed project.
Is there anything more detailed, or perhaps more general, and official source I can go to for the structure and type of Lambda event payloads for API Gateway and other AWS service integrations?
This is the closest I've come to an answer, so far. If anyone has more detailed (or general information) I'd gladly take it. In the Lambda documentation you can read that (my emphasis):
The runtime passes three arguments to the handler method. The first
argument is the event object, which contains information from the
invoker. The invoker passes this information as a JSON-formatted
string when it calls Invoke, and the runtime converts it to an object.
When an AWS service invokes your function, the event structure varies
by service.
Under "Working with other services" in the lambda documentation we only find the examples presented in the question. Instead we can look in the documentation for the service in question, e.g. API Gateway. For API Gateway, under "Working with [HTTP/REST] APIs", you will find descriptions of the event fields for REST and HTTP type APIs.
With that said, the documentation provided still only refer to the described payload structure as "examples". It only provides you with the names of the fields and some basic structure. Data types would have to be inferred from the examples, see the snippets below.
The story seems to be similar for other services you might want to integrate with Lambda like:
S3
DynamoDB
SQS
All that is to be found is examples of payloads, some on the services' own developer's guide other under Lambda's developer's guide.
HTTP v2.0
The following is an example of the payload format for version 2.0 using HTTP type API. For version 1 see the HTTP type integration docs.
{
version: '2.0',
routeKey: '$default',
rawPath: '/my/path',
rawQueryString: 'parameter1=value1&parameter1=value2&parameter2=value',
cookies: [ 'cookie1', 'cookie2' ],
headers: {
'Header1': 'value1',
'Header2': 'value2'
},
queryStringParameters: { parameter1: 'value1,value2', parameter2: 'value' },
requestContext: {
accountId: '123456789012',
apiId: 'api-id',
authorizer: { jwt: {
claims: {'claim1': 'value1', 'claim2': 'value2'},
scopes: ['scope1', 'scope2']
}
},
domainName: 'id.execute-api.us-east-1.amazonaws.com',
domainPrefix: 'id',
http: {
method: 'POST',
path: '/my/path',
protocol: 'HTTP/1.1',
sourceIp: 'IP',
userAgent: 'agent'
},
requestId: 'id',
routeKey: '$default',
stage: '$default',
time: '12/Mar/2020:19:03:58 +0000',
timeEpoch: 1583348638390
},
body: 'Hello from Lambda',
pathParameters: {'parameter1': 'value1'},
isBase64Encoded: false,
stageVariables: {'stageVariable1': 'value1', 'stageVariable2': 'value2'}
}
REST v3.0
The following is an example of the payload format for version 3.0 using REST type API. For version 2.0 see the REST type integration docs.
{
"openapi": "3.0.0",
"info": {
"version": "2016-09-12T17:50:37Z",
"title": "ProxyIntegrationWithLambda"
},
"paths": {
"/{proxy+}": {
"x-amazon-apigateway-any-method": {
"parameters": [
{
"name": "proxy",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {},
"x-amazon-apigateway-integration": {
"responses": {
"default": {
"statusCode": "200"
}
},
"uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:SimpleLambda4ProxyResource/invocations",
"passthroughBehavior": "when_no_match",
"httpMethod": "POST",
"cacheNamespace": "roq9wj",
"cacheKeyParameters": [
"method.request.path.proxy"
],
"type": "aws_proxy"
}
}
}
},
"servers": [
{
"url": "https://gy415nuibc.execute-api.us-east-1.amazonaws.com/{basePath}",
"variables": {
"basePath": {
"default": "/testStage"
}
}
}
]
}

Why is the API Gateway corrupting my binary file?

I have an AWS API Gateway acting as a proxy to a backend service:
{
"apiKeySource": "HEADER",
"name": "-",
"createdDate": 1513820260,
"binaryMediaTypes": [
"application/zip",
"application/octet-stream"
],
"endpointConfiguration": {
"types": [
"EDGE"
]
},
"id": "-"
}
The integration definition is here:
{
"integrationResponses": {
"200": {
"responseTemplates": {
"application/json": null
},
"statusCode": "200"
}
},
"passthroughBehavior": "WHEN_NO_MATCH",
"timeoutInMillis": 29000,
"uri": "http://${stageVariables.backend}:7000/{proxy}",
"connectionType": "INTERNET",
"httpMethod": "ANY",
"cacheNamespace": "iv06s3",
"type": "HTTP_PROXY",
"requestParameters": {
"integration.request.path.proxy": "method.request.path.proxy",
"integration.request.header.X-Source-IP": "context.identity.sourceIp"
},
"cacheKeyParameters": [
"method.request.path.proxy"
]
}
I have an endpoint that generates a Zip file on the fly and returns it to the requester.
When I access the endpoint directly, the file is fine. When I access it via the API Gateway, it gets corrupted.
The corruption takes the form of bytes in the original file being converted to 0xEFBFBD. This is the UTF-8 'replacement character'.
My request has Accept set to application/zip and the response has Content-Type: application/zip.
My expectation is that the API Gateway should recognize this as a binary media type and leave the file alone, but it seems pretty clear that it's processing it as text content.
What am I doing wrong?
Setting the "Binary Media Type" to "multipart/form-data" resolved a similar issue for me.
See here: AWS Api Gateway as a HTTP Proxy is currupting binary uploaded image files

"Export as Swagger" and "Export as Swagger + Postman" from API gateway is missing 'parameters' object

I have an API gateway endpoint (/item/{itemId}) with a path parameter 'itemId' in it. Here's the swagger definition I used to create the endpoint in API gateway.
{
"swagger": "2.0",
"info": {
"version": "v1",
"title": "sample-postman-import"
},
"host": "example.com",
"basePath": "/api",
"schemes": ["https"],
"paths": {
"/item/{itemId}": {
"get": {
"produces": [
"application/json"
],
"parameters": [
{
"name": "itemId",
"in": "path",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "200 response"
}
}
}
}
}
After deploying the API, when I export it as a swagger definition, the exported definition is missing the 'parameters' object, making it an incomplete swagger file. I've seen the same issue when I
Tried exporting from the UI as shown in https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-export-api.html
Used the getExport API call from javascript aws-sdk https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/APIGateway.html#getExport-property
Here's what the export looks like:
{
"swagger": "2.0",
"info": {
"version": "v1",
"title": "sample-postman-import_old"
},
"host": "example.com",
"basePath": "/api",
"schemes": ["https"],
"paths": {
"/item/{itemId}": {
"get": {
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "200 response"
}
}
}
}
}
}
I had the very same issue when I have created my API Gateway with Terraform. On the AWS UI the parameter was shown but when I requested method information through the AWS CLI with aws apigateway get-method command the parameter was not provided. So make sure you have the "requestParameters" set as you want and if not, set it through the CLI or Terraform.