com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-serverless-java-container-spring Show documentation
Show all versions of aws-serverless-java-container-spring Show documentation
Allows Java applications written for the Spring framework to run in AWS Lambda
/*
* Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
* with the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
package com.amazonaws.serverless.proxy.spring;
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler;
import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter;
import com.amazonaws.serverless.proxy.ExceptionHandler;
import com.amazonaws.serverless.proxy.RequestReader;
import com.amazonaws.serverless.proxy.ResponseWriter;
import com.amazonaws.serverless.proxy.SecurityContextWriter;
import com.amazonaws.serverless.proxy.internal.testutils.Timer;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.internal.servlet.*;
import com.amazonaws.services.lambda.runtime.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.web.SpringServletContainerInitializer;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.*;
import java.util.*;
import java.util.concurrent.CountDownLatch;
/**
* Spring implementation of the `LambdaContainerHandler` abstract class. This class uses the `LambdaSpringApplicationInitializer`
* object behind the scenes to proxy requests. The default implementation leverages the `AwsProxyHttpServletRequest` and
* `AwsHttpServletResponse` implemented in the `aws-serverless-java-container-core` package.
*
* Important: Make sure to add LambdaFlushResponseListener
in your SpringBootServletInitializer subclass configure().
*
* @param The incoming event type
* @param The expected return type
*/
public class SpringBootLambdaContainerHandler extends AwsLambdaServletContainerHandler {
private final Class extends WebApplicationInitializer> springBootInitializer;
private static final Logger log = LoggerFactory.getLogger(SpringBootLambdaContainerHandler.class);
private String[] springProfiles = null;
private DispatcherServlet dispatcherServlet;
// State vars
private boolean initialized;
/**
* Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects
* @param springBootInitializer {@code SpringBootServletInitializer} class
* @return An initialized instance of the `SpringLambdaContainerHandler`
* @throws ContainerInitializationException If an error occurs while initializing the Spring framework
*/
public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class extends WebApplicationInitializer> springBootInitializer)
throws ContainerInitializationException {
SpringBootLambdaContainerHandler newHandler = new SpringBootLambdaContainerHandler<>(
AwsProxyRequest.class,
AwsProxyResponse.class,
new AwsProxyHttpServletRequestReader(),
new AwsProxyHttpServletResponseWriter(),
new AwsProxySecurityContextWriter(),
new AwsProxyExceptionHandler(),
springBootInitializer);
newHandler.initialize();
return newHandler;
}
/**
* Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects and the given Spring profiles
* @param springBootInitializer {@code SpringBootServletInitializer} class
* @param profiles A list of Spring profiles to activate
* @return An initialized instance of the `SpringLambdaContainerHandler`
* @throws ContainerInitializationException If an error occurs while initializing the Spring framework
*/
public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class extends WebApplicationInitializer> springBootInitializer, String... profiles)
throws ContainerInitializationException {
SpringBootLambdaContainerHandler newHandler = new SpringBootLambdaContainerHandler<>(
AwsProxyRequest.class,
AwsProxyResponse.class,
new AwsProxyHttpServletRequestReader(),
new AwsProxyHttpServletResponseWriter(),
new AwsProxySecurityContextWriter(),
new AwsProxyExceptionHandler(),
springBootInitializer);
newHandler.activateSpringProfiles(profiles);
newHandler.initialize();
return newHandler;
}
/**
* Creates a new container handler with the given reader and writer objects
*
* @param requestTypeClass The class for the incoming Lambda event
* @param requestReader An implementation of `RequestReader`
* @param responseWriter An implementation of `ResponseWriter`
* @param securityContextWriter An implementation of `SecurityContextWriter`
* @param exceptionHandler An implementation of `ExceptionHandler`
* @param springBootInitializer {@code SpringBootServletInitializer} class
* @throws ContainerInitializationException If an error occurs while initializing the Spring framework
*/
public SpringBootLambdaContainerHandler(Class requestTypeClass,
Class responseTypeClass,
RequestReader requestReader,
ResponseWriter responseWriter,
SecurityContextWriter securityContextWriter,
ExceptionHandler exceptionHandler,
Class extends WebApplicationInitializer> springBootInitializer)
throws ContainerInitializationException {
super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler);
Timer.start("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR");
setServletContext(new SpringBootAwsServletContext());
initialized = false;
this.springBootInitializer = springBootInitializer;
Timer.stop("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR");
}
public void activateSpringProfiles(String... profiles) {
springProfiles = profiles;
// force a re-initialization
initialized = false;
}
@Override
protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) {
return new AwsHttpServletResponse(request, latch);
}
@Override
protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception {
// this method of the AwsLambdaServletContainerHandler sets the servlet context
Timer.start("SPRINGBOOT_HANDLE_REQUEST");
// wire up the application context on the first invocation
if (!initialized) {
initialize();
}
containerRequest.setServletContext(getServletContext());
// process filters & invoke servlet
doFilter(containerRequest, containerResponse, dispatcherServlet);
Timer.stop("SPRINGBOOT_HANDLE_REQUEST");
}
@Override
public void initialize()
throws ContainerInitializationException {
Timer.start("SPRINGBOOT_COLD_START");
if (springProfiles != null && springProfiles.length > 0) {
System.setProperty("spring.profiles.active", String.join(",", springProfiles));
}
SpringServletContainerInitializer springServletContainerInitializer = new SpringServletContainerInitializer();
LinkedHashSet> webAppInitializers = new LinkedHashSet<>();
webAppInitializers.add(springBootInitializer);
try {
springServletContainerInitializer.onStartup(webAppInitializers, getServletContext());
} catch (ServletException e) {
throw new ContainerInitializationException("Could not initialize Spring Boot", e);
}
if (springProfiles != null && springProfiles.length > 0) {
ConfigurableEnvironment springEnv = new StandardEnvironment();
springEnv.setActiveProfiles(springProfiles);
}
WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
dispatcherServlet = applicationContext.getBean("dispatcherServlet", DispatcherServlet.class);
initialized = true;
Timer.stop("SPRINGBOOT_COLD_START");
}
private class SpringBootAwsServletContext extends AwsServletContext {
public SpringBootAwsServletContext() {
super(SpringBootLambdaContainerHandler.this);
}
@Override
public ServletRegistration.Dynamic addServlet(String s, String s1) {
throw new UnsupportedOperationException("Only dispatcherServlet is supported");
}
@Override
public ServletRegistration.Dynamic addServlet(String s, Class extends Servlet> aClass) {
throw new UnsupportedOperationException("Only dispatcherServlet is supported");
}
@Override
public ServletRegistration.Dynamic addServlet(String s, Servlet servlet) {
if ("dispatcherServlet".equals(s)) {
try {
servlet.init(new ServletConfig() {
@Override
public String getServletName() {
return s;
}
@Override
public ServletContext getServletContext() {
return SpringBootAwsServletContext.this;
}
@Override
public String getInitParameter(String name) {
return null;
}
@Override
public Enumeration getInitParameterNames() {
return new Enumeration() {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public String nextElement() {
return null;
}
};
}
});
} catch (ServletException e) {
throw new RuntimeException("Cannot add servlet " + servlet, e);
}
return new ServletRegistration.Dynamic() {
@Override
public String getName() {
return s;
}
@Override
public String getClassName() {
return null;
}
@Override
public boolean setInitParameter(String name, String value) {
return false;
}
@Override
public String getInitParameter(String name) {
return null;
}
@Override
public Set setInitParameters(Map initParameters) {
return null;
}
@Override
public Map getInitParameters() {
return null;
}
@Override
public Set addMapping(String... urlPatterns) {
return null;
}
@Override
public Collection getMappings() {
return null;
}
@Override
public String getRunAsRole() {
return null;
}
@Override
public void setAsyncSupported(boolean isAsyncSupported) {
}
@Override
public void setLoadOnStartup(int loadOnStartup) {
}
@Override
public Set setServletSecurity(ServletSecurityElement constraint) {
return null;
}
@Override
public void setMultipartConfig(MultipartConfigElement multipartConfig) {
}
@Override
public void setRunAsRole(String roleName) {
}
};
} else {
throw new UnsupportedOperationException("Only dispatcherServlet is supported");
}
}
}
}