A set of tools to ease REST AWS Lambda Functions
Lambda Forest is a microframework that provides a set of tools to make it easier to develop rest api's using the aws lambda function and api gateway. Lambda Forest attempts to make the develpment faster by easing common tasks, such as:
- Exception handling
- Request body deserialization
- Response body serialization
- Request validation
In order to use Lambda Forest the AWS API Gateway needs to be configured to use Proxy Integration.
A detailed documentation can be found here.
The recommended way to use Lambda Forest is to consume it from Maven. To add the lastest version of Lambda Forest in your project declare the following dependency in your pom.xml file:
<dependency>
<groupId>br.com.tdsis</groupId>
<artifactId>lambda-forest</artifactId>
<version>1.0.0</version>
</dependency>The Lambda Forest framework provides an abstract base class called AbstratRequestHandler that handles a Lambda execution call and perform some operations such as request body deserialization, request validation, response body serialization and exception handling.
publicclassPostHandlerextendsAbstractRequestHandler<UserRequest, UserResponse> {
@Overridepublicvoidbefore(Contextcontext) throwsHttpException {
addResponseHeader("Access-Control-Allow-Origin", "*");
}
@OverridepublicUserResponseexecute(UserRequestinput, Contextcontext) throwsHttpException {
UserResponseresponse = newUserResponse();
response.setId(UUID.randomUUID().toString());
response.setName(input.getName());
response.setAddress(input.getAddress());
returnresponse;
}
}publicclassGetHandlerextendsAbstractRequestHandler<Void, UserResponse> {
@Overridepublicvoidbefore(Contextcontext) throwsHttpException {
addResponseHeader("Access-Control-Allow-Origin", "*");
}
@OverridepublicUserResponseexecute(Voidinput, Contextcontext) throwsHttpException {
Optional<String> optional = getQueryStringParameter("name");
Stringname = optional.orElseThrow(() -> newUnprocessableEntityException());
UserResponseresponse = newUserResponse();
response.setId(UUID.randomUUID().toString());
response.setName(name);
returnresponse;
}
}You can also specify a pojo as an input of a GET request to be deserialized based on the query string parameters:
curl http://my-aws-api-gateway-resource/users?name=myname&address=myaddresspublicclassGetRequest {
privateStringname;
privateStringaddress;
...gettersandsetters
}publicclassGetHandlerextendsAbstractRequestHandler<GetRequest, UserResponse> {
@Overridepublicvoidbefore(Contextcontext) throwsHttpException {
addResponseHeader("Access-Control-Allow-Origin", "*");
}
@OverridepublicUserResponseexecute(GetRequestinput, Contextcontext) throwsHttpException { UserResponseresponse = newUserResponse();
response.setId(UUID.randomUUID().toString());
response.setName(input.getName());
response.setName(input.getAddress());
returnresponse;
}
}publicclassDeleteHandlerextendsAbstractRequestHandler<Void, UserResponse> {
@Overridepublicvoidbefore(Contextcontext) throwsHttpException {
addResponseHeader("Access-Control-Allow-Origin", "*");
}
@OverridepublicUserResponseexecute(Voidinput, Contextcontext) throwsHttpException { Optional<String> optional = getPathParameter("id");
Stringid = optional.get();
// some delete operation...UserResponseresponse = newUserResponse(); returnresponse;
}
}The AbstractRequestHandler class provides methods to access all the request / response attributes and the Lambda environment context as well:
| Attribute | Method |
|---|---|
| Request Headers | getHeader("headerName") |
| Response Headers | addResponseHeader("headerName") |
| Raw Request Body | getRawRequestBody() |
| Http Method | getHttpMethod() |
| Resource Path | getPath() |
| Stage Variables | getStageVariable("stageVariableName") |
| Path Parameters | getPathParameter("parameterName") |
| Query String Parameters | getQueryStringParameter("parameterName") |
Lambda Forest uses the Hibernate bean validation implementation to validate requests. To validate the incoming request the input parameter of the execute method must be annotated with @Valid
Eg.:
publicclassUserRequest {
@Size(min=1, max=50, message="Invalid name message")
privateStringname;
@NotBlank(message="Invalid address message")
privateStringaddress;
... gettersandsetters
}publicclassLambdaHandlerextendsAbstractRequestHandler<UserRequest, UserResponse> {
@Overridepublicvoidbefore(Contextcontext) throwsHttpException {
addResponseHeader("Access-Control-Allow-Origin", "*");
}
@OverridepublicUserResponseexecute(@ValidUserRequestinput, Contextcontext) throwsHttpException {
UserResponseresponse = newUserResponse();
response.setId(UUID.randomUUID().toString());
response.setName(input.getName());
response.setAddress(input.getAddress());
returnresponse;
}
}If any constraint violation occurs an UnprocessableEntityException will be thrown and the http response will be serialized with the error details.
Eg.:
curl -X POST http://my-api-gateway-resource/users -d '{"name": "my name", "address": ""}'I this particular example the server will respond the above request with a http status code 422 and the following response body:
{
"message": "Unprocessable entity",
"errors": [
{
"attribute": "address",
"message": "Invalid address message"
}
]
}A detailed documentation of the Hibernate validation can be found here.
If you want to provide your own bean validation mechanism, the method resolveRequestValidator should be overridden:
@OverrideprotectedRequestValidatorresolveRequestValidator() {
returnnewMyCustomRequestValidator();
}Lambda Forest uses Jackson to serialize and deserialize the request and response body.
The deserialization and serialization strategies are based on two http headers:
- Content-Type for deserialization
- Accept for serialization
If you want to provide your own request body deserializer, the method resolveDeserializerStrategy should be overridden:
@OverrideprotectedRequestBodyDeserializerStrategyresolveDeserializerStrategy(StringcontentType) {
returnnewMyCustomRequestBodyDeserializer();
}If you want to provide your own response body serializer, the method resolveSerializerStrategy should be overridden:
@OverrideprotectedResponseBodySerializerStrategyresolveSerializerStrategy(Stringaccept) {
returnnewMyCustomResponseBodySerializer();
}Lambda Forest offers a simple way to create a custom API Gateway Authorizer:
publicclassCustomAPIGatewayAuthorizerextendsAbstractAPIGatewayAuthorizer {
@OverridepublicAuthPolicyauthorize(AuthRequestrequest, Contextcontext) throwsHttpException {
// your custom authorization logic herePolicyStatementpolicyStatement = newPolicyStatement(
PolicyAction.INVOKE, PolicyEffect.ALLOW, request.getMethodArn());
returnnewAuthPolicyBuilder()
.withPrincipalId("principal-id") .addPolicyStatement(policyStatement)
.addToContext("myCustomKey", "myCustomValue")
.build();
}
}publicclassCustomAPIGatewayAuthorizerextendsAbstractAPIGatewayAuthorizer {
@OverridepublicAuthPolicyauthorize(AuthRequestrequest, Contextcontext) throwsHttpException {
// your custom authorization logic herereturnnewAuthPolicyBuilder("principal-id")
.denyAll()
.build();
}
}publicclassCustomAPIGatewayAuthorizerextendsAbstractAPIGatewayAuthorizer {
@OverridepublicAuthPolicyauthorize(AuthRequestrequest, Contextcontext) throwsHttpException {
// your custom authorization logic herethrownewUnauthorizedException();
}
}The Lambda Forest framework provides a class that simulates a Lambda execution call.
To simulate a Lambda execution call it is necessary to define an execution specification in your project resource folder.
Eg.:
project
└───src
│ └───main
│ └───resources
│ lambda-spec.json
{
"context": {
"awsRequestId": "",
"logGroupName": "",
"logStreamName": "",
"functionName": "",
"functionVersion": "",
"invokedFunctionArn": "",
"identity": null,
"clientContext": {
"client": {
"installationId": "",
"appTitle": "",
"appVersionName": "",
"appVersionCode": "",
"appPackageName": ""
},
"custom": {
},
"environment": {
}
},
"remainingTimeInMillis": 30,
"memoryLimitInMB": 128,
"logger": null
},
"request":{
"path": "/users",
"pathParameters": {
},
"queryStringParameters": {
},
"resource": "users",
"stageVariables": {
},
"method":"POST",
"headers": {
},
"body": { "name": "my name",
"message": "This is my message"
}
}
}publicclassLambdaHandlerextendsAbstractRequestHandler<UserRequest, UserResponse> {
@Overridepublicvoidbefore(Contextcontext) throwsHttpException {
addResponseHeader("Access-Control-Allow-Origin", "*");
}
@OverridepublicUserResponseexecute(@ValidUserRequestinput, Contextcontext) throwsHttpException {
UserResponseresponse = newUserResponse();
response.setId(UUID.randomUUID().toString());
response.setName(input.getName());
response.setAddress(input.getAddress());
returnresponse;
}
publicstaticvoidmain(String [] args) {
LambdaRunner.run("lambda-spec.json", LambdaHandler.class, args)
.print()
.printBody()
.printHeaders()
.printStatusCode();
}
}The Lambda Forest framework is open-source software licensed under the MIT license .