From d8c1c4e1c4f026685a0b8d16e0fc034f573f03cc Mon Sep 17 00:00:00 2001 From: sapessi Date: Fri, 19 Jul 2019 11:02:46 -0700 Subject: [PATCH 01/40] Addressed bug reported in #269. Explictly setting the character encoding in the response overwrites whatever is passed in with the content type header --- .../servlet/AwsHttpServletResponse.java | 39 ++++++++++++------- .../servlet/AwsHttpServletResponseTest.java | 22 +++++------ .../proxy/spring/SpringBootAppTest.java | 16 +++++++- .../spring/springbootapp/TestApplication.java | 2 + .../spring/springbootapp/TestController.java | 11 ++++++ .../resources/boot-application.properties | 3 ++ 6 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 aws-serverless-java-container-spring/src/test/resources/boot-application.properties diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java index e603a8c37..3a61c07de 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java @@ -12,6 +12,7 @@ */ package com.amazonaws.serverless.proxy.internal.servlet; +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.SecurityUtils; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.Headers; @@ -60,6 +61,7 @@ public class AwsHttpServletResponse private int statusCode; private String statusMessage; private String responseBody; + private String characterEncoding; private PrintWriter writer; private ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream(); private CountDownLatch writersCountDownLatch; @@ -80,6 +82,7 @@ public class AwsHttpServletResponse */ public AwsHttpServletResponse(AwsHttpServletRequest req, CountDownLatch latch) { writersCountDownLatch = latch; + characterEncoding = null; request = req; statusCode = 0; } @@ -207,7 +210,7 @@ public void setHeader(String s, String s1) { public void addHeader(String s, String s1) { // TODO: We should probably have a list of headers that we are not allowed to have multiple values for if (s.toLowerCase(Locale.getDefault()).equals(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault()))) { - setHeader(s, s1, true); + setContentType(s1); } else { setHeader(s, s1, false); } @@ -273,12 +276,7 @@ public Collection getHeaderNames() { @Override public String getCharacterEncoding() { - final String contentType = Optional.ofNullable(getContentType()).orElse(""); - if (contentType.contains(";")) { - return contentType.split(";")[1].split("=")[1].trim().toLowerCase(Locale.getDefault()); - } else { - return ""; - } + return characterEncoding; } @@ -345,10 +343,11 @@ public PrintWriter getWriter() throws IOException { @Override public void setCharacterEncoding(String s) { - final String characterEncoding = Optional.ofNullable(s).orElse("").toLowerCase(Locale.getDefault()); - final String oldValue = Optional.ofNullable(getHeader(HttpHeaders.CONTENT_TYPE)).orElse(""); - String contentType = oldValue.contains(";") ? oldValue.split(";")[0].trim(): oldValue; - setHeader(HttpHeaders.CONTENT_TYPE, String.format("%s; charset=%s", contentType, characterEncoding), true); + characterEncoding = s.toUpperCase(Locale.getDefault()); + // The char encoding is being forced, if we already have a content-type header we recreate it + if (headers.getFirst(HttpHeaders.CONTENT_TYPE) != null) { + setContentType(headers.getFirst(HttpHeaders.CONTENT_TYPE)); + } } @@ -366,7 +365,21 @@ public void setContentLengthLong(long l) { @Override public void setContentType(String s) { - setHeader(HttpHeaders.CONTENT_TYPE, s, true); + if (s == null) { + return; + } + + // we have no forced character encoding + if (characterEncoding == null) { + setHeader(HttpHeaders.CONTENT_TYPE, s, true); + return; + } + + if (s.contains(";")) { // we have a forced charset + setHeader(HttpHeaders.CONTENT_TYPE, String.format("%s; charset=%s", s.split(";")[0], characterEncoding), true); + } else { + setHeader(HttpHeaders.CONTENT_TYPE, String.format("%s; charset=%s", s, characterEncoding), true); + } } @@ -387,7 +400,7 @@ public void flushBuffer() throws IOException { if (null != writer) { writer.flush(); } - responseBody = new String(bodyOutputStream.toByteArray(), StandardCharsets.UTF_8); + responseBody = new String(bodyOutputStream.toByteArray(), LambdaContainerHandler.getContainerConfig().getDefaultContentCharset()); log.debug("Response buffer flushed with {} bytes, latch={}", responseBody.length(), writersCountDownLatch.getCount()); isCommitted = true; writersCountDownLatch.countDown(); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java index d9a4e742b..1feeb5e94 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java @@ -272,8 +272,8 @@ public void characterEncoding_setCharacterEncoding() { resp.setContentType("application/json"); resp.setCharacterEncoding("UTF-8"); assertNotEquals("UTF-8", resp.getHeader("Content-Encoding")); - assertEquals("application/json; charset=utf-8", resp.getContentType()); - assertEquals("application/json; charset=utf-8", resp.getHeader("Content-Type")); + assertEquals("application/json; charset=UTF-8", resp.getContentType()); + assertEquals("application/json; charset=UTF-8", resp.getHeader("Content-Type")); } @Test @@ -282,9 +282,9 @@ public void characterEncoding_setContentType() { resp.setContentType("application/json; charset=utf-8"); resp.setCharacterEncoding("UTF-8"); - assertEquals("application/json; charset=utf-8", resp.getContentType()); - assertEquals("application/json; charset=utf-8", resp.getHeader("Content-Type")); - assertEquals("utf-8", resp.getCharacterEncoding()); + assertEquals("application/json; charset=UTF-8", resp.getContentType()); + assertEquals("application/json; charset=UTF-8", resp.getHeader("Content-Type")); + assertEquals("UTF-8", resp.getCharacterEncoding()); } @Test @@ -293,9 +293,9 @@ public void characterEncoding_setContentTypeAndsetCharacterEncoding() { resp.setContentType("application/json"); resp.setCharacterEncoding("UTF-8"); - assertEquals("application/json; charset=utf-8", resp.getContentType()); - assertEquals("application/json; charset=utf-8", resp.getHeader("Content-Type")); - assertEquals("utf-8", resp.getCharacterEncoding()); + assertEquals("application/json; charset=UTF-8", resp.getContentType()); + assertEquals("application/json; charset=UTF-8", resp.getHeader("Content-Type")); + assertEquals("UTF-8", resp.getCharacterEncoding()); } @Test @@ -304,9 +304,9 @@ public void characterEncoding_setCharacterEncodingAndsetContentType() { resp.setCharacterEncoding("UTF-8"); resp.setContentType("application/json"); - assertEquals("application/json", resp.getContentType()); - assertEquals("application/json", resp.getHeader("Content-Type")); - assertEquals("", resp.getCharacterEncoding()); + assertEquals("application/json; charset=UTF-8", resp.getContentType()); + assertEquals("application/json; charset=UTF-8", resp.getHeader("Content-Type")); + assertEquals("UTF-8", resp.getCharacterEncoding()); } private int getMaxAge(String header) { diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java index f27d6e57a..0a17814d9 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java @@ -42,7 +42,7 @@ public void defaultError_requestForward_springBootForwardsToDefaultErrorPage() { assertEquals(404, resp.getStatusCode()); assertNotNull(resp.getMultiValueHeaders()); assertTrue(resp.getMultiValueHeaders().containsKey("Content-Type")); - assertEquals("application/json;charset=UTF-8", resp.getMultiValueHeaders().getFirst("Content-Type")); + assertEquals("application/json; charset=UTF-8", resp.getMultiValueHeaders().getFirst("Content-Type")); try { JsonNode errorData = mapper.readTree(resp.getBody()); assertNotNull(errorData.findValue("status")); @@ -89,6 +89,20 @@ public void staticContent_getHtmlFile_returnsHtmlContent() { assertTrue(output.getBody().contains("

Static

")); } + @Test + public void utf8_returnUtf8String_expectCorrectHeaderMediaAndCharset() { + LambdaContainerHandler.getContainerConfig().setDefaultContentCharset("UTF-8"); + AwsProxyRequest request = new AwsProxyRequestBuilder("/test/utf8", "GET") + .build(); + AwsProxyResponse output = handler.handleRequest(request, context); + System.out.println("Response: " + output.getBody()); + System.out.println("Content-Type:" + output.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + validateSingleValueModel(output, TestController.UTF8_TEST_STRING); + assertTrue(output.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); + assertTrue(output.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).contains(";")); + assertTrue(output.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).contains("charset=UTF-8")); + } + private void validateSingleValueModel(AwsProxyResponse output, String value) { try { diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestApplication.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestApplication.java index d203a56e0..fda1f8012 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestApplication.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestApplication.java @@ -4,9 +4,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.PropertySource; @SpringBootApplication @ComponentScan(basePackages = "com.amazonaws.serverless.proxy.spring.springbootapp") +@PropertySource("classpath:boot-application.properties") public class TestApplication extends SpringBootServletInitializer { } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java index e03ff434a..51094bd1c 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java @@ -16,13 +16,17 @@ import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; import java.util.List; +import java.util.Map; @RestController @EnableWebSecurity public class TestController extends WebSecurityConfigurerAdapter { public static final String TEST_VALUE = "test"; + public static final String UTF8_TEST_STRING = "health心跳测试完成。可正常使用"; // workaround to address the most annoying issue in the world: https://blog.georgovassilis.com/2015/10/29/spring-mvc-rest-controller-says-406-when-emails-are-part-url-path/ @Configuration @@ -61,6 +65,13 @@ public SingleValueModel testQueryStringList(@RequestParam("list") List q return value; } + @RequestMapping(value="/test/utf8",method=RequestMethod.GET) + public Object testUtf8(String name, HttpServletResponse response){ + SingleValueModel model = new SingleValueModel(); + model.setValue(UTF8_TEST_STRING); + return model; + } + @Override protected void configure(HttpSecurity http) throws Exception { http.sessionManagement().disable(); diff --git a/aws-serverless-java-container-spring/src/test/resources/boot-application.properties b/aws-serverless-java-container-spring/src/test/resources/boot-application.properties new file mode 100644 index 000000000..c57cb12a4 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/resources/boot-application.properties @@ -0,0 +1,3 @@ +spring.http.encoding.charset=UTF-8 +spring.http.encoding.enabled=true +spring.http.encoding.force=true \ No newline at end of file From 130eedaf8f67d3b1d0dbbff3bb8bc39b22b991cc Mon Sep 17 00:00:00 2001 From: sapessi Date: Fri, 19 Jul 2019 11:03:48 -0700 Subject: [PATCH 02/40] Bump owasp plugin version to address NPE on build and bump Spring versions to address vulnerability from wasp database --- aws-serverless-java-container-spring/pom.xml | 6 +++--- pom.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index bbab50a57..e1a3e9c24 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -16,9 +16,9 @@ - 5.1.1.RELEASE - 1.5.17.RELEASE - 5.1.1.RELEASE + 5.1.8.RELEASE + 1.5.21.RELEASE + 5.1.5.RELEASE 2.9.9 diff --git a/pom.xml b/pom.xml index 74903bc77..72fc1071c 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 0.5 - 4.0.1 + 5.1.0 From 5da368ef082a78edd21702b096c9926f3be335b3 Mon Sep 17 00:00:00 2001 From: Mikael Quist Date: Tue, 30 Jul 2019 16:22:37 -0700 Subject: [PATCH 03/40] Added handling of all exceptions in LambdaContainerHandler.proxyStream() to return an error rather than a 200. --- .../serverless/proxy/internal/LambdaContainerHandler.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java index 0e61495a0..9af153f56 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java @@ -215,6 +215,9 @@ public void proxyStream(InputStream input, OutputStream output, Context context) } catch (JsonMappingException e) { log.error("Error while mapping object to RequestType class", e); getObjectMapper().writeValue(output, exceptionHandler.handle(e)); + } catch (Exception e) { + log.error("Error while proxying request.", e); + getObjectMapper().writeValue(output, exceptionHandler.handle(e)); } finally { output.flush(); output.close(); From 089637083e74e626a777399356bdf27543a5378c Mon Sep 17 00:00:00 2001 From: Mikael Quist Date: Wed, 31 Jul 2019 12:16:59 -0700 Subject: [PATCH 04/40] Adding InternalServerError to exception handler, modified the JerseyServletResponseWriter.failure() to throw if an error occured rather than return ultimately returning a 200 --- .../proxy/AwsProxyExceptionHandler.java | 6 ++- .../internal/LambdaContainerHandler.java | 3 -- .../proxy/AwsProxyExceptionHandlerTest.java | 43 ++++++++++++++++- .../jersey/JerseyServletResponseWriter.java | 10 +--- .../proxy/jersey/EchoJerseyResource.java | 4 ++ .../proxy/jersey/JerseyAwsProxyTest.java | 47 ++++++++++++++----- .../proxy/jersey/JerseyDependency.java | 6 +++ .../proxy/jersey/JerseyInjectionTest.java | 2 +- .../proxy/jersey/JerseyParamEncodingTest.java | 6 +-- .../proxy/jersey/ResourceBinder.java | 12 +++++ 10 files changed, 108 insertions(+), 31 deletions(-) create mode 100644 aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyDependency.java create mode 100644 aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/ResourceBinder.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java index d2b96d8e2..cf2aae3e7 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java @@ -22,6 +22,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; @@ -31,7 +32,8 @@ /** * Default implementation of the ExceptionHandler object that returns AwsProxyResponse objects. * - * Returns application/json messages with a status code of 500 when the RequestReader failed to read the incoming event. + * Returns application/json messages with a status code of 500 when the RequestReader failed to read the incoming event + * or if InternalServerErrorException is thrown. * For all other exceptions returns a 502. Responses are populated with a JSON object containing a message property. * * @see ExceptionHandler @@ -76,7 +78,7 @@ public AwsProxyResponse handle(Throwable ex) { // adding a print stack trace in case we have no appender or we are running inside SAM local, where need the // output to go to the stderr. ex.printStackTrace(); - if (ex instanceof InvalidRequestEventException) { + if (ex instanceof InvalidRequestEventException || ex instanceof InternalServerErrorException) { return new AwsProxyResponse(500, headers, getErrorJson(INTERNAL_SERVER_ERROR)); } else { return new AwsProxyResponse(502, headers, getErrorJson(GATEWAY_TIMEOUT_ERROR)); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java index 9af153f56..0e61495a0 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java @@ -215,9 +215,6 @@ public void proxyStream(InputStream input, OutputStream output, Context context) } catch (JsonMappingException e) { log.error("Error while mapping object to RequestType class", e); getObjectMapper().writeValue(output, exceptionHandler.handle(e)); - } catch (Exception e) { - log.error("Error while proxying request.", e); - getObjectMapper().writeValue(output, exceptionHandler.handle(e)); } finally { output.flush(); output.close(); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java index b5ed6f77a..cf86c57a6 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandlerTest.java @@ -3,7 +3,6 @@ import com.amazonaws.serverless.exceptions.InvalidRequestEventException; import com.amazonaws.serverless.exceptions.InvalidResponseObjectException; -import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.model.ErrorModel; import com.fasterxml.jackson.core.JsonProcessingException; @@ -16,18 +15,22 @@ import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; +import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import java.io.*; public class AwsProxyExceptionHandlerTest { + private static final String INTERNAL_SERVER_ERROR_MESSAGE = "Internal server error"; private static final String INVALID_REQUEST_MESSAGE = "Invalid request error"; private static final String INVALID_RESPONSE_MESSAGE = "Invalid response error"; private AwsProxyExceptionHandler exceptionHandler; private ObjectMapper objectMapper; + @Before public void setUp() { exceptionHandler = new AwsProxyExceptionHandler(); @@ -88,6 +91,44 @@ public void typedHandle_InvalidResponseObjectException_jsonContentTypeHeader() { assertEquals(MediaType.APPLICATION_JSON, resp.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); } + @Test + public void typedHandle_InternalServerErrorException_500State() { + // Needed to mock InternalServerErrorException because it leverages RuntimeDelegate to set an internal + // response object. + InternalServerErrorException mockInternalServerErrorException = Mockito.mock(InternalServerErrorException.class); + Mockito.when(mockInternalServerErrorException.getMessage()).thenReturn(INTERNAL_SERVER_ERROR_MESSAGE); + + AwsProxyResponse resp = exceptionHandler.handle(mockInternalServerErrorException); + + assertNotNull(resp); + assertEquals(500, resp.getStatusCode()); + } + + @Test + public void typedHandle_InternalServerErrorException_responseString() + throws JsonProcessingException { + InternalServerErrorException mockInternalServerErrorException = Mockito.mock(InternalServerErrorException.class); + Mockito.when(mockInternalServerErrorException.getMessage()).thenReturn(INTERNAL_SERVER_ERROR_MESSAGE); + + AwsProxyResponse resp = exceptionHandler.handle(mockInternalServerErrorException); + + assertNotNull(resp); + String body = objectMapper.writeValueAsString(new ErrorModel(AwsProxyExceptionHandler.INTERNAL_SERVER_ERROR)); + assertEquals(body, resp.getBody()); + } + + @Test + public void typedHandle_InternalServerErrorException_jsonContentTypeHeader() { + InternalServerErrorException mockInternalServerErrorException = Mockito.mock(InternalServerErrorException.class); + Mockito.when(mockInternalServerErrorException.getMessage()).thenReturn(INTERNAL_SERVER_ERROR_MESSAGE); + + AwsProxyResponse resp = exceptionHandler.handle(mockInternalServerErrorException); + + assertNotNull(resp); + assertTrue(resp.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); + assertEquals(MediaType.APPLICATION_JSON, resp.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + } + @Test public void typedHandle_NullPointerException_responseObject() throws JsonProcessingException { diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java index c2cba7319..938c6f9ff 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java @@ -122,14 +122,8 @@ public void commit() { public void failure(Throwable throwable) { - try { - log.error("failure", throwable); - jerseyLatch.countDown(); - servletResponse.flushBuffer(); - } catch (IOException e) { - log.error("Could not fail response", e); - throw new InternalServerErrorException(e); - } + log.error("failure", throwable); + throw new InternalServerErrorException("Jersey failed to process request", throwable); } diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java index da67dcf36..ec0f4b2ac 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java @@ -24,6 +24,7 @@ import org.glassfish.jersey.media.multipart.FormDataMultiPart; import org.glassfish.jersey.media.multipart.FormDataParam; +import javax.inject.Inject; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -59,6 +60,9 @@ public class EchoJerseyResource { @Context SecurityContext securityCtx; + @Inject + JerseyDependency jerseyDependency; + @Path("/decoded-param") @GET @Produces(MediaType.APPLICATION_JSON) public SingleValueModel echoDecodedParam(@QueryParam("param") String param) { diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java index c54de6c5a..e791ae810 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java @@ -14,16 +14,15 @@ import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; -import com.amazonaws.serverless.proxy.jersey.providers.ServletRequestFilter; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.internal.servlet.AwsServletContext; -import com.amazonaws.serverless.proxy.jersey.model.MapResponseModel; -import com.amazonaws.serverless.proxy.jersey.model.SingleValueModel; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.jersey.model.MapResponseModel; +import com.amazonaws.serverless.proxy.jersey.model.SingleValueModel; +import com.amazonaws.serverless.proxy.jersey.providers.ServletRequestFilter; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.services.lambda.runtime.Context; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; @@ -37,13 +36,15 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; - import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.UUID; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Unit test class for the Jersey AWS_PROXY default implementation @@ -57,13 +58,26 @@ public class JerseyAwsProxyTest { private static ObjectMapper objectMapper = new ObjectMapper(); + private static ResourceConfig app = new ResourceConfig().packages("com.amazonaws.serverless.proxy.jersey") + .register(LoggingFeature.class) + .register(ServletRequestFilter.class) + .register(MultiPartFeature.class) + .register(new ResourceBinder()) + .property(LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER, LoggingFeature.Verbosity.PAYLOAD_ANY); + + private static ResourceConfig appWithoutRegisteredDependencies = new ResourceConfig() + .packages("com.amazonaws.serverless.proxy.jersey") .register(LoggingFeature.class) .register(ServletRequestFilter.class) .register(MultiPartFeature.class) .property(LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER, LoggingFeature.Verbosity.PAYLOAD_ANY); + private static JerseyLambdaContainerHandler handler = JerseyLambdaContainerHandler.getAwsProxyHandler(app); + private static JerseyLambdaContainerHandler handlerWithoutRegisteredDependencies + = JerseyLambdaContainerHandler.getAwsProxyHandler(appWithoutRegisteredDependencies); + private static Context lambdaContext = new MockLambdaContext(); private boolean isAlb; @@ -76,15 +90,14 @@ public JerseyAwsProxyTest(boolean alb) { public static Collection data() { return Arrays.asList(new Object[] { false, true }); } - + private AwsProxyRequestBuilder getRequestBuilder(String path, String method) { AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder(path, method); if (isAlb) builder.alb(); - + return builder; } - @Test public void alb_basicRequest_expectSuccess() { AwsProxyRequest request = getRequestBuilder("/echo/headers", "GET") @@ -130,6 +143,18 @@ public void headers_servletRequest_echo() { validateMapResponseModel(output); } + @Test + public void headers_servletRequest_failedDependencyInjection_expectInternalServerError() { + AwsProxyRequest request = getRequestBuilder("/echo/servlet-headers", "GET") + .json() + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .build(); + + AwsProxyResponse output = handlerWithoutRegisteredDependencies.proxy(request, lambdaContext); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), output.getStatusCode()); + } + @Test public void context_servletResponse_setCustomHeader() { AwsProxyRequest request = getRequestBuilder("/echo/servlet-response", "GET") diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyDependency.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyDependency.java new file mode 100644 index 000000000..0633b96b4 --- /dev/null +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyDependency.java @@ -0,0 +1,6 @@ +package com.amazonaws.serverless.proxy.jersey; + +// This class is used to test HK2 dependency injection. +public class JerseyDependency { + +} diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java index 0af2785ee..e8e86270b 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java @@ -30,7 +30,7 @@ */ public class JerseyInjectionTest { - // Test ressource binder + // Test resource binder private static class ResourceBinder extends AbstractBinder { @Override diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java index e3eb92de6..06edbdaab 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java @@ -11,7 +11,6 @@ import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.databind.ObjectMapper; -import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.server.ResourceConfig; import org.junit.Ignore; @@ -26,10 +25,6 @@ import java.net.URLEncoder; import java.util.Arrays; import java.util.Collection; -import java.util.logging.ConsoleHandler; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.logging.SimpleFormatter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -69,6 +64,7 @@ public class JerseyParamEncodingTest { private static ObjectMapper objectMapper = new ObjectMapper(); private static ResourceConfig app = new ResourceConfig().packages("com.amazonaws.serverless.proxy.jersey") .register(MultiPartFeature.class) + .register(new ResourceBinder()) .property("jersey.config.server.tracing.type", "ALL") .property("jersey.config.server.tracing.threshold", "VERBOSE"); private static JerseyLambdaContainerHandler handler = JerseyLambdaContainerHandler.getAwsProxyHandler(app); diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/ResourceBinder.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/ResourceBinder.java new file mode 100644 index 000000000..e98ea805a --- /dev/null +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/ResourceBinder.java @@ -0,0 +1,12 @@ +package com.amazonaws.serverless.proxy.jersey; + +import org.glassfish.jersey.internal.inject.AbstractBinder; + +import javax.inject.Singleton; + +public class ResourceBinder extends AbstractBinder { + @Override + protected void configure() { + bind(new JerseyDependency()).to(JerseyDependency.class).in(Singleton.class); + } +} From b1574ab2d1d4e97e40bd4e4655cf1cafac55b374 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 14 Aug 2019 10:24:02 -0700 Subject: [PATCH 05/40] More complete implementation of include and forward methods for the request dispatcher to address #275 --- aws-serverless-java-container-core/pom.xml | 6 + .../servlet/AwsHttpServletRequest.java | 11 +- .../servlet/AwsHttpServletResponse.java | 24 ++ .../AwsLambdaServletContainerHandler.java | 60 +---- .../servlet/AwsProxyRequestDispatcher.java | 90 +++++-- .../internal/servlet/AwsServletContext.java | 2 +- .../AwsProxyRequestDispatcherTest.java | 221 ++++++++++++++++++ .../jersey/JerseyLambdaContainerHandler.java | 5 + .../spark/SparkLambdaContainerHandler.java | 5 + .../SpringBootLambdaContainerHandler.java | 4 + .../spring/SpringLambdaContainerHandler.java | 5 + .../proxy/spring/SpringBootSecurityTest.java | 63 +++++ .../spring/sbsecurityapp/LambdaHandler.java | 33 +++ .../spring/sbsecurityapp/TestApplication.java | 13 ++ .../spring/sbsecurityapp/TestController.java | 48 ++++ .../sbsecurityapp/TestSecurityConfig.java | 45 ++++ .../Struts2LambdaContainerHandler.java | 5 + 17 files changed, 557 insertions(+), 83 deletions(-) create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcherTest.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/LambdaHandler.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestApplication.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestController.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestSecurityConfig.java diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml index 82d218f9d..fe0e66258 100644 --- a/aws-serverless-java-container-core/pom.xml +++ b/aws-serverless-java-container-core/pom.xml @@ -86,6 +86,12 @@ 4.4.10 compile + + org.springframework.security + spring-security-web + 5.1.5.RELEASE + test + diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java index 8712285ac..d6a11a967 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java @@ -60,6 +60,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { static final String FORM_DATA_SEPARATOR = "&"; static final DateTimeFormatter dateFormatter = DateTimeFormatter.RFC_1123_DATE_TIME; static final String ENCODING_VALUE_KEY = "charset"; + static final String DISPATCHER_TYPE_ATTRIBUTE = "com.amazonaws.serverless.javacontainer.dispatchertype"; // We need this to pickup the protocol from the CloudFront header since Lambda doesn't receive this // information from anywhere else @@ -80,8 +81,6 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { private String queryString; private BasicHeaderValueParser headerParser; - protected DispatcherType dispatcherType; - private Logger log = LoggerFactory.getLogger(AwsHttpServletRequest.class); @@ -98,6 +97,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { this.lambdaContext = lambdaContext; attributes = new HashMap<>(); headerParser = new BasicHeaderValueParser(); + setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.REQUEST); } @@ -250,6 +250,9 @@ public AsyncContext getAsyncContext() { @Override public DispatcherType getDispatcherType() { + if (getAttribute(DISPATCHER_TYPE_ATTRIBUTE) != null) { + return (DispatcherType) getAttribute(DISPATCHER_TYPE_ATTRIBUTE); + } return DispatcherType.REQUEST; } @@ -258,10 +261,6 @@ public DispatcherType getDispatcherType() { // Methods - Getter/Setter //------------------------------------------------------------- - public void setDispatcherType(DispatcherType type) { - dispatcherType = type; - } - public void setServletContext(ServletContext context) { servletContext = context; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java index 3a61c07de..d477587f4 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java @@ -21,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.DispatcherType; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.Cookie; @@ -96,6 +97,9 @@ public AwsHttpServletResponse(AwsHttpServletRequest req, CountDownLatch latch) { @SuppressFBWarnings("COOKIE_USAGE") @Override public void addCookie(Cookie cookie) { + if (request != null && request.getDispatcherType() == DispatcherType.INCLUDE && isCommitted()) { + throw new IllegalStateException("Cannot add Cookies for include request when response is committed"); + } String cookieData = cookie.getName() + "=" + cookie.getValue(); if (cookie.getPath() != null) { cookieData += "; Path=" + cookie.getPath(); @@ -162,6 +166,7 @@ public String encodeRedirectUrl(String s) { @Override public void sendError(int i, String s) throws IOException { + request.setAttribute(AwsHttpServletRequest.DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ERROR); setStatus(i, s); flushBuffer(); } @@ -169,6 +174,7 @@ public void sendError(int i, String s) throws IOException { @Override public void sendError(int i) throws IOException { + request.setAttribute(AwsHttpServletRequest.DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ERROR); setStatus(i); flushBuffer(); } @@ -184,6 +190,7 @@ public void sendRedirect(String s) throws IOException { @Override public void setDateHeader(String s, long l) { + if (!canSetHeader()) return; SimpleDateFormat sdf = new SimpleDateFormat(HEADER_DATE_PATTERN); Date responseDate = new Date(); responseDate.setTime(l); @@ -193,6 +200,7 @@ public void setDateHeader(String s, long l) { @Override public void addDateHeader(String s, long l) { + if (!canSetHeader()) return; SimpleDateFormat sdf = new SimpleDateFormat(HEADER_DATE_PATTERN); Date responseDate = new Date(); responseDate.setTime(l); @@ -202,12 +210,14 @@ public void addDateHeader(String s, long l) { @Override public void setHeader(String s, String s1) { + if (!canSetHeader()) return; setHeader(s, s1, true); } @Override public void addHeader(String s, String s1) { + if (!canSetHeader()) return; // TODO: We should probably have a list of headers that we are not allowed to have multiple values for if (s.toLowerCase(Locale.getDefault()).equals(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault()))) { setContentType(s1); @@ -219,18 +229,21 @@ public void addHeader(String s, String s1) { @Override public void setIntHeader(String s, int i) { + if (!canSetHeader()) return; setHeader(s, "" + i, true); } @Override public void addIntHeader(String s, int i) { + if (!canSetHeader()) return; setHeader(s, "" + i, false); } @Override public void setStatus(int i) { + if (!canSetHeader()) return; statusCode = i; } @@ -238,6 +251,7 @@ public void setStatus(int i) { @Override @Deprecated public void setStatus(int i, String s) { + if (!canSetHeader()) return; statusCode = i; statusMessage = s; } @@ -343,6 +357,7 @@ public PrintWriter getWriter() throws IOException { @Override public void setCharacterEncoding(String s) { + if (!canSetHeader()) return; characterEncoding = s.toUpperCase(Locale.getDefault()); // The char encoding is being forced, if we already have a content-type header we recreate it if (headers.getFirst(HttpHeaders.CONTENT_TYPE) != null) { @@ -353,18 +368,21 @@ public void setCharacterEncoding(String s) { @Override public void setContentLength(int i) { + if (!canSetHeader()) return; setHeader(HttpHeaders.CONTENT_LENGTH, "" + i, true); } @Override public void setContentLengthLong(long l) { + if (!canSetHeader()) return; setHeader(HttpHeaders.CONTENT_LENGTH, "" + l, true); } @Override public void setContentType(String s) { + if (!canSetHeader()) return; if (s == null) { return; } @@ -430,6 +448,7 @@ public void reset() { @Override public void setLocale(Locale locale) { + if (!canSetHeader()) return; setHeader(HttpHeaders.CONTENT_LANGUAGE, locale.getLanguage(), true); } @@ -470,6 +489,7 @@ AwsProxyRequest getAwsProxyRequest() { //------------------------------------------------------------- private void setHeader(String key, String value, boolean overwrite) { + if (!canSetHeader()) return; String encodedKey = SecurityUtils.crlf(key); String encodedValue = SecurityUtils.crlf(value); List values = headers.get(encodedKey); @@ -482,4 +502,8 @@ private void setHeader(String key, String value, boolean overwrite) { headers.put(encodedKey, values); } + + private boolean canSetHeader() { + return request == null || request.getDispatcherType() != DispatcherType.INCLUDE; + } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java index 991213ea6..77310a423 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java @@ -18,6 +18,7 @@ import com.amazonaws.serverless.proxy.ResponseWriter; import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.services.lambda.runtime.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,57 +86,6 @@ protected AwsLambdaServletContainerHandler(Class requestTypeClass, // Methods - Public //------------------------------------------------------------- - /** - * Forwards a request to the existing framework container. This is called by the AwsProxyRequestDispatcher object - * @param servletRequest The modified request object with the new request path - * @param servletResponse The original servlet response - * @throws ServletException - * @throws IOException - */ - public void forward(ContainerRequestType servletRequest, ContainerResponseType servletResponse) - throws ServletException, IOException { - try { - handleRequest(servletRequest, (ContainerResponseType)getServletResponse(servletResponse), lambdaContext); - } catch (Exception e) { - log.error("Could not forward request", e); - throw new ServletException(e); - } - } - - - /** - * Includes a request to the existing framework container. This is called by the AwsProxyRequestDispatcher object - * @param servletRequest The modified request object with the new request path - * @param servletResponse The original servlet response - * @throws ServletException - * @throws IOException - */ - public void include(ContainerRequestType servletRequest, ContainerResponseType servletResponse) - throws ServletException, IOException { - try { - handleRequest(servletRequest, (ContainerResponseType)getServletResponse(servletResponse), lambdaContext); - } catch (Exception e) { - log.error("Could not include request", e); - throw new ServletException(e); - } - } - - private HttpServletResponse getServletResponse(ContainerResponseType resp) { - if (HttpServletResponseWrapper.class.isAssignableFrom(resp.getClass())) { - ServletResponse servletResp = ((HttpServletResponseWrapper)resp).getResponse(); - assert servletResp instanceof HttpServletResponse : servletResp.getClass(); - return (HttpServletResponse)servletResp; - } - - if (HttpServletResponse.class.isAssignableFrom(resp.getClass())) { - return resp; - } - - - throw new UnsupportedOperationException("Response type of " + resp.getClass().getName() + " is not supported"); - } - - /** * You can use the onStartup to intercept the ServletContext as the Spring application is * initialized and inject custom values. The StartupHandler is called after the onStartup method @@ -184,7 +134,7 @@ protected void setServletContext(final ServletContext context) { filterChainManager = new AwsFilterChainManager((AwsServletContext)servletContext); } - protected FilterChain getFilterChain(ContainerRequestType req, Servlet servlet) { + protected FilterChain getFilterChain(HttpServletRequest req, Servlet servlet) { return filterChainManager.getFilterChain(req, servlet); } @@ -201,16 +151,18 @@ protected FilterChain getFilterChain(ContainerRequestType req, Servlet servlet) * @throws IOException * @throws ServletException */ - protected void doFilter(ContainerRequestType request, ContainerResponseType response, Servlet servlet) throws IOException, ServletException { + protected void doFilter(HttpServletRequest request, HttpServletResponse response, Servlet servlet) throws IOException, ServletException { FilterChain chain = getFilterChain(request, servlet); chain.doFilter(request, response); // if for some reason the response wasn't flushed yet, we force it here. - if (request.getDispatcherType() != DispatcherType.FORWARD && request.getDispatcherType() != DispatcherType.INCLUDE && !response.isCommitted()) { + if (!response.isCommitted()) { response.flushBuffer(); } } + public abstract Servlet getServlet(); + //------------------------------------------------------------- // Inner Class - //------------------------------------------------------------- diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java index 2155323b4..bb61b7b2c 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java @@ -1,6 +1,12 @@ package com.amazonaws.serverless.proxy.internal.servlet; +import com.amazonaws.serverless.proxy.internal.SecurityUtils; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; @@ -11,6 +17,9 @@ import java.io.IOException; +import static com.amazonaws.serverless.proxy.RequestReader.API_GATEWAY_EVENT_PROPERTY; +import static com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.DISPATCHER_TYPE_ATTRIBUTE; + /** * Default RequestDispatcher implementation for the AwsProxyHttpServletRequest type. A new @@ -23,8 +32,9 @@ public class AwsProxyRequestDispatcher implements RequestDispatcher { //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- - - private String dispatchPath; + private static final Logger log = LoggerFactory.getLogger(AwsHttpSession.class); + private String dispatchTo; + private boolean isNamedDispatcher; private AwsLambdaServletContainerHandler lambdaContainerHandler; //------------------------------------------------------------- @@ -32,12 +42,13 @@ public class AwsProxyRequestDispatcher implements RequestDispatcher { //------------------------------------------------------------- - public AwsProxyRequestDispatcher(final String path, final AwsLambdaServletContainerHandler handler) { - if (!path.startsWith("/")) { + public AwsProxyRequestDispatcher(final String target, final boolean namedDispatcher, final AwsLambdaServletContainerHandler handler) { + if (!namedDispatcher && !target.startsWith("/")) { throw new UnsupportedOperationException("Only dispatchers with absolute paths are supported"); } - dispatchPath = path; + isNamedDispatcher = namedDispatcher; + dispatchTo = target; lambdaContainerHandler = handler; } @@ -50,38 +61,73 @@ public AwsProxyRequestDispatcher(final String path, final AwsLambdaServletContai @SuppressWarnings("unchecked") public void forward(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { - if (!(servletRequest instanceof AwsProxyHttpServletRequest)) { - throw new IOException("Invalid request type: " + servletRequest.getClass().getSimpleName() + ". Only AwsProxyHttpServletRequest is supported"); + if (lambdaContainerHandler == null) { + throw new IllegalStateException("Null container handler in dispatcher"); + } + if (servletResponse.isCommitted()) { + throw new IllegalStateException("Cannot forward request with committed response"); } - if (lambdaContainerHandler == null) { - throw new IOException("Null container handler in dispatcher"); + try { + // Reset any output that has been buffered, but keep headers/cookies + servletResponse.resetBuffer(); + } catch (IllegalStateException e) { + throw e; } - ((AwsProxyHttpServletRequest) servletRequest).setDispatcherType(DispatcherType.FORWARD); - ((AwsProxyHttpServletRequest) servletRequest).getAwsProxyRequest().setPath(dispatchPath); + if (isNamedDispatcher) { + lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, lambdaContainerHandler.getServlet()); + return; + } - assert servletResponse instanceof HttpServletResponse : servletResponse.getClass(); - lambdaContainerHandler.forward((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse); + servletRequest.setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.FORWARD); + setRequestPath(servletRequest, dispatchTo); + lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, lambdaContainerHandler.getServlet()); } @Override @SuppressWarnings("unchecked") + @SuppressFBWarnings("SERVLET_QUERY_STRING") public void include(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { - if (!(servletRequest instanceof AwsProxyHttpServletRequest)) { - throw new IOException("Invalid request type: " + servletRequest.getClass().getSimpleName() + ". Only AwsProxyHttpServletRequest is supported"); - } - if (lambdaContainerHandler == null) { - throw new IOException("Null container handler in dispatcher"); + throw new IllegalStateException("Null container handler in dispatcher"); + } + if (servletResponse.isCommitted()) { + throw new IllegalStateException("Cannot forward request with committed response"); + } + servletRequest.setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.INCLUDE); + if (!isNamedDispatcher) { + servletRequest.setAttribute("javax.servlet.include.request_uri", ((HttpServletRequest)servletRequest).getRequestURI()); + servletRequest.setAttribute("javax.servlet.include.context_path", ((HttpServletRequest) servletRequest).getContextPath()); + servletRequest.setAttribute("javax.servlet.include.servlet_path", ((HttpServletRequest) servletRequest).getServletPath()); + servletRequest.setAttribute("javax.servlet.include.path_info", ((HttpServletRequest) servletRequest).getPathInfo()); + servletRequest.setAttribute("javax.servlet.include.query_string", + SecurityUtils.encode(SecurityUtils.crlf(((HttpServletRequest) servletRequest).getQueryString()))); + setRequestPath(servletRequest, dispatchTo); } + lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, lambdaContainerHandler.getServlet()); + } - ((AwsProxyHttpServletRequest) servletRequest).setDispatcherType(DispatcherType.INCLUDE); - ((AwsProxyHttpServletRequest) servletRequest).getAwsProxyRequest().setPath(dispatchPath); + /** + * Sets the destination path in the given request. Uses the AwsProxyRequest.setPath method which + * is in turn read by the HttpServletRequest implementation. + * @param req The request object to be modified + * @param destinationPath The new path for the request + * @throws IllegalStateException If the given request object does not include the API_GATEWAY_EVENT_PROPERTY + * attribute or the value for the attribute is not of the correct type: AwsProxyRequest. + */ + void setRequestPath(ServletRequest req, final String destinationPath) { + if (req instanceof AwsProxyHttpServletRequest) { + ((AwsProxyHttpServletRequest) req).getAwsProxyRequest().setPath(dispatchTo); + return; + } - assert servletResponse instanceof HttpServletResponse : servletResponse.getClass(); - lambdaContainerHandler.include((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse); + log.debug("Request is not an AwsProxyHttpServletRequest, attempting to extract the proxy event type"); + if (req.getAttribute(API_GATEWAY_EVENT_PROPERTY) == null || !(req.getAttribute(API_GATEWAY_EVENT_PROPERTY) instanceof AwsProxyRequest)) { + throw new IllegalStateException("ServletRequest object does not contain API Gateway event"); + } + ((AwsProxyRequest)req.getAttribute(API_GATEWAY_EVENT_PROPERTY)).setPath(dispatchTo); } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java index cb72e9532..b3f2f1fd6 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java @@ -179,7 +179,7 @@ public InputStream getResourceAsStream(String s) { @Override public RequestDispatcher getRequestDispatcher(String s) { - return new AwsProxyRequestDispatcher(s, containerHandler); + return new AwsProxyRequestDispatcher(s, false, containerHandler); } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcherTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcherTest.java new file mode 100644 index 000000000..05aa6ae65 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcherTest.java @@ -0,0 +1,221 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.exceptions.InvalidRequestEventException; +import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; +import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.ContainerConfig; +import com.amazonaws.services.lambda.runtime.Context; +import org.junit.Test; +import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.concurrent.CountDownLatch; + +import static junit.framework.TestCase.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class AwsProxyRequestDispatcherTest { + public static final String FORWARD_PATH = "/newpath"; + static AwsProxyHttpServletRequestReader requestReader = new AwsProxyHttpServletRequestReader(); + + + @Test + public void setPath_forwardByPath_proxyRequestObjectInPropertyReferencesSameProxyRequest() throws InvalidRequestEventException { + AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyHttpServletRequest servletRequest = requestReader.readRequest(proxyRequest,null, new MockLambdaContext(), ContainerConfig.defaultConfig()); + + AwsProxyRequestDispatcher dispatcher = new AwsProxyRequestDispatcher(FORWARD_PATH, false, null); + dispatcher.setRequestPath(servletRequest, FORWARD_PATH); + assertEquals(FORWARD_PATH, servletRequest.getRequestURI()); + } + + @Test + public void setPathForWrappedRequest_forwardByPath_proxyRequestObjectInPropertyReferencesSameProxyRequest() throws InvalidRequestEventException { + AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyHttpServletRequest servletRequest = requestReader.readRequest(proxyRequest,null, new MockLambdaContext(), ContainerConfig.defaultConfig()); + SecurityContextHolderAwareRequestWrapper springSecurityRequest = new SecurityContextHolderAwareRequestWrapper(servletRequest, "ADMIN"); + + AwsProxyRequestDispatcher dispatcher = new AwsProxyRequestDispatcher(FORWARD_PATH, false, null); + dispatcher.setRequestPath(springSecurityRequest, FORWARD_PATH); + assertEquals(FORWARD_PATH, springSecurityRequest.getRequestURI()); + } + + @Test + public void setPathForWrappedRequestWithoutGatewayEvent_forwardByPath_throwsException() { + AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyHttpServletRequest servletRequest = new AwsProxyHttpServletRequest(proxyRequest, new MockLambdaContext(), null); + SecurityContextHolderAwareRequestWrapper springSecurityRequest = new SecurityContextHolderAwareRequestWrapper(servletRequest, "ADMIN"); + + AwsProxyRequestDispatcher dispatcher = new AwsProxyRequestDispatcher(FORWARD_PATH, false, null); + try { + dispatcher.setRequestPath(springSecurityRequest, FORWARD_PATH); + } catch (Exception e) { + assertTrue(e instanceof IllegalStateException); + return; + } + fail(); + } + + @Test + public void forwardRequest_nullHandler_throwsIllegalStateException() throws InvalidRequestEventException { + AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyHttpServletRequest servletRequest = requestReader.readRequest(proxyRequest,null, new MockLambdaContext(), ContainerConfig.defaultConfig()); + AwsProxyRequestDispatcher dispatcher = new AwsProxyRequestDispatcher(FORWARD_PATH, false, null); + try { + dispatcher.forward(servletRequest, new AwsHttpServletResponse(servletRequest, new CountDownLatch(1))); + } catch (ServletException e) { + fail("Unexpected ServletException"); + } catch (IOException e) { + fail("Unexpected IOException"); + } catch (Exception e) { + assertTrue(e instanceof IllegalStateException); + return; + } + fail(); + } + + @Test + public void forwardRequest_committedResponse_throwsIllegalStateException() throws InvalidRequestEventException { + AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyHttpServletRequest servletRequest = requestReader.readRequest(proxyRequest,null, new MockLambdaContext(), ContainerConfig.defaultConfig()); + AwsProxyRequestDispatcher dispatcher = new AwsProxyRequestDispatcher(FORWARD_PATH, false, mockLambdaHandler(null)); + AwsHttpServletResponse resp = new AwsHttpServletResponse(servletRequest, new CountDownLatch(1)); + + try { + resp.flushBuffer(); + dispatcher.forward(servletRequest, resp); + } catch (ServletException e) { + fail("Unexpected ServletException"); + } catch (IOException e) { + fail("Unexpected IOException"); + } catch (Exception e) { + assertTrue(e instanceof IllegalStateException); + return; + } + fail(); + } + + @Test + public void forwardRequest_partiallyWrittenResponse_resetsBuffer() throws InvalidRequestEventException { + AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyHttpServletRequest servletRequest = requestReader.readRequest(proxyRequest,null, new MockLambdaContext(), ContainerConfig.defaultConfig()); + AwsProxyRequestDispatcher dispatcher = new AwsProxyRequestDispatcher(FORWARD_PATH, false, mockLambdaHandler(null)); + AwsHttpServletResponse resp = new AwsHttpServletResponse(servletRequest, new CountDownLatch(1)); + + try { + resp.getOutputStream().write("this is a test write".getBytes()); + assertEquals("this is a test write", new String(resp.getAwsResponseBodyBytes(), Charset.defaultCharset())); + dispatcher.forward(servletRequest, resp); + assertEquals(0, resp.getAwsResponseBodyBytes().length); + + } catch (ServletException e) { + fail("Unexpected ServletException"); + } catch (IOException e) { + fail("Unexpected IOException"); + } + } + + @Test + public void include_addsToResponse_appendsCorrectly() throws InvalidRequestEventException, IOException { + final String firstPart = "first"; + final String secondPart = "second"; + AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); + + AwsProxyResponse resp = mockLambdaHandler((AwsProxyHttpServletRequest req, AwsHttpServletResponse res)-> { + if (req.getAttribute("cnt") == null) { + res.getOutputStream().write(firstPart.getBytes()); + req.setAttribute("cnt", 1); + req.getRequestDispatcher("/includer").include(req, res); + res.setStatus(200); + res.flushBuffer(); + } else { + res.getOutputStream().write(secondPart.getBytes()); + } + }).proxy(proxyRequest, new MockLambdaContext()); + assertEquals(firstPart + secondPart, resp.getBody()); + } + + @Test + public void include_appendsNewHeader_cannotAppendNewHeaders() throws InvalidRequestEventException, IOException { + final String firstPart = "first"; + final String secondPart = "second"; + final String headerKey = "X-Custom-Header"; + AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/hello", "GET").build(); + + AwsProxyResponse resp = mockLambdaHandler((AwsProxyHttpServletRequest req, AwsHttpServletResponse res)-> { + if (req.getAttribute("cnt") == null) { + res.getOutputStream().write(firstPart.getBytes()); + req.setAttribute("cnt", 1); + req.getRequestDispatcher("/includer").include(req, res); + res.setStatus(200); + res.flushBuffer(); + } else { + res.getOutputStream().write(secondPart.getBytes()); + res.addHeader(headerKey, "value"); + } + }).proxy(proxyRequest, new MockLambdaContext()); + assertEquals(firstPart + secondPart, resp.getBody()); + assertFalse(resp.getMultiValueHeaders().containsKey(headerKey)); + } + + private interface RequestHandler { + void handleRequest(AwsProxyHttpServletRequest req, AwsHttpServletResponse resp) throws ServletException, IOException; + } + + + private AwsLambdaServletContainerHandler mockLambdaHandler(RequestHandler h) { + return new AwsLambdaServletContainerHandler( + AwsProxyRequest.class, + AwsProxyResponse.class, + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler() + ) { + @Override + public Servlet getServlet() { + return null; + } + + @Override + protected void doFilter(HttpServletRequest request, HttpServletResponse response, Servlet servlet) throws IOException, ServletException { + if (h != null) { + h.handleRequest((AwsProxyHttpServletRequest)request, (AwsHttpServletResponse)response); + } + } + + @Override + protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + return new AwsHttpServletResponse(request, latch); + } + + @Override + protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + if (h != null) { + + setServletContext(new AwsServletContext(this)); + containerRequest.setServletContext(getServletContext()); + + h.handleRequest(containerRequest, containerResponse); + } + containerResponse.flushBuffer(); + } + + @Override + public void initialize() throws ContainerInitializationException { + + } + }; + } +} diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java index dd9166f7d..cd99133a4 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java @@ -40,6 +40,7 @@ import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; +import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -205,4 +206,8 @@ public InjectionManager getInjectionManager() { } return jerseyFilter.getApplicationHandler().getInjectionManager(); } + + public Servlet getServlet() { + return null; + } } diff --git a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java index 247140413..dfea29017 100644 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java +++ b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java @@ -37,6 +37,7 @@ import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; +import javax.servlet.Servlet; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -217,4 +218,8 @@ public void initialize() sparkRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); Timer.stop("SPARK_COLD_START"); } + + public Servlet getServlet() { + return null; + } } diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java index 6de1fcaf5..68cab4f79 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -186,6 +186,10 @@ public void initialize() Timer.stop("SPRINGBOOT_COLD_START"); } + public Servlet getServlet() { + return dispatcherServlet; + } + private class SpringBootAwsServletContext extends AwsServletContext { public SpringBootAwsServletContext() { diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java index 28bff8af2..7fd87803b 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java @@ -27,6 +27,7 @@ import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import javax.servlet.Servlet; import javax.servlet.ServletException; import java.util.concurrent.CountDownLatch; @@ -197,4 +198,8 @@ public void initialize() initialized = true; Timer.stop("SPRING_COLD_START"); } + + public Servlet getServlet() { + return initializer.getDispatcherServlet(); + } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java new file mode 100644 index 000000000..dc46ed339 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java @@ -0,0 +1,63 @@ +package com.amazonaws.serverless.proxy.spring; + + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel; +import com.amazonaws.serverless.proxy.spring.sbsecurityapp.LambdaHandler; +import com.amazonaws.serverless.proxy.spring.sbsecurityapp.TestController; +import com.amazonaws.serverless.proxy.spring.sbsecurityapp.TestSecurityConfig; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.junit.Test; + +import javax.ws.rs.core.HttpHeaders; +import java.io.IOException; +import java.util.Base64; + +import static org.junit.Assert.*; + + +public class SpringBootSecurityTest { + private LambdaHandler handler = new LambdaHandler(); + private MockLambdaContext context = new MockLambdaContext(); + private ObjectMapper mapper = new ObjectMapper(); + + @Test + public void correctUser_springSecurityBasicAuth_requestSucceeds() throws JsonProcessingException { + String authValue = Base64.getMimeEncoder().encodeToString((TestSecurityConfig.USERNAME + ":" + TestSecurityConfig.PASSWORD).getBytes()); + AwsProxyRequest req = new AwsProxyRequestBuilder("/user", "GET") + .header(HttpHeaders.AUTHORIZATION, "Basic " + authValue).build(); + AwsProxyResponse resp = handler.handleRequest(req, context); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, TestSecurityConfig.USERNAME); + } + + @Test + public void wrongUser_springSecurityBasicAuth_requestRedirectsSuccessfully() throws JsonProcessingException { + String authValue = Base64.getMimeEncoder().encodeToString((TestSecurityConfig.NO_ADMIN_USERNAME + ":" + TestSecurityConfig.PASSWORD).getBytes()); + AwsProxyRequest req = new AwsProxyRequestBuilder("/user", "GET") + .header(HttpHeaders.AUTHORIZATION, "Basic " + authValue).build(); + AwsProxyResponse resp = handler.handleRequest(req, context); + assertNotNull(resp); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + System.out.println(mapper.writeValueAsString(resp.getMultiValueHeaders())); + assertEquals(403, resp.getStatusCode()); + validateSingleValueModel(resp, TestController.ACCESS_DENIED); + } + + private void validateSingleValueModel(AwsProxyResponse output, String value) { + try { + SingleValueModel response = mapper.readValue(output.getBody(), SingleValueModel.class); + assertNotNull(response.getValue()); + assertEquals(value, response.getValue()); + } catch (IOException e) { + fail("Exception while parsing response body: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/LambdaHandler.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/LambdaHandler.java new file mode 100644 index 000000000..b5a7ea96d --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/LambdaHandler.java @@ -0,0 +1,33 @@ +package com.amazonaws.serverless.proxy.spring.sbsecurityapp; + + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + + +public class LambdaHandler + implements RequestHandler +{ + SpringBootLambdaContainerHandler handler; + boolean isinitialized = false; + + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) + { + if (!isinitialized) { + isinitialized = true; + try { + handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(TestApplication.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + return null; + } + } + AwsProxyResponse res = handler.proxy(awsProxyRequest, context); + return res; + } +} + diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestApplication.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestApplication.java new file mode 100644 index 000000000..34be320f0 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestApplication.java @@ -0,0 +1,13 @@ +package com.amazonaws.serverless.proxy.spring.sbsecurityapp; + + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.support.SpringBootServletInitializer; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.PropertySource; + + +@SpringBootApplication +@ComponentScan(basePackages = "com.amazonaws.serverless.proxy.spring.sbsecurityapp") +public class TestApplication extends SpringBootServletInitializer { +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestController.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestController.java new file mode 100644 index 000000000..351ddce17 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestController.java @@ -0,0 +1,48 @@ +package com.amazonaws.serverless.proxy.spring.sbsecurityapp; + + +import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +import javax.servlet.http.HttpServletResponse; +import java.security.Principal; +import java.util.List; + + +@RestController +public class TestController { + public static final String ACCESS_DENIED = "AccessDenied"; + + @RequestMapping(path = "/user", method = { RequestMethod.GET }) + public SingleValueModel testGet() { + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + SingleValueModel value = new SingleValueModel(); + + if (principal instanceof UserDetails) { + String username = ((UserDetails)principal).getUsername(); + value.setValue(username); + } else { + value.setValue(null); + } + System.out.println("Principal: " + value.getValue()); + return value; + } + + @RequestMapping(path = "/access-denied", method = { RequestMethod.GET }) + public SingleValueModel accessDenied() { + SingleValueModel model = new SingleValueModel(); + model.setValue(ACCESS_DENIED); + return model; + } + + +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestSecurityConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestSecurityConfig.java new file mode 100644 index 000000000..13b1ed874 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestSecurityConfig.java @@ -0,0 +1,45 @@ +package com.amazonaws.serverless.proxy.spring.sbsecurityapp; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) +@EnableWebSecurity +public class TestSecurityConfig extends WebSecurityConfigurerAdapter { + public static final String USERNAME = "test"; + public static final String NO_ADMIN_USERNAME = "test2"; + public static final String PASSWORD = "123"; + public static BCryptPasswordEncoder pEncoder = new BCryptPasswordEncoder(); + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication().withUser(USERNAME).password(pEncoder.encode(PASSWORD)).roles("ADMIN") + .and().withUser(NO_ADMIN_USERNAME).password(pEncoder.encode(PASSWORD)).roles("USER") + .and().passwordEncoder(pEncoder); + System.out.println("Configure authentication manager"); + } + + @Override + public void configure(WebSecurity web) throws Exception { + System.out.println("Configure Web security"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable(); + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + http.httpBasic(); + http + .authorizeRequests() + .antMatchers("/user").hasRole("ADMIN") + .and().exceptionHandling().accessDeniedPage("/access-denied"); + } +} diff --git a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java index 003390ec0..b9f4f587e 100644 --- a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java +++ b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java @@ -22,6 +22,7 @@ import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; +import javax.servlet.Servlet; import java.util.EnumSet; import java.util.concurrent.CountDownLatch; @@ -109,4 +110,8 @@ public void initialize() throws ContainerInitializationException { Timer.stop(TIMER_STRUTS_2_COLD_START_INIT); log.info("... initialize of Struts2 Lambda Application completed!"); } + + public Servlet getServlet() { + return null; + } } From d22d84bc38dd88540d64f0fa96ee353cdd85b677 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 14 Aug 2019 11:10:35 -0700 Subject: [PATCH 06/40] Updated filter chain to skip filters that should not be applied based on their registration's DispatcherTypes. Changed the default registration to include REQUEST and ASYNC types. This completes the changes for #275 --- .../proxy/internal/servlet/FilterChainHolder.java | 6 ++++++ .../serverless/proxy/internal/servlet/FilterHolder.java | 1 + .../proxy/jersey/JerseyLambdaContainerHandler.java | 5 ++++- .../serverless/proxy/spark/SparkLambdaContainerHandler.java | 4 +++- .../proxy/struts2/Struts2LambdaContainerHandler.java | 4 +++- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java index 44c8fa95b..bec7a8f4b 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java @@ -75,6 +75,12 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo if (currentFilter <= filters.size() - 1) { FilterHolder holder = filters.get(currentFilter); + // confirm that this filter needs to be executed + if (!holder.getRegistration().getDispatcherTypes().contains(servletRequest.getDispatcherType())) { + // skip to the next filter - we have already incremented the currentFilter + doFilter(servletRequest, servletResponse); + } + // lazily initialize filters when they are needed if (!holder.isFilterInitialized()) { holder.init(); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolder.java index 2610622d6..893075d2c 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolder.java @@ -289,6 +289,7 @@ public Collection getServletNameMappings() { public void addMappingForUrlPatterns(EnumSet types, boolean isLast, String... patterns) { if (types == null) { dispatcherTypes.add(DispatcherType.REQUEST); + dispatcherTypes.add(DispatcherType.ASYNC); } else { dispatcherTypes.addAll(types); } diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java index cd99133a4..83c01a334 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java @@ -193,7 +193,10 @@ public void initialize() { // manually add the spark filter to the chain. This should the last one and match all uris FilterRegistration.Dynamic jerseyFilterReg = getServletContext().addFilter("JerseyFilter", jerseyFilter); - jerseyFilterReg.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); + jerseyFilterReg.addMappingForUrlPatterns( + EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.INCLUDE, DispatcherType.FORWARD), + true, "/*" + ); Timer.stop("JERSEY_COLD_START_INIT"); initialized = true; diff --git a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java index dfea29017..368a5e815 100644 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java +++ b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java @@ -215,7 +215,9 @@ public void initialize() // manually add the spark filter to the chain. This should the last one and match all uris FilterRegistration.Dynamic sparkRegistration = getServletContext().addFilter("SparkFilter", embeddedServer.getSparkFilter()); - sparkRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); + sparkRegistration.addMappingForUrlPatterns( + EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.INCLUDE, DispatcherType.FORWARD), + true, "/*"); Timer.stop("SPARK_COLD_START"); } diff --git a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java index b9f4f587e..25a2f7f96 100644 --- a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java +++ b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java @@ -101,7 +101,9 @@ public void initialize() throws ContainerInitializationException { StrutsPrepareAndExecuteFilter filter = new StrutsPrepareAndExecuteFilter(); FilterRegistration.Dynamic filterRegistration = this.getServletContext() .addFilter(STRUTS_FILTER_NAME, filter); - filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); + filterRegistration.addMappingForUrlPatterns( + EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.INCLUDE, DispatcherType.FORWARD), + true, "/*"); } catch (Exception e) { throw new ContainerInitializationException("Could not initialize Struts2", e); } From ce6710efa750d17a2d09425db60061cfbdcaaa8b Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 14 Aug 2019 11:59:35 -0700 Subject: [PATCH 07/40] Bump jackson version to address vulnerability reported by github --- aws-serverless-java-container-core/pom.xml | 10 ++++++++-- aws-serverless-java-container-jersey/pom.xml | 2 +- aws-serverless-java-container-spring/pom.xml | 2 +- aws-serverless-java-container-struts2/pom.xml | 2 +- .../src/main/resources/archetype-resources/pom.xml | 2 +- .../src/main/resources/archetype-resources/pom.xml | 2 +- samples/jersey/pet-store/pom.xml | 2 +- samples/spark/pet-store/pom.xml | 2 +- samples/struts/pet-store/pom.xml | 2 +- 9 files changed, 16 insertions(+), 10 deletions(-) diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml index fe0e66258..52d05b29e 100644 --- a/aws-serverless-java-container-core/pom.xml +++ b/aws-serverless-java-container-core/pom.xml @@ -16,7 +16,7 @@ - 2.9.9 + 2.9.9.3 2.1 3.1.0 @@ -54,7 +54,13 @@ com.fasterxml.jackson.module jackson-module-afterburner - ${jackson.version} + 2.9.9 + + + com.fasterxml.jackson.core + jackson-databind + + diff --git a/aws-serverless-java-container-jersey/pom.xml b/aws-serverless-java-container-jersey/pom.xml index f92c9d702..80a21388e 100644 --- a/aws-serverless-java-container-jersey/pom.xml +++ b/aws-serverless-java-container-jersey/pom.xml @@ -60,7 +60,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.9 + 2.9.9.3 true test diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index e1a3e9c24..15e629305 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -19,7 +19,7 @@ 5.1.8.RELEASE 1.5.21.RELEASE 5.1.5.RELEASE - 2.9.9 + 2.9.9.3 diff --git a/aws-serverless-java-container-struts2/pom.xml b/aws-serverless-java-container-struts2/pom.xml index f981e0876..e414eab51 100644 --- a/aws-serverless-java-container-struts2/pom.xml +++ b/aws-serverless-java-container-struts2/pom.xml @@ -16,7 +16,7 @@ 2.5.20 - 2.9.9 + 2.9.9.3 diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml index 549511720..fd34a171e 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml @@ -15,7 +15,7 @@ 1.8 1.8 2.27 - 2.9.9 + 2.9.9.3 diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml index ca9d4dd18..6d8db9922 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml @@ -16,7 +16,7 @@ 1.8 1.8 - 2.9.9 + 2.9.9.3 2.8.0 diff --git a/samples/jersey/pet-store/pom.xml b/samples/jersey/pet-store/pom.xml index d85e13b08..ddf808ceb 100644 --- a/samples/jersey/pet-store/pom.xml +++ b/samples/jersey/pet-store/pom.xml @@ -77,7 +77,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.9 + 2.9.9.3 diff --git a/samples/spark/pet-store/pom.xml b/samples/spark/pet-store/pom.xml index 32181f3c9..e71a773bc 100644 --- a/samples/spark/pet-store/pom.xml +++ b/samples/spark/pet-store/pom.xml @@ -26,7 +26,7 @@ 1.8 1.8 - 2.9.9 + 2.9.9.3 2.8.0 diff --git a/samples/struts/pet-store/pom.xml b/samples/struts/pet-store/pom.xml index 5763b94a8..5d278dd0e 100644 --- a/samples/struts/pet-store/pom.xml +++ b/samples/struts/pet-store/pom.xml @@ -27,7 +27,7 @@ 1.8 1.8 2.5.20 - 2.9.9 + 2.9.9.3 4.12 2.11.1 From 0e72367669eee8132bddf6a3f1f476b56b2614cd Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 14 Aug 2019 13:53:39 -0700 Subject: [PATCH 08/40] Fixed Jackson versions to only bump databind --- samples/struts/pet-store/pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/struts/pet-store/pom.xml b/samples/struts/pet-store/pom.xml index 5d278dd0e..2cff49e5a 100644 --- a/samples/struts/pet-store/pom.xml +++ b/samples/struts/pet-store/pom.xml @@ -27,7 +27,8 @@ 1.8 1.8 2.5.20 - 2.9.9.3 + 2.9.9 + 2.9.9.3 4.12 2.11.1 @@ -97,7 +98,7 @@ com.fasterxml.jackson.core jackson-databind - ${jackson.version} + ${jackson-databind.version} From cf60ea63b9ddc0bfc5aa29378107b8900559b026 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 14 Aug 2019 17:59:50 -0700 Subject: [PATCH 09/40] Unit tests for SecurityUtils and new jacoco coverage check to block the build at 70%. Finally closing #160 --- aws-serverless-java-container-core/pom.xml | 3 + .../proxy/internal/SecurityUtilsTest.java | 81 +++++++++++++++++++ pom.xml | 2 +- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/SecurityUtilsTest.java diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml index 52d05b29e..f5fafe2ec 100644 --- a/aws-serverless-java-container-core/pom.xml +++ b/aws-serverless-java-container-core/pom.xml @@ -109,6 +109,9 @@ ${basedir}/target/coverage-reports/jacoco-unit.exec ${basedir}/target/coverage-reports/jacoco-unit.exec + + com/amazonaws/serverless/proxy/internal/testutils/** + diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/SecurityUtilsTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/SecurityUtilsTest.java new file mode 100644 index 000000000..2e8fb5c27 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/SecurityUtilsTest.java @@ -0,0 +1,81 @@ +package com.amazonaws.serverless.proxy.internal; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static junit.framework.TestCase.fail; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class SecurityUtilsTest { + + private static final HashMap NAUGHTY_UNICODE_STRINGS = new HashMap<>(); + static { + NAUGHTY_UNICODE_STRINGS.put("Ω≈ç√∫˜µ≤≥÷", "\\u03A9\\u2248\\u00E7\\u221A\\u222B\\u02DC\\u00B5\\u2264\\u2265\\u00F7"); + NAUGHTY_UNICODE_STRINGS.put("åß∂ƒ©˙∆˚¬…æ", "\\u00E5\\u00DF\\u2202\\u0192\\u00A9\\u02D9\\u2206\\u02DA\\u00AC\\u2026\\u00E6"); + NAUGHTY_UNICODE_STRINGS.put("œ∑´®†¥¨ˆøπ“‘", "\\u0153\\u2211\\u00B4\\u00AE\\u2020\\u00A5\\u00A8\\u02C6\\u00F8\\u03C0\\u201C\\u2018"); + NAUGHTY_UNICODE_STRINGS.put("¡™£¢∞§¶•ªº–≠", "\\u00A1\\u2122\\u00A3\\u00A2\\u221E\\u00A7\\u00B6\\u2022\\u00AA\\u00BA\\u2013\\u2260"); + NAUGHTY_UNICODE_STRINGS.put("¸˛Ç◊ı˜Â¯˘¿", "\\u00B8\\u02DB\\u00C7\\u25CA\\u0131\\u02DC\\u00C2\\u00AF\\u02D8\\u00BF"); + NAUGHTY_UNICODE_STRINGS.put("ÅÍÎÏ˝ÓÔÒÚÆ☃", "\\u00C5\\u00CD\\u00CE\\u00CF\\u02DD\\u00D3\\u00D4\\uF8FF\\u00D2\\u00DA\\u00C6\\u2603"); + NAUGHTY_UNICODE_STRINGS.put("Œ„´‰ˇÁ¨ˆØ∏”’", "\\u0152\\u201E\\u00B4\\u2030\\u02C7\\u00C1\\u00A8\\u02C6\\u00D8\\u220F\\u201D\\u2019"); + NAUGHTY_UNICODE_STRINGS.put("⅛⅜⅝⅞", "\\u215B\\u215C\\u215D\\u215E"); + NAUGHTY_UNICODE_STRINGS.put("ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя", "\\u0401\\u0402\\u0403\\u0404\\u0405\\u0406\\u0407\\u0408\\u0409\\u040A\\u040B\\u040C\\u040D\\u040E\\u040F\\u0410\\u0411\\u0412\\u0413\\u0414\\u0415\\u0416\\u0417\\u0418\\u0419\\u041A\\u041B\\u041C\\u041D\\u041E\\u041F\\u0420\\u0421\\u0422\\u0423\\u0424\\u0425\\u0426\\u0427\\u0428\\u0429\\u042A\\u042B\\u042C\\u042D\\u042E\\u042F\\u0430\\u0431\\u0432\\u0433\\u0434\\u0435\\u0436\\u0437\\u0438\\u0439\\u043A\\u043B\\u043C\\u043D\\u043E\\u043F\\u0440\\u0441\\u0442\\u0443\\u0444\\u0445\\u0446\\u0447\\u0448\\u0449\\u044A\\u044B\\u044C\\u044D\\u044E\\u044F"); + NAUGHTY_UNICODE_STRINGS.put("\bhello\nhello\thello\fhello\r", "\\bhello\\nhello\\thello\\fhello\\r"); + NAUGHTY_UNICODE_STRINGS.put("\'", "\'"); + NAUGHTY_UNICODE_STRINGS.put("\"", "\\\""); + NAUGHTY_UNICODE_STRINGS.put("\\", "\\\\"); + NAUGHTY_UNICODE_STRINGS.put("ò", "\\u00F2"); + } + + + @Test + public void encode_nullString_returnsNullIfStringIsNull() { + assertNull(SecurityUtils.encode(null)); + } + + @Test + public void encode_naughtyStrings_encodedCorrectly() { + for (Map.Entry e : NAUGHTY_UNICODE_STRINGS.entrySet()) { + assertEquals(e.getValue(), SecurityUtils.encode(e.getKey())); + } + } + + @Test + public void getValidFilePath_nullOrEmpty_returnsNull() { + assertNull(SecurityUtils.getValidFilePath("")); + assertNull(SecurityUtils.getValidFilePath(null)); + } + + @Test + public void getValidFilePath_writeToTaskPath_throwsIllegalArgumentException() { + boolean thrown = false; + try { + SecurityUtils.getValidFilePath("/var/task/test.txt", true); + } catch (IllegalArgumentException e) { + thrown = true; + } + if (!thrown) { + fail("Did not throw exception"); + } + + try { + SecurityUtils.getValidFilePath("file:///var/task/test.txt", true); + } catch (IllegalArgumentException e) { + return; + } + + fail(); + } + + @Test + public void getValidFilePath_writeToBlockedPath_throwsIllegalArgumentException() { + try { + SecurityUtils.getValidFilePath("/usr/lib/test.txt"); + } catch (IllegalArgumentException e) { + return; + } + fail("Did not throw exception"); + } +} diff --git a/pom.xml b/pom.xml index 72fc1071c..807aecd9b 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ - 0.5 + 0.7 5.1.0 From fa6b5961a5bf25eb9b5f4bc9957c17e73e306d94 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 15 Aug 2019 09:17:05 -0700 Subject: [PATCH 10/40] New unit tests for path validation filter and response writer --- .../servlet/filters/UrlPathValidator.java | 12 ++ .../serverless/proxy/ResponseWriterTest.java | 76 +++++++++++ .../servlet/filters/UrlPathValidatorTest.java | 120 ++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/ResponseWriterTest.java create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidatorTest.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java index a8df22176..23214a3ed 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java @@ -54,6 +54,10 @@ public class UrlPathValidator implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { + if (filterConfig == null) { + invalidStatusCode = DEFAULT_ERROR_CODE; + return; + } if (filterConfig.getInitParameter(PARAM_INVALID_STATUS_CODE) != null) { String statusCode = filterConfig.getInitParameter(PARAM_INVALID_STATUS_CODE); try { @@ -104,6 +108,14 @@ public void destroy() { } + /** + * Returns the status code used in the errors generated by this filter. + * @return The default status code the filter will use + */ + public int getInvalidStatusCode() { + return invalidStatusCode; + } + //------------------------------------------------------------- // Methods - Private diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/ResponseWriterTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/ResponseWriterTest.java new file mode 100644 index 000000000..863ea583f --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/ResponseWriterTest.java @@ -0,0 +1,76 @@ +package com.amazonaws.serverless.proxy; + +import com.amazonaws.serverless.exceptions.InvalidResponseObjectException; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.services.lambda.runtime.Context; +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; + +import java.nio.ByteBuffer; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertFalse; + +public class ResponseWriterTest { + private static int[][] NAUGHTY_STRINGS = { + new int[] { 0b11111110 }, new int[] { 0xff }, new int[] {0xfe, 0xfe, 0xff, 0xff } + }; + + private static String[] VALID_STRINGS = { + "ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ\nᛋᚳᛖᚪᛚ᛫ᚦᛖᚪᚻ᛫ᛗᚪᚾᚾᚪ᛫ᚷᛖᚻᚹᛦᛚᚳ᛫ᛗᛁᚳᛚᚢᚾ᛫ᚻᛦᛏ᛫ᛞᚫᛚᚪᚾ\nᚷᛁᚠ᛫ᚻᛖ᛫ᚹᛁᛚᛖ᛫ᚠᚩᚱ᛫ᛞᚱᛁᚻᛏᚾᛖ᛫ᛞᚩᛗᛖᛋ᛫ᚻᛚᛇᛏᚪᚾ᛬", + "Τη γλώσσα μου έδωσαν ελληνική\nτο σπίτι φτωχικό στις αμμουδιές του Ομήρου.\nΜονάχη έγνοια η γλώσσα μου στις αμμουδιές του Ομήρου.", + "ვეპხის ტყაოსანი შოთა რუსთაველი\nღმერთსი შემვედრე, ნუთუ კვლა დამხსნას სოფლისა შრომასა, ცეცხლს, წყალსა და მიწასა, ჰაერთა თანა მრომასა; მომცნეს ფრთენი და აღვფრინდე, მივჰხვდე მას ჩემსა ნდომასა, დღისით და ღამით ვჰხედვიდე მზისა ელვათა კრთომაასა.", + "ಬಾ ಇಲ್ಲಿ ಸಂಭವಿಸು ಇಂದೆನ್ನ ಹೃದಯದಲಿ \nನಿತ್ಯವೂ ಅವತರಿಪ ಸತ್ಯಾವತಾರ\nಮಣ್ಣಾಗಿ ಮರವಾಗಿ ಮಿಗವಾಗಿ ಕಗವಾಗೀ... \nಮಣ್ಣಾಗಿ ಮರವಾಗಿ ಮಿಗವಾಗಿ ಕಗವಾಗಿ \nಭವ ಭವದಿ ಭತಿಸಿಹೇ ಭವತಿ ದೂರ \nನಿತ್ಯವೂ ಅವತರಿಪ ಸತ್ಯಾವತಾರ || ಬಾ ಇಲ್ಲಿ ||" + }; + + @Test + public void isValidUtf8_testNaughtyStrings_allShouldFail() { + MockResponseWriter rw = new MockResponseWriter(); + for (int[] s : NAUGHTY_STRINGS) { + byte[] buf = new byte[s.length * 4]; + int pos = 0; + for (int v : s) { + for (byte b : convert2Bytes(v)) { + buf[pos] = b; + pos++; + } + } + assertFalse(rw.isValidUtf8(buf)); + } + } + + @Test + public void isValidUtf8_testUtf8Strings_allShouldSucceed() { + MockResponseWriter rw = new MockResponseWriter(); + for (String s : VALID_STRINGS) { + assertTrue(rw.isValidUtf8(s.getBytes())); + } + } + + //little endian + public static byte[] convert2Bytes(int src) { + //an int is equivalent to 32 bits, 4 bytes + byte tgt[] = new byte[4]; + int mask = 0377; /* 0377 in octal*/ + + tgt[3] = (byte)(src >>> 24); + tgt[2] = (byte)((src >> 16) & 0xff); + tgt[1] = (byte)((src >> 8) & 0xff); + tgt[0] = (byte)(src & 0xff); + + return tgt; + } + + public class MockResponseWriter extends ResponseWriter { + + @Override + public HttpServletRequest writeResponse(AwsProxyHttpServletRequest containerResponse, Context lambdaContext) throws InvalidResponseObjectException { + return null; + } + + public boolean testValidUtf8(final byte[] input) { + return isValidUtf8(input); + } + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidatorTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidatorTest.java new file mode 100644 index 000000000..beb273d56 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidatorTest.java @@ -0,0 +1,120 @@ +package com.amazonaws.serverless.proxy.internal.servlet.filters; + +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.servlet.FilterHolder; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import org.junit.Test; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import java.io.IOException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.fail; + +public class UrlPathValidatorTest { + @Test + public void init_noConfig_setsDefaultStatusCode() { + UrlPathValidator pathValidator = new UrlPathValidator(); + try { + pathValidator.init(null); + assertEquals(UrlPathValidator.DEFAULT_ERROR_CODE, pathValidator.getInvalidStatusCode()); + } catch (ServletException e) { + e.printStackTrace(); + fail("Unexpected ServletException"); + } + } + + @Test + public void init_withConfig_setsCorrectStatusCode() { + UrlPathValidator pathValidator = new UrlPathValidator(); + Map params = new HashMap<>(); + params.put(UrlPathValidator.PARAM_INVALID_STATUS_CODE, "401"); + FilterConfig cnf = mockFilterConfig(params); + try { + pathValidator.init(cnf); + assertEquals(401, pathValidator.getInvalidStatusCode()); + } catch (ServletException e) { + e.printStackTrace(); + fail("Unexpected ServletException"); + } + } + + @Test + public void init_withWrongConfig_setsDefaultStatusCode() { + UrlPathValidator pathValidator = new UrlPathValidator(); + Map params = new HashMap<>(); + params.put(UrlPathValidator.PARAM_INVALID_STATUS_CODE, "hello"); + FilterConfig cnf = mockFilterConfig(params); + try { + pathValidator.init(cnf); + assertEquals(UrlPathValidator.DEFAULT_ERROR_CODE, pathValidator.getInvalidStatusCode()); + } catch (ServletException e) { + e.printStackTrace(); + fail("Unexpected ServletException"); + } + } + + @Test + public void doFilter_invalidRelativePathUri_setsDefaultStatusCode() { + AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest(new AwsProxyRequestBuilder("../..", "GET").build(), null, null); + AwsHttpServletResponse resp = new AwsHttpServletResponse(req, null); + UrlPathValidator pathValidator = new UrlPathValidator(); + try { + pathValidator.init(null); + pathValidator.doFilter(req, resp, null); + assertEquals(UrlPathValidator.DEFAULT_ERROR_CODE, resp.getStatus()); + } catch (Exception e) { + e.printStackTrace(); + fail("Unexpected exception"); + } + } + + @Test + public void doFilter_invalidUri_setsDefaultStatusCode() { + AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest(new AwsProxyRequestBuilder("wonkyprotocol://˝Ó#\u0009", "GET").build(), null, null); + AwsHttpServletResponse resp = new AwsHttpServletResponse(req, null); + UrlPathValidator pathValidator = new UrlPathValidator(); + try { + pathValidator.init(null); + pathValidator.doFilter(req, resp, null); + assertEquals(UrlPathValidator.DEFAULT_ERROR_CODE, resp.getStatus()); + } catch (Exception e) { + e.printStackTrace(); + fail("Unexpected exception"); + } + } + + + private FilterConfig mockFilterConfig(Map initParams) { + return new FilterConfig() { + @Override + public String getFilterName() { + return null; + } + + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public String getInitParameter(String s) { + return initParams.get(s); + } + + @Override + public Enumeration getInitParameterNames() { + return Collections.enumeration(initParams.keySet()); + } + }; + } +} From 8128f7809611642f35a66bdbfa77d85067074ca9 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 11 Sep 2019 12:37:13 -0700 Subject: [PATCH 11/40] First basic implementation of the AsyncContext. This allows us to support WebFlux and reactive embedded servers for Spring to address #239 --- .../internal/servlet/AwsAsyncContext.java | 205 +++++++++ .../servlet/AwsHttpServletRequest.java | 17 +- .../servlet/AwsHttpServletRequestWrapper.java | 393 ++++++++++++++++++ .../servlet/AwsHttpServletResponse.java | 5 +- .../internal/servlet/AwsHttpSession.java | 2 +- .../AwsLambdaServletContainerHandler.java | 12 +- .../servlet/AwsProxyHttpServletRequest.java | 48 ++- .../servlet/AwsProxyRequestDispatcher.java | 16 +- .../internal/servlet/AwsServletContext.java | 114 +++-- .../servlet/AwsServletRegistration.java | 157 +++++++ .../internal/servlet/AwsAsyncContextTest.java | 140 +++++++ .../servlet/AwsFilterChainManagerTest.java | 5 +- .../servlet/AwsHttpServletRequestTest.java | 7 - .../servlet/AwsHttpServletResponseTest.java | 9 - .../internal/servlet/AwsHttpSessionTest.java | 109 +++++ .../AwsProxyHttpServletRequestTest.java | 109 +++-- .../AwsProxyRequestDispatcherTest.java | 4 - .../servlet/AwsServletContextTest.java | 150 ++++--- .../servlet/AwsServletRegistrationTest.java | 109 +++++ .../proxy/model/AwsProxyRequestTest.java | 2 +- .../LambdaSpringApplicationInitializer.java | 222 ---------- 21 files changed, 1415 insertions(+), 420 deletions(-) create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestWrapper.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletRegistration.java create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSessionTest.java create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletRegistrationTest.java delete mode 100644 aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java new file mode 100644 index 000000000..ae6d18694 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java @@ -0,0 +1,205 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + + +import com.amazonaws.serverless.proxy.internal.SecurityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Async context for Serverless Java Container. This is used to support reactive embedded servers for our support for + * Spring Boot 2. Behind the scenes, the Async context still uses the CountDownLatch to synchronize response + * generation. + */ +public class AwsAsyncContext implements AsyncContext { + private HttpServletRequest req; + private HttpServletResponse res; + private AwsLambdaServletContainerHandler handler; + private List listeners; + private long timeout; + + private Logger log = LoggerFactory.getLogger(AwsAsyncContext.class); + + public AwsAsyncContext(HttpServletRequest request, HttpServletResponse response, AwsLambdaServletContainerHandler servletHandler) { + log.debug("Initializing async context for request: " + SecurityUtils.crlf(request.getPathInfo()) + " - " + SecurityUtils.crlf(request.getMethod())); + req = request; + res = response; + handler = servletHandler; + listeners = new ArrayList<>(); + timeout = 3000; + } + + @Override + public ServletRequest getRequest() { + return req; + } + + @Override + public ServletResponse getResponse() { + return res; + } + + @Override + public boolean hasOriginalRequestAndResponse() { + return true; + } + + @Override + public void dispatch() { + try { + log.debug("Dispatching request"); + notifyListeners(NotificationType.START_ASYNC, null); + Servlet servlet = ((AwsServletContext)handler.getServletContext()).getServletForPath(req.getPathInfo()); + handler.doFilter(req, res, servlet); + } catch (ServletException | IOException e) { + notifyListeners(NotificationType.ERROR, e); + } + } + + @Override + public void dispatch(String s) { + // amend the request path + req = new AwsHttpServletRequestWrapper(req, s); + + dispatch(); + } + + @Override + public void dispatch(ServletContext servletContext, String s) { + req = new AwsHttpServletRequestWrapper(req, s); + ((AwsHttpServletRequestWrapper)req).setServletContext(servletContext); + dispatch(s); + } + + @Override + public void complete() { + try { + log.debug("Completing request"); + notifyListeners(NotificationType.COMPLETE, null); + res.flushBuffer(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void start(Runnable runnable) { + throw new UnsupportedOperationException("Cannot start background tasks"); + } + + @Override + public void addListener(AsyncListener asyncListener) { + listeners.add(new AsyncListenerHolder(asyncListener, this)); + } + + @Override + public void addListener(AsyncListener asyncListener, ServletRequest servletRequest, ServletResponse servletResponse) { + AsyncListenerHolder holder = new AsyncListenerHolder(asyncListener, this); + holder.setSuppliedRequest(servletRequest); + holder.setSuppliedResponse(servletResponse); + listeners.add(holder); + } + + @Override + public T createListener(Class aClass) throws ServletException { + return null; + } + + @Override + public void setTimeout(long l) { + timeout = l; + } + + @Override + public long getTimeout() { + return timeout; + } + + private void notifyListeners(NotificationType type, Throwable t) { + listeners.forEach((h) -> { + try { + switch (type) { + case COMPLETE: + case START_ASYNC: + case TIMEOUT: + h.getListener().onComplete(h.getAsyncEvent()); + break; + case ERROR: + h.getListener().onError(h.getAsyncEvent(t)); + break; + } + } catch (IOException e) { + if (type != NotificationType.ERROR) { + notifyListeners(NotificationType.ERROR, e); + } + } + }); + } + + private enum NotificationType { + COMPLETE, + ERROR, + START_ASYNC, + TIMEOUT + } + + /** + * The listener holder wraps and AsyncListener with information about its context such as the request, + * response, and async servlet context. + */ + private static final class AsyncListenerHolder { + private AsyncListener listener; + private ServletRequest suppliedRequest; + private ServletResponse suppliedResponse; + private AsyncContext context; + + public AsyncListenerHolder(AsyncListener l, AsyncContext ctx) { + listener = l; + context = ctx; + } + + public AsyncListener getListener() { + return listener; + } + + public void setListener(AsyncListener listener) { + this.listener = listener; + } + + public ServletRequest getSuppliedRequest() { + return suppliedRequest; + } + + public void setSuppliedRequest(ServletRequest suppliedRequest) { + this.suppliedRequest = suppliedRequest; + } + + public ServletResponse getSuppliedResponse() { + return suppliedResponse; + } + + public void setSuppliedResponse(ServletResponse suppliedResponse) { + this.suppliedResponse = suppliedResponse; + } + + public AsyncEvent getAsyncEvent(Throwable t) { + if (suppliedRequest != null && suppliedResponse != null) { + if (t != null) { + return new AsyncEvent(context, suppliedRequest, suppliedResponse, t); + } + return new AsyncEvent(context, suppliedRequest, suppliedResponse); + } + return new AsyncEvent(context); + } + + public AsyncEvent getAsyncEvent() { + return getAsyncEvent(null); + } + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java index d6a11a967..35773cee9 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java @@ -23,12 +23,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.AsyncContext; -import javax.servlet.DispatcherType; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; +import javax.servlet.*; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.UnsupportedEncodingException; @@ -81,7 +79,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { private String queryString; private BasicHeaderValueParser headerParser; - private Logger log = LoggerFactory.getLogger(AwsHttpServletRequest.class); + private static Logger log = LoggerFactory.getLogger(AwsHttpServletRequest.class); //------------------------------------------------------------- @@ -241,13 +239,6 @@ public boolean isAsyncSupported() { return false; } - - @Override - public AsyncContext getAsyncContext() { - return null; - } - - @Override public DispatcherType getDispatcherType() { if (getAttribute(DISPATCHER_TYPE_ATTRIBUTE) != null) { @@ -424,7 +415,7 @@ protected List parseHeaderValue(String headerValue, String valueSep return values; } - protected String decodeRequestPath(String requestPath, ContainerConfig config) { + static String decodeRequestPath(String requestPath, ContainerConfig config) { try { return URLDecoder.decode(requestPath, config.getUriEncoding()); } catch (UnsupportedEncodingException ex) { diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestWrapper.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestWrapper.java new file mode 100644 index 000000000..a8ee1840c --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestWrapper.java @@ -0,0 +1,393 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import javax.servlet.*; +import javax.servlet.http.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Map; + +import static com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest.cleanUri; + +public class AwsHttpServletRequestWrapper implements HttpServletRequest { + private HttpServletRequest originalRequest; + private String newPath; + private ServletContext ctx; + + public AwsHttpServletRequestWrapper(HttpServletRequest req, String path) { + originalRequest = req; + newPath = path; + ctx = originalRequest.getServletContext(); + } + + public void setServletContext(ServletContext newContext) { + ctx = newContext; + } + + @Override + public String getAuthType() { + return originalRequest.getAuthType(); + } + + @Override + public Cookie[] getCookies() { + return originalRequest.getCookies(); + } + + @Override + public long getDateHeader(String s) { + return originalRequest.getDateHeader(s); + } + + @Override + @SuppressFBWarnings("SERVLET_HEADER") + public String getHeader(String s) { + return originalRequest.getHeader(s); + } + + @Override + public Enumeration getHeaders(String s) { + return originalRequest.getHeaders(s); + } + + @Override + public Enumeration getHeaderNames() { + return originalRequest.getHeaderNames(); + } + + @Override + public int getIntHeader(String s) { + return originalRequest.getIntHeader(s); + } + + @Override + public String getMethod() { + return originalRequest.getMethod(); + } + + @Override + public String getPathInfo() { + String pathInfo = cleanUri(newPath); + return AwsHttpServletRequest.decodeRequestPath(pathInfo, LambdaContainerHandler.getContainerConfig()); + } + + @Override + public String getPathTranslated() { + return originalRequest.getPathTranslated(); + } + + @Override + public String getContextPath() { + return originalRequest.getContextPath(); + } + + @Override + @SuppressFBWarnings(value = "SERVLET_QUERY_STRING", justification = "Already Validated on AwsProxyHttpServletRequest") + public String getQueryString() { + return originalRequest.getQueryString(); + } + + @Override + public String getRemoteUser() { + return originalRequest.getRemoteUser(); + } + + @Override + public boolean isUserInRole(String s) { + return originalRequest.isUserInRole(s); + } + + @Override + public Principal getUserPrincipal() { + return originalRequest.getUserPrincipal(); + } + + @Override + @SuppressFBWarnings(value = "SERVLET_SESSION_ID", justification = "Already Validated on AwsProxyHttpServletRequest") + public String getRequestedSessionId() { + return originalRequest.getRequestedSessionId(); + } + + @Override + public String getRequestURI() { + return cleanUri(getContextPath()) + cleanUri(newPath); + } + + @Override + public StringBuffer getRequestURL() { + String url = ""; + url += getServerName(); + url += cleanUri(getContextPath()); + url += cleanUri(newPath); + + return new StringBuffer(getScheme() + "://" + url); + } + + @Override + public String getServletPath() { + return originalRequest.getServletPath(); + } + + @Override + public HttpSession getSession(boolean b) { + return originalRequest.getSession(b); + } + + @Override + public HttpSession getSession() { + return originalRequest.getSession(); + } + + @Override + public String changeSessionId() { + return originalRequest.changeSessionId(); + } + + @Override + public boolean isRequestedSessionIdValid() { + return originalRequest.isRequestedSessionIdValid(); + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return originalRequest.isRequestedSessionIdFromCookie(); + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return originalRequest.isRequestedSessionIdFromURL(); + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + return originalRequest.isRequestedSessionIdFromUrl(); + } + + @Override + public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { + return originalRequest.authenticate(httpServletResponse); + } + + @Override + public void login(String s, String s1) throws ServletException { + originalRequest.login(s, s1); + } + + @Override + public void logout() throws ServletException { + originalRequest.logout(); + } + + @Override + public Collection getParts() throws IOException, ServletException { + return originalRequest.getParts(); + } + + @Override + public Part getPart(String s) throws IOException, ServletException { + return originalRequest.getPart(s); + } + + @Override + public T upgrade(Class aClass) throws IOException, ServletException { + return originalRequest.upgrade(aClass); + } + + @Override + public Object getAttribute(String s) { + return originalRequest.getAttribute(s); + } + + @Override + public Enumeration getAttributeNames() { + return originalRequest.getAttributeNames(); + } + + @Override + public String getCharacterEncoding() { + return originalRequest.getCharacterEncoding(); + } + + @Override + public void setCharacterEncoding(String s) throws UnsupportedEncodingException { + originalRequest.setCharacterEncoding(s); + } + + @Override + public int getContentLength() { + return originalRequest.getContentLength(); + } + + @Override + public long getContentLengthLong() { + return originalRequest.getContentLengthLong(); + } + + @Override + @SuppressFBWarnings(value = "SERVLET_CONTENT_TYPE", justification = "Already Validated on AwsProxyHttpServletRequest") + public String getContentType() { + return originalRequest.getContentType(); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return originalRequest.getInputStream(); + } + + @Override + @SuppressFBWarnings(value = "SERVLET_PARAMETER", justification = "Already Validated on AwsProxyHttpServletRequest") + public String getParameter(String s) { + return originalRequest.getParameter(s); + } + + @Override + @SuppressFBWarnings(value = "SERVLET_PARAMETER", justification = "Already Validated on AwsProxyHttpServletRequest") + public Enumeration getParameterNames() { + return originalRequest.getParameterNames(); + } + + @Override + @SuppressFBWarnings(value = "SERVLET_PARAMETER", justification = "Already Validated on AwsProxyHttpServletRequest") + public String[] getParameterValues(String s) { + return originalRequest.getParameterValues(s); + } + + @Override + @SuppressFBWarnings(value = "SERVLET_PARAMETER", justification = "Already Validated on AwsProxyHttpServletRequest") + public Map getParameterMap() { + return originalRequest.getParameterMap(); + } + + @Override + public String getProtocol() { + return originalRequest.getProtocol(); + } + + @Override + public String getScheme() { + return originalRequest.getScheme(); + } + + @Override + @SuppressFBWarnings(value = "SERVLET_SERVER_NAME", justification = "Already Validated on AwsProxyHttpServletRequest") + public String getServerName() { + return originalRequest.getServerName(); + } + + @Override + public int getServerPort() { + return originalRequest.getServerPort(); + } + + @Override + public BufferedReader getReader() throws IOException { + return originalRequest.getReader(); + } + + @Override + public String getRemoteAddr() { + return originalRequest.getRemoteAddr(); + } + + @Override + public String getRemoteHost() { + return originalRequest.getRemoteAddr(); + } + + @Override + public void setAttribute(String s, Object o) { + originalRequest.setAttribute(s, o); + } + + @Override + public void removeAttribute(String s) { + originalRequest.removeAttribute(s); + } + + @Override + public Locale getLocale() { + return originalRequest.getLocale(); + } + + @Override + public Enumeration getLocales() { + return originalRequest.getLocales(); + } + + @Override + public boolean isSecure() { + return originalRequest.isSecure(); + } + + @Override + public RequestDispatcher getRequestDispatcher(String s) { + return originalRequest.getRequestDispatcher(s); + } + + @Override + public String getRealPath(String s) { + return originalRequest.getRealPath(s); + } + + @Override + public int getRemotePort() { + return originalRequest.getRemotePort(); + } + + @Override + public String getLocalName() { + return originalRequest.getLocalName(); + } + + @Override + public String getLocalAddr() { + return originalRequest.getLocalAddr(); + } + + @Override + public int getLocalPort() { + return originalRequest.getLocalPort(); + } + + @Override + public ServletContext getServletContext() { + return ctx; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + return originalRequest.startAsync(); + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { + return originalRequest.startAsync(servletRequest, servletResponse); + } + + @Override + public boolean isAsyncStarted() { + return originalRequest.isAsyncStarted(); + } + + @Override + public boolean isAsyncSupported() { + return originalRequest.isAsyncSupported(); + } + + @Override + public AsyncContext getAsyncContext() { + return originalRequest.getAsyncContext(); + } + + @Override + public DispatcherType getDispatcherType() { + return originalRequest.getDispatcherType(); + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java index d477587f4..da695c509 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java @@ -25,6 +25,7 @@ import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.HttpHeaders; import java.io.ByteArrayOutputStream; @@ -66,7 +67,7 @@ public class AwsHttpServletResponse private PrintWriter writer; private ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream(); private CountDownLatch writersCountDownLatch; - private AwsHttpServletRequest request; + private HttpServletRequest request; private boolean isCommitted = false; private Logger log = LoggerFactory.getLogger(AwsHttpServletResponse.class); @@ -81,7 +82,7 @@ public class AwsHttpServletResponse * function while the response is asynchronously written by the underlying container/application * @param latch A latch used to inform the ContainerHandler that we are done receiving the response data */ - public AwsHttpServletResponse(AwsHttpServletRequest req, CountDownLatch latch) { + public AwsHttpServletResponse(HttpServletRequest req, CountDownLatch latch) { writersCountDownLatch = latch; characterEncoding = null; request = req; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSession.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSession.java index 1372b8f3d..1ac0908fd 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSession.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSession.java @@ -58,7 +58,7 @@ public String getId() { @Override public long getLastAccessedTime() { - return 0; + return lastAccessedTime; } @Override diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java index 77310a423..95af74e67 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java @@ -22,17 +22,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.DispatcherType; -import javax.servlet.FilterChain; -import javax.servlet.Servlet; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletResponse; +import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.IOException; +import java.util.Map; /** @@ -56,7 +52,6 @@ public abstract class AwsLambdaServletContainerHandler filterChainManager; - private boolean startupExecuted; //------------------------------------------------------------- // Variables - Protected @@ -79,7 +74,6 @@ protected AwsLambdaServletContainerHandler(Class requestTypeClass, // set the default log formatter for servlet implementations setLogFormatter(new ApacheCombinedServletLogFormatter<>()); setServletContext(new AwsServletContext(this)); - startupExecuted = false; } //------------------------------------------------------------- @@ -161,8 +155,6 @@ protected void doFilter(HttpServletRequest request, HttpServletResponse response } } - public abstract Servlet getServlet(); - //------------------------------------------------------------- // Inner Class - //------------------------------------------------------------- diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java index a4d0ab816..cf9729b5a 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java @@ -38,10 +38,7 @@ import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpUpgradeHandler; -import javax.servlet.http.Part; +import javax.servlet.http.*; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.SecurityContext; @@ -84,10 +81,13 @@ public class AwsProxyHttpServletRequest extends AwsHttpServletRequest { private AwsProxyRequest request; private SecurityContext securityContext; + private AsyncContext asyncContext; private Map> urlEncodedFormParameters; private Map multipartFormParameters; private static Logger log = LoggerFactory.getLogger(AwsProxyHttpServletRequest.class); private ContainerConfig config; + private AwsHttpServletResponse response; + private AwsLambdaServletContainerHandler containerHandler; //------------------------------------------------------------- // Constructors @@ -111,6 +111,22 @@ public AwsProxyRequest getAwsProxyRequest() { return this.request; } + public AwsHttpServletResponse getResponse() { + return response; + } + + public void setResponse(AwsHttpServletResponse response) { + this.response = response; + } + + public AwsLambdaServletContainerHandler getContainerHandler() { + return containerHandler; + } + + public void setContainerHandler(AwsLambdaServletContainerHandler containerHandler) { + this.containerHandler = containerHandler; + } + //------------------------------------------------------------- // Implementation - HttpServletRequest //------------------------------------------------------------- @@ -660,19 +676,37 @@ public int getRemotePort() { } + @Override + public boolean isAsyncSupported() { + return true; + } + + @Override public AsyncContext startAsync() throws IllegalStateException { - return null; + asyncContext = new AwsAsyncContext(this, response, containerHandler); + log.debug("Starting async context for request: " + SecurityUtils.crlf(request.getRequestContext().getRequestId())); + return asyncContext; } @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { - return null; + asyncContext = new AwsAsyncContext((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, containerHandler); + log.debug("Starting async context for request: " + SecurityUtils.crlf(request.getRequestContext().getRequestId())); + return asyncContext; } + @Override + public AsyncContext getAsyncContext() { + if (asyncContext == null) { + throw new IllegalStateException("Request " + SecurityUtils.crlf(request.getRequestContext().getRequestId()) + + " is not in asynchronous mode. Call startAsync before atttempting to get the async context."); + } + return asyncContext; + } //------------------------------------------------------------- // Methods - Private @@ -728,7 +762,7 @@ private Map getMultipartFormParametersMap() { } - private String cleanUri(String uri) { + static String cleanUri(String uri) { String finalUri = (uri == null ? "/" : uri); if (finalUri.equals("/")) { return finalUri; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java index bb61b7b2c..da1c6d38a 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java @@ -7,11 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.DispatcherType; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -76,13 +72,13 @@ public void forward(ServletRequest servletRequest, ServletResponse servletRespon } if (isNamedDispatcher) { - lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, lambdaContainerHandler.getServlet()); + lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, getServlet((HttpServletRequest)servletRequest)); return; } servletRequest.setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.FORWARD); setRequestPath(servletRequest, dispatchTo); - lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, lambdaContainerHandler.getServlet()); + lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, getServlet((HttpServletRequest)servletRequest)); } @@ -107,7 +103,7 @@ public void include(ServletRequest servletRequest, ServletResponse servletRespon SecurityUtils.encode(SecurityUtils.crlf(((HttpServletRequest) servletRequest).getQueryString()))); setRequestPath(servletRequest, dispatchTo); } - lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, lambdaContainerHandler.getServlet()); + lambdaContainerHandler.doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, getServlet((HttpServletRequest)servletRequest)); } /** @@ -130,4 +126,8 @@ void setRequestPath(ServletRequest req, final String destinationPath) { } ((AwsProxyRequest)req.getAttribute(API_GATEWAY_EVENT_PROPERTY)).setPath(dispatchTo); } + + private Servlet getServlet(HttpServletRequest req) { + return ((AwsServletContext)lambdaContainerHandler.getServletContext()).getServletForPath(req.getPathInfo()); + } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java index b3f2f1fd6..97e866e78 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java @@ -21,32 +21,16 @@ import org.slf4j.LoggerFactory; import javax.activation.MimetypesFileTypeMap; -import javax.servlet.Filter; -import javax.servlet.FilterRegistration; -import javax.servlet.RequestDispatcher; -import javax.servlet.Servlet; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletRegistration; -import javax.servlet.SessionCookieConfig; -import javax.servlet.SessionTrackingMode; +import javax.servlet.*; import javax.servlet.descriptor.JspConfigDescriptor; import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.Enumeration; -import java.util.EventListener; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; /** @@ -69,6 +53,7 @@ public class AwsServletContext // Variables - Private //------------------------------------------------------------- private Map filters; + private Map servletRegistrations; private Map attributes; private Map initParameters; private AwsLambdaServletContainerHandler containerHandler; @@ -91,6 +76,7 @@ public AwsServletContext(AwsLambdaServletContainerHandler containerHandler) { this.attributes = new HashMap<>(); this.initParameters = new HashMap<>(); this.filters = new LinkedHashMap<>(); + this.servletRegistrations = new HashMap<>(); } @@ -192,21 +178,59 @@ public RequestDispatcher getNamedDispatcher(String s) { @Override @Deprecated public Servlet getServlet(String s) throws ServletException { - throw new UnsupportedOperationException(); + return servletRegistrations.get(s).getServlet(); + } + + public Servlet getServletForPath(String path) { + String[] pathParts = path.split("/"); + for (AwsServletRegistration reg : servletRegistrations.values()) { + for (String p : reg.getMappings()) { + try { + if ("".equals(p) || "/".equals(p) || "/*".equals(p)) { + return reg.getServlet(); + } + // if I have no path and I haven't matched something now I'll just move on to the next + if ("".equals(path) || "/".equals(path)) { + continue; + } + String[] regParts = p.split("/"); + for (int i = 0; i < regParts.length; i++) { + if (!regParts[i].equals(pathParts[i]) && !"*".equals(regParts[i])) { + break; + } + if (i == regParts.length - 1 && (regParts[i].equals(pathParts[i]) || "*".equals(regParts[i]))) { + return reg.getServlet(); + } + } + } catch (ServletException e) { + return null; + } + } + } + return null; } @Override @Deprecated public Enumeration getServlets() { - throw new UnsupportedOperationException(); + return Collections.enumeration(servletRegistrations.entrySet().stream() + .map((e) -> { + try { + return e.getValue().getServlet(); + } catch (ServletException ex) { + ex.printStackTrace(); + return null; + } + } ) + .collect(Collectors.toList())); } @Override @Deprecated public Enumeration getServletNames() { - throw new UnsupportedOperationException(); + return Collections.enumeration(servletRegistrations.keySet()); } @@ -302,47 +326,61 @@ public String getServletContextName() { @Override public ServletRegistration.Dynamic addServlet(String s, String s1) { - log("Called addServlet: " + s1); - log("Implemented frameworks are responsible for registering servlets"); - throw new UnsupportedOperationException(); + try { + Class servletClass = (Class) this.getClassLoader().loadClass(s1); + Servlet servlet = createServlet(servletClass); + servletRegistrations.put(s, new AwsServletRegistration(s, servlet, this)); + return servletRegistrations.get(s); + } catch (ServletException | ClassNotFoundException e) { + throw new RuntimeException(e); + } } @Override public ServletRegistration.Dynamic addServlet(String s, Servlet servlet) { - log("Called addServlet: " + servlet.getClass().getName()); - log("Implemented frameworks are responsible for registering servlets"); - throw new UnsupportedOperationException(); + servletRegistrations.put(s, new AwsServletRegistration(s, servlet, this)); + return servletRegistrations.get(s); } @Override public ServletRegistration.Dynamic addServlet(String s, Class aClass) { - log("Called addServlet: " + aClass.getName()); - log("Implemented frameworks are responsible for registering servlets"); - throw new UnsupportedOperationException(); + try { + Servlet servlet = createServlet(aClass); + servletRegistrations.put(s, new AwsServletRegistration(s, servlet, this)); + return servletRegistrations.get(s); + } catch (ServletException e) { + throw new RuntimeException(e); + } } @Override public T createServlet(Class aClass) throws ServletException { - log("Called createServlet: " + aClass.getName()); - log("Implemented frameworks are responsible for creating servlets"); - throw new UnsupportedOperationException(); + /*log("Called createServlet: " + aClass.getName()); + log("Implemented frameworks are responsible for creating servlets");*/ + // TODO: This method introspects the given clazz for the following annotations: ServletSecurity, MultipartConfig, + // javax.annotation.security.RunAs, and javax.annotation.security.DeclareRoles. In addition, this method supports + // resource injection if the given clazz represents a Managed Bean. See the Java EE platform and JSR 299 specifications + // for additional details about Managed Beans and resource injection. + try { + return aClass.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new ServletException(e); + } } @Override public ServletRegistration getServletRegistration(String s) { - // TODO: This could come from the reader interface - return null; + return servletRegistrations.get(s); } @Override public Map getServletRegistrations() { - // TODO: This could come from the reader interface - return null; + return servletRegistrations; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletRegistration.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletRegistration.java new file mode 100644 index 000000000..e3ce620bf --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletRegistration.java @@ -0,0 +1,157 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import javax.servlet.*; +import java.util.*; + +/** + * Stores information about a servlet registered with Serverless Java Container's ServletContext. + */ +public class AwsServletRegistration implements ServletRegistration, ServletRegistration.Dynamic { + private String servletName; + private Servlet servlet; + private AwsServletContext ctx; + private Map initParameters; + private int loadOnStartup; + private String runAsRole; + private boolean asyncSupported; + private Map servletPathMappings; + + + public AwsServletRegistration(String name, Servlet s, AwsServletContext context) { + servletName = name; + servlet = s; + ctx = context; + initParameters = new HashMap<>(); + servletPathMappings = new HashMap<>(); + loadOnStartup = -1; + asyncSupported = true; + } + + @Override + public Set addMapping(String... strings) { + Set failedMappings = new HashSet<>(); + for (String s : strings) { + if (servletPathMappings.containsKey(s)) { + failedMappings.add(s); + continue; + } + servletPathMappings.put(s, this); + } + return failedMappings; + } + + @Override + public Collection getMappings() { + return servletPathMappings.keySet(); + } + + @Override + public String getRunAsRole() { + return runAsRole; + } + + @Override + public String getName() { + return servletName; + } + + @Override + public String getClassName() { + return servlet.getClass().getName(); + } + + @Override + public boolean setInitParameter(String s, String s1) { + if (initParameters.containsKey(s)) { + return false; + } + initParameters.put(s, s1); + return true; + } + + @Override + public String getInitParameter(String s) { + return initParameters.get(s); + } + + @Override + public Set setInitParameters(Map map) { + Set failedParameters = new HashSet<>(); + for (Map.Entry param : map.entrySet()) { + if (initParameters.containsKey(param.getKey())) { + failedParameters.add(param.getKey()); + } + initParameters.put(param.getKey(), param.getValue()); + } + return failedParameters; + } + + @Override + public Map getInitParameters() { + return initParameters; + } + + public Servlet getServlet() throws ServletException { + if (servlet.getServletConfig() == null) { + servlet.init(getServletConfig()); + } + return servlet; + } + + @Override + public void setLoadOnStartup(int i) { + loadOnStartup = i; + } + + public int getLoadOnStartup() { + return loadOnStartup; + } + + @Override + public Set setServletSecurity(ServletSecurityElement servletSecurityElement) { + return null; + } + + @Override + public void setMultipartConfig(MultipartConfigElement multipartConfigElement) { + + } + + @Override + public void setRunAsRole(String s) { + runAsRole = s; + } + + @Override + public void setAsyncSupported(boolean b) { + asyncSupported = b; + } + + public boolean isAsyncSupported() { + return asyncSupported; + } + + public ServletConfig getServletConfig() { + return new ServletConfig() { + @Override + public String getServletName() { + return servletName; + } + + @Override + public ServletContext getServletContext() { + return ctx; + } + + @Override + public String getInitParameter(String s) { + return initParameters.get(s); + } + + @Override + public Enumeration getInitParameterNames() { + return Collections.enumeration(initParameters.keySet()); + } + }; + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java new file mode 100644 index 000000000..d5c86f1c4 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java @@ -0,0 +1,140 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; +import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.services.lambda.runtime.Context; +import org.junit.Test; + +import javax.servlet.AsyncContext; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; + +import static junit.framework.TestCase.assertNotNull; +import static org.junit.Assert.assertEquals; + +public class AwsAsyncContextTest { + private MockLambdaContext lambdaCtx = new MockLambdaContext(); + private MockContainerHandler handler = new MockContainerHandler(); + private AwsServletContextTest.TestServlet srv1 = new AwsServletContextTest.TestServlet("srv1"); + private AwsServletContextTest.TestServlet srv2 = new AwsServletContextTest.TestServlet("srv2"); + private AwsServletContext ctx = getCtx(); + + @Test + public void dispatch_sendsToCorrectServlet() { + AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest(new AwsProxyRequestBuilder("/srv1/hello", "GET").build(), lambdaCtx, null); + req.setResponse(handler.getContainerResponse(req, new CountDownLatch(1))); + req.setServletContext(ctx); + req.setContainerHandler(handler); + + AsyncContext asyncCtx = req.startAsync(); + handler.setDesiredStatus(201); + asyncCtx.dispatch(); + assertNotNull(handler.getSelectedServlet()); + assertEquals(srv1, handler.getSelectedServlet()); + assertEquals(201, handler.getResponse().getStatus()); + + req = new AwsProxyHttpServletRequest(new AwsProxyRequestBuilder("/srv5/hello", "GET").build(), lambdaCtx, null); + req.setResponse(handler.getContainerResponse(req, new CountDownLatch(1))); + req.setServletContext(ctx); + req.setContainerHandler(handler); + asyncCtx = req.startAsync(); + handler.setDesiredStatus(202); + asyncCtx.dispatch(); + assertNotNull(handler.getSelectedServlet()); + assertEquals(srv2, handler.getSelectedServlet()); + assertEquals(202, handler.getResponse().getStatus()); + } + + @Test + public void dispatchNewPath_sendsToCorrectServlet() { + AwsProxyHttpServletRequest req = new AwsProxyHttpServletRequest(new AwsProxyRequestBuilder("/srv1/hello", "GET").build(), lambdaCtx, null); + req.setResponse(handler.getContainerResponse(req, new CountDownLatch(1))); + req.setServletContext(ctx); + req.setContainerHandler(handler); + + AsyncContext asyncCtx = req.startAsync(); + handler.setDesiredStatus(301); + asyncCtx.dispatch("/srv4/hello"); + assertNotNull(handler.getSelectedServlet()); + assertEquals(srv2, handler.getSelectedServlet()); + assertNotNull(handler.getResponse()); + assertEquals(301, handler.getResponse().getStatus()); + } + + private AwsServletContext getCtx() { + AwsServletContext ctx = new AwsServletContext(handler); + handler.setServletContext(ctx); + + ServletRegistration.Dynamic reg1 = ctx.addServlet("srv1", srv1); + reg1.addMapping("/srv1"); + + ServletRegistration.Dynamic reg2 = ctx.addServlet("srv2", srv2); + reg2.addMapping("/"); + return ctx; + } + + public static class MockContainerHandler extends AwsLambdaServletContainerHandler { + private int desiredStatus; + private HttpServletResponse response; + private Servlet selectedServlet; + + public MockContainerHandler() { + super(AwsProxyRequest.class, AwsProxyResponse.class, new AwsProxyHttpServletRequestReader(), new AwsProxyHttpServletResponseWriter(), new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler()); + desiredStatus = 200; + } + + @Override + protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + return new AwsHttpServletResponse(request, latch); + } + + @Override + protected void doFilter(HttpServletRequest request, HttpServletResponse response, Servlet servlet) throws IOException, ServletException { + selectedServlet = servlet; + try { + this.response = response; + //handleRequest((AwsProxyHttpServletRequest)request, , new MockLambdaContext()); + if (AwsProxyHttpServletRequest.class.isAssignableFrom(request.getClass())) { + ((AwsProxyHttpServletRequest)request).setResponse((AwsHttpServletResponse)this.response); + } + this.response.setStatus(desiredStatus); + this.response.flushBuffer(); + } catch (Exception e) { + e.printStackTrace(); + throw new ServletException(e); + } + } + + @Override + protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + + } + + @Override + public void initialize() throws ContainerInitializationException { + + } + + public void setDesiredStatus(int status) { + desiredStatus = status; + } + + public AwsHttpServletResponse getResponse() { + return (AwsHttpServletResponse)response; + } + + public Servlet getSelectedServlet() { + return selectedServlet; + } + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java index c5ed8f248..dd344d3b0 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java @@ -230,19 +230,18 @@ private static class MockFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { - System.out.println("Init"); + } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - System.out.println("DoFilter"); servletRequest.setAttribute(REQUEST_CUSTOM_ATTRIBUTE_NAME, REQUEST_CUSTOM_ATTRIBUTE_VALUE); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { - System.out.println("Destroy"); + } } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java index 3cebc02fb..5372dfa95 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java @@ -71,11 +71,6 @@ public void headers_parseHeaderValue_complexAccept() { AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(complexAcceptHeader, mockContext, null, config); List values = request.parseHeaderValue(request.getHeader(HttpHeaders.ACCEPT), ",", ";"); - try { - System.out.println(new ObjectMapper().writeValueAsString(values)); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } assertEquals(4, values.size()); } @@ -124,7 +119,6 @@ public void headers_parseHeaderValue_headerWithPaddingButNotBase64Encoded() { List result = context.parseHeaderValue("hello="); assertTrue(result.size() > 0); assertEquals("hello", result.get(0).getKey()); - System.out.println("\"" + result.get(0).getValue() + "\""); assertNull(result.get(0).getValue()); } @@ -139,7 +133,6 @@ public void queryString_generateQueryString_validQuery() { e.printStackTrace(); fail("Could not generate query string"); } - System.out.println(parsedString); assertTrue(parsedString.contains("one=two")); assertTrue(parsedString.contains("three=four")); assertTrue(parsedString.contains("&") && parsedString.indexOf("&") > 0 && parsedString.indexOf("&") < parsedString.length()); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java index 1feeb5e94..65fc91776 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java @@ -44,7 +44,6 @@ public void cookie_addCookie_verifyPath() { resp.addCookie(pathCookie); String cookieHeader = resp.getHeader(HttpHeaders.SET_COOKIE); - System.out.println("Cookie string: " + cookieHeader); assertNotNull(cookieHeader); assertTrue(cookieHeader.contains("Path=" + COOKIE_PATH)); assertTrue(cookieHeader.contains(COOKIE_NAME + "=" + COOKIE_VALUE)); @@ -58,7 +57,6 @@ public void cookie_addCookie_verifySecure() { resp.addCookie(secureCookie); String cookieHeader = resp.getHeader(HttpHeaders.SET_COOKIE); - System.out.println("Cookie string: " + cookieHeader); assertNotNull(cookieHeader); assertTrue(cookieHeader.contains("; Secure")); assertTrue(cookieHeader.contains(COOKIE_NAME + "=" + COOKIE_VALUE)); @@ -72,7 +70,6 @@ public void cookie_addCookie_verifyDomain() { resp.addCookie(domainCookie); String cookieHeader = resp.getHeader(HttpHeaders.SET_COOKIE); - System.out.println("Cookie string: " + cookieHeader); assertNotNull(cookieHeader); assertTrue(cookieHeader.contains("; Domain=" + COOKIE_DOMAIN)); assertTrue(cookieHeader.contains(COOKIE_NAME + "=" + COOKIE_VALUE)); @@ -86,7 +83,6 @@ public void cookie_addCookie_defaultMaxAgeIsNegative() { resp.addCookie(maxAgeCookie); String cookieHeader = resp.getHeader(HttpHeaders.SET_COOKIE); - System.out.println("Cookie string: " + cookieHeader); assertNotNull(cookieHeader); assertFalse(cookieHeader.contains("Max-Age=")); assertTrue(cookieHeader.contains(COOKIE_NAME + "=" + COOKIE_VALUE)); @@ -100,7 +96,6 @@ public void cookie_addCookie_positiveMaxAgeIsPresent() { resp.addCookie(maxAgeCookie); String cookieHeader = resp.getHeader(HttpHeaders.SET_COOKIE); - System.out.println("Cookie string: " + cookieHeader); assertNotNull(cookieHeader); assertTrue(cookieHeader.contains("; Max-Age=")); assertTrue(cookieHeader.contains(COOKIE_NAME + "=" + COOKIE_VALUE)); @@ -121,7 +116,6 @@ public void cookie_addCookie_positiveMaxAgeExpiresDate() { testExpiration.setTimeZone(TimeZone.getTimeZone(AwsHttpServletResponse.COOKIE_DEFAULT_TIME_ZONE)); String cookieHeader = resp.getHeader(HttpHeaders.SET_COOKIE); - System.out.println("Cookie string: " + cookieHeader); assertNotNull(cookieHeader); assertTrue(cookieHeader.contains("; Max-Age=")); assertTrue(cookieHeader.contains(COOKIE_NAME + "=" + COOKIE_VALUE)); @@ -129,8 +123,6 @@ public void cookie_addCookie_positiveMaxAgeExpiresDate() { SimpleDateFormat dateFormat = new SimpleDateFormat(AwsHttpServletResponse.HEADER_DATE_PATTERN); Calendar expiration = getExpires(cookieHeader); - System.out.println("Cookie date: " + dateFormat.format(expiration.getTime())); - System.out.println("Test date: " + dateFormat.format(testExpiration.getTime())); long dateDiff = testExpiration.getTimeInMillis() - expiration.getTimeInMillis(); assertTrue(Math.abs(dateDiff) < COOKIE_GRACE_COMPARE_MILLIS); @@ -314,7 +306,6 @@ private int getMaxAge(String header) { assertTrue(ageMatcher.find()); assertTrue(ageMatcher.groupCount() >= 1); String ageString = ageMatcher.group(1); - System.out.println("Age string: " + ageString); return Integer.parseInt(ageString); } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSessionTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSessionTest.java new file mode 100644 index 000000000..bb9e0d476 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSessionTest.java @@ -0,0 +1,109 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import org.junit.Test; + +import java.time.Instant; +import java.util.Enumeration; + +import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.fail; +import static org.junit.Assert.*; + +public class AwsHttpSessionTest { + + @Test + public void new_withNullId_throwsException() { + try { + AwsHttpSession session = new AwsHttpSession(null); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("cannot be null")); + return; + } + fail("Did not throw exception with null ID"); + } + + @Test + public void new_withValidId_setsIdCorrectly() { + AwsHttpSession session = new AwsHttpSession("id"); + assertEquals("id", session.getId()); + } + + @Test + public void new_creationTimePopulatedCorrectly() { + AwsHttpSession session = new AwsHttpSession("id"); + assertTrue(session.getCreationTime() > Instant.now().getEpochSecond() - 1); + assertEquals(AwsHttpSession.SESSION_DURATION_SEC, session.getMaxInactiveInterval()); + assertEquals(session.getLastAccessedTime(), session.getCreationTime()); + } + + @Test + public void values_throwsUnsupportedOperationException() { + int exCount = 0; + AwsHttpSession sess = new AwsHttpSession("id"); + + try { + sess.putValue("test", "test"); + } catch (UnsupportedOperationException e) { + exCount++; + } + try { + sess.removeValue("test"); + } catch (UnsupportedOperationException e) { + exCount++; + } + try { + sess.getValue("test"); + } catch (UnsupportedOperationException e) { + exCount++; + } + try { + sess.getValueNames(); + } catch (UnsupportedOperationException e) { + exCount++; + } + assertEquals(4, exCount); + } + + @Test + public void attributes_dataStoredCorrectly() throws InterruptedException { + AwsHttpSession sess = new AwsHttpSession("id"); + sess.setAttribute("test", "test"); + sess.setAttribute("test2", "test2"); + Enumeration attrs = sess.getAttributeNames(); + int attrsCnt = 0; + while (attrs.hasMoreElements()) { + attrs.nextElement(); + attrsCnt++; + } + assertEquals(2, attrsCnt); + assertEquals(sess.getAttribute("test"), "test"); + sess.removeAttribute("test2"); + attrs = sess.getAttributeNames(); + attrsCnt = 0; + while (attrs.hasMoreElements()) { + attrs.nextElement(); + attrsCnt++; + } + assertEquals(1, attrsCnt); + + + // changing attribute should touch the session + Thread.sleep(1000); + sess.setAttribute("test3", "test3"); + assertTrue(sess.getLastAccessedTime() > sess.getCreationTime()); + } + + @Test + public void validSession_expectCorrectValidationOrInvalidation() throws InterruptedException { + AwsHttpSession sess = new AwsHttpSession("id"); + assertTrue(sess.isValid()); + assertTrue(sess.isNew()); + + Thread.sleep(1000); + sess.setAttribute("test", "test"); + assertFalse(sess.isNew()); + sess.invalidate(); + assertFalse(sess.isValid()); + assertNull(sess.getAttribute("test")); + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java index 05a731a89..62c785451 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java @@ -4,11 +4,15 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.services.lambda.runtime.Context; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.SecurityContext; import java.io.IOException; import java.io.InputStream; @@ -16,16 +20,14 @@ import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; import static org.junit.Assert.*; +@RunWith(Parameterized.class) public class AwsProxyHttpServletRequestTest { + private boolean isWrapped; + private static final String CUSTOM_HEADER_KEY = "X-Custom-Header"; private static final String CUSTOM_HEADER_VALUE = "Custom-Header-Value"; private static final String FORM_PARAM_NAME = "name"; @@ -74,9 +76,27 @@ public class AwsProxyHttpServletRequestTest { .queryString(FORM_PARAM_NAME, QUERY_STRING_NAME_VALUE).build(); + public AwsProxyHttpServletRequestTest(boolean wrap) { + isWrapped = wrap; + } + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[] { false, true }); + } + + private HttpServletRequest getRequest(AwsProxyRequest req, Context lambdaCtx, SecurityContext securityCtx) { + HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(req, lambdaCtx, securityCtx); + if (isWrapped) { + servletRequest = new AwsHttpServletRequestWrapper(servletRequest, req.getPath()); + } + return servletRequest; + } + + @Test public void headers_getHeader_validRequest() { - HttpServletRequest request = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null); + HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); assertNotNull(request.getHeader(CUSTOM_HEADER_KEY)); assertEquals(CUSTOM_HEADER_VALUE, request.getHeader(CUSTOM_HEADER_KEY)); assertEquals(MediaType.APPLICATION_JSON, request.getContentType()); @@ -84,7 +104,7 @@ public void headers_getHeader_validRequest() { @Test public void headers_getRefererAndUserAgent_returnsContextValues() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_USER_AGENT_REFERER, null, null); + HttpServletRequest request = getRequest(REQUEST_USER_AGENT_REFERER, null, null); assertNotNull(request.getHeader("Referer")); assertEquals(REFERER, request.getHeader("Referer")); assertEquals(REFERER, request.getHeader("referer")); @@ -96,7 +116,7 @@ public void headers_getRefererAndUserAgent_returnsContextValues() { @Test public void formParams_getParameter_validForm() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_FORM_URLENCODED, null, null); + HttpServletRequest request = getRequest(REQUEST_FORM_URLENCODED, null, null); assertNotNull(request); assertNotNull(request.getParameter(FORM_PARAM_NAME)); assertEquals(FORM_PARAM_NAME_VALUE, request.getParameter(FORM_PARAM_NAME)); @@ -104,21 +124,21 @@ public void formParams_getParameter_validForm() { @Test public void formParams_getParameter_null() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_INVALID_FORM_URLENCODED, null, null); + HttpServletRequest request = getRequest(REQUEST_INVALID_FORM_URLENCODED, null, null); assertNotNull(request); assertNull(request.getParameter(FORM_PARAM_NAME)); } @Test public void formParams_getParameter_multipleParams() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_FORM_URLENCODED_AND_QUERY, null, null); + HttpServletRequest request = getRequest(REQUEST_FORM_URLENCODED_AND_QUERY, null, null); assertNotNull(request); assertEquals(2, request.getParameterValues(FORM_PARAM_NAME).length); } @Test public void formParams_getParameter_queryStringPrecendence() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_FORM_URLENCODED_AND_QUERY, null, null); + HttpServletRequest request = getRequest(REQUEST_FORM_URLENCODED_AND_QUERY, null, null); assertNotNull(request); assertEquals(2, request.getParameterValues(FORM_PARAM_NAME).length); assertEquals(QUERY_STRING_NAME_VALUE, request.getParameter(FORM_PARAM_NAME)); @@ -126,14 +146,14 @@ public void formParams_getParameter_queryStringPrecendence() { @Test public void dateHeader_noDate_returnNegativeOne() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_FORM_URLENCODED_AND_QUERY, null, null); + HttpServletRequest request = getRequest(REQUEST_FORM_URLENCODED_AND_QUERY, null, null); assertNotNull(request); assertEquals(-1L, request.getDateHeader(HttpHeaders.DATE)); } @Test public void dateHeader_correctDate_parseToCorrectLong() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_WITH_DATE, null, null); + HttpServletRequest request = getRequest(REQUEST_WITH_DATE, null, null); assertNotNull(request); String instantString = AwsHttpServletRequest.dateFormatter.format(REQUEST_DATE); @@ -143,7 +163,7 @@ public void dateHeader_correctDate_parseToCorrectLong() { @Test public void scheme_getScheme_https() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_FORM_URLENCODED, null, null); + HttpServletRequest request = getRequest(REQUEST_FORM_URLENCODED, null, null); assertNotNull(request); assertNotNull(request.getScheme()); assertEquals("https", request.getScheme()); @@ -151,7 +171,7 @@ public void scheme_getScheme_https() { @Test public void scheme_getScheme_http() { - HttpServletRequest request = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null); + HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); assertNotNull(request); assertNotNull(request.getScheme()); assertEquals(REQUEST_SCHEME_HTTP, request.getScheme()); @@ -159,7 +179,7 @@ public void scheme_getScheme_http() { @Test public void cookie_getCookies_noCookies() { - HttpServletRequest request = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null); + HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); assertNotNull(request); assertNotNull(request.getCookies()); assertEquals(0, request.getCookies().length); @@ -167,7 +187,7 @@ public void cookie_getCookies_noCookies() { @Test public void cookie_getCookies_singleCookie() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_SINGLE_COOKIE, null, null); + HttpServletRequest request = getRequest(REQUEST_SINGLE_COOKIE, null, null); assertNotNull(request); assertNotNull(request.getCookies()); assertEquals(1, request.getCookies().length); @@ -177,7 +197,7 @@ public void cookie_getCookies_singleCookie() { @Test public void cookie_getCookies_multipleCookies() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_MULTIPLE_COOKIES, null, null); + HttpServletRequest request = getRequest(REQUEST_MULTIPLE_COOKIES, null, null); assertNotNull(request); assertNotNull(request.getCookies()); assertEquals(2, request.getCookies().length); @@ -189,7 +209,7 @@ public void cookie_getCookies_multipleCookies() { @Test public void cookie_getCookies_emptyCookies() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_MALFORMED_COOKIE, null, null); + HttpServletRequest request = getRequest(REQUEST_MALFORMED_COOKIE, null, null); assertNotNull(request); assertNotNull(request.getCookies()); assertEquals(0, request.getCookies().length); @@ -197,14 +217,14 @@ public void cookie_getCookies_emptyCookies() { @Test public void queryParameters_getParameterMap_null() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_NULL_QUERY_STRING, null, null); + HttpServletRequest request = getRequest(REQUEST_NULL_QUERY_STRING, null, null); assertNotNull(request); assertEquals(0, request.getParameterMap().size()); } @Test public void queryParameters_getParameterMap_nonNull() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_QUERY, null, null); + HttpServletRequest request = getRequest(REQUEST_QUERY, null, null); assertNotNull(request); assertEquals(1, request.getParameterMap().size()); assertEquals(QUERY_STRING_NAME_VALUE, request.getParameterMap().get(FORM_PARAM_NAME)[0]); @@ -212,7 +232,7 @@ public void queryParameters_getParameterMap_nonNull() { @Test public void queryParameters_getParameterNames_null() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_NULL_QUERY_STRING, null, null); + HttpServletRequest request = getRequest(REQUEST_NULL_QUERY_STRING, null, null); List parameterNames = Collections.list(request.getParameterNames()); assertNotNull(request); assertEquals(0, parameterNames.size()); @@ -220,7 +240,7 @@ public void queryParameters_getParameterNames_null() { @Test public void queryParameters_getParameterNames_notNull() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_QUERY, null, null); + HttpServletRequest request = getRequest(REQUEST_QUERY, null, null); List parameterNames = Collections.list(request.getParameterNames()); assertNotNull(request); assertEquals(1, parameterNames.size()); @@ -229,7 +249,7 @@ public void queryParameters_getParameterNames_notNull() { @Test public void queryParameter_getParameterMap_avoidDuplicationOnMultipleCalls() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_MULTIPLE_FORM_AND_QUERY, null, null); + HttpServletRequest request = getRequest(REQUEST_MULTIPLE_FORM_AND_QUERY, null, null); Map params = request.getParameterMap(); assertNotNull(params); @@ -250,7 +270,7 @@ public void queryParameter_getParameterMap_avoidDuplicationOnMultipleCalls() { @Test public void charEncoding_getEncoding_expectNoEncodingWithoutContentType() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_SINGLE_COOKIE, null, null); + HttpServletRequest request = getRequest(REQUEST_SINGLE_COOKIE, null, null); try { request.setCharacterEncoding(StandardCharsets.UTF_8.name()); // we have not specified a content type so the encoding will not be set @@ -264,7 +284,7 @@ public void charEncoding_getEncoding_expectNoEncodingWithoutContentType() { @Test public void charEncoding_getEncoding_expectContentTypeOnly() { - HttpServletRequest request = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null); + HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); // we have not specified a content type so the encoding will not be set assertEquals(null, request.getCharacterEncoding()); assertEquals(MediaType.APPLICATION_JSON, request.getContentType()); @@ -282,7 +302,7 @@ public void charEncoding_getEncoding_expectContentTypeOnly() { @Test public void charEncoding_addCharEncodingTwice_expectSingleMediaTypeAndEncoding() { - HttpServletRequest request = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null); + HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); // we have not specified a content type so the encoding will not be set assertEquals(null, request.getCharacterEncoding()); assertEquals(MediaType.APPLICATION_JSON, request.getContentType()); @@ -308,7 +328,7 @@ public void charEncoding_addCharEncodingTwice_expectSingleMediaTypeAndEncoding() @Test public void contentType_lowerCaseHeaderKey_expectUpdatedMediaType() { - HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_WITH_LOWERCASE_HEADER, null, null); + HttpServletRequest request = getRequest(REQUEST_WITH_LOWERCASE_HEADER, null, null); try { request.setCharacterEncoding(StandardCharsets.UTF_8.name()); String newHeaderValue = MediaType.APPLICATION_JSON + "; charset=" + StandardCharsets.UTF_8.name(); @@ -325,7 +345,7 @@ public void contentType_lowerCaseHeaderKey_expectUpdatedMediaType() { @Test public void contentType_duplicateCase_expectSingleContentTypeHeader() { AwsProxyRequest proxyRequest = getRequestWithHeaders(); - HttpServletRequest request = new AwsProxyHttpServletRequest(proxyRequest, null, null); + HttpServletRequest request = getRequest(proxyRequest, null, null); try { request.setCharacterEncoding(StandardCharsets.ISO_8859_1.name()); @@ -342,7 +362,7 @@ public void requestURL_getUrl_expectHttpSchemaAndLocalhostForLocalTesting() { AwsProxyRequest req = getRequestWithHeaders(); req.getRequestContext().setApiId("test-id"); LambdaContainerHandler.getContainerConfig().enableLocalhost(); - HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(req, null, null); + HttpServletRequest servletRequest = getRequest(req, null, null); String requestUrl = servletRequest.getRequestURL().toString(); assertTrue(requestUrl.contains("http://")); assertTrue(requestUrl.contains("test-id.execute-api.")); @@ -350,7 +370,7 @@ public void requestURL_getUrl_expectHttpSchemaAndLocalhostForLocalTesting() { // set localhost req.getMultiValueHeaders().putSingle("Host", "localhost"); - servletRequest = new AwsProxyHttpServletRequest(req, null, null); + servletRequest = getRequest(req, null, null); requestUrl = servletRequest.getRequestURL().toString(); assertTrue(requestUrl.contains("http://localhost")); assertTrue(requestUrl.endsWith("localhost/hello")); @@ -361,7 +381,7 @@ public void requestURL_getUrl_expectHttpSchemaAndLocalhostForLocalTesting() { public void requestURL_getUrlWithCustomBasePath_expectCustomBasePath() { AwsProxyRequest req = getRequestWithHeaders(); LambdaContainerHandler.getContainerConfig().setServiceBasePath("test"); - HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(req, null, null); + HttpServletRequest servletRequest = getRequest(req, null, null); String requestUrl = servletRequest.getRequestURL().toString(); assertTrue(requestUrl.contains("/test/hello")); LambdaContainerHandler.getContainerConfig().setServiceBasePath(null); @@ -372,9 +392,8 @@ public void requestURL_getUrlWithContextPath_expectStageAsContextPath() { AwsProxyRequest req = getRequestWithHeaders(); req.getRequestContext().setStage("test-stage"); LambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(true); - HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(req, null, null); + HttpServletRequest servletRequest = getRequest(req, null, null); String requestUrl = servletRequest.getRequestURL().toString(); - System.out.println("Url: " + requestUrl); assertTrue(requestUrl.contains("/test-stage/")); LambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(false); } @@ -382,7 +401,7 @@ public void requestURL_getUrlWithContextPath_expectStageAsContextPath() { @Test public void getLocales_emptyAcceptHeader_expectDefaultLocale() { AwsProxyRequest req = getRequestWithHeaders(); - HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(req, null, null); + HttpServletRequest servletRequest = getRequest(req, null, null); Enumeration locales = servletRequest.getLocales(); int localesNo = 0; while (locales.hasMoreElements()) { @@ -397,7 +416,7 @@ public void getLocales_emptyAcceptHeader_expectDefaultLocale() { public void getLocales_validAcceptHeader_expectSingleLocale() { AwsProxyRequest req = getRequestWithHeaders(); req.getMultiValueHeaders().putSingle(HttpHeaders.ACCEPT_LANGUAGE, "fr-CH"); - HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(req, null, null); + HttpServletRequest servletRequest = getRequest(req, null, null); Enumeration locales = servletRequest.getLocales(); int localesNo = 0; while (locales.hasMoreElements()) { @@ -412,7 +431,7 @@ public void getLocales_validAcceptHeader_expectSingleLocale() { public void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleList() { AwsProxyRequest req = getRequestWithHeaders(); req.getMultiValueHeaders().putSingle(HttpHeaders.ACCEPT_LANGUAGE, "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5"); - HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(req, null, null); + HttpServletRequest servletRequest = getRequest(req, null, null); Enumeration locales = servletRequest.getLocales(); List localesList = new ArrayList<>(); while (locales.hasMoreElements()) { @@ -433,7 +452,7 @@ public void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleList() { public void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleListOrdered() { AwsProxyRequest req = getRequestWithHeaders(); req.getMultiValueHeaders().putSingle(HttpHeaders.ACCEPT_LANGUAGE, "fr-CH, en;q=0.8, de;q=0.7, *;q=0.5, fr;q=0.9"); - HttpServletRequest servletRequest = new AwsProxyHttpServletRequest(req, null, null); + HttpServletRequest servletRequest = getRequest(req, null, null); Enumeration locales = servletRequest.getLocales(); List localesList = new ArrayList<>(); while (locales.hasMoreElements()) { @@ -450,7 +469,7 @@ public void getLocales_validAcceptHeaderMultipleLocales_expectFullLocaleListOrde @Test public void nullQueryString_expectNoExceptions() { AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); - AwsProxyHttpServletRequest servletReq = new AwsProxyHttpServletRequest(req, null, null); + HttpServletRequest servletReq = getRequest(req, null, null); assertNull(servletReq.getQueryString()); assertEquals(0, servletReq.getParameterMap().size()); assertFalse(servletReq.getParameterNames().hasMoreElements()); @@ -462,7 +481,7 @@ public void nullQueryString_expectNoExceptions() { public void inputStream_emptyBody_expectNullInputStream() { AwsProxyRequest proxyReq = getRequestWithHeaders(); assertNull(proxyReq.getBody()); - HttpServletRequest req = new AwsProxyHttpServletRequest(proxyReq, null, null); + HttpServletRequest req = getRequest(proxyReq, null, null); try { InputStream is = req.getInputStream(); @@ -476,13 +495,13 @@ public void inputStream_emptyBody_expectNullInputStream() { @Test public void getHeaders_emptyHeaders_expectEmptyEnumeration() { AwsProxyRequest proxyReq = new AwsProxyRequestBuilder("/hello", "GET").build(); - HttpServletRequest req = new AwsProxyHttpServletRequest(proxyReq, null, null); + HttpServletRequest req = getRequest(proxyReq, null, null); assertFalse(req.getHeaders("param").hasMoreElements()); } @Test public void getServerPort_defaultPort_expect443() { - HttpServletRequest req = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null); + HttpServletRequest req = getRequest(getRequestWithHeaders(), null, null); assertEquals(443, req.getServerPort()); } @@ -490,7 +509,7 @@ public void getServerPort_defaultPort_expect443() { public void getServerPort_customPortFromHeader_expectCustomPort() { AwsProxyRequest proxyReq = getRequestWithHeaders(); proxyReq.getMultiValueHeaders().putSingle(AwsProxyHttpServletRequest.PORT_HEADER_NAME, "80"); - HttpServletRequest req = new AwsProxyHttpServletRequest(proxyReq, null, null); + HttpServletRequest req = getRequest(proxyReq, null, null); assertEquals(80, req.getServerPort()); } @@ -498,7 +517,7 @@ public void getServerPort_customPortFromHeader_expectCustomPort() { public void getServerPort_invalidCustomPortFromHeader_expectDefaultPort() { AwsProxyRequest proxyReq = getRequestWithHeaders(); proxyReq.getMultiValueHeaders().putSingle(AwsProxyHttpServletRequest.PORT_HEADER_NAME, "7200"); - HttpServletRequest req = new AwsProxyHttpServletRequest(proxyReq, null, null); + HttpServletRequest req = getRequest(proxyReq, null, null); assertEquals(443, req.getServerPort()); } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcherTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcherTest.java index 05aa6ae65..898f251bf 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcherTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcherTest.java @@ -183,10 +183,6 @@ private AwsLambdaServletContainerHandler invalidMappings = reg.addMapping("/"); + assertEquals(1, invalidMappings.size()); + assertEquals("/", invalidMappings.toArray(new String[]{})[0]); + reg.addMapping("/hello", "/world"); + assertEquals(3, reg.getMappings().size()); + } + + @Test + public void metadata_savedAndReturnedCorrectly() { + ServletRegistration.Dynamic reg = new AwsServletRegistration("test", null, new AwsServletContext(null)); + assertEquals("test", reg.getName()); + reg.setLoadOnStartup(2); + assertEquals(2, ((AwsServletRegistration)reg).getLoadOnStartup()); + assertNull(reg.getRunAsRole()); + reg.setRunAsRole("role"); + assertEquals("role", reg.getRunAsRole()); + reg.setAsyncSupported(true); + assertTrue(((AwsServletRegistration)reg).isAsyncSupported()); + } + + @Test + public void setInitParameter_savedCorrectly() { + ServletRegistration.Dynamic reg = new AwsServletRegistration("test", null, new AwsServletContext(null)); + assertTrue(reg.setInitParameter("param", "value")); + assertFalse(reg.setInitParameter("param", "value")); + Map params = new HashMap<>(); + params.put("param2", "value2"); + params.put("param", "value"); + Set invalidParams = reg.setInitParameters(params); + assertEquals(1, invalidParams.size()); + assertEquals("param", invalidParams.toArray(new String[]{})[0]); + assertEquals(2, reg.getInitParameters().size()); + assertEquals("value2", reg.getInitParameter("param2")); + } + + @Test + public void servletConfig_populatesConfig() throws ServletException { + AwsServletContext servletCtx = new AwsServletContext(null); + TestServlet servlet = new TestServlet(); + ServletRegistration.Dynamic reg = new AwsServletRegistration("test", servlet, servletCtx); + assertEquals(servlet, ((AwsServletRegistration)reg).getServlet()); + Map params = new HashMap<>(); + params.put("param2", "value2"); + params.put("param", "value"); + Set invalidParams = reg.setInitParameters(params); + assertEquals(0, invalidParams.size()); + ServletConfig config = ((AwsServletRegistration)reg).getServletConfig(); + assertNotNull(config); + assertEquals("test", config.getServletName()); + assertEquals(servletCtx, config.getServletContext()); + int paramCnt = 0; + Enumeration paramNames = config.getInitParameterNames(); + while (paramNames.hasMoreElements()) { + paramNames.nextElement(); + paramCnt++; + } + assertEquals(2, paramCnt); + + } + + private class TestServlet implements Servlet { + + @Override + public void init(ServletConfig servletConfig) throws ServletException { + + } + + @Override + public ServletConfig getServletConfig() { + return null; + } + + @Override + public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { + + } + + @Override + public String getServletInfo() { + return null; + } + + @Override + public void destroy() { + + } + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java index 0b82c4bd3..50f5cf054 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java @@ -96,7 +96,7 @@ public void serialize_base64Encoded_fieldContainsIsPrefix() throws IOException { .fromJsonString(getRequestJson(true, CUSTOM_HEADER_KEY_LOWER_CASE, CUSTOM_HEADER_VALUE)).build(); ObjectMapper mapper = new ObjectMapper(); String serializedRequest = mapper.writeValueAsString(req); - System.out.println(serializedRequest); + assertTrue(serializedRequest.contains("\"isBase64Encoded\":true")); } diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java deleted file mode 100644 index af9d721d6..000000000 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file 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 com.amazonaws.serverless.proxy.spring; - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.internal.testutils.Timer; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.web.WebApplicationInitializer; -import org.springframework.web.context.ConfigurableWebApplicationContext; -import org.springframework.web.context.ContextLoaderListener; -import org.springframework.web.context.support.ServletRequestHandledEvent; -import org.springframework.web.servlet.DispatcherServlet; - -import javax.servlet.*; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.*; - -/** - * Custom implementation of Spring's `WebApplicationInitializer`. Uses internal variables to keep application state - * and creates a DispatcherServlet to handle incoming events. When the first event arrives it extracts the ServletContext - * and starts the Spring application. This class assumes that the implementation of `HttpServletRequest` that is passed - * in correctly implements the `getServletContext` method. - * - * State is kept using the `initialized` boolean variable. Each time a new event is received, the app sets the - * `currentResponse` private property to the value of the new `HttpServletResponse` object. This is used to intercept - * Spring notifications for the `ServletRequestHandledEvent` and call the flush method to release the latch. - */ -@SuppressFBWarnings("MTIA_SUSPECT_SERVLET_INSTANCE_FIELD") // we assume we are running in a "single threaded" environment - AWS Lambda -public class LambdaSpringApplicationInitializer extends HttpServlet implements WebApplicationInitializer { - public static final String ERROR_NO_CONTEXT = "No application context or configuration classes provided"; - - private static final String DEFAULT_SERVLET_NAME = "aws-servless-java-container"; - - private static final long serialVersionUID = 42L; - - // all of these variables are declared as volatile for correctness, technically this class is an implementation of - // HttpServlet and could live in a multi-threaded environment. Because the library runs inside AWS Lambda, we can - // assume we will be in a single-threaded environment. - - // Configuration variables that can be passed in - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - private transient volatile ConfigurableWebApplicationContext applicationContext; - private volatile boolean refreshContext = true; - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - private transient volatile List contextListeners; - private volatile List springProfiles; - - // Dynamically instantiated properties - private volatile DispatcherServlet dispatcherServlet; - - // The current response is used to release the latch when Spring emits the request handled event - private transient volatile HttpServletResponse currentResponse; - - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - private transient Logger log = LoggerFactory.getLogger(LambdaSpringApplicationInitializer.class); - - /** - * Creates a new instance of the WebApplicationInitializer - * @param applicationContext A custom ConfigurableWebApplicationContext to be used - */ - public LambdaSpringApplicationInitializer(ConfigurableWebApplicationContext applicationContext) { - this.contextListeners = new ArrayList<>(); - this.applicationContext = applicationContext; - } - - /** - * Adds a new listener for the servlet context events. At the moment the library only emits events when the application - * is initialized. Because we don't have container lifecycle notifications from Lambda the `contextDestroyed` - * method is never called - * @param listener An implementation of `ServletContextListener` - */ - public void addListener(ServletContextListener listener) { - contextListeners.add(listener); - } - - public void setRefreshContext(boolean refreshContext) { - this.refreshContext = refreshContext; - } - - /** - * Given a request and response objects, triggers the filters set in the servlet context and - * @param request The incoming request - * @param response The response object Spring should write to. - * @throws ServletException When an error occurs during processing or of the request - * @throws IOException When an error occurs while writing the response - */ - public void dispatch(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - currentResponse = response; - dispatcherServlet.service(request, response); - } - - /** - * Gets the initialized Spring dispatcher servlet instance. - * @return The Spring dispatcher servlet - */ - public DispatcherServlet getDispatcherServlet() { - return dispatcherServlet; - } - - public void setSpringProfiles(ServletContext ctx, String... springProfiles) - throws ContainerInitializationException { - this.springProfiles = Arrays.asList(springProfiles); - - applicationContext.registerShutdownHook(); - applicationContext.close(); - contextListeners.clear(); - try { - onStartup(ctx); - } catch (ServletException e) { - throw new ContainerInitializationException("Could not reload Spring context", e); - } - - } - - @Override - public void onStartup(ServletContext servletContext) throws ServletException { - Timer.start("SPRING_INITIALIZER_ONSTARTUP"); - if (springProfiles != null) { - applicationContext.getEnvironment().setActiveProfiles(springProfiles.toArray(new String[0])); - } - applicationContext.setServletContext(servletContext); - - DefaultDispatcherConfig dispatcherConfig = new DefaultDispatcherConfig(servletContext); - applicationContext.setServletConfig(dispatcherConfig); - - // Manage the lifecycle of the root application context - this.addListener(new ContextLoaderListener(applicationContext)); - - // Register and map the dispatcher servlet - dispatcherServlet = new DispatcherServlet(applicationContext); - - if (refreshContext) { - dispatcherServlet.refresh(); - } - - dispatcherServlet.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); - dispatcherServlet.init(dispatcherConfig); - - notifyStartListeners(servletContext); - Timer.stop("SPRING_INITIALIZER_ONSTARTUP"); - } - - private void notifyStartListeners(ServletContext context) { - for (ServletContextListener listener : contextListeners) { - listener.contextInitialized(new ServletContextEvent(context)); - } - } - - - /////////////////////////////////////////////////////////////// - // HttpServlet implementation // - // This is used to pass the initializer to the filter chain // - // to handle requests // - /////////////////////////////////////////////////////////////// - - @Override - public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { - Timer.start("SPRING_INITIALIZER_SERVICE"); - Timer.start("SPRING_INITIALIZER_SERVICE_CAST"); - if (!(req instanceof HttpServletRequest)) { - throw new ServletException("Cannot service request that is not instance of HttpServletRequest"); - } - - if (!(res instanceof HttpServletResponse)) { - throw new ServletException("Cannot work with response that is not instance of HttpServletResponse"); - } - Timer.stop("SPRING_INITIALIZER_SERVICE_CAST"); - dispatch((HttpServletRequest)req, (HttpServletResponse)res); - Timer.stop("SPRING_INITIALIZER_SERVICE"); - } - - /** - * Default configuration class for the DispatcherServlet. This just mocks the behaviour of a default - * ServletConfig object with no init parameters - */ - private static class DefaultDispatcherConfig implements ServletConfig { - private ServletContext servletContext; - - DefaultDispatcherConfig(ServletContext context) { - servletContext = context; - } - - @Override - public String getServletName() { - return DEFAULT_SERVLET_NAME; - } - - @Override - public ServletContext getServletContext() { - return servletContext; - } - - @Override - public String getInitParameter(String s) { - return null; - } - - @Override - public Enumeration getInitParameterNames() { - return Collections.emptyEnumeration(); - } - } -} From ab0217e435afa7f4e7c86541acac9140ff4d2bc5 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 11 Sep 2019 12:38:41 -0700 Subject: [PATCH 12/40] Updated tests to work well with new context implementation and removed debug System.out calls (#239 contd) --- .../serverless/proxy/jersey/EchoJerseyResource.java | 7 ------- .../serverless/proxy/jersey/JerseyAwsProxyTest.java | 4 ---- .../serverless/proxy/jersey/JerseyParamEncodingTest.java | 1 - .../proxy/jersey/providers/CustomExceptionMapper.java | 3 +-- .../serverless/proxy/spark/InitExceptionHandlerTest.java | 1 - .../proxy/spark/SparkLambdaContainerHandlerTest.java | 2 -- .../serverless/proxy/spark/filter/CustomHeaderFilter.java | 5 ++--- .../proxy/spark/filter/UnauthenticatedFilter.java | 3 --- 8 files changed, 3 insertions(+), 23 deletions(-) diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java index ec0f4b2ac..5da93ca08 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java @@ -86,7 +86,6 @@ public SingleValueModel returnFilterAttribute(@Context HttpServletRequest req) { @Path("/list-query-string") @GET @Produces(MediaType.APPLICATION_JSON) public SingleValueModel echoQueryStringLength(@QueryParam("list") List param) { - System.out.println("param: " + param + " = " + param.size()); SingleValueModel model = new SingleValueModel(); model.setValue(param.size() + ""); return model; @@ -157,7 +156,6 @@ public MapResponseModel echoQueryString(@Context UriInfo context) { @Produces(MediaType.APPLICATION_JSON) public SingleValueModel echoRequestScheme(@Context UriInfo context) { SingleValueModel model = new SingleValueModel(); - System.out.println("RequestUri: " + context.getRequestUri().toString()); model.setValue(context.getRequestUri().getScheme()); return model; } @@ -261,11 +259,6 @@ public Response fileSize(@FormDataParam("file") final File uploadedFile, @Context ContainerRequestContext req) { SingleValueModel sv = new SingleValueModel(); - System.out.println( - "Is base64 encoded: " + - ((AwsProxyHttpServletRequest)req.getProperty(JERSEY_SERVLET_REQUEST_PROPERTY)).getAwsProxyRequest().isBase64Encoded() - ); - try { InputStream fileIs = new FileInputStream(uploadedFile); System.out.println("File: " + fileDetail.getName() + " " + fileDetail.getFileName() + " " + fileDetail.getSize()); diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java index e791ae810..baecb13aa 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java @@ -110,7 +110,6 @@ public void alb_basicRequest_expectSuccess() { assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); assertNotNull(output.getStatusDescription()); - System.out.println(output.getStatusDescription()); validateMapResponseModel(output); } @@ -170,9 +169,6 @@ public void context_servletResponse_setCustomHeader() { public void context_serverInfo_correctContext() { AwsProxyRequest request = getRequestBuilder("/echo/servlet-context", "GET").build(); AwsProxyResponse output = handler.proxy(request, lambdaContext); - for (String header : output.getMultiValueHeaders().keySet()) { - System.out.println(header + ": " + output.getMultiValueHeaders().getFirst(header)); - } assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java index 06edbdaab..0484e62d9 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java @@ -187,7 +187,6 @@ public void queryParam_encoding_expectFullyEncodedUrl() { assertNotNull(resp); assertEquals(resp.getStatusCode(), 200); validateSingleValueModel(resp, "%2F%2B%3D"); - System.out.println("body:" + resp.getBody()); } @Test diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/CustomExceptionMapper.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/CustomExceptionMapper.java index a800d114b..e435aff96 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/CustomExceptionMapper.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/providers/CustomExceptionMapper.java @@ -12,7 +12,7 @@ public class CustomExceptionMapper implements ExceptionMapper { public CustomExceptionMapper() { - System.out.println("Starting custom exception mapper"); + } @Inject @@ -23,7 +23,6 @@ public Response toResponse(UnsupportedOperationException throwable) { if (request == null) { return Response.status(Response.Status.NOT_FOUND).build(); } else { - System.out.println("Request uri: " + request.get().getRequestURI()); return Response.ok(throwable.getMessage()).status(Response.Status.NOT_IMPLEMENTED).build(); } } diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java index fb39d9b69..6042899e4 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java @@ -67,7 +67,6 @@ public static void stopSpark() { private static void configureRoutes() { initExceptionHandler((e) -> { - System.out.println("Exception Handler called: " + e.getLocalizedMessage()); assertEquals(TEST_EXCEPTION_MESSAGE, e.getLocalizedMessage()); }); diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java index 400373a40..c778bd57e 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java @@ -38,7 +38,6 @@ public void filters_onStartupMethod_executeFilters() { handler.onStartup(c -> { if (c == null) { - System.out.println("Null servlet context"); fail(); } FilterRegistration.Dynamic registration = c.addFilter("CustomHeaderFilter", CustomHeaderFilter.class); @@ -75,7 +74,6 @@ public void filters_unauthenticatedFilter_stopRequestProcessing() { handler.onStartup(c -> { if (c == null) { - System.out.println("Null servlet context"); fail(); } FilterRegistration.Dynamic registration = c.addFilter("UnauthenticatedFilter", UnauthenticatedFilter.class); diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/CustomHeaderFilter.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/CustomHeaderFilter.java index 349994456..e3733f3a9 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/CustomHeaderFilter.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/CustomHeaderFilter.java @@ -18,12 +18,11 @@ public class CustomHeaderFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { - System.out.println("Called init on filter"); + } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - System.out.println("Called doFilter"); HttpServletResponse resp = (HttpServletResponse)servletResponse; resp.addHeader(HEADER_NAME, HEADER_VALUE); @@ -33,6 +32,6 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo @Override public void destroy() { - System.out.println("Called destroy"); + } } \ No newline at end of file diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java index 60e8eb429..742072ca5 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java @@ -27,13 +27,10 @@ public void init(FilterConfig filterConfig) @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - System.out.println("Running unauth filter"); if (((HttpServletRequest)servletRequest).getHeader(HEADER_NAME) != null) { ((HttpServletResponse) servletResponse).setStatus(401); - System.out.println("Returning 401"); return; } - System.out.println("Continue chain"); filterChain.doFilter(servletRequest, servletResponse); } From 961ed1b114d881b3b1df2ade66f774519dd63947 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 11 Sep 2019 12:40:42 -0700 Subject: [PATCH 13/40] Simplified Spring and SpringBoot 1.x integration to use an embedded servlet container (#239 contd) --- aws-serverless-java-container-spring/pom.xml | 16 ++ .../SpringBootLambdaContainerHandler.java | 195 ++++-------------- ...SpringBootServletConfigurationSupport.java | 12 ++ .../spring/SpringLambdaContainerHandler.java | 68 +++--- ...erverlessServletEmbeddedServerFactory.java | 55 +++++ .../proxy/spring/SpringAwsProxyTest.java | 3 - .../proxy/spring/SpringBootAppTest.java | 18 +- .../proxy/spring/SpringBootSecurityTest.java | 1 - .../proxy/spring/echoapp/ContextResource.java | 1 - .../spring/echoapp/CustomHeaderFilter.java | 5 +- .../proxy/spring/echoapp/EchoResource.java | 3 +- .../spring/echoapp/EchoSpringAppConfig.java | 10 +- .../spring/echoapp/UnauthenticatedFilter.java | 3 - ...rlessServletEmbeddedServerFactoryTest.java | 106 ++++++++++ .../spring/sbsecurityapp/TestController.java | 1 - .../sbsecurityapp/TestSecurityConfig.java | 3 +- .../spring/springbootapp/TestController.java | 1 - .../proxy/struts2/Struts2AwsProxyTest.java | 5 - 18 files changed, 265 insertions(+), 241 deletions(-) create mode 100644 aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootServletConfigurationSupport.java create mode 100644 aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 15e629305..8862e4787 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -81,6 +81,22 @@ 2.2.4 test + + org.springframework.boot + spring-boot + ${springboot.version} + true + + + org.springframework + spring-context + + + org.springframework + spring-core + + + org.springframework.boot spring-boot-autoconfigure diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java index 68cab4f79..f84f6ca38 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -23,24 +23,21 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.internal.servlet.*; +import com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory; import com.amazonaws.services.lambda.runtime.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; -import org.springframework.web.SpringServletContainerInitializer; import org.springframework.web.WebApplicationInitializer; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.*; -import java.util.*; import java.util.concurrent.CountDownLatch; /** - * Spring implementation of the `LambdaContainerHandler` abstract class. This class uses the `LambdaSpringApplicationInitializer` + * SpringBoot 1.x implementation of the `LambdaContainerHandler` abstract class. This class uses the `LambdaSpringApplicationInitializer` * object behind the scenes to proxy requests. The default implementation leverages the `AwsProxyHttpServletRequest` and * `AwsHttpServletResponse` implemented in the `aws-serverless-java-container-core` package. * @@ -53,11 +50,24 @@ public class SpringBootLambdaContainerHandler extends private final Class springBootInitializer; private static final Logger log = LoggerFactory.getLogger(SpringBootLambdaContainerHandler.class); private String[] springProfiles = null; - private DispatcherServlet dispatcherServlet; + + private static SpringBootLambdaContainerHandler instance; // State vars private boolean initialized; + /** + * We need to rely on the static instance of this for SpringBoot because we need it to access the ServletContext. + * Normally, SpringBoot would initialize its own embedded container through the SpringApplication.run() + * method. However, in our case we need to rely on the pre-initialized handler and need to fetch information from it + * for our mock {@link com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory}. + * + * @return The initialized instance + */ + public static SpringBootLambdaContainerHandler getInstance() { + return instance; + } + /** * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects * @param springBootInitializer {@code SpringBootServletInitializer} class @@ -121,12 +131,20 @@ public SpringBootLambdaContainerHandler(Class requestTypeClass, throws ContainerInitializationException { super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); Timer.start("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR"); - setServletContext(new SpringBootAwsServletContext()); initialized = false; this.springBootInitializer = springBootInitializer; + SpringBootLambdaContainerHandler.setInstance(this); + Timer.stop("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR"); } + // this is not pretty. However, because SpringBoot wants to control all of the initialization + // we need to access this handler as a singleton from the EmbeddedContainer to set the servlet + // context and from the ServletConfigurationSupport implementation + private static void setInstance(SpringBootLambdaContainerHandler h) { + SpringBootLambdaContainerHandler.instance = h; + } + public void activateSpringProfiles(String... profiles) { springProfiles = profiles; // force a re-initialization @@ -151,7 +169,8 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt containerRequest.setServletContext(getServletContext()); // process filters & invoke servlet - doFilter(containerRequest, containerResponse, dispatcherServlet); + Servlet reqServlet = ((AwsServletContext)getServletContext()).getServletForPath(containerRequest.getPathInfo()); + doFilter(containerRequest, containerResponse, reqServlet); Timer.stop("SPRINGBOOT_HANDLE_REQUEST"); } @@ -161,163 +180,19 @@ public void initialize() throws ContainerInitializationException { Timer.start("SPRINGBOOT_COLD_START"); - if (springProfiles != null && springProfiles.length > 0) { - System.setProperty("spring.profiles.active", String.join(",", springProfiles)); - } - SpringServletContainerInitializer springServletContainerInitializer = new SpringServletContainerInitializer(); - LinkedHashSet> webAppInitializers = new LinkedHashSet<>(); - webAppInitializers.add(springBootInitializer); - try { - springServletContainerInitializer.onStartup(webAppInitializers, getServletContext()); - } catch (ServletException e) { - throw new ContainerInitializationException("Could not initialize Spring Boot", e); - } - + SpringApplication app = new SpringApplication( + springBootInitializer, + ServerlessServletEmbeddedServerFactory.class, + SpringBootServletConfigurationSupport.class + ); if (springProfiles != null && springProfiles.length > 0) { ConfigurableEnvironment springEnv = new StandardEnvironment(); springEnv.setActiveProfiles(springProfiles); + app.setEnvironment(springEnv); } - - WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); - - dispatcherServlet = applicationContext.getBean("dispatcherServlet", DispatcherServlet.class); + app.run(); initialized = true; Timer.stop("SPRINGBOOT_COLD_START"); } - - public Servlet getServlet() { - return dispatcherServlet; - } - - - private class SpringBootAwsServletContext extends AwsServletContext { - public SpringBootAwsServletContext() { - super(SpringBootLambdaContainerHandler.this); - } - - @Override - public ServletRegistration.Dynamic addServlet(String s, String s1) { - throw new UnsupportedOperationException("Only dispatcherServlet is supported"); - } - - @Override - public ServletRegistration.Dynamic addServlet(String s, Class aClass) { - throw new UnsupportedOperationException("Only dispatcherServlet is supported"); - } - - @Override - public ServletRegistration.Dynamic addServlet(String s, Servlet servlet) { - if ("dispatcherServlet".equals(s)) { - try { - servlet.init(new ServletConfig() { - @Override - public String getServletName() { - return s; - } - - @Override - public ServletContext getServletContext() { - return SpringBootAwsServletContext.this; - } - - @Override - public String getInitParameter(String name) { - return null; - } - - @Override - public Enumeration getInitParameterNames() { - return new Enumeration() { - @Override - public boolean hasMoreElements() { - return false; - } - - @Override - public String nextElement() { - return null; - } - }; - } - }); - } catch (ServletException e) { - throw new RuntimeException("Cannot add servlet " + servlet, e); - } - return new ServletRegistration.Dynamic() { - @Override - public String getName() { - return s; - } - - @Override - public String getClassName() { - return null; - } - - @Override - public boolean setInitParameter(String name, String value) { - return false; - } - - @Override - public String getInitParameter(String name) { - return null; - } - - @Override - public Set setInitParameters(Map initParameters) { - return null; - } - - @Override - public Map getInitParameters() { - return null; - } - - @Override - public Set addMapping(String... urlPatterns) { - return null; - } - - @Override - public Collection getMappings() { - return null; - } - - @Override - public String getRunAsRole() { - return null; - } - - @Override - public void setAsyncSupported(boolean isAsyncSupported) { - - } - - @Override - public void setLoadOnStartup(int loadOnStartup) { - - } - - @Override - public Set setServletSecurity(ServletSecurityElement constraint) { - return null; - } - - @Override - public void setMultipartConfig(MultipartConfigElement multipartConfig) { - - } - - @Override - public void setRunAsRole(String roleName) { - - } - }; - } else { - throw new UnsupportedOperationException("Only dispatcherServlet is supported"); - } - } - } } diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootServletConfigurationSupport.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootServletConfigurationSupport.java new file mode 100644 index 000000000..d975bd20e --- /dev/null +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootServletConfigurationSupport.java @@ -0,0 +1,12 @@ +package com.amazonaws.serverless.proxy.spring; + +import org.springframework.boot.context.embedded.WebApplicationContextServletContextAwareProcessor; +import org.springframework.web.context.ConfigurableWebApplicationContext; + + +public class SpringBootServletConfigurationSupport extends WebApplicationContextServletContextAwareProcessor { + public SpringBootServletConfigurationSupport(ConfigurableWebApplicationContext webApplicationContext) { + super(webApplicationContext); + webApplicationContext.setServletContext(SpringBootLambdaContainerHandler.getInstance().getServletContext()); + } +} diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java index 7fd87803b..fcd9e7fd4 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java @@ -26,9 +26,10 @@ import com.amazonaws.services.lambda.runtime.Context; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.Servlet; -import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; import java.util.concurrent.CountDownLatch; @@ -40,10 +41,11 @@ * @param The expected return type */ public class SpringLambdaContainerHandler extends AwsLambdaServletContainerHandler { - private LambdaSpringApplicationInitializer initializer; + private ConfigurableWebApplicationContext appContext; + private String[] profiles; // State vars - private boolean initialized; + private boolean refreshContext = false; /** * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects @@ -120,30 +122,21 @@ public SpringLambdaContainerHandler(Class requestTypeClass, throws ContainerInitializationException { super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); Timer.start("SPRING_CONTAINER_HANDLER_CONSTRUCTOR"); - initialized = false; - initializer = new LambdaSpringApplicationInitializer(applicationContext); + appContext = applicationContext; Timer.stop("SPRING_CONTAINER_HANDLER_CONSTRUCTOR"); } /** * Asks the custom web application initializer to refresh the Spring context. - * @param refreshContext true if the context should be refreshed + * @param refresh true if the context should be refreshed */ - public void setRefreshContext(boolean refreshContext) { - this.initializer.setRefreshContext(refreshContext); + public void setRefreshContext(boolean refresh) { + //this.initializer.setRefreshContext(refreshContext); + refreshContext = refresh; } - /** - * Returns the custom web application initializer used by the object. The custom application initializer gives you access - * to the dispatcher servlet. - * @return The instance of the custom {@link org.springframework.web.WebApplicationInitializer} - */ - public LambdaSpringApplicationInitializer getApplicationInitializer() { - return initializer; - } - @Override protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { return new AwsHttpServletResponse(request, latch); @@ -153,33 +146,31 @@ protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest /** * Activates the given Spring profiles in the application. This method will cause the context to be * refreshed. To use a single Spring profile, use the static method {@link SpringLambdaContainerHandler#getAwsProxyHandler(ConfigurableWebApplicationContext, String...)} - * @param profiles A number of spring profiles + * @param p A number of spring profiles * @throws ContainerInitializationException if the initializer is not set yet. */ - public void activateSpringProfiles(String... profiles) throws ContainerInitializationException { - if (initializer == null) { - throw new ContainerInitializationException(LambdaSpringApplicationInitializer.ERROR_NO_CONTEXT, null); - } + public void activateSpringProfiles(String... p) throws ContainerInitializationException { + profiles = p; setServletContext(new AwsServletContext(this)); - initializer.setSpringProfiles(getServletContext(), profiles); + appContext.registerShutdownHook(); + appContext.close(); + initialize(); } @Override protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { Timer.start("SPRING_HANDLE_REQUEST"); - if (initializer == null) { - throw new ContainerInitializationException(LambdaSpringApplicationInitializer.ERROR_NO_CONTEXT, null); - } - // wire up the application context on the first invocation - if (!initialized) { - initialize(); + if (refreshContext) { + appContext.refresh(); + refreshContext = false; } containerRequest.setServletContext(getServletContext()); // process filters - doFilter(containerRequest, containerResponse, initializer); + Servlet reqServlet = ((AwsServletContext)getServletContext()).getServletForPath(containerRequest.getPathInfo()); + doFilter(containerRequest, containerResponse, reqServlet); Timer.stop("SPRING_HANDLE_REQUEST"); } @@ -188,18 +179,13 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt public void initialize() throws ContainerInitializationException { Timer.start("SPRING_COLD_START"); - - try { - initializer.onStartup(getServletContext()); - } catch (ServletException e) { - throw new ContainerInitializationException("Could not initialize Spring", e); + if (profiles != null) { + appContext.getEnvironment().setActiveProfiles(profiles); } - - initialized = true; + appContext.setServletContext(getServletContext()); + DispatcherServlet dispatcher = new DispatcherServlet(appContext); + ServletRegistration.Dynamic reg = getServletContext().addServlet("dispatcherServlet", dispatcher); + reg.addMapping("/"); Timer.stop("SPRING_COLD_START"); } - - public Servlet getServlet() { - return initializer.getDispatcherServlet(); - } } diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java new file mode 100644 index 000000000..4e9a8836c --- /dev/null +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java @@ -0,0 +1,55 @@ +package com.amazonaws.serverless.proxy.spring.embedded; + +import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.EmbeddedServletContainer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerException; +import org.springframework.boot.web.servlet.ServletContextInitializer; + +import javax.servlet.ServletException; + +/** + * Implementation of SpringBoot's embedded container factory and servlet container. This replaces SpringBoot's default + * embedded container and uses the {@link SpringBootLambdaContainerHandler} as a singleton to retrieve the current + * {@link javax.servlet.ServletContext} and pass it to the array of {@link javax.servlet.ServletContainerInitializer}. + */ +public class ServerlessServletEmbeddedServerFactory extends AbstractEmbeddedServletContainerFactory implements EmbeddedServletContainer { + private AwsLambdaServletContainerHandler handler; + private ServletContextInitializer[] initializers; + + public ServerlessServletEmbeddedServerFactory() { + super(); + handler = SpringBootLambdaContainerHandler.getInstance(); + } + + @Override + public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... servletContextInitializers) { + initializers = servletContextInitializers; + + return this; + } + + @Override + public void start() throws EmbeddedServletContainerException { + for (ServletContextInitializer i : initializers) { + try { + if (handler.getServletContext() == null) { + throw new EmbeddedServletContainerException("Attempting to initialize ServletEmbeddedWebServer without ServletContext in Handler", null); + } + i.onStartup(handler.getServletContext()); + } catch (ServletException e) { + throw new EmbeddedServletContainerException("Could not initialize Servlets", e); + } + } + } + + ServletContextInitializer[] getInitializers() { + return initializers; + } + + @Override + public void stop() throws EmbeddedServletContainerException { + + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java index 3fe730da1..6ed37b776 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java @@ -246,7 +246,6 @@ public void responseBody_responseWriter_validBody() throws JsonProcessingExcepti AwsProxyResponse output = handler.proxy(request, lambdaContext); assertEquals(200, output.getStatusCode()); assertNotNull(output.getBody()); - System.out.println("Output:" + output.getBody()); validateSingleValueModel(output, CUSTOM_HEADER_VALUE); } @@ -372,7 +371,6 @@ public void contextPath_generateLink_returnsCorrectPath() { AwsProxyResponse output = handler.proxy(request, lambdaContext); assertEquals(200, output.getStatusCode()); - System.out.println("Response: " + output.getBody()); String expectedUri = "https://api.myserver.com/prod/echo/encoded-request-uri/" + EchoResource.TEST_GENERATE_URI; @@ -390,7 +388,6 @@ public void multipart_getFileName_returnsCorrectFileName() AwsProxyResponse output = handler.proxy(request, lambdaContext); assertEquals(200, output.getStatusCode()); - System.out.println("Response: " + output.getBody()); assertEquals("testFile", output.getBody()); } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java index 0a17814d9..57d8fda4a 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java @@ -10,7 +10,7 @@ import com.amazonaws.serverless.proxy.spring.springbootapp.LambdaHandler; import com.amazonaws.serverless.proxy.spring.springbootapp.TestController; -import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; @@ -41,19 +41,6 @@ public void defaultError_requestForward_springBootForwardsToDefaultErrorPage() { assertNotNull(resp); assertEquals(404, resp.getStatusCode()); assertNotNull(resp.getMultiValueHeaders()); - assertTrue(resp.getMultiValueHeaders().containsKey("Content-Type")); - assertEquals("application/json; charset=UTF-8", resp.getMultiValueHeaders().getFirst("Content-Type")); - try { - JsonNode errorData = mapper.readTree(resp.getBody()); - assertNotNull(errorData.findValue("status")); - assertEquals(404, errorData.findValue("status").asInt()); - assertNotNull(errorData.findValue("message")); - assertEquals("No message available", errorData.findValue("message").asText()); - - } catch (IOException e) { - e.printStackTrace(); - fail(); - } } @Test @@ -84,7 +71,6 @@ public void staticContent_getHtmlFile_returnsHtmlContent() { .header(HttpHeaders.CONTENT_TYPE, "text/plain") .build(); AwsProxyResponse output = handler.handleRequest(request, context); - System.out.println("Response: " + output.getBody()); assertEquals(200, output.getStatusCode()); assertTrue(output.getBody().contains("

Static

")); } @@ -95,8 +81,6 @@ public void utf8_returnUtf8String_expectCorrectHeaderMediaAndCharset() { AwsProxyRequest request = new AwsProxyRequestBuilder("/test/utf8", "GET") .build(); AwsProxyResponse output = handler.handleRequest(request, context); - System.out.println("Response: " + output.getBody()); - System.out.println("Content-Type:" + output.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); validateSingleValueModel(output, TestController.UTF8_TEST_STRING); assertTrue(output.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); assertTrue(output.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).contains(";")); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java index dc46ed339..2fc6dd0d5 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java @@ -45,7 +45,6 @@ public void wrongUser_springSecurityBasicAuth_requestRedirectsSuccessfully() thr AwsProxyResponse resp = handler.handleRequest(req, context); assertNotNull(resp); mapper.enable(SerializationFeature.INDENT_OUTPUT); - System.out.println(mapper.writeValueAsString(resp.getMultiValueHeaders())); assertEquals(403, resp.getStatusCode()); validateSingleValueModel(resp, TestController.ACCESS_DENIED); } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/ContextResource.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/ContextResource.java index 59e74ad33..d52237566 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/ContextResource.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/ContextResource.java @@ -47,7 +47,6 @@ public ResponseEntity getContext() { public ResponseEntity createUser(@Valid @RequestBody ValidatedUserModel newUser, BindingResult results) { if (results.hasErrors()) { - System.out.println("Has errors"); return new ResponseEntity(newUser, HttpStatus.BAD_REQUEST); } return new ResponseEntity(newUser, HttpStatus.OK); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/CustomHeaderFilter.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/CustomHeaderFilter.java index 0d3eb903e..ca0030439 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/CustomHeaderFilter.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/CustomHeaderFilter.java @@ -18,12 +18,11 @@ public class CustomHeaderFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { - System.out.println("Called init on filter"); + } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - System.out.println("Called doFilter"); HttpServletResponse resp = (HttpServletResponse)servletResponse; resp.addHeader(HEADER_NAME, HEADER_VALUE); @@ -33,6 +32,6 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo @Override public void destroy() { - System.out.println("Called destroy"); + } } \ No newline at end of file diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java index 1487297d3..c07020013 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java @@ -156,7 +156,6 @@ public SingleValueModel echoRequestURL(HttpServletRequest request) { public SingleValueModel helloForPopulatedBody(@RequestBody(required = false) Optional input) { SingleValueModel valueModel = new SingleValueModel(); if (input.isPresent() && !"null".equals(input.get())) { - System.out.println("Input: \"" + input.get() + "\""); valueModel.setValue("true"); } @@ -194,7 +193,7 @@ public ResponseEntity echoLastModified() { public ResponseEntity receiveFile(@RequestParam("testFile") MultipartFile file) throws IOException { String fileName = file.getName(); byte[] fileContents = file.getBytes(); - System.out.println("Content length: " + fileContents.length); + return ResponseEntity.ok(fileName); } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java index d9f49e7c8..17e09d923 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java @@ -5,6 +5,7 @@ import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.serverless.proxy.internal.servlet.AwsServletRegistration; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; @@ -17,9 +18,11 @@ import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.context.annotation.PropertySource; import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; +import javax.servlet.ServletException; import java.util.EnumSet; @@ -50,7 +53,12 @@ public SpringLambdaContainerHandler springLambdaContainerHandler() throws Contai registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/echo/*"); // servlet name mappings are disabled and will throw an exception - handler.getApplicationInitializer().getDispatcherServlet().setThrowExceptionIfNoHandlerFound(true); + //handler.getApplicationInitializer().getDispatcherServlet().setThrowExceptionIfNoHandlerFound(true); + try { + ((DispatcherServlet)((AwsServletRegistration)c.getServletRegistration("dispatcherServlet")).getServlet()).setThrowExceptionIfNoHandlerFound(true); + } catch (ServletException e) { + e.printStackTrace(); + } }); return handler; } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java index b63401572..ba16d905f 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java @@ -27,13 +27,10 @@ public void init(FilterConfig filterConfig) @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - System.out.println("Running unauth filter"); if (((HttpServletRequest)servletRequest).getHeader(HEADER_NAME) != null) { ((HttpServletResponse) servletResponse).setStatus(401); - System.out.println("Returning 401"); return; } - System.out.println("Continue chain"); filterChain.doFilter(servletRequest, servletResponse); } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java new file mode 100644 index 000000000..b9e31a052 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java @@ -0,0 +1,106 @@ +package com.amazonaws.serverless.proxy.spring.embedded; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.serverless.proxy.spring.springbootapp.TestApplication; +import org.junit.Test; +import org.springframework.boot.context.embedded.EmbeddedServletContainer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerException; +import org.springframework.boot.web.servlet.ServletContextInitializer; + +import javax.servlet.*; +import java.io.IOException; + +import static org.junit.Assert.*; + +public class ServerlessServletEmbeddedServerFactoryTest { + + // initialize a container handler to that the embedded factory can grab its instnace + private SpringBootLambdaContainerHandler h = SpringBootLambdaContainerHandler.getAwsProxyHandler(TestApplication.class); + + public ServerlessServletEmbeddedServerFactoryTest() throws ContainerInitializationException { + } + + @Test + public void getContainer_populatesInitializers() { + ServerlessServletEmbeddedServerFactory factory = new ServerlessServletEmbeddedServerFactory(); + TestServlet initializer = new TestServlet(false); + EmbeddedServletContainer container = factory.getEmbeddedServletContainer(initializer); + assertNotNull(((ServerlessServletEmbeddedServerFactory)container).getInitializers()); + assertEquals(1, ((ServerlessServletEmbeddedServerFactory)container).getInitializers().length); + assertEquals(initializer, ((ServerlessServletEmbeddedServerFactory)container).getInitializers()[0]); + container.stop(); // calling stop just once to get the test coverage since there's no code in it + } + + @Test + public void start_throwsException() { + ServerlessServletEmbeddedServerFactory factory = new ServerlessServletEmbeddedServerFactory(); + TestServlet initializer = new TestServlet(true); + EmbeddedServletContainer container = factory.getEmbeddedServletContainer(initializer); + try { + container.start(); + } catch (EmbeddedServletContainerException e) { + assertTrue(ServletException.class.isAssignableFrom(e.getCause().getClass())); + assertEquals(TestServlet.EXCEPTION_MESSAGE, e.getCause().getMessage()); + return; + } + fail("Did not throw the expected exception"); + } + + @Test + public void start_withoutException_setsServletContext() { + ServerlessServletEmbeddedServerFactory factory = new ServerlessServletEmbeddedServerFactory(); + TestServlet initializer = new TestServlet(false); + EmbeddedServletContainer container = factory.getEmbeddedServletContainer(initializer); + container.start(); + assertNotNull(initializer.getCtx()); + assertEquals(h.getServletContext(), initializer.getCtx()); + } + + public static class TestServlet implements Servlet, ServletContextInitializer { + private ServletContext ctx; + private boolean throwOnInit; + public static final String EXCEPTION_MESSAGE = "Throw on init"; + + public TestServlet(boolean throwOnInit) { + this.throwOnInit = throwOnInit; + } + + @Override + public void init(ServletConfig servletConfig) throws ServletException { + + } + + @Override + public ServletConfig getServletConfig() { + return null; + } + + @Override + public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { + + } + + @Override + public String getServletInfo() { + return null; + } + + @Override + public void destroy() { + + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + ctx = servletContext; + if (throwOnInit) { + throw new ServletException(EXCEPTION_MESSAGE); + } + } + + public ServletContext getCtx() { + return ctx; + } + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestController.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestController.java index 351ddce17..33be36f80 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestController.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestController.java @@ -33,7 +33,6 @@ public SingleValueModel testGet() { } else { value.setValue(null); } - System.out.println("Principal: " + value.getValue()); return value; } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestSecurityConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestSecurityConfig.java index 13b1ed874..b3ca81eaf 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestSecurityConfig.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/sbsecurityapp/TestSecurityConfig.java @@ -24,12 +24,11 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser(USERNAME).password(pEncoder.encode(PASSWORD)).roles("ADMIN") .and().withUser(NO_ADMIN_USERNAME).password(pEncoder.encode(PASSWORD)).roles("USER") .and().passwordEncoder(pEncoder); - System.out.println("Configure authentication manager"); + } @Override public void configure(WebSecurity web) throws Exception { - System.out.println("Configure Web security"); } @Override diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java index 51094bd1c..0357328e1 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java @@ -59,7 +59,6 @@ public SingleValueModel testDomainInPath(@PathVariable("domain") String domainNa @RequestMapping(path = "/test/query-string", method = { RequestMethod.GET }) public SingleValueModel testQueryStringList(@RequestParam("list") List qsValues) { assert qsValues != null; - System.out.println("QUERY_STRING_VALUES: " + qsValues); SingleValueModel value = new SingleValueModel(); value.setValue(qsValues.size() + ""); return value; diff --git a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java index d362030f5..1e6ad63ae 100644 --- a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java +++ b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java @@ -92,12 +92,8 @@ public void context_serverInfo_correctContext() { .queryString("contentType", "true") .build(); AwsProxyResponse output = handler.proxy(request, lambdaContext); - for (String header : output.getMultiValueHeaders().keySet()) { - System.out.println(header + ": " + output.getMultiValueHeaders().getFirst(header)); - } assertEquals(200, output.getStatusCode()); assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getMultiValueHeaders().getFirst("Content-Type")); - System.out.println("Body: " + output.getBody()); validateSingleValueModel(output, "Hello Struts2"); } @@ -271,7 +267,6 @@ public void queryParam_encoding_expectUnencodedParam() { String decodedParam = ""; try { decodedParam = URLDecoder.decode(paramValue, "UTF-8"); - System.out.println(decodedParam); } catch (UnsupportedEncodingException e) { e.printStackTrace(); fail("Could not decode parameter"); From ea39ff61ce673eaba5e87def8627148aa5399e66 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 11 Sep 2019 12:41:18 -0700 Subject: [PATCH 14/40] Added new springboot2 module that supports both WebFlux and Servlet embedded servers, actually addressing #239. --- .../pom.xml | 171 +++++++++++++++ .../SpringBootLambdaContainerHandler.java | 204 ++++++++++++++++++ ...rverlessReactiveEmbeddedServerFactory.java | 93 ++++++++ ...erverlessServletEmbeddedServerFactory.java | 55 +++++ .../proxy/spring/ServletAppTest.java | 24 +++ .../proxy/spring/WebFluxAppTest.java | 32 +++ .../spring/servletapp/LambdaHandler.java | 25 +++ .../spring/servletapp/MessageController.java | 15 ++ .../spring/servletapp/ServletApplication.java | 8 + .../spring/webfluxapp/LambdaHandler.java | 25 +++ .../spring/webfluxapp/MessageController.java | 26 +++ .../webfluxapp/WebFluxTestApplication.java | 8 + pom.xml | 1 + 13 files changed, 687 insertions(+) create mode 100644 aws-serverless-java-container-springboot2/pom.xml create mode 100644 aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java create mode 100644 aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveEmbeddedServerFactory.java create mode 100644 aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/ServletAppTest.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/WebFluxAppTest.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/LambdaHandler.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/MessageController.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/ServletApplication.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/LambdaHandler.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/MessageController.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/WebFluxTestApplication.java diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot2/pom.xml new file mode 100644 index 000000000..f0a8258a3 --- /dev/null +++ b/aws-serverless-java-container-springboot2/pom.xml @@ -0,0 +1,171 @@ + + + + aws-serverless-java-container + com.amazonaws.serverless + 1.4-SNAPSHOT + + 4.0.0 + + com.amazonaws.serverless + aws-serverless-java-container-springboot2 + AWS Serverless Java container support - SpringBoot 2 implementation + Allows Java applications written for SpringBoot 2 to run in AWS Lambda + https://aws.amazon.com/lambda + 1.4-SNAPSHOT + + + 5.1.9.RELEASE + 2.1.8.RELEASE + + 1.8 + 1.8 + + + + + + com.amazonaws.serverless + aws-serverless-java-container-core + 1.4-SNAPSHOT + + + + org.springframework + spring-webflux + ${spring.version} + true + + + org.springframework.boot + spring-boot + ${springboot.version} + true + + + org.springframework.boot + spring-boot-autoconfigure + ${springboot.version} + true + + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.1 + + ${basedir}/target/coverage-reports/jacoco-unit.exec + ${basedir}/target/coverage-reports/jacoco-unit.exec + + + + default-prepare-agent + + prepare-agent + + + + jacoco-site + package + + report + + + + jacoco-check + test + + check + + + true + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.minCoverage} + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.9 + + always + + + + com.github.spotbugs + spotbugs-maven-plugin + 3.1.1 + + + Max + + Low + + true + + ${project.build.directory}/spotbugs + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.7.1 + + + + + + + analyze-compile + compile + + check + + + + + + org.owasp + dependency-check-maven + ${dependencyCheck.version} + + true + + ${project.basedir}/../owasp-suppression.xml + + 7 + + + + + check + + + + + + + diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java new file mode 100644 index 000000000..c111b2c87 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -0,0 +1,204 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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 com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.*; +import com.amazonaws.serverless.proxy.internal.servlet.*; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveEmbeddedServerFactory; +import com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory; +import com.amazonaws.services.lambda.runtime.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.StandardEnvironment; + +import javax.servlet.Servlet; +import java.util.concurrent.CountDownLatch; + +/** + * SpringBoot 1.x implementation of the `LambdaContainerHandler` abstract class. This class uses the `LambdaSpringApplicationInitializer` + * object behind the scenes to proxy requests. The default implementation leverages the `AwsProxyHttpServletRequest` and + * `AwsHttpServletResponse` implemented in the `aws-serverless-java-container-core` package. + * + * Important: Make sure to add LambdaFlushResponseListener in your SpringBootServletInitializer subclass configure(). + * + * @param The incoming event type + * @param The expected return type + */ +public class SpringBootLambdaContainerHandler extends AwsLambdaServletContainerHandler { + private final Class springBootInitializer; + private static final Logger log = LoggerFactory.getLogger(SpringBootLambdaContainerHandler.class); + private String[] springProfiles = null; + + private static SpringBootLambdaContainerHandler instance; + + // State vars + private boolean initialized; + + /** + * We need to rely on the static instance of this for SpringBoot because we need it to access the ServletContext. + * Normally, SpringBoot would initialize its own embedded container through the SpringApplication.run() + * method. However, in our case we need to rely on the pre-initialized handler and need to fetch information from it + * for our mock {@link com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveEmbeddedServerFactory}. + * + * @return The initialized instance + */ + public static SpringBootLambdaContainerHandler getInstance() { + return instance; + } + + /** + * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects + * @param springBootInitializer {@code SpringBootServletInitializer} class + * @return An initialized instance of the `SpringLambdaContainerHandler` + * @throws ContainerInitializationException If an error occurs while initializing the Spring framework + */ + public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class springBootInitializer) + throws ContainerInitializationException { + SpringBootLambdaContainerHandler newHandler = new SpringBootLambdaContainerHandler<>( + AwsProxyRequest.class, + AwsProxyResponse.class, + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + springBootInitializer); + newHandler.initialize(); + return newHandler; + } + + /** + * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects and the given Spring profiles + * @param springBootInitializer {@code SpringBootServletInitializer} class + * @param profiles A list of Spring profiles to activate + * @return An initialized instance of the `SpringLambdaContainerHandler` + * @throws ContainerInitializationException If an error occurs while initializing the Spring framework + */ + public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class springBootInitializer, String... profiles) + throws ContainerInitializationException { + SpringBootLambdaContainerHandler newHandler = new SpringBootLambdaContainerHandler<>( + AwsProxyRequest.class, + AwsProxyResponse.class, + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + springBootInitializer); + newHandler.activateSpringProfiles(profiles); + newHandler.initialize(); + return newHandler; + } + + /** + * Creates a new container handler with the given reader and writer objects + * + * @param requestTypeClass The class for the incoming Lambda event + * @param requestReader An implementation of `RequestReader` + * @param responseWriter An implementation of `ResponseWriter` + * @param securityContextWriter An implementation of `SecurityContextWriter` + * @param exceptionHandler An implementation of `ExceptionHandler` + * @param springBootInitializer {@code SpringBootServletInitializer} class + * @throws ContainerInitializationException If an error occurs while initializing the Spring framework + */ + public SpringBootLambdaContainerHandler(Class requestTypeClass, + Class responseTypeClass, + RequestReader requestReader, + ResponseWriter responseWriter, + SecurityContextWriter securityContextWriter, + ExceptionHandler exceptionHandler, + Class springBootInitializer) + throws ContainerInitializationException { + super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); + Timer.start("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR"); + initialized = false; + this.springBootInitializer = springBootInitializer; + SpringBootLambdaContainerHandler.setInstance(this); + + Timer.stop("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR"); + } + + // this is not pretty. However, because SpringBoot wants to control all of the initialization + // we need to access this handler as a singleton from the EmbeddedContainer to set the servlet + // context and from the ServletConfigurationSupport implementation + private static void setInstance(SpringBootLambdaContainerHandler h) { + SpringBootLambdaContainerHandler.instance = h; + } + + public void activateSpringProfiles(String... profiles) { + springProfiles = profiles; + // force a re-initialization + initialized = false; + } + + @Override + protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + return new AwsHttpServletResponse(request, latch); + } + + @Override + protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + // this method of the AwsLambdaServletContainerHandler sets the servlet context + Timer.start("SPRINGBOOT_HANDLE_REQUEST"); + + // wire up the application context on the first invocation + if (!initialized) { + initialize(); + } + + containerRequest.setServletContext(getServletContext()); + + // process filters & invoke servlet + Servlet reqServlet = ((AwsServletContext)getServletContext()).getServletForPath(containerRequest.getPathInfo()); + doFilter(containerRequest, containerResponse, reqServlet); + Timer.stop("SPRINGBOOT_HANDLE_REQUEST"); + } + + + @Override + public void initialize() + throws ContainerInitializationException { + Timer.start("SPRINGBOOT_COLD_START"); + + SpringApplication app = new SpringApplication(getEmbeddedContainerClasses()); + if (springProfiles != null && springProfiles.length > 0) { + ConfigurableEnvironment springEnv = new StandardEnvironment(); + springEnv.setActiveProfiles(springProfiles); + app.setEnvironment(springEnv); + } + + app.run(); + + initialized = true; + Timer.stop("SPRINGBOOT_COLD_START"); + } + + private Class[] getEmbeddedContainerClasses() { + Class[] classes = new Class[2]; + try { + // if HandlerAdapter is available we assume they are using WebFlux. Otherwise plain servlet. + this.getClass().getClassLoader().loadClass("org.springframework.web.reactive.HandlerAdapter"); + log.debug("Found WebFlux HandlerAdapter on classpath, using reactive server factory"); + classes[0] = ServerlessReactiveEmbeddedServerFactory.class; + } catch (ClassNotFoundException e) { + classes[0] = ServerlessServletEmbeddedServerFactory.class; + } + + classes[1] = springBootInitializer; + return classes; + } +} diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveEmbeddedServerFactory.java b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveEmbeddedServerFactory.java new file mode 100644 index 000000000..94a9d2074 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveEmbeddedServerFactory.java @@ -0,0 +1,93 @@ +package com.amazonaws.serverless.proxy.spring.embedded; + +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory; +import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.web.server.WebServerException; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; + +import javax.servlet.*; +import java.io.IOException; +import java.util.Enumeration; + +@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +public class ServerlessReactiveEmbeddedServerFactory extends AbstractReactiveWebServerFactory implements WebServer, Servlet { + private ServletHttpHandlerAdapter handler; + private ServletConfig config; + static final String SERVLET_NAME = "com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveEmbeddedServerFactory"; + static final String SERVLET_INFO = "ServerlessReactiveEmbeddedServerFactory"; + + @Override + @SuppressFBWarnings("MTIA_SUSPECT_SERVLET_INSTANCE_FIELD") + public WebServer getWebServer(HttpHandler httpHandler) { + handler = new ServletHttpHandlerAdapter(httpHandler); + return this; + } + + @Override + public void start() throws WebServerException { + // register this object as the main handler servlet with a mapping of / + SpringBootLambdaContainerHandler + .getInstance() + .getServletContext() + .addServlet(SERVLET_NAME, this) + .addMapping("/"); + handler.init(new ServletAdapterConfig()); + } + + @Override + public void stop() throws WebServerException { + // nothing to do here. + } + + @Override + public void init(ServletConfig servletConfig) throws ServletException { + config = servletConfig; + } + + @Override + public ServletConfig getServletConfig() { + return config; + } + + @Override + public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { + handler.service(servletRequest, servletResponse); + } + + @Override + public String getServletInfo() { + return SERVLET_INFO; + } + + @Override + public void destroy() { + + } + + private static class ServletAdapterConfig implements ServletConfig { + @Override + public String getServletName() { + return SERVLET_NAME; + } + + @Override + public ServletContext getServletContext() { + return SpringBootLambdaContainerHandler.getInstance().getServletContext(); + } + + @Override + public String getInitParameter(String s) { + return null; + } + + @Override + public Enumeration getInitParameterNames() { + return null; + } + } +} diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java new file mode 100644 index 000000000..b47b081c7 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactory.java @@ -0,0 +1,55 @@ +package com.amazonaws.serverless.proxy.spring.embedded; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.web.server.WebServerException; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; +import org.springframework.core.Ordered; + +import javax.servlet.ServletException; + +@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +public class ServerlessServletEmbeddedServerFactory implements ServletWebServerFactory, WebServer { + private ServletContextInitializer[] initializers; + private AwsLambdaServletContainerHandler handler; + + public ServerlessServletEmbeddedServerFactory() { + super(); + handler = SpringBootLambdaContainerHandler.getInstance(); + } + + @Override + public WebServer getWebServer(ServletContextInitializer... initializers) { + this.initializers = initializers; + for (ServletContextInitializer i : initializers) { + try { + if (handler.getServletContext() == null) { + throw new WebServerException("Attempting to initialize ServletEmbeddedWebServer without ServletContext in Handler", null); + } + i.onStartup(handler.getServletContext()); + } catch (ServletException e) { + throw new WebServerException("Could not initialize Servlets", e); + } + } + return this; + } + + @Override + public void start() throws WebServerException { + + } + + @Override + public void stop() throws WebServerException { + + } + + @Override + public int getPort() { + return 0; + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/ServletAppTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/ServletAppTest.java new file mode 100644 index 000000000..b7353cd82 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/ServletAppTest.java @@ -0,0 +1,24 @@ +package com.amazonaws.servlerss.proxy.spring; + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.servlerss.proxy.spring.servletapp.LambdaHandler; +import com.amazonaws.servlerss.proxy.spring.servletapp.MessageController; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ServletAppTest { + + LambdaHandler handler = new LambdaHandler(); + MockLambdaContext lambdaContext = new MockLambdaContext(); + + @Test + public void helloRequest_respondsWithSingleMessage() { + AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/WebFluxAppTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/WebFluxAppTest.java new file mode 100644 index 000000000..ffdb98e41 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/WebFluxAppTest.java @@ -0,0 +1,32 @@ +package com.amazonaws.servlerss.proxy.spring; + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.servlerss.proxy.spring.webfluxapp.LambdaHandler; +import com.amazonaws.servlerss.proxy.spring.webfluxapp.MessageController; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class WebFluxAppTest { + + LambdaHandler handler = new LambdaHandler(); + MockLambdaContext lambdaContext = new MockLambdaContext(); + + @Test + public void helloRequest_respondsWithSingleMessage() { + AwsProxyRequest req = new AwsProxyRequestBuilder("/single", "GET").build(); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertEquals(MessageController.MESSAGE, resp.getBody()); + } + + @Test + public void helloDoubleRequest_respondsWithDoubleMessage() { + AwsProxyRequest req = new AwsProxyRequestBuilder("/double", "GET").build(); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + + assertEquals(MessageController.MESSAGE + MessageController.MESSAGE, resp.getBody()); + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/LambdaHandler.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/LambdaHandler.java new file mode 100644 index 000000000..c6b4e0b76 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/LambdaHandler.java @@ -0,0 +1,25 @@ +package com.amazonaws.servlerss.proxy.spring.servletapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +public class LambdaHandler implements RequestHandler { + private static SpringBootLambdaContainerHandler handler; + + static { + try { + handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(ServletApplication.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + } + } + + @Override + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + return handler.proxy(awsProxyRequest, context); + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/MessageController.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/MessageController.java new file mode 100644 index 000000000..48731347b --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/MessageController.java @@ -0,0 +1,15 @@ +package com.amazonaws.servlerss.proxy.spring.servletapp; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class MessageController { + public static final String HELLO_MESSAGE = "Hello"; + + @RequestMapping(path="/hello", method= RequestMethod.GET) + public String hello() { + return HELLO_MESSAGE; + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/ServletApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/ServletApplication.java new file mode 100644 index 000000000..c7050f1b3 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/ServletApplication.java @@ -0,0 +1,8 @@ +package com.amazonaws.servlerss.proxy.spring.servletapp; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +@SpringBootApplication +public class ServletApplication extends SpringBootServletInitializer { +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/LambdaHandler.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/LambdaHandler.java new file mode 100644 index 000000000..60dcbd4a3 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/LambdaHandler.java @@ -0,0 +1,25 @@ +package com.amazonaws.servlerss.proxy.spring.webfluxapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +public class LambdaHandler implements RequestHandler { + private static SpringBootLambdaContainerHandler handler; + + static { + try { + handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(WebFluxTestApplication.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + } + } + + @Override + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + return handler.proxy(awsProxyRequest, context); + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/MessageController.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/MessageController.java new file mode 100644 index 000000000..a507ff978 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/MessageController.java @@ -0,0 +1,26 @@ +package com.amazonaws.servlerss.proxy.spring.webfluxapp; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + +@RestController +public class MessageController { + public static final String MESSAGE = "Hello"; + + @RequestMapping(path="/single", method= RequestMethod.GET) + Flux singleMessage(){ + return Flux.just( + MESSAGE + ); + } + + @RequestMapping(path="/double", method= RequestMethod.GET) + Flux doubleMessage(){ + return Flux.just( + MESSAGE, + MESSAGE + ); + } +} \ No newline at end of file diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/WebFluxTestApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/WebFluxTestApplication.java new file mode 100644 index 000000000..ec06ac4d8 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/WebFluxTestApplication.java @@ -0,0 +1,8 @@ +package com.amazonaws.servlerss.proxy.spring.webfluxapp; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.reactive.config.EnableWebFlux; + +@SpringBootApplication +public class WebFluxTestApplication { +} diff --git a/pom.xml b/pom.xml index 807aecd9b..00617337b 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ aws-serverless-spring-archetype aws-serverless-springboot-archetype aws-serverless-springboot2-archetype + aws-serverless-java-container-springboot2 From db4664cad08322c26fb5ff5f321a7ec04691344c Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 11 Sep 2019 13:02:49 -0700 Subject: [PATCH 15/40] Fixed springboot2 archetype to use the new module --- .../resources/archetype-resources/pom.xml | 10 +++++-- .../src/main/java/Application.java | 30 +------------------ 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/pom.xml index fcdf963fd..3dfaa1235 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/pom.xml @@ -16,7 +16,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.1.RELEASE + 2.1.8.RELEASE @@ -27,13 +27,19 @@ com.amazonaws.serverless - aws-serverless-java-container-spring + aws-serverless-java-container-springboot2 ${project.version} org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/Application.java b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/Application.java index 7351392ff..1b74086f7 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/Application.java +++ b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/Application.java @@ -5,21 +5,9 @@ #set($logging = $logging.replaceAll("\n", "").trim()) package ${groupId}; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; -import org.springframework.context.annotation.Bean; 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; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import ${groupId}.controller.PingController; @@ -28,23 +16,7 @@ // We use direct @Import instead of @ComponentScan to speed up cold starts // @ComponentScan(basePackages = "${groupId}.controller") @Import({ PingController.class }) -public class Application extends SpringBootServletInitializer { - - /* - * 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(); - } +public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); From 248fad3f84bbf5f8ba3538757bda53b02076e183 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 11 Sep 2019 13:56:04 -0700 Subject: [PATCH 16/40] Unit test for Servlet embedded factory --- ...rlessServletEmbeddedServerFactoryTest.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java new file mode 100644 index 000000000..60412ec1e --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java @@ -0,0 +1,46 @@ +package com.amazonaws.servlerss.proxy.spring.embedded; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; +import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory; +import org.junit.Test; +import org.springframework.boot.web.servlet.ServletContextInitializer; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import static org.junit.Assert.fail; + +public class ServerlessServletEmbeddedServerFactoryTest { + private SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler( + AwsProxyRequest.class, + AwsProxyResponse.class, + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + null + ); + + public ServerlessServletEmbeddedServerFactoryTest() throws ContainerInitializationException { + } + + @Test + public void getWebServer_callsInitializers() { + ServerlessServletEmbeddedServerFactory factory = new ServerlessServletEmbeddedServerFactory(); + factory.getWebServer(new ServletContextInitializer() { + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + if (servletContext == null) { + fail("Null servlet context"); + } + } + }); + } +} From 0ddf275b7550b7e8c1f8b0d60555877b476b8130 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 11 Sep 2019 19:38:13 -0700 Subject: [PATCH 17/40] Correct order of submodules in pom to fix build --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 00617337b..203dd981b 100644 --- a/pom.xml +++ b/pom.xml @@ -20,13 +20,13 @@ aws-serverless-java-container-spark aws-serverless-java-container-spring aws-serverless-java-container-struts2 + aws-serverless-java-container-springboot2 aws-serverless-struts2-archetype aws-serverless-jersey-archetype aws-serverless-spark-archetype aws-serverless-spring-archetype aws-serverless-springboot-archetype aws-serverless-springboot2-archetype - aws-serverless-java-container-springboot2 From d026ca23e0e9de2e8ab71685aa0b4e7b39c13774 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 11 Sep 2019 19:39:32 -0700 Subject: [PATCH 18/40] Cleaned up exception dumps in unit tests --- .../internal/servlet/AwsAsyncContext.java | 3 ++- .../testutils/AwsProxyRequestBuilder.java | 1 - .../internal/servlet/AwsAsyncContextTest.java | 1 - .../servlet/AwsFilterChainManagerTest.java | 4 ++-- .../AwsProxyHttpServletRequestReaderTest.java | 4 +--- .../AwsProxyHttpServletRequestTest.java | 18 +++++++++--------- .../servlet/AwsServletContextTest.java | 4 ++-- .../model/CognitoAuthorizerClaimsTest.java | 1 - .../proxy/jersey/JerseyAwsProxyTest.java | 4 ++-- .../proxy/jersey/JerseyParamEncodingTest.java | 4 ++-- .../proxy/spring/SpringAwsProxyTest.java | 4 ++-- .../proxy/spring/SpringBootAppTest.java | 2 +- .../proxy/spring/SpringBootSecurityTest.java | 2 +- .../proxy/struts2/Struts2AwsProxyTest.java | 4 ++-- 14 files changed, 26 insertions(+), 30 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java index ae6d18694..daab77e39 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java @@ -84,7 +84,8 @@ public void complete() { notifyListeners(NotificationType.COMPLETE, null); res.flushBuffer(); } catch (IOException e) { - e.printStackTrace(); + log.error("Could not flush response buffer", e); + throw new RuntimeException(e); } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java index 47c3b97c0..d861227bc 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java @@ -209,7 +209,6 @@ public AwsProxyRequestBuilder queryString(String key, String value) { ); //} } catch (UnsupportedEncodingException e) { - e.printStackTrace(); throw new RuntimeException(e); } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java index d5c86f1c4..349a95497 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContextTest.java @@ -110,7 +110,6 @@ protected void doFilter(HttpServletRequest request, HttpServletResponse response this.response.setStatus(desiredStatus); this.response.flushBuffer(); } catch (Exception e) { - e.printStackTrace(); throw new ServletException(e); } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java index dd344d3b0..8bc753540 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java @@ -193,11 +193,11 @@ public void filerChain_executeMultipleFilters_expectRunEachTime() { try { fcHolder2.doFilter(req2, resp2); } catch (IOException e) { - fail("IO Exception while executing filters"); e.printStackTrace(); + fail("IO Exception while executing filters"); } catch (ServletException e) { - fail("Servlet exception while executing filters"); e.printStackTrace(); + fail("Servlet exception while executing filters"); } assertTrue(req2.getAttribute(REQUEST_CUSTOM_ATTRIBUTE_NAME) != null); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReaderTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReaderTest.java index 72270b879..de246bc04 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReaderTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReaderTest.java @@ -117,8 +117,8 @@ public void readRequest_validEventEmptyPath_expectException() { AwsProxyHttpServletRequest servletReq = reader.readRequest(req, null, null, ContainerConfig.defaultConfig()); assertNotNull(servletReq); } catch (InvalidRequestEventException e) { - fail("Could not read a request with a null path"); e.printStackTrace(); + fail("Could not read a request with a null path"); } } @@ -130,7 +130,6 @@ public void readRequest_invalidEventEmptyMethod_expectException() { fail("Expected InvalidRequestEventException"); } catch (InvalidRequestEventException e) { assertEquals(AwsProxyHttpServletRequestReader.INVALID_REQUEST_ERROR, e.getMessage()); - e.printStackTrace(); } } @@ -143,7 +142,6 @@ public void readRequest_invalidEventEmptyContext_expectException() { fail("Expected InvalidRequestEventException"); } catch (InvalidRequestEventException e) { assertEquals(AwsProxyHttpServletRequestReader.INVALID_REQUEST_ERROR, e.getMessage()); - e.printStackTrace(); } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java index 62c785451..c73e64ab9 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestTest.java @@ -274,11 +274,12 @@ public void charEncoding_getEncoding_expectNoEncodingWithoutContentType() { try { request.setCharacterEncoding(StandardCharsets.UTF_8.name()); // we have not specified a content type so the encoding will not be set - assertEquals(null, request.getCharacterEncoding()); - assertEquals(null, request.getContentType()); + assertNull(request.getCharacterEncoding()); + assertNull(request.getContentType()); } catch (UnsupportedEncodingException e) { - fail("Unsupported encoding"); e.printStackTrace(); + fail("Unsupported encoding"); + } } @@ -286,7 +287,7 @@ public void charEncoding_getEncoding_expectNoEncodingWithoutContentType() { public void charEncoding_getEncoding_expectContentTypeOnly() { HttpServletRequest request = getRequest(getRequestWithHeaders(), null, null); // we have not specified a content type so the encoding will not be set - assertEquals(null, request.getCharacterEncoding()); + assertNull(request.getCharacterEncoding()); assertEquals(MediaType.APPLICATION_JSON, request.getContentType()); try { request.setCharacterEncoding(StandardCharsets.UTF_8.name()); @@ -295,8 +296,8 @@ public void charEncoding_getEncoding_expectContentTypeOnly() { assertEquals(newHeaderValue, request.getContentType()); assertEquals(StandardCharsets.UTF_8.name(), request.getCharacterEncoding()); } catch (UnsupportedEncodingException e) { - fail("Unsupported encoding"); e.printStackTrace(); + fail("Unsupported encoding"); } } @@ -321,8 +322,8 @@ public void charEncoding_addCharEncodingTwice_expectSingleMediaTypeAndEncoding() assertEquals(newHeaderValue, request.getContentType()); assertEquals(StandardCharsets.ISO_8859_1.name(), request.getCharacterEncoding()); } catch (UnsupportedEncodingException e) { - fail("Unsupported encoding"); e.printStackTrace(); + fail("Unsupported encoding"); } } @@ -335,10 +336,9 @@ public void contentType_lowerCaseHeaderKey_expectUpdatedMediaType() { assertEquals(newHeaderValue, request.getHeader(HttpHeaders.CONTENT_TYPE)); assertEquals(newHeaderValue, request.getContentType()); assertEquals(StandardCharsets.UTF_8.name(), request.getCharacterEncoding()); - } catch (UnsupportedEncodingException e) { - fail("Unsupported encoding"); e.printStackTrace(); + fail("Unsupported encoding"); } } @@ -352,8 +352,8 @@ public void contentType_duplicateCase_expectSingleContentTypeHeader() { assertNotNull(request.getHeader(HttpHeaders.CONTENT_TYPE)); assertNotNull(request.getHeader(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault()))); } catch (UnsupportedEncodingException e) { - fail("Unsupported encoding"); e.printStackTrace(); + fail("Unsupported encoding"); } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java index ccb62bad9..07628f187 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java @@ -34,8 +34,8 @@ public static void setUp() { try { LambdaContainerHandler.getContainerConfig().addValidFilePath(tmpFile.getCanonicalPath()); } catch (IOException e) { - fail("Could not add tmp dir to valid paths"); e.printStackTrace(); + fail("Could not add tmp dir to valid paths"); } LambdaContainerHandler.getContainerConfig().addValidFilePath("C:\\MyTestFolder"); } @@ -48,8 +48,8 @@ public void getMimeType_disabledPath_expectException() { } catch (IllegalArgumentException e) { assertTrue(e.getMessage().startsWith("File path not allowed")); } catch (Exception e) { - fail("Unrecognized exception"); e.printStackTrace(); + fail("Unrecognized exception"); } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/CognitoAuthorizerClaimsTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/CognitoAuthorizerClaimsTest.java index de8b36547..55b5d936f 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/CognitoAuthorizerClaimsTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/CognitoAuthorizerClaimsTest.java @@ -85,7 +85,6 @@ public void claims_serialize_validJsonString() { assertEquals(EMAIL, req.getRequestContext().getAuthorizer().getClaims().getEmail()); assertTrue(req.getRequestContext().getAuthorizer().getClaims().isEmailVerified()); } catch (IOException e) { - e.printStackTrace(); fail(); } } diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java index baecb13aa..be89c3a06 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java @@ -408,8 +408,8 @@ private void validateMapResponseModel(AwsProxyResponse output, String key, Strin assertNotNull(response.getValues().get(key)); assertEquals(value, response.getValues().get(key)); } catch (IOException e) { - fail("Exception while parsing response body: " + e.getMessage()); e.printStackTrace(); + fail("Exception while parsing response body: " + e.getMessage()); } } @@ -419,8 +419,8 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) { assertNotNull(response.getValue()); assertEquals(value, response.getValue()); } catch (IOException e) { - fail("Exception while parsing response body: " + e.getMessage()); e.printStackTrace(); + fail("Exception while parsing response body: " + e.getMessage()); } } } diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java index 0484e62d9..d3b951aea 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyParamEncodingTest.java @@ -239,8 +239,8 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) { assertNotNull(response.getValue()); assertEquals(value, response.getValue()); } catch (IOException e) { - fail("Exception while parsing response body: " + e.getMessage()); e.printStackTrace(); + fail("Exception while parsing response body: " + e.getMessage()); } } @@ -250,8 +250,8 @@ private void validateMapResponseModel(AwsProxyResponse output, String key, Strin assertNotNull(response.getValues().get(key)); assertEquals(value, response.getValues().get(key)); } catch (IOException e) { - fail("Exception while parsing response body: " + e.getMessage()); e.printStackTrace(); + fail("Exception while parsing response body: " + e.getMessage()); } } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java index 6ed37b776..b9cb728d6 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java @@ -398,8 +398,8 @@ private void validateMapResponseModel(AwsProxyResponse output) { assertNotNull(response.getValues().get(CUSTOM_HEADER_KEY)); assertEquals(CUSTOM_HEADER_VALUE, response.getValues().get(CUSTOM_HEADER_KEY)); } catch (IOException e) { - fail("Exception while parsing response body: " + e.getMessage()); e.printStackTrace(); + fail("Exception while parsing response body: " + e.getMessage()); } } @@ -409,8 +409,8 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) { assertNotNull(response.getValue()); assertEquals(value, response.getValue()); } catch (IOException e) { - fail("Exception while parsing response body: " + e.getMessage()); e.printStackTrace(); + fail("Exception while parsing response body: " + e.getMessage()); } } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java index 57d8fda4a..6fcedce61 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java @@ -94,8 +94,8 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) { assertNotNull(response.getValue()); assertEquals(value, response.getValue()); } catch (IOException e) { - fail("Exception while parsing response body: " + e.getMessage()); e.printStackTrace(); + fail("Exception while parsing response body: " + e.getMessage()); } } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java index 2fc6dd0d5..b8d504462 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootSecurityTest.java @@ -55,8 +55,8 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) { assertNotNull(response.getValue()); assertEquals(value, response.getValue()); } catch (IOException e) { - fail("Exception while parsing response body: " + e.getMessage()); e.printStackTrace(); + fail("Exception while parsing response body: " + e.getMessage()); } } } diff --git a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java index 1e6ad63ae..45e14ccdb 100644 --- a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java +++ b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java @@ -304,8 +304,8 @@ private void validateMapResponseModel(AwsProxyResponse output, String key, Strin assertNotNull(response.get(key)); assertEquals(value, response.get(key)); } catch (IOException e) { - fail("Exception while parsing response body: " + e.getMessage()); e.printStackTrace(); + fail("Exception while parsing response body: " + e.getMessage()); } } @@ -314,8 +314,8 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) { assertNotNull(output.getBody()); assertEquals(value, objectMapper.readerFor(String.class).readValue(output.getBody())); } catch (Exception e) { - fail("Exception while parsing response body: " + e.getMessage()); e.printStackTrace(); + fail("Exception while parsing response body: " + e.getMessage()); } } } From 9f28d25e34d9663466080287217987809155dea3 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 11 Sep 2019 19:54:37 -0700 Subject: [PATCH 19/40] Updated springboot2 sample to use new package --- samples/springboot2/pet-store/build.gradle | 6 ++++-- samples/springboot2/pet-store/pom.xml | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/samples/springboot2/pet-store/build.gradle b/samples/springboot2/pet-store/build.gradle index 8ad81f122..7a9c8b9f2 100644 --- a/samples/springboot2/pet-store/build.gradle +++ b/samples/springboot2/pet-store/build.gradle @@ -11,8 +11,10 @@ repositories { dependencies { compile ( - 'org.springframework.boot:spring-boot-starter-web:2.1.1.RELEASE', - 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', + implementation('org.springframework.boot:spring-boot-starter-web:2.1.1.RELEASE') { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' + }, + 'com.amazonaws.serverless:aws-serverless-java-container-springboot2:[1.0,)', 'io.symphonia:lambda-logging:1.0.1' ) testCompile("junit:junit") diff --git a/samples/springboot2/pet-store/pom.xml b/samples/springboot2/pet-store/pom.xml index b9d47fc1a..547a7ead1 100644 --- a/samples/springboot2/pet-store/pom.xml +++ b/samples/springboot2/pet-store/pom.xml @@ -10,7 +10,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.1.RELEASE + 2.1.8.RELEASE @@ -22,11 +22,17 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + com.amazonaws.serverless - aws-serverless-java-container-spring + aws-serverless-java-container-springboot2 [0.1,) From e5a9bba2d05721d5ea83c1698ada1625529181c1 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 12 Sep 2019 08:50:34 -0700 Subject: [PATCH 20/40] Fixed gradle build file for springboot2 archetype to use the new dependency --- .../src/main/resources/archetype-resources/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle index 7bd0e7fec..e48a0dc99 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle +++ b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle @@ -12,7 +12,7 @@ repositories { dependencies { compile ( 'org.springframework.boot:spring-boot-starter-web:2.1.1.RELEASE', - 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', + 'com.amazonaws.serverless:aws-serverless-java-container-springboot2:[1.0,)', 'io.symphonia:lambda-logging:1.0.1' ) From d1ad9c7e36a97f27fdc850ffe2f89ba43f9a8bae Mon Sep 17 00:00:00 2001 From: sapessi Date: Fri, 20 Sep 2019 12:56:11 -0700 Subject: [PATCH 21/40] Updated getParameterValues to support multi-value query string prameters and address #280 --- .../servlet/AwsProxyHttpServletRequest.java | 13 +++---------- .../internal/testutils/AwsProxyRequestBuilder.java | 9 +++++++++ .../serverless/proxy/spring/SpringBootAppTest.java | 10 ++++++++++ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java index cf9729b5a..83e99c746 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java @@ -474,7 +474,7 @@ public String getParameter(String s) { } String[] bodyParams = getFormBodyParameterCaseInsensitive(s); - if (bodyParams == null || bodyParams.length == 0) { + if (bodyParams.length == 0) { return null; } else { return bodyParams[0]; @@ -495,16 +495,9 @@ public Enumeration getParameterNames() { @Override @SuppressFBWarnings("PZLA_PREFER_ZERO_LENGTH_ARRAYS") // suppressing this as according to the specs we should be returning null here if we can't find params public String[] getParameterValues(String s) { - List values = new ArrayList<>(); - String queryValue = getFirstQueryParamValue(s, config.isQueryStringCaseSensitive()); - if (queryValue != null) { - values.add(queryValue); - } + List values = new ArrayList<>(Arrays.asList(getQueryParamValues(s, config.isQueryStringCaseSensitive()))); - String[] formBodyValues = getFormBodyParameterCaseInsensitive(s); - if (formBodyValues != null) { - values.addAll(Arrays.asList(formBodyValues)); - } + values.addAll(Arrays.asList(getFormBodyParameterCaseInsensitive(s))); if (values.size() == 0) { return null; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java index d861227bc..3260d433c 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java @@ -366,6 +366,15 @@ public AwsProxyRequestBuilder referer(String referer) { return this; } + + public AwsProxyRequestBuilder basicAuth(String username, String password) { + // we remove the existing authorization strategy + request.getMultiValueHeaders().remove(HttpHeaders.AUTHORIZATION); + String authHeader = "Basic " + Base64.getMimeEncoder().encodeToString((username + ":" + password).getBytes(Charset.defaultCharset())); + request.getMultiValueHeaders().add(HttpHeaders.AUTHORIZATION, authHeader); + return this; + } + public AwsProxyRequestBuilder fromJsonString(String jsonContent) throws IOException { request = LambdaContainerHandler.getObjectMapper().readValue(jsonContent, AwsProxyRequest.class); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java index 6fcedce61..286d69ccd 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java @@ -63,6 +63,16 @@ public void queryString_commaSeparatedList_expectUnmarshalAsList() { validateSingleValueModel(resp, "3"); } + @Test + public void queryString_multipleParamsWithSameName_expectUnmarshalAsList() { + AwsProxyRequest req = new AwsProxyRequestBuilder("/test/query-string", "GET") + .queryString("list", "v1").queryString("list", "v2").build(); + AwsProxyResponse resp = handler.handleRequest(req, context); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, "2"); + } + @Test public void staticContent_getHtmlFile_returnsHtmlContent() { LambdaContainerHandler.getContainerConfig().addValidFilePath("/Users/bulianis/workspace/aws-serverless-java-container/aws-serverless-java-container-spring"); From 301f718287a9155f9fd3b48865838a45d83c58e2 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 25 Sep 2019 21:51:15 -0700 Subject: [PATCH 22/40] Added new InitializationWrapper and Async implementation to address #210 and #234. Also added a new builder object that makes it easier to construct container handlers --- .../proxy/AsyncInitializationWrapper.java | 103 ++++++++++++ .../proxy/InitializationWrapper.java | 34 ++++ .../internal/LambdaContainerHandler.java | 46 +++++- .../ServletLambdaContainerHandlerBuilder.java | 148 ++++++++++++++++++ .../proxy/model/ContainerConfig.java | 26 +++ ...vletLambdaContainerHandlerBuilderTest.java | 106 +++++++++++++ .../SpringBootLambdaContainerHandler.java | 48 ++---- .../spring/SpringBootProxyHandlerBuilder.java | 63 ++++++++ .../spring/SpringLambdaContainerHandler.java | 57 ++----- .../spring/SpringProxyHandlerBuilder.java | 80 ++++++++++ .../SpringBootLambdaContainerHandler.java | 56 ++----- .../spring/SpringBootProxyHandlerBuilder.java | 62 ++++++++ .../proxy/spring/ServletAppTest.java | 0 .../proxy/spring/WebFluxAppTest.java | 0 ...rlessServletEmbeddedServerFactoryTest.java | 4 +- .../spring/servletapp/LambdaHandler.java | 0 .../spring/servletapp/MessageController.java | 0 .../spring/servletapp/ServletApplication.java | 13 ++ .../spring/webfluxapp/LambdaHandler.java | 0 .../spring/webfluxapp/MessageController.java | 0 .../webfluxapp/WebFluxTestApplication.java | 12 ++ .../spring/servletapp/ServletApplication.java | 8 - .../webfluxapp/WebFluxTestApplication.java | 8 - 23 files changed, 730 insertions(+), 144 deletions(-) create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AsyncInitializationWrapper.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/InitializationWrapper.java create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilder.java create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilderTest.java create mode 100644 aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java create mode 100644 aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringProxyHandlerBuilder.java create mode 100644 aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java rename aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/{servlerss => serverless}/proxy/spring/ServletAppTest.java (100%) rename aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/{servlerss => serverless}/proxy/spring/WebFluxAppTest.java (100%) rename aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/{servlerss => serverless}/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java (94%) rename aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/{servlerss => serverless}/proxy/spring/servletapp/LambdaHandler.java (100%) rename aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/{servlerss => serverless}/proxy/spring/servletapp/MessageController.java (100%) create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java rename aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/{servlerss => serverless}/proxy/spring/webfluxapp/LambdaHandler.java (100%) rename aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/{servlerss => serverless}/proxy/spring/webfluxapp/MessageController.java (100%) create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java delete mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/ServletApplication.java delete mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/WebFluxTestApplication.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AsyncInitializationWrapper.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AsyncInitializationWrapper.java new file mode 100644 index 000000000..7ef74fc8b --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AsyncInitializationWrapper.java @@ -0,0 +1,103 @@ +package com.amazonaws.serverless.proxy; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Instant; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * An async implementation of the InitializationWrapper interface. This initializer calls the + * {@link LambdaContainerHandler#initialize()} in a separate thread. Then uses a latch to wait for the maximum Lambda + * initialization time of 10 seconds, if the initialize method takes longer than 10 seconds to return, the + * {@link #start(LambdaContainerHandler)} returns control to the caller and lets the initialization thread continue in + * the background. The {@link LambdaContainerHandler#proxy(Object, Context)} automatically waits for the latch of the + * initializer to be released. + * + * The constructor of this class expects an epoch long. This is meant to be as close as possible to the time the Lambda + * function actually started. In most cases, the first action in the constructor of the handler class should be to populate + * this long value ({@code Instant.now().toEpochMs();}). This class uses the value to estimate how much of the init 10 + * seconds has already been used up. + */ +public class AsyncInitializationWrapper extends InitializationWrapper { + private static final int INIT_GRACE_TIME_MS = 250; + private static final int LAMBDA_MAX_INIT_TIME_MS = 10_000; + + private CountDownLatch initializationLatch; + private long actualStartTime; + private Logger log = LoggerFactory.getLogger(AsyncInitializationWrapper.class); + + /** + * Creates a new instance of the async initializer. + * @param startTime The epoch ms start time of the Lambda function, this should be measured as close as possible to + * the initialization of the function. + */ + public AsyncInitializationWrapper(long startTime) { + actualStartTime = startTime; + } + + @Override + public void start(LambdaContainerHandler handler) throws ContainerInitializationException { + initializationLatch = new CountDownLatch(1); + AsyncInitializer initializer = new AsyncInitializer(initializationLatch, handler); + Thread initThread = new Thread(initializer); + initThread.start(); + try { + long curTime = Instant.now().toEpochMilli(); + // account for the time it took to call the various constructors with the actual start time + a grace of 500ms + long awaitTime = LAMBDA_MAX_INIT_TIME_MS - (curTime - actualStartTime) - INIT_GRACE_TIME_MS; + log.info("Async initialization will wait for " + awaitTime + "ms"); + if (!initializationLatch.await(awaitTime, TimeUnit.MILLISECONDS)) { + log.info("Initialization took longer than " + LAMBDA_MAX_INIT_TIME_MS + ", setting new CountDownLatch and " + + "continuing in event handler"); + initializationLatch = new CountDownLatch(1); + initializer.replaceLatch(initializationLatch); + } + } catch (InterruptedException e) { + // at the moment we assume that this happened because of a timeout since the init thread calls System.exit + // when an exception is thrown. + throw new ContainerInitializationException("Container initialization interrupted", e); + } + } + + @Override + public CountDownLatch getInitializationLatch() { + return initializationLatch; + } + + private static class AsyncInitializer implements Runnable { + private LambdaContainerHandler handler; + private CountDownLatch initLatch; + private Logger log = LoggerFactory.getLogger(AsyncInitializationWrapper.class); + + AsyncInitializer(CountDownLatch latch, LambdaContainerHandler h) { + initLatch = latch; + handler = h; + } + + synchronized void replaceLatch(CountDownLatch newLatch) { + initLatch = newLatch; + } + + @Override + @SuppressFBWarnings("DM_EXIT") + public void run() { + log.info("Starting async initializer"); + try { + handler.initialize(); + } catch (ContainerInitializationException e) { + log.error("Failed to initialize container handler", e); + // we cannot return the exception so we crash the whole kaboodle here + System.exit(1); + } + synchronized(this) { + initLatch.countDown(); + } + } + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/InitializationWrapper.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/InitializationWrapper.java new file mode 100644 index 000000000..188a83bd3 --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/InitializationWrapper.java @@ -0,0 +1,34 @@ +package com.amazonaws.serverless.proxy; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; + +import java.util.concurrent.CountDownLatch; + +/** + * This class is in charge of initializing a {@link LambdaContainerHandler}. + * In most cases, this means calling the {@link LambdaContainerHandler#initialize()} method. Some implementations may + * require additional initialization steps, in this case implementations should provide their own + * InitializationWrapper. This library includes an async implementation of this class + * {@link AsyncInitializationWrapper} for frameworks that are likely to take longer than 10 seconds to start. + */ +public class InitializationWrapper { + /** + * This is the main entry point. Container handler builder and the static getAwsProxyHandler() methods + * of the various implementations will call this to initialize the underlying framework + * @param handler The container handler to be initializer + * @throws ContainerInitializationException If anything goes wrong during container initialization. + */ + public void start(LambdaContainerHandler handler) throws ContainerInitializationException { + handler.initialize(); + } + + /** + * Asynchronous implementations of the framework should return a latch that the container handler can use to decide + * whether it can start handling events. Synchronous implementations of this interface should return null. + * @return An initialized latch if the underlying container is starting in a separate thread, null otherwise. + */ + public CountDownLatch getInitializationLatch() { + return null; + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java index 0e61495a0..1e9bfaa80 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java @@ -14,13 +14,9 @@ import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.LogFormatter; +import com.amazonaws.serverless.proxy.*; import com.amazonaws.serverless.proxy.internal.servlet.ApacheCombinedServletLogFormatter; import com.amazonaws.serverless.proxy.model.ContainerConfig; -import com.amazonaws.serverless.proxy.ExceptionHandler; -import com.amazonaws.serverless.proxy.RequestReader; -import com.amazonaws.serverless.proxy.ResponseWriter; -import com.amazonaws.serverless.proxy.SecurityContextWriter; import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonParseException; @@ -38,6 +34,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** @@ -67,6 +64,7 @@ public abstract class LambdaContainerHandler exceptionHandler; private Class requestTypeClass; private Class responseTypeClass; + private InitializationWrapper initializationWrapper; protected Context lambdaContext; private LogFormatter logFormatter; @@ -97,7 +95,8 @@ protected LambdaContainerHandler(Class requestClass, RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, - ExceptionHandler exceptionHandler) { + ExceptionHandler exceptionHandler, + InitializationWrapper init) { log.info("Starting Lambda Container Handler"); requestTypeClass = requestClass; responseTypeClass = responseClass; @@ -105,8 +104,19 @@ protected LambdaContainerHandler(Class requestClass, this.responseWriter = responseWriter; this.securityContextWriter = securityContextWriter; this.exceptionHandler = exceptionHandler; + initializationWrapper = init; objectReader = getObjectMapper().readerFor(requestTypeClass); objectWriter = getObjectMapper().writerFor(responseTypeClass); + + } + + protected LambdaContainerHandler(Class requestClass, + Class responseClass, + RequestReader requestReader, + ResponseWriter responseWriter, + SecurityContextWriter securityContextWriter, + ExceptionHandler exceptionHandler) { + this(requestClass, responseClass, requestReader, responseWriter, securityContextWriter, exceptionHandler, new InitializationWrapper()); } @@ -131,6 +141,23 @@ public static ObjectMapper getObjectMapper() { return objectMapper; } + /** + * Returns the initialization wrapper this container handler will monitor to handle events + * @return The initialization wrapper that was passed to the constructor and this instance will use to decide + * whether it can start handling events. + */ + public InitializationWrapper getInitializationWrapper() { + return initializationWrapper; + } + + /** + * Sets a new initialization wrapper. + * @param wrapper The wrapper this instance will use to decide whether it can start handling events. + */ + public void setInitializationWrapper(InitializationWrapper wrapper) { + initializationWrapper = wrapper; + } + /** * Configures the library to strip a base path from incoming requests before passing them on to the wrapped * framework. This was added in response to issue #34 (https://github.com/awslabs/aws-serverless-java-container/issues/34). @@ -174,6 +201,13 @@ public ResponseType proxy(RequestType request, Context context) { ContainerRequestType containerRequest = requestReader.readRequest(request, securityContext, context, config); ContainerResponseType containerResponse = getContainerResponse(containerRequest, latch); + if (initializationWrapper != null && initializationWrapper.getInitializationLatch() != null) { + // we let the potential InterruptedException bubble up + if (!initializationWrapper.getInitializationLatch().await(config.getInitializationTimeout(), TimeUnit.MILLISECONDS)) { + throw new ContainerInitializationException("Could not initialize framework within the " + config.getInitializationTimeout() + "ms timeout", null); + } + } + handleRequest(containerRequest, containerResponse, context); latch.await(); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilder.java new file mode 100644 index 000000000..50743e73f --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilder.java @@ -0,0 +1,148 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.*; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; + +/** + * Base builder class for {@link AwsLambdaServletContainerHandler}. Implmentations can extend this class to have setters + * for the basic parameters. + * @param The event object class + * @param The output object class + * @param The container request type. For proxy implementations, this is {@link AwsProxyHttpServletRequest}. + * The response type is hardcoded to {@link AwsHttpServletResponse} since it is a generic + * servlet response implementation. + * @param The type of the handler we are building + * @param The builder object itself. This is used to allow implementations to re-use the setter method from this + * abstract class through + * "curiously recurring generic patterns" + */ +public abstract class ServletLambdaContainerHandlerBuilder< + RequestType, + ResponseType, + ContainerRequestType extends HttpServletRequest, + HandlerType extends AwsLambdaServletContainerHandler, + Builder extends ServletLambdaContainerHandlerBuilder> +{ + private static final String MISSING_FIELD_ERROR = "Missing %s in lambda container handler builder"; + + protected InitializationWrapper initializationWrapper; + protected RequestReader requestReader; + protected ResponseWriter responseWriter; + protected SecurityContextWriter securityContextWriter; + protected ExceptionHandler exceptionHandler; + protected Class requestTypeClass; + protected Class responseTypeClass; + + /** + * Validates that all of the required fields are populated. + * @throws ContainerInitializationException If values have not been set on the builder. The message in the exception + * contains a standard error message {@link ServletLambdaContainerHandlerBuilder#MISSING_FIELD_ERROR} populated with + * the list of missing fields. + */ + protected void validate() throws ContainerInitializationException { + List errFields = new ArrayList<>(); + if (requestTypeClass == null) { + errFields.add("request type class"); + } + if (responseTypeClass == null) { + errFields.add("response type class"); + } + if (requestReader == null) { + errFields.add("request reader"); + } + if (responseWriter == null) { + errFields.add("response writer"); + } + if (securityContextWriter == null) { + errFields.add("security context writer"); + } + if (exceptionHandler == null) { + errFields.add("exception handler"); + } + if (initializationWrapper == null) { + errFields.add("initialization wrapper"); + } + if (!errFields.isEmpty()) { + throw new ContainerInitializationException(String.format(MISSING_FIELD_ERROR, String.join(", ", errFields)), null); + } + } + + /** + * Sets all of the required fields in the builder to the default settings for a Servlet-compatible framework that wants + * to support AWS proxy event and output types. + * @return A populated builder + */ + public Builder defaultProxy() { + initializationWrapper(new InitializationWrapper()) + .requestReader((RequestReader) new AwsProxyHttpServletRequestReader()) + .responseWriter((ResponseWriter) new AwsProxyHttpServletResponseWriter()) + .securityContextWriter((SecurityContextWriter) new AwsProxySecurityContextWriter()) + .exceptionHandler((ExceptionHandler) new AwsProxyExceptionHandler()) + .requestTypeClass((Class) AwsProxyRequest.class) + .responseTypeClass((Class) AwsProxyResponse.class); + return self(); + } + + /** + * Sets the initialization wrapper to be used by the {@link ServletLambdaContainerHandlerBuilder#buildAndInitialize()} + * method to start the framework implementations + * @param initializationWrapper An implementation of InitializationWrapper. In most cases, this will be + * set to {@link InitializationWrapper}. The {@link ServletLambdaContainerHandlerBuilder#asyncInit(long)} + * method sets this to {@link AsyncInitializationWrapper}. + * @return This builder object + */ + public Builder initializationWrapper(InitializationWrapper initializationWrapper) { + this.initializationWrapper = initializationWrapper; + return self(); + } + + public Builder requestReader(RequestReader requestReader) { + this.requestReader = requestReader; + return self(); + } + + public Builder responseWriter(ResponseWriter responseWriter) { + this.responseWriter = responseWriter; + return self(); + } + + public Builder securityContextWriter(SecurityContextWriter securityContextWriter) { + this.securityContextWriter = securityContextWriter; + return self(); + } + + public Builder exceptionHandler(ExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + return self(); + } + + public Builder requestTypeClass(Class requestType) { + this.requestTypeClass = requestType; + return self(); + } + + public Builder responseTypeClass(Class responseType) { + this.responseTypeClass = responseType; + return self(); + } + + public Builder asyncInit(long actualStartTime) { + this.initializationWrapper = new AsyncInitializationWrapper(actualStartTime); + return self(); + } + + /** + * Implementations should implement this method to return their type. All of the builder methods in this abstract + * class use this method to return the correct builder type. + * @return The current builder. + */ + protected abstract Builder self(); + public abstract HandlerType build() throws ContainerInitializationException; + public abstract HandlerType buildAndInitialize() throws ContainerInitializationException; +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/ContainerConfig.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/ContainerConfig.java index 18175dd73..e5c80d635 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/ContainerConfig.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/ContainerConfig.java @@ -16,6 +16,7 @@ public class ContainerConfig { public static final String DEFAULT_URI_ENCODING = "UTF-8"; public static final String DEFAULT_CONTENT_CHARSET = "ISO-8859-1"; private static final List DEFAULT_FILE_PATHS = new ArrayList() {{ add("/tmp"); add("/var/task"); }}; + private static final int MAX_INIT_TIMEOUT_MS = 10_000; public static ContainerConfig defaultConfig() { ContainerConfig configuration = new ContainerConfig(); @@ -27,6 +28,7 @@ public static ContainerConfig defaultConfig() { configuration.setQueryStringCaseSensitive(false); configuration.addBinaryContentTypes("application/octet-stream", "image/jpeg", "image/png", "image/gif"); configuration.setDefaultContentCharset(DEFAULT_CONTENT_CHARSET); + configuration.setInitializationTimeout(MAX_INIT_TIMEOUT_MS); return configuration; } @@ -45,6 +47,7 @@ public static ContainerConfig defaultConfig() { private List customDomainNames; private boolean queryStringCaseSensitive; private final HashSet binaryContentTypes; + private int initializationTimeout; public ContainerConfig() { validFilePaths = new ArrayList<>(); @@ -271,4 +274,27 @@ public String getDefaultContentCharset() { public void setDefaultContentCharset(String defaultContentCharset) { this.defaultContentCharset = defaultContentCharset; } + + /** + * Returns the maximum amount of time (in milliseconds) set for the initialization time. See documentation on the + * {@link #setInitializationTimeout(int)} for additional details. + * @return The max time allocated for initialization + */ + public int getInitializationTimeout() { + return initializationTimeout; + } + + /** + * Sets the initialization timeout. When using an async {@link com.amazonaws.serverless.proxy.InitializationWrapper} + * the underlying framework is initialized in a separate thread. Serverless Java Container will wait for the maximum + * time available during AWS Lambda's init step (~10 seconds) and then return control to the main thread. In the meanwhile, + * the initialization process of the underlying framework can continue in a separate thread. AWS Lambda will then call + * the handler class to handle an event. This timeout is the maximum amount of time Serverless Java Container framework + * will wait for the underlying framework to initialize before returning an error. By default, this is set to 10 seconds. + * @param initializationTimeout The maximum amount of time to wait for the underlying framework initialization after + * an event is received in milliseconds. + */ + public void setInitializationTimeout(int initializationTimeout) { + this.initializationTimeout = initializationTimeout; + } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilderTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilderTest.java new file mode 100644 index 000000000..a9fef9b19 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/ServletLambdaContainerHandlerBuilderTest.java @@ -0,0 +1,106 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; +import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.services.lambda.runtime.Context; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; + +import static org.junit.Assert.*; + +public class ServletLambdaContainerHandlerBuilderTest { + + @Test + public void validation_throwsException() { + TestBuilder testBuilder = new TestBuilder(); + try { + testBuilder.validate(); + } catch (ContainerInitializationException e) { + return; + } + fail("Did not throw exception"); + } + + @Test + public void additionalMethod_testSetter() { + TestBuilder test = new TestBuilder().exceptionHandler(new AwsProxyExceptionHandler()).name("test"); + assertEquals("test", test.getName()); + } + + @Test + public void defaultProxy_setsValuesCorrectly() { + TestBuilder test = new TestBuilder().defaultProxy().name("test"); + assertNotNull(test.initializationWrapper); + assertTrue(test.exceptionHandler instanceof AwsProxyExceptionHandler); + assertTrue(test.requestReader instanceof AwsProxyHttpServletRequestReader); + assertTrue(test.responseWriter instanceof AwsProxyHttpServletResponseWriter); + assertTrue(test.securityContextWriter instanceof AwsProxySecurityContextWriter); + assertSame(test.requestTypeClass, AwsProxyRequest.class); + assertSame(test.responseTypeClass, AwsProxyResponse.class); + assertEquals("test", test.name); + } + + public static final class TestHandler extends AwsLambdaServletContainerHandler { + + public TestHandler() { + super(AwsProxyRequest.class, AwsProxyResponse.class, new AwsProxyHttpServletRequestReader(), new AwsProxyHttpServletResponseWriter(), new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler()); + } + @Override + protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + return null; + } + + @Override + protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + + } + + @Override + public void initialize() throws ContainerInitializationException { + + } + } + + public static final class TestBuilder + extends ServletLambdaContainerHandlerBuilder< + AwsProxyRequest, + AwsProxyResponse, + AwsProxyHttpServletRequest, + TestHandler, + TestBuilder> { + + public TestBuilder() { + super(); + } + + private String name; + + public TestBuilder name(String n) { + name = n; + return this; + } + + public String getName() { + return name; + } + + @Override + protected TestBuilder self() { + return this; + } + + @Override + public TestHandler build() throws ContainerInitializationException { + return null; + } + + @Override + public TestHandler buildAndInitialize() throws ContainerInitializationException { + return null; + } + } +} diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java index f84f6ca38..38994ed3b 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -13,12 +13,7 @@ package com.amazonaws.serverless.proxy.spring; import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; -import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; -import com.amazonaws.serverless.proxy.ExceptionHandler; -import com.amazonaws.serverless.proxy.RequestReader; -import com.amazonaws.serverless.proxy.ResponseWriter; -import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.serverless.proxy.*; import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; @@ -68,26 +63,6 @@ public static SpringBootLambdaContainerHandler getInstance() { return instance; } - /** - * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects - * @param springBootInitializer {@code SpringBootServletInitializer} class - * @return An initialized instance of the `SpringLambdaContainerHandler` - * @throws ContainerInitializationException If an error occurs while initializing the Spring framework - */ - public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class springBootInitializer) - throws ContainerInitializationException { - SpringBootLambdaContainerHandler newHandler = new SpringBootLambdaContainerHandler<>( - AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler(), - springBootInitializer); - newHandler.initialize(); - return newHandler; - } - /** * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects and the given Spring profiles * @param springBootInitializer {@code SpringBootServletInitializer} class @@ -97,17 +72,12 @@ public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class springBootInitializer, String... profiles) throws ContainerInitializationException { - SpringBootLambdaContainerHandler newHandler = new SpringBootLambdaContainerHandler<>( - AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler(), - springBootInitializer); - newHandler.activateSpringProfiles(profiles); - newHandler.initialize(); - return newHandler; + return new SpringBootProxyHandlerBuilder() + .defaultProxy() + .initializationWrapper(new InitializationWrapper()) + .springBootApplication(springBootInitializer) + .profiles(profiles) + .buildAndInitialize(); } /** @@ -127,12 +97,14 @@ public SpringBootLambdaContainerHandler(Class requestTypeClass, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, - Class springBootInitializer) + Class springBootInitializer, + InitializationWrapper init) throws ContainerInitializationException { super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); Timer.start("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR"); initialized = false; this.springBootInitializer = springBootInitializer; + setInitializationWrapper(init); SpringBootLambdaContainerHandler.setInstance(this); Timer.stop("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR"); diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java new file mode 100644 index 000000000..4ead5bc4d --- /dev/null +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java @@ -0,0 +1,63 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.servlet.ServletLambdaContainerHandlerBuilder; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import org.springframework.web.WebApplicationInitializer; + +public final class SpringBootProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< + AwsProxyRequest, + AwsProxyResponse, + AwsProxyHttpServletRequest, + SpringBootLambdaContainerHandler, + SpringBootProxyHandlerBuilder> { + private Class springBootInitializer; + private String[] profiles; + + @Override + protected SpringBootProxyHandlerBuilder self() { + return this; + } + + + public SpringBootProxyHandlerBuilder springBootApplication(Class app) { + springBootInitializer = app; + return self(); + } + + public SpringBootProxyHandlerBuilder profiles(String... profiles) { + this.profiles = profiles; + return self(); + } + + @Override + public SpringBootLambdaContainerHandler build() throws ContainerInitializationException { + validate(); + if (springBootInitializer == null) { + throw new ContainerInitializationException("Missing spring boot application class in builder", null); + } + SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler<>( + requestTypeClass, + responseTypeClass, + requestReader, + responseWriter, + securityContextWriter, + exceptionHandler, + springBootInitializer, + initializationWrapper + ); + if (profiles != null) { + handler.activateSpringProfiles(profiles); + } + return handler; + } + + @Override + public SpringBootLambdaContainerHandler buildAndInitialize() throws ContainerInitializationException { + SpringBootLambdaContainerHandler handler = build(); + initializationWrapper.start(handler); + return handler; + } +} diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java index fcd9e7fd4..c63165a43 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java @@ -13,12 +13,7 @@ package com.amazonaws.serverless.proxy.spring; import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; -import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; -import com.amazonaws.serverless.proxy.ExceptionHandler; -import com.amazonaws.serverless.proxy.RequestReader; -import com.amazonaws.serverless.proxy.ResponseWriter; -import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.serverless.proxy.*; import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; @@ -54,30 +49,11 @@ public class SpringLambdaContainerHandler extends Aws * @throws ContainerInitializationException */ public static SpringLambdaContainerHandler getAwsProxyHandler(Class... config) throws ContainerInitializationException { - AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); - applicationContext.register(config); - - return getAwsProxyHandler(applicationContext); - } - - /** - * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects - * @param applicationContext A custom ConfigurableWebApplicationContext to be used - * @return An initialized instance of the `SpringLambdaContainerHandler` - * @throws ContainerInitializationException - */ - public static SpringLambdaContainerHandler getAwsProxyHandler(ConfigurableWebApplicationContext applicationContext) - throws ContainerInitializationException { - SpringLambdaContainerHandler newHandler = new SpringLambdaContainerHandler<>( - AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler(), - applicationContext); - newHandler.initialize(); - return newHandler; + return new SpringProxyHandlerBuilder() + .defaultProxy() + .initializationWrapper(new InitializationWrapper()) + .configurationClasses(config) + .buildAndInitialize(); } /** @@ -89,17 +65,12 @@ public static SpringLambdaContainerHandler ge */ public static SpringLambdaContainerHandler getAwsProxyHandler(ConfigurableWebApplicationContext applicationContext, String... profiles) throws ContainerInitializationException { - SpringLambdaContainerHandler newHandler = new SpringLambdaContainerHandler<>( - AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler(), - applicationContext); - newHandler.activateSpringProfiles(profiles); - newHandler.initialize(); - return newHandler; + return new SpringProxyHandlerBuilder() + .defaultProxy() + .initializationWrapper(new InitializationWrapper()) + .springApplicationContext(applicationContext) + .profiles(profiles) + .buildAndInitialize(); } /** @@ -118,11 +89,13 @@ public SpringLambdaContainerHandler(Class requestTypeClass, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, - ConfigurableWebApplicationContext applicationContext) + ConfigurableWebApplicationContext applicationContext, + InitializationWrapper init) throws ContainerInitializationException { super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); Timer.start("SPRING_CONTAINER_HANDLER_CONSTRUCTOR"); appContext = applicationContext; + setInitializationWrapper(init); Timer.stop("SPRING_CONTAINER_HANDLER_CONSTRUCTOR"); } diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringProxyHandlerBuilder.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringProxyHandlerBuilder.java new file mode 100644 index 000000000..9bfbf3cbc --- /dev/null +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringProxyHandlerBuilder.java @@ -0,0 +1,80 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.servlet.ServletLambdaContainerHandlerBuilder; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +public final class SpringProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< + AwsProxyRequest, + AwsProxyResponse, + AwsProxyHttpServletRequest, + SpringLambdaContainerHandler, + SpringProxyHandlerBuilder> { + private ConfigurableWebApplicationContext springContext; + private Class[] configurationClasses; + private String[] profiles; + + @Override + protected SpringProxyHandlerBuilder self() { + return this; + } + + + public SpringProxyHandlerBuilder springApplicationContext(ConfigurableWebApplicationContext app) { + springContext = app; + return self(); + } + + public SpringProxyHandlerBuilder configurationClasses(Class... config) { + configurationClasses = config; + return self(); + } + + public SpringProxyHandlerBuilder profiles(String... profiles) { + this.profiles = profiles; + return self(); + } + + @Override + public SpringLambdaContainerHandler build() throws ContainerInitializationException { + validate(); + if (springContext == null && (configurationClasses == null || configurationClasses.length == 0)) { + throw new ContainerInitializationException("Missing both configuration classes and application context, at least" + + " one of the two must be populated", null); + } + ConfigurableWebApplicationContext ctx = springContext; + if (ctx == null) { + ctx = new AnnotationConfigWebApplicationContext(); + if (configurationClasses != null) { + ((AnnotationConfigWebApplicationContext)ctx).register(configurationClasses); + } + } + + SpringLambdaContainerHandler handler = new SpringLambdaContainerHandler<>( + requestTypeClass, + responseTypeClass, + requestReader, + responseWriter, + securityContextWriter, + exceptionHandler, + ctx, + initializationWrapper + ); + if (profiles != null) { + handler.activateSpringProfiles(profiles); + } + return handler; + } + + @Override + public SpringLambdaContainerHandler buildAndInitialize() throws ContainerInitializationException { + SpringLambdaContainerHandler handler = build(); + initializationWrapper.start(handler); + return handler; + } +} diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java index c111b2c87..b6f6973ca 100644 --- a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java +++ b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -62,26 +62,6 @@ public static SpringBootLambdaContainerHandler getInstance() { return instance; } - /** - * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects - * @param springBootInitializer {@code SpringBootServletInitializer} class - * @return An initialized instance of the `SpringLambdaContainerHandler` - * @throws ContainerInitializationException If an error occurs while initializing the Spring framework - */ - public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class springBootInitializer) - throws ContainerInitializationException { - SpringBootLambdaContainerHandler newHandler = new SpringBootLambdaContainerHandler<>( - AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler(), - springBootInitializer); - newHandler.initialize(); - return newHandler; - } - /** * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects and the given Spring profiles * @param springBootInitializer {@code SpringBootServletInitializer} class @@ -91,17 +71,12 @@ public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class springBootInitializer, String... profiles) throws ContainerInitializationException { - SpringBootLambdaContainerHandler newHandler = new SpringBootLambdaContainerHandler<>( - AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler(), - springBootInitializer); - newHandler.activateSpringProfiles(profiles); - newHandler.initialize(); - return newHandler; + return new SpringBootProxyHandlerBuilder() + .defaultProxy() + .initializationWrapper(new InitializationWrapper()) + .springBootApplication(springBootInitializer) + .profiles(profiles) + .buildAndInitialize(); } /** @@ -121,15 +96,16 @@ public SpringBootLambdaContainerHandler(Class requestTypeClass, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, - Class springBootInitializer) - throws ContainerInitializationException { + Class springBootInitializer, + InitializationWrapper init) { super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); - Timer.start("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR"); + Timer.start("SPRINGBOOT2_CONTAINER_HANDLER_CONSTRUCTOR"); initialized = false; this.springBootInitializer = springBootInitializer; + setInitializationWrapper(init); SpringBootLambdaContainerHandler.setInstance(this); - Timer.stop("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR"); + Timer.stop("SPRINGBOOT2_CONTAINER_HANDLER_CONSTRUCTOR"); } // this is not pretty. However, because SpringBoot wants to control all of the initialization @@ -153,26 +129,24 @@ protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest @Override protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { // this method of the AwsLambdaServletContainerHandler sets the servlet context - Timer.start("SPRINGBOOT_HANDLE_REQUEST"); + Timer.start("SPRINGBOOT2_HANDLE_REQUEST"); // wire up the application context on the first invocation if (!initialized) { initialize(); } - containerRequest.setServletContext(getServletContext()); - // process filters & invoke servlet Servlet reqServlet = ((AwsServletContext)getServletContext()).getServletForPath(containerRequest.getPathInfo()); doFilter(containerRequest, containerResponse, reqServlet); - Timer.stop("SPRINGBOOT_HANDLE_REQUEST"); + Timer.stop("SPRINGBOOT2_HANDLE_REQUEST"); } @Override public void initialize() throws ContainerInitializationException { - Timer.start("SPRINGBOOT_COLD_START"); + Timer.start("SPRINGBOOT2_COLD_START"); SpringApplication app = new SpringApplication(getEmbeddedContainerClasses()); if (springProfiles != null && springProfiles.length > 0) { @@ -184,7 +158,7 @@ public void initialize() app.run(); initialized = true; - Timer.stop("SPRINGBOOT_COLD_START"); + Timer.stop("SPRINGBOOT2_COLD_START"); } private Class[] getEmbeddedContainerClasses() { diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java new file mode 100644 index 000000000..943cc69ff --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootProxyHandlerBuilder.java @@ -0,0 +1,62 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.servlet.ServletLambdaContainerHandlerBuilder; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; + +public final class SpringBootProxyHandlerBuilder extends ServletLambdaContainerHandlerBuilder< + AwsProxyRequest, + AwsProxyResponse, + AwsProxyHttpServletRequest, + SpringBootLambdaContainerHandler, + SpringBootProxyHandlerBuilder> { + private Class springBootInitializer; + private String[] profiles; + + @Override + protected SpringBootProxyHandlerBuilder self() { + return this; + } + + + public SpringBootProxyHandlerBuilder springBootApplication(Class app) { + springBootInitializer = app; + return self(); + } + + public SpringBootProxyHandlerBuilder profiles(String... profiles) { + this.profiles = profiles; + return self(); + } + + @Override + public SpringBootLambdaContainerHandler build() throws ContainerInitializationException { + validate(); + if (springBootInitializer == null) { + throw new ContainerInitializationException("Missing spring boot application class in builder", null); + } + SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler<>( + requestTypeClass, + responseTypeClass, + requestReader, + responseWriter, + securityContextWriter, + exceptionHandler, + springBootInitializer, + initializationWrapper + ); + if (profiles != null) { + handler.activateSpringProfiles(profiles); + } + return handler; + } + + @Override + public SpringBootLambdaContainerHandler buildAndInitialize() throws ContainerInitializationException { + SpringBootLambdaContainerHandler handler = build(); + initializationWrapper.start(handler); + return handler; + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/ServletAppTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/ServletAppTest.java rename to aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/WebFluxAppTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/WebFluxAppTest.java rename to aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java similarity index 94% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java rename to aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java index 60412ec1e..e9b9cfb11 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java @@ -3,6 +3,7 @@ import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.SyncInitializationWrapper; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; @@ -25,7 +26,8 @@ public class ServerlessServletEmbeddedServerFactoryTest { new AwsProxyHttpServletResponseWriter(), new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler(), - null + null, + new SyncInitializationWrapper() ); public ServerlessServletEmbeddedServerFactoryTest() throws ContainerInitializationException { diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/LambdaHandler.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/LambdaHandler.java rename to aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/MessageController.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/MessageController.java rename to aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java new file mode 100644 index 000000000..a85851b88 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java @@ -0,0 +1,13 @@ +package com.amazonaws.servlerss.proxy.spring.servletapp; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +@SpringBootApplication(exclude = { + org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class +}) +public class ServletApplication extends SpringBootServletInitializer { +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/LambdaHandler.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/LambdaHandler.java rename to aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/MessageController.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java similarity index 100% rename from aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/MessageController.java rename to aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java new file mode 100644 index 000000000..b3f14a0e4 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java @@ -0,0 +1,12 @@ +package com.amazonaws.servlerss.proxy.spring.webfluxapp; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.web.reactive.config.EnableWebFlux; + +@SpringBootApplication(exclude = { + org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.class +}) +public class WebFluxTestApplication { +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/ServletApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/ServletApplication.java deleted file mode 100644 index c7050f1b3..000000000 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/servletapp/ServletApplication.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.amazonaws.servlerss.proxy.spring.servletapp; - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; - -@SpringBootApplication -public class ServletApplication extends SpringBootServletInitializer { -} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/WebFluxTestApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/WebFluxTestApplication.java deleted file mode 100644 index ec06ac4d8..000000000 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/servlerss/proxy/spring/webfluxapp/WebFluxTestApplication.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.amazonaws.servlerss.proxy.spring.webfluxapp; - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.web.reactive.config.EnableWebFlux; - -@SpringBootApplication -public class WebFluxTestApplication { -} From e87a86e11b65e71db42f7b476245798c5c91621e Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 25 Sep 2019 21:53:24 -0700 Subject: [PATCH 23/40] Bug fix in servlet request impl to null-check headers in case Lambda gets sent an event without the multiValueHeaders property (#281). Also moved the ServletContext to be managed by the ServletRequestReader istead of set on each request by the LambdaContainerHandler. Additional bug fix to set the correct dispatcher type when startAsync is called on the request --- .../servlet/AwsProxyHttpServletRequest.java | 18 +++++++----------- .../AwsProxyHttpServletRequestReader.java | 8 +++++++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java index 83e99c746..eb5d90252 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java @@ -18,6 +18,7 @@ import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.ContainerConfig; +import com.amazonaws.serverless.proxy.model.Headers; import com.amazonaws.services.lambda.runtime.Context; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -31,13 +32,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.AsyncContext; -import javax.servlet.ReadListener; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import javax.servlet.*; import javax.servlet.http.*; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; @@ -119,10 +114,6 @@ public void setResponse(AwsHttpServletResponse response) { this.response = response; } - public AwsLambdaServletContainerHandler getContainerHandler() { - return containerHandler; - } - public void setContainerHandler(AwsLambdaServletContainerHandler containerHandler) { this.containerHandler = containerHandler; } @@ -380,6 +371,9 @@ public String getCharacterEncoding() { @Override public void setCharacterEncoding(String s) throws UnsupportedEncodingException { + if (request.getMultiValueHeaders() == null) { + request.setMultiValueHeaders(new Headers()); + } String currentContentType = request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE); if (currentContentType == null || "".equals(currentContentType)) { log.debug("Called set character encoding to " + SecurityUtils.crlf(s) + " on a request without a content type. Character encoding will not be set"); @@ -679,6 +673,7 @@ public boolean isAsyncSupported() { public AsyncContext startAsync() throws IllegalStateException { asyncContext = new AwsAsyncContext(this, response, containerHandler); + setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ASYNC); log.debug("Starting async context for request: " + SecurityUtils.crlf(request.getRequestContext().getRequestId())); return asyncContext; } @@ -688,6 +683,7 @@ public AsyncContext startAsync() public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { asyncContext = new AwsAsyncContext((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, containerHandler); + setAttribute(DISPATCHER_TYPE_ATTRIBUTE, DispatcherType.ASYNC); log.debug("Starting async context for request: " + SecurityUtils.crlf(request.getRequestContext().getRequestId())); return asyncContext; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java index 6b687fb82..405ae9739 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java @@ -18,6 +18,7 @@ import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.services.lambda.runtime.Context; +import javax.servlet.ServletContext; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.SecurityContext; @@ -27,11 +28,15 @@ */ public class AwsProxyHttpServletRequestReader extends RequestReader { static final String INVALID_REQUEST_ERROR = "The incoming event is not a valid request from Amazon API Gateway or an Application Load Balancer"; - + private ServletContext servletContext; //------------------------------------------------------------- // Methods - Implementation //------------------------------------------------------------- + public void setServletContext(ServletContext ctx) { + servletContext = ctx; + } + @Override public AwsProxyHttpServletRequest readRequest(AwsProxyRequest request, SecurityContext securityContext, Context lambdaContext, ContainerConfig config) throws InvalidRequestEventException { @@ -48,6 +53,7 @@ public AwsProxyHttpServletRequest readRequest(AwsProxyRequest request, SecurityC request.getMultiValueHeaders().putSingle(HttpHeaders.CONTENT_TYPE, getContentTypeWithCharset(contentType, config)); } AwsProxyHttpServletRequest servletRequest = new AwsProxyHttpServletRequest(request, lambdaContext, securityContext, config); + servletRequest.setServletContext(servletContext); servletRequest.setAttribute(API_GATEWAY_CONTEXT_PROPERTY, request.getRequestContext()); servletRequest.setAttribute(API_GATEWAY_STAGE_VARS_PROPERTY, request.getStageVariables()); servletRequest.setAttribute(API_GATEWAY_EVENT_PROPERTY, request); From a4b9ab072d332a64155097645cff61f1a7b81349 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 25 Sep 2019 21:53:58 -0700 Subject: [PATCH 24/40] Lots of new integration tests for springboot2 support --- .../proxy/spring/SpringBootAppTest.java | 52 ++++++++++++++++ .../spring/echoapp/EchoSpringAppConfig.java | 17 +++--- .../spring/springbootapp/TestController.java | 16 +++-- .../pom.xml | 20 +++++- .../proxy/spring/SecurityAppTest.java | 46 ++++++++++++++ .../proxy/spring/ServletAppTest.java | 9 +-- .../serverless/proxy/spring/SlowAppTest.java | 33 ++++++++++ .../proxy/spring/WebFluxAppTest.java | 9 +-- ...rlessServletEmbeddedServerFactoryTest.java | 9 ++- .../spring/securityapp/LambdaHandler.java | 26 ++++++++ .../spring/securityapp/MessageController.java | 16 +++++ .../spring/securityapp/SecurityConfig.java | 61 +++++++++++++++++++ .../securityapp/ServletApplication.java | 13 ++++ .../spring/servletapp/LambdaHandler.java | 3 +- .../spring/servletapp/MessageController.java | 2 +- .../spring/servletapp/ServletApplication.java | 2 +- .../proxy/spring/slowapp/LambdaHandler.java | 41 +++++++++++++ .../spring/slowapp/MessageController.java | 15 +++++ .../spring/slowapp/SlowTestApplication.java | 24 ++++++++ .../spring/webfluxapp/LambdaHandler.java | 3 +- .../spring/webfluxapp/MessageController.java | 2 +- .../webfluxapp/WebFluxTestApplication.java | 2 +- 22 files changed, 386 insertions(+), 35 deletions(-) create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/LambdaHandler.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/MessageController.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityConfig.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/ServletApplication.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/MessageController.java create mode 100644 aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java index 286d69ccd..91a559caa 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java @@ -6,6 +6,8 @@ import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.model.Headers; +import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel; import com.amazonaws.serverless.proxy.spring.springbootapp.LambdaHandler; import com.amazonaws.serverless.proxy.spring.springbootapp.TestController; @@ -17,6 +19,8 @@ import javax.ws.rs.core.HttpHeaders; import java.io.IOException; +import static com.amazonaws.serverless.proxy.spring.springbootapp.TestController.CUSTOM_HEADER_NAME; +import static com.amazonaws.serverless.proxy.spring.springbootapp.TestController.CUSTOM_QS_NAME; import static org.junit.Assert.*; @@ -34,6 +38,54 @@ public void testMethod_springSecurity_doesNotThrowException() { validateSingleValueModel(resp, TestController.TEST_VALUE); } + @Test + public void testMethod_testRequestFromString_doesNotThrowNpe() throws IOException { + AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString("{\n" + + " \"resource\": \"/missing-params\",\n" + + " \"path\": \"/missing-params\",\n" + + " \"httpMethod\": \"GET\",\n" + + " \"headers\": null,\n" + + " \"multiValueHeaders\": null,\n" + + " \"queryStringParameters\": null,\n" + + " \"multiValueQueryStringParameters\": null,\n" + + " \"pathParameters\": null,\n" + + " \"stageVariables\": null,\n" + + " \"requestContext\": {\n" + + " \"resourcePath\": \"/path/resource\",\n" + + " \"httpMethod\": \"POST\",\n" + + " \"path\": \"//path/resource\",\n" + + " \"accountId\": \"accountIdNumber\",\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"stage\": \"test-invoke-stage\",\n" + + " \"domainPrefix\": \"testPrefix\",\n" + + " \"identity\": {\n" + + " \"cognitoIdentityPoolId\": null,\n" + + " \"cognitoIdentityId\": null,\n" + + " \"apiKey\": \"test-invoke-api-key\",\n" + + " \"principalOrgId\": null,\n" + + " \"cognitoAuthenticationType\": null,\n" + + " \"userArn\": \"actual arn\",\n" + + " \"apiKeyId\": \"test-invoke-api-key-id\"\n" + + " }\n" + + " },\n" + + " \"body\": \"{ \\\"Key1\\\": \\\"Value1\\\", \\\"Key2\\\": \\\"Value2\\\", \\\"Key3\\\": \\\"Vaue3\\\" }\",\n" + + " \"isBase64Encoded\": \"false\"\n" + + "}").build(); + + AwsProxyResponse resp = handler.handleRequest(req, context); + assertNotNull(resp); + // Spring identifies the missing header + assertEquals(400, resp.getStatusCode()); + req.setMultiValueHeaders(new Headers()); + req.getMultiValueHeaders().add(CUSTOM_HEADER_NAME, "val"); + resp = handler.handleRequest(req, context); + assertEquals(400, resp.getStatusCode()); + req.setMultiValueQueryStringParameters(new MultiValuedTreeMap<>()); + req.getMultiValueQueryStringParameters().add(CUSTOM_QS_NAME, "val"); + resp = handler.handleRequest(req, context); + assertEquals(200, resp.getStatusCode()); + } + @Test public void defaultError_requestForward_springBootForwardsToDefaultErrorPage() { AwsProxyRequest req = new AwsProxyRequestBuilder("/test2", "GET").build(); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java index 17e09d923..c32935c70 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java @@ -3,6 +3,7 @@ import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.InitializationWrapper; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; import com.amazonaws.serverless.proxy.internal.servlet.AwsServletRegistration; @@ -10,6 +11,7 @@ import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler; +import com.amazonaws.serverless.proxy.spring.SpringProxyHandlerBuilder; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -37,16 +39,11 @@ public class EchoSpringAppConfig { @Bean public SpringLambdaContainerHandler springLambdaContainerHandler() throws ContainerInitializationException { - SpringLambdaContainerHandler handler = new SpringLambdaContainerHandler<>( - AwsProxyRequest.class, - AwsProxyResponse.class, - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler(), - applicationContext); - handler.setRefreshContext(false); - handler.initialize(); + SpringLambdaContainerHandler handler = new SpringProxyHandlerBuilder() + .defaultProxy() + .initializationWrapper(new InitializationWrapper()) + .springApplicationContext(applicationContext) + .buildAndInitialize(); handler.onStartup(c -> { FilterRegistration.Dynamic registration = c.addFilter("UnauthenticatedFilter", UnauthenticatedFilter.class); // update the registration to map to a path diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java index 0357328e1..900ce50ea 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestController.java @@ -7,11 +7,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @@ -27,6 +23,8 @@ public class TestController extends WebSecurityConfigurerAdapter { public static final String TEST_VALUE = "test"; public static final String UTF8_TEST_STRING = "health心跳测试完成。可正常使用"; + public static final String CUSTOM_HEADER_NAME = "X-Custom-Header"; + public static final String CUSTOM_QS_NAME = "qs"; // workaround to address the most annoying issue in the world: https://blog.georgovassilis.com/2015/10/29/spring-mvc-rest-controller-says-406-when-emails-are-part-url-path/ @Configuration @@ -49,6 +47,14 @@ public SingleValueModel testGet() { return value; } + @RequestMapping(path = "/missing-params", method = {RequestMethod.GET}) + public Map testInvalidParameters(@RequestHeader(CUSTOM_HEADER_NAME) String h1, @RequestParam(CUSTOM_QS_NAME) String qsValue) { + Map output = new HashMap<>(); + output.put("header", h1 == null); + output.put("queryString", qsValue == null); + return output; + } + @RequestMapping(path = "/test/{domain}", method = { RequestMethod.GET}) public SingleValueModel testDomainInPath(@PathVariable("domain") String domainName) { SingleValueModel value = new SingleValueModel(); diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot2/pom.xml index f0a8258a3..90134a048 100644 --- a/aws-serverless-java-container-springboot2/pom.xml +++ b/aws-serverless-java-container-springboot2/pom.xml @@ -18,6 +18,7 @@ 5.1.9.RELEASE + 5.1.6.RELEASE 2.1.8.RELEASE 1.8 @@ -50,7 +51,24 @@ ${springboot.version} true - + + org.springframework.security + spring-security-config + ${springsecurity.version} + test + + + org.springframework.security + spring-security-web + ${springsecurity.version} + test + + + org.jetbrains + annotations + 17.0.0 + compile + diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java new file mode 100644 index 000000000..34a593fd2 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SecurityAppTest.java @@ -0,0 +1,46 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.securityapp.LambdaHandler; +import com.amazonaws.serverless.proxy.spring.securityapp.MessageController; +import com.amazonaws.serverless.proxy.spring.securityapp.SecurityConfig; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.Test; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SecurityAppTest { + + LambdaHandler handler = new LambdaHandler(); + MockLambdaContext lambdaContext = new MockLambdaContext(); + + public SecurityAppTest() { + System.setProperty("logging.level.root", "DEBUG"); + } + + @Test + public void helloRequest_withAuth_respondsWithSingleMessage() { + AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); + AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); + assertEquals(401, resp.getStatusCode()); + assertTrue(resp.getMultiValueHeaders().containsKey(HttpHeaders.WWW_AUTHENTICATE)); + req = new AwsProxyRequestBuilder("/hello", "GET") + .basicAuth(SecurityConfig.USERNAME, SecurityConfig.PASSWORD) + .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN) + .build(); + resp = handler.handleRequest(req, lambdaContext); + assertEquals(200, resp.getStatusCode()); + } + + public void helloRequest_withoutAuith_respondsWithError() { + + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java index b7353cd82..4d5797e5f 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/ServletAppTest.java @@ -1,11 +1,12 @@ -package com.amazonaws.servlerss.proxy.spring; +package com.amazonaws.serverless.proxy.spring; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.servlerss.proxy.spring.servletapp.LambdaHandler; -import com.amazonaws.servlerss.proxy.spring.servletapp.MessageController; +import com.amazonaws.serverless.proxy.spring.servletapp.LambdaHandler; +import com.amazonaws.serverless.proxy.spring.servletapp.MessageController; +import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -19,6 +20,6 @@ public class ServletAppTest { public void helloRequest_respondsWithSingleMessage() { AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); - assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + Assert.assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); } } diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java new file mode 100644 index 000000000..8dabc5f66 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java @@ -0,0 +1,33 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.slowapp.LambdaHandler; +import com.amazonaws.serverless.proxy.spring.slowapp.MessageController; +import com.amazonaws.serverless.proxy.spring.slowapp.SlowTestApplication; +import org.junit.Assert; +import org.junit.Test; + +import java.time.Instant; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SlowAppTest { + + @Test + public void slowAppInit_continuesInBackgroundThread_returnsCorrect() { + LambdaHandler slowApp = new LambdaHandler(); + System.out.println("Start time: " + slowApp.getConstructorTime()); + assertTrue(slowApp.getConstructorTime() < 10_000); + AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); + long startRequestTime = Instant.now().toEpochMilli(); + AwsProxyResponse resp = slowApp.handleRequest(req, new MockLambdaContext()); + long endRequestTime = Instant.now().toEpochMilli(); + assertTrue(endRequestTime - startRequestTime > SlowTestApplication.SlowDownInit.INIT_SLEEP_TIME_MS - 10_000); + assertEquals(200, resp.getStatusCode()); + Assert.assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java index ffdb98e41..7148e3b67 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/WebFluxAppTest.java @@ -1,11 +1,12 @@ -package com.amazonaws.servlerss.proxy.spring; +package com.amazonaws.serverless.proxy.spring; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.servlerss.proxy.spring.webfluxapp.LambdaHandler; -import com.amazonaws.servlerss.proxy.spring.webfluxapp.MessageController; +import com.amazonaws.serverless.proxy.spring.webfluxapp.LambdaHandler; +import com.amazonaws.serverless.proxy.spring.webfluxapp.MessageController; +import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -19,7 +20,7 @@ public class WebFluxAppTest { public void helloRequest_respondsWithSingleMessage() { AwsProxyRequest req = new AwsProxyRequestBuilder("/single", "GET").build(); AwsProxyResponse resp = handler.handleRequest(req, lambdaContext); - assertEquals(MessageController.MESSAGE, resp.getBody()); + Assert.assertEquals(MessageController.MESSAGE, resp.getBody()); } @Test diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java index e9b9cfb11..010bcbdcf 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessServletEmbeddedServerFactoryTest.java @@ -1,15 +1,14 @@ -package com.amazonaws.servlerss.proxy.spring.embedded; +package com.amazonaws.serverless.proxy.spring.embedded; import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; -import com.amazonaws.serverless.proxy.SyncInitializationWrapper; +import com.amazonaws.serverless.proxy.InitializationWrapper; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; -import com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory; import org.junit.Test; import org.springframework.boot.web.servlet.ServletContextInitializer; @@ -19,7 +18,7 @@ import static org.junit.Assert.fail; public class ServerlessServletEmbeddedServerFactoryTest { - private SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler( + private SpringBootLambdaContainerHandler handler = new SpringBootLambdaContainerHandler<>( AwsProxyRequest.class, AwsProxyResponse.class, new AwsProxyHttpServletRequestReader(), @@ -27,7 +26,7 @@ public class ServerlessServletEmbeddedServerFactoryTest { new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler(), null, - new SyncInitializationWrapper() + new InitializationWrapper() ); public ServerlessServletEmbeddedServerFactoryTest() throws ContainerInitializationException { diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/LambdaHandler.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/LambdaHandler.java new file mode 100644 index 000000000..75dc158ab --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/LambdaHandler.java @@ -0,0 +1,26 @@ +package com.amazonaws.serverless.proxy.spring.securityapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +public class LambdaHandler implements RequestHandler { + private static SpringBootLambdaContainerHandler handler; + + static { + try { + handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(ServletApplication.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + } + } + + @Override + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + return handler.proxy(awsProxyRequest, context); + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/MessageController.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/MessageController.java new file mode 100644 index 000000000..c1d3f157b --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/MessageController.java @@ -0,0 +1,16 @@ +package com.amazonaws.serverless.proxy.spring.securityapp; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class MessageController { + public static final String HELLO_MESSAGE = "Hello"; + + @RequestMapping(path="/hello", method=RequestMethod.GET, produces = {"text/plain"}) + public String hello() { + System.out.println("Invoke hello"); + return HELLO_MESSAGE; + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityConfig.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityConfig.java new file mode 100644 index 000000000..128ae3dd8 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/SecurityConfig.java @@ -0,0 +1,61 @@ +package com.amazonaws.serverless.proxy.spring.securityapp; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.server.SecurityWebFilterChain; + +@Configuration +@EnableWebFluxSecurity +public class SecurityConfig { + public static final String USERNAME = "user"; + public static String PASSWORD = "testPassword"; + public static BCryptPasswordEncoder pEncoder = new BCryptPasswordEncoder(); + + @Bean + public SecurityWebFilterChain securitygWebFilterChain( + ServerHttpSecurity http) { + return http.authorizeExchange() + .anyExchange().authenticated() + .and().httpBasic() + .and().build(); + } + + @Bean + public MapReactiveUserDetailsService reactiveUserDetailsService(SecurityProperties properties, ObjectProvider passwordEncoder) { + return new MapReactiveUserDetailsService(getUser()); + } + + private UserDetails getUser() { + return User.builder().username(USERNAME).password(passwordEncoder().encode(PASSWORD)).authorities("USER").build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return pEncoder; + } + + /*@Autowired + public void configureGlobal(AuthenticationManagerBuilder authentication) + throws Exception + { + authentication.inMemoryAuthentication() + .withUser(USERNAME) + .password(passwordEncoder().encode(PASSWORD)) + .authorities("ROLE_USER"); + if (userService != null) { + if (userService.loadUserByUsername("user") != null) { + System.out.println("Setting password in configureGlobal"); + PASSWORD = userService.loadUserByUsername("user").getPassword().replace("{noop}", ""); + } + } + }*/ +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/ServletApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/ServletApplication.java new file mode 100644 index 000000000..2f1def12a --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/securityapp/ServletApplication.java @@ -0,0 +1,13 @@ +package com.amazonaws.serverless.proxy.spring.securityapp; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + +@SpringBootApplication() +public class ServletApplication { +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java index c6b4e0b76..7faf9b1b8 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/LambdaHandler.java @@ -1,6 +1,7 @@ -package com.amazonaws.servlerss.proxy.spring.servletapp; +package com.amazonaws.serverless.proxy.spring.servletapp; import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java index 48731347b..937b2432b 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/MessageController.java @@ -1,4 +1,4 @@ -package com.amazonaws.servlerss.proxy.spring.servletapp; +package com.amazonaws.serverless.proxy.spring.servletapp; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java index a85851b88..927dd6ce5 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/servletapp/ServletApplication.java @@ -1,4 +1,4 @@ -package com.amazonaws.servlerss.proxy.spring.servletapp; +package com.amazonaws.serverless.proxy.spring.servletapp; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java new file mode 100644 index 000000000..bd696a990 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/LambdaHandler.java @@ -0,0 +1,41 @@ +package com.amazonaws.serverless.proxy.spring.slowapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.serverless.proxy.spring.SpringBootProxyHandlerBuilder; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import java.time.Instant; + +public class LambdaHandler implements RequestHandler { + private SpringBootLambdaContainerHandler handler; + private long constructorTime; + + public LambdaHandler() { + try { + long startTime = Instant.now().toEpochMilli(); + System.out.println("startCall: " + startTime); + handler = new SpringBootProxyHandlerBuilder() + .defaultProxy() + .asyncInit(startTime) + .springBootApplication(SlowTestApplication.class) + .buildAndInitialize(); + constructorTime = Instant.now().toEpochMilli() - startTime; + } catch (ContainerInitializationException e) { + e.printStackTrace(); + } + } + + public long getConstructorTime() { + return constructorTime; + } + + @Override + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + return handler.proxy(awsProxyRequest, context); + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/MessageController.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/MessageController.java new file mode 100644 index 000000000..098e8e7df --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/MessageController.java @@ -0,0 +1,15 @@ +package com.amazonaws.serverless.proxy.spring.slowapp; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class MessageController { + public static final String HELLO_MESSAGE = "Hello"; + + @RequestMapping(path="/hello", method= RequestMethod.GET) + public String hello() { + return HELLO_MESSAGE; + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java new file mode 100644 index 000000000..b3fe177a1 --- /dev/null +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/slowapp/SlowTestApplication.java @@ -0,0 +1,24 @@ +package com.amazonaws.serverless.proxy.spring.slowapp; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.stereotype.Component; + +import java.time.Instant; + +@SpringBootApplication(exclude = { + org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.class +}) +public class SlowTestApplication { + + @Component + public static class SlowDownInit implements InitializingBean { + public static final int INIT_SLEEP_TIME_MS = 13_000; + + @Override + public void afterPropertiesSet() throws Exception { + Thread.sleep(INIT_SLEEP_TIME_MS); + } + } +} diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java index 60dcbd4a3..913da5bbc 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/LambdaHandler.java @@ -1,6 +1,7 @@ -package com.amazonaws.servlerss.proxy.spring.webfluxapp; +package com.amazonaws.serverless.proxy.spring.webfluxapp; import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java index a507ff978..65ce07862 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/MessageController.java @@ -1,4 +1,4 @@ -package com.amazonaws.servlerss.proxy.spring.webfluxapp; +package com.amazonaws.serverless.proxy.spring.webfluxapp; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; diff --git a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java index b3f14a0e4..04d48ff3d 100644 --- a/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java +++ b/aws-serverless-java-container-springboot2/src/test/java/com/amazonaws/serverless/proxy/spring/webfluxapp/WebFluxTestApplication.java @@ -1,4 +1,4 @@ -package com.amazonaws.servlerss.proxy.spring.webfluxapp; +package com.amazonaws.serverless.proxy.spring.webfluxapp; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; From a007be98c308617918b9b0f0eff040c07b175425 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 26 Sep 2019 11:59:37 -0700 Subject: [PATCH 25/40] Renamed Reactive embedded server class to clarify that it still relies on the Servlet specs --- .../proxy/spring/SpringBootLambdaContainerHandler.java | 6 +++--- ... => ServerlessReactiveServletEmbeddedServerFactory.java} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/{ServerlessReactiveEmbeddedServerFactory.java => ServerlessReactiveServletEmbeddedServerFactory.java} (95%) diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java index b6f6973ca..d1debceaa 100644 --- a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java +++ b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -18,7 +18,7 @@ import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveEmbeddedServerFactory; +import com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveServletEmbeddedServerFactory; import com.amazonaws.serverless.proxy.spring.embedded.ServerlessServletEmbeddedServerFactory; import com.amazonaws.services.lambda.runtime.Context; import org.slf4j.Logger; @@ -54,7 +54,7 @@ public class SpringBootLambdaContainerHandler extends * We need to rely on the static instance of this for SpringBoot because we need it to access the ServletContext. * Normally, SpringBoot would initialize its own embedded container through the SpringApplication.run() * method. However, in our case we need to rely on the pre-initialized handler and need to fetch information from it - * for our mock {@link com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveEmbeddedServerFactory}. + * for our mock {@link ServerlessReactiveServletEmbeddedServerFactory}. * * @return The initialized instance */ @@ -167,7 +167,7 @@ private Class[] getEmbeddedContainerClasses() { // if HandlerAdapter is available we assume they are using WebFlux. Otherwise plain servlet. this.getClass().getClassLoader().loadClass("org.springframework.web.reactive.HandlerAdapter"); log.debug("Found WebFlux HandlerAdapter on classpath, using reactive server factory"); - classes[0] = ServerlessReactiveEmbeddedServerFactory.class; + classes[0] = ServerlessReactiveServletEmbeddedServerFactory.class; } catch (ClassNotFoundException e) { classes[0] = ServerlessServletEmbeddedServerFactory.class; } diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveEmbeddedServerFactory.java b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java similarity index 95% rename from aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveEmbeddedServerFactory.java rename to aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java index 94a9d2074..7fa366dd9 100644 --- a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveEmbeddedServerFactory.java +++ b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/embedded/ServerlessReactiveServletEmbeddedServerFactory.java @@ -15,7 +15,7 @@ import java.util.Enumeration; @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) -public class ServerlessReactiveEmbeddedServerFactory extends AbstractReactiveWebServerFactory implements WebServer, Servlet { +public class ServerlessReactiveServletEmbeddedServerFactory extends AbstractReactiveWebServerFactory implements WebServer, Servlet { private ServletHttpHandlerAdapter handler; private ServletConfig config; static final String SERVLET_NAME = "com.amazonaws.serverless.proxy.spring.embedded.ServerlessReactiveEmbeddedServerFactory"; From 1d3456763f32fda30c065637d3c3f2517f32a16b Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 26 Sep 2019 12:44:07 -0700 Subject: [PATCH 26/40] Added comment explaining async initializer in springboot 2 archetyoe --- .../src/main/java/StreamLambdaHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java index e022540c1..ba15c6d69 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java +++ b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambdaHandler.java @@ -18,6 +18,13 @@ public class StreamLambdaHandler implements RequestStreamHandler { static { try { handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class); + // For applications that take longer than 10 seconds to start, use the async builder: + // long startTime = Instant.now().toEpochMilli(); + // handler = new SpringBootProxyHandlerBuilder() + // .defaultProxy() + // .asyncInit(startTime) + // .springBootApplication(Application.class) + // .buildAndInitialize(); } catch (ContainerInitializationException e) { // if we fail here. We re-throw the exception to force another cold start e.printStackTrace(); From 50b76dffb190a9fe3464a640b4caf7bca862d292 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 26 Sep 2019 14:37:52 -0700 Subject: [PATCH 27/40] Bump Jersey version to address #266 --- aws-serverless-java-container-jersey/pom.xml | 8 +++++++- .../src/main/resources/archetype-resources/pom.xml | 2 +- samples/jersey/pet-store/pom.xml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/aws-serverless-java-container-jersey/pom.xml b/aws-serverless-java-container-jersey/pom.xml index 80a21388e..6a0dc711f 100644 --- a/aws-serverless-java-container-jersey/pom.xml +++ b/aws-serverless-java-container-jersey/pom.xml @@ -16,7 +16,7 @@ - 2.27 + 2.29.1 @@ -46,6 +46,12 @@ ${jersey.version} true test + + + jakarta.annotation + jakarta.annotation-api + +
diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml index fd34a171e..dcb7a596a 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml @@ -14,7 +14,7 @@ 1.8 1.8 - 2.27 + 2.29.1 2.9.9.3 diff --git a/samples/jersey/pet-store/pom.xml b/samples/jersey/pet-store/pom.xml index ddf808ceb..2ac418cd8 100644 --- a/samples/jersey/pet-store/pom.xml +++ b/samples/jersey/pet-store/pom.xml @@ -26,7 +26,7 @@ 1.8 1.8 - 2.27 + 2.29.1 From 5434a8fa76d9881622a5a4b2b81eecc152adff70 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 26 Sep 2019 14:57:49 -0700 Subject: [PATCH 28/40] Bump Spring version --- aws-serverless-java-container-spring/pom.xml | 2 +- .../src/main/resources/archetype-resources/pom.xml | 2 +- samples/spring/pet-store/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 8862e4787..11eb7ad31 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -16,7 +16,7 @@ - 5.1.8.RELEASE + 5.1.9.RELEASE 1.5.21.RELEASE 5.1.5.RELEASE 2.9.9.3 diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/pom.xml index f9c2ee985..a6a574c8b 100644 --- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/pom.xml @@ -16,7 +16,7 @@ 1.8 1.8 - 5.1.1.RELEASE + 5.1.9.RELEASE 4.12 2.8.2 diff --git a/samples/spring/pet-store/pom.xml b/samples/spring/pet-store/pom.xml index 1f60ce0b0..d25d1dc02 100644 --- a/samples/spring/pet-store/pom.xml +++ b/samples/spring/pet-store/pom.xml @@ -26,7 +26,7 @@ 1.8 1.8 - 5.1.1.RELEASE + 5.1.9.RELEASE 4.12 2.8.2 From 39756cd11d7796e89436ebb3af7ed83e0a8c6ace Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 26 Sep 2019 15:02:27 -0700 Subject: [PATCH 29/40] Bump spring boot version --- aws-serverless-java-container-spring/pom.xml | 2 +- .../src/main/resources/archetype-resources/pom.xml | 2 +- samples/springboot/pet-store/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 11eb7ad31..eef766406 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -17,7 +17,7 @@ 5.1.9.RELEASE - 1.5.21.RELEASE + 1.5.22.RELEASE 5.1.5.RELEASE 2.9.9.3 diff --git a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/pom.xml index e0ee7b747..f3a4a650b 100644 --- a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/pom.xml @@ -16,7 +16,7 @@ org.springframework.boot spring-boot-starter-parent - 1.5.17.RELEASE + 1.5.22.RELEASE diff --git a/samples/springboot/pet-store/pom.xml b/samples/springboot/pet-store/pom.xml index 51f177e59..639b927a1 100644 --- a/samples/springboot/pet-store/pom.xml +++ b/samples/springboot/pet-store/pom.xml @@ -10,7 +10,7 @@ org.springframework.boot spring-boot-starter-parent - 1.5.17.RELEASE + 1.5.22.RELEASE From 8e70836b352114c719a75629498de2a7952fd601 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 26 Sep 2019 15:05:06 -0700 Subject: [PATCH 30/40] Updated SpringBoot 2 sample to latest code --- .../serverless/sample/springboot2/Application.java | 2 +- .../sample/springboot2/StreamLambdaHandler.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/Application.java b/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/Application.java index e49b1f6d2..d613fc073 100644 --- a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/Application.java +++ b/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/Application.java @@ -21,7 +21,7 @@ @SpringBootApplication @Import({ PetsController.class }) -public class Application extends SpringBootServletInitializer { +public class Application { // silence console logging @Value("${logging.level.root:OFF}") diff --git a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/StreamLambdaHandler.java b/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/StreamLambdaHandler.java index cecee6db4..0b7cf5541 100644 --- a/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/StreamLambdaHandler.java +++ b/samples/springboot2/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot2/StreamLambdaHandler.java @@ -25,6 +25,14 @@ public class StreamLambdaHandler implements RequestStreamHandler { try { handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class); + // For applications that take longer than 10 seconds to start, use the async builder: + // long startTime = Instant.now().toEpochMilli(); + // handler = new SpringBootProxyHandlerBuilder() + // .defaultProxy() + // .asyncInit(startTime) + // .springBootApplication(Application.class) + // .buildAndInitialize(); + // we use the onStartup method of the handler to register our custom filter handler.onStartup(servletContext -> { FilterRegistration.Dynamic registration = servletContext.addFilter("CognitoIdentityFilter", CognitoIdentityFilter.class); From bf87475100a05101efba4e0f1b7623e13df82205 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 26 Sep 2019 15:27:39 -0700 Subject: [PATCH 31/40] Bump jackson version and moved dependency version management to property in main pom --- aws-serverless-java-container-core/pom.xml | 3 +-- aws-serverless-java-container-jersey/pom.xml | 2 +- aws-serverless-java-container-spring/pom.xml | 1 - aws-serverless-java-container-struts2/pom.xml | 1 - .../src/main/resources/archetype-resources/pom.xml | 2 +- .../src/main/resources/archetype-resources/pom.xml | 2 +- pom.xml | 1 + samples/jersey/pet-store/pom.xml | 3 ++- samples/spark/pet-store/pom.xml | 2 +- samples/struts/pet-store/pom.xml | 5 ++--- 10 files changed, 10 insertions(+), 12 deletions(-) diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml index f5fafe2ec..c893a4f68 100644 --- a/aws-serverless-java-container-core/pom.xml +++ b/aws-serverless-java-container-core/pom.xml @@ -16,7 +16,6 @@ - 2.9.9.3 2.1 3.1.0 @@ -54,7 +53,7 @@ com.fasterxml.jackson.module jackson-module-afterburner - 2.9.9 + ${jackson.version} com.fasterxml.jackson.core diff --git a/aws-serverless-java-container-jersey/pom.xml b/aws-serverless-java-container-jersey/pom.xml index 6a0dc711f..164211245 100644 --- a/aws-serverless-java-container-jersey/pom.xml +++ b/aws-serverless-java-container-jersey/pom.xml @@ -66,7 +66,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.9.3 + ${jackson.version} true test diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index eef766406..5d84ed558 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -19,7 +19,6 @@ 5.1.9.RELEASE 1.5.22.RELEASE 5.1.5.RELEASE - 2.9.9.3 diff --git a/aws-serverless-java-container-struts2/pom.xml b/aws-serverless-java-container-struts2/pom.xml index e414eab51..e7537b431 100644 --- a/aws-serverless-java-container-struts2/pom.xml +++ b/aws-serverless-java-container-struts2/pom.xml @@ -16,7 +16,6 @@ 2.5.20 - 2.9.9.3 diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml index dcb7a596a..3282eb25d 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml @@ -15,7 +15,7 @@ 1.8 1.8 2.29.1 - 2.9.9.3 + 2.9.10 diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml index 6d8db9922..86d63cf26 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml @@ -16,7 +16,7 @@ 1.8 1.8 - 2.9.9.3 + 2.9.10 2.8.0 diff --git a/pom.xml b/pom.xml index 203dd981b..c8ecf0828 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,7 @@ 0.7 5.1.0 + 2.9.10 diff --git a/samples/jersey/pet-store/pom.xml b/samples/jersey/pet-store/pom.xml index 2ac418cd8..bd3195460 100644 --- a/samples/jersey/pet-store/pom.xml +++ b/samples/jersey/pet-store/pom.xml @@ -27,6 +27,7 @@ 1.8 1.8 2.29.1 + 2.9.10 @@ -77,7 +78,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.9.3 + ${jackson.version} diff --git a/samples/spark/pet-store/pom.xml b/samples/spark/pet-store/pom.xml index e71a773bc..9c0bd8d21 100644 --- a/samples/spark/pet-store/pom.xml +++ b/samples/spark/pet-store/pom.xml @@ -26,7 +26,7 @@ 1.8 1.8 - 2.9.9.3 + 2.9.10 2.8.0 diff --git a/samples/struts/pet-store/pom.xml b/samples/struts/pet-store/pom.xml index 2cff49e5a..80c668794 100644 --- a/samples/struts/pet-store/pom.xml +++ b/samples/struts/pet-store/pom.xml @@ -27,8 +27,7 @@ 1.8 1.8 2.5.20 - 2.9.9 - 2.9.9.3 + 2.9.10 4.12 2.11.1 @@ -98,7 +97,7 @@ com.fasterxml.jackson.core jackson-databind - ${jackson-databind.version} + ${jackson.version} From ea3fb4b4b2f9029d636e16db2109022326315532 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 26 Sep 2019 15:50:46 -0700 Subject: [PATCH 32/40] Bump various versions of dependencies in gradle build files (I always forget them) --- .../main/resources/archetype-resources/build.gradle | 6 +++--- .../main/resources/archetype-resources/build.gradle | 4 ++-- .../src/main/resources/archetype-resources/pom.xml | 2 +- .../main/resources/archetype-resources/build.gradle | 6 +++--- .../main/resources/archetype-resources/build.gradle | 2 +- .../main/resources/archetype-resources/build.gradle | 2 +- .../main/resources/archetype-resources/build.gradle | 10 +++++----- samples/jersey/pet-store/build.gradle | 6 +++--- samples/spark/pet-store/build.gradle | 4 ++-- samples/spark/pet-store/pom.xml | 2 +- samples/spring/pet-store/build.gradle | 6 +++--- samples/springboot/pet-store/build.gradle | 2 +- samples/springboot2/pet-store/build.gradle | 4 ++-- samples/struts/pet-store/build.gradle | 2 +- 14 files changed, 29 insertions(+), 29 deletions(-) diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/build.gradle index eda69dc96..e9324e090 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/build.gradle +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/build.gradle @@ -9,17 +9,17 @@ dependencies { compile ( 'com.amazonaws:aws-lambda-java-core:1.2.0', 'com.amazonaws.serverless:aws-serverless-java-container-jersey:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', + 'com.fasterxml.jackson.core:jackson-databind:2.9.10', 'io.symphonia:lambda-logging:1.0.1' ) - compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.27") { + compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.29.1") { exclude group: 'com.fasterxml.jackson.core', module: "jackson-annotations" exclude group: 'com.fasterxml.jackson.core', module: "jackson-databind" exclude group: 'com.fasterxml.jackson.core', module: "jackson-core" } - compile("org.glassfish.jersey.inject:jersey-hk2:2.27") { + compile("org.glassfish.jersey.inject:jersey-hk2:2.29.1") { exclude group: 'javax.inject', module: "javax.inject" } diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/build.gradle index ace7a6b3e..f4e280ce7 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/build.gradle +++ b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/build.gradle @@ -7,9 +7,9 @@ repositories { dependencies { compile ( - 'com.sparkjava:spark-core:2.8.0', + 'com.sparkjava:spark-core:2.9.1', 'com.amazonaws.serverless:aws-serverless-java-container-spark:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', + 'com.fasterxml.jackson.core:jackson-databind:2.9.10', 'io.symphonia:lambda-logging:1.0.1' ) diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml index 86d63cf26..055fc4321 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml @@ -17,7 +17,7 @@ 1.8 1.8 2.9.10 - 2.8.0 + 2.9.1 diff --git a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/build.gradle index ec235eba6..e67eb52e4 100644 --- a/aws-serverless-spring-archetype/src/main/resources/archetype-resources/build.gradle +++ b/aws-serverless-spring-archetype/src/main/resources/archetype-resources/build.gradle @@ -7,13 +7,13 @@ repositories { dependencies { compile ( - 'org.springframework:spring-webmvc:5.1.1.RELEASE', - 'org.springframework:spring-context:5.1.1.RELEASE', + 'org.springframework:spring-webmvc:5.1.9.RELEASE', + 'org.springframework:spring-context:5.1.9.RELEASE', 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', 'org.apache.logging.log4j:log4j-core:2.8.2', 'org.apache.logging.log4j:log4j-api:2.8.2', 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.2', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', + 'com.fasterxml.jackson.core:jackson-databind:2.9.10', 'com.amazonaws:aws-lambda-java-log4j2:1.1.0', ) diff --git a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/build.gradle index 88c7d62f7..53c675deb 100644 --- a/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/build.gradle +++ b/aws-serverless-springboot-archetype/src/main/resources/archetype-resources/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.springframework.boot' version '1.5.17.RELEASE' + id 'org.springframework.boot' version '1.5.22.RELEASE' } apply plugin: 'java' diff --git a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle index e48a0dc99..e85ac40d8 100644 --- a/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle +++ b/aws-serverless-springboot2-archetype/src/main/resources/archetype-resources/build.gradle @@ -11,7 +11,7 @@ repositories { dependencies { compile ( - 'org.springframework.boot:spring-boot-starter-web:2.1.1.RELEASE', + 'org.springframework.boot:spring-boot-starter-web:2.1.8.RELEASE', 'com.amazonaws.serverless:aws-serverless-java-container-springboot2:[1.0,)', 'io.symphonia:lambda-logging:1.0.1' ) diff --git a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/build.gradle b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/build.gradle index 41046f45e..ee08f5d4c 100644 --- a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/build.gradle +++ b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/build.gradle @@ -8,13 +8,13 @@ repositories { dependencies { compile ( 'com.amazonaws.serverless:aws-serverless-java-container-struts2:[1.0,)', - 'org.apache.struts:struts2-convention-plugin:2.5.17', - 'org.apache.struts:struts2-rest-plugin:2.5.17', - 'org.apache.struts:struts2-bean-validation-plugin:2.5.17', - 'org.apache.struts:struts2-junit-plugin:2.5.17', + 'org.apache.struts:struts2-convention-plugin:2.5.20', + 'org.apache.struts:struts2-rest-plugin:2.5.20', + 'org.apache.struts:struts2-bean-validation-plugin:2.5.20', + 'org.apache.struts:struts2-junit-plugin:2.5.20', 'com.jgeppert.struts2:struts2-aws-lambda-support-plugin:1.0.0', 'org.hibernate:hibernate-validator:4.3.2.Final', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', + 'com.fasterxml.jackson.core:jackson-databind:2.9.10', 'org.apache.logging.log4j:log4j-core:2.8.2', 'org.apache.logging.log4j:log4j-api:2.8.2', 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.2', diff --git a/samples/jersey/pet-store/build.gradle b/samples/jersey/pet-store/build.gradle index 3e9e97628..c719ba130 100644 --- a/samples/jersey/pet-store/build.gradle +++ b/samples/jersey/pet-store/build.gradle @@ -9,17 +9,17 @@ dependencies { compile ( 'com.amazonaws:aws-lambda-java-core:1.2.0', 'com.amazonaws.serverless:aws-serverless-java-container-jersey:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', + 'com.fasterxml.jackson.core:jackson-databind:2.9.10', 'io.symphonia:lambda-logging:1.0.1' ) - compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.27") { + compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.29.1") { exclude group: 'com.fasterxml.jackson.core', module: "jackson-annotations" exclude group: 'com.fasterxml.jackson.core', module: "jackson-databind" exclude group: 'com.fasterxml.jackson.core', module: "jackson-core" } - compile("org.glassfish.jersey.inject:jersey-hk2:2.27") { + compile("org.glassfish.jersey.inject:jersey-hk2:2.29.1") { exclude group: 'javax.inject', module: "javax.inject" } } diff --git a/samples/spark/pet-store/build.gradle b/samples/spark/pet-store/build.gradle index 681dd4df2..6b6c70129 100644 --- a/samples/spark/pet-store/build.gradle +++ b/samples/spark/pet-store/build.gradle @@ -7,9 +7,9 @@ repositories { dependencies { compile ( - 'com.sparkjava:spark-core:2.8.0', + 'com.sparkjava:spark-core:2.9.1', 'com.amazonaws.serverless:aws-serverless-java-container-spark:[1.0,)', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', + 'com.fasterxml.jackson.core:jackson-databind:2.9.10', 'io.symphonia:lambda-logging:1.0.1' ) } diff --git a/samples/spark/pet-store/pom.xml b/samples/spark/pet-store/pom.xml index 9c0bd8d21..1b1a370d6 100644 --- a/samples/spark/pet-store/pom.xml +++ b/samples/spark/pet-store/pom.xml @@ -27,7 +27,7 @@ 1.8 1.8 2.9.10 - 2.8.0 + 2.9.1 diff --git a/samples/spring/pet-store/build.gradle b/samples/spring/pet-store/build.gradle index 73e18d968..7d7a74c44 100644 --- a/samples/spring/pet-store/build.gradle +++ b/samples/spring/pet-store/build.gradle @@ -7,13 +7,13 @@ repositories { dependencies { compile ( - 'org.springframework:spring-webmvc:5.1.1.RELEASE', - 'org.springframework:spring-context:5.1.1.RELEASE', + 'org.springframework:spring-webmvc:5.1.9.RELEASE', + 'org.springframework:spring-context:5.1.9.RELEASE', 'com.amazonaws.serverless:aws-serverless-java-container-spring:[1.0,)', 'org.apache.logging.log4j:log4j-core:2.8.2', 'org.apache.logging.log4j:log4j-api:2.8.2', 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.2', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', + 'com.fasterxml.jackson.core:jackson-databind:2.9.10', 'com.amazonaws:aws-lambda-java-log4j2:1.1.0', ) } diff --git a/samples/springboot/pet-store/build.gradle b/samples/springboot/pet-store/build.gradle index 1c5e5d489..53295266a 100644 --- a/samples/springboot/pet-store/build.gradle +++ b/samples/springboot/pet-store/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.springframework.boot' version '1.5.17.RELEASE' + id 'org.springframework.boot' version '1.5.22.RELEASE' } apply plugin: 'java' diff --git a/samples/springboot2/pet-store/build.gradle b/samples/springboot2/pet-store/build.gradle index 7a9c8b9f2..65826e900 100644 --- a/samples/springboot2/pet-store/build.gradle +++ b/samples/springboot2/pet-store/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.springframework.boot' version '2.1.1.RELEASE' + id 'org.springframework.boot' version '2.1.8.RELEASE' } apply plugin: 'java' @@ -11,7 +11,7 @@ repositories { dependencies { compile ( - implementation('org.springframework.boot:spring-boot-starter-web:2.1.1.RELEASE') { + implementation('org.springframework.boot:spring-boot-starter-web:2.1.8.RELEASE') { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' }, 'com.amazonaws.serverless:aws-serverless-java-container-springboot2:[1.0,)', diff --git a/samples/struts/pet-store/build.gradle b/samples/struts/pet-store/build.gradle index 829ca79bb..9f2ccf09a 100644 --- a/samples/struts/pet-store/build.gradle +++ b/samples/struts/pet-store/build.gradle @@ -14,7 +14,7 @@ dependencies { 'org.apache.struts:struts2-junit-plugin:2.5.20', 'com.jgeppert.struts2:struts2-aws-lambda-support-plugin:1.1.0', 'org.hibernate:hibernate-validator:4.3.2.Final', - 'com.fasterxml.jackson.core:jackson-databind:2.9.8', + 'com.fasterxml.jackson.core:jackson-databind:2.9.10', 'org.apache.logging.log4j:log4j-core:2.8.2', 'org.apache.logging.log4j:log4j-api:2.8.2', 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.2', From 96bc2f597ad017984799cc1bf100895a6b1282eb Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 26 Sep 2019 20:52:12 -0700 Subject: [PATCH 33/40] Added latch countdown on exception to address a potential leak in the implementation of #273 --- .../serverless/proxy/internal/LambdaContainerHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java index 1e9bfaa80..3bb401d17 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java @@ -195,9 +195,9 @@ public void setLogFormatter(LogFormatter Date: Thu, 26 Sep 2019 21:23:30 -0700 Subject: [PATCH 34/40] Basic implementation of createListener in async context --- .../proxy/internal/servlet/AwsAsyncContext.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java index daab77e39..906bc6fcd 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java @@ -91,7 +91,7 @@ public void complete() { @Override public void start(Runnable runnable) { - throw new UnsupportedOperationException("Cannot start background tasks"); + throw new UnsupportedOperationException("Operation not supported"); } @Override @@ -109,7 +109,11 @@ public void addListener(AsyncListener asyncListener, ServletRequest servletReque @Override public T createListener(Class aClass) throws ServletException { - return null; + try { + return aClass.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new ServletException(e); + } } @Override From 4fec44933fcc61a46a58de48ccd8886089fcc9e7 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 26 Sep 2019 22:06:06 -0700 Subject: [PATCH 35/40] Attempting to replicate cookie issue reported in #274 --- .../serverless/proxy/spark/HelloWorldSparkTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java index 4e09a35dd..108eeabeb 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java @@ -35,6 +35,8 @@ public class HelloWorldSparkTest { private static final String COOKIE_DOMAIN = "mydomain.com"; private static final String COOKIE_PATH = "/"; + private static final String READ_COOKIE_NAME = "customCookie"; + private static SparkLambdaContainerHandler handler; private boolean isAlb; @@ -122,6 +124,13 @@ public void rootResource_basicRequest_expectSuccess() { assertEquals(BODY_TEXT_RESPONSE, response.getBody()); } + @Test + public void readCookie_customDomainName_expectValidCookie() { + AwsProxyRequest req = getRequestBuilder().method("GET").path("/cookie-read").cookie(READ_COOKIE_NAME, "test").build(); + AwsProxyResponse response = handler.proxy(req, new MockLambdaContext()); + assertEquals("test", response.getBody()); + } + private static void configureRoutes() { get("/", (req, res) -> { res.status(200); @@ -154,5 +163,7 @@ private static void configureRoutes() { res.raw().addCookie(testCookie2); return BODY_TEXT_RESPONSE; }); + + get("/cookie-read", (req, res) -> req.cookie(READ_COOKIE_NAME)); } } From ae6fd4fd1e3fed751b7d6713d2db2830e061c5ee Mon Sep 17 00:00:00 2001 From: sapessi Date: Fri, 27 Sep 2019 08:34:54 -0700 Subject: [PATCH 36/40] Added slow init tests for Spring and SpringBoot 1.x applications that use the asyn initialization wrapper (#210) --- .../serverless/proxy/spring/SlowAppTest.java | 60 +++++++++++++++++++ .../springbootslowapp/MessageController.java | 15 +++++ .../springbootslowapp/SBLambdaHandler.java | 40 +++++++++++++ .../springbootslowapp/TestApplication.java | 25 ++++++++ .../spring/springslowapp/LambdaHandler.java | 35 +++++++++++ .../springslowapp/MessageController.java | 15 +++++ .../spring/springslowapp/SlowAppConfig.java | 20 +++++++ 7 files changed, 210 insertions(+) create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/MessageController.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/SBLambdaHandler.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/TestApplication.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/LambdaHandler.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/MessageController.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/SlowAppConfig.java diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java new file mode 100644 index 000000000..2d1495905 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SlowAppTest.java @@ -0,0 +1,60 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.springbootslowapp.SBLambdaHandler; +import com.amazonaws.serverless.proxy.spring.springslowapp.LambdaHandler; +import com.amazonaws.serverless.proxy.spring.springslowapp.MessageController; +import com.amazonaws.serverless.proxy.spring.springslowapp.SlowAppConfig; +import org.junit.Assert; +import org.junit.Test; + +import java.time.Instant; + +import static org.junit.Assert.*; + +public class SlowAppTest { + + @Test + public void springSlowApp_continuesInBackgroundThread_returnsCorrect() { + LambdaHandler slowApp = null; + try { + slowApp = new LambdaHandler(); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + fail("Exception during initialization"); + } + System.out.println("Start time: " + slowApp.getConstructorTime()); + assertTrue(slowApp.getConstructorTime() < 10_000); + AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); + long startRequestTime = Instant.now().toEpochMilli(); + AwsProxyResponse resp = slowApp.handleRequest(req, new MockLambdaContext()); + long endRequestTime = Instant.now().toEpochMilli(); + assertTrue(endRequestTime - startRequestTime > SlowAppConfig.SlowDownInit.INIT_SLEEP_TIME_MS - 10_000); + assertEquals(200, resp.getStatusCode()); + Assert.assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + } + + @Test + public void springBootSlowApp_continuesInBackgroundThread_returnsCorrect() { + SBLambdaHandler slowApp = null; + try { + slowApp = new SBLambdaHandler(); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + fail("Exception during initialization"); + } + System.out.println("Start time: " + slowApp.getConstructorTime()); + assertTrue(slowApp.getConstructorTime() < 10_000); + AwsProxyRequest req = new AwsProxyRequestBuilder("/hello", "GET").build(); + long startRequestTime = Instant.now().toEpochMilli(); + AwsProxyResponse resp = slowApp.handleRequest(req, new MockLambdaContext()); + long endRequestTime = Instant.now().toEpochMilli(); + assertTrue(endRequestTime - startRequestTime > SlowAppConfig.SlowDownInit.INIT_SLEEP_TIME_MS - 10_000); + assertEquals(200, resp.getStatusCode()); + Assert.assertEquals(MessageController.HELLO_MESSAGE, resp.getBody()); + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/MessageController.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/MessageController.java new file mode 100644 index 000000000..20dda4174 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/MessageController.java @@ -0,0 +1,15 @@ +package com.amazonaws.serverless.proxy.spring.springbootslowapp; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class MessageController { + public static final String HELLO_MESSAGE = "Hello"; + + @RequestMapping(path="/hello", method= RequestMethod.GET) + public String hello() { + return HELLO_MESSAGE; + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/SBLambdaHandler.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/SBLambdaHandler.java new file mode 100644 index 000000000..64a399a22 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/SBLambdaHandler.java @@ -0,0 +1,40 @@ +package com.amazonaws.serverless.proxy.spring.springbootslowapp; + + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.serverless.proxy.spring.SpringBootProxyHandlerBuilder; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import java.time.Instant; + + +public class SBLambdaHandler + implements RequestHandler +{ + SpringBootLambdaContainerHandler handler; + private long constructorTime; + + public SBLambdaHandler() throws ContainerInitializationException { + long startTime = Instant.now().toEpochMilli(); + handler = new SpringBootProxyHandlerBuilder() + .defaultProxy() + .asyncInit(startTime) + .springBootApplication(TestApplication.class) + .buildAndInitialize(); + constructorTime = Instant.now().toEpochMilli() - startTime; + } + + public long getConstructorTime() { + return constructorTime; + } + + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) + { + return handler.proxy(awsProxyRequest, context); + } +} + diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/TestApplication.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/TestApplication.java new file mode 100644 index 000000000..d55fc30c5 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/TestApplication.java @@ -0,0 +1,25 @@ +package com.amazonaws.serverless.proxy.spring.springbootslowapp; + + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.support.SpringBootServletInitializer; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + + +@SpringBootApplication +@ComponentScan(basePackages = "com.amazonaws.serverless.proxy.spring.springbootslowapp") +@PropertySource("classpath:boot-application.properties") +public class TestApplication extends SpringBootServletInitializer { + @Component + public static class SlowDownInit implements InitializingBean { + public static final int INIT_SLEEP_TIME_MS = 13_000; + + @Override + public void afterPropertiesSet() throws Exception { + Thread.sleep(INIT_SLEEP_TIME_MS); + } + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/LambdaHandler.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/LambdaHandler.java new file mode 100644 index 000000000..b61a7191f --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/LambdaHandler.java @@ -0,0 +1,35 @@ +package com.amazonaws.serverless.proxy.spring.springslowapp; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler; +import com.amazonaws.serverless.proxy.spring.SpringProxyHandlerBuilder; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import java.time.Instant; + +public class LambdaHandler implements RequestHandler { + private SpringLambdaContainerHandler handler; + private long constructorTime; + + public LambdaHandler() throws ContainerInitializationException { + long startTime = Instant.now().toEpochMilli(); + handler = new SpringProxyHandlerBuilder() + .defaultProxy() + .asyncInit(startTime) + .configurationClasses(SlowAppConfig.class) + .buildAndInitialize(); + constructorTime = Instant.now().toEpochMilli() - startTime; + } + + public long getConstructorTime() { + return constructorTime; + } + + @Override + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + return handler.proxy(awsProxyRequest, context); + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/MessageController.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/MessageController.java new file mode 100644 index 000000000..85bce73ae --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/MessageController.java @@ -0,0 +1,15 @@ +package com.amazonaws.serverless.proxy.spring.springslowapp; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class MessageController { + public static final String HELLO_MESSAGE = "Hello"; + + @RequestMapping(path="/hello", method= RequestMethod.GET) + public String hello() { + return HELLO_MESSAGE; + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/SlowAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/SlowAppConfig.java new file mode 100644 index 000000000..1b8eec186 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/SlowAppConfig.java @@ -0,0 +1,20 @@ +package com.amazonaws.serverless.proxy.spring.springslowapp; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Component; + +@Configuration +@Import({MessageController.class}) +public class SlowAppConfig { + @Component + public static class SlowDownInit implements InitializingBean { + public static final int INIT_SLEEP_TIME_MS = 13_000; + + @Override + public void afterPropertiesSet() throws Exception { + Thread.sleep(INIT_SLEEP_TIME_MS); + } + } +} From 3d0570028a0dc07aa4445b2fc4389180a2366ab9 Mon Sep 17 00:00:00 2001 From: sapessi Date: Sat, 28 Sep 2019 16:26:47 -0700 Subject: [PATCH 37/40] Implementation of isAsyncStarted in proxy request was missing --- .../proxy/internal/servlet/AwsProxyHttpServletRequest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java index eb5d90252..2c54816d3 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java @@ -668,6 +668,11 @@ public boolean isAsyncSupported() { return true; } + @Override + public boolean isAsyncStarted() { + return asyncContext != null; + } + @Override public AsyncContext startAsync() From 02792504b45697d315232216247446ffff46ec83 Mon Sep 17 00:00:00 2001 From: sapessi Date: Sat, 28 Sep 2019 16:27:37 -0700 Subject: [PATCH 38/40] Fix on request dispatcher to maintain compatibility with spring 4.3.x and test fixes for older spring --- .../internal/servlet/AwsProxyRequestDispatcher.java | 4 ---- .../spring/springbootslowapp/TestApplication.java | 11 ++++++----- .../proxy/spring/springslowapp/MessageController.java | 2 ++ .../proxy/spring/springslowapp/SlowAppConfig.java | 2 ++ 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java index da1c6d38a..2ffcc97de 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyRequestDispatcher.java @@ -39,10 +39,6 @@ public class AwsProxyRequestDispatcher implements RequestDispatcher { public AwsProxyRequestDispatcher(final String target, final boolean namedDispatcher, final AwsLambdaServletContainerHandler handler) { - if (!namedDispatcher && !target.startsWith("/")) { - throw new UnsupportedOperationException("Only dispatchers with absolute paths are supported"); - } - isNamedDispatcher = namedDispatcher; dispatchTo = target; lambdaContainerHandler = handler; diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/TestApplication.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/TestApplication.java index d55fc30c5..7e9dce5a9 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/TestApplication.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootslowapp/TestApplication.java @@ -5,13 +5,14 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; - -@SpringBootApplication -@ComponentScan(basePackages = "com.amazonaws.serverless.proxy.spring.springbootslowapp") -@PropertySource("classpath:boot-application.properties") +// Need to explicitly exclude security because of some bizarre witchcraft inside SpringBoot that +// enables it even when I don't ask for it - this is only true when I test against spring-webmvc 4.3.x +@SpringBootApplication(exclude = { + org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class +}) +@ComponentScan(basePackages="com.amazonaws.serverless.proxy.spring.springbootslowapp") public class TestApplication extends SpringBootServletInitializer { @Component public static class SlowDownInit implements InitializingBean { diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/MessageController.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/MessageController.java index 85bce73ae..1c8abd20d 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/MessageController.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/MessageController.java @@ -3,8 +3,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; @RestController +@EnableWebMvc public class MessageController { public static final String HELLO_MESSAGE = "Hello"; diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/SlowAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/SlowAppConfig.java index 1b8eec186..d698e30e0 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/SlowAppConfig.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springslowapp/SlowAppConfig.java @@ -5,9 +5,11 @@ import org.springframework.context.annotation.Import; import org.springframework.stereotype.Component; + @Configuration @Import({MessageController.class}) public class SlowAppConfig { + @Component public static class SlowDownInit implements InitializingBean { public static final int INIT_SLEEP_TIME_MS = 13_000; From b96a9f8af9fc8907a9504232c9cbf90403197724 Mon Sep 17 00:00:00 2001 From: sapessi Date: Sat, 28 Sep 2019 22:34:09 -0700 Subject: [PATCH 39/40] Fix for async context and setting response in proxy servlet request before processing --- .../proxy/internal/servlet/AwsAsyncContext.java | 15 +++++++++++++++ .../servlet/AwsProxyHttpServletRequest.java | 10 ++++++++-- .../spring/SpringBootLambdaContainerHandler.java | 1 + .../spring/SpringLambdaContainerHandler.java | 1 + .../spring/SpringBootLambdaContainerHandler.java | 1 + 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java index 906bc6fcd..0b35d7ba5 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsAsyncContext.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** * Async context for Serverless Java Container. This is used to support reactive embedded servers for our support for @@ -23,6 +24,8 @@ public class AwsAsyncContext implements AsyncContext { private AwsLambdaServletContainerHandler handler; private List listeners; private long timeout; + private AtomicBoolean dispatched; + private AtomicBoolean completed; private Logger log = LoggerFactory.getLogger(AwsAsyncContext.class); @@ -33,6 +36,8 @@ public AwsAsyncContext(HttpServletRequest request, HttpServletResponse response, handler = servletHandler; listeners = new ArrayList<>(); timeout = 3000; + dispatched = new AtomicBoolean(false); + completed = new AtomicBoolean(false); } @Override @@ -57,6 +62,7 @@ public void dispatch() { notifyListeners(NotificationType.START_ASYNC, null); Servlet servlet = ((AwsServletContext)handler.getServletContext()).getServletForPath(req.getPathInfo()); handler.doFilter(req, res, servlet); + dispatched.set(true); } catch (ServletException | IOException e) { notifyListeners(NotificationType.ERROR, e); } @@ -83,6 +89,7 @@ public void complete() { log.debug("Completing request"); notifyListeners(NotificationType.COMPLETE, null); res.flushBuffer(); + completed.set(true); } catch (IOException e) { log.error("Could not flush response buffer", e); throw new RuntimeException(e); @@ -126,6 +133,14 @@ public long getTimeout() { return timeout; } + public boolean isDispatched() { + return dispatched.get(); + } + + public boolean isCompleted() { + return completed.get(); + } + private void notifyListeners(NotificationType type, Throwable t) { listeners.forEach((h) -> { try { diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java index 2c54816d3..8d58850cf 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java @@ -76,7 +76,7 @@ public class AwsProxyHttpServletRequest extends AwsHttpServletRequest { private AwsProxyRequest request; private SecurityContext securityContext; - private AsyncContext asyncContext; + private AwsAsyncContext asyncContext; private Map> urlEncodedFormParameters; private Map multipartFormParameters; private static Logger log = LoggerFactory.getLogger(AwsProxyHttpServletRequest.class); @@ -670,7 +670,13 @@ public boolean isAsyncSupported() { @Override public boolean isAsyncStarted() { - return asyncContext != null; + if (asyncContext == null) { + return false; + } + if (asyncContext.isCompleted() || asyncContext.isDispatched()) { + return false; + } + return true; } diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java index 38994ed3b..4cccb0bf5 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -142,6 +142,7 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt // process filters & invoke servlet Servlet reqServlet = ((AwsServletContext)getServletContext()).getServletForPath(containerRequest.getPathInfo()); + containerRequest.setResponse(containerResponse); doFilter(containerRequest, containerResponse, reqServlet); Timer.stop("SPRINGBOOT_HANDLE_REQUEST"); } diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java index c63165a43..5168fa96b 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java @@ -143,6 +143,7 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt // process filters Servlet reqServlet = ((AwsServletContext)getServletContext()).getServletForPath(containerRequest.getPathInfo()); + containerRequest.setResponse(containerResponse); doFilter(containerRequest, containerResponse, reqServlet); Timer.stop("SPRING_HANDLE_REQUEST"); } diff --git a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java index d1debceaa..0a854509a 100644 --- a/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java +++ b/aws-serverless-java-container-springboot2/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -138,6 +138,7 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt // process filters & invoke servlet Servlet reqServlet = ((AwsServletContext)getServletContext()).getServletForPath(containerRequest.getPathInfo()); + containerRequest.setResponse(containerResponse); doFilter(containerRequest, containerResponse, reqServlet); Timer.stop("SPRINGBOOT2_HANDLE_REQUEST"); } From aa72b6de6b74d6de7b53eb758b85c0d5f2858426 Mon Sep 17 00:00:00 2001 From: sapessi Date: Sun, 29 Sep 2019 08:12:02 -0700 Subject: [PATCH 40/40] Temporarily disable dependency check until the TLS issue that emerged on the night of 9/28 is fixed --- aws-serverless-java-container-core/pom.xml | 1 + aws-serverless-java-container-jersey/pom.xml | 2 +- aws-serverless-java-container-spark/pom.xml | 2 +- aws-serverless-java-container-spring/pom.xml | 1 + aws-serverless-java-container-springboot2/pom.xml | 1 + aws-serverless-java-container-struts2/pom.xml | 1 + pom.xml | 2 +- 7 files changed, 7 insertions(+), 3 deletions(-) diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml index c893a4f68..bb42cf04c 100644 --- a/aws-serverless-java-container-core/pom.xml +++ b/aws-serverless-java-container-core/pom.xml @@ -197,6 +197,7 @@ ${project.basedir}/../owasp-suppression.xml 7 + false diff --git a/aws-serverless-java-container-jersey/pom.xml b/aws-serverless-java-container-jersey/pom.xml index 164211245..d53142a83 100644 --- a/aws-serverless-java-container-jersey/pom.xml +++ b/aws-serverless-java-container-jersey/pom.xml @@ -206,7 +206,7 @@ ${project.basedir}/../owasp-suppression.xml 7 - + false diff --git a/aws-serverless-java-container-spark/pom.xml b/aws-serverless-java-container-spark/pom.xml index 8dce826b1..c185bec6a 100644 --- a/aws-serverless-java-container-spark/pom.xml +++ b/aws-serverless-java-container-spark/pom.xml @@ -146,7 +146,7 @@ ${project.basedir}/../owasp-suppression.xml 7 - + false diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 5d84ed558..8e6bff692 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -278,6 +278,7 @@ ${project.basedir}/../owasp-suppression.xml 7 + false diff --git a/aws-serverless-java-container-springboot2/pom.xml b/aws-serverless-java-container-springboot2/pom.xml index 90134a048..caa07ae35 100644 --- a/aws-serverless-java-container-springboot2/pom.xml +++ b/aws-serverless-java-container-springboot2/pom.xml @@ -175,6 +175,7 @@ ${project.basedir}/../owasp-suppression.xml 7 + false diff --git a/aws-serverless-java-container-struts2/pom.xml b/aws-serverless-java-container-struts2/pom.xml index e7537b431..66cfd17a2 100644 --- a/aws-serverless-java-container-struts2/pom.xml +++ b/aws-serverless-java-container-struts2/pom.xml @@ -204,6 +204,7 @@ ${project.basedir}/../owasp-suppression.xml 7 + false diff --git a/pom.xml b/pom.xml index c8ecf0828..e2e48c5ce 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ 0.7 - 5.1.0 + 5.2.2 2.9.10