diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java index ef3e79775..d8e31deeb 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/FunctionInvoker.java @@ -82,7 +82,7 @@ public class FunctionInvoker implements RequestStreamHandler { this.start(); } - @SuppressWarnings("rawtypes") + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { final byte[] payload = StreamUtils.copyToByteArray(input); @@ -99,10 +99,18 @@ public class FunctionInvoker implements RequestStreamHandler { // TODO we should eventually completely delegate to message converter - //Message requestMessage = MessageBuilder.withPayload(payload).setHeader(AWSLambdaUtils.AWS_API_GATEWAY, true).build(); - Message requestMessage = isApiGateway - ? MessageBuilder.withPayload(payload).setHeader(AWSLambdaUtils.AWS_API_GATEWAY, true).build() - : AWSLambdaUtils.generateMessage(payload, new MessageHeaders(Collections.emptyMap()), function.getInputType(), this.jsonMapper, context); + Message requestMessage; + if (isApiGateway) { + MessageBuilder builder = MessageBuilder.withPayload(payload).setHeader(AWSLambdaUtils.AWS_API_GATEWAY, true); + if (structMessage instanceof Map && ((Map) structMessage).containsKey("headers")) { + builder.copyHeaders((Map) ((Map) structMessage).get("headers")); + } + requestMessage = builder.build(); + } + else { + requestMessage = AWSLambdaUtils + .generateMessage(payload, new MessageHeaders(Collections.emptyMap()), function.getInputType(), this.jsonMapper, context); + } try { Object response = this.function.apply(requestMessage); @@ -111,15 +119,20 @@ public class FunctionInvoker implements RequestStreamHandler { } catch (Exception e) { logger.error(e); - StreamUtils.copy(this.buildExceptionResult(requestMessage, e), output); + StreamUtils.copy(this.buildExceptionResult(requestMessage, e, isApiGateway), output); } } - private byte[] buildExceptionResult(Message requestMessage, Exception exception) throws IOException { - APIGatewayProxyResponseEvent event = new APIGatewayProxyResponseEvent(); - event.setStatusCode(HttpStatus.EXPECTATION_FAILED.value()); - event.setBody(exception.getMessage()); - return this.jsonMapper.toJson(event); + private byte[] buildExceptionResult(Message requestMessage, Exception exception, boolean isApiGateway) throws IOException { + if (isApiGateway) { + APIGatewayProxyResponseEvent event = new APIGatewayProxyResponseEvent(); + event.setStatusCode(HttpStatus.EXPECTATION_FAILED.value()); + event.setBody(exception.getMessage()); + return this.jsonMapper.toJson(event); + } + else { + throw new IllegalStateException(exception); + } } @SuppressWarnings("unchecked") diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java index 63445f1af..dd6b965b3 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/RoutingFunction.java @@ -137,7 +137,7 @@ public class RoutingFunction implements Function { else { throw new IllegalStateException("Failed to establish route, since neither were provided: " + "'spring.cloud.function.definition' as Message header or as application property or " - + "'spring.cloud.function.routing-expression' as application property."); + + "'spring.cloud.function.routing-expression' as application property. Incoming message: " + input); } } } diff --git a/spring-cloud-function-samples/function-sample-aws-routing/README.adoc b/spring-cloud-function-samples/function-sample-aws-routing/README.adoc index 181929a3e..6d0c545a7 100644 --- a/spring-cloud-function-samples/function-sample-aws-routing/README.adoc +++ b/spring-cloud-function-samples/function-sample-aws-routing/README.adoc @@ -10,18 +10,17 @@ You can do so in two different ways. 1. You can provide `spring_cloud_function_definition` environment variable setting its value to the desired function definition, which could also be composition (e.g., `spring_cloud_function_definition=foo|bar`). -2. A more dynamic and recommended approach would be to fallback on auto routing capabilities of spring-cloud function's in AWS environment. -Basically every time you have more then one function in your configuration, the framework will bind -[Routing Function](https://docs.spring.io/spring-cloud-function/docs/3.1.3/reference/html/spring-cloud-function.html#_function_routing_and_filtering) -as AWS Lambda, and all you need to to is provide a routing instruction via Message headers or environment variables. The instructions could themselves be very dynamic, -since we support both SpEL and registering a callback interface. For more details on routing mechanisms please refer to -[Function Routing and Filtering](https://docs.spring.io/spring-cloud-function/docs/3.1.3/reference/html/spring-cloud-function.html#_function_routing_and_filtering) section. - - NOTE: Keep in mind though that since AWS does not allow dots `.` and/or hyphens`-` in the name of the environment variable, you can benefit from boot support and simply substitute dots with underscores and hyphens with camel case. So for example `spring.cloud.function.definition` becomes `spring_cloud_function_definition` and `spring.cloud.function.routing-expression` becomes `spring_cloud_function_routingExpression`. +2. A more dynamic and recommended approach would be to fallback on auto routing capabilities of spring-cloud function's in AWS environment. +Basically every time you have more then one function in your configuration, the framework will bind +[Routing Function](https://docs.spring.io/spring-cloud-function/docs/3.1.3/reference/html/spring-cloud-function.html#_function_routing_and_filtering) +as AWS Lambda, and all you need to to is provide a routing instruction via Message headers or environment variables. The instructions could themselves be very dynamic, since we support both SpEL and registering a callback interface. For more details on routing mechanisms please refer to +[Function Routing and Filtering](https://docs.spring.io/spring-cloud-function/docs/3.1.3/reference/html/spring-cloud-function.html#_function_routing_and_filtering) section. + + In this example we have configuration with two functions; `uppercase` and `reverse`. When executing from AWS Lambda functions dashboard you can simply provide one of the mentioned properties as environment variables via Configuration tab. For example, you can set `spring_cloud_function_routingExpression` environment variable with the value of literal; SpEL expression `'uppercase'` (not the single quotes). diff --git a/spring-cloud-function-samples/function-sample-aws-routing/pom.xml b/spring-cloud-function-samples/function-sample-aws-routing/pom.xml index 005997393..3e0304a30 100644 --- a/spring-cloud-function-samples/function-sample-aws-routing/pom.xml +++ b/spring-cloud-function-samples/function-sample-aws-routing/pom.xml @@ -33,6 +33,10 @@ org.springframework.cloud spring-cloud-function-adapter-aws + + org.springframework.boot + spring-boot-starter-web + com.amazonaws