From 017093a8b5240614fdbb7d0b00829d531219a3ce Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 7 Mar 2023 14:30:00 +0100 Subject: [PATCH] Fix Request/Response attributes, add more assertions in tests --- .../pom.xml | 3 +- .../adapter/aws/web/WebProxyInvoker.java | 77 ++--- .../adapter/aws/web/WebProxyInvokerTests.java | 213 ++++++++----- .../pom.xml | 2 +- .../serverless/web/HeaderValueHolder.java | 78 ----- .../web/ProxyHttpServletRequest.java | 282 +++++------------- .../web/ProxyHttpServletResponse.java | 193 ++---------- .../function/serverless/web/ProxyMvc.java | 9 + .../web/RequestResponseTests.java | 9 +- .../{adapter/aws/web => test/app}/Pet.java | 2 +- .../aws/web => test/app}/PetData.java | 2 +- .../app}/PetStoreSpringAppConfig.java | 2 +- .../aws/web => test/app}/PetsController.java | 2 +- spring-cloud-function-dependencies/pom.xml | 5 + 14 files changed, 282 insertions(+), 597 deletions(-) delete mode 100644 spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/HeaderValueHolder.java rename spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/{adapter/aws => serverless}/web/RequestResponseTests.java (92%) rename spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/{adapter/aws/web => test/app}/Pet.java (95%) rename spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/{adapter/aws/web => test/app}/PetData.java (98%) rename spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/{adapter/aws/web => test/app}/PetStoreSpringAppConfig.java (97%) rename spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/{adapter/aws/web => test/app}/PetsController.java (97%) 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 index 51609db00..46517981e 100644 --- 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 @@ -10,7 +10,7 @@ org.springframework.cloud spring-cloud-function-adapter-parent - 3.2.9-SNAPSHOT + 3.2.10-SNAPSHOT UTF-8 @@ -25,7 +25,6 @@ org.springframework.cloud spring-cloud-function-serverless-web - ${project.version} org.springframework 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 index be5809b23..109d9008d 100644 --- 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 @@ -19,31 +19,25 @@ 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.cloud.function.serverless.web.ProxyHttpServletRequest; import org.springframework.cloud.function.serverless.web.ProxyHttpServletResponse; import org.springframework.cloud.function.serverless.web.ProxyMvc; -import org.springframework.cloud.function.serverless.web.ProxyServletContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.servlet.DispatcherServlet; /** * @@ -74,10 +68,9 @@ public class WebProxyInvoker { */ @SuppressWarnings("unchecked") private HttpServletRequest prepareRequest(InputStream input) throws IOException { - Map request = mapper.readValue(input, Map.class); - if (logger.isDebugEnabled()) { - logger.debug("Request: " + request); + if (logger.isInfoEnabled()) { + logger.info("Request: " + request); } String httpMethod = (String) request.get("httpMethod"); String path = (String) request.get("path"); @@ -86,18 +79,29 @@ public class WebProxyInvoker { logger.debug("path: " + path); } ProxyHttpServletRequest httpRequest = new ProxyHttpServletRequest(null, httpMethod, path); + + // CONTENT 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")); + + // REQUEST PARAM + if (request.get("multiValueQueryStringParameters") != null) { + Map> parameters = (Map>) request.get("multiValueQueryStringParameters"); + for (Entry> parameter : parameters.entrySet()) { + httpRequest.setParameter(parameter.getKey(), parameter.getValue().toArray(new String[] {})); + } } - Map headers = (Map) request.get("headers"); - headers.putAll((Map) request.get("multiValueHeaders")); - for (Entry entry : headers.entrySet()) { - httpRequest.addHeader(entry.getKey(), entry.getValue()); + // HEADERS + Map> headers = (Map>) request.get("multiValueHeaders"); + HttpHeaders httpHeaders = new HttpHeaders(); + for (Entry> entry : headers.entrySet()) { + // TODO may need to do some header formatting + httpHeaders.addAll(entry.getKey(), entry.getValue()); } + httpRequest.setHeaders(httpHeaders); + return httpRequest; } @@ -121,47 +125,20 @@ public class WebProxyInvoker { } Map apiGatewayResponseStructure = new HashMap(); apiGatewayResponseStructure.put("isBase64Encoded", false); - apiGatewayResponseStructure.put("statusCode", 200); + apiGatewayResponseStructure.put("statusCode", HttpStatus.OK.value()); apiGatewayResponseStructure.put("body", responseString); Map> multiValueHeaders = new HashMap<>(); + Map headers = new HashMap<>(); for (String headerName : httpResponse.getHeaderNames()) { multiValueHeaders.put(headerName, httpResponse.getHeaders(headerName)); + headers.put(headerName, httpResponse.getHeaders(headerName).toString()); } - // TODO investigate why AWS doesn't like List as value -// apiGatewayResponseStructure.put("headers", multiValueHeaders); + apiGatewayResponseStructure.put("multiValueHeaders", multiValueHeaders); + apiGatewayResponseStructure.put("headers", headers); 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-web/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 index 0054cc48f..764e73b34 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/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 @@ -19,106 +19,157 @@ package org.springframework.cloud.function.adapter.aws.web; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest; +import org.springframework.http.HttpHeaders; +import org.springframework.util.ReflectionUtils; +import static org.assertj.core.api.Assertions.assertThat; - +/** + * @author Oleg Zhurakousky + */ 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" + - "}"; + static String API_GATEWAY_EVENT = "{\n" + + " \"version\": \"1.0\",\n" + + " \"resource\": \"$default\",\n" + + " \"path\": \"/pets\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"headers\": {\n" + + " \"Content-Length\": \"45\",\n" + + " \"Content-Type\": \"application/json\",\n" + + " \"Host\": \"i76bfhczs0.execute-api.eu-west-3.amazonaws.com\",\n" + + " \"User-Agent\": \"curl/7.79.1\",\n" + + " \"X-Amzn-Trace-Id\": \"Root=1-64087690-2151375b219d3ba3389ea84e\",\n" + + " \"X-Forwarded-For\": \"109.210.252.44\",\n" + + " \"X-Forwarded-Port\": \"443\",\n" + + " \"X-Forwarded-Proto\": \"https\",\n" + + " \"accept\": \"*/*\"\n" + + " },\n" + + " \"multiValueHeaders\": {\n" + + " \"Content-Length\": [\n" + + " \"45\"\n" + + " ],\n" + + " \"Content-Type\": [\n" + + " \"application/json\"\n" + + " ],\n" + + " \"Host\": [\n" + + " \"i76bfhczs0.execute-api.eu-west-3.amazonaws.com\"\n" + + " ],\n" + + " \"User-Agent\": [\n" + + " \"curl/7.79.1\"\n" + + " ],\n" + + " \"X-Amzn-Trace-Id\": [\n" + + " \"Root=1-64087690-2151375b219d3ba3389ea84e\"\n" + + " ],\n" + + " \"X-Forwarded-For\": [\n" + + " \"109.210.252.44\"\n" + + " ],\n" + + " \"X-Forwarded-Port\": [\n" + + " \"443\"\n" + + " ],\n" + + " \"X-Forwarded-Proto\": [\n" + + " \"https\"\n" + + " ],\n" + + " \"accept\": [\n" + + " \"*/*\"\n" + + " ]\n" + + " },\n" + + " \"queryStringParameters\": {\n" + + " \"abc\": \"xyz\",\n" + + " \"foo\": \"baz\"\n" + + " },\n" + + " \"multiValueQueryStringParameters\": {\n" + + " \"abc\": [\n" + + " \"xyz\"\n" + + " ],\n" + + " \"foo\": [\n" + + " \"bar\",\n" + + " \"baz\"\n" + + " ]\n" + + " },\n" + + " \"requestContext\": {\n" + + " \"accountId\": \"123456789098\",\n" + + " \"apiId\": \"i76bfhczs0\",\n" + + " \"domainName\": \"i76bfhczs0.execute-api.eu-west-3.amazonaws.com\",\n" + + " \"domainPrefix\": \"i76bfhczs0\",\n" + + " \"extendedRequestId\": \"Bdd2ngt5iGYEMIg=\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"identity\": {\n" + + " \"accessKey\": null,\n" + + " \"accountId\": null,\n" + + " \"caller\": null,\n" + + " \"cognitoAmr\": null,\n" + + " \"cognitoAuthenticationProvider\": null,\n" + + " \"cognitoAuthenticationType\": null,\n" + + " \"cognitoIdentityId\": null,\n" + + " \"cognitoIdentityPoolId\": null,\n" + + " \"principalOrgId\": null,\n" + + " \"sourceIp\": \"109.210.252.44\",\n" + + " \"user\": null,\n" + + " \"userAgent\": \"curl/7.79.1\",\n" + + " \"userArn\": null\n" + + " },\n" + + " \"path\": \"/pets\",\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"requestId\": \"Bdd2ngt5iGYEMIg=\",\n" + + " \"requestTime\": \"08/Mar/2023:11:50:40 +0000\",\n" + + " \"requestTimeEpoch\": 1678276240455,\n" + + " \"resourceId\": \"$default\",\n" + + " \"resourcePath\": \"$default\",\n" + + " \"stage\": \"$default\"\n" + + " },\n" + + " \"pathParameters\": null,\n" + + " \"stageVariables\": null,\n" + + " \"body\": \"{\\\"id\\\":\\\"123\\\",\\\"breed\\\":\\\"Datsun\\\",\\\"name\\\":\\\"Donald\\\"}\",\n" + + " \"isBase64Encoded\": false\n" + + "}"; + + + + + @Test + public void validateHttpServletRequestConstruction() throws Exception { + System.setProperty("MAIN_CLASS", PetStoreSpringAppConfig.class.getName()); + WebProxyInvoker invoker = new WebProxyInvoker(); + InputStream targetStream = new ByteArrayInputStream(API_GATEWAY_EVENT.getBytes()); + Method prepareRequest = ReflectionUtils.findMethod(WebProxyInvoker.class, "prepareRequest", InputStream.class); + prepareRequest.setAccessible(true); + ProxyHttpServletRequest request = (ProxyHttpServletRequest) prepareRequest.invoke(invoker, targetStream); + + assertThat(request.getContentType()).isEqualTo("application/json"); + assertThat(request.getParameterValues("foo").length).isEqualTo(2); + assertThat(request.getParameterValues("foo")[0]).isEqualTo("bar"); + assertThat(request.getParameterValues("abc").length).isEqualTo(1); + assertThat(request.getParameterValues("abc")[0]).isEqualTo("xyz"); + assertThat(request.getHeaders(HttpHeaders.CONTENT_TYPE).nextElement()).isEqualTo("application/json"); + assertThat(request.getContentAsString()).isEqualTo("{\"id\":\"123\",\"breed\":\"Datsun\",\"name\":\"Donald\"}"); + assertThat(request.getContentLength()).isEqualTo(45); + assertThat(request.getMethod()).isEqualTo("POST"); + } @Test public void testApiGatewayProxy() throws Exception { System.setProperty("MAIN_CLASS", PetStoreSpringAppConfig.class.getName()); WebProxyInvoker invoker = new WebProxyInvoker(); - InputStream targetStream = new ByteArrayInputStream(this.apiGatewayEvent.getBytes()); + InputStream targetStream = new ByteArrayInputStream(API_GATEWAY_EVENT.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); + assertThat((boolean) result.get("isBase64Encoded")).isFalse(); + assertThat(((Map>) result.get("multiValueHeaders")).get("Content-Type").get(0)).isEqualTo("application/json"); + assertThat(result.get("statusCode")).isEqualTo(200); + Pet pet = mapper.readValue((String) result.get("body"), Pet.class); + assertThat(pet.getName()).isEqualTo("Donald"); } } diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml index 9fdb644a0..d8837b163 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml @@ -10,7 +10,7 @@ org.springframework.cloud spring-cloud-function-adapter-parent - 3.2.9-SNAPSHOT + 3.2.10-SNAPSHOT UTF-8 diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/HeaderValueHolder.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/HeaderValueHolder.java deleted file mode 100644 index b654d2d3c..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/HeaderValueHolder.java +++ /dev/null @@ -1,78 +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.serverless.web; - -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 values = new LinkedList<>(); - - void setValue(@Nullable Object value) { - this.values.clear(); - if (value != null) { - this.values.add(value); - } - } - - void addValue(Object value) { - this.values.add(value); - } - - void addValues(Collection values) { - this.values.addAll(values); - } - - void addValueArray(Object values) { - CollectionUtils.mergeArrayIntoCollection(values, this.values); - } - - List getValues() { - return Collections.unmodifiableList(this.values); - } - - List getStringValues() { - List stringList = new ArrayList<>(this.values.size()); - for (Object value : this.values) { - stringList.add(value.toString()); - } - return Collections.unmodifiableList(stringList); - } - - @Nullable - Object getValue() { - return (!this.values.isEmpty() ? this.values.get(0) : null); - } - - @Nullable - String getStringValue() { - return (!this.values.isEmpty() ? String.valueOf(this.values.get(0)) : null); - } - - @Override - public String toString() { - return this.values.toString(); - } - -} diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletRequest.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletRequest.java index d5af925c7..5d793a3ce 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletRequest.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletRequest.java @@ -24,6 +24,7 @@ import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.security.Principal; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -58,18 +59,19 @@ import javax.servlet.http.HttpUpgradeHandler; import javax.servlet.http.Part; import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.LinkedCaseInsensitiveMap; +import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; +/** + * + * @author Oleg Zhurakousky + * + */ public class ProxyHttpServletRequest implements HttpServletRequest { - private static final String CHARSET_PREFIX = "charset="; - private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); private static final BufferedReader EMPTY_BUFFERED_READER = new BufferedReader(new StringReader("")); @@ -97,9 +99,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest { @Nullable private byte[] content; - @Nullable - private String contentType; - @Nullable private ServletInputStream inputStream; @@ -117,17 +116,13 @@ public class ProxyHttpServletRequest implements HttpServletRequest { private DispatcherType dispatcherType = DispatcherType.REQUEST; - // --------------------------------------------------------------------- - // HttpServletRequest properties - // --------------------------------------------------------------------- - @Nullable private String authType; @Nullable private Cookie[] cookies; - private final Map headers = new LinkedCaseInsensitiveMap<>(); + private final HttpHeaders headers = new HttpHeaders(); @Nullable private String method; @@ -202,18 +197,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest { @Override public void setCharacterEncoding(@Nullable String characterEncoding) { this.characterEncoding = characterEncoding; - updateContentTypeHeader(); - } - - private void updateContentTypeHeader() { - if (StringUtils.hasLength(this.contentType)) { - String value = this.contentType; - if (StringUtils.hasLength(this.characterEncoding) - && !this.contentType.toLowerCase().contains(CHARSET_PREFIX)) { - value += ';' + CHARSET_PREFIX + this.characterEncoding; - } - doAddHeaderValue(HttpHeaders.CONTENT_TYPE, value, true); - } } /** @@ -262,13 +245,13 @@ public class ProxyHttpServletRequest implements HttpServletRequest { */ @Nullable public String getContentAsString() throws IllegalStateException, UnsupportedEncodingException { - Assert.state(this.characterEncoding != null, "Cannot get content as a String for a null character encoding. " - + "Consider setting the characterEncoding in the request."); +// Assert.state(this.characterEncoding != null, "Cannot get content as a String for a null character encoding. " +// + "Consider setting the characterEncoding in the request."); if (this.content == null) { return null; } - return new String(this.content, this.characterEncoding); + return new String(this.content, StandardCharsets.UTF_8); } @Override @@ -282,29 +265,13 @@ public class ProxyHttpServletRequest implements HttpServletRequest { } public void setContentType(@Nullable String contentType) { - this.contentType = contentType; - if (contentType != null) { - try { - MediaType mediaType = MediaType.parseMediaType(contentType); - if (mediaType.getCharset() != null) { - this.characterEncoding = mediaType.getCharset().name(); - } - } - catch (IllegalArgumentException ex) { - // Try to get charset value anyway - int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX); - if (charsetIndex != -1) { - this.characterEncoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length()); - } - } - updateContentTypeHeader(); - } + this.headers.set(HttpHeaders.CONTENT_TYPE, contentType); } @Override @Nullable public String getContentType() { - return this.contentType; + return this.headers.containsKey(HttpHeaders.CONTENT_TYPE) ? this.headers.get(HttpHeaders.CONTENT_TYPE).get(0) : null; } @Override @@ -564,37 +531,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest { this.attributes.clear(); } - /** - * Add a new preferred locale, before any existing locales. - * - * @see #setPreferredLocales - */ - public void addPreferredLocale(Locale locale) { - Assert.notNull(locale, "Locale must not be null"); - this.locales.addFirst(locale); - updateAcceptLanguageHeader(); - } - - /** - * Set the list of preferred locales, in descending order, effectively replacing - * any existing locales. - * - * @since 3.2 - * @see #addPreferredLocale - */ - public void setPreferredLocales(List locales) { - Assert.notEmpty(locales, "Locale list must not be empty"); - this.locales.clear(); - this.locales.addAll(locales); - updateAcceptLanguageHeader(); - } - - private void updateAcceptLanguageHeader() { - HttpHeaders headers = new HttpHeaders(); - headers.setAcceptLanguageAsLocales(this.locales); - doAddHeaderValue(HttpHeaders.ACCEPT_LANGUAGE, headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE), true); - } - /** * Return the first preferred {@linkplain Locale locale} configured in this mock * request. @@ -758,104 +694,86 @@ public class ProxyHttpServletRequest implements HttpServletRequest { return this.cookies; } - /** - * Add an HTTP header entry for the given name. - *

- * While this method can take any {@code Object} as a parameter, it is - * recommended to use the following types: - *

    - *
  • String or any Object to be converted using {@code toString()}; see - * {@link #getHeader}.
  • - *
  • String, Number, or Date for date headers; see - * {@link #getDateHeader}.
  • - *
  • String or Number for integer headers; see {@link #getIntHeader}.
  • - *
  • {@code String[]} or {@code Collection} for multiple values; see - * {@link #getHeaders}.
  • - *
- * - * @see #getHeaderNames - * @see #getHeaders - * @see #getHeader - * @see #getDateHeader - */ - public void addHeader(String name, Object value) { - if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name) && !this.headers.containsKey(HttpHeaders.CONTENT_TYPE)) { - setContentType(value.toString()); - } - else if (HttpHeaders.ACCEPT_LANGUAGE.equalsIgnoreCase(name) - && !this.headers.containsKey(HttpHeaders.ACCEPT_LANGUAGE)) { - try { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ACCEPT_LANGUAGE, value.toString()); - List locales = headers.getAcceptLanguageAsLocales(); - this.locales.clear(); - this.locales.addAll(locales); - if (this.locales.isEmpty()) { - this.locales.add(Locale.ENGLISH); - } + @Override + @Nullable + public String getHeader(String name) { + return this.headers.containsKey(name) ? this.headers.get(name).toString() : null; + } + + @Override + public Enumeration getHeaders(String name) { + return Collections.enumeration(this.headers.containsKey(name) ? this.headers.get(name) : new LinkedList<>()); + } + + @Override + public Enumeration getHeaderNames() { + return Collections.enumeration(this.headers.keySet()); + } + + public void setHeader(String name, @Nullable String value) { + this.headers.set(name, value); + } + + public void addHeader(String name, @Nullable String value) { + this.headers.add(name, value); + } + + public void addHeaders(MultiValueMap headers) { + this.headers.addAll(headers); + } + + public void setHeaders(MultiValueMap headers) { + this.headers.clear(); + this.addHeaders(headers); + } + + @Override + public int getIntHeader(String name) { + List header = this.headers.get(name); + if (!CollectionUtils.isEmpty(header) && header.size() == 1) { + Object value = header.get(0); + if (value instanceof Number) { + return ((Number) value).intValue(); } - catch (IllegalArgumentException ex) { - // Invalid Accept-Language format -> just store plain header + else if (value instanceof String) { + return Integer.parseInt((String) value); + } + else if (value != null) { + throw new NumberFormatException("Value for header '" + name + "' is not a Number: " + value); + } + else { + return -1; } - doAddHeaderValue(name, value, true); } else { - doAddHeaderValue(name, value, false); + return -1; } } - private void doAddHeaderValue(String name, @Nullable Object value, boolean replace) { - HeaderValueHolder header = this.headers.get(name); - Assert.notNull(value, "Header value must not be null"); - if (header == null || replace) { - header = new HeaderValueHolder(); - this.headers.put(name, header); - } - if (value instanceof Collection) { - header.addValues((Collection) value); - } - else if (value.getClass().isArray()) { - header.addValueArray(value); - } - else { - header.addValue(value); - } - } - - /** - * Return the long timestamp for the date header with the given {@code name}. - *

- * If the internal value representation is a String, this method will try to - * parse it as a date using the supported date formats: - *

    - *
  • "EEE, dd MMM yyyy HH:mm:ss zzz"
  • - *
  • "EEE, dd-MMM-yy HH:mm:ss zzz"
  • - *
  • "EEE MMM dd HH:mm:ss yyyy"
  • - *
- * - * @param name the header name - * @see Section - * 7.1.1.1 of RFC 7231 - */ @Override public long getDateHeader(String name) { - HeaderValueHolder header = this.headers.get(name); - Object value = (header != null ? header.getValue() : null); - if (value instanceof Date) { - return ((Date) value).getTime(); - } - else if (value instanceof Number) { - return ((Number) value).longValue(); - } - else if (value instanceof String) { - return parseDateHeader(name, (String) value); - } - else if (value != null) { - throw new IllegalArgumentException( - "Value for header '" + name + "' is not a Date, Number, or String: " + value); + List header = this.headers.get(name); + if (!CollectionUtils.isEmpty(header) && header.size() == 1) { + Object value = header.get(0); + if (value instanceof Date) { + return ((Date) value).getTime(); + } + else if (value instanceof Number) { + return ((Number) value).longValue(); + } + else if (value instanceof String) { + return parseDateHeader(name, (String) value); + } + else if (value != null) { + throw new IllegalArgumentException( + "Value for header '" + name + "' is not a Date, Number, or String: " + value); + } + else { + return -1L; + } } else { - return -1L; + return -1; } } @@ -873,42 +791,6 @@ public class ProxyHttpServletRequest implements HttpServletRequest { throw new IllegalArgumentException("Cannot parse date value '" + value + "' for '" + name + "' header"); } - @Override - @Nullable - public String getHeader(String name) { - HeaderValueHolder header = this.headers.get(name); - return (header != null ? header.getStringValue() : null); - } - - @Override - public Enumeration getHeaders(String name) { - HeaderValueHolder header = this.headers.get(name); - return Collections.enumeration(header != null ? header.getStringValues() : new LinkedList<>()); - } - - @Override - public Enumeration getHeaderNames() { - return Collections.enumeration(this.headers.keySet()); - } - - @Override - public int getIntHeader(String name) { - HeaderValueHolder header = this.headers.get(name); - Object value = (header != null ? header.getValue() : null); - if (value instanceof Number) { - return ((Number) value).intValue(); - } - else if (value instanceof String) { - return Integer.parseInt((String) value); - } - else if (value != null) { - throw new NumberFormatException("Value for header '" + name + "' is not a Number: " + value); - } - else { - return -1; - } - } - public void setMethod(@Nullable String method) { this.method = method; } diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletResponse.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletResponse.java index 3531a8330..e57251e5a 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletResponse.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletResponse.java @@ -22,16 +22,12 @@ import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.text.DateFormat; -import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; @@ -41,86 +37,43 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.web.util.WebUtils; +/** + * + * @author Oleg Zhurakousky + * + */ public class ProxyHttpServletResponse implements HttpServletResponse { - private static final String CHARSET_PREFIX = "charset="; - private static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz"; - private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); - - // --------------------------------------------------------------------- - // ServletResponse properties - // --------------------------------------------------------------------- - - private boolean outputStreamAccessAllowed = true; - private String defaultCharacterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; private String characterEncoding = this.defaultCharacterEncoding; - /** - * {@code true} if the character encoding has been explicitly set through - * {@link HttpServletResponse} methods or through a {@code charset} parameter on - * the {@code Content-Type}. - */ - private boolean characterEncodingSet = false; - private final ByteArrayOutputStream content = new ByteArrayOutputStream(1024); private final ServletOutputStream outputStream = new ResponseServletOutputStream(); - private long contentLength = 0; - private String contentType; private int bufferSize = 4096; - private boolean committed; - private Locale locale = Locale.getDefault(); - // --------------------------------------------------------------------- - // HttpServletResponse properties - // --------------------------------------------------------------------- - private final List cookies = new ArrayList<>(); - private final Map headers = new LinkedCaseInsensitiveMap<>(); + private final HttpHeaders headers = new HttpHeaders(); private int status = HttpServletResponse.SC_OK; @Nullable private String errorMessage; - // --------------------------------------------------------------------- - // ServletResponse interface - // --------------------------------------------------------------------- - @Override public void setCharacterEncoding(String characterEncoding) { - setExplicitCharacterEncoding(characterEncoding); - updateContentTypePropertyAndHeader(); - } - - private void setExplicitCharacterEncoding(String characterEncoding) { - Assert.notNull(characterEncoding, "'characterEncoding' must not be null"); this.characterEncoding = characterEncoding; - this.characterEncodingSet = true; - } - - private void updateContentTypePropertyAndHeader() { - if (this.contentType != null) { - String value = this.contentType; - if (this.characterEncodingSet && !value.toLowerCase().contains(CHARSET_PREFIX)) { - value += ';' + CHARSET_PREFIX + getCharacterEncoding(); - this.contentType = value; - } - doAddHeaderValue(HttpHeaders.CONTENT_TYPE, value, true); - } } @Override @@ -130,7 +83,6 @@ public class ProxyHttpServletResponse implements HttpServletResponse { @Override public ServletOutputStream getOutputStream() { - Assert.state(this.outputStreamAccessAllowed, "OutputStream access not allowed"); return this.outputStream; } @@ -162,24 +114,8 @@ public class ProxyHttpServletResponse implements HttpServletResponse { return this.content.toString(getCharacterEncoding()); } - /** - * Get the content of the response body as a {@code String}, using the provided - * {@code fallbackCharset} if no charset has been explicitly defined and - * otherwise using the charset specified for the response by the application, - * either through {@link HttpServletResponse} methods or through a charset - * parameter on the {@code Content-Type}. - * - * @return the content as a {@code String} - * @throws UnsupportedEncodingException if the character encoding is not - * supported - * @since 5.2 - * @see #getContentAsString() - * @see #setCharacterEncoding(String) - * @see #setContentType(String) - */ public String getContentAsString(Charset fallbackCharset) throws UnsupportedEncodingException { - String charsetName = (this.characterEncodingSet ? getCharacterEncoding() : fallbackCharset.name()); - return this.content.toString(charsetName); + return this.content.toString(getCharacterEncoding()); } @Override @@ -224,21 +160,15 @@ public class ProxyHttpServletResponse implements HttpServletResponse { this.content.reset(); } - public void setCommitted(boolean committed) { - this.committed = committed; - } - @Override public boolean isCommitted() { - return this.committed; + return true; } @Override public void reset() { resetBuffer(); this.characterEncoding = this.defaultCharacterEncoding; - this.characterEncodingSet = false; - this.contentLength = 0; this.contentType = null; this.locale = Locale.getDefault(); this.cookies.clear(); @@ -249,16 +179,11 @@ public class ProxyHttpServletResponse implements HttpServletResponse { @Override public void setLocale(@Nullable Locale locale) { - // Although the Javadoc for javax.servlet.ServletResponse.setLocale(Locale) does - // not - // state how a null value for the supplied Locale should be handled, both Tomcat - // and - // Jetty simply ignore a null value. So we do the same here. if (locale == null) { return; } this.locale = locale; - doAddHeaderValue(HttpHeaders.CONTENT_LANGUAGE, locale.toLanguageTag(), true); + this.headers.add(HttpHeaders.CONTENT_LANGUAGE, locale.toLanguageTag()); } @Override @@ -314,8 +239,7 @@ public class ProxyHttpServletResponse implements HttpServletResponse { @Override @Nullable public String getHeader(String name) { - HeaderValueHolder header = this.headers.get(name); - return (header != null ? header.getStringValue() : null); + return this.headers.containsKey(name) ? this.headers.get(name).toString() : null; } /** @@ -331,13 +255,7 @@ public class ProxyHttpServletResponse implements HttpServletResponse { */ @Override public List getHeaders(String name) { - HeaderValueHolder header = this.headers.get(name); - if (header != null) { - return header.getStringValues(); - } - else { - return Collections.emptyList(); - } + return this.headers.get(name); } /** @@ -350,24 +268,7 @@ public class ProxyHttpServletResponse implements HttpServletResponse { */ @Nullable public Object getHeaderValue(String name) { - HeaderValueHolder header = this.headers.get(name); - return (header != null ? header.getValue() : null); - } - - /** - * Return all values for the given header as a List of value objects. - * - * @param name the name of the header - * @return the associated header values, or an empty List if none - */ - public List getHeaderValues(String name) { - HeaderValueHolder header = this.headers.get(name); - if (header != null) { - return header.getValues(); - } - else { - return Collections.emptyList(); - } + return this.headers.containsKey(name) ? this.headers.get(name).get(0) : null; } /** @@ -411,14 +312,12 @@ public class ProxyHttpServletResponse implements HttpServletResponse { Assert.state(!isCommitted(), "Cannot set error status - response is already committed"); this.status = status; this.errorMessage = errorMessage; - setCommitted(true); } @Override public void sendError(int status) throws IOException { Assert.state(!isCommitted(), "Cannot set error status - response is already committed"); this.status = status; - setCommitted(true); } @Override @@ -427,7 +326,6 @@ public class ProxyHttpServletResponse implements HttpServletResponse { Assert.notNull(url, "Redirect URL must not be null"); setHeader(HttpHeaders.LOCATION, url); setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); - setCommitted(true); } @Nullable @@ -437,25 +335,12 @@ public class ProxyHttpServletResponse implements HttpServletResponse { @Override public void setDateHeader(String name, long value) { - setHeaderValue(name, formatDate(value)); + this.headers.set(name, formatDate(value)); } @Override public void addDateHeader(String name, long value) { - addHeaderValue(name, formatDate(value)); - } - - public long getDateHeader(String name) { - String headerValue = getHeader(name); - if (headerValue == null) { - return -1; - } - try { - return newDateFormat().parse(getHeader(name)).getTime(); - } - catch (ParseException ex) { - throw new IllegalArgumentException("Value for header '" + name + "' is not a valid Date: " + headerValue); - } + this.headers.add(name, formatDate(value)); } private String formatDate(long date) { @@ -464,55 +349,27 @@ public class ProxyHttpServletResponse implements HttpServletResponse { private DateFormat newDateFormat() { SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.US); - dateFormat.setTimeZone(GMT); return dateFormat; } @Override public void setHeader(String name, @Nullable String value) { - setHeaderValue(name, value); + this.headers.set(name, value); } @Override public void addHeader(String name, @Nullable String value) { - addHeaderValue(name, value); + this.headers.add(name, value); } @Override public void setIntHeader(String name, int value) { - setHeaderValue(name, value); + this.headers.set(name, String.valueOf(value)); } @Override public void addIntHeader(String name, int value) { - addHeaderValue(name, value); - } - - private void setHeaderValue(String name, @Nullable Object value) { - if (value == null) { - return; - } - boolean replaceHeader = true; - doAddHeaderValue(name, value, replaceHeader); - } - - private void addHeaderValue(String name, @Nullable Object value) { - if (value == null) { - return; - } - boolean replaceHeader = false; - doAddHeaderValue(name, value, replaceHeader); - } - - private void doAddHeaderValue(String name, Object value, boolean replace) { - Assert.notNull(value, "Header value must not be null"); - HeaderValueHolder header = this.headers.computeIfAbsent(name, key -> new HeaderValueHolder()); - if (replace) { - header.setValue(value); - } - else { - header.addValue(value); - } + this.headers.add(name, String.valueOf(value)); } @Override @@ -538,20 +395,6 @@ public class ProxyHttpServletResponse implements HttpServletResponse { return this.errorMessage; } - // --------------------------------------------------------------------- - // Methods for MockRequestDispatcher - // --------------------------------------------------------------------- - - @Nullable - public String getForwardedUrl() { - throw new UnsupportedOperationException(); - } - - @Nullable - public String getIncludedUrl() { - throw new UnsupportedOperationException(); - } - /** * Inner class that adapts the ServletOutputStream to mark the response as * committed once the buffer size is exceeded. diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java index 519fa5404..51fd12b55 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java @@ -44,6 +44,15 @@ import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; +/** + * Represents the main entry point into interaction with web application over light-weight proxy. + * After creating an instance via {@link #INSTANCE(Class...)} operation which will initialize the provided component + * classes of your web application (effectively starting your web application less web server), + * you use {@link #service(HttpServletRequest, HttpServletResponse)} operation to send request and receive a response. + * + * @author Oleg Zhurakousky + * + */ public class ProxyMvc { static final String MVC_RESULT_ATTRIBUTE = ProxyMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE"); diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/RequestResponseTests.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/serverless/web/RequestResponseTests.java similarity index 92% rename from spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/RequestResponseTests.java rename to spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/serverless/web/RequestResponseTests.java index 6a29ba3ab..348d3b264 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/RequestResponseTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/serverless/web/RequestResponseTests.java @@ -14,9 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.function.adapter.aws.web; - - +package org.springframework.cloud.function.serverless.web; import java.util.List; @@ -28,9 +26,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest; -import org.springframework.cloud.function.serverless.web.ProxyHttpServletResponse; -import org.springframework.cloud.function.serverless.web.ProxyMvc; +import org.springframework.cloud.function.test.app.Pet; +import org.springframework.cloud.function.test.app.PetStoreSpringAppConfig; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/Pet.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/Pet.java similarity index 95% rename from spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/Pet.java rename to spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/Pet.java index 846faca8e..c0b8f0689 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/Pet.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/Pet.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.function.adapter.aws.web; +package org.springframework.cloud.function.test.app; import java.util.Date; diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetData.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetData.java similarity index 98% rename from spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetData.java rename to spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetData.java index c0fda4d4a..90b2a736f 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetData.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetData.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.function.adapter.aws.web; +package org.springframework.cloud.function.test.app; import java.util.ArrayList; diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetStoreSpringAppConfig.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetStoreSpringAppConfig.java similarity index 97% rename from spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetStoreSpringAppConfig.java rename to spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetStoreSpringAppConfig.java index 93888ccdd..22bf2fe9e 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetStoreSpringAppConfig.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetStoreSpringAppConfig.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.function.adapter.aws.web; +package org.springframework.cloud.function.test.app; import java.io.IOException; diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetsController.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetsController.java similarity index 97% rename from spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetsController.java rename to spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetsController.java index 544e2f806..0d3f9b9d1 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/adapter/aws/web/PetsController.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetsController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.function.adapter.aws.web; +package org.springframework.cloud.function.test.app; import java.security.Principal; import java.util.Optional; diff --git a/spring-cloud-function-dependencies/pom.xml b/spring-cloud-function-dependencies/pom.xml index dec3359db..3cda57926 100644 --- a/spring-cloud-function-dependencies/pom.xml +++ b/spring-cloud-function-dependencies/pom.xml @@ -81,6 +81,11 @@ spring-cloud-function-grpc-cloudevent-ext ${project.version} + + org.springframework.cloud + spring-cloud-function-serverless-web + ${project.version} +