-
Notifications
You must be signed in to change notification settings - Fork 561
Home
AWS Serverless Java Container makes it easy to run your Spring, Jersey, or Spark applications using AWS Lambda and Amazon API Gateway. The library defines a set of interfaces and abstract classes required to support other frameworks in the future. Serverless Java Container starts each framework acting as a container, such as Tomcat, and translates API Gateway proxy events into the request format accepted by the underlying framework, such as an HttpServletRequest
or ContainerRequest
. HTTP responses from the frameworks are translated in the object structure API Gateway expects as a return value from Lambda.
-
Spring -
aws-serverless-java-container-spring
-
Spring Boot -
aws-serverless-java-container-spring
-
Jersey -
aws-serverless-java-container-jersey
-
Spark -
aws-serverless-java-container-spark
The primary purpose of the library is to act as a container; it receives events object from Lambda and translates them to a request object for the framework. Similarly, it translates responses from the framework into valid return values for API Gateway.
The framework can be used with both POJO and stream handlers. For applications that leverage context values from custom authorizers, we recommend using a stream handler: The framework uses Jackson's @JsonAnySetter
/Getter annotations to extract custom values from the authorizer context, the serializer included in AWS Lambda does not process annotated fields.
This is the basic example of a stream handler using Jersey:
public class StreamLambdaHandler implements RequestStreamHandler {
private final ResourceConfig jerseyApplication = new ResourceConfig()
.packages("com.amazonaws.serverless.sample.jersey")
.register(JacksonFeature.class);
private final JerseyLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler
= JerseyLambdaContainerHandler.getAwsProxyHandler(jerseyApplication);
@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
throws IOException {
AwsProxyRequest request = LambdaContainerHandler.getObjectMapper().readValue(inputStream, AwsProxyRequest.class);
AwsProxyResponse resp = handler.proxy(request, context);
LambdaContainerHandler.getObjectMapper().writeValue(outputStream, resp);
// just in case it wasn't closed by the mapper
outputStream.close();
}
}
The samples directory in the repository contains a sample pet store application for each framework. All of the samples include a stream handler as well as a SAM template for deployment.
The samples folder includes a simple pet store application implemented with each framework supported by this library. With each application, we have included its maven pom.xml
file as well as a SAM template.
- Using a shell, navigate to the folder for the sample application
$ cd ~/library-folder/samples/spark/pet-store
- In the root folder of the application, run the package command to build a jar
$ mvn package
- Once the build completes, use the AWS CLI to pre-package the SAM template for deployment. You will need an S3 bucket in your deployment region for this step to work
$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket my-deployment-bucket
- The package step uploads your built jar to the deployment bucket and generates an output SAM template with the correct values. With the AWS CLI, run the deploy command to create a new CloudFormation stack for your API.
$ aws cloudformation deploy --template-file output-sam.yaml --stack-name MySampleStack --capabilities CAPABILITY_IAM
- The deployed SAM template outputs the API endpoint you can use to test the sample app. Run the following command to extract the endpoint.
$ aws cloudformation describe-stacks --stack-name MySampleStack
...
"Outputs": [
{
"Description": "URL for application",
"ExportName": "SparkPetStoreApi",
"OutputKey": "SparkPetStoreApi",
"OutputValue": "https://xxxxxxxxx.execute-api.xx-xxxx-x.amazonaws.com/Prod/pets"
}
],
...
- With the output endpoint, run a curl command to test that your API is working as expected.
$ curl https://xxxxxxxxx.execute-api.xx-xxxx-x.amazonaws.com/Prod/pets?limit=2 | python -m json.tool
[
{
"breed": "Dalmatian",
"dateOfBirth": 1126208154522,
"id": "9de24d7a-cdeb-41a9-ac9a-11bd501db728",
"name": "Jack"
},
{
"breed": "Jack Russell Terrier",
"dateOfBirth": 1375040154522,
"id": "1c49bb0e-a2bd-4807-b19f-6581f5fb79f7",
"name": "Lily"
}
]
API Gateway supports authentication and authorization using IAM credentials (SigV4) or bearer tokens via Cognito User Pools or custom authorizers.
The library contains a default implementation of the SecurityContextWriter that supports API Gateway's proxy integration. The generated security context uses the API Gateway $context
object to establish the request security context.
For requests authorized via IAM credentials, all information about the user is available in the ApiGatewayRequestContext
object and its identity
property. Custom authorizer data, including any custom values, are stored in the ApiGatewayAuthorizerContext
object.
Context information that are not part of the standard HTTP request, such as the Cognito identity or custom authorizer claims, are stored in request attributes by the RequestReader
object. From your implementations, you can access this data using the getAttribute(String)
method of the request object. The example below extracts the API Gateway context property from the request and reads the "picture" value from the custom authorizer claims.
get("/pets", (req, res) -> {
ApiGatewayRequestContext ctx = (ApiGatewayRequestContext)req.raw().getAttribute(API_GATEWAY_CONTEXT_PROPERTY);
ApiGatewayAuthorizerContext authCtx = ctx.getAuthorizer();
String picture = authCtx.getContextValue("picture");
});
You can register Filter
implementations by implementing a StartupsHandler
as defined in the AwsLambdaServletContainerHandler
class. The onStartup
methods receives a reference to the current ServletContext
.
handler.onStartup(c -> {
FilterRegistration.Dynamic registration = c.addFilter("CustomHeaderFilter", CustomHeaderFilter.class);
// update the registration to map to a path
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
// servlet name mappings are disabled and will throw an exception
});
To translate incoming events, the library declares two abstract classes: The RequestReader
and the ResponseWriter
. Both these classes use generic types for the input and output objects. Implementing libraries, such as the Jersey one, extend these classes to support their types.
Out of the box, the library supports proxy integration events.