Skip to content

Latest commit

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

lambda-forest

A set of tools to ease REST AWS Lambda Functions

About Lambda Forest

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

Before you begin

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.

Getting Started

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>

Handling Requests

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.

POST / PUT / PATCH

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;
}
}

GET

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=myaddress
publicclassGetRequest {
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;
}
}

DELETE

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 Request / Response Attributes

The AbstractRequestHandler class provides methods to access all the request / response attributes and the Lambda environment context as well:

AttributeMethod
Request HeadersgetHeader("headerName")
Response HeadersaddResponseHeader("headerName")
Raw Request BodygetRawRequestBody()
Http MethodgetHttpMethod()
Resource PathgetPath()
Stage VariablesgetStageVariable("stageVariableName")
Path ParametersgetPathParameter("parameterName")
Query String ParametersgetQueryStringParameter("parameterName")

Request Validation

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();
}

Serialization and Deserialization

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();
}

API Gateway Custom Authorizer

Lambda Forest offers a simple way to create a custom API Gateway Authorizer:

Allowing a Request

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();
}
}

Denying access to all API Gateway Resources

publicclassCustomAPIGatewayAuthorizerextendsAbstractAPIGatewayAuthorizer {
@OverridepublicAuthPolicyauthorize(AuthRequestrequest, Contextcontext) throwsHttpException {
// your custom authorization logic herereturnnewAuthPolicyBuilder("principal-id")
.denyAll()
.build();
}
}

Denying access with a HTTP Exception

publicclassCustomAPIGatewayAuthorizerextendsAbstractAPIGatewayAuthorizer {
@OverridepublicAuthPolicyauthorize(AuthRequestrequest, Contextcontext) throwsHttpException {
// your custom authorization logic herethrownewUnauthorizedException();
}
}

Running locally

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();
}
}

License

The Lambda Forest framework is open-source software licensed under the MIT license .

About

A set of tools to ease REST AWS Lambda functions

Resources

Stars

2 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages