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

com.amazonaws.serverless.proxy.spark.SparkLambdaContainerHandler Maven / Gradle / Ivy

There is a newer version: 1.9.4
Show newest version
/*
 * 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.spark;


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.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServer;
import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServerFactory;

import com.amazonaws.services.lambda.runtime.Context;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.Service;
import spark.Spark;
import spark.embeddedserver.EmbeddedServers;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.EnumSet;
import java.util.concurrent.CountDownLatch;


/**
 * Implementation of the LambdaContainerHandler object that supports the Spark framework: http://sparkjava.com/
 * 

* Because of the way this container is implemented, using reflection to change accessibility of methods in the Spark * framework and inserting itself as the default embedded container, it is important that you initialize the Handler * before declaring your spark routes. *

* This implementation uses the default AwsProxyHttpServletRequest and Response implementations. *

*

 * {@code
 *     // always initialize the handler first
 *     SparkLambdaContainerHandler handler =
 *             SparkLambdaContainerHandler.getAwsProxyHandler();
 *
 *     get("/hello", (req, res) -> {
 *         res.status(200);
 *         res.body("Hello World");
 *     });
 * }
 * 
* * @param The request object used by the RequestReader implementation passed to the constructor * @param The response object produced by the ResponseWriter implementation in the constructor */ public class SparkLambdaContainerHandler extends AwsLambdaServletContainerHandler { //------------------------------------------------------------- // Constants //------------------------------------------------------------- private static final String LAMBDA_EMBEDDED_SERVER_CODE = "AWS_LAMBDA"; //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- private LambdaEmbeddedServer embeddedServer; private LambdaEmbeddedServerFactory lambdaServerFactory; private Logger log = LoggerFactory.getLogger(SparkLambdaContainerHandler.class); //------------------------------------------------------------- // Methods - Public - Static //------------------------------------------------------------- /** * Returns a new instance of an SparkLambdaContainerHandler initialized to work with AwsProxyRequest * and AwsProxyResponse objects. * * @return a new instance of SparkLambdaContainerHandler * * @throws ContainerInitializationException Throws this exception if we fail to initialize the Spark container. * This could be caused by the introspection used to insert the library as the default embedded container */ public static SparkLambdaContainerHandler getAwsProxyHandler() throws ContainerInitializationException { SparkLambdaContainerHandler newHandler = new SparkLambdaContainerHandler<>(AwsProxyRequest.class, AwsProxyResponse.class, new AwsProxyHttpServletRequestReader(), new AwsProxyHttpServletResponseWriter(), new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler(), new LambdaEmbeddedServerFactory()); // For Spark we cannot call intialize here. It needs to be called manually after the routes are set //newHandler.initialize(); return newHandler; } //------------------------------------------------------------- // Constructors //------------------------------------------------------------- public SparkLambdaContainerHandler(Class requestTypeClass, Class responseTypeClass, RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, LambdaEmbeddedServerFactory embeddedServerFactory) throws ContainerInitializationException { super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); Timer.start("SPARK_CONTAINER_HANDLER_CONSTRUCTOR"); EmbeddedServers.add(LAMBDA_EMBEDDED_SERVER_CODE, embeddedServerFactory); this.lambdaServerFactory = embeddedServerFactory; // TODO: This is pretty bad but we are not given access to the embeddedServerIdentifier property of the // Service object try { AccessController.doPrivileged((PrivilegedExceptionAction) () -> { log.debug("Changing visibility of getInstance method and embeddedServerIdentifier properties"); Method serviceInstanceMethod = Spark.class.getDeclaredMethod("getInstance"); serviceInstanceMethod.setAccessible(true); Service sparkService = (Service) serviceInstanceMethod.invoke(null); Field serverIdentifierField = Service.class.getDeclaredField("embeddedServerIdentifier"); serverIdentifierField.setAccessible(true); serverIdentifierField.set(sparkService, LAMBDA_EMBEDDED_SERVER_CODE); return null; }); } catch (PrivilegedActionException e) { if (e.getException() instanceof NoSuchFieldException) { log.error("Could not fine embeddedServerIdentifier field in Service class", e.getException()); } else if (e.getException() instanceof NoSuchMethodException) { log.error("Could not find getInstance method in Spark class", e.getException()); } else if (e.getException() instanceof IllegalAccessException) { log.error("Could not access getInstance method in Spark class", e.getException()); } else if (e.getException() instanceof InvocationTargetException) { log.error("Could not invoke getInstance method in Spark class", e.getException()); } else { log.error("Unknown exception while modifying Spark class", e.getException()); } Timer.stop("SPARK_CONTAINER_HANDLER_CONSTRUCTOR"); throw new ContainerInitializationException("Could not initialize Spark server", e.getException()); } Timer.stop("SPARK_CONTAINER_HANDLER_CONSTRUCTOR"); } //------------------------------------------------------------- // Methods - Implementation //------------------------------------------------------------- @Override protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { return new AwsHttpServletResponse(request, latch); } @Override protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) throws Exception { Timer.start("SPARK_HANDLE_REQUEST"); if (embeddedServer == null) { initialize(); } httpServletRequest.setServletContext(getServletContext()); doFilter(httpServletRequest, httpServletResponse, null); Timer.stop("SPARK_HANDLE_REQUEST"); } @Override public void initialize() throws ContainerInitializationException { Timer.start("SPARK_COLD_START"); log.debug("First request, getting new server instance"); // trying to call init in case the embedded server had not been initialized. Spark.init(); // adding this call to make sure that the framework is fully initialized. This should address a race // condition and solve GitHub issue #71. Spark.awaitInitialization(); embeddedServer = lambdaServerFactory.getServerInstance(); // manually add the spark filter to the chain. This should the last one and match all uris FilterRegistration.Dynamic sparkRegistration = getServletContext().addFilter("SparkFilter", embeddedServer.getSparkFilter()); sparkRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); Timer.stop("SPARK_COLD_START"); } }