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

com.networknt.aws.lambda.LambdaFunctionHandler Maven / Gradle / Ivy

Go to download

A LightHttpHandler implementation to invoke a lambda function directly from light-proxy

The newest version!
package com.networknt.aws.lambda;

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.networknt.body.BodyHandler;
import com.networknt.config.Config;
import com.networknt.config.JsonMapper;
import com.networknt.handler.Handler;
import com.networknt.handler.LightHttpHandler;
import com.networknt.httpstring.AttachmentConstants;
import com.networknt.metrics.AbstractMetricsHandler;
import com.networknt.metrics.MetricsConfig;
import com.networknt.utility.Constants;
import com.networknt.utility.ModuleRegistry;
import com.networknt.utility.StringUtils;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HeaderMap;
import io.undertow.util.HttpString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lambda.LambdaAsyncClient;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.LambdaClientBuilder;
import software.amazon.awssdk.services.lambda.model.InvokeRequest;
import software.amazon.awssdk.services.lambda.model.InvokeResponse;
import software.amazon.awssdk.services.lambda.model.LambdaException;

import java.net.URI;
import java.time.Duration;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class LambdaFunctionHandler implements LightHttpHandler {
    private static final Logger logger = LoggerFactory.getLogger(LambdaFunctionHandler.class);
    private static LambdaInvokerConfig config;
    private static final String MISSING_ENDPOINT_FUNCTION = "ERR10063";
    private static final String EMPTY_LAMBDA_RESPONSE = "ERR10064";
    private static AbstractMetricsHandler metricsHandler;

    private static LambdaAsyncClient client;

    public LambdaFunctionHandler() {
        config = LambdaInvokerConfig.load();
        SdkAsyncHttpClient asyncHttpClient = NettyNioAsyncHttpClient.builder()
                .readTimeout(Duration.ofMillis(config.getApiCallAttemptTimeout()))
                .writeTimeout(Duration.ofMillis(config.getApiCallAttemptTimeout()))
                .connectionTimeout(Duration.ofMillis(config.getApiCallAttemptTimeout()))
                .build();
        ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration.builder()
                .apiCallTimeout(Duration.ofMillis(config.getApiCallTimeout()))
                .apiCallAttemptTimeout(Duration.ofSeconds(config.getApiCallAttemptTimeout()))
                .build();

        var builder = LambdaAsyncClient.builder().region(Region.of(config.getRegion()))
                .httpClient(asyncHttpClient)
                .overrideConfiguration(overrideConfig);

        if(!StringUtils.isEmpty(config.getEndpointOverride())) {
            builder.endpointOverride(URI.create(config.getEndpointOverride()));
        }
        client = builder.build();

        if(config.isMetricsInjection()) {
            // get the metrics handler from the handler chain for metrics registration. If we cannot get the
            // metrics handler, then an error message will be logged.
            Map handlers = Handler.getHandlers();
            metricsHandler = (AbstractMetricsHandler) handlers.get(MetricsConfig.CONFIG_NAME);
            if(metricsHandler == null) {
                logger.error("An instance of MetricsHandler is not configured in the handler.yml.");
            }
        }
        ModuleRegistry.registerModule(LambdaInvokerConfig.CONFIG_NAME, LambdaFunctionHandler.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(LambdaInvokerConfig.CONFIG_NAME), null);
        if(logger.isInfoEnabled()) logger.info("LambdaFunctionHandler is loaded.");
    }

    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        long startTime = System.nanoTime();
        String httpMethod = exchange.getRequestMethod().toString();
        String requestPath = exchange.getRequestPath();
        Map headers = convertHeaders(exchange.getRequestHeaders());
        Map queryStringParameters = convertQueryParameters(exchange.getQueryParameters());
        Map pathParameters = convertPathParameters(exchange.getPathParameters());
        String body = exchange.getAttachment(AttachmentConstants.REQUEST_BODY_STRING);
        if(logger.isTraceEnabled()) {
            logger.trace("requestPath = " + requestPath + " httpMethod = " + httpMethod + " body = " + body);
            logger.trace("headers = " + JsonMapper.toJson(headers));
            logger.trace("queryParameters = " + JsonMapper.toJson(queryStringParameters));
            logger.trace("pathParameters = " + JsonMapper.toJson(pathParameters));
        }
        String endpoint = (String)exchange.getAttachment(AttachmentConstants.AUDIT_INFO).get(Constants.ENDPOINT_STRING);
        String functionName = config.getFunctions().get(endpoint);
        if(functionName == null) {
            setExchangeStatus(exchange, MISSING_ENDPOINT_FUNCTION, endpoint);
            if(config.isMetricsInjection() && metricsHandler != null) metricsHandler.injectMetrics(exchange, startTime, config.getMetricsName(), endpoint);
            return;
        }
        APIGatewayProxyRequestEvent requestEvent = new APIGatewayProxyRequestEvent();
        requestEvent.setHttpMethod(httpMethod);
        requestEvent.setPath(requestPath);
        requestEvent.setHeaders(headers);
        requestEvent.setPathParameters(pathParameters);
        requestEvent.setQueryStringParameters(queryStringParameters);
        requestEvent.setBody(body == null ? null : body);
        String requestBody = JsonMapper.objectMapper.writeValueAsString(requestEvent);
        if(logger.isTraceEnabled()) logger.trace("requestBody = {}", requestBody);
        String res = invokeFunction(client, functionName, requestBody);
        if(logger.isDebugEnabled()) logger.debug("response = {}", res);
        if(res == null) {
            setExchangeStatus(exchange, EMPTY_LAMBDA_RESPONSE, functionName);
            if(config.isMetricsInjection() && metricsHandler != null) metricsHandler.injectMetrics(exchange, startTime, config.getMetricsName(), endpoint);
            return;
        }
        APIGatewayProxyResponseEvent responseEvent = JsonMapper.fromJson(res, APIGatewayProxyResponseEvent.class);
        setResponseHeaders(exchange, responseEvent.getHeaders());
        exchange.setStatusCode(responseEvent.getStatusCode());
        exchange.getResponseSender().send(responseEvent.getBody());
        if(config.isMetricsInjection() && metricsHandler != null) metricsHandler.injectMetrics(exchange, startTime, config.getMetricsName(), endpoint);
    }

    private String invokeFunction(LambdaAsyncClient client, String functionName, String requestBody)  {
        String response = null;
        try {
            //Need a SdkBytes instance for the payload
            SdkBytes payload = SdkBytes.fromUtf8String(requestBody) ;

            //Setup an InvokeRequest
            InvokeRequest request = InvokeRequest.builder()
                    .functionName(functionName)
                    .logType(config.getLogType())
                    .payload(payload)
                    .build();
            CompletableFuture futureResponse = client.invoke(request)
                    .thenApply(res -> {
                        if(logger.isTraceEnabled()) logger.trace("LambdaFunctionHandler.invokeFunction response: {}", res);
                        return res.payload().asUtf8String();
                    })
                    .exceptionally(e -> {
                        logger.error("Error invoking lambda function: {}", functionName, e);
                        return null;
                    });
            return futureResponse.get();
        } catch(InterruptedException | ExecutionException e) {
            logger.error("LambdaException", e);
        }
        return null;
    }

    private Map convertHeaders(HeaderMap headerMap) {
        Map headers = new HashMap<>();
        if(headerMap != null) {
            for(HttpString headerName : headerMap.getHeaderNames()) {
                headers.put(headerName.toString(), headerMap.get(headerName).getFirst());
            }
        }
        return headers;
    }

    private Map convertQueryParameters(Map> params) {
        Map queryParameters = new HashMap<>();
        if(params != null) {
            for(String key : params.keySet()) {
                queryParameters.put(key, params.get(key).getFirst());
            }
        }
        return queryParameters;

    }

    private Map convertPathParameters(Map> params) {
        Map pathParameters = new HashMap<>();
        if(params != null) {
            for(String key : params.keySet()) {
                pathParameters.put(key, params.get(key).getFirst());
            }
        }
        return pathParameters;

    }

    private void setResponseHeaders(HttpServerExchange exchange, Map headers) {
        if(headers != null) {
            for(String key : headers.keySet()) {
                exchange.getResponseHeaders().put(new HttpString(key), headers.get(key));
            }
        }
    }

    public static void reload() {
        config.reload();
        SdkAsyncHttpClient asyncHttpClient = NettyNioAsyncHttpClient.builder()
                .readTimeout(Duration.ofMillis(config.getApiCallAttemptTimeout()))
                .writeTimeout(Duration.ofMillis(config.getApiCallAttemptTimeout()))
                .connectionTimeout(Duration.ofMillis(config.getApiCallAttemptTimeout()))
                .build();
        ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration.builder()
                .apiCallTimeout(Duration.ofMillis(config.getApiCallTimeout()))
                .apiCallAttemptTimeout(Duration.ofSeconds(config.getApiCallAttemptTimeout()))
                .build();

        var builder = LambdaAsyncClient.builder().region(Region.of(config.getRegion()))
                .httpClient(asyncHttpClient)
                .overrideConfiguration(overrideConfig);

        if(!StringUtils.isEmpty(config.getEndpointOverride())) {
            builder.endpointOverride(URI.create(config.getEndpointOverride()));
        }
        client = builder.build();

        if(config.isMetricsInjection()) {
            // get the metrics handler from the handler chain for metrics registration. If we cannot get the
            // metrics handler, then an error message will be logged.
            Map handlers = Handler.getHandlers();
            metricsHandler = (AbstractMetricsHandler) handlers.get(MetricsConfig.CONFIG_NAME);
            if(metricsHandler == null) {
                logger.error("An instance of MetricsHandler is not configured in the handler.yml.");
            }
        }
        ModuleRegistry.registerModule(LambdaInvokerConfig.CONFIG_NAME, LambdaFunctionHandler.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(LambdaInvokerConfig.CONFIG_NAME), null);
        if(logger.isInfoEnabled()) logger.info("LambdaFunctionHandler is loaded.");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy