GH-630, GH-530 Additional improvements in AWS Custom Runtime

Ensured we have Custom Runtime examples for functional and '@Bean' style
Improve AWSLambdaUtils to ensure it works without APIGatewayProxyRequestEvent on classpath
This commit is contained in:
Oleg Zhurakousky
2021-01-22 12:31:31 +01:00
parent a1d10f0771
commit 75112076f7
9 changed files with 71 additions and 104 deletions

View File

@@ -29,7 +29,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
import org.springframework.cloud.function.json.JsonMapper;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
@@ -37,6 +36,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
*
@@ -52,21 +52,20 @@ final class AWSLambdaUtils {
}
public static Message<byte[]> generateMessage(byte[] payload, MessageHeaders headers,
FunctionInvocationWrapper function, JsonMapper mapper) {
return generateMessage(payload, headers, function, mapper, null);
Type inputType, JsonMapper mapper) {
return generateMessage(payload, headers, inputType, mapper, null);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Message<byte[]> generateMessage(byte[] payload, MessageHeaders headers,
FunctionInvocationWrapper function, JsonMapper mapper, @Nullable Context awsContext) {
Type inputType, JsonMapper mapper, @Nullable Context awsContext) {
if (logger.isInfoEnabled()) {
logger.info("Incoming JSON for ApiGateway Event: " + new String(payload));
logger.info("Incoming JSON Event: " + new String(payload));
}
MessageBuilder messageBuilder = null;
Object request = mapper.fromJson(payload, Object.class);
Type inputType = function.getInputType();
if (FunctionTypeUtils.isMessage(inputType)) {
inputType = FunctionTypeUtils.getImmediateGenericType(inputType, 0);
}
@@ -81,7 +80,7 @@ final class AWSLambdaUtils {
}
else if (requestMap.containsKey("httpMethod")) { // API Gateway
logger.info("Incoming request is API Gateway");
if (inputType.getTypeName().endsWith(APIGatewayProxyRequestEvent.class.getSimpleName())) {
if (isTypeAnApiGatewayRequest(inputType)) {
APIGatewayProxyRequestEvent gatewayEvent = mapper.fromJson(requestMap, APIGatewayProxyRequestEvent.class);
messageBuilder = MessageBuilder.withPayload(gatewayEvent);
}
@@ -108,7 +107,8 @@ final class AWSLambdaUtils {
public static byte[] generateOutput(Message requestMessage, Message<byte[]> responseMessage,
JsonMapper mapper) {
byte[] responseBytes = responseMessage.getPayload();
if (requestMessage.getHeaders().containsKey("httpMethod") || requestMessage.getPayload() instanceof APIGatewayProxyRequestEvent) { // API Gateway
if (requestMessage.getHeaders().containsKey("httpMethod")
|| isPayloadAnApiGatewayRequest(responseMessage.getPayload())) { // API Gateway
Map<String, Object> response = new HashMap<String, Object>();
response.put("isBase64Encoded", false);
@@ -136,6 +136,23 @@ final class AWSLambdaUtils {
return responseBytes;
}
private static boolean isPayloadAnApiGatewayRequest(Object payload) {
return isAPIGatewayProxyRequestEventPresent()
? payload instanceof APIGatewayProxyRequestEvent
: false;
}
private static boolean isTypeAnApiGatewayRequest(Type type) {
return isAPIGatewayProxyRequestEventPresent()
? type.getTypeName().endsWith(APIGatewayProxyRequestEvent.class.getSimpleName())
: false;
}
private static boolean isAPIGatewayProxyRequestEventPresent() {
return ClassUtils.isPresent("com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent",
ClassUtils.getDefaultClassLoader());
}
private static void logEvent(List<Map<String, ?>> records) {
if (isKinesisEvent(records.get(0))) {
logger.info("Incoming request is Kinesis Event");

View File

@@ -95,7 +95,7 @@ public class CustomRuntimeEventLoop {
FunctionInvocationWrapper function = locateFunction(functionCatalog, response.getHeaders().getContentType());
Message<byte[]> eventMessage = AWSLambdaUtils.generateMessage(response.getBody().getBytes(StandardCharsets.UTF_8),
fromHttp(response.getHeaders()), function, mapper);
fromHttp(response.getHeaders()), function.getInputType(), mapper);
if (logger.isDebugEnabled()) {
logger.debug("Event message: " + eventMessage);
}

View File

@@ -16,6 +16,9 @@
package org.springframework.cloud.function.adapter.aws;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer;
import org.springframework.cloud.function.web.source.DestinationResolver;
@@ -31,18 +34,33 @@ import org.springframework.util.StringUtils;
@Order(0)
public class CustomRuntimeInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
private static Log logger = LogFactory.getLog(CustomRuntimeInitializer.class);
@Override
public void initialize(GenericApplicationContext context) {
Boolean enabled = context.getEnvironment().getProperty("spring.cloud.function.web.export.enabled",
Boolean.class);
if (enabled == null || !enabled) {
if (StringUtils.hasText(System.getenv("AWS_LAMBDA_RUNTIME_API"))) {
if (context.getBeanFactory().getBeanNamesForType(CustomRuntimeEventLoop.class, false, false).length == 0) {
context.registerBean(StringUtils.uncapitalize(CustomRuntimeEventLoop.class.getSimpleName()),
CommandLineRunner.class, () -> args -> CustomRuntimeEventLoop.eventLoop(context));
}
if (logger.isDebugEnabled()) {
logger.debug("AWS Environment: " + System.getenv());
}
// the presence of AWS_LAMBDA_RUNTIME_API signifies Custom Runtime
if (!this.isWebExportEnabled(context) && StringUtils.hasText(System.getenv("AWS_LAMBDA_RUNTIME_API"))) {
if (context.getBeanFactory().getBeanNamesForType(CustomRuntimeEventLoop.class, false, false).length == 0) {
context.registerBean(StringUtils.uncapitalize(CustomRuntimeEventLoop.class.getSimpleName()),
CommandLineRunner.class, () -> args -> CustomRuntimeEventLoop.eventLoop(context));
}
}
// Boolean enabled = context.getEnvironment()
// .getProperty("spring.cloud.function.web.export.enabled", Boolean.class);
// if (enabled == null || !enabled) {
// if (StringUtils.hasText(System.getenv("AWS_LAMBDA_RUNTIME_API"))) {
// if (context.getBeanFactory().getBeanNamesForType(CustomRuntimeEventLoop.class, false, false).length == 0) {
// context.registerBean(StringUtils.uncapitalize(CustomRuntimeEventLoop.class.getSimpleName()),
// CommandLineRunner.class, () -> args -> CustomRuntimeEventLoop.eventLoop(context));
// }
// }
// }
else if (ContextFunctionCatalogInitializer.enabled
&& context.getEnvironment().getProperty("spring.functional.enabled", Boolean.class, false)) {
if (context.getBeanFactory().getBeanNamesForType(DestinationResolver.class, false, false).length == 0) {
@@ -53,4 +71,10 @@ public class CustomRuntimeInitializer implements ApplicationContextInitializer<G
}
}
private boolean isWebExportEnabled(GenericApplicationContext context) {
Boolean enabled = context.getEnvironment()
.getProperty("spring.cloud.function.web.export.enabled", Boolean.class);
return enabled != null && enabled;
}
}