AWS Lambda is a powerful tool to build serverless applications, especially when backed by APIGateway and Swagger. Lambda executes your code without the need for you to manage a dedicated server. APIGateway provides a front-end for your Lambda to be easily accessed from the Internet via endpoints that can be configured with the Swagger framework. In this article we’ll take a look at one specific example of an AJAX endpoint that uses custom path parameters, something typically problematic to implement because of Swagger limitations.
Task
Build an HTTP proxy endpoint reachable from a browser using an AJAX-call with an address like path/to/endpoint/{route+}
, where route
is a URL path (this means that it can contain forward slashes – e.g. path/to/endpoint/foo/bar
).
Framework
AWS Lambda – provides the backend for the endpoint. It contains code with business logic, processes user input, and returns a JSON response.
Amazon APIGateway – provides the connection layer to the Lambda from the Internet over HTTP, because you can’t call Lambda functions directly without AWS credentials.
Swagger – describes the APIGateway resource configuration.
Plan
Let’s assume that you have a Lambda function already. If not, then create a new one with the following code:
exports.handler = (request, _context, callback) => {
const body = {
url: 'prefix/' + request.pathParameters.route
};
const response = {
statusCode: 200,
body: JSON.stringify(body)
};
callback(null,response);
});
This function takes a request and returns a JSON with the modified URL. In this case, the function just attaches prefix
to the requested path. This is good enough for the shown example but in production you would likely have something more meaningful such as traffic-splitting, domain-switching, or other routing functionality.
In order to connect the Lambda to the Internet, we need to define a Swagger configuration for the APIGateway. You should note that the route
parameter can contain any string as is the case when you build a request proxy and your route
parameter contains the requested URL. Unfortunately, doing this with a basic Swagger configuration is not possible because the parameters cannot contain forward slashes. In APIGateway this would only be possible with a special extension for Swagger called x-amazon-apigateway-any-method
. But herein lies the problem…
Problem
The ANY
method will pass all requests to the Lambda function. It’s a good-enough solution if you call an endpoint only from a backend or mobile app but if you call it from a browser, the browser will fire a pre-flight request with the OPTIONS
method. The response should contain an empty body and CORS
headers which would allow us to perform the AJAX request. In this case however, the request will end up in the Lambda function and the main code will be executed, returning JSON as a result and no CORS
. The browser will then reject it and throw an error.
You can hack-fix it on the side of the Lambda function by checking the request method and returning a mock for OPTIONS, but in that case, why use tools as powerful as APIGateway and Swagger in the first place?
Solution
Actually, all you need to do is to define x-amazon-apigateway-any-method
with default behaviour and override the necessary methods with an actual configuration.
Here is an example:
swagger: '2.0'
info:
title: HTTP-proxy API
description: Example HTTP-proxy API
version: '1.0.0'
schemes:
- https
produces:
- application/json
paths:
/path/to/endpoint/{route+}:
x-amazon-apigateway-any-method:
produces:
- application/json
consumes:
- application/json
x-amazon-apigateway-integration:
type: mock
passthroughBehavior: when_no_templates
responses:
default:
statusCode: "403"
responseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
responseTemplates:
application/json: __passthrough__
requestTemplates:
application/json: "{\"statusCode\": 403}"
responses:
403:
headers:
Access-Control-Allow-Origin:
type: string
description: 403 response
get:
produces:
- application/json
consumes:
- application/json
summary: This is a test Lamda HTTP-endpoint
parameters: ¶meters
- name: route
in: path
type: string
required: true
x-amazon-apigateway-integration:
type: aws_proxy
httpMethod: POST
uri: %HERE_GOES_YOUR_LAMBDA_ARN%
credentials: %HERE_GOES_YOUR_LAMBDA_INVOCATION_ROLE%
responses:
200:
headers:
Access-Control-Allow-Origin:
type: string
description: Returns the experiment configuration and the destination for a specific target
options:
produces:
- application/json
consumes:
- application/json
summary: OPTIONS method defined for AJAX-calls
parameters:
- name: route
in: path
type: string
required: true
responses:
200:
description: 200 response
headers:
Access-Control-Allow-Origin:
type: string
Access-Control-Allow-Methods:
type: string
Access-Control-Allow-Headers:
type: string
x-amazon-apigateway-integration:
passthroughBehavior: when_no_templates
responses:
default:
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'GET,POST,PUT,PATCH,DELETE,OPTIONS'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Origin: "'*'"
responseTemplates:
application/json: __passthrough__
requestTemplates:
application/json: "{\"statusCode\": 200}"
type: mock
Here we define a x-amazon-apigateway-any-method
block that returns a 403 status code by default. After, we define a GET
method, overriding the default behaviour with a call to the Lambda function. Finally, we define an OPTIONS
method that returns Access-Control-Allow-*
headers, necessary for AJAX calls. All we need to do now is to return the Access-Control-Allow-Origin
header together with the Lambda response. Let’s modify our code:
exports.handler = (request, _context, callback) => {
const body = {
url: ‘prefix/’ + request.pathParameters.route
};
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(body)
};
callback(null,response);
});
Now our problem is fixed. We’ve defined an AJAX-compatible API endpoint using APIGateway tooling and can call our Lambda function from the Internet.
Photo by Jeremy Goldberg on Unsplash