GH-786 Fix regression with RoutingFunction over AWS APIGateway

Resolves #786
This commit is contained in:
Oleg Zhurakousky
2022-01-03 14:42:31 +01:00
parent dfc700e2c9
commit fe060f6a6c
4 changed files with 36 additions and 20 deletions

View File

@@ -82,7 +82,7 @@ public class FunctionInvoker implements RequestStreamHandler {
this.start(); this.start();
} }
@SuppressWarnings("rawtypes") @SuppressWarnings({ "rawtypes", "unchecked" })
@Override @Override
public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
final byte[] payload = StreamUtils.copyToByteArray(input); final byte[] payload = StreamUtils.copyToByteArray(input);
@@ -99,10 +99,18 @@ public class FunctionInvoker implements RequestStreamHandler {
// TODO we should eventually completely delegate to message converter // TODO we should eventually completely delegate to message converter
//Message requestMessage = MessageBuilder.withPayload(payload).setHeader(AWSLambdaUtils.AWS_API_GATEWAY, true).build(); Message requestMessage;
Message requestMessage = isApiGateway if (isApiGateway) {
? MessageBuilder.withPayload(payload).setHeader(AWSLambdaUtils.AWS_API_GATEWAY, true).build() MessageBuilder builder = MessageBuilder.withPayload(payload).setHeader(AWSLambdaUtils.AWS_API_GATEWAY, true);
: AWSLambdaUtils.generateMessage(payload, new MessageHeaders(Collections.emptyMap()), function.getInputType(), this.jsonMapper, context); 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 { try {
Object response = this.function.apply(requestMessage); Object response = this.function.apply(requestMessage);
@@ -111,15 +119,20 @@ public class FunctionInvoker implements RequestStreamHandler {
} }
catch (Exception e) { catch (Exception e) {
logger.error(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 { private byte[] buildExceptionResult(Message<?> requestMessage, Exception exception, boolean isApiGateway) throws IOException {
APIGatewayProxyResponseEvent event = new APIGatewayProxyResponseEvent(); if (isApiGateway) {
event.setStatusCode(HttpStatus.EXPECTATION_FAILED.value()); APIGatewayProxyResponseEvent event = new APIGatewayProxyResponseEvent();
event.setBody(exception.getMessage()); event.setStatusCode(HttpStatus.EXPECTATION_FAILED.value());
return this.jsonMapper.toJson(event); event.setBody(exception.getMessage());
return this.jsonMapper.toJson(event);
}
else {
throw new IllegalStateException(exception);
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@@ -137,7 +137,7 @@ public class RoutingFunction implements Function<Object, Object> {
else { else {
throw new IllegalStateException("Failed to establish route, since neither were provided: " 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.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);
} }
} }
} }

View File

@@ -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 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`). (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 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` 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`. 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`. 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. 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). For example, you can set `spring_cloud_function_routingExpression` environment variable with the value of literal; SpEL expression `'uppercase'` (not the single quotes).

View File

@@ -33,6 +33,10 @@
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId> <artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.amazonaws</groupId> <groupId>com.amazonaws</groupId>