From ee777a796bdb6bb9bd02f8db5e691a10315bf416 Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Fri, 3 Mar 2023 09:03:11 +0100 Subject: [PATCH] cache intialisation. add test --- ...Invoker.java => AzureWebProxyInvoker.java} | 50 +++-- ...ctions.spi.inject.FunctionInstanceInjector | 2 +- .../azure/web/AzureWebProxyInvokerTests.java | 180 ++++++++++++++++++ .../azure/web/TestExecutionContext.java | 47 +++++ .../azure/web/WebProxyInvokerTests.java | 113 ----------- 5 files changed, 259 insertions(+), 133 deletions(-) rename spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/{WebProxyInvoker.java => AzureWebProxyInvoker.java} (75%) create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/AzureWebProxyInvokerTests.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/TestExecutionContext.java delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/WebProxyInvokerTests.java 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/AzureWebProxyInvoker.java similarity index 75% rename from spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/WebProxyInvoker.java rename to spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/AzureWebProxyInvoker.java index 1c9b53222..9a0aa3811 100644 --- 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/AzureWebProxyInvoker.java @@ -57,9 +57,9 @@ import org.springframework.web.servlet.DispatcherServlet; * @author Oleg Zhurakousky * */ -public class WebProxyInvoker implements FunctionInstanceInjector { +public class AzureWebProxyInvoker implements FunctionInstanceInjector { - private static Log logger = LogFactory.getLog(WebProxyInvoker.class); + private static Log logger = LogFactory.getLog(AzureWebProxyInvoker.class); private ProxyMvc mvc; @@ -69,28 +69,38 @@ public class WebProxyInvoker implements FunctionInstanceInjector { @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 + // System.setProperty("MAIN_CLASS", "oz.spring.petstore.PetStoreSpringAppConfig"); this.initialize(); return (T) this; } - public void initialize() throws ServletException { - Class startClass = FunctionClassUtils.getStartClass(); - AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); - applicationContext.register(startClass); + /** + * Because the getInstance is called by Azure Java Function on every function request we need to cache the Spring + * context initialization on the first function call. + * @throws ServletException error. + */ + private void initialize() throws ServletException { + synchronized (AzureWebProxyInvoker.class.getName()) { + if (this.servletContext == null) { + Class startClass = FunctionClassUtils.getStartClass(); + AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); + applicationContext.register(startClass); - this.servletContext = new ProxyServletContext(); - ServletConfig servletConfig = new ProxyServletConfig(this.servletContext); + 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])); + 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) { + // Note: Currently this is the only way to pass the the application + // route (e.g. the execution REST url) String path = request.getQueryParameters().get("path"); if (!StringUtils.hasText(path)) { @@ -99,16 +109,18 @@ public class WebProxyInvoker implements FunctionInstanceInjector { ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(servletContext, request.getHttpMethod().toString(), path); - if (request.getBody().isPresent()) { - httpRequest.setContent(request.getBody().get().getBytes()); - } + request.getBody().ifPresent(body -> { + httpRequest.setContent(body.getBytes()); + }); if (!CollectionUtils.isEmpty(request.getQueryParameters())) { httpRequest.setParameters(request.getQueryParameters()); } - for (Entry entry : request.getHeaders().entrySet()) { - httpRequest.addHeader(entry.getKey(), entry.getValue()); + if (!CollectionUtils.isEmpty(request.getHeaders())) { + for (Entry entry : request.getHeaders().entrySet()) { + httpRequest.addHeader(entry.getKey(), entry.getValue()); + } } return httpRequest; diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/resources/META-INF/services/com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/resources/META-INF/services/com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector index c679920d5..e01d42baf 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/resources/META-INF/services/com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/resources/META-INF/services/com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector @@ -1 +1 @@ -org.springframework.cloud.function.adapter.azure.web.WebProxyInvoker \ No newline at end of file +org.springframework.cloud.function.adapter.azure.web.AzureWebProxyInvoker \ No newline at end of file diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/AzureWebProxyInvokerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/AzureWebProxyInvokerTests.java new file mode 100644 index 000000000..23702183e --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/AzureWebProxyInvokerTests.java @@ -0,0 +1,180 @@ +/* + * 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.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +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.HttpStatusType; +import org.junit.jupiter.api.Test; + +public class AzureWebProxyInvokerTests { + + @Test + public void test() throws Exception { + System.setProperty("MAIN_CLASS", PetStoreSpringAppConfig.class.getName()); + AzureWebProxyInvoker proxyInvoker = new AzureWebProxyInvoker(); + AzureWebProxyInvoker instance = proxyInvoker.getInstance(AzureWebProxyInvoker.class); + + HttpRequestMessageStub> request = new HttpRequestMessageStub>(); + + request.setHttpMethod(HttpMethod.GET); + request.setQueryParameters(Collections.singletonMap("path", "/pets")); + + request.setBody(Optional.of("{\"id\":\"535932f1-d18b-488a-ad8f-8d50b9678492\"" + + "\"breed\":\"Beagle\",\"name\":\"Murphy\",\"dateOfBirth\":1591682824313}")); + + HttpResponseMessage response = instance.execute(request, new TestExecutionContext("execute")); + + System.out.println(response.getBody()); + + } + + public static class HttpRequestMessageStub implements HttpRequestMessage { + + private URI uri; + private HttpMethod httpMethod; + private Map headers; + private Map queryParameters; + private I body; + + public void setUri(URI uri) { + this.uri = uri; + } + + public void setHttpMethod(HttpMethod httpMethod) { + this.httpMethod = httpMethod; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public void setQueryParameters(Map queryParameters) { + this.queryParameters = queryParameters; + } + + public void setBody(I body) { + this.body = body; + } + + @Override + public URI getUri() { + return this.uri; + } + + @Override + public HttpMethod getHttpMethod() { + return this.httpMethod; + } + + @Override + public Map getHeaders() { + return this.headers; + } + + @Override + public Map getQueryParameters() { + return this.queryParameters; + } + + @Override + public I getBody() { + return this.body; + } + + @Override + public HttpResponseMessage.Builder createResponseBuilder(HttpStatusType status) { + return new BuilderStub().status(status); + } + + @Override + public Builder createResponseBuilder(HttpStatus status) { + return new BuilderStub().status(status); + } + + } + + public static class BuilderStub implements Builder { + + private HttpStatusType status; + private Map headers = new HashMap<>(); + private Object body; + + @Override + public Builder status(HttpStatusType status) { + this.status = status; + return this; + } + + @Override + public Builder header(String key, String value) { + headers.put(key, value); + return this; + } + + @Override + public Builder body(Object body) { + this.body = body; + return this; + } + + @Override + public HttpResponseMessage build() { + return new HttpResponseMessageStub(this.status, this.headers, this.body); + } + + } + + public static class HttpResponseMessageStub implements HttpResponseMessage { + + private HttpStatusType status; + private Map headers = new HashMap<>(); + private Object body; + + HttpResponseMessageStub(HttpStatusType status, Map headers, + Object body) { + this.status = status; + this.headers = headers; + this.body = body; + } + + @Override + public HttpStatusType getStatus() { + return this.status; + } + + @Override + public String getHeader(String key) { + return this.headers.get(key); + } + + @Override + public Object getBody() { + return this.body; + } + + } +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/TestExecutionContext.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/TestExecutionContext.java new file mode 100644 index 000000000..bbdf32f56 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/TestExecutionContext.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2019 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.UUID; +import java.util.logging.Logger; + +import com.microsoft.azure.functions.ExecutionContext; + +public class TestExecutionContext implements ExecutionContext { + + private String name; + + public TestExecutionContext(String name) { + this.name = name; + } + + @Override + public Logger getLogger() { + return Logger.getLogger(TestExecutionContext.class.getName()); + } + + @Override + public String getInvocationId() { + return UUID.randomUUID().toString(); + } + + @Override + public String getFunctionName() { + return this.name; + } + +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/WebProxyInvokerTests.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/WebProxyInvokerTests.java deleted file mode 100644 index 8616272c3..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/test/java/org/springframework/cloud/function/adapter/azure/web/WebProxyInvokerTests.java +++ /dev/null @@ -1,113 +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.azure.web; - -public class WebProxyInvokerTests { - - static String apiGatewayEvent = "{\n" + - " \"resource\": \"/pets\",\n" + - " \"path\": \"/pets/64f56d94-a059-4111-9eeb-ee0c994b1ba8?foo=bar\",\n" + - " \"httpMethod\": \"GET\",\n" + - " \"headers\": {\n" + - " \"accept\": \"*/*\",\n" + - " \"content-type\": \"application/json\",\n" + - " \"Host\": \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\",\n" + - " \"User-Agent\": \"curl/7.54.0\",\n" + - " \"X-Amzn-Trace-Id\": \"Root=1-5ece339e-e0595766066d703ec70f1522\",\n" + - " \"X-Forwarded-For\": \"90.37.8.133\",\n" + - " \"X-Forwarded-Port\": \"443\",\n" + - " \"X-Forwarded-Proto\": \"https\"\n" + - " },\n" + - " \"multiValueHeaders\": {\n" + - " \"accept\": [\n" + - " \"*/*\"\n" + - " ],\n" + - " \"content-type\": [\n" + - " \"application/json\"\n" + - " ],\n" + - " \"Host\": [\n" + - " \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\"\n" + - " ],\n" + - " \"User-Agent\": [\n" + - " \"curl/7.54.0\"\n" + - " ],\n" + - " \"X-Amzn-Trace-Id\": [\n" + - " \"Root=1-5ece339e-e0595766066d703ec70f1522\"\n" + - " ],\n" + - " \"X-Forwarded-For\": [\n" + - " \"90.37.8.133\"\n" + - " ],\n" + - " \"X-Forwarded-Port\": [\n" + - " \"443\"\n" + - " ],\n" + - " \"X-Forwarded-Proto\": [\n" + - " \"https\"\n" + - " ]\n" + - " },\n" + - " \"queryStringParameters\": null,\n" + - " \"multiValueQueryStringParameters\": null,\n" + - " \"pathParameters\": null,\n" + - " \"stageVariables\": null,\n" + - " \"requestContext\": {\n" + - " \"resourceId\": \"qf0io6\",\n" + - " \"resourcePath\": \"/pets\",\n" + - " \"httpMethod\": \"GET\",\n" + - " \"extendedRequestId\": \"NL0A1EokCGYFZOA=\",\n" + - " \"requestTime\": \"27/May/2020:09:32:14 +0000\",\n" + - " \"path\": \"/test/uppercase2\",\n" + - " \"accountId\": \"123456789098\",\n" + - " \"protocol\": \"HTTP/1.1\",\n" + - " \"stage\": \"test\",\n" + - " \"domainPrefix\": \"fhul32ccy2\",\n" + - " \"requestTimeEpoch\": 1590571934872,\n" + - " \"requestId\": \"b96500aa-f92a-43c3-9360-868ba4053a00\",\n" + - " \"identity\": {\n" + - " \"cognitoIdentityPoolId\": null,\n" + - " \"accountId\": null,\n" + - " \"cognitoIdentityId\": null,\n" + - " \"caller\": null,\n" + - " \"sourceIp\": \"90.37.8.133\",\n" + - " \"principalOrgId\": null,\n" + - " \"accessKey\": null,\n" + - " \"cognitoAuthenticationType\": null,\n" + - " \"cognitoAuthenticationProvider\": null,\n" + - " \"userArn\": null,\n" + - " \"userAgent\": \"curl/7.54.0\",\n" + - " \"user\": null\n" + - " },\n" + - " \"domainName\": \"fhul32ccy2.execute-api.eu-west-3.amazonaws.com\",\n" + - " \"apiId\": \"fhul32ccy2\"\n" + - " },\n" + - " \"body\":\"\",\n" + - " \"isBase64Encoded\": false\n" + - "}"; - - // @Test - // public void testApiGatewayProxy() throws Exception { - // System.setProperty("MAIN_CLASS", PetStoreSpringAppConfig.class.getName()); - // WebProxyInvoker invoker = new WebProxyInvoker(); - - // InputStream targetStream = new ByteArrayInputStream(this.apiGatewayEvent.getBytes()); - // ByteArrayOutputStream output = new ByteArrayOutputStream(); - // invoker.handleRequest(targetStream, output); - - // ObjectMapper mapper = new ObjectMapper(); - // System.out.println("RESULT: =======> " + new String(output.toByteArray())); - // Map result = mapper.readValue(output.toByteArray(), Map.class); - // System.out.println(result); - // } -}