From 26ef1fa76e37b701a83db36a8d971ad2f709d0eb Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 13 Jan 2025 13:23:15 +0100 Subject: [PATCH] GH-1187 Fix AWS Context initialization for Custom Runtime Updated sample to show that Context is not null Resolves #1187 --- .../adapter/aws/CustomRuntimeEventLoop.java | 91 ++++++++++++++++++- .../context/FunctionTypeProcessor.java | 12 +++ .../function-sample-aws-custom/pom.xml | 6 +- .../function-sample-aws-native/pom.xml | 1 - .../demo/NativeFunctionApplication.java | 13 ++- 5 files changed, 114 insertions(+), 9 deletions(-) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java index 917ca8693..0b55bc283 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/CustomRuntimeEventLoop.java @@ -28,10 +28,14 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import com.amazonaws.services.lambda.runtime.ClientContext; +import com.amazonaws.services.lambda.runtime.CognitoIdentity; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.LambdaRuntime; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.cloud.function.context.config.RoutingFunction; @@ -130,6 +134,8 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { logger.debug("Attempting to get new event"); ResponseEntity response = this.pollForData(rest, requestEntity); + Context clientContext = generateClientContext(response.getHeaders()); + if (logger.isDebugEnabled()) { logger.debug("New Event received: " + response); } @@ -140,9 +146,9 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { FunctionInvocationWrapper function = locateFunction(environment, functionCatalog, response.getHeaders()); ByteArrayInputStream is = new ByteArrayInputStream(response.getBody().getBytes(StandardCharsets.UTF_8)); - Message requestMessage = AWSLambdaUtils.generateMessage(is, function.getInputType(), function.isSupplier(), mapper, null); - + Message requestMessage = AWSLambdaUtils.generateMessage(is, function.getInputType(), function.isSupplier(), mapper, clientContext); Object functionResponse = function.apply(requestMessage); + byte[] responseBytes = AWSLambdaUtils.generateOutputFromObject(requestMessage, functionResponse, mapper, function.getOutputType()); String invocationUrl = MessageFormat @@ -157,12 +163,91 @@ public final class CustomRuntimeEventLoop implements SmartLifecycle { } } catch (Exception e) { + e.printStackTrace(); this.propagateAwsError(requestId, e, mapper, runtimeApi, rest); } } } } + private Context generateClientContext(HttpHeaders headers) { + + Map environment = System.getenv(); + + Context context = new Context() { + + @Override + public int getRemainingTimeInMillis() { + long now = System.currentTimeMillis(); + if (!headers.containsKey("Lambda-Runtime-Deadline-Ms")) { + return 0; + } + int delta = (int) (Long.parseLong(headers.getFirst("Lambda-Runtime-Deadline-Ms")) - now); + return delta > 0 ? delta : 0; + } + + @Override + public int getMemoryLimitInMB() { + if (!environment.containsKey("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")) { + return 128; + } + return Integer.parseInt(environment.getOrDefault("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128")); + } + + @Override + public LambdaLogger getLogger() { + return LambdaRuntime.getLogger(); + } + + @Override + public String getLogStreamName() { + return environment.get("LOG_STREAM_NAME"); + } + + @Override + public String getLogGroupName() { + return environment.get("LOG_GROUP_NAME"); + } + + @Override + public String getInvokedFunctionArn() { + return headers.getFirst("Lambda-Runtime-Invoked-Function-Arn"); + } + + @Override + public CognitoIdentity getIdentity() { + return null; + } + + @Override + public String getFunctionVersion() { + return environment.get("FUNCTION_VERSION"); + } + + @Override + public String getFunctionName() { + return environment.get("FUNCTION_NAME"); + } + + @Override + public ClientContext getClientContext() { + return null; + } + + @Override + public String getAwsRequestId() { + return headers.getFirst("Lambda-Runtime-Aws-Request-Id"); + } + + public String toString() { + return "FUNCTION NAME: " + getFunctionName() + ", FUNCTION VERSION: " + getFunctionVersion() + + ", FUNCTION ARN: " + getInvokedFunctionArn() + ", FUNCTION MEM LIMIT: " + getMemoryLimitInMB() + + ", FUNCTION DEADLINE: " + getRemainingTimeInMillis(); + } + }; + return context; + } + private void propagateAwsError(String requestId, Exception e, JsonMapper mapper, String runtimeApi, RestTemplate rest) { String errorMessage = e.getMessage(); String errorType = e.getClass().getSimpleName(); diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionTypeProcessor.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionTypeProcessor.java index 67349fc9a..5aae6a037 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionTypeProcessor.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionTypeProcessor.java @@ -31,6 +31,9 @@ import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContrib import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.web.server.Ssl; +import org.springframework.boot.web.server.Ssl.ServerNameSslBundle; import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.context.config.FunctionContextUtils; import org.springframework.cloud.function.context.message.MessageUtils; @@ -105,6 +108,15 @@ public class FunctionTypeProcessor implements BeanFactoryInitializationAotProces // known static types runtimeHints.reflection().registerType(MessageUtils.MessageStructureWithCaseInsensitiveHeaderKeys.class, MemberCategory.INVOKE_PUBLIC_METHODS); + + + // temporary due to bug in boot + runtimeHints.reflection().registerType(ClientHttpRequestFactorySettings.class, + MemberCategory.INVOKE_PUBLIC_METHODS); + runtimeHints.reflection().registerType(Ssl.class, + MemberCategory.INVOKE_PUBLIC_METHODS); + runtimeHints.reflection().registerType(ServerNameSslBundle.class, + MemberCategory.INVOKE_PUBLIC_METHODS); } } diff --git a/spring-cloud-function-samples/function-sample-aws-custom/pom.xml b/spring-cloud-function-samples/function-sample-aws-custom/pom.xml index 5371458a7..70c392526 100644 --- a/spring-cloud-function-samples/function-sample-aws-custom/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws-custom/pom.xml @@ -42,7 +42,11 @@ org.springframework.boot spring-boot-starter - + + com.amazonaws + aws-lambda-java-core + 1.1.0 + org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud-function-samples/function-sample-aws-native/pom.xml b/spring-cloud-function-samples/function-sample-aws-native/pom.xml index 4ffee8fa4..6aa89fa42 100644 --- a/spring-cloud-function-samples/function-sample-aws-native/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws-native/pom.xml @@ -47,7 +47,6 @@ com.amazonaws aws-lambda-java-core 1.1.0 - provided diff --git a/spring-cloud-function-samples/function-sample-aws-native/src/main/java/com/example/demo/NativeFunctionApplication.java b/spring-cloud-function-samples/function-sample-aws-native/src/main/java/com/example/demo/NativeFunctionApplication.java index 5c544413a..399d786db 100644 --- a/spring-cloud-function-samples/function-sample-aws-native/src/main/java/com/example/demo/NativeFunctionApplication.java +++ b/spring-cloud-function-samples/function-sample-aws-native/src/main/java/com/example/demo/NativeFunctionApplication.java @@ -7,10 +7,14 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.function.adapter.aws.AWSLambdaUtils; import org.springframework.context.annotation.Bean; // import org.springframework.cloud.function.context.DefaultMessageRoutingHandler; // import org.springframework.cloud.function.context.MessageRoutingCallback; // import org.springframework.messaging.Message; +import org.springframework.messaging.Message; + +import com.amazonaws.services.lambda.runtime.Context; @SpringBootApplication public class NativeFunctionApplication { @@ -33,10 +37,11 @@ public class NativeFunctionApplication { // } @Bean - public Function uppercase() { - return v -> { - System.out.println("Uppercasing " + v); - return v.toUpperCase(Locale.ROOT); + public Function, String> uppercase() { + return message -> { + System.out.println("AWS Context: " + message.getHeaders().get(AWSLambdaUtils.AWS_CONTEXT)); + System.out.println("Uppercasing " + message.getPayload()); + return message.getPayload().toUpperCase(Locale.ROOT); }; }