diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/README.md b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/README.md
new file mode 100644
index 000000000..19552843c
--- /dev/null
+++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/README.md
@@ -0,0 +1,12 @@
+#### Introduction
+
+This module represents a concept of a light weight AWS forwarding proxy which deploys and interacts with existing
+Spring Boot web application deployed as AWS Lambda.
+
+A sample is provided in [sample](https://github.com/spring-cloud/spring-cloud-function/tree/serverless-web/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store) directory. It contain README and SAM template file to simplify the deployment. This module is identified as the only additional dependnecy to the existing web-app.
+
+_NOTE: Although this module is AWS specific, this dependency is protocol only (not binary), therefore there is no AWS dependnecies._
+
+The aformentioned proxy is identified as AWS Lambda [handler](https://github.com/spring-cloud/spring-cloud-function/blob/serverless-web/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/template.yml#L14)
+
+The main Spring Boot configuration file is identified as [MAIN_CLASS](https://github.com/spring-cloud/spring-cloud-function/blob/serverless-web/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/template.yml#L22)
diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/pom.xml
new file mode 100644
index 000000000..19081d7a5
--- /dev/null
+++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/pom.xml
@@ -0,0 +1,62 @@
+
+
+ 4.0.0
+ spring-cloud-function-adapter-azure-web
+ jar
+ spring-cloud-function-adapter-azure-web
+ Azure Function Adapter for Spring Cloud Function
+
+ org.springframework.cloud
+ spring-cloud-function-adapter-parent
+ 3.2.9-SNAPSHOT
+
+
+ UTF-8
+ UTF-8
+ 1.8
+ 3.0.0
+ 1.0.0
+
+
+
+ com.google.code.findbugs
+ annotations
+ 3.0.1
+
+
+ com.microsoft.azure.functions
+ azure-functions-java-library
+ ${azure.functions.java.core.version}
+
+
+
+ com.microsoft.azure.functions
+ azure-functions-java-spi
+ ${azure.functions.java.spi.version}
+
+
+
+ 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-azure-web/sample/pet-store/README.md b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/sample/pet-store/README.md
new file mode 100644
index 000000000..bbe5db289
--- /dev/null
+++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-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-azure-web/sample/pet-store/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/sample/pet-store/pom.xml
new file mode 100644
index 000000000..7116bb98d
--- /dev/null
+++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/sample/pet-store/pom.xml
@@ -0,0 +1,147 @@
+
+
+ 4.0.0
+
+ oz.spring.petstore
+ pet-store
+ 1.0-SNAPSHOT
+ pet-store
+ Simple pet store written with the Spring framework
+
+
+ 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
+
+
+
+
+ 8
+ 1.0.28.RELEASE
+
+
+ oz.spring.petstore.PetStoreSpringAppConfig
+
+
+ 1.23.0
+ spring-cloud-function-samples
+ westus
+ java-functions-group
+ java-functions-app-service-plan
+ EP1
+
+ 8
+ 8
+
+ 5.3.25
+ 4.13.2
+ 2.19.0
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-function-adapter-azure-web
+ 3.2.9-SNAPSHOT
+
+
+
+
+
+
+
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+
+
+
+
+ com.microsoft.azure
+ azure-functions-maven-plugin
+ ${azure.functions.maven.plugin.version}
+
+
+ transport=dt_socket,server=y,suspend=y,address=5005
+ ${functionAppName}
+ ${functionResourceGroup}
+ ${functionAppRegion}
+ ${functionAppServicePlanName}
+ ${functionPricingTier}
+
+ ${project.basedir}/src/main/resources/host.json
+
+
+ linux
+ 11
+
+
+ 7072
+
+
+
+ FUNCTIONS_EXTENSION_VERSION
+ ~4
+
+
+
+
+
+ package-functions
+
+ package
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.springframework.boot.experimental
+ spring-boot-thin-layout
+ ${spring-boot-thin-layout.version}
+
+
+
+
+
+
+
diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/sample/pet-store/src/main/java/oz/spring/petstore/PetStoreSpringAppConfig.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/sample/pet-store/src/main/java/oz/spring/petstore/PetStoreSpringAppConfig.java
new file mode 100644
index 000000000..1ac2164ae
--- /dev/null
+++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-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-azure-web/sample/pet-store/src/main/java/oz/spring/petstore/PetsController.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-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-azure-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-azure-web/sample/pet-store/src/main/java/oz/spring/petstore/model/Error.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-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-azure-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-azure-web/sample/pet-store/src/main/java/oz/spring/petstore/model/Pet.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-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-azure-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-azure-web/sample/pet-store/src/main/java/oz/spring/petstore/model/PetData.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-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-azure-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-azure-web/sample/pet-store/src/main/resources/host.json b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/sample/pet-store/src/main/resources/host.json
new file mode 100644
index 000000000..10d0c0748
--- /dev/null
+++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/sample/pet-store/src/main/resources/host.json
@@ -0,0 +1,7 @@
+{
+ "version": "2.0",
+ "extensionBundle": {
+ "id": "Microsoft.Azure.Functions.ExtensionBundle",
+ "version": "[3.*, 4.0.0)"
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/FunctionClassUtils.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/FunctionClassUtils.java
new file mode 100644
index 000000000..af6c32802
--- /dev/null
+++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/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.azure.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-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/WebProxyInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/WebProxyInvoker.java
new file mode 100644
index 000000000..1c9b53222
--- /dev/null
+++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/WebProxyInvoker.java
@@ -0,0 +1,181 @@
+/*
+ * 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.azure.web;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map.Entry;
+import java.util.Optional;
+
+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 com.microsoft.azure.functions.ExecutionContext;
+import com.microsoft.azure.functions.HttpMethod;
+import com.microsoft.azure.functions.HttpRequestMessage;
+import com.microsoft.azure.functions.HttpResponseMessage;
+import com.microsoft.azure.functions.HttpResponseMessage.Builder;
+import com.microsoft.azure.functions.HttpStatus;
+import com.microsoft.azure.functions.annotation.AuthorizationLevel;
+import com.microsoft.azure.functions.annotation.FunctionName;
+import com.microsoft.azure.functions.annotation.HttpTrigger;
+import com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.CollectionUtils;
+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;
+
+/**
+ *
+ * @author Christian Tzolov
+ * @author Oleg Zhurakousky
+ *
+ */
+public class WebProxyInvoker implements FunctionInstanceInjector {
+
+ private static Log logger = LogFactory.getLog(WebProxyInvoker.class);
+
+ private ProxyMvc mvc;
+
+ private ServletContext servletContext;
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ @Override
+ public T getInstance(Class functionClass) throws Exception {
+ System.setProperty("MAIN_CLASS", "oz.spring.petstore.PetStoreSpringAppConfig");
+ // TODO: Cache the initialization as the getInstance is called before each function invokatoin
+ this.initialize();
+ return (T) this;
+ }
+
+ public void initialize() throws ServletException {
+ Class> startClass = FunctionClassUtils.getStartClass();
+ AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
+ applicationContext.register(startClass);
+
+ this.servletContext = new ProxyServletContext();
+ ServletConfig servletConfig = new ProxyServletConfig(this.servletContext);
+
+ DispatcherServlet servlet = new DispatcherServlet(applicationContext);
+ servlet.init(servletConfig);
+ this.mvc = new ProxyMvc(servlet,
+ applicationContext.getBeansOfType(Filter.class).values().toArray(new Filter[0]));
+ }
+
+ private HttpServletRequest prepareRequest(HttpRequestMessage> request) {
+
+ String path = request.getQueryParameters().get("path");
+
+ if (!StringUtils.hasText(path)) {
+ throw new IllegalStateException("Missing path parameter");
+ }
+ ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(servletContext,
+ request.getHttpMethod().toString(), path);
+
+ if (request.getBody().isPresent()) {
+ httpRequest.setContent(request.getBody().get().getBytes());
+ }
+
+ if (!CollectionUtils.isEmpty(request.getQueryParameters())) {
+ httpRequest.setParameters(request.getQueryParameters());
+ }
+
+ for (Entry entry : request.getHeaders().entrySet()) {
+ httpRequest.addHeader(entry.getKey(), entry.getValue());
+ }
+
+ return httpRequest;
+ }
+
+ @FunctionName("AzureWebAdapter")
+ public HttpResponseMessage execute(
+ @HttpTrigger(name = "req", methods = { HttpMethod.GET,
+ HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request,
+ ExecutionContext context) {
+
+ context.getLogger().info("Request body is: " + request.getBody().orElse("[empty]"));
+
+ HttpServletRequest httpRequest = this.prepareRequest(request);
+
+ ProxyHttpServletResponse httpResponse = new ProxyHttpServletResponse();
+ try {
+ this.mvc.perform(httpRequest, httpResponse);
+
+ Builder responseBuilder = request.createResponseBuilder(HttpStatus.OK);
+ for (String headerName : httpResponse.getHeaderNames()) {
+ responseBuilder.header(headerName, httpResponse.getHeader(headerName));
+ }
+
+ String responseString = httpResponse.getContentAsString();
+ if (StringUtils.hasText(responseString)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Response: " + responseString);
+ }
+ responseBuilder.body(responseString);
+ } // TODO: what to do with bodyless response?
+
+ return responseBuilder.build();
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ throw new IllegalStateException(e);
+ }
+
+ }
+
+ 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-azure-web/src/main/java/org/springframework/web/client/HeaderValueHolder.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/web/client/HeaderValueHolder.java
new file mode 100644
index 000000000..74cef84db
--- /dev/null
+++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/web/client/HeaderValueHolder.java
@@ -0,0 +1,78 @@
+/*
+ * 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.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.CollectionUtils;
+
+class HeaderValueHolder {
+
+ private final List