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;
- }
-}