All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler Maven / Gradle / Ivy

/*
 * 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 java.util.concurrent.CountDownLatch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.ExceptionHandler;
import com.amazonaws.serverless.proxy.InitializationWrapper;
import com.amazonaws.serverless.proxy.RequestReader;
import com.amazonaws.serverless.proxy.ResponseWriter;
import com.amazonaws.serverless.proxy.SecurityContextWriter;
import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest;
import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse;
import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler;
import com.amazonaws.serverless.proxy.internal.servlet.AwsServletContext;
import com.amazonaws.serverless.proxy.internal.servlet.AwsServletRegistration;
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.model.HttpApiV2ProxyRequest;
import com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveServletEmbeddedServerFactory;
import com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory;
import com.amazonaws.services.lambda.runtime.Context;

import jakarta.servlet.Servlet;
import jakarta.servlet.http.HttpServletRequest;

/**
 * SpringBoot 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 static final String DISPATCHER_SERVLET_REGISTRATION_NAME = "dispatcherServlet";

    private final Class springBootInitializer;
    private static final Logger log = LoggerFactory.getLogger(SpringBootLambdaContainerHandler.class);
    private String[] springProfiles = null;
    private WebApplicationType springWebApplicationType;
    private ConfigurableApplicationContext applicationContext;

    private static SpringBootLambdaContainerHandler instance;

    // State vars
    private boolean initialized;

    /**
     * We need to rely on the static instance of this for SpringBoot because we need it to access the ServletContext.
     * Normally, SpringBoot would initialize its own embedded container through the SpringApplication.run()
     * method. However, in our case we need to rely on the pre-initialized handler and need to fetch information from it
     * for our mock {@link ServerlessReactiveServletEmbeddedServerFactory}.
     *
     * @return The initialized instance
     */
    public static SpringBootLambdaContainerHandler getInstance() {
        return instance;
    }

    /**
     * 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 springBootInitializer, String... profiles)
            throws ContainerInitializationException {
        return new SpringBootProxyHandlerBuilder()
                .defaultProxy()
                .initializationWrapper(new InitializationWrapper())
                .springBootApplication(springBootInitializer)
                .profiles(profiles)
                .buildAndInitialize();
    }

    /**
     * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `HttpApiV2ProxyRequest` 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 getHttpApiV2ProxyHandler(Class springBootInitializer, String... profiles)
            throws ContainerInitializationException {
        return new SpringBootProxyHandlerBuilder()
                .defaultHttpApiV2Proxy()
                .initializationWrapper(new InitializationWrapper())
                .springBootApplication(springBootInitializer)
                .profiles(profiles)
                .buildAndInitialize();
    }

    /**
     * Creates a new container handler with the given reader and writer objects
     *
     * @param requestTypeClass The class for the incoming Lambda event
     * @param responseTypeClass The class for the Lambda function output
     * @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
     * @param init The initialization Wrapper that will be used to start Spring Boot
     * @param applicationType The Spring Web Application Type
     */
    public SpringBootLambdaContainerHandler(Class requestTypeClass,
                                            Class responseTypeClass,
                                            RequestReader requestReader,
                                            ResponseWriter responseWriter,
                                            SecurityContextWriter securityContextWriter,
                                            ExceptionHandler exceptionHandler,
                                            Class springBootInitializer,
                                            InitializationWrapper init,
                                            WebApplicationType applicationType) {
        super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler);
        Timer.start("SPRINGBOOT2_CONTAINER_HANDLER_CONSTRUCTOR");
        initialized = false;
        this.springBootInitializer = springBootInitializer;
        springWebApplicationType = applicationType;
        setInitializationWrapper(init);
        SpringBootLambdaContainerHandler.setInstance(this);

        Timer.stop("SPRINGBOOT2_CONTAINER_HANDLER_CONSTRUCTOR");
    }

    // this is not pretty. However, because SpringBoot wants to control all of the initialization
    // we need to access this handler as a singleton from the EmbeddedContainer to set the servlet
    // context and from the ServletConfigurationSupport implementation
    private static void setInstance(SpringBootLambdaContainerHandler h) {
        SpringBootLambdaContainerHandler.instance = h;
    }

    public void activateSpringProfiles(String... profiles) {
        springProfiles = profiles;
        // force a re-initialization
        initialized = false;
    }

    @Override
    protected AwsHttpServletResponse getContainerResponse(HttpServletRequest request, CountDownLatch latch) {
        return new AwsHttpServletResponse(request, latch);
    }

    @Override
    protected void handleRequest(HttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception {
        // this method of the AwsLambdaServletContainerHandler sets the servlet context
        Timer.start("SPRINGBOOT2_HANDLE_REQUEST");

        // wire up the application context on the first invocation
        if (!initialized) {
            initialize();
        }

        // process filters & invoke servlet
        Servlet reqServlet = ((AwsServletContext)getServletContext()).getServletForPath(containerRequest.getPathInfo());
        if (AwsHttpServletRequest.class.isAssignableFrom(containerRequest.getClass())) {
            ((AwsHttpServletRequest)containerRequest).setServletContext(getServletContext());
            ((AwsHttpServletRequest)containerRequest).setResponse(containerResponse);
        }
        doFilter(containerRequest, containerResponse, reqServlet);
        Timer.stop("SPRINGBOOT2_HANDLE_REQUEST");
    }


    @Override
    public void initialize()
            throws ContainerInitializationException {
        Timer.start("SPRINGBOOT2_COLD_START");

        SpringApplicationBuilder builder = new SpringApplicationBuilder(getEmbeddedContainerClasses())
                .web(springWebApplicationType); // .REACTIVE, .SERVLET
        if (springProfiles != null) {
            builder.profiles(springProfiles);
        }
        applicationContext = builder.run();
        if (springWebApplicationType == WebApplicationType.SERVLET) {
            ((AnnotationConfigServletWebServerApplicationContext)applicationContext).setServletContext(getServletContext());
            AwsServletRegistration reg = (AwsServletRegistration)getServletContext().getServletRegistration(DISPATCHER_SERVLET_REGISTRATION_NAME);
            if (reg != null) {
                reg.setLoadOnStartup(1);
            }
        }
        super.initialize();
        initialized = true;
        Timer.stop("SPRINGBOOT2_COLD_START");
    }

    private Class[] getEmbeddedContainerClasses() {
        Class[] classes = new Class[2];
        if (springWebApplicationType == WebApplicationType.REACTIVE) {
            try {
                // if HandlerAdapter is available we assume they are using WebFlux. Otherwise plain servlet.
                this.getClass().getClassLoader().loadClass("org.springframework.web.reactive.HandlerAdapter");
                log.debug("Found WebFlux HandlerAdapter on classpath, using reactive server factory");
                classes[0] = ServerlessReactiveServletEmbeddedServerFactory.class;
            } catch (ClassNotFoundException e) {
                springWebApplicationType = WebApplicationType.SERVLET;
                classes[0] = ServerlessServletEmbeddedServerFactory.class;
            }
        } else {
            classes[0] = ServerlessServletEmbeddedServerFactory.class;
        }

        classes[1] = springBootInitializer;
        return classes;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy