From ebff56ebb3221ded8eaf4adfa07ddc9bcf6d31c1 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 28 Jun 2023 17:46:03 +0200 Subject: [PATCH 01/10] Add implementation of delegating RequestStreamHandler This new handler delegates to spring-cloud-function-serverless-web module which is managed by Spring team --- .../proxy/model/AwsProxyRequest.java | 12 +- .../pom.xml | 5 + ...pringDelegatingLambdaContainerHandler.java | 119 +++++++ ...DelegatingLambdaContainerHandlerTests.java | 300 ++++++++++++++++++ 4 files changed, 434 insertions(+), 2 deletions(-) create mode 100644 aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java create mode 100644 aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java index 075db0ac3..af4e6ea77 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java @@ -29,10 +29,11 @@ public class AwsProxyRequest { //------------------------------------------------------------- private String body; - private String resource; + private String version; + private String resource; private AwsProxyRequestContext requestContext; private MultiValuedTreeMap multiValueQueryStringParameters; - private Map queryStringParameters; + private Map queryStringParameters; private Headers multiValueHeaders; private SingleValueHeaders headers; private Map pathParameters; @@ -95,6 +96,13 @@ public String getResource() { return resource; } + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } public void setResource(String resource) { this.resource = resource; diff --git a/aws-serverless-java-container-springboot3/pom.xml b/aws-serverless-java-container-springboot3/pom.xml index 507f6b8e2..94c671cd8 100644 --- a/aws-serverless-java-container-springboot3/pom.xml +++ b/aws-serverless-java-container-springboot3/pom.xml @@ -22,6 +22,11 @@ + + org.springframework.cloud + spring-cloud-function-serverless-web + 4.1.0-SNAPSHOT + com.amazonaws.serverless aws-serverless-java-container-core diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java new file mode 100644 index 000000000..9647aad2f --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java @@ -0,0 +1,119 @@ +package com.amazonaws.serverless.proxy.spring; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest; +import org.springframework.cloud.function.serverless.web.ProxyMvc; + +import com.amazonaws.serverless.proxy.AwsHttpApiV2SecurityContextWriter; +import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.http.HttpServletRequest; + +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/** + * An implementation of {@link RequestStreamHandler} which delegates to + * Spring Cloud Function serverless web module managed by Spring team. + * + * It requires no sub-classing from the user other then being identified as "Handler". + * The configuration class(es) should be provided via MAIN_CLASS environment variable. + * + */ +public class SpringDelegatingLambdaContainerHandler implements RequestStreamHandler { + + private final Class[] startupClasses; + + private final ProxyMvc mvc; + + private final ObjectMapper mapper; + + private final AwsProxyHttpServletResponseWriter responseWriter; + + public SpringDelegatingLambdaContainerHandler(Class... startupClasses) { + this.startupClasses = startupClasses; + this.mvc = ProxyMvc.INSTANCE(this.startupClasses); + this.mapper = new ObjectMapper(); + this.responseWriter = new AwsProxyHttpServletResponseWriter(); + } + + @SuppressWarnings({"rawtypes" }) + @Override + public void handleRequest(InputStream input, OutputStream output, Context lambdaContext) throws IOException { + Map request = mapper.readValue(input, Map.class); + SecurityContextWriter securityWriter = "2.0".equals(request.get("version")) + ? new AwsHttpApiV2SecurityContextWriter() : new AwsProxySecurityContextWriter(); + HttpServletRequest httpServletRequest = "2.0".equals(request.get("version")) + ? this.generateRequest2(request, lambdaContext, securityWriter) : this.generateRequest(request, lambdaContext, securityWriter); + + CountDownLatch latch = new CountDownLatch(1); + AwsHttpServletResponse httpServletResponse = new AwsHttpServletResponse(httpServletRequest, latch); + try { + mvc.service(httpServletRequest, httpServletResponse); + latch.await(10, TimeUnit.SECONDS); + mapper.writeValue(output, responseWriter.writeResponse(httpServletResponse, lambdaContext)); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private HttpServletRequest generateRequest(Map request, Context lambdaContext, SecurityContextWriter securityWriter) { + AwsProxyRequest v1Request = this.mapper.convertValue(request, AwsProxyRequest.class); + + ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(this.mvc.getApplicationContext().getServletContext(), + v1Request.getHttpMethod(), v1Request.getPath()); + httpRequest.setContentType("application/json"); + httpRequest.setContent(v1Request.getBody().getBytes(StandardCharsets.UTF_8)); + httpRequest.setAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY, v1Request.getRequestContext()); + httpRequest.setAttribute(RequestReader.API_GATEWAY_STAGE_VARS_PROPERTY, v1Request.getStageVariables()); + httpRequest.setAttribute(RequestReader.API_GATEWAY_EVENT_PROPERTY, v1Request); + httpRequest.setAttribute(RequestReader.ALB_CONTEXT_PROPERTY, v1Request.getRequestContext().getElb()); + httpRequest.setAttribute(RequestReader.LAMBDA_CONTEXT_PROPERTY, lambdaContext); + httpRequest.setAttribute(RequestReader.JAX_SECURITY_CONTEXT_PROPERTY, securityWriter.writeSecurityContext(v1Request, lambdaContext)); + return httpRequest; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public HttpServletRequest generateRequest2(Map request, Context lambdaContext, SecurityContextWriter securityWriter) { + HttpApiV2ProxyRequest v2Request = this.mapper.convertValue(request, HttpApiV2ProxyRequest.class); + ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(this.mvc.getApplicationContext().getServletContext(), + v2Request.getRequestContext().getHttp().getMethod(), v2Request.getRequestContext().getHttp().getPath()); + httpRequest.setContentType("application/json"); + httpRequest.setContent(v2Request.getBody().getBytes(StandardCharsets.UTF_8)); + httpRequest.setAttribute(RequestReader.HTTP_API_CONTEXT_PROPERTY, v2Request.getRequestContext()); + httpRequest.setAttribute(RequestReader.HTTP_API_STAGE_VARS_PROPERTY, v2Request.getStageVariables()); + httpRequest.setAttribute(RequestReader.HTTP_API_EVENT_PROPERTY, v2Request); + httpRequest.setAttribute(RequestReader.LAMBDA_CONTEXT_PROPERTY, lambdaContext); + httpRequest.setAttribute(RequestReader.JAX_SECURITY_CONTEXT_PROPERTY, securityWriter.writeSecurityContext(v2Request, lambdaContext)); + return httpRequest; + } +} diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java new file mode 100644 index 000000000..50bedc320 --- /dev/null +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java @@ -0,0 +1,300 @@ +package com.amazonaws.serverless.proxy.spring; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.util.CollectionUtils; + +import com.amazonaws.serverless.proxy.spring.servletapp.MessageData; +import com.amazonaws.serverless.proxy.spring.servletapp.ServletApplication; +import com.amazonaws.serverless.proxy.spring.servletapp.UserData; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.ws.rs.core.HttpHeaders; + +public class SpringDelegatingLambdaContainerHandlerTests { + + private static String API_GATEWAY_EVENT = "{\n" + + " \"version\": \"1.0\",\n" + + " \"resource\": \"$default\",\n" + + " \"path\": \"/async\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"headers\": {\n" + + " \"Content-Length\": \"45\",\n" + + " \"Content-Type\": \"application/json\",\n" + + " \"Host\": \"i76bfh111.execute-api.eu-west-3.amazonaws.com\",\n" + + " \"User-Agent\": \"curl/7.79.1\",\n" + + " \"X-Amzn-Trace-Id\": \"Root=1-64087690-2151375b219d3ba3389ea84e\",\n" + + " \"X-Forwarded-For\": \"109.210.252.44\",\n" + + " \"X-Forwarded-Port\": \"443\",\n" + + " \"X-Forwarded-Proto\": \"https\",\n" + + " \"accept\": \"*/*\"\n" + + " },\n" + + " \"multiValueHeaders\": {\n" + + " \"Content-Length\": [\n" + + " \"45\"\n" + + " ],\n" + + " \"Content-Type\": [\n" + + " \"application/json\"\n" + + " ],\n" + + " \"Host\": [\n" + + " \"i76bfhczs0.execute-api.eu-west-3.amazonaws.com\"\n" + + " ],\n" + + " \"User-Agent\": [\n" + + " \"curl/7.79.1\"\n" + + " ],\n" + + " \"X-Amzn-Trace-Id\": [\n" + + " \"Root=1-64087690-2151375b219d3ba3389ea84e\"\n" + + " ],\n" + + " \"X-Forwarded-For\": [\n" + + " \"109.210.252.44\"\n" + + " ],\n" + + " \"X-Forwarded-Port\": [\n" + + " \"443\"\n" + + " ],\n" + + " \"X-Forwarded-Proto\": [\n" + + " \"https\"\n" + + " ],\n" + + " \"accept\": [\n" + + " \"*/*\"\n" + + " ]\n" + + " },\n" + + " \"queryStringParameters\": {\n" + + " \"abc\": \"xyz\",\n" + + " \"foo\": \"baz\"\n" + + " },\n" + + " \"multiValueQueryStringParameters\": {\n" + + " \"abc\": [\n" + + " \"xyz\"\n" + + " ],\n" + + " \"foo\": [\n" + + " \"bar\",\n" + + " \"baz\"\n" + + " ]\n" + + " },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789098\",\n" + + " \"apiId\": \"i76bfhczs0\",\n" + + " \"domainName\": \"i76bfhc111.execute-api.eu-west-3.amazonaws.com\",\n" + + " \"domainPrefix\": \"i76bfhczs0\",\n" + + " \"extendedRequestId\": \"Bdd2ngt5iGYEMIg=\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"identity\": {\n" + + " \"accessKey\": null,\n" + + " \"accountId\": null,\n" + + " \"caller\": null,\n" + + " \"cognitoAmr\": null,\n" + + " \"cognitoAuthenticationProvider\": null,\n" + + " \"cognitoAuthenticationType\": null,\n" + + " \"cognitoIdentityId\": null,\n" + + " \"cognitoIdentityPoolId\": null,\n" + + " \"principalOrgId\": null,\n" + + " \"sourceIp\": \"109.210.252.44\",\n" + + " \"user\": null,\n" + + " \"userAgent\": \"curl/7.79.1\",\n" + + " \"userArn\": null\n" + + " },\n" + + " \"path\": \"/pets\",\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"requestId\": \"Bdd2ngt5iGYEMIg=\",\n" + + " \"requestTime\": \"08/Mar/2023:11:50:40 +0000\",\n" + + " \"requestTimeEpoch\": 1678276240455,\n" + + " \"resourceId\": \"$default\",\n" + + " \"resourcePath\": \"$default\",\n" + + " \"stage\": \"$default\"\n" + + " },\n" + + " \"pathParameters\": null,\n" + + " \"stageVariables\": null,\n" + + " \"body\": \"{\\\"name\\\":\\\"bob\\\"}\",\n" + + " \"isBase64Encoded\": false\n" + + "}"; + + private static String API_GATEWAY_EVENT_V2 = "{\n" + + " \"version\": \"2.0\",\n" + + " \"routeKey\": \"$default\",\n" + + " \"rawPath\": \"/my/path\",\n" + + " \"rawQueryString\": \"parameter1=value1¶meter1=value2¶meter2=value\",\n" + + " \"cookies\": [\n" + + " \"cookie1\",\n" + + " \"cookie2\"\n" + + " ],\n" + + " \"headers\": {\n" + + " \"header1\": \"value1\",\n" + + " \"header2\": \"value1,value2\"\n" + + " },\n" + + " \"queryStringParameters\": {\n" + + " \"parameter1\": \"value1,value2\",\n" + + " \"parameter2\": \"value\"\n" + + " },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789012\",\n" + + " \"apiId\": \"api-id\",\n" + + " \"authentication\": {\n" + + " \"clientCert\": {\n" + + " \"clientCertPem\": \"CERT_CONTENT\",\n" + + " \"subjectDN\": \"www.example.com\",\n" + + " \"issuerDN\": \"Example issuer\",\n" + + " \"serialNumber\": \"a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1\",\n" + + " \"validity\": {\n" + + " \"notBefore\": \"May 28 12:30:02 2019 GMT\",\n" + + " \"notAfter\": \"Aug 5 09:36:04 2021 GMT\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"authorizer\": {\n" + + " \"jwt\": {\n" + + " \"claims\": {\n" + + " \"claim1\": \"value1\",\n" + + " \"claim2\": \"value2\"\n" + + " },\n" + + " \"scopes\": [\n" + + " \"scope1\",\n" + + " \"scope2\"\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"domainName\": \"id.execute-api.us-east-1.amazonaws.com\",\n" + + " \"domainPrefix\": \"id\",\n" + + " \"http\": {\n" + + " \"method\": \"POST\",\n" + + " \"path\": \"/my/path\",\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"sourceIp\": \"IP\",\n" + + " \"userAgent\": \"agent\"\n" + + " },\n" + + " \"requestId\": \"id\",\n" + + " \"routeKey\": \"$default\",\n" + + " \"stage\": \"$default\",\n" + + " \"time\": \"12/Mar/2020:19:03:58 +0000\",\n" + + " \"timeEpoch\": 1583348638390\n" + + " },\n" + + " \"body\": \"Hello from Lambda\",\n" + + " \"pathParameters\": {\n" + + " \"parameter1\": \"value1\"\n" + + " },\n" + + " \"isBase64Encoded\": false,\n" + + " \"stageVariables\": {\n" + + " \"stageVariable1\": \"value1\",\n" + + " \"stageVariable2\": \"value2\"\n" + + " }\n" + + "}"; + + private SpringDelegatingLambdaContainerHandler handler; + + private ObjectMapper mapper = new ObjectMapper(); + + public void initServletAppTest() { + this.handler = new SpringDelegatingLambdaContainerHandler(ServletApplication.class); + } + + public static Collection data() { + return Arrays.asList(new String[]{API_GATEWAY_EVENT, API_GATEWAY_EVENT_V2}); + } + + @MethodSource("data") + @ParameterizedTest + public void testAsyncPost(String jsonEvent) throws Exception { + initServletAppTest(); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/async", "{\"name\":\"bob\"}", null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + System.out.println(output.toString(StandardCharsets.UTF_8)); + } + + @MethodSource("data") + @ParameterizedTest + public void testValidate400(String jsonEvent) throws Exception { + initServletAppTest(); + UserData ud = new UserData(); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", mapper.writeValueAsString(ud), null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + System.out.println(output.toString(StandardCharsets.UTF_8)); + } + + @MethodSource("data") + @ParameterizedTest + public void testValidate200(String jsonEvent) throws Exception { + initServletAppTest(); + UserData ud = new UserData(); + ud.setFirstName("bob"); + ud.setLastName("smith"); + ud.setEmail("foo@bar.com"); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", mapper.writeValueAsString(ud), null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + System.out.println(output.toString(StandardCharsets.UTF_8)); + } + + @MethodSource("data") + @ParameterizedTest + public void messageObject_parsesObject_returnsCorrectMessage(String jsonEvent) throws Exception { + initServletAppTest(); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/message", + mapper.writeValueAsString(new MessageData("test message")), null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + System.out.println(output.toString(StandardCharsets.UTF_8)); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @MethodSource("data") + @ParameterizedTest + void messageObject_propertiesInContentType_returnsCorrectMessage(String jsonEvent) throws Exception { + initServletAppTest(); + + Map headers = new HashMap<>(); + headers.put(HttpHeaders.CONTENT_TYPE, "application/json;v=1"); + headers.put(HttpHeaders.ACCEPT, "application/json;v=1"); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/message", + mapper.writeValueAsString(new MessageData("test message")), headers)); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals("test message", result.get("body")); + } + + @SuppressWarnings({ "rawtypes" }) + private byte[] generateHttpRequest(String jsonEvent, String method, String path, String body, Map headers) throws Exception { + Map requestMap = mapper.readValue(jsonEvent, Map.class); + if (requestMap.get("version").equals("2.0")) { + return generateHttpRequest2(requestMap, method, path, body, headers); + } + return generateHttpRequest(requestMap, method, path, body, headers); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private byte[] generateHttpRequest(Map requestMap, String method, String path, String body, Map headers) throws Exception { + requestMap.put("path", path); + requestMap.put("httpMethod", method); + requestMap.put("body", body); + if (!CollectionUtils.isEmpty(headers)) { + requestMap.put("headers", headers); + } + return mapper.writeValueAsBytes(requestMap); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private byte[] generateHttpRequest2(Map requestMap, String method, String path, String body, Map headers) throws Exception { + Map map = mapper.readValue(API_GATEWAY_EVENT_V2, Map.class); + Map http = (Map) ((Map) map.get("requestContext")).get("http"); + http.put("path", path); + http.put("method", method); + map.put("body", body); + if (!CollectionUtils.isEmpty(headers)) { + map.put("headers", headers); + } + return mapper.writeValueAsBytes(map); + } +} From 42e0309fb2c65f195e98e07d8714cf0ccde275df Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 28 Jun 2023 18:29:53 +0200 Subject: [PATCH 02/10] Add spring snapshot repo --- .../pom.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/aws-serverless-java-container-springboot3/pom.xml b/aws-serverless-java-container-springboot3/pom.xml index 94c671cd8..37cc85156 100644 --- a/aws-serverless-java-container-springboot3/pom.xml +++ b/aws-serverless-java-container-springboot3/pom.xml @@ -286,4 +286,22 @@ + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + From fb6f20504f7b93d9a5b1e3f0141ac3ba3fd4f669 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 29 Jun 2023 08:37:36 +0200 Subject: [PATCH 03/10] Fix tests assertions --- ...DelegatingLambdaContainerHandlerTests.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java index 50bedc320..8b50eb9f5 100644 --- a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java @@ -22,6 +22,7 @@ import jakarta.ws.rs.core.HttpHeaders; +@SuppressWarnings("rawtypes") public class SpringDelegatingLambdaContainerHandlerTests { private static String API_GATEWAY_EVENT = "{\n" @@ -208,7 +209,9 @@ public void testAsyncPost(String jsonEvent) throws Exception { InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/async", "{\"name\":\"bob\"}", null)); ByteArrayOutputStream output = new ByteArrayOutputStream(); handler.handleRequest(targetStream, output, null); - System.out.println(output.toString(StandardCharsets.UTF_8)); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + assertEquals("{\"name\":\"BOB\"}", result.get("body")); } @MethodSource("data") @@ -219,7 +222,9 @@ public void testValidate400(String jsonEvent) throws Exception { InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", mapper.writeValueAsString(ud), null)); ByteArrayOutputStream output = new ByteArrayOutputStream(); handler.handleRequest(targetStream, output, null); - System.out.println(output.toString(StandardCharsets.UTF_8)); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(400, result.get("statusCode")); + assertEquals("3", result.get("body")); } @MethodSource("data") @@ -233,7 +238,9 @@ public void testValidate200(String jsonEvent) throws Exception { InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", mapper.writeValueAsString(ud), null)); ByteArrayOutputStream output = new ByteArrayOutputStream(); handler.handleRequest(targetStream, output, null); - System.out.println(output.toString(StandardCharsets.UTF_8)); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + assertEquals("VALID", result.get("body")); } @MethodSource("data") @@ -244,10 +251,12 @@ public void messageObject_parsesObject_returnsCorrectMessage(String jsonEvent) t mapper.writeValueAsString(new MessageData("test message")), null)); ByteArrayOutputStream output = new ByteArrayOutputStream(); handler.handleRequest(targetStream, output, null); - System.out.println(output.toString(StandardCharsets.UTF_8)); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + assertEquals("VALID", result.get("test message")); } - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({"unchecked" }) @MethodSource("data") @ParameterizedTest void messageObject_propertiesInContentType_returnsCorrectMessage(String jsonEvent) throws Exception { @@ -265,7 +274,6 @@ void messageObject_propertiesInContentType_returnsCorrectMessage(String jsonEven assertEquals("test message", result.get("body")); } - @SuppressWarnings({ "rawtypes" }) private byte[] generateHttpRequest(String jsonEvent, String method, String path, String body, Map headers) throws Exception { Map requestMap = mapper.readValue(jsonEvent, Map.class); if (requestMap.get("version").equals("2.0")) { @@ -274,7 +282,7 @@ private byte[] generateHttpRequest(String jsonEvent, String method, String path, return generateHttpRequest(requestMap, method, path, body, headers); } - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked"}) private byte[] generateHttpRequest(Map requestMap, String method, String path, String body, Map headers) throws Exception { requestMap.put("path", path); requestMap.put("httpMethod", method); @@ -285,7 +293,7 @@ private byte[] generateHttpRequest(Map requestMap, String method, String path, S return mapper.writeValueAsBytes(requestMap); } - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked"}) private byte[] generateHttpRequest2(Map requestMap, String method, String path, String body, Map headers) throws Exception { Map map = mapper.readValue(API_GATEWAY_EVENT_V2, Map.class); Map http = (Map) ((Map) map.get("requestContext")).get("http"); From 6fb6ff266a8fe5b70c2b2d583e5d6df710df1149 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 30 Jun 2023 11:41:01 +0200 Subject: [PATCH 04/10] Fix tests --- .../spring/SpringDelegatingLambdaContainerHandlerTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java index 8b50eb9f5..e242ea6e3 100644 --- a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java @@ -253,7 +253,7 @@ public void messageObject_parsesObject_returnsCorrectMessage(String jsonEvent) t handler.handleRequest(targetStream, output, null); Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); assertEquals(200, result.get("statusCode")); - assertEquals("VALID", result.get("test message")); + assertEquals("test message", result.get("body")); } @SuppressWarnings({"unchecked" }) From 42d92bb8440d955f5bf06b225256ae7b717f3bdb Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 30 Jun 2023 13:17:28 +0200 Subject: [PATCH 05/10] Add spring snapshot/milestone repo to gradle file of pet-store sample --- samples/springboot3/pet-store/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/samples/springboot3/pet-store/build.gradle b/samples/springboot3/pet-store/build.gradle index 6d0270f40..00a9d3a1f 100644 --- a/samples/springboot3/pet-store/build.gradle +++ b/samples/springboot3/pet-store/build.gradle @@ -3,6 +3,8 @@ apply plugin: 'java' repositories { mavenLocal() mavenCentral() + maven {url "https://repo.spring.io/milestone"} + maven {url "https://repo.spring.io/snapshot"} } dependencies { From 870d75ec56da7a1faf158a049d908b32403f647e Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 30 Jun 2023 13:28:55 +0200 Subject: [PATCH 06/10] Add spring snapshot/milestone repo to gradle file of springboot3 archetype --- .../src/main/resources/archetype-resources/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/build.gradle index 6de5e677e..13c8e76b2 100644 --- a/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/build.gradle +++ b/aws-serverless-springboot3-archetype/src/main/resources/archetype-resources/build.gradle @@ -3,6 +3,8 @@ apply plugin: 'java' repositories { mavenLocal() mavenCentral() + maven {url "https://repo.spring.io/milestone"} + maven {url "https://repo.spring.io/snapshot"} } dependencies { From 801890d09ce7de593b271e393bb86dffed088c57 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 30 Jun 2023 17:33:26 +0200 Subject: [PATCH 07/10] Add sample and documentation --- .gitignore | 3 + ...pringDelegatingLambdaContainerHandler.java | 35 +++- samples/springboot3/alt-pet-store/README.md | 39 +++++ .../springboot3/alt-pet-store/builds.gradle | 29 ++++ samples/springboot3/alt-pet-store/pom.xml | 154 ++++++++++++++++++ .../alt-pet-store/src/assembly/bin.xml | 27 +++ .../sample/springboot3/Application.java | 51 ++++++ .../controller/PetsController.java | 77 +++++++++ .../filter/CognitoIdentityFilter.java | 69 ++++++++ .../sample/springboot3/model/Error.java | 29 ++++ .../sample/springboot3/model/Pet.java | 55 +++++++ .../sample/springboot3/model/PetData.java | 117 +++++++++++++ .../src/main/resources/logback.xml | 5 + .../springboot3/alt-pet-store/template.yml | 41 +++++ 14 files changed, 726 insertions(+), 5 deletions(-) create mode 100644 samples/springboot3/alt-pet-store/README.md create mode 100644 samples/springboot3/alt-pet-store/builds.gradle create mode 100644 samples/springboot3/alt-pet-store/pom.xml create mode 100644 samples/springboot3/alt-pet-store/src/assembly/bin.xml create mode 100644 samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java create mode 100644 samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java create mode 100644 samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java create mode 100644 samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java create mode 100644 samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java create mode 100644 samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java create mode 100644 samples/springboot3/alt-pet-store/src/main/resources/logback.xml create mode 100644 samples/springboot3/alt-pet-store/template.yml diff --git a/.gitignore b/.gitignore index 8f6b9d359..2aa1654c2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ *.jar *.war *.ear +*.project +*.classpath +*.settings # Idea project files .idea/ diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java index 9647aad2f..46ccfb13b 100644 --- a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java @@ -3,13 +3,27 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URL; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.function.serverless.web.FunctionClassUtils; import org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest; import org.springframework.cloud.function.serverless.web.ProxyMvc; +import org.springframework.core.KotlinDetector; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; import com.amazonaws.serverless.proxy.AwsHttpApiV2SecurityContextWriter; import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; @@ -48,6 +62,8 @@ */ public class SpringDelegatingLambdaContainerHandler implements RequestStreamHandler { + private static Log logger = LogFactory.getLog(SpringDelegatingLambdaContainerHandler.class); + private final Class[] startupClasses; private final ProxyMvc mvc; @@ -56,6 +72,10 @@ public class SpringDelegatingLambdaContainerHandler implements RequestStreamHand private final AwsProxyHttpServletResponseWriter responseWriter; + public SpringDelegatingLambdaContainerHandler() { + this(new Class[] {FunctionClassUtils.getStartClass()}); + } + public SpringDelegatingLambdaContainerHandler(Class... startupClasses) { this.startupClasses = startupClasses; this.mvc = ProxyMvc.INSTANCE(this.startupClasses); @@ -84,15 +104,17 @@ public void handleRequest(InputStream input, OutputStream output, Context lambda } } - @SuppressWarnings({ "unchecked", "rawtypes" }) private HttpServletRequest generateRequest(Map request, Context lambdaContext, SecurityContextWriter securityWriter) { AwsProxyRequest v1Request = this.mapper.convertValue(request, AwsProxyRequest.class); ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(this.mvc.getApplicationContext().getServletContext(), v1Request.getHttpMethod(), v1Request.getPath()); - httpRequest.setContentType("application/json"); - httpRequest.setContent(v1Request.getBody().getBytes(StandardCharsets.UTF_8)); + + if (StringUtils.hasText(v1Request.getBody())) { + httpRequest.setContentType("application/json"); + httpRequest.setContent(v1Request.getBody().getBytes(StandardCharsets.UTF_8)); + } httpRequest.setAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY, v1Request.getRequestContext()); httpRequest.setAttribute(RequestReader.API_GATEWAY_STAGE_VARS_PROPERTY, v1Request.getStageVariables()); httpRequest.setAttribute(RequestReader.API_GATEWAY_EVENT_PROPERTY, v1Request); @@ -107,8 +129,11 @@ public HttpServletRequest generateRequest2(Map request, Context lambdaContext, S HttpApiV2ProxyRequest v2Request = this.mapper.convertValue(request, HttpApiV2ProxyRequest.class); ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(this.mvc.getApplicationContext().getServletContext(), v2Request.getRequestContext().getHttp().getMethod(), v2Request.getRequestContext().getHttp().getPath()); - httpRequest.setContentType("application/json"); - httpRequest.setContent(v2Request.getBody().getBytes(StandardCharsets.UTF_8)); + + if (StringUtils.hasText(v2Request.getBody())) { + httpRequest.setContentType("application/json"); + httpRequest.setContent(v2Request.getBody().getBytes(StandardCharsets.UTF_8)); + } httpRequest.setAttribute(RequestReader.HTTP_API_CONTEXT_PROPERTY, v2Request.getRequestContext()); httpRequest.setAttribute(RequestReader.HTTP_API_STAGE_VARS_PROPERTY, v2Request.getStageVariables()); httpRequest.setAttribute(RequestReader.HTTP_API_EVENT_PROPERTY, v2Request); diff --git a/samples/springboot3/alt-pet-store/README.md b/samples/springboot3/alt-pet-store/README.md new file mode 100644 index 000000000..d8cf8383d --- /dev/null +++ b/samples/springboot3/alt-pet-store/README.md @@ -0,0 +1,39 @@ +# Serverless Spring Boot 3 example +A basic pet store written with the [Spring Boot 3 framework](https://projects.spring.io/spring-boot/). Unlike older examples, this example is relying on the new +`SpringDelegatingLambdaContainerHandler`, which you simply need to identify as a _handler_ of the Lambda function. The main configuration class identified as `MAIN_CLASS` +environment variable or `Start-Class` or `Main-Class` entry in Manifest file. See provided `template.yml` file for reference. + + +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition. + +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package +``` +$ sam build +``` + +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. + +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen + +``` +$ sam deploy --guided +``` + +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` or a web browser to make a call to the URL + +``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +--------------------------------------------------------------------------------------------------------- + +$ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets +``` \ No newline at end of file diff --git a/samples/springboot3/alt-pet-store/builds.gradle b/samples/springboot3/alt-pet-store/builds.gradle new file mode 100644 index 000000000..00a9d3a1f --- /dev/null +++ b/samples/springboot3/alt-pet-store/builds.gradle @@ -0,0 +1,29 @@ +apply plugin: 'java' + +repositories { + mavenLocal() + mavenCentral() + maven {url "https://repo.spring.io/milestone"} + maven {url "https://repo.spring.io/snapshot"} +} + +dependencies { + implementation ( + implementation('org.springframework.boot:spring-boot-starter-web:3.1.1') { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' + }, + 'com.amazonaws.serverless:aws-serverless-java-container-springboot3:[2.0-SNAPSHOT,)', + ) +} + +task buildZip(type: Zip) { + from compileJava + from processResources + into('lib') { + from(configurations.compileClasspath) { + exclude 'tomcat-embed-*' + } + } +} + +build.dependsOn buildZip diff --git a/samples/springboot3/alt-pet-store/pom.xml b/samples/springboot3/alt-pet-store/pom.xml new file mode 100644 index 000000000..a8f6220b5 --- /dev/null +++ b/samples/springboot3/alt-pet-store/pom.xml @@ -0,0 +1,154 @@ + + + 4.0.0 + + com.amazonaws.serverless.sample + petstore-springboot3-example + 2.0-SNAPSHOT + Spring Boot example for the aws-serverless-java-container library + Simple pet store written with the Spring framework and Spring Boot + https://aws.amazon.com/lambda/ + + + org.springframework.boot + spring-boot-starter-parent + 3.1.1 + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 17 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + com.amazonaws.serverless + aws-serverless-java-container-springboot3 + 2.0.0-SNAPSHOT + + + + + + shaded-jar + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + false + + + + package + + shade + + + + + org.apache.tomcat.embed:* + + + + + + + + + + + assembly-zip + + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + default-jar + none + + + + + org.apache.maven.plugins + maven-install-plugin + 3.1.1 + + true + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.6.0 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + runtime + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.6.0 + + + zip-assembly + package + + single + + + ${project.artifactId}-${project.version} + + src${file.separator}assembly${file.separator}bin.xml + + false + + + + + + + + + + + diff --git a/samples/springboot3/alt-pet-store/src/assembly/bin.xml b/samples/springboot3/alt-pet-store/src/assembly/bin.xml new file mode 100644 index 000000000..1e085057d --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/assembly/bin.xml @@ -0,0 +1,27 @@ + + lambda-package + + zip + + false + + + + ${project.build.directory}${file.separator}lib + lib + + tomcat-embed* + + + + + ${project.build.directory}${file.separator}classes + + ** + + ${file.separator} + + + \ No newline at end of file diff --git a/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java new file mode 100644 index 000000000..ee9989df5 --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java @@ -0,0 +1,51 @@ +package com.amazonaws.serverless.sample.springboot3; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import com.amazonaws.serverless.sample.springboot3.controller.PetsController; +import com.amazonaws.serverless.sample.springboot3.filter.CognitoIdentityFilter; + +import jakarta.servlet.Filter; + + +@SpringBootApplication +@Import({ PetsController.class }) +public class Application { + + // silence console logging + @Value("${logging.level.root:OFF}") + String message = ""; + + /* + * Create required HandlerMapping, to avoid several default HandlerMapping instances being created + */ + @Bean + public HandlerMapping handlerMapping() { + return new RequestMappingHandlerMapping(); + } + + /* + * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created + */ + @Bean + public HandlerAdapter handlerAdapter() { + return new RequestMappingHandlerAdapter(); + } + + @Bean("CognitoIdentityFilter") + public Filter cognitoFilter() { + return new CognitoIdentityFilter(); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} \ No newline at end of file diff --git a/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java new file mode 100644 index 000000000..680e629d3 --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java @@ -0,0 +1,77 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.springboot3.controller; + + + +import com.amazonaws.serverless.sample.springboot3.model.Pet; +import com.amazonaws.serverless.sample.springboot3.model.PetData; + +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import java.security.Principal; +import java.util.Optional; +import java.util.UUID; + + +@RestController +@EnableWebMvc +public class PetsController { + @RequestMapping(path = "/pets", method = RequestMethod.POST) + public Pet createPet(@RequestBody Pet newPet) { + if (newPet.getName() == null || newPet.getBreed() == null) { + return null; + } + + Pet dbPet = newPet; + dbPet.setId(UUID.randomUUID().toString()); + return dbPet; + } + + @RequestMapping(path = "/pets", method = RequestMethod.GET) + public Pet[] listPets(@RequestParam("limit") Optional limit, Principal principal) { + int queryLimit = 10; + if (limit.isPresent()) { + queryLimit = limit.get(); + } + + Pet[] outputPets = new Pet[queryLimit]; + + for (int i = 0; i < queryLimit; i++) { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setName(PetData.getRandomName()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + outputPets[i] = newPet; + } + + return outputPets; + } + + @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET) + public Pet listPets() { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + newPet.setName(PetData.getRandomName()); + return newPet; + } + +} diff --git a/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java new file mode 100644 index 000000000..d6ccae765 --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java @@ -0,0 +1,69 @@ +package com.amazonaws.serverless.sample.springboot3.filter; + + +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +import java.io.IOException; + + +/** + * Simple Filter implementation that looks for a Cognito identity id in the API Gateway request context + * and stores the value in a request attribute. The filter is registered with aws-serverless-java-container + * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler} class. + */ +public class CognitoIdentityFilter implements Filter { + public static final String COGNITO_IDENTITY_ATTRIBUTE = "com.amazonaws.serverless.cognitoId"; + + private static Logger log = LoggerFactory.getLogger(CognitoIdentityFilter.class); + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + // nothing to do in init + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + Object apiGwContext = servletRequest.getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); + if (apiGwContext == null) { + log.warn("API Gateway context is null"); + filterChain.doFilter(servletRequest, servletResponse); + return; + } + if (!AwsProxyRequestContext.class.isAssignableFrom(apiGwContext.getClass())) { + log.warn("API Gateway context object is not of valid type"); + filterChain.doFilter(servletRequest, servletResponse); + } + + AwsProxyRequestContext ctx = (AwsProxyRequestContext)apiGwContext; + if (ctx.getIdentity() == null) { + log.warn("Identity context is null"); + filterChain.doFilter(servletRequest, servletResponse); + } + String cognitoIdentityId = ctx.getIdentity().getCognitoIdentityId(); + if (cognitoIdentityId == null || "".equals(cognitoIdentityId.trim())) { + log.warn("Cognito identity id in request is null"); + } + servletRequest.setAttribute(COGNITO_IDENTITY_ATTRIBUTE, cognitoIdentityId); + filterChain.doFilter(servletRequest, servletResponse); + } + + + @Override + public void destroy() { + // nothing to do in destroy + } +} diff --git a/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java new file mode 100644 index 000000000..320f21582 --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Error.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.springboot3.model; + +public class Error { + private String message; + + public Error(String errorMessage) { + message = errorMessage; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java new file mode 100644 index 000000000..4f0c4ba8e --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.springboot3.model; + +import java.util.Date; + + +public class Pet { + private String id; + private String breed; + private String name; + private Date dateOfBirth; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBreed() { + return breed; + } + + public void setBreed(String breed) { + this.breed = breed; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getDateOfBirth() { + return dateOfBirth; + } + + public void setDateOfBirth(Date dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } +} diff --git a/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java new file mode 100644 index 000000000..68ea3c18b --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/PetData.java @@ -0,0 +1,117 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.springboot3.model; + + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + + +public class PetData { + private static List breeds = new ArrayList<>(); + static { + breeds.add("Afghan Hound"); + breeds.add("Beagle"); + breeds.add("Bernese Mountain Dog"); + breeds.add("Bloodhound"); + breeds.add("Dalmatian"); + breeds.add("Jack Russell Terrier"); + breeds.add("Norwegian Elkhound"); + } + + private static List names = new ArrayList<>(); + static { + names.add("Bailey"); + names.add("Bella"); + names.add("Max"); + names.add("Lucy"); + names.add("Charlie"); + names.add("Molly"); + names.add("Buddy"); + names.add("Daisy"); + names.add("Rocky"); + names.add("Maggie"); + names.add("Jake"); + names.add("Sophie"); + names.add("Jack"); + names.add("Sadie"); + names.add("Toby"); + names.add("Chloe"); + names.add("Cody"); + names.add("Bailey"); + names.add("Buster"); + names.add("Lola"); + names.add("Duke"); + names.add("Zoe"); + names.add("Cooper"); + names.add("Abby"); + names.add("Riley"); + names.add("Ginger"); + names.add("Harley"); + names.add("Roxy"); + names.add("Bear"); + names.add("Gracie"); + names.add("Tucker"); + names.add("Coco"); + names.add("Murphy"); + names.add("Sasha"); + names.add("Lucky"); + names.add("Lily"); + names.add("Oliver"); + names.add("Angel"); + names.add("Sam"); + names.add("Princess"); + names.add("Oscar"); + names.add("Emma"); + names.add("Teddy"); + names.add("Annie"); + names.add("Winston"); + names.add("Rosie"); + } + + public static List getBreeds() { + return breeds; + } + + public static List getNames() { + return names; + } + + public static String getRandomBreed() { + return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1)); + } + + public static String getRandomName() { + return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1)); + } + + public static Date getRandomDoB() { + GregorianCalendar gc = new GregorianCalendar(); + + int year = ThreadLocalRandom.current().nextInt( + Calendar.getInstance().get(Calendar.YEAR) - 15, + Calendar.getInstance().get(Calendar.YEAR) + ); + + gc.set(Calendar.YEAR, year); + + int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR)); + + gc.set(Calendar.DAY_OF_YEAR, dayOfYear); + return gc.getTime(); + } +} diff --git a/samples/springboot3/alt-pet-store/src/main/resources/logback.xml b/samples/springboot3/alt-pet-store/src/main/resources/logback.xml new file mode 100644 index 000000000..14a3a84fa --- /dev/null +++ b/samples/springboot3/alt-pet-store/src/main/resources/logback.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/samples/springboot3/alt-pet-store/template.yml b/samples/springboot3/alt-pet-store/template.yml new file mode 100644 index 000000000..8a51c8d1d --- /dev/null +++ b/samples/springboot3/alt-pet-store/template.yml @@ -0,0 +1,41 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Example Pet Store API written with spring-cloud-function web-proxy support + +Globals: + Api: + # API Gateway regional endpoints + EndpointConfiguration: REGIONAL + +Resources: + PetStoreFunction: + Type: AWS::Serverless::Function + Properties: +# AutoPublishAlias: bcn + FunctionName: pet-store-boot-3 + Handler: com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler::handleRequest + Runtime: java17 + SnapStart: + ApplyOn: PublishedVersions + CodeUri: . + MemorySize: 1024 + Policies: AWSLambdaBasicExecutionRole + Timeout: 30 + Environment: + Variables: + MAIN_CLASS: com.amazonaws.serverless.sample.springboot3.Application + Events: + HttpApiEvent: + Type: HttpApi + Properties: + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' + +Outputs: + SpringPetStoreApi: + Description: URL for application + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/pets' + Export: + Name: SpringPetStoreApi + + From 224e5fb0f0a91b99b1a7c29ece158ebfada66df7 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 30 Jun 2023 19:13:18 +0200 Subject: [PATCH 08/10] Cleanup --- .../SpringDelegatingLambdaContainerHandler.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java index 46ccfb13b..20e154743 100644 --- a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java @@ -3,26 +3,16 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.jar.JarFile; -import java.util.jar.Manifest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.function.serverless.web.FunctionClassUtils; import org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest; import org.springframework.cloud.function.serverless.web.ProxyMvc; -import org.springframework.core.KotlinDetector; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import com.amazonaws.serverless.proxy.AwsHttpApiV2SecurityContextWriter; @@ -62,8 +52,6 @@ */ public class SpringDelegatingLambdaContainerHandler implements RequestStreamHandler { - private static Log logger = LogFactory.getLog(SpringDelegatingLambdaContainerHandler.class); - private final Class[] startupClasses; private final ProxyMvc mvc; From 3a96f35a70b184f17c73a686174e50f996cb6df8 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 4 Jul 2023 10:10:20 +0200 Subject: [PATCH 09/10] Substitute tabs for spaces --- .../proxy/model/AwsProxyRequest.java | 12 +- .../pom.xml | 34 +- ...pringDelegatingLambdaContainerHandler.java | 134 +++-- ...DelegatingLambdaContainerHandlerTests.java | 514 +++++++++--------- .../sample/springboot3/Application.java | 2 +- 5 files changed, 347 insertions(+), 349 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java index af4e6ea77..53ad758f1 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java @@ -30,7 +30,7 @@ public class AwsProxyRequest { private String body; private String version; - private String resource; + private String resource; private AwsProxyRequestContext requestContext; private MultiValuedTreeMap multiValueQueryStringParameters; private Map queryStringParameters; @@ -97,12 +97,12 @@ public String getResource() { } public String getVersion() { - return version; - } + return version; + } - public void setVersion(String version) { - this.version = version; - } + public void setVersion(String version) { + this.version = version; + } public void setResource(String resource) { this.resource = resource; diff --git a/aws-serverless-java-container-springboot3/pom.xml b/aws-serverless-java-container-springboot3/pom.xml index 37cc85156..532ce8312 100644 --- a/aws-serverless-java-container-springboot3/pom.xml +++ b/aws-serverless-java-container-springboot3/pom.xml @@ -287,21 +287,21 @@ - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - true - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java index 20e154743..ce3142369 100644 --- a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandler.java @@ -8,8 +8,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.cloud.function.serverless.web.FunctionClassUtils; import org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest; import org.springframework.cloud.function.serverless.web.ProxyMvc; @@ -52,75 +50,75 @@ */ public class SpringDelegatingLambdaContainerHandler implements RequestStreamHandler { - private final Class[] startupClasses; - - private final ProxyMvc mvc; - - private final ObjectMapper mapper; - - private final AwsProxyHttpServletResponseWriter responseWriter; - - public SpringDelegatingLambdaContainerHandler() { - this(new Class[] {FunctionClassUtils.getStartClass()}); - } - - public SpringDelegatingLambdaContainerHandler(Class... startupClasses) { - this.startupClasses = startupClasses; - this.mvc = ProxyMvc.INSTANCE(this.startupClasses); - this.mapper = new ObjectMapper(); - this.responseWriter = new AwsProxyHttpServletResponseWriter(); - } - - @SuppressWarnings({"rawtypes" }) - @Override - public void handleRequest(InputStream input, OutputStream output, Context lambdaContext) throws IOException { - Map request = mapper.readValue(input, Map.class); - SecurityContextWriter securityWriter = "2.0".equals(request.get("version")) - ? new AwsHttpApiV2SecurityContextWriter() : new AwsProxySecurityContextWriter(); - HttpServletRequest httpServletRequest = "2.0".equals(request.get("version")) - ? this.generateRequest2(request, lambdaContext, securityWriter) : this.generateRequest(request, lambdaContext, securityWriter); - - CountDownLatch latch = new CountDownLatch(1); - AwsHttpServletResponse httpServletResponse = new AwsHttpServletResponse(httpServletRequest, latch); - try { - mvc.service(httpServletRequest, httpServletResponse); - latch.await(10, TimeUnit.SECONDS); - mapper.writeValue(output, responseWriter.writeResponse(httpServletResponse, lambdaContext)); - } - catch (Exception e) { - throw new IllegalStateException(e); - } - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private HttpServletRequest generateRequest(Map request, Context lambdaContext, SecurityContextWriter securityWriter) { - AwsProxyRequest v1Request = this.mapper.convertValue(request, AwsProxyRequest.class); - - ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(this.mvc.getApplicationContext().getServletContext(), - v1Request.getHttpMethod(), v1Request.getPath()); - - if (StringUtils.hasText(v1Request.getBody())) { - httpRequest.setContentType("application/json"); - httpRequest.setContent(v1Request.getBody().getBytes(StandardCharsets.UTF_8)); - } - httpRequest.setAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY, v1Request.getRequestContext()); - httpRequest.setAttribute(RequestReader.API_GATEWAY_STAGE_VARS_PROPERTY, v1Request.getStageVariables()); - httpRequest.setAttribute(RequestReader.API_GATEWAY_EVENT_PROPERTY, v1Request); - httpRequest.setAttribute(RequestReader.ALB_CONTEXT_PROPERTY, v1Request.getRequestContext().getElb()); - httpRequest.setAttribute(RequestReader.LAMBDA_CONTEXT_PROPERTY, lambdaContext); - httpRequest.setAttribute(RequestReader.JAX_SECURITY_CONTEXT_PROPERTY, securityWriter.writeSecurityContext(v1Request, lambdaContext)); - return httpRequest; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public HttpServletRequest generateRequest2(Map request, Context lambdaContext, SecurityContextWriter securityWriter) { - HttpApiV2ProxyRequest v2Request = this.mapper.convertValue(request, HttpApiV2ProxyRequest.class); + private final Class[] startupClasses; + + private final ProxyMvc mvc; + + private final ObjectMapper mapper; + + private final AwsProxyHttpServletResponseWriter responseWriter; + + public SpringDelegatingLambdaContainerHandler() { + this(new Class[] {FunctionClassUtils.getStartClass()}); + } + + public SpringDelegatingLambdaContainerHandler(Class... startupClasses) { + this.startupClasses = startupClasses; + this.mvc = ProxyMvc.INSTANCE(this.startupClasses); + this.mapper = new ObjectMapper(); + this.responseWriter = new AwsProxyHttpServletResponseWriter(); + } + + @SuppressWarnings({"rawtypes" }) + @Override + public void handleRequest(InputStream input, OutputStream output, Context lambdaContext) throws IOException { + Map request = mapper.readValue(input, Map.class); + SecurityContextWriter securityWriter = "2.0".equals(request.get("version")) + ? new AwsHttpApiV2SecurityContextWriter() : new AwsProxySecurityContextWriter(); + HttpServletRequest httpServletRequest = "2.0".equals(request.get("version")) + ? this.generateRequest2(request, lambdaContext, securityWriter) : this.generateRequest(request, lambdaContext, securityWriter); + + CountDownLatch latch = new CountDownLatch(1); + AwsHttpServletResponse httpServletResponse = new AwsHttpServletResponse(httpServletRequest, latch); + try { + mvc.service(httpServletRequest, httpServletResponse); + latch.await(10, TimeUnit.SECONDS); + mapper.writeValue(output, responseWriter.writeResponse(httpServletResponse, lambdaContext)); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private HttpServletRequest generateRequest(Map request, Context lambdaContext, SecurityContextWriter securityWriter) { + AwsProxyRequest v1Request = this.mapper.convertValue(request, AwsProxyRequest.class); + + ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(this.mvc.getApplicationContext().getServletContext(), + v1Request.getHttpMethod(), v1Request.getPath()); + + if (StringUtils.hasText(v1Request.getBody())) { + httpRequest.setContentType("application/json"); + httpRequest.setContent(v1Request.getBody().getBytes(StandardCharsets.UTF_8)); + } + httpRequest.setAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY, v1Request.getRequestContext()); + httpRequest.setAttribute(RequestReader.API_GATEWAY_STAGE_VARS_PROPERTY, v1Request.getStageVariables()); + httpRequest.setAttribute(RequestReader.API_GATEWAY_EVENT_PROPERTY, v1Request); + httpRequest.setAttribute(RequestReader.ALB_CONTEXT_PROPERTY, v1Request.getRequestContext().getElb()); + httpRequest.setAttribute(RequestReader.LAMBDA_CONTEXT_PROPERTY, lambdaContext); + httpRequest.setAttribute(RequestReader.JAX_SECURITY_CONTEXT_PROPERTY, securityWriter.writeSecurityContext(v1Request, lambdaContext)); + return httpRequest; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public HttpServletRequest generateRequest2(Map request, Context lambdaContext, SecurityContextWriter securityWriter) { + HttpApiV2ProxyRequest v2Request = this.mapper.convertValue(request, HttpApiV2ProxyRequest.class); ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(this.mvc.getApplicationContext().getServletContext(), - v2Request.getRequestContext().getHttp().getMethod(), v2Request.getRequestContext().getHttp().getPath()); + v2Request.getRequestContext().getHttp().getMethod(), v2Request.getRequestContext().getHttp().getPath()); if (StringUtils.hasText(v2Request.getBody())) { - httpRequest.setContentType("application/json"); - httpRequest.setContent(v2Request.getBody().getBytes(StandardCharsets.UTF_8)); + httpRequest.setContentType("application/json"); + httpRequest.setContent(v2Request.getBody().getBytes(StandardCharsets.UTF_8)); } httpRequest.setAttribute(RequestReader.HTTP_API_CONTEXT_PROPERTY, v2Request.getRequestContext()); httpRequest.setAttribute(RequestReader.HTTP_API_STAGE_VARS_PROPERTY, v2Request.getStageVariables()); diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java index e242ea6e3..dabc30e24 100644 --- a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/SpringDelegatingLambdaContainerHandlerTests.java @@ -25,284 +25,284 @@ @SuppressWarnings("rawtypes") public class SpringDelegatingLambdaContainerHandlerTests { - private static String API_GATEWAY_EVENT = "{\n" - + " \"version\": \"1.0\",\n" - + " \"resource\": \"$default\",\n" - + " \"path\": \"/async\",\n" - + " \"httpMethod\": \"POST\",\n" - + " \"headers\": {\n" - + " \"Content-Length\": \"45\",\n" - + " \"Content-Type\": \"application/json\",\n" - + " \"Host\": \"i76bfh111.execute-api.eu-west-3.amazonaws.com\",\n" - + " \"User-Agent\": \"curl/7.79.1\",\n" - + " \"X-Amzn-Trace-Id\": \"Root=1-64087690-2151375b219d3ba3389ea84e\",\n" - + " \"X-Forwarded-For\": \"109.210.252.44\",\n" - + " \"X-Forwarded-Port\": \"443\",\n" - + " \"X-Forwarded-Proto\": \"https\",\n" - + " \"accept\": \"*/*\"\n" - + " },\n" - + " \"multiValueHeaders\": {\n" - + " \"Content-Length\": [\n" - + " \"45\"\n" - + " ],\n" - + " \"Content-Type\": [\n" - + " \"application/json\"\n" - + " ],\n" - + " \"Host\": [\n" - + " \"i76bfhczs0.execute-api.eu-west-3.amazonaws.com\"\n" - + " ],\n" - + " \"User-Agent\": [\n" - + " \"curl/7.79.1\"\n" - + " ],\n" - + " \"X-Amzn-Trace-Id\": [\n" - + " \"Root=1-64087690-2151375b219d3ba3389ea84e\"\n" - + " ],\n" - + " \"X-Forwarded-For\": [\n" - + " \"109.210.252.44\"\n" - + " ],\n" - + " \"X-Forwarded-Port\": [\n" - + " \"443\"\n" - + " ],\n" - + " \"X-Forwarded-Proto\": [\n" - + " \"https\"\n" - + " ],\n" - + " \"accept\": [\n" - + " \"*/*\"\n" - + " ]\n" - + " },\n" - + " \"queryStringParameters\": {\n" - + " \"abc\": \"xyz\",\n" - + " \"foo\": \"baz\"\n" - + " },\n" - + " \"multiValueQueryStringParameters\": {\n" - + " \"abc\": [\n" - + " \"xyz\"\n" - + " ],\n" - + " \"foo\": [\n" - + " \"bar\",\n" - + " \"baz\"\n" - + " ]\n" - + " },\n" - + " \"requestContext\": {\n" - + " \"accountId\": \"123456789098\",\n" - + " \"apiId\": \"i76bfhczs0\",\n" - + " \"domainName\": \"i76bfhc111.execute-api.eu-west-3.amazonaws.com\",\n" - + " \"domainPrefix\": \"i76bfhczs0\",\n" - + " \"extendedRequestId\": \"Bdd2ngt5iGYEMIg=\",\n" - + " \"httpMethod\": \"POST\",\n" - + " \"identity\": {\n" - + " \"accessKey\": null,\n" - + " \"accountId\": null,\n" - + " \"caller\": null,\n" - + " \"cognitoAmr\": null,\n" - + " \"cognitoAuthenticationProvider\": null,\n" - + " \"cognitoAuthenticationType\": null,\n" - + " \"cognitoIdentityId\": null,\n" - + " \"cognitoIdentityPoolId\": null,\n" - + " \"principalOrgId\": null,\n" - + " \"sourceIp\": \"109.210.252.44\",\n" - + " \"user\": null,\n" - + " \"userAgent\": \"curl/7.79.1\",\n" - + " \"userArn\": null\n" - + " },\n" - + " \"path\": \"/pets\",\n" - + " \"protocol\": \"HTTP/1.1\",\n" - + " \"requestId\": \"Bdd2ngt5iGYEMIg=\",\n" - + " \"requestTime\": \"08/Mar/2023:11:50:40 +0000\",\n" - + " \"requestTimeEpoch\": 1678276240455,\n" - + " \"resourceId\": \"$default\",\n" - + " \"resourcePath\": \"$default\",\n" - + " \"stage\": \"$default\"\n" - + " },\n" - + " \"pathParameters\": null,\n" - + " \"stageVariables\": null,\n" - + " \"body\": \"{\\\"name\\\":\\\"bob\\\"}\",\n" - + " \"isBase64Encoded\": false\n" - + "}"; + private static String API_GATEWAY_EVENT = "{\n" + + " \"version\": \"1.0\",\n" + + " \"resource\": \"$default\",\n" + + " \"path\": \"/async\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"headers\": {\n" + + " \"Content-Length\": \"45\",\n" + + " \"Content-Type\": \"application/json\",\n" + + " \"Host\": \"i76bfh111.execute-api.eu-west-3.amazonaws.com\",\n" + + " \"User-Agent\": \"curl/7.79.1\",\n" + + " \"X-Amzn-Trace-Id\": \"Root=1-64087690-2151375b219d3ba3389ea84e\",\n" + + " \"X-Forwarded-For\": \"109.210.252.44\",\n" + + " \"X-Forwarded-Port\": \"443\",\n" + + " \"X-Forwarded-Proto\": \"https\",\n" + + " \"accept\": \"*/*\"\n" + + " },\n" + + " \"multiValueHeaders\": {\n" + + " \"Content-Length\": [\n" + + " \"45\"\n" + + " ],\n" + + " \"Content-Type\": [\n" + + " \"application/json\"\n" + + " ],\n" + + " \"Host\": [\n" + + " \"i76bfhczs0.execute-api.eu-west-3.amazonaws.com\"\n" + + " ],\n" + + " \"User-Agent\": [\n" + + " \"curl/7.79.1\"\n" + + " ],\n" + + " \"X-Amzn-Trace-Id\": [\n" + + " \"Root=1-64087690-2151375b219d3ba3389ea84e\"\n" + + " ],\n" + + " \"X-Forwarded-For\": [\n" + + " \"109.210.252.44\"\n" + + " ],\n" + + " \"X-Forwarded-Port\": [\n" + + " \"443\"\n" + + " ],\n" + + " \"X-Forwarded-Proto\": [\n" + + " \"https\"\n" + + " ],\n" + + " \"accept\": [\n" + + " \"*/*\"\n" + + " ]\n" + + " },\n" + + " \"queryStringParameters\": {\n" + + " \"abc\": \"xyz\",\n" + + " \"foo\": \"baz\"\n" + + " },\n" + + " \"multiValueQueryStringParameters\": {\n" + + " \"abc\": [\n" + + " \"xyz\"\n" + + " ],\n" + + " \"foo\": [\n" + + " \"bar\",\n" + + " \"baz\"\n" + + " ]\n" + + " },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789098\",\n" + + " \"apiId\": \"i76bfhczs0\",\n" + + " \"domainName\": \"i76bfhc111.execute-api.eu-west-3.amazonaws.com\",\n" + + " \"domainPrefix\": \"i76bfhczs0\",\n" + + " \"extendedRequestId\": \"Bdd2ngt5iGYEMIg=\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"identity\": {\n" + + " \"accessKey\": null,\n" + + " \"accountId\": null,\n" + + " \"caller\": null,\n" + + " \"cognitoAmr\": null,\n" + + " \"cognitoAuthenticationProvider\": null,\n" + + " \"cognitoAuthenticationType\": null,\n" + + " \"cognitoIdentityId\": null,\n" + + " \"cognitoIdentityPoolId\": null,\n" + + " \"principalOrgId\": null,\n" + + " \"sourceIp\": \"109.210.252.44\",\n" + + " \"user\": null,\n" + + " \"userAgent\": \"curl/7.79.1\",\n" + + " \"userArn\": null\n" + + " },\n" + + " \"path\": \"/pets\",\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"requestId\": \"Bdd2ngt5iGYEMIg=\",\n" + + " \"requestTime\": \"08/Mar/2023:11:50:40 +0000\",\n" + + " \"requestTimeEpoch\": 1678276240455,\n" + + " \"resourceId\": \"$default\",\n" + + " \"resourcePath\": \"$default\",\n" + + " \"stage\": \"$default\"\n" + + " },\n" + + " \"pathParameters\": null,\n" + + " \"stageVariables\": null,\n" + + " \"body\": \"{\\\"name\\\":\\\"bob\\\"}\",\n" + + " \"isBase64Encoded\": false\n" + + "}"; - private static String API_GATEWAY_EVENT_V2 = "{\n" + - " \"version\": \"2.0\",\n" + - " \"routeKey\": \"$default\",\n" + - " \"rawPath\": \"/my/path\",\n" + - " \"rawQueryString\": \"parameter1=value1¶meter1=value2¶meter2=value\",\n" + - " \"cookies\": [\n" + - " \"cookie1\",\n" + - " \"cookie2\"\n" + - " ],\n" + - " \"headers\": {\n" + - " \"header1\": \"value1\",\n" + - " \"header2\": \"value1,value2\"\n" + - " },\n" + - " \"queryStringParameters\": {\n" + - " \"parameter1\": \"value1,value2\",\n" + - " \"parameter2\": \"value\"\n" + - " },\n" + - " \"requestContext\": {\n" + - " \"accountId\": \"123456789012\",\n" + - " \"apiId\": \"api-id\",\n" + - " \"authentication\": {\n" + - " \"clientCert\": {\n" + - " \"clientCertPem\": \"CERT_CONTENT\",\n" + - " \"subjectDN\": \"www.example.com\",\n" + - " \"issuerDN\": \"Example issuer\",\n" + - " \"serialNumber\": \"a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1\",\n" + - " \"validity\": {\n" + - " \"notBefore\": \"May 28 12:30:02 2019 GMT\",\n" + - " \"notAfter\": \"Aug 5 09:36:04 2021 GMT\"\n" + - " }\n" + - " }\n" + - " },\n" + - " \"authorizer\": {\n" + - " \"jwt\": {\n" + - " \"claims\": {\n" + - " \"claim1\": \"value1\",\n" + - " \"claim2\": \"value2\"\n" + - " },\n" + - " \"scopes\": [\n" + - " \"scope1\",\n" + - " \"scope2\"\n" + - " ]\n" + - " }\n" + - " },\n" + - " \"domainName\": \"id.execute-api.us-east-1.amazonaws.com\",\n" + - " \"domainPrefix\": \"id\",\n" + - " \"http\": {\n" + - " \"method\": \"POST\",\n" + - " \"path\": \"/my/path\",\n" + - " \"protocol\": \"HTTP/1.1\",\n" + - " \"sourceIp\": \"IP\",\n" + - " \"userAgent\": \"agent\"\n" + - " },\n" + - " \"requestId\": \"id\",\n" + - " \"routeKey\": \"$default\",\n" + - " \"stage\": \"$default\",\n" + - " \"time\": \"12/Mar/2020:19:03:58 +0000\",\n" + - " \"timeEpoch\": 1583348638390\n" + - " },\n" + - " \"body\": \"Hello from Lambda\",\n" + - " \"pathParameters\": {\n" + - " \"parameter1\": \"value1\"\n" + - " },\n" + - " \"isBase64Encoded\": false,\n" + - " \"stageVariables\": {\n" + - " \"stageVariable1\": \"value1\",\n" + - " \"stageVariable2\": \"value2\"\n" + - " }\n" + - "}"; + private static String API_GATEWAY_EVENT_V2 = "{\n" + + " \"version\": \"2.0\",\n" + + " \"routeKey\": \"$default\",\n" + + " \"rawPath\": \"/my/path\",\n" + + " \"rawQueryString\": \"parameter1=value1¶meter1=value2¶meter2=value\",\n" + + " \"cookies\": [\n" + + " \"cookie1\",\n" + + " \"cookie2\"\n" + + " ],\n" + + " \"headers\": {\n" + + " \"header1\": \"value1\",\n" + + " \"header2\": \"value1,value2\"\n" + + " },\n" + + " \"queryStringParameters\": {\n" + + " \"parameter1\": \"value1,value2\",\n" + + " \"parameter2\": \"value\"\n" + + " },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789012\",\n" + + " \"apiId\": \"api-id\",\n" + + " \"authentication\": {\n" + + " \"clientCert\": {\n" + + " \"clientCertPem\": \"CERT_CONTENT\",\n" + + " \"subjectDN\": \"www.example.com\",\n" + + " \"issuerDN\": \"Example issuer\",\n" + + " \"serialNumber\": \"a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1\",\n" + + " \"validity\": {\n" + + " \"notBefore\": \"May 28 12:30:02 2019 GMT\",\n" + + " \"notAfter\": \"Aug 5 09:36:04 2021 GMT\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"authorizer\": {\n" + + " \"jwt\": {\n" + + " \"claims\": {\n" + + " \"claim1\": \"value1\",\n" + + " \"claim2\": \"value2\"\n" + + " },\n" + + " \"scopes\": [\n" + + " \"scope1\",\n" + + " \"scope2\"\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"domainName\": \"id.execute-api.us-east-1.amazonaws.com\",\n" + + " \"domainPrefix\": \"id\",\n" + + " \"http\": {\n" + + " \"method\": \"POST\",\n" + + " \"path\": \"/my/path\",\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"sourceIp\": \"IP\",\n" + + " \"userAgent\": \"agent\"\n" + + " },\n" + + " \"requestId\": \"id\",\n" + + " \"routeKey\": \"$default\",\n" + + " \"stage\": \"$default\",\n" + + " \"time\": \"12/Mar/2020:19:03:58 +0000\",\n" + + " \"timeEpoch\": 1583348638390\n" + + " },\n" + + " \"body\": \"Hello from Lambda\",\n" + + " \"pathParameters\": {\n" + + " \"parameter1\": \"value1\"\n" + + " },\n" + + " \"isBase64Encoded\": false,\n" + + " \"stageVariables\": {\n" + + " \"stageVariable1\": \"value1\",\n" + + " \"stageVariable2\": \"value2\"\n" + + " }\n" + + "}"; - private SpringDelegatingLambdaContainerHandler handler; + private SpringDelegatingLambdaContainerHandler handler; - private ObjectMapper mapper = new ObjectMapper(); + private ObjectMapper mapper = new ObjectMapper(); - public void initServletAppTest() { - this.handler = new SpringDelegatingLambdaContainerHandler(ServletApplication.class); - } + public void initServletAppTest() { + this.handler = new SpringDelegatingLambdaContainerHandler(ServletApplication.class); + } - public static Collection data() { + public static Collection data() { return Arrays.asList(new String[]{API_GATEWAY_EVENT, API_GATEWAY_EVENT_V2}); } - @MethodSource("data") - @ParameterizedTest - public void testAsyncPost(String jsonEvent) throws Exception { - initServletAppTest(); - InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/async", "{\"name\":\"bob\"}", null)); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - handler.handleRequest(targetStream, output, null); - Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); - assertEquals(200, result.get("statusCode")); - assertEquals("{\"name\":\"BOB\"}", result.get("body")); - } + @MethodSource("data") + @ParameterizedTest + public void testAsyncPost(String jsonEvent) throws Exception { + initServletAppTest(); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/async", "{\"name\":\"bob\"}", null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + assertEquals("{\"name\":\"BOB\"}", result.get("body")); + } - @MethodSource("data") - @ParameterizedTest - public void testValidate400(String jsonEvent) throws Exception { - initServletAppTest(); - UserData ud = new UserData(); - InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", mapper.writeValueAsString(ud), null)); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - handler.handleRequest(targetStream, output, null); - Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); - assertEquals(400, result.get("statusCode")); - assertEquals("3", result.get("body")); - } + @MethodSource("data") + @ParameterizedTest + public void testValidate400(String jsonEvent) throws Exception { + initServletAppTest(); + UserData ud = new UserData(); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", mapper.writeValueAsString(ud), null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(400, result.get("statusCode")); + assertEquals("3", result.get("body")); + } - @MethodSource("data") - @ParameterizedTest - public void testValidate200(String jsonEvent) throws Exception { - initServletAppTest(); - UserData ud = new UserData(); - ud.setFirstName("bob"); - ud.setLastName("smith"); - ud.setEmail("foo@bar.com"); - InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", mapper.writeValueAsString(ud), null)); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - handler.handleRequest(targetStream, output, null); - Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); - assertEquals(200, result.get("statusCode")); - assertEquals("VALID", result.get("body")); - } + @MethodSource("data") + @ParameterizedTest + public void testValidate200(String jsonEvent) throws Exception { + initServletAppTest(); + UserData ud = new UserData(); + ud.setFirstName("bob"); + ud.setLastName("smith"); + ud.setEmail("foo@bar.com"); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/validate", mapper.writeValueAsString(ud), null)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + assertEquals("VALID", result.get("body")); + } - @MethodSource("data") - @ParameterizedTest - public void messageObject_parsesObject_returnsCorrectMessage(String jsonEvent) throws Exception { - initServletAppTest(); + @MethodSource("data") + @ParameterizedTest + public void messageObject_parsesObject_returnsCorrectMessage(String jsonEvent) throws Exception { + initServletAppTest(); InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/message", - mapper.writeValueAsString(new MessageData("test message")), null)); + mapper.writeValueAsString(new MessageData("test message")), null)); ByteArrayOutputStream output = new ByteArrayOutputStream(); - handler.handleRequest(targetStream, output, null); - Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); - assertEquals(200, result.get("statusCode")); - assertEquals("test message", result.get("body")); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals(200, result.get("statusCode")); + assertEquals("test message", result.get("body")); } - @SuppressWarnings({"unchecked" }) - @MethodSource("data") - @ParameterizedTest - void messageObject_propertiesInContentType_returnsCorrectMessage(String jsonEvent) throws Exception { + @SuppressWarnings({"unchecked" }) + @MethodSource("data") + @ParameterizedTest + void messageObject_propertiesInContentType_returnsCorrectMessage(String jsonEvent) throws Exception { initServletAppTest(); Map headers = new HashMap<>(); - headers.put(HttpHeaders.CONTENT_TYPE, "application/json;v=1"); - headers.put(HttpHeaders.ACCEPT, "application/json;v=1"); - InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/message", - mapper.writeValueAsString(new MessageData("test message")), headers)); + headers.put(HttpHeaders.CONTENT_TYPE, "application/json;v=1"); + headers.put(HttpHeaders.ACCEPT, "application/json;v=1"); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", "/message", + mapper.writeValueAsString(new MessageData("test message")), headers)); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - handler.handleRequest(targetStream, output, null); - Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); - assertEquals("test message", result.get("body")); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.handleRequest(targetStream, output, null); + Map result = mapper.readValue(output.toString(StandardCharsets.UTF_8), Map.class); + assertEquals("test message", result.get("body")); } - private byte[] generateHttpRequest(String jsonEvent, String method, String path, String body, Map headers) throws Exception { - Map requestMap = mapper.readValue(jsonEvent, Map.class); - if (requestMap.get("version").equals("2.0")) { - return generateHttpRequest2(requestMap, method, path, body, headers); - } - return generateHttpRequest(requestMap, method, path, body, headers); - } + private byte[] generateHttpRequest(String jsonEvent, String method, String path, String body, Map headers) throws Exception { + Map requestMap = mapper.readValue(jsonEvent, Map.class); + if (requestMap.get("version").equals("2.0")) { + return generateHttpRequest2(requestMap, method, path, body, headers); + } + return generateHttpRequest(requestMap, method, path, body, headers); + } - @SuppressWarnings({ "unchecked"}) - private byte[] generateHttpRequest(Map requestMap, String method, String path, String body, Map headers) throws Exception { - requestMap.put("path", path); - requestMap.put("httpMethod", method); - requestMap.put("body", body); - if (!CollectionUtils.isEmpty(headers)) { - requestMap.put("headers", headers); - } - return mapper.writeValueAsBytes(requestMap); - } + @SuppressWarnings({ "unchecked"}) + private byte[] generateHttpRequest(Map requestMap, String method, String path, String body, Map headers) throws Exception { + requestMap.put("path", path); + requestMap.put("httpMethod", method); + requestMap.put("body", body); + if (!CollectionUtils.isEmpty(headers)) { + requestMap.put("headers", headers); + } + return mapper.writeValueAsBytes(requestMap); + } - @SuppressWarnings({ "unchecked"}) - private byte[] generateHttpRequest2(Map requestMap, String method, String path, String body, Map headers) throws Exception { - Map map = mapper.readValue(API_GATEWAY_EVENT_V2, Map.class); - Map http = (Map) ((Map) map.get("requestContext")).get("http"); - http.put("path", path); - http.put("method", method); - map.put("body", body); - if (!CollectionUtils.isEmpty(headers)) { - map.put("headers", headers); - } - return mapper.writeValueAsBytes(map); - } + @SuppressWarnings({ "unchecked"}) + private byte[] generateHttpRequest2(Map requestMap, String method, String path, String body, Map headers) throws Exception { + Map map = mapper.readValue(API_GATEWAY_EVENT_V2, Map.class); + Map http = (Map) ((Map) map.get("requestContext")).get("http"); + http.put("path", path); + http.put("method", method); + map.put("body", body); + if (!CollectionUtils.isEmpty(headers)) { + map.put("headers", headers); + } + return mapper.writeValueAsBytes(map); + } } diff --git a/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java index ee9989df5..428d67267 100644 --- a/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java +++ b/samples/springboot3/alt-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java @@ -42,7 +42,7 @@ public HandlerAdapter handlerAdapter() { @Bean("CognitoIdentityFilter") public Filter cognitoFilter() { - return new CognitoIdentityFilter(); + return new CognitoIdentityFilter(); } public static void main(String[] args) { From da5cb2b0f37da5397a945080dd9c157486284254 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 6 Jul 2023 10:30:10 +0200 Subject: [PATCH 10/10] Upgrade s-c-function to 4.0.4 (release) --- aws-serverless-java-container-springboot3/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-serverless-java-container-springboot3/pom.xml b/aws-serverless-java-container-springboot3/pom.xml index 532ce8312..4c41ab834 100644 --- a/aws-serverless-java-container-springboot3/pom.xml +++ b/aws-serverless-java-container-springboot3/pom.xml @@ -25,7 +25,7 @@ org.springframework.cloud spring-cloud-function-serverless-web - 4.1.0-SNAPSHOT + 4.0.4 com.amazonaws.serverless