Fix AWS FunctionInvoker to ensure it properly interprets translation of incoming APIGateway request to user FunctionInvoker

This assumes that
- 'body' will be extracted for cases such as POJO or String input
- Map input will simply represent the incoming request
- APIGatewayProxyRequestEvent input represents native representation of API Gateway request

Similar fixes went for Kinesis side of things
Added additional tests for both Kinesis and ApiGateway
This commit is contained in:
Oleg Zhurakousky
2020-05-27 13:49:55 +02:00
parent fe66d6020f
commit 7c8ba881c9
2 changed files with 420 additions and 91 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,8 +23,10 @@ import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.function.Function;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -41,6 +43,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class FunctionInvokerTests {
ObjectMapper mapper = new ObjectMapper();
String sampleLBEvent = "{" +
" \"requestContext\": {" +
" \"elb\": {" +
@@ -102,59 +106,179 @@ public class FunctionInvokerTests {
" ]" +
"}";
@SuppressWarnings("rawtypes")
// @Test
public void testLBStringMessageEvent() throws Exception {
System.setProperty("MAIN_CLASS", GenericConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "echoStringMessage");
FunctionInvoker invoker = new FunctionInvoker();
String apiGatewayEvent = "{\n" +
" \"resource\": \"/uppercase2\",\n" +
" \"path\": \"/uppercase2\",\n" +
" \"httpMethod\": \"POST\",\n" +
" \"headers\": {\n" +
" \"accept\": \"*/*\",\n" +
" \"content-type\": \"application/json\",\n" +
" \"Host\": \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\",\n" +
" \"User-Agent\": \"curl/7.54.0\",\n" +
" \"X-Amzn-Trace-Id\": \"Root=1-5ece339e-e0595766066d703ec70f1522\",\n" +
" \"X-Forwarded-For\": \"90.37.8.133\",\n" +
" \"X-Forwarded-Port\": \"443\",\n" +
" \"X-Forwarded-Proto\": \"https\"\n" +
" },\n" +
" \"multiValueHeaders\": {\n" +
" \"accept\": [\n" +
" \"*/*\"\n" +
" ],\n" +
" \"content-type\": [\n" +
" \"application/json\"\n" +
" ],\n" +
" \"Host\": [\n" +
" \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\"\n" +
" ],\n" +
" \"User-Agent\": [\n" +
" \"curl/7.54.0\"\n" +
" ],\n" +
" \"X-Amzn-Trace-Id\": [\n" +
" \"Root=1-5ece339e-e0595766066d703ec70f1522\"\n" +
" ],\n" +
" \"X-Forwarded-For\": [\n" +
" \"90.37.8.133\"\n" +
" ],\n" +
" \"X-Forwarded-Port\": [\n" +
" \"443\"\n" +
" ],\n" +
" \"X-Forwarded-Proto\": [\n" +
" \"https\"\n" +
" ]\n" +
" },\n" +
" \"queryStringParameters\": null,\n" +
" \"multiValueQueryStringParameters\": null,\n" +
" \"pathParameters\": null,\n" +
" \"stageVariables\": null,\n" +
" \"requestContext\": {\n" +
" \"resourceId\": \"qf0io6\",\n" +
" \"resourcePath\": \"/uppercase2\",\n" +
" \"httpMethod\": \"POST\",\n" +
" \"extendedRequestId\": \"NL0A1EokCGYFZOA=\",\n" +
" \"requestTime\": \"27/May/2020:09:32:14 +0000\",\n" +
" \"path\": \"/test/uppercase2\",\n" +
" \"accountId\": \"123456789098\",\n" +
" \"protocol\": \"HTTP/1.1\",\n" +
" \"stage\": \"test\",\n" +
" \"domainPrefix\": \"fhul32ccy2\",\n" +
" \"requestTimeEpoch\": 1590571934872,\n" +
" \"requestId\": \"b96500aa-f92a-43c3-9360-868ba4053a00\",\n" +
" \"identity\": {\n" +
" \"cognitoIdentityPoolId\": null,\n" +
" \"accountId\": null,\n" +
" \"cognitoIdentityId\": null,\n" +
" \"caller\": null,\n" +
" \"sourceIp\": \"90.37.8.133\",\n" +
" \"principalOrgId\": null,\n" +
" \"accessKey\": null,\n" +
" \"cognitoAuthenticationType\": null,\n" +
" \"cognitoAuthenticationProvider\": null,\n" +
" \"userArn\": null,\n" +
" \"userAgent\": \"curl/7.54.0\",\n" +
" \"user\": null\n" +
" },\n" +
" \"domainName\": \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\",\n" +
" \"apiId\": \"fhul32ccy2\"\n" +
" },\n" +
" \"body\":\"hello\",\n" +
" \"isBase64Encoded\": false\n" +
"}";
InputStream targetStream = new ByteArrayInputStream(this.sampleLBEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
String apiGatewayEventWithStructuredBody = "{\n" +
" \"resource\": \"/uppercase2\",\n" +
" \"path\": \"/uppercase2\",\n" +
" \"httpMethod\": \"POST\",\n" +
" \"headers\": {\n" +
" \"accept\": \"*/*\",\n" +
" \"content-type\": \"application/json\",\n" +
" \"Host\": \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\",\n" +
" \"User-Agent\": \"curl/7.54.0\",\n" +
" \"X-Amzn-Trace-Id\": \"Root=1-5ece339e-e0595766066d703ec70f1522\",\n" +
" \"X-Forwarded-For\": \"90.37.8.133\",\n" +
" \"X-Forwarded-Port\": \"443\",\n" +
" \"X-Forwarded-Proto\": \"https\"\n" +
" },\n" +
" \"multiValueHeaders\": {\n" +
" \"accept\": [\n" +
" \"*/*\"\n" +
" ],\n" +
" \"content-type\": [\n" +
" \"application/json\"\n" +
" ],\n" +
" \"Host\": [\n" +
" \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\"\n" +
" ],\n" +
" \"User-Agent\": [\n" +
" \"curl/7.54.0\"\n" +
" ],\n" +
" \"X-Amzn-Trace-Id\": [\n" +
" \"Root=1-5ece339e-e0595766066d703ec70f1522\"\n" +
" ],\n" +
" \"X-Forwarded-For\": [\n" +
" \"90.37.8.133\"\n" +
" ],\n" +
" \"X-Forwarded-Port\": [\n" +
" \"443\"\n" +
" ],\n" +
" \"X-Forwarded-Proto\": [\n" +
" \"https\"\n" +
" ]\n" +
" },\n" +
" \"queryStringParameters\": null,\n" +
" \"multiValueQueryStringParameters\": null,\n" +
" \"pathParameters\": null,\n" +
" \"stageVariables\": null,\n" +
" \"requestContext\": {\n" +
" \"resourceId\": \"qf0io6\",\n" +
" \"resourcePath\": \"/uppercase2\",\n" +
" \"httpMethod\": \"POST\",\n" +
" \"extendedRequestId\": \"NL0A1EokCGYFZOA=\",\n" +
" \"requestTime\": \"27/May/2020:09:32:14 +0000\",\n" +
" \"path\": \"/test/uppercase2\",\n" +
" \"accountId\": \"123456789098\",\n" +
" \"protocol\": \"HTTP/1.1\",\n" +
" \"stage\": \"test\",\n" +
" \"domainPrefix\": \"fhul32ccy2\",\n" +
" \"requestTimeEpoch\": 1590571934872,\n" +
" \"requestId\": \"b96500aa-f92a-43c3-9360-868ba4053a00\",\n" +
" \"identity\": {\n" +
" \"cognitoIdentityPoolId\": null,\n" +
" \"accountId\": null,\n" +
" \"cognitoIdentityId\": null,\n" +
" \"caller\": null,\n" +
" \"sourceIp\": \"90.37.8.133\",\n" +
" \"principalOrgId\": null,\n" +
" \"accessKey\": null,\n" +
" \"cognitoAuthenticationType\": null,\n" +
" \"cognitoAuthenticationProvider\": null,\n" +
" \"userArn\": null,\n" +
" \"userAgent\": \"curl/7.54.0\",\n" +
" \"user\": null\n" +
" },\n" +
" \"domainName\": \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\",\n" +
" \"apiId\": \"fhul32ccy2\"\n" +
" },\n" +
" \"body\":{\"name\":\"Jim Lahey\"},\n" +
" \"isBase64Encoded\": false\n" +
"}";
String result = new String(output.toByteArray(), StandardCharsets.UTF_8);
ObjectMapper mapper = new ObjectMapper();
Map responseMap = mapper.readValue(result, Map.class);
assertThat(responseMap.get("statusCode")).isEqualTo(200);
assertThat(responseMap.get("statusDescription")).isEqualTo("200 OK");
}
// @Test
public void testKinesisStringMessageEvent() throws Exception {
System.setProperty("MAIN_CLASS", GenericConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "echoStringMessage");
FunctionInvoker invoker = new FunctionInvoker();
InputStream targetStream = new ByteArrayInputStream(this.sampleKinesisEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
String result = new String(output.toByteArray(), StandardCharsets.UTF_8);
assertThat(result).isEqualTo(this.sampleKinesisEvent);
}
// @Test
@Test
public void testKinesisStringEvent() throws Exception {
System.setProperty("MAIN_CLASS", GenericConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "echoString");
FunctionInvoker invoker = new FunctionInvoker();
Assertions.assertThrows(IllegalArgumentException.class, () -> {
System.setProperty("MAIN_CLASS", KinesisConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "echoString");
FunctionInvoker invoker = new FunctionInvoker();
InputStream targetStream = new ByteArrayInputStream(this.sampleKinesisEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
String result = new String(output.toByteArray(), StandardCharsets.UTF_8);
System.out.println(result);
assertThat(result).isEqualTo(this.sampleKinesisEvent);
InputStream targetStream = new ByteArrayInputStream(this.sampleKinesisEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
});
}
@Test
public void testKinesisEvent() throws Exception {
System.setProperty("MAIN_CLASS", KinesisConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "echoKinesisEvent");
System.setProperty("spring.cloud.function.definition", "inputKinesisEvent");
FunctionInvoker invoker = new FunctionInvoker();
InputStream targetStream = new ByteArrayInputStream(this.sampleKinesisEvent.getBytes());
@@ -162,42 +286,192 @@ public class FunctionInvokerTests {
invoker.handleRequest(targetStream, output, null);
String result = new String(output.toByteArray(), StandardCharsets.UTF_8);
assertThat(result).contains("49590338271490256608559692538361571095921575989136588898");
}
@Test
public void testKinesisEventAsMessage() throws Exception {
System.setProperty("MAIN_CLASS", KinesisConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "inputKinesisEventAsMessage");
FunctionInvoker invoker = new FunctionInvoker();
InputStream targetStream = new ByteArrayInputStream(this.sampleKinesisEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
String result = new String(output.toByteArray(), StandardCharsets.UTF_8);
assertThat(result).contains("49590338271490256608559692538361571095921575989136588898");
}
@Test
public void testKinesisEventAsMap() throws Exception {
System.setProperty("MAIN_CLASS", KinesisConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "inputKinesisEventAsMap");
FunctionInvoker invoker = new FunctionInvoker();
InputStream targetStream = new ByteArrayInputStream(this.sampleKinesisEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
String result = new String(output.toByteArray(), StandardCharsets.UTF_8);
assertThat(result).contains("49590338271490256608559692538361571095921575989136588898");
}
@SuppressWarnings("rawtypes")
@Test
public void testApiGatewayStringEventBody() throws Exception {
System.setProperty("MAIN_CLASS", ApiGatewayConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "uppercase");
FunctionInvoker invoker = new FunctionInvoker();
InputStream targetStream = new ByteArrayInputStream(this.apiGatewayEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
ObjectMapper mapper = new ObjectMapper();
Map result = mapper.readValue(output.toByteArray(), Map.class);
assertThat(result.get("body")).isEqualTo("HELLO");
}
@SuppressWarnings("rawtypes")
@Test
public void testApiGatewayMapEventBody() throws Exception {
System.setProperty("MAIN_CLASS", ApiGatewayConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "uppercasePojo");
FunctionInvoker invoker = new FunctionInvoker();
InputStream targetStream = new ByteArrayInputStream(this.apiGatewayEventWithStructuredBody.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
Map result = mapper.readValue(output.toByteArray(), Map.class);
assertThat(result.get("body")).isEqualTo("JIM LAHEY");
}
@SuppressWarnings("rawtypes")
@Test
public void testApiGatewayEvent() throws Exception {
System.setProperty("MAIN_CLASS", ApiGatewayConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "inputApiEvent");
FunctionInvoker invoker = new FunctionInvoker();
InputStream targetStream = new ByteArrayInputStream(this.apiGatewayEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
Map result = mapper.readValue(output.toByteArray(), Map.class);
System.out.println(result);
assertThat(result).contains("\"sequenceNumber\":\"49590338271490256608559692538361571095921575989136588898\"");
assertThat(result.get("body")).isEqualTo("hello");
}
@EnableAutoConfiguration
@Configuration
public static class GenericConfiguration {
@SuppressWarnings("rawtypes")
@Test
public void testApiGatewayEventAsMessage() throws Exception {
System.setProperty("MAIN_CLASS", ApiGatewayConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "inputApiEventAsMessage");
FunctionInvoker invoker = new FunctionInvoker();
@Bean
public Function<Message<String>, Message<String>> echoStringMessage() {
return v -> {
System.out.println("Received: " + v);
return v;
};
}
InputStream targetStream = new ByteArrayInputStream(this.apiGatewayEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
@Bean
public Function<String, String> echoString() {
return v -> {
System.out.println("Received: " + v);
return v;
};
}
Map result = mapper.readValue(output.toByteArray(), Map.class);
System.out.println(result);
assertThat(result.get("body")).isEqualTo("hello");
}
@Test
public void testApiGatewayEventAsMap() throws Exception {
System.setProperty("MAIN_CLASS", ApiGatewayConfiguration.class.getName());
System.setProperty("spring.cloud.function.definition", "inputApiEventAsMap");
FunctionInvoker invoker = new FunctionInvoker();
InputStream targetStream = new ByteArrayInputStream(this.apiGatewayEvent.getBytes());
ByteArrayOutputStream output = new ByteArrayOutputStream();
invoker.handleRequest(targetStream, output, null);
Map result = mapper.readValue(output.toByteArray(), Map.class);
System.out.println(result);
assertThat(result.get("body")).isEqualTo("hello");
}
@EnableAutoConfiguration
@Configuration
public static class KinesisConfiguration {
@Bean
public Function<String, String> echoString() {
return v -> v;
}
@Bean
public Function<KinesisEvent, KinesisEvent> echoKinesisEvent() {
public Function<KinesisEvent, String> inputKinesisEvent() {
return v -> {
System.out.println("Received: " + v);
return v;
return v.toString();
};
}
@Bean
public Function<Message<KinesisEvent>, String> inputKinesisEventAsMessage() {
return v -> {
System.out.println("Received: " + v);
return v.toString();
};
}
@Bean
public Function<Map<String, Object>, String> inputKinesisEventAsMap() {
return v -> {
System.out.println("Received: " + v);
return v.toString();
};
}
}
@EnableAutoConfiguration
@Configuration
public static class ApiGatewayConfiguration {
@Bean
public Function<String, String> uppercase() {
return v -> v.toUpperCase();
}
@Bean
public Function<Person, String> uppercasePojo() {
return v -> v.getName().toUpperCase();
}
@Bean
public Function<APIGatewayProxyRequestEvent, String> inputApiEvent() {
return v -> {
return v.getBody();
};
}
@Bean
public Function<Message<APIGatewayProxyRequestEvent>, String> inputApiEventAsMessage() {
return v -> {
return v.getPayload().getBody();
};
}
@Bean
public Function<Map<String, Object>, String> inputApiEventAsMap() {
return v -> {
String body = (String) v.get("body");
return body;
};
}
}
public static class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}