From 2008a0ead37b6d6d7eb7a05d487250cb061890c4 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 22 Feb 2023 12:56:13 +0100 Subject: [PATCH] Stopping point with initial POC --- .../.jdk8 | 0 .../README.md | 3 + .../pom.xml | 44 ++++ .../sample/pet-store/.aws-sam/build.toml | 13 ++ .../sample/pet-store/README.md | 38 ++++ .../sample/pet-store/pom.xml | 192 ++++++++++++++++++ .../sample/pet-store/src/assembly/bin.xml | 24 +++ .../petstore/PetStoreSpringAppConfig.java | 71 +++++++ .../oz/spring/petstore/PetsController.java | 80 ++++++++ .../java/oz/spring/petstore/model/Error.java | 33 +++ .../java/oz/spring/petstore/model/Pet.java | 58 ++++++ .../oz/spring/petstore/model/PetData.java | 115 +++++++++++ .../sample/pet-store/template.yml | 37 ++++ .../adapter/aws/web/FunctionClassUtils.java | 153 ++++++++++++++ .../cloud/function/adapter/aws/web/README.md | 5 + .../adapter/aws/web/WebProxyInvoker.java | 178 ++++++++++++++++ .../web/client/HeaderValueHolder.java | 0 .../web/client/ProxyHttpServletRequest.java | 127 ------------ .../web/client/ProxyHttpServletResponse.java | 0 .../springframework/web/client/ProxyMvc.java | 0 .../web/client/ProxyServletContext.java | 0 .../org/springframework/web/client/README.md | 3 + .../cloud/function/adapter/aws/web/Pet.java | 0 .../function/adapter/aws/web/PetData.java | 0 .../aws/web/PetStoreSpringAppConfig.java | 2 - .../adapter/aws/web/PetsController.java | 0 .../adapter/aws/web/WebProxyInvokerTests.java | 12 +- .../adapter/aws/web/WebProxyInvoker.java | 104 ---------- .../web/client/ProxyServletConfig.java | 53 ----- 29 files changed, 1052 insertions(+), 293 deletions(-) create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/.jdk8 create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/README.md create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/pom.xml create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/.aws-sam/build.toml create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/README.md create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/pom.xml create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/assembly/bin.xml create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/PetStoreSpringAppConfig.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/PetsController.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/Error.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/Pet.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/PetData.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/template.yml create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/FunctionClassUtils.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/README.md create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvoker.java rename spring-cloud-function-adapters/{spring-cloud-function-adapter-aws => spring-cloud-function-adapter-aws-web}/src/main/java/org/springframework/web/client/HeaderValueHolder.java (100%) rename spring-cloud-function-adapters/{spring-cloud-function-adapter-aws => spring-cloud-function-adapter-aws-web}/src/main/java/org/springframework/web/client/ProxyHttpServletRequest.java (89%) rename spring-cloud-function-adapters/{spring-cloud-function-adapter-aws => spring-cloud-function-adapter-aws-web}/src/main/java/org/springframework/web/client/ProxyHttpServletResponse.java (100%) rename spring-cloud-function-adapters/{spring-cloud-function-adapter-aws => spring-cloud-function-adapter-aws-web}/src/main/java/org/springframework/web/client/ProxyMvc.java (100%) rename spring-cloud-function-adapters/{spring-cloud-function-adapter-aws => spring-cloud-function-adapter-aws-web}/src/main/java/org/springframework/web/client/ProxyServletContext.java (100%) create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/README.md rename spring-cloud-function-adapters/{spring-cloud-function-adapter-aws => spring-cloud-function-adapter-aws-web}/src/test/java/org/springframework/cloud/function/adapter/aws/web/Pet.java (100%) rename spring-cloud-function-adapters/{spring-cloud-function-adapter-aws => spring-cloud-function-adapter-aws-web}/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetData.java (100%) rename spring-cloud-function-adapters/{spring-cloud-function-adapter-aws => spring-cloud-function-adapter-aws-web}/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetStoreSpringAppConfig.java (94%) rename spring-cloud-function-adapters/{spring-cloud-function-adapter-aws => spring-cloud-function-adapter-aws-web}/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetsController.java (100%) rename spring-cloud-function-adapters/{spring-cloud-function-adapter-aws => spring-cloud-function-adapter-aws-web}/src/test/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvokerTests.java (92%) delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvoker.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyServletConfig.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/.jdk8 b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/.jdk8 new file mode 100644 index 000000000..e69de29bb diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/README.md b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/README.md new file mode 100644 index 000000000..46ee43c43 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/README.md @@ -0,0 +1,3 @@ +Classes in this package should ideally reside in spring-web somewhere as a light weight HTTP proxy, since they are independent of the +context of the execution (i.e., AWS or Azure or whatever). +In fact classes in these package is a slimed-down copy of similar classes in MockMVC. diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/pom.xml new file mode 100644 index 000000000..db6aa34ce --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + spring-cloud-function-adapter-aws-web + jar + spring-cloud-function-adapter-aws-web + AWS Lambda Adapter for Spring Cloud Function + + org.springframework.cloud + spring-cloud-function-adapter-parent + 3.2.9-SNAPSHOT + + + UTF-8 + UTF-8 + 1.8 + + + + com.fasterxml.jackson.core + jackson-databind + + + org.springframework + spring-webmvc + + + javax.servlet + javax.servlet-api + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-web + test + + + diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/.aws-sam/build.toml b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/.aws-sam/build.toml new file mode 100644 index 000000000..05c08b5fa --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/.aws-sam/build.toml @@ -0,0 +1,13 @@ +# This file is auto generated by SAM CLI build command + +[function_build_definitions] +[function_build_definitions.9341c1d5-9265-48ef-836e-25df000b0c59] +codeuri = "/Users/ozhurakousky/Documents/dev/repo/spring-cloud-function/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store" +runtime = "java11" +architecture = "x86_64" +handler = "org.springframework.cloud.function.adapter.aws.web.WebProxyInvoker::handleRequest" +manifest_hash = "" +packagetype = "Zip" +functions = ["PetStoreFunction"] + +[layer_build_definitions] diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/README.md b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/README.md new file mode 100644 index 000000000..bbe5db289 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/README.md @@ -0,0 +1,38 @@ +Copied from https://github.com/awslabs/aws-serverless-java-container/tree/main/samples/spring/pet-store + +# Serverless Spring example +A basic pet store written with the [Spring framework](https://projects.spring.io/spring-framework/). The `StreamLambdaHandler` object is the main entry point for Lambda. + +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/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/pom.xml new file mode 100644 index 000000000..59aa050ae --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/pom.xml @@ -0,0 +1,192 @@ + + + 4.0.0 + + oz.spring.petstore + pet-store + 1.0-SNAPSHOT + pet-store + Simple pet store written with the Spring framework + https://aws.amazon.com/lambda/ + + + https://github.com/awslabs/aws-serverless-java-container.git + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 1.8 + 1.8 + 5.3.25 + 4.13.2 + 2.19.0 + + + + + org.springframework.cloud + spring-cloud-function-adapter-aws-web + 3.2.9-SNAPSHOT + + + + org.springframework + spring-context-indexer + ${spring.version} + true + + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + + + + com.amazonaws + aws-lambda-java-log4j2 + 1.5.1 + + + + junit + junit + ${junit.version} + test + + + + + + shaded-jar + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + false + + + + + + + + + + com.github.edwgiz + maven-shade-plugin.log4j2-cachefile-transformer + 2.8.1 + + + + + + + + assembly-zip + + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + default-jar + none + + + + + org.apache.maven.plugins + maven-install-plugin + 3.0.0-M1 + + true + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.2.0 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + runtime + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + zip-assembly + package + + single + + + ${project.artifactId}-${project.version} + + src${file.separator}assembly${file.separator}bin.xml + + false + + + + + + + + + diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/assembly/bin.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/assembly/bin.xml new file mode 100644 index 000000000..1ffd82d1c --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/assembly/bin.xml @@ -0,0 +1,24 @@ + + lambda-package + + zip + + false + + + + ${project.build.directory}${file.separator}lib + lib + + + + ${project.build.directory}${file.separator}classes + + ** + + ${file.separator} + + + \ No newline at end of file diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/PetStoreSpringAppConfig.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/PetStoreSpringAppConfig.java new file mode 100644 index 000000000..3969ea641 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/PetStoreSpringAppConfig.java @@ -0,0 +1,71 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 oz.spring.petstore; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + + +@Configuration +@Import({ PetsController.class }) +public class PetStoreSpringAppConfig { + /* + * 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(); + } + + /* + * optimization - avoids creating default exception resolvers; not required as the serverless container handles + * all exceptions + * + * By default, an ExceptionHandlerExceptionResolver is created which creates many dependent object, including + * an expensive ObjectMapper instance. + * + * To enable custom @ControllerAdvice classes remove this bean. + */ + @Bean + public HandlerExceptionResolver handlerExceptionResolver() { + return new HandlerExceptionResolver() { + + @Override + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + return null; + } + }; + } +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/PetsController.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/PetsController.java new file mode 100644 index 000000000..b04a26d92 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/PetsController.java @@ -0,0 +1,80 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 oz.spring.petstore; + +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import oz.spring.petstore.model.Pet; +import oz.spring.petstore.model.PetData; + +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; + } + + @GetMapping("favicon.ico") + @ResponseBody + void returnNoFavicon() { + } + + @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/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/Error.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/Error.java new file mode 100644 index 000000000..bb19a9027 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/Error.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 oz.spring.petstore.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/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/Pet.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/Pet.java new file mode 100644 index 000000000..20f170a99 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/Pet.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 oz.spring.petstore.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/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/PetData.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/PetData.java new file mode 100644 index 000000000..1df3632cc --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/src/main/java/oz/spring/petstore/model/PetData.java @@ -0,0 +1,115 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 oz.spring.petstore.model; + +import java.util.*; +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/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/template.yml b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/template.yml new file mode 100644 index 000000000..7c5cea2e3 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/template.yml @@ -0,0 +1,37 @@ +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: + Handler: org.springframework.cloud.function.adapter.aws.web.WebProxyInvoker::handleRequest + Runtime: java11 + CodeUri: . + MemorySize: 512 + Policies: AWSLambdaBasicExecutionRole + Timeout: 30 + Environment: + Variables: + MAIN_CLASS: oz.spring.petstore.PetStoreSpringAppConfig + 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: PetStoreLambda + + diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/FunctionClassUtils.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/FunctionClassUtils.java new file mode 100644 index 000000000..030a15902 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/FunctionClassUtils.java @@ -0,0 +1,153 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 org.springframework.cloud.function.adapter.aws.web; + +import java.io.InputStream; +import java.net.URL; +import java.util.Collections; +import java.util.List; +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.boot.SpringBootConfiguration; +//import org.springframework.boot.autoconfigure.SpringBootApplication; +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; + +/** + * General utility class which aggregates various class-level utility functions + * used by the framework. + * + * @author Oleg Zhurakousky + * @since 3.0.1 + */ +public final class FunctionClassUtils { + + private static Log logger = LogFactory.getLog(FunctionClassUtils.class); + + private FunctionClassUtils() { + + } + + /** + * Discovers the start class in the currently running application. + * The discover search order is 'MAIN_CLASS' environment property, + * 'MAIN_CLASS' system property, META-INF/MANIFEST.MF:'Start-Class' attribute, + * meta-inf/manifest.mf:'Start-Class' attribute. + * + * @return instance of Class which represent the start class of the application. + */ + public static Class getStartClass() { + ClassLoader classLoader = FunctionClassUtils.class.getClassLoader(); + return getStartClass(classLoader); + } + + static Class getStartClass(ClassLoader classLoader) { + Class mainClass = null; + if (System.getenv("MAIN_CLASS") != null) { + mainClass = ClassUtils.resolveClassName(System.getenv("MAIN_CLASS"), classLoader); + } + else if (System.getProperty("MAIN_CLASS") != null) { + mainClass = ClassUtils.resolveClassName(System.getProperty("MAIN_CLASS"), classLoader); + } + else { + try { + Class result = getStartClass( + Collections.list(classLoader.getResources(JarFile.MANIFEST_NAME)), classLoader); + if (result == null) { + result = getStartClass(Collections + .list(classLoader.getResources("meta-inf/manifest.mf")), classLoader); + } + Assert.notNull(result, "Failed to locate main class"); + mainClass = result; + } + catch (Exception ex) { + throw new IllegalStateException("Failed to discover main class. An attempt was made to discover " + + "main class as 'MAIN_CLASS' environment variable, system property as well as " + + "entry in META-INF/MANIFEST.MF (in that order).", ex); + } + } + logger.info("Main class: " + mainClass); + return mainClass; + } + + private static Class getStartClass(List list, ClassLoader classLoader) { + if (logger.isTraceEnabled()) { + logger.trace("Searching manifests: " + list); + } + for (URL url : list) { + try { + InputStream inputStream = null; + Manifest manifest = new Manifest(url.openStream()); + logger.info("Searching for start class in manifest: " + url); + if (logger.isDebugEnabled()) { + manifest.write(System.out); + } + try { + String startClassName = manifest.getMainAttributes().getValue("Start-Class"); + if (!StringUtils.hasText(startClassName)) { + startClassName = manifest.getMainAttributes().getValue("Main-Class"); + } + + if (StringUtils.hasText(startClassName)) { + Class startClass = ClassUtils.forName(startClassName, classLoader); + + if (KotlinDetector.isKotlinType(startClass)) { + PathMatchingResourcePatternResolver r = new PathMatchingResourcePatternResolver(classLoader); + String packageName = startClass.getPackage().getName(); + Resource[] resources = r.getResources("classpath:" + packageName.replace(".", "/") + "/*.class"); + for (int i = 0; i < resources.length; i++) { + Resource resource = resources[i]; + String className = packageName + "." + (resource.getFilename().replace("/", ".")).replace(".class", ""); + startClass = ClassUtils.forName(className, classLoader); +// if (isSpringBootApplication(startClass)) { +// logger.info("Loaded Main Kotlin Class: " + startClass); +// return startClass; +// } + } + } +// else if (isSpringBootApplication(startClass)) { +// logger.info("Loaded Start Class: " + startClass); +// return startClass; +// } + } + } + finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + catch (Exception ex) { + logger.debug("Failed to determine Start-Class in manifest file of " + url, ex); + } + } + return null; + } + +// private static boolean isSpringBootApplication(Class startClass) { +// return startClass.getDeclaredAnnotation(SpringBootApplication.class) != null +// || startClass.getDeclaredAnnotation(SpringBootConfiguration.class) != null; +// } +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/README.md b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/README.md new file mode 100644 index 000000000..de06b2658 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/README.md @@ -0,0 +1,5 @@ +Classes in this package would remain specific to AWS (in this case). There would be something similar in Azure and others. + +And these classes would depend on what is currently in `org.springframework.web.client` package of this module. +However, ideally the contents of the `org.springframework.web.client` package should reside in spring-web somewhere as a light weight +HTTP proxy as we technically already have it in a form of MockMVC. \ No newline at end of file diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvoker.java new file mode 100644 index 000000000..11075e5b8 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvoker.java @@ -0,0 +1,178 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 org.springframework.cloud.function.adapter.aws.web; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.servlet.Filter; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +import org.springframework.util.StreamUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.client.ProxyHttpServletRequest; +import org.springframework.web.client.ProxyHttpServletResponse; +import org.springframework.web.client.ProxyMvc; +import org.springframework.web.client.ProxyServletContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * + * AWS Lambda specific handler that will proxy API Gateway request to Spring Web-app + * This class represents AWS Lambda fronted by API Gateway and is identified as 'handler' during the deployment. + * + * @author Oleg Zhurakousky + * + */ +public class WebProxyInvoker { + + private static Log logger = LogFactory.getLog(WebProxyInvoker.class); + + private final ProxyMvc mvc; + + private final ServletContext servletContext; + + ObjectMapper mapper = new ObjectMapper(); + + public WebProxyInvoker() throws ServletException { + Class startClass = FunctionClassUtils.getStartClass(); + AnnotationConfigWebApplicationContext applpicationContext = new AnnotationConfigWebApplicationContext(); + applpicationContext.register(startClass); + + this.servletContext = new ProxyServletContext(); + ServletConfig servletConfig = new ProxyServletConfig(this.servletContext); + + DispatcherServlet servlet = new DispatcherServlet(applpicationContext); + servlet.init(servletConfig); + this.mvc = new ProxyMvc(servlet, applpicationContext.getBeansOfType(Filter.class).values().toArray(new Filter[0])); + } + + /* + * TODO + * - Security context propagation from AWS API Gateway (easy) + * - Error handling + */ + @SuppressWarnings("unchecked") + private HttpServletRequest prepareRequest(InputStream input) throws IOException { + + Map request = mapper.readValue(input, Map.class); + if (logger.isDebugEnabled()) { + logger.debug("Request: " + request); + } + String httpMethod = (String) request.get("httpMethod"); + String path = (String) request.get("path"); + if (logger.isDebugEnabled()) { + logger.debug("httpMethod: " + httpMethod); + logger.debug("path: " + path); + } + ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(servletContext, httpMethod, path); + if (StringUtils.hasText((String) request.get("body"))) { + httpRequest.setContent(((String) request.get("body")).getBytes()); + } + if (request.get("queryStringParameters") != null) { + httpRequest.setParameters((Map) request.get("queryStringParameters")); + } + + Map headers = (Map) request.get("headers"); + headers.putAll((Map) request.get("multiValueHeaders")); + for (Entry entry : headers.entrySet()) { + httpRequest.addHeader(entry.getKey(), entry.getValue()); + } + return httpRequest; + } + + + public void handleRequest(InputStream input, OutputStream output) throws IOException { + HttpServletRequest httpRequest = this.prepareRequest(input); + + ProxyHttpServletResponse httpResponse = new ProxyHttpServletResponse(); + try { + this.mvc.perform(httpRequest, httpResponse); + } + catch (Exception e) { + e.printStackTrace(); + throw new IllegalStateException(e); + } + + String responseString = httpResponse.getContentAsString(); + if (StringUtils.hasText(responseString)) { + if (logger.isDebugEnabled()) { + logger.debug("Response: " + responseString); + } + Map apiGatewayResponseStructure = new HashMap(); + apiGatewayResponseStructure.put("isBase64Encoded", false); + apiGatewayResponseStructure.put("statusCode", 200); + apiGatewayResponseStructure.put("body", responseString); + + Map> multiValueHeaders = new HashMap<>(); + for (String headerName : httpResponse.getHeaderNames()) { + multiValueHeaders.put(headerName, httpResponse.getHeaders(headerName)); + } + // TODO investigate why AWS doesn't like List as value +// apiGatewayResponseStructure.put("headers", multiValueHeaders); + + byte[] apiGatewayResponseBytes = mapper.writeValueAsBytes(apiGatewayResponseStructure); + StreamUtils.copy(apiGatewayResponseBytes, output); + } + } + + private static class ProxyServletConfig implements ServletConfig { + + private final ServletContext servletContext; + + ProxyServletConfig(ServletContext servletContext) { + this.servletContext = servletContext; + } + + @Override + public String getServletName() { + return "serverless-proxy"; + } + + @Override + public ServletContext getServletContext() { + return this.servletContext; + } + + @Override + public Enumeration getInitParameterNames() { + return Collections.enumeration(new ArrayList()); + } + + @Override + public String getInitParameter(String name) { + return null; + } + } +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/HeaderValueHolder.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/HeaderValueHolder.java similarity index 100% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/HeaderValueHolder.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/HeaderValueHolder.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyHttpServletRequest.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/ProxyHttpServletRequest.java similarity index 89% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyHttpServletRequest.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/ProxyHttpServletRequest.java index 203f1e031..4c64ca23a 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyHttpServletRequest.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/ProxyHttpServletRequest.java @@ -164,30 +164,9 @@ public class ProxyHttpServletRequest implements HttpServletRequest { private final Map parameters = new LinkedHashMap<>(16); -// private String protocol = DEFAULT_PROTOCOL; -// -// private String scheme = DEFAULT_SCHEME; -// -// private String serverName = DEFAULT_SERVER_NAME; -// -// private int serverPort = DEFAULT_SERVER_PORT; -// -// private String remoteAddr = DEFAULT_REMOTE_ADDR; -// -// private String remoteHost = DEFAULT_REMOTE_HOST; - /** List of locales in descending order. */ private final LinkedList locales = new LinkedList<>(); - private boolean secure = false; - -// private int remotePort = DEFAULT_SERVER_PORT; -// -// private String localName = DEFAULT_SERVER_NAME; -// -// private String localAddr = DEFAULT_SERVER_ADDR; -// -// private int localPort = DEFAULT_SERVER_PORT; private boolean asyncStarted = false; @@ -249,29 +228,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest { // Constructors // --------------------------------------------------------------------- -// /** -// * Create a new {@code MockHttpServletRequest} with a default -// * {@link MockServletContext}. -// * @param method the request method (may be {@code null}) -// * @param requestURI the request URI (may be {@code null}) -// * @see #setMethod -// * @see #setRequestURI -// * @see #MockHttpServletRequest(ServletContext, String, String) -// */ -// public ServerlessHttpServletRequest(@Nullable String method, @Nullable String requestURI) { -// this(null, method, requestURI); -// } -// -// /** -// * Create a new {@code MockHttpServletRequest} with the supplied {@link ServletContext}. -// * @param servletContext the ServletContext that the request runs in -// * (may be {@code null} to use a default {@link MockServletContext}) -// * @see #MockHttpServletRequest(ServletContext, String, String) -// */ -// public ServerlessHttpServletRequest(@Nullable ServletContext servletContext) { -// this(servletContext, "", ""); -// } - /** * Create a new {@code MockHttpServletRequest} with the supplied * {@link ServletContext}, {@code method}, and {@code requestURI}. @@ -470,18 +426,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest { @Override public ServletInputStream getInputStream() { -// if (this.inputStream != null) { -// return this.inputStream; -// } -// else if (this.reader != null) { -// throw new IllegalStateException( -// "Cannot call getInputStream() after getReader() has already been called for the current request") ; -// } -// -// this.inputStream = (this.content != null ? -// new DelegatingServletInputStream(new ByteArrayInputStream(this.content)) : -// EMPTY_SERVLET_INPUT_STREAM); -// return this.inputStream; throw new UnsupportedOperationException(); } @@ -635,64 +579,24 @@ public class ProxyHttpServletRequest implements HttpServletRequest { @Override public String getScheme() { -// return this.scheme; throw new UnsupportedOperationException(); } public void setServerName(String serverName) { -// this.serverName = serverName; throw new UnsupportedOperationException(); } @Override public String getServerName() { -// String rawHostHeader = getHeader(HttpHeaders.HOST); -// String host = rawHostHeader; -// if (host != null) { -// host = host.trim(); -// if (host.startsWith("[")) { -// int indexOfClosingBracket = host.indexOf(']'); -// Assert.state(indexOfClosingBracket > -1, () -> "Invalid Host header: " + rawHostHeader); -// host = host.substring(0, indexOfClosingBracket + 1); -// } -// else if (host.contains(":")) { -// host = host.substring(0, host.indexOf(':')); -// } -// return host; -// } -// -// // else -// return this.serverName; throw new UnsupportedOperationException(); } public void setServerPort(int serverPort) { -// this.serverPort = serverPort; throw new UnsupportedOperationException(); } @Override public int getServerPort() { -// String rawHostHeader = getHeader(HttpHeaders.HOST); -// String host = rawHostHeader; -// if (host != null) { -// host = host.trim(); -// int idx; -// if (host.startsWith("[")) { -// int indexOfClosingBracket = host.indexOf(']'); -// Assert.state(indexOfClosingBracket > -1, () -> "Invalid Host header: " + rawHostHeader); -// idx = host.indexOf(':', indexOfClosingBracket); -// } -// else { -// idx = host.indexOf(':'); -// } -// if (idx != -1) { -// return Integer.parseInt(host.substring(idx + 1)); -// } -// } -// -// // else -// return this.serverPort; throw new UnsupportedOperationException(); } @@ -720,24 +624,20 @@ public class ProxyHttpServletRequest implements HttpServletRequest { } public void setRemoteAddr(String remoteAddr) { -// this.remoteAddr = remoteAddr; throw new UnsupportedOperationException(); } @Override public String getRemoteAddr() { return "proxy"; -// throw new UnsupportedOperationException(); } public void setRemoteHost(String remoteHost) { -// this.remoteHost = remoteHost; throw new UnsupportedOperationException(); } @Override public String getRemoteHost() { -// return this.remoteHost; throw new UnsupportedOperationException(); } @@ -840,18 +740,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest { return Collections.enumeration(this.locales); } - /** - * Set the boolean {@code secure} flag indicating whether the mock request was - * made using a secure channel, such as HTTPS. - * - * @see #isSecure() - * @see #getScheme() - * @see #setScheme(String) - */ - public void setSecure(boolean secure) { - this.secure = secure; - } - /** * Return {@code true} if the {@link #setSecure secure} flag has been set to * {@code true} or if the {@link #getScheme scheme} is {@code https}. @@ -860,13 +748,11 @@ public class ProxyHttpServletRequest implements HttpServletRequest { */ @Override public boolean isSecure() { -// return (this.secure || HTTPS.equalsIgnoreCase(this.scheme)); throw new UnsupportedOperationException(); } @Override public RequestDispatcher getRequestDispatcher(String path) { -// return new MockRequestDispatcher(path); throw new UnsupportedOperationException(); } @@ -877,29 +763,24 @@ public class ProxyHttpServletRequest implements HttpServletRequest { } public void setRemotePort(int remotePort) { -// this.remotePort = remotePort; throw new UnsupportedOperationException(); } @Override public int getRemotePort() { -// return this.remotePort; throw new UnsupportedOperationException(); } public void setLocalName(String localName) { -// this.localName = localName; throw new UnsupportedOperationException(); } @Override public String getLocalName() { -// return this.localName; throw new UnsupportedOperationException(); } public void setLocalAddr(String localAddr) { -// this.localAddr = localAddr; throw new UnsupportedOperationException(); } @@ -909,7 +790,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest { } public void setLocalPort(int localPort) { -// this.localPort = localPort; throw new UnsupportedOperationException(); } @@ -926,10 +806,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest { @Override public AsyncContext startAsync(ServletRequest request, @Nullable ServletResponse response) { -// Assert.state(this.asyncSupported, "Async not supported"); -// this.asyncStarted = true; -// this.asyncContext = new MockAsyncContext(request, response); -// return this.asyncContext; throw new UnsupportedOperationException(); } @@ -952,15 +828,12 @@ public class ProxyHttpServletRequest implements HttpServletRequest { } public void setAsyncContext(@Nullable AsyncContext asyncContext) { -// this.asyncContext = asyncContext; throw new UnsupportedOperationException(); } @Override @Nullable public AsyncContext getAsyncContext() { -// return this.asyncContext; -// throw new UnsupportedOperationException(); return null; } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyHttpServletResponse.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/ProxyHttpServletResponse.java similarity index 100% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyHttpServletResponse.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/ProxyHttpServletResponse.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyMvc.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/ProxyMvc.java similarity index 100% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyMvc.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/ProxyMvc.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyServletContext.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/ProxyServletContext.java similarity index 100% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyServletContext.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/ProxyServletContext.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/README.md b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/README.md new file mode 100644 index 000000000..46ee43c43 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/main/java/org/springframework/web/client/README.md @@ -0,0 +1,3 @@ +Classes in this package should ideally reside in spring-web somewhere as a light weight HTTP proxy, since they are independent of the +context of the execution (i.e., AWS or Azure or whatever). +In fact classes in these package is a slimed-down copy of similar classes in MockMVC. diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/Pet.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/Pet.java similarity index 100% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/Pet.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/Pet.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetData.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetData.java similarity index 100% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetData.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetData.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetStoreSpringAppConfig.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetStoreSpringAppConfig.java similarity index 94% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetStoreSpringAppConfig.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetStoreSpringAppConfig.java index 8498cb2c8..93888ccdd 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetStoreSpringAppConfig.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetStoreSpringAppConfig.java @@ -24,8 +24,6 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetsController.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetsController.java similarity index 100% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetsController.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetsController.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvokerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvokerTests.java similarity index 92% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvokerTests.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvokerTests.java index 798bcd587..ebfd545c7 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/test/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvokerTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvokerTests.java @@ -21,19 +21,17 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.Map; - import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.springframework.cloud.function.adapter.aws.TestContext; -import org.springframework.cloud.function.json.JacksonMapper; + public class WebProxyInvokerTests { static String apiGatewayEvent = "{\n" + " \"resource\": \"/pets\",\n" + - " \"path\": \"/pets/64f56d94-a059-4111-9eeb-ee0c994b1ba8\",\n" + + " \"path\": \"/pets/64f56d94-a059-4111-9eeb-ee0c994b1ba8?foo=bar\",\n" + " \"httpMethod\": \"GET\",\n" + " \"headers\": {\n" + " \"accept\": \"*/*\",\n" + @@ -116,11 +114,11 @@ public class WebProxyInvokerTests { InputStream targetStream = new ByteArrayInputStream(this.apiGatewayEvent.getBytes()); ByteArrayOutputStream output = new ByteArrayOutputStream(); - invoker.handleRequest(targetStream, output, new TestContext()); + invoker.handleRequest(targetStream, output); - JacksonMapper mapper = new JacksonMapper(new ObjectMapper()); + ObjectMapper mapper = new ObjectMapper(); System.out.println("RESULT: =======> " + new String(output.toByteArray())); - Map result = mapper.fromJson(output.toByteArray(), Map.class); + Map result = mapper.readValue(output.toByteArray(), Map.class); System.out.println(result); } } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvoker.java deleted file mode 100644 index 9b5bc44de..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/web/WebProxyInvoker.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2023-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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 org.springframework.cloud.function.adapter.aws.web; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.Filter; -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.cloud.function.json.JacksonMapper; -import org.springframework.cloud.function.json.JsonMapper; -import org.springframework.cloud.function.utils.FunctionClassUtils; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StreamUtils; -import org.springframework.web.client.ProxyDispatcherServlet; -import org.springframework.web.client.ProxyHttpServletRequest; -import org.springframework.web.client.ProxyHttpServletResponse; -import org.springframework.web.client.ProxyMvc; -import org.springframework.web.client.ProxyServletConfig; -import org.springframework.web.client.ProxyServletContext; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.servlet.DispatcherServlet; - - - -public class WebProxyInvoker implements RequestStreamHandler { - - private final ProxyMvc mvc; - - public WebProxyInvoker() throws ServletException { - Class startClass = FunctionClassUtils.getStartClass(); - AnnotationConfigWebApplicationContext applpicationContext = new AnnotationConfigWebApplicationContext(); - applpicationContext.register(startClass); - ServletContext servletContext = new ProxyServletContext(); - ServletConfig servletConfig = new ProxyServletConfig(servletContext); - applpicationContext.setServletConfig(servletConfig); - - DispatcherServlet servlet = new DispatcherServlet(applpicationContext); - servlet.init(servletConfig); - this.mvc = new ProxyMvc(servlet, applpicationContext.getBeansOfType(Filter.class).values().toArray(new Filter[0])); - } - - @Override - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { - ProxyServletContext servletContext = new ProxyServletContext(); - JsonMapper mapper = new JacksonMapper(new ObjectMapper()); - Map request = mapper.fromJson(StreamUtils.copyToByteArray(input), Map.class); - System.out.println("!!!==> REQUEST: " + request); - String httpMethod = (String) request.get("httpMethod"); - String path = (String) request.get("path"); - System.out.println("!!!==> httpMethod: " + httpMethod); - System.out.println("!!!==> path: " + path); - HttpServletRequest resquest = new ProxyHttpServletRequest(null, httpMethod, path); - ProxyHttpServletResponse response = new ProxyHttpServletResponse(); - try { - this.mvc.perform(resquest, response); - } - catch (Exception e) { - e.printStackTrace(); - throw new IllegalStateException(e); - } - byte[] responseBytes = response.getContentAsByteArray(); - if (!ObjectUtils.isEmpty(responseBytes)) { - System.out.println("!!!==> responseBytes: " + response.getContentAsString()); - - Map apiGatewayResponseStructure = new HashMap(); - apiGatewayResponseStructure.put("isBase64Encoded", false); - apiGatewayResponseStructure.put("statusCode", 200); - apiGatewayResponseStructure.put("body", response.getContentAsString()); - apiGatewayResponseStructure.put("headers", Collections.singletonMap("foo", "bar")); - - byte[] apiGatewayResponseBytes = mapper.toJson(apiGatewayResponseStructure); - System.out.println("!!!==> apiGatewayResponseStructure: " + apiGatewayResponseStructure); - StreamUtils.copy(apiGatewayResponseBytes, output); - System.out.println("!!!==> COPIED RESPONSE"); - } - } -} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyServletConfig.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyServletConfig.java deleted file mode 100644 index c8462bde6..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/web/client/ProxyServletConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2023-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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 org.springframework.web.client; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; - -public class ProxyServletConfig implements ServletConfig { - - private final ServletContext servletContext; - - public ProxyServletConfig(ServletContext servletContext) { - this.servletContext = servletContext; - } - - @Override - public String getServletName() { - return "hello-oleg"; - } - - @Override - public ServletContext getServletContext() { - return this.servletContext; - } - - @Override - public Enumeration getInitParameterNames() { - return Collections.enumeration(new ArrayList()); - } - - @Override - public String getInitParameter(String name) { - return null; - } -}