diff --git a/README.md b/README.md index f1d0e1d..7170311 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ -# Spring boot +# Spring boot with AWS Lambda + +Sample application to demonstrate running a Spring Boot application as an AWS Lambda built with Cloud formation. + +# AWS Lambda + +Build the code and deploy with AWS SAM. + +mvn clean package + +aws cloudformation package --template-file sam.yaml --output-template-file target/output-sam.yaml --s3-bucket lambda-cfn + +aws cloudformation deploy --template-file target/output-sam.yaml --stack-name spring-boot-lambda --capabilities CAPABILITY_IAM + +aws cloudformation describe-stacks --stack-name spring-boot-lambda -Sample Spring Boot application with a single REST API. # Run @@ -10,14 +23,14 @@ To build and run from a packaged jar locally: or - mvn clean package + mvn clean package -Dboot java -jar target/spring-boot-lambda-1.0.0-SNAPSHOT.jar # Docker To build the image. First build the application, then build the docker image - mvn package + mvn package -Dboot docker build -t spring-boot-lambda . ## Run diff --git a/pom.xml b/pom.xml index 762cf58..3c2409a 100644 --- a/pom.xml +++ b/pom.xml @@ -23,15 +23,65 @@ org.springframework.boot spring-boot-starter-web + + + com.amazonaws.serverless + aws-serverless-java-container-spring + [0.1,) + - - - - org.springframework.boot - spring-boot-maven-plugin - - - + + + boot + + + boot + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + default + + true + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + + + org.apache.tomcat.embed:* + + + + + + + + + + + \ No newline at end of file diff --git a/sam.yaml b/sam.yaml new file mode 100644 index 0000000..76e2e4c --- /dev/null +++ b/sam.yaml @@ -0,0 +1,27 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Lambda with spring boot. + +Resources: + LambdaSpringBootFunction: + Type: AWS::Serverless::Function + Properties: + Handler: com.keyholesoftware.lambda.LambdaHandler::handleRequest + Runtime: java8 + CodeUri: target/spring-boot-lambda-1.0.0-SNAPSHOT.jar + MemorySize: 1512 + Policies: AWSLambdaBasicExecutionRole + Timeout: 60 + Events: + GetResource: + Type: Api + Properties: + Path: /{proxy+} + Method: any + +Outputs: + LambdaSpringBootApi: + Description: URL for application + Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Stage/languages' + Export: + Name: LambdaSpringBootApi diff --git a/src/main/java/com/keyholesoftware/lambda/LambdaHandler.java b/src/main/java/com/keyholesoftware/lambda/LambdaHandler.java new file mode 100644 index 0000000..b91b8d3 --- /dev/null +++ b/src/main/java/com/keyholesoftware/lambda/LambdaHandler.java @@ -0,0 +1,36 @@ +package com.keyholesoftware.lambda; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; + +public class LambdaHandler implements RequestStreamHandler { + + private static SpringBootLambdaContainerHandler handler; + + static { + try { + handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class); + handler.activateSpringProfiles("lambda"); + } catch (ContainerInitializationException e) { + // Re-throw the exception to force another cold start + e.printStackTrace(); + throw new RuntimeException("Could not initialize Spring Boot application", e); + } + } + + @Override + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + handler.proxyStream(inputStream, outputStream, context); + + // just in case it wasn't closed by the mapper + outputStream.close(); + } +} diff --git a/src/main/java/com/keyholesoftware/lambda/config/Config.java b/src/main/java/com/keyholesoftware/lambda/config/Config.java new file mode 100644 index 0000000..5fa592d --- /dev/null +++ b/src/main/java/com/keyholesoftware/lambda/config/Config.java @@ -0,0 +1,56 @@ +package com.keyholesoftware.lambda.config; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +@Configuration +@EnableWebMvc +@Profile("lambda") +public class Config { + + /** + * Create required HandlerMapping, to avoid several default HandlerMapping instances being created + */ + @Bean + public HandlerMapping handlerMapping() { + return new RequestMappingHandlerMapping(); + } + + /** + * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created + */ + @Bean + public HandlerAdapter handlerAdapter() { + return new RequestMappingHandlerAdapter(); + } + + /** + * optimization - avoids creating default exception resolvers; not required as the serverless container handles + * all exceptions + * + * By default, an ExceptionHandlerExceptionResolver is created which creates many dependent object, including + * an expensive ObjectMapper instance. + */ + @Bean + public HandlerExceptionResolver handlerExceptionResolver() { + return new HandlerExceptionResolver() { + + @Override + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + return null; + } + }; + } + +}