com.networknt.aws.lambda.handler.Handler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lambda-native Show documentation
Show all versions of lambda-native Show documentation
A middleware Lambda function that handles all the cross-cutting concerns for the downstream Lambda function.
The newest version!
package com.networknt.aws.lambda.handler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.networknt.aws.lambda.handler.chain.Chain;
import com.networknt.config.Config;
import com.networknt.handler.config.EndpointSource;
import com.networknt.handler.config.HandlerConfig;
import com.networknt.handler.config.PathChain;
import com.networknt.utility.ModuleRegistry;
import com.networknt.utility.PathTemplateMatcher;
import com.networknt.utility.Tuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
public class Handler {
private static final Logger LOG = LoggerFactory.getLogger(Handler.class);
// Accessed directly.
public static HandlerConfig config = HandlerConfig.load();
// handlers defined in the handlers section. each handler keyed by a name.
static final Map handlers = new HashMap<>();
// chain name to list of handlers mapping
static final Map handlerListById = new HashMap<>();
static final Map> methodToMatcherMap = new HashMap<>();
static Chain defaultChain;
public static void init() {
initHandlers();
initChains();
initPaths();
initDefaultHandlers();
ModuleRegistry.registerModule(HandlerConfig.CONFIG_NAME, Handler.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(HandlerConfig.CONFIG_NAME), null);
}
/**
* Construct the named map of handlers. Note: All handlers in use for this
* microservice should be listed in this handlers list.
*/
static void initHandlers() {
if (config != null && config.getHandlers() != null) {
// initialize handlers
for (var handler : config.getHandlers()) {
// handler is a fully qualified class name with a default constructor.
initStringDefinedHandler(handler);
}
}
}
/**
* Construct chains of handlers, if any are configured NOTE: It is recommended
* to define reusable chains of handlers
*/
static void initChains() {
if (config != null && config.getChains() != null) {
// add the chains to the handler list by id list.
for (var chainName : config.getChains().keySet()) {
var chain = config.getChains().get(chainName);
Chain handlerChain = new Chain(false);
for (var chainItemName : chain) {
var chainItem = handlers.get(chainItemName);
if (chainItem == null) {
throw new RuntimeException("Unknown handler in chain: " + chainItemName);
}
handlerChain.addChainable(chainItem);
}
handlerListById.put(chainName, handlerChain);
}
}
}
/**
* Build "handlerListById" and "reqTypeMatcherMap" from the paths in the config.
*/
static void initPaths() {
if (config != null && config.getPaths() != null) {
for (var pathChain : config.getPaths()) {
pathChain.validate(HandlerConfig.CONFIG_NAME + " config"); // raises exception on misconfiguration
if (pathChain.getPath() == null)
addSourceChain(pathChain);
else addPathChain(pathChain);
}
}
}
/**
* Build "defaultHandlers" from the defaultHandlers in the config.
*/
static void initDefaultHandlers() {
if (config != null && config.getDefaultHandlers() != null) {
defaultChain = getHandlersFromExecList(config.getDefaultHandlers());
}
}
/**
* Add PathChains crated from the EndpointSource given in sourceChain
*/
private static void addSourceChain(PathChain sourceChain) {
try {
var sourceClass = Class.forName(sourceChain.getSource());
var source = (EndpointSource) (sourceClass.getDeclaredConstructor().newInstance());
for (var endpoint : source.listEndpoints()) {
var sourcedPath = new PathChain();
sourcedPath.setPath(endpoint.getPath());
sourcedPath.setMethod(endpoint.getMethod());
sourcedPath.setExec(sourceChain.getExec());
sourcedPath.validate(sourceChain.getSource());
addPathChain(sourcedPath);
}
} catch (Exception e) {
if (LOG.isErrorEnabled())
LOG.error("Failed to inject handler.yml paths from: " + sourceChain);
if (e instanceof RuntimeException)
throw (RuntimeException) e;
else throw new RuntimeException(e);
}
}
/**
* Add a PathChain (having a non-null path) to the handler data structures.
*/
private static void addPathChain(PathChain pathChain) {
var method = pathChain.getMethod();
// Use a random integer as the id for a given path.
int randInt = new Random().nextInt();
while (handlerListById.containsKey(Integer.toString(randInt)))
randInt = new Random().nextInt();
// Flatten out the execution list from a mix of middleware chains and handlers.
var handlers = getHandlersFromExecList(pathChain.getExec());
PathTemplateMatcher pathTemplateMatcher = methodToMatcherMap.containsKey(method)
? methodToMatcherMap.get(method)
: new PathTemplateMatcher<>();
if (pathTemplateMatcher.get(pathChain.getPath()) == null)
pathTemplateMatcher.add(pathChain.getPath(), Integer.toString(randInt));
methodToMatcherMap.put(method, pathTemplateMatcher);
handlerListById.put(Integer.toString(randInt), handlers);
}
/**
* Converts the list of chains and handlers to a flat list of handlers. If a
* chain is named the same as a handler, the chain is resolved first.
*
* @param execs The list of names of chains and handlers.
* @return A list containing references to the instantiated handlers
*/
private static Chain getHandlersFromExecList(List execs) {
var handlersFromExecList = new Chain(false);
if (execs != null) {
for (var exec : execs) {
var handlerChain = handlerListById.get(exec);
if (handlerChain == null) {
// not a chain, try to resolve it as a handler
LambdaHandler handler = handlers.get(exec);
if (handler != null) {
if(handler.isEnabled()) handlersFromExecList.addChainable(handler);
} else {
throw new RuntimeException("Unknown handler or chain: " + exec);
}
} else {
for (LambdaHandler handler : handlerChain.getChain()) {
if (handler.isEnabled())
handlersFromExecList.addChainable(handler);
}
}
}
}
handlersFromExecList.setupGroupedChain();
return handlersFromExecList;
}
/**
* Detect if the handler is a MiddlewareHandler instance. If yes, then register it.
*
* @param handler Object
*/
private static void registerLambdaHandler(Object handler) {
if (handler instanceof LambdaHandler) {
// register the lambda handler if it is enabled.
if (((LambdaHandler) handler).isEnabled())
((LambdaHandler) handler).register();
}
}
/**
* Helper method for generating the instance of a handler from its string
* definition in config. Ie. No mapped values for setters, or list of
* constructor fields. To note: It could either implement HttpHandler, or
* HandlerProvider.
*
* @param handler handler string
*/
private static void initStringDefinedHandler(String handler) {
// split the class name and its label, if defined
Tuple namedClass = splitClassAndName(handler);
// create an instance of the handler
Object handlerOrProviderObject = null;
try {
handlerOrProviderObject = namedClass.second.getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException |
InvocationTargetException e) {
LOG.error("Could not instantiate handler class " + namedClass.second, e);
throw new RuntimeException("Could not instantiate handler class: " + namedClass.second);
}
LambdaHandler resolvedHandler;
if (handlerOrProviderObject instanceof LambdaHandler)
resolvedHandler = (LambdaHandler) handlerOrProviderObject;
else throw new RuntimeException("Unsupported type of handler provided: " + handlerOrProviderObject);
registerLambdaHandler(resolvedHandler);
handlers.put(namedClass.first, resolvedHandler);
}
/**
* To support multiple instances of the same class, support a naming
*
* @param classLabel The label as seen in the config file.
* @return A tuple where the first value is the name, and the second is the
* class.
* @throws Exception On invalid format of label.
*/
static Tuple splitClassAndName(String classLabel) {
String[] stringNameSplit = classLabel.split("@");
// If i don't have a @, then no name is provided, use the class as the name.
if (stringNameSplit.length == 1) {
try {
return new Tuple<>(classLabel, Class.forName(classLabel));
} catch (ClassNotFoundException e) {
throw new RuntimeException("Configured class: " + classLabel + " has not been found");
}
} else if (stringNameSplit.length > 1) { // Found a @, use that as the name, and
try {
return new Tuple<>(stringNameSplit[1], Class.forName(stringNameSplit[0]));
} catch (ClassNotFoundException e) {
throw new RuntimeException("Configured class: " + stringNameSplit[0]
+ " has not been found. Declared label was: " + stringNameSplit[1]);
}
}
throw new RuntimeException("Invalid format provided for class label: " + classLabel);
}
// Exposed for testing only.
static void setConfig(String configName) throws Exception {
config = HandlerConfig.load(configName);
initHandlers();
initChains();
initPaths();
}
public static Map getHandlers() {
return handlers;
}
public static Chain getChain(APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent) {
var requestPath = apiGatewayProxyRequestEvent.getPath();
var requestMethod = apiGatewayProxyRequestEvent.getHttpMethod();
// Get the matcher corresponding to the current request type.
var pathTemplateMatcher = methodToMatcherMap.get(requestMethod.toLowerCase());
if (pathTemplateMatcher != null) {
// Match the current request path to the configured paths.
var result = pathTemplateMatcher.match(requestPath);
if (result != null) {
// inject the path and query parameters into the request.
for (var entry : result.getParameters().entrySet()) {
// the values shouldn't be added to query param. but this is left as it was to keep backward compatability
apiGatewayProxyRequestEvent.getQueryStringParameters().put(entry.getKey(), entry.getValue());
// put values in path param map
apiGatewayProxyRequestEvent.getPathParameters().put(entry.getKey(), entry.getValue());
}
var id = result.getValue();
return handlerListById.get(id);
}
}
return null;
}
public static Chain getDefaultChain() {
return defaultChain;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy