diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java index d268bd2e4..c7e507f39 100644 --- a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java @@ -2,7 +2,10 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Iterator; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -10,6 +13,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cloud.function.serverless.web.ServerlessHttpServletRequest; import org.springframework.cloud.function.serverless.web.ServerlessMVC; +import org.springframework.util.CollectionUtils; import org.springframework.util.FileCopyUtils; import org.springframework.util.MultiValueMapAdapter; import org.springframework.util.StringUtils; @@ -109,6 +113,9 @@ private static HttpServletRequest generateRequest1(String request, Context lambd AwsProxyRequest v1Request = readValue(request, AwsProxyRequest.class, mapper); ServerlessHttpServletRequest httpRequest = new ServerlessHttpServletRequest(servletContext, v1Request.getHttpMethod(), v1Request.getPath()); + + populateQueryStringparameters(v1Request.getQueryStringParameters(), httpRequest); + if (v1Request.getMultiValueHeaders() != null) { MultiValueMapAdapter headers = new MultiValueMapAdapter(v1Request.getMultiValueHeaders()); httpRequest.setHeaders(headers); @@ -128,16 +135,21 @@ private static HttpServletRequest generateRequest1(String request, Context lambd securityWriter.writeSecurityContext(v1Request, lambdaContext)); return httpRequest; } + + @SuppressWarnings({ "rawtypes", "unchecked" }) private static HttpServletRequest generateRequest2(String request, Context lambdaContext, SecurityContextWriter securityWriter, ObjectMapper mapper, ServletContext servletContext) { HttpApiV2ProxyRequest v2Request = readValue(request, HttpApiV2ProxyRequest.class, mapper); + + ServerlessHttpServletRequest httpRequest = new ServerlessHttpServletRequest(servletContext, v2Request.getRequestContext().getHttp().getMethod(), v2Request.getRequestContext().getHttp().getPath()); + populateQueryStringparameters(v2Request.getQueryStringParameters(), httpRequest); v2Request.getHeaders().forEach(httpRequest::setHeader); - + if (StringUtils.hasText(v2Request.getBody())) { httpRequest.setContentType("application/json"); httpRequest.setContent(v2Request.getBody().getBytes(StandardCharsets.UTF_8)); @@ -151,6 +163,14 @@ private static HttpServletRequest generateRequest2(String request, Context lambd return httpRequest; } + private static void populateQueryStringparameters(Map requestParameters, ServerlessHttpServletRequest httpRequest) { + if (!CollectionUtils.isEmpty(requestParameters)) { + for (Entry entry : requestParameters.entrySet()) { + httpRequest.setParameter(entry.getKey(), entry.getValue()); + } + } + } + private static T readValue(String json, Class clazz, ObjectMapper mapper) { try { return mapper.readValue(json, clazz); 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 dabc30e24..ff1f209b5 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 @@ -13,6 +13,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.cloud.function.serverless.web.ServerlessServletContext; import org.springframework.util.CollectionUtils; import com.amazonaws.serverless.proxy.spring.servletapp.MessageData; @@ -72,12 +73,16 @@ public class SpringDelegatingLambdaContainerHandlerTests { + " },\n" + " \"queryStringParameters\": {\n" + " \"abc\": \"xyz\",\n" + + " \"name\": \"Ricky\",\n" + " \"foo\": \"baz\"\n" + " },\n" + " \"multiValueQueryStringParameters\": {\n" + " \"abc\": [\n" + " \"xyz\"\n" + " ],\n" + + " \"name\": [\n" + + " \"Ricky\"\n" + + " ],\n" + " \"foo\": [\n" + " \"bar\",\n" + " \"baz\"\n" @@ -124,7 +129,7 @@ public class SpringDelegatingLambdaContainerHandlerTests { " \"version\": \"2.0\",\n" + " \"routeKey\": \"$default\",\n" + " \"rawPath\": \"/my/path\",\n" + - " \"rawQueryString\": \"parameter1=value1¶meter1=value2¶meter2=value\",\n" + + " \"rawQueryString\": \"parameter1=value1¶meter1=value2&name=Ricky¶meter2=value\",\n" + " \"cookies\": [\n" + " \"cookie1\",\n" + " \"cookie2\"\n" + @@ -135,6 +140,7 @@ public class SpringDelegatingLambdaContainerHandlerTests { " },\n" + " \"queryStringParameters\": {\n" + " \"parameter1\": \"value1,value2\",\n" + + " \"name\": \"Ricky\",\n" + " \"parameter2\": \"value\"\n" + " },\n" + " \"requestContext\": {\n" + @@ -202,6 +208,22 @@ public static Collection data() { return Arrays.asList(new String[]{API_GATEWAY_EVENT, API_GATEWAY_EVENT_V2}); } + @MethodSource("data") + @ParameterizedTest + public void validateComplesrequest(String jsonEvent) throws Exception { + initServletAppTest(); + InputStream targetStream = new ByteArrayInputStream(this.generateHttpRequest(jsonEvent, "POST", + "/foo/male/list/24", "{\"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")); + String[] respponseBody = ((String) result.get("body")).split("/"); + assertEquals("male", respponseBody[0]); + assertEquals("24", respponseBody[1]); + assertEquals("Ricky", respponseBody[2]); + } + @MethodSource("data") @ParameterizedTest public void testAsyncPost(String jsonEvent) throws Exception { diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java index 0cb001ed1..9f01859aa 100644 --- a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java @@ -1,9 +1,13 @@ package com.amazonaws.serverless.proxy.spring.servletapp; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PathVariable; +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; @SpringBootApplication(exclude = { org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration.class, @@ -14,5 +18,15 @@ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.class }) @Import(MessageController.class) +@RestController public class ServletApplication { + + @RequestMapping(path = "/foo/{gender}/list/{age}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public String complexRequest( + @PathVariable("gender") String gender, + @PathVariable("age") String age, + @RequestParam("name") String name + ) { + return gender + "/" + age + "/" + name; + } } diff --git a/samples/springboot3/alt-pet-store/README.md b/samples/springboot3/alt-pet-store/README.md index d8cf8383d..b91cd42f6 100644 --- a/samples/springboot3/alt-pet-store/README.md +++ b/samples/springboot3/alt-pet-store/README.md @@ -36,4 +36,21 @@ PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-w --------------------------------------------------------------------------------------------------------- $ curl https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/pets -``` \ No newline at end of file +``` + +You can also try a complex request passing both path and request parameters to complex endpoint such as this: + + +``` +@RequestMapping(path = "/foo/{gender}/bar/{age}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) +public String complexRequest(@RequestBody String body, + @PathVariable("gender") String gender, + @PathVariable("age") String age, + @RequestParam("name") String name +) +``` +For example. + +``` +curl -d '{"key1":"value1", "key2":"value2"}' -H "Content-Type: application/json" -X POST https://zuhd709386.execute-api.us-east-2.amazonaws.com/foo/male/bar/25?name=Ricky +``` diff --git a/samples/springboot3/alt-pet-store/pom.xml b/samples/springboot3/alt-pet-store/pom.xml index afda4120e..0af236afe 100644 --- a/samples/springboot3/alt-pet-store/pom.xml +++ b/samples/springboot3/alt-pet-store/pom.xml @@ -39,6 +39,12 @@ aws-serverless-java-container-springboot3 2.0.0-SNAPSHOT + + + io.swagger.core.v3 + swagger-annotations + 2.2.20 + 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 index 680e629d3..769db35f3 100644 --- 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 @@ -17,6 +17,8 @@ import com.amazonaws.serverless.sample.springboot3.model.Pet; import com.amazonaws.serverless.sample.springboot3.model.PetData; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -32,6 +34,7 @@ @RestController @EnableWebMvc public class PetsController { + @RequestMapping(path = "/pets", method = RequestMethod.POST) public Pet createPet(@RequestBody Pet newPet) { if (newPet.getName() == null || newPet.getBreed() == null) { @@ -73,5 +76,15 @@ public Pet listPets() { newPet.setName(PetData.getRandomName()); return newPet; } + + @RequestMapping(path = "/foo/{gender}/bar/{age}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public String complexRequest(@RequestBody String body, + @PathVariable("gender") String gender, + @PathVariable("age") String age, + @RequestParam("name") String name + ) { + System.out.println("Body: " + body + " - " + gender + "/" + age + "/" + name); + return gender + "/" + age + "/" + name; + } } diff --git a/samples/springboot3/alt-pet-store/template.yml b/samples/springboot3/alt-pet-store/template.yml index c883f0850..8a51c8d1d 100644 --- a/samples/springboot3/alt-pet-store/template.yml +++ b/samples/springboot3/alt-pet-store/template.yml @@ -14,7 +14,7 @@ Resources: # AutoPublishAlias: bcn FunctionName: pet-store-boot-3 Handler: com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler::handleRequest - Runtime: java21 + Runtime: java17 SnapStart: ApplyOn: PublishedVersions CodeUri: .